From c5ad991b74d17670ab29b59161d230e1757aac8d Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 05:09:06 +0200 Subject: [PATCH 001/175] [Mod Manager] - Work on preliminary UI structure. --- OpenKh.Tools.ModManager/App.axaml | 15 ++++ OpenKh.Tools.ModManager/App.axaml.cs | 28 ++++++ .../Assets/avalonia-logo.ico | Bin 0 -> 175875 bytes OpenKh.Tools.ModManager/Models/ModModel.cs | 23 +++++ .../OpenKh.Tools.ModManager.csproj | 31 +++++++ OpenKh.Tools.ModManager/Program.cs | 24 +++++ OpenKh.Tools.ModManager/ViewLocator.cs | 37 ++++++++ .../ViewModels/MainViewModel.cs | 10 +++ .../ViewModels/ViewModelBase.cs | 7 ++ OpenKh.Tools.ModManager/Views/MainView.axaml | 85 ++++++++++++++++++ .../Views/MainView.axaml.cs | 13 +++ .../Views/ModDetailsView.axaml | 28 ++++++ .../Views/ModDetailsView.axaml.cs | 14 +++ .../Views/ModListView.axaml | 25 ++++++ .../Views/ModListView.axaml.cs | 22 +++++ OpenKh.Tools.ModManager/app.manifest | 18 ++++ OpenKh.sln | 15 +++- 17 files changed, 393 insertions(+), 2 deletions(-) create mode 100644 OpenKh.Tools.ModManager/App.axaml create mode 100644 OpenKh.Tools.ModManager/App.axaml.cs create mode 100644 OpenKh.Tools.ModManager/Assets/avalonia-logo.ico create mode 100644 OpenKh.Tools.ModManager/Models/ModModel.cs create mode 100644 OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj create mode 100644 OpenKh.Tools.ModManager/Program.cs create mode 100644 OpenKh.Tools.ModManager/ViewLocator.cs create mode 100644 OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs create mode 100644 OpenKh.Tools.ModManager/ViewModels/ViewModelBase.cs create mode 100644 OpenKh.Tools.ModManager/Views/MainView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/MainView.axaml.cs create mode 100644 OpenKh.Tools.ModManager/Views/ModDetailsView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs create mode 100644 OpenKh.Tools.ModManager/Views/ModListView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/ModListView.axaml.cs create mode 100644 OpenKh.Tools.ModManager/app.manifest diff --git a/OpenKh.Tools.ModManager/App.axaml b/OpenKh.Tools.ModManager/App.axaml new file mode 100644 index 000000000..bbfe7c301 --- /dev/null +++ b/OpenKh.Tools.ModManager/App.axaml @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/App.axaml.cs b/OpenKh.Tools.ModManager/App.axaml.cs new file mode 100644 index 000000000..d47ba7812 --- /dev/null +++ b/OpenKh.Tools.ModManager/App.axaml.cs @@ -0,0 +1,28 @@ +using Avalonia; +using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Markup.Xaml; +using OpenKh.Tools.ModManager.ViewModels; +using OpenKh.Tools.ModManager.Views; + +namespace OpenKh.Tools.ModManager; + +public partial class App : Application +{ + public override void Initialize() + { + AvaloniaXamlLoader.Load(this); + } + + public override void OnFrameworkInitializationCompleted() + { + if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) + { + desktop.MainWindow = new MainWindow + { + DataContext = new MainViewModel(), + }; + } + + base.OnFrameworkInitializationCompleted(); + } +} \ No newline at end of file diff --git a/OpenKh.Tools.ModManager/Assets/avalonia-logo.ico b/OpenKh.Tools.ModManager/Assets/avalonia-logo.ico new file mode 100644 index 0000000000000000000000000000000000000000..f7da8bb5863b7cecec2adcdebd948fe2f9418d0c GIT binary patch literal 175875 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 + + OpenKh.Tools.ModManager + WinExe + net8.0 + enable + app.manifest + + + + + + + + + + + + + None + All + + + + + + + MainView.axaml + + + diff --git a/OpenKh.Tools.ModManager/Program.cs b/OpenKh.Tools.ModManager/Program.cs new file mode 100644 index 000000000..b0baf373c --- /dev/null +++ b/OpenKh.Tools.ModManager/Program.cs @@ -0,0 +1,24 @@ +using Avalonia; +using System; + +namespace OpenKh.Tools.ModManager; + +sealed class Program +{ + // Initialization code. Don't use any Avalonia, third-party APIs or any + // SynchronizationContext-reliant code before AppMain is called: things aren't initialized + // yet and stuff might break. + [STAThread] + public static void Main(string[] args) => BuildAvaloniaApp() + .StartWithClassicDesktopLifetime(args); + + // Avalonia configuration, don't remove; also used by visual designer. + public static AppBuilder BuildAvaloniaApp() + => AppBuilder.Configure() + .UsePlatformDetect() +#if DEBUG + .WithDeveloperTools() +#endif + .WithInterFont() + .LogToTrace(); +} diff --git a/OpenKh.Tools.ModManager/ViewLocator.cs b/OpenKh.Tools.ModManager/ViewLocator.cs new file mode 100644 index 000000000..2d82e8a36 --- /dev/null +++ b/OpenKh.Tools.ModManager/ViewLocator.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Avalonia.Controls; +using Avalonia.Controls.Templates; +using OpenKh.Tools.ModManager.ViewModels; + +namespace OpenKh.Tools.ModManager; + +/// +/// Given a view model, returns the corresponding view if possible. +/// +[RequiresUnreferencedCode( + "Default implementation of ViewLocator involves reflection which may be trimmed away.", + Url = "https://docs.avaloniaui.net/docs/concepts/view-locator")] +public class ViewLocator : IDataTemplate +{ + public Control? Build(object? param) + { + if (param is null) + return null; + + var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal); + var type = Type.GetType(name); + + if (type != null) + { + return (Control)Activator.CreateInstance(type)!; + } + + return new TextBlock { Text = "Not Found: " + name }; + } + + public bool Match(object? data) + { + return data is ViewModelBase; + } +} diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs new file mode 100644 index 000000000..a28369e1d --- /dev/null +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -0,0 +1,10 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using OpenKh.Tools.ModManager.Models; + +namespace OpenKh.Tools.ModManager.ViewModels; + +public partial class MainViewModel : ViewModelBase +{ + [ObservableProperty] + private ModModel? _currentMod = null; +} diff --git a/OpenKh.Tools.ModManager/ViewModels/ViewModelBase.cs b/OpenKh.Tools.ModManager/ViewModels/ViewModelBase.cs new file mode 100644 index 000000000..2b8d8d2c1 --- /dev/null +++ b/OpenKh.Tools.ModManager/ViewModels/ViewModelBase.cs @@ -0,0 +1,7 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace OpenKh.Tools.ModManager.ViewModels; + +public abstract class ViewModelBase : ObservableObject +{ +} diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml new file mode 100644 index 000000000..82ee68cb1 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -0,0 +1,85 @@ + + + + M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM12 17.5C12 14.4624 9.53757 12 6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5ZM7.00065 18L7.00111 20.5035C7.00111 20.7797 6.77725 21.0035 6.50111 21.0035C6.22497 21.0035 6.00111 20.7797 6.00111 20.5035L6.00065 18H3.4956C3.21973 18 2.99609 17.7762 2.99609 17.5C2.99609 17.2239 3.21973 17 3.4956 17H6.00046L6 14.4993C6 14.2231 6.22386 13.9993 6.5 13.9993C6.77614 13.9993 7 14.2231 7 14.4993L7.00046 17H9.49659C9.77246 17 9.99609 17.2239 9.99609 17.5C9.99609 17.7762 9.77246 18 9.49659 18H7.00065Z + M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5C12 14.4624 9.53757 12 6.5 12ZM8.97823 15.7313L7.20843 17.5001L8.97806 19.2697C9.17332 19.465 9.17332 19.7816 8.97806 19.9768C8.78279 20.1721 8.46621 20.1721 8.27095 19.9768L6.50112 18.207L4.73246 19.9746C4.5372 20.1699 4.22061 20.1699 4.02535 19.9746C3.83009 19.7794 3.83009 19.4628 4.02535 19.2675L5.79401 17.4999L4.02535 15.7312C3.83009 15.536 3.83009 15.2194 4.02535 15.0241C4.22061 14.8289 4.5372 14.8289 4.73246 15.0241L6.50133 16.793L8.27113 15.0242C8.46639 14.829 8.78297 14.829 8.97823 15.0242C9.1735 15.2195 9.1735 15.5361 8.97823 15.7313Z + M1.99902 12C1.99902 17.5228 6.47617 22 11.999 22C17.5219 22 21.999 17.5228 21.999 12C21.999 6.47715 17.5219 2 11.999 2C6.47618 2 1.99902 6.47715 1.99902 12ZM16.5297 11.7199C16.796 11.9862 16.8202 12.4028 16.6023 12.6964L16.5297 12.7805L12.5289 16.7814C12.2626 17.0477 11.8459 17.0718 11.5523 16.8539L11.4681 16.7813L7.46814 12.7797C7.1753 12.4868 7.17539 12.0119 7.46834 11.7191C7.73466 11.4529 8.15132 11.4287 8.44489 11.6466L8.529 11.7193L11.2497 14.4412L11.2494 7.74951C11.2494 7.36982 11.5315 7.05602 11.8976 7.00636L11.9994 6.99951C12.3791 6.99951 12.6929 7.28167 12.7425 7.64774L12.7494 7.74951L12.7497 14.4392L15.4691 11.7199C15.7353 11.4536 16.152 11.4294 16.4456 11.6473L16.5297 11.7199Z + M22.001 12C22.001 6.47715 17.5238 2 12.001 2C6.47813 2 2.00098 6.47715 2.00098 12C2.00098 17.5228 6.47813 22 12.001 22C17.5238 22 22.001 17.5228 22.001 12ZM7.47028 12.2801C7.20401 12.0138 7.17981 11.5972 7.39766 11.3036L7.47028 11.2195L11.4711 7.21863C11.7374 6.95233 12.1541 6.92816 12.4477 7.14609L12.5319 7.21873L16.5319 11.2203C16.8247 11.5132 16.8246 11.9881 16.5317 12.2809C16.2653 12.5471 15.8487 12.5713 15.5551 12.3534L15.471 12.2807L12.7503 9.55878L12.7506 16.2505C12.7506 16.6302 12.4685 16.944 12.1024 16.9936L12.0006 17.0005C11.6209 17.0005 11.3071 16.7183 11.2575 16.3523L11.2506 16.2505L11.2503 9.56078L8.53094 12.2801C8.26467 12.5464 7.84801 12.5706 7.5544 12.3527L7.47028 12.2801Z + M16.0883 6.41228C16.016 6.31886 15.9377 6.2298 15.8539 6.14569C15.5417 5.83255 15.1606 5.59666 14.741 5.45683L13.3632 5.00939C13.257 4.97196 13.165 4.90253 13.1 4.81068C13.0349 4.71883 13 4.60908 13 4.49656C13 4.38404 13.0349 4.27429 13.1 4.18244C13.165 4.09058 13.257 4.02116 13.3632 3.98372L14.741 3.53628C15.1547 3.39352 15.5299 3.15705 15.837 2.84537C16.1357 2.54224 16.3623 2.17595 16.5 1.77372L16.5114 1.73963L16.9592 0.362894C16.9967 0.256782 17.0662 0.164895 17.1581 0.0998993C17.25 0.0349035 17.3598 0 17.4724 0C17.5851 0 17.6949 0.0349035 17.7868 0.0998993C17.8787 0.164895 17.9482 0.256782 17.9857 0.362894L18.4335 1.73963C18.5727 2.15819 18.8077 2.53853 19.1198 2.85041C19.432 3.1623 19.8126 3.39715 20.2315 3.53628L21.6093 3.98372L21.6368 3.99061C21.743 4.02804 21.835 4.09747 21.9 4.18932C21.9651 4.28117 22 4.39092 22 4.50344C22 4.61596 21.9651 4.72571 21.9 4.81756C21.835 4.90942 21.743 4.97884 21.6368 5.01628L20.259 5.46372C19.8402 5.60285 19.4595 5.8377 19.1474 6.14959C18.8353 6.46147 18.6003 6.84181 18.461 7.26037L18.0132 8.63711C18.0092 8.64855 18.0048 8.65983 18 8.67093C17.9605 8.76273 17.8964 8.84212 17.8144 8.9001C17.7224 8.9651 17.6126 9 17.5 9C17.3874 9 17.2776 8.9651 17.1856 8.9001C17.0937 8.8351 17.0242 8.74322 16.9868 8.63711L16.539 7.26037C16.4378 6.95331 16.2851 6.66664 16.0883 6.41228ZM23.7829 10.2132L23.0175 9.9646C22.7848 9.8873 22.5733 9.75683 22.3999 9.58356C22.2265 9.41029 22.0959 9.199 22.0186 8.96646L21.7698 8.20161C21.749 8.14266 21.7104 8.09161 21.6593 8.0555C21.6083 8.01939 21.5473 8 21.4847 8C21.4221 8 21.3611 8.01939 21.31 8.0555C21.259 8.09161 21.2204 8.14266 21.1996 8.20161L20.9508 8.96646C20.875 9.19736 20.7467 9.40761 20.5761 9.58076C20.4055 9.75392 20.1971 9.88529 19.9672 9.9646L19.2018 10.2132C19.1428 10.234 19.0917 10.2725 19.0555 10.3236C19.0194 10.3746 19 10.4356 19 10.4981C19 10.5606 19.0194 10.6216 19.0555 10.6726C19.0917 10.7236 19.1428 10.7622 19.2018 10.783L19.9672 11.0316C20.2003 11.1093 20.412 11.2403 20.5855 11.4143C20.7589 11.5882 20.8893 11.8003 20.9661 12.0335L21.2149 12.7984C21.2357 12.8573 21.2743 12.9084 21.3254 12.9445C21.3764 12.9806 21.4374 13 21.5 13C21.5626 13 21.6236 12.9806 21.6746 12.9445C21.7257 12.9084 21.7643 12.8573 21.7851 12.7984L22.0339 12.0335C22.1113 11.801 22.2418 11.5897 22.4152 11.4164C22.5886 11.2432 22.8001 11.1127 23.0328 11.0354L23.7982 10.7868C23.8572 10.766 23.9083 10.7275 23.9445 10.6764C23.9806 10.6254 24 10.5644 24 10.5019C24 10.4394 23.9806 10.3784 23.9445 10.3274C23.9083 10.2764 23.8572 10.2378 23.7982 10.217L23.7829 10.2132ZM12 2.00073C12.957 2.00073 13.8827 2.13518 14.7591 2.38621C14.6432 2.4573 14.5193 2.51563 14.39 2.55903L13.05 2.99903C12.4247 3.22211 12.0018 3.80273 12 4.46903C12.002 5.10403 12.4159 5.74618 13 5.99903L14.4 6.45903C14.9164 6.63419 15.4143 7.02503 15.59 7.54903L16 8.99903C16.2128 9.62184 16.8014 10.047 17.46 10.049C17.6963 10.0519 17.9298 9.99699 18.14 9.88903C18.0212 10.0886 17.959 10.3168 17.96 10.549C17.9556 11.0886 18.322 11.5971 18.83 11.779C18.9034 11.8023 18.9865 11.8233 19.0738 11.8454C19.4335 11.9365 19.8642 12.0456 19.98 12.399L19.9943 12.4444C20.2303 13.1929 20.5003 14.0495 21.46 14.0495C21.5839 14.0495 21.6961 14.0353 21.7983 14.009C20.8685 18.5694 16.835 22.0007 12 22.0007C6.47715 22.0007 2 17.5236 2 12.0007C2 6.47788 6.47715 2.00073 12 2.00073ZM7.4693 12.2808L7.55342 12.3535C7.84703 12.5713 8.2637 12.5471 8.52996 12.2808L11.2493 9.56152L11.2496 16.2512L11.2565 16.353C11.3061 16.7191 11.6199 17.0012 11.9996 17.0012L12.1014 16.9944C12.4675 16.9447 12.7496 16.6309 12.7496 16.2512L12.7493 9.55952L15.47 12.2814L15.5541 12.3541C15.8477 12.572 16.2644 12.5479 16.5307 12.2817C16.8236 11.9888 16.8237 11.5139 16.5309 11.221L12.5309 7.21947L12.4468 7.14682C12.1532 6.92889 11.7364 6.95306 11.4701 7.21936L7.4693 11.2202L7.39669 11.3043C7.17883 11.5979 7.20304 12.0146 7.4693 12.2808Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs new file mode 100644 index 000000000..6674dcc91 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -0,0 +1,13 @@ +using System; +using Avalonia.Controls; +using OpenKh.Tools.ModManager.Models; + +namespace OpenKh.Tools.ModManager.Views; + +public partial class MainWindow : Window +{ + public MainWindow() + { + InitializeComponent(); + } +} diff --git a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml new file mode 100644 index 000000000..9826ef22d --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs new file mode 100644 index 000000000..5f113c9af --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace OpenKh.Tools.ModManager.Views +{ + public partial class ModDetailsView : ContentPage + { + public ModDetailsView() + { + InitializeComponent(); + } + } +} diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml new file mode 100644 index 000000000..5c3a181d7 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs new file mode 100644 index 000000000..dcac9151b --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs @@ -0,0 +1,22 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; +using OpenKh.Tools.ModManager.Models; +using System; + +namespace OpenKh.Tools.ModManager.Views +{ + public partial class ModListView : ContentPage + { + public ModListView() + { + InitializeComponent(); + + ModList.ItemsSource = new ModModel[] + { + new ModModel{ ModTitle = "Kingdom Hearts II - Re:Fined", ModAuthor = "The Re:Fined Team", ModDescription = "Kingdom Hearts - Re:Fined for Kingdom Hearts II | r190826-0855-FC", ModSource = new Uri("https://codeberg.org/KH-ReFined/KH-ReFined"), ModIssues = new Uri("https://codeberg.org/KH-ReFined/KH-ReFined/issues"), ModActive = true }, + new ModModel{ ModTitle = "GOA ROM Edition", ModAuthor = "1234567890num", ModActive = false } + }; + } + } +} diff --git a/OpenKh.Tools.ModManager/app.manifest b/OpenKh.Tools.ModManager/app.manifest new file mode 100644 index 000000000..8c970c5fd --- /dev/null +++ b/OpenKh.Tools.ModManager/app.manifest @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/OpenKh.sln b/OpenKh.sln index 65f4f3209..e7b042f03 100644 --- a/OpenKh.sln +++ b/OpenKh.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.32203.90 +# Visual Studio Version 18 +VisualStudioVersion = 18.8.12021.73 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Kh2", "OpenKh.Kh2\OpenKh.Kh2.csproj", "{D5B36CD1-5B5D-4CE9-A673-209AD386D68A}" EndProject @@ -234,6 +234,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Openkh.Tools.Kh2LocalSetEdi EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Recom", "OpenKh.Recom\OpenKh.Recom.csproj", "{E9CAA582-1823-4B42-8218-F4B7B2C788D3}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Tools.ModManager", "OpenKh.Tools.ModManager\OpenKh.Tools.ModManager.csproj", "{02106CAB-5C1D-4651-B133-743E4CD6F302}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution .NET Debug|Any CPU = .NET Debug|Any CPU @@ -952,6 +954,14 @@ Global {E9CAA582-1823-4B42-8218-F4B7B2C788D3}.Debug|Any CPU.Build.0 = Debug|Any CPU {E9CAA582-1823-4B42-8218-F4B7B2C788D3}.Release|Any CPU.ActiveCfg = Release|Any CPU {E9CAA582-1823-4B42-8218-F4B7B2C788D3}.Release|Any CPU.Build.0 = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}..NET Debug|Any CPU.ActiveCfg = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}..NET Debug|Any CPU.Build.0 = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}..NET Release|Any CPU.ActiveCfg = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}..NET Release|Any CPU.Build.0 = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}.Debug|Any CPU.Build.0 = Debug|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}.Release|Any CPU.ActiveCfg = Release|Any CPU + {02106CAB-5C1D-4651-B133-743E4CD6F302}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1050,6 +1060,7 @@ Global {A8DAC079-E416-4574-9B54-19EED185D368} = {9C52D787-9819-4B89-989B-EEA6FEB20731} {D58EEC5B-962C-4888-953B-4A443015E45D} = {402B2669-D594-4DFA-965B-02A92626ADC6} {E9CAA582-1823-4B42-8218-F4B7B2C788D3} = {0FB7CD6A-EE31-467D-A590-267DCD028618} + {02106CAB-5C1D-4651-B133-743E4CD6F302} = {402B2669-D594-4DFA-965B-02A92626ADC6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {1DF3960B-4FE5-4E56-92D4-2FC0A912E823} From 636959219dd55d87828d20732033a209342b58f2 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 06:53:21 +0200 Subject: [PATCH 002/175] [Mod Manager] - Status bar implementation. --- .../Converters/XamlConverters.cs | 18 +++ .../ViewModels/MainViewModel.cs | 3 + OpenKh.Tools.ModManager/Views/MainView.axaml | 120 ++++++++++-------- .../Views/ModStatusView.axaml | 27 ++++ .../Views/ModStatusView.axaml.cs | 17 +++ 5 files changed, 129 insertions(+), 56 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Converters/XamlConverters.cs create mode 100644 OpenKh.Tools.ModManager/Views/ModStatusView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs diff --git a/OpenKh.Tools.ModManager/Converters/XamlConverters.cs b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs new file mode 100644 index 000000000..e3260c9ba --- /dev/null +++ b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs @@ -0,0 +1,18 @@ +using Avalonia.Data.Converters; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.Converters +{ + public class BooleanValueMapper : IValueConverter + { + public object? IfTrue { get; set; } + public object? IfFalse { get; set; } + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => value is bool b && b ? IfTrue : IfFalse; + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException(); + } +} diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index a28369e1d..df5c1bc9f 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -7,4 +7,7 @@ public partial class MainViewModel : ViewModelBase { [ObservableProperty] private ModModel? _currentMod = null; + + [ObservableProperty] + private bool _configurationValid = true; } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 82ee68cb1..688326f71 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -5,7 +5,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:OpenKh.Tools.ModManager.Models" xmlns:view="clr-namespace:OpenKh.Tools.ModManager.Views" - xmlns:svg="clr-namespace:Avalonia.Svg;assembly=Avalonia.Svg" mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="850" Width="750" Height="850" MinWidth="750" MinHeight="850" @@ -15,71 +14,80 @@ Title="OpenKh.Tools.ModManager"> - M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM12 17.5C12 14.4624 9.53757 12 6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5ZM7.00065 18L7.00111 20.5035C7.00111 20.7797 6.77725 21.0035 6.50111 21.0035C6.22497 21.0035 6.00111 20.7797 6.00111 20.5035L6.00065 18H3.4956C3.21973 18 2.99609 17.7762 2.99609 17.5C2.99609 17.2239 3.21973 17 3.4956 17H6.00046L6 14.4993C6 14.2231 6.22386 13.9993 6.5 13.9993C6.77614 13.9993 7 14.2231 7 14.4993L7.00046 17H9.49659C9.77246 17 9.99609 17.2239 9.99609 17.5C9.99609 17.7762 9.77246 18 9.49659 18H7.00065Z - M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5C12 14.4624 9.53757 12 6.5 12ZM8.97823 15.7313L7.20843 17.5001L8.97806 19.2697C9.17332 19.465 9.17332 19.7816 8.97806 19.9768C8.78279 20.1721 8.46621 20.1721 8.27095 19.9768L6.50112 18.207L4.73246 19.9746C4.5372 20.1699 4.22061 20.1699 4.02535 19.9746C3.83009 19.7794 3.83009 19.4628 4.02535 19.2675L5.79401 17.4999L4.02535 15.7312C3.83009 15.536 3.83009 15.2194 4.02535 15.0241C4.22061 14.8289 4.5372 14.8289 4.73246 15.0241L6.50133 16.793L8.27113 15.0242C8.46639 14.829 8.78297 14.829 8.97823 15.0242C9.1735 15.2195 9.1735 15.5361 8.97823 15.7313Z - M1.99902 12C1.99902 17.5228 6.47617 22 11.999 22C17.5219 22 21.999 17.5228 21.999 12C21.999 6.47715 17.5219 2 11.999 2C6.47618 2 1.99902 6.47715 1.99902 12ZM16.5297 11.7199C16.796 11.9862 16.8202 12.4028 16.6023 12.6964L16.5297 12.7805L12.5289 16.7814C12.2626 17.0477 11.8459 17.0718 11.5523 16.8539L11.4681 16.7813L7.46814 12.7797C7.1753 12.4868 7.17539 12.0119 7.46834 11.7191C7.73466 11.4529 8.15132 11.4287 8.44489 11.6466L8.529 11.7193L11.2497 14.4412L11.2494 7.74951C11.2494 7.36982 11.5315 7.05602 11.8976 7.00636L11.9994 6.99951C12.3791 6.99951 12.6929 7.28167 12.7425 7.64774L12.7494 7.74951L12.7497 14.4392L15.4691 11.7199C15.7353 11.4536 16.152 11.4294 16.4456 11.6473L16.5297 11.7199Z - M22.001 12C22.001 6.47715 17.5238 2 12.001 2C6.47813 2 2.00098 6.47715 2.00098 12C2.00098 17.5228 6.47813 22 12.001 22C17.5238 22 22.001 17.5228 22.001 12ZM7.47028 12.2801C7.20401 12.0138 7.17981 11.5972 7.39766 11.3036L7.47028 11.2195L11.4711 7.21863C11.7374 6.95233 12.1541 6.92816 12.4477 7.14609L12.5319 7.21873L16.5319 11.2203C16.8247 11.5132 16.8246 11.9881 16.5317 12.2809C16.2653 12.5471 15.8487 12.5713 15.5551 12.3534L15.471 12.2807L12.7503 9.55878L12.7506 16.2505C12.7506 16.6302 12.4685 16.944 12.1024 16.9936L12.0006 17.0005C11.6209 17.0005 11.3071 16.7183 11.2575 16.3523L11.2506 16.2505L11.2503 9.56078L8.53094 12.2801C8.26467 12.5464 7.84801 12.5706 7.5544 12.3527L7.47028 12.2801Z - M16.0883 6.41228C16.016 6.31886 15.9377 6.2298 15.8539 6.14569C15.5417 5.83255 15.1606 5.59666 14.741 5.45683L13.3632 5.00939C13.257 4.97196 13.165 4.90253 13.1 4.81068C13.0349 4.71883 13 4.60908 13 4.49656C13 4.38404 13.0349 4.27429 13.1 4.18244C13.165 4.09058 13.257 4.02116 13.3632 3.98372L14.741 3.53628C15.1547 3.39352 15.5299 3.15705 15.837 2.84537C16.1357 2.54224 16.3623 2.17595 16.5 1.77372L16.5114 1.73963L16.9592 0.362894C16.9967 0.256782 17.0662 0.164895 17.1581 0.0998993C17.25 0.0349035 17.3598 0 17.4724 0C17.5851 0 17.6949 0.0349035 17.7868 0.0998993C17.8787 0.164895 17.9482 0.256782 17.9857 0.362894L18.4335 1.73963C18.5727 2.15819 18.8077 2.53853 19.1198 2.85041C19.432 3.1623 19.8126 3.39715 20.2315 3.53628L21.6093 3.98372L21.6368 3.99061C21.743 4.02804 21.835 4.09747 21.9 4.18932C21.9651 4.28117 22 4.39092 22 4.50344C22 4.61596 21.9651 4.72571 21.9 4.81756C21.835 4.90942 21.743 4.97884 21.6368 5.01628L20.259 5.46372C19.8402 5.60285 19.4595 5.8377 19.1474 6.14959C18.8353 6.46147 18.6003 6.84181 18.461 7.26037L18.0132 8.63711C18.0092 8.64855 18.0048 8.65983 18 8.67093C17.9605 8.76273 17.8964 8.84212 17.8144 8.9001C17.7224 8.9651 17.6126 9 17.5 9C17.3874 9 17.2776 8.9651 17.1856 8.9001C17.0937 8.8351 17.0242 8.74322 16.9868 8.63711L16.539 7.26037C16.4378 6.95331 16.2851 6.66664 16.0883 6.41228ZM23.7829 10.2132L23.0175 9.9646C22.7848 9.8873 22.5733 9.75683 22.3999 9.58356C22.2265 9.41029 22.0959 9.199 22.0186 8.96646L21.7698 8.20161C21.749 8.14266 21.7104 8.09161 21.6593 8.0555C21.6083 8.01939 21.5473 8 21.4847 8C21.4221 8 21.3611 8.01939 21.31 8.0555C21.259 8.09161 21.2204 8.14266 21.1996 8.20161L20.9508 8.96646C20.875 9.19736 20.7467 9.40761 20.5761 9.58076C20.4055 9.75392 20.1971 9.88529 19.9672 9.9646L19.2018 10.2132C19.1428 10.234 19.0917 10.2725 19.0555 10.3236C19.0194 10.3746 19 10.4356 19 10.4981C19 10.5606 19.0194 10.6216 19.0555 10.6726C19.0917 10.7236 19.1428 10.7622 19.2018 10.783L19.9672 11.0316C20.2003 11.1093 20.412 11.2403 20.5855 11.4143C20.7589 11.5882 20.8893 11.8003 20.9661 12.0335L21.2149 12.7984C21.2357 12.8573 21.2743 12.9084 21.3254 12.9445C21.3764 12.9806 21.4374 13 21.5 13C21.5626 13 21.6236 12.9806 21.6746 12.9445C21.7257 12.9084 21.7643 12.8573 21.7851 12.7984L22.0339 12.0335C22.1113 11.801 22.2418 11.5897 22.4152 11.4164C22.5886 11.2432 22.8001 11.1127 23.0328 11.0354L23.7982 10.7868C23.8572 10.766 23.9083 10.7275 23.9445 10.6764C23.9806 10.6254 24 10.5644 24 10.5019C24 10.4394 23.9806 10.3784 23.9445 10.3274C23.9083 10.2764 23.8572 10.2378 23.7982 10.217L23.7829 10.2132ZM12 2.00073C12.957 2.00073 13.8827 2.13518 14.7591 2.38621C14.6432 2.4573 14.5193 2.51563 14.39 2.55903L13.05 2.99903C12.4247 3.22211 12.0018 3.80273 12 4.46903C12.002 5.10403 12.4159 5.74618 13 5.99903L14.4 6.45903C14.9164 6.63419 15.4143 7.02503 15.59 7.54903L16 8.99903C16.2128 9.62184 16.8014 10.047 17.46 10.049C17.6963 10.0519 17.9298 9.99699 18.14 9.88903C18.0212 10.0886 17.959 10.3168 17.96 10.549C17.9556 11.0886 18.322 11.5971 18.83 11.779C18.9034 11.8023 18.9865 11.8233 19.0738 11.8454C19.4335 11.9365 19.8642 12.0456 19.98 12.399L19.9943 12.4444C20.2303 13.1929 20.5003 14.0495 21.46 14.0495C21.5839 14.0495 21.6961 14.0353 21.7983 14.009C20.8685 18.5694 16.835 22.0007 12 22.0007C6.47715 22.0007 2 17.5236 2 12.0007C2 6.47788 6.47715 2.00073 12 2.00073ZM7.4693 12.2808L7.55342 12.3535C7.84703 12.5713 8.2637 12.5471 8.52996 12.2808L11.2493 9.56152L11.2496 16.2512L11.2565 16.353C11.3061 16.7191 11.6199 17.0012 11.9996 17.0012L12.1014 16.9944C12.4675 16.9447 12.7496 16.6309 12.7496 16.2512L12.7493 9.55952L15.47 12.2814L15.5541 12.3541C15.8477 12.572 16.2644 12.5479 16.5307 12.2817C16.8236 11.9888 16.8237 11.5139 16.5309 11.221L12.5309 7.21947L12.4468 7.14682C12.1532 6.92889 11.7364 6.95306 11.4701 7.21936L7.4693 11.2202L7.39669 11.3043C7.17883 11.5979 7.20304 12.0146 7.4693 12.2808Z + M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM12 17.5C12 14.4624 9.53757 12 6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5ZM7.00065 18L7.00111 20.5035C7.00111 20.7797 6.77725 21.0035 6.50111 21.0035C6.22497 21.0035 6.00111 20.7797 6.00111 20.5035L6.00065 18H3.4956C3.21973 18 2.99609 17.7762 2.99609 17.5C2.99609 17.2239 3.21973 17 3.4956 17H6.00046L6 14.4993C6 14.2231 6.22386 13.9993 6.5 13.9993C6.77614 13.9993 7 14.2231 7 14.4993L7.00046 17H9.49659C9.77246 17 9.99609 17.2239 9.99609 17.5C9.99609 17.7762 9.77246 18 9.49659 18H7.00065Z + M12 2V8C12 9.10457 12.8954 10 14 10H20V20C20 21.1046 19.1046 22 18 22H11.1904C12.3112 20.8321 13 19.2465 13 17.5C13 13.9101 10.0899 11 6.5 11C5.6141 11 4.76959 11.1772 4 11.4982V4C4 2.89543 4.89543 2 6 2H12ZM13.5 2.5V8C13.5 8.27614 13.7239 8.5 14 8.5H19.5L13.5 2.5ZM6.5 12C3.46243 12 1 14.4624 1 17.5C1 20.5376 3.46243 23 6.5 23C9.53757 23 12 20.5376 12 17.5C12 14.4624 9.53757 12 6.5 12ZM8.97823 15.7313L7.20843 17.5001L8.97806 19.2697C9.17332 19.465 9.17332 19.7816 8.97806 19.9768C8.78279 20.1721 8.46621 20.1721 8.27095 19.9768L6.50112 18.207L4.73246 19.9746C4.5372 20.1699 4.22061 20.1699 4.02535 19.9746C3.83009 19.7794 3.83009 19.4628 4.02535 19.2675L5.79401 17.4999L4.02535 15.7312C3.83009 15.536 3.83009 15.2194 4.02535 15.0241C4.22061 14.8289 4.5372 14.8289 4.73246 15.0241L6.50133 16.793L8.27113 15.0242C8.46639 14.829 8.78297 14.829 8.97823 15.0242C9.1735 15.2195 9.1735 15.5361 8.97823 15.7313Z + M1.99902 12C1.99902 17.5228 6.47617 22 11.999 22C17.5219 22 21.999 17.5228 21.999 12C21.999 6.47715 17.5219 2 11.999 2C6.47618 2 1.99902 6.47715 1.99902 12ZM16.5297 11.7199C16.796 11.9862 16.8202 12.4028 16.6023 12.6964L16.5297 12.7805L12.5289 16.7814C12.2626 17.0477 11.8459 17.0718 11.5523 16.8539L11.4681 16.7813L7.46814 12.7797C7.1753 12.4868 7.17539 12.0119 7.46834 11.7191C7.73466 11.4529 8.15132 11.4287 8.44489 11.6466L8.529 11.7193L11.2497 14.4412L11.2494 7.74951C11.2494 7.36982 11.5315 7.05602 11.8976 7.00636L11.9994 6.99951C12.3791 6.99951 12.6929 7.28167 12.7425 7.64774L12.7494 7.74951L12.7497 14.4392L15.4691 11.7199C15.7353 11.4536 16.152 11.4294 16.4456 11.6473L16.5297 11.7199Z + M22.001 12C22.001 6.47715 17.5238 2 12.001 2C6.47813 2 2.00098 6.47715 2.00098 12C2.00098 17.5228 6.47813 22 12.001 22C17.5238 22 22.001 17.5228 22.001 12ZM7.47028 12.2801C7.20401 12.0138 7.17981 11.5972 7.39766 11.3036L7.47028 11.2195L11.4711 7.21863C11.7374 6.95233 12.1541 6.92816 12.4477 7.14609L12.5319 7.21873L16.5319 11.2203C16.8247 11.5132 16.8246 11.9881 16.5317 12.2809C16.2653 12.5471 15.8487 12.5713 15.5551 12.3534L15.471 12.2807L12.7503 9.55878L12.7506 16.2505C12.7506 16.6302 12.4685 16.944 12.1024 16.9936L12.0006 17.0005C11.6209 17.0005 11.3071 16.7183 11.2575 16.3523L11.2506 16.2505L11.2503 9.56078L8.53094 12.2801C8.26467 12.5464 7.84801 12.5706 7.5544 12.3527L7.47028 12.2801Z + M16.0883 6.41228C16.016 6.31886 15.9377 6.2298 15.8539 6.14569C15.5417 5.83255 15.1606 5.59666 14.741 5.45683L13.3632 5.00939C13.257 4.97196 13.165 4.90253 13.1 4.81068C13.0349 4.71883 13 4.60908 13 4.49656C13 4.38404 13.0349 4.27429 13.1 4.18244C13.165 4.09058 13.257 4.02116 13.3632 3.98372L14.741 3.53628C15.1547 3.39352 15.5299 3.15705 15.837 2.84537C16.1357 2.54224 16.3623 2.17595 16.5 1.77372L16.5114 1.73963L16.9592 0.362894C16.9967 0.256782 17.0662 0.164895 17.1581 0.0998993C17.25 0.0349035 17.3598 0 17.4724 0C17.5851 0 17.6949 0.0349035 17.7868 0.0998993C17.8787 0.164895 17.9482 0.256782 17.9857 0.362894L18.4335 1.73963C18.5727 2.15819 18.8077 2.53853 19.1198 2.85041C19.432 3.1623 19.8126 3.39715 20.2315 3.53628L21.6093 3.98372L21.6368 3.99061C21.743 4.02804 21.835 4.09747 21.9 4.18932C21.9651 4.28117 22 4.39092 22 4.50344C22 4.61596 21.9651 4.72571 21.9 4.81756C21.835 4.90942 21.743 4.97884 21.6368 5.01628L20.259 5.46372C19.8402 5.60285 19.4595 5.8377 19.1474 6.14959C18.8353 6.46147 18.6003 6.84181 18.461 7.26037L18.0132 8.63711C18.0092 8.64855 18.0048 8.65983 18 8.67093C17.9605 8.76273 17.8964 8.84212 17.8144 8.9001C17.7224 8.9651 17.6126 9 17.5 9C17.3874 9 17.2776 8.9651 17.1856 8.9001C17.0937 8.8351 17.0242 8.74322 16.9868 8.63711L16.539 7.26037C16.4378 6.95331 16.2851 6.66664 16.0883 6.41228ZM23.7829 10.2132L23.0175 9.9646C22.7848 9.8873 22.5733 9.75683 22.3999 9.58356C22.2265 9.41029 22.0959 9.199 22.0186 8.96646L21.7698 8.20161C21.749 8.14266 21.7104 8.09161 21.6593 8.0555C21.6083 8.01939 21.5473 8 21.4847 8C21.4221 8 21.3611 8.01939 21.31 8.0555C21.259 8.09161 21.2204 8.14266 21.1996 8.20161L20.9508 8.96646C20.875 9.19736 20.7467 9.40761 20.5761 9.58076C20.4055 9.75392 20.1971 9.88529 19.9672 9.9646L19.2018 10.2132C19.1428 10.234 19.0917 10.2725 19.0555 10.3236C19.0194 10.3746 19 10.4356 19 10.4981C19 10.5606 19.0194 10.6216 19.0555 10.6726C19.0917 10.7236 19.1428 10.7622 19.2018 10.783L19.9672 11.0316C20.2003 11.1093 20.412 11.2403 20.5855 11.4143C20.7589 11.5882 20.8893 11.8003 20.9661 12.0335L21.2149 12.7984C21.2357 12.8573 21.2743 12.9084 21.3254 12.9445C21.3764 12.9806 21.4374 13 21.5 13C21.5626 13 21.6236 12.9806 21.6746 12.9445C21.7257 12.9084 21.7643 12.8573 21.7851 12.7984L22.0339 12.0335C22.1113 11.801 22.2418 11.5897 22.4152 11.4164C22.5886 11.2432 22.8001 11.1127 23.0328 11.0354L23.7982 10.7868C23.8572 10.766 23.9083 10.7275 23.9445 10.6764C23.9806 10.6254 24 10.5644 24 10.5019C24 10.4394 23.9806 10.3784 23.9445 10.3274C23.9083 10.2764 23.8572 10.2378 23.7982 10.217L23.7829 10.2132ZM12 2.00073C12.957 2.00073 13.8827 2.13518 14.7591 2.38621C14.6432 2.4573 14.5193 2.51563 14.39 2.55903L13.05 2.99903C12.4247 3.22211 12.0018 3.80273 12 4.46903C12.002 5.10403 12.4159 5.74618 13 5.99903L14.4 6.45903C14.9164 6.63419 15.4143 7.02503 15.59 7.54903L16 8.99903C16.2128 9.62184 16.8014 10.047 17.46 10.049C17.6963 10.0519 17.9298 9.99699 18.14 9.88903C18.0212 10.0886 17.959 10.3168 17.96 10.549C17.9556 11.0886 18.322 11.5971 18.83 11.779C18.9034 11.8023 18.9865 11.8233 19.0738 11.8454C19.4335 11.9365 19.8642 12.0456 19.98 12.399L19.9943 12.4444C20.2303 13.1929 20.5003 14.0495 21.46 14.0495C21.5839 14.0495 21.6961 14.0353 21.7983 14.009C20.8685 18.5694 16.835 22.0007 12 22.0007C6.47715 22.0007 2 17.5236 2 12.0007C2 6.47788 6.47715 2.00073 12 2.00073ZM7.4693 12.2808L7.55342 12.3535C7.84703 12.5713 8.2637 12.5471 8.52996 12.2808L11.2493 9.56152L11.2496 16.2512L11.2565 16.353C11.3061 16.7191 11.6199 17.0012 11.9996 17.0012L12.1014 16.9944C12.4675 16.9447 12.7496 16.6309 12.7496 16.2512L12.7493 9.55952L15.47 12.2814L15.5541 12.3541C15.8477 12.572 16.2644 12.5479 16.5307 12.2817C16.8236 11.9888 16.8237 11.5139 16.5309 11.221L12.5309 7.21947L12.4468 7.14682C12.1532 6.92889 11.7364 6.95306 11.4701 7.21936L7.4693 11.2202L7.39669 11.3043C7.17883 11.5979 7.20304 12.0146 7.4693 12.2808Z - - - - - - - - - - - - + + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + - - + + + + + + + + + + + - + + + - + + + + diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml new file mode 100644 index 000000000..ae79c08e5 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml @@ -0,0 +1,27 @@ + + + + M18 13C18.1678 13 18.3344 13.0083 18.498 13.0244C18.7956 13.0538 19.0252 13.2867 19.0878 13.5791L19.3544 14.8223C19.4237 15.146 19.7582 15.339 20.0732 15.2373L21.2822 14.8457C21.5662 14.754 21.882 14.837 22.0566 15.0791C22.2496 15.3471 22.417 15.6353 22.5546 15.9395C22.6778 16.2119 22.591 16.5271 22.3691 16.7275L21.4257 17.5801C21.1801 17.802 21.1801 18.1883 21.4257 18.4102L22.373 19.2646C22.5948 19.4648 22.6812 19.7804 22.5585 20.0527C22.4217 20.3565 22.2555 20.6443 22.0634 20.9121C21.889 21.1551 21.5717 21.2386 21.2871 21.1465L20.0732 20.7529C19.7582 20.6512 19.4237 20.8442 19.3544 21.168L19.0859 22.4199C19.0233 22.7124 18.7937 22.9457 18.496 22.9756C18.3331 22.9919 18.1672 23 18 23C17.8341 23 17.6695 22.9915 17.5078 22.9756C17.2099 22.946 16.9786 22.7136 16.916 22.4209L16.6494 21.168C16.5801 20.8442 16.2457 20.6511 15.9306 20.7529L14.7119 21.1465C14.4274 21.2382 14.1107 21.155 13.9365 20.9121C13.745 20.6451 13.5789 20.3584 13.4423 20.0557C13.3196 19.7834 13.4062 19.4678 13.6279 19.2676L14.5781 18.4102C14.8238 18.1883 14.8238 17.802 14.5781 17.5801L13.6308 16.7246C13.4088 16.5241 13.3218 16.208 13.4453 15.9355C13.5828 15.6321 13.7505 15.3445 13.9433 15.0771C14.1179 14.8353 14.4339 14.753 14.7177 14.8447L15.9306 15.2373C16.2457 15.3392 16.5801 15.1461 16.6494 14.8223L16.914 13.5791C16.9765 13.2865 17.2072 13.0541 17.5048 13.0244C17.6674 13.0084 17.8333 13 18 13ZM18 16.5C17.1719 16.5004 16.5 17.1718 16.5 18C16.5 18.8282 17.1719 19.4996 18 19.5C18.8284 19.5 19.5 18.8284 19.5 18C19.5 17.1716 18.8284 16.5 18 16.5ZM9.00776 1.49805C9.34737 1.49806 9.68098 1.52151 10.0068 1.56543L10.33 1.61621L10.4394 1.64453C10.6888 1.72927 10.8786 1.94064 10.9326 2.2041L11.2978 3.98633L11.3173 4.06055C11.4334 4.42205 11.8259 4.62709 12.1953 4.50391L13.9228 3.92773L14.0322 3.89941C14.2913 3.8546 14.5589 3.94943 14.7324 4.1543C15.3037 4.82893 15.7593 5.60646 16.0654 6.45605C16.1691 6.74458 16.0867 7.06796 15.8574 7.27148L14.4921 8.48242C14.1819 8.75784 14.1809 9.24216 14.4912 9.51758L15.8554 10.7266C16.0851 10.9302 16.1676 11.2542 16.0634 11.543C15.7567 12.3932 15.3011 13.1705 14.7294 13.8447C14.5313 14.0783 14.2105 14.168 13.9199 14.0713L12.1953 13.4961C11.8259 13.3729 11.4334 13.5779 11.3173 13.9395L11.2978 14.0137L10.9335 15.791C10.8719 16.0918 10.6332 16.3248 10.331 16.3789C9.90421 16.4549 9.46182 16.498 9.00776 16.498C8.54901 16.498 8.10153 16.455 7.6689 16.377C7.36709 16.3225 7.12887 16.0895 7.06733 15.7891L6.70503 14.0137C6.62183 13.6069 6.20142 13.3649 5.80757 13.4961L4.09175 14.0674C3.80095 14.1642 3.48023 14.0737 3.28218 13.8398C2.7102 13.1641 2.25332 12.3856 1.94722 11.5332C1.84354 11.2444 1.92649 10.9212 2.1562 10.7178L3.51167 9.51758C3.8217 9.24218 3.8217 8.75782 3.51167 8.48242L2.15522 7.28027C1.92584 7.07698 1.84288 6.75435 1.94624 6.46582C2.25177 5.61404 2.70754 4.83434 3.27925 4.1582L3.35835 4.07715C3.55642 3.90439 3.83422 3.8458 4.08882 3.93066L5.80757 4.50391C6.20142 4.63511 6.62183 4.39309 6.70503 3.98633L7.06831 2.20605L7.09858 2.09668C7.18953 1.85006 7.4057 1.66578 7.66987 1.61816C8.10313 1.5401 8.55035 1.49805 9.00776 1.49805ZM8.99995 6.75C7.75731 6.75 6.74995 7.75736 6.74995 9C6.74995 10.2426 7.75731 11.25 8.99995 11.25C10.2425 11.2499 11.25 10.2426 11.25 9C11.25 7.75739 10.2425 6.75005 8.99995 6.75Z + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs new file mode 100644 index 000000000..7d1167294 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs @@ -0,0 +1,17 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System; +using System.Globalization; + +namespace OpenKh.Tools.ModManager.Views +{ + public partial class ModStatusView : ContentPage + { + public ModStatusView() + { + InitializeComponent(); + } + } +} From e51fcb5a2d25ec9f2533d4447f19c4df2978bf3d Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 10:00:46 +0200 Subject: [PATCH 003/175] [Mod Manager] - Some View fixes. --- OpenKh.Tools.ModManager/Models/ModModel.cs | 5 +++-- OpenKh.Tools.ModManager/Views/MainView.axaml | 10 +++++----- OpenKh.Tools.ModManager/Views/ModListView.axaml | 6 ++++-- OpenKh.Tools.ModManager/Views/ModListView.axaml.cs | 7 +------ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/OpenKh.Tools.ModManager/Models/ModModel.cs b/OpenKh.Tools.ModManager/Models/ModModel.cs index 5b79bb653..32444b967 100644 --- a/OpenKh.Tools.ModManager/Models/ModModel.cs +++ b/OpenKh.Tools.ModManager/Models/ModModel.cs @@ -1,3 +1,4 @@ +using Avalonia.Media.Imaging; using System; using System.Collections.Generic; using System.Linq; @@ -12,8 +13,8 @@ public class ModModel public string? ModAuthor { get; set; } public string? ModDescription { get; set; } public string? ModFilesList { get; set; } - public string? ModIconSource { get; set; } - public string? ModYamlPath { get; set; } + public Bitmap? ModIcon { get; set; } + public string? ModPath { get; set; } public bool? ModActive { get; set; } public Uri? ModSource { get; set; } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 688326f71..eedc9ece4 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -8,7 +8,7 @@ mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="850" Width="750" Height="850" MinWidth="750" MinHeight="850" - x:Class="OpenKh.Tools.ModManager.Views.MainWindow" + x:Class="OpenKh.Tools.ModManager.Views.MainView" x:DataType="vm:MainViewModel" Icon="/Assets/avalonia-logo.ico" Title="OpenKh.Tools.ModManager"> @@ -30,7 +30,7 @@ - + @@ -57,7 +57,7 @@ - + @@ -81,11 +81,11 @@ - + - + diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml index 5c3a181d7..f555ed896 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -8,12 +8,14 @@ xmlns:view="clr-namespace:OpenKh.Tools.ModManager.Views" x:Class="OpenKh.Tools.ModManager.Views.ModListView"> - + - + + + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 492dba060..714d5268d 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -22,8 +22,10 @@ private void OnViewLoaded(object? sender, RoutedEventArgs e) // Yea so Avalonian lists do not automatically highlight shit if they ain't empty so we gotta do it here. // If this ever breaks that means something is wrong. - if (ModList.MainList.Items.Count > 0) - (DataContext as MainViewModel).CurrentMod = ModList.MainList.Items[0] as ModModel; + var _fetchContext = DataContext as MainViewModel; + + if (_fetchContext.InstalledMods != null && _fetchContext.InstalledMods.Count > 0) + _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; } protected override void OnClosing(WindowClosingEventArgs e) @@ -35,6 +37,51 @@ protected override void OnClosing(WindowClosingEventArgs e) // Overwrite the config file. File.WriteAllText("config.yml", _fetchSerialize); - base.OnClosing(e); + base.OnClosing(e); + } + + private void OnTargetGameChanged(object? sender, SelectionChangedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + + _fetchContext.InitializeView(); + + if (_fetchContext.InstalledMods.Count > 0) + _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; + } + + private void OnUtmostClicked(object? sender, RoutedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchModList = _fetchContext.InstalledMods; + + _fetchModList.Move(ModList.MainList.SelectedIndex, 0); + _fetchContext.CurrentMod = _fetchModList[0]; + } + + private void OnUpPriorityClicked(object? sender, RoutedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchModList = _fetchContext.InstalledMods; + var _fetchCurrentIndex = ModList.MainList.SelectedIndex; + + if (_fetchCurrentIndex == 0) + return; + + _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex - 1); + _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex - 1]; + } + + private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchModList = _fetchContext.InstalledMods; + var _fetchCurrentIndex = ModList.MainList.SelectedIndex; + + if (_fetchCurrentIndex == ModList.MainList.ItemCount - 1) + return; + + _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex + 1); + _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; } } From 0673f8eda3c4b0257ece149915197f45e32efefa Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 12:49:48 +0200 Subject: [PATCH 010/175] [Mod Manager] - Gracefully handle mod invalidity. --- .../Assets/invalid_mod.png | Bin 0 -> 79167 bytes OpenKh.Tools.ModManager/Models/ModModel.cs | 6 +- .../ViewModels/MainViewModel.cs | 52 ++++++++++++++---- .../Views/ModListView.axaml | 34 ++++++++---- 4 files changed, 68 insertions(+), 24 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Assets/invalid_mod.png diff --git a/OpenKh.Tools.ModManager/Assets/invalid_mod.png b/OpenKh.Tools.ModManager/Assets/invalid_mod.png new file mode 100644 index 0000000000000000000000000000000000000000..06f4fbd0fda1e3efc54397a94db3e3124041a5bb GIT binary patch literal 79167 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%0DB4823t7)aA-{$CAi5`z_S{}UkgIA6 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 Ef)*Vma{vGU literal 0 HcmV?d00001 diff --git a/OpenKh.Tools.ModManager/Models/ModModel.cs b/OpenKh.Tools.ModManager/Models/ModModel.cs index 32444b967..d43e15c9f 100644 --- a/OpenKh.Tools.ModManager/Models/ModModel.cs +++ b/OpenKh.Tools.ModManager/Models/ModModel.cs @@ -12,11 +12,11 @@ public class ModModel public string? ModTitle { get; set; } public string? ModAuthor { get; set; } public string? ModDescription { get; set; } - public string? ModFilesList { get; set; } + public string[]? ModFilesList { get; set; } public Bitmap? ModIcon { get; set; } public string? ModPath { get; set; } - public bool? ModActive { get; set; } - + public bool ModActive { get; set; } + public bool ModValid { get; set; } public Uri? ModSource { get; set; } public Uri? ModIssues { get; set; } diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 091edc903..bdeb801c1 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -1,6 +1,9 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Media.Imaging; +using Avalonia.Platform; using CommunityToolkit.Mvvm.ComponentModel; +using OpenKh.Patcher; using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.Views; @@ -8,10 +11,9 @@ using SharpYaml.Serialization; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; -using OpenKh.Patcher; using System.Linq; -using Avalonia.Media.Imaging; namespace OpenKh.Tools.ModManager.ViewModels; @@ -21,7 +23,7 @@ public partial class MainViewModel : ViewModelBase private ModModel? _currentMod = null; [ObservableProperty] - private List? _installedMods = null; + private ObservableCollection? _installedMods = null; [ObservableProperty] private bool _configurationValid = true; @@ -29,8 +31,12 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private Configuration? _currentConfig = null; - public MainViewModel() + public void InitializeView() { + CurrentMod = null; + InstalledMods = null; + ConfigurationValid = true; + // === Configuration Parsing and Verification === // // If the config is null, parse it. @@ -59,7 +65,7 @@ public MainViewModel() ConfigurationValid = false; } - InstalledMods = new List(); + var _fetchMods = new ObservableCollection(); var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", CurrentConfig.TargetGame.ToString().ToLower()); foreach (var _fetchDirectory in Directory.EnumerateDirectories(_fetchModsPath)) @@ -70,22 +76,48 @@ public MainViewModel() if (!File.Exists(_fetchPathYaml)) continue; - using (var _fileStream = new FileStream(_fetchPathYaml, FileMode.Open)) + var _metadata = Metadata.Read(_fetchPathYaml); + + if (_metadata.IsValid) { - var _metadata = Metadata.Read(_fileStream); var _modModel = new ModModel { ModTitle = _metadata.Title, ModAuthor = _metadata.OriginalAuthor, ModDescription = _metadata.Description, ModPath = _fetchDirectory, - ModFilesList = string.Join("\n", _metadata.Assets.Select(x => x.Name)), + ModFilesList = _metadata.Assets.Select(x => x.Name).ToArray(), ModIcon = new Bitmap(_fetchPathIcon), - ModActive = true + ModActive = true, + ModValid = true + }; + + _fetchMods.Add(_modModel); + } + + else + { + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = _metadata.Title, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchDirectory, + ModValid = false }; - InstalledMods.Add(_modModel); + _fetchMods.Add(_modModel); } } + + InstalledMods = _fetchMods; + } + + public MainViewModel() + { + InitializeView(); } } diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml index f555ed896..79cd54b2f 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -6,21 +6,33 @@ xmlns:model="using:OpenKh.Tools.ModManager.Models" xmlns:vm="using:OpenKh.Tools.ModManager.ViewModels" xmlns:view="clr-namespace:OpenKh.Tools.ModManager.Views" + xmlns:convert="using:OpenKh.Tools.ModManager.Converters" + xmlns:service="using:OpenKh.Tools.ModManager.Services" x:Class="OpenKh.Tools.ModManager.Views.ModListView"> - - + + M18 13C18.1678 13 18.3344 13.0083 18.498 13.0244C18.7956 13.0538 19.0252 13.2867 19.0878 13.5791L19.3544 14.8223C19.4237 15.146 19.7582 15.339 20.0732 15.2373L21.2822 14.8457C21.5662 14.754 21.882 14.837 22.0566 15.0791C22.2496 15.3471 22.417 15.6353 22.5546 15.9395C22.6778 16.2119 22.591 16.5271 22.3691 16.7275L21.4257 17.5801C21.1801 17.802 21.1801 18.1883 21.4257 18.4102L22.373 19.2646C22.5948 19.4648 22.6812 19.7804 22.5585 20.0527C22.4217 20.3565 22.2555 20.6443 22.0634 20.9121C21.889 21.1551 21.5717 21.2386 21.2871 21.1465L20.0732 20.7529C19.7582 20.6512 19.4237 20.8442 19.3544 21.168L19.0859 22.4199C19.0233 22.7124 18.7937 22.9457 18.496 22.9756C18.3331 22.9919 18.1672 23 18 23C17.8341 23 17.6695 22.9915 17.5078 22.9756C17.2099 22.946 16.9786 22.7136 16.916 22.4209L16.6494 21.168C16.5801 20.8442 16.2457 20.6511 15.9306 20.7529L14.7119 21.1465C14.4274 21.2382 14.1107 21.155 13.9365 20.9121C13.745 20.6451 13.5789 20.3584 13.4423 20.0557C13.3196 19.7834 13.4062 19.4678 13.6279 19.2676L14.5781 18.4102C14.8238 18.1883 14.8238 17.802 14.5781 17.5801L13.6308 16.7246C13.4088 16.5241 13.3218 16.208 13.4453 15.9355C13.5828 15.6321 13.7505 15.3445 13.9433 15.0771C14.1179 14.8353 14.4339 14.753 14.7177 14.8447L15.9306 15.2373C16.2457 15.3392 16.5801 15.1461 16.6494 14.8223L16.914 13.5791C16.9765 13.2865 17.2072 13.0541 17.5048 13.0244C17.6674 13.0084 17.8333 13 18 13ZM18 16.5C17.1719 16.5004 16.5 17.1718 16.5 18C16.5 18.8282 17.1719 19.4996 18 19.5C18.8284 19.5 19.5 18.8284 19.5 18C19.5 17.1716 18.8284 16.5 18 16.5ZM9.00776 1.49805C9.34737 1.49806 9.68098 1.52151 10.0068 1.56543L10.33 1.61621L10.4394 1.64453C10.6888 1.72927 10.8786 1.94064 10.9326 2.2041L11.2978 3.98633L11.3173 4.06055C11.4334 4.42205 11.8259 4.62709 12.1953 4.50391L13.9228 3.92773L14.0322 3.89941C14.2913 3.8546 14.5589 3.94943 14.7324 4.1543C15.3037 4.82893 15.7593 5.60646 16.0654 6.45605C16.1691 6.74458 16.0867 7.06796 15.8574 7.27148L14.4921 8.48242C14.1819 8.75784 14.1809 9.24216 14.4912 9.51758L15.8554 10.7266C16.0851 10.9302 16.1676 11.2542 16.0634 11.543C15.7567 12.3932 15.3011 13.1705 14.7294 13.8447C14.5313 14.0783 14.2105 14.168 13.9199 14.0713L12.1953 13.4961C11.8259 13.3729 11.4334 13.5779 11.3173 13.9395L11.2978 14.0137L10.9335 15.791C10.8719 16.0918 10.6332 16.3248 10.331 16.3789C9.90421 16.4549 9.46182 16.498 9.00776 16.498C8.54901 16.498 8.10153 16.455 7.6689 16.377C7.36709 16.3225 7.12887 16.0895 7.06733 15.7891L6.70503 14.0137C6.62183 13.6069 6.20142 13.3649 5.80757 13.4961L4.09175 14.0674C3.80095 14.1642 3.48023 14.0737 3.28218 13.8398C2.7102 13.1641 2.25332 12.3856 1.94722 11.5332C1.84354 11.2444 1.92649 10.9212 2.1562 10.7178L3.51167 9.51758C3.8217 9.24218 3.8217 8.75782 3.51167 8.48242L2.15522 7.28027C1.92584 7.07698 1.84288 6.75435 1.94624 6.46582C2.25177 5.61404 2.70754 4.83434 3.27925 4.1582L3.35835 4.07715C3.55642 3.90439 3.83422 3.8458 4.08882 3.93066L5.80757 4.50391C6.20142 4.63511 6.62183 4.39309 6.70503 3.98633L7.06831 2.20605L7.09858 2.09668C7.18953 1.85006 7.4057 1.66578 7.66987 1.61816C8.10313 1.5401 8.55035 1.49805 9.00776 1.49805ZM8.99995 6.75C7.75731 6.75 6.74995 7.75736 6.74995 9C6.74995 10.2426 7.75731 11.25 8.99995 11.25C10.2425 11.2499 11.25 10.2426 11.25 9C11.25 7.75739 10.2425 6.75005 8.99995 6.75Z + + + + + + + - - - - - - - + From 1699581104fcf177309f98c43bf7bb875320a25d Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 12:57:55 +0200 Subject: [PATCH 011/175] [Mod Manager] - Commenting. --- .../ViewModels/MainViewModel.cs | 14 ++++++++++++++ .../Views/MainView.axaml.cs | 18 +++++++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index bdeb801c1..7f985b377 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -65,19 +65,30 @@ public void InitializeView() ConfigurationValid = false; } + // === Mod Parsing and Verification === // + + // Construct the mod folder path for the specified game. + var _fetchMods = new ObservableCollection(); var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", CurrentConfig.TargetGame.ToString().ToLower()); + // For each directory that exists in the mod folder: + foreach (var _fetchDirectory in Directory.EnumerateDirectories(_fetchModsPath)) { + // Construct the paths to YAML and PNG files. var _fetchPathYaml = Path.Combine(_fetchDirectory, "mod.yml"); var _fetchPathIcon = Path.Combine(_fetchDirectory, "icon.png"); + // YAML don't do it? Don't do it! if (!File.Exists(_fetchPathYaml)) continue; + // Fetch and read the YAML to parse the metadata. var _metadata = Metadata.Read(_fetchPathYaml); + // If the metadata is valid, parse the mod and push it to the ViewModel. + if (_metadata.IsValid) { var _modModel = new ModModel @@ -95,6 +106,8 @@ public void InitializeView() _fetchMods.Add(_modModel); } + // Otherwise, make it known that the mod sucks ASS and is no good for us, but still push it to the ViewModel so we know about it :D + else { var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); @@ -113,6 +126,7 @@ public void InitializeView() } } + // Sync the list. InstalledMods = _fetchMods; } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 714d5268d..1dc76d836 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -19,7 +19,7 @@ public MainView() private void OnViewLoaded(object? sender, RoutedEventArgs e) { - // Yea so Avalonian lists do not automatically highlight shit if they ain't empty so we gotta do it here. + // Yea so Avalonia lists do not automatically highlight shit if they ain't empty so we gotta do it here. // If this ever breaks that means something is wrong. var _fetchContext = DataContext as MainViewModel; @@ -37,43 +37,59 @@ protected override void OnClosing(WindowClosingEventArgs e) // Overwrite the config file. File.WriteAllText("config.yml", _fetchSerialize); + // We gotta still CLOSE the thing, no? base.OnClosing(e); } + // This executes if the combobox that holds the games changes its value. private void OnTargetGameChanged(object? sender, SelectionChangedEventArgs e) { + // Fetch the context. var _fetchContext = DataContext as MainViewModel; + // Re-Initialize the view. _fetchContext.InitializeView(); + // If there is at least one mod, select the first mod. if (_fetchContext.InstalledMods.Count > 0) _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; } + // This executes if the utmost button is clicked. private void OnUtmostClicked(object? sender, RoutedEventArgs e) { + // Fetch the context and the mod list. var _fetchContext = DataContext as MainViewModel; var _fetchModList = _fetchContext.InstalledMods; + // Make the move, select the mod. _fetchModList.Move(ModList.MainList.SelectedIndex, 0); _fetchContext.CurrentMod = _fetchModList[0]; } + // This executes if the move up button is clicked. private void OnUpPriorityClicked(object? sender, RoutedEventArgs e) { + // Fetch the context, the mod list, and the current index. var _fetchContext = DataContext as MainViewModel; var _fetchModList = _fetchContext.InstalledMods; var _fetchCurrentIndex = ModList.MainList.SelectedIndex; + // If the mod is already super-hot-fire number one, we don't need to do nothin'. if (_fetchCurrentIndex == 0) return; + // Make the move, select the mod. _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex - 1); _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex - 1]; } + // This executes if the move up button is clicked. + // ...why am I even commenting this, ain't it obvious?! private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) { + // Same shit as OnUpPriorityClicked, but reversed. + var _fetchContext = DataContext as MainViewModel; var _fetchModList = _fetchContext.InstalledMods; var _fetchCurrentIndex = ModList.MainList.SelectedIndex; From c6d431f10d062568e666a84b0d19edfe4709ea7e Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 14:59:07 +0200 Subject: [PATCH 012/175] [Mod Manager] - Remedy collision with LibGit2Sharp --- .../Services/{Configuration.cs => Config.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename OpenKh.Tools.ModManager/Services/{Configuration.cs => Config.cs} (98%) diff --git a/OpenKh.Tools.ModManager/Services/Configuration.cs b/OpenKh.Tools.ModManager/Services/Config.cs similarity index 98% rename from OpenKh.Tools.ModManager/Services/Configuration.cs rename to OpenKh.Tools.ModManager/Services/Config.cs index 73a94a6cc..888ef6277 100644 --- a/OpenKh.Tools.ModManager/Services/Configuration.cs +++ b/OpenKh.Tools.ModManager/Services/Config.cs @@ -31,7 +31,7 @@ public enum Game DREAM_DROP_DISTANCE }; - public class Configuration + public class Config { public static Dictionary GameExecutable = new Dictionary() { From 7188ca42eaa943608e9e7ac6b8529dd066104b0c Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 14:59:53 +0200 Subject: [PATCH 013/175] [Mod Manager] - Installing mods from Git hosts. --- .../Dialogs/InstallModDialog.axaml | 18 +++ .../Dialogs/InstallModDialog.axaml.cs | 38 ++++++ OpenKh.Tools.ModManager/Models/ModModel.cs | 2 + .../OpenKh.Tools.ModManager.csproj | 1 + OpenKh.Tools.ModManager/Views/MainView.axaml | 4 +- .../Views/MainView.axaml.cs | 128 +++++++++++++++++- 6 files changed, 188 insertions(+), 3 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml create mode 100644 OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml.cs diff --git a/OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml b/OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml new file mode 100644 index 000000000..fa70782b5 --- /dev/null +++ b/OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml @@ -0,0 +1,18 @@ + + + + + + - diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 1dc76d836..67adad65f 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -1,11 +1,17 @@ using Avalonia.Controls; using Avalonia.Interactivity; +using LibGit2Sharp; +using LibGit2Sharp.Handlers; +using OpenKh.Tools.ModManager.Dialogs; using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; using SharpYaml; using System; using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; namespace OpenKh.Tools.ModManager.Views; @@ -32,7 +38,7 @@ protected override void OnClosing(WindowClosingEventArgs e) { // Fetch the main ViewModel and serialize the config it has. var _fetchViewModel = DataContext as MainViewModel; - var _fetchSerialize = YamlSerializer.Serialize(_fetchViewModel.CurrentConfig); + var _fetchSerialize = YamlSerializer.Serialize(_fetchViewModel.CurrentConfig); // Overwrite the config file. File.WriteAllText("config.yml", _fetchSerialize); @@ -100,4 +106,124 @@ private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex + 1); _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; } + + private async void OnRemoveClicked(object? sender, RoutedEventArgs e) + { + var _removeDialog = new RemoveModDialog(); + bool? _fetchResult = await _removeDialog.ShowDialog(this); + + if (_fetchResult == true) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchModList = _fetchContext.InstalledMods; + var _fetchCurrentMod = _fetchContext.CurrentMod; + + var _fetchModPath = _fetchCurrentMod.ModPath; + + _fetchModList.Remove(_fetchCurrentMod); + + if (_fetchModList.Count > 0) + _fetchCurrentMod = _fetchModList[0]; + + else + _fetchCurrentMod = null; + + if (Directory.Exists(_fetchModPath)) + Directory.Delete(_fetchModPath, true); + } + } + + private async void OnInstallClicked(object? sender, RoutedEventArgs e) + { + var _installDialog = new InstallModDialog(); + string? _fetchResult = await _installDialog.ShowDialog(this); + + var _fetchContext = DataContext as MainViewModel; + var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", _fetchContext.CurrentConfig.TargetGame.ToString().ToLower()); + + if (_fetchResult != null && !string.IsNullOrEmpty(_fetchResult)) + { + if (File.Exists(_fetchResult)) + { + + } + + else + { + var _fetchPlatform = _fetchResult.Contains('@') ? _fetchResult.Split('@').Last() : null; + _fetchResult = _fetchPlatform != null ? _fetchResult.Replace("@" + _fetchPlatform, "") : _fetchResult; + + var _fetchBranch = _fetchResult.Contains(':') ? _fetchResult.Split(':').Last() : null; + _fetchResult = _fetchBranch != null ? _fetchResult.Replace(":" + _fetchBranch, "") : _fetchResult; + + var _fetchAuthor = _fetchResult.Split('/').First(); + var _fetchName = _fetchResult.Split('/').Last(); + + var _fetchCurrentModDir = Path.Combine(_fetchModsPath, _fetchName); + var _fetchCurrentGitPath = Path.Combine(_fetchCurrentModDir, ".git"); + + var _fetchBaseUri = new Uri("https://" + (_fetchPlatform != null ? _fetchPlatform : "github.com")); + var _fetchRelativeUri = new Uri(_fetchBaseUri, $"{_fetchResult}"); + + var _cloneOptions = new CloneOptions + { + Checkout = true, + BranchName = _fetchBranch, + }; + + _cloneOptions.FetchOptions.Depth = 1; + _cloneOptions.FetchOptions.Prune = true; + + if (!Directory.Exists(_fetchCurrentModDir)) + Directory.CreateDirectory(_fetchCurrentModDir); + + var _fetchRemotes = Repository.ListRemoteReferences(_fetchRelativeUri.ToString()); + + if (_fetchPlatform == null) + { + using var _makeClient = new HttpClient(); + using var _fetchResponse = await _makeClient.GetAsync($"https://raw.githubusercontent.com/{_fetchResult}/" + (_fetchBranch != null ? _fetchBranch : _fetchRemotes.First().TargetIdentifier) + "/mod.yml", HttpCompletionOption.ResponseHeadersRead); + + if (_fetchResponse.StatusCode != HttpStatusCode.OK) + return; // TODO: Error box -- Mod Invalid. + + Repository.Clone(_fetchRelativeUri.ToString(), _fetchCurrentModDir, _cloneOptions); + + var _fetchGitDir = new DirectoryInfo(_fetchCurrentGitPath); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + + else + { + Repository.Clone(_fetchRelativeUri.ToString(), _fetchCurrentModDir, _cloneOptions); + + var _fetchGit = new Repository(_fetchCurrentModDir); + + var _fetchCommit = _fetchGit.Head.Tip; + var _doesModFileExist = _fetchCommit["mod.yml"] != null; + + _fetchGit.Dispose(); + + var _fetchGitDir = new DirectoryInfo(_fetchCurrentGitPath); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + + if (!_doesModFileExist) + { + Directory.Delete(_fetchCurrentModDir, true); + return; // TODO: Error box -- Mod Invalid. + } + } + } + + _fetchContext.InitializeView(); + + // If there is at least one mod, select the first mod. + if (_fetchContext.InstalledMods.Count > 0) + _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; + } + } } From cfae5901ac6e625d9bad40f351520c6cc43f5ea6 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 15:00:03 +0200 Subject: [PATCH 014/175] [Mod Manager] - More detailing on the UI. --- OpenKh.Tools.ModManager/Views/ModDetailsView.axaml | 4 ++-- OpenKh.Tools.ModManager/Views/ModListView.axaml | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml index 15329cecc..057bdaabe 100644 --- a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml @@ -14,8 +14,8 @@ - - + + diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml index 79cd54b2f..bc6e2c8ac 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -29,7 +29,8 @@ From 461e5261bbbfd9e2ac3abfb3d05897bbf7c29e4f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 29 Aug 2026 15:00:15 +0200 Subject: [PATCH 015/175] [Mod Manager] - Removing mods. --- .../Dialogs/RemoveModDialog.axaml | 17 +++++++++++++++++ .../Dialogs/RemoveModDialog.axaml.cs | 19 +++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml create mode 100644 OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml.cs diff --git a/OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml b/OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml new file mode 100644 index 000000000..4e138892e --- /dev/null +++ b/OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml @@ -0,0 +1,17 @@ + + + + + - - - - - diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index bb384a4f8..109071aa8 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -14,6 +14,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Threading.Tasks; using static OpenKh.Kh2.Ard.AreaDataScript; namespace OpenKh.Tools.ModManager.Views; @@ -33,6 +34,9 @@ private void OnViewLoaded(object? sender, RoutedEventArgs e) var _fetchContext = DataContext as MainViewModel; + if (_fetchContext == null) + return; + if (_fetchContext.InstalledMods != null && _fetchContext.InstalledMods.Count > 0) _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; } @@ -110,29 +114,44 @@ private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; } + // This executes when we click the remove mod button. private async void OnRemoveClicked(object? sender, RoutedEventArgs e) { + // Ask the user if they *really* want to remove said mod. + var _removeDialog = new RemoveModDialog(); bool? _fetchResult = await _removeDialog.ShowDialog(this); + // If they indeed do: + if (_fetchResult == true) { + // Fetch the context, the mod list, and the current mod. var _fetchContext = DataContext as MainViewModel; var _fetchModList = _fetchContext.InstalledMods; var _fetchCurrentMod = _fetchContext.CurrentMod; + // Fetch the path too we kinda need it. var _fetchModPath = _fetchCurrentMod.ModPath; + // Remove the mod from the list. _fetchModList.Remove(_fetchCurrentMod); + // If we still have mods, select one. if (_fetchModList.Count > 0) - _fetchCurrentMod = _fetchModList[0]; + _fetchCurrentMod = _fetchModList.First(); + // If we do not, nullify selection. else _fetchCurrentMod = null; - if (Directory.Exists(_fetchModPath)) - Directory.Delete(_fetchModPath, true); + // Remove the mod directory. + // This is a task for a reason. This isn't being awaited for a reason. Do not listen to IntelliSense on this one. + Task.Run(() => + { + if (Directory.Exists(_fetchModPath)) + Directory.Delete(_fetchModPath, true); + }); } } @@ -158,7 +177,7 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) _progressDialog.InstallProgress.Value = progress.ReceivedObjects; }); - if (GitService.CancelToken.IsCancellationRequested) + if (InstallService.CancelToken.IsCancellationRequested) return false; return true; @@ -172,17 +191,17 @@ bool _localProcessHandler(int processed, int total) _progressDialog.InstallProgress.Value = processed; }); - if (GitService.CancelToken.IsCancellationRequested) + if (InstallService.CancelToken.IsCancellationRequested) return false; return true; }; if (File.Exists(_fetchResult)) - _fetchInstallResult = await GitService.InstallLocal(_fetchModsPath, _fetchResult, _localProcessHandler); + _fetchInstallResult = await InstallService.InstallLocal(_fetchModsPath, _fetchResult, _localProcessHandler); else - _fetchInstallResult = await GitService.InstallGit(_fetchModsPath, _fetchResult, _gitProcessHandler); + _fetchInstallResult = await InstallService.InstallGit(_fetchModsPath, _fetchResult, _gitProcessHandler); _progressDialog.Close(true); From 9acdf52cfa7264fdbe9a0d145089347d5f39e65a Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 03:49:09 +0200 Subject: [PATCH 023/175] [Mod Manager] - Duplicate all BDX stuffs to OpenKh.Kh2 and remove BdxIo linkage from OpenKh.Patcher to allow Linux deployment. --- OpenKh.Kh2/Bdx/Antlr/BdxScript.g4 | 49 + OpenKh.Kh2/Bdx/Antlr/BdxScriptLexer.cs | 177 +++ OpenKh.Kh2/Bdx/Antlr/BdxScriptParser.cs | 1240 +++++++++++++++++ OpenKh.Kh2/Bdx/Antlr/README.md | 40 + OpenKh.Kh2/Bdx/Models/BdxHeader.cs | 35 + OpenKh.Kh2/Bdx/Models/BdxInstruction.cs | 32 + OpenKh.Kh2/Bdx/Models/BdxInstructionDesc.cs | 43 + OpenKh.Kh2/Bdx/Models/BdxInstructionDescs.cs | 97 ++ OpenKh.Kh2/Bdx/Models/BdxTrap.cs | 10 + OpenKh.Kh2/Bdx/Models/BdxTraps.cs | 845 +++++++++++ OpenKh.Kh2/Bdx/Utils/BdxAsciiModel.cs | 45 + OpenKh.Kh2/Bdx/Utils/BdxDecoder.cs | 808 +++++++++++ OpenKh.Kh2/Bdx/Utils/BdxEncoder.cs | 867 ++++++++++++ OpenKh.Kh2/Bdx/Utils/BdxTrigger.cs | 37 + OpenKh.Kh2/OpenKh.Kh2.csproj | 6 +- .../ProvideExtractorsService.cs | 2 +- OpenKh.Patcher/OpenKh.Patcher.csproj | 1 - OpenKh.Patcher/PatcherProcessor.cs | 5 +- OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs | 1 + OpenKh.Tests.Commands/BdxMassiveTests.cs | 1 + OpenKh.Tests.Commands/BdxRegressionTests.cs | 1 + OpenKh.Tests/Patcher/PatcherTests.cs | 2 +- .../Modules/AI/ModuleAI_VM.cs | 1 + 23 files changed, 4338 insertions(+), 7 deletions(-) create mode 100644 OpenKh.Kh2/Bdx/Antlr/BdxScript.g4 create mode 100644 OpenKh.Kh2/Bdx/Antlr/BdxScriptLexer.cs create mode 100644 OpenKh.Kh2/Bdx/Antlr/BdxScriptParser.cs create mode 100644 OpenKh.Kh2/Bdx/Antlr/README.md create mode 100644 OpenKh.Kh2/Bdx/Models/BdxHeader.cs create mode 100644 OpenKh.Kh2/Bdx/Models/BdxInstruction.cs create mode 100644 OpenKh.Kh2/Bdx/Models/BdxInstructionDesc.cs create mode 100644 OpenKh.Kh2/Bdx/Models/BdxInstructionDescs.cs create mode 100644 OpenKh.Kh2/Bdx/Models/BdxTrap.cs create mode 100644 OpenKh.Kh2/Bdx/Models/BdxTraps.cs create mode 100644 OpenKh.Kh2/Bdx/Utils/BdxAsciiModel.cs create mode 100644 OpenKh.Kh2/Bdx/Utils/BdxDecoder.cs create mode 100644 OpenKh.Kh2/Bdx/Utils/BdxEncoder.cs create mode 100644 OpenKh.Kh2/Bdx/Utils/BdxTrigger.cs diff --git a/OpenKh.Kh2/Bdx/Antlr/BdxScript.g4 b/OpenKh.Kh2/Bdx/Antlr/BdxScript.g4 new file mode 100644 index 000000000..8241372bd --- /dev/null +++ b/OpenKh.Kh2/Bdx/Antlr/BdxScript.g4 @@ -0,0 +1,49 @@ +grammar BdxScript; + +prog: NL* (statement NL*)* EOF; +statement: + section + | equ + | label ':' NL + | label ':' order + | order; + +label: name = id; + +equ: name = id 'equ' numberdata; + +order: db | resb | dw | resw | instruction | include; + +dw: 'dw' worddata (',' worddata)* NL; +db: 'db' bytedata (',' bytedata)* NL; +resb: 'resb' (numberdata | id) NL; +resw: 'resw' (numberdata | id) NL; + +instruction: id (arg (',' arg)*)? NL; + +arg: (numberdata | id); +offset: 'offset' id; +bytedata: numberdata | id | StringData; +worddata: numberdata | id; +numberdata: + decimalnumber = DEC + | '0x' hexnumber = (DEC | HEX) + | floatnumber = FLOATNUM; +hexbody: DEC | HEX; +id: DEC | HEX | ID; +include: '%include' FileName NL; +section: 'section' section_id NL; +section_id: '.text' | '.bss'; + +StringData: '\'' ~[']* '\''; +DEC: [-+]? [0-9]+; +HEX: [0-9a-fA-F]+; +FLOATNUM: + [-+]? ([0-9]* '.' [0-9]+) + | ([0-9]+ [eE] [-+]? [0-9]+); +ID: [a-zA-Z_][a-zA-Z0-9_]* ('.' [a-zA-Z0-9_]+)*; +WS: [ \t] -> skip; +NL: '\r'? '\n' | '\r'; +FileName: '"' ~["]* '"'; + +LINE_COMMENT: ';' ~[\r\n]* -> skip; diff --git a/OpenKh.Kh2/Bdx/Antlr/BdxScriptLexer.cs b/OpenKh.Kh2/Bdx/Antlr/BdxScriptLexer.cs new file mode 100644 index 000000000..f242237c9 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Antlr/BdxScriptLexer.cs @@ -0,0 +1,177 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// ANTLR Version: 4.13.2 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// Generated from BdxScript.g4 by ANTLR 4.13.2 + +// Unreachable code detected +#pragma warning disable 0162 +// The variable '...' is assigned but its value is never used +#pragma warning disable 0219 +// Missing XML comment for publicly visible type or member '...' +#pragma warning disable 1591 +// Ambiguous reference in cref attribute +#pragma warning disable 419 + +using System; +using System.IO; +using System.Text; +using Antlr4.Runtime; +using Antlr4.Runtime.Atn; +using Antlr4.Runtime.Misc; +using DFA = Antlr4.Runtime.Dfa.DFA; + +[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")] +[System.CLSCompliant(false)] +public partial class BdxScriptLexer : Lexer { + protected static DFA[] decisionToDFA; + protected static PredictionContextCache sharedContextCache = new PredictionContextCache(); + public const int + T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, + T__9=10, T__10=11, T__11=12, T__12=13, StringData=14, DEC=15, HEX=16, + FLOATNUM=17, ID=18, WS=19, NL=20, FileName=21, LINE_COMMENT=22; + public static string[] channelNames = { + "DEFAULT_TOKEN_CHANNEL", "HIDDEN" + }; + + public static string[] modeNames = { + "DEFAULT_MODE" + }; + + public static readonly string[] ruleNames = { + "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", + "T__9", "T__10", "T__11", "T__12", "StringData", "DEC", "HEX", "FLOATNUM", + "ID", "WS", "NL", "FileName", "LINE_COMMENT" + }; + + + public BdxScriptLexer(ICharStream input) + : this(input, Console.Out, Console.Error) { } + + public BdxScriptLexer(ICharStream input, TextWriter output, TextWriter errorOutput) + : base(input, output, errorOutput) + { + Interpreter = new LexerATNSimulator(this, _ATN, decisionToDFA, sharedContextCache); + } + + private static readonly string[] _LiteralNames = { + null, "':'", "'equ'", "'dw'", "','", "'db'", "'resb'", "'resw'", "'offset'", + "'0x'", "'%include'", "'section'", "'.text'", "'.bss'" + }; + private static readonly string[] _SymbolicNames = { + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, "StringData", "DEC", "HEX", "FLOATNUM", "ID", "WS", "NL", + "FileName", "LINE_COMMENT" + }; + public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames); + + [NotNull] + public override IVocabulary Vocabulary + { + get + { + return DefaultVocabulary; + } + } + + public override string GrammarFileName { get { return "BdxScript.g4"; } } + + public override string[] RuleNames { get { return ruleNames; } } + + public override string[] ChannelNames { get { return channelNames; } } + + public override string[] ModeNames { get { return modeNames; } } + + public override int[] SerializedAtn { get { return _serializedATN; } } + + static BdxScriptLexer() { + decisionToDFA = new DFA[_ATN.NumberOfDecisions]; + for (int i = 0; i < _ATN.NumberOfDecisions; i++) { + decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i); + } + } + private static int[] _serializedATN = { + 4,0,22,207,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, + 6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14, + 7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21, + 7,21,1,0,1,0,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1,4,1,5,1,5,1, + 5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8, + 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, + 1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,13,1,13, + 5,13,110,8,13,10,13,12,13,113,9,13,1,13,1,13,1,14,3,14,118,8,14,1,14,4, + 14,121,8,14,11,14,12,14,122,1,15,4,15,126,8,15,11,15,12,15,127,1,16,3, + 16,131,8,16,1,16,5,16,134,8,16,10,16,12,16,137,9,16,1,16,1,16,4,16,141, + 8,16,11,16,12,16,142,1,16,4,16,146,8,16,11,16,12,16,147,1,16,1,16,3,16, + 152,8,16,1,16,4,16,155,8,16,11,16,12,16,156,3,16,159,8,16,1,17,1,17,5, + 17,163,8,17,10,17,12,17,166,9,17,1,17,1,17,4,17,170,8,17,11,17,12,17,171, + 5,17,174,8,17,10,17,12,17,177,9,17,1,18,1,18,1,18,1,18,1,19,3,19,184,8, + 19,1,19,1,19,3,19,188,8,19,1,20,1,20,5,20,192,8,20,10,20,12,20,195,9,20, + 1,20,1,20,1,21,1,21,5,21,201,8,21,10,21,12,21,204,9,21,1,21,1,21,0,0,22, + 1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14, + 29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,1,0,10,1,0,39,39,2,0,43, + 43,45,45,1,0,48,57,3,0,48,57,65,70,97,102,2,0,69,69,101,101,3,0,65,90, + 95,95,97,122,4,0,48,57,65,90,95,95,97,122,2,0,9,9,32,32,1,0,34,34,2,0, + 10,10,13,13,224,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1, + 0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0, + 0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31, + 1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0, + 0,0,43,1,0,0,0,1,45,1,0,0,0,3,47,1,0,0,0,5,51,1,0,0,0,7,54,1,0,0,0,9,56, + 1,0,0,0,11,59,1,0,0,0,13,64,1,0,0,0,15,69,1,0,0,0,17,76,1,0,0,0,19,79, + 1,0,0,0,21,88,1,0,0,0,23,96,1,0,0,0,25,102,1,0,0,0,27,107,1,0,0,0,29,117, + 1,0,0,0,31,125,1,0,0,0,33,158,1,0,0,0,35,160,1,0,0,0,37,178,1,0,0,0,39, + 187,1,0,0,0,41,189,1,0,0,0,43,198,1,0,0,0,45,46,5,58,0,0,46,2,1,0,0,0, + 47,48,5,101,0,0,48,49,5,113,0,0,49,50,5,117,0,0,50,4,1,0,0,0,51,52,5,100, + 0,0,52,53,5,119,0,0,53,6,1,0,0,0,54,55,5,44,0,0,55,8,1,0,0,0,56,57,5,100, + 0,0,57,58,5,98,0,0,58,10,1,0,0,0,59,60,5,114,0,0,60,61,5,101,0,0,61,62, + 5,115,0,0,62,63,5,98,0,0,63,12,1,0,0,0,64,65,5,114,0,0,65,66,5,101,0,0, + 66,67,5,115,0,0,67,68,5,119,0,0,68,14,1,0,0,0,69,70,5,111,0,0,70,71,5, + 102,0,0,71,72,5,102,0,0,72,73,5,115,0,0,73,74,5,101,0,0,74,75,5,116,0, + 0,75,16,1,0,0,0,76,77,5,48,0,0,77,78,5,120,0,0,78,18,1,0,0,0,79,80,5,37, + 0,0,80,81,5,105,0,0,81,82,5,110,0,0,82,83,5,99,0,0,83,84,5,108,0,0,84, + 85,5,117,0,0,85,86,5,100,0,0,86,87,5,101,0,0,87,20,1,0,0,0,88,89,5,115, + 0,0,89,90,5,101,0,0,90,91,5,99,0,0,91,92,5,116,0,0,92,93,5,105,0,0,93, + 94,5,111,0,0,94,95,5,110,0,0,95,22,1,0,0,0,96,97,5,46,0,0,97,98,5,116, + 0,0,98,99,5,101,0,0,99,100,5,120,0,0,100,101,5,116,0,0,101,24,1,0,0,0, + 102,103,5,46,0,0,103,104,5,98,0,0,104,105,5,115,0,0,105,106,5,115,0,0, + 106,26,1,0,0,0,107,111,5,39,0,0,108,110,8,0,0,0,109,108,1,0,0,0,110,113, + 1,0,0,0,111,109,1,0,0,0,111,112,1,0,0,0,112,114,1,0,0,0,113,111,1,0,0, + 0,114,115,5,39,0,0,115,28,1,0,0,0,116,118,7,1,0,0,117,116,1,0,0,0,117, + 118,1,0,0,0,118,120,1,0,0,0,119,121,7,2,0,0,120,119,1,0,0,0,121,122,1, + 0,0,0,122,120,1,0,0,0,122,123,1,0,0,0,123,30,1,0,0,0,124,126,7,3,0,0,125, + 124,1,0,0,0,126,127,1,0,0,0,127,125,1,0,0,0,127,128,1,0,0,0,128,32,1,0, + 0,0,129,131,7,1,0,0,130,129,1,0,0,0,130,131,1,0,0,0,131,135,1,0,0,0,132, + 134,7,2,0,0,133,132,1,0,0,0,134,137,1,0,0,0,135,133,1,0,0,0,135,136,1, + 0,0,0,136,138,1,0,0,0,137,135,1,0,0,0,138,140,5,46,0,0,139,141,7,2,0,0, + 140,139,1,0,0,0,141,142,1,0,0,0,142,140,1,0,0,0,142,143,1,0,0,0,143,159, + 1,0,0,0,144,146,7,2,0,0,145,144,1,0,0,0,146,147,1,0,0,0,147,145,1,0,0, + 0,147,148,1,0,0,0,148,149,1,0,0,0,149,151,7,4,0,0,150,152,7,1,0,0,151, + 150,1,0,0,0,151,152,1,0,0,0,152,154,1,0,0,0,153,155,7,2,0,0,154,153,1, + 0,0,0,155,156,1,0,0,0,156,154,1,0,0,0,156,157,1,0,0,0,157,159,1,0,0,0, + 158,130,1,0,0,0,158,145,1,0,0,0,159,34,1,0,0,0,160,164,7,5,0,0,161,163, + 7,6,0,0,162,161,1,0,0,0,163,166,1,0,0,0,164,162,1,0,0,0,164,165,1,0,0, + 0,165,175,1,0,0,0,166,164,1,0,0,0,167,169,5,46,0,0,168,170,7,6,0,0,169, + 168,1,0,0,0,170,171,1,0,0,0,171,169,1,0,0,0,171,172,1,0,0,0,172,174,1, + 0,0,0,173,167,1,0,0,0,174,177,1,0,0,0,175,173,1,0,0,0,175,176,1,0,0,0, + 176,36,1,0,0,0,177,175,1,0,0,0,178,179,7,7,0,0,179,180,1,0,0,0,180,181, + 6,18,0,0,181,38,1,0,0,0,182,184,5,13,0,0,183,182,1,0,0,0,183,184,1,0,0, + 0,184,185,1,0,0,0,185,188,5,10,0,0,186,188,5,13,0,0,187,183,1,0,0,0,187, + 186,1,0,0,0,188,40,1,0,0,0,189,193,5,34,0,0,190,192,8,8,0,0,191,190,1, + 0,0,0,192,195,1,0,0,0,193,191,1,0,0,0,193,194,1,0,0,0,194,196,1,0,0,0, + 195,193,1,0,0,0,196,197,5,34,0,0,197,42,1,0,0,0,198,202,5,59,0,0,199,201, + 8,9,0,0,200,199,1,0,0,0,201,204,1,0,0,0,202,200,1,0,0,0,202,203,1,0,0, + 0,203,205,1,0,0,0,204,202,1,0,0,0,205,206,6,21,0,0,206,44,1,0,0,0,19,0, + 111,117,122,127,130,135,142,147,151,156,158,164,171,175,183,187,193,202, + 1,6,0,0 + }; + + public static readonly ATN _ATN = + new ATNDeserializer().Deserialize(_serializedATN); + + +} diff --git a/OpenKh.Kh2/Bdx/Antlr/BdxScriptParser.cs b/OpenKh.Kh2/Bdx/Antlr/BdxScriptParser.cs new file mode 100644 index 000000000..5f65fa505 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Antlr/BdxScriptParser.cs @@ -0,0 +1,1240 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// ANTLR Version: 4.13.2 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +// Generated from BdxScript.g4 by ANTLR 4.13.2 + +// Unreachable code detected +#pragma warning disable 0162 +// The variable '...' is assigned but its value is never used +#pragma warning disable 0219 +// Missing XML comment for publicly visible type or member '...' +#pragma warning disable 1591 +// Ambiguous reference in cref attribute +#pragma warning disable 419 + +using System; +using System.IO; +using System.Text; +using System.Diagnostics; +using System.Collections.Generic; +using Antlr4.Runtime; +using Antlr4.Runtime.Atn; +using Antlr4.Runtime.Misc; +using Antlr4.Runtime.Tree; +using DFA = Antlr4.Runtime.Dfa.DFA; + +[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.13.2")] +[System.CLSCompliant(false)] +public partial class BdxScriptParser : Parser { + protected static DFA[] decisionToDFA; + protected static PredictionContextCache sharedContextCache = new PredictionContextCache(); + public const int + T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, + T__9=10, T__10=11, T__11=12, T__12=13, StringData=14, DEC=15, HEX=16, + FLOATNUM=17, ID=18, WS=19, NL=20, FileName=21, LINE_COMMENT=22; + public const int + RULE_prog = 0, RULE_statement = 1, RULE_label = 2, RULE_equ = 3, RULE_order = 4, + RULE_dw = 5, RULE_db = 6, RULE_resb = 7, RULE_resw = 8, RULE_instruction = 9, + RULE_arg = 10, RULE_offset = 11, RULE_bytedata = 12, RULE_worddata = 13, + RULE_numberdata = 14, RULE_hexbody = 15, RULE_id = 16, RULE_include = 17, + RULE_section = 18, RULE_section_id = 19; + public static readonly string[] ruleNames = { + "prog", "statement", "label", "equ", "order", "dw", "db", "resb", "resw", + "instruction", "arg", "offset", "bytedata", "worddata", "numberdata", + "hexbody", "id", "include", "section", "section_id" + }; + + private static readonly string[] _LiteralNames = { + null, "':'", "'equ'", "'dw'", "','", "'db'", "'resb'", "'resw'", "'offset'", + "'0x'", "'%include'", "'section'", "'.text'", "'.bss'" + }; + private static readonly string[] _SymbolicNames = { + null, null, null, null, null, null, null, null, null, null, null, null, + null, null, "StringData", "DEC", "HEX", "FLOATNUM", "ID", "WS", "NL", + "FileName", "LINE_COMMENT" + }; + public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames); + + [NotNull] + public override IVocabulary Vocabulary + { + get + { + return DefaultVocabulary; + } + } + + public override string GrammarFileName { get { return "BdxScript.g4"; } } + + public override string[] RuleNames { get { return ruleNames; } } + + public override int[] SerializedAtn { get { return _serializedATN; } } + + static BdxScriptParser() { + decisionToDFA = new DFA[_ATN.NumberOfDecisions]; + for (int i = 0; i < _ATN.NumberOfDecisions; i++) { + decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i); + } + } + + public BdxScriptParser(ITokenStream input) : this(input, Console.Out, Console.Error) { } + + public BdxScriptParser(ITokenStream input, TextWriter output, TextWriter errorOutput) + : base(input, output, errorOutput) + { + Interpreter = new ParserATNSimulator(this, _ATN, decisionToDFA, sharedContextCache); + } + + public partial class ProgContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode Eof() { return GetToken(BdxScriptParser.Eof, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode[] NL() { return GetTokens(BdxScriptParser.NL); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL(int i) { + return GetToken(BdxScriptParser.NL, i); + } + [System.Diagnostics.DebuggerNonUserCode] public StatementContext[] statement() { + return GetRuleContexts(); + } + [System.Diagnostics.DebuggerNonUserCode] public StatementContext statement(int i) { + return GetRuleContext(i); + } + public ProgContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_prog; } } + } + + [RuleVersion(0)] + public ProgContext prog() { + ProgContext _localctx = new ProgContext(Context, State); + EnterRule(_localctx, 0, RULE_prog); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 43; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while (_la==NL) { + { + { + State = 40; + Match(NL); + } + } + State = 45; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + State = 55; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while ((((_la) & ~0x3f) == 0 && ((1L << _la) & 363752L) != 0)) { + { + { + State = 46; + statement(); + State = 50; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while (_la==NL) { + { + { + State = 47; + Match(NL); + } + } + State = 52; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + } + } + State = 57; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + State = 58; + Match(Eof); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class StatementContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public SectionContext section() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public EquContext equ() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public LabelContext label() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + [System.Diagnostics.DebuggerNonUserCode] public OrderContext order() { + return GetRuleContext(0); + } + public StatementContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_statement; } } + } + + [RuleVersion(0)] + public StatementContext statement() { + StatementContext _localctx = new StatementContext(Context, State); + EnterRule(_localctx, 2, RULE_statement); + try { + State = 71; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,3,Context) ) { + case 1: + EnterOuterAlt(_localctx, 1); + { + State = 60; + section(); + } + break; + case 2: + EnterOuterAlt(_localctx, 2); + { + State = 61; + equ(); + } + break; + case 3: + EnterOuterAlt(_localctx, 3); + { + State = 62; + label(); + State = 63; + Match(T__0); + State = 64; + Match(NL); + } + break; + case 4: + EnterOuterAlt(_localctx, 4); + { + State = 66; + label(); + State = 67; + Match(T__0); + State = 68; + order(); + } + break; + case 5: + EnterOuterAlt(_localctx, 5); + { + State = 70; + order(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class LabelContext : ParserRuleContext { + public IdContext name; + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public LabelContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_label; } } + } + + [RuleVersion(0)] + public LabelContext label() { + LabelContext _localctx = new LabelContext(Context, State); + EnterRule(_localctx, 4, RULE_label); + try { + EnterOuterAlt(_localctx, 1); + { + State = 73; + _localctx.name = id(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class EquContext : ParserRuleContext { + public IdContext name; + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public EquContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_equ; } } + } + + [RuleVersion(0)] + public EquContext equ() { + EquContext _localctx = new EquContext(Context, State); + EnterRule(_localctx, 6, RULE_equ); + try { + EnterOuterAlt(_localctx, 1); + { + State = 75; + _localctx.name = id(); + State = 76; + Match(T__1); + State = 77; + numberdata(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class OrderContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public DbContext db() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ResbContext resb() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public DwContext dw() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ReswContext resw() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public InstructionContext instruction() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IncludeContext include() { + return GetRuleContext(0); + } + public OrderContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_order; } } + } + + [RuleVersion(0)] + public OrderContext order() { + OrderContext _localctx = new OrderContext(Context, State); + EnterRule(_localctx, 8, RULE_order); + try { + State = 85; + ErrorHandler.Sync(this); + switch (TokenStream.LA(1)) { + case T__4: + EnterOuterAlt(_localctx, 1); + { + State = 79; + db(); + } + break; + case T__5: + EnterOuterAlt(_localctx, 2); + { + State = 80; + resb(); + } + break; + case T__2: + EnterOuterAlt(_localctx, 3); + { + State = 81; + dw(); + } + break; + case T__6: + EnterOuterAlt(_localctx, 4); + { + State = 82; + resw(); + } + break; + case DEC: + case HEX: + case ID: + EnterOuterAlt(_localctx, 5); + { + State = 83; + instruction(); + } + break; + case T__9: + EnterOuterAlt(_localctx, 6); + { + State = 84; + include(); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class DwContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public WorddataContext[] worddata() { + return GetRuleContexts(); + } + [System.Diagnostics.DebuggerNonUserCode] public WorddataContext worddata(int i) { + return GetRuleContext(i); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + public DwContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_dw; } } + } + + [RuleVersion(0)] + public DwContext dw() { + DwContext _localctx = new DwContext(Context, State); + EnterRule(_localctx, 10, RULE_dw); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 87; + Match(T__2); + State = 88; + worddata(); + State = 93; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while (_la==T__3) { + { + { + State = 89; + Match(T__3); + State = 90; + worddata(); + } + } + State = 95; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + State = 96; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class DbContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public BytedataContext[] bytedata() { + return GetRuleContexts(); + } + [System.Diagnostics.DebuggerNonUserCode] public BytedataContext bytedata(int i) { + return GetRuleContext(i); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + public DbContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_db; } } + } + + [RuleVersion(0)] + public DbContext db() { + DbContext _localctx = new DbContext(Context, State); + EnterRule(_localctx, 12, RULE_db); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 98; + Match(T__4); + State = 99; + bytedata(); + State = 104; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while (_la==T__3) { + { + { + State = 100; + Match(T__3); + State = 101; + bytedata(); + } + } + State = 106; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + State = 107; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class ResbContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public ResbContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_resb; } } + } + + [RuleVersion(0)] + public ResbContext resb() { + ResbContext _localctx = new ResbContext(Context, State); + EnterRule(_localctx, 14, RULE_resb); + try { + EnterOuterAlt(_localctx, 1); + { + State = 109; + Match(T__5); + State = 112; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,7,Context) ) { + case 1: + { + State = 110; + numberdata(); + } + break; + case 2: + { + State = 111; + id(); + } + break; + } + State = 114; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class ReswContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public ReswContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_resw; } } + } + + [RuleVersion(0)] + public ReswContext resw() { + ReswContext _localctx = new ReswContext(Context, State); + EnterRule(_localctx, 16, RULE_resw); + try { + EnterOuterAlt(_localctx, 1); + { + State = 116; + Match(T__6); + State = 119; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,8,Context) ) { + case 1: + { + State = 117; + numberdata(); + } + break; + case 2: + { + State = 118; + id(); + } + break; + } + State = 121; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class InstructionContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ArgContext[] arg() { + return GetRuleContexts(); + } + [System.Diagnostics.DebuggerNonUserCode] public ArgContext arg(int i) { + return GetRuleContext(i); + } + public InstructionContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_instruction; } } + } + + [RuleVersion(0)] + public InstructionContext instruction() { + InstructionContext _localctx = new InstructionContext(Context, State); + EnterRule(_localctx, 18, RULE_instruction); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 123; + id(); + State = 132; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + if ((((_la) & ~0x3f) == 0 && ((1L << _la) & 492032L) != 0)) { + { + State = 124; + arg(); + State = 129; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + while (_la==T__3) { + { + { + State = 125; + Match(T__3); + State = 126; + arg(); + } + } + State = 131; + ErrorHandler.Sync(this); + _la = TokenStream.LA(1); + } + } + } + + State = 134; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class ArgContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public ArgContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_arg; } } + } + + [RuleVersion(0)] + public ArgContext arg() { + ArgContext _localctx = new ArgContext(Context, State); + EnterRule(_localctx, 20, RULE_arg); + try { + EnterOuterAlt(_localctx, 1); + { + State = 138; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,11,Context) ) { + case 1: + { + State = 136; + numberdata(); + } + break; + case 2: + { + State = 137; + id(); + } + break; + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class OffsetContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public OffsetContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_offset; } } + } + + [RuleVersion(0)] + public OffsetContext offset() { + OffsetContext _localctx = new OffsetContext(Context, State); + EnterRule(_localctx, 22, RULE_offset); + try { + EnterOuterAlt(_localctx, 1); + { + State = 140; + Match(T__7); + State = 141; + id(); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class BytedataContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode StringData() { return GetToken(BdxScriptParser.StringData, 0); } + public BytedataContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_bytedata; } } + } + + [RuleVersion(0)] + public BytedataContext bytedata() { + BytedataContext _localctx = new BytedataContext(Context, State); + EnterRule(_localctx, 24, RULE_bytedata); + try { + State = 146; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,12,Context) ) { + case 1: + EnterOuterAlt(_localctx, 1); + { + State = 143; + numberdata(); + } + break; + case 2: + EnterOuterAlt(_localctx, 2); + { + State = 144; + id(); + } + break; + case 3: + EnterOuterAlt(_localctx, 3); + { + State = 145; + Match(StringData); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class WorddataContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { + return GetRuleContext(0); + } + public WorddataContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_worddata; } } + } + + [RuleVersion(0)] + public WorddataContext worddata() { + WorddataContext _localctx = new WorddataContext(Context, State); + EnterRule(_localctx, 26, RULE_worddata); + try { + State = 150; + ErrorHandler.Sync(this); + switch ( Interpreter.AdaptivePredict(TokenStream,13,Context) ) { + case 1: + EnterOuterAlt(_localctx, 1); + { + State = 148; + numberdata(); + } + break; + case 2: + EnterOuterAlt(_localctx, 2); + { + State = 149; + id(); + } + break; + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class NumberdataContext : ParserRuleContext { + public IToken decimalnumber; + public IToken hexnumber; + public IToken floatnumber; + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode FLOATNUM() { return GetToken(BdxScriptParser.FLOATNUM, 0); } + public NumberdataContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_numberdata; } } + } + + [RuleVersion(0)] + public NumberdataContext numberdata() { + NumberdataContext _localctx = new NumberdataContext(Context, State); + EnterRule(_localctx, 28, RULE_numberdata); + int _la; + try { + State = 156; + ErrorHandler.Sync(this); + switch (TokenStream.LA(1)) { + case DEC: + EnterOuterAlt(_localctx, 1); + { + State = 152; + _localctx.decimalnumber = Match(DEC); + } + break; + case T__8: + EnterOuterAlt(_localctx, 2); + { + State = 153; + Match(T__8); + State = 154; + _localctx.hexnumber = TokenStream.LT(1); + _la = TokenStream.LA(1); + if ( !(_la==DEC || _la==HEX) ) { + _localctx.hexnumber = ErrorHandler.RecoverInline(this); + } + else { + ErrorHandler.ReportMatch(this); + Consume(); + } + } + break; + case FLOATNUM: + EnterOuterAlt(_localctx, 3); + { + State = 155; + _localctx.floatnumber = Match(FLOATNUM); + } + break; + default: + throw new NoViableAltException(this); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class HexbodyContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } + public HexbodyContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_hexbody; } } + } + + [RuleVersion(0)] + public HexbodyContext hexbody() { + HexbodyContext _localctx = new HexbodyContext(Context, State); + EnterRule(_localctx, 30, RULE_hexbody); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 158; + _la = TokenStream.LA(1); + if ( !(_la==DEC || _la==HEX) ) { + ErrorHandler.RecoverInline(this); + } + else { + ErrorHandler.ReportMatch(this); + Consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class IdContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode ID() { return GetToken(BdxScriptParser.ID, 0); } + public IdContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_id; } } + } + + [RuleVersion(0)] + public IdContext id() { + IdContext _localctx = new IdContext(Context, State); + EnterRule(_localctx, 32, RULE_id); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 160; + _la = TokenStream.LA(1); + if ( !((((_la) & ~0x3f) == 0 && ((1L << _la) & 360448L) != 0)) ) { + ErrorHandler.RecoverInline(this); + } + else { + ErrorHandler.ReportMatch(this); + Consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class IncludeContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode FileName() { return GetToken(BdxScriptParser.FileName, 0); } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + public IncludeContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_include; } } + } + + [RuleVersion(0)] + public IncludeContext include() { + IncludeContext _localctx = new IncludeContext(Context, State); + EnterRule(_localctx, 34, RULE_include); + try { + EnterOuterAlt(_localctx, 1); + { + State = 162; + Match(T__9); + State = 163; + Match(FileName); + State = 164; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class SectionContext : ParserRuleContext { + [System.Diagnostics.DebuggerNonUserCode] public Section_idContext section_id() { + return GetRuleContext(0); + } + [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } + public SectionContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_section; } } + } + + [RuleVersion(0)] + public SectionContext section() { + SectionContext _localctx = new SectionContext(Context, State); + EnterRule(_localctx, 36, RULE_section); + try { + EnterOuterAlt(_localctx, 1); + { + State = 166; + Match(T__10); + State = 167; + section_id(); + State = 168; + Match(NL); + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + public partial class Section_idContext : ParserRuleContext { + public Section_idContext(ParserRuleContext parent, int invokingState) + : base(parent, invokingState) + { + } + public override int RuleIndex { get { return RULE_section_id; } } + } + + [RuleVersion(0)] + public Section_idContext section_id() { + Section_idContext _localctx = new Section_idContext(Context, State); + EnterRule(_localctx, 38, RULE_section_id); + int _la; + try { + EnterOuterAlt(_localctx, 1); + { + State = 170; + _la = TokenStream.LA(1); + if ( !(_la==T__11 || _la==T__12) ) { + ErrorHandler.RecoverInline(this); + } + else { + ErrorHandler.ReportMatch(this); + Consume(); + } + } + } + catch (RecognitionException re) { + _localctx.exception = re; + ErrorHandler.ReportError(this, re); + ErrorHandler.Recover(this, re); + } + finally { + ExitRule(); + } + return _localctx; + } + + private static int[] _serializedATN = { + 4,1,22,173,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7, + 7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14, + 2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,1,0,5,0,42,8,0,10,0, + 12,0,45,9,0,1,0,1,0,5,0,49,8,0,10,0,12,0,52,9,0,5,0,54,8,0,10,0,12,0,57, + 9,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,72,8,1,1,2, + 1,2,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,3,4,86,8,4,1,5,1,5,1,5,1,5, + 5,5,92,8,5,10,5,12,5,95,9,5,1,5,1,5,1,6,1,6,1,6,1,6,5,6,103,8,6,10,6,12, + 6,106,9,6,1,6,1,6,1,7,1,7,1,7,3,7,113,8,7,1,7,1,7,1,8,1,8,1,8,3,8,120, + 8,8,1,8,1,8,1,9,1,9,1,9,1,9,5,9,128,8,9,10,9,12,9,131,9,9,3,9,133,8,9, + 1,9,1,9,1,10,1,10,3,10,139,8,10,1,11,1,11,1,11,1,12,1,12,1,12,3,12,147, + 8,12,1,13,1,13,3,13,151,8,13,1,14,1,14,1,14,1,14,3,14,157,8,14,1,15,1, + 15,1,16,1,16,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,19,1,19,1,19,0, + 0,20,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,0,3,1,0,15, + 16,2,0,15,16,18,18,1,0,12,13,176,0,43,1,0,0,0,2,71,1,0,0,0,4,73,1,0,0, + 0,6,75,1,0,0,0,8,85,1,0,0,0,10,87,1,0,0,0,12,98,1,0,0,0,14,109,1,0,0,0, + 16,116,1,0,0,0,18,123,1,0,0,0,20,138,1,0,0,0,22,140,1,0,0,0,24,146,1,0, + 0,0,26,150,1,0,0,0,28,156,1,0,0,0,30,158,1,0,0,0,32,160,1,0,0,0,34,162, + 1,0,0,0,36,166,1,0,0,0,38,170,1,0,0,0,40,42,5,20,0,0,41,40,1,0,0,0,42, + 45,1,0,0,0,43,41,1,0,0,0,43,44,1,0,0,0,44,55,1,0,0,0,45,43,1,0,0,0,46, + 50,3,2,1,0,47,49,5,20,0,0,48,47,1,0,0,0,49,52,1,0,0,0,50,48,1,0,0,0,50, + 51,1,0,0,0,51,54,1,0,0,0,52,50,1,0,0,0,53,46,1,0,0,0,54,57,1,0,0,0,55, + 53,1,0,0,0,55,56,1,0,0,0,56,58,1,0,0,0,57,55,1,0,0,0,58,59,5,0,0,1,59, + 1,1,0,0,0,60,72,3,36,18,0,61,72,3,6,3,0,62,63,3,4,2,0,63,64,5,1,0,0,64, + 65,5,20,0,0,65,72,1,0,0,0,66,67,3,4,2,0,67,68,5,1,0,0,68,69,3,8,4,0,69, + 72,1,0,0,0,70,72,3,8,4,0,71,60,1,0,0,0,71,61,1,0,0,0,71,62,1,0,0,0,71, + 66,1,0,0,0,71,70,1,0,0,0,72,3,1,0,0,0,73,74,3,32,16,0,74,5,1,0,0,0,75, + 76,3,32,16,0,76,77,5,2,0,0,77,78,3,28,14,0,78,7,1,0,0,0,79,86,3,12,6,0, + 80,86,3,14,7,0,81,86,3,10,5,0,82,86,3,16,8,0,83,86,3,18,9,0,84,86,3,34, + 17,0,85,79,1,0,0,0,85,80,1,0,0,0,85,81,1,0,0,0,85,82,1,0,0,0,85,83,1,0, + 0,0,85,84,1,0,0,0,86,9,1,0,0,0,87,88,5,3,0,0,88,93,3,26,13,0,89,90,5,4, + 0,0,90,92,3,26,13,0,91,89,1,0,0,0,92,95,1,0,0,0,93,91,1,0,0,0,93,94,1, + 0,0,0,94,96,1,0,0,0,95,93,1,0,0,0,96,97,5,20,0,0,97,11,1,0,0,0,98,99,5, + 5,0,0,99,104,3,24,12,0,100,101,5,4,0,0,101,103,3,24,12,0,102,100,1,0,0, + 0,103,106,1,0,0,0,104,102,1,0,0,0,104,105,1,0,0,0,105,107,1,0,0,0,106, + 104,1,0,0,0,107,108,5,20,0,0,108,13,1,0,0,0,109,112,5,6,0,0,110,113,3, + 28,14,0,111,113,3,32,16,0,112,110,1,0,0,0,112,111,1,0,0,0,113,114,1,0, + 0,0,114,115,5,20,0,0,115,15,1,0,0,0,116,119,5,7,0,0,117,120,3,28,14,0, + 118,120,3,32,16,0,119,117,1,0,0,0,119,118,1,0,0,0,120,121,1,0,0,0,121, + 122,5,20,0,0,122,17,1,0,0,0,123,132,3,32,16,0,124,129,3,20,10,0,125,126, + 5,4,0,0,126,128,3,20,10,0,127,125,1,0,0,0,128,131,1,0,0,0,129,127,1,0, + 0,0,129,130,1,0,0,0,130,133,1,0,0,0,131,129,1,0,0,0,132,124,1,0,0,0,132, + 133,1,0,0,0,133,134,1,0,0,0,134,135,5,20,0,0,135,19,1,0,0,0,136,139,3, + 28,14,0,137,139,3,32,16,0,138,136,1,0,0,0,138,137,1,0,0,0,139,21,1,0,0, + 0,140,141,5,8,0,0,141,142,3,32,16,0,142,23,1,0,0,0,143,147,3,28,14,0,144, + 147,3,32,16,0,145,147,5,14,0,0,146,143,1,0,0,0,146,144,1,0,0,0,146,145, + 1,0,0,0,147,25,1,0,0,0,148,151,3,28,14,0,149,151,3,32,16,0,150,148,1,0, + 0,0,150,149,1,0,0,0,151,27,1,0,0,0,152,157,5,15,0,0,153,154,5,9,0,0,154, + 157,7,0,0,0,155,157,5,17,0,0,156,152,1,0,0,0,156,153,1,0,0,0,156,155,1, + 0,0,0,157,29,1,0,0,0,158,159,7,0,0,0,159,31,1,0,0,0,160,161,7,1,0,0,161, + 33,1,0,0,0,162,163,5,10,0,0,163,164,5,21,0,0,164,165,5,20,0,0,165,35,1, + 0,0,0,166,167,5,11,0,0,167,168,3,38,19,0,168,169,5,20,0,0,169,37,1,0,0, + 0,170,171,7,2,0,0,171,39,1,0,0,0,15,43,50,55,71,85,93,104,112,119,129, + 132,138,146,150,156 + }; + + public static readonly ATN _ATN = + new ATNDeserializer().Deserialize(_serializedATN); + + +} diff --git a/OpenKh.Kh2/Bdx/Antlr/README.md b/OpenKh.Kh2/Bdx/Antlr/README.md new file mode 100644 index 000000000..a7cb4b06d --- /dev/null +++ b/OpenKh.Kh2/Bdx/Antlr/README.md @@ -0,0 +1,40 @@ +# Using Antlr4 + +Use Visual Studio Code. + +Install vscode plugin `mike-lischke.vscode-antlr4`. + +Edit `BdxScript.g4`. + +After edit, run this command: + +```bat +java.exe -jar antlr-4.11.1-complete.jar BdxScript.g4 -Dlanguage=CSharp -no-listener -no-visitor +``` + +Only `BdxScriptLexer.cs` and `BdxScriptParser.cs` are needed. Others are not needed. + +## How bdx text is parsed to tree + +Use Visual Studio Code `mike-lischke.vscode-antlr4` plugin. + +And write this at `OpenKh/OpenKh.Command.Bdxio/Antlr4/.vscode/launch.json` + +``` +{ + "version": "1.0.0", + "configurations": [ + { + "name": "Debug ANTLR4 grammar", + "type": "antlr-debug", + "request": "launch", + "input": "test.bdx", + "grammar": "BdxScript.g4", + "printParseTree": true, + "visualParseTree": true + }, + ] +} +``` + +Write something to `test.bdx` and press `F5` key. diff --git a/OpenKh.Kh2/Bdx/Models/BdxHeader.cs b/OpenKh.Kh2/Bdx/Models/BdxHeader.cs new file mode 100644 index 000000000..9ececbaf1 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxHeader.cs @@ -0,0 +1,35 @@ +using System.Text; +using Xe.BinaryMapper; +using YamlDotNet.Serialization; + +namespace OpenKh.Kh2.Bdx.Models +{ + public class BdxHeader + { + [Data(Count = 16), YamlIgnore()] public byte[] RawName { get; set; } = new byte[0]; + [Data] public int WorkSize { get; set; } + [Data] public int StackSize { get; set; } + [Data] public int TempSize { get; set; } + public Trigger[] Triggers { get; set; } = new Trigger[0]; + + [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsEmpty { get; set; } + + public string Name + { + get => Encoding.ASCII.GetString(RawName).Split('\0')[0]; + set => RawName = Encoding.ASCII.GetBytes((value ?? "").PadRight(16, '\0'), 0, 16); + } + + public class IntTrigger + { + [Data] public int Key { get; set; } + [Data] public int Addr { get; set; } + } + + public class Trigger + { + public int Key { get; set; } + public string Addr { get; set; } + } + } +} diff --git a/OpenKh.Kh2/Bdx/Models/BdxInstruction.cs b/OpenKh.Kh2/Bdx/Models/BdxInstruction.cs new file mode 100644 index 000000000..f272df200 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxInstruction.cs @@ -0,0 +1,32 @@ +using OpenKh.Common.Utils; + +namespace OpenKh.Kh2.Bdx.Models +{ + public class BdxInstruction + { + public ushort Code { get; set; } + + public BdxInstruction(ushort code) + { + Code = code; + } + + public byte Opcode + { + get => (byte)BitsUtil.Int.GetBits(Code, 0, 4); + set => Code = (ushort)BitsUtil.Int.SetBits(Code, 0, 4, value); + } + + public byte Sub + { + get => (byte)BitsUtil.Int.GetBits(Code, 4, 2); + set => Code = (ushort)BitsUtil.Int.SetBits(Code, 4, 2, value); + } + + public ushort Ssub + { + get => (ushort)BitsUtil.Int.GetBits(Code, 6, 12); + set => Code = (ushort)BitsUtil.Int.SetBits(Code, 6, 12, value); + } + } +} diff --git a/OpenKh.Kh2/Bdx/Models/BdxInstructionDesc.cs b/OpenKh.Kh2/Bdx/Models/BdxInstructionDesc.cs new file mode 100644 index 000000000..42d5d4a96 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxInstructionDesc.cs @@ -0,0 +1,43 @@ +namespace OpenKh.Kh2.Bdx.Models +{ + public record BdxInstructionDesc + { + public ushort Code { get; set; } + public ushort CodeMask { get; set; } + + public string Name { get; set; } = ""; + public bool IsSyscall { get; set; } + public bool IsGosub { get; set; } + public bool IsJump { get; set; } + public bool IsJumpConditional { get; set; } + public bool NeverReturn { get; set; } + public bool IsGosubRet { get; set; } + public bool CodeRevealerLabeling { get; set; } + public int CodeSize { get; set; } + public Arg[] Args { get; set; } = new Arg[0]; + public string[] OldNames { get; set; } = new string[0]; + + public override string ToString() => Name; + + public record Arg + { + public string Name { get; set; } = ""; + public ArgType Type { get; set; } + + /// + /// Is relative offset to bdx instruction + /// + public bool IsRelative { get; set; } + public bool AiPos { get; set; } + public bool WorkPos { get; set; } + } + + public enum ArgType + { + Ssub, + Imm16, + Imm32, + Float32, + } + } +} diff --git a/OpenKh.Kh2/Bdx/Models/BdxInstructionDescs.cs b/OpenKh.Kh2/Bdx/Models/BdxInstructionDescs.cs new file mode 100644 index 000000000..bec1a8164 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxInstructionDescs.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using System.Linq; +using static OpenKh.Kh2.Bdx.Models.BdxInstructionDesc; + +namespace OpenKh.Kh2.Bdx.Models +{ + public class BdxInstructionDescs + { + public static IEnumerable GetDescs() => _descs; + + public static BdxInstructionDesc? FindByCode(ushort code) + { + return _descs + .FirstOrDefault(it => (code & it.CodeMask) == it.Code); + } + + private static readonly BdxInstructionDesc[] _descs = new[] + { + #region Generated + + new BdxInstructionDesc { Code = 0x0000, CodeMask = 0x003F, Name = "push", CodeRevealerLabeling = true, CodeSize = 3, Args = new[] { new Arg { Name = "imm32", Type = ArgType.Imm32 } }, OldNames = new[] { "pushImm" } }, + new BdxInstructionDesc { Code = 0x0010, CodeMask = 0x003F, Name = "push.s", CodeSize = 3, Args = new[] { new Arg { Name = "float32", Type = ArgType.Float32 } }, OldNames = new[] { "pushImmf" } }, + new BdxInstructionDesc { Code = 0x0020, CodeMask = 0xFFFF, Name = "push.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromPSp" } }, + new BdxInstructionDesc { Code = 0x0060, CodeMask = 0xFFFF, Name = "push.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "pushFromPWp" } }, + new BdxInstructionDesc { Code = 0x00A0, CodeMask = 0xFFFF, Name = "push.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromPSpVal" } }, + new BdxInstructionDesc { Code = 0x00E0, CodeMask = 0xFFFF, Name = "push.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "pushFromPAi" } }, + new BdxInstructionDesc { Code = 0x0030, CodeMask = 0xFFFF, Name = "push.d.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromFSp" } }, + new BdxInstructionDesc { Code = 0x0070, CodeMask = 0xFFFF, Name = "push.d.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "pushFromFWp" } }, + new BdxInstructionDesc { Code = 0x00B0, CodeMask = 0xFFFF, Name = "push.d.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromFSpVal" } }, + new BdxInstructionDesc { Code = 0x00F0, CodeMask = 0xFFFF, Name = "push.d.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "pushFromFAi" } }, + new BdxInstructionDesc { Code = 0x0001, CodeMask = 0xFFCF, Name = "pop.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "popToSp" } }, + new BdxInstructionDesc { Code = 0x0041, CodeMask = 0xFFCF, Name = "pop.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "popToWp" } }, + new BdxInstructionDesc { Code = 0x0081, CodeMask = 0xFFCF, Name = "pop.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "popToSpVal" } }, + new BdxInstructionDesc { Code = 0x00C1, CodeMask = 0xFFCF, Name = "pop.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "popToAi" } }, + new BdxInstructionDesc { Code = 0x0002, CodeMask = 0xFFCF, Name = "memcpy.sp", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16 } }, OldNames = new[] { "memcpyToSp" } }, + new BdxInstructionDesc { Code = 0x0042, CodeMask = 0xFFCF, Name = "memcpy.wp", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "memcpyToWp" } }, + new BdxInstructionDesc { Code = 0x0082, CodeMask = 0xFFCF, Name = "memcpy.sp.d", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16 } }, OldNames = new[] { "memcpyToSpVal" } }, + new BdxInstructionDesc { Code = 0x00C2, CodeMask = 0xFFCF, Name = "memcpy.bd", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "memcpyToSpAi" } }, + new BdxInstructionDesc { Code = 0x0003, CodeMask = 0x000F, Name = "push.d.pop", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "fetchValue" } }, + new BdxInstructionDesc { Code = 0x0004, CodeMask = 0x000F, Name = "memcpy", CodeSize = 1, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub } } }, + new BdxInstructionDesc { Code = 0x0005, CodeMask = 0xFFFF, Name = "cvt.w.s", CodeSize = 1, OldNames = new[] { "citf" } }, + new BdxInstructionDesc { Code = 0x0085, CodeMask = 0xFFFF, Name = "neg", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x00C5, CodeMask = 0xFFFF, Name = "not", CodeSize = 1, OldNames = new[] { "inv" } }, + new BdxInstructionDesc { Code = 0x0105, CodeMask = 0xFFFF, Name = "seqz", CodeSize = 1, OldNames = new[] { "eqz" } }, + new BdxInstructionDesc { Code = 0x0145, CodeMask = 0xFFFF, Name = "abs", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0185, CodeMask = 0xFFFF, Name = "sltz", CodeSize = 1, OldNames = new[] { "msb" } }, + new BdxInstructionDesc { Code = 0x01C5, CodeMask = 0xFFFF, Name = "slez", CodeSize = 1, OldNames = new[] { "info" } }, + new BdxInstructionDesc { Code = 0x0205, CodeMask = 0xFFFF, Name = "seqz", CodeSize = 1, OldNames = new[] { "eqz" } }, + new BdxInstructionDesc { Code = 0x0245, CodeMask = 0xFFFF, Name = "snez", CodeSize = 1, OldNames = new[] { "neqz" } }, + new BdxInstructionDesc { Code = 0x0285, CodeMask = 0xFFFF, Name = "sgez", CodeSize = 1, OldNames = new[] { "msbi" } }, + new BdxInstructionDesc { Code = 0x02C5, CodeMask = 0xFFFF, Name = "sgtz", CodeSize = 1, OldNames = new[] { "ipos" } }, + new BdxInstructionDesc { Code = 0x0055, CodeMask = 0xFFFF, Name = "cvt.s.w", CodeSize = 1, OldNames = new[] { "cfti" } }, + new BdxInstructionDesc { Code = 0x0095, CodeMask = 0xFFFF, Name = "neg.s", CodeSize = 1, OldNames = new[] { "negf" } }, + new BdxInstructionDesc { Code = 0x0155, CodeMask = 0xFFFF, Name = "abs.s", CodeSize = 1, OldNames = new[] { "absf" } }, + new BdxInstructionDesc { Code = 0x0195, CodeMask = 0xFFFF, Name = "sltz.s", CodeSize = 1, OldNames = new[] { "infzf" } }, + new BdxInstructionDesc { Code = 0x01D5, CodeMask = 0xFFFF, Name = "slez.s", CodeSize = 1, OldNames = new[] { "infoezf" } }, + new BdxInstructionDesc { Code = 0x0215, CodeMask = 0xFFFF, Name = "seqz.s", CodeSize = 1, OldNames = new[] { "eqzf" } }, + new BdxInstructionDesc { Code = 0x0255, CodeMask = 0xFFFF, Name = "snez.s", CodeSize = 1, OldNames = new[] { "neqzf" } }, + new BdxInstructionDesc { Code = 0x0295, CodeMask = 0xFFFF, Name = "sgez.s", CodeSize = 1, OldNames = new[] { "supoezf" } }, + new BdxInstructionDesc { Code = 0x02D5, CodeMask = 0xFFFF, Name = "sgtz.s", CodeSize = 1, OldNames = new[] { "supzf" } }, + new BdxInstructionDesc { Code = 0x0006, CodeMask = 0xFFFF, Name = "add", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0046, CodeMask = 0xFFFF, Name = "sub", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0086, CodeMask = 0xFFFF, Name = "mul", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x00C6, CodeMask = 0xFFFF, Name = "div", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0106, CodeMask = 0xFFFF, Name = "mod", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0146, CodeMask = 0xFFFF, Name = "and", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0186, CodeMask = 0xFFFF, Name = "or", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x01C6, CodeMask = 0xFFFF, Name = "xor", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0206, CodeMask = 0xFFFF, Name = "sll", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0246, CodeMask = 0xFFFF, Name = "sra", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0286, CodeMask = 0xFFFF, Name = "land", CodeSize = 1, OldNames = new[] { "eqzv" } }, + new BdxInstructionDesc { Code = 0x02C6, CodeMask = 0xFFFF, Name = "lor", CodeSize = 1, OldNames = new[] { "neqzv" } }, + new BdxInstructionDesc { Code = 0x0016, CodeMask = 0xFFFF, Name = "add.s", CodeSize = 1, OldNames = new[] { "addf" } }, + new BdxInstructionDesc { Code = 0x0056, CodeMask = 0xFFFF, Name = "sub.s", CodeSize = 1, OldNames = new[] { "subf" } }, + new BdxInstructionDesc { Code = 0x0096, CodeMask = 0xFFFF, Name = "mul.s", CodeSize = 1, OldNames = new[] { "mulf" } }, + new BdxInstructionDesc { Code = 0x00D6, CodeMask = 0xFFFF, Name = "div.s", CodeSize = 1, OldNames = new[] { "divf" } }, + new BdxInstructionDesc { Code = 0x0116, CodeMask = 0xFFFF, Name = "mod.s", CodeSize = 1, OldNames = new[] { "modf" } }, + new BdxInstructionDesc { Code = 0x0007, CodeMask = 0xFFCF, Name = "b", IsJump = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jmp" } }, + new BdxInstructionDesc { Code = 0x0047, CodeMask = 0xFFCF, Name = "beqz", IsJump = true, IsJumpConditional = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jz" } }, + new BdxInstructionDesc { Code = 0x0087, CodeMask = 0xFFCF, Name = "bnez", IsJump = true, IsJumpConditional = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jnz" } }, + new BdxInstructionDesc { Code = 0x0008, CodeMask = 0x000F, Name = "jal", IsGosub = true, CodeSize = 2, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "gosub" } }, + new BdxInstructionDesc { Code = 0x0009, CodeMask = 0xFFCF, Name = "halt", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0049, CodeMask = 0xFFCF, Name = "exit", NeverReturn = true, CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0089, CodeMask = 0xFFCF, Name = "ret", IsGosubRet = true, CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x00C9, CodeMask = 0xFFCF, Name = "drop", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0149, CodeMask = 0xFFCF, Name = "dup", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0189, CodeMask = 0xFFCF, Name = "sin", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x01C9, CodeMask = 0xFFCF, Name = "cos", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0209, CodeMask = 0xFFCF, Name = "degr", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x0249, CodeMask = 0xFFCF, Name = "radd", CodeSize = 1 }, + new BdxInstructionDesc { Code = 0x000A, CodeMask = 0x000F, Name = "syscall", IsSyscall = true, CodeSize = 2, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm16", Type = ArgType.Imm16 } } }, + new BdxInstructionDesc { Code = 0x000B, CodeMask = 0x000F, Name = "jal32", IsGosub = true, CodeSize = 3, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm32", Type = ArgType.Imm32, IsRelative = true } }, OldNames = new[] { "gosub32" } }, + + #endregion + }; + } +} diff --git a/OpenKh.Kh2/Bdx/Models/BdxTrap.cs b/OpenKh.Kh2/Bdx/Models/BdxTrap.cs new file mode 100644 index 000000000..4f9851063 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxTrap.cs @@ -0,0 +1,10 @@ +namespace OpenKh.Kh2.Bdx.Models +{ + public class BdxTrap + { + public int TableIndex { get; set; } + public int TrapIndex { get; set; } + public string Name { get; set; } + public int Flags { get; set; } + } +} diff --git a/OpenKh.Kh2/Bdx/Models/BdxTraps.cs b/OpenKh.Kh2/Bdx/Models/BdxTraps.cs new file mode 100644 index 000000000..f4e8baecf --- /dev/null +++ b/OpenKh.Kh2/Bdx/Models/BdxTraps.cs @@ -0,0 +1,845 @@ +using System.Collections.Generic; + +namespace OpenKh.Kh2.Bdx.Models +{ + public class BdxTraps + { + public static IEnumerable GetTraps() => _traps; + + private static readonly BdxTrap[] _traps = new[] + { + #region Generated + + new BdxTrap { TableIndex = 0, TrapIndex = 0, Name = "trap_puti", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 1, Name = "trap_putf", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 2, Name = "trap_puts", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 3, Name = "trap_frametime", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 4, Name = "trap_vector_add", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 5, Name = "trap_vector_sub", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 6, Name = "trap_vector_len", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 7, Name = "trap_vector_normalize", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 8, Name = "trap_vector_dump", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 9, Name = "trap_thread_start", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 0, TrapIndex = 11, Name = "trap_file_is_reading", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 12, Name = "trap_file_flush", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 13, Name = "trap_vector_roty", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 14, Name = "trap_progress_set_flag", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 15, Name = "trap_progress_check_flag", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 16, Name = "trap_random_get", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 17, Name = "trap_random_getf", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 18, Name = "trap_random_range", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 19, Name = "trap_worldflag_set", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 20, Name = "trap_worldflag_check", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 21, Name = "trap_vector_get_rot_xz", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 22, Name = "trap_abs", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 23, Name = "trap_absf", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 24, Name = "trap_stputi", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 25, Name = "trap_stputf", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 26, Name = "trap_stputs", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 27, Name = "func_system_set_game_speed", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 28, Name = "method_blur_init", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 29, Name = "method_blur_start", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 30, Name = "method_blur_stop", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 31, Name = "func_screen_whiteout", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 32, Name = "func_screen_whitein", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 35, Name = "method_vector_scale", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 36, Name = "trap_vector_mul", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 37, Name = "trap_vector_div", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 38, Name = "trap_effect_set_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 39, Name = "trap_effect_set_scale", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 40, Name = "trap_effect_set_rot", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 41, Name = "trap_effect_set_dir", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 42, Name = "trap_vector_atan_xz", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 43, Name = "trap_fixrad", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 44, Name = "trap_effect_loop_end", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 45, Name = "trap_vector_addf", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 46, Name = "trap_vector_homing", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 47, Name = "trap_memory_alloc", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 48, Name = "trap_memory_free", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 49, Name = "trap_effect_is_alive", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 50, Name = "trap_effect_is_active", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 51, Name = "trap_effect_kill", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 52, Name = "trap_effect_fadeout", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 53, Name = "trap_effect_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 54, Name = "trap_effect_dir", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 55, Name = "trap_timer_count_down", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 56, Name = "trap_timer_count_up", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 57, Name = "trap_saveflag_set", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 58, Name = "trap_saveflag_reset", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 59, Name = "trap_saveflag_check", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 60, Name = "trap_assert", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 61, Name = "trap_saveram_get_partram", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 62, Name = "trap_partram_set_item_max", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 63, Name = "trap_item_get", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 64, Name = "trap_sound_disable", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 65, Name = "trap_sound_play_system", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 66, Name = "trap_effect_pause", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 67, Name = "trap_effect_set_color", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 68, Name = "trap_vector_rotx", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 69, Name = "trap_menuflag_set", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 70, Name = "trap_progress_is_second", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 73, Name = "trap_menuflag_reset", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 74, Name = "trap_screen_show_picture", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 75, Name = "trap_saveram_set_weapon", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 76, Name = "trap_saveram_set_form_weapon", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 77, Name = "trap_screen_cross_fade", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 78, Name = "trap_vector_inter", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 79, Name = "trap_effect_add_dead_block", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 80, Name = "trap_pad_is_button", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 81, Name = "trap_pad_is_trigger", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 82, Name = "trap_vector_outer_product", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 83, Name = "trap_vector_rot", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 84, Name = "trap_vector_angle", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 85, Name = "trap_effect_loop_end_kill", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 86, Name = "trap_effect_set_type", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 87, Name = "trap_screen_fadeout", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 88, Name = "trap_screen_fadein", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 89, Name = "trap_menuflag_check", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 90, Name = "trap_vector_draw", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 0, TrapIndex = 91, Name = "trap_vector_inner_prodcut", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 92, Name = "trap_partram_add_attack", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 93, Name = "trap_partram_add_wisdom", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 94, Name = "trap_partram_add_defence", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 95, Name = "trap_partram_set_levelup_type", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 96, Name = "trap_partram_add_ap", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 97, Name = "trap_item_reduce", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 98, Name = "trap_saveram_set_form_ability", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 0, TrapIndex = 99, Name = "trap_partram_add_ability", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 0, TrapIndex = 100, Name = "trap_saveram_increment_friend_recov", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 101, Name = "trap_progress_is_secret_movie", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 102, Name = "trap_vector_to_angle", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 0, TrapIndex = 103, Name = "trap_progress_is_fm_secret_movie", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 0, TrapIndex = 104, Name = "trap_sound_set_bgse_volume", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 0, Name = "trap_sysobj_appear", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 1, Name = "trap_obj_set_rot", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 2, Name = "trap_sysobj_moveto", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 3, Name = "trap_sysobj_player", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 4, Name = "trap_obj_wish_dir", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 5, Name = "trap_act_table_init", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 6, Name = "trap_act_table_add", Flags = 0x0000000C }, + new BdxTrap { TableIndex = 1, TrapIndex = 7, Name = "trap_obj_set_act_table", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 8, Name = "trap_obj_act_start", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 9, Name = "trap_obj_act_push", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 10, Name = "trap_obj_is_act_exec", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 11, Name = "trap_sysobj_motion_start", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 12, Name = "trap_sysobj_motion_change", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 13, Name = "trap_sysobj_motion_push", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 14, Name = "trap_sysobj_motion_is_end", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 15, Name = "trap_sysobj_motion_id", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 17, Name = "trap_obj_leave_force", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 18, Name = "trap_obj_attach", Flags = 0x00000006 }, + new BdxTrap { TableIndex = 1, TrapIndex = 19, Name = "trap_sysobj_fadeout", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 20, Name = "trap_sysobj_fadein", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 21, Name = "trap_obj_effect_start", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 22, Name = "trap_obj_effect_start_pos", Flags = 0x40000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 23, Name = "trap_area_world", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 24, Name = "trap_area_area", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 25, Name = "trap_area_map_set", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 26, Name = "trap_area_battle_set", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 27, Name = "trap_area_event_set", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 28, Name = "trap_obj_leave", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 29, Name = "trap_obj_motion_capture", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 30, Name = "trap_area_jump", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 31, Name = "trap_area_setjump", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 32, Name = "trap_message_open", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 33, Name = "trap_message_close", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 34, Name = "trap_event_is_exec", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 35, Name = "trap_area_init", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 36, Name = "trap_bg_hide", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 37, Name = "trap_bg_show", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 38, Name = "trap_obj_set_team", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 39, Name = "trap_obj_unit_arg", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 40, Name = "trap_obj_is_pirate_shade", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 41, Name = "trap_signal_call", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 42, Name = "func_obj_control_off", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 43, Name = "func_obj_control_on", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 44, Name = "func_history_clear_enemy", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 45, Name = "func_area_activate_unit", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 46, Name = "func_bg_barrier_on", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 47, Name = "func_bg_barrier_off", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 48, Name = "method_message_is_end", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 49, Name = "method_obj_enable_reaction_command", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 50, Name = "method_obj_disable_reaction_command", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 51, Name = "method_obj_reset_reaction_command", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 52, Name = "method_obj_enable_collision", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 53, Name = "method_obj_disable_collision", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 54, Name = "method_obj_reset_collision", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 55, Name = "method_obj_jump", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 56, Name = "method_obj_is_culling", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 57, Name = "trap_obj_is_jump", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 58, Name = "trap_obj_fly", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 59, Name = "trap_obj_is_fly", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 60, Name = "trap_obj_is_air", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 61, Name = "trap_sysobj_motion_frame_start", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 62, Name = "trap_obj_get_moved", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 63, Name = "trap_obj_is_motion_in_loop", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 64, Name = "trap_obj_get_wish_movement", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 65, Name = "trap_obj_exec_fall", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 66, Name = "trap_obj_exec_land", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 67, Name = "trap_obj_motion_get_length", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 68, Name = "trap_obj_motion_get_loop_top", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 69, Name = "trap_obj_motion_get_time", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 70, Name = "trap_obj_set_flag", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 71, Name = "trap_obj_reset_flag", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 72, Name = "trap_obj_check_flag", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 73, Name = "trap_obj_hover", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 74, Name = "trap_obj_idle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 75, Name = "trap_obj_motion_hook", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 76, Name = "trap_obj_motion_unhook", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 77, Name = "trap_obj_motion_is_hook", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 78, Name = "trap_obj_motion_is_no_control", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 79, Name = "trap_obj_set_dir", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 80, Name = "trap_obj_turn_dir", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 81, Name = "trap_obj_act_wedge", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 82, Name = "trap_obj_thread_start", Flags = 0x40000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 83, Name = "trap_obj_apply_bone_matrix", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 84, Name = "trap_obj_sheet", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 85, Name = "trap_obj_texanm_start", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 86, Name = "trap_obj_texanm_stop", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 87, Name = "trap_obj_effect_start_bind", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 88, Name = "trap_obj_target_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 89, Name = "trap_obj_move_request", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 90, Name = "trap_obj_act_shout", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 91, Name = "trap_obj_star", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 92, Name = "trap_obj_scatter_prize", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 93, Name = "trap_sysobj_party", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 94, Name = "trap_sysobj_is_exist", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 95, Name = "trap_obj_fly_to_jump", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 96, Name = "trap_obj_get_action", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 97, Name = "trap_obj_spec", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 98, Name = "trap_obj_step_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 99, Name = "trap_obj_float_height", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 100, Name = "trap_obj_jump_height_to_uptime", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 101, Name = "trap_obj_motion_is_capture", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 102, Name = "trap_obj_detach", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 103, Name = "trap_obj_set_detach_callback", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 104, Name = "trap_obj_shadow_move_start", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 105, Name = "trap_obj_shadow_move_end", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 106, Name = "trap_signal_reserve_hp", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 107, Name = "trap_obj_motion_speed", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 108, Name = "trap_obj_show_part", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 109, Name = "trap_obj_hide_part", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 110, Name = "trap_obj_get_appear_way", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 111, Name = "trap_obj_set_movement", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 112, Name = "trap_obj_hook", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 113, Name = "trap_player_get_movement", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 114, Name = "trap_obj_search_by_entry", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 115, Name = "trap_obj_set_jump_motion", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 117, Name = "trap_command_cage_on", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 118, Name = "trap_command_cage_off", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 119, Name = "trap_obj_check_step", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 120, Name = "trap_target_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 121, Name = "trap_target_search", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 122, Name = "trap_obj_dump", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 123, Name = "trap_obj_tex_fade_set", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 124, Name = "trap_obj_is_entry_fly", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 125, Name = "trap_obj_tex_fade_start", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 126, Name = "trap_obj_motion_sync", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 127, Name = "trap_obj_act_clear", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 128, Name = "trap_obj_sysjump", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 129, Name = "trap_obj_blow", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 130, Name = "trap_obj_cmp", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 131, Name = "trap_target_dup", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 132, Name = "trap_target_free", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 133, Name = "trap_obj_hide", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 134, Name = "trap_obj_show", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 135, Name = "trap_bg_cross_pos", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 136, Name = "trap_bg_is_floor", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 137, Name = "trap_bg_get_normal", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 138, Name = "trap_pax_start", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 139, Name = "trap_pax_start_bind", Flags = 0x40000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 140, Name = "trap_target_is_exist", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 141, Name = "trap_bg_ground_pos", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 142, Name = "trap_signal_reserve_min_hp", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 143, Name = "trap_obj_search_by_serial", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 144, Name = "trap_obj_serial", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 145, Name = "trap_obj_touch_zone", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 146, Name = "trap_obj_hitback", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 147, Name = "trap_obj_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 148, Name = "trap_obj_set_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 149, Name = "trap_obj_effect_start_bind_other", Flags = 0x40000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 150, Name = "trap_obj_motion_check_range", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 151, Name = "trap_obj_motion_check_trigger", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 152, Name = "trap_status_is_mission", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 153, Name = "trap_obj_reset_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 154, Name = "trap_status_secure_mode_start", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 155, Name = "trap_obj_add_hp", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 156, Name = "trap_obj_hop", Flags = 0x00000007 }, + new BdxTrap { TableIndex = 1, TrapIndex = 157, Name = "trap_obj_camera_start", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 158, Name = "trap_bg_set_belt_conveyor", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 159, Name = "trap_bg_set_uvscroll_speed", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 160, Name = "trap_target_set_obj", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 161, Name = "trap_obj_is_attach", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 162, Name = "trap_target_set_before_player", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 163, Name = "trap_target_set_after_player", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 164, Name = "trap_obj_camera_start_global", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 165, Name = "trap_command_override", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 166, Name = "trap_target_attack", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 167, Name = "trap_obj_act_start_pri", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 168, Name = "trap_obj_flyjump", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 169, Name = "trap_obj_effect_unbind", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 170, Name = "trap_obj_unit_group", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 171, Name = "trap_status_no_leave", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 172, Name = "trap_obj_can_look", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 173, Name = "trap_obj_can_look_pos", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 174, Name = "trap_obj_look_start", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 175, Name = "trap_obj_look_start_pos", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 176, Name = "trap_obj_look_end", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 177, Name = "trap_obj_set_path", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 178, Name = "trap_obj_get_path_movement", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 179, Name = "trap_obj_set_fall_motion", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 180, Name = "trap_obj_set_land_motion", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 181, Name = "trap_light_create", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 182, Name = "trap_light_set_flag", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 183, Name = "trap_light_set_color", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 184, Name = "trap_light_fadeout", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 185, Name = "trap_obj_set_parts_color", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 186, Name = "trap_obj_reset_parts_color", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 187, Name = "trap_status_prize_drain_start", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 188, Name = "trap_status_prize_drain_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 189, Name = "trap_obj_history_mark", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 190, Name = "trap_obj_is_history_mark", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 191, Name = "trap_obj_lockon_target", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 192, Name = "trap_obj_is_motion_cancel", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 193, Name = "trap_camera_warp", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 194, Name = "trap_obj_set_stealth", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 195, Name = "trap_obj_reset_stealth", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 196, Name = "trap_area_entrance", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 197, Name = "trap_area_cost_rest", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 198, Name = "trap_obj_set_player_random_pos", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 199, Name = "trap_obj_set_random_pos", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 200, Name = "trap_obj_set_bg_collision_type", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 201, Name = "trap_obj_dir", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 202, Name = "trap_unit_disable", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 203, Name = "trap_unit_enable", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 204, Name = "trap_status_force_leave_start", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 205, Name = "trap_status_force_leave_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 206, Name = "trap_status_is_force_leave", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 207, Name = "trap_camera_watch", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 208, Name = "trap_obj_is_hover", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 209, Name = "trap_obj_dead", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 210, Name = "trap_obj_search_by_part", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 211, Name = "trap_obj_pattern_enable", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 212, Name = "trap_obj_pattern_disable", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 213, Name = "trap_obj_part", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 214, Name = "trap_obj_hook_stop", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 217, Name = "trap_obj_set_pos_trans", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 218, Name = "trap_obj_set_unit_arg", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 219, Name = "trap_obj_camera_start", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 220, Name = "trap_obj_move_to_space", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 221, Name = "trap_obj_can_decide_command", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 222, Name = "trap_obj_get_entry_id", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 223, Name = "trap_camera_cancel", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 224, Name = "trap_obj_is_action_air", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 225, Name = "trap_obj_is_star", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 226, Name = "trap_obj_scatter_prize_mu", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 227, Name = "trap_obj_jump_direct", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 228, Name = "trap_sheet_hp", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 229, Name = "trap_sheet_max_hp", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 230, Name = "trap_sheet_hp_rate", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 231, Name = "trap_sheet_set_min_hp", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 232, Name = "trap_sheet_min_hp", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 233, Name = "trap_sheet_set_hp", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 234, Name = "trap_party_get_weapon", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 235, Name = "trap_party_hand_to_bone", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 236, Name = "trap_obj_motion_unsync", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 237, Name = "trap_command_override_top", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 238, Name = "trap_obj_motion_capture_id", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 239, Name = "trap_obj_is_unit_active", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 240, Name = "trap_obj_scatter_prize_tt", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 241, Name = "trap_act_shout", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 242, Name = "trap_player_capture_form", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 243, Name = "trap_player_capture_form_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 244, Name = "trap_status_is_battle", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 245, Name = "trap_target_clear_before_player", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 246, Name = "trap_target_clear_after_player", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 247, Name = "trap_bg_get_random_pos", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 248, Name = "trap_bg_get_random_pos_air", Flags = 0x40000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 249, Name = "trap_status_set_prize_ratio", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 250, Name = "trap_status_set_lockon_ratio", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 251, Name = "trap_light_fadein", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 252, Name = "trap_camera_apply_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 253, Name = "trap_obj_can_capture_control", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 254, Name = "trap_obj_is_ride", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 255, Name = "trap_obj_disable_occ", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 256, Name = "trap_obj_enable_occ", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 257, Name = "trap_light_set_fog_near", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 258, Name = "trap_light_set_fog_far", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 259, Name = "trap_light_set_fog_min", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 260, Name = "trap_light_set_fog_max", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 261, Name = "trap_sheet_munny", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 262, Name = "trap_obj_voice", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 263, Name = "trap_player_set_exec_rc", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 264, Name = "trap_status_secure_mode_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 265, Name = "trap_obj_set_medal", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 266, Name = "trap_obj_get_medal", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 267, Name = "trap_obj_scatter_medal", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 268, Name = "trap_obj_action_lightcycle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 269, Name = "trap_obj_get_lightcycle_movement", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 270, Name = "trap_obj_motion_disable_anmatr_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 271, Name = "trap_obj_motion_enable_anmatr_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 272, Name = "trap_obj_is_dead", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 273, Name = "trap_signal_hook", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 274, Name = "trap_event_get_rest_time", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 275, Name = "trap_obj_recov_holylight", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 276, Name = "trap_obj_use_mp", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 277, Name = "trap_obj_reraise", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 278, Name = "trap_obj_scatter_prize_tr", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 279, Name = "trap_prize_appear_tr", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 280, Name = "trap_sheet_add_munny", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 281, Name = "trap_camera_begin_scope", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 283, Name = "trap_camera_end_scope", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 284, Name = "trap_tutorial_pause", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 285, Name = "trap_obj_show_picture", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 286, Name = "trap_status_hide_shadow", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 287, Name = "trap_status_show_shadow", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 288, Name = "trap_status_begin_free_ability", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 289, Name = "trap_status_end_free_ability", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 290, Name = "trap_picture_change", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 291, Name = "trap_obj_levelup_unit", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 292, Name = "trap_obj_search_by_unit_arg", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 293, Name = "trap_event_control_off", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 294, Name = "trap_event_control_on", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 295, Name = "trap_camera_reset", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 296, Name = "trap_tutorial_open", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 297, Name = "trap_player_get_rc", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 298, Name = "trap_worldwork_get", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 299, Name = "trap_area_set_next_entrance", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 300, Name = "trap_prize_num", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 301, Name = "trap_tutorial_is_open", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 302, Name = "trap_obj_set_skateboard_mode", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 303, Name = "trap_area_cost_ratio", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 304, Name = "trap_obj_search_by_glance", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 305, Name = "trap_camera_eye", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 306, Name = "trap_camera_at", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 307, Name = "trap_obj_search_by_camera", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 308, Name = "trap_obj_capture_command", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 309, Name = "trap_sysobj_is_player", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 310, Name = "trap_obj_get_weight", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 311, Name = "trap_sheet_set_element_rate", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 312, Name = "trap_camera_set_scope_zoom", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 313, Name = "trap_camera_set_scope_closeup_distance", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 314, Name = "trap_camera_set_scope_target_pos", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 315, Name = "trap_picture_set_pos", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 316, Name = "trap_camera_get_projection_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 317, Name = "trap_status_no_gameover", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 318, Name = "trap_obj_play_se", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 319, Name = "trap_sysobj_is_sora", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 320, Name = "trap_unit_get_enemy_num", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 321, Name = "trap_player_lockon", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 322, Name = "trap_command_enable_item", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 323, Name = "trap_obj_count_entry", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 324, Name = "trap_obj_pattern_reset", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 325, Name = "trap_obj_reaction_callback", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 326, Name = "trap_bg_set_animation_speed", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 327, Name = "trap_prize_get_all_tr", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 328, Name = "trap_obj_dead_mark", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 329, Name = "trap_sheet_set_prize_range", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 330, Name = "trap_obj_set_cannon_camera_offset", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 331, Name = "trap_obj_each_all", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 332, Name = "trap_sysobj_is_btlnpc", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 333, Name = "trap_obj_set_cannon_param", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 1, TrapIndex = 334, Name = "trap_command_enable", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 335, Name = "trap_obj_disable_occ_bone", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 336, Name = "trap_obj_enable_occ_bone", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 337, Name = "trap_command_set_side_b", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 338, Name = "trap_prize_return_ca", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 339, Name = "trap_prize_vacuum_ca", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 340, Name = "trap_prize_vacuum_range_ca", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 341, Name = "trap_prize_num_ca", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 342, Name = "trap_prize_appear_num", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 1, TrapIndex = 343, Name = "trap_obj_is_equip_ability", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 344, Name = "trap_obj_clear_occ", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 345, Name = "trap_command_override_slot", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 1, TrapIndex = 346, Name = "trap_command_slot_set_status", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 347, Name = "trap_obj_can_see", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 348, Name = "trap_sheet_set_hitback_addition", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 349, Name = "trap_obj_effect_kill_all", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 350, Name = "trap_status_close_pete_curtain", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 351, Name = "trap_status_open_pete_curtain", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 352, Name = "trap_area_set_return_tr", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 353, Name = "trap_obj_start_mpdrive", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 354, Name = "trap_event_layer_off", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 355, Name = "trap_player_can_capture_form", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 356, Name = "trap_event_layer_on", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 1, TrapIndex = 357, Name = "trap_sheet_attack_level", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 358, Name = "trap_sheet_set_attack_level", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 359, Name = "trap_obj_hook_command_image", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 360, Name = "trap_obj_reset_command_image", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 361, Name = "trap_sheet_level", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 362, Name = "trap_treasure_get", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 363, Name = "trap_prize_appear_xaldin", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 364, Name = "trap_jigsaw_get", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 1, TrapIndex = 365, Name = "trap_command_disable_group", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 366, Name = "trap_command_enable_group", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 1, TrapIndex = 367, Name = "trap_obj_get_move_to_space_pos", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 0, Name = "trap_enemy_exec_damage", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 1, Name = "trap_enemy_exec_damage_blow", Flags = 0x00000006 }, + new BdxTrap { TableIndex = 2, TrapIndex = 2, Name = "trap_enemy_exec_damage_small", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 3, Name = "trap_enemy_exec_damage_hitback", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 4, Name = "trap_enemy_each", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 5, Name = "trap_enemy_is_no_control", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 6, Name = "trap_enemy_is_damage_motion", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 7, Name = "trap_damage_reaction", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 8, Name = "trap_damage_is_reaction", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 9, Name = "trap_btlobj_set_sheet", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 10, Name = "trap_attack_new", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 11, Name = "trap_attack_set_radius", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 12, Name = "trap_attack_set_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 13, Name = "trap_attack_free", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 14, Name = "trap_attack_is_hit", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 15, Name = "trap_damage_exec_reaction", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 16, Name = "trap_damage_is_exec_reaction", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 17, Name = "trap_attack_strike", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 18, Name = "trap_attack_is_strike", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 19, Name = "trap_attack_set_line", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 20, Name = "trap_magic_start_thread", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 21, Name = "trap_teamwork_alloc", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 22, Name = "trap_attack_set_obj_pax", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 23, Name = "trap_btlobj_target", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 24, Name = "trap_attack_get_owner", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 25, Name = "trap_attack_get_param_id", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 26, Name = "trap_attack_exec_reflect", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 27, Name = "trap_enemy_exec_reflect", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 2, TrapIndex = 28, Name = "trap_attack_refresh", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 29, Name = "trap_attack_is_hit_bg", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 30, Name = "trap_attack_set_pax", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 31, Name = "trap_attack_dup", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 32, Name = "trap_damage_blow_up", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 33, Name = "trap_damage_blow_speed", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 34, Name = "trap_attack_get_type", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 35, Name = "trap_damage_attack_type", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 36, Name = "trap_enemy_add_damage", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 37, Name = "trap_attack_set_team", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 38, Name = "trap_attack_set_hit_callback", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 39, Name = "trap_attack_is_reflect", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 40, Name = "trap_attack_is_hit_wall", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 41, Name = "trap_attack_is_hit_floor", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 42, Name = "trap_attack_hit_bg_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 43, Name = "trap_attack_get_reflect_vector", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 44, Name = "trap_attack_reflecter", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 45, Name = "trap_damage_attack_param_id", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 46, Name = "trap_damage_damage", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 47, Name = "trap_limit_motion_start", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 48, Name = "trap_limit_player", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 49, Name = "trap_limit_friend", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 50, Name = "trap_limit_camera_start", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 51, Name = "trap_attack_set_rc", Flags = 0x00000005 }, + new BdxTrap { TableIndex = 2, TrapIndex = 52, Name = "trap_attack_rc_receiver", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 53, Name = "trap_enemy_last_dead", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 2, TrapIndex = 54, Name = "trap_limit_start_thread", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 55, Name = "trap_limit_light", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 56, Name = "trap_btlobj_lockon_target", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 57, Name = "trap_limit_effect_start", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 58, Name = "trap_limit_effect_start_pos", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 59, Name = "trap_limit_effect_start_bind", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 60, Name = "trap_limit_time", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 61, Name = "trap_attack_set_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 62, Name = "trap_attack_set_time", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 63, Name = "trap_limit_reference", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 64, Name = "trap_damage_orig_reaction", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 65, Name = "trap_enemy_count_damager", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 2, TrapIndex = 66, Name = "trap_attack_get_reflect_count", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 67, Name = "trap_attack_new_combo_group", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 2, TrapIndex = 68, Name = "trap_magic_set_cost", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 69, Name = "trap_magic_can_add_cost", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 70, Name = "trap_damage_parts", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 71, Name = "trap_attack_set_hitmark_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 72, Name = "trap_damage_is_cure", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 73, Name = "trap_bonuslevel_up", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 74, Name = "trap_attack_set_reflect_callback", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 75, Name = "trap_summon_is_tink_exist", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 2, TrapIndex = 76, Name = "trap_enemy_set_karma_limit", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 77, Name = "trap_vacuum_create", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 78, Name = "trap_vacuum_destroy", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 79, Name = "trap_vacuum_set_ignore_type", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 80, Name = "trap_vacuum_set_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 81, Name = "trap_vacuum_set_speed", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 2, TrapIndex = 82, Name = "trap_vacuum_set_rot_speed", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 83, Name = "trap_vacuum_set_near_range", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 84, Name = "trap_vacuum_set_dist_rate", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 85, Name = "trap_damage_element", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 86, Name = "trap_damage_get_hitback", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 87, Name = "trap_enemy_exec_damage_large", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 2, TrapIndex = 88, Name = "trap_enemy_get_attacker", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 89, Name = "trap_limit_reset_special_command", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 90, Name = "trap_limit_close_gauge", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 91, Name = "trap_damage_get_reaction_type", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 92, Name = "trap_damage_is_finish", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 93, Name = "trap_damage_is_normal", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 94, Name = "trap_attack_set_system_pax", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 95, Name = "trap_btlobj_dup_sheet", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 2, TrapIndex = 96, Name = "trap_attack_is_valid", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 2, TrapIndex = 97, Name = "trap_enemy_set_attacker", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 2, Name = "trap_event_is_exec", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 3, Name = "trap_mission_complete", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 4, Name = "trap_mission_information", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 5, Name = "trap_mission_set_count", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 6, Name = "trap_mission_increment_count", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 7, Name = "trap_mission_restart_timer", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 8, Name = "trap_mission_set_gauge", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 9, Name = "trap_mission_add_gauge", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 10, Name = "trap_mission_set_gauge_ratio", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 11, Name = "trap_mission_failed", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 12, Name = "trap_mission_get_gauge_ratio", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 13, Name = "trap_mission_pause_timer", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 14, Name = "trap_mission_activate2d", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 15, Name = "trap_mission_deactivate2d", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 16, Name = "trap_mission_dead_boss", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 17, Name = "trap_mission_set_timer_param", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 4, TrapIndex = 18, Name = "trap_mission_set_count_param", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 4, TrapIndex = 19, Name = "trap_mission_set_gauge_param", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 4, TrapIndex = 20, Name = "trap_mission_decrement_count", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 21, Name = "trap_mission_is_activate2d", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 22, Name = "trap_mission_exit", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 23, Name = "trap_mission_reset_pause_mode", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 24, Name = "trap_mission_cancel_pause_timer", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 25, Name = "trap_mission_start_combo_counter", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 26, Name = "trap_mission_get_timer", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 27, Name = "trap_mission_stop_combo_counter", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 28, Name = "trap_mission_get_gauge_warning_ratio", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 29, Name = "trap_mission_get_count", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 30, Name = "trap_mission_get_max_combo_counter", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 31, Name = "trap_mission_get_combo_counter", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 32, Name = "trap_mission_return", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 33, Name = "trap_mission_add_combo_counter", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 34, Name = "trap_mission_is_gauge_warning", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 35, Name = "trap_score_type", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 36, Name = "trap_score_score", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 37, Name = "trap_score_update", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 4, TrapIndex = 38, Name = "trap_score_get", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 39, Name = "trap_mission_set_watch", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 40, Name = "trap_mission_get_timer_second", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 41, Name = "trap_mission_add_count", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 42, Name = "trap_struggle_increment", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 43, Name = "trap_mission_set_max_combo_counter", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 44, Name = "trap_mission_disable_count", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 45, Name = "trap_mission_disable_watch", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 46, Name = "trap_mission_set_warning_se", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 47, Name = "trap_mission_warning_timer", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 48, Name = "trap_mission_set_count_figure_num", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 4, TrapIndex = 49, Name = "trap_mission_disable_timer", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 50, Name = "trap_mission_warning_count", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 51, Name = "trap_mission_set_combo_counter_param", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 4, TrapIndex = 52, Name = "trap_mission_warning_combo_counter", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 4, TrapIndex = 53, Name = "trap_mission_set_combo_counter_warning_se", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 54, Name = "trap_mission_lock", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 55, Name = "trap_mission_is_lock", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 56, Name = "trap_event_continue_control_off", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 4, TrapIndex = 57, Name = "trap_mission_warning_gauge", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 4, TrapIndex = 58, Name = "trap_mission_reset_warning_count", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 0, Name = "trap_get_start_rtn_action", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 1, Name = "trap_set_path_way", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 2, Name = "trap_reverse_path_way", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 3, Name = "trap_get_path_dir", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 4, Name = "trap_end_rtn_action", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 5, Name = "trap_get_rtn_action", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 6, Name = "trap_get_rtn_action_dir", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 7, Name = "trap_is_rtn_change_dir", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 8, Name = "trap_create_active_path", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 9, Name = "trap_get_path_dir_from_obj", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 5, TrapIndex = 10, Name = "trap_forward_path_current_pointer", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 11, Name = "trap_is_end_rtn_action", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 12, Name = "trap_reset_active_path", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 13, Name = "trap_set_path_target_point", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 14, Name = "trap_get_path_point_pos", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 15, Name = "trap_clear_active_path", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 16, Name = "trap_reset_leave_way", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 17, Name = "trap_check_rtn_option_flag", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 18, Name = "trap_reset_path_current_pointer", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 19, Name = "trap_get_path_current_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 20, Name = "trap_get_path_current_dir", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 21, Name = "trap_get_path_first_point_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 22, Name = "trap_get_path_last_point_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 23, Name = "trap_set_path_by_id", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 24, Name = "trap_set_path_by_group", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 25, Name = "trap_get_path_dir_r", Flags = 0x40000003 }, + new BdxTrap { TableIndex = 5, TrapIndex = 26, Name = "trap_set_rtn_partner", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 27, Name = "trap_set_rtn_option_flag", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 28, Name = "trap_eh22_path_move_next", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 29, Name = "trap_eh22_path_move_before", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 30, Name = "trap_eh22_path_is_moving", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 31, Name = "trap_eh22_path_get_point", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 5, TrapIndex = 32, Name = "trap_eh22_path_play", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 5, TrapIndex = 33, Name = "trap_set_rtn_time_param", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 5, TrapIndex = 34, Name = "trap_get_obj_head_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 0, Name = "trap_camera_shake", Flags = 0x00000007 }, + new BdxTrap { TableIndex = 6, TrapIndex = 1, Name = "trap_prize_appear", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 2, Name = "trap_player_get_form", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 3, Name = "trap_target_searcher_init", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 4, Name = "trap_target_searcher_reset", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 5, Name = "trap_target_seracher_search", Flags = 0x00000007 }, + new BdxTrap { TableIndex = 6, TrapIndex = 6, Name = "trap_obj_stop", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 7, Name = "trap_obj_restart", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 8, Name = "trap_target_searcher_add", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 9, Name = "trap_target_dist", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 10, Name = "trap_obj_is_hit_attack", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 11, Name = "trap_target_searcher_search_obj", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 12, Name = "trap_target_searcher_get_old", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 13, Name = "trap_friend_force_warp", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 14, Name = "trap_friend_get", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 15, Name = "trap_friend_set_warp_level", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 16, Name = "trap_target_clear", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 17, Name = "trap_lockon_show", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 18, Name = "trap_lockon_hide", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 19, Name = "trap_status_peterpan_prize_drain_start", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 20, Name = "trap_status_peterpan_prize_drain_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 21, Name = "trap_target_searcher_add_target", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 22, Name = "trap_target_searcher_get_target_num", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 23, Name = "trap_obj_near_parts", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 24, Name = "trap_obj_get_bg_press", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 25, Name = "trap_obj_tt_ball_blow", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 26, Name = "trap_obj_limit_hover", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 27, Name = "trap_player_dice", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 28, Name = "trap_dice_set_spec", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 29, Name = "trap_player_card", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 30, Name = "trap_card_set_spec", Flags = 0x00000007 }, + new BdxTrap { TableIndex = 6, TrapIndex = 31, Name = "trap_limit_aladdin_prize_drain", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 6, TrapIndex = 32, Name = "trap_skateboard_ride", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 33, Name = "trap_skateboard_trick", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 34, Name = "trap_skateboard_trick_motion_push", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 35, Name = "trap_obj_attach_camera", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 36, Name = "trap_obj_detach_camera", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 37, Name = "trap_obj_is_attach_camera", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 38, Name = "trap_obj_limit_mulan_idle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 39, Name = "trap_skateboard_ride_edge", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 40, Name = "trap_obj_limit_peterpan_idle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 41, Name = "trap_skateboard_edge_jump", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 6, TrapIndex = 42, Name = "trap_obj_hop_direct", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 43, Name = "trap_command_limit_trinity_commbo_start", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 44, Name = "trap_obj_limit_riku_idle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 45, Name = "trap_obj_hide_shadow", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 46, Name = "trap_obj_rc_stop_all", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 47, Name = "trap_obj_stop_end_all", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 48, Name = "trap_skateboardscore_add_count", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 49, Name = "trap_obj_is_stop", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 50, Name = "trap_obj_stop_start", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 51, Name = "trap_bghit_check_line", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 52, Name = "trap_bghit_get_normal", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 53, Name = "trap_bghit_is_hit", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 54, Name = "trap_bghit_get_cross_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 55, Name = "trap_bghit_get_kind", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 56, Name = "trap_target_set_group", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 57, Name = "trap_target_get_group", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 58, Name = "trap_obj_act_child_push", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 59, Name = "trap_xemnas_get_obj", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 60, Name = "trap_obj_set_stealth_color", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 6, TrapIndex = 61, Name = "trap_obj_is_hook", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 62, Name = "trap_obj_carpet_obj_idle", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 63, Name = "trap_obj_is_damage_motion", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 64, Name = "trap_obj_show_shadow", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 65, Name = "trap_obj_set_scissoring", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 66, Name = "trap_obj_clear_hitback", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 67, Name = "trap_obj_party_attack", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 68, Name = "trap_strike_raid_calc_xyzrot", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 69, Name = "trap_larxene_dead", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 6, TrapIndex = 70, Name = "trap_obj_play_se_loop", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 6, TrapIndex = 71, Name = "trap_obj_fadeout_se", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 0, Name = "trap_enemy_stop_all_start", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 1, Name = "trap_enemy_stop_all_end", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 2, Name = "trap_attack_hit_mark_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 3, Name = "trap_flare_init", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 4, Name = "trap_flare_new", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 5, Name = "trap_flare_free", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 6, Name = "trap_flare_set_pos", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 7, Name = "trap_flare_set_radius", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 7, TrapIndex = 8, Name = "trap_flare_set_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 9, Name = "trap_flare_set_target", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 10, Name = "trap_flare_get_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 11, Name = "trap_flare_is_empty", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 12, Name = "trap_limit_aladdin_exclamation_mark_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 13, Name = "trap_magic_calc_speed", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 7, TrapIndex = 14, Name = "trap_attack_set_reaction_offset", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 15, Name = "trap_friend_get_target_size", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 16, Name = "trap_friend_get_current_action", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 17, Name = "trap_friend_set_script_status", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 18, Name = "trap_friend_get_main_status", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 19, Name = "trap_friend_update_target", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 21, Name = "trap_obj_limit_hover_set_spec", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 24, Name = "trap_friend_enable_system_wishdir", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 25, Name = "trap_friend_disable_system_wishdir", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 26, Name = "trap_friend_call", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 27, Name = "trap_limit_start_command", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 28, Name = "trap_trinity_shot_init", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 29, Name = "trap_trinity_shot_start", Flags = 0x40000004 }, + new BdxTrap { TableIndex = 7, TrapIndex = 30, Name = "trap_trinity_shot_ensure", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 31, Name = "trap_trinity_shot_set_effect_id", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 32, Name = "trap_vacuum_set_effective_range", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 33, Name = "trap_enemy_summon_entry", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 7, TrapIndex = 34, Name = "trap_attack_set_rc_owner", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 7, TrapIndex = 35, Name = "trap_summon_is_exec", Flags = 0x40000000 }, + new BdxTrap { TableIndex = 7, TrapIndex = 36, Name = "trap_limit_reset_hit_counter", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 8, TrapIndex = 0, Name = "trap_obj_target_radius", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 8, TrapIndex = 1, Name = "trap_player_push_ability_button", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 8, TrapIndex = 2, Name = "trap_obj_set_xyzrot", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 8, TrapIndex = 3, Name = "trap_special_last_xemnus_laser_start", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 8, TrapIndex = 4, Name = "trap_special_last_xemnus_laser_attack", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 8, TrapIndex = 5, Name = "trap_special_last_xemnus_laser_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 8, TrapIndex = 6, Name = "trap_special_last_xemnus_laser_optimize", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 8, TrapIndex = 7, Name = "trap_special_last_xemnus_laser_optimize_end", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 8, TrapIndex = 8, Name = "trap_camera_apply_inverse_pos", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 0, Name = "trap_empty_func", Flags = 0x00000000 }, + new BdxTrap { TableIndex = 10, TrapIndex = 1, Name = "trap_stitch_set_screen_position", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 2, Name = "trap_stitch_get_screen_position", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 3, Name = "trap_friend_start_limit", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 4, Name = "trap_friend_end_limit", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 5, Name = "trap_chickenlittle_get_shoot_target", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 6, Name = "trap_obj_set_special_command", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 7, Name = "trap_obj_reset_special_command", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 8, Name = "trap_friend_get_target_last_position", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 9, Name = "trap_genie_change_form", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 10, Name = "trap_genie_get_limit_command", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 11, Name = "trap_obj_set_stop_timer", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 10, TrapIndex = 12, Name = "trap_stitch_effect_start", Flags = 0x40000008 }, + new BdxTrap { TableIndex = 10, TrapIndex = 13, Name = "trap_stitch_shot_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 14, Name = "trap_friend_set_target", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 10, TrapIndex = 15, Name = "trap_sysobj_motion_cont_push", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 10, TrapIndex = 16, Name = "trap_stitch_effect_kill", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 17, Name = "trap_sysobj_is_zako", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 18, Name = "trap_sysobj_is_boss", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 19, Name = "trap_sysobj_is_limit", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 20, Name = "trap_friend_follow_player", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 21, Name = "trap_friend_follow_enemy", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 22, Name = "trap_sysobj_is_blow", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 23, Name = "trap_enemy_is_attacked_from", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 24, Name = "tarp_friend_is_equiped_ability", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 25, Name = "trap_peterpan_receive_notify_player_target", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 26, Name = "trap_peterpan_accept_notify_player_target", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 27, Name = "trap_friend_enable_inertia", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 28, Name = "trap_friend_disable_inertia", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 29, Name = "trap_friend_use_item", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 10, TrapIndex = 30, Name = "trap_sysobj_is_finish_blow", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 31, Name = "trap_sysobj_is_summon", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 32, Name = "trap_stitch_move_request", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 33, Name = "trap_friend_get_player_attacker", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 34, Name = "trap_friend_remove_player_attacker", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 35, Name = "trap_friend_get_action_param", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 36, Name = "trap_friend_is_control", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 37, Name = "trap_friend_is_moveonly", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 38, Name = "trap_attack_is_finish", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 39, Name = "trap_friend_action_clear", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 40, Name = "trap_obj_is_motion_sync", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 41, Name = "trap_friend_start_leave", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 42, Name = "trap_friend_is_start_leave", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 43, Name = "trap_obj_set_use_mp", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 44, Name = "trap_obj_is_tornado", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 45, Name = "trap_sheet_get_drive_time", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 46, Name = "trap_friend_disable_follow_enemy", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 47, Name = "trap_friend_enable_follow_enemy", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 48, Name = "trap_friend_disable_follow_player", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 49, Name = "trap_friend_enable_follow_player", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 50, Name = "trap_sheet_get_mp", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 51, Name = "trap_chickenlittle_get_nearest_target", Flags = 0x00000004 }, + new BdxTrap { TableIndex = 10, TrapIndex = 52, Name = "trap_btlobj_is_reflect_motion", Flags = 0x40000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 53, Name = "trap_friend_add_watch_effect", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 54, Name = "trap_friend_is_effect_exist", Flags = 0x40000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 55, Name = "trap_target_searcher_set_check_hide_from_friend", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 56, Name = "trap_friend_invalidate_warp_point", Flags = 0x00000002 }, + new BdxTrap { TableIndex = 10, TrapIndex = 57, Name = "trap_friend_add_warp_point", Flags = 0x00000001 }, + new BdxTrap { TableIndex = 10, TrapIndex = 58, Name = "trap_friend_link_magic", Flags = 0x00000003 }, + new BdxTrap { TableIndex = 10, TrapIndex = 59, Name = "trap_chickenlittle_set_shoot_target", Flags = 0x00000002 }, + + #endregion + }; + } +} diff --git a/OpenKh.Kh2/Bdx/Utils/BdxAsciiModel.cs b/OpenKh.Kh2/Bdx/Utils/BdxAsciiModel.cs new file mode 100644 index 000000000..9c156cd96 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Utils/BdxAsciiModel.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; + +namespace OpenKh.Kh2.Bdx.Utils +{ + public class BdxAsciiModel + { + public bool IsEmpty { get; set; } + + public string? Header { get; set; } + public string? Script { get; set; } + + public int FirstHeaderLineNumber { get; set; } + public int FirstScriptLineNumber { get; set; } + + public string GetLineNumberRetainedScriptBody() => new string('\n', FirstScriptLineNumber) + Script; + + public static BdxAsciiModel ParseText(string body) + { + var lines = body.Replace("\r\n", "\n").Split('\n'); + int separator1 = Array.IndexOf(lines, "---"); + if (separator1 < 0) + { + throw new InvalidDataException(); + } + int separator2 = Array.IndexOf(lines, "---", separator1 + 1); + if (separator2 < 0) + { + throw new InvalidDataException(); + } + + var header = string.Join("\n", lines, separator1 + 1, separator2 - separator1 - 1); + var script = string.Join("\n", lines.Skip(separator2 + 1)); + + return new BdxAsciiModel + { + FirstHeaderLineNumber = 1 + separator1, + FirstScriptLineNumber = 1 + separator2, + IsEmpty = header.Trim().Length == 0 && script.Trim().Length == 0, + Header = header, + Script = script, + }; + } + } +} diff --git a/OpenKh.Kh2/Bdx/Utils/BdxDecoder.cs b/OpenKh.Kh2/Bdx/Utils/BdxDecoder.cs new file mode 100644 index 000000000..af6a22c85 --- /dev/null +++ b/OpenKh.Kh2/Bdx/Utils/BdxDecoder.cs @@ -0,0 +1,808 @@ +using OpenKh.Common; +using OpenKh.Kh2.Bdx.Models; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using Xe.BinaryMapper; +using YamlDotNet.Serialization; +using static OpenKh.Kh2.Bdx.Models.BdxHeader; + +namespace OpenKh.Kh2.Bdx.Utils +{ + public class BdxDecoder + { + public BdxHeader Header { get; } + + public IDictionary AddressAnnotated { get; } + public IDictionary AddressLabels { get; } + public IDictionary AddressComments { get; } + public IDictionary AddressCodeComments { get; } + public IDictionary WorkLabels { get; } + public string TopComment { get; set; } + + public BdxDecoder(Stream read, int[]? codeLabels = null, bool codeRevealer = false, bool codeRevealerLabeling = false) + { + if (read.Length == 0) + { + Header = new BdxHeader { IsEmpty = true }; + AddressAnnotated = new Dictionary(); + AddressLabels = new Dictionary(); + AddressComments = new Dictionary(); + AddressCodeComments = new Dictionary(); + WorkLabels = new Dictionary(); + TopComment = ""; + return; + } + + Header = BinaryMapping.ReadObject(read); + + var triggers = new List(); + while (true) + { + var one = BinaryMapping.ReadObject(read); + if (one.Key == 0 && one.Addr == 0) + { + break; + } + triggers.Add(one); + } + + int firstAddr = (12 + 8 * (triggers.Count + 1)) / 2; + + var addrAnnotated = new SortedDictionary(); + var addrLabels = new SortedDictionary(); + var addrComments = new SortedDictionary(); + var addrCodeComments = new SortedDictionary(); + var workLabels = new SortedDictionary(); + var topComment = ""; + + void AddAddr(int entry, string label) + { + if (addrLabels.TryGetValue(entry, out string? existing) && existing != null) + { + addrLabels[entry] = string.Join(",", existing.Split(',').Append(label).Distinct()); + } + else + { + addrLabels[entry] = label; + } + } + + void Walk(int entry) + { + var topAddr = entry; + + while (true) + { + var needClearance = false; + + if (addrAnnotated.TryGetValue(entry, out var annotHere)) + { + if (annotHere is IndeterminateContent) + { + needClearance = true; + } + else + { + return; + } + } + + var newPos = 16 + 2 * entry; + if (read.Length <= newPos) + { + // bug? + break; + } + + read.Position = newPos; + var codeWord = read.ReadUInt16(); + + var desc = BdxInstructionDescs.FindByCode(codeWord); + if (desc == null) + { + break; + } + + var code = new CodeContent(desc); + + if (needClearance) + { + var overrideDetected = false; + int x = 1; + for (; x < desc.CodeSize; x++) + { + if (true + && addrAnnotated.TryGetValue(entry + x, out var codePartHit) + && codePartHit is not IndeterminateContent + ) + { + overrideDetected = true; + break; + } + + if (addrLabels.TryGetValue(entry + x, out var codePartLabels)) + { + overrideDetected = true; + break; + } + } + if (overrideDetected) + { + addrComments[entry] = $"Although L{entry + x} is in IndeterminateContent area (L{entry} to L{entry + desc.CodeSize - 1}), there is a collision with text data? Maybe codeRevealer problem. Possibly need more better bottom text pool detection."; + break; + } + } + + addrAnnotated[entry] = code; + + if (desc.NeverReturn || desc.IsGosubRet) + { + break; + } + + if (desc.Args != null) + { + var parsedArgs = new List(); + + foreach (var arg in desc.Args) + { + var parsedArg = new ParsedArg(); + + int argValue; + if (arg.Type == BdxInstructionDesc.ArgType.Imm16) + { + argValue = read.ReadInt16(); + } + else if (arg.Type == BdxInstructionDesc.ArgType.Imm32) + { + argValue = read.ReadInt32(); + } + else if (arg.Type == BdxInstructionDesc.ArgType.Float32) + { + argValue = read.ReadInt32(); + parsedArg.PreferFloat32 = true; + } + else + { + argValue = new BdxInstruction(codeWord).Ssub; + } + + + if (arg.IsRelative) + { + var nextEntry = (ushort)(entry + (desc.CodeSize) + argValue); + + parsedArg.Value = nextEntry; + parsedArg.AddrRef = nextEntry; + + if (desc.IsJump || desc.IsGosub) + { + var newLabel = $"L{nextEntry}"; + parsedArg.Label = newLabel; + AddAddr(nextEntry, newLabel); + Walk(nextEntry); + } + } + else if (arg.AiPos) + { + parsedArg.Value = (ushort)argValue; + + var newLabel = $"L{(ushort)argValue}"; + parsedArg.Label = newLabel; + AddAddr((ushort)argValue, newLabel); + } + else if (arg.WorkPos) + { + parsedArg.Value = (ushort)argValue; + + var newLabel = $"W{(ushort)argValue}"; + parsedArg.Label = newLabel; + workLabels[(ushort)argValue] = newLabel; + } + else + { + parsedArg.Value = argValue; + } + + parsedArgs.Add(parsedArg); + } + + if (desc.IsSyscall) + { + var tableIdx = new BdxInstruction(codeWord).Ssub; + var trapIdx = parsedArgs[1].Value; + var trap = BdxTraps.GetTraps() + .FirstOrDefault(it => it.TableIndex == tableIdx && it.TrapIndex == trapIdx); + addrCodeComments[entry] = (trap != null) + ? $"{trap.Name} ({trap.Flags & 65535} in, {(((trap.Flags & 0x40000000) != 0) ? 1 : 0)} out)" + : "?"; + } + + code.Args = parsedArgs.ToArray(); + } + + for (int x = 1; x < desc.CodeSize; x++) + { + addrAnnotated[entry + x] = CodeArg.Value; + } + + if (desc.IsJump && !desc.IsJumpConditional) + { + break; + } + + entry += desc.CodeSize; + } + } + + var knownItems = BdxTrigger.GetKnownItems() + .ToDictionary( + one => one.Key, + one => one.Label + ); + + string GetTriggerLabelFor(int key) + { + if (knownItems.TryGetValue(key, out string? label) && label != null) + { + return label; + } + else + { + return $"TR{key}"; + } + } + + foreach (var trigger in triggers) + { + AddAddr(trigger.Addr, GetTriggerLabelFor(trigger.Key)); + Walk(trigger.Addr); + } + + foreach (var codeLabel in codeLabels ?? new int[0]) + { + AddAddr(codeLabel, $"L{codeLabel}"); + Walk(codeLabel); + } + + Header.Triggers = triggers + .Select( + it => new Trigger + { + Addr = it.Addr.ToString(), + Key = it.Key, + } + ) + .ToArray(); + + var revealed = new List(); + + for (int addr = firstAddr; addr < read.Length / 2 - 8; addr++) + { + if (addrAnnotated.ContainsKey(addr)) + { + continue; + } + + read.Position = 16 + 2 * addr; + var dataWord = read.ReadUInt16(); + + addrAnnotated[addr] = new IndeterminateContent(dataWord); + + if (addrAnnotated.TryGetValue(addr - 1, out var prevAnnot) && prevAnnot is not IndeterminateContent prevWord) + { + AddAddr(addr, $"D{addr}"); + } + } + + var firstTextPoolAddr = FindFirstTextPoolAddr(addrAnnotated); + + { + // resolve strings + + var dataList = addrAnnotated + .Where(pair => pair.Value is IndeterminateContent) + .ToList(); + + while (dataList.Any()) + { + var baseAddr = dataList[0].Key; + + if (firstTextPoolAddr <= baseAddr) + { + int numContinuous = 1; + while (true) + { + if (true + && numContinuous < dataList.Count + && dataList[numContinuous].Value is IndeterminateContent + && dataList[numContinuous].Key == baseAddr + numContinuous + ) + { + numContinuous += 1; + continue; + } + else + { + break; + } + } + + if (numContinuous >= 2) + { + var bytes = new byte[2 * numContinuous]; + for (int x = 0; x < numContinuous; x++) + { + var word = ((IndeterminateContent)dataList[x].Value).Word; + bytes[2 * x + 0] = (byte)(word); + bytes[2 * x + 1] = (byte)(word >> 8); + } + + static (int numValidWords, IAnnotation? annot) TryToDecode(byte[] bytes) + { + for (int x = 0; x < bytes.Length; x++) + { + if (bytes[x] == 0) + { + if (x < 1) + { + // The length is not enough to decide this is a text. + break; + } + if ((x & 1) == 0) + { + x++; + } + + // This is a valid text + return ( + (x + 1) / 2, + new TextContent(Encoding.ASCII.GetString(bytes, 0, x + 1)) + ); + } + if (IsValidChar(bytes[x])) + { + // A valid char + continue; + } + else + { + // An invalid char + break; + } + } + + // No valid text found + return (0, null); + } + + var (numValidWords, annot) = TryToDecode(bytes); + + if (1 <= numValidWords && annot != null) + { + dataList.RemoveRange(0, numValidWords); + for (int x = 0; x < numValidWords; x++) + { + if (x == 0) + { + AddAddr(baseAddr + x, $"TXT{baseAddr + x}"); + addrAnnotated[baseAddr + x] = annot; + } + else + { + addrAnnotated[baseAddr + x] = TextPart.Value; + } + } + continue; + } + } + } + + dataList.RemoveAt(0); + } + } + + if (codeRevealer) + { + while (true) + { + var firstIndeterminate = addrAnnotated + .FirstOrDefault( + pair => true + && pair.Key < firstTextPoolAddr + && pair.Value is IndeterminateContent + ); + + // KeyValuePair is struct (value) and it cannot be null. While Value is null-able instead. + + if (firstIndeterminate.Value == null) + { + break; + } + + var addr = firstIndeterminate.Key; + + addrAnnotated.Remove(firstIndeterminate.Key); + + AddAddr(addr, $"L{addr}"); + Walk(addr); + + revealed.Add(addr); + } + } + + if (codeRevealerLabeling) + { + foreach (var pair in addrAnnotated.ToArray()) + { + if (pair.Value is CodeContent content) + { + if (content.Desc.CodeRevealerLabeling + && content.Args.Length == 1 + && content.Args[0] is ParsedArg arg + && arg.Label == null + && arg.Value != 0 + ) + { + if (revealed.Contains(arg.Value)) + { + content.Args[0].Label = $"L{arg.Value}"; + content.Args[0].AddrRef = arg.Value; + } + } + } + } + } + + { + foreach (var pair in addrAnnotated.ToArray()) + { + if (pair.Value is IndeterminateContent indeterminate) + { + addrAnnotated[pair.Key] = new WordContent(indeterminate.Word); + } + } + } + + topComment = string.Join("\n" + , $"{nameof(codeLabels)}: {((codeLabels != null) ? string.Join(" ", codeLabels.Select(it => $"-l {it}")) : "")}" + , $"{nameof(codeRevealer)}: {((revealed != null) ? string.Join(" ", revealed.Select(it => $"-l {it}")) : "")}" + ); + + AddressAnnotated = addrAnnotated; + AddressLabels = addrLabels; + AddressComments = addrComments; + AddressCodeComments = addrCodeComments; + WorkLabels = workLabels; + TopComment = topComment; + } + + private static int FindFirstTextPoolAddr(IDictionary addrAnnotated) + { + var firstTextPoolAddr = int.MaxValue; + + var dataPairs = addrAnnotated + .OrderByDescending(pair => pair.Key) + .Where(pair => pair.Value is IndeterminateContent); + + if (dataPairs.Any()) + { + foreach (var pair in dataPairs) + { + var content = (IndeterminateContent)pair.Value; + var bytes = new byte[] { (byte)(content.Word), (byte)(content.Word >> 8) }; + if (true + && (bytes[0] == 0 || IsValidChar(bytes[0])) + && (bytes[1] == 0 || IsValidChar(bytes[1])) + ) + { + firstTextPoolAddr = pair.Key; + continue; + } + else + { + break; + } + } + } + + return firstTextPoolAddr; + } + + private static bool IsValidChar(byte v) + { + var chr = (char)v; + return 32 <= chr && chr <= 126; + } + + public interface IAnnotation + { + + } + + public class ParsedArg + { + public string? Label { get; set; } + public int Value { get; set; } + public int? AddrRef { get; set; } + public bool PreferFloat32 { get; set; } + } + + public sealed class CodeContent : IAnnotation + { + public CodeContent(BdxInstructionDesc desc) + { + Desc = desc; + } + + public BdxInstructionDesc Desc { get; set; } + public ParsedArg[] Args { get; set; } = new ParsedArg[0]; + + public override string ToString() => Desc?.ToString() ?? "?"; + } + + public sealed class CodeArg : IAnnotation + { + private CodeArg() + { + + } + + public override string ToString() => $"CodeArg"; + + public static readonly CodeArg Value = new CodeArg(); + } + + public sealed class IndeterminateContent : IAnnotation + { + public ushort Word { get; set; } + + public IndeterminateContent(ushort word) + { + Word = word; + } + + public override string ToString() => $"Indeterminate"; + } + + public sealed class WordContent : IAnnotation + { + public ushort Word { get; set; } + + public WordContent(ushort word) + { + Word = word; + } + + public override string ToString() => $"Word"; + } + + public sealed class TextContent : IAnnotation + { + public string Text { get; set; } + + public TextContent(string text) + { + Text = text; + } + + public override string ToString() => $"Text: {Text}"; + } + + public sealed class TextPart : IAnnotation + { + private TextPart() + { + + } + + public override string ToString() => $"TextPart"; + + public static readonly TextPart Value = new TextPart(); + } + + public static class TextFormatter + { + private static ISerializer ser = new SerializerBuilder() + .Build(); + + public static string Format(BdxDecoder decoder) + { + if (decoder.Header.IsEmpty) + { + var empty = new StringWriter(); + empty.WriteLine("---"); + empty.WriteLine("---"); + return empty.ToString(); + } + + var writer = new StringWriter(); + + writer.WriteLine("---"); + writer.WriteLine( + ser + .Serialize( + new BdxHeader + { + Name = decoder.Header.Name, + WorkSize = decoder.Header.WorkSize, + StackSize = decoder.Header.StackSize, + TempSize = decoder.Header.TempSize, + Triggers = decoder.Header.Triggers + .Select( + it => + { + if (int.TryParse(it.Addr, out int addr)) + { + decoder.AddressLabels.TryGetValue(addr, out string? labels); + + return new Trigger + { + Key = it.Key, + Addr = ((labels != null) ? labels.Split(',')[0] : it.Addr), + }; + } + else + { + return new Trigger + { + Key = it.Key, + Addr = it.Addr, + }; + } + } + ) + .ToArray(), + } + ) + ); + writer.WriteLine("---"); + + { + if (!string.IsNullOrEmpty(decoder.TopComment)) + { + foreach (var comment in decoder.TopComment.Split('\n')) + { + writer.WriteLine($"; {comment}"); + } + } + } + + writer.WriteLine(" section .text"); + + foreach (var line in decoder.AddressAnnotated) + { + if (decoder.AddressComments.TryGetValue(line.Key, out var commentBody) && commentBody != null) + { + foreach (var comment in commentBody.Replace("\r\n", "\n").Split('\n')) + { + writer.WriteLine($" ; {comment}"); + } + } + + if (decoder.AddressLabels.TryGetValue(line.Key, out string? labels) && labels != null) + { + foreach (var label in labels.Split(',')) + { + writer.WriteLine($"{label}:"); + } + } + + if (line.Value is CodeContent code) + { + if (decoder.AddressCodeComments.TryGetValue(line.Key, out string? commentPart)) + { + commentPart = " ; " + commentPart; + } + + writer.WriteLine($" {code.Desc.Name} {string.Join(", ", code.Args.Select(FormatArg))}{commentPart}"); + } + else if (line.Value is WordContent data) + { + writer.WriteLine($" dw 0x{data.Word:X4}"); + } + else if (line.Value is TextContent text) + { + writer.WriteLine($" db {MakeDbBody(text.Text)}"); + } + } + + if (decoder.Header.WorkSize != 0) + { + writer.WriteLine(); + writer.WriteLine(" section .bss"); + if (decoder.WorkLabels.Any()) + { + int lastPos = 0; + foreach (var workLabel in decoder.WorkLabels) + { + var thisPos = workLabel.Key; + if (lastPos < thisPos) + { + writer.WriteLine($" resb {thisPos - lastPos}"); + lastPos = thisPos; + } + writer.WriteLine($"{workLabel.Value}:"); + } + { + var thisPos = decoder.Header.WorkSize; + if (lastPos < thisPos) + { + writer.WriteLine($" resb {thisPos - lastPos}"); + } + } + } + else + { + writer.WriteLine($" resb {decoder.Header.WorkSize}"); + } + } + + return writer.ToString(); + } + + private static string FormatArg(ParsedArg arg) + { + if (arg.Label != null) + { + return $"{arg.Label}"; //offset + } + else if (arg.PreferFloat32) + { + return BitConverter.ToSingle(BitConverter.GetBytes(arg.Value)).ToString("R", invariantNumberFormat); + } + else + { + return arg.Value.ToString(); + } + } + + private static string MakeDbBody(string text) + { + var bytes = Encoding.ASCII.GetBytes(text); + + bool IsPrint(byte b) => 0x20 <= b && b <= 0x7E && b != 0x27 && b != 0x5C; + + var tokens = new List(); + + for (int x = 0, cx = bytes.Length; x < cx;) + { + { + int from = x; + while (x < cx && IsPrint(bytes[x])) + { + x++; + } + if (from != x) + { + tokens.Add("'" + Encoding.ASCII.GetString(bytes, from, x - from) + "'"); + } + } + + { + int from = x; + while (x < cx && !IsPrint(bytes[x])) + { + x++; + } + if (from != x) + { + for (; from < x; from++) + { + tokens.Add(bytes[from].ToString()); + } + } + } + } + + return string.Join(",", tokens); + } + } + + private static readonly IFormatProvider invariantNumberFormat = CultureInfo.InvariantCulture.NumberFormat; + } +} diff --git a/OpenKh.Kh2/Bdx/Utils/BdxEncoder.cs b/OpenKh.Kh2/Bdx/Utils/BdxEncoder.cs new file mode 100644 index 000000000..6dec2c14e --- /dev/null +++ b/OpenKh.Kh2/Bdx/Utils/BdxEncoder.cs @@ -0,0 +1,867 @@ +using Antlr4.Runtime; +using Antlr4.Runtime.Tree; +using NLog; +using OpenKh.Kh2.Bdx.Models; +using System; +using System.Globalization; +using System.IO; +using System.Text; +using Xe.BinaryMapper; +using static BdxScriptParser; +using static OpenKh.Kh2.Bdx.Models.BdxHeader; +using static OpenKh.Kh2.Bdx.Models.BdxInstructionDesc; +namespace OpenKh.Kh2.Bdx.Utils +{ + public class BdxEncoder + { + public byte[] Content { get; } = new byte[0]; + + public BdxEncoder( + BdxHeader header, + string script, + string scriptName, + Func loadScript + ) + { + var logger = LogManager.GetLogger("BdxEncoder"); + + if (string.IsNullOrWhiteSpace(script) && header.IsEmpty) + { + return; + } + + var labels = new SortedDictionary(); + + var instructionDict = new (string, BdxInstructionDesc)[0] + .Concat( + BdxInstructionDescs.GetDescs() + .Select(desc => (desc.Name, desc)) + ) + .Concat( + BdxInstructionDescs.GetDescs() + .SelectMany( + desc => desc.OldNames + .Select( + oldName => (oldName, desc) + ) + ) + ) + .GroupBy(pair => pair.Item1) + .Select(it => it.First()) + .ToDictionary(it => it.Item1, it => it.Item2); + + Func ResolveInt32Factory( + Func stringToInt32, + Func, int> labelToInt32 + ) + { + return (ArgContext argContext, CompilePass pass) => + { + if (pass.First) + { + return 0; + } + else + { + if (argContext.numberdata() is NumberdataContext numberdata) + { + return stringToInt32(numberdata); + } + else if (argContext.id() is IdContext id) + { + var name = id.GetText(); + return labelToInt32(name, () => id.Start); + } + else + { + throw new InvalidDataException($"{FormatLineColumn(argContext.Start)} Unknown arg!"); + } + } + }; + } + + int LabelToInt32Converter(string name, Func getStart) + { + if (!labels.TryGetValue(name, out var any) || any == null) + { + throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} not found")); + } + + if (any is CodeLabel label) + { + return label.PC; + } + else if (any is ConstLabel constLabel) + { + return constLabel.Value; + } + else if (any is BssLabel bssLabel) + { + return bssLabel.ByteOffset; + } + + throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} cannot be used here")); + } + + float LabelToFloat32Converter(string name, Func getStart) + { + if (!labels.TryGetValue(name, out var any) || any == null) + { + throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} not found")); + } + + if (any is ConstLabel constLabel) + { + return constLabel.Value; + } + + throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} cannot be used here")); + } + + Func ResolveFloat32Factory( + Func, float> labelToFloat32 + ) + { + return (ArgContext argContext, CompilePass pass) => + { + if (pass.First) + { + return 0; + } + else + { + if (argContext.numberdata() is NumberdataContext numberdata) + { + return float.Parse(numberdata.GetText(), invariantNumberFormat); + } + else if (argContext.id() is IdContext id) + { + var name = id.GetText(); + return labelToFloat32(name, () => id.Start); + } + else + { + throw new InvalidDataException($"{FormatLineColumn(argContext.Start)} Unknown arg!"); + } + } + }; + } + + var resolveInt32ForIntTrigger = ResolveInt32Factory( + stringToInt32: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), + labelToInt32: LabelToInt32Converter + ); + + var resolveInt32ForSsub = ResolveInt32Factory( + stringToInt32: Helper.AllowedRangeFactory( + factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), + min: -2048, + max: 4095 + ), + labelToInt32: LabelToInt32Converter + ); + + var resolveInt32ForRelativeImm16 = ResolveInt32Factory( + stringToInt32: Helper.AllowedRangeFactory( + factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), + min: short.MinValue, + max: short.MaxValue + ), + labelToInt32: LabelToInt32Converter + ); + + var resolveInt32ForValueImm16 = ResolveInt32Factory( + stringToInt32: Helper.AllowedRangeFactory( + factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), + min: short.MinValue, + max: ushort.MaxValue + ), + labelToInt32: LabelToInt32Converter + ); + + var resolveInt32ForRelativeImm32 = ResolveInt32Factory( + stringToInt32: Helper.AllowedRangeFactory( + factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), + min: int.MinValue, + max: int.MaxValue + ), + labelToInt32: LabelToInt32Converter + ); + + var resolveInt32ForValueImm32 = ResolveInt32Factory( + stringToInt32: Helper.AllowedRangeFactory( + factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: true), + min: int.MinValue, + max: int.MaxValue + ), + labelToInt32: LabelToInt32Converter + ); + + var resolveFloat32ForValueFloat32 = ResolveFloat32Factory( + labelToFloat32: LabelToFloat32Converter + ); + + int lastBssBytes = 0; + + var compilePasses = new CompilePass[] { + new CompilePass(Index: 0, First: true, Last: false), // resolve code placement and labels offset + new CompilePass(Index: 1, First: false, Last: false), // resolve equ (const) + new CompilePass(Index: 2, First: false, Last: true), // compile + }; + + foreach (var pass in compilePasses) + { + var words = new MemoryStream(); + + void WordAlignment() + { + if ((words.Length & 1) != 0) + { + words.WriteByte(0); + } + } + + BinaryMapping.WriteObject(words, header); + + if (!pass.First) + { + if (header.WorkSize < lastBssBytes) + { + logger.Warn($"WorkSize {header.WorkSize} is extended to {lastBssBytes}"); + + words.Position = 16; + words.Write(BitConverter.GetBytes(lastBssBytes)); + words.Seek(0, SeekOrigin.End); + } + } + + foreach (var intTrigger in header.Triggers + .Select( + item => + { + return new IntTrigger + { + Key = item.Key, + Addr = resolveInt32ForIntTrigger(Helper.ParseAsArg(item.Addr), pass), + }; + } + ) + ) + { + BinaryMapping.WriteObject(words, intTrigger); + } + + BinaryMapping.WriteObject(words, new IntTrigger { Addr = 0, Key = 0, }); + + var codeSeg = new CodeSegment(words); + var bssSeg = new BssSegment(); + ISegment segment = codeSeg; + + void AddLabel(string labelName, ILabel labelValue, Func getTokenPos) + { + if (pass.First) + { + if (!labels.TryAdd(labelName, labelValue)) + { + throw new InvalidDataException($"{FormatLineColumn(getTokenPos().Start)} Redefine of label {labelName}"); + } + } + else + { + labels[labelName] = labelValue; + } + } + + void WalkStatements(ProgContext root) + { + foreach (var st in root.statement()) + { + //var pc = (int)(words.Length / 2 - 8); + + if (st.section() is SectionContext section) + { + var id = section.section_id(); + var sectionName = id.GetText(); + if (sectionName == ".text") + { + segment = codeSeg; + } + else if (sectionName == ".bss") + { + segment = bssSeg; + } + else + { + throw new InvalidDataException($"{FormatLineColumn(id.Start)} Unknown section name {sectionName}"); + } + continue; + } + else if (st.equ() is EquContext equ) + { + var intValue = Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false).Invoke(equ.numberdata()); + + AddLabel( + equ.name.GetText(), + new ConstLabel { Value = intValue, }, + () => equ.name + ); + } + + if (st.label() is LabelContext label) + { + WordAlignment(); + var id = label.id(); + AddLabel( + id.GetText(), + segment.GetLabel(), + () => id + ); + } + + if (st.order() is OrderContext order) + { + if (order.db() is DbContext db) + { + foreach (var byteData in db.bytedata()) + { + if (byteData.numberdata() is NumberdataContext numberData) + { + segment.WriteByte(Helper.GetAsByte(numberData)); + } + else if (byteData.StringData() is ITerminalNode stringData) + { + var text = stringData.GetText(); + var bytes = Encoding.ASCII.GetBytes(text.Substring(1, text.Length - 2)); + segment.Write(bytes); + } + else if (byteData.id() is IdContext id) + { + var value = pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start); + segment.WriteByte((byte)value); + } + else + { + throw new InvalidDataException($"{FormatLineColumn(byteData.Start)} Unknown byte data!"); + } + } + } + else if (order.resb() is ResbContext resb) + { + if (resb.numberdata() is NumberdataContext numberdata) + { + var numBytes = Helper.GetAsInt32(numberdata); + segment.AllocateBytes(numBytes); + } + else if (resb.id() is IdContext id) + { + var numBytes = LimitInt32Range( + pass.First + ? 0 + : LabelToInt32Converter(id.GetText(), () => id.Start), + min: int.MinValue, + max: int.MaxValue, + getStart: () => id.Start + ); + segment.AllocateBytes(numBytes); + } + } + else if (order.dw() is DwContext dw) + { + WordAlignment(); + foreach (var wordData in dw.worddata()) + { + if (wordData.numberdata() is NumberdataContext numberdata) + { + segment.Write(BitConverter.GetBytes(Helper.GetAsUInt16(numberdata))); + } + else if (wordData.id() is IdContext id) + { + var value = (ushort)LimitInt32Range(pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start), min: -127, max: 255, () => id.Start); + segment.Write(BitConverter.GetBytes(value)); + } + else + { + throw new InvalidDataException($"{FormatLineColumn(wordData.Start)} Unknown word data!"); + } + } + } + else if (order.resw() is ReswContext resw) + { + if (resw.numberdata() is NumberdataContext numberdata) + { + var numBytes = 2 * Helper.GetAsInt32(numberdata); + segment.AllocateBytes(numBytes); + } + else if (resw.id() is IdContext id) + { + var numBytes = 2 * LimitInt32Range(pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start), min: int.MinValue, max: int.MaxValue, () => id.Start); + segment.AllocateBytes(numBytes); + } + } + else if (order.instruction() is InstructionContext instr) + { + var name = instr.id().GetText(); + if (!instructionDict.TryGetValue(name, out var desc)) + { + throw new InvalidDataException($"{FormatLineColumn(instr.Start)} Unknown instruction '{name}'!"); + } + + if (segment is not CodeSegment thisSegment) + { + throw new InvalidDataException($"{FormatLineColumn(instr.Start)} This must be placed at `section .text`!"); + } + + var pc = thisSegment.PC; + + var word = new BdxInstruction(desc.Code); + + var args = desc.Args ?? new Arg[0]; + + var instrArgs = instr.arg(); + + for (int x = 0; x < args.Length; x++) + { + if (args[x].Type == ArgType.Ssub) + { + word.Ssub = (ushort)resolveInt32ForSsub(instrArgs[x], pass); + } + } + + segment.Write(BitConverter.GetBytes(word.Code)); + + for (int x = 0; x < args.Length; x++) + { + if (args[x].Type == ArgType.Imm16) + { + if (args[x].IsRelative) + { + var baseOffset = -pc - desc.CodeSize; + + segment.Write(BitConverter.GetBytes((ushort)(baseOffset + resolveInt32ForRelativeImm16(instrArgs[x], pass)))); + } + else + { + segment.Write(BitConverter.GetBytes((ushort)(resolveInt32ForValueImm16(instrArgs[x], pass)))); + } + } + else if (args[x].Type == ArgType.Imm32) + { + if (args[x].IsRelative) + { + var baseOffset = -pc - desc.CodeSize; + + segment.Write(BitConverter.GetBytes((uint)(baseOffset + resolveInt32ForRelativeImm32(instrArgs[x], pass)))); + } + else + { + segment.Write(BitConverter.GetBytes((uint)(resolveInt32ForValueImm32(instrArgs[x], pass)))); + } + } + else if (args[x].Type == ArgType.Float32) + { + { + segment.Write(BitConverter.GetBytes((float)(resolveFloat32ForValueFloat32(instrArgs[x], pass)))); + } + } + } + } + else if (order.include() is IncludeContext include) + { + var scriptName = include.FileName().GetText().Trim('"'); + var subScript = loadScript(scriptName); + + var stream = FromString(subScript, scriptName); + var lexer = new BdxScriptLexer(stream); + var tokens = new CommonTokenStream(lexer); + var parser = new BdxScriptParser(tokens); + var subRoot = parser.prog(); + + WalkStatements(subRoot); + } + else + { + //throw new InvalidDataException("Unknown statement!"); + // maybe NL + } + } + } + } + + { + var stream = FromString(script, scriptName); + var lexer = new BdxScriptLexer(stream); + var tokens = new CommonTokenStream(lexer); + var parser = new BdxScriptParser(tokens); + var root = parser.prog(); + + WalkStatements(root); + } + + WordAlignment(); + + lastBssBytes = bssSeg.numBytes; + + if (pass.Last) + { + Content = words.ToArray(); + } + } + } + + private int LimitInt32Range(int value, int min, int max, Func getStart) + { + if (min <= value && value <= max) + { + return value; + } + + throw new ArgumentOutOfRangeException(FormatErrorMessage(getStart(), $"Integer must be in range of from {min} to {max}")); + } + + private static ICharStream FromString(string script, string sourceName) + { + var stream = CharStreams.fromString(script); + if (stream is CodePointCharStream charStream) + { + charStream.name = sourceName; + } + return stream; + } + + private static string FormatLineColumn(IToken start) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column}"; + + private static string FormatErrorMessage(IToken start, IFormattable text) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column} {text}"; + private static string FormatErrorMessage(IToken start, string text) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column} {text}"; + + private static class Helper + { + internal static int GetAsInt32(NumberdataContext numberData) + { + var decimalNumber = numberData.decimalnumber; + if (decimalNumber != null) + { + var body = decimalNumber.Text; + + if (uint.TryParse(body, out uint uintVal)) + { + return (int)uintVal; + } + else if (int.TryParse(body, out int intVal)) + { + return intVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); + } + } + else if (numberData.hexnumber is IToken hexNumber) + { + var body = hexNumber.Text; + + if (uint.TryParse(body, NumberStyles.HexNumber, null, out uint uintVal)) + { + return (int)uintVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); + } + } + else if (numberData.floatnumber is IToken) + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); + } + else + { + throw new InvalidDataException($"{FormatLineColumn(numberData.Start)} Unknown number data!"); + } + } + + internal static ArgContext ParseAsArg(string addr) + { + var stream = CharStreams.fromString(addr); + var lexer = new BdxScriptLexer(stream); + var tokens = new CommonTokenStream(lexer); + var parser = new BdxScriptParser(tokens); + + return parser.arg(); + } + + internal static Func NumberdataContextToInt32OrFloat32ConverterFactory() + { + return (NumberdataContext numberData) => + { + var decimalNumber = numberData.decimalnumber; + if (decimalNumber != null) + { + var body = decimalNumber.Text; + + if (uint.TryParse(body, out uint uintVal)) + { + return (int)uintVal; + } + else if (int.TryParse(body, out int intVal)) + { + return intVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); + } + } + else if (numberData.hexnumber is IToken hexNumber) + { + var body = hexNumber.Text; + + if (uint.TryParse(body, NumberStyles.HexNumber, null, out uint uintVal)) + { + return (int)uintVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); + } + } + else if (numberData.floatnumber is IToken floatnumber) + { + var body = floatnumber.Text; + + if (float.TryParse(body, NumberStyles.HexNumber, invariantNumberFormat, out float floatVal)) + { + return (float)floatVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be float32 here")); + } + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); + } + }; + } + + internal static Func NumberdataContextToInt32ConverterFactory(bool allowFloat) + { + var func = NumberdataContextToInt32OrFloat32ConverterFactory(); + + return (NumberdataContext numberData) => + { + var value = func(numberData); + + if (value is int intValue) + { + return intValue; + } + else if (value is float floatValue) + { + if (!allowFloat) + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); + } + return BitConverter.ToInt32(BitConverter.GetBytes(floatValue)); + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Numeric converter bug")); + } + }; + } + + internal static Func AllowedRangeFactory(Func factory, int min, int max) + { + return (NumberdataContext numberData) => + { + var value = factory(numberData); + if (min <= value && value <= max) + { + return value; + } + + throw new ArgumentOutOfRangeException(FormatErrorMessage(numberData.Start, $"The value must be in range of from {min} to {max}!")); + }; + } + + internal static byte GetAsByte(NumberdataContext numberData) + { + var decimalNumber = numberData.decimalnumber; + if (decimalNumber != null) + { + var body = decimalNumber.Text; + + if (byte.TryParse(body, out byte byteVal)) + { + return byteVal; + } + else if (sbyte.TryParse(body, out sbyte sbyteVal)) + { + return (byte)sbyteVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be byte here")); + } + } + else if (numberData.hexnumber is IToken hexNumber) + { + var body = hexNumber.Text; + + if (byte.TryParse(body, NumberStyles.HexNumber, null, out byte byteVal)) + { + return byteVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be byte here")); + } + } + else if (numberData.floatnumber is IToken) + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); + } + } + + internal static ushort GetAsUInt16(NumberdataContext numberData) + { + var decimalNumber = numberData.decimalnumber; + if (decimalNumber != null) + { + var body = decimalNumber.Text; + + if (ushort.TryParse(body, out ushort ushortVal)) + { + return ushortVal; + } + else if (short.TryParse(body, out short shortVal)) + { + return (byte)shortVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int16 here")); + } + } + else if (numberData.hexnumber is IToken hexNumber) + { + var body = hexNumber.Text; + + if (ushort.TryParse(body, NumberStyles.HexNumber, null, out ushort ushortVal)) + { + return ushortVal; + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int16 here")); + } + } + else if (numberData.floatnumber is IToken) + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); + } + else + { + throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); + } + } + } + + private interface ISegment + { + void Write(byte[] bytes); + void WriteByte(byte value); + void AllocateBytes(int num); + ILabel GetLabel(); + } + + private class BssSegment : ISegment + { + public int numBytes = 0; + + public void AllocateBytes(int num) + { + numBytes += num; + } + + public ILabel GetLabel() + { + return new BssLabel { ByteOffset = numBytes }; + } + + public void Write(byte[] bytes) + { + throw new InvalidOperationException("`section .bss` cannot place static data. Use `resb 4` or `resw 2` or such."); + } + + public void WriteByte(byte value) + { + throw new InvalidOperationException("`section .bss` cannot place static data. Use `resb 4` or `resw 2` or such."); + } + } + + private class CodeSegment : ISegment + { + private readonly MemoryStream _words; + + public CodeSegment(MemoryStream words) + { + _words = words; + } + + public int PC => (int)(_words.Length / 2 - 8); + + public void AllocateBytes(int num) + { + _words.Write(new byte[num]); + } + + public ILabel GetLabel() + { + return new CodeLabel { PC = PC }; + } + + public void Write(byte[] buffer) + { + _words.Write(buffer); + } + + public void WriteByte(byte value) + { + _words.WriteByte(value); + } + } + + private interface ILabel + { + + } + + private class CodeLabel : ILabel + { + public int PC { get; set; } + } + + private class BssLabel : ILabel + { + public int ByteOffset { get; set; } + } + + private class ConstLabel : ILabel + { + public int Value { get; set; } + } + + private record CompilePass( + int Index, + bool First, + bool Last + ); + + private static readonly IFormatProvider invariantNumberFormat = CultureInfo.InvariantCulture.NumberFormat; + } +} diff --git a/OpenKh.Kh2/Bdx/Utils/BdxTrigger.cs b/OpenKh.Kh2/Bdx/Utils/BdxTrigger.cs new file mode 100644 index 000000000..de823f34b --- /dev/null +++ b/OpenKh.Kh2/Bdx/Utils/BdxTrigger.cs @@ -0,0 +1,37 @@ +namespace OpenKh.Kh2.Bdx.Utils +{ + public record BdxTrigger(string Label, int Key) + { + public static BdxTrigger[] GetKnownItems() => new BdxTrigger[] + { + new BdxTrigger("OBJ_INIT", 0x0), + new BdxTrigger("OBJ_MAIN", 0x1), + new BdxTrigger("UNIT_INIT", 0x2), + new BdxTrigger("UNIT_MAIN", 0x3), + new BdxTrigger("UNIT_FINALIZER", 0x4), + new BdxTrigger("TEST", 0x5), + new BdxTrigger("OBJ_DAMAGE", 0x6), + new BdxTrigger("NEWGAME", 0x7), + new BdxTrigger("PROGRESS_CALLBACK", 0x8), + new BdxTrigger("GAMEOVER", 0x9), + new BdxTrigger("SIGNAL_CALLBACK", 0xA), + new BdxTrigger("REACTION_CALLBACK", 0xB), + new BdxTrigger("SECOND_SET_CALLBACK", 0xC), + new BdxTrigger("OBJ_FALL", 0xD), + new BdxTrigger("OBJ_LAND", 0xE), + new BdxTrigger("OBJ_ATTACK", 0xF), + new BdxTrigger("OBJ_ANMATR_EFFECT", 0x10), + new BdxTrigger("OBJ_ANMATR_CALLBACK", 0x11), + new BdxTrigger("OBJ_CHANGE_ACT", 0x12), + new BdxTrigger("OBJ_REFLECT", 0x13), + new BdxTrigger("MAGIC_START", 0x14), + new BdxTrigger("MAGIC_SHOT", 0x15), + new BdxTrigger("LIMIT_START", 0x16), + new BdxTrigger("LIMIT_CALL", 0x17), + new BdxTrigger("LIMIT_ANMATR_EFFECT", 0x18), + new BdxTrigger("LIMIT_ANMATR_CALLBACK", 0x19), + new BdxTrigger("LIMIT_ATTACK", 0x1A), + new BdxTrigger("OBJ_IK_ADJUST_CALLBACK", 0x1B), + }; + } +} diff --git a/OpenKh.Kh2/OpenKh.Kh2.csproj b/OpenKh.Kh2/OpenKh.Kh2.csproj index a7f14128d..1ef158e8a 100644 --- a/OpenKh.Kh2/OpenKh.Kh2.csproj +++ b/OpenKh.Kh2/OpenKh.Kh2.csproj @@ -1,11 +1,13 @@ - netstandard2.0;net8.0 + net8.0 latest - 25.02.10.025.02.10.025.02.10.0-local25.02.10.0-localOpenKH contributorsOpenKHCopyright (C) OpenKH https://github.com/OpenKH/OpenKh + 25.02.10.025.02.10.025.02.10.0-local25.02.10.0-localOpenKH contributorsOpenKHCopyright (C) OpenKH https://github.com/OpenKH/OpenKhenable + + diff --git a/OpenKh.Patcher/BarEntryExtractor/ProvideExtractorsService.cs b/OpenKh.Patcher/BarEntryExtractor/ProvideExtractorsService.cs index fd1456cf6..67d1c90fa 100644 --- a/OpenKh.Patcher/BarEntryExtractor/ProvideExtractorsService.cs +++ b/OpenKh.Patcher/BarEntryExtractor/ProvideExtractorsService.cs @@ -1,6 +1,6 @@ -using OpenKh.Command.Bdxio.Utils; using OpenKh.Common; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Utils; using OpenKh.Kh2.Messages; using System; using System.Collections.Generic; diff --git a/OpenKh.Patcher/OpenKh.Patcher.csproj b/OpenKh.Patcher/OpenKh.Patcher.csproj index 483687d2c..443c5966b 100644 --- a/OpenKh.Patcher/OpenKh.Patcher.csproj +++ b/OpenKh.Patcher/OpenKh.Patcher.csproj @@ -11,7 +11,6 @@ - diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 1b817b0ac..69980ec97 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -1,9 +1,10 @@ using OpenKh.Bbs; -using OpenKh.Command.Bdxio.Models; -using OpenKh.Command.Bdxio.Utils; + using OpenKh.Common; using OpenKh.Imaging; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Models; +using OpenKh.Kh2.Bdx.Utils; using OpenKh.Kh2.Messages; using System; using System.Collections.Generic; diff --git a/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs b/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs index 66b15c25b..46d281c79 100644 --- a/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs +++ b/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs @@ -1,5 +1,6 @@ using Antlr4.Runtime; using OpenKh.Command.Bdxio.Models; +using OpenKh.Kh2.Bdx.Models; using System.Collections.Generic; using System.Linq; using Xunit; diff --git a/OpenKh.Tests.Commands/BdxMassiveTests.cs b/OpenKh.Tests.Commands/BdxMassiveTests.cs index 5b8561fdd..b627c8577 100644 --- a/OpenKh.Tests.Commands/BdxMassiveTests.cs +++ b/OpenKh.Tests.Commands/BdxMassiveTests.cs @@ -1,6 +1,7 @@ using OpenKh.Command.Bdxio.Utils; using OpenKh.Common; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Utils; using System; using System.Collections.Generic; using System.IO; diff --git a/OpenKh.Tests.Commands/BdxRegressionTests.cs b/OpenKh.Tests.Commands/BdxRegressionTests.cs index e3dd47210..0dc49ee0c 100644 --- a/OpenKh.Tests.Commands/BdxRegressionTests.cs +++ b/OpenKh.Tests.Commands/BdxRegressionTests.cs @@ -1,5 +1,6 @@ using OpenKh.Command.Bdxio.Models; using OpenKh.Command.Bdxio.Utils; +using OpenKh.Kh2.Bdx.Utils; using System; using System.Collections.Generic; using System.Globalization; diff --git a/OpenKh.Tests/Patcher/PatcherTests.cs b/OpenKh.Tests/Patcher/PatcherTests.cs index 891f157e1..32c10ee64 100644 --- a/OpenKh.Tests/Patcher/PatcherTests.cs +++ b/OpenKh.Tests/Patcher/PatcherTests.cs @@ -1,8 +1,8 @@ using OpenKh.Bbs; -using OpenKh.Command.Bdxio.Utils; using OpenKh.Common; using OpenKh.Imaging; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Utils; using OpenKh.Kh2.Messages; using OpenKh.Patcher; using System; diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs index 09a39a14d..f67847941 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs @@ -1,6 +1,7 @@ using OpenKh.Command.Bdxio.Models; using OpenKh.Command.Bdxio.Utils; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Utils; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; using System; From 48ab2f576879c8d2d582dfc6cd40e97becd4e20c Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 06:58:52 +0200 Subject: [PATCH 024/175] [Mod Manager] - Fix some dependencies. --- OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj | 2 +- OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj index ea90b4b38..90a441327 100644 --- a/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj +++ b/OpenKh.Command.AnbMaker/OpenKh.Command.AnbMaker.csproj @@ -12,7 +12,7 @@ - + diff --git a/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj b/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj index 21f7c9247..85396ee7a 100644 --- a/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj +++ b/OpenKh.Command.MapGen/OpenKh.Command.MapGen.csproj @@ -14,7 +14,7 @@ - + From 293159be6876010f774038077ffd3c31eea5b756 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 06:59:24 +0200 Subject: [PATCH 025/175] [Mod Manager] - Some bugfixes on properties, mod memory implementation. --- OpenKh.Tools.ModManager/Models/MemoryModel.cs | 10 ++++ OpenKh.Tools.ModManager/Models/ModModel.cs | 3 +- .../ViewModels/MainViewModel.cs | 34 +++++++++++++- OpenKh.Tools.ModManager/Views/MainView.axaml | 2 +- .../Views/MainView.axaml.cs | 35 +++++++++----- .../Views/ModDetailsView.axaml | 3 +- .../Views/ModDetailsView.axaml.cs | 2 + .../Views/ModListView.axaml | 4 +- .../Views/ModListView.axaml.cs | 46 +++++++++++++++++++ 9 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Models/MemoryModel.cs diff --git a/OpenKh.Tools.ModManager/Models/MemoryModel.cs b/OpenKh.Tools.ModManager/Models/MemoryModel.cs new file mode 100644 index 000000000..189dfca24 --- /dev/null +++ b/OpenKh.Tools.ModManager/Models/MemoryModel.cs @@ -0,0 +1,10 @@ + +namespace OpenKh.Tools.ModManager.Models +{ + public class MemoryModel + { + public string? ModHash { get; set; } + public bool ModActive { get; set; } + public int ModIndex { get; set; } + } +} diff --git a/OpenKh.Tools.ModManager/Models/ModModel.cs b/OpenKh.Tools.ModManager/Models/ModModel.cs index bf0cb782e..8663ab82e 100644 --- a/OpenKh.Tools.ModManager/Models/ModModel.cs +++ b/OpenKh.Tools.ModManager/Models/ModModel.cs @@ -1,7 +1,9 @@ using Avalonia.Media.Imaging; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -21,6 +23,5 @@ public class ModModel public Uri? ModSource { get; set; } public Uri? ModIssues { get; set; } public int ModBehindBy { get; set; } - } } diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index b01891c92..9c122a94e 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -1,15 +1,19 @@ +using Avalonia.Interactivity; using Avalonia.Media.Imaging; using Avalonia.Platform; using CommunityToolkit.Mvvm.ComponentModel; +using LibGit2Sharp; using OpenKh.Patcher; using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.Services; using SharpYaml; using System; using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.IO; using System.Linq; -using LibGit2Sharp; +using System.Security.Cryptography; +using System.Text; namespace OpenKh.Tools.ModManager.ViewModels; @@ -30,6 +34,29 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private double _installProgress = 0; + protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + var _fetchModList = sender as ObservableCollection; + var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", CurrentConfig.TargetGame.ToString().ToLower()); + + var _fetchMemory = new ObservableCollection(); + + foreach (var _fetchMod in _fetchModList) + { + var _fetchRelativePath = Path.GetRelativePath(System.AppDomain.CurrentDomain.BaseDirectory, _fetchMod.ModPath); + + var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); + var _fetchHash = MD5.HashData(_fetchBytes); + + var _fetchHashStr = Convert.ToHexString(_fetchHash); + + _fetchMemory.Add(new MemoryModel { ModHash = _fetchHashStr, ModActive = _fetchMod.ModActive, ModIndex = _fetchModList.IndexOf(_fetchMod) }); + } + + var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); + File.WriteAllText(Path.Combine(_fetchModsPath, "mod_memory.yml"), _fetchSerial); + } + public void InitializeView() { CurrentMod = null; @@ -161,6 +188,11 @@ public void InitializeView() // Sync the list. InstalledMods = _fetchMods; + InstalledMods.CollectionChanged += OnModPropertyChanged; + + // If there is at least one mod, select the first mod. + if (InstalledMods.Count > 0) + CurrentMod = InstalledMods.First(); } public MainViewModel() diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 69eae3a75..c7f6175cf 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -57,7 +57,7 @@ - + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 109071aa8..299c4dfdf 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -5,17 +5,20 @@ using LibGit2Sharp.Handlers; using OpenKh.Tools.ModManager.Dialogs; using OpenKh.Tools.ModManager.Models; +using OpenKh.Tools.ModManager.Views; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; using SharpYaml; using System; +using System.Collections.ObjectModel; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; -using static OpenKh.Kh2.Ard.AreaDataScript; +using System.Text; +using System.Security.Cryptography; namespace OpenKh.Tools.ModManager.Views; @@ -62,10 +65,6 @@ private void OnTargetGameChanged(object? sender, SelectionChangedEventArgs e) // Re-Initialize the view. _fetchContext.InitializeView(); - - // If there is at least one mod, select the first mod. - if (_fetchContext.InstalledMods.Count > 0) - _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; } // This executes if the utmost button is clicked. @@ -171,7 +170,7 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) var _gitProcessHandler = new TransferProgressHandler((progress) => { - Dispatcher.UIThread.Post(() => + Dispatcher.UIThread.Post(() => { _progressDialog.InstallProgress.Maximum = progress.TotalObjects; _progressDialog.InstallProgress.Value = progress.ReceivedObjects; @@ -195,7 +194,8 @@ bool _localProcessHandler(int processed, int total) return false; return true; - }; + } + ; if (File.Exists(_fetchResult)) _fetchInstallResult = await InstallService.InstallLocal(_fetchModsPath, _fetchResult, _localProcessHandler); @@ -211,11 +211,22 @@ bool _localProcessHandler(int processed, int total) await _errorDialog.ShowDialog(this); } - _fetchContext.InitializeView(); - - // If there is at least one mod, select the first mod. - if (_fetchContext.InstalledMods.Count > 0) - _fetchContext.CurrentMod = _fetchContext.InstalledMods[0]; + else if (_fetchInstallResult == 0x00) + _fetchContext.InitializeView(); } } + + private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchModList = _fetchContext.InstalledMods; + + var _fetchTargetMod = e.TargetMod; + var _fetchTargetIndex = _fetchModList.IndexOf(_fetchTargetMod); + + _fetchTargetMod.ModActive = e.IsChecked; + _fetchModList[_fetchTargetIndex] = _fetchTargetMod; + + _fetchContext.CurrentMod = _fetchModList[_fetchTargetIndex]; + } } diff --git a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml index 057bdaabe..f81888977 100644 --- a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml @@ -9,8 +9,9 @@ x:Class="OpenKh.Tools.ModManager.Views.ModDetailsView"> + - + diff --git a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs index 5f113c9af..283371644 100644 --- a/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/ModDetailsView.axaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using System; namespace OpenKh.Tools.ModManager.Views { diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml index bc6e2c8ac..3b8bfdb39 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -18,12 +18,12 @@ - + - + diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs index 104ff77a5..91e08f9f9 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.ViewModels; @@ -9,9 +10,54 @@ namespace OpenKh.Tools.ModManager.Views { public partial class ModListView : ContentPage { + #region Custom Events + public class ModActiveChangedEventArgs : RoutedEventArgs + { + public ModModel TargetMod { get; } + public bool IsChecked { get; } + + public ModActiveChangedEventArgs(RoutedEvent routedEvent, ModModel targetMod, bool? isChecked) : base(routedEvent) + { + TargetMod = targetMod; + IsChecked = isChecked.HasValue ? isChecked.Value : false; + } + } + + public static readonly RoutedEvent ModActiveChangedEvent = RoutedEvent.Register(nameof(ModActiveChanged), RoutingStrategies.Direct); + + public event EventHandler ModActiveChanged + { + add => AddHandler(ModActiveChangedEvent, value); + remove => RemoveHandler(ModActiveChangedEvent, value); + } + + protected virtual void OnModActiveChanged(ModModel targetMod, bool? isChecked) + { + RoutedEventArgs args = new ModActiveChangedEventArgs(ModActiveChangedEvent, targetMod, isChecked); + RaiseEvent(args); + } + + #endregion + + private bool _canTriggerEvents = false; + public ModListView() { InitializeComponent(); + Loaded += OnViewLoaded; + } + + private void OnViewLoaded(object? sender, RoutedEventArgs e) => _canTriggerEvents = true; + + private void OnModCheckChanged(object? sender, RoutedEventArgs e) + { + var _fetchSender = sender as CheckBox; + var _fetchParent = _fetchSender.Parent.DataContext as ModModel; + + if (_fetchParent == null || !_canTriggerEvents) + return; + + OnModActiveChanged(_fetchParent, _fetchSender.IsChecked); } } } From fbed8e29b242e7774c745f3f1386a601aeed0d8d Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 07:56:34 +0200 Subject: [PATCH 026/175] [Mod Manager] - Ensure the buttons for reorder don't work on invalid mods. --- .../Converters/XamlConverters.cs | 11 ++++++++ OpenKh.Tools.ModManager/Views/MainView.axaml | 27 ++++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/OpenKh.Tools.ModManager/Converters/XamlConverters.cs b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs index e3260c9ba..750ec57a2 100644 --- a/OpenKh.Tools.ModManager/Converters/XamlConverters.cs +++ b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs @@ -15,4 +15,15 @@ public class BooleanValueMapper : IValueConverter public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) => value is bool b && b ? IfTrue : IfFalse; public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException(); } + + public class NonNullPropertyConverter : IMultiValueConverter + { + public object Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values == null || values.Count < 2) + return false; + + return values[0] != null && values[1] is bool b && b; + } + } } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index c7f6175cf..6719da317 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -5,6 +5,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:model="using:OpenKh.Tools.ModManager.Models" xmlns:view="clr-namespace:OpenKh.Tools.ModManager.Views" + xmlns:convert="using:OpenKh.Tools.ModManager.Converters" mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="850" Width="750" Height="850" MinWidth="750" MinHeight="850" @@ -19,6 +20,8 @@ M1.99902 12C1.99902 17.5228 6.47617 22 11.999 22C17.5219 22 21.999 17.5228 21.999 12C21.999 6.47715 17.5219 2 11.999 2C6.47618 2 1.99902 6.47715 1.99902 12ZM16.5297 11.7199C16.796 11.9862 16.8202 12.4028 16.6023 12.6964L16.5297 12.7805L12.5289 16.7814C12.2626 17.0477 11.8459 17.0718 11.5523 16.8539L11.4681 16.7813L7.46814 12.7797C7.1753 12.4868 7.17539 12.0119 7.46834 11.7191C7.73466 11.4529 8.15132 11.4287 8.44489 11.6466L8.529 11.7193L11.2497 14.4412L11.2494 7.74951C11.2494 7.36982 11.5315 7.05602 11.8976 7.00636L11.9994 6.99951C12.3791 6.99951 12.6929 7.28167 12.7425 7.64774L12.7494 7.74951L12.7497 14.4392L15.4691 11.7199C15.7353 11.4536 16.152 11.4294 16.4456 11.6473L16.5297 11.7199Z M22.001 12C22.001 6.47715 17.5238 2 12.001 2C6.47813 2 2.00098 6.47715 2.00098 12C2.00098 17.5228 6.47813 22 12.001 22C17.5238 22 22.001 17.5228 22.001 12ZM7.47028 12.2801C7.20401 12.0138 7.17981 11.5972 7.39766 11.3036L7.47028 11.2195L11.4711 7.21863C11.7374 6.95233 12.1541 6.92816 12.4477 7.14609L12.5319 7.21873L16.5319 11.2203C16.8247 11.5132 16.8246 11.9881 16.5317 12.2809C16.2653 12.5471 15.8487 12.5713 15.5551 12.3534L15.471 12.2807L12.7503 9.55878L12.7506 16.2505C12.7506 16.6302 12.4685 16.944 12.1024 16.9936L12.0006 17.0005C11.6209 17.0005 11.3071 16.7183 11.2575 16.3523L11.2506 16.2505L11.2503 9.56078L8.53094 12.2801C8.26467 12.5464 7.84801 12.5706 7.5544 12.3527L7.47028 12.2801Z M16.0883 6.41228C16.016 6.31886 15.9377 6.2298 15.8539 6.14569C15.5417 5.83255 15.1606 5.59666 14.741 5.45683L13.3632 5.00939C13.257 4.97196 13.165 4.90253 13.1 4.81068C13.0349 4.71883 13 4.60908 13 4.49656C13 4.38404 13.0349 4.27429 13.1 4.18244C13.165 4.09058 13.257 4.02116 13.3632 3.98372L14.741 3.53628C15.1547 3.39352 15.5299 3.15705 15.837 2.84537C16.1357 2.54224 16.3623 2.17595 16.5 1.77372L16.5114 1.73963L16.9592 0.362894C16.9967 0.256782 17.0662 0.164895 17.1581 0.0998993C17.25 0.0349035 17.3598 0 17.4724 0C17.5851 0 17.6949 0.0349035 17.7868 0.0998993C17.8787 0.164895 17.9482 0.256782 17.9857 0.362894L18.4335 1.73963C18.5727 2.15819 18.8077 2.53853 19.1198 2.85041C19.432 3.1623 19.8126 3.39715 20.2315 3.53628L21.6093 3.98372L21.6368 3.99061C21.743 4.02804 21.835 4.09747 21.9 4.18932C21.9651 4.28117 22 4.39092 22 4.50344C22 4.61596 21.9651 4.72571 21.9 4.81756C21.835 4.90942 21.743 4.97884 21.6368 5.01628L20.259 5.46372C19.8402 5.60285 19.4595 5.8377 19.1474 6.14959C18.8353 6.46147 18.6003 6.84181 18.461 7.26037L18.0132 8.63711C18.0092 8.64855 18.0048 8.65983 18 8.67093C17.9605 8.76273 17.8964 8.84212 17.8144 8.9001C17.7224 8.9651 17.6126 9 17.5 9C17.3874 9 17.2776 8.9651 17.1856 8.9001C17.0937 8.8351 17.0242 8.74322 16.9868 8.63711L16.539 7.26037C16.4378 6.95331 16.2851 6.66664 16.0883 6.41228ZM23.7829 10.2132L23.0175 9.9646C22.7848 9.8873 22.5733 9.75683 22.3999 9.58356C22.2265 9.41029 22.0959 9.199 22.0186 8.96646L21.7698 8.20161C21.749 8.14266 21.7104 8.09161 21.6593 8.0555C21.6083 8.01939 21.5473 8 21.4847 8C21.4221 8 21.3611 8.01939 21.31 8.0555C21.259 8.09161 21.2204 8.14266 21.1996 8.20161L20.9508 8.96646C20.875 9.19736 20.7467 9.40761 20.5761 9.58076C20.4055 9.75392 20.1971 9.88529 19.9672 9.9646L19.2018 10.2132C19.1428 10.234 19.0917 10.2725 19.0555 10.3236C19.0194 10.3746 19 10.4356 19 10.4981C19 10.5606 19.0194 10.6216 19.0555 10.6726C19.0917 10.7236 19.1428 10.7622 19.2018 10.783L19.9672 11.0316C20.2003 11.1093 20.412 11.2403 20.5855 11.4143C20.7589 11.5882 20.8893 11.8003 20.9661 12.0335L21.2149 12.7984C21.2357 12.8573 21.2743 12.9084 21.3254 12.9445C21.3764 12.9806 21.4374 13 21.5 13C21.5626 13 21.6236 12.9806 21.6746 12.9445C21.7257 12.9084 21.7643 12.8573 21.7851 12.7984L22.0339 12.0335C22.1113 11.801 22.2418 11.5897 22.4152 11.4164C22.5886 11.2432 22.8001 11.1127 23.0328 11.0354L23.7982 10.7868C23.8572 10.766 23.9083 10.7275 23.9445 10.6764C23.9806 10.6254 24 10.5644 24 10.5019C24 10.4394 23.9806 10.3784 23.9445 10.3274C23.9083 10.2764 23.8572 10.2378 23.7982 10.217L23.7829 10.2132ZM12 2.00073C12.957 2.00073 13.8827 2.13518 14.7591 2.38621C14.6432 2.4573 14.5193 2.51563 14.39 2.55903L13.05 2.99903C12.4247 3.22211 12.0018 3.80273 12 4.46903C12.002 5.10403 12.4159 5.74618 13 5.99903L14.4 6.45903C14.9164 6.63419 15.4143 7.02503 15.59 7.54903L16 8.99903C16.2128 9.62184 16.8014 10.047 17.46 10.049C17.6963 10.0519 17.9298 9.99699 18.14 9.88903C18.0212 10.0886 17.959 10.3168 17.96 10.549C17.9556 11.0886 18.322 11.5971 18.83 11.779C18.9034 11.8023 18.9865 11.8233 19.0738 11.8454C19.4335 11.9365 19.8642 12.0456 19.98 12.399L19.9943 12.4444C20.2303 13.1929 20.5003 14.0495 21.46 14.0495C21.5839 14.0495 21.6961 14.0353 21.7983 14.009C20.8685 18.5694 16.835 22.0007 12 22.0007C6.47715 22.0007 2 17.5236 2 12.0007C2 6.47788 6.47715 2.00073 12 2.00073ZM7.4693 12.2808L7.55342 12.3535C7.84703 12.5713 8.2637 12.5471 8.52996 12.2808L11.2493 9.56152L11.2496 16.2512L11.2565 16.353C11.3061 16.7191 11.6199 17.0012 11.9996 17.0012L12.1014 16.9944C12.4675 16.9447 12.7496 16.6309 12.7496 16.2512L12.7493 9.55952L15.47 12.2814L15.5541 12.3541C15.8477 12.572 16.2644 12.5479 16.5307 12.2817C16.8236 11.9888 16.8237 11.5139 16.5309 11.221L12.5309 7.21947L12.4468 7.14682C12.1532 6.92889 11.7364 6.95306 11.4701 7.21936L7.4693 11.2202L7.39669 11.3043C7.17883 11.5979 7.20304 12.0146 7.4693 12.2808Z + + @@ -68,13 +71,31 @@ - - - From 25883794c07954ec1c466c9fea6206d8bfc1db65 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 07:57:03 +0200 Subject: [PATCH 027/175] [Mod Manager] - Finish Mod Memory implementation, make sure invalid mods are always on the bottom, fix some bugs,. --- .../ViewModels/MainViewModel.cs | 51 +++++++++- .../Views/MainView.axaml.cs | 96 +++++++++++++++++-- 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 9c122a94e..c8d14ee14 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -8,12 +8,14 @@ using OpenKh.Tools.ModManager.Services; using SharpYaml; using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; +using Xe.BinaryMapper; namespace OpenKh.Tools.ModManager.ViewModels; @@ -43,6 +45,9 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent foreach (var _fetchMod in _fetchModList) { + if (!_fetchMod.ModValid) + continue; + var _fetchRelativePath = Path.GetRelativePath(System.AppDomain.CurrentDomain.BaseDirectory, _fetchMod.ModPath); var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); @@ -57,7 +62,7 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent File.WriteAllText(Path.Combine(_fetchModsPath, "mod_memory.yml"), _fetchSerial); } - public void InitializeView() + public void InitializeView(bool selectLast = false) { CurrentMod = null; InstalledMods = null; @@ -179,6 +184,7 @@ public void InitializeView() ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", ModIcon = new Bitmap(AssetLoader.Open(uri)), ModPath = _fetchDirectory, + ModActive = false, ModValid = false }; @@ -186,8 +192,47 @@ public void InitializeView() } } - // Sync the list. - InstalledMods = _fetchMods; + var _fetchModMemoryPath = Path.Combine(_fetchModsPath, "mod_memory.yml"); + + if (File.Exists(_fetchModMemoryPath)) + { + var _fetchRawYaml = File.ReadAllText(_fetchModMemoryPath); + var _fetchModMemory = YamlSerializer.Deserialize>(_fetchRawYaml); + + var _fetchTempList = new ModModel[_fetchMods.Count]; + + foreach (var _fetchMemory in _fetchModMemory) + { + var _fetchMod = _fetchMods.First(x => + { + var _fetchRelativePath = Path.GetRelativePath(System.AppDomain.CurrentDomain.BaseDirectory, x.ModPath); + var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); + var _fetchHash = MD5.HashData(_fetchBytes); + + return Convert.ToHexString(_fetchHash) == _fetchMemory.ModHash; + }); + + if (_fetchMod == null || !_fetchMod.ModValid) + continue; + + _fetchMod.ModActive = _fetchMemory.ModActive; + _fetchTempList[_fetchMemory.ModIndex] = _fetchMod; + + _fetchMods.Remove(_fetchMod); + } + + InstalledMods = new ObservableCollection(_fetchTempList.Where(x => x != null)); + + foreach (var _fetchMod in _fetchMods.Where(x => x.ModValid)) + InstalledMods.Add(_fetchMod); + + foreach (var _fetchMod in _fetchMods.Where(x => !x.ModValid)) + InstalledMods.Add(_fetchMod); + } + + else + InstalledMods = _fetchMods; + InstalledMods.CollectionChanged += OnModPropertyChanged; // If there is at least one mod, select the first mod. diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 299c4dfdf..0d1770831 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -1,13 +1,16 @@ using Avalonia.Controls; using Avalonia.Interactivity; +using Avalonia.Media.Imaging; +using Avalonia.Platform; using Avalonia.Threading; using LibGit2Sharp; using LibGit2Sharp.Handlers; +using OpenKh.Patcher; using OpenKh.Tools.ModManager.Dialogs; using OpenKh.Tools.ModManager.Models; -using OpenKh.Tools.ModManager.Views; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; +using OpenKh.Tools.ModManager.Views; using SharpYaml; using System; using System.Collections.ObjectModel; @@ -16,9 +19,9 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Threading.Tasks; -using System.Text; using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; namespace OpenKh.Tools.ModManager.Views; @@ -109,6 +112,9 @@ private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) if (_fetchCurrentIndex == ModList.MainList.ItemCount - 1) return; + if (!_fetchModList[_fetchCurrentIndex + 1].ModValid) + return; + _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex + 1); _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; } @@ -138,11 +144,11 @@ private async void OnRemoveClicked(object? sender, RoutedEventArgs e) // If we still have mods, select one. if (_fetchModList.Count > 0) - _fetchCurrentMod = _fetchModList.First(); + _fetchContext.CurrentMod = _fetchModList.First(); // If we do not, nullify selection. else - _fetchCurrentMod = null; + _fetchContext.CurrentMod = null; // Remove the mod directory. // This is a task for a reason. This isn't being awaited for a reason. Do not listen to IntelliSense on this one. @@ -160,6 +166,8 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) string? _fetchResult = await _installDialog.ShowDialog(this); var _fetchContext = DataContext as MainViewModel; + var _fetchModsList = _fetchContext.InstalledMods; + var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", _fetchContext.CurrentConfig.TargetGame.ToString().ToLower()); var _fetchInstallResult = 0x00; @@ -212,7 +220,83 @@ bool _localProcessHandler(int processed, int total) } else if (_fetchInstallResult == 0x00) - _fetchContext.InitializeView(); + { + var _fetchLatestDirectory = new DirectoryInfo(_fetchModsPath).GetDirectories().OrderByDescending(d => d.LastWriteTimeUtc).FirstOrDefault(); + var _fetchYamlName = Path.Combine(_fetchLatestDirectory.FullName, "mod.yml"); + + var _fetchPathGit = Path.Combine(_fetchLatestDirectory.FullName, ".git"); + var _fetchPathIcon = Path.Combine(_fetchLatestDirectory.FullName, "icon.png"); + + var _fetchMetadata = Metadata.Read(_fetchYamlName); + + if (_fetchMetadata.IsValid) + { + var _modModel = new ModModel + { + ModTitle = _fetchMetadata.Title, + ModAuthor = _fetchMetadata.OriginalAuthor, + ModDescription = _fetchMetadata.Description, + ModPath = _fetchLatestDirectory.FullName, + ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, + ModActive = true, + ModValid = true + }; + + if (Directory.Exists(_fetchPathGit)) + { + if (Repository.IsValid(_fetchPathGit)) + { + var _fetchGit = new Repository(_fetchPathGit); + + if (!_fetchGit.Info.IsHeadDetached) + { + var _fetchRemote = _fetchGit.Network.Remotes["origin"]; + + _modModel.ModSource = new Uri(_fetchRemote.Url); + _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); + + _modModel.ModPlatform = _modModel.ModSource.Host; + + Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); + + var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; + _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; + } + + _fetchGit.Dispose(); + + var _fetchGitDir = new DirectoryInfo(_fetchPathGit); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + } + + var _fetchFirstInvalid = _fetchModsList.FirstOrDefault(x => !x.ModValid); + var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : _fetchModsList.Count; + + _fetchModsList.Insert(_fetchModIndex, _modModel); + } + + else + { + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = _fetchMetadata.Title, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchLatestDirectory.FullName, + ModActive = false, + ModValid = false + }; + + _fetchModsList.Add(_modModel); + } + } } } From fb798743ac6979b118cd54366981613d70c1d51f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 30 Aug 2026 08:03:11 +0200 Subject: [PATCH 028/175] [Mod Manager] - Add separator between mods. --- OpenKh.Tools.ModManager/Views/ModListView.axaml | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenKh.Tools.ModManager/Views/ModListView.axaml b/OpenKh.Tools.ModManager/Views/ModListView.axaml index 3b8bfdb39..931e7b5f4 100644 --- a/OpenKh.Tools.ModManager/Views/ModListView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModListView.axaml @@ -33,6 +33,7 @@ + From 7ab41a10c19475f9125ceaa8df8caa975e2899c5 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 31 Aug 2026 05:07:12 +0200 Subject: [PATCH 029/175] [Mod Manager] - Preliminary fix. --- OpenKh.Command.IdxImg/EpicGamesAssets.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenKh.Command.IdxImg/EpicGamesAssets.cs b/OpenKh.Command.IdxImg/EpicGamesAssets.cs index 2f2ef6302..88ed18b8f 100644 --- a/OpenKh.Command.IdxImg/EpicGamesAssets.cs +++ b/OpenKh.Command.IdxImg/EpicGamesAssets.cs @@ -130,6 +130,8 @@ private class BuildCommand protected int OnExecute(CommandLineApplication app) { + List _gameMap = new List() { "kh1", "kh2", "Recom", "bbs", "kh3d" }; + var enabled = File.ReadAllLines(ModsFile); if (Directory.Exists(OutputFolder)) @@ -143,7 +145,7 @@ protected int OnExecute(CommandLineApplication app) var mod_folder = Path.Combine(ModsFolder, mod_name); var metadata = Metadata.Read(Path.Combine(mod_folder, "mod.yml")); Console.WriteLine($"Patching {mod_name}"); - patcher.Patch(DataFolder, OutputFolder, metadata, mod_folder, platform: 2, packageMap: map, LaunchGame: GameId); + patcher.Patch(DataFolder, OutputFolder, metadata, mod_folder, targetPlatform: 1, packageMap: map, targetGame: _gameMap.IndexOf(GameId)); } using (var packageMapWriter = new StreamWriter(Path.Combine(OutputFolder, "patch-package-map.txt"))) From e3ec08cb34af530c049f2e0c153862061c1e8051 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 31 Aug 2026 05:07:27 +0200 Subject: [PATCH 030/175] [Mod Manager] - Building both with and without extraction. --- OpenKh.Patcher/PatcherProcessor.cs | 365 +++++++----------- OpenKh.Tools.ModManager/Services/Config.cs | 6 +- .../Services/InstallService.cs | 42 ++ OpenKh.Tools.ModManager/Views/MainView.axaml | 2 +- .../Views/MainView.axaml.cs | 7 + .../Views/ModStatusView.axaml | 1 + .../Views/ModStatusView.axaml.cs | 22 ++ 7 files changed, 218 insertions(+), 227 deletions(-) diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 69980ec97..ba9ec8553 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -1,6 +1,8 @@ using OpenKh.Bbs; using OpenKh.Common; +using OpenKh.Common.Archives; +using OpenKh.Egs; using OpenKh.Imaging; using OpenKh.Kh2; using OpenKh.Kh2.Bdx.Models; @@ -11,7 +13,11 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using System.Xml.Linq; using YamlDotNet.Serialization; +using static OpenKh.Kh2.Dpd; +using static OpenKh.Patcher.PatcherProcessor; namespace OpenKh.Patcher { @@ -72,288 +78,203 @@ public void Patch(string originalAssets, string outputDir, string modFilePath) Patch(originalAssets, outputDir, metadata, modBasePath); } - List GamesList = new List() + Dictionary GameShorthand = new Dictionary() { - "kh2", - "kh1", - "bbs", - "Recom", + { 0, "kh1" }, + { 1, "kh2" }, + { 2, "Recom" }, + { 3, "bbs" }, + { 4, "ddd" } }; - /// (GameEdition) 0=OpenKH, 1=PCSX2-EX, 2=PC - /// If true, always the first package file (kh1_first, bbs_first, kh2_first or such) is selected. - /// If true, always invoke context.CopyOriginalFile - /// (GameId) "kh1", "kh2", "bbs", "Recom", "kh3d" - /// en, jp public void Patch( - string originalAssets, - string outputDir, - Metadata metadata, - string modBasePath, - int platform = 1, - bool fastMode = false, + string extractDataPath, + string buildOutputPath, + Metadata modMetadata, + string modFilesPath, + string gameFilesPath = null, + int targetPlatform = 0x00, + int targetGame = 0x01, IDictionary packageMap = null, - string LaunchGame = null, - string Language = "en", - bool Tests = false, - Dictionary collectionOptionalEnabledMods = null - ) + Dictionary collectionOptionalEnabledMods = null, + bool Tests = false) { - if (collectionOptionalEnabledMods == null) - collectionOptionalEnabledMods = new Dictionary { }; - var context = new Context(metadata, originalAssets, modBasePath, outputDir); + var _fetchCollectionMods = new Dictionary { }; + var _isExtraction = extractDataPath != null; + + var _fetchGameId = GameShorthand[targetGame]; + + if (collectionOptionalEnabledMods != null) + _fetchCollectionMods = collectionOptionalEnabledMods; + try { + var _fetchContext = new Context(modMetadata, extractDataPath, modFilesPath, buildOutputPath); - if (metadata.Assets == null) + if (!_isExtraction && targetPlatform == 0x00) + throw new InvalidDataException("PS2 Building requires extraction!"); + + if (modMetadata.Assets == null) throw new Exception("No assets found."); - if (metadata.Game != null && GamesList.Contains(metadata.Game.ToLower()) && metadata.Game.ToLower() != LaunchGame.ToLower()) + + if (modMetadata.Game != null && modMetadata.Game.ToLower() != _fetchGameId) return; - if (metadata.IsCollection && !metadata.CollectionGames.Contains(LaunchGame)) + + if (modMetadata.IsCollection && !modMetadata.CollectionGames.Contains(_fetchGameId)) return; - var exclusiveLock = new object(); - metadata.Assets.AsParallel().ForAll(assetFile => + var _protectPackageMap = new object(); + + modMetadata.Assets.AsParallel().ForAll(_fetchAsset => { - if (assetFile.Game != null && assetFile.Game != LaunchGame) + if (_fetchAsset.Game != null && _fetchAsset.Game != _fetchGameId) + return; + + if (_fetchAsset.CollectionOptional == true && (!_fetchCollectionMods.ContainsKey(_fetchAsset.Name) || !_fetchCollectionMods[_fetchAsset.Name])) return; - if (assetFile.CollectionOptional == true) - if (!collectionOptionalEnabledMods.ContainsKey(assetFile.Name)) - return; - else if (!collectionOptionalEnabledMods[assetFile.Name]) - return; - var names = new List(); - names.Add(assetFile.Name); - if (assetFile.Multi != null) - names.AddRange(assetFile.Multi.Select(x => x.Name).Where(x => !string.IsNullOrEmpty(x))); - - foreach (var name in names) + + var _fetchFileNames = new List() { _fetchAsset.Name }; + var _fetchMultiNames = _fetchAsset.Multi != null ? _fetchAsset.Multi.Select(x => x.Name) + .Where(x => !string.IsNullOrEmpty(x)) : null; + + if (_fetchMultiNames != null) + _fetchFileNames.AddRange(_fetchMultiNames); + + foreach (var _fetchName in _fetchFileNames) { - if (assetFile.Platform == null) - assetFile.Platform = "both"; + var _fetchAssetPath = _isExtraction ? Path.Combine(extractDataPath, _fetchName) : null; - if (assetFile.Required && !File.Exists(context.GetOriginalAssetPath(name))) - continue; + var _fetchPackage = _fetchAsset.Package != null ? _fetchAsset.Package : $"{_fetchGameId}_first"; - string _packageFile = null; - switch (LaunchGame) - { - case "kh1": - _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "kh1_first"; - break; - case "bbs": - _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "bbs_first"; - break; - case "Recom": - if (assetFile != null) - _packageFile = "Recom"; - break; - default: - _packageFile = assetFile.Package != null && !fastMode ? assetFile.Package : "kh2_first"; - break; - } + _fetchAsset.Platform = _fetchAsset.Platform != null ? _fetchAsset.Platform.ToLower() : "both"; + + if (_fetchAsset.Required) + if (!_isExtraction && !File.Exists(_fetchAssetPath)) + continue; + + var _fetchFileParent = _fetchName.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]).FirstOrDefault(); - if (Path.IsPathRooted(name) && !Path.GetPathRoot(name).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) + if (Path.IsPathRooted(_fetchName) && !Path.GetPathRoot(_fetchName).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) { - Log.Err($"File Path \"" + name + "\" cannot be copied as it is rooted and can cause instability! Aborting..."); - throw new PatcherException(metadata, new InvalidOperationException("Root Copy Detected!")); + Console.WriteLine($"File Path \"" + _fetchName + "\" cannot be copied as it is rooted and can cause instability! Aborting..."); + throw new PatcherException(modMetadata, new InvalidOperationException("Root Copy Detected!")); } - var dstFile = context.GetDestinationPath(name); - var packageMapLocation = ""; - var _pcFile = name.Contains("remastered") || name.Contains("raw"); + var _fetchOutputPath = Path.Combine(buildOutputPath, _fetchName); + var _fetchOutputDir = Path.GetDirectoryName(_fetchOutputPath); - // Special case for mods that want to bundle scripts or DLL files - var nonGameFile = name.StartsWith("scripts/") || name.StartsWith("scripts\\") || name.StartsWith("dlls/") || name.StartsWith("dlls\\"); + var _fetchPackageMap = ""; - var _extraPath = _pcFile ? "" : "original/"; + var _isFileRemastered = _fetchName.StartsWith("remastered"); + var _isFileRAW = _fetchName.StartsWith("raw"); - if (nonGameFile) - { - packageMapLocation = "special/" + name; - } - else - { - switch (platform) - { - default: - { - if (assetFile.Platform.ToLower() == "pc") - continue; + var _isFilePC = _isFileRemastered || _isFileRAW; + var _isFileSpecial = _fetchFileParent == "scripts" || _fetchFileParent == "dll"; - else if (_pcFile) - continue; - } - break; + if (_isFileSpecial) + _fetchPackageMap = "special/" + _fetchName; - case 2: - { - if (assetFile.Platform.ToLower() == "ps2") - continue; + else + { + if (targetPlatform == 0x00 && (_fetchAsset.Platform == "pc" || _isFilePC)) + continue; - if (assetFile.Platform.ToLower() != "ps2") - packageMapLocation = _packageFile + "/" + _extraPath + name; + else if (targetPlatform != 0x00) + { + if (_fetchAsset.Platform == "ps2") + continue; - else if (_pcFile) - packageMapLocation = _packageFile + "/" + name; - } - break; + _fetchPackageMap = Path.Combine(_fetchPackage, _isFilePC ? "" : "original/", _fetchName); } } - if (packageMap != null && packageMapLocation.Length > 0) + if (packageMap != null && _fetchPackageMap.Length > 0) { - // Protect against multiple mods having the same file where one uses forward slash and one uses backslash - lock (exclusiveLock) - { - packageMap[name.Replace("\\", "/")] = packageMapLocation; - } + lock (_protectPackageMap) + packageMap[_fetchName.Replace("\\", "/")] = _fetchPackageMap; } - context.EnsureDirectoryExists(dstFile); + if (!Directory.Exists(_fetchOutputDir)) + Directory.CreateDirectory(_fetchOutputDir); + + var _multiExists = !_isExtraction; - //Update: Prevent from copying a blank file should it not exist. - try + if (!_multiExists && _fetchAsset.Multi != null) { - bool multi = false; - if (assetFile.Multi != null) + foreach (var _multiEntry in _fetchAsset.Multi) { - foreach (var entry in assetFile.Multi) - { - if (File.Exists(context.GetOriginalAssetPath(entry.Name))) - { - multi = true; - } - } + var _fetchMultiPath = Path.Combine(extractDataPath, _multiEntry.Name); + + if (File.Exists(_fetchMultiPath)) + _multiExists = true; } - //If editing subfiles (not Method: copy and not Method: imd) make sure the original file exists OR - //If copying a file from the mod (NOT Type: internal) make sure it exists (doesnt check if the location its going normally exists) OR - //If copying a file from the users extraction (Type: internal) make sure it exists (doesnt check if the location its going normally exists) OR - //Ignore if its from a test - - var needToCopyOriginalFile = - (false - || (true - && assetFile.Method != "copy" - && assetFile.Method != "imd" - && (false - || File.Exists(context.GetOriginalAssetPath(assetFile.Name)) - || multi - ) - ) - || (true - && (assetFile.Method == "copy" || assetFile.Method == "imd") - && assetFile.Source[0].Type != "internal" - && File.Exists(context.GetSourceModAssetPath(assetFile.Source[0].Name)) - ) - || (true - && (assetFile.Method == "copy" || assetFile.Method == "imd") - && assetFile.Source[0].Type == "internal" - && (false - || File.Exists(context.GetOriginalAssetPath(assetFile.Source[0].Name)) - || multi - ) - ) - || Tests - ); + } - if (needToCopyOriginalFile) - { - context.CopyOriginalFile(name, dstFile); + var _fetchSourcePath = (_fetchAsset.Source[0].Type != "internal" && _fetchAsset.Source[0].Name != null) ? Path.Combine(modFilesPath, _fetchAsset.Source[0].Name) : null; + var _fetchSourceAssetPath = (_isExtraction && _fetchAsset.Source[0].Type == "internal" && _fetchAsset.Source[0].Name != null) ? Path.Combine(extractDataPath, _fetchAsset.Source[0].Name) : null; - using var _stream = File.Open(dstFile, FileMode.OpenOrCreate, FileAccess.ReadWrite); - PatchFile(context, assetFile, _stream); + var _shouldCopyFile = (_fetchAsset.Method != "copy" && _fetchAsset.Method != "imd" && ((_fetchAssetPath != null && File.Exists(_fetchAssetPath)) || _multiExists)) || + ((_fetchAsset.Method == "copy" || _fetchAsset.Method == "imd") && (_fetchSourcePath != null && File.Exists(_fetchSourcePath))) || + ((_fetchAsset.Method == "copy" || _fetchAsset.Method == "imd") && (_fetchSourceAssetPath != null && File.Exists(_fetchSourceAssetPath)) || _multiExists) || + Tests; - _stream.Close(); - _stream.Dispose(); + if (_shouldCopyFile) + { + if (_fetchAssetPath != null) + { + if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchAssetPath)) + File.Copy(_fetchAssetPath, _fetchOutputPath); } + else { - // The following codes are for validation purposes only. + var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : "en"); + var _fetchHeaderFiles = Directory.GetFiles(_fetchDataPath).Where(x => x.Contains(_fetchGameId) && x.EndsWith(".hed")); - List globalFilePaths = new List { ".a.fr", ".a.gr", ".a.it", ".a.sp", ".a.us", "/fr/", "/gr/", "/it/", "/sp/", "/us/" }; - List emulatorRegionPaths = new List { "/jp/", "/us/", "/it/", "/sp/", "/gr/", "/fr/", "/fm/" }; - if (assetFile.Method != "copy" && assetFile.Method != "imd") + _fetchHeaderFiles.AsParallel().ForAll(_fetchHeader => { - if (platform == 2) - { - if (Language != "jp") - { - if (!context.GetOriginalAssetPath(name).Contains(".a.fm") && !context.GetOriginalAssetPath(name).Contains("/jp/")) - { - Log.Warn("File not found: " + context.GetOriginalAssetPath(name) + " Skipping. \nPlease check your game extraction."); - } - } - else - { - if (!globalFilePaths.Any(x => context.GetOriginalAssetPath(name).Contains(x))) - { - Log.Warn("File not found: " + context.GetOriginalAssetPath(name) + " Skipping. \nPlease check your game extraction."); - } - } - } - else - { - string matchedRegion = emulatorRegionPaths.FirstOrDefault(x => name.Contains(x)); - if (matchedRegion != null) - { - string emuRegionPath = context.GetOriginalAssetPath(name.Substring(0, name.IndexOf(matchedRegion) + 3)); - if (Directory.Exists(emuRegionPath)) - { - Log.Warn("File not found: " + context.GetOriginalAssetPath(name) + " Skipping. \nPlease check your game extraction."); - } - } - } - } - else if (assetFile.Source[0].Type == "internal") - { - if (platform == 2) - { - if (Language != "jp") - { - if (!context.GetOriginalAssetPath(assetFile.Source[0].Name).Contains(".a.fm") && !context.GetOriginalAssetPath(assetFile.Source[0].Name).Contains("/jp/") && (assetFile.Multi == null || assetFile.Name == name)) - { - Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Source[0].Name) + " Skipping. \nPlease check your game extraction."); - } - } - else - { - if (!globalFilePaths.Any(x => context.GetOriginalAssetPath(assetFile.Source[0].Name).Contains(x)) && (assetFile.Multi == null || assetFile.Name == name)) - { - Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Source[0].Name) + " Skipping. \nPlease check your game extraction."); - } - } - } - else + var _fetchPackageFile = Path.ChangeExtension(_fetchHeader, "pkg"); + var _fetchRegularName = _fetchName; + + if (_isFileRAW) + _fetchRegularName = _fetchName.Replace("raw/", ""); + + if (_isFileRemastered) + _fetchRegularName = _fetchName.Replace("remastered/", ""); + + using (var _fetchHeaderStream = File.OpenRead(_fetchHeader)) { - string matchedRegion = emulatorRegionPaths.FirstOrDefault(x => assetFile.Source[0].Name.Contains(x)); - if (matchedRegion != null) + var _fetchEntries = Hed.Read(_fetchHeaderStream); + var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Egs.Helpers.ToString(x.MD5) == Egs.Helpers.CreateMD5(_fetchRegularName)); + + if (_fetchTargetEntry != null) { - string emuRegionPath = context.GetOriginalAssetPath(assetFile.Source[0].Name.Substring(0, assetFile.Source[0].Name.IndexOf(matchedRegion) + 3)); - if (Directory.Exists(emuRegionPath)) + using (var _fetchPackageStream = File.OpenRead(_fetchPackageFile)) { - Log.Warn("File not found: " + context.GetOriginalAssetPath(assetFile.Source[0].Name) + " Skipping. \nPlease check your game extraction."); + if (!_isFileRemastered) + { + var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); + var _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; + + File.WriteAllBytes(_fetchOutputPath, _fetchAssetData); + } } } } - } - else - { - Log.Warn("File not found: " + context.GetSourceModAssetPath(assetFile.Source[0].Name) + " Skipping. \nPlease check your mod install of: " + metadata.Title); - } + }); } + + using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) + PatchFile(_fetchContext, _fetchAsset, _fileStream); } - catch (IOException) { } - //This is here so the user does not have to close Mod Manager to see what the warnings were if any. Helpful especially on PC since the build window closes after build unlike emulator where it stays open during mod injection. - Log.Flush(); } }); } catch (Exception ex) { - Log.Err($"Patcher failed: {ex.Message}"); - throw new PatcherException(metadata, ex); + Console.WriteLine($"Patcher failed: {ex.Message}"); + throw new PatcherException(modMetadata, ex); } } @@ -415,8 +336,6 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea stream.SetLength(stream.Position); } - - private static void CopyFile(Context context, AssetFile assetFile, Stream stream) { if (assetFile.Source == null || assetFile.Source.Count == 0) diff --git a/OpenKh.Tools.ModManager/Services/Config.cs b/OpenKh.Tools.ModManager/Services/Config.cs index 888ef6277..261acb1b7 100644 --- a/OpenKh.Tools.ModManager/Services/Config.cs +++ b/OpenKh.Tools.ModManager/Services/Config.cs @@ -9,20 +9,20 @@ namespace OpenKh.Tools.ModManager.Services { - public enum Platform + public enum Platform : int { PCSX2, STEAM, EPIC_GAMES_STORE }; - public enum BuildType + public enum BuildType : int { PATCH, PANACEA }; - public enum Game + public enum Game : int { KINGDOM_HEARTS, KINGDOM_HEARTS_II, diff --git a/OpenKh.Tools.ModManager/Services/InstallService.cs b/OpenKh.Tools.ModManager/Services/InstallService.cs index 266303285..70e96c2ed 100644 --- a/OpenKh.Tools.ModManager/Services/InstallService.cs +++ b/OpenKh.Tools.ModManager/Services/InstallService.cs @@ -4,8 +4,10 @@ using OpenKh.Bbs; using OpenKh.Kh2; using OpenKh.Patcher; +using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.ViewModels; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -442,5 +444,45 @@ await Task.Run(() => // All is good, return success. return 0x00; } + + public static async Task Build(IEnumerable modsList, Config currentConfig) + { + var _fetchPatcher = new PatcherProcessor(); + var _fetchPackageMap = new ConcurrentDictionary(); + + var _fetchGameName = currentConfig.TargetGame.ToString().ToLower(); + var _fetchDataPath = !string.IsNullOrEmpty(currentConfig.DataPath) ? Path.Combine(currentConfig.DataPath, _fetchGameName) : null; + var _fetchBuildPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "build"); + + await Task.Run(() => + { + foreach (var _fetchMod in modsList) + { + if (!_fetchMod.ModValid || !_fetchMod.ModActive) + continue; + + var _fetchYamlPath = Path.Combine(_fetchMod.ModPath, "mod.yml"); + var _fetchMetadata = Metadata.Read(_fetchYamlPath); + + _fetchPatcher.Patch + ( + _fetchDataPath, + _fetchBuildPath, + _fetchMetadata, + _fetchMod.ModPath, + currentConfig.GamePath, + (int)currentConfig.TargetPlatform, + (int)currentConfig.TargetGame, + _fetchPackageMap + ); + } + }); + + using (var _writePackageMap = new StreamWriter(Path.Combine(_fetchBuildPath, "patch-package-map.txt"))) + foreach (var _mapEntry in _fetchPackageMap) + _writePackageMap.WriteLine(_mapEntry.Key + " $$$$ " + _mapEntry.Value); + + return 0x00; + } } } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 6719da317..df4158f43 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -106,7 +106,7 @@ - + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 0d1770831..4703525b7 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -13,6 +13,7 @@ using OpenKh.Tools.ModManager.Views; using SharpYaml; using System; +using System.Collections.Concurrent; using System.Collections.ObjectModel; using System.IO; using System.IO.Compression; @@ -313,4 +314,10 @@ private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEven _fetchContext.CurrentMod = _fetchModList[_fetchTargetIndex]; } + + private async void OnRunRequested(object? sender, RoutedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _buildResult = await InstallService.Build(_fetchContext.InstalledMods, _fetchContext.CurrentConfig); + } } diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml index ae79c08e5..d66da7c70 100644 --- a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml @@ -22,6 +22,7 @@ + - + diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs index b57b0bf1f..27798e92f 100644 --- a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs @@ -27,6 +27,20 @@ protected virtual void OnRunRequested() RaiseEvent(args); } + public static readonly RoutedEvent BuildRunRequestedEvent = RoutedEvent.Register(nameof(BuildRunRequested), RoutingStrategies.Direct); + + public event EventHandler BuildRunRequested + { + add => AddHandler(BuildRunRequestedEvent, value); + remove => RemoveHandler(BuildRunRequestedEvent, value); + } + + protected virtual void OnBuildRunRequested() + { + RoutedEventArgs args = new RoutedEventArgs(BuildRunRequestedEvent); + RaiseEvent(args); + } + #endregion public ModStatusView() @@ -34,6 +48,7 @@ public ModStatusView() InitializeComponent(); } - private void OnProcessClicked(object? sender, RoutedEventArgs e) => OnRunRequested(); + private void OnProcessBuildRun(object? sender, RoutedEventArgs e) => OnBuildRunRequested(); + private void OnProcessRun(object? sender, RoutedEventArgs e) => OnRunRequested(); } } From 0a56678e50fe3be24aec6da128011edc9949b565 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Tue, 1 Sep 2026 08:01:38 +0200 Subject: [PATCH 041/175] [Mod Manager] - Fix extractionless internal tag. --- OpenKh.Patcher/PatcherProcessor.cs | 37 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 34b682a83..f6b06a54d 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -226,6 +226,8 @@ public void Patch( if (_shouldCopyFile) { + byte[] _fetchAssetData = null; + if (_fetchAssetPath != null) { if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchAssetPath)) @@ -260,7 +262,7 @@ public void Patch( if (!_isFileRemastered) { var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); - var _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; + _fetchAssetData = _fetchTargetAsset.OriginalData; File.WriteAllBytes(_fetchOutputPath, _fetchAssetData); } @@ -271,7 +273,7 @@ public void Patch( } using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - PatchFile(_fetchContext, _fetchAsset, _fileStream); + PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); } } @@ -294,7 +296,7 @@ public void Patch( } } - private static void PatchFile(Context context, AssetFile assetFile, Stream stream) + private static void PatchFile(Context context, AssetFile assetFile, Stream stream, byte[] originalData = null) { if (assetFile == null) throw new Exception("Asset file is null."); @@ -302,7 +304,7 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea switch (assetFile.Method) { case "copy": - CopyFile(context, assetFile, stream); + CopyFile(context, assetFile, stream, originalData); break; case "bar": case "binarc": @@ -352,26 +354,39 @@ private static void PatchFile(Context context, AssetFile assetFile, Stream strea stream.SetLength(stream.Position); } - private static void CopyFile(Context context, AssetFile assetFile, Stream stream) + private static void CopyFile(Context context, AssetFile assetFile, Stream stream, byte[] originalData = null) { if (assetFile.Source == null || assetFile.Source.Count == 0) throw new Exception($"File '{assetFile.Name}' does not contain any source"); - string _fetchSource; if (assetFile.Source[0].Type == "internal") - _fetchSource = Path.Combine(context.OriginalAssetPath, assetFile.Source[0].Name); + { + if (context.OriginalAssetPath != null) + { + var _fetchSource = Path.Combine(context.OriginalAssetPath, assetFile.Source[0].Name); + + using (var _fetchStream = File.OpenRead(_fetchSource)) + _fetchStream.CopyTo(stream); + } + + else if (originalData != null) + { + using (var _memoryRead = new MemoryStream(originalData)) + _memoryRead.CopyTo(stream); + } + } else { - _fetchSource = Path.Combine(context.SourceModAssetPath, assetFile.Source[0].Name); + var _fetchSource = Path.Combine(context.SourceModAssetPath, assetFile.Source[0].Name); if (!File.Exists(_fetchSource)) throw new FileNotFoundException($"The mod does not contain the file {assetFile.Source[0].Name}", _fetchSource); - } - using (var _fetchStream = File.OpenRead(_fetchSource)) - _fetchStream.CopyTo(stream); + using (var _fetchStream = File.OpenRead(_fetchSource)) + _fetchStream.CopyTo(stream); + } } //Binarc Update: Specify by Name OR Index. Some files in BARS may have the same name but different indexes, and you want to patch a later index only. From 42037709c8eea7b7d33d8458c3161feea24b47f3 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Tue, 1 Sep 2026 08:03:55 +0200 Subject: [PATCH 042/175] [Mod Manager] - Fix Linux Pathing and SymLink [Thanks Solt11] --- OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs | 10 +++++----- OpenKh.Tools.ModManager/Views/MainView.axaml.cs | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index feefecf52..5744e2196 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -43,7 +43,7 @@ public partial class MainViewModel : ViewModelBase protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEventArgs e) { var _fetchModList = sender as ObservableCollection; - var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", _currentConfig.Frontend.TargetGame.ToString().ToLower()); + var _fetchModsPath = Path.Combine(AppContext.BaseDirectory, "mods", _currentConfig.Frontend.TargetGame.ToString().ToLower()); var _fetchMemory = new ObservableCollection(); @@ -52,7 +52,7 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent if (!_fetchMod.ModValid) continue; - var _fetchRelativePath = Path.GetRelativePath(System.AppDomain.CurrentDomain.BaseDirectory, _fetchMod.ModPath); + var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, _fetchMod.ModPath); var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); var _fetchHash = MD5.HashData(_fetchBytes); @@ -82,7 +82,7 @@ public void InitializeView(bool selectLast = false) if (CurrentConfig == null) { - var _fetchConfigFile = File.ReadAllText("config.yml"); + var _fetchConfigFile = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "config.yml")); CurrentConfig = YamlSerializer.Deserialize(_fetchConfigFile); } @@ -108,7 +108,7 @@ public void InitializeView(bool selectLast = false) // Construct the mod folder path for the specified game. var _fetchMods = new ObservableCollection(); - var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", CurrentConfig.Frontend.TargetGame.ToString().ToLower()); + var _fetchModsPath = Path.Combine(AppContext.BaseDirectory, "mods", CurrentConfig.Frontend.TargetGame.ToString().ToLower()); // For each directory that exists in the mod folder: @@ -220,7 +220,7 @@ public void InitializeView(bool selectLast = false) { var _fetchMod = _fetchMods.First(x => { - var _fetchRelativePath = Path.GetRelativePath(System.AppDomain.CurrentDomain.BaseDirectory, x.ModPath); + var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, x.ModPath); var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); var _fetchHash = MD5.HashData(_fetchBytes); diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index d6a53daf2..77053c6c5 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -57,7 +57,7 @@ protected override void OnClosing(WindowClosingEventArgs e) var _fetchSerialize = YamlSerializer.Serialize(_fetchViewModel.CurrentConfig); // Overwrite the config file. - File.WriteAllText("config.yml", _fetchSerialize); + File.WriteAllText(Path.Combine(AppContext.BaseDirectory, "config.yml"), _fetchSerialize); // We gotta still CLOSE the thing, no? base.OnClosing(e); @@ -175,7 +175,7 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) var _fetchModsList = _fetchContext.InstalledMods; var _fetchConfig = _fetchContext.CurrentConfig.Frontend; - var _fetchModsPath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "mods", _fetchConfig.TargetGame.ToString().ToLower()); + var _fetchModsPath = Path.Combine(System.AppContext.BaseDirectory, "mods", _fetchConfig.TargetGame.ToString().ToLower()); var _fetchInstallResult = 0x00; @@ -327,12 +327,12 @@ private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEven var _fetchModList = _fetchContext.InstalledMods; var _fetchConfig = _fetchContext.CurrentConfig; - var _fetchMemoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "mods", _fetchConfig.Frontend.TargetGame.ToString().ToLower(), "mod_memory.yml"); + var _fetchMemoryPath = Path.Combine(AppContext.BaseDirectory, "mods", _fetchConfig.Frontend.TargetGame.ToString().ToLower(), "mod_memory.yml"); var _fetchMemoryRAW = File.ReadAllText(_fetchMemoryPath); var _fetchMemory = YamlSerializer.Deserialize>(_fetchMemoryRAW); - var _fetchRelativePath = Path.GetRelativePath(AppDomain.CurrentDomain.BaseDirectory, e.TargetMod.ModPath); + var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, e.TargetMod.ModPath); var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); var _fetchHash = MD5.HashData(_fetchBytes); @@ -388,7 +388,7 @@ private async void OnRunRequested(object? sender, RoutedEventArgs e) if (!File.Exists(_fetchReMIXBackup)) { File.Move(_fetchReMIXFilePath, _fetchReMIXBackup); - File.Copy("resources/OpenKh.Command.Launcher.exe", _fetchReMIXFilePath, true); + File.Copy(Path.Combine(AppContext.BaseDirectory, "resources/OpenKh.Command.Launcher.exe"), _fetchReMIXFilePath, true); } } From eb239a435fcbc0262af9936c99a7258bb3deefd8 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Tue, 1 Sep 2026 08:10:09 +0200 Subject: [PATCH 043/175] [Mod Manager] - Whoopsies --- OpenKh.Tools.ModManager/Services/InstallService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKh.Tools.ModManager/Services/InstallService.cs b/OpenKh.Tools.ModManager/Services/InstallService.cs index 2e0048e60..bc9c36ecd 100644 --- a/OpenKh.Tools.ModManager/Services/InstallService.cs +++ b/OpenKh.Tools.ModManager/Services/InstallService.cs @@ -468,7 +468,7 @@ public static async Task Build(IEnumerable modsList, Config curr var _fetchGameId = PatcherProcessor.GameShorthand[(int)currentConfig.Frontend.TargetGame]; var _fetchDataPath = !string.IsNullOrEmpty(currentConfig.Frontend.DataPath) ? Path.Combine(currentConfig.Frontend.DataPath, _fetchGameId) : null; - var _fetchBuildPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "build", _fetchGameId); + var _fetchBuildPath = Path.Combine(AppContext.BaseDirectory, "build", _fetchGameId); if (!Directory.Exists(_fetchBuildPath)) Directory.CreateDirectory(_fetchBuildPath); From 945ce182d607357ad804ca94f4298864f6169cc4 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Wed, 2 Sep 2026 12:45:39 +0200 Subject: [PATCH 044/175] [Mod Manager] - PathService, some bug fixes, and ignore multi-writes to the same file by the same mod. --- OpenKh.Patcher/Metadata.cs | 13 +- OpenKh.Patcher/PatcherProcessor.cs | 2 +- .../{Services => Classes}/Config.cs | 15 +- .../Dialogs/BuildProgressDialog.axaml.cs | 4 +- .../Dialogs/ModProgressDialog.axaml.cs | 4 +- .../OpenKh.Tools.ModManager.csproj | 4 + .../{InstallService.cs => ModService.cs} | 161 +++++++++++++--- .../Services/PathService.cs | 60 ++++++ .../ViewModels/MainViewModel.cs | 106 ++++++---- .../Views/MainView.axaml.cs | 182 +++++------------- .../Wizard/WizardGameSetup.axaml | 8 + .../Wizard/WizardGameSetup.axaml.cs | 14 ++ 12 files changed, 367 insertions(+), 206 deletions(-) rename OpenKh.Tools.ModManager/{Services => Classes}/Config.cs (78%) rename OpenKh.Tools.ModManager/Services/{InstallService.cs => ModService.cs} (78%) create mode 100644 OpenKh.Tools.ModManager/Services/PathService.cs create mode 100644 OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml create mode 100644 OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index f74b816c8..6953d4d80 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Serialization; @@ -40,7 +41,17 @@ public static Metadata Read(string fileName) // Replace all back slashes as Windows supports forward slashes but Linux doesn't support back slashes. _fetchYamlRaw = _fetchYamlRaw.Replace('\\', '/'); - try { return YamlSerializer.Deserialize(_fetchYamlRaw, new YamlSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); } + try + { + var _fetchSerial = YamlSerializer.Deserialize(_fetchYamlRaw, new YamlSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + + // A single mod should not be able to patch a file more than once. + // Ignore any and all duplicates that target the same file. + var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => x.Name); + _fetchSerial.Assets = _fetchAssetUnique.ToList(); + + return _fetchSerial; + } catch (SharpYaml.YamlException ex) { diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index f6b06a54d..d0c07d1a1 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -232,7 +232,7 @@ public void Patch( { if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchAssetPath)) File.Copy(_fetchAssetPath, _fetchOutputPath); - } + } else { diff --git a/OpenKh.Tools.ModManager/Services/Config.cs b/OpenKh.Tools.ModManager/Classes/Config.cs similarity index 78% rename from OpenKh.Tools.ModManager/Services/Config.cs rename to OpenKh.Tools.ModManager/Classes/Config.cs index f0b8b27bc..75ea38650 100644 --- a/OpenKh.Tools.ModManager/Services/Config.cs +++ b/OpenKh.Tools.ModManager/Classes/Config.cs @@ -7,7 +7,7 @@ using SharpYaml.Model; using SharpYaml.Serialization; -namespace OpenKh.Tools.ModManager.Services +namespace OpenKh.Tools.ModManager.Classes { public enum Platform : int { @@ -37,9 +37,11 @@ public class Frontend public Platform TargetPlatform { get; set; } public Game TargetGame { get; set; } public BuildType ModBuildType { get; set; } + public string? ModPath { get; set; } + public string? BuildPath { get; set; } public bool UpdateMods { get; set; } public string? DataPath { get; set; } - public string? GamePath { get; set; } + public string[]? GamePath { get; set; } } public class Panacea @@ -69,6 +71,15 @@ public class Config { Game.DREAM_DROP_DISTANCE, "KINGDOM HEARTS Dream Drop Distance.exe" }, }; + public static Dictionary GameShorthand = new Dictionary() + { + { Game.KINGDOM_HEARTS, "kh1" }, + { Game.KINGDOM_HEARTS_II, "kh2" }, + { Game.CHAIN_OF_MEMORIES, "com" }, + { Game.BIRTH_BY_SLEEP, "bbs" }, + { Game.DREAM_DROP_DISTANCE, "ddd" }, + }; + public Frontend Frontend { get; set; } public Panacea Panacea { get; set; } public Emulator Emulator { get; set; } diff --git a/OpenKh.Tools.ModManager/Dialogs/BuildProgressDialog.axaml.cs b/OpenKh.Tools.ModManager/Dialogs/BuildProgressDialog.axaml.cs index 8566acfd4..ed65c1278 100644 --- a/OpenKh.Tools.ModManager/Dialogs/BuildProgressDialog.axaml.cs +++ b/OpenKh.Tools.ModManager/Dialogs/BuildProgressDialog.axaml.cs @@ -14,14 +14,14 @@ public BuildProgressDialog() private void OnCancelEvent(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - InstallService.CancelTokenSource.Cancel(); + ModService.CancelTokenSource.Cancel(); Close(false); } protected override void OnClosing(WindowClosingEventArgs e) { if (e.CloseReason == WindowCloseReason.WindowClosing && !e.IsProgrammatic) - InstallService.CancelTokenSource.Cancel(); + ModService.CancelTokenSource.Cancel(); } } } diff --git a/OpenKh.Tools.ModManager/Dialogs/ModProgressDialog.axaml.cs b/OpenKh.Tools.ModManager/Dialogs/ModProgressDialog.axaml.cs index 490ebd61c..9a63eacc6 100644 --- a/OpenKh.Tools.ModManager/Dialogs/ModProgressDialog.axaml.cs +++ b/OpenKh.Tools.ModManager/Dialogs/ModProgressDialog.axaml.cs @@ -16,14 +16,14 @@ public ModProgressDialog() private void OnCancelEvent(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { - InstallService.CancelTokenSource.Cancel(); + ModService.CancelTokenSource.Cancel(); Close(false); } protected override void OnClosing(WindowClosingEventArgs e) { if (e.CloseReason == WindowCloseReason.WindowClosing && !e.IsProgrammatic) - InstallService.CancelTokenSource.Cancel(); + ModService.CancelTokenSource.Cancel(); } } } diff --git a/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj b/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj index a85c67b7f..032ce5129 100644 --- a/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj +++ b/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj @@ -11,6 +11,10 @@ + + + + diff --git a/OpenKh.Tools.ModManager/Services/InstallService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs similarity index 78% rename from OpenKh.Tools.ModManager/Services/InstallService.cs rename to OpenKh.Tools.ModManager/Services/ModService.cs index bc9c36ecd..533c7f4b5 100644 --- a/OpenKh.Tools.ModManager/Services/InstallService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -1,37 +1,42 @@ +using Avalonia.Controls; using Avalonia.Threading; using LibGit2Sharp; using LibGit2Sharp.Handlers; using OpenKh.Bbs; using OpenKh.Kh2; using OpenKh.Patcher; +using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Models; using OpenKh.Tools.ModManager.ViewModels; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; +using System.Security.Cryptography; using System.Text; using System.Threading; using System.Threading.Tasks; namespace OpenKh.Tools.ModManager.Services { - public static class InstallService + public static class ModService { // We need a cancellation token to interrupt what we are doing should the user not want to do that anymore. public static CancellationTokenSource CancelTokenSource = new CancellationTokenSource(); public static CancellationToken CancelToken = CancelTokenSource.Token; - private static void ResetToken() + public static string ResolveMD5(ModModel currentMod, Config currentConfig) { - CancelTokenSource.Dispose(); + var _fetchRelativePath = Path.GetRelativePath(PathService.ResolveMod(currentConfig), currentMod.ModPath); + var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); + var _fetchHash = MD5.HashData(_fetchBytes); - CancelTokenSource = new CancellationTokenSource(); - CancelToken = CancelTokenSource.Token; + return Convert.ToHexString(_fetchHash); } /// @@ -43,7 +48,15 @@ private static void ResetToken() /// Error code. 0x00 is success, 0x01 is invlaid mod, 0x03 is cancellation. public static async Task InstallGit(string modPath, string repoName, TransferProgressHandler? reportProgress = null) { - ResetToken(); + // Reset the cancel token if it was called prior. + + if (CancelToken.IsCancellationRequested) + { + CancelTokenSource.Dispose(); + + CancelTokenSource = new CancellationTokenSource(); + CancelToken = CancelTokenSource.Token; + } // Fetch the platfrom from the given string. var _fetchPlatform = repoName.Contains('@') ? repoName.Split('@').Last() : null; @@ -224,7 +237,15 @@ await Task.Run(() => /// Error code. 0x00 is success, 0x01 is invlaid mod, 0x03 is cancellation. public static async Task InstallLocal(string modPath, string fileName, Func? reportProgress = null) { - ResetToken(); + // Reset the cancel token if it was called prior. + + if (CancelToken.IsCancellationRequested) + { + CancelTokenSource.Dispose(); + + CancelTokenSource = new CancellationTokenSource(); + CancelToken = CancelTokenSource.Token; + } // Get the extension to check and the mod path to process. var _fetchExtension = Path.GetExtension(fileName).ToLower(); @@ -312,11 +333,12 @@ await Task.Run(() => File.Copy(fileName, _fetchCurrentLuaName); - var _fetchMetadata = new Metadata(); - - _fetchMetadata.Title = Path.GetFileNameWithoutExtension(fileName) + " (Lua)"; - _fetchMetadata.Description = "This Metadata has been automatically generated for this Lua Modification."; - _fetchMetadata.Assets = new List(); + var _createMetadata = new Metadata + { + Title = Path.GetFileNameWithoutExtension(fileName) + " (Lua)", + Description = "This Metadata has been automatically generated for this Lua Modification.", + Assets = new List() + }; var _createSource = new AssetFile() { Name = Path.GetFileName(fileName) }; var _createFile = new AssetFile() @@ -326,7 +348,7 @@ await Task.Run(() => Source = new List() { _createSource }, }; - _fetchMetadata.Assets.Add(_createFile); + _createMetadata.Assets.Add(_createFile); using (var _strReader = new StreamReader(_fetchCurrentLuaName)) { @@ -345,13 +367,13 @@ await Task.Run(() => switch (_lineLead) { case "LUAGUI_NAME": - _fetchMetadata.Title = "\"" + _lineGib + "\""; + _createMetadata.Title = "\"" + _lineGib + "\""; break; case "LUAGUI_AUTH": - _fetchMetadata.OriginalAuthor = "\"" + _lineGib + "\""; + _createMetadata.OriginalAuthor = "\"" + _lineGib + "\""; break; case "LUAGUI_DESC": - _fetchMetadata.Description = "\"" + _lineGib + "\""; + _createMetadata.Description = "\"" + _lineGib + "\""; break; } } @@ -359,7 +381,7 @@ await Task.Run(() => } var _yamlPath = Path.Combine(_fetchCurrentModDir, "mod.yml"); - File.WriteAllText(_yamlPath, _fetchMetadata.ToString()); + File.WriteAllText(_yamlPath, _createMetadata.ToString()); } // If the file is ANY PCPatch Package, handle it. @@ -457,9 +479,25 @@ await Task.Run(() => return 0x00; } + /// + /// Builds the mods given according to the config given and reports back progress. + /// + /// The mods to build. + /// The config to base the build on. + /// [Optional] Callback to the mod build progress. + /// [Optional] Callback to the asset build progress per mod. + /// Error code. 0x00 is success, 0x01 is failure, 0x03 is cancellation. public static async Task Build(IEnumerable modsList, Config currentConfig, Func? reportModProgress = null, Func? reportAssetProgress = null) { - ResetToken(); + // Reset the cancel token if it was called prior. + + if (CancelToken.IsCancellationRequested) + { + CancelTokenSource.Dispose(); + + CancelTokenSource = new CancellationTokenSource(); + CancelToken = CancelTokenSource.Token; + } var _fetchPatcher = new PatcherProcessor(); var _fetchPackageMap = new ConcurrentDictionary(); @@ -467,8 +505,11 @@ public static async Task Build(IEnumerable modsList, Config curr var _fetchGameName = currentConfig.Frontend.TargetGame.ToString().ToLower(); var _fetchGameId = PatcherProcessor.GameShorthand[(int)currentConfig.Frontend.TargetGame]; - var _fetchDataPath = !string.IsNullOrEmpty(currentConfig.Frontend.DataPath) ? Path.Combine(currentConfig.Frontend.DataPath, _fetchGameId) : null; - var _fetchBuildPath = Path.Combine(AppContext.BaseDirectory, "build", _fetchGameId); + var _fetchDataPath = PathService.ResolveData(currentConfig); + var _fetchGamePath = PathService.ResolveGame(currentConfig); + var _fetchBuildPath = PathService.ResolveBuild(currentConfig); + + var _fetchPKGMapPath = Path.Combine(_fetchBuildPath, "patch-package-map.txt"); if (!Directory.Exists(_fetchBuildPath)) Directory.CreateDirectory(_fetchBuildPath); @@ -479,19 +520,17 @@ public static async Task Build(IEnumerable modsList, Config curr Directory.CreateDirectory(_fetchBuildPath); } + var _currentModName = ""; var _currentModIndex = 0; var _currentModCount = modsList.Where(x => x.ModValid && x.ModActive).Count(); - var _isBuilding = true; - string _currentMod = ""; - if (reportModProgress != null) { Task.Run(async () => { while (!CancelToken.IsCancellationRequested) { - var _fetchProgress = reportModProgress(_currentMod, _currentModIndex, _currentModCount); + var _fetchProgress = reportModProgress(_currentModName, _currentModIndex, _currentModCount); if (!_fetchProgress) break; @@ -512,7 +551,7 @@ await Task.Run(async () => if (!_fetchMod.ModValid || !_fetchMod.ModActive) continue; - _currentMod = _fetchMod.ModTitle; + _currentModName = _fetchMod.ModTitle; var _fetchYamlPath = Path.Combine(_fetchMod.ModPath, "mod.yml"); var _fetchMetadata = Metadata.Read(_fetchYamlPath); @@ -525,7 +564,7 @@ await Task.Run(() => _fetchBuildPath, _fetchMetadata, _fetchMod.ModPath, - currentConfig.Frontend.GamePath, + _fetchGamePath, (int)currentConfig.Frontend.TargetPlatform, (int)currentConfig.Frontend.TargetGame, _fetchPackageMap, @@ -544,11 +583,79 @@ await Task.Run(() => return 0x03; } - using (var _writePackageMap = new StreamWriter(Path.Combine(_fetchBuildPath, "patch-package-map.txt"))) + using (var _writePackageMap = new StreamWriter(_fetchPKGMapPath)) foreach (var _mapEntry in _fetchPackageMap) _writePackageMap.WriteLine(_mapEntry.Key + " $$$$ " + _mapEntry.Value); return 0x00; } + + /// + /// Runs the game. Requires to be called from a View because of Avalonia's cross-platform launch service. + /// + /// The config to base the launch on. + /// The top-level fetched from the calling View. + /// Only true for now. + public static async Task Run(Config currentConfig, TopLevel topLevelObject) + { + var _fetchTargetGame = currentConfig.Frontend.TargetGame; + var _fetchTargetPlatform = currentConfig.Frontend.TargetPlatform; + + var _fetchGamePath = PathService.ResolveGame(currentConfig); + + var _fetchAPIFilePath = Path.Combine(_fetchGamePath, "steam_appid.txt"); + + var _fetchReMIXFilePath = Path.Combine(_fetchGamePath, "KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe"); + var _fetchLauncherPath = Path.Combine(AppContext.BaseDirectory, "resources/OpenKh.Command.Launcher.exe"); + var _fetchTargetGamePath = Path.Combine(_fetchGamePath, Config.GameExecutable[_fetchTargetGame]); + + var _fetchAPIExists = _fetchTargetPlatform == Platform.STEAM ? File.Exists(_fetchAPIFilePath) : false; + + Uri _fetchTargetUri = null; + FileInfo _fetchTargetFile = null; + + if (!OperatingSystem.IsWindows() || !_fetchAPIExists) + { + switch (_fetchTargetPlatform) + { + case Platform.STEAM: + _fetchTargetUri = new Uri($"steam://rungameid/2552430" + (_fetchAPIExists ? $"//{ Config.GameShorthand[_fetchTargetGame] }" : "")); + break; + case Platform.EPIC_GAMES_STORE: + _fetchTargetUri = new Uri("com.epicgames.launcher://apps/4158b699dd70447a981fee752d970a3e%3A5aac304f0e8948268ddfd404334dbdc7%3A68c214c58f694ae88c2dab6f209b43e4?action=launch"); + break; + } + } + + if (topLevelObject.Launcher != null && _fetchTargetUri != null) + { + if (_fetchAPIExists) + { + var _fetchReMIXBackup = Path.ChangeExtension(_fetchReMIXFilePath, ".bak"); + + if (!File.Exists(_fetchReMIXBackup)) + { + File.Move(_fetchReMIXFilePath, _fetchReMIXBackup); + File.Copy(_fetchLauncherPath, _fetchReMIXFilePath, true); + } + } + + await topLevelObject.Launcher.LaunchUriAsync(_fetchTargetUri); + } + + else + { + var _fetchProcessInfo = new ProcessStartInfo + { + FileName = _fetchTargetGamePath, + WorkingDirectory = _fetchGamePath, + UseShellExecute = true + }; + + Process.Start(_fetchProcessInfo); + } + + return true; + } } } diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs new file mode 100644 index 000000000..2f09946d5 --- /dev/null +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using OpenKh.Tools.ModManager.Classes; + +namespace OpenKh.Tools.ModManager.Services +{ + public static class PathService + { + public static string ResolveMod(Config input) + { + var _fetchConfigPath = input.Frontend.ModPath; + var _fetchTargetGame = input.Frontend.TargetGame; + + if (String.IsNullOrEmpty(_fetchConfigPath)) + return Path.Combine(AppContext.BaseDirectory, "mods", Config.GameShorthand[_fetchTargetGame]); + + else if (!Path.IsPathFullyQualified(_fetchConfigPath)) + return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + + else + return Path.Combine(_fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + } + + public static string ResolveBuild(Config input) + { + var _fetchConfigPath = input.Frontend.BuildPath; + var _fetchTargetGame = input.Frontend.TargetGame; + + if (String.IsNullOrEmpty(_fetchConfigPath)) + return Path.Combine(AppContext.BaseDirectory, "build", Config.GameShorthand[_fetchTargetGame]); + + else if (!Path.IsPathFullyQualified(_fetchConfigPath)) + return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + + else + return Path.Combine(_fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + } + + public static string? ResolveData(Config input) + { + var _fetchConfigPath = input.Frontend.DataPath; + var _fetchTargetGame = input.Frontend.TargetGame; + + if (String.IsNullOrEmpty(_fetchConfigPath)) + return null; + + else if (!Path.IsPathFullyQualified(_fetchConfigPath)) + return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + + else + return Path.Combine(_fetchConfigPath, Config.GameShorthand[_fetchTargetGame]); + } + + public static string? ResolveGame(Config input) => input.Frontend.TargetGame == Game.DREAM_DROP_DISTANCE ? input.Frontend.GamePath[1] : input.Frontend.GamePath[0]; + } +} diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 5744e2196..adaceeff9 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -5,7 +5,7 @@ using LibGit2Sharp; using OpenKh.Patcher; using OpenKh.Tools.ModManager.Models; -using OpenKh.Tools.ModManager.Services; +using OpenKh.Tools.ModManager.Classes; using SharpYaml; using System; using System.Collections.Generic; @@ -17,6 +17,7 @@ using System.Text; using System.Threading.Tasks; using Xe.BinaryMapper; +using OpenKh.Tools.ModManager.Services; namespace OpenKh.Tools.ModManager.ViewModels; @@ -40,30 +41,41 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _hasModsInstalled = false; + /// + /// Activates whenever InstalledMods has a property change. + /// It commits said changes to the targeted game's mod memory. + /// protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEventArgs e) { + // Fetch the mod memory path and fetch the modlist from the sender. var _fetchModList = sender as ObservableCollection; - var _fetchModsPath = Path.Combine(AppContext.BaseDirectory, "mods", _currentConfig.Frontend.TargetGame.ToString().ToLower()); + var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(CurrentConfig), "mod_memory.yml"); - var _fetchMemory = new ObservableCollection(); + // Create the actual mod memory collection. + var _modMemoryList = new ObservableCollection(); + // For every mod that exists: foreach (var _fetchMod in _fetchModList) { + // If the mod is invalid do not memorize it. if (!_fetchMod.ModValid) continue; - var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, _fetchMod.ModPath); - - var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); - var _fetchHash = MD5.HashData(_fetchBytes); - - var _fetchHashStr = Convert.ToHexString(_fetchHash); - - _fetchMemory.Add(new MemoryModel { ModHash = _fetchHashStr, ModActive = _fetchMod.ModActive, ModIndex = _fetchModList.IndexOf(_fetchMod) }); + // Construct the memory structure (Hash, Is Active, Current Index) + _modMemoryList.Add + ( + new MemoryModel + { + ModHash = ModService.ResolveMD5(_fetchMod, CurrentConfig), + ModActive = _fetchMod.ModActive, + ModIndex = _fetchModList.IndexOf(_fetchMod) + } + ); } - var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); - File.WriteAllText(Path.Combine(_fetchModsPath, "mod_memory.yml"), _fetchSerial); + // Serialize and commit the mod memory. + var _fetchSerial = YamlSerializer.Serialize(_modMemoryList); + File.WriteAllText(_fetchMemoryPath, _fetchSerial); } public void InitializeView(bool selectLast = false) @@ -90,13 +102,29 @@ public void InitializeView(bool selectLast = false) if (CurrentConfig.Frontend.TargetPlatform != Platform.PCSX2) { + // Fetch the bare arguments we will use. + var _configFrontend = CurrentConfig.Frontend; + var _fetchTargetGame = _configFrontend.TargetGame; + + // Check if the game is Dream Drop Distance and the second Game Path has been declared. + // Otherwise, check if the game is NOT Dream Drop Distance. + // If neither of these are true, the config isn't valid. + + var _isDDDConfigValid = _fetchTargetGame != Game.DREAM_DROP_DISTANCE || (_fetchTargetGame == Game.DREAM_DROP_DISTANCE && _configFrontend.GamePath.Length < 2); + + if (!_isDDDConfigValid) + ConfigurationValid = false; + + // Fetch the second Game Path if the game is Dream Drop Distance, fetch the first one otherwise. + var _fetchGamePath = _fetchTargetGame == Game.DREAM_DROP_DISTANCE ? _configFrontend.GamePath[1] : _configFrontend.GamePath[0]; + // Construct the paths for the game executable and Panacea. - var _gameExecutablePath = Path.Combine(CurrentConfig.Frontend.GamePath, Config.GameExecutable[CurrentConfig.Frontend.TargetGame]); - var _panaceaPath = Path.Combine(CurrentConfig.Frontend.GamePath, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + var _fetchExePath = Path.Combine(_fetchGamePath, Config.GameExecutable[CurrentConfig.Frontend.TargetGame]); + var _fetchPanaceaPath = Path.Combine(_fetchGamePath, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); // Verify the game executable and directory exists as configured. If the build type is PANACEA, also verify Panacea's existence. - var _isGameConfigValid = Directory.Exists(CurrentConfig.Frontend.GamePath) && File.Exists(_gameExecutablePath); - var _isPanaceaConfigValid = (CurrentConfig.Frontend.ModBuildType == BuildType.PANACEA && File.Exists(_panaceaPath)) || CurrentConfig.Frontend.ModBuildType == BuildType.PATCH; + var _isGameConfigValid = Directory.Exists(_fetchGamePath) && File.Exists(_fetchExePath); + var _isPanaceaConfigValid = (CurrentConfig.Frontend.ModBuildType == BuildType.PANACEA && File.Exists(_fetchPanaceaPath)) || CurrentConfig.Frontend.ModBuildType == BuildType.PATCH; // If either are not valid, mark the config as faulty. if (!_isGameConfigValid || !_isPanaceaConfigValid) @@ -108,7 +136,7 @@ public void InitializeView(bool selectLast = false) // Construct the mod folder path for the specified game. var _fetchMods = new ObservableCollection(); - var _fetchModsPath = Path.Combine(AppContext.BaseDirectory, "mods", CurrentConfig.Frontend.TargetGame.ToString().ToLower()); + var _fetchModsPath = PathService.ResolveMod(CurrentConfig); // For each directory that exists in the mod folder: @@ -178,7 +206,8 @@ public void InitializeView(bool selectLast = false) var _fetchGitDir = new DirectoryInfo(_fetchPathGit); foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) - _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; } } }); @@ -207,56 +236,63 @@ public void InitializeView(bool selectLast = false) } } + // When all mods are loaded, start processing the "mod memory" for the targeted game.\ + // If the mod memory does not exist, assume default order and mark all active. + var _fetchModMemoryPath = Path.Combine(_fetchModsPath, "mod_memory.yml"); if (File.Exists(_fetchModMemoryPath)) { + // Fetch the raw YAML data and deserialize it. var _fetchRawYaml = File.ReadAllText(_fetchModMemoryPath); var _fetchModMemory = YamlSerializer.Deserialize>(_fetchRawYaml); - var _fetchTempList = new ModModel[_fetchMods.Count]; + // Make a temporary array for us to order the mods. + // And a temporary list for us to commit the mods. + + var _tempModArray = new ModModel[_fetchMods.Count]; + var _tempModList = new List(); foreach (var _fetchMemory in _fetchModMemory) { - var _fetchMod = _fetchMods.First(x => - { - var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, x.ModPath); - var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); - var _fetchHash = MD5.HashData(_fetchBytes); - - return Convert.ToHexString(_fetchHash) == _fetchMemory.ModHash; - }); + var _fetchMod = _fetchMods.FirstOrDefault(x => ModService.ResolveMD5(x, CurrentConfig) == _fetchMemory.ModHash); if (_fetchMod == null || !_fetchMod.ModValid) continue; _fetchMod.ModActive = _fetchMemory.ModActive; - _fetchTempList[_fetchMemory.ModIndex] = _fetchMod; + _tempModArray[_fetchMemory.ModIndex] = _fetchMod; _fetchMods.Remove(_fetchMod); } - InstalledMods = new ObservableCollection(_fetchTempList.Where(x => x != null)); + // Add all the existing mods from the array. + _tempModList.AddRange(_tempModArray.Where(x => x != null)); + + // Add all the valid mods from the original list which didn't exist on the mod memory. + _tempModList.AddRange(_fetchMods.Where(x => x.ModValid)); - foreach (var _fetchMod in _fetchMods.Where(x => x.ModValid)) - InstalledMods.Add(_fetchMod); + // Add all the invalid mods from the original list which didn't exist on the mod memory. + _tempModList.AddRange(_fetchMods.Where(x => !x.ModValid)); - foreach (var _fetchMod in _fetchMods.Where(x => !x.ModValid)) - InstalledMods.Add(_fetchMod); + // Sync to the actual installed mods collection. + InstalledMods = new ObservableCollection(_tempModList); } else InstalledMods = _fetchMods; + // Register mod property event to handle mod memory. InstalledMods.CollectionChanged += OnModPropertyChanged; - // If there is at least one mod, select the first mod. + // If there is at least one mod, select the first mod and declare we have mods. if (InstalledMods.Count > 0) { HasModsInstalled = true; CurrentMod = InstalledMods.First(); } + // Initialization is complete. Initialized = true; } diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 77053c6c5..99d698a34 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -9,6 +9,7 @@ using OpenKh.Patcher; using OpenKh.Tools.ModManager.Dialogs; using OpenKh.Tools.ModManager.Models; +using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; using OpenKh.Tools.ModManager.Views; @@ -25,6 +26,7 @@ using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; +using Avalonia.Media; namespace OpenKh.Tools.ModManager.Views; @@ -173,9 +175,9 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) var _fetchContext = DataContext as MainViewModel; var _fetchModsList = _fetchContext.InstalledMods; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; + var _fetchConfig = _fetchContext.CurrentConfig; - var _fetchModsPath = Path.Combine(System.AppContext.BaseDirectory, "mods", _fetchConfig.TargetGame.ToString().ToLower()); + var _fetchModPath = PathService.ResolveMod(_fetchConfig); var _fetchInstallResult = 0x00; @@ -192,7 +194,7 @@ private async void OnInstallClicked(object? sender, RoutedEventArgs e) _progressDialog.InstallProgress.Value = progress.ReceivedObjects; }); - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -206,7 +208,7 @@ bool _localProcessHandler(int processed, int total) _progressDialog.InstallProgress.Value = processed; }); - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -214,10 +216,10 @@ bool _localProcessHandler(int processed, int total) ; if (File.Exists(_fetchResult)) - _fetchInstallResult = await InstallService.InstallLocal(_fetchModsPath, _fetchResult, _localProcessHandler); + _fetchInstallResult = await ModService.InstallLocal(_fetchModPath, _fetchResult, _localProcessHandler); else - _fetchInstallResult = await InstallService.InstallGit(_fetchModsPath, _fetchResult, _gitProcessHandler); + _fetchInstallResult = await ModService.InstallGit(_fetchModPath, _fetchResult, _gitProcessHandler); _progressDialog.Close(true); @@ -229,11 +231,13 @@ bool _localProcessHandler(int processed, int total) else if (_fetchInstallResult == 0x00) { - var _fetchLatestDirectory = new DirectoryInfo(_fetchModsPath).GetDirectories().OrderByDescending(d => d.LastWriteTimeUtc).FirstOrDefault(); - var _fetchYamlName = Path.Combine(_fetchLatestDirectory.FullName, "mod.yml"); + var _fetchLatestMod = new DirectoryInfo(_fetchModPath).GetDirectories() + .OrderByDescending(d => d.LastWriteTimeUtc) + .First(); - var _fetchPathGit = Path.Combine(_fetchLatestDirectory.FullName, ".git"); - var _fetchPathIcon = Path.Combine(_fetchLatestDirectory.FullName, "icon.png"); + var _fetchPathGit = Path.Combine(_fetchLatestMod.FullName, ".git"); + var _fetchYamlName = Path.Combine(_fetchLatestMod.FullName, "mod.yml"); + var _fetchPathIcon = Path.Combine(_fetchLatestMod.FullName, "icon.png"); var _fetchMetadata = Metadata.Read(_fetchYamlName); @@ -244,7 +248,7 @@ bool _localProcessHandler(int processed, int total) ModTitle = _fetchMetadata.Title, ModAuthor = _fetchMetadata.OriginalAuthor, ModDescription = _fetchMetadata.Description, - ModPath = _fetchLatestDirectory.FullName, + ModPath = _fetchLatestMod.FullName, ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, ModActive = true, @@ -277,7 +281,8 @@ bool _localProcessHandler(int processed, int total) var _fetchGitDir = new DirectoryInfo(_fetchPathGit); foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) - _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; } } @@ -298,7 +303,7 @@ bool _localProcessHandler(int processed, int total) ModAuthor = "This mod is invalid!", ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", ModIcon = new Bitmap(AssetLoader.Open(uri)), - ModPath = _fetchLatestDirectory.FullName, + ModPath = _fetchLatestMod.FullName, ModActive = false, ModValid = false }; @@ -314,8 +319,8 @@ private async void OnOpenFolderClicked(object? sender, RoutedEventArgs e) var _fetchContext = DataContext as MainViewModel; var _fetchCurrentMod = _fetchContext.CurrentMod; + var _fetchTopLevel = GetTopLevel(this); var _fetchDirectoryInfo = new DirectoryInfo(_fetchCurrentMod.ModPath); - var _fetchTopLevel = TopLevel.GetTopLevel(this); if (_fetchTopLevel?.Launcher != null) await _fetchTopLevel.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); @@ -327,21 +332,31 @@ private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEven var _fetchModList = _fetchContext.InstalledMods; var _fetchConfig = _fetchContext.CurrentConfig; - var _fetchMemoryPath = Path.Combine(AppContext.BaseDirectory, "mods", _fetchConfig.Frontend.TargetGame.ToString().ToLower(), "mod_memory.yml"); + var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(_fetchConfig), "mod_memory.yml"); var _fetchMemoryRAW = File.ReadAllText(_fetchMemoryPath); var _fetchMemory = YamlSerializer.Deserialize>(_fetchMemoryRAW); - var _fetchRelativePath = Path.GetRelativePath(AppContext.BaseDirectory, e.TargetMod.ModPath); + var _fetchSenderHash = ModService.ResolveMD5(e.TargetMod, _fetchConfig); + var _fetchMemoryItem = _fetchMemory.FirstOrDefault(x => x.ModHash == _fetchSenderHash); - var _fetchBytes = Encoding.ASCII.GetBytes(_fetchRelativePath); - var _fetchHash = MD5.HashData(_fetchBytes); - var _fetchHashStr = Convert.ToHexString(_fetchHash); + if (_fetchMemoryItem == null) + { + _fetchMemoryItem = new MemoryModel + { + ModHash = _fetchSenderHash, + ModActive = e.IsChecked, + ModIndex = _fetchModList.IndexOf(e.TargetMod) + }; - var _fetchMemoryItem = _fetchMemory.FirstOrDefault(x => x.ModHash == _fetchHashStr); - var _fetchMemoryIndex = _fetchMemory.IndexOf(_fetchMemoryItem); + _fetchMemory.Add(_fetchMemoryItem); + } - _fetchMemory[_fetchMemoryIndex].ModActive = e.IsChecked; + else + { + var _fetchMemoryIndex = _fetchMemory.IndexOf(_fetchMemoryItem); + _fetchMemory[_fetchMemoryIndex].ModActive = e.IsChecked; + } var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); File.WriteAllText(_fetchMemoryPath, _fetchSerial); @@ -350,62 +365,9 @@ private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEven private async void OnRunRequested(object? sender, RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; - var _fetchFrontConfig = _fetchConfig.Frontend; - - var _fetchAPIFilePath = Path.Combine(_fetchFrontConfig.GamePath, "steam_appid.txt"); - - var _fetchReMIXFilePath = Path.Combine(_fetchFrontConfig.GamePath, "KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe"); - var _fetchLaunchFilePath = Path.Combine(_fetchFrontConfig.GamePath, Config.GameExecutable[_fetchFrontConfig.TargetGame]); - - var _fetchAPIExists = _fetchFrontConfig.TargetPlatform == Platform.STEAM ? File.Exists(_fetchAPIFilePath) : false; - - Uri _fetchTargetUri = null; - FileInfo _fetchTargetFile = null; - - if (!OperatingSystem.IsWindows() || !_fetchAPIExists) - { - switch (_fetchFrontConfig.TargetPlatform) - { - case Platform.STEAM: - _fetchTargetUri = new Uri($"steam://rungameid/2552430" + (_fetchAPIExists ? $"//{PatcherProcessor.GameShorthand[(int)_fetchFrontConfig.TargetGame]}" : "")); - break; - case Platform.EPIC_GAMES_STORE: - _fetchTargetUri = new Uri("com.epicgames.launcher://apps/4158b699dd70447a981fee752d970a3e%3A5aac304f0e8948268ddfd404334dbdc7%3A68c214c58f694ae88c2dab6f209b43e4?action=launch"); - break; - } - } - - var _fetchTopLevel = TopLevel.GetTopLevel(this); - - if (_fetchTopLevel?.Launcher != null && _fetchTargetUri != null) - { - if (_fetchAPIExists) - { - var _fetchReMIXBackup = Path.ChangeExtension(_fetchReMIXFilePath, ".bak"); - - if (!File.Exists(_fetchReMIXBackup)) - { - File.Move(_fetchReMIXFilePath, _fetchReMIXBackup); - File.Copy(Path.Combine(AppContext.BaseDirectory, "resources/OpenKh.Command.Launcher.exe"), _fetchReMIXFilePath, true); - } - } - - await _fetchTopLevel.Launcher.LaunchUriAsync(_fetchTargetUri); - } - - else - { - var _fetchProcessInfo = new ProcessStartInfo - { - FileName = _fetchLaunchFilePath, - WorkingDirectory = _fetchFrontConfig.GamePath, - UseShellExecute = true - }; - Process.Start(_fetchProcessInfo); - } + await ModService.Run(_fetchConfig, GetTopLevel(this)); } @@ -424,7 +386,7 @@ private async void OnBuildRequested(object? sender, RoutedEventArgs e) var _fetchFrontConfig = _fetchConfig.Frontend; var _buildResult = - await InstallService.Build + await ModService.Build ( _fetchContext.InstalledMods, _fetchConfig, @@ -443,7 +405,7 @@ await InstallService.Build _progressDialog.AssetProgress.Value = _assetProcessed; }); - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -454,7 +416,7 @@ await InstallService.Build _assetProcessed = processed; _assetTotal = total; - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -485,7 +447,7 @@ private async void OnBuildRunRequested(object? sender, RoutedEventArgs e) var _fetchFrontConfig = _fetchConfig.Frontend; var _buildResult = - await InstallService.Build + await ModService.Build ( _fetchContext.InstalledMods, _fetchConfig, @@ -504,7 +466,7 @@ await InstallService.Build _progressDialog.AssetProgress.Value = _assetProcessed; }); - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -515,7 +477,7 @@ await InstallService.Build _assetProcessed = processed; _assetTotal = total; - if (InstallService.CancelToken.IsCancellationRequested) + if (ModService.CancelToken.IsCancellationRequested) return false; return true; @@ -526,60 +488,8 @@ await InstallService.Build switch (_buildResult) { case 0: - { - var _fetchAPIFilePath = Path.Combine(_fetchFrontConfig.GamePath, "steam_appid.txt"); - - var _fetchReMIXFilePath = Path.Combine(_fetchFrontConfig.GamePath, "KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe"); - var _fetchLaunchFilePath = Path.Combine(_fetchFrontConfig.GamePath, Config.GameExecutable[_fetchFrontConfig.TargetGame]); - - var _fetchAPIExists = _fetchFrontConfig.TargetPlatform == Platform.STEAM ? File.Exists(_fetchAPIFilePath) : false; - - Uri _fetchTargetUri = null; - FileInfo _fetchTargetFile = null; - - if (!OperatingSystem.IsWindows() || !_fetchAPIExists) - { - switch (_fetchFrontConfig.TargetPlatform) - { - case Platform.STEAM: - _fetchTargetUri = new Uri($"steam://rungameid/2552430" + (_fetchAPIExists ? $"//{PatcherProcessor.GameShorthand[(int)_fetchFrontConfig.TargetGame]}" : "")); - break; - case Platform.EPIC_GAMES_STORE: - _fetchTargetUri = new Uri("com.epicgames.launcher://apps/4158b699dd70447a981fee752d970a3e%3A5aac304f0e8948268ddfd404334dbdc7%3A68c214c58f694ae88c2dab6f209b43e4?action=launch"); - break; - } - } - - var _fetchTopLevel = TopLevel.GetTopLevel(this); - - if (_fetchTopLevel?.Launcher != null && _fetchTargetUri != null) - { - if (_fetchAPIExists) - { - var _fetchReMIXBackup = Path.ChangeExtension(_fetchReMIXFilePath, ".bak"); - - if (!File.Exists(_fetchReMIXBackup)) - { - File.Move(_fetchReMIXFilePath, _fetchReMIXBackup); - File.Copy("resources/OpenKh.Command.Launcher.exe", _fetchReMIXFilePath, true); - } - } - - await _fetchTopLevel.Launcher.LaunchUriAsync(_fetchTargetUri); - } - - else - { - var _fetchProcessInfo = new ProcessStartInfo - { - FileName = _fetchLaunchFilePath, - WorkingDirectory = _fetchFrontConfig.GamePath, - UseShellExecute = true - }; - - Process.Start(_fetchProcessInfo); - } - } break; + await ModService.Run(_fetchConfig, GetTopLevel(this)); + break; case 1: { diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml new file mode 100644 index 000000000..42b1f344a --- /dev/null +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml @@ -0,0 +1,8 @@ + + Welcome to Avalonia! + diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs new file mode 100644 index 000000000..3f576ecb7 --- /dev/null +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace OpenKh.Tools.ModManager.Wizard +{ + public partial class WizardGameSetup : ContentPage + { + public WizardGameSetup() + { + InitializeComponent(); + } + } +} \ No newline at end of file From e08f99ca518bff80857f26d6725b5a28f6fad7fb Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Wed, 2 Sep 2026 17:13:49 +0200 Subject: [PATCH 045/175] [Mod Manager] - Setup Wizard Prequel. --- OpenKh.Tools.ModManager/Classes/Config.cs | 4 +- .../Converters/XamlConverters.cs | 43 ++++++ .../ViewModels/WizardViewModel.cs | 22 +++ OpenKh.Tools.ModManager/Views/MainView.axaml | 2 +- .../Views/MainView.axaml.cs | 15 +- .../Views/ModStatusView.axaml | 2 +- .../Views/ModStatusView.axaml.cs | 16 +++ .../Views/SetupWizardView.axaml | 30 ++++ .../Views/SetupWizardView.axaml.cs | 81 +++++++++++ .../Wizard/WizardGameSetup.axaml | 132 +++++++++++++++++- .../Wizard/WizardGameSetup.axaml.cs | 4 +- .../Wizard/WizardPanaceaSetup.axaml | 46 ++++++ .../Wizard/WizardPanaceaSetup.axaml.cs | 14 ++ 13 files changed, 395 insertions(+), 16 deletions(-) create mode 100644 OpenKh.Tools.ModManager/ViewModels/WizardViewModel.cs create mode 100644 OpenKh.Tools.ModManager/Views/SetupWizardView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs create mode 100644 OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml create mode 100644 OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs diff --git a/OpenKh.Tools.ModManager/Classes/Config.cs b/OpenKh.Tools.ModManager/Classes/Config.cs index 75ea38650..f8e3673e5 100644 --- a/OpenKh.Tools.ModManager/Classes/Config.cs +++ b/OpenKh.Tools.ModManager/Classes/Config.cs @@ -41,7 +41,9 @@ public class Frontend public string? BuildPath { get; set; } public bool UpdateMods { get; set; } public string? DataPath { get; set; } - public string[]? GamePath { get; set; } + public string[]? GamePath { + get; + set; } } public class Panacea diff --git a/OpenKh.Tools.ModManager/Converters/XamlConverters.cs b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs index 29ad620e2..3824a0637 100644 --- a/OpenKh.Tools.ModManager/Converters/XamlConverters.cs +++ b/OpenKh.Tools.ModManager/Converters/XamlConverters.cs @@ -1,6 +1,7 @@ using Avalonia.Data.Converters; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Text; @@ -27,6 +28,48 @@ public object Convert(IList values, Type targetType, object? parameter, } } + public class IsListNotEmptyConverter : IValueConverter + { + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is not IEnumerable) + return false; + + var _fetchValue = value as ObservableCollection; + var _fetchCount = _fetchValue.Count(); + + return _fetchCount != 0; + } + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) => throw new NotSupportedException(); + } + + public class PropertyNotEqualConverter : IMultiValueConverter + { + public object Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values == null || values.Count != 2 || values[0] == null || values[1] == null) + return false; + + var _fetchFirst = (values[0] is Enum) ? System.Convert.ToInt32(values[0]) : values[0]; + var _fetchSecond = (values[1] is Enum) ? System.Convert.ToInt32(values[1]) : values[1]; + + return _fetchFirst is int a && _fetchSecond is int b && a != b; + } + } + public class PropertyIsEqualConverter : IMultiValueConverter + { + public object Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values == null || values.Count != 2 || values[0] == null || values[1] == null) + return false; + + var _fetchFirst = (values[0] is Enum) ? System.Convert.ToInt32(values[0]) : values[0]; + var _fetchSecond = (values[1] is Enum) ? System.Convert.ToInt32(values[1]) : values[1]; + + return _fetchFirst is int a && _fetchSecond is int b && a == b; + } + } + public class NonNullPropertyConverter : IMultiValueConverter { public object Convert(IList values, Type targetType, object? parameter, CultureInfo culture) diff --git a/OpenKh.Tools.ModManager/ViewModels/WizardViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/WizardViewModel.cs new file mode 100644 index 000000000..3478b9e9f --- /dev/null +++ b/OpenKh.Tools.ModManager/ViewModels/WizardViewModel.cs @@ -0,0 +1,22 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.ViewModels +{ + public partial class WizardViewModel : ViewModelBase + { + [ObservableProperty] + private object? _currentWizardPage; + + [ObservableProperty] + private ObservableCollection _pastWizardPages; + + [ObservableProperty] + private ObservableCollection _futureWizardPages; + } +} diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 914204500..3ac15753e 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -119,7 +119,7 @@ - + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 99d698a34..9e1a8c5d0 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -385,10 +385,10 @@ private async void OnBuildRequested(object? sender, RoutedEventArgs e) var _fetchConfig = _fetchContext.CurrentConfig; var _fetchFrontConfig = _fetchConfig.Frontend; - var _buildResult = + var _buildResult = await ModService.Build ( - _fetchContext.InstalledMods, + _fetchContext.InstalledMods, _fetchConfig, (string currModName, int procMod, int totalMod) => @@ -409,9 +409,9 @@ await ModService.Build return false; return true; - }, + }, - (int processed, int total) => + (int processed, int total) => { _assetProcessed = processed; _assetTotal = total; @@ -489,14 +489,17 @@ await ModService.Build { case 0: await ModService.Run(_fetchConfig, GetTopLevel(this)); - break; + break; case 1: { var _errorDialog = new BuildModError(); _errorDialog.MainText.Text = string.Format(_errorDialog.MainText.Text, _currentModName); await _errorDialog.ShowDialog(this); - } break; + } + break; } } + + private async void OnSetupRequested(object? sender, RoutedEventArgs e) => await new SetupWizardView().ShowDialog(this); } diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml index 17aaba54e..e45ffb0dd 100644 --- a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml @@ -21,7 +21,7 @@ + Enter path for KINGDOM HEARTS 2.8 + Final Chapter Prologue: + + + + + + + + + + + You can also choose to detect your installations + instead. Note that this will try to fetch your platform-specific library locations + and may not always be successful. + + + + + NOTE: Only PCSX2 versions up to 1.6.0 and PCSX2-EX are supported. + + + diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index 3f576ecb7..2ebcb4737 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -1,6 +1,8 @@ using Avalonia; using Avalonia.Controls; +using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; +using OpenKh.Tools.ModManager.ViewModels; namespace OpenKh.Tools.ModManager.Wizard { @@ -11,4 +13,4 @@ public WizardGameSetup() InitializeComponent(); } } -} \ No newline at end of file +} diff --git a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml new file mode 100644 index 000000000..121a31a88 --- /dev/null +++ b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml @@ -0,0 +1,46 @@ + + + + M2 8V6.25C2 4.45507 3.45507 3 5.25 + 3H8.12868C8.72542 3 9.29771 3.23705 9.71967 3.65901L11.25 5.18934L8.65901 + 7.78033C8.51836 7.92098 8.32759 8 8.12868 8H2ZM2 9.5V17.75C2 19.5449 3.45507 21 5.25 + 21H18.75C20.5449 21 22 19.5449 22 17.75V8.75C22 6.95507 20.5449 5.5 18.75 + 5.5H13.0607L9.71967 8.84099C9.29771 9.26295 8.72542 9.5 8.12868 9.5H2Z + + + + + + + + + + + + + Panacea allows the PC version of KINGDOM HEARTS titles to load the mods you have installed + without any modification done to the game files. This saves time and space whenever you want to disable mods, + since you will not need to have backups of the original files and restore them beforehand. + + + It seems that Panacea is not currently installed. You can skip Panacea + installation, but you will be required to hard-patch the game files should + you wish to use mods. In addition: Mods that use DLL files to add extra + functionality, such as Kingdom Hearts - Re:Fined, will NOT work without Panacea! + + + + + @@ -80,17 +80,23 @@ HorizontalAlignment="Stretch" PlaceholderText="Path goes here..." Margin="0, 0, 10, 0" /> - You can also choose to detect your installations + + You can also choose to detect your installations instead. Note that this will try to fetch your platform-specific library locations - and may not always be successful. + and may not always be successful. + + + + NOTE: Detecting installations on Linux works for Steam only. + - - + - - - - - diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index 245390955..9b93008b8 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -1,15 +1,16 @@ using Avalonia; using Avalonia.Controls; -using Avalonia.Threading; using Avalonia.Markup.Xaml; +using Avalonia.Platform.Storage; +using Avalonia.Threading; using CommunityToolkit.Mvvm.Messaging; +using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; using OpenKh.Tools.ModManager.Views; using System; using System.Collections.Generic; using System.Threading.Tasks; -using OpenKh.Tools.ModManager.Classes; namespace OpenKh.Tools.ModManager.Wizard { @@ -90,5 +91,19 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE } private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => ModService.CancelTokenSource.Cancel(); + + private async void OnExtractPathClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var _fetchTopLevel = TopLevel.GetTopLevel(this); + + var _fetchFolder = await _fetchTopLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for Game Data...", + AllowMultiple = false + }); + + if (_fetchFolder.Count >= 1) + PathExtractData.Text = _fetchFolder[0].Path.LocalPath; + } } } From d0eb33932db1a30ef812d83f6d0d0530887bed84 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 10:25:39 +0200 Subject: [PATCH 062/175] [Mod Manager] - Maybe DON'T crash if stuff exists. --- OpenKh.Tools.ModManager/Views/MainView.axaml.cs | 4 ++-- OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 7b939305c..274ef7bb8 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -570,7 +570,7 @@ private async void OnPanaceaClicked(object? sender, RoutedEventArgs e) foreach (var _file in _fetchDependencyFiles) { var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath); + File.Copy(_file, _fetchTargetPath, true); } File.Copy(_fetchPanaceaPath, _fetchTarget1525, true); @@ -589,7 +589,7 @@ private async void OnPanaceaClicked(object? sender, RoutedEventArgs e) foreach (var _file in _fetchDependencyFiles) { var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath); + File.Copy(_file, _fetchTargetPath, true); } File.Copy(_fetchPanaceaPath, _fetchTarget28, true); diff --git a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs index b8449465a..a25ea0641 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs @@ -138,7 +138,7 @@ private void OnInstallClick(object? sender, Avalonia.Interactivity.RoutedEventAr foreach (var _file in _fetchDependencyFiles) { var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath); + File.Copy(_file, _fetchTargetPath, true); } File.Copy(_fetchPanaceaPath, _fetchTarget1525, true); @@ -157,7 +157,7 @@ private void OnInstallClick(object? sender, Avalonia.Interactivity.RoutedEventAr foreach (var _file in _fetchDependencyFiles) { var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath); + File.Copy(_file, _fetchTargetPath, true); } File.Copy(_fetchPanaceaPath, _fetchTarget28, true); From 196fbff2414a0b8396edad916868d10264fed84c Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 12:05:14 +0200 Subject: [PATCH 063/175] [Mod Manager] - Remove console for now. --- OpenKh.Patcher/PatcherProcessor.cs | 19 ++---------- .../Services/ModService.cs | 29 ------------------- 2 files changed, 2 insertions(+), 46 deletions(-) diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 5a467a352..62f8057f3 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -146,7 +146,7 @@ public void Patch( var _fetchFileNames = new List() { _fetchAsset.Name }; var _fetchMultiNames = _fetchAsset.Multi != null ? _fetchAsset.Multi.Select(x => x.Name) - .Where(x => !string.IsNullOrEmpty(x)) : null; + .Where(x => !string.IsNullOrEmpty(x)) : null; if (_fetchMultiNames != null) _fetchFileNames.AddRange(_fetchMultiNames); @@ -166,10 +166,7 @@ public void Patch( var _fetchFileParent = _fetchName.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]).FirstOrDefault(); if (Path.IsPathRooted(_fetchName) && !Path.GetPathRoot(_fetchName).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - { - Console.WriteLine($"File Path \"" + _fetchName + "\" cannot be copied as it is rooted and can cause instability! Aborting..."); throw new PatcherException(modMetadata, new InvalidOperationException("Root Copy Detected!")); - } var _fetchOutputPath = Path.Combine(buildOutputPath, _fetchName); var _fetchOutputDir = Path.GetDirectoryName(_fetchOutputPath); @@ -237,7 +234,7 @@ public void Patch( { if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchAssetPath)) File.Copy(_fetchAssetPath, _fetchOutputPath); - } + } else { @@ -279,10 +276,7 @@ public void Patch( } using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - { - Console.WriteLine($"{_colorGreen}[{_colorCyan}{modMetadata.Title}{_colorGreen}] {_colorYellow}Processing file: \"{_fetchName}\""); PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); - } } else @@ -293,8 +287,6 @@ public void Patch( { if (targetPlatform != 0x00) { - Console.WriteLine($"{_colorGreen}[{_colorCyan}{modMetadata.Title}{_colorGreen}] {_colorRed}File {_colorYellow}\"{_fetchName}\" {_colorRed}doesn't exist in extraction. Fetching from game data..."); - byte[] _fetchAssetData = null; var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : "en"); @@ -333,14 +325,8 @@ public void Patch( }); using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate, FileAccess.ReadWrite)) - { - Console.WriteLine($"{_colorGreen}[{_colorCyan}{modMetadata.Title}{_colorGreen}] {_colorYellow}Processing file: \"{_fetchName}\""); PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); - } } - - else - Console.WriteLine($"{_colorGreen}[{_colorCyan}{modMetadata.Title}{_colorGreen}] {_colorRed}File {_colorYellow}\"{_fetchName}\" {_colorRed}doesn't exist in extraction. Skipping..."); } } @@ -361,7 +347,6 @@ public void Patch( catch (Exception ex) { - Console.WriteLine($"Patcher failed: {ex.Message}"); throw new PatcherException(modMetadata, ex); } } diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 4b9874a60..f1840ce9f 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -33,22 +33,6 @@ public static class ModService // We need a cancellation token to interrupt what we are doing should the user not want to do that anymore. public static CancellationTokenSource CancelTokenSource = new CancellationTokenSource(); public static CancellationToken CancelToken = CancelTokenSource.Token; - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool FreeConsole(); - - [DllImport("kernel32.dll", SetLastError = true)] - static extern bool AllocConsole(); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern IntPtr GetStdHandle(int nStdHandle); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool GetConsoleMode(IntPtr hConsoleHandle, out uint lpMode); - - [DllImport("kernel32.dll", SetLastError = true)] - public static extern bool SetConsoleMode(IntPtr hConsoleHandle, uint dwMode); - public static string ResolveMD5(ModModel currentMod, Config currentConfig) { var _fetchRelativePath = Path.GetRelativePath(PathService.ResolveMod(currentConfig), currentMod.ModPath); @@ -530,16 +514,6 @@ public static async Task Build(IEnumerable modsList, Config curr var _fetchPKGMapPath = Path.Combine(_fetchBuildPath, "patch-package-map.txt"); - AllocConsole(); - - var _consoleHandle = GetStdHandle(-11); - - if (GetConsoleMode(_consoleHandle, out uint _fetchMode)) - { - _fetchMode |= 0x0004; - SetConsoleMode(_consoleHandle, _fetchMode); - } - if (!Directory.Exists(_fetchBuildPath)) Directory.CreateDirectory(_fetchBuildPath); @@ -610,8 +584,6 @@ await Task.Run(() => if (CancelToken.IsCancellationRequested) { - FreeConsole(); - Directory.Delete(_fetchBuildPath, true); return 0x03; } @@ -622,7 +594,6 @@ await Task.Run(() => foreach (var _mapEntry in _fetchPackageMap) _writePackageMap.WriteLine(_mapEntry.Key + " $$$$ " + _mapEntry.Value); - return 0x00; } From 4534e8f6c1b9810eabc10583cc92de63d42ff74e Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 16:38:43 +0200 Subject: [PATCH 064/175] [Mod Manager] - Introduce Regex Timeouts. --- OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs | 8 ++++---- .../Wizard/WizardPanaceaSetup.axaml.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index 4aebd45b6..f6f97a224 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -104,7 +104,7 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv return; var _fetchLibraryConfig = File.ReadAllLines(_fetchConfigPath); - var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\""); + var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); var _fetchPath1525 = ""; var _fetchPath28 = ""; @@ -125,7 +125,7 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv var _manifestPath1525 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552430.acf"); var _manifestPath28 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552440.acf"); - var _installDirRegex = new Regex("installdir[^\"]*\"\\s*\"([^\"]*)\""); + var _installDirRegex = new Regex("installdir[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); if (File.Exists(_manifestPath1525)) { @@ -190,8 +190,8 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv if (Path.GetExtension(_fetchFile) == ".item") { var _fetchFileRAW = File.ReadAllLines(_fetchFile); - var _catalogRegex = new Regex("\"CatalogNamespace\":\\s+\"([^\"]*)\""); - var _installDirRegex = new Regex("\"InstallLocation\":\\s+\"([^\"]*)\""); + var _catalogRegex = new Regex("\"CatalogNamespace\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + var _installDirRegex = new Regex("\"InstallLocation\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); var _isManifest1525 = false; var _isManifest28 = false; diff --git a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs index a25ea0641..1f5d26412 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs @@ -49,7 +49,7 @@ public WizardPanaceaSetup() var _isConfigValid1525 = false; var _isConfigValid28 = false; - var _regexModPath = new Regex("mod_path=(.*)"); + var _regexModPath = new Regex("mod_path=(.*)", RegexOptions.None, TimeSpan.FromMilliseconds(500)); if (File.Exists(_fetchAssembly1525) && File.Exists(_fetchSettings1525)) { From a1d57709787e17e96ea7bdd29c52997e29dc2817 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 17:12:58 +0200 Subject: [PATCH 065/175] [Mod Manager] - Move Panacea, Backend, and SteamAPI stuff to a service. --- .../Services/PackageService.cs | 431 ++++++++++++++++++ .../Wizard/WizardDirectLaunch.axaml.cs | 58 +-- .../Wizard/WizardPanaceaSetup.axaml.cs | 159 +------ .../Wizard/WizardScriptSetup.axaml.cs | 185 +------- 4 files changed, 473 insertions(+), 360 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Services/PackageService.cs diff --git a/OpenKh.Tools.ModManager/Services/PackageService.cs b/OpenKh.Tools.ModManager/Services/PackageService.cs new file mode 100644 index 000000000..f8770a4ef --- /dev/null +++ b/OpenKh.Tools.ModManager/Services/PackageService.cs @@ -0,0 +1,431 @@ +using OpenKh.Tools.ModManager.Classes; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Tomlyn; +using Tomlyn.Model; + +namespace OpenKh.Tools.ModManager.Services +{ + public static class PackageService + { + private static Dictionary _backendGameIDs = new Dictionary() + { + { Game.KINGDOM_HEARTS, "kh1" }, + { Game.KINGDOM_HEARTS_II, "kh2" }, + { Game.CHAIN_OF_MEMORIES, "recom" }, + { Game.BIRTH_BY_SLEEP, "bbs" }, + { Game.DREAM_DROP_DISTANCE, "kh3d" }, + }; + + public static bool EnsurePanacea(Config currentConfig) + { + var _fetchPaths = currentConfig.Frontend.GamePath; + + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + if (String.IsNullOrEmpty(_fetchPath1525) && String.IsNullOrEmpty(_fetchPath28)) + return false; + + var _fetchSettings1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); + var _fetchAssembly1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + + var _fetchSettings28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); + var _fetchAssembly28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + + var _isConfigValid1525 = false; + var _isConfigValid28 = false; + + var _regexModPath = new Regex("mod_path=(.*)", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + + if (File.Exists(_fetchAssembly1525) && File.Exists(_fetchSettings1525)) + { + var _fetchSettingsRAW = File.ReadAllLines(_fetchSettings1525); + var _fetchPanaceaPath = _fetchSettingsRAW.FirstOrDefault(x => _regexModPath.IsMatch(x)); + + if (_fetchPanaceaPath != null) + { + var _fetchMatch = _regexModPath.Match(_fetchPanaceaPath); + var _fetchValue = _fetchMatch.Groups[1].Value; + + var _fetchConfigPath = Path.GetFullPath(_fetchValue); + + var _fetchManagerPath = PathService.ResolveBuild(currentConfig, true); + var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + if (String.Equals(_fetchConfigPath, _fetchManagerPath, _comparisonRules)) + _isConfigValid1525 = true; + } + } + + if (File.Exists(_fetchAssembly28) && File.Exists(_fetchSettings28)) + { + var _fetchSettingsRAW = File.ReadAllLines(_fetchSettings28); + var _fetchPanaceaPath = _fetchSettingsRAW.FirstOrDefault(x => _regexModPath.IsMatch(x)); + + if (_fetchPanaceaPath != null) + { + var _fetchMatch = _regexModPath.Match(_fetchPanaceaPath); + var _fetchValue = _fetchMatch.Groups[1].Value; + + var _fetchConfigPath = Path.GetFullPath(_fetchValue); + + var _fetchManagerPath = PathService.ResolveBuild(currentConfig, true); + var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + if (String.Equals(_fetchConfigPath, _fetchManagerPath, _comparisonRules)) + _isConfigValid28 = true; + } + } + + _isConfigValid1525 = String.IsNullOrEmpty(_fetchPath1525) || _isConfigValid1525; + _isConfigValid28 = String.IsNullOrEmpty(_fetchPath28) || _isConfigValid28; + + return _isConfigValid28 && _isConfigValid1525; + } + + public static void InstallPanacea(Config currentConfig) + { + var _fetchBuildPath = PathService.ResolveBuild(currentConfig, true); + + var _createPath = $"mod_path={_fetchBuildPath}"; + var _fetchPanaceaPath = Path.Combine(AppContext.BaseDirectory, "assembly", "OpenKh.Research.Panacea.dll"); + var _fetchDependenciesPath = Path.Combine(AppContext.BaseDirectory, "assembly", "dependencies"); + + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + var _fetchTarget1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + var _fetchTarget28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + + if (!String.IsNullOrEmpty(_fetchPath1525)) + { + var _fetchDependencyDir = Path.Combine(_fetchPath1525, "dependencies"); + + if (!Directory.Exists(_fetchDependencyDir)) + Directory.CreateDirectory(_fetchDependencyDir); + + var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); + + foreach (var _file in _fetchDependencyFiles) + { + var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); + File.Copy(_file, _fetchTargetPath, true); + } + + File.Copy(_fetchPanaceaPath, _fetchTarget1525, true); + File.WriteAllText(Path.Combine(_fetchPath1525, "panacea_settings.txt"), _createPath); + } + + if (!String.IsNullOrEmpty(_fetchPath28)) + { + var _fetchDependencyDir = Path.Combine(_fetchPath28, "dependencies"); + + if (!Directory.Exists(_fetchDependencyDir)) + Directory.CreateDirectory(_fetchDependencyDir); + + var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); + + foreach (var _file in _fetchDependencyFiles) + { + var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); + File.Copy(_file, _fetchTargetPath, true); + } + + File.Copy(_fetchPanaceaPath, _fetchTarget28, true); + File.WriteAllText(Path.Combine(_fetchPath28, "panacea_settings.txt"), _createPath); + } + + } + + public static void RemovePanacea(Config currentConfig) + { + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + var _fetchTarget1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + var _fetchTarget28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + + if (!String.IsNullOrEmpty(_fetchPath1525)) + { + var _fetchDependencyDir = Path.Combine(_fetchPath1525, "dependencies"); + + if (!Directory.Exists(_fetchDependencyDir)) + Directory.Delete(_fetchDependencyDir, true); + + File.Delete(_fetchTarget1525); + File.Delete(Path.Combine(_fetchPath1525, "panacea_settings.txt")); + } + + if (!String.IsNullOrEmpty(_fetchPath28)) + { + var _fetchDependencyDir = Path.Combine(_fetchPath28, "dependencies"); + + if (!Directory.Exists(_fetchDependencyDir)) + Directory.Delete(_fetchDependencyDir, true); + + File.Delete(_fetchTarget28); + File.Delete(Path.Combine(_fetchPath28, "panacea_settings.txt")); + } + } + + public static bool EnsureBackend(Config currentConfig) + { + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + if (String.IsNullOrEmpty(_fetchPath1525) && String.IsNullOrEmpty(_fetchPath28)) + return false; + + var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); + var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); + + var _fetchSettings1525 = Path.Combine(_fetchPath1525, "LuaBackend.toml"); + var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + var _fetchSettings28 = Path.Combine(_fetchPath28, "LuaBackend.toml"); + var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + var _isConfigValid1525 = false; + var _isConfigValid28 = false; + + if (File.Exists(_fetchAssembly1525) && File.Exists(_fetchSettings1525)) + { + var _fetchSettingsRAW = File.ReadAllText(_fetchSettings1525); + var _fetchSettingsSerial = TomlSerializer.Deserialize(_fetchSettingsRAW); + + if (_fetchSettingsSerial != null) + { + var _fetchTruthTable = new bool[4]; + + for (var i = 0; i < _fetchTruthTable.Length; i++) + { + var _fetchGame = (Game)i; + var _fetchLuaGameID = _backendGameIDs[_fetchGame]; + var _fetchManagerGameID = Config.GameShorthand[_fetchGame]; + + var _fetchGameBuildPath = PathService.ResolveBuild(currentConfig, true); + var _fetchGameScriptPath = Path.Combine(_fetchGameBuildPath, _fetchManagerGameID, "scripts"); + + var _fetchTableRoot = _fetchSettingsSerial[_fetchLuaGameID] as TomlTable; + var _fetchTableScript = _fetchTableRoot != null ? _fetchTableRoot["scripts"] as TomlArray : null; + + if (_fetchTableScript == null) + continue; + + foreach (var _fetchScript in _fetchTableScript) + { + var _fetchTable = _fetchScript as TomlTable; + + if (_fetchTable == null) + continue; + + var _fetchPath = (string)_fetchTable["path"]; + var _fetchIsRelative = (bool)_fetchTable["relative"]; + + if (_fetchIsRelative || String.IsNullOrEmpty(_fetchPath)) + continue; + + var _fetchFullPath = Path.GetFullPath(_fetchPath); + var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + if (String.Equals(_fetchGameScriptPath, _fetchFullPath, _comparisonRules)) + { + _fetchTruthTable[i] = true; + break; + } + } + } + + _isConfigValid1525 = _fetchTruthTable.All(x => x == true); + } + } + + if (File.Exists(_fetchAssembly28) && File.Exists(_fetchSettings28)) + { + var _fetchSettingsRAW = File.ReadAllText(_fetchSettings28); + var _fetchSettingsSerial = TomlSerializer.Deserialize(_fetchSettingsRAW); + + var _fetchGameBuildPath = PathService.ResolveBuild(currentConfig, true); + var _fetchGameScriptPath = Path.Combine(_fetchGameBuildPath, "ddd", "scripts"); + + var _fetchTableRoot = _fetchSettingsSerial["kh3d"] as TomlTable; + var _fetchTableScript = _fetchTableRoot["scripts"] as TomlArray; + + foreach (TomlTable _fetchScript in _fetchTableScript) + { + var _fetchPath = (string)_fetchScript["path"]; + var _fetchIsRelative = (bool)_fetchScript["relative"]; + + if (_fetchIsRelative || String.IsNullOrEmpty(_fetchPath)) + continue; + + var _fetchFullPath = Path.GetFullPath(_fetchPath); + var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + if (String.Equals(_fetchGameScriptPath, _fetchFullPath, _comparisonRules)) + { + _isConfigValid28 = true; + break; + } + } + } + + _isConfigValid1525 = String.IsNullOrEmpty(_fetchPath1525) || _isConfigValid1525; + _isConfigValid28 = String.IsNullOrEmpty(_fetchPath28) || _isConfigValid28; + + return _isConfigValid28 && _isConfigValid1525; + } + + public static void InstallBackend(Config currentConfig) + { + var _templateConfig = "[{0}]\n" + + "scripts = [{{ path = \"{1}\", relative = false }}]\n" + + "exe = \"{2}\"\n" + + "game_docs = \"{3}\"\n"; + + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + var _configLines = new List(); + + for (int i = 0x00; i < _backendGameIDs.Count(); i++) + { + var _fetchGame = (Game)i; + var _fetchLuaGameID = _backendGameIDs[_fetchGame]; + var _fetchExecutable = Config.GameExecutable[_fetchGame]; + var _fetchManagerGameID = Config.GameShorthand[_fetchGame]; + + var _fetchGamePath = _fetchGame == Game.DREAM_DROP_DISTANCE ? "KINGDOM HEARTS HD 2.8 Final Chapter Prologue" : "KINGDOM HEARTS HD 1.5+2.5 ReMIX"; + var _fetchFolderBuild = PathService.ResolveBuild(currentConfig, true); + + var _fetchFolderScripts = Path.Combine(_fetchFolderBuild, _fetchManagerGameID, "scripts"); + var _fetchFolderDocs = Path.Combine(currentConfig.Frontend.TargetPlatform == Platform.STEAM ? "My Games" : "", _fetchGamePath); + + var _formatTemplate = String.Format(_templateConfig, _fetchLuaGameID, _fetchFolderScripts, _fetchExecutable, _fetchFolderDocs).Replace("\\", "/"); + + _configLines.AddRange(_formatTemplate.Split('\n')); + } + + var _fetchBackendPath = Path.Combine(AppContext.BaseDirectory, "assembly", "LuaBackend.dll"); + + if (!String.IsNullOrEmpty(_fetchPath1525)) + { + var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); + var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + File.Copy(_fetchBackendPath, _fetchAssembly1525, true); + File.WriteAllLines(Path.Combine(_fetchPath1525, "LuaBackend.toml"), _configLines); + } + + if (!String.IsNullOrEmpty(_fetchPath28)) + { + var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); + var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + File.Copy(_fetchBackendPath, _fetchAssembly28, true); + File.WriteAllLines(Path.Combine(_fetchPath28, "LuaBackend.toml"), _configLines); + } + } + + public static void RemoveBackend(Config currentConfig) + { + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + if (!String.IsNullOrEmpty(_fetchPath1525)) + { + var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); + var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + File.Delete(_fetchAssembly1525); + File.Delete(Path.Combine(_fetchPath1525, "LuaBackend.toml")); + } + + if (!String.IsNullOrEmpty(_fetchPath28)) + { + var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); + var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + + File.Delete(_fetchAssembly28); + File.Delete(Path.Combine(_fetchPath28, "LuaBackend.toml")); + } + } + + public static bool EnsureSteamAPI(Config currentConfig) + { + var _isValid1525 = false; + var _isValid28 = false; + + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + if (String.IsNullOrEmpty(_fetchPath1525) && String.IsNullOrEmpty(_fetchPath28)) + return false; + + var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); + var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); + + if (!File.Exists(_fetchSteamID1525) && !File.Exists(_fetchSteamID28)) + return false; + + if (!File.Exists(_fetchSteamID1525)) + _isValid1525 = String.IsNullOrEmpty(_fetchPath1525); + + else + { + var _fetchContents = File.ReadAllText(_fetchSteamID1525); + _isValid1525 = String.Equals(_fetchContents, "2552430"); + } + + if (!File.Exists(_fetchSteamID28)) + _isValid28 = String.IsNullOrEmpty(_fetchPath28); + + else + { + var _fetchContents = File.ReadAllText(_fetchSteamID28); + _isValid28 = String.Equals(_fetchContents, "2552440"); + } + + return _isValid1525 && _isValid28; + } + + public static void InstallSteamAPI(Config currentConfig) + { + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); + var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); + + if (!String.IsNullOrEmpty(_fetchPath1525)) + File.WriteAllText(_fetchSteamID1525, "2552430"); + + if (!String.IsNullOrEmpty(_fetchPath28)) + File.WriteAllText(_fetchSteamID28, "2552440"); + } + + public static void RemoveSteamAPI(Config currentConfig) + { + var _fetchPath1525 = PathService.ResolvePath1525(currentConfig); + var _fetchPath28 = PathService.ResolvePath28(currentConfig); + + var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); + var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); + + var _isValid1525 = !String.IsNullOrEmpty(_fetchPath1525) && File.Exists(_fetchSteamID1525); + var _isValid28 = !String.IsNullOrEmpty(_fetchPath28) && File.Exists(_fetchSteamID28); + + if (_isValid1525) + File.Delete(_fetchSteamID1525); + + if (_isValid28) + File.Delete(_fetchSteamID28); + } + } +} diff --git a/OpenKh.Tools.ModManager/Wizard/WizardDirectLaunch.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardDirectLaunch.axaml.cs index 2f4806acf..b62a215a0 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardDirectLaunch.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardDirectLaunch.axaml.cs @@ -32,18 +32,7 @@ public WizardDirectLaunch() InstallPanel.IsVisible = false; NotInstallPanel.IsVisible = true; - var _fetchConfig = message.CurrentConfig; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchConfig); - - var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); - var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); - - var _isValid1525 = String.IsNullOrEmpty(_fetchPath1525) || File.Exists(_fetchSteamID1525); - var _isValid28 = String.IsNullOrEmpty(_fetchPath28) || File.Exists(_fetchSteamID28); - - if (_isValid1525 || _isValid28) + if (PackageService.EnsureSteamAPI(message.CurrentConfig)) { InstallPanel.IsVisible = true; NotInstallPanel.IsVisible = false; @@ -57,46 +46,29 @@ public WizardDirectLaunch() private void OnInstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchConfig); - - var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); - var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); - - if (!String.IsNullOrEmpty(_fetchPath1525)) - File.WriteAllText(_fetchSteamID1525, "2552430"); + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (!String.IsNullOrEmpty(_fetchPath28)) - File.WriteAllText(_fetchSteamID28, "2552440"); + if (_fetchConfig != null) + { + PackageService.InstallSteamAPI(_fetchConfig); - InstallPanel.IsVisible = true; - NotInstallPanel.IsVisible = false; + InstallPanel.IsVisible = true; + NotInstallPanel.IsVisible = false; + } } private void OnUninstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchConfig); - - var _fetchSteamID1525 = Path.Combine(_fetchPath1525, "steam_appid.txt"); - var _fetchSteamID28 = Path.Combine(_fetchPath28, "steam_appid.txt"); + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _isValid1525 = !String.IsNullOrEmpty(_fetchPath1525) && File.Exists(_fetchSteamID1525); - var _isValid28 = !String.IsNullOrEmpty(_fetchPath28) && File.Exists(_fetchSteamID28); - - if (_isValid1525) - File.Delete(_fetchSteamID1525); - - if (_isValid28) - File.Delete(_fetchSteamID28); + if (_fetchConfig != null) + { + PackageService.RemoveSteamAPI(_fetchConfig); - InstallPanel.IsVisible = false; - NotInstallPanel.IsVisible = true; + InstallPanel.IsVisible = false; + NotInstallPanel.IsVisible = true; + } } } } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs index 1f5d26412..25dee104c 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardPanaceaSetup.axaml.cs @@ -24,9 +24,7 @@ public WizardPanaceaSetup() if (message.Self != this) return; - var _fetchFrontend = message.CurrentConfig.Frontend; - - if (_fetchFrontend.TargetPlatform == Platform.PCSX2) + if (message.CurrentConfig.Frontend.TargetPlatform == Platform.PCSX2) message.Reply(false); else @@ -35,66 +33,9 @@ public WizardPanaceaSetup() NotInstallPanel.IsVisible = true; var _fetchConfig = message.CurrentConfig; - var _fetchPaths = _fetchConfig.Frontend.GamePath; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchConfig); - - var _fetchSettings1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); - var _fetchAssembly1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - - var _fetchSettings28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); - var _fetchAssembly28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - - var _isConfigValid1525 = false; - var _isConfigValid28 = false; - - var _regexModPath = new Regex("mod_path=(.*)", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - - if (File.Exists(_fetchAssembly1525) && File.Exists(_fetchSettings1525)) - { - var _fetchSettingsRAW = File.ReadAllLines(_fetchSettings1525); - var _fetchPanaceaPath = _fetchSettingsRAW.FirstOrDefault(x => _regexModPath.IsMatch(x)); - - if (_fetchPanaceaPath != null) - { - var _fetchMatch = _regexModPath.Match(_fetchPanaceaPath); - var _fetchValue = _fetchMatch.Groups[1].Value; - - var _fetchConfigPath = Path.GetFullPath(_fetchValue); - - var _fetchManagerPath = PathService.ResolveBuild(_fetchConfig, true); - var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (String.Equals(_fetchConfigPath, _fetchManagerPath, _comparisonRules)) - _isConfigValid1525 = true; - } - } + _fetchConfig.Frontend.ModBuildType = BuildType.PATCH; - if (File.Exists(_fetchAssembly28) && File.Exists(_fetchSettings28)) - { - var _fetchSettingsRAW = File.ReadAllLines(_fetchSettings28); - var _fetchPanaceaPath = _fetchSettingsRAW.FirstOrDefault(x => _regexModPath.IsMatch(x)); - - if (_fetchPanaceaPath != null) - { - var _fetchMatch = _regexModPath.Match(_fetchPanaceaPath); - var _fetchValue = _fetchMatch.Groups[1].Value; - - var _fetchConfigPath = Path.GetFullPath(_fetchValue); - - var _fetchManagerPath = PathService.ResolveBuild(_fetchConfig, true); - var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (String.Equals(_fetchConfigPath, _fetchManagerPath, _comparisonRules)) - _isConfigValid28 = true; - } - } - - _isConfigValid1525 = String.IsNullOrEmpty(_fetchPath1525) || _isConfigValid1525; - _isConfigValid28 = String.IsNullOrEmpty(_fetchPath28) || _isConfigValid28; - - if (_isConfigValid1525 && _isConfigValid28) + if (PackageService.EnsurePanacea(_fetchConfig)) { _fetchConfig.Frontend.ModBuildType = BuildType.PANACEA; @@ -102,9 +43,6 @@ public WizardPanaceaSetup() NotInstallPanel.IsVisible = false; } - else - _fetchConfig.Frontend.ModBuildType = BuildType.PATCH; - message.Reply(true); } }); @@ -113,100 +51,33 @@ public WizardPanaceaSetup() private void OnInstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; - var _fetchBuildPath = PathService.ResolveBuild(_fetchContext.CurrentConfig, true); - - var _createPath = $"mod_path={_fetchBuildPath}"; - var _fetchPanaceaPath = Path.Combine(AppContext.BaseDirectory, "assembly", "OpenKh.Research.Panacea.dll"); - var _fetchDependenciesPath = Path.Combine(AppContext.BaseDirectory, "assembly", "dependencies"); - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - var _fetchTarget1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - var _fetchTarget28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (!String.IsNullOrEmpty(_fetchPath1525)) + if (_fetchConfig != null) { - var _fetchDependencyDir = Path.Combine(_fetchPath1525, "dependencies"); + PackageService.InstallPanacea(_fetchConfig); - if (!Directory.Exists(_fetchDependencyDir)) - Directory.CreateDirectory(_fetchDependencyDir); + InstallPanel.IsVisible = true; + NotInstallPanel.IsVisible = false; - var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); - - foreach (var _file in _fetchDependencyFiles) - { - var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath, true); - } - - File.Copy(_fetchPanaceaPath, _fetchTarget1525, true); - File.WriteAllText(Path.Combine(_fetchPath1525, "panacea_settings.txt"), _createPath); - } - - if (!String.IsNullOrEmpty(_fetchPath28)) - { - var _fetchDependencyDir = Path.Combine(_fetchPath28, "dependencies"); - - if (!Directory.Exists(_fetchDependencyDir)) - Directory.CreateDirectory(_fetchDependencyDir); - - var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); - - foreach (var _file in _fetchDependencyFiles) - { - var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath, true); - } - - File.Copy(_fetchPanaceaPath, _fetchTarget28, true); - File.WriteAllText(Path.Combine(_fetchPath28, "panacea_settings.txt"), _createPath); + _fetchConfig.Frontend.ModBuildType = BuildType.PANACEA; } - - InstallPanel.IsVisible = true; - NotInstallPanel.IsVisible = false; - - _fetchConfig.ModBuildType = BuildType.PANACEA; } private void OnUninstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - var _fetchTarget1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - var _fetchTarget28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - - if (!String.IsNullOrEmpty(_fetchPath1525)) - { - var _fetchDependencyDir = Path.Combine(_fetchPath1525, "dependencies"); - - if (!Directory.Exists(_fetchDependencyDir)) - Directory.Delete(_fetchDependencyDir, true); - - File.Delete(_fetchTarget1525); - File.Delete(Path.Combine(_fetchPath1525, "panacea_settings.txt")); - } + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (!String.IsNullOrEmpty(_fetchPath28)) + if (_fetchConfig != null) { - var _fetchDependencyDir = Path.Combine(_fetchPath28, "dependencies"); + PackageService.RemovePanacea(_fetchConfig); - if (!Directory.Exists(_fetchDependencyDir)) - Directory.Delete(_fetchDependencyDir, true); + InstallPanel.IsVisible = false; + NotInstallPanel.IsVisible = true; - File.Delete(_fetchTarget28); - File.Delete(Path.Combine(_fetchPath28, "panacea_settings.txt")); + _fetchConfig.Frontend.ModBuildType = BuildType.PATCH; } - - InstallPanel.IsVisible = false; - NotInstallPanel.IsVisible = true; - - _fetchConfig.ModBuildType = BuildType.PATCH; } } } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardScriptSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardScriptSetup.axaml.cs index 644eef0a0..440506b2f 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardScriptSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardScriptSetup.axaml.cs @@ -18,20 +18,6 @@ namespace OpenKh.Tools.ModManager.Wizard { public partial class WizardScriptSetup : ContentPage { - private static string _templateConfig = "[{0}]\n" + - "scripts = [{{ path = \"{1}\", relative = false }}]\n" + - "exe = \"{2}\"\n" + - "game_docs = \"{3}\"\n"; - - public static Dictionary GameIds = new Dictionary() - { - { Game.KINGDOM_HEARTS, "kh1" }, - { Game.KINGDOM_HEARTS_II, "kh2" }, - { Game.CHAIN_OF_MEMORIES, "recom" }, - { Game.BIRTH_BY_SLEEP, "bbs" }, - { Game.DREAM_DROP_DISTANCE, "kh3d" }, - }; - public WizardScriptSetup() { InitializeComponent(); @@ -41,9 +27,7 @@ public WizardScriptSetup() if (message.Self != this) return; - var _fetchFrontend = message.CurrentConfig.Frontend; - - if (_fetchFrontend.TargetPlatform == Platform.PCSX2) + if (message.CurrentConfig.Frontend.TargetPlatform == Platform.PCSX2) message.Reply(false); else @@ -53,98 +37,7 @@ public WizardScriptSetup() var _fetchConfig = message.CurrentConfig; - var _fetchPath1525 = PathService.ResolvePath1525(_fetchConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchConfig); - - var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); - var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); - - var _fetchSettings1525 = Path.Combine(_fetchPath1525, "LuaBackend.toml"); - var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - var _fetchSettings28 = Path.Combine(_fetchPath28, "LuaBackend.toml"); - var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - var _isConfigValid1525 = false; - var _isConfigValid28 = false; - - if (File.Exists(_fetchAssembly1525) && File.Exists(_fetchSettings1525)) - { - var _fetchSettingsRAW = File.ReadAllText(_fetchSettings1525); - var _fetchSettingsSerial = TomlSerializer.Deserialize(_fetchSettingsRAW); - - var _fetchTruthTable = new bool[4]; - - for (var i = 0; i < _fetchTruthTable.Length; i++) - { - var _fetchGame = (Game)i; - var _fetchLuaGameID = GameIds[_fetchGame]; - var _fetchManagerGameID = Config.GameShorthand[_fetchGame]; - - var _fetchGameBuildPath = PathService.ResolveBuild(_fetchConfig, true); - var _fetchGameScriptPath = Path.Combine(_fetchGameBuildPath, _fetchManagerGameID, "scripts"); - - var _fetchTableRoot = _fetchSettingsSerial[_fetchLuaGameID] as TomlTable; - var _fetchTableScript = _fetchTableRoot["scripts"] as TomlArray; - - foreach (TomlTable _fetchScript in _fetchTableScript) - { - var _fetchPath = (string)_fetchScript["path"]; - var _fetchIsRelative = (bool)_fetchScript["relative"]; - - if (_fetchIsRelative || String.IsNullOrEmpty(_fetchPath)) - continue; - - var _fetchFullPath = Path.GetFullPath(_fetchPath); - var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (String.Equals(_fetchGameScriptPath, _fetchFullPath, _comparisonRules)) - { - _fetchTruthTable[i] = true; - break; - } - } - } - ; - - _isConfigValid1525 = _fetchTruthTable.All(x => x == true); - } - - if (File.Exists(_fetchAssembly28) && File.Exists(_fetchSettings28)) - { - var _fetchSettingsRAW = File.ReadAllText(_fetchSettings28); - var _fetchSettingsSerial = TomlSerializer.Deserialize(_fetchSettingsRAW); - - var _fetchGameBuildPath = PathService.ResolveBuild(_fetchConfig, true); - var _fetchGameScriptPath = Path.Combine(_fetchGameBuildPath, "ddd", "scripts"); - - var _fetchTableRoot = _fetchSettingsSerial["kh3d"] as TomlTable; - var _fetchTableScript = _fetchTableRoot["scripts"] as TomlArray; - - foreach (TomlTable _fetchScript in _fetchTableScript) - { - var _fetchPath = (string)_fetchScript["path"]; - var _fetchIsRelative = (bool)_fetchScript["relative"]; - - if (_fetchIsRelative || String.IsNullOrEmpty(_fetchPath)) - continue; - - var _fetchFullPath = Path.GetFullPath(_fetchPath); - var _comparisonRules = OperatingSystem.IsWindows() ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; - - if (String.Equals(_fetchGameScriptPath, _fetchFullPath, _comparisonRules)) - { - _isConfigValid28 = true; - break; - } - } - } - ; - - _isConfigValid1525 = String.IsNullOrEmpty(_fetchPath1525) || _isConfigValid1525; - _isConfigValid28 = String.IsNullOrEmpty(_fetchPath28) || _isConfigValid28; - - if (_isConfigValid1525 && _isConfigValid28) + if (PackageService.EnsureBackend(_fetchConfig)) { InstallPanel.IsVisible = true; NotInstallPanel.IsVisible = false; @@ -158,83 +51,29 @@ public WizardScriptSetup() private void OnInstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - var _configLines = new List(); - - for (int i = 0x00; i < GameIds.Count(); i++) - { - var _fetchGame = (Game)i; - var _fetchLuaGameID = GameIds[_fetchGame]; - var _fetchExecutable = Config.GameExecutable[_fetchGame]; - var _fetchManagerGameID = Config.GameShorthand[_fetchGame]; - - var _fetchGamePath = _fetchGame == Game.DREAM_DROP_DISTANCE ? "KINGDOM HEARTS HD 2.8 Final Chapter Prologue" : "KINGDOM HEARTS HD 1.5+2.5 ReMIX"; - var _fetchFolderBuild = PathService.ResolveBuild(_fetchContext.CurrentConfig, true); - - var _fetchFolderScripts = Path.Combine(_fetchFolderBuild, _fetchManagerGameID, "scripts"); - var _fetchFolderDocs = Path.Combine(_fetchConfig.TargetPlatform == Platform.STEAM ? "My Games" : "", _fetchGamePath); - - var _formatTemplate = String.Format(_templateConfig, _fetchLuaGameID, _fetchFolderScripts, _fetchExecutable, _fetchFolderDocs).Replace("\\", "/"); - - _configLines.AddRange(_formatTemplate.Split('\n')); - } - - var _fetchBackendPath = Path.Combine(AppContext.BaseDirectory, "assembly", "LuaBackend.dll"); + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (!String.IsNullOrEmpty(_fetchPath1525)) + if (_fetchConfig != null) { - var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); - var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + PackageService.InstallBackend(_fetchConfig); - File.Copy(_fetchBackendPath, _fetchAssembly1525, true); - File.WriteAllLines(Path.Combine(_fetchPath1525, "LuaBackend.toml"), _configLines); + InstallPanel.IsVisible = true; + NotInstallPanel.IsVisible = false; } - - if (!String.IsNullOrEmpty(_fetchPath28)) - { - var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); - var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - File.Copy(_fetchBackendPath, _fetchAssembly28, true); - File.WriteAllLines(Path.Combine(_fetchPath28, "LuaBackend.toml"), _configLines); - } - - InstallPanel.IsVisible = true; - NotInstallPanel.IsVisible = false; } private void OnUninstallClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - if (!String.IsNullOrEmpty(_fetchPath1525)) - { - var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); - var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - File.Delete(_fetchAssembly1525); - File.Delete(Path.Combine(_fetchPath1525, "LuaBackend.toml")); - } - - if (!String.IsNullOrEmpty(_fetchPath28)) + if (_fetchConfig != null) { - var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); - var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); + PackageService.RemoveBackend(_fetchConfig); - File.Delete(_fetchAssembly28); - File.Delete(Path.Combine(_fetchPath28, "LuaBackend.toml")); + InstallPanel.IsVisible = false; + NotInstallPanel.IsVisible = true; } - - InstallPanel.IsVisible = false; - NotInstallPanel.IsVisible = true; } } } From 150104a2ce7113ee89fcc741d4c7d6c1d7dbd42f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 17:17:48 +0200 Subject: [PATCH 066/175] [Mod Manager] - Some null gates. --- .../Wizard/WizardExtractSetup.axaml.cs | 48 ++-- .../Wizard/WizardGameSetup.axaml.cs | 267 +++++++++--------- 2 files changed, 167 insertions(+), 148 deletions(-) diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index 9b93008b8..de2cd3948 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -73,21 +73,25 @@ private async Task ExtractTask(List extractList, Config currentConfi private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - ExtractStartButton.IsVisible = false; - ExtractStopButton.IsVisible = true; - - var _fetchExtractList = new List() + if (_fetchConfig != null) { - ExtractKH1.IsChecked.Value, - ExtractKH2.IsChecked.Value, - ExtractCOM.IsChecked.Value, - ExtractBBS.IsChecked.Value, - ExtractDDD.IsChecked.Value, - }; - - WeakReferenceMessenger.Default.Send(new BlockNextRequest()); - ExtractTask(_fetchExtractList, _fetchContext.CurrentConfig); + ExtractStartButton.IsVisible = false; + ExtractStopButton.IsVisible = true; + + var _fetchExtractList = new List() + { + ExtractKH1.IsChecked.HasValue ? ExtractKH1.IsChecked.Value : false, + ExtractKH2.IsChecked.HasValue ? ExtractKH2.IsChecked.Value : false, + ExtractCOM.IsChecked.HasValue ? ExtractCOM.IsChecked.Value : false, + ExtractBBS.IsChecked.HasValue ? ExtractBBS.IsChecked.Value : false, + ExtractDDD.IsChecked.HasValue ? ExtractDDD.IsChecked.Value : false + }; + + WeakReferenceMessenger.Default.Send(new BlockNextRequest()); + ExtractTask(_fetchExtractList, _fetchConfig); + } } private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => ModService.CancelTokenSource.Cancel(); @@ -95,15 +99,19 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE private async void OnExtractPathClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; - var _fetchFolder = await _fetchTopLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + if (_storageProvider != null) { - Title = "Select a Folder for Game Data...", - AllowMultiple = false - }); - - if (_fetchFolder.Count >= 1) - PathExtractData.Text = _fetchFolder[0].Path.LocalPath; + var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for Game Data...", + AllowMultiple = false + }); + + if (_fetchFolder.Count >= 1) + PathExtractData.Text = _fetchFolder[0].Path.LocalPath; + } } } } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index f6f97a224..111607f03 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -40,55 +40,65 @@ public WizardGameSetup() private async void OnFolderClickFirst(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; - var _fetchFolder = await _fetchTopLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + if (_storageProvider != null) { - Title = "Select a Folder for 1.5+2.5...", - AllowMultiple = false - }); + var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for 1.5+2.5...", + AllowMultiple = false + }); - if (_fetchFolder.Count >= 1) - PathCollectionFirst.Text = _fetchFolder[0].Path.LocalPath; + if (_fetchFolder.Count >= 1) + PathCollectionFirst.Text = _fetchFolder[0].Path.LocalPath; + } } private async void OnFolderClickSecond(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; - var _fetchFolder = await _fetchTopLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + if (_storageProvider != null) { - Title = "Select a Folder for 2.8...", - AllowMultiple = false - }); + var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for 2.8...", + AllowMultiple = false + }); - if (_fetchFolder.Count >= 1) - PathCollectionSecond.Text = _fetchFolder[0].Path.LocalPath; + if (_fetchFolder.Count >= 1) + PathCollectionSecond.Text = _fetchFolder[0].Path.LocalPath; + } } private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (_fetchConfig.Frontend.TargetPlatform == Platform.STEAM) + if (_fetchConfig != null) { - var _fetchFolders = new List(); - var _fetchConfigPath = ""; - - if (OperatingSystem.IsWindows()) + if (_fetchConfig.Frontend.TargetPlatform == Platform.STEAM) { - var _fetchSteamKey = Registry.LocalMachine.OpenSubKey("Software\\Valve\\Steam") ?? Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Valve\\Steam"); - var _fetchInstallDir = _fetchSteamKey.GetValue("InstallPath").ToString(); - _fetchConfigPath = Path.Combine(_fetchInstallDir, "steamapps", "libraryfolders.vdf"); - } + var _fetchFolders = new List(); + var _fetchConfigPath = ""; - else if (OperatingSystem.IsLinux()) - { - var _fetchHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + if (OperatingSystem.IsWindows()) + { + var _fetchSteamKey = Registry.LocalMachine.OpenSubKey("Software\\Valve\\Steam") ?? Registry.LocalMachine.OpenSubKey("Software\\Wow6432Node\\Valve\\Steam"); + var _fetchInstallDir = _fetchSteamKey.GetValue("InstallPath").ToString(); + _fetchConfigPath = Path.Combine(_fetchInstallDir, "steamapps", "libraryfolders.vdf"); + } + + else if (OperatingSystem.IsLinux()) + { + var _fetchHome = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - // It is stupid that I have to do this. - // If someone knows a better way PLEASE tell me. - var _steamPossibleDirs = new List() + // It is stupid that I have to do this. + // If someone knows a better way PLEASE tell me. + var _steamPossibleDirs = new List() { Path.Combine(_fetchHome, ".steam/steam"), Path.Combine(_fetchHome, ".local/share/Steam"), @@ -96,144 +106,145 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv Path.Combine(_fetchHome, ".var/app/com.valvesoftware.Steam/data/Steam") }; - var _fetchInstallDir = _steamPossibleDirs.FirstOrDefault(x => Directory.Exists(x)); - _fetchConfigPath = Path.Combine(_fetchInstallDir, "steamapps", "libraryfolders.vdf"); - } - - if (String.IsNullOrEmpty(_fetchConfigPath)) - return; + var _fetchInstallDir = _steamPossibleDirs.FirstOrDefault(x => Directory.Exists(x)); + _fetchConfigPath = Path.Combine(_fetchInstallDir, "steamapps", "libraryfolders.vdf"); + } - var _fetchLibraryConfig = File.ReadAllLines(_fetchConfigPath); - var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + if (String.IsNullOrEmpty(_fetchConfigPath)) + return; - var _fetchPath1525 = ""; - var _fetchPath28 = ""; + var _fetchLibraryConfig = File.ReadAllLines(_fetchConfigPath); + var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - _fetchLibraryConfig.AsParallel().ForAll(_fetchLine => - { - var _fetchMatch = _pathRegex.Match(_fetchLine); + var _fetchPath1525 = ""; + var _fetchPath28 = ""; - if (_fetchMatch.Success) + _fetchLibraryConfig.AsParallel().ForAll(_fetchLine => { - var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); - _fetchFolders.Add(_fetchValue); - } - }); + var _fetchMatch = _pathRegex.Match(_fetchLine); - _fetchFolders.AsParallel().ForAll(_fetchFolder => - { - var _manifestPath1525 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552430.acf"); - var _manifestPath28 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552440.acf"); - - var _installDirRegex = new Regex("installdir[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + if (_fetchMatch.Success) + { + var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); + _fetchFolders.Add(_fetchValue); + } + }); - if (File.Exists(_manifestPath1525)) + _fetchFolders.AsParallel().ForAll(_fetchFolder => { - var _fetchManifest = File.ReadAllLines(_manifestPath1525); + var _manifestPath1525 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552430.acf"); + var _manifestPath28 = Path.Combine(_fetchFolder, "steamapps", "appmanifest_2552440.acf"); - _fetchManifest.AsParallel().ForAll(_fetchLine => + var _installDirRegex = new Regex("installdir[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + + if (File.Exists(_manifestPath1525)) { - var _fetchMatch = _installDirRegex.Match(_fetchLine); + var _fetchManifest = File.ReadAllLines(_manifestPath1525); - if (_fetchMatch.Success) + _fetchManifest.AsParallel().ForAll(_fetchLine => { - var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); - var _fetchLibraryPath = Path.Combine(_fetchFolder, "steamapps", "common", _fetchValue); - _fetchPath1525 = _fetchLibraryPath.Replace("\\", "/"); - } - }); - } + var _fetchMatch = _installDirRegex.Match(_fetchLine); - if (File.Exists(_manifestPath28)) - { - var _fetchManifest = File.ReadAllLines(_manifestPath28); + if (_fetchMatch.Success) + { + var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); + var _fetchLibraryPath = Path.Combine(_fetchFolder, "steamapps", "common", _fetchValue); + _fetchPath1525 = _fetchLibraryPath.Replace("\\", "/"); + } + }); + } - _fetchManifest.AsParallel().ForAll(_fetchLine => + if (File.Exists(_manifestPath28)) { - var _fetchMatch = _installDirRegex.Match(_fetchLine); + var _fetchManifest = File.ReadAllLines(_manifestPath28); - if (_fetchMatch.Success) + _fetchManifest.AsParallel().ForAll(_fetchLine => { - var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); - var _fetchLibraryPath = Path.Combine(_fetchFolder, "steamapps", "common", _fetchValue); - _fetchPath28 = _fetchLibraryPath.Replace("\\", "/"); - } - }); - } - }); + var _fetchMatch = _installDirRegex.Match(_fetchLine); + if (_fetchMatch.Success) + { + var _fetchValue = Regex.Unescape(_fetchMatch.Groups[1].Value); + var _fetchLibraryPath = Path.Combine(_fetchFolder, "steamapps", "common", _fetchValue); + _fetchPath28 = _fetchLibraryPath.Replace("\\", "/"); + } + }); + } + }); - _fetchConfig.Frontend.GamePath[0] = !String.IsNullOrEmpty(_fetchPath1525) ? _fetchPath1525 : ""; - _fetchConfig.Frontend.GamePath[1] = !String.IsNullOrEmpty(_fetchPath28) ? _fetchPath28 : ""; - PathCollectionFirst.Text = _fetchConfig.Frontend.GamePath[0]; - PathCollectionSecond.Text = _fetchConfig.Frontend.GamePath[1]; + _fetchConfig.Frontend.GamePath[0] = !String.IsNullOrEmpty(_fetchPath1525) ? _fetchPath1525 : ""; + _fetchConfig.Frontend.GamePath[1] = !String.IsNullOrEmpty(_fetchPath28) ? _fetchPath28 : ""; - PathCollectionFirst.BorderBrush = !String.IsNullOrEmpty(_fetchPath1525) ? _successBrush : _failBrush; - PathCollectionSecond.BorderBrush = !String.IsNullOrEmpty(_fetchPath28) ? _successBrush : _failBrush; - } + PathCollectionFirst.Text = _fetchConfig.Frontend.GamePath[0]; + PathCollectionSecond.Text = _fetchConfig.Frontend.GamePath[1]; - else if (_fetchConfig.Frontend.TargetPlatform == Platform.EPIC_GAMES_STORE) - { - if (OperatingSystem.IsWindows()) + PathCollectionFirst.BorderBrush = !String.IsNullOrEmpty(_fetchPath1525) ? _successBrush : _failBrush; + PathCollectionSecond.BorderBrush = !String.IsNullOrEmpty(_fetchPath28) ? _successBrush : _failBrush; + } + + else if (_fetchConfig.Frontend.TargetPlatform == Platform.EPIC_GAMES_STORE) { - var _fetchProgramData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); - var _fetchEpicManifestPath = Path.Combine(_fetchProgramData, "Epic", "EpicGamesLauncher", "Data", "Manifests"); + if (OperatingSystem.IsWindows()) + { + var _fetchProgramData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + var _fetchEpicManifestPath = Path.Combine(_fetchProgramData, "Epic", "EpicGamesLauncher", "Data", "Manifests"); - var _fetchAllFiles = Directory.GetFiles(_fetchEpicManifestPath); + var _fetchAllFiles = Directory.GetFiles(_fetchEpicManifestPath); - var _fetchPath1525 = ""; - var _fetchPath28 = ""; + var _fetchPath1525 = ""; + var _fetchPath28 = ""; - _fetchAllFiles.AsParallel().ForAll(_fetchFile => - { - if (Path.GetExtension(_fetchFile) == ".item") + _fetchAllFiles.AsParallel().ForAll(_fetchFile => { - var _fetchFileRAW = File.ReadAllLines(_fetchFile); - var _catalogRegex = new Regex("\"CatalogNamespace\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - var _installDirRegex = new Regex("\"InstallLocation\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - - var _isManifest1525 = false; - var _isManifest28 = false; + if (Path.GetExtension(_fetchFile) == ".item") + { + var _fetchFileRAW = File.ReadAllLines(_fetchFile); + var _catalogRegex = new Regex("\"CatalogNamespace\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + var _installDirRegex = new Regex("\"InstallLocation\":\\s+\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - var _fetchDirectory = ""; + var _isManifest1525 = false; + var _isManifest28 = false; - _fetchFileRAW.AsParallel().ForAll(_fetchLine => - { - var _fetchCatalogMatch = _catalogRegex.Match(_fetchLine); - var _fetchDirectoryMatch = _installDirRegex.Match(_fetchLine); + var _fetchDirectory = ""; - if (_fetchCatalogMatch.Success) + _fetchFileRAW.AsParallel().ForAll(_fetchLine => { - var _fetchValue = Regex.Unescape(_fetchCatalogMatch.Groups[1].Value); + var _fetchCatalogMatch = _catalogRegex.Match(_fetchLine); + var _fetchDirectoryMatch = _installDirRegex.Match(_fetchLine); - _isManifest1525 = _fetchValue == "4158b699dd70447a981fee752d970a3e"; - _isManifest28 = _fetchValue == "c8ff067c1c984cd7ab1998e8a9afc8b6"; - } + if (_fetchCatalogMatch.Success) + { + var _fetchValue = Regex.Unescape(_fetchCatalogMatch.Groups[1].Value); - if (_fetchDirectoryMatch.Success) - _fetchDirectory = Regex.Unescape(_fetchDirectoryMatch.Groups[1].Value); - }); + _isManifest1525 = _fetchValue == "4158b699dd70447a981fee752d970a3e"; + _isManifest28 = _fetchValue == "c8ff067c1c984cd7ab1998e8a9afc8b6"; + } - if (_fetchDirectory != "" && _isManifest1525) - _fetchPath1525 = _fetchDirectory.Replace("\\", "/"); + if (_fetchDirectoryMatch.Success) + _fetchDirectory = Regex.Unescape(_fetchDirectoryMatch.Groups[1].Value); + }); - if (_fetchDirectory != "" && _isManifest28) - _fetchPath28 = _fetchDirectory.Replace("\\", "/"); - } - }); + if (_fetchDirectory != "" && _isManifest1525) + _fetchPath1525 = _fetchDirectory.Replace("\\", "/"); - PathCollectionFirst.Text = !String.IsNullOrEmpty(_fetchPath1525) ? _fetchPath1525 : PathCollectionFirst.Text; - PathCollectionFirst.BorderBrush = !String.IsNullOrEmpty(_fetchPath1525) ? _successBrush : _failBrush; + if (_fetchDirectory != "" && _isManifest28) + _fetchPath28 = _fetchDirectory.Replace("\\", "/"); + } + }); - PathCollectionSecond.Text = !String.IsNullOrEmpty(_fetchPath28) ? _fetchPath28 : PathCollectionSecond.Text; - PathCollectionSecond.BorderBrush = !String.IsNullOrEmpty(_fetchPath28) ? _successBrush : _failBrush; - } + PathCollectionFirst.Text = !String.IsNullOrEmpty(_fetchPath1525) ? _fetchPath1525 : PathCollectionFirst.Text; + PathCollectionFirst.BorderBrush = !String.IsNullOrEmpty(_fetchPath1525) ? _successBrush : _failBrush; - else - { - PathCollectionFirst.BorderBrush = _failBrush; - PathCollectionSecond.BorderBrush = _failBrush; + PathCollectionSecond.Text = !String.IsNullOrEmpty(_fetchPath28) ? _fetchPath28 : PathCollectionSecond.Text; + PathCollectionSecond.BorderBrush = !String.IsNullOrEmpty(_fetchPath28) ? _successBrush : _failBrush; + } + + else + { + PathCollectionFirst.BorderBrush = _failBrush; + PathCollectionSecond.BorderBrush = _failBrush; + } } } } From e855786f676b10c0570aa5290a4dc7ac4cc01a04 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 17:26:06 +0200 Subject: [PATCH 067/175] [Mod Manager] - MOAR null gates. --- .../Views/MainView.axaml.cs | 632 ++++++++---------- .../Views/ModStatusView.axaml.cs | 1 + .../Views/SetupWizardView.axaml.cs | 124 ++-- 3 files changed, 350 insertions(+), 407 deletions(-) diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 274ef7bb8..e89dd03e8 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -113,16 +113,22 @@ private void OnUpPriorityClicked(object? sender, RoutedEventArgs e) { // Fetch the context, the mod list, and the current index. var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext.InstalledMods; - var _fetchCurrentIndex = ModList.MainList.SelectedIndex; - // If the mod is already super-hot-fire number one, we don't need to do nothin'. - if (_fetchCurrentIndex == 0) - return; + var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - // Make the move, select the mod. - _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex - 1); - _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex - 1]; + if (_fetchConfig != null && _fetchModList != null) + { + var _fetchCurrentIndex = ModList.MainList.SelectedIndex; + + // If the mod is already super-hot-fire number one, we don't need to do nothin'. + if (_fetchCurrentIndex == 0) + return; + + // Make the move, select the mod. + _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex - 1); + _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex - 1]; + } } // This executes if the move up button is clicked. @@ -130,213 +136,224 @@ private void OnUpPriorityClicked(object? sender, RoutedEventArgs e) private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) { // Same shit as OnUpPriorityClicked, but reversed. - var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext.InstalledMods; - var _fetchCurrentIndex = ModList.MainList.SelectedIndex; - if (_fetchCurrentIndex == ModList.MainList.ItemCount - 1) - return; + var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (!_fetchModList[_fetchCurrentIndex + 1].ModValid) - return; + if (_fetchConfig != null && _fetchModList != null) + { + var _fetchCurrentIndex = ModList.MainList.SelectedIndex; + + if (_fetchCurrentIndex == ModList.MainList.ItemCount - 1) + return; + + if (!_fetchModList[_fetchCurrentIndex + 1].ModValid) + return; - _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex + 1); - _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; + _fetchModList.Move(_fetchCurrentIndex, _fetchCurrentIndex + 1); + _fetchContext.CurrentMod = _fetchModList[_fetchCurrentIndex + 1]; + } } // This executes when we click the remove mod button. private async void OnRemoveClicked(object? sender, RoutedEventArgs e) { - // Ask the user if they *really* want to remove said mod. - - var _removeDialog = new RemoveModDialog(); - bool? _fetchResult = await _removeDialog.ShowDialog(this); + var _fetchContext = DataContext as MainViewModel; - // If they indeed do: + var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; + var _fetchCurrentMod = _fetchContext != null ? _fetchContext.CurrentMod : null; - if (_fetchResult == true) + if (_fetchModList != null) { - // Fetch the context, the mod list, and the current mod. - var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext.InstalledMods; - var _fetchCurrentMod = _fetchContext.CurrentMod; + // Ask the user if they *really* want to remove said mod. - // Fetch the path too we kinda need it. - var _fetchModPath = _fetchCurrentMod.ModPath; + var _removeDialog = new RemoveModDialog(); + bool? _fetchResult = await _removeDialog.ShowDialog(this); - // Remove the mod from the list. - _fetchModList.Remove(_fetchCurrentMod); + // If they indeed do: - // If we still have mods, select one. - if (_fetchModList.Count > 0) - _fetchContext.CurrentMod = _fetchModList.First(); - - // If we do not, nullify selection. - else + if (_fetchResult == true) { - _fetchContext.HasModsInstalled = false; - _fetchContext.CurrentMod = null; - } + // Fetch the path too we kinda need it. + var _fetchModPath = _fetchCurrentMod.ModPath; - // Remove the mod directory. - // This is a task for a reason. This isn't being awaited for a reason. Do not listen to IntelliSense on this one. - Task.Run(() => - { - if (Directory.Exists(_fetchModPath)) - Directory.Delete(_fetchModPath, true); - }); + // Remove the mod from the list. + _fetchModList.Remove(_fetchCurrentMod); + + // If we still have mods, select one. + if (_fetchModList.Count > 0) + _fetchCurrentMod = _fetchModList.First(); + + // If we do not, nullify selection. + else + { + _fetchContext.HasModsInstalled = false; + _fetchCurrentMod = null; + } + + // Remove the mod directory. + // This is a task for a reason. This isn't being awaited for a reason. Do not listen to IntelliSense on this one. + Task.Run(() => + { + if (Directory.Exists(_fetchModPath)) + Directory.Delete(_fetchModPath, true); + }); + } } } private async void OnInstallClicked(object? sender, RoutedEventArgs e) { - var _installDialog = new InstallModDialog(); - string? _fetchResult = await _installDialog.ShowDialog(this); - var _fetchContext = DataContext as MainViewModel; - var _fetchModsList = _fetchContext.InstalledMods; - var _fetchConfig = _fetchContext.CurrentConfig; - - var _fetchModPath = PathService.ResolveMod(_fetchConfig); - var _fetchInstallResult = 0x00; + var _fetchModsList = _fetchContext != null ? _fetchContext.InstalledMods : null; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (_fetchResult != null && !string.IsNullOrEmpty(_fetchResult)) + if (_fetchConfig != null) { - var _progressDialog = new ModProgressDialog(); - _progressDialog.ShowDialog(this); + var _installDialog = new InstallModDialog(); + string? _fetchResult = await _installDialog.ShowDialog(this); - if (File.Exists(_fetchResult)) - _fetchInstallResult = - await ModService.InstallLocal - ( - _fetchModPath, - _fetchResult, - (int processed, int total) => - { - Dispatcher.UIThread.Post(() => + var _fetchModPath = PathService.ResolveMod(_fetchConfig); + var _fetchInstallResult = 0x00; + + if (_fetchResult != null && !string.IsNullOrEmpty(_fetchResult)) + { + var _progressDialog = new ModProgressDialog(); + _progressDialog.ShowDialog(this); + + if (File.Exists(_fetchResult)) + _fetchInstallResult = + await ModService.InstallLocal + ( + _fetchModPath, + _fetchResult, + (int processed, int total) => { - _progressDialog.InstallProgress.Maximum = total; - _progressDialog.InstallProgress.Value = processed; - }); + Dispatcher.UIThread.Post(() => + { + _progressDialog.InstallProgress.Maximum = total; + _progressDialog.InstallProgress.Value = processed; + }); - if (ModService.CancelToken.IsCancellationRequested) - return false; + if (ModService.CancelToken.IsCancellationRequested) + return false; - return true; - } - ); + return true; + } + ); - else - _fetchInstallResult = - await ModService.InstallGit - ( - _fetchModPath, - _fetchResult, - new TransferProgressHandler((progress) => - { - Dispatcher.UIThread.Post(() => + else + _fetchInstallResult = + await ModService.InstallGit + ( + _fetchModPath, + _fetchResult, + new TransferProgressHandler((progress) => { - _progressDialog.InstallProgress.Maximum = progress.TotalObjects; - _progressDialog.InstallProgress.Value = progress.ReceivedObjects; - }); + Dispatcher.UIThread.Post(() => + { + _progressDialog.InstallProgress.Maximum = progress.TotalObjects; + _progressDialog.InstallProgress.Value = progress.ReceivedObjects; + }); - if (ModService.CancelToken.IsCancellationRequested) - return false; + if (ModService.CancelToken.IsCancellationRequested) + return false; - return true; - } - )); + return true; + } + )); - _progressDialog.Close(true); + _progressDialog.Close(true); - if (_fetchInstallResult == 0x01) - { - var _errorDialog = new InvalidYamlError(); - await _errorDialog.ShowDialog(this); - } + if (_fetchInstallResult == 0x01) + { + var _errorDialog = new InvalidYamlError(); + await _errorDialog.ShowDialog(this); + } - else if (_fetchInstallResult == 0x00) - { - var _fetchLatestMod = new DirectoryInfo(_fetchModPath).GetDirectories() - .OrderByDescending(d => d.LastWriteTimeUtc) - .First(); + else if (_fetchInstallResult == 0x00) + { + var _fetchLatestMod = new DirectoryInfo(_fetchModPath).GetDirectories() + .OrderByDescending(d => d.LastWriteTimeUtc) + .First(); - var _fetchPathGit = Path.Combine(_fetchLatestMod.FullName, ".git"); - var _fetchYamlName = Path.Combine(_fetchLatestMod.FullName, "mod.yml"); - var _fetchPathIcon = Path.Combine(_fetchLatestMod.FullName, "icon.png"); + var _fetchPathGit = Path.Combine(_fetchLatestMod.FullName, ".git"); + var _fetchYamlName = Path.Combine(_fetchLatestMod.FullName, "mod.yml"); + var _fetchPathIcon = Path.Combine(_fetchLatestMod.FullName, "icon.png"); - var _fetchMetadata = Metadata.Read(_fetchYamlName); + var _fetchMetadata = Metadata.Read(_fetchYamlName); - if (_fetchMetadata.IsValid) - { - var _modModel = new ModModel - { - ModTitle = _fetchMetadata.Title, - ModAuthor = _fetchMetadata.OriginalAuthor, - ModDescription = _fetchMetadata.Description, - ModPath = _fetchLatestMod.FullName, - ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), - ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, - ModActive = true, - ModValid = true - }; - - if (Directory.Exists(_fetchPathGit)) + if (_fetchMetadata.IsValid) { - if (Repository.IsValid(_fetchPathGit)) + var _modModel = new ModModel { - var _fetchGit = new Repository(_fetchPathGit); - - if (!_fetchGit.Info.IsHeadDetached) + ModTitle = _fetchMetadata.Title, + ModAuthor = _fetchMetadata.OriginalAuthor, + ModDescription = _fetchMetadata.Description, + ModPath = _fetchLatestMod.FullName, + ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, + ModActive = true, + ModValid = true + }; + + if (Directory.Exists(_fetchPathGit)) + { + if (Repository.IsValid(_fetchPathGit)) { - var _fetchRemote = _fetchGit.Network.Remotes["origin"]; + var _fetchGit = new Repository(_fetchPathGit); - _modModel.ModSource = new Uri(_fetchRemote.Url); - _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); + if (!_fetchGit.Info.IsHeadDetached) + { + var _fetchRemote = _fetchGit.Network.Remotes["origin"]; - _modModel.ModPlatform = _modModel.ModSource.Host; + _modModel.ModSource = new Uri(_fetchRemote.Url); + _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); - Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); + _modModel.ModPlatform = _modModel.ModSource.Host; - var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; - _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; - } + Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); - _fetchGit.Dispose(); + var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; + _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; + } - var _fetchGitDir = new DirectoryInfo(_fetchPathGit); + _fetchGit.Dispose(); - foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) - if (_fetchFile.Exists) - _fetchFile.Attributes &= ~FileAttributes.ReadOnly; - } - } + var _fetchGitDir = new DirectoryInfo(_fetchPathGit); - var _fetchFirstInvalid = _fetchModsList.FirstOrDefault(x => !x.ModValid); - var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : _fetchModsList.Count; + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + } - _fetchModsList.Insert(_fetchModIndex, _modModel); - _fetchContext.HasModsInstalled = true; - } + var _fetchFirstInvalid = _fetchModsList.FirstOrDefault(x => !x.ModValid); + var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : _fetchModsList.Count; - else - { - var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + _fetchModsList.Insert(_fetchModIndex, _modModel); + _fetchContext.HasModsInstalled = true; + } - var _modModel = new ModModel + else { - ModTitle = _fetchMetadata.Title, - ModAuthor = "This mod is invalid!", - ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", - ModIcon = new Bitmap(AssetLoader.Open(uri)), - ModPath = _fetchLatestMod.FullName, - ModActive = false, - ModValid = false - }; - - _fetchModsList.Add(_modModel); + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = _fetchMetadata.Title, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchLatestMod.FullName, + ModActive = false, + ModValid = false + }; + + _fetchModsList.Add(_modModel); + } } } } @@ -345,60 +362,66 @@ await ModService.InstallGit private async void OnOpenFolderClicked(object? sender, RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchCurrentMod = _fetchContext.CurrentMod; + var _fetchCurrentMod = _fetchContext != null ? _fetchContext.CurrentMod : null; - var _fetchTopLevel = GetTopLevel(this); - var _fetchDirectoryInfo = new DirectoryInfo(_fetchCurrentMod.ModPath); + if (_fetchCurrentMod != null) + { + var _fetchTopLevel = GetTopLevel(this); + var _fetchDirectoryInfo = new DirectoryInfo(_fetchCurrentMod.ModPath); - if (_fetchTopLevel?.Launcher != null) - await _fetchTopLevel.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); + if (_fetchTopLevel?.Launcher != null) + await _fetchTopLevel.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); + } } private void OnModActiveChanged(object? sender, ModListView.ModActiveChangedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext.InstalledMods; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(_fetchConfig), "mod_memory.yml"); + if (_fetchConfig != null) + { + var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(_fetchConfig), "mod_memory.yml"); - var _fetchMemoryRAW = File.ReadAllText(_fetchMemoryPath); - var _fetchMemory = YamlSerializer.Deserialize>(_fetchMemoryRAW); + var _fetchMemoryRAW = File.ReadAllText(_fetchMemoryPath); + var _fetchMemory = YamlSerializer.Deserialize>(_fetchMemoryRAW); - var _fetchSenderHash = ModService.ResolveMD5(e.TargetMod, _fetchConfig); - var _fetchMemoryItem = _fetchMemory.FirstOrDefault(x => x.ModHash == _fetchSenderHash); + var _fetchSenderHash = ModService.ResolveMD5(e.TargetMod, _fetchConfig); + var _fetchMemoryItem = _fetchMemory.FirstOrDefault(x => x.ModHash == _fetchSenderHash); - if (_fetchMemoryItem == null) - { - _fetchMemoryItem = new MemoryModel + if (_fetchMemoryItem == null) { - ModHash = _fetchSenderHash, - ModActive = e.IsChecked, - ModIndex = _fetchModList.IndexOf(e.TargetMod) - }; + _fetchMemoryItem = new MemoryModel + { + ModHash = _fetchSenderHash, + ModActive = e.IsChecked, + ModIndex = _fetchModList.IndexOf(e.TargetMod) + }; - _fetchMemory.Add(_fetchMemoryItem); - } + _fetchMemory.Add(_fetchMemoryItem); + } - else - { - var _fetchMemoryIndex = _fetchMemory.IndexOf(_fetchMemoryItem); - _fetchMemory[_fetchMemoryIndex].ModActive = e.IsChecked; - } + else + { + var _fetchMemoryIndex = _fetchMemory.IndexOf(_fetchMemoryItem); + _fetchMemory[_fetchMemoryIndex].ModActive = e.IsChecked; + } - var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); - File.WriteAllText(_fetchMemoryPath, _fetchSerial); + var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); + File.WriteAllText(_fetchMemoryPath, _fetchSerial); + } } private async void OnRunRequested(object? sender, RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - await ModService.Run(_fetchConfig, GetTopLevel(this)); + if (_fetchConfig != null) + await ModService.Run(_fetchConfig, GetTopLevel(this)); } - private async void OnBuildRequested(object? sender, RoutedEventArgs e) { var _progressDialog = new BuildProgressDialog(); @@ -462,208 +485,117 @@ await ModService.Build private async void OnBuildRunRequested(object? sender, RoutedEventArgs e) { - var _progressDialog = new BuildProgressDialog(); - _progressDialog.ShowDialog(this); + var _fetchContext = DataContext as MainViewModel; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - int _assetProcessed = 0; - int _assetTotal = 0; - string _currentModName = "N/A"; + if (_fetchConfig != null) + { + var _progressDialog = new BuildProgressDialog(); + _progressDialog.ShowDialog(this); - var _fetchContext = DataContext as MainViewModel; + int _assetProcessed = 0; + int _assetTotal = 0; + string _currentModName = "N/A"; - var _fetchConfig = _fetchContext.CurrentConfig; - var _fetchFrontConfig = _fetchConfig.Frontend; + var _fetchFrontConfig = _fetchConfig.Frontend; - var _buildResult = - await ModService.Build - ( - _fetchContext.InstalledMods, - _fetchConfig, + var _buildResult = + await ModService.Build + ( + _fetchContext.InstalledMods, + _fetchConfig, - (string currModName, int procMod, int totalMod) => - { - Dispatcher.UIThread.Post(() => + (string currModName, int procMod, int totalMod) => { - _currentModName = currModName; - - _progressDialog.ModProgress.Maximum = totalMod; - _progressDialog.ModProgress.Value = procMod; - _progressDialog.ModProgress.ProgressTextFormat = $"Currently Building: {currModName}"; + Dispatcher.UIThread.Post(() => + { + _currentModName = currModName; - _progressDialog.AssetProgress.Maximum = _assetTotal; - _progressDialog.AssetProgress.Value = _assetProcessed; - }); + _progressDialog.ModProgress.Maximum = totalMod; + _progressDialog.ModProgress.Value = procMod; + _progressDialog.ModProgress.ProgressTextFormat = $"Currently Building: {currModName}"; - if (ModService.CancelToken.IsCancellationRequested) - return false; + _progressDialog.AssetProgress.Maximum = _assetTotal; + _progressDialog.AssetProgress.Value = _assetProcessed; + }); - return true; - }, + if (ModService.CancelToken.IsCancellationRequested) + return false; - (int processed, int total) => - { - _assetProcessed = processed; - _assetTotal = total; + return true; + }, - if (ModService.CancelToken.IsCancellationRequested) - return false; + (int processed, int total) => + { + _assetProcessed = processed; + _assetTotal = total; - return true; - }); + if (ModService.CancelToken.IsCancellationRequested) + return false; - _progressDialog.Close(true); + return true; + }); - switch (_buildResult) - { - case 0: - await ModService.Run(_fetchConfig, GetTopLevel(this)); - break; + _progressDialog.Close(true); - case 1: + switch (_buildResult) { - var _errorDialog = new BuildModError(); - _errorDialog.MainText.Text = string.Format(_errorDialog.MainText.Text, _currentModName); - await _errorDialog.ShowDialog(this); + case 0: + await ModService.Run(_fetchConfig, GetTopLevel(this)); + break; + + case 1: + { + var _errorDialog = new BuildModError(); + _errorDialog.MainText.Text = string.Format(_errorDialog.MainText.Text, _currentModName); + await _errorDialog.ShowDialog(this); + } + break; } - break; } } private async void OnSetupRequested(object? sender, RoutedEventArgs e) => await new SetupWizardView().ShowDialog(this); + private async void OnArgumentsRequested(object? sender, RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchDialog = new LaunchArgsDialog(); - var _fetchResult = await _fetchDialog.ShowDialog(this); + if (_fetchConfig != null) + { + var _fetchDialog = new LaunchArgsDialog(); + var _fetchResult = await _fetchDialog.ShowDialog(this); - if (!String.IsNullOrEmpty(_fetchResult)) - _fetchConfig.Frontend.LaunchArguments = _fetchResult; + if (!String.IsNullOrEmpty(_fetchResult)) + _fetchConfig.Frontend.LaunchArguments = _fetchResult; + } } private async void OnPanaceaClicked(object? sender, RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; - var _fetchBuildPath = PathService.ResolveBuild(_fetchContext.CurrentConfig, true); + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _createPath = $"mod_path={_fetchBuildPath}"; - var _fetchPanaceaPath = Path.Combine(AppContext.BaseDirectory, "assembly", "OpenKh.Research.Panacea.dll"); - var _fetchDependenciesPath = Path.Combine(AppContext.BaseDirectory, "assembly", "dependencies"); - - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - var _fetchTarget1525 = Path.Combine(_fetchPath1525, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - var _fetchTarget28 = Path.Combine(_fetchPath28, OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll"); - - if (!String.IsNullOrEmpty(_fetchPath1525)) + if (_fetchConfig != null) { - var _fetchDependencyDir = Path.Combine(_fetchPath1525, "dependencies"); - - if (!Directory.Exists(_fetchDependencyDir)) - Directory.CreateDirectory(_fetchDependencyDir); - - var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); + PackageService.InstallPanacea(_fetchConfig); + _fetchContext.ConfigurationValid = _fetchConfig.IsValid(); - foreach (var _file in _fetchDependencyFiles) - { - var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath, true); - } - - File.Copy(_fetchPanaceaPath, _fetchTarget1525, true); - File.WriteAllText(Path.Combine(_fetchPath1525, "panacea_settings.txt"), _createPath); + await new PanaceaInstallDialog().ShowDialog(this); } - - if (!String.IsNullOrEmpty(_fetchPath28)) - { - var _fetchDependencyDir = Path.Combine(_fetchPath28, "dependencies"); - - if (!Directory.Exists(_fetchDependencyDir)) - Directory.CreateDirectory(_fetchDependencyDir); - - var _fetchDependencyFiles = Directory.GetFiles(_fetchDependenciesPath); - - foreach (var _file in _fetchDependencyFiles) - { - var _fetchTargetPath = Path.Combine(_fetchDependencyDir, Path.GetFileName(_file)); - File.Copy(_file, _fetchTargetPath, true); - } - - File.Copy(_fetchPanaceaPath, _fetchTarget28, true); - File.WriteAllText(Path.Combine(_fetchPath28, "panacea_settings.txt"), _createPath); - } - - _fetchConfig.ModBuildType = BuildType.PANACEA; - _fetchContext.ConfigurationValid = _fetchContext.CurrentConfig.IsValid(); - - await new PanaceaInstallDialog().ShowDialog(this); } private async void OnBackendClicked(object? sender, RoutedEventArgs e) { - string _templateConfig = "[{0}]\n" + - "scripts = [{{ path = \"{1}\", relative = false }}]\n" + - "exe = \"{2}\"\n" + - "game_docs = \"{3}\"\n"; - - Dictionary GameIds = new Dictionary() - { - { Game.KINGDOM_HEARTS, "kh1" }, - { Game.KINGDOM_HEARTS_II, "kh2" }, - { Game.CHAIN_OF_MEMORIES, "recom" }, - { Game.BIRTH_BY_SLEEP, "bbs" }, - { Game.DREAM_DROP_DISTANCE, "kh3d" }, - }; - var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig.Frontend; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchPath1525 = PathService.ResolvePath1525(_fetchContext.CurrentConfig); - var _fetchPath28 = PathService.ResolvePath28(_fetchContext.CurrentConfig); - - var _configLines = new List(); - - for (int i = 0x00; i < GameIds.Count(); i++) + if (_fetchConfig != null) { - var _fetchGame = (Game)i; - var _fetchLuaGameID = GameIds[_fetchGame]; - var _fetchExecutable = Config.GameExecutable[_fetchGame]; - var _fetchManagerGameID = Config.GameShorthand[_fetchGame]; - - var _fetchGamePath = _fetchGame == Game.DREAM_DROP_DISTANCE ? "KINGDOM HEARTS HD 2.8 Final Chapter Prologue" : "KINGDOM HEARTS HD 1.5+2.5 ReMIX"; - var _fetchFolderBuild = PathService.ResolveBuild(_fetchContext.CurrentConfig, true); - - var _fetchFolderScripts = Path.Combine(_fetchFolderBuild, _fetchManagerGameID, "scripts"); - var _fetchFolderDocs = Path.Combine(_fetchConfig.TargetPlatform == Platform.STEAM ? "My Games" : "", _fetchGamePath); - - var _formatTemplate = String.Format(_templateConfig, _fetchLuaGameID, _fetchFolderScripts, _fetchExecutable, _fetchFolderDocs).Replace("\\", "/"); + PackageService.InstallBackend(_fetchConfig); - _configLines.AddRange(_formatTemplate.Split('\n')); + await new BackendInstallDialog().ShowDialog(this); } - - var _fetchBackendPath = Path.Combine(AppContext.BaseDirectory, "assembly", "LuaBackend.dll"); - - if (!String.IsNullOrEmpty(_fetchPath1525)) - { - var _fetchPanacea1525 = Path.Combine(_fetchPath1525, "panacea_settings.txt"); - var _fetchAssembly1525 = Path.Combine(_fetchPath1525, File.Exists(_fetchPanacea1525) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - File.Copy(_fetchBackendPath, _fetchAssembly1525, true); - File.WriteAllLines(Path.Combine(_fetchPath1525, "LuaBackend.toml"), _configLines); - } - - if (!String.IsNullOrEmpty(_fetchPath28)) - { - var _fetchPanacea28 = Path.Combine(_fetchPath28, "panacea_settings.txt"); - var _fetchAssembly28 = Path.Combine(_fetchPath28, File.Exists(_fetchPanacea28) ? "LuaBackend.dll" : (OperatingSystem.IsWindows() ? "DBGHELP.dll" : "version.dll")); - - File.Copy(_fetchBackendPath, _fetchAssembly28, true); - File.WriteAllLines(Path.Combine(_fetchPath28, "LuaBackend.toml"), _configLines); - } - - await new BackendInstallDialog().ShowDialog(this); } } diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs index d19dceb3b..1f564813d 100644 --- a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs @@ -63,6 +63,7 @@ public ModStatusView() } private void OnProcessBuildRun(object? sender, RoutedEventArgs e) => OnBuildRunRequested(); + private void OnProcessRun(object? sender, RoutedEventArgs e) => OnRunRequested(); private void OnSetupRequested(object? sender, RoutedEventArgs e) => OnSetupRequested(); diff --git a/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs b/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs index ddbbd0e0a..dea235d56 100644 --- a/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs @@ -22,6 +22,7 @@ namespace OpenKh.Tools.ModManager.Views { public record BlockNextRequest(); + public record UnblockNextRequest(); public class PageRequestMessage : RequestMessage @@ -93,98 +94,107 @@ public SetupWizardView() private void OnNextPage(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchBackList = _rootModel.PastWizardPages; - var _fetchNextList = _rootModel.FutureWizardPages; + if (_fetchConfig != null) + { + var _fetchBackList = _rootModel.PastWizardPages; + var _fetchNextList = _rootModel.FutureWizardPages; - var _fetchNextPage = _fetchNextList.FirstOrDefault(); + var _fetchNextPage = _fetchNextList.FirstOrDefault(); - while (_fetchNextPage != null) - { - _fetchNextList.Remove(_fetchNextPage); - _fetchBackList.Add(_rootModel.CurrentWizardPage); + while (_fetchNextPage != null) + { + _fetchNextList.Remove(_fetchNextPage); + _fetchBackList.Add(_rootModel.CurrentWizardPage); - _rootModel.CurrentWizardPage = _fetchNextPage; + _rootModel.CurrentWizardPage = _fetchNextPage; - var _fetchMessage = new PageRequestMessage(_rootModel.CurrentWizardPage, _fetchConfig); - WeakReferenceMessenger.Default.Send(_fetchMessage); + var _fetchMessage = new PageRequestMessage(_rootModel.CurrentWizardPage, _fetchConfig); + WeakReferenceMessenger.Default.Send(_fetchMessage); - if (_fetchMessage.HasReceivedResponse) - { - if (!_fetchMessage.Response) - _fetchNextPage = _fetchNextList.FirstOrDefault(); + if (_fetchMessage.HasReceivedResponse) + { + if (!_fetchMessage.Response) + _fetchNextPage = _fetchNextList.FirstOrDefault(); - else - break; + else + break; + } } - } - if (_fetchNextPage == null) - OnSubmitClick(sender, e); + if (_fetchNextPage == null) + OnSubmitClick(sender, e); - if (_fetchNextList.Count == 0) - { - NextButton.IsEnabled = false; + if (_fetchNextList.Count == 0) + { + NextButton.IsEnabled = false; - FinishButton.IsVisible = true; - CancelButton.IsVisible = false; - } + FinishButton.IsVisible = true; + CancelButton.IsVisible = false; + } - BackButton.IsEnabled = true; + BackButton.IsEnabled = true; + } } private void OnBackPage(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - var _fetchBackList = _rootModel.PastWizardPages; - var _fetchNextList = _rootModel.FutureWizardPages; + if (_fetchConfig != null) + { + var _fetchBackList = _rootModel.PastWizardPages; + var _fetchNextList = _rootModel.FutureWizardPages; - var _fetchBackPage = _fetchBackList.LastOrDefault(); + var _fetchBackPage = _fetchBackList.LastOrDefault(); - while (_fetchBackPage != null) - { - _fetchBackList.Remove(_fetchBackPage); - _fetchNextList.Insert(0, _rootModel.CurrentWizardPage); + while (_fetchBackPage != null) + { + _fetchBackList.Remove(_fetchBackPage); + _fetchNextList.Insert(0, _rootModel.CurrentWizardPage); - _rootModel.CurrentWizardPage = _fetchBackPage; + _rootModel.CurrentWizardPage = _fetchBackPage; - var _fetchMessage = new PageRequestMessage(_rootModel.CurrentWizardPage, _fetchConfig); - WeakReferenceMessenger.Default.Send(_fetchMessage); + var _fetchMessage = new PageRequestMessage(_rootModel.CurrentWizardPage, _fetchConfig); + WeakReferenceMessenger.Default.Send(_fetchMessage); - if (_fetchMessage.HasReceivedResponse) - { - if (!_fetchMessage.Response) - _fetchBackPage = _fetchBackList.LastOrDefault(); + if (_fetchMessage.HasReceivedResponse) + { + if (!_fetchMessage.Response) + _fetchBackPage = _fetchBackList.LastOrDefault(); - else - break; + else + break; + } } - } - if (_fetchBackPage == null) - OnSubmitClick(sender, e); + if (_fetchBackPage == null) + OnSubmitClick(sender, e); - if (_fetchBackList.Count == 0) - BackButton.IsEnabled = false; + if (_fetchBackList.Count == 0) + BackButton.IsEnabled = false; - FinishButton.IsVisible = false; - CancelButton.IsVisible = true; + FinishButton.IsVisible = false; + CancelButton.IsVisible = true; - NextButton.IsEnabled = true; + NextButton.IsEnabled = true; + } } - private void OnSubmitClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; - var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - _fetchContext.ConfigurationValid = _fetchConfig.IsValid(); - _fetchConfig.Commit(); + if (_fetchConfig != null) + { - Close(); + _fetchContext.ConfigurationValid = _fetchConfig.IsValid(); + _fetchConfig.Commit(); + + Close(); + } } } } From 7ed6d733f3ce656a1a85994b6edbd2ffd3d0acf0 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 17:26:30 +0200 Subject: [PATCH 068/175] [Mod Manager] - EVEN MOAR NULL GATES --- .../Views/MainView.axaml.cs | 20 +++++------ .../Views/ModStatusView.axaml.cs | 32 ++++++++--------- .../Views/SetupWizardView.axaml.cs | 2 +- .../Wizard/WizardExtractSetup.axaml.cs | 36 +++++++++---------- 4 files changed, 43 insertions(+), 47 deletions(-) diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index e89dd03e8..e4c5aabd4 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -99,13 +99,15 @@ private void OnTargetGameChanged(object? sender, SelectionChangedEventArgs e) // This executes if the utmost button is clicked. private void OnUtmostClicked(object? sender, RoutedEventArgs e) { - // Fetch the context and the mod list. var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext.InstalledMods; + var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; - // Make the move, select the mod. - _fetchModList.Move(ModList.MainList.SelectedIndex, 0); - _fetchContext.CurrentMod = _fetchModList[0]; + if (_fetchModList != null) + { + // Make the move, select the mod. + _fetchModList.Move(ModList.MainList.SelectedIndex, 0); + _fetchContext.CurrentMod = _fetchModList[0]; + } } // This executes if the move up button is clicked. @@ -113,11 +115,9 @@ private void OnUpPriorityClicked(object? sender, RoutedEventArgs e) { // Fetch the context, the mod list, and the current index. var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; - var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (_fetchConfig != null && _fetchModList != null) + if (_fetchModList != null) { var _fetchCurrentIndex = ModList.MainList.SelectedIndex; @@ -137,11 +137,9 @@ private void OnDownPriorityClicked(object? sender, RoutedEventArgs e) { // Same shit as OnUpPriorityClicked, but reversed. var _fetchContext = DataContext as MainViewModel; - var _fetchModList = _fetchContext != null ? _fetchContext.InstalledMods : null; - var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; - if (_fetchConfig != null && _fetchModList != null) + if (_fetchModList != null) { var _fetchCurrentIndex = ModList.MainList.SelectedIndex; diff --git a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs index 1f564813d..868a14511 100644 --- a/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/ModStatusView.axaml.cs @@ -14,39 +14,37 @@ public partial class ModStatusView : ContentPage #region Custom Events public static readonly RoutedEvent SetupRequestedEvent = RoutedEvent.Register(nameof(SetupRequested), RoutingStrategies.Direct); + public static readonly RoutedEvent RunRequestedEvent = RoutedEvent.Register(nameof(RunRequested), RoutingStrategies.Direct); + public static readonly RoutedEvent BuildRunRequestedEvent = RoutedEvent.Register(nameof(BuildRunRequested), RoutingStrategies.Direct); public event EventHandler SetupRequested { add => AddHandler(SetupRequestedEvent, value); remove => RemoveHandler(SetupRequestedEvent, value); } - - protected virtual void OnSetupRequested() - { - RoutedEventArgs args = new RoutedEventArgs(SetupRequestedEvent); - RaiseEvent(args); - } - - public static readonly RoutedEvent RunRequestedEvent = RoutedEvent.Register(nameof(RunRequested), RoutingStrategies.Direct); - + public event EventHandler RunRequested { add => AddHandler(RunRequestedEvent, value); remove => RemoveHandler(RunRequestedEvent, value); } - - protected virtual void OnRunRequested() + + public event EventHandler BuildRunRequested { - RoutedEventArgs args = new RoutedEventArgs(RunRequestedEvent); + add => AddHandler(BuildRunRequestedEvent, value); + remove => RemoveHandler(BuildRunRequestedEvent, value); + } + + protected virtual void OnSetupRequested() + { + RoutedEventArgs args = new RoutedEventArgs(SetupRequestedEvent); RaiseEvent(args); } - public static readonly RoutedEvent BuildRunRequestedEvent = RoutedEvent.Register(nameof(BuildRunRequested), RoutingStrategies.Direct); - - public event EventHandler BuildRunRequested + protected virtual void OnRunRequested() { - add => AddHandler(BuildRunRequestedEvent, value); - remove => RemoveHandler(BuildRunRequestedEvent, value); + RoutedEventArgs args = new RoutedEventArgs(RunRequestedEvent); + RaiseEvent(args); } protected virtual void OnBuildRunRequested() diff --git a/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs b/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs index dea235d56..fa31a62db 100644 --- a/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/SetupWizardView.axaml.cs @@ -182,6 +182,7 @@ private void OnBackPage(object? sender, Avalonia.Interactivity.RoutedEventArgs e NextButton.IsEnabled = true; } } + private void OnSubmitClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; @@ -189,7 +190,6 @@ private void OnSubmitClick(object? sender, Avalonia.Interactivity.RoutedEventArg if (_fetchConfig != null) { - _fetchContext.ConfigurationValid = _fetchConfig.IsValid(); _fetchConfig.Commit(); diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index de2cd3948..cbcc32df8 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -70,6 +70,24 @@ private async Task ExtractTask(List extractList, Config currentConfi return true; } + private async void OnExtractPathClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; + + if (_storageProvider != null) + { + var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for Game Data...", + AllowMultiple = false + }); + + if (_fetchFolder.Count >= 1) + PathExtractData.Text = _fetchFolder[0].Path.LocalPath; + } + } + private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; @@ -95,23 +113,5 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE } private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => ModService.CancelTokenSource.Cancel(); - - private async void OnExtractPathClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) - { - var _fetchTopLevel = TopLevel.GetTopLevel(this); - var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; - - if (_storageProvider != null) - { - var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions - { - Title = "Select a Folder for Game Data...", - AllowMultiple = false - }); - - if (_fetchFolder.Count >= 1) - PathExtractData.Text = _fetchFolder[0].Path.LocalPath; - } - } } } From a6b505721b8d232381df45da48bbeca523743bd0 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 6 Sep 2026 17:35:25 +0200 Subject: [PATCH 069/175] [Mod Manager] - Should be able to build now. [Removed old manager and WinShell] --- OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs | 1 - OpenKh.Tests.Commands/BdxMassiveTests.cs | 1 - OpenKh.Tests.Commands/BdxRegressionTests.cs | 4 +- .../OpenKh.Tests.Commands.csproj | 1 - OpenKh.Tests/Patcher/PatcherTests.cs | 4 +- .../Modules/AI/ModuleAI_VM.cs | 3 +- .../OpenKh.Tools.Kh2ObjectEditor.csproj | 1 - OpenKh.sln | 47 ------------------- 8 files changed, 6 insertions(+), 56 deletions(-) diff --git a/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs b/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs index 46d281c79..25e7a00ad 100644 --- a/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs +++ b/OpenKh.Tests.Commands/BdxAntlrGrammarTests.cs @@ -1,5 +1,4 @@ using Antlr4.Runtime; -using OpenKh.Command.Bdxio.Models; using OpenKh.Kh2.Bdx.Models; using System.Collections.Generic; using System.Linq; diff --git a/OpenKh.Tests.Commands/BdxMassiveTests.cs b/OpenKh.Tests.Commands/BdxMassiveTests.cs index b627c8577..3f4f65d3a 100644 --- a/OpenKh.Tests.Commands/BdxMassiveTests.cs +++ b/OpenKh.Tests.Commands/BdxMassiveTests.cs @@ -1,4 +1,3 @@ -using OpenKh.Command.Bdxio.Utils; using OpenKh.Common; using OpenKh.Kh2; using OpenKh.Kh2.Bdx.Utils; diff --git a/OpenKh.Tests.Commands/BdxRegressionTests.cs b/OpenKh.Tests.Commands/BdxRegressionTests.cs index 0dc49ee0c..e08340a81 100644 --- a/OpenKh.Tests.Commands/BdxRegressionTests.cs +++ b/OpenKh.Tests.Commands/BdxRegressionTests.cs @@ -1,6 +1,6 @@ -using OpenKh.Command.Bdxio.Models; -using OpenKh.Command.Bdxio.Utils; + using OpenKh.Kh2.Bdx.Utils; +using OpenKh.Kh2.Bdx.Models; using System; using System.Collections.Generic; using System.Globalization; diff --git a/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj b/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj index 437e940eb..8026fbec1 100644 --- a/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj +++ b/OpenKh.Tests.Commands/OpenKh.Tests.Commands.csproj @@ -20,7 +20,6 @@ - diff --git a/OpenKh.Tests/Patcher/PatcherTests.cs b/OpenKh.Tests/Patcher/PatcherTests.cs index 32c10ee64..4ab0250a7 100644 --- a/OpenKh.Tests/Patcher/PatcherTests.cs +++ b/OpenKh.Tests/Patcher/PatcherTests.cs @@ -4242,6 +4242,7 @@ public void Kh1ArdResourceReplaceTest() [Fact] public void Kh1ArdResourceReadsTheReplacementsFromItsSourceFileTest() { + /* TODO: Rewrite this. var yml = "title: source file test\n" + "assets:\n" + @@ -4250,7 +4251,7 @@ public void Kh1ArdResourceReadsTheReplacementsFromItsSourceFileTest() " source:\n" + " - name: files/tw01.yml\n"; - var patch = new MemoryStream(Encoding.UTF8.GetBytes(yml)).Using(Metadata.Read); + var patch = Metadata.Read(); CreateFile(ModInputDir, "files/tw01.yml") .Using(x => x.Write(Encoding.UTF8.GetBytes("1: xa_al_9999.mset\n"))); @@ -4261,6 +4262,7 @@ public void Kh1ArdResourceReadsTheReplacementsFromItsSourceFileTest() AssertArdResources(new[] { "a.mdls", "xa_al_9999.mset", "b.moa", "b.moa.mset" }, ModOutputDir, "tw01.ard"); + */ } [Fact] diff --git a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs index f67847941..1a5305550 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs +++ b/OpenKh.Tools.Kh2ObjectEditor/Modules/AI/ModuleAI_VM.cs @@ -1,6 +1,5 @@ -using OpenKh.Command.Bdxio.Models; -using OpenKh.Command.Bdxio.Utils; using OpenKh.Kh2; +using OpenKh.Kh2.Bdx.Models; using OpenKh.Kh2.Bdx.Utils; using OpenKh.Tools.Kh2ObjectEditor.Services; using OpenKh.Tools.Kh2ObjectEditor.Utils; diff --git a/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj b/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj index b38214c40..930f81bd6 100644 --- a/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj +++ b/OpenKh.Tools.Kh2ObjectEditor/OpenKh.Tools.Kh2ObjectEditor.csproj @@ -19,7 +19,6 @@ - diff --git a/OpenKh.sln b/OpenKh.sln index 950b0aa20..60a1e80ea 100644 --- a/OpenKh.sln +++ b/OpenKh.sln @@ -148,12 +148,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Research.Kh2Anim", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Command.TexFooter", "OpenKh.Command.TexFooter\OpenKh.Command.TexFooter.csproj", "{408B6E0A-20C1-4C55-A6BF-5342F2A16668}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WinShell", "WinShell", "{C9B53164-6989-479B-81BE-0D00822B8B46}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.WinShell.IMDUtilities", "OpenKh.WinShell.IMDUtilities\OpenKh.WinShell.IMDUtilities.csproj", "{82CA6AE4-DD88-4CD7-A471-CD838739B749}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.WinShell.IMZUtilities", "OpenKh.WinShell.IMZUtilities\OpenKh.WinShell.IMZUtilities.csproj", "{D0B22AFA-0E6F-46E4-9949-62DED6B65084}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Command.PmoConverter", "OpenKh.Command.PmoConverter\OpenKh.Command.PmoConverter.csproj", "{18FFB18D-B254-4F93-821F-2BBAE6389BB2}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Command.PmpConverter", "OpenKh.Command.PmpConverter\OpenKh.Command.PmpConverter.csproj", "{4D954FE4-0B5A-4699-A1A8-A2D02BE05B9C}" @@ -178,8 +172,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.OloEditor", "O EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.MissionEditor", "OpenKh.Tools.MissionEditor\OpenKh.Tools.MissionEditor.csproj", "{A88E268F-A8CC-48E4-8AE9-5CC7A774022D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.ModsManager", "OpenKh.Tools.ModsManager\OpenKh.Tools.ModsManager.csproj", "{A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Tools.Launcher", "OpenKh.Tools.Launcher\OpenKh.Tools.Launcher.csproj", "{B33409B4-C097-4CE2-98A7-36C4FF41BCFA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenKh.Kh1", "OpenKh.Kh1\OpenKh.Kh1.csproj", "{EB189B6E-AFA0-463D-8DE8-A51ED32969FD}" @@ -226,8 +218,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ModelingToolkit", "Modeling EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Graphical tool tests", "Graphical tool tests", "{EC12F6FC-5753-430C-AE13-09EFD219E432}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Tests.ModsManager", "OpenKh.Tests.ModsManager\OpenKh.Tests.ModsManager.csproj", "{788CDE3D-77D8-4910-8D1A-C829EFD591D2}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenKh.Command.KHAnimationConverter", "OpenKh.Command.KHAnimationConverter\OpenKh.Command.KHAnimationConverter.csproj", "{A8DAC079-E416-4574-9B54-19EED185D368}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Openkh.Tools.Kh2LocalSetEditor", "Openkh.Tools.Kh2LocalSetEditor\Openkh.Tools.Kh2LocalSetEditor.csproj", "{D58EEC5B-962C-4888-953B-4A443015E45D}" @@ -627,25 +617,8 @@ Global {408B6E0A-20C1-4C55-A6BF-5342F2A16668}..NET Release|Any CPU.ActiveCfg = Release|Any CPU {408B6E0A-20C1-4C55-A6BF-5342F2A16668}..NET Release|Any CPU.Build.0 = Release|Any CPU {408B6E0A-20C1-4C55-A6BF-5342F2A16668}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {408B6E0A-20C1-4C55-A6BF-5342F2A16668}.Release|Any CPU.Build.0 = Release|Any CPU {408B6E0A-20C1-4C55-A6BF-5342F2A16668}.Debug|Any CPU.Build.0 = Debug|Any CPU {408B6E0A-20C1-4C55-A6BF-5342F2A16668}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}..NET Debug|Any CPU.Build.0 = Debug|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}..NET Release|Any CPU.ActiveCfg = Release|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}..NET Release|Any CPU.Build.0 = Release|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82CA6AE4-DD88-4CD7-A471-CD838739B749}.Release|Any CPU.Build.0 = Release|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}..NET Debug|Any CPU.Build.0 = Debug|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}..NET Release|Any CPU.ActiveCfg = Release|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}..NET Release|Any CPU.Build.0 = Release|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D0B22AFA-0E6F-46E4-9949-62DED6B65084}.Release|Any CPU.Build.0 = Release|Any CPU {408B6E0A-20C1-4C55-A6BF-5342F2A16668}.Release|Any CPU.Build.0 = Release|Any CPU {18FFB18D-B254-4F93-821F-2BBAE6389BB2}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU {18FFB18D-B254-4F93-821F-2BBAE6389BB2}..NET Debug|Any CPU.Build.0 = Debug|Any CPU @@ -743,14 +716,6 @@ Global {A88E268F-A8CC-48E4-8AE9-5CC7A774022D}.Debug|Any CPU.Build.0 = Debug|Any CPU {A88E268F-A8CC-48E4-8AE9-5CC7A774022D}.Release|Any CPU.ActiveCfg = Release|Any CPU {A88E268F-A8CC-48E4-8AE9-5CC7A774022D}.Release|Any CPU.Build.0 = Release|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}..NET Debug|Any CPU.Build.0 = Debug|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}..NET Release|Any CPU.ActiveCfg = Release|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}..NET Release|Any CPU.Build.0 = Release|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2}.Release|Any CPU.Build.0 = Release|Any CPU {B33409B4-C097-4CE2-98A7-36C4FF41BCFA}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU {B33409B4-C097-4CE2-98A7-36C4FF41BCFA}..NET Debug|Any CPU.Build.0 = Debug|Any CPU {B33409B4-C097-4CE2-98A7-36C4FF41BCFA}..NET Release|Any CPU.ActiveCfg = Release|Any CPU @@ -925,14 +890,6 @@ Global {3603444C-3A1B-487D-AA6B-9AACB5530DD6}.Debug|Any CPU.Build.0 = Debug|Any CPU {3603444C-3A1B-487D-AA6B-9AACB5530DD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {3603444C-3A1B-487D-AA6B-9AACB5530DD6}.Release|Any CPU.Build.0 = Release|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}..NET Debug|Any CPU.Build.0 = Debug|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}..NET Release|Any CPU.ActiveCfg = Release|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}..NET Release|Any CPU.Build.0 = Release|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {788CDE3D-77D8-4910-8D1A-C829EFD591D2}.Release|Any CPU.Build.0 = Release|Any CPU {A8DAC079-E416-4574-9B54-19EED185D368}..NET Debug|Any CPU.ActiveCfg = Debug|Any CPU {A8DAC079-E416-4574-9B54-19EED185D368}..NET Debug|Any CPU.Build.0 = Debug|Any CPU {A8DAC079-E416-4574-9B54-19EED185D368}..NET Release|Any CPU.ActiveCfg = Release|Any CPU @@ -1020,8 +977,6 @@ Global {340458E3-FE2B-470E-9C4F-D3E58F218670} = {9C52D787-9819-4B89-989B-EEA6FEB20731} {1B89F510-3B9A-4A3B-A08F-4FFB5C5B817B} = {402B2669-D594-4DFA-965B-02A92626ADC6} {41706B1E-DFF9-472F-AD74-A936B10D80F6} = {9C52D787-9819-4B89-989B-EEA6FEB20731} - {82CA6AE4-DD88-4CD7-A471-CD838739B749} = {C9B53164-6989-479B-81BE-0D00822B8B46} - {D0B22AFA-0E6F-46E4-9949-62DED6B65084} = {C9B53164-6989-479B-81BE-0D00822B8B46} {198657C3-DC06-4AA6-A594-6D4D82218CA8} = {939E8104-3A37-41FC-A411-CAEE67A803B6} {8D0CBCCA-3F3E-4775-B001-2DDA4D962A8A} = {0FB7CD6A-EE31-467D-A590-267DCD028618} {77ADD488-0297-4EB7-9884-2068F84A9C67} = {402B2669-D594-4DFA-965B-02A92626ADC6} @@ -1045,7 +1000,6 @@ Global {5B6A7706-0028-4634-AE35-18E2BEAFD3D4} = {402B2669-D594-4DFA-965B-02A92626ADC6} {940E9927-13D3-4CC7-8034-37982E3885C2} = {402B2669-D594-4DFA-965B-02A92626ADC6} {A88E268F-A8CC-48E4-8AE9-5CC7A774022D} = {402B2669-D594-4DFA-965B-02A92626ADC6} - {A1BCC739-3BDC-41AF-BD0B-78C5E55F88A2} = {402B2669-D594-4DFA-965B-02A92626ADC6} {B33409B4-C097-4CE2-98A7-36C4FF41BCFA} = {402B2669-D594-4DFA-965B-02A92626ADC6} {EB189B6E-AFA0-463D-8DE8-A51ED32969FD} = {0FB7CD6A-EE31-467D-A590-267DCD028618} {3EF82533-6397-496F-A70F-429BB3611A17} = {9AEB4887-7C23-4992-A26D-A1E73DC4FE53} @@ -1067,7 +1021,6 @@ Global {A635E041-7F5D-4CD5-BB87-0F692EA1AD3E} = {4FBC958A-4685-4DF2-8D4F-98850BAE61B3} {E9AE2FAE-4335-4C75-9272-3B6C6D70072B} = {402B2669-D594-4DFA-965B-02A92626ADC6} {3603444C-3A1B-487D-AA6B-9AACB5530DD6} = {4FBC958A-4685-4DF2-8D4F-98850BAE61B3} - {788CDE3D-77D8-4910-8D1A-C829EFD591D2} = {EC12F6FC-5753-430C-AE13-09EFD219E432} {A8DAC079-E416-4574-9B54-19EED185D368} = {9C52D787-9819-4B89-989B-EEA6FEB20731} {D58EEC5B-962C-4888-953B-4A443015E45D} = {402B2669-D594-4DFA-965B-02A92626ADC6} {E9CAA582-1823-4B42-8218-F4B7B2C788D3} = {0FB7CD6A-EE31-467D-A590-267DCD028618} From ee3f9e8c97d568266a191648cfe5ce8a306f206f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 7 Sep 2026 03:44:01 +0200 Subject: [PATCH 070/175] [Mod Manager] - Fix problems SonarQube mentioned. --- OpenKh.Command.Launcher/Program.cs | 12 ++------ OpenKh.Patcher/Metadata.cs | 2 +- OpenKh.Tests/Patcher/PatcherTests.cs | 5 ++-- OpenKh.Tools.ModManager/Classes/Config.cs | 2 +- .../Services/ModService.cs | 29 ++++++++++--------- .../Wizard/WizardExtractSetup.axaml.cs | 2 +- .../Wizard/WizardGameSetup.axaml.cs | 2 +- 7 files changed, 26 insertions(+), 28 deletions(-) diff --git a/OpenKh.Command.Launcher/Program.cs b/OpenKh.Command.Launcher/Program.cs index cc7c69e8b..ccfc09e8d 100644 --- a/OpenKh.Command.Launcher/Program.cs +++ b/OpenKh.Command.Launcher/Program.cs @@ -16,16 +16,10 @@ public partial class Program public static void Main(string[] args) { - var _fetchArguments = String.Join(" ", args.Skip(1)); + var _fetchArguments = KEY_LAUNCH.FirstOrDefault(x => x.Key == args[0]).Key == "" ? String.Join(" ", args.Skip(1)) : String.Join(" ", args); + var _launchPath = args.Length == 0 || !KEY_LAUNCH.ContainsKey(args[0]) ? Path.Combine(AppContext.BaseDirectory, MAIN_LAUNCH) : Path.Combine(AppContext.BaseDirectory, KEY_LAUNCH[args[0]]); - if (args.Length == 0) - Process.Start(MAIN_LAUNCH, _fetchArguments); - - else if (!KEY_LAUNCH.ContainsKey(args[0])) - Process.Start(MAIN_LAUNCH, _fetchArguments); - - else - Process.Start(KEY_LAUNCH[args[0]], _fetchArguments); + Process.Start(_launchPath, _fetchArguments); } } } diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index 6953d4d80..3b3bb7923 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -59,7 +59,7 @@ public static Metadata Read(string fileName) Debug.WriteLine($"Error deserializing YAML: {ex.Message}"); var _fetchTitle = string.Empty; - var _fetchMatch = Regex.Match(_fetchYamlRaw, @"(?<=title:).*"); + var _fetchMatch = Regex.Match(_fetchYamlRaw, @"(?<=title:).*", RegexOptions.None, TimeSpan.FromMilliseconds(500)); if (_fetchMatch.Success) _fetchTitle = _fetchMatch.Value.Trim(); diff --git a/OpenKh.Tests/Patcher/PatcherTests.cs b/OpenKh.Tests/Patcher/PatcherTests.cs index 4ab0250a7..6339a9458 100644 --- a/OpenKh.Tests/Patcher/PatcherTests.cs +++ b/OpenKh.Tests/Patcher/PatcherTests.cs @@ -4239,10 +4239,10 @@ public void Kh1ArdResourceReplaceTest() }, ModOutputDir, "tw01.ard"); } + /* TODO: Rewrite this. [Fact] public void Kh1ArdResourceReadsTheReplacementsFromItsSourceFileTest() { - /* TODO: Rewrite this. var yml = "title: source file test\n" + "assets:\n" + @@ -4262,8 +4262,9 @@ public void Kh1ArdResourceReadsTheReplacementsFromItsSourceFileTest() AssertArdResources(new[] { "a.mdls", "xa_al_9999.mset", "b.moa", "b.moa.mset" }, ModOutputDir, "tw01.ard"); - */ + } + */ [Fact] public void Kh1ArdResourceReplaceOfReplacedFileTest() diff --git a/OpenKh.Tools.ModManager/Classes/Config.cs b/OpenKh.Tools.ModManager/Classes/Config.cs index 5fd3d2bef..835e6aff0 100644 --- a/OpenKh.Tools.ModManager/Classes/Config.cs +++ b/OpenKh.Tools.ModManager/Classes/Config.cs @@ -146,7 +146,7 @@ public bool IsValid() if (_isPanaceaConfigValid && Frontend.ModBuildType == BuildType.PANACEA) { - var _regexModPath = new Regex("mod_path=(.*)"); + var _regexModPath = new Regex("mod_path=(.*)", RegexOptions.None, TimeSpan.FromMilliseconds(500)); if (File.Exists(_fetchPanaceaPath) && File.Exists(_fetchSettingsPath)) { diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index f1840ce9f..0cfc7a78c 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -33,6 +33,8 @@ public static class ModService // We need a cancellation token to interrupt what we are doing should the user not want to do that anymore. public static CancellationTokenSource CancelTokenSource = new CancellationTokenSource(); public static CancellationToken CancelToken = CancelTokenSource.Token; + + #pragma warning disable S4790 public static string ResolveMD5(ModModel currentMod, Config currentConfig) { var _fetchRelativePath = Path.GetRelativePath(PathService.ResolveMod(currentConfig), currentMod.ModPath); @@ -41,6 +43,7 @@ public static string ResolveMD5(ModModel currentMod, Config currentConfig) return Convert.ToHexString(_fetchHash); } + #pragma warning restore S4790 /// /// Installs a mod from any Git repository. @@ -141,7 +144,7 @@ public static async Task InstallGit(string modPath, string repoName, Trans // Make an HTTP client and use GitHub's RAW API to see if the mod.yml exists. // This is the fastest way to handle this, otherwise I sadly have to fetch the mod FIRST and then check it. using var _makeClient = new HttpClient(); - using var _fetchResponse = await _makeClient.GetAsync($"https://raw.githubusercontent.com/{repoName}/{_branchString}/mod.yml", HttpCompletionOption.ResponseHeadersRead); + using var _fetchResponse = await _makeClient.GetAsync($"https://raw.githubusercontent.com/{repoName}/{_branchString}/mod.yml", HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); // If the file doesn't exist, abort. if (_fetchResponse.StatusCode != HttpStatusCode.OK) @@ -157,7 +160,7 @@ await Task.Run(() => { try { Repository.Clone(_fetchRelativeUri.ToString(), _fetchCurrentModDir, _cloneOptions); } catch (LibGit2SharpException) { } - }); + }, CancelToken); // After the task finishes/aborts, if we requested cancellation, abort. if (CancelToken.IsCancellationRequested) @@ -186,7 +189,7 @@ await Task.Run(() => try { Repository.Clone(_fetchRelativeUri.ToString(), _fetchCurrentModDir, _cloneOptions); } catch (LibGit2SharpException) { } - }); + }, CancelToken); // Fetch the Git directory and fix all permissions before moving on. // Again, may not be necessary here. Again, not risking it. @@ -313,7 +316,7 @@ await Task.Run(() => else if (CancelToken.IsCancellationRequested) break; } - }); + }, CancelToken); // If the task ended/aborted and cancellation was requested, abort. if (CancelToken.IsCancellationRequested) @@ -357,7 +360,7 @@ await Task.Run(() => { while (!_strReader.EndOfStream) { - string _fetchLine = _strReader.ReadLine(); + string _fetchLine = await _strReader.ReadLineAsync(); if (_fetchLine.Contains("LUAGUI")) { @@ -384,7 +387,7 @@ await Task.Run(() => } var _yamlPath = Path.Combine(_fetchCurrentModDir, "mod.yml"); - File.WriteAllText(_yamlPath, _createMetadata.ToString()); + await File.WriteAllTextAsync(_yamlPath, _createMetadata.ToString()); } // If the file is ANY PCPatch Package, handle it. @@ -426,7 +429,7 @@ await Task.Run(() => if (!_fetchProgress) break; } - }); + }, CancelToken); if (CancelToken.IsCancellationRequested) { @@ -465,7 +468,7 @@ await Task.Run(() => if (CancelToken.IsCancellationRequested) break; } - }); + }, CancelToken); if (CancelToken.IsCancellationRequested) { @@ -474,7 +477,7 @@ await Task.Run(() => } var _yamlPath = Path.Combine(_fetchCurrentModDir, "mod.yml"); - File.WriteAllText(_yamlPath, _fetchMetadata.ToString()); + await File.WriteAllTextAsync(_yamlPath, _fetchMetadata.ToString()); } } @@ -542,7 +545,7 @@ public static async Task Build(IEnumerable modsList, Config curr await Task.Delay(TimeSpan.FromMilliseconds(10), CancelToken); } - }); + }, CancelToken); } await Task.Run(async () => @@ -575,7 +578,7 @@ await Task.Run(() => _fetchPackageMap, reportProgress: reportAssetProgress ); - }); + }, CancelToken); if (CancelToken.IsCancellationRequested) break; @@ -592,7 +595,7 @@ await Task.Run(() => using (var _writePackageMap = new StreamWriter(_fetchPKGMapPath)) foreach (var _mapEntry in _fetchPackageMap) - _writePackageMap.WriteLine(_mapEntry.Key + " $$$$ " + _mapEntry.Value); + await _writePackageMap.WriteLineAsync(_mapEntry.Key + " $$$$ " + _mapEntry.Value); return 0x00; } @@ -811,7 +814,7 @@ await Task.Run(async () => if (CancelToken.IsCancellationRequested) _fetchStateGame.Stop(); }); - }); + }, CancelToken); if (CancelToken.IsCancellationRequested) return 0x03; diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index cbcc32df8..fb308a5fd 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -112,6 +112,6 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE } } - private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => ModService.CancelTokenSource.Cancel(); + private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => await ModService.CancelTokenSource.CancelAsync(); } } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index 111607f03..47904cc4d 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -113,7 +113,7 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv if (String.IsNullOrEmpty(_fetchConfigPath)) return; - var _fetchLibraryConfig = File.ReadAllLines(_fetchConfigPath); + var _fetchLibraryConfig = await File.ReadAllLinesAsync(_fetchConfigPath); var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); var _fetchPath1525 = ""; From a3cf58d596f99fd656b50839c0efa73f1bac3b8e Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 7 Sep 2026 03:49:00 +0200 Subject: [PATCH 071/175] [Mod Manager] - Fix more SonarQube complaints. --- OpenKh.Tools.ModManager/Services/ModService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 0cfc7a78c..9661a8809 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -387,7 +387,7 @@ await Task.Run(() => } var _yamlPath = Path.Combine(_fetchCurrentModDir, "mod.yml"); - await File.WriteAllTextAsync(_yamlPath, _createMetadata.ToString()); + await File.WriteAllTextAsync(_yamlPath, _createMetadata.ToString(), CancellationToken.None); } // If the file is ANY PCPatch Package, handle it. @@ -477,7 +477,7 @@ await Task.Run(() => } var _yamlPath = Path.Combine(_fetchCurrentModDir, "mod.yml"); - await File.WriteAllTextAsync(_yamlPath, _fetchMetadata.ToString()); + await File.WriteAllTextAsync(_yamlPath, _fetchMetadata.ToString(), CancellationToken.None); } } @@ -583,7 +583,7 @@ await Task.Run(() => if (CancelToken.IsCancellationRequested) break; } - }); + }, CancelToken); if (CancelToken.IsCancellationRequested) { From 9dc481e44c0cb2424d11f606ff9cc17c675551db Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 7 Sep 2026 03:50:26 +0200 Subject: [PATCH 072/175] [Mod Manager] - Forgot to save this one. --- OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index fb308a5fd..aac565d0c 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -112,6 +112,6 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE } } - private async void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => await ModService.CancelTokenSource.CancelAsync(); + private void OnExtractStop(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => ModService.CancelTokenSource.Cancel(); } } From f7835ac9fb0001be8d35dcd0a09799e5fc119f49 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 11 Sep 2026 15:15:46 +0200 Subject: [PATCH 073/175] [BdxIo] - Make the project use OpenKh.Kh2 instead. --- OpenKh.Command.Bdxio/Antlr4/.gitignore | 7 - OpenKh.Command.Bdxio/Antlr4/BdxScript.g4 | 49 - OpenKh.Command.Bdxio/Antlr4/BdxScriptLexer.cs | 177 --- .../Antlr4/BdxScriptParser.cs | 1240 ----------------- OpenKh.Command.Bdxio/Antlr4/README.md | 40 - .../Commands/DecodeCommand.cs | 2 +- .../Commands/EncodeCommand.cs | 4 +- OpenKh.Command.Bdxio/Models/BdxHeader.cs | 35 - OpenKh.Command.Bdxio/Models/BdxInstruction.cs | 32 - .../Models/BdxInstructionDesc.cs | 43 - .../Models/BdxInstructionDescs.cs | 95 -- OpenKh.Command.Bdxio/Models/BdxTrap.cs | 10 - OpenKh.Command.Bdxio/Models/BdxTraps.cs | 843 ----------- .../OpenKh.Command.Bdxio.csproj | 3 +- OpenKh.Command.Bdxio/Utils/BdxAsciiModel.cs | 42 - OpenKh.Command.Bdxio/Utils/BdxDecoder.cs | 804 ----------- OpenKh.Command.Bdxio/Utils/BdxEncoder.cs | 866 ------------ OpenKh.Command.Bdxio/Utils/BdxTrigger.cs | 37 - 18 files changed, 5 insertions(+), 4324 deletions(-) delete mode 100644 OpenKh.Command.Bdxio/Antlr4/.gitignore delete mode 100644 OpenKh.Command.Bdxio/Antlr4/BdxScript.g4 delete mode 100644 OpenKh.Command.Bdxio/Antlr4/BdxScriptLexer.cs delete mode 100644 OpenKh.Command.Bdxio/Antlr4/BdxScriptParser.cs delete mode 100644 OpenKh.Command.Bdxio/Antlr4/README.md delete mode 100644 OpenKh.Command.Bdxio/Models/BdxHeader.cs delete mode 100644 OpenKh.Command.Bdxio/Models/BdxInstruction.cs delete mode 100644 OpenKh.Command.Bdxio/Models/BdxInstructionDesc.cs delete mode 100644 OpenKh.Command.Bdxio/Models/BdxInstructionDescs.cs delete mode 100644 OpenKh.Command.Bdxio/Models/BdxTrap.cs delete mode 100644 OpenKh.Command.Bdxio/Models/BdxTraps.cs delete mode 100644 OpenKh.Command.Bdxio/Utils/BdxAsciiModel.cs delete mode 100644 OpenKh.Command.Bdxio/Utils/BdxDecoder.cs delete mode 100644 OpenKh.Command.Bdxio/Utils/BdxEncoder.cs delete mode 100644 OpenKh.Command.Bdxio/Utils/BdxTrigger.cs diff --git a/OpenKh.Command.Bdxio/Antlr4/.gitignore b/OpenKh.Command.Bdxio/Antlr4/.gitignore deleted file mode 100644 index 6161d9e8a..000000000 --- a/OpenKh.Command.Bdxio/Antlr4/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/.antlr -/.vscode -/BdxScript.interp -/BdxScript.tokens -/BdxScriptLexer.interp -/BdxScriptLexer.tokens -/test.bdxa diff --git a/OpenKh.Command.Bdxio/Antlr4/BdxScript.g4 b/OpenKh.Command.Bdxio/Antlr4/BdxScript.g4 deleted file mode 100644 index de02fc9d4..000000000 --- a/OpenKh.Command.Bdxio/Antlr4/BdxScript.g4 +++ /dev/null @@ -1,49 +0,0 @@ -grammar BdxScript; - -prog: NL* (statement NL*)* EOF; -statement: - section - | equ - | label ':' NL - | label ':' order - | order; - -label: name = id; - -equ: name = id 'equ' numberdata; - -order: db | resb | dw | resw | instruction | include; - -dw: 'dw' worddata (',' worddata)* NL; -db: 'db' bytedata (',' bytedata)* NL; -resb: 'resb' (numberdata | id) NL; -resw: 'resw' (numberdata | id) NL; - -instruction: id (arg (',' arg)*)? NL; - -arg: (numberdata | id); -offset: 'offset' id; -bytedata: numberdata | id | StringData; -worddata: numberdata | id; -numberdata: - decimalnumber = DEC - | '0x' hexnumber = (DEC | HEX) - | floatnumber = FLOATNUM; -hexbody: DEC | HEX; -id: DEC | HEX | ID; -include: '%include' FileName NL; -section: 'section' section_id NL; -section_id: '.text' | '.bss'; - -StringData: '\'' ~[']* '\''; -DEC: [-+]? [0-9]+; -HEX: [0-9a-fA-F]+; -FLOATNUM: - [-+]? ([0-9]* '.' [0-9]+) - | ([0-9]+ [eE] [-+]? [0-9]+); -ID: [a-zA-Z_][a-zA-Z0-9_]* ('.' [a-zA-Z0-9_]+)*; -WS: [ \t] -> skip; -NL: '\r'? '\n' | '\r'; -FileName: '"' ~["]* '"'; - -LINE_COMMENT: ';' ~[\r\n]* -> skip; diff --git a/OpenKh.Command.Bdxio/Antlr4/BdxScriptLexer.cs b/OpenKh.Command.Bdxio/Antlr4/BdxScriptLexer.cs deleted file mode 100644 index aebe4593b..000000000 --- a/OpenKh.Command.Bdxio/Antlr4/BdxScriptLexer.cs +++ /dev/null @@ -1,177 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// ANTLR Version: 4.11.1 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// Generated from BdxScript.g4 by ANTLR 4.11.1 - -// Unreachable code detected -#pragma warning disable 0162 -// The variable '...' is assigned but its value is never used -#pragma warning disable 0219 -// Missing XML comment for publicly visible type or member '...' -#pragma warning disable 1591 -// Ambiguous reference in cref attribute -#pragma warning disable 419 - -using System; -using System.IO; -using System.Text; -using Antlr4.Runtime; -using Antlr4.Runtime.Atn; -using Antlr4.Runtime.Misc; -using DFA = Antlr4.Runtime.Dfa.DFA; - -[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.11.1")] -[System.CLSCompliant(false)] -public partial class BdxScriptLexer : Lexer { - protected static DFA[] decisionToDFA; - protected static PredictionContextCache sharedContextCache = new PredictionContextCache(); - public const int - T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, - T__9=10, T__10=11, T__11=12, T__12=13, StringData=14, DEC=15, HEX=16, - FLOATNUM=17, ID=18, WS=19, NL=20, FileName=21, LINE_COMMENT=22; - public static string[] channelNames = { - "DEFAULT_TOKEN_CHANNEL", "HIDDEN" - }; - - public static string[] modeNames = { - "DEFAULT_MODE" - }; - - public static readonly string[] ruleNames = { - "T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8", - "T__9", "T__10", "T__11", "T__12", "StringData", "DEC", "HEX", "FLOATNUM", - "ID", "WS", "NL", "FileName", "LINE_COMMENT" - }; - - - public BdxScriptLexer(ICharStream input) - : this(input, Console.Out, Console.Error) { } - - public BdxScriptLexer(ICharStream input, TextWriter output, TextWriter errorOutput) - : base(input, output, errorOutput) - { - Interpreter = new LexerATNSimulator(this, _ATN, decisionToDFA, sharedContextCache); - } - - private static readonly string[] _LiteralNames = { - null, "':'", "'equ'", "'dw'", "','", "'db'", "'resb'", "'resw'", "'offset'", - "'0x'", "'%include'", "'section'", "'.text'", "'.bss'" - }; - private static readonly string[] _SymbolicNames = { - null, null, null, null, null, null, null, null, null, null, null, null, - null, null, "StringData", "DEC", "HEX", "FLOATNUM", "ID", "WS", "NL", - "FileName", "LINE_COMMENT" - }; - public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames); - - [NotNull] - public override IVocabulary Vocabulary - { - get - { - return DefaultVocabulary; - } - } - - public override string GrammarFileName { get { return "BdxScript.g4"; } } - - public override string[] RuleNames { get { return ruleNames; } } - - public override string[] ChannelNames { get { return channelNames; } } - - public override string[] ModeNames { get { return modeNames; } } - - public override int[] SerializedAtn { get { return _serializedATN; } } - - static BdxScriptLexer() { - decisionToDFA = new DFA[_ATN.NumberOfDecisions]; - for (int i = 0; i < _ATN.NumberOfDecisions; i++) { - decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i); - } - } - private static int[] _serializedATN = { - 4,0,22,207,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7, - 6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14, - 7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,2,20,7,20,2,21, - 7,21,1,0,1,0,1,1,1,1,1,1,1,1,1,2,1,2,1,2,1,3,1,3,1,4,1,4,1,4,1,5,1,5,1, - 5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,7,1,7,1,8,1,8,1,8, - 1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,10,1,10, - 1,10,1,11,1,11,1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,13,1,13, - 5,13,110,8,13,10,13,12,13,113,9,13,1,13,1,13,1,14,3,14,118,8,14,1,14,4, - 14,121,8,14,11,14,12,14,122,1,15,4,15,126,8,15,11,15,12,15,127,1,16,3, - 16,131,8,16,1,16,5,16,134,8,16,10,16,12,16,137,9,16,1,16,1,16,4,16,141, - 8,16,11,16,12,16,142,1,16,4,16,146,8,16,11,16,12,16,147,1,16,1,16,3,16, - 152,8,16,1,16,4,16,155,8,16,11,16,12,16,156,3,16,159,8,16,1,17,1,17,5, - 17,163,8,17,10,17,12,17,166,9,17,1,17,1,17,4,17,170,8,17,11,17,12,17,171, - 5,17,174,8,17,10,17,12,17,177,9,17,1,18,1,18,1,18,1,18,1,19,3,19,184,8, - 19,1,19,1,19,3,19,188,8,19,1,20,1,20,5,20,192,8,20,10,20,12,20,195,9,20, - 1,20,1,20,1,21,1,21,5,21,201,8,21,10,21,12,21,204,9,21,1,21,1,21,0,0,22, - 1,1,3,2,5,3,7,4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14, - 29,15,31,16,33,17,35,18,37,19,39,20,41,21,43,22,1,0,10,1,0,39,39,2,0,43, - 43,45,45,1,0,48,57,3,0,48,57,65,70,97,102,2,0,69,69,101,101,3,0,65,90, - 95,95,97,122,4,0,48,57,65,90,95,95,97,122,2,0,9,9,32,32,1,0,34,34,2,0, - 10,10,13,13,224,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,1, - 0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,1,0,0,0, - 0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,1,0,0,0,0,31, - 1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,1,0,0,0,0,41,1,0,0, - 0,0,43,1,0,0,0,1,45,1,0,0,0,3,47,1,0,0,0,5,51,1,0,0,0,7,54,1,0,0,0,9,56, - 1,0,0,0,11,59,1,0,0,0,13,64,1,0,0,0,15,69,1,0,0,0,17,76,1,0,0,0,19,79, - 1,0,0,0,21,88,1,0,0,0,23,96,1,0,0,0,25,102,1,0,0,0,27,107,1,0,0,0,29,117, - 1,0,0,0,31,125,1,0,0,0,33,158,1,0,0,0,35,160,1,0,0,0,37,178,1,0,0,0,39, - 187,1,0,0,0,41,189,1,0,0,0,43,198,1,0,0,0,45,46,5,58,0,0,46,2,1,0,0,0, - 47,48,5,101,0,0,48,49,5,113,0,0,49,50,5,117,0,0,50,4,1,0,0,0,51,52,5,100, - 0,0,52,53,5,119,0,0,53,6,1,0,0,0,54,55,5,44,0,0,55,8,1,0,0,0,56,57,5,100, - 0,0,57,58,5,98,0,0,58,10,1,0,0,0,59,60,5,114,0,0,60,61,5,101,0,0,61,62, - 5,115,0,0,62,63,5,98,0,0,63,12,1,0,0,0,64,65,5,114,0,0,65,66,5,101,0,0, - 66,67,5,115,0,0,67,68,5,119,0,0,68,14,1,0,0,0,69,70,5,111,0,0,70,71,5, - 102,0,0,71,72,5,102,0,0,72,73,5,115,0,0,73,74,5,101,0,0,74,75,5,116,0, - 0,75,16,1,0,0,0,76,77,5,48,0,0,77,78,5,120,0,0,78,18,1,0,0,0,79,80,5,37, - 0,0,80,81,5,105,0,0,81,82,5,110,0,0,82,83,5,99,0,0,83,84,5,108,0,0,84, - 85,5,117,0,0,85,86,5,100,0,0,86,87,5,101,0,0,87,20,1,0,0,0,88,89,5,115, - 0,0,89,90,5,101,0,0,90,91,5,99,0,0,91,92,5,116,0,0,92,93,5,105,0,0,93, - 94,5,111,0,0,94,95,5,110,0,0,95,22,1,0,0,0,96,97,5,46,0,0,97,98,5,116, - 0,0,98,99,5,101,0,0,99,100,5,120,0,0,100,101,5,116,0,0,101,24,1,0,0,0, - 102,103,5,46,0,0,103,104,5,98,0,0,104,105,5,115,0,0,105,106,5,115,0,0, - 106,26,1,0,0,0,107,111,5,39,0,0,108,110,8,0,0,0,109,108,1,0,0,0,110,113, - 1,0,0,0,111,109,1,0,0,0,111,112,1,0,0,0,112,114,1,0,0,0,113,111,1,0,0, - 0,114,115,5,39,0,0,115,28,1,0,0,0,116,118,7,1,0,0,117,116,1,0,0,0,117, - 118,1,0,0,0,118,120,1,0,0,0,119,121,7,2,0,0,120,119,1,0,0,0,121,122,1, - 0,0,0,122,120,1,0,0,0,122,123,1,0,0,0,123,30,1,0,0,0,124,126,7,3,0,0,125, - 124,1,0,0,0,126,127,1,0,0,0,127,125,1,0,0,0,127,128,1,0,0,0,128,32,1,0, - 0,0,129,131,7,1,0,0,130,129,1,0,0,0,130,131,1,0,0,0,131,135,1,0,0,0,132, - 134,7,2,0,0,133,132,1,0,0,0,134,137,1,0,0,0,135,133,1,0,0,0,135,136,1, - 0,0,0,136,138,1,0,0,0,137,135,1,0,0,0,138,140,5,46,0,0,139,141,7,2,0,0, - 140,139,1,0,0,0,141,142,1,0,0,0,142,140,1,0,0,0,142,143,1,0,0,0,143,159, - 1,0,0,0,144,146,7,2,0,0,145,144,1,0,0,0,146,147,1,0,0,0,147,145,1,0,0, - 0,147,148,1,0,0,0,148,149,1,0,0,0,149,151,7,4,0,0,150,152,7,1,0,0,151, - 150,1,0,0,0,151,152,1,0,0,0,152,154,1,0,0,0,153,155,7,2,0,0,154,153,1, - 0,0,0,155,156,1,0,0,0,156,154,1,0,0,0,156,157,1,0,0,0,157,159,1,0,0,0, - 158,130,1,0,0,0,158,145,1,0,0,0,159,34,1,0,0,0,160,164,7,5,0,0,161,163, - 7,6,0,0,162,161,1,0,0,0,163,166,1,0,0,0,164,162,1,0,0,0,164,165,1,0,0, - 0,165,175,1,0,0,0,166,164,1,0,0,0,167,169,5,46,0,0,168,170,7,6,0,0,169, - 168,1,0,0,0,170,171,1,0,0,0,171,169,1,0,0,0,171,172,1,0,0,0,172,174,1, - 0,0,0,173,167,1,0,0,0,174,177,1,0,0,0,175,173,1,0,0,0,175,176,1,0,0,0, - 176,36,1,0,0,0,177,175,1,0,0,0,178,179,7,7,0,0,179,180,1,0,0,0,180,181, - 6,18,0,0,181,38,1,0,0,0,182,184,5,13,0,0,183,182,1,0,0,0,183,184,1,0,0, - 0,184,185,1,0,0,0,185,188,5,10,0,0,186,188,5,13,0,0,187,183,1,0,0,0,187, - 186,1,0,0,0,188,40,1,0,0,0,189,193,5,34,0,0,190,192,8,8,0,0,191,190,1, - 0,0,0,192,195,1,0,0,0,193,191,1,0,0,0,193,194,1,0,0,0,194,196,1,0,0,0, - 195,193,1,0,0,0,196,197,5,34,0,0,197,42,1,0,0,0,198,202,5,59,0,0,199,201, - 8,9,0,0,200,199,1,0,0,0,201,204,1,0,0,0,202,200,1,0,0,0,202,203,1,0,0, - 0,203,205,1,0,0,0,204,202,1,0,0,0,205,206,6,21,0,0,206,44,1,0,0,0,19,0, - 111,117,122,127,130,135,142,147,151,156,158,164,171,175,183,187,193,202, - 1,6,0,0 - }; - - public static readonly ATN _ATN = - new ATNDeserializer().Deserialize(_serializedATN); - - -} diff --git a/OpenKh.Command.Bdxio/Antlr4/BdxScriptParser.cs b/OpenKh.Command.Bdxio/Antlr4/BdxScriptParser.cs deleted file mode 100644 index 5ec6fad21..000000000 --- a/OpenKh.Command.Bdxio/Antlr4/BdxScriptParser.cs +++ /dev/null @@ -1,1240 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// ANTLR Version: 4.11.1 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -// Generated from BdxScript.g4 by ANTLR 4.11.1 - -// Unreachable code detected -#pragma warning disable 0162 -// The variable '...' is assigned but its value is never used -#pragma warning disable 0219 -// Missing XML comment for publicly visible type or member '...' -#pragma warning disable 1591 -// Ambiguous reference in cref attribute -#pragma warning disable 419 - -using System; -using System.IO; -using System.Text; -using System.Diagnostics; -using System.Collections.Generic; -using Antlr4.Runtime; -using Antlr4.Runtime.Atn; -using Antlr4.Runtime.Misc; -using Antlr4.Runtime.Tree; -using DFA = Antlr4.Runtime.Dfa.DFA; - -[System.CodeDom.Compiler.GeneratedCode("ANTLR", "4.11.1")] -[System.CLSCompliant(false)] -public partial class BdxScriptParser : Parser { - protected static DFA[] decisionToDFA; - protected static PredictionContextCache sharedContextCache = new PredictionContextCache(); - public const int - T__0=1, T__1=2, T__2=3, T__3=4, T__4=5, T__5=6, T__6=7, T__7=8, T__8=9, - T__9=10, T__10=11, T__11=12, T__12=13, StringData=14, DEC=15, HEX=16, - FLOATNUM=17, ID=18, WS=19, NL=20, FileName=21, LINE_COMMENT=22; - public const int - RULE_prog = 0, RULE_statement = 1, RULE_label = 2, RULE_equ = 3, RULE_order = 4, - RULE_dw = 5, RULE_db = 6, RULE_resb = 7, RULE_resw = 8, RULE_instruction = 9, - RULE_arg = 10, RULE_offset = 11, RULE_bytedata = 12, RULE_worddata = 13, - RULE_numberdata = 14, RULE_hexbody = 15, RULE_id = 16, RULE_include = 17, - RULE_section = 18, RULE_section_id = 19; - public static readonly string[] ruleNames = { - "prog", "statement", "label", "equ", "order", "dw", "db", "resb", "resw", - "instruction", "arg", "offset", "bytedata", "worddata", "numberdata", - "hexbody", "id", "include", "section", "section_id" - }; - - private static readonly string[] _LiteralNames = { - null, "':'", "'equ'", "'dw'", "','", "'db'", "'resb'", "'resw'", "'offset'", - "'0x'", "'%include'", "'section'", "'.text'", "'.bss'" - }; - private static readonly string[] _SymbolicNames = { - null, null, null, null, null, null, null, null, null, null, null, null, - null, null, "StringData", "DEC", "HEX", "FLOATNUM", "ID", "WS", "NL", - "FileName", "LINE_COMMENT" - }; - public static readonly IVocabulary DefaultVocabulary = new Vocabulary(_LiteralNames, _SymbolicNames); - - [NotNull] - public override IVocabulary Vocabulary - { - get - { - return DefaultVocabulary; - } - } - - public override string GrammarFileName { get { return "BdxScript.g4"; } } - - public override string[] RuleNames { get { return ruleNames; } } - - public override int[] SerializedAtn { get { return _serializedATN; } } - - static BdxScriptParser() { - decisionToDFA = new DFA[_ATN.NumberOfDecisions]; - for (int i = 0; i < _ATN.NumberOfDecisions; i++) { - decisionToDFA[i] = new DFA(_ATN.GetDecisionState(i), i); - } - } - - public BdxScriptParser(ITokenStream input) : this(input, Console.Out, Console.Error) { } - - public BdxScriptParser(ITokenStream input, TextWriter output, TextWriter errorOutput) - : base(input, output, errorOutput) - { - Interpreter = new ParserATNSimulator(this, _ATN, decisionToDFA, sharedContextCache); - } - - public partial class ProgContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode Eof() { return GetToken(BdxScriptParser.Eof, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode[] NL() { return GetTokens(BdxScriptParser.NL); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL(int i) { - return GetToken(BdxScriptParser.NL, i); - } - [System.Diagnostics.DebuggerNonUserCode] public StatementContext[] statement() { - return GetRuleContexts(); - } - [System.Diagnostics.DebuggerNonUserCode] public StatementContext statement(int i) { - return GetRuleContext(i); - } - public ProgContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_prog; } } - } - - [RuleVersion(0)] - public ProgContext prog() { - ProgContext _localctx = new ProgContext(Context, State); - EnterRule(_localctx, 0, RULE_prog); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 43; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (_la==NL) { - { - { - State = 40; - Match(NL); - } - } - State = 45; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - State = 55; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (((_la) & ~0x3f) == 0 && ((1L << _la) & 363752L) != 0) { - { - { - State = 46; - statement(); - State = 50; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (_la==NL) { - { - { - State = 47; - Match(NL); - } - } - State = 52; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - } - } - State = 57; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - State = 58; - Match(Eof); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class StatementContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public SectionContext section() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public EquContext equ() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public LabelContext label() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - [System.Diagnostics.DebuggerNonUserCode] public OrderContext order() { - return GetRuleContext(0); - } - public StatementContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_statement; } } - } - - [RuleVersion(0)] - public StatementContext statement() { - StatementContext _localctx = new StatementContext(Context, State); - EnterRule(_localctx, 2, RULE_statement); - try { - State = 71; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,3,Context) ) { - case 1: - EnterOuterAlt(_localctx, 1); - { - State = 60; - section(); - } - break; - case 2: - EnterOuterAlt(_localctx, 2); - { - State = 61; - equ(); - } - break; - case 3: - EnterOuterAlt(_localctx, 3); - { - State = 62; - label(); - State = 63; - Match(T__0); - State = 64; - Match(NL); - } - break; - case 4: - EnterOuterAlt(_localctx, 4); - { - State = 66; - label(); - State = 67; - Match(T__0); - State = 68; - order(); - } - break; - case 5: - EnterOuterAlt(_localctx, 5); - { - State = 70; - order(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class LabelContext : ParserRuleContext { - public IdContext name; - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public LabelContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_label; } } - } - - [RuleVersion(0)] - public LabelContext label() { - LabelContext _localctx = new LabelContext(Context, State); - EnterRule(_localctx, 4, RULE_label); - try { - EnterOuterAlt(_localctx, 1); - { - State = 73; - _localctx.name = id(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class EquContext : ParserRuleContext { - public IdContext name; - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public EquContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_equ; } } - } - - [RuleVersion(0)] - public EquContext equ() { - EquContext _localctx = new EquContext(Context, State); - EnterRule(_localctx, 6, RULE_equ); - try { - EnterOuterAlt(_localctx, 1); - { - State = 75; - _localctx.name = id(); - State = 76; - Match(T__1); - State = 77; - numberdata(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class OrderContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public DbContext db() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ResbContext resb() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public DwContext dw() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ReswContext resw() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public InstructionContext instruction() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IncludeContext include() { - return GetRuleContext(0); - } - public OrderContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_order; } } - } - - [RuleVersion(0)] - public OrderContext order() { - OrderContext _localctx = new OrderContext(Context, State); - EnterRule(_localctx, 8, RULE_order); - try { - State = 85; - ErrorHandler.Sync(this); - switch (TokenStream.LA(1)) { - case T__4: - EnterOuterAlt(_localctx, 1); - { - State = 79; - db(); - } - break; - case T__5: - EnterOuterAlt(_localctx, 2); - { - State = 80; - resb(); - } - break; - case T__2: - EnterOuterAlt(_localctx, 3); - { - State = 81; - dw(); - } - break; - case T__6: - EnterOuterAlt(_localctx, 4); - { - State = 82; - resw(); - } - break; - case DEC: - case HEX: - case ID: - EnterOuterAlt(_localctx, 5); - { - State = 83; - instruction(); - } - break; - case T__9: - EnterOuterAlt(_localctx, 6); - { - State = 84; - include(); - } - break; - default: - throw new NoViableAltException(this); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class DwContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public WorddataContext[] worddata() { - return GetRuleContexts(); - } - [System.Diagnostics.DebuggerNonUserCode] public WorddataContext worddata(int i) { - return GetRuleContext(i); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - public DwContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_dw; } } - } - - [RuleVersion(0)] - public DwContext dw() { - DwContext _localctx = new DwContext(Context, State); - EnterRule(_localctx, 10, RULE_dw); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 87; - Match(T__2); - State = 88; - worddata(); - State = 93; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (_la==T__3) { - { - { - State = 89; - Match(T__3); - State = 90; - worddata(); - } - } - State = 95; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - State = 96; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class DbContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public BytedataContext[] bytedata() { - return GetRuleContexts(); - } - [System.Diagnostics.DebuggerNonUserCode] public BytedataContext bytedata(int i) { - return GetRuleContext(i); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - public DbContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_db; } } - } - - [RuleVersion(0)] - public DbContext db() { - DbContext _localctx = new DbContext(Context, State); - EnterRule(_localctx, 12, RULE_db); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 98; - Match(T__4); - State = 99; - bytedata(); - State = 104; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (_la==T__3) { - { - { - State = 100; - Match(T__3); - State = 101; - bytedata(); - } - } - State = 106; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - State = 107; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class ResbContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public ResbContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_resb; } } - } - - [RuleVersion(0)] - public ResbContext resb() { - ResbContext _localctx = new ResbContext(Context, State); - EnterRule(_localctx, 14, RULE_resb); - try { - EnterOuterAlt(_localctx, 1); - { - State = 109; - Match(T__5); - State = 112; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,7,Context) ) { - case 1: - { - State = 110; - numberdata(); - } - break; - case 2: - { - State = 111; - id(); - } - break; - } - State = 114; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class ReswContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public ReswContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_resw; } } - } - - [RuleVersion(0)] - public ReswContext resw() { - ReswContext _localctx = new ReswContext(Context, State); - EnterRule(_localctx, 16, RULE_resw); - try { - EnterOuterAlt(_localctx, 1); - { - State = 116; - Match(T__6); - State = 119; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,8,Context) ) { - case 1: - { - State = 117; - numberdata(); - } - break; - case 2: - { - State = 118; - id(); - } - break; - } - State = 121; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class InstructionContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ArgContext[] arg() { - return GetRuleContexts(); - } - [System.Diagnostics.DebuggerNonUserCode] public ArgContext arg(int i) { - return GetRuleContext(i); - } - public InstructionContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_instruction; } } - } - - [RuleVersion(0)] - public InstructionContext instruction() { - InstructionContext _localctx = new InstructionContext(Context, State); - EnterRule(_localctx, 18, RULE_instruction); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 123; - id(); - State = 132; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - if (((_la) & ~0x3f) == 0 && ((1L << _la) & 492032L) != 0) { - { - State = 124; - arg(); - State = 129; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - while (_la==T__3) { - { - { - State = 125; - Match(T__3); - State = 126; - arg(); - } - } - State = 131; - ErrorHandler.Sync(this); - _la = TokenStream.LA(1); - } - } - } - - State = 134; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class ArgContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public ArgContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_arg; } } - } - - [RuleVersion(0)] - public ArgContext arg() { - ArgContext _localctx = new ArgContext(Context, State); - EnterRule(_localctx, 20, RULE_arg); - try { - EnterOuterAlt(_localctx, 1); - { - State = 138; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,11,Context) ) { - case 1: - { - State = 136; - numberdata(); - } - break; - case 2: - { - State = 137; - id(); - } - break; - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class OffsetContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public OffsetContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_offset; } } - } - - [RuleVersion(0)] - public OffsetContext offset() { - OffsetContext _localctx = new OffsetContext(Context, State); - EnterRule(_localctx, 22, RULE_offset); - try { - EnterOuterAlt(_localctx, 1); - { - State = 140; - Match(T__7); - State = 141; - id(); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class BytedataContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode StringData() { return GetToken(BdxScriptParser.StringData, 0); } - public BytedataContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_bytedata; } } - } - - [RuleVersion(0)] - public BytedataContext bytedata() { - BytedataContext _localctx = new BytedataContext(Context, State); - EnterRule(_localctx, 24, RULE_bytedata); - try { - State = 146; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,12,Context) ) { - case 1: - EnterOuterAlt(_localctx, 1); - { - State = 143; - numberdata(); - } - break; - case 2: - EnterOuterAlt(_localctx, 2); - { - State = 144; - id(); - } - break; - case 3: - EnterOuterAlt(_localctx, 3); - { - State = 145; - Match(StringData); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class WorddataContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public NumberdataContext numberdata() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public IdContext id() { - return GetRuleContext(0); - } - public WorddataContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_worddata; } } - } - - [RuleVersion(0)] - public WorddataContext worddata() { - WorddataContext _localctx = new WorddataContext(Context, State); - EnterRule(_localctx, 26, RULE_worddata); - try { - State = 150; - ErrorHandler.Sync(this); - switch ( Interpreter.AdaptivePredict(TokenStream,13,Context) ) { - case 1: - EnterOuterAlt(_localctx, 1); - { - State = 148; - numberdata(); - } - break; - case 2: - EnterOuterAlt(_localctx, 2); - { - State = 149; - id(); - } - break; - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class NumberdataContext : ParserRuleContext { - public IToken decimalnumber; - public IToken hexnumber; - public IToken floatnumber; - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode FLOATNUM() { return GetToken(BdxScriptParser.FLOATNUM, 0); } - public NumberdataContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_numberdata; } } - } - - [RuleVersion(0)] - public NumberdataContext numberdata() { - NumberdataContext _localctx = new NumberdataContext(Context, State); - EnterRule(_localctx, 28, RULE_numberdata); - int _la; - try { - State = 156; - ErrorHandler.Sync(this); - switch (TokenStream.LA(1)) { - case DEC: - EnterOuterAlt(_localctx, 1); - { - State = 152; - _localctx.decimalnumber = Match(DEC); - } - break; - case T__8: - EnterOuterAlt(_localctx, 2); - { - State = 153; - Match(T__8); - State = 154; - _localctx.hexnumber = TokenStream.LT(1); - _la = TokenStream.LA(1); - if ( !(_la==DEC || _la==HEX) ) { - _localctx.hexnumber = ErrorHandler.RecoverInline(this); - } - else { - ErrorHandler.ReportMatch(this); - Consume(); - } - } - break; - case FLOATNUM: - EnterOuterAlt(_localctx, 3); - { - State = 155; - _localctx.floatnumber = Match(FLOATNUM); - } - break; - default: - throw new NoViableAltException(this); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class HexbodyContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } - public HexbodyContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_hexbody; } } - } - - [RuleVersion(0)] - public HexbodyContext hexbody() { - HexbodyContext _localctx = new HexbodyContext(Context, State); - EnterRule(_localctx, 30, RULE_hexbody); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 158; - _la = TokenStream.LA(1); - if ( !(_la==DEC || _la==HEX) ) { - ErrorHandler.RecoverInline(this); - } - else { - ErrorHandler.ReportMatch(this); - Consume(); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class IdContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode DEC() { return GetToken(BdxScriptParser.DEC, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode HEX() { return GetToken(BdxScriptParser.HEX, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode ID() { return GetToken(BdxScriptParser.ID, 0); } - public IdContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_id; } } - } - - [RuleVersion(0)] - public IdContext id() { - IdContext _localctx = new IdContext(Context, State); - EnterRule(_localctx, 32, RULE_id); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 160; - _la = TokenStream.LA(1); - if ( !(((_la) & ~0x3f) == 0 && ((1L << _la) & 360448L) != 0) ) { - ErrorHandler.RecoverInline(this); - } - else { - ErrorHandler.ReportMatch(this); - Consume(); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class IncludeContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode FileName() { return GetToken(BdxScriptParser.FileName, 0); } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - public IncludeContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_include; } } - } - - [RuleVersion(0)] - public IncludeContext include() { - IncludeContext _localctx = new IncludeContext(Context, State); - EnterRule(_localctx, 34, RULE_include); - try { - EnterOuterAlt(_localctx, 1); - { - State = 162; - Match(T__9); - State = 163; - Match(FileName); - State = 164; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class SectionContext : ParserRuleContext { - [System.Diagnostics.DebuggerNonUserCode] public Section_idContext section_id() { - return GetRuleContext(0); - } - [System.Diagnostics.DebuggerNonUserCode] public ITerminalNode NL() { return GetToken(BdxScriptParser.NL, 0); } - public SectionContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_section; } } - } - - [RuleVersion(0)] - public SectionContext section() { - SectionContext _localctx = new SectionContext(Context, State); - EnterRule(_localctx, 36, RULE_section); - try { - EnterOuterAlt(_localctx, 1); - { - State = 166; - Match(T__10); - State = 167; - section_id(); - State = 168; - Match(NL); - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - public partial class Section_idContext : ParserRuleContext { - public Section_idContext(ParserRuleContext parent, int invokingState) - : base(parent, invokingState) - { - } - public override int RuleIndex { get { return RULE_section_id; } } - } - - [RuleVersion(0)] - public Section_idContext section_id() { - Section_idContext _localctx = new Section_idContext(Context, State); - EnterRule(_localctx, 38, RULE_section_id); - int _la; - try { - EnterOuterAlt(_localctx, 1); - { - State = 170; - _la = TokenStream.LA(1); - if ( !(_la==T__11 || _la==T__12) ) { - ErrorHandler.RecoverInline(this); - } - else { - ErrorHandler.ReportMatch(this); - Consume(); - } - } - } - catch (RecognitionException re) { - _localctx.exception = re; - ErrorHandler.ReportError(this, re); - ErrorHandler.Recover(this, re); - } - finally { - ExitRule(); - } - return _localctx; - } - - private static int[] _serializedATN = { - 4,1,22,173,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,2,6,7,6,2,7, - 7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,13,7,13,2,14,7,14, - 2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,19,1,0,5,0,42,8,0,10,0, - 12,0,45,9,0,1,0,1,0,5,0,49,8,0,10,0,12,0,52,9,0,5,0,54,8,0,10,0,12,0,57, - 9,0,1,0,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,3,1,72,8,1,1,2, - 1,2,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,1,4,1,4,3,4,86,8,4,1,5,1,5,1,5,1,5, - 5,5,92,8,5,10,5,12,5,95,9,5,1,5,1,5,1,6,1,6,1,6,1,6,5,6,103,8,6,10,6,12, - 6,106,9,6,1,6,1,6,1,7,1,7,1,7,3,7,113,8,7,1,7,1,7,1,8,1,8,1,8,3,8,120, - 8,8,1,8,1,8,1,9,1,9,1,9,1,9,5,9,128,8,9,10,9,12,9,131,9,9,3,9,133,8,9, - 1,9,1,9,1,10,1,10,3,10,139,8,10,1,11,1,11,1,11,1,12,1,12,1,12,3,12,147, - 8,12,1,13,1,13,3,13,151,8,13,1,14,1,14,1,14,1,14,3,14,157,8,14,1,15,1, - 15,1,16,1,16,1,17,1,17,1,17,1,17,1,18,1,18,1,18,1,18,1,19,1,19,1,19,0, - 0,20,0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,0,3,1,0,15, - 16,2,0,15,16,18,18,1,0,12,13,176,0,43,1,0,0,0,2,71,1,0,0,0,4,73,1,0,0, - 0,6,75,1,0,0,0,8,85,1,0,0,0,10,87,1,0,0,0,12,98,1,0,0,0,14,109,1,0,0,0, - 16,116,1,0,0,0,18,123,1,0,0,0,20,138,1,0,0,0,22,140,1,0,0,0,24,146,1,0, - 0,0,26,150,1,0,0,0,28,156,1,0,0,0,30,158,1,0,0,0,32,160,1,0,0,0,34,162, - 1,0,0,0,36,166,1,0,0,0,38,170,1,0,0,0,40,42,5,20,0,0,41,40,1,0,0,0,42, - 45,1,0,0,0,43,41,1,0,0,0,43,44,1,0,0,0,44,55,1,0,0,0,45,43,1,0,0,0,46, - 50,3,2,1,0,47,49,5,20,0,0,48,47,1,0,0,0,49,52,1,0,0,0,50,48,1,0,0,0,50, - 51,1,0,0,0,51,54,1,0,0,0,52,50,1,0,0,0,53,46,1,0,0,0,54,57,1,0,0,0,55, - 53,1,0,0,0,55,56,1,0,0,0,56,58,1,0,0,0,57,55,1,0,0,0,58,59,5,0,0,1,59, - 1,1,0,0,0,60,72,3,36,18,0,61,72,3,6,3,0,62,63,3,4,2,0,63,64,5,1,0,0,64, - 65,5,20,0,0,65,72,1,0,0,0,66,67,3,4,2,0,67,68,5,1,0,0,68,69,3,8,4,0,69, - 72,1,0,0,0,70,72,3,8,4,0,71,60,1,0,0,0,71,61,1,0,0,0,71,62,1,0,0,0,71, - 66,1,0,0,0,71,70,1,0,0,0,72,3,1,0,0,0,73,74,3,32,16,0,74,5,1,0,0,0,75, - 76,3,32,16,0,76,77,5,2,0,0,77,78,3,28,14,0,78,7,1,0,0,0,79,86,3,12,6,0, - 80,86,3,14,7,0,81,86,3,10,5,0,82,86,3,16,8,0,83,86,3,18,9,0,84,86,3,34, - 17,0,85,79,1,0,0,0,85,80,1,0,0,0,85,81,1,0,0,0,85,82,1,0,0,0,85,83,1,0, - 0,0,85,84,1,0,0,0,86,9,1,0,0,0,87,88,5,3,0,0,88,93,3,26,13,0,89,90,5,4, - 0,0,90,92,3,26,13,0,91,89,1,0,0,0,92,95,1,0,0,0,93,91,1,0,0,0,93,94,1, - 0,0,0,94,96,1,0,0,0,95,93,1,0,0,0,96,97,5,20,0,0,97,11,1,0,0,0,98,99,5, - 5,0,0,99,104,3,24,12,0,100,101,5,4,0,0,101,103,3,24,12,0,102,100,1,0,0, - 0,103,106,1,0,0,0,104,102,1,0,0,0,104,105,1,0,0,0,105,107,1,0,0,0,106, - 104,1,0,0,0,107,108,5,20,0,0,108,13,1,0,0,0,109,112,5,6,0,0,110,113,3, - 28,14,0,111,113,3,32,16,0,112,110,1,0,0,0,112,111,1,0,0,0,113,114,1,0, - 0,0,114,115,5,20,0,0,115,15,1,0,0,0,116,119,5,7,0,0,117,120,3,28,14,0, - 118,120,3,32,16,0,119,117,1,0,0,0,119,118,1,0,0,0,120,121,1,0,0,0,121, - 122,5,20,0,0,122,17,1,0,0,0,123,132,3,32,16,0,124,129,3,20,10,0,125,126, - 5,4,0,0,126,128,3,20,10,0,127,125,1,0,0,0,128,131,1,0,0,0,129,127,1,0, - 0,0,129,130,1,0,0,0,130,133,1,0,0,0,131,129,1,0,0,0,132,124,1,0,0,0,132, - 133,1,0,0,0,133,134,1,0,0,0,134,135,5,20,0,0,135,19,1,0,0,0,136,139,3, - 28,14,0,137,139,3,32,16,0,138,136,1,0,0,0,138,137,1,0,0,0,139,21,1,0,0, - 0,140,141,5,8,0,0,141,142,3,32,16,0,142,23,1,0,0,0,143,147,3,28,14,0,144, - 147,3,32,16,0,145,147,5,14,0,0,146,143,1,0,0,0,146,144,1,0,0,0,146,145, - 1,0,0,0,147,25,1,0,0,0,148,151,3,28,14,0,149,151,3,32,16,0,150,148,1,0, - 0,0,150,149,1,0,0,0,151,27,1,0,0,0,152,157,5,15,0,0,153,154,5,9,0,0,154, - 157,7,0,0,0,155,157,5,17,0,0,156,152,1,0,0,0,156,153,1,0,0,0,156,155,1, - 0,0,0,157,29,1,0,0,0,158,159,7,0,0,0,159,31,1,0,0,0,160,161,7,1,0,0,161, - 33,1,0,0,0,162,163,5,10,0,0,163,164,5,21,0,0,164,165,5,20,0,0,165,35,1, - 0,0,0,166,167,5,11,0,0,167,168,3,38,19,0,168,169,5,20,0,0,169,37,1,0,0, - 0,170,171,7,2,0,0,171,39,1,0,0,0,15,43,50,55,71,85,93,104,112,119,129, - 132,138,146,150,156 - }; - - public static readonly ATN _ATN = - new ATNDeserializer().Deserialize(_serializedATN); - - -} diff --git a/OpenKh.Command.Bdxio/Antlr4/README.md b/OpenKh.Command.Bdxio/Antlr4/README.md deleted file mode 100644 index 2c1c429ce..000000000 --- a/OpenKh.Command.Bdxio/Antlr4/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Using Antlr4 - -Use Visual Studio Code. - -Install vscode plugin `mike-lischke.vscode-antlr4`. - -Edit `BdxScript.g4`. - -After edit, run this command: - -```bat -java.exe -jar antlr-4.11.1-complete.jar BdxScript.g4 -Dlanguage=CSharp -no-listener -no-visitor -``` - -Only `BdxScriptLexer.cs` and `BdxScriptParser.cs` are needed. Others are not needed. - -## How bdx text is parsed to tree - -Use Visual Studio Code `mike-lischke.vscode-antlr4` plugin. - -And write this at `OpenKh/OpenKh.Command.Bdxio/Antlr4/.vscode/launch.json` - -``` -{ - "version": "1.0.0", - "configurations": [ - { - "name": "Debug ANTLR4 grammar", - "type": "antlr-debug", - "request": "launch", - "input": "test.bdx", - "grammar": "BdxScript.g4", - "printParseTree": true, - "visualParseTree": true - }, - ] -} -``` - -Write something to `test.bdx` and press `F5` key. diff --git a/OpenKh.Command.Bdxio/Commands/DecodeCommand.cs b/OpenKh.Command.Bdxio/Commands/DecodeCommand.cs index 2a3217590..84731e7d8 100644 --- a/OpenKh.Command.Bdxio/Commands/DecodeCommand.cs +++ b/OpenKh.Command.Bdxio/Commands/DecodeCommand.cs @@ -1,6 +1,6 @@ using McMaster.Extensions.CommandLineUtils; using NLog; -using OpenKh.Command.Bdxio.Utils; +using OpenKh.Kh2.Bdx.Utils; using System.ComponentModel.DataAnnotations; namespace OpenKh.Command.Bdxio.Commands diff --git a/OpenKh.Command.Bdxio/Commands/EncodeCommand.cs b/OpenKh.Command.Bdxio/Commands/EncodeCommand.cs index 40ee6230e..7a92963cf 100644 --- a/OpenKh.Command.Bdxio/Commands/EncodeCommand.cs +++ b/OpenKh.Command.Bdxio/Commands/EncodeCommand.cs @@ -1,7 +1,7 @@ using McMaster.Extensions.CommandLineUtils; using NLog; -using OpenKh.Command.Bdxio.Models; -using OpenKh.Command.Bdxio.Utils; +using OpenKh.Kh2.Bdx.Models; +using OpenKh.Kh2.Bdx.Utils; using System.ComponentModel.DataAnnotations; namespace OpenKh.Command.Bdxio.Commands diff --git a/OpenKh.Command.Bdxio/Models/BdxHeader.cs b/OpenKh.Command.Bdxio/Models/BdxHeader.cs deleted file mode 100644 index 4a8b28f8e..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxHeader.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Text; -using Xe.BinaryMapper; -using YamlDotNet.Serialization; - -namespace OpenKh.Command.Bdxio.Models -{ - public class BdxHeader - { - [Data(Count = 16), YamlIgnore()] public byte[] RawName { get; set; } = new byte[0]; - [Data] public int WorkSize { get; set; } - [Data] public int StackSize { get; set; } - [Data] public int TempSize { get; set; } - public Trigger[] Triggers { get; set; } = new Trigger[0]; - - [YamlMember(DefaultValuesHandling = DefaultValuesHandling.OmitDefaults)] public bool IsEmpty { get; set; } - - public string Name - { - get => Encoding.ASCII.GetString(RawName).Split('\0')[0]; - set => RawName = Encoding.ASCII.GetBytes((value ?? "").PadRight(16, '\0'), 0, 16); - } - - public class IntTrigger - { - [Data] public int Key { get; set; } - [Data] public int Addr { get; set; } - } - - public class Trigger - { - public int Key { get; set; } - public string Addr { get; set; } - } - } -} diff --git a/OpenKh.Command.Bdxio/Models/BdxInstruction.cs b/OpenKh.Command.Bdxio/Models/BdxInstruction.cs deleted file mode 100644 index 162ee715c..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxInstruction.cs +++ /dev/null @@ -1,32 +0,0 @@ -using OpenKh.Common.Utils; - -namespace OpenKh.Command.Bdxio.Models -{ - public class BdxInstruction - { - public ushort Code { get; set; } - - public BdxInstruction(ushort code) - { - Code = code; - } - - public byte Opcode - { - get => (byte)BitsUtil.Int.GetBits(Code, 0, 4); - set => Code = (ushort)BitsUtil.Int.SetBits(Code, 0, 4, value); - } - - public byte Sub - { - get => (byte)BitsUtil.Int.GetBits(Code, 4, 2); - set => Code = (ushort)BitsUtil.Int.SetBits(Code, 4, 2, value); - } - - public ushort Ssub - { - get => (ushort)BitsUtil.Int.GetBits(Code, 6, 12); - set => Code = (ushort)BitsUtil.Int.SetBits(Code, 6, 12, value); - } - } -} diff --git a/OpenKh.Command.Bdxio/Models/BdxInstructionDesc.cs b/OpenKh.Command.Bdxio/Models/BdxInstructionDesc.cs deleted file mode 100644 index fed0781cc..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxInstructionDesc.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace OpenKh.Command.Bdxio.Models -{ - public record BdxInstructionDesc - { - public ushort Code { get; set; } - public ushort CodeMask { get; set; } - - public string Name { get; set; } = ""; - public bool IsSyscall { get; set; } - public bool IsGosub { get; set; } - public bool IsJump { get; set; } - public bool IsJumpConditional { get; set; } - public bool NeverReturn { get; set; } - public bool IsGosubRet { get; set; } - public bool CodeRevealerLabeling { get; set; } - public int CodeSize { get; set; } - public Arg[] Args { get; set; } = new Arg[0]; - public string[] OldNames { get; set; } = new string[0]; - - public override string ToString() => Name; - - public record Arg - { - public string Name { get; set; } = ""; - public ArgType Type { get; set; } - - /// - /// Is relative offset to bdx instruction - /// - public bool IsRelative { get; set; } - public bool AiPos { get; set; } - public bool WorkPos { get; set; } - } - - public enum ArgType - { - Ssub, - Imm16, - Imm32, - Float32, - } - } -} diff --git a/OpenKh.Command.Bdxio/Models/BdxInstructionDescs.cs b/OpenKh.Command.Bdxio/Models/BdxInstructionDescs.cs deleted file mode 100644 index e6b41152e..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxInstructionDescs.cs +++ /dev/null @@ -1,95 +0,0 @@ -using static OpenKh.Command.Bdxio.Models.BdxInstructionDesc; - -namespace OpenKh.Command.Bdxio.Models -{ - public class BdxInstructionDescs - { - public static IEnumerable GetDescs() => _descs; - - public static BdxInstructionDesc? FindByCode(ushort code) - { - return _descs - .FirstOrDefault(it => (code & it.CodeMask) == it.Code); - } - - private static readonly BdxInstructionDesc[] _descs = new[] - { - #region Generated - - new BdxInstructionDesc { Code = 0x0000, CodeMask = 0x003F, Name = "push", CodeRevealerLabeling = true, CodeSize = 3, Args = new[] { new Arg { Name = "imm32", Type = ArgType.Imm32 } }, OldNames = new[] { "pushImm" } }, - new BdxInstructionDesc { Code = 0x0010, CodeMask = 0x003F, Name = "push.s", CodeSize = 3, Args = new[] { new Arg { Name = "float32", Type = ArgType.Float32 } }, OldNames = new[] { "pushImmf" } }, - new BdxInstructionDesc { Code = 0x0020, CodeMask = 0xFFFF, Name = "push.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromPSp" } }, - new BdxInstructionDesc { Code = 0x0060, CodeMask = 0xFFFF, Name = "push.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "pushFromPWp" } }, - new BdxInstructionDesc { Code = 0x00A0, CodeMask = 0xFFFF, Name = "push.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromPSpVal" } }, - new BdxInstructionDesc { Code = 0x00E0, CodeMask = 0xFFFF, Name = "push.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "pushFromPAi" } }, - new BdxInstructionDesc { Code = 0x0030, CodeMask = 0xFFFF, Name = "push.d.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromFSp" } }, - new BdxInstructionDesc { Code = 0x0070, CodeMask = 0xFFFF, Name = "push.d.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "pushFromFWp" } }, - new BdxInstructionDesc { Code = 0x00B0, CodeMask = 0xFFFF, Name = "push.d.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "pushFromFSpVal" } }, - new BdxInstructionDesc { Code = 0x00F0, CodeMask = 0xFFFF, Name = "push.d.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "pushFromFAi" } }, - new BdxInstructionDesc { Code = 0x0001, CodeMask = 0xFFCF, Name = "pop.sp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "popToSp" } }, - new BdxInstructionDesc { Code = 0x0041, CodeMask = 0xFFCF, Name = "pop.wp", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "popToWp" } }, - new BdxInstructionDesc { Code = 0x0081, CodeMask = 0xFFCF, Name = "pop.sp.d", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "popToSpVal" } }, - new BdxInstructionDesc { Code = 0x00C1, CodeMask = 0xFFCF, Name = "pop.bd", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "popToAi" } }, - new BdxInstructionDesc { Code = 0x0002, CodeMask = 0xFFCF, Name = "memcpy.sp", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16 } }, OldNames = new[] { "memcpyToSp" } }, - new BdxInstructionDesc { Code = 0x0042, CodeMask = 0xFFCF, Name = "memcpy.wp", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16, WorkPos = true } }, OldNames = new[] { "memcpyToWp" } }, - new BdxInstructionDesc { Code = 0x0082, CodeMask = 0xFFCF, Name = "memcpy.sp.d", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16 } }, OldNames = new[] { "memcpyToSpVal" } }, - new BdxInstructionDesc { Code = 0x00C2, CodeMask = 0xFFCF, Name = "memcpy.bd", CodeSize = 3, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 }, new Arg { Name = "imm16_2", Type = ArgType.Imm16, AiPos = true } }, OldNames = new[] { "memcpyToSpAi" } }, - new BdxInstructionDesc { Code = 0x0003, CodeMask = 0x000F, Name = "push.d.pop", CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16 } }, OldNames = new[] { "fetchValue" } }, - new BdxInstructionDesc { Code = 0x0004, CodeMask = 0x000F, Name = "memcpy", CodeSize = 1, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub } } }, - new BdxInstructionDesc { Code = 0x0005, CodeMask = 0xFFFF, Name = "cvt.w.s", CodeSize = 1, OldNames = new[] { "citf" } }, - new BdxInstructionDesc { Code = 0x0085, CodeMask = 0xFFFF, Name = "neg", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x00C5, CodeMask = 0xFFFF, Name = "not", CodeSize = 1, OldNames = new[] { "inv" } }, - new BdxInstructionDesc { Code = 0x0105, CodeMask = 0xFFFF, Name = "seqz", CodeSize = 1, OldNames = new[] { "eqz" } }, - new BdxInstructionDesc { Code = 0x0145, CodeMask = 0xFFFF, Name = "abs", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0185, CodeMask = 0xFFFF, Name = "sltz", CodeSize = 1, OldNames = new[] { "msb" } }, - new BdxInstructionDesc { Code = 0x01C5, CodeMask = 0xFFFF, Name = "slez", CodeSize = 1, OldNames = new[] { "info" } }, - new BdxInstructionDesc { Code = 0x0205, CodeMask = 0xFFFF, Name = "seqz", CodeSize = 1, OldNames = new[] { "eqz" } }, - new BdxInstructionDesc { Code = 0x0245, CodeMask = 0xFFFF, Name = "snez", CodeSize = 1, OldNames = new[] { "neqz" } }, - new BdxInstructionDesc { Code = 0x0285, CodeMask = 0xFFFF, Name = "sgez", CodeSize = 1, OldNames = new[] { "msbi" } }, - new BdxInstructionDesc { Code = 0x02C5, CodeMask = 0xFFFF, Name = "sgtz", CodeSize = 1, OldNames = new[] { "ipos" } }, - new BdxInstructionDesc { Code = 0x0055, CodeMask = 0xFFFF, Name = "cvt.s.w", CodeSize = 1, OldNames = new[] { "cfti" } }, - new BdxInstructionDesc { Code = 0x0095, CodeMask = 0xFFFF, Name = "neg.s", CodeSize = 1, OldNames = new[] { "negf" } }, - new BdxInstructionDesc { Code = 0x0155, CodeMask = 0xFFFF, Name = "abs.s", CodeSize = 1, OldNames = new[] { "absf" } }, - new BdxInstructionDesc { Code = 0x0195, CodeMask = 0xFFFF, Name = "sltz.s", CodeSize = 1, OldNames = new[] { "infzf" } }, - new BdxInstructionDesc { Code = 0x01D5, CodeMask = 0xFFFF, Name = "slez.s", CodeSize = 1, OldNames = new[] { "infoezf" } }, - new BdxInstructionDesc { Code = 0x0215, CodeMask = 0xFFFF, Name = "seqz.s", CodeSize = 1, OldNames = new[] { "eqzf" } }, - new BdxInstructionDesc { Code = 0x0255, CodeMask = 0xFFFF, Name = "snez.s", CodeSize = 1, OldNames = new[] { "neqzf" } }, - new BdxInstructionDesc { Code = 0x0295, CodeMask = 0xFFFF, Name = "sgez.s", CodeSize = 1, OldNames = new[] { "supoezf" } }, - new BdxInstructionDesc { Code = 0x02D5, CodeMask = 0xFFFF, Name = "sgtz.s", CodeSize = 1, OldNames = new[] { "supzf" } }, - new BdxInstructionDesc { Code = 0x0006, CodeMask = 0xFFFF, Name = "add", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0046, CodeMask = 0xFFFF, Name = "sub", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0086, CodeMask = 0xFFFF, Name = "mul", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x00C6, CodeMask = 0xFFFF, Name = "div", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0106, CodeMask = 0xFFFF, Name = "mod", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0146, CodeMask = 0xFFFF, Name = "and", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0186, CodeMask = 0xFFFF, Name = "or", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x01C6, CodeMask = 0xFFFF, Name = "xor", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0206, CodeMask = 0xFFFF, Name = "sll", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0246, CodeMask = 0xFFFF, Name = "sra", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0286, CodeMask = 0xFFFF, Name = "land", CodeSize = 1, OldNames = new[] { "eqzv" } }, - new BdxInstructionDesc { Code = 0x02C6, CodeMask = 0xFFFF, Name = "lor", CodeSize = 1, OldNames = new[] { "neqzv" } }, - new BdxInstructionDesc { Code = 0x0016, CodeMask = 0xFFFF, Name = "add.s", CodeSize = 1, OldNames = new[] { "addf" } }, - new BdxInstructionDesc { Code = 0x0056, CodeMask = 0xFFFF, Name = "sub.s", CodeSize = 1, OldNames = new[] { "subf" } }, - new BdxInstructionDesc { Code = 0x0096, CodeMask = 0xFFFF, Name = "mul.s", CodeSize = 1, OldNames = new[] { "mulf" } }, - new BdxInstructionDesc { Code = 0x00D6, CodeMask = 0xFFFF, Name = "div.s", CodeSize = 1, OldNames = new[] { "divf" } }, - new BdxInstructionDesc { Code = 0x0116, CodeMask = 0xFFFF, Name = "mod.s", CodeSize = 1, OldNames = new[] { "modf" } }, - new BdxInstructionDesc { Code = 0x0007, CodeMask = 0xFFCF, Name = "b", IsJump = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jmp" } }, - new BdxInstructionDesc { Code = 0x0047, CodeMask = 0xFFCF, Name = "beqz", IsJump = true, IsJumpConditional = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jz" } }, - new BdxInstructionDesc { Code = 0x0087, CodeMask = 0xFFCF, Name = "bnez", IsJump = true, IsJumpConditional = true, CodeSize = 2, Args = new[] { new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "jnz" } }, - new BdxInstructionDesc { Code = 0x0008, CodeMask = 0x000F, Name = "jal", IsGosub = true, CodeSize = 2, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm16", Type = ArgType.Imm16, IsRelative = true } }, OldNames = new[] { "gosub" } }, - new BdxInstructionDesc { Code = 0x0009, CodeMask = 0xFFCF, Name = "halt", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0049, CodeMask = 0xFFCF, Name = "exit", NeverReturn = true, CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0089, CodeMask = 0xFFCF, Name = "ret", IsGosubRet = true, CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x00C9, CodeMask = 0xFFCF, Name = "drop", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0149, CodeMask = 0xFFCF, Name = "dup", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0189, CodeMask = 0xFFCF, Name = "sin", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x01C9, CodeMask = 0xFFCF, Name = "cos", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0209, CodeMask = 0xFFCF, Name = "degr", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x0249, CodeMask = 0xFFCF, Name = "radd", CodeSize = 1 }, - new BdxInstructionDesc { Code = 0x000A, CodeMask = 0x000F, Name = "syscall", IsSyscall = true, CodeSize = 2, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm16", Type = ArgType.Imm16 } } }, - new BdxInstructionDesc { Code = 0x000B, CodeMask = 0x000F, Name = "jal32", IsGosub = true, CodeSize = 3, Args = new[] { new Arg { Name = "ssub", Type = ArgType.Ssub }, new Arg { Name = "imm32", Type = ArgType.Imm32, IsRelative = true } }, OldNames = new[] { "gosub32" } }, - - #endregion - }; - } -} diff --git a/OpenKh.Command.Bdxio/Models/BdxTrap.cs b/OpenKh.Command.Bdxio/Models/BdxTrap.cs deleted file mode 100644 index b44a23d28..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxTrap.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace OpenKh.Command.Bdxio.Models -{ - public class BdxTrap - { - public int TableIndex { get; set; } - public int TrapIndex { get; set; } - public string Name { get; set; } - public int Flags { get; set; } - } -} diff --git a/OpenKh.Command.Bdxio/Models/BdxTraps.cs b/OpenKh.Command.Bdxio/Models/BdxTraps.cs deleted file mode 100644 index 51a55a3db..000000000 --- a/OpenKh.Command.Bdxio/Models/BdxTraps.cs +++ /dev/null @@ -1,843 +0,0 @@ -namespace OpenKh.Command.Bdxio.Models -{ - public class BdxTraps - { - public static IEnumerable GetTraps() => _traps; - - private static readonly BdxTrap[] _traps = new[] - { - #region Generated - - new BdxTrap { TableIndex = 0, TrapIndex = 0, Name = "trap_puti", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 1, Name = "trap_putf", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 2, Name = "trap_puts", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 3, Name = "trap_frametime", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 4, Name = "trap_vector_add", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 5, Name = "trap_vector_sub", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 6, Name = "trap_vector_len", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 7, Name = "trap_vector_normalize", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 8, Name = "trap_vector_dump", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 9, Name = "trap_thread_start", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 0, TrapIndex = 11, Name = "trap_file_is_reading", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 12, Name = "trap_file_flush", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 13, Name = "trap_vector_roty", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 14, Name = "trap_progress_set_flag", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 15, Name = "trap_progress_check_flag", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 16, Name = "trap_random_get", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 17, Name = "trap_random_getf", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 18, Name = "trap_random_range", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 19, Name = "trap_worldflag_set", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 20, Name = "trap_worldflag_check", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 21, Name = "trap_vector_get_rot_xz", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 22, Name = "trap_abs", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 23, Name = "trap_absf", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 24, Name = "trap_stputi", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 25, Name = "trap_stputf", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 26, Name = "trap_stputs", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 27, Name = "func_system_set_game_speed", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 28, Name = "method_blur_init", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 29, Name = "method_blur_start", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 30, Name = "method_blur_stop", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 31, Name = "func_screen_whiteout", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 32, Name = "func_screen_whitein", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 35, Name = "method_vector_scale", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 36, Name = "trap_vector_mul", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 37, Name = "trap_vector_div", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 38, Name = "trap_effect_set_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 39, Name = "trap_effect_set_scale", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 40, Name = "trap_effect_set_rot", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 41, Name = "trap_effect_set_dir", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 42, Name = "trap_vector_atan_xz", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 43, Name = "trap_fixrad", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 44, Name = "trap_effect_loop_end", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 45, Name = "trap_vector_addf", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 46, Name = "trap_vector_homing", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 47, Name = "trap_memory_alloc", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 48, Name = "trap_memory_free", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 49, Name = "trap_effect_is_alive", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 50, Name = "trap_effect_is_active", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 51, Name = "trap_effect_kill", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 52, Name = "trap_effect_fadeout", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 53, Name = "trap_effect_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 54, Name = "trap_effect_dir", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 55, Name = "trap_timer_count_down", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 56, Name = "trap_timer_count_up", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 57, Name = "trap_saveflag_set", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 58, Name = "trap_saveflag_reset", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 59, Name = "trap_saveflag_check", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 60, Name = "trap_assert", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 61, Name = "trap_saveram_get_partram", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 62, Name = "trap_partram_set_item_max", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 63, Name = "trap_item_get", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 64, Name = "trap_sound_disable", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 65, Name = "trap_sound_play_system", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 66, Name = "trap_effect_pause", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 67, Name = "trap_effect_set_color", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 68, Name = "trap_vector_rotx", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 69, Name = "trap_menuflag_set", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 70, Name = "trap_progress_is_second", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 73, Name = "trap_menuflag_reset", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 74, Name = "trap_screen_show_picture", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 75, Name = "trap_saveram_set_weapon", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 76, Name = "trap_saveram_set_form_weapon", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 77, Name = "trap_screen_cross_fade", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 78, Name = "trap_vector_inter", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 79, Name = "trap_effect_add_dead_block", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 80, Name = "trap_pad_is_button", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 81, Name = "trap_pad_is_trigger", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 82, Name = "trap_vector_outer_product", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 83, Name = "trap_vector_rot", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 84, Name = "trap_vector_angle", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 85, Name = "trap_effect_loop_end_kill", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 86, Name = "trap_effect_set_type", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 87, Name = "trap_screen_fadeout", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 88, Name = "trap_screen_fadein", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 89, Name = "trap_menuflag_check", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 90, Name = "trap_vector_draw", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 0, TrapIndex = 91, Name = "trap_vector_inner_prodcut", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 92, Name = "trap_partram_add_attack", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 93, Name = "trap_partram_add_wisdom", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 94, Name = "trap_partram_add_defence", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 95, Name = "trap_partram_set_levelup_type", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 96, Name = "trap_partram_add_ap", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 97, Name = "trap_item_reduce", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 98, Name = "trap_saveram_set_form_ability", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 0, TrapIndex = 99, Name = "trap_partram_add_ability", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 0, TrapIndex = 100, Name = "trap_saveram_increment_friend_recov", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 101, Name = "trap_progress_is_secret_movie", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 102, Name = "trap_vector_to_angle", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 0, TrapIndex = 103, Name = "trap_progress_is_fm_secret_movie", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 0, TrapIndex = 104, Name = "trap_sound_set_bgse_volume", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 0, Name = "trap_sysobj_appear", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 1, Name = "trap_obj_set_rot", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 2, Name = "trap_sysobj_moveto", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 3, Name = "trap_sysobj_player", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 4, Name = "trap_obj_wish_dir", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 5, Name = "trap_act_table_init", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 6, Name = "trap_act_table_add", Flags = 0x0000000C }, - new BdxTrap { TableIndex = 1, TrapIndex = 7, Name = "trap_obj_set_act_table", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 8, Name = "trap_obj_act_start", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 9, Name = "trap_obj_act_push", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 10, Name = "trap_obj_is_act_exec", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 11, Name = "trap_sysobj_motion_start", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 12, Name = "trap_sysobj_motion_change", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 13, Name = "trap_sysobj_motion_push", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 14, Name = "trap_sysobj_motion_is_end", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 15, Name = "trap_sysobj_motion_id", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 17, Name = "trap_obj_leave_force", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 18, Name = "trap_obj_attach", Flags = 0x00000006 }, - new BdxTrap { TableIndex = 1, TrapIndex = 19, Name = "trap_sysobj_fadeout", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 20, Name = "trap_sysobj_fadein", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 21, Name = "trap_obj_effect_start", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 22, Name = "trap_obj_effect_start_pos", Flags = 0x40000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 23, Name = "trap_area_world", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 24, Name = "trap_area_area", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 25, Name = "trap_area_map_set", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 26, Name = "trap_area_battle_set", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 27, Name = "trap_area_event_set", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 28, Name = "trap_obj_leave", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 29, Name = "trap_obj_motion_capture", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 30, Name = "trap_area_jump", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 31, Name = "trap_area_setjump", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 32, Name = "trap_message_open", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 33, Name = "trap_message_close", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 34, Name = "trap_event_is_exec", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 35, Name = "trap_area_init", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 36, Name = "trap_bg_hide", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 37, Name = "trap_bg_show", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 38, Name = "trap_obj_set_team", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 39, Name = "trap_obj_unit_arg", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 40, Name = "trap_obj_is_pirate_shade", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 41, Name = "trap_signal_call", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 42, Name = "func_obj_control_off", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 43, Name = "func_obj_control_on", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 44, Name = "func_history_clear_enemy", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 45, Name = "func_area_activate_unit", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 46, Name = "func_bg_barrier_on", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 47, Name = "func_bg_barrier_off", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 48, Name = "method_message_is_end", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 49, Name = "method_obj_enable_reaction_command", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 50, Name = "method_obj_disable_reaction_command", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 51, Name = "method_obj_reset_reaction_command", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 52, Name = "method_obj_enable_collision", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 53, Name = "method_obj_disable_collision", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 54, Name = "method_obj_reset_collision", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 55, Name = "method_obj_jump", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 56, Name = "method_obj_is_culling", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 57, Name = "trap_obj_is_jump", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 58, Name = "trap_obj_fly", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 59, Name = "trap_obj_is_fly", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 60, Name = "trap_obj_is_air", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 61, Name = "trap_sysobj_motion_frame_start", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 62, Name = "trap_obj_get_moved", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 63, Name = "trap_obj_is_motion_in_loop", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 64, Name = "trap_obj_get_wish_movement", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 65, Name = "trap_obj_exec_fall", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 66, Name = "trap_obj_exec_land", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 67, Name = "trap_obj_motion_get_length", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 68, Name = "trap_obj_motion_get_loop_top", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 69, Name = "trap_obj_motion_get_time", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 70, Name = "trap_obj_set_flag", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 71, Name = "trap_obj_reset_flag", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 72, Name = "trap_obj_check_flag", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 73, Name = "trap_obj_hover", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 74, Name = "trap_obj_idle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 75, Name = "trap_obj_motion_hook", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 76, Name = "trap_obj_motion_unhook", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 77, Name = "trap_obj_motion_is_hook", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 78, Name = "trap_obj_motion_is_no_control", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 79, Name = "trap_obj_set_dir", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 80, Name = "trap_obj_turn_dir", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 81, Name = "trap_obj_act_wedge", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 82, Name = "trap_obj_thread_start", Flags = 0x40000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 83, Name = "trap_obj_apply_bone_matrix", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 84, Name = "trap_obj_sheet", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 85, Name = "trap_obj_texanm_start", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 86, Name = "trap_obj_texanm_stop", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 87, Name = "trap_obj_effect_start_bind", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 88, Name = "trap_obj_target_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 89, Name = "trap_obj_move_request", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 90, Name = "trap_obj_act_shout", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 91, Name = "trap_obj_star", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 92, Name = "trap_obj_scatter_prize", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 93, Name = "trap_sysobj_party", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 94, Name = "trap_sysobj_is_exist", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 95, Name = "trap_obj_fly_to_jump", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 96, Name = "trap_obj_get_action", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 97, Name = "trap_obj_spec", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 98, Name = "trap_obj_step_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 99, Name = "trap_obj_float_height", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 100, Name = "trap_obj_jump_height_to_uptime", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 101, Name = "trap_obj_motion_is_capture", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 102, Name = "trap_obj_detach", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 103, Name = "trap_obj_set_detach_callback", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 104, Name = "trap_obj_shadow_move_start", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 105, Name = "trap_obj_shadow_move_end", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 106, Name = "trap_signal_reserve_hp", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 107, Name = "trap_obj_motion_speed", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 108, Name = "trap_obj_show_part", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 109, Name = "trap_obj_hide_part", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 110, Name = "trap_obj_get_appear_way", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 111, Name = "trap_obj_set_movement", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 112, Name = "trap_obj_hook", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 113, Name = "trap_player_get_movement", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 114, Name = "trap_obj_search_by_entry", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 115, Name = "trap_obj_set_jump_motion", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 117, Name = "trap_command_cage_on", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 118, Name = "trap_command_cage_off", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 119, Name = "trap_obj_check_step", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 120, Name = "trap_target_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 121, Name = "trap_target_search", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 122, Name = "trap_obj_dump", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 123, Name = "trap_obj_tex_fade_set", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 124, Name = "trap_obj_is_entry_fly", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 125, Name = "trap_obj_tex_fade_start", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 126, Name = "trap_obj_motion_sync", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 127, Name = "trap_obj_act_clear", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 128, Name = "trap_obj_sysjump", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 129, Name = "trap_obj_blow", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 130, Name = "trap_obj_cmp", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 131, Name = "trap_target_dup", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 132, Name = "trap_target_free", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 133, Name = "trap_obj_hide", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 134, Name = "trap_obj_show", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 135, Name = "trap_bg_cross_pos", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 136, Name = "trap_bg_is_floor", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 137, Name = "trap_bg_get_normal", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 138, Name = "trap_pax_start", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 139, Name = "trap_pax_start_bind", Flags = 0x40000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 140, Name = "trap_target_is_exist", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 141, Name = "trap_bg_ground_pos", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 142, Name = "trap_signal_reserve_min_hp", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 143, Name = "trap_obj_search_by_serial", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 144, Name = "trap_obj_serial", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 145, Name = "trap_obj_touch_zone", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 146, Name = "trap_obj_hitback", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 147, Name = "trap_obj_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 148, Name = "trap_obj_set_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 149, Name = "trap_obj_effect_start_bind_other", Flags = 0x40000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 150, Name = "trap_obj_motion_check_range", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 151, Name = "trap_obj_motion_check_trigger", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 152, Name = "trap_status_is_mission", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 153, Name = "trap_obj_reset_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 154, Name = "trap_status_secure_mode_start", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 155, Name = "trap_obj_add_hp", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 156, Name = "trap_obj_hop", Flags = 0x00000007 }, - new BdxTrap { TableIndex = 1, TrapIndex = 157, Name = "trap_obj_camera_start", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 158, Name = "trap_bg_set_belt_conveyor", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 159, Name = "trap_bg_set_uvscroll_speed", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 160, Name = "trap_target_set_obj", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 161, Name = "trap_obj_is_attach", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 162, Name = "trap_target_set_before_player", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 163, Name = "trap_target_set_after_player", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 164, Name = "trap_obj_camera_start_global", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 165, Name = "trap_command_override", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 166, Name = "trap_target_attack", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 167, Name = "trap_obj_act_start_pri", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 168, Name = "trap_obj_flyjump", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 169, Name = "trap_obj_effect_unbind", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 170, Name = "trap_obj_unit_group", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 171, Name = "trap_status_no_leave", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 172, Name = "trap_obj_can_look", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 173, Name = "trap_obj_can_look_pos", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 174, Name = "trap_obj_look_start", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 175, Name = "trap_obj_look_start_pos", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 176, Name = "trap_obj_look_end", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 177, Name = "trap_obj_set_path", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 178, Name = "trap_obj_get_path_movement", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 179, Name = "trap_obj_set_fall_motion", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 180, Name = "trap_obj_set_land_motion", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 181, Name = "trap_light_create", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 182, Name = "trap_light_set_flag", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 183, Name = "trap_light_set_color", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 184, Name = "trap_light_fadeout", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 185, Name = "trap_obj_set_parts_color", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 186, Name = "trap_obj_reset_parts_color", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 187, Name = "trap_status_prize_drain_start", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 188, Name = "trap_status_prize_drain_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 189, Name = "trap_obj_history_mark", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 190, Name = "trap_obj_is_history_mark", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 191, Name = "trap_obj_lockon_target", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 192, Name = "trap_obj_is_motion_cancel", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 193, Name = "trap_camera_warp", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 194, Name = "trap_obj_set_stealth", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 195, Name = "trap_obj_reset_stealth", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 196, Name = "trap_area_entrance", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 197, Name = "trap_area_cost_rest", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 198, Name = "trap_obj_set_player_random_pos", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 199, Name = "trap_obj_set_random_pos", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 200, Name = "trap_obj_set_bg_collision_type", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 201, Name = "trap_obj_dir", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 202, Name = "trap_unit_disable", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 203, Name = "trap_unit_enable", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 204, Name = "trap_status_force_leave_start", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 205, Name = "trap_status_force_leave_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 206, Name = "trap_status_is_force_leave", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 207, Name = "trap_camera_watch", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 208, Name = "trap_obj_is_hover", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 209, Name = "trap_obj_dead", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 210, Name = "trap_obj_search_by_part", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 211, Name = "trap_obj_pattern_enable", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 212, Name = "trap_obj_pattern_disable", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 213, Name = "trap_obj_part", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 214, Name = "trap_obj_hook_stop", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 217, Name = "trap_obj_set_pos_trans", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 218, Name = "trap_obj_set_unit_arg", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 219, Name = "trap_obj_camera_start", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 220, Name = "trap_obj_move_to_space", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 221, Name = "trap_obj_can_decide_command", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 222, Name = "trap_obj_get_entry_id", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 223, Name = "trap_camera_cancel", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 224, Name = "trap_obj_is_action_air", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 225, Name = "trap_obj_is_star", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 226, Name = "trap_obj_scatter_prize_mu", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 227, Name = "trap_obj_jump_direct", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 228, Name = "trap_sheet_hp", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 229, Name = "trap_sheet_max_hp", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 230, Name = "trap_sheet_hp_rate", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 231, Name = "trap_sheet_set_min_hp", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 232, Name = "trap_sheet_min_hp", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 233, Name = "trap_sheet_set_hp", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 234, Name = "trap_party_get_weapon", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 235, Name = "trap_party_hand_to_bone", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 236, Name = "trap_obj_motion_unsync", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 237, Name = "trap_command_override_top", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 238, Name = "trap_obj_motion_capture_id", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 239, Name = "trap_obj_is_unit_active", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 240, Name = "trap_obj_scatter_prize_tt", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 241, Name = "trap_act_shout", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 242, Name = "trap_player_capture_form", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 243, Name = "trap_player_capture_form_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 244, Name = "trap_status_is_battle", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 245, Name = "trap_target_clear_before_player", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 246, Name = "trap_target_clear_after_player", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 247, Name = "trap_bg_get_random_pos", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 248, Name = "trap_bg_get_random_pos_air", Flags = 0x40000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 249, Name = "trap_status_set_prize_ratio", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 250, Name = "trap_status_set_lockon_ratio", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 251, Name = "trap_light_fadein", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 252, Name = "trap_camera_apply_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 253, Name = "trap_obj_can_capture_control", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 254, Name = "trap_obj_is_ride", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 255, Name = "trap_obj_disable_occ", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 256, Name = "trap_obj_enable_occ", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 257, Name = "trap_light_set_fog_near", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 258, Name = "trap_light_set_fog_far", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 259, Name = "trap_light_set_fog_min", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 260, Name = "trap_light_set_fog_max", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 261, Name = "trap_sheet_munny", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 262, Name = "trap_obj_voice", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 263, Name = "trap_player_set_exec_rc", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 264, Name = "trap_status_secure_mode_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 265, Name = "trap_obj_set_medal", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 266, Name = "trap_obj_get_medal", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 267, Name = "trap_obj_scatter_medal", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 268, Name = "trap_obj_action_lightcycle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 269, Name = "trap_obj_get_lightcycle_movement", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 270, Name = "trap_obj_motion_disable_anmatr_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 271, Name = "trap_obj_motion_enable_anmatr_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 272, Name = "trap_obj_is_dead", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 273, Name = "trap_signal_hook", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 274, Name = "trap_event_get_rest_time", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 275, Name = "trap_obj_recov_holylight", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 276, Name = "trap_obj_use_mp", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 277, Name = "trap_obj_reraise", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 278, Name = "trap_obj_scatter_prize_tr", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 279, Name = "trap_prize_appear_tr", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 280, Name = "trap_sheet_add_munny", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 281, Name = "trap_camera_begin_scope", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 283, Name = "trap_camera_end_scope", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 284, Name = "trap_tutorial_pause", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 285, Name = "trap_obj_show_picture", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 286, Name = "trap_status_hide_shadow", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 287, Name = "trap_status_show_shadow", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 288, Name = "trap_status_begin_free_ability", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 289, Name = "trap_status_end_free_ability", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 290, Name = "trap_picture_change", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 291, Name = "trap_obj_levelup_unit", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 292, Name = "trap_obj_search_by_unit_arg", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 293, Name = "trap_event_control_off", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 294, Name = "trap_event_control_on", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 295, Name = "trap_camera_reset", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 296, Name = "trap_tutorial_open", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 297, Name = "trap_player_get_rc", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 298, Name = "trap_worldwork_get", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 299, Name = "trap_area_set_next_entrance", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 300, Name = "trap_prize_num", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 301, Name = "trap_tutorial_is_open", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 302, Name = "trap_obj_set_skateboard_mode", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 303, Name = "trap_area_cost_ratio", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 304, Name = "trap_obj_search_by_glance", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 305, Name = "trap_camera_eye", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 306, Name = "trap_camera_at", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 307, Name = "trap_obj_search_by_camera", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 308, Name = "trap_obj_capture_command", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 309, Name = "trap_sysobj_is_player", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 310, Name = "trap_obj_get_weight", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 311, Name = "trap_sheet_set_element_rate", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 312, Name = "trap_camera_set_scope_zoom", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 313, Name = "trap_camera_set_scope_closeup_distance", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 314, Name = "trap_camera_set_scope_target_pos", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 315, Name = "trap_picture_set_pos", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 316, Name = "trap_camera_get_projection_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 317, Name = "trap_status_no_gameover", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 318, Name = "trap_obj_play_se", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 319, Name = "trap_sysobj_is_sora", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 320, Name = "trap_unit_get_enemy_num", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 321, Name = "trap_player_lockon", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 322, Name = "trap_command_enable_item", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 323, Name = "trap_obj_count_entry", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 324, Name = "trap_obj_pattern_reset", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 325, Name = "trap_obj_reaction_callback", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 326, Name = "trap_bg_set_animation_speed", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 327, Name = "trap_prize_get_all_tr", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 328, Name = "trap_obj_dead_mark", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 329, Name = "trap_sheet_set_prize_range", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 330, Name = "trap_obj_set_cannon_camera_offset", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 331, Name = "trap_obj_each_all", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 332, Name = "trap_sysobj_is_btlnpc", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 333, Name = "trap_obj_set_cannon_param", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 1, TrapIndex = 334, Name = "trap_command_enable", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 335, Name = "trap_obj_disable_occ_bone", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 336, Name = "trap_obj_enable_occ_bone", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 337, Name = "trap_command_set_side_b", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 338, Name = "trap_prize_return_ca", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 339, Name = "trap_prize_vacuum_ca", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 340, Name = "trap_prize_vacuum_range_ca", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 341, Name = "trap_prize_num_ca", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 342, Name = "trap_prize_appear_num", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 1, TrapIndex = 343, Name = "trap_obj_is_equip_ability", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 344, Name = "trap_obj_clear_occ", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 345, Name = "trap_command_override_slot", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 1, TrapIndex = 346, Name = "trap_command_slot_set_status", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 347, Name = "trap_obj_can_see", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 348, Name = "trap_sheet_set_hitback_addition", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 349, Name = "trap_obj_effect_kill_all", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 350, Name = "trap_status_close_pete_curtain", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 351, Name = "trap_status_open_pete_curtain", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 352, Name = "trap_area_set_return_tr", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 353, Name = "trap_obj_start_mpdrive", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 354, Name = "trap_event_layer_off", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 355, Name = "trap_player_can_capture_form", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 356, Name = "trap_event_layer_on", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 1, TrapIndex = 357, Name = "trap_sheet_attack_level", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 358, Name = "trap_sheet_set_attack_level", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 359, Name = "trap_obj_hook_command_image", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 360, Name = "trap_obj_reset_command_image", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 361, Name = "trap_sheet_level", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 362, Name = "trap_treasure_get", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 363, Name = "trap_prize_appear_xaldin", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 364, Name = "trap_jigsaw_get", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 1, TrapIndex = 365, Name = "trap_command_disable_group", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 366, Name = "trap_command_enable_group", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 1, TrapIndex = 367, Name = "trap_obj_get_move_to_space_pos", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 0, Name = "trap_enemy_exec_damage", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 1, Name = "trap_enemy_exec_damage_blow", Flags = 0x00000006 }, - new BdxTrap { TableIndex = 2, TrapIndex = 2, Name = "trap_enemy_exec_damage_small", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 3, Name = "trap_enemy_exec_damage_hitback", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 4, Name = "trap_enemy_each", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 5, Name = "trap_enemy_is_no_control", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 6, Name = "trap_enemy_is_damage_motion", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 7, Name = "trap_damage_reaction", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 8, Name = "trap_damage_is_reaction", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 9, Name = "trap_btlobj_set_sheet", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 10, Name = "trap_attack_new", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 11, Name = "trap_attack_set_radius", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 12, Name = "trap_attack_set_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 13, Name = "trap_attack_free", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 14, Name = "trap_attack_is_hit", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 15, Name = "trap_damage_exec_reaction", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 16, Name = "trap_damage_is_exec_reaction", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 17, Name = "trap_attack_strike", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 18, Name = "trap_attack_is_strike", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 19, Name = "trap_attack_set_line", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 20, Name = "trap_magic_start_thread", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 21, Name = "trap_teamwork_alloc", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 22, Name = "trap_attack_set_obj_pax", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 23, Name = "trap_btlobj_target", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 24, Name = "trap_attack_get_owner", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 25, Name = "trap_attack_get_param_id", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 26, Name = "trap_attack_exec_reflect", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 27, Name = "trap_enemy_exec_reflect", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 2, TrapIndex = 28, Name = "trap_attack_refresh", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 29, Name = "trap_attack_is_hit_bg", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 30, Name = "trap_attack_set_pax", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 31, Name = "trap_attack_dup", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 32, Name = "trap_damage_blow_up", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 33, Name = "trap_damage_blow_speed", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 34, Name = "trap_attack_get_type", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 35, Name = "trap_damage_attack_type", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 36, Name = "trap_enemy_add_damage", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 37, Name = "trap_attack_set_team", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 38, Name = "trap_attack_set_hit_callback", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 39, Name = "trap_attack_is_reflect", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 40, Name = "trap_attack_is_hit_wall", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 41, Name = "trap_attack_is_hit_floor", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 42, Name = "trap_attack_hit_bg_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 43, Name = "trap_attack_get_reflect_vector", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 44, Name = "trap_attack_reflecter", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 45, Name = "trap_damage_attack_param_id", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 46, Name = "trap_damage_damage", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 47, Name = "trap_limit_motion_start", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 48, Name = "trap_limit_player", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 49, Name = "trap_limit_friend", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 50, Name = "trap_limit_camera_start", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 51, Name = "trap_attack_set_rc", Flags = 0x00000005 }, - new BdxTrap { TableIndex = 2, TrapIndex = 52, Name = "trap_attack_rc_receiver", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 53, Name = "trap_enemy_last_dead", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 2, TrapIndex = 54, Name = "trap_limit_start_thread", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 55, Name = "trap_limit_light", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 56, Name = "trap_btlobj_lockon_target", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 57, Name = "trap_limit_effect_start", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 58, Name = "trap_limit_effect_start_pos", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 59, Name = "trap_limit_effect_start_bind", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 60, Name = "trap_limit_time", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 61, Name = "trap_attack_set_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 62, Name = "trap_attack_set_time", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 63, Name = "trap_limit_reference", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 64, Name = "trap_damage_orig_reaction", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 65, Name = "trap_enemy_count_damager", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 2, TrapIndex = 66, Name = "trap_attack_get_reflect_count", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 67, Name = "trap_attack_new_combo_group", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 2, TrapIndex = 68, Name = "trap_magic_set_cost", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 69, Name = "trap_magic_can_add_cost", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 70, Name = "trap_damage_parts", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 71, Name = "trap_attack_set_hitmark_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 72, Name = "trap_damage_is_cure", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 73, Name = "trap_bonuslevel_up", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 74, Name = "trap_attack_set_reflect_callback", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 75, Name = "trap_summon_is_tink_exist", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 2, TrapIndex = 76, Name = "trap_enemy_set_karma_limit", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 77, Name = "trap_vacuum_create", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 78, Name = "trap_vacuum_destroy", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 79, Name = "trap_vacuum_set_ignore_type", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 80, Name = "trap_vacuum_set_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 81, Name = "trap_vacuum_set_speed", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 2, TrapIndex = 82, Name = "trap_vacuum_set_rot_speed", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 83, Name = "trap_vacuum_set_near_range", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 84, Name = "trap_vacuum_set_dist_rate", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 85, Name = "trap_damage_element", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 86, Name = "trap_damage_get_hitback", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 87, Name = "trap_enemy_exec_damage_large", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 2, TrapIndex = 88, Name = "trap_enemy_get_attacker", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 89, Name = "trap_limit_reset_special_command", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 90, Name = "trap_limit_close_gauge", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 91, Name = "trap_damage_get_reaction_type", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 92, Name = "trap_damage_is_finish", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 93, Name = "trap_damage_is_normal", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 94, Name = "trap_attack_set_system_pax", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 95, Name = "trap_btlobj_dup_sheet", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 2, TrapIndex = 96, Name = "trap_attack_is_valid", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 2, TrapIndex = 97, Name = "trap_enemy_set_attacker", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 2, Name = "trap_event_is_exec", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 3, Name = "trap_mission_complete", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 4, Name = "trap_mission_information", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 5, Name = "trap_mission_set_count", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 6, Name = "trap_mission_increment_count", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 7, Name = "trap_mission_restart_timer", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 8, Name = "trap_mission_set_gauge", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 9, Name = "trap_mission_add_gauge", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 10, Name = "trap_mission_set_gauge_ratio", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 11, Name = "trap_mission_failed", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 12, Name = "trap_mission_get_gauge_ratio", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 13, Name = "trap_mission_pause_timer", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 14, Name = "trap_mission_activate2d", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 15, Name = "trap_mission_deactivate2d", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 16, Name = "trap_mission_dead_boss", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 17, Name = "trap_mission_set_timer_param", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 4, TrapIndex = 18, Name = "trap_mission_set_count_param", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 4, TrapIndex = 19, Name = "trap_mission_set_gauge_param", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 4, TrapIndex = 20, Name = "trap_mission_decrement_count", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 21, Name = "trap_mission_is_activate2d", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 22, Name = "trap_mission_exit", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 23, Name = "trap_mission_reset_pause_mode", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 24, Name = "trap_mission_cancel_pause_timer", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 25, Name = "trap_mission_start_combo_counter", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 26, Name = "trap_mission_get_timer", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 27, Name = "trap_mission_stop_combo_counter", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 28, Name = "trap_mission_get_gauge_warning_ratio", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 29, Name = "trap_mission_get_count", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 30, Name = "trap_mission_get_max_combo_counter", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 31, Name = "trap_mission_get_combo_counter", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 32, Name = "trap_mission_return", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 33, Name = "trap_mission_add_combo_counter", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 34, Name = "trap_mission_is_gauge_warning", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 35, Name = "trap_score_type", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 36, Name = "trap_score_score", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 37, Name = "trap_score_update", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 4, TrapIndex = 38, Name = "trap_score_get", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 39, Name = "trap_mission_set_watch", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 40, Name = "trap_mission_get_timer_second", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 41, Name = "trap_mission_add_count", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 42, Name = "trap_struggle_increment", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 43, Name = "trap_mission_set_max_combo_counter", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 44, Name = "trap_mission_disable_count", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 45, Name = "trap_mission_disable_watch", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 46, Name = "trap_mission_set_warning_se", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 47, Name = "trap_mission_warning_timer", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 48, Name = "trap_mission_set_count_figure_num", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 4, TrapIndex = 49, Name = "trap_mission_disable_timer", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 50, Name = "trap_mission_warning_count", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 51, Name = "trap_mission_set_combo_counter_param", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 4, TrapIndex = 52, Name = "trap_mission_warning_combo_counter", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 4, TrapIndex = 53, Name = "trap_mission_set_combo_counter_warning_se", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 54, Name = "trap_mission_lock", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 55, Name = "trap_mission_is_lock", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 56, Name = "trap_event_continue_control_off", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 4, TrapIndex = 57, Name = "trap_mission_warning_gauge", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 4, TrapIndex = 58, Name = "trap_mission_reset_warning_count", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 0, Name = "trap_get_start_rtn_action", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 1, Name = "trap_set_path_way", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 2, Name = "trap_reverse_path_way", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 3, Name = "trap_get_path_dir", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 4, Name = "trap_end_rtn_action", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 5, Name = "trap_get_rtn_action", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 6, Name = "trap_get_rtn_action_dir", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 7, Name = "trap_is_rtn_change_dir", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 8, Name = "trap_create_active_path", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 9, Name = "trap_get_path_dir_from_obj", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 5, TrapIndex = 10, Name = "trap_forward_path_current_pointer", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 11, Name = "trap_is_end_rtn_action", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 12, Name = "trap_reset_active_path", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 13, Name = "trap_set_path_target_point", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 14, Name = "trap_get_path_point_pos", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 15, Name = "trap_clear_active_path", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 16, Name = "trap_reset_leave_way", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 17, Name = "trap_check_rtn_option_flag", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 18, Name = "trap_reset_path_current_pointer", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 19, Name = "trap_get_path_current_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 20, Name = "trap_get_path_current_dir", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 21, Name = "trap_get_path_first_point_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 22, Name = "trap_get_path_last_point_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 23, Name = "trap_set_path_by_id", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 24, Name = "trap_set_path_by_group", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 25, Name = "trap_get_path_dir_r", Flags = 0x40000003 }, - new BdxTrap { TableIndex = 5, TrapIndex = 26, Name = "trap_set_rtn_partner", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 27, Name = "trap_set_rtn_option_flag", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 28, Name = "trap_eh22_path_move_next", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 29, Name = "trap_eh22_path_move_before", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 30, Name = "trap_eh22_path_is_moving", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 31, Name = "trap_eh22_path_get_point", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 5, TrapIndex = 32, Name = "trap_eh22_path_play", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 5, TrapIndex = 33, Name = "trap_set_rtn_time_param", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 5, TrapIndex = 34, Name = "trap_get_obj_head_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 0, Name = "trap_camera_shake", Flags = 0x00000007 }, - new BdxTrap { TableIndex = 6, TrapIndex = 1, Name = "trap_prize_appear", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 2, Name = "trap_player_get_form", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 3, Name = "trap_target_searcher_init", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 4, Name = "trap_target_searcher_reset", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 5, Name = "trap_target_seracher_search", Flags = 0x00000007 }, - new BdxTrap { TableIndex = 6, TrapIndex = 6, Name = "trap_obj_stop", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 7, Name = "trap_obj_restart", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 8, Name = "trap_target_searcher_add", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 9, Name = "trap_target_dist", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 10, Name = "trap_obj_is_hit_attack", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 11, Name = "trap_target_searcher_search_obj", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 12, Name = "trap_target_searcher_get_old", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 13, Name = "trap_friend_force_warp", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 14, Name = "trap_friend_get", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 15, Name = "trap_friend_set_warp_level", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 16, Name = "trap_target_clear", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 17, Name = "trap_lockon_show", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 18, Name = "trap_lockon_hide", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 19, Name = "trap_status_peterpan_prize_drain_start", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 20, Name = "trap_status_peterpan_prize_drain_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 21, Name = "trap_target_searcher_add_target", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 22, Name = "trap_target_searcher_get_target_num", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 23, Name = "trap_obj_near_parts", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 24, Name = "trap_obj_get_bg_press", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 25, Name = "trap_obj_tt_ball_blow", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 26, Name = "trap_obj_limit_hover", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 27, Name = "trap_player_dice", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 28, Name = "trap_dice_set_spec", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 29, Name = "trap_player_card", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 30, Name = "trap_card_set_spec", Flags = 0x00000007 }, - new BdxTrap { TableIndex = 6, TrapIndex = 31, Name = "trap_limit_aladdin_prize_drain", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 6, TrapIndex = 32, Name = "trap_skateboard_ride", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 33, Name = "trap_skateboard_trick", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 34, Name = "trap_skateboard_trick_motion_push", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 35, Name = "trap_obj_attach_camera", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 36, Name = "trap_obj_detach_camera", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 37, Name = "trap_obj_is_attach_camera", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 38, Name = "trap_obj_limit_mulan_idle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 39, Name = "trap_skateboard_ride_edge", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 40, Name = "trap_obj_limit_peterpan_idle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 41, Name = "trap_skateboard_edge_jump", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 6, TrapIndex = 42, Name = "trap_obj_hop_direct", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 43, Name = "trap_command_limit_trinity_commbo_start", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 44, Name = "trap_obj_limit_riku_idle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 45, Name = "trap_obj_hide_shadow", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 46, Name = "trap_obj_rc_stop_all", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 47, Name = "trap_obj_stop_end_all", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 48, Name = "trap_skateboardscore_add_count", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 49, Name = "trap_obj_is_stop", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 50, Name = "trap_obj_stop_start", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 51, Name = "trap_bghit_check_line", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 52, Name = "trap_bghit_get_normal", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 53, Name = "trap_bghit_is_hit", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 54, Name = "trap_bghit_get_cross_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 55, Name = "trap_bghit_get_kind", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 56, Name = "trap_target_set_group", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 57, Name = "trap_target_get_group", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 58, Name = "trap_obj_act_child_push", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 59, Name = "trap_xemnas_get_obj", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 60, Name = "trap_obj_set_stealth_color", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 6, TrapIndex = 61, Name = "trap_obj_is_hook", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 62, Name = "trap_obj_carpet_obj_idle", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 63, Name = "trap_obj_is_damage_motion", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 64, Name = "trap_obj_show_shadow", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 65, Name = "trap_obj_set_scissoring", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 66, Name = "trap_obj_clear_hitback", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 67, Name = "trap_obj_party_attack", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 68, Name = "trap_strike_raid_calc_xyzrot", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 69, Name = "trap_larxene_dead", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 6, TrapIndex = 70, Name = "trap_obj_play_se_loop", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 6, TrapIndex = 71, Name = "trap_obj_fadeout_se", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 0, Name = "trap_enemy_stop_all_start", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 1, Name = "trap_enemy_stop_all_end", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 2, Name = "trap_attack_hit_mark_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 3, Name = "trap_flare_init", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 4, Name = "trap_flare_new", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 5, Name = "trap_flare_free", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 6, Name = "trap_flare_set_pos", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 7, Name = "trap_flare_set_radius", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 7, TrapIndex = 8, Name = "trap_flare_set_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 9, Name = "trap_flare_set_target", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 10, Name = "trap_flare_get_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 11, Name = "trap_flare_is_empty", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 12, Name = "trap_limit_aladdin_exclamation_mark_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 13, Name = "trap_magic_calc_speed", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 7, TrapIndex = 14, Name = "trap_attack_set_reaction_offset", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 15, Name = "trap_friend_get_target_size", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 16, Name = "trap_friend_get_current_action", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 17, Name = "trap_friend_set_script_status", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 18, Name = "trap_friend_get_main_status", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 19, Name = "trap_friend_update_target", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 21, Name = "trap_obj_limit_hover_set_spec", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 24, Name = "trap_friend_enable_system_wishdir", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 25, Name = "trap_friend_disable_system_wishdir", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 26, Name = "trap_friend_call", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 27, Name = "trap_limit_start_command", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 28, Name = "trap_trinity_shot_init", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 29, Name = "trap_trinity_shot_start", Flags = 0x40000004 }, - new BdxTrap { TableIndex = 7, TrapIndex = 30, Name = "trap_trinity_shot_ensure", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 31, Name = "trap_trinity_shot_set_effect_id", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 32, Name = "trap_vacuum_set_effective_range", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 33, Name = "trap_enemy_summon_entry", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 7, TrapIndex = 34, Name = "trap_attack_set_rc_owner", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 7, TrapIndex = 35, Name = "trap_summon_is_exec", Flags = 0x40000000 }, - new BdxTrap { TableIndex = 7, TrapIndex = 36, Name = "trap_limit_reset_hit_counter", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 8, TrapIndex = 0, Name = "trap_obj_target_radius", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 8, TrapIndex = 1, Name = "trap_player_push_ability_button", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 8, TrapIndex = 2, Name = "trap_obj_set_xyzrot", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 8, TrapIndex = 3, Name = "trap_special_last_xemnus_laser_start", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 8, TrapIndex = 4, Name = "trap_special_last_xemnus_laser_attack", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 8, TrapIndex = 5, Name = "trap_special_last_xemnus_laser_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 8, TrapIndex = 6, Name = "trap_special_last_xemnus_laser_optimize", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 8, TrapIndex = 7, Name = "trap_special_last_xemnus_laser_optimize_end", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 8, TrapIndex = 8, Name = "trap_camera_apply_inverse_pos", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 0, Name = "trap_empty_func", Flags = 0x00000000 }, - new BdxTrap { TableIndex = 10, TrapIndex = 1, Name = "trap_stitch_set_screen_position", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 2, Name = "trap_stitch_get_screen_position", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 3, Name = "trap_friend_start_limit", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 4, Name = "trap_friend_end_limit", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 5, Name = "trap_chickenlittle_get_shoot_target", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 6, Name = "trap_obj_set_special_command", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 7, Name = "trap_obj_reset_special_command", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 8, Name = "trap_friend_get_target_last_position", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 9, Name = "trap_genie_change_form", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 10, Name = "trap_genie_get_limit_command", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 11, Name = "trap_obj_set_stop_timer", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 10, TrapIndex = 12, Name = "trap_stitch_effect_start", Flags = 0x40000008 }, - new BdxTrap { TableIndex = 10, TrapIndex = 13, Name = "trap_stitch_shot_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 14, Name = "trap_friend_set_target", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 10, TrapIndex = 15, Name = "trap_sysobj_motion_cont_push", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 10, TrapIndex = 16, Name = "trap_stitch_effect_kill", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 17, Name = "trap_sysobj_is_zako", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 18, Name = "trap_sysobj_is_boss", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 19, Name = "trap_sysobj_is_limit", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 20, Name = "trap_friend_follow_player", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 21, Name = "trap_friend_follow_enemy", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 22, Name = "trap_sysobj_is_blow", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 23, Name = "trap_enemy_is_attacked_from", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 24, Name = "tarp_friend_is_equiped_ability", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 25, Name = "trap_peterpan_receive_notify_player_target", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 26, Name = "trap_peterpan_accept_notify_player_target", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 27, Name = "trap_friend_enable_inertia", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 28, Name = "trap_friend_disable_inertia", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 29, Name = "trap_friend_use_item", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 10, TrapIndex = 30, Name = "trap_sysobj_is_finish_blow", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 31, Name = "trap_sysobj_is_summon", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 32, Name = "trap_stitch_move_request", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 33, Name = "trap_friend_get_player_attacker", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 34, Name = "trap_friend_remove_player_attacker", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 35, Name = "trap_friend_get_action_param", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 36, Name = "trap_friend_is_control", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 37, Name = "trap_friend_is_moveonly", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 38, Name = "trap_attack_is_finish", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 39, Name = "trap_friend_action_clear", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 40, Name = "trap_obj_is_motion_sync", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 41, Name = "trap_friend_start_leave", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 42, Name = "trap_friend_is_start_leave", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 43, Name = "trap_obj_set_use_mp", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 44, Name = "trap_obj_is_tornado", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 45, Name = "trap_sheet_get_drive_time", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 46, Name = "trap_friend_disable_follow_enemy", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 47, Name = "trap_friend_enable_follow_enemy", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 48, Name = "trap_friend_disable_follow_player", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 49, Name = "trap_friend_enable_follow_player", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 50, Name = "trap_sheet_get_mp", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 51, Name = "trap_chickenlittle_get_nearest_target", Flags = 0x00000004 }, - new BdxTrap { TableIndex = 10, TrapIndex = 52, Name = "trap_btlobj_is_reflect_motion", Flags = 0x40000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 53, Name = "trap_friend_add_watch_effect", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 54, Name = "trap_friend_is_effect_exist", Flags = 0x40000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 55, Name = "trap_target_searcher_set_check_hide_from_friend", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 56, Name = "trap_friend_invalidate_warp_point", Flags = 0x00000002 }, - new BdxTrap { TableIndex = 10, TrapIndex = 57, Name = "trap_friend_add_warp_point", Flags = 0x00000001 }, - new BdxTrap { TableIndex = 10, TrapIndex = 58, Name = "trap_friend_link_magic", Flags = 0x00000003 }, - new BdxTrap { TableIndex = 10, TrapIndex = 59, Name = "trap_chickenlittle_set_shoot_target", Flags = 0x00000002 }, - - #endregion - }; - } -} diff --git a/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj b/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj index f517bf759..6e7675814 100644 --- a/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj +++ b/OpenKh.Command.Bdxio/OpenKh.Command.Bdxio.csproj @@ -9,12 +9,13 @@ - + + diff --git a/OpenKh.Command.Bdxio/Utils/BdxAsciiModel.cs b/OpenKh.Command.Bdxio/Utils/BdxAsciiModel.cs deleted file mode 100644 index 6802fae90..000000000 --- a/OpenKh.Command.Bdxio/Utils/BdxAsciiModel.cs +++ /dev/null @@ -1,42 +0,0 @@ -namespace OpenKh.Command.Bdxio.Utils -{ - public class BdxAsciiModel - { - public bool IsEmpty { get; set; } - - public string? Header { get; set; } - public string? Script { get; set; } - - public int FirstHeaderLineNumber { get; set; } - public int FirstScriptLineNumber { get; set; } - - public string GetLineNumberRetainedScriptBody() => new string('\n', FirstScriptLineNumber) + Script; - - public static BdxAsciiModel ParseText(string body) - { - var lines = body.Replace("\r\n", "\n").Split('\n'); - int separator1 = Array.IndexOf(lines, "---"); - if (separator1 < 0) - { - throw new InvalidDataException(); - } - int separator2 = Array.IndexOf(lines, "---", separator1 + 1); - if (separator2 < 0) - { - throw new InvalidDataException(); - } - - var header = string.Join("\n", lines, separator1 + 1, separator2 - separator1 - 1); - var script = string.Join("\n", lines.Skip(separator2 + 1)); - - return new BdxAsciiModel - { - FirstHeaderLineNumber = 1 + separator1, - FirstScriptLineNumber = 1 + separator2, - IsEmpty = header.Trim().Length == 0 && script.Trim().Length == 0, - Header = header, - Script = script, - }; - } - } -} diff --git a/OpenKh.Command.Bdxio/Utils/BdxDecoder.cs b/OpenKh.Command.Bdxio/Utils/BdxDecoder.cs deleted file mode 100644 index 7782ab888..000000000 --- a/OpenKh.Command.Bdxio/Utils/BdxDecoder.cs +++ /dev/null @@ -1,804 +0,0 @@ -using OpenKh.Command.Bdxio.Models; -using OpenKh.Common; -using System.Globalization; -using System.Text; -using Xe.BinaryMapper; -using YamlDotNet.Serialization; -using static OpenKh.Command.Bdxio.Models.BdxHeader; - -namespace OpenKh.Command.Bdxio.Utils -{ - public class BdxDecoder - { - public BdxHeader Header { get; } - - public IDictionary AddressAnnotated { get; } - public IDictionary AddressLabels { get; } - public IDictionary AddressComments { get; } - public IDictionary AddressCodeComments { get; } - public IDictionary WorkLabels { get; } - public string TopComment { get; set; } - - public BdxDecoder(Stream read, int[]? codeLabels = null, bool codeRevealer = false, bool codeRevealerLabeling = false) - { - if (read.Length == 0) - { - Header = new BdxHeader { IsEmpty = true }; - AddressAnnotated = new Dictionary(); - AddressLabels = new Dictionary(); - AddressComments = new Dictionary(); - AddressCodeComments = new Dictionary(); - WorkLabels = new Dictionary(); - TopComment = ""; - return; - } - - Header = BinaryMapping.ReadObject(read); - - var triggers = new List(); - while (true) - { - var one = BinaryMapping.ReadObject(read); - if (one.Key == 0 && one.Addr == 0) - { - break; - } - triggers.Add(one); - } - - int firstAddr = (12 + 8 * (triggers.Count + 1)) / 2; - - var addrAnnotated = new SortedDictionary(); - var addrLabels = new SortedDictionary(); - var addrComments = new SortedDictionary(); - var addrCodeComments = new SortedDictionary(); - var workLabels = new SortedDictionary(); - var topComment = ""; - - void AddAddr(int entry, string label) - { - if (addrLabels.TryGetValue(entry, out string? existing) && existing != null) - { - addrLabels[entry] = string.Join(",", existing.Split(',').Append(label).Distinct()); - } - else - { - addrLabels[entry] = label; - } - } - - void Walk(int entry) - { - var topAddr = entry; - - while (true) - { - var needClearance = false; - - if (addrAnnotated.TryGetValue(entry, out var annotHere)) - { - if (annotHere is IndeterminateContent) - { - needClearance = true; - } - else - { - return; - } - } - - var newPos = 16 + 2 * entry; - if (read.Length <= newPos) - { - // bug? - break; - } - - read.Position = newPos; - var codeWord = read.ReadUInt16(); - - var desc = BdxInstructionDescs.FindByCode(codeWord); - if (desc == null) - { - break; - } - - var code = new CodeContent(desc); - - if (needClearance) - { - var overrideDetected = false; - int x = 1; - for (; x < desc.CodeSize; x++) - { - if (true - && addrAnnotated.TryGetValue(entry + x, out var codePartHit) - && codePartHit is not IndeterminateContent - ) - { - overrideDetected = true; - break; - } - - if (addrLabels.TryGetValue(entry + x, out var codePartLabels)) - { - overrideDetected = true; - break; - } - } - if (overrideDetected) - { - addrComments[entry] = $"Although L{entry + x} is in IndeterminateContent area (L{entry} to L{entry + desc.CodeSize - 1}), there is a collision with text data? Maybe codeRevealer problem. Possibly need more better bottom text pool detection."; - break; - } - } - - addrAnnotated[entry] = code; - - if (desc.NeverReturn || desc.IsGosubRet) - { - break; - } - - if (desc.Args != null) - { - var parsedArgs = new List(); - - foreach (var arg in desc.Args) - { - var parsedArg = new ParsedArg(); - - int argValue; - if (arg.Type == BdxInstructionDesc.ArgType.Imm16) - { - argValue = read.ReadInt16(); - } - else if (arg.Type == BdxInstructionDesc.ArgType.Imm32) - { - argValue = read.ReadInt32(); - } - else if (arg.Type == BdxInstructionDesc.ArgType.Float32) - { - argValue = read.ReadInt32(); - parsedArg.PreferFloat32 = true; - } - else - { - argValue = new BdxInstruction(codeWord).Ssub; - } - - - if (arg.IsRelative) - { - var nextEntry = (ushort)(entry + (desc.CodeSize) + argValue); - - parsedArg.Value = nextEntry; - parsedArg.AddrRef = nextEntry; - - if (desc.IsJump || desc.IsGosub) - { - var newLabel = $"L{nextEntry}"; - parsedArg.Label = newLabel; - AddAddr(nextEntry, newLabel); - Walk(nextEntry); - } - } - else if (arg.AiPos) - { - parsedArg.Value = (ushort)argValue; - - var newLabel = $"L{(ushort)argValue}"; - parsedArg.Label = newLabel; - AddAddr((ushort)argValue, newLabel); - } - else if (arg.WorkPos) - { - parsedArg.Value = (ushort)argValue; - - var newLabel = $"W{(ushort)argValue}"; - parsedArg.Label = newLabel; - workLabels[(ushort)argValue] = newLabel; - } - else - { - parsedArg.Value = argValue; - } - - parsedArgs.Add(parsedArg); - } - - if (desc.IsSyscall) - { - var tableIdx = new BdxInstruction(codeWord).Ssub; - var trapIdx = parsedArgs[1].Value; - var trap = BdxTraps.GetTraps() - .FirstOrDefault(it => it.TableIndex == tableIdx && it.TrapIndex == trapIdx); - addrCodeComments[entry] = (trap != null) - ? $"{trap.Name} ({trap.Flags & 65535} in, {(((trap.Flags & 0x40000000) != 0) ? 1 : 0)} out)" - : "?"; - } - - code.Args = parsedArgs.ToArray(); - } - - for (int x = 1; x < desc.CodeSize; x++) - { - addrAnnotated[entry + x] = CodeArg.Value; - } - - if (desc.IsJump && !desc.IsJumpConditional) - { - break; - } - - entry += desc.CodeSize; - } - } - - var knownItems = BdxTrigger.GetKnownItems() - .ToDictionary( - one => one.Key, - one => one.Label - ); - - string GetTriggerLabelFor(int key) - { - if (knownItems.TryGetValue(key, out string? label) && label != null) - { - return label; - } - else - { - return $"TR{key}"; - } - } - - foreach (var trigger in triggers) - { - AddAddr(trigger.Addr, GetTriggerLabelFor(trigger.Key)); - Walk(trigger.Addr); - } - - foreach (var codeLabel in codeLabels ?? new int[0]) - { - AddAddr(codeLabel, $"L{codeLabel}"); - Walk(codeLabel); - } - - Header.Triggers = triggers - .Select( - it => new Trigger - { - Addr = it.Addr.ToString(), - Key = it.Key, - } - ) - .ToArray(); - - var revealed = new List(); - - for (int addr = firstAddr; addr < read.Length / 2 - 8; addr++) - { - if (addrAnnotated.ContainsKey(addr)) - { - continue; - } - - read.Position = 16 + 2 * addr; - var dataWord = read.ReadUInt16(); - - addrAnnotated[addr] = new IndeterminateContent(dataWord); - - if (addrAnnotated.TryGetValue(addr - 1, out var prevAnnot) && prevAnnot is not IndeterminateContent prevWord) - { - AddAddr(addr, $"D{addr}"); - } - } - - var firstTextPoolAddr = FindFirstTextPoolAddr(addrAnnotated); - - { - // resolve strings - - var dataList = addrAnnotated - .Where(pair => pair.Value is IndeterminateContent) - .ToList(); - - while (dataList.Any()) - { - var baseAddr = dataList[0].Key; - - if (firstTextPoolAddr <= baseAddr) - { - int numContinuous = 1; - while (true) - { - if (true - && numContinuous < dataList.Count - && dataList[numContinuous].Value is IndeterminateContent - && dataList[numContinuous].Key == baseAddr + numContinuous - ) - { - numContinuous += 1; - continue; - } - else - { - break; - } - } - - if (numContinuous >= 2) - { - var bytes = new byte[2 * numContinuous]; - for (int x = 0; x < numContinuous; x++) - { - var word = ((IndeterminateContent)dataList[x].Value).Word; - bytes[2 * x + 0] = (byte)(word); - bytes[2 * x + 1] = (byte)(word >> 8); - } - - static (int numValidWords, IAnnotation? annot) TryToDecode(byte[] bytes) - { - for (int x = 0; x < bytes.Length; x++) - { - if (bytes[x] == 0) - { - if (x < 1) - { - // The length is not enough to decide this is a text. - break; - } - if ((x & 1) == 0) - { - x++; - } - - // This is a valid text - return ( - (x + 1) / 2, - new TextContent(Encoding.ASCII.GetString(bytes, 0, x + 1)) - ); - } - if (IsValidChar(bytes[x])) - { - // A valid char - continue; - } - else - { - // An invalid char - break; - } - } - - // No valid text found - return (0, null); - } - - var (numValidWords, annot) = TryToDecode(bytes); - - if (1 <= numValidWords && annot != null) - { - dataList.RemoveRange(0, numValidWords); - for (int x = 0; x < numValidWords; x++) - { - if (x == 0) - { - AddAddr(baseAddr + x, $"TXT{baseAddr + x}"); - addrAnnotated[baseAddr + x] = annot; - } - else - { - addrAnnotated[baseAddr + x] = TextPart.Value; - } - } - continue; - } - } - } - - dataList.RemoveAt(0); - } - } - - if (codeRevealer) - { - while (true) - { - var firstIndeterminate = addrAnnotated - .FirstOrDefault( - pair => true - && pair.Key < firstTextPoolAddr - && pair.Value is IndeterminateContent - ); - - // KeyValuePair is struct (value) and it cannot be null. While Value is null-able instead. - - if (firstIndeterminate.Value == null) - { - break; - } - - var addr = firstIndeterminate.Key; - - addrAnnotated.Remove(firstIndeterminate.Key); - - AddAddr(addr, $"L{addr}"); - Walk(addr); - - revealed.Add(addr); - } - } - - if (codeRevealerLabeling) - { - foreach (var pair in addrAnnotated.ToArray()) - { - if (pair.Value is CodeContent content) - { - if (content.Desc.CodeRevealerLabeling - && content.Args.Length == 1 - && content.Args[0] is ParsedArg arg - && arg.Label == null - && arg.Value != 0 - ) - { - if (revealed.Contains(arg.Value)) - { - content.Args[0].Label = $"L{arg.Value}"; - content.Args[0].AddrRef = arg.Value; - } - } - } - } - } - - { - foreach (var pair in addrAnnotated.ToArray()) - { - if (pair.Value is IndeterminateContent indeterminate) - { - addrAnnotated[pair.Key] = new WordContent(indeterminate.Word); - } - } - } - - topComment = string.Join("\n" - , $"{nameof(codeLabels)}: {((codeLabels != null) ? string.Join(" ", codeLabels.Select(it => $"-l {it}")) : "")}" - , $"{nameof(codeRevealer)}: {((revealed != null) ? string.Join(" ", revealed.Select(it => $"-l {it}")) : "")}" - ); - - AddressAnnotated = addrAnnotated; - AddressLabels = addrLabels; - AddressComments = addrComments; - AddressCodeComments = addrCodeComments; - WorkLabels = workLabels; - TopComment = topComment; - } - - private static int FindFirstTextPoolAddr(IDictionary addrAnnotated) - { - var firstTextPoolAddr = int.MaxValue; - - var dataPairs = addrAnnotated - .OrderByDescending(pair => pair.Key) - .Where(pair => pair.Value is IndeterminateContent); - - if (dataPairs.Any()) - { - foreach (var pair in dataPairs) - { - var content = (IndeterminateContent)pair.Value; - var bytes = new byte[] { (byte)(content.Word), (byte)(content.Word >> 8) }; - if (true - && (bytes[0] == 0 || IsValidChar(bytes[0])) - && (bytes[1] == 0 || IsValidChar(bytes[1])) - ) - { - firstTextPoolAddr = pair.Key; - continue; - } - else - { - break; - } - } - } - - return firstTextPoolAddr; - } - - private static bool IsValidChar(byte v) - { - var chr = (char)v; - return 32 <= chr && chr <= 126; - } - - public interface IAnnotation - { - - } - - public class ParsedArg - { - public string? Label { get; set; } - public int Value { get; set; } - public int? AddrRef { get; set; } - public bool PreferFloat32 { get; set; } - } - - public sealed class CodeContent : IAnnotation - { - public CodeContent(BdxInstructionDesc desc) - { - Desc = desc; - } - - public BdxInstructionDesc Desc { get; set; } - public ParsedArg[] Args { get; set; } = new ParsedArg[0]; - - public override string ToString() => Desc?.ToString() ?? "?"; - } - - public sealed class CodeArg : IAnnotation - { - private CodeArg() - { - - } - - public override string ToString() => $"CodeArg"; - - public static readonly CodeArg Value = new CodeArg(); - } - - public sealed class IndeterminateContent : IAnnotation - { - public ushort Word { get; set; } - - public IndeterminateContent(ushort word) - { - Word = word; - } - - public override string ToString() => $"Indeterminate"; - } - - public sealed class WordContent : IAnnotation - { - public ushort Word { get; set; } - - public WordContent(ushort word) - { - Word = word; - } - - public override string ToString() => $"Word"; - } - - public sealed class TextContent : IAnnotation - { - public string Text { get; set; } - - public TextContent(string text) - { - Text = text; - } - - public override string ToString() => $"Text: {Text}"; - } - - public sealed class TextPart : IAnnotation - { - private TextPart() - { - - } - - public override string ToString() => $"TextPart"; - - public static readonly TextPart Value = new TextPart(); - } - - public static class TextFormatter - { - private static ISerializer ser = new SerializerBuilder() - .Build(); - - public static string Format(BdxDecoder decoder) - { - if (decoder.Header.IsEmpty) - { - var empty = new StringWriter(); - empty.WriteLine("---"); - empty.WriteLine("---"); - return empty.ToString(); - } - - var writer = new StringWriter(); - - writer.WriteLine("---"); - writer.WriteLine( - ser - .Serialize( - new BdxHeader - { - Name = decoder.Header.Name, - WorkSize = decoder.Header.WorkSize, - StackSize = decoder.Header.StackSize, - TempSize = decoder.Header.TempSize, - Triggers = decoder.Header.Triggers - .Select( - it => - { - if (int.TryParse(it.Addr, out int addr)) - { - decoder.AddressLabels.TryGetValue(addr, out string? labels); - - return new Trigger - { - Key = it.Key, - Addr = ((labels != null) ? labels.Split(',')[0] : it.Addr), - }; - } - else - { - return new Trigger - { - Key = it.Key, - Addr = it.Addr, - }; - } - } - ) - .ToArray(), - } - ) - ); - writer.WriteLine("---"); - - { - if (!string.IsNullOrEmpty(decoder.TopComment)) - { - foreach (var comment in decoder.TopComment.Split('\n')) - { - writer.WriteLine($"; {comment}"); - } - } - } - - writer.WriteLine(" section .text"); - - foreach (var line in decoder.AddressAnnotated) - { - if (decoder.AddressComments.TryGetValue(line.Key, out var commentBody) && commentBody != null) - { - foreach (var comment in commentBody.Replace("\r\n", "\n").Split('\n')) - { - writer.WriteLine($" ; {comment}"); - } - } - - if (decoder.AddressLabels.TryGetValue(line.Key, out string? labels) && labels != null) - { - foreach (var label in labels.Split(',')) - { - writer.WriteLine($"{label}:"); - } - } - - if (line.Value is CodeContent code) - { - if (decoder.AddressCodeComments.TryGetValue(line.Key, out string? commentPart)) - { - commentPart = " ; " + commentPart; - } - - writer.WriteLine($" {code.Desc.Name} {string.Join(", ", code.Args.Select(FormatArg))}{commentPart}"); - } - else if (line.Value is WordContent data) - { - writer.WriteLine($" dw 0x{data.Word:X4}"); - } - else if (line.Value is TextContent text) - { - writer.WriteLine($" db {MakeDbBody(text.Text)}"); - } - } - - if (decoder.Header.WorkSize != 0) - { - writer.WriteLine(); - writer.WriteLine(" section .bss"); - if (decoder.WorkLabels.Any()) - { - int lastPos = 0; - foreach (var workLabel in decoder.WorkLabels) - { - var thisPos = workLabel.Key; - if (lastPos < thisPos) - { - writer.WriteLine($" resb {thisPos - lastPos}"); - lastPos = thisPos; - } - writer.WriteLine($"{workLabel.Value}:"); - } - { - var thisPos = decoder.Header.WorkSize; - if (lastPos < thisPos) - { - writer.WriteLine($" resb {thisPos - lastPos}"); - } - } - } - else - { - writer.WriteLine($" resb {decoder.Header.WorkSize}"); - } - } - - return writer.ToString(); - } - - private static string FormatArg(ParsedArg arg) - { - if (arg.Label != null) - { - return $"{arg.Label}"; //offset - } - else if (arg.PreferFloat32) - { - return BitConverter.ToSingle(BitConverter.GetBytes(arg.Value)).ToString("R", invariantNumberFormat); - } - else - { - return arg.Value.ToString(); - } - } - - private static string MakeDbBody(string text) - { - var bytes = Encoding.ASCII.GetBytes(text); - - bool IsPrint(byte b) => 0x20 <= b && b <= 0x7E && b != 0x27 && b != 0x5C; - - var tokens = new List(); - - for (int x = 0, cx = bytes.Length; x < cx;) - { - { - int from = x; - while (x < cx && IsPrint(bytes[x])) - { - x++; - } - if (from != x) - { - tokens.Add("'" + Encoding.ASCII.GetString(bytes, from, x - from) + "'"); - } - } - - { - int from = x; - while (x < cx && !IsPrint(bytes[x])) - { - x++; - } - if (from != x) - { - for (; from < x; from++) - { - tokens.Add(bytes[from].ToString()); - } - } - } - } - - return string.Join(",", tokens); - } - } - - private static readonly IFormatProvider invariantNumberFormat = CultureInfo.InvariantCulture.NumberFormat; - } -} diff --git a/OpenKh.Command.Bdxio/Utils/BdxEncoder.cs b/OpenKh.Command.Bdxio/Utils/BdxEncoder.cs deleted file mode 100644 index 3fe1c1445..000000000 --- a/OpenKh.Command.Bdxio/Utils/BdxEncoder.cs +++ /dev/null @@ -1,866 +0,0 @@ -using Antlr4.Runtime; -using Antlr4.Runtime.Tree; -using NLog; -using OpenKh.Command.Bdxio.Models; -using System.Globalization; -using System.Text; -using Xe.BinaryMapper; -using static BdxScriptParser; -using static OpenKh.Command.Bdxio.Models.BdxHeader; -using static OpenKh.Command.Bdxio.Models.BdxInstructionDesc; - -namespace OpenKh.Command.Bdxio.Utils -{ - public class BdxEncoder - { - public byte[] Content { get; } = new byte[0]; - - public BdxEncoder( - BdxHeader header, - string script, - string scriptName, - Func loadScript - ) - { - var logger = LogManager.GetLogger("BdxEncoder"); - - if (string.IsNullOrWhiteSpace(script) && header.IsEmpty) - { - return; - } - - var labels = new SortedDictionary(); - - var instructionDict = new (string, BdxInstructionDesc)[0] - .Concat( - BdxInstructionDescs.GetDescs() - .Select(desc => (desc.Name, desc)) - ) - .Concat( - BdxInstructionDescs.GetDescs() - .SelectMany( - desc => desc.OldNames - .Select( - oldName => (oldName, desc) - ) - ) - ) - .GroupBy(pair => pair.Item1) - .Select(it => it.First()) - .ToDictionary(it => it.Item1, it => it.Item2); - - Func ResolveInt32Factory( - Func stringToInt32, - Func, int> labelToInt32 - ) - { - return (ArgContext argContext, CompilePass pass) => - { - if (pass.First) - { - return 0; - } - else - { - if (argContext.numberdata() is NumberdataContext numberdata) - { - return stringToInt32(numberdata); - } - else if (argContext.id() is IdContext id) - { - var name = id.GetText(); - return labelToInt32(name, () => id.Start); - } - else - { - throw new InvalidDataException($"{FormatLineColumn(argContext.Start)} Unknown arg!"); - } - } - }; - } - - int LabelToInt32Converter(string name, Func getStart) - { - if (!labels.TryGetValue(name, out var any) || any == null) - { - throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} not found")); - } - - if (any is CodeLabel label) - { - return label.PC; - } - else if (any is ConstLabel constLabel) - { - return constLabel.Value; - } - else if (any is BssLabel bssLabel) - { - return bssLabel.ByteOffset; - } - - throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} cannot be used here")); - } - - float LabelToFloat32Converter(string name, Func getStart) - { - if (!labels.TryGetValue(name, out var any) || any == null) - { - throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} not found")); - } - - if (any is ConstLabel constLabel) - { - return constLabel.Value; - } - - throw new ArgumentException(FormatErrorMessage(getStart(), $"Label {name} cannot be used here")); - } - - Func ResolveFloat32Factory( - Func, float> labelToFloat32 - ) - { - return (ArgContext argContext, CompilePass pass) => - { - if (pass.First) - { - return 0; - } - else - { - if (argContext.numberdata() is NumberdataContext numberdata) - { - return float.Parse(numberdata.GetText(), invariantNumberFormat); - } - else if (argContext.id() is IdContext id) - { - var name = id.GetText(); - return labelToFloat32(name, () => id.Start); - } - else - { - throw new InvalidDataException($"{FormatLineColumn(argContext.Start)} Unknown arg!"); - } - } - }; - } - - var resolveInt32ForIntTrigger = ResolveInt32Factory( - stringToInt32: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), - labelToInt32: LabelToInt32Converter - ); - - var resolveInt32ForSsub = ResolveInt32Factory( - stringToInt32: Helper.AllowedRangeFactory( - factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), - min: -2048, - max: 4095 - ), - labelToInt32: LabelToInt32Converter - ); - - var resolveInt32ForRelativeImm16 = ResolveInt32Factory( - stringToInt32: Helper.AllowedRangeFactory( - factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), - min: short.MinValue, - max: short.MaxValue - ), - labelToInt32: LabelToInt32Converter - ); - - var resolveInt32ForValueImm16 = ResolveInt32Factory( - stringToInt32: Helper.AllowedRangeFactory( - factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), - min: short.MinValue, - max: ushort.MaxValue - ), - labelToInt32: LabelToInt32Converter - ); - - var resolveInt32ForRelativeImm32 = ResolveInt32Factory( - stringToInt32: Helper.AllowedRangeFactory( - factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false), - min: int.MinValue, - max: int.MaxValue - ), - labelToInt32: LabelToInt32Converter - ); - - var resolveInt32ForValueImm32 = ResolveInt32Factory( - stringToInt32: Helper.AllowedRangeFactory( - factory: Helper.NumberdataContextToInt32ConverterFactory(allowFloat: true), - min: int.MinValue, - max: int.MaxValue - ), - labelToInt32: LabelToInt32Converter - ); - - var resolveFloat32ForValueFloat32 = ResolveFloat32Factory( - labelToFloat32: LabelToFloat32Converter - ); - - int lastBssBytes = 0; - - var compilePasses = new CompilePass[] { - new CompilePass(Index: 0, First: true, Last: false), // resolve code placement and labels offset - new CompilePass(Index: 1, First: false, Last: false), // resolve equ (const) - new CompilePass(Index: 2, First: false, Last: true), // compile - }; - - foreach (var pass in compilePasses) - { - var words = new MemoryStream(); - - void WordAlignment() - { - if ((words.Length & 1) != 0) - { - words.WriteByte(0); - } - } - - BinaryMapping.WriteObject(words, header); - - if (!pass.First) - { - if (header.WorkSize < lastBssBytes) - { - logger.Warn($"WorkSize {header.WorkSize} is extended to {lastBssBytes}"); - - words.Position = 16; - words.Write(BitConverter.GetBytes(lastBssBytes)); - words.Seek(0, SeekOrigin.End); - } - } - - foreach (var intTrigger in header.Triggers - .Select( - item => - { - return new IntTrigger - { - Key = item.Key, - Addr = resolveInt32ForIntTrigger(Helper.ParseAsArg(item.Addr), pass), - }; - } - ) - ) - { - BinaryMapping.WriteObject(words, intTrigger); - } - - BinaryMapping.WriteObject(words, new IntTrigger { Addr = 0, Key = 0, }); - - var codeSeg = new CodeSegment(words); - var bssSeg = new BssSegment(); - ISegment segment = codeSeg; - - void AddLabel(string labelName, ILabel labelValue, Func getTokenPos) - { - if (pass.First) - { - if (!labels.TryAdd(labelName, labelValue)) - { - throw new InvalidDataException($"{FormatLineColumn(getTokenPos().Start)} Redefine of label {labelName}"); - } - } - else - { - labels[labelName] = labelValue; - } - } - - void WalkStatements(ProgContext root) - { - foreach (var st in root.statement()) - { - //var pc = (int)(words.Length / 2 - 8); - - if (st.section() is SectionContext section) - { - var id = section.section_id(); - var sectionName = id.GetText(); - if (sectionName == ".text") - { - segment = codeSeg; - } - else if (sectionName == ".bss") - { - segment = bssSeg; - } - else - { - throw new InvalidDataException($"{FormatLineColumn(id.Start)} Unknown section name {sectionName}"); - } - continue; - } - else if (st.equ() is EquContext equ) - { - var intValue = Helper.NumberdataContextToInt32ConverterFactory(allowFloat: false).Invoke(equ.numberdata()); - - AddLabel( - equ.name.GetText(), - new ConstLabel { Value = intValue, }, - () => equ.name - ); - } - - if (st.label() is LabelContext label) - { - WordAlignment(); - var id = label.id(); - AddLabel( - id.GetText(), - segment.GetLabel(), - () => id - ); - } - - if (st.order() is OrderContext order) - { - if (order.db() is DbContext db) - { - foreach (var byteData in db.bytedata()) - { - if (byteData.numberdata() is NumberdataContext numberData) - { - segment.WriteByte(Helper.GetAsByte(numberData)); - } - else if (byteData.StringData() is ITerminalNode stringData) - { - var text = stringData.GetText(); - var bytes = Encoding.ASCII.GetBytes(text.Substring(1, text.Length - 2)); - segment.Write(bytes); - } - else if (byteData.id() is IdContext id) - { - var value = pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start); - segment.WriteByte((byte)value); - } - else - { - throw new InvalidDataException($"{FormatLineColumn(byteData.Start)} Unknown byte data!"); - } - } - } - else if (order.resb() is ResbContext resb) - { - if (resb.numberdata() is NumberdataContext numberdata) - { - var numBytes = Helper.GetAsInt32(numberdata); - segment.AllocateBytes(numBytes); - } - else if (resb.id() is IdContext id) - { - var numBytes = LimitInt32Range( - pass.First - ? 0 - : LabelToInt32Converter(id.GetText(), () => id.Start), - min: int.MinValue, - max: int.MaxValue, - getStart: () => id.Start - ); - segment.AllocateBytes(numBytes); - } - } - else if (order.dw() is DwContext dw) - { - WordAlignment(); - foreach (var wordData in dw.worddata()) - { - if (wordData.numberdata() is NumberdataContext numberdata) - { - segment.Write(BitConverter.GetBytes(Helper.GetAsUInt16(numberdata))); - } - else if (wordData.id() is IdContext id) - { - var value = (ushort)LimitInt32Range(pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start), min: -127, max: 255, () => id.Start); - segment.Write(BitConverter.GetBytes(value)); - } - else - { - throw new InvalidDataException($"{FormatLineColumn(wordData.Start)} Unknown word data!"); - } - } - } - else if (order.resw() is ReswContext resw) - { - if (resw.numberdata() is NumberdataContext numberdata) - { - var numBytes = 2 * Helper.GetAsInt32(numberdata); - segment.AllocateBytes(numBytes); - } - else if (resw.id() is IdContext id) - { - var numBytes = 2 * LimitInt32Range(pass.First ? 0 : LabelToInt32Converter(id.GetText(), () => id.Start), min: int.MinValue, max: int.MaxValue, () => id.Start); - segment.AllocateBytes(numBytes); - } - } - else if (order.instruction() is InstructionContext instr) - { - var name = instr.id().GetText(); - if (!instructionDict.TryGetValue(name, out var desc)) - { - throw new InvalidDataException($"{FormatLineColumn(instr.Start)} Unknown instruction '{name}'!"); - } - - if (segment is not CodeSegment thisSegment) - { - throw new InvalidDataException($"{FormatLineColumn(instr.Start)} This must be placed at `section .text`!"); - } - - var pc = thisSegment.PC; - - var word = new BdxInstruction(desc.Code); - - var args = desc.Args ?? new Arg[0]; - - var instrArgs = instr.arg(); - - for (int x = 0; x < args.Length; x++) - { - if (args[x].Type == ArgType.Ssub) - { - word.Ssub = (ushort)resolveInt32ForSsub(instrArgs[x], pass); - } - } - - segment.Write(BitConverter.GetBytes(word.Code)); - - for (int x = 0; x < args.Length; x++) - { - if (args[x].Type == ArgType.Imm16) - { - if (args[x].IsRelative) - { - var baseOffset = -pc - desc.CodeSize; - - segment.Write(BitConverter.GetBytes((ushort)(baseOffset + resolveInt32ForRelativeImm16(instrArgs[x], pass)))); - } - else - { - segment.Write(BitConverter.GetBytes((ushort)(resolveInt32ForValueImm16(instrArgs[x], pass)))); - } - } - else if (args[x].Type == ArgType.Imm32) - { - if (args[x].IsRelative) - { - var baseOffset = -pc - desc.CodeSize; - - segment.Write(BitConverter.GetBytes((uint)(baseOffset + resolveInt32ForRelativeImm32(instrArgs[x], pass)))); - } - else - { - segment.Write(BitConverter.GetBytes((uint)(resolveInt32ForValueImm32(instrArgs[x], pass)))); - } - } - else if (args[x].Type == ArgType.Float32) - { - { - segment.Write(BitConverter.GetBytes((float)(resolveFloat32ForValueFloat32(instrArgs[x], pass)))); - } - } - } - } - else if (order.include() is IncludeContext include) - { - var scriptName = include.FileName().GetText().Trim('"'); - var subScript = loadScript(scriptName); - - var stream = FromString(subScript, scriptName); - var lexer = new BdxScriptLexer(stream); - var tokens = new CommonTokenStream(lexer); - var parser = new BdxScriptParser(tokens); - var subRoot = parser.prog(); - - WalkStatements(subRoot); - } - else - { - //throw new InvalidDataException("Unknown statement!"); - // maybe NL - } - } - } - } - - { - var stream = FromString(script, scriptName); - var lexer = new BdxScriptLexer(stream); - var tokens = new CommonTokenStream(lexer); - var parser = new BdxScriptParser(tokens); - var root = parser.prog(); - - WalkStatements(root); - } - - WordAlignment(); - - lastBssBytes = bssSeg.numBytes; - - if (pass.Last) - { - Content = words.ToArray(); - } - } - } - - private int LimitInt32Range(int value, int min, int max, Func getStart) - { - if (min <= value && value <= max) - { - return value; - } - - throw new ArgumentOutOfRangeException(FormatErrorMessage(getStart(), $"Integer must be in range of from {min} to {max}")); - } - - private static ICharStream FromString(string script, string sourceName) - { - var stream = CharStreams.fromString(script); - if (stream is CodePointCharStream charStream) - { - charStream.name = sourceName; - } - return stream; - } - - private static string FormatLineColumn(IToken start) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column}"; - - private static string FormatErrorMessage(IToken start, IFormattable text) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column} {text}"; - private static string FormatErrorMessage(IToken start, string text) => $"{start.InputStream.SourceName} line {start.Line}:{start.Column} {text}"; - - private static class Helper - { - internal static int GetAsInt32(NumberdataContext numberData) - { - var decimalNumber = numberData.decimalnumber; - if (decimalNumber != null) - { - var body = decimalNumber.Text; - - if (uint.TryParse(body, out uint uintVal)) - { - return (int)uintVal; - } - else if (int.TryParse(body, out int intVal)) - { - return intVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); - } - } - else if (numberData.hexnumber is IToken hexNumber) - { - var body = hexNumber.Text; - - if (uint.TryParse(body, NumberStyles.HexNumber, null, out uint uintVal)) - { - return (int)uintVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); - } - } - else if (numberData.floatnumber is IToken) - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); - } - else - { - throw new InvalidDataException($"{FormatLineColumn(numberData.Start)} Unknown number data!"); - } - } - - internal static ArgContext ParseAsArg(string addr) - { - var stream = CharStreams.fromString(addr); - var lexer = new BdxScriptLexer(stream); - var tokens = new CommonTokenStream(lexer); - var parser = new BdxScriptParser(tokens); - - return parser.arg(); - } - - internal static Func NumberdataContextToInt32OrFloat32ConverterFactory() - { - return (NumberdataContext numberData) => - { - var decimalNumber = numberData.decimalnumber; - if (decimalNumber != null) - { - var body = decimalNumber.Text; - - if (uint.TryParse(body, out uint uintVal)) - { - return (int)uintVal; - } - else if (int.TryParse(body, out int intVal)) - { - return intVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); - } - } - else if (numberData.hexnumber is IToken hexNumber) - { - var body = hexNumber.Text; - - if (uint.TryParse(body, NumberStyles.HexNumber, null, out uint uintVal)) - { - return (int)uintVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int32 here")); - } - } - else if (numberData.floatnumber is IToken floatnumber) - { - var body = floatnumber.Text; - - if (float.TryParse(body, NumberStyles.HexNumber, invariantNumberFormat, out float floatVal)) - { - return (float)floatVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be float32 here")); - } - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); - } - }; - } - - internal static Func NumberdataContextToInt32ConverterFactory(bool allowFloat) - { - var func = NumberdataContextToInt32OrFloat32ConverterFactory(); - - return (NumberdataContext numberData) => - { - var value = func(numberData); - - if (value is int intValue) - { - return intValue; - } - else if (value is float floatValue) - { - if (!allowFloat) - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); - } - return BitConverter.ToInt32(BitConverter.GetBytes(floatValue)); - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Numeric converter bug")); - } - }; - } - - internal static Func AllowedRangeFactory(Func factory, int min, int max) - { - return (NumberdataContext numberData) => - { - var value = factory(numberData); - if (min <= value && value <= max) - { - return value; - } - - throw new ArgumentOutOfRangeException(FormatErrorMessage(numberData.Start, $"The value must be in range of from {min} to {max}!")); - }; - } - - internal static byte GetAsByte(NumberdataContext numberData) - { - var decimalNumber = numberData.decimalnumber; - if (decimalNumber != null) - { - var body = decimalNumber.Text; - - if (byte.TryParse(body, out byte byteVal)) - { - return byteVal; - } - else if (sbyte.TryParse(body, out sbyte sbyteVal)) - { - return (byte)sbyteVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be byte here")); - } - } - else if (numberData.hexnumber is IToken hexNumber) - { - var body = hexNumber.Text; - - if (byte.TryParse(body, NumberStyles.HexNumber, null, out byte byteVal)) - { - return byteVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be byte here")); - } - } - else if (numberData.floatnumber is IToken) - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); - } - } - - internal static ushort GetAsUInt16(NumberdataContext numberData) - { - var decimalNumber = numberData.decimalnumber; - if (decimalNumber != null) - { - var body = decimalNumber.Text; - - if (ushort.TryParse(body, out ushort ushortVal)) - { - return ushortVal; - } - else if (short.TryParse(body, out short shortVal)) - { - return (byte)shortVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int16 here")); - } - } - else if (numberData.hexnumber is IToken hexNumber) - { - var body = hexNumber.Text; - - if (ushort.TryParse(body, NumberStyles.HexNumber, null, out ushort ushortVal)) - { - return ushortVal; - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Must be int16 here")); - } - } - else if (numberData.floatnumber is IToken) - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Float number is not allowed here")); - } - else - { - throw new InvalidDataException(FormatErrorMessage(numberData.Start, "Unknown number data!")); - } - } - } - - private interface ISegment - { - void Write(byte[] bytes); - void WriteByte(byte value); - void AllocateBytes(int num); - ILabel GetLabel(); - } - - private class BssSegment : ISegment - { - public int numBytes = 0; - - public void AllocateBytes(int num) - { - numBytes += num; - } - - public ILabel GetLabel() - { - return new BssLabel { ByteOffset = numBytes }; - } - - public void Write(byte[] bytes) - { - throw new InvalidOperationException("`section .bss` cannot place static data. Use `resb 4` or `resw 2` or such."); - } - - public void WriteByte(byte value) - { - throw new InvalidOperationException("`section .bss` cannot place static data. Use `resb 4` or `resw 2` or such."); - } - } - - private class CodeSegment : ISegment - { - private readonly MemoryStream _words; - - public CodeSegment(MemoryStream words) - { - _words = words; - } - - public int PC => (int)(_words.Length / 2 - 8); - - public void AllocateBytes(int num) - { - _words.Write(new byte[num]); - } - - public ILabel GetLabel() - { - return new CodeLabel { PC = PC }; - } - - public void Write(byte[] buffer) - { - _words.Write(buffer); - } - - public void WriteByte(byte value) - { - _words.WriteByte(value); - } - } - - private interface ILabel - { - - } - - private class CodeLabel : ILabel - { - public int PC { get; set; } - } - - private class BssLabel : ILabel - { - public int ByteOffset { get; set; } - } - - private class ConstLabel : ILabel - { - public int Value { get; set; } - } - - private record CompilePass( - int Index, - bool First, - bool Last - ); - - private static readonly IFormatProvider invariantNumberFormat = CultureInfo.InvariantCulture.NumberFormat; - } -} diff --git a/OpenKh.Command.Bdxio/Utils/BdxTrigger.cs b/OpenKh.Command.Bdxio/Utils/BdxTrigger.cs deleted file mode 100644 index fc8747bc6..000000000 --- a/OpenKh.Command.Bdxio/Utils/BdxTrigger.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace OpenKh.Command.Bdxio.Utils -{ - public record BdxTrigger(string Label, int Key) - { - public static BdxTrigger[] GetKnownItems() => new BdxTrigger[] - { - new BdxTrigger("OBJ_INIT", 0x0), - new BdxTrigger("OBJ_MAIN", 0x1), - new BdxTrigger("UNIT_INIT", 0x2), - new BdxTrigger("UNIT_MAIN", 0x3), - new BdxTrigger("UNIT_FINALIZER", 0x4), - new BdxTrigger("TEST", 0x5), - new BdxTrigger("OBJ_DAMAGE", 0x6), - new BdxTrigger("NEWGAME", 0x7), - new BdxTrigger("PROGRESS_CALLBACK", 0x8), - new BdxTrigger("GAMEOVER", 0x9), - new BdxTrigger("SIGNAL_CALLBACK", 0xA), - new BdxTrigger("REACTION_CALLBACK", 0xB), - new BdxTrigger("SECOND_SET_CALLBACK", 0xC), - new BdxTrigger("OBJ_FALL", 0xD), - new BdxTrigger("OBJ_LAND", 0xE), - new BdxTrigger("OBJ_ATTACK", 0xF), - new BdxTrigger("OBJ_ANMATR_EFFECT", 0x10), - new BdxTrigger("OBJ_ANMATR_CALLBACK", 0x11), - new BdxTrigger("OBJ_CHANGE_ACT", 0x12), - new BdxTrigger("OBJ_REFLECT", 0x13), - new BdxTrigger("MAGIC_START", 0x14), - new BdxTrigger("MAGIC_SHOT", 0x15), - new BdxTrigger("LIMIT_START", 0x16), - new BdxTrigger("LIMIT_CALL", 0x17), - new BdxTrigger("LIMIT_ANMATR_EFFECT", 0x18), - new BdxTrigger("LIMIT_ANMATR_CALLBACK", 0x19), - new BdxTrigger("LIMIT_ATTACK", 0x1A), - new BdxTrigger("OBJ_IK_ADJUST_CALLBACK", 0x1B), - }; - } -} From f52520cba02ecc57bfb71474bb17b020f18d9295 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 11 Sep 2026 15:28:54 +0200 Subject: [PATCH 074/175] [Mod Manager] - Ensure MOD and BUILD paths exist. --- .../Services/ModService.cs | 7 +--- .../Services/PathService.cs | 38 +++++++++++++------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 9661a8809..a1a9ffca8 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -517,13 +517,8 @@ public static async Task Build(IEnumerable modsList, Config curr var _fetchPKGMapPath = Path.Combine(_fetchBuildPath, "patch-package-map.txt"); - if (!Directory.Exists(_fetchBuildPath)) - Directory.CreateDirectory(_fetchBuildPath); - - else + if (Directory.Exists(_fetchBuildPath)) { - Console.WriteLine("Clearing build path before building..."); - Directory.Delete(_fetchBuildPath, true); Directory.CreateDirectory(_fetchBuildPath); } diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index aa591169c..d9db60442 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -16,14 +16,21 @@ public static string ResolveMod(Config input, bool barePath = false) var _fetchTargetGame = input.Frontend.TargetGame; var _fetchGameTarget = barePath ? "" : Config.GameShorthand[_fetchTargetGame]; - if (String.IsNullOrEmpty(_fetchConfigPath)) - return Path.Combine(AppContext.BaseDirectory, "mods", _fetchGameTarget); + var _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, "mods", _fetchGameTarget); - else if (!Path.IsPathFullyQualified(_fetchConfigPath)) - return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); + if (!String.IsNullOrEmpty(_fetchConfigPath)) + { + if (!Path.IsPathFullyQualified(_fetchConfigPath)) + _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); - else - return Path.Combine(_fetchConfigPath, _fetchGameTarget); + else + _fetchTargetPath = Path.Combine(_fetchConfigPath, _fetchGameTarget); + } + + if (!Directory.Exists(_fetchTargetPath)) + Directory.CreateDirectory(_fetchTargetPath); + + return _fetchTargetPath; } public static string ResolveBuild(Config input, bool barePath = false) @@ -32,14 +39,21 @@ public static string ResolveBuild(Config input, bool barePath = false) var _fetchTargetGame = input.Frontend.TargetGame; var _fetchGameTarget = barePath ? "" : Config.GameShorthand[_fetchTargetGame]; - if (String.IsNullOrEmpty(_fetchConfigPath)) - return Path.Combine(AppContext.BaseDirectory, "build", _fetchGameTarget); + var _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, "build", _fetchGameTarget); - else if (!Path.IsPathFullyQualified(_fetchConfigPath)) - return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); + if (!String.IsNullOrEmpty(_fetchConfigPath)) + { + if (!Path.IsPathFullyQualified(_fetchConfigPath)) + _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); - else - return Path.Combine(_fetchConfigPath, _fetchGameTarget); + else + _fetchTargetPath = Path.Combine(_fetchConfigPath, _fetchGameTarget); + } + + if (!Directory.Exists(_fetchTargetPath)) + Directory.CreateDirectory(_fetchTargetPath); + + return _fetchTargetPath; } public static string? ResolveData(Config input, bool barePath = false) From 0ba67b9c9b142ca92e964ad9c4b47f3cc2556e7f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 11 Sep 2026 15:30:32 +0200 Subject: [PATCH 075/175] [Mod Manager] - Ensure DATA path also exists. --- .../Services/PathService.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index d9db60442..b1fa99e6a 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -62,14 +62,24 @@ public static string ResolveBuild(Config input, bool barePath = false) var _fetchTargetGame = input.Frontend.TargetGame; var _fetchGameTarget = barePath ? "" : Config.GameShorthand[_fetchTargetGame]; - if (String.IsNullOrEmpty(_fetchConfigPath)) - return null; + string? _fetchTargetPath = null; + + if (!String.IsNullOrEmpty(_fetchConfigPath)) + { + if (!Path.IsPathFullyQualified(_fetchConfigPath)) + _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); - else if (!Path.IsPathFullyQualified(_fetchConfigPath)) - return Path.Combine(AppContext.BaseDirectory, _fetchConfigPath, _fetchGameTarget); + else + _fetchTargetPath = Path.Combine(_fetchConfigPath, _fetchGameTarget); + + if (!Directory.Exists(_fetchTargetPath)) + Directory.CreateDirectory(_fetchTargetPath); + + return _fetchTargetPath; + } else - return Path.Combine(_fetchConfigPath, _fetchGameTarget); + return null; } public static string ResolvePath1525(Config input) From 7186a86e94f1c54852c329aa71283ab92ca1d32c Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 11 Sep 2026 17:47:20 +0200 Subject: [PATCH 076/175] [Mod Manager] - Remove dialogs to make them a service. --- .../Dialogs/BackendInstallDialog.axaml | 16 ------- .../Dialogs/BackendInstallDialog.axaml.cs | 16 ------- .../Dialogs/BuildModError.axaml | 16 ------- .../Dialogs/BuildModError.axaml.cs | 16 ------- .../Dialogs/InstallModDialog.axaml | 18 -------- .../Dialogs/InstallModDialog.axaml.cs | 46 ------------------- .../Dialogs/InvalidYamlError.axaml | 16 ------- .../Dialogs/InvalidYamlError.axaml.cs | 17 ------- .../Dialogs/LaunchArgsDialog.axaml | 15 ------ .../Dialogs/LaunchArgsDialog.axaml.cs | 43 ----------------- .../Dialogs/PanaceaInstallDialog.axaml | 16 ------- .../Dialogs/PanaceaInstallDialog.axaml.cs | 15 ------ .../Dialogs/RemoveModDialog.axaml | 17 ------- .../Dialogs/RemoveModDialog.axaml.cs | 19 -------- 14 files changed, 286 deletions(-) delete mode 100644 OpenKh.Tools.ModManager/Dialogs/BackendInstallDialog.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/BackendInstallDialog.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/BuildModError.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/BuildModError.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/InstallModDialog.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/InvalidYamlError.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/InvalidYamlError.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/LaunchArgsDialog.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/LaunchArgsDialog.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/PanaceaInstallDialog.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/PanaceaInstallDialog.axaml.cs delete mode 100644 OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml delete mode 100644 OpenKh.Tools.ModManager/Dialogs/RemoveModDialog.axaml.cs diff --git a/OpenKh.Tools.ModManager/Dialogs/BackendInstallDialog.axaml b/OpenKh.Tools.ModManager/Dialogs/BackendInstallDialog.axaml deleted file mode 100644 index eb402b42f..000000000 --- a/OpenKh.Tools.ModManager/Dialogs/BackendInstallDialog.axaml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index aac565d0c..d8b7e66d1 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -54,7 +54,7 @@ private async Task ExtractTask(List extractList, Config currentConfi ExtractStartButton.IsVisible = true; ExtractStopButton.IsVisible = false; - WeakReferenceMessenger.Default.Send(new UnblockNextRequest()); + WeakReferenceMessenger.Default.Send(new UnblockAllRequest()); if (_fetchResult != 0x00) { @@ -100,14 +100,14 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE var _fetchExtractList = new List() { - ExtractKH1.IsChecked.HasValue ? ExtractKH1.IsChecked.Value : false, - ExtractKH2.IsChecked.HasValue ? ExtractKH2.IsChecked.Value : false, - ExtractCOM.IsChecked.HasValue ? ExtractCOM.IsChecked.Value : false, - ExtractBBS.IsChecked.HasValue ? ExtractBBS.IsChecked.Value : false, - ExtractDDD.IsChecked.HasValue ? ExtractDDD.IsChecked.Value : false + ExtractKH1.IsChecked ?? false, + ExtractKH2.IsChecked ?? false, + ExtractCOM.IsChecked ?? false, + ExtractBBS.IsChecked ?? false, + ExtractDDD.IsChecked ?? false }; - WeakReferenceMessenger.Default.Send(new BlockNextRequest()); + WeakReferenceMessenger.Default.Send(new BlockAllRequest()); ExtractTask(_fetchExtractList, _fetchConfig); } } From 225c79a2e3160cdfb02d62016fba8f7b5ebf6198 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 13 Sep 2026 06:29:49 +0200 Subject: [PATCH 101/175] [Mod Manager] - Hopefully fix building for everything PC this time. --- OpenKh.Patcher/Metadata.cs | 2 +- OpenKh.Patcher/PatcherProcessor.cs | 352 ++++++++---------- .../Services/ModService.cs | 4 +- 3 files changed, 148 insertions(+), 210 deletions(-) diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index 3b3bb7923..36aa2a539 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -47,7 +47,7 @@ public static Metadata Read(string fileName) // A single mod should not be able to patch a file more than once. // Ignore any and all duplicates that target the same file. - var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => x.Name); + var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => new { x.Name, x.Platform }); _fetchSerial.Assets = _fetchAssetUnique.ToList(); return _fetchSerial; diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 21227aa95..0cb5ca82e 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -108,51 +108,60 @@ public async Task Patch( Func reportProgress = null, bool Tests = false) { - var _fetchCollectionMods = new Dictionary { }; var _isExtraction = extractDataPath != null; - var _fetchGameId = GameShorthand[targetGame]; + var _protectPackageMap = new object(); + + var _fetchCollectionMods = new Dictionary { }; + var _fetchFileDictionary = new ConcurrentDictionary(); + if (collectionOptionalEnabledMods != null) _fetchCollectionMods = collectionOptionalEnabledMods; try { + // A lot of this class relies on this existing. I am in no mood to rewrite that much code yet. var _fetchContext = new Context(modMetadata, extractDataPath, modFilesPath, buildOutputPath); + // If the targeted platform is PS2 and an extraction doesn't exist, throw an exception. + // We cannot do extractionless on PS2. if (!_isExtraction && targetPlatform == 0x00) - throw new InvalidDataException("PS2 Building requires extraction!"); - - if (modMetadata.Assets == null) - throw new Exception("No assets found."); + throw new InvalidDataException("The PlayStation 2 PLatform requires an extraction to be present for builds."); + // If the mod has a game declared and it doesn't match the current game, don't build it. if (modMetadata.Game != null && modMetadata.Game.ToLower() != _fetchGameId) return; + // If the mod is part of a collection and the collection doesn't support the current game, don't build it. if (modMetadata.IsCollection && !modMetadata.CollectionGames.Contains(_fetchGameId)) return; - var _protectPackageMap = new object(); - - var _currentAssetIndex = 0; - var _totalAssetCount = modMetadata.Assets.Count; - - var _fetchFileDictionary = new ConcurrentDictionary(); + // If the mod does not have any assets, throw an error. This should not happen. + if (modMetadata.Assets == null) + throw new NullReferenceException("The mod doesn't contain any assets."); - var _parallelOptions = new ParallelOptions() { CancellationToken = cancelToken.HasValue ? cancelToken.Value : new CancellationToken() }; + var _assetProcessCount = 0; + var _assetTotalCount = modMetadata.Assets.Count; + // If the build is not extraction-based: if (!_isExtraction) { + // Fetch a list of all header files for the targeted game. var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); var _fetchHeaderFiles = Directory.GetFiles(_fetchDataPath).Where(x => x.Contains(_fetchGameId) && x.EndsWith(".hed")); - await Parallel.ForEachAsync(_fetchHeaderFiles.AsParallel(), _parallelOptions, async (_fetchHeader, _fetchCancelToken) => + // Start a parallel for every header file. + await Parallel.ForEachAsync(_fetchHeaderFiles.AsParallel(), new ParallelOptions() { CancellationToken = cancelToken ?? new CancellationToken() }, async (_fetchHeader, _fetchCancelToken) => { + // Load up the header file and read it as a HED class. using (var _fetchHeaderStream = File.OpenRead(_fetchHeader)) { var _fetchPackageName = Path.GetFileNameWithoutExtension(_fetchHeader); var _fetchHeaderChildren = Hed.Read(_fetchHeaderStream); + // Record every hash with its corresponding package name for speedier handling. + // This eliminates the need to inquire every package for every single file. foreach (var _fetchEntry in _fetchHeaderChildren) { _fetchFileDictionary.TryAdd(Convert.ToHexString(_fetchEntry.MD5), _fetchPackageName); @@ -164,289 +173,218 @@ await Parallel.ForEachAsync(_fetchHeaderFiles.AsParallel(), _parallelOptions, as }); } - await Parallel.ForEachAsync(modMetadata.Assets.AsParallel(), _parallelOptions, async (_fetchAsset, _fetchCancelToken) => + // Start a parallel for all the assets: + await Parallel.ForEachAsync(modMetadata.Assets.AsParallel(), new ParallelOptions() { CancellationToken = cancelToken ?? new CancellationToken() }, async (_fetchAsset, _fetchCancelToken) => { + // If the asset has a game declared and it doesn't match the current game, skip it. if (_fetchAsset.Game != null && _fetchAsset.Game != _fetchGameId) return; - if ((targetPlatform != 0x00 && _fetchAsset.Platform == "ps2") || (targetPlatform == 0x00 && _fetchAsset.Platform == "pc")) - return; - + // I don't know what this does but it is here for compat. if (_fetchAsset.CollectionOptional == true && (!_fetchCollectionMods.ContainsKey(_fetchAsset.Name) || !_fetchCollectionMods[_fetchAsset.Name])) return; - var _fetchFileNames = new List() { _fetchAsset.Name }; + // Fetch all the target names to be used. + var _fetchFileNames = new List(); var _fetchMultiNames = _fetchAsset.Multi != null ? _fetchAsset.Multi.Select(x => x.Name) .Where(x => !string.IsNullOrEmpty(x)) : null; + _fetchFileNames.Add(_fetchAsset.Name); + if (_fetchMultiNames != null) _fetchFileNames.AddRange(_fetchMultiNames); + // If the platform for the asset isn't declared, default it to be both. + _fetchAsset.Platform = _fetchAsset.Platform != null ? _fetchAsset.Platform.ToLower() : "both"; + foreach (var _fetchName in _fetchFileNames) { - if (targetPlatform != 0x00 && _fetchName.Contains(".a.fm")) - continue; + // Check discrepancy for JP-specific files. + var _doesRegionMatch = (!isJapanese && !_fetchName.Contains(".a.jp") && !_fetchName.Contains("/jp/")) || (isJapanese && (_fetchName.Contains(".a.jp") || _fetchName.Contains("/jp/"))); + var _isAssetExclusivePS2 = _fetchName.Contains(".a.fm") || _fetchName.Contains("/fm/"); - var _fetchAssetPath = _isExtraction ? Path.Combine(extractDataPath, _fetchName) : null; + // Declare whether the asset is patchable or not. + var _isAssetPatchable = _fetchAsset.Method != "copy" && _fetchAsset.Method != "imd"; - var _fetchPackage = _fetchAsset.Package != null ? _fetchAsset.Package : $"{_fetchGameId}_first"; + // This is used for package map purposes. + var _fetchPackageMap = ""; - _fetchAsset.Platform = _fetchAsset.Platform != null ? _fetchAsset.Platform.ToLower() : "both"; + // Construct the many paths that will be needed for the asset. - if (_fetchAsset.Required) - if (!_isExtraction && !File.Exists(_fetchAssetPath)) - continue; + /* + * -> _fetchOriginalPath = Path to the target file from the game data. Constructed and used only if extraction exists. + * -> _fetchVariablePath = Path to the target file as it exists in the build folder. Only used if the file is patchable. [Type isn't COPY or IMD] + * -> _fetchExternalPath = Path to the source file as it exists in the mod's source folder. Used if the file is neither patchable nor internal. + * -> _fetchInternalPath = Path to the source file from the game data. Used if the file is not patchable but is internal. Constructed and used only if extraction exists. + */ + + var _fetchOriginalPath = _isExtraction ? Path.Combine(extractDataPath, _fetchName) : null; + var _fetchVariablePath = _isAssetPatchable && _fetchAsset.Source[0].Type != "internal" ? Path.Combine(buildOutputPath, _fetchName) : null; + var _fetchExternalPath = !_isAssetPatchable && (_fetchAsset.Source[0].Type != "internal" && _fetchAsset.Source[0].Name != null) ? Path.Combine(modFilesPath, _fetchAsset.Source[0].Name) : null; + var _fetchInternalPath = !_isAssetPatchable && (_fetchAsset.Source[0].Type == "internal" && _fetchAsset.Source[0].Name != null && _isExtraction) ? Path.Combine(extractDataPath, _fetchAsset.Source[0].Name) : null; + // Fetch the parent directory of the file. var _fetchFileParent = _fetchName.Split([Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar]).FirstOrDefault(); - if (Path.IsPathRooted(_fetchName) && !Path.GetPathRoot(_fetchName).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal)) - throw new PatcherException(modMetadata, new InvalidOperationException("Root Copy Detected!")); + // Construct the target path and directory for the file. - var _fetchOutputPath = Path.Combine(buildOutputPath, _fetchName); - var _fetchOutputDir = Path.GetDirectoryName(_fetchOutputPath); + var _fetchTargetPath = Path.Combine(buildOutputPath, _fetchName); + var _fetchTargetDir = Path.GetDirectoryName(_fetchTargetPath); - var _fetchPackageMap = ""; + // If declared, fetch the package name for the asset. + // This is used in hard-patching and for package mapping. + var _fetchPackage = _fetchAsset.Package != null ? _fetchAsset.Package : $"{_fetchGameId}_first"; - var _isFileRemastered = _fetchName.Contains("remastered"); - var _isFileRAW = _fetchName.Contains("raw"); + // If the asset is declared REQUIRED, the game is extracted, yet the original file doesn't exist: Skip it. + if (_fetchAsset.Required && !_isExtraction && !File.Exists(_fetchOriginalPath)) + continue; - var _isFilePC = _isFileRemastered || _isFileRAW; + // Fetch certain information about the file. + + var _isFileRAW = _fetchFileParent == "raw"; + var _isFileRemastered = _fetchFileParent == "remastered"; var _isFileSpecial = _fetchFileParent == "scripts" || _fetchFileParent == "dll"; + var _isFilePC = _isFileRemastered || _isFileRAW; + + // If the file is special, commit it to the package map as such. if (_isFileSpecial) _fetchPackageMap = "special/" + _fetchName; else { - if (targetPlatform == 0x00 && (_fetchAsset.Platform == "pc" || _isFilePC)) + // If the target platform is the PS2 and the file is PC-specific, skip it. + if (targetPlatform == 0x00 && _isFilePC && (_fetchFileParent == "dll" || _fetchAsset.Platform == "pc")) continue; + // If the target platform is not PS2: else if (targetPlatform != 0x00) { - if (_fetchAsset.Platform == "ps2") + // But the asset is, skip it. + if (_fetchAsset.Platform == "ps2" || _isAssetExclusivePS2) continue; + if (!_doesRegionMatch && (_isAssetPatchable || _fetchAsset.Source[0].Type == "internal")) + continue; + + // Otherwise, commit it to the package map. _fetchPackageMap = Path.Combine(_fetchPackage, _isFilePC ? "" : "original/", _fetchName); } } + // Lock the package map and commit the constructed string. if (packageMap != null && _fetchPackageMap.Length > 0) { lock (_protectPackageMap) packageMap[_fetchName.Replace("\\", "/")] = _fetchPackageMap; } - if (!Directory.Exists(_fetchOutputDir)) - Directory.CreateDirectory(_fetchOutputDir); - - var _multiExists = !_isExtraction; + // If the target directory does not exist, create it. + if (!Directory.Exists(_fetchTargetDir)) + Directory.CreateDirectory(_fetchTargetDir); - if (_fetchAsset.Multi == null) - _multiExists = false; - - else if (!_multiExists) + // If a built version of the file already exists, patch that one. + if (_fetchVariablePath != null && File.Exists(_fetchVariablePath)) { - foreach (var _multiEntry in _fetchAsset.Multi) - { - var _fetchMultiPath = Path.Combine(extractDataPath, _multiEntry.Name); - - if (File.Exists(_fetchMultiPath)) - _multiExists = true; - } + using (var _fileStream = File.Open(_fetchVariablePath, FileMode.OpenOrCreate)) + PatchFile(_fetchContext, _fetchAsset, _fileStream); } - var _fetchBuildPath = _fetchAsset.Method != "copy" && _fetchAsset.Method != "imd" && _fetchAsset.Source[0].Type != "internal" ? Path.Combine(buildOutputPath, _fetchName) : null; - var _fetchSourcePath = (_fetchAsset.Method == "copy" || _fetchAsset.Method == "imd") && (_fetchAsset.Source[0].Type != "internal" && _fetchAsset.Source[0].Name != null) ? Path.Combine(modFilesPath, _fetchAsset.Source[0].Name) : null; - - var _shouldCopySource = (_fetchAsset.Method != "copy" && _fetchAsset.Method != "imd" && ((_fetchAssetPath != null && File.Exists(_fetchAssetPath)) || _multiExists || !_isExtraction)) || - ((_fetchAsset.Method == "copy" || _fetchAsset.Method == "imd") && (_fetchSourcePath != null && File.Exists(_fetchSourcePath))) || - Tests; - - var _doesRegionMatch = (!isJapanese && !_fetchName.Contains(".a.jp") && !_fetchName.Contains("/jp/")) || (isJapanese && (_fetchName.Contains(".a.jp") || _fetchName.Contains("/jp/"))); - - if (_shouldCopySource || _fetchAsset.Source[0].Type == "internal") + // Otherwise; + else { byte[] _fetchAssetData = null; - if (_fetchBuildPath != null && File.Exists(_fetchBuildPath)) - { - using (var _fileStream = File.Open(_fetchBuildPath, FileMode.OpenOrCreate)) - PatchFile(_fetchContext, _fetchAsset, _fileStream); - } - - else + // If the asset is not patchable, meaning it has to be copied from somewhere: + if (!_isAssetPatchable) { - if (_fetchSourcePath != null) - { - if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchSourcePath)) - File.Copy(_fetchSourcePath, _fetchOutputPath, true); - } - - else if (_fetchAssetPath != null) - { - if (!File.Exists(_fetchOutputPath) && File.Exists(_fetchAssetPath)) - File.Copy(_fetchAssetPath, _fetchOutputPath, true); - } + // If the file type is internal, the internal path is valid, and the file exists: Copy it to target. + if (_fetchAsset.Source[0].Type == "internal" && _fetchInternalPath != null && File.Exists(_fetchInternalPath)) + File.Copy(_fetchInternalPath, _fetchTargetPath, true); - else - { - var _fetchTarget = _fetchAsset.Method != "copy" && _fetchAsset.Method != "imd" ? _fetchName : _fetchAsset.Source[0].Name; - - var _fetchParent = ""; - var _fetchChild = ""; - - if (_isFileRAW) - _fetchTarget = _fetchTarget.Replace("raw/", ""); - - if (_isFileRemastered) - { - var _fetchMatch = Regex.Match(_fetchTarget, "[\\w,-]+\\.[a-zA-Z0-9]{2,4}"); - - var _fetchIndex = _fetchTarget.IndexOf(_fetchMatch.Value); - var _fetchEndPoint = _fetchIndex + _fetchMatch.Value.Length; - - var _fetchSubFirst = _fetchTarget.Substring(0, _fetchEndPoint); - var _fetchSubSecond = _fetchTarget.Substring(_fetchEndPoint, _fetchTarget.Length - _fetchEndPoint); - - _fetchParent = _fetchSubFirst.Replace("remastered/", ""); - _fetchChild = _fetchSubSecond.Trim('/'); - } - - var _fetchNameHash = Egs.Helpers.CreateMD5(_isFileRemastered ? _fetchParent : _fetchTarget); - var _fetchKeyExists = _fetchFileDictionary.ContainsKey(_fetchNameHash); + // If the file type is NOT internal, the external path is valid, and the file exists: Copy it to target. + else if (_fetchExternalPath != null && File.Exists(_fetchExternalPath)) + File.Copy(_fetchExternalPath, _fetchTargetPath, true); + } - if (_fetchKeyExists) - { - var _fetchFilePack = _fetchFileDictionary[_fetchNameHash]; + // If the asset is patchable, the original path is valid, and the file exists: Copy it to target. + else if (_isAssetPatchable && _fetchOriginalPath != null && File.Exists(_fetchOriginalPath)) + File.Copy(_fetchOriginalPath, _fetchTargetPath, true); - if (!String.IsNullOrEmpty(_fetchFilePack)) - { - var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); + // If after the above the target file STILL does not exists, try to fetch it from the game data. + if (!File.Exists(_fetchTargetPath)) + { + var _fetchTarget = _isAssetPatchable ? _fetchName : _fetchAsset.Source[0].Name; - var _fetchHeaderName = Path.Combine(_fetchDataPath, _fetchFilePack + ".hed"); - var _fetchPackageName = Path.Combine(_fetchDataPath, _fetchFilePack + ".pkg"); + var _fetchParent = ""; + var _fetchChild = ""; - using (var _fetchHeaderStream = File.OpenRead(_fetchHeaderName)) - { - var _fetchEntries = Hed.Read(_fetchHeaderStream); - var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Convert.ToHexString(x.MD5) == _fetchNameHash); + if (_isFileRAW) + _fetchTarget = _fetchTarget.Replace("raw/", ""); - using (var _fetchPackageStream = File.OpenRead(_fetchPackageName)) - { - var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); + if (_isFileRemastered) + { + var _fetchMatch = Regex.Match(_fetchTarget, "[\\w,-]+\\.[a-zA-Z0-9]{2,4}"); - if (_isFileRemastered) - _fetchTargetAsset.RemasteredAssetsDecompressedData.TryGetValue(_fetchChild, out _fetchAssetData); + var _fetchIndex = _fetchTarget.IndexOf(_fetchMatch.Value); + var _fetchEndPoint = _fetchIndex + _fetchMatch.Value.Length; - else - _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; + var _fetchSubFirst = _fetchTarget.Substring(0, _fetchEndPoint); + var _fetchSubSecond = _fetchTarget.Substring(_fetchEndPoint, _fetchTarget.Length - _fetchEndPoint); - if (_fetchAssetData != null) - File.WriteAllBytes(_fetchOutputPath, _fetchAssetData); - } - } - } - } + _fetchParent = _fetchSubFirst.Replace("remastered/", ""); + _fetchChild = _fetchSubSecond.Trim('/'); } - using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate)) - PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); - } - } - - else - { - var _shouldCheckData = ((_fetchAsset.Method != "copy" && _fetchAsset.Method != "imd") || _fetchAsset.Source[0].Type == "internal"); - - if (_fetchBuildPath != null && File.Exists(_fetchBuildPath)) - { - using (var _fileStream = File.Open(_fetchBuildPath, FileMode.OpenOrCreate)) - PatchFile(_fetchContext, _fetchAsset, _fileStream); - } - - else - { - byte[] _fetchAssetData = null; + var _fetchNameHash = Egs.Helpers.CreateMD5(_isFileRemastered ? _fetchParent : _fetchTarget); + var _fetchKeyExists = _fetchFileDictionary.ContainsKey(_fetchNameHash); - if (_fetchAssetPath != null && _shouldCheckData && _doesRegionMatch) + if (_fetchKeyExists) { - var _fetchTarget = _fetchAsset.Method != "copy" && _fetchAsset.Method != "imd" ? _fetchName : _fetchAsset.Source[0].Name; - - var _fetchParent = ""; - var _fetchChild = ""; + var _fetchFilePack = _fetchFileDictionary[_fetchNameHash]; - if (_isFileRAW) - _fetchTarget = _fetchTarget.Replace("raw/", ""); - - if (_isFileRemastered) + if (!String.IsNullOrEmpty(_fetchFilePack)) { - var _fetchMatch = Regex.Match(_fetchTarget, "[\\w,-]+\\.[a-zA-Z0-9]{2,4}"); - - var _fetchIndex = _fetchTarget.IndexOf(_fetchMatch.Value); - var _fetchEndPoint = _fetchIndex + _fetchMatch.Value.Length; - - var _fetchSubFirst = _fetchTarget.Substring(0, _fetchEndPoint); - var _fetchSubSecond = _fetchTarget.Substring(_fetchEndPoint, _fetchTarget.Length - _fetchEndPoint); + var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); - _fetchParent = _fetchSubFirst.Replace("remastered/", ""); - _fetchChild = _fetchSubSecond.Trim('/'); - } - - var _fetchNameHash = Egs.Helpers.CreateMD5(_isFileRemastered ? _fetchParent : _fetchTarget); - var _fetchKeyExists = _fetchFileDictionary.ContainsKey(_fetchNameHash); - - if (_fetchKeyExists) - { - var _fetchFilePack = _fetchFileDictionary[_fetchNameHash]; + var _fetchHeaderName = Path.Combine(_fetchDataPath, _fetchFilePack + ".hed"); + var _fetchPackageName = Path.Combine(_fetchDataPath, _fetchFilePack + ".pkg"); - if (!String.IsNullOrEmpty(_fetchFilePack)) + using (var _fetchHeaderStream = File.OpenRead(_fetchHeaderName)) { - var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); - - var _fetchHeaderName = Path.Combine(_fetchDataPath, _fetchFilePack + ".hed"); - var _fetchPackageName = Path.Combine(_fetchDataPath, _fetchFilePack + ".pkg"); + var _fetchEntries = Hed.Read(_fetchHeaderStream); + var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Convert.ToHexString(x.MD5) == _fetchNameHash); - using (var _fetchHeaderStream = File.OpenRead(_fetchHeaderName)) + using (var _fetchPackageStream = File.OpenRead(_fetchPackageName)) { - var _fetchEntries = Hed.Read(_fetchHeaderStream); - var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Convert.ToHexString(x.MD5) == _fetchNameHash); - - using (var _fetchPackageStream = File.OpenRead(_fetchPackageName)) - { - var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); + var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); - if (_isFileRemastered) - _fetchTargetAsset.RemasteredAssetsDecompressedData.TryGetValue(_fetchChild, out _fetchAssetData); + if (_isFileRemastered) + _fetchTargetAsset.RemasteredAssetsDecompressedData.TryGetValue(_fetchChild, out _fetchAssetData); - else - _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; + else + _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; - if (_fetchAssetData != null) - File.WriteAllBytes(_fetchOutputPath, _fetchAssetData); - } + if (_fetchAssetData != null) + File.WriteAllBytes(_fetchTargetPath, _fetchAssetData); } } } - - using (var _fileStream = File.Open(_fetchOutputPath, FileMode.OpenOrCreate)) - PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); } - - else if (_fetchSourcePath != null && File.Exists(_fetchSourcePath)) - File.Copy(_fetchSourcePath, _fetchOutputPath, true); - - using (var _fileStream = File.Open(_fetchBuildPath, FileMode.OpenOrCreate)) - PatchFile(_fetchContext, _fetchAsset, _fileStream); } + + // After all that, if the target file actually exists now: Patch it. + if (File.Exists(_fetchTargetPath)) + using (var _fileStream = File.Open(_fetchTargetPath, FileMode.OpenOrCreate)) + PatchFile(_fetchContext, _fetchAsset, _fileStream, _fetchAssetData); } } - _currentAssetIndex++; + // Process callback for progress. + _assetProcessCount++; if (reportProgress != null) - reportProgress(_currentAssetIndex, _totalAssetCount); - - await Task.Delay(1, _fetchCancelToken); + reportProgress(_assetProcessCount, _assetTotalCount); }); } diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 6f474a62b..2a659063d 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -774,7 +774,7 @@ await Task.Run(async () => var _currentModCount = modsList.Where(x => x.ModValid && x.ModActive).Count(); // Check if the game we are patching is the Japanese version of KHPC. - var _fetchJapanese = PathService.ResolveRegionJP(currentConfig); + var _fetchJapanese = PathService.ResolveRegionJP(currentConfig) ?? false; // If the callback function isn't null, create a feedback task. // This task should NOT be awaited. @@ -828,7 +828,7 @@ await _fetchPatcher.Patch _fetchGamePath, (int)currentConfig.Frontend.TargetPlatform, (int)currentConfig.Frontend.TargetGame, - _fetchJapanese.Value, + _fetchJapanese, CancelToken, _fetchPackageMap, reportProgress: reportAssetProgress From 897a8acf18380c5a08dfa5518e96a3cbb156bc5c Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 13 Sep 2026 07:13:25 +0200 Subject: [PATCH 102/175] [Mod Manager] - Install new mods at highest pri. --- OpenKh.Tools.ModManager/Views/MainView.axaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 471a55d98..0ae8b6a13 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -432,7 +432,7 @@ await ModService.InstallGit _fetchModsList.Remove(_fetchExistingMod); var _fetchFirstInvalid = _fetchModsList.FirstOrDefault(x => !x.ModValid); - var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : _fetchModsList.Count; + var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : 0x00; _fetchModsList.Insert(_fetchModIndex, _modModel); _fetchContext.HasModsInstalled = true; @@ -536,7 +536,7 @@ await ModService.InstallGit _fetchModsList.Remove(_fetchExistingMod); var _fetchFirstInvalid = _fetchModsList.FirstOrDefault(x => !x.ModValid); - var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : _fetchModsList.Count; + var _fetchModIndex = _fetchFirstInvalid != null ? _fetchModsList.IndexOf(_fetchFirstInvalid) : 0x00; _fetchModsList.Insert(_fetchModIndex, _modModel); _fetchContext.HasModsInstalled = true; From 4b9e68305a7fb24bafd5fa0ad1eca00f045e13e7 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 13 Sep 2026 16:50:06 +0200 Subject: [PATCH 103/175] [Mod Manager] - Online Catalog --- OpenKh.Patcher/Metadata.cs | 47 +++ .../OpenKh.Tools.ModManager.csproj | 1 + .../Services/ModService.cs | 84 ++--- .../ViewModels/CatalogViewModel.cs | 102 ++++++ .../Views/CatalogView.axaml | 43 +++ .../Views/CatalogView.axaml.cs | 95 ++++++ OpenKh.Tools.ModManager/Views/MainView.axaml | 1 + .../Views/MainView.axaml.cs | 314 ++++++++++++++++++ 8 files changed, 648 insertions(+), 39 deletions(-) create mode 100644 OpenKh.Tools.ModManager/ViewModels/CatalogViewModel.cs create mode 100644 OpenKh.Tools.ModManager/Views/CatalogView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/CatalogView.axaml.cs diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index 36aa2a539..67cb7738f 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -81,6 +81,53 @@ public static Metadata Read(string fileName) } } + public static Metadata Read(StringReader fileStream) + { + var _fetchYamlRaw = fileStream.ReadToEnd(); + + // Replace all back slashes as Windows supports forward slashes but Linux doesn't support back slashes. + _fetchYamlRaw = _fetchYamlRaw.Replace('\\', '/'); + + try + { + var _fetchSerial = YamlSerializer.Deserialize(_fetchYamlRaw, new YamlSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + + // A single mod should not be able to patch a file more than once. + // Ignore any and all duplicates that target the same file. + var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => new { x.Name, x.Platform }); + _fetchSerial.Assets = _fetchAssetUnique.ToList(); + + return _fetchSerial; + } + + catch (SharpYaml.YamlException ex) + { + // Handle YAML parsing errors + Debug.WriteLine($"Error deserializing YAML: {ex.Message}"); + + var _fetchTitle = string.Empty; + var _fetchMatch = Regex.Match(_fetchYamlRaw, @"(?<=title:).*", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + + if (_fetchMatch.Success) + _fetchTitle = _fetchMatch.Value.Trim(); + + var metadata = new Metadata + { + Title = _fetchTitle, + IsValid = false + }; + + return metadata; // Return modified metadata indicating failure + } + + catch (Exception ex) + { + // Handle other unexpected errors + Debug.WriteLine($"Unexpected error: {ex.Message}"); + throw; // Rethrow other exceptions for further investigation + } + } + public void Write(Stream stream) { using (var writer = new StreamWriter(stream)) diff --git a/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj b/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj index 7bb863ef6..95ffdb2ab 100644 --- a/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj +++ b/OpenKh.Tools.ModManager/OpenKh.Tools.ModManager.csproj @@ -28,6 +28,7 @@ + diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 2a659063d..64c8c399c 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -794,50 +794,56 @@ await Task.Run(async () => }, CancelToken); } + // Run the actual patcher task: await Task.Run(async () => { - // The mods will be handles sequentially to ensure their build is sound. - // This operation CANNOT be done in parallel. - for (int _modIdx = modsList.Count() - 1; _modIdx >= 0; _modIdx--) + try { - // Fetch the target mod. - var _fetchMod = modsList.ElementAt(_modIdx); - - // Increase handles mod progress. - _currentModIndex++; - - // If the mod isn't valid or isn't active, skip it. - if (!_fetchMod.ModValid || !_fetchMod.ModActive) - continue; - - // Feed the title of the mod to the callback. - _currentModName = _fetchMod.ModTitle; - - // Fetch the [mod.yml] file for the mod and read it as Metadata. - var _fetchYamlPath = Path.Combine(_fetchMod.ModPath, "mod.yml"); - var _fetchMetadata = Metadata.Read(_fetchYamlPath); - - // Await the patcher process for this mod. - await _fetchPatcher.Patch - ( - _fetchDataPath, - _fetchBuildPath, - _fetchMetadata, - _fetchMod.ModPath, - _fetchGamePath, - (int)currentConfig.Frontend.TargetPlatform, - (int)currentConfig.Frontend.TargetGame, - _fetchJapanese, - CancelToken, - _fetchPackageMap, - reportProgress: reportAssetProgress - ); - - // If cancellation is requested, break out. - if (CancelToken.IsCancellationRequested) - break; + // The mods will be handles sequentially to ensure their build is sound. + // This operation CANNOT be done in parallel. + for (int _modIdx = modsList.Count() - 1; _modIdx >= 0; _modIdx--) + { + // Fetch the target mod. + var _fetchMod = modsList.ElementAt(_modIdx); + + // Increase handles mod progress. + _currentModIndex++; + + // If the mod isn't valid or isn't active, skip it. + if (!_fetchMod.ModValid || !_fetchMod.ModActive) + continue; + + // Feed the title of the mod to the callback. + _currentModName = _fetchMod.ModTitle; + + // Fetch the [mod.yml] file for the mod and read it as Metadata. + var _fetchYamlPath = Path.Combine(_fetchMod.ModPath, "mod.yml"); + var _fetchMetadata = Metadata.Read(_fetchYamlPath); + + // Await the patcher process for this mod. + await _fetchPatcher.Patch + ( + _fetchDataPath, + _fetchBuildPath, + _fetchMetadata, + _fetchMod.ModPath, + _fetchGamePath, + (int)currentConfig.Frontend.TargetPlatform, + (int)currentConfig.Frontend.TargetGame, + _fetchJapanese, + CancelToken, + _fetchPackageMap, + reportProgress: reportAssetProgress + ); + + // If cancellation is requested, break out. + if (CancelToken.IsCancellationRequested) + break; + } } + catch (TaskCanceledException) { } + }, CancelToken); // If cancellation is requested, cancel the process, but clean the build folder beforehand. diff --git a/OpenKh.Tools.ModManager/ViewModels/CatalogViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/CatalogViewModel.cs new file mode 100644 index 000000000..a4221ec2c --- /dev/null +++ b/OpenKh.Tools.ModManager/ViewModels/CatalogViewModel.cs @@ -0,0 +1,102 @@ +using Avalonia.Media.Imaging; +using CommunityToolkit.Mvvm.ComponentModel; +using Octokit; +using OpenKh.Patcher; +using OpenKh.Tools.ModManager.Classes; +using OpenKh.Tools.ModManager.Models; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.ViewModels +{ + public partial class CatalogViewModel : ViewModelBase + { + [ObservableProperty] + private bool _initialized = false; + + [ObservableProperty] + private ModModel? _currentMod = null; + + [ObservableProperty] + private ObservableCollection? _installedMods = null; + + [ObservableProperty] + private ObservableCollection? _selectedMods = null; + + [ObservableProperty] + private Config? _currentConfig = null; + + public async Task InitializeView(bool selectLast = false) + { + Initialized = false; + + CurrentMod = null; + SelectedMods = null; + InstalledMods = null; + + if (CurrentConfig != null) + { + InstalledMods = new ObservableCollection(); + SelectedMods = new ObservableCollection(); + + var _fetchHeader = new ProductHeaderValue("OpenKH-ModSearcher"); + var _fetchClient = new GitHubClient(_fetchHeader); + + var _fetchGame = Config.GameShorthand[CurrentConfig.Frontend.TargetGame]; + + // 2. Define the search request using the "topic:topic-name" qualifier + var _fetchInquiry = new SearchRepositoriesRequest($"topic:openkh-mod topic:{_fetchGame}"); + var _fetchResult = await _fetchClient.Search.SearchRepo(_fetchInquiry); + + await Parallel.ForEachAsync(_fetchResult.Items.AsParallel(), async (_fetchMod, _fetchToken) => + { + var _fetchMetadataURL = $"https://raw.githubusercontent.com/{_fetchMod.FullName}/{_fetchMod.DefaultBranch}/mod.yml"; + var _fetchIconURL = $"https://raw.githubusercontent.com/{_fetchMod.FullName}/{_fetchMod.DefaultBranch}/icon.png"; + + using var _makeClient = new HttpClient(); + + using var _fetchMetadataOK = await _makeClient.GetAsync(_fetchMetadataURL, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); + using var _fetchImageOK = await _makeClient.GetAsync(_fetchIconURL, HttpCompletionOption.ResponseHeadersRead, CancellationToken.None); + + // If the file doesn't exist, abort. + if (_fetchMetadataOK.StatusCode == HttpStatusCode.OK) + { + var _fetchContent = await _makeClient.GetStringAsync(_fetchMetadataURL, CancellationToken.None); + var _fetchImageRAW = _fetchImageOK.StatusCode == HttpStatusCode.OK ? await _makeClient.GetByteArrayAsync(_fetchIconURL, CancellationToken.None) : null; + + var _fetchMetadata = Metadata.Read(new StringReader(_fetchContent)); + + if (_fetchMetadata.IsValid) + { + var _modModel = new ModModel + { + ModTitle = !String.IsNullOrEmpty(_fetchMetadata.Title) ? _fetchMetadata.Title : _fetchMod.Name, + ModAuthor = !String.IsNullOrEmpty(_fetchMetadata.OriginalAuthor) ? _fetchMetadata.OriginalAuthor : _fetchMod.Owner.Login, + ModDescription = !String.IsNullOrEmpty(_fetchMetadata.Description) ? _fetchMetadata.Description : "This mod does not have a description, but we believe it's pretty cool.", + ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = _fetchImageRAW != null ? new Bitmap(new MemoryStream(_fetchImageRAW)) : null, + ModPath = _fetchMod.FullName, + ModActive = false, + ModValid = true + }; + + InstalledMods.Add(_modModel); + } + } + }); + + return true; + } + + return false; + } + } +} diff --git a/OpenKh.Tools.ModManager/Views/CatalogView.axaml b/OpenKh.Tools.ModManager/Views/CatalogView.axaml new file mode 100644 index 000000000..133ab2e60 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/CatalogView.axaml @@ -0,0 +1,43 @@ + + + + + + + Here are the mods that are available on GitHub for the currently selected game. + This list is not curated and as such OpenKH is not responsible for the mods present here. + + + + + + + + + + + + + + + + + + + + + + + + - - protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEventArgs e) { - // Fetch the mod memory path and fetch the modlist from the sender. + // Fetch the modlist from the sender. var _fetchModList = sender as ObservableCollection; - var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(CurrentConfig), "mod_memory.yml"); - - // Create the actual mod memory collection. - var _modMemoryList = new ObservableCollection(); - // For every mod that exists: - foreach (var _fetchMod in _fetchModList) + if (CurrentConfig != null && _fetchModList != null) { - // If the mod is invalid do not memorize it. - if (!_fetchMod.ModValid) - continue; - - // Construct the memory structure (Hash, Is Active, Current Index) - _modMemoryList.Add - ( - new MemoryModel - { - ModHash = ModService.ResolveMD5(_fetchMod, CurrentConfig), - ModActive = _fetchMod.ModActive, - ModIndex = _fetchModList.IndexOf(_fetchMod) - } - ); + // Fetch the mod memory path. + var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(CurrentConfig), "mod_memory.yml"); + + // Create the actual mod memory collection. + var _modMemoryList = new ObservableCollection(); + + // For every mod that exists: + foreach (var _fetchMod in _fetchModList) + { + // If the mod is invalid do not memorize it. + if (!_fetchMod.ModValid) + continue; + + // Construct the memory structure (Hash, Is Active, Current Index) + _modMemoryList.Add + ( + new MemoryModel + { + ModHash = ModService.ResolveMD5(_fetchMod, CurrentConfig), + ModActive = _fetchMod.ModActive, + ModIndex = _fetchModList.IndexOf(_fetchMod) + } + ); + } + + // Serialize and commit the mod memory. + var _fetchSerial = YamlSerializer.Serialize(_modMemoryList); + File.WriteAllText(_fetchMemoryPath, _fetchSerial); } + } + + public TopLevel? FetchTopLevel() + { + var _fetchApplication = Application.Current; - // Serialize and commit the mod memory. - var _fetchSerial = YamlSerializer.Serialize(_modMemoryList); - File.WriteAllText(_fetchMemoryPath, _fetchSerial); + if (_fetchApplication == null) + return null; + + var _fetchLifetime = _fetchApplication.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; + var _fetchMainView = _fetchLifetime != null ? _fetchLifetime.MainWindow : null; + + return _fetchMainView; } - public void InitializeView(bool selectLast = false) + public MainViewModel() => InitializeView(); + + public void InitializeView() { Initialized = false; @@ -101,11 +129,11 @@ public void InitializeView(bool selectLast = false) if (CurrentConfig == null) { - var _fetchConfigFile = Path.Combine(AppContext.BaseDirectory, "config.yml"); + var CurrentConfigFile = Path.Combine(AppContext.BaseDirectory, "config.yml"); - if (File.Exists(_fetchConfigFile)) + if (File.Exists(CurrentConfigFile)) { - var _fetchConfigRAW = File.ReadAllText(_fetchConfigFile); + var CurrentConfigRAW = File.ReadAllText(CurrentConfigFile); DoesConfigExist = true; CurrentConfig = Config.Load(); @@ -146,7 +174,7 @@ public void InitializeView(bool selectLast = false) var _fetchMods = new ObservableCollection(); var _fetchModsPath = PathService.ResolveMod(CurrentConfig); - + // For each directory that exists in the mod folder: foreach (var _fetchDirectory in Directory.EnumerateDirectories(_fetchModsPath)) @@ -315,8 +343,799 @@ public void InitializeView(bool selectLast = false) Initialized = true; } - public MainViewModel() + [RelayCommand] + private async Task Install(string? inputParameter) + { + var _fetchErroredList = new List(); + var _fetchSuccessList = new List(); + + if (CurrentConfig != null && InstalledMods != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchResult = inputParameter ?? await DialogService.ShowInput(_fetchTopLevel, "Install a new Mod", "Enter the name of the repository to install.", "Install", "Ex. OpenKH/a-very-cool-mod@github.com", "Select and Install an Archive or Script", async (inputParent) => + { + var _fetchFiles = await _fetchTopLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = "Select an Archive or Script File...", + AllowMultiple = false, + FileTypeFilter = + [ + new FilePickerFileType("OpenKH Mod Archive") { Patterns = ["*.zip"] }, + new FilePickerFileType("PCPatch Package") { Patterns = ["*.kh1pcpatch", "*.kh2pcpatch", "*.bbspcpatch", "*.compcpatch", "*.dddpcpatch"] }, + new FilePickerFileType("LuaBackend Script") { Patterns = ["*.lua"] }, + new FilePickerFileType("All Files") { Patterns = ["*"] }, + ] + }); + + if (_fetchFiles.Count >= 1) + return _fetchFiles[0].Path.LocalPath; + + else + return null; + }); + + var _fetchModPath = PathService.ResolveMod(CurrentConfig); + var _fetchInstallResult = 0x00; + + if (_fetchResult != null && !string.IsNullOrEmpty(_fetchResult)) + { + var _singleProgress = new SingleProgressDialog(); + var _multiProgress = new MultiProgressDialog(); + + var _fetchFileInfo = new FileInfo(@$"{_fetchResult}"); + + if (_fetchFileInfo.Exists) + { + _singleProgress.ShowDialog(_fetchTopLevel); + + _fetchInstallResult = + await ModService.InstallLocal + ( + _fetchResult, + CurrentConfig, + (int processed, int total) => + { + Dispatcher.UIThread.Post(() => + { + _singleProgress.InstallProgress.Maximum = total; + _singleProgress.InstallProgress.Value = processed; + }); + + if (ModService.CancelToken.IsCancellationRequested) + return false; + + return true; + } + ); + + _singleProgress.Close(true); + } + + else + { + var _fetchMultiInstall = _fetchResult.Contains(';') ? _fetchResult.Split(';') : null; + + if (_fetchMultiInstall == null) + { + _singleProgress.ShowDialog(_fetchTopLevel); + + _fetchInstallResult = + await ModService.InstallGit + ( + _fetchResult, + CurrentConfig, + new TransferProgressHandler((progress) => + { + Dispatcher.UIThread.Post(() => + { + _singleProgress.InstallProgress.Maximum = progress.TotalObjects; + _singleProgress.InstallProgress.Value = progress.ReceivedObjects; + }); + + if (ModService.CancelToken.IsCancellationRequested) + return false; + + return true; + } + )); + + _singleProgress.Close(true); + } + + else + { + _multiProgress.ShowDialog(_fetchTopLevel); + + for (int i = 0; i < _fetchMultiInstall.Length; i++) + { + var _fetchMod = _fetchMultiInstall[i]; + + var _fetchInstallStatus = + await ModService.InstallGit + ( + _fetchMod, + CurrentConfig, + new TransferProgressHandler((progress) => + { + Dispatcher.UIThread.Post(() => + { + _multiProgress.ModProgress.ProgressTextFormat = $"Processing Mod: {_fetchMod}"; + + _multiProgress.ModProgress.Value = i + 0x01; + _multiProgress.ModProgress.Maximum = _fetchMultiInstall.Length; + + _multiProgress.InstallProgress.Maximum = progress.TotalObjects; + _multiProgress.InstallProgress.Value = progress.ReceivedObjects; + }); + + if (ModService.CancelToken.IsCancellationRequested) + return false; + + return true; + } + )); + + if (_fetchInstallStatus == 0x01) + _fetchErroredList.Add(_fetchMod); + + else + _fetchSuccessList.Add(_fetchMod); + + if (ModService.CancelToken.IsCancellationRequested) + break; + } + + _multiProgress.Close(true); + _fetchInstallResult = 0x02; + } + } + + if (ModService.CancelToken.IsCancellationRequested || _fetchInstallResult == 0x03) + return false; + + switch (_fetchInstallResult) + { + case 0x00: + { + var _fetchModFolder = ""; + + var _fetchModAuthor = ""; + var _fetchModName = ""; + + if (_fetchFileInfo.Exists) + _fetchModFolder = Path.Combine(_fetchModPath, $"local/{Path.GetFileNameWithoutExtension(_fetchResult)}"); + + else + { + _fetchModAuthor = _fetchResult.Split('/').First(); + _fetchModName = _fetchResult.Split('/').Last(); + + _fetchModName = _fetchModName.Split(':').First(); + _fetchModName = _fetchModName.Split('@').First(); + + _fetchModFolder = Path.Combine(_fetchModPath, _fetchModAuthor, _fetchModName); + } + + var _fetchPathGit = Path.Combine(_fetchModFolder, ".git"); + var _fetchYamlName = Path.Combine(_fetchModFolder, "mod.yml"); + var _fetchPathIcon = Path.Combine(_fetchModFolder, "icon.png"); + + var _fetchMetadata = Metadata.Read(_fetchYamlName); + + if (_fetchMetadata.IsValid) + { + var _modModel = new ModModel + { + ModTitle = !String.IsNullOrEmpty(_fetchMetadata.Title) ? _fetchMetadata.Title : _fetchModName, + ModAuthor = !String.IsNullOrEmpty(_fetchMetadata.OriginalAuthor) ? _fetchMetadata.OriginalAuthor : _fetchModAuthor, + ModDescription = !String.IsNullOrEmpty(_fetchMetadata.Description) ? _fetchMetadata.Description : "This mod does not have a description, but we believe it's pretty cool.", + ModPath = _fetchModFolder, + ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, + ModActive = true, + ModValid = true + }; + + if (Directory.Exists(_fetchPathGit)) + { + if (Repository.IsValid(_fetchPathGit)) + { + var _fetchGit = new Repository(_fetchPathGit); + + if (!_fetchGit.Info.IsHeadDetached) + { + var _fetchRemote = _fetchGit.Network.Remotes["origin"]; + + _modModel.ModSource = new Uri(_fetchRemote.Url); + _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); + + _modModel.ModPlatform = _modModel.ModSource.Host; + + Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); + + var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; + _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; + } + + _fetchGit.Dispose(); + + var _fetchGitDir = new DirectoryInfo(_fetchPathGit); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + } + + var _fetchExistingMod = InstalledMods.FirstOrDefault(x => x.ModPath == _modModel.ModPath); + + if (_fetchExistingMod != null) + InstalledMods.Remove(_fetchExistingMod); + + var _fetchFirstInvalid = InstalledMods.FirstOrDefault(x => !x.ModValid); + var _fetchModIndex = _fetchFirstInvalid != null ? InstalledMods.IndexOf(_fetchFirstInvalid) : 0x00; + + InstalledMods.Insert(_fetchModIndex, _modModel); + HasModsInstalled = true; + } + + else + { + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = !String.IsNullOrEmpty(_fetchMetadata.Title) ? _fetchMetadata.Title : _fetchModName, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchModFolder, + ModActive = false, + ModValid = false + }; + + var _fetchExistingMod = InstalledMods.FirstOrDefault(x => x.ModPath == _modModel.ModPath); + + if (_fetchExistingMod != null) + InstalledMods.Remove(_fetchExistingMod); + + InstalledMods.Add(_modModel); + } + + return true; + } + + case 0x01: + await DialogService.ShowMessage(_fetchTopLevel, "ERROR - Invalid Mod", "This is NOT a valid/compliant Mod Manager Mod. Please make sure it exists and it is valid.", MessageType.ERROR); + return false; + + case 0x02: + { + foreach (var _fetchSuccess in _fetchSuccessList) + { + var _fetchModAuthor = _fetchSuccess.Split('/').First(); + var _fetchModName = _fetchSuccess.Split('/').Last(); + + // Just in case we have platform and branch info, lose them. + _fetchModName = _fetchModName.Split(':').First(); + _fetchModName = _fetchModName.Split('@').First(); + + var _fetchCurrentPath = Path.Combine(_fetchModPath, _fetchModAuthor, _fetchModName); + + var _fetchPathGit = Path.Combine(_fetchCurrentPath, ".git"); + var _fetchYamlName = Path.Combine(_fetchCurrentPath, "mod.yml"); + var _fetchPathIcon = Path.Combine(_fetchCurrentPath, "icon.png"); + + var _fetchMetadata = Metadata.Read(_fetchYamlName); + + if (_fetchMetadata.IsValid) + { + var _modModel = new ModModel + { + ModTitle = _fetchMetadata.Title, + ModAuthor = _fetchMetadata.OriginalAuthor, + ModDescription = _fetchMetadata.Description, + ModPath = _fetchCurrentPath, + ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, + ModActive = true, + ModValid = true + }; + + if (Directory.Exists(_fetchPathGit)) + { + if (Repository.IsValid(_fetchPathGit)) + { + var _fetchGit = new Repository(_fetchPathGit); + + if (!_fetchGit.Info.IsHeadDetached) + { + var _fetchRemote = _fetchGit.Network.Remotes["origin"]; + + _modModel.ModSource = new Uri(_fetchRemote.Url); + _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); + + _modModel.ModPlatform = _modModel.ModSource.Host; + + Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); + + var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; + _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; + } + + _fetchGit.Dispose(); + + var _fetchGitDir = new DirectoryInfo(_fetchPathGit); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + } + + var _fetchExistingMod = InstalledMods.FirstOrDefault(x => x.ModPath == _modModel.ModPath); + + if (_fetchExistingMod != null) + InstalledMods.Remove(_fetchExistingMod); + + var _fetchFirstInvalid = InstalledMods.FirstOrDefault(x => !x.ModValid); + var _fetchModIndex = _fetchFirstInvalid != null ? InstalledMods.IndexOf(_fetchFirstInvalid) : 0x00; + + InstalledMods.Insert(_fetchModIndex, _modModel); + HasModsInstalled = true; + } + + else + { + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = _fetchMetadata.Title, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchCurrentPath, + ModActive = false, + ModValid = false + }; + + var _fetchExistingMod = InstalledMods.FirstOrDefault(x => x.ModPath == _modModel.ModPath); + + if (_fetchExistingMod != null) + InstalledMods.Remove(_fetchExistingMod); + + InstalledMods.Add(_modModel); + } + } + + if (_fetchErroredList.Count > 0) + { + var _fetchError = String.Join('\n', _fetchErroredList); + await DialogService.ShowMessage(_fetchTopLevel, "Some Mods were invalid!", $"The following mods were invalid and thus were not installed:\n {_fetchError}", MessageType.WARNING); + } + + return true; + } + + default: + return false; + } + } + + else + return false; + } + + else + return false; + } + + [RelayCommand] + private async Task Remove() + { + if (CurrentMod != null && InstalledMods != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + bool? _fetchResult = await DialogService.ShowQuestion(_fetchTopLevel, "Remove Mod?", "Are you sure you want to remove the selected mod?"); + + // If they indeed do: + + if (_fetchResult == true) + { + // Fetch the path too we kinda need it. + var _fetchModPath = CurrentMod.ModPath; + + // Remove the mod from the list. + InstalledMods.Remove(CurrentMod); + + // If we still have mods, select one. + if (InstalledMods.Count > 0) + CurrentMod = InstalledMods.First(); + + // If we do not, nullify selection. + else + { + HasModsInstalled = false; + CurrentMod = null; + } + + // Remove the mod directory. + // This is a task for a reason. This isn't being awaited for a reason. Do not listen to IntelliSense on this one. + Task.Run(() => + { + if (Directory.Exists(_fetchModPath)) + Directory.Delete(_fetchModPath, true); + }); + + return true; + } + + else + return false; + } + + else + return false; + } + + [RelayCommand] + private void Toggle(Tuple inputParameter) { - InitializeView(); + if (CurrentConfig != null && InstalledMods != null) + { + var _fetchMod = inputParameter.Item1; + var _fetchIsChecked = inputParameter.Item2; + + var _fetchMemoryPath = Path.Combine(PathService.ResolveMod(CurrentConfig), "mod_memory.yml"); + + var _fetchMemoryRAW = File.Exists(_fetchMemoryPath) ? File.ReadAllText(_fetchMemoryPath) : "[]"; + var _fetchMemory = YamlSerializer.Deserialize>(_fetchMemoryRAW); + + if (_fetchMemory == null) + return; + + var _fetchSenderHash = ModService.ResolveMD5(_fetchMod, CurrentConfig); + var _fetchMemoryItem = _fetchMemory.FirstOrDefault(x => x.ModHash == _fetchSenderHash); + + if (_fetchMemoryItem == null) + { + _fetchMemoryItem = new MemoryModel + { + ModHash = _fetchSenderHash, + ModActive = _fetchIsChecked, + ModIndex = InstalledMods.IndexOf(inputParameter.Item1) + }; + + _fetchMemory.Add(_fetchMemoryItem); + } + + else + { + var _fetchMemoryIndex = _fetchMemory.IndexOf(_fetchMemoryItem); + _fetchMemory[_fetchMemoryIndex].ModActive = _fetchIsChecked; + } + + var _fetchSerial = YamlSerializer.Serialize(_fetchMemory); + File.WriteAllText(_fetchMemoryPath, _fetchSerial); + } } + + + [RelayCommand] + private void MoveUtmost() + { + if (InstalledMods != null && CurrentMod != null) + { + var _fetchModIndex = InstalledMods.IndexOf(CurrentMod); + InstalledMods.Move(_fetchModIndex, 0); + + CurrentMod = InstalledMods[0]; + } + } + + [RelayCommand] + private void MoveUp() + { + if (InstalledMods != null && CurrentMod != null) + { + var _fetchModIndex = InstalledMods.IndexOf(CurrentMod); + + if (_fetchModIndex != 0 && CurrentMod.ModValid) + { + InstalledMods.Move(_fetchModIndex, _fetchModIndex - 1); + CurrentMod = InstalledMods[_fetchModIndex - 1]; + } + } + } + + [RelayCommand] + private void MoveDown() + { + if (InstalledMods != null && CurrentMod != null) + { + var _fetchModIndex = InstalledMods.IndexOf(CurrentMod); + + if ( _fetchModIndex != InstalledMods.Count - 1 && CurrentMod.ModValid) + { + InstalledMods.Move(_fetchModIndex, _fetchModIndex + 1); + CurrentMod = InstalledMods[_fetchModIndex + 1]; + } + } + } + + [RelayCommand] + private async Task OpenFolder() + { + if (CurrentMod != null) + { + var _fetchTopLevel = FetchTopLevel(); + + if (_fetchTopLevel == null) + return false; + + var _fetchDirectoryInfo = new DirectoryInfo(CurrentMod.ModPath); + + if (_fetchTopLevel?.Launcher != null) + await _fetchTopLevel.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); + + return true; + } + + else + return false; + } + + + [RelayCommand] + private async Task Run() + { + if (CurrentConfig != null) + { + var _fetchTopLevel = FetchTopLevel(); + + if (_fetchTopLevel == null) + return false; + + return await ModService.Run(CurrentConfig, _fetchTopLevel); + } + + else + return false; + } + + [RelayCommand] + private async Task Build() + { + if (CurrentConfig != null && InstalledMods != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _progressDialog = new BuildProgressDialog(); + _progressDialog.ShowDialog(_fetchTopLevel); + + int _assetTotal = 0; + int _assetProcessed = 0; + + string _currentModName = "N/A"; + + var _buildResult = + await ModService.Build + ( + InstalledMods, + CurrentConfig, + (string currModName, int procMod, int totalMod) => + { + Dispatcher.UIThread.Post(() => + { + _currentModName = currModName; + + _progressDialog.ModProgress.Maximum = totalMod; + _progressDialog.ModProgress.Value = procMod; + _progressDialog.ModProgress.ProgressTextFormat = $"Currently Building: {currModName}"; + + _progressDialog.AssetProgress.Maximum = _assetTotal; + _progressDialog.AssetProgress.Value = _assetProcessed; + }); + + if (ModService.CancelToken.IsCancellationRequested) + return false; + + return true; + }, + (int processed, int total) => + { + _assetProcessed = processed; + _assetTotal = total; + + if (ModService.CancelToken.IsCancellationRequested) + return false; + + return true; + } + ); + + _progressDialog.Close(true); + + if (_buildResult == 0x00) + return true; + + else if (_buildResult == 0x01) + await DialogService.ShowMessage(_fetchTopLevel, "ERROR - Failed to build Mod", string.Format("Building failed on this Mod: {0}\nPlease re-install the Mod and try again!", _currentModName), MessageType.ERROR); + + return false; + } + + else + return false; + } + + [RelayCommand] + private async Task Restore() + { + if (CurrentConfig != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchBuildDir = PathService.ResolveBuild(CurrentConfig); + Directory.Delete(_fetchBuildDir, true); + + await DialogService.ShowMessage(_fetchTopLevel, "Restore Completed!", "Restoration complete! The game should run as if it isn't modded!", MessageType.INFO); + + return true; + } + + else + return false; + } + + [RelayCommand] + private async Task BuildRun() + { + if (CurrentConfig != null && InstalledMods != null) + { + var _buildResult = await Build(); + + if (!_buildResult) + return false; + + var _runResult = await Run(); + + if (!_runResult) + return false; + + return true; + } + + else + return false; + } + + + [RelayCommand] + private async Task InstallPanacea() + { + if (CurrentConfig != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + PackageService.InstallPanacea(CurrentConfig); + ConfigurationValid = CurrentConfig.IsValid(); + + await DialogService.ShowMessage(_fetchTopLevel, "Panacea Installed!", "Panacea has been installed in accordance to current settings.", MessageType.INFO); + return true; + } + + else + return false; + } + + [RelayCommand] + private async Task InstallBackend() + { + if (CurrentConfig != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + PackageService.InstallBackend(CurrentConfig); + ConfigurationValid = CurrentConfig.IsValid(); + + await DialogService.ShowMessage(_fetchTopLevel, "LuaBackend Installed!", "LuaBackend has been installed in accordance to current settings.", MessageType.INFO); + return true; + } + + else + return false; + } + + + [RelayCommand] + private async Task LaunchSetup() + { + if (CurrentConfig != null && InstalledMods != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + await new SetupWizardView().ShowDialog(_fetchTopLevel); + return true; + } + + else + return false; + } + + [RelayCommand] + private async Task LaunchCatalog() + { + if (CurrentConfig != null && InstalledMods != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _catalogWindow = new CatalogView(); + var _fetchResult = await _catalogWindow.ShowDialog(_fetchTopLevel); + + if (!String.IsNullOrEmpty(_fetchResult) && InstallCommand.CanExecute(_fetchResult)) + { + InstallCommand.Execute(_fetchResult); + return true; + } + + else + return false; + } + + else + return false; + } + + [RelayCommand] + private async Task PromptArguments() + { + if (CurrentConfig != null) + { + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Declare Launch Aruguments", "Enter the launch arguments to use when launching on Steam.", "Done", "Ex. -fastboot -noaspect"); + + if (!String.IsNullOrEmpty(_fetchResult)) + CurrentConfig.Frontend.LaunchArguments = _fetchResult; + + return true; + } + + else + return false; + } + } + +#pragma warning restore CS4014 diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 53df6842a..095f4763e 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -30,27 +30,27 @@ - - + + - - - - - + + + + + - - - + + + - + - - + + @@ -59,7 +59,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -110,14 +110,14 @@ - - - - - public string Package { get; set; } + public Condition Condition { get; set; } public List Multi { get; set; } public List Source { get; set; } diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index 4a51684af..d67719971 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -176,6 +176,9 @@ public async Task Patch( // Start a parallel for all the assets: await Parallel.ForEachAsync(modMetadata.Assets.AsParallel(), new ParallelOptions() { CancellationToken = cancelToken ?? new CancellationToken() }, async (_fetchAsset, _fetchCancelToken) => { + // Process callback for progress. + _assetProcessCount++; + // If the asset has a game declared and it doesn't match the current game, skip it. if (_fetchAsset.Game != null && _fetchAsset.Game != _fetchGameId) return; @@ -184,6 +187,17 @@ public async Task Patch( if (_fetchAsset.CollectionOptional == true && (!_fetchCollectionMods.ContainsKey(_fetchAsset.Name) || !_fetchCollectionMods[_fetchAsset.Name])) return; + if (_fetchAsset.Condition != null) + { + var _fetchConditionKey = _fetchAsset.Condition.Key; + var _fetchConditionValue = _fetchAsset.Condition.Value; + + var _fetchConditionMain = modMetadata.Preferences.FirstOrDefault(x => x.Key == _fetchConditionKey); + + if (_fetchConditionMain != null && !Object.Equals(_fetchConditionValue, _fetchConditionMain.Value)) + return; + } + // Fetch all the target names to be used. var _fetchFileNames = new List(); var _fetchMultiNames = _fetchAsset.Multi != null ? _fetchAsset.Multi.Select(x => x.Name) @@ -380,9 +394,6 @@ public async Task Patch( } } - // Process callback for progress. - _assetProcessCount++; - if (reportProgress != null) reportProgress(_assetProcessCount, _assetTotalCount); }); @@ -502,6 +513,17 @@ private static void PatchBinarc(Context context, AssetFile assetFile, Stream str foreach (var file in assetFile.Source) { + if (file.Condition != null) + { + var _fetchConditionKey = file.Condition.Key; + var _fetchConditionValue = file.Condition.Value; + + var _fetchConditionMain = context.Metadata.Preferences.FirstOrDefault(x => x.Key == _fetchConditionKey); + + if (_fetchConditionMain != null && !Object.Equals(_fetchConditionValue, _fetchConditionMain.Value)) + return; + } + if (!Enum.TryParse(file.Type, true, out var barEntryType)) throw new Exception($"BinArc type {file.Type} not recognized"); diff --git a/OpenKh.Tools.ModManager/Controls/ModDetailsControl.axaml b/OpenKh.Tools.ModManager/Controls/ModDetailsControl.axaml index f81888977..c7274f2ae 100644 --- a/OpenKh.Tools.ModManager/Controls/ModDetailsControl.axaml +++ b/OpenKh.Tools.ModManager/Controls/ModDetailsControl.axaml @@ -19,17 +19,42 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKh.Tools.ModManager/Models/ModModel.cs b/OpenKh.Tools.ModManager/Models/ModModel.cs index 8663ab82e..05d230ffc 100644 --- a/OpenKh.Tools.ModManager/Models/ModModel.cs +++ b/OpenKh.Tools.ModManager/Models/ModModel.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using static OpenKh.Patcher.Metadata; namespace OpenKh.Tools.ModManager.Models { @@ -14,6 +15,7 @@ public class ModModel public string? ModTitle { get; set; } public string? ModAuthor { get; set; } public string? ModPlatform { get; set; } + public List? ModPreferences { get; set; } public string? ModDescription { get; set; } public string[]? ModFilesList { get; set; } public Bitmap? ModIcon { get; set; } diff --git a/OpenKh.Tools.ModManager/Models/PreferenceModel.cs b/OpenKh.Tools.ModManager/Models/PreferenceModel.cs new file mode 100644 index 000000000..54405c2e1 --- /dev/null +++ b/OpenKh.Tools.ModManager/Models/PreferenceModel.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.Models +{ + public class PreferenceModel + { + public string Title { get; set; } + public string Key { get; set; } + public string Type { get; set; } + public string Description { get; set; } + public string Category { get; set; } + + public string[]? Options { get; set; } + public object Value { get; set; } + + public bool IsValueBoolean => Type == "bool"; + public bool IsValueComboBox => Type == "options"; + } +} diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 77b4d8202..003151db9 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -817,6 +817,28 @@ await Task.Run(async () => var _fetchYamlPath = Path.Combine(_fetchMod.ModPath, "mod.yml"); var _fetchMetadata = Metadata.Read(_fetchYamlPath); + if (_fetchMod.ModPreferences != null) + { + var _fetchListMetadata = new List(); + + foreach (var _fetchPref in _fetchMod.ModPreferences) + { + var _fetchPrefMetadata = new Preference() + { + Title = _fetchPref.Title, + Key = _fetchPref.Key, + Description = _fetchPref.Description, + Options = _fetchPref.Options, + Category = _fetchPref.Category, + Value = _fetchPref.Value + }; + + _fetchListMetadata.Add(_fetchPrefMetadata); + } + + _fetchMetadata.Preferences = _fetchListMetadata; + } + // Await the patcher process for this mod. await _fetchPatcher.Patch ( diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index d16b25958..860ac4b54 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -214,6 +214,26 @@ public void InitializeView() ModValid = true }; + if (_metadata.Preferences != null) + { + _modModel.ModPreferences = new List(); + + foreach (var _preference in _metadata.Preferences) + { + var _fetchPref = new PreferenceModel + { + Title = _preference.Title, + Key = _preference.Key, + Description = _preference.Description, + Type = _preference.Type, + Options = _preference.Options, + Value = _preference.Value + }; + + _modModel.ModPreferences.Add(_fetchPref); + } + } + // We have found a Git Repository, let's see what's up. Task.Run(() => @@ -617,6 +637,26 @@ await ModService.InstallGit ModValid = true }; + if (_fetchMetadata.Preferences != null) + { + _modModel.ModPreferences = new List(); + + foreach (var _preference in _fetchMetadata.Preferences) + { + var _fetchPref = new PreferenceModel + { + Title = _preference.Title, + Key = _preference.Key, + Description = _preference.Description, + Type = _preference.Type, + Options = _preference.Options, + Value = _preference.Value + }; + + _modModel.ModPreferences.Add(_fetchPref); + } + } + if (Directory.Exists(_fetchPathGit)) { if (Repository.IsValid(_fetchPathGit)) @@ -723,6 +763,26 @@ await ModService.InstallGit ModValid = true }; + if (_fetchMetadata.Preferences != null) + { + _modModel.ModPreferences = new List(); + + foreach (var _preference in _fetchMetadata.Preferences) + { + var _fetchPref = new PreferenceModel + { + Title = _preference.Title, + Key = _preference.Key, + Description = _preference.Description, + Type = _preference.Type, + Options = _preference.Options, + Value = _preference.Value + }; + + _modModel.ModPreferences.Add(_fetchPref); + } + } + if (Directory.Exists(_fetchPathGit)) { if (Repository.IsValid(_fetchPathGit)) diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 41c823bc3..7a54057ad 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -7,8 +7,8 @@ xmlns:view="clr-namespace:OpenKh.Tools.ModManager.Views" xmlns:convert="using:OpenKh.Tools.ModManager.Converters" xmlns:class="using:OpenKh.Tools.ModManager.Classes" - mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="850" - Width="750" Height="850" + mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="850" + Width="800" Height="850" x:Class="OpenKh.Tools.ModManager.Views.MainView" x:DataType="vm:MainViewModel" Icon="/Assets/avalonia-logo.ico" From 8d6320639ae7da995f028fd5399a8e7e1190cabc Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 18 Sep 2026 03:12:25 +0200 Subject: [PATCH 132/175] [Mod Manager] - Fix some rudimentary problems. --- .../Services/PathService.cs | 2 +- .../ViewModels/MainViewModel.cs | 47 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index d6d810c86..4efaf41a9 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -182,7 +182,7 @@ public static string ResolvePath28(Config input) { var _fetchConfigPath = Path.Combine(_fetchFolder, "steamapps", "libraryfolders.vdf"); - if (!String.IsNullOrEmpty(_fetchConfigPath)) + if (File.Exists(_fetchConfigPath)) { var _fetchLibraryConfig = File.ReadAllLines(_fetchConfigPath); var _pathRegex = new Regex("path[^\"]*\"\\s*\"([^\"]*)\"", RegexOptions.None, TimeSpan.FromMilliseconds(500)); diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 860ac4b54..e274bcc83 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -498,10 +498,11 @@ await ModService.InstallGit // Free all the pointers allocated. - Marshal.FreeHGlobal(_progressTextPtr); - Marshal.FreeHGlobal(_progressMaximumPtr); - Marshal.FreeHGlobal(_progressCurrentPtr); + Marshal.FreeHGlobal(_progressTextPtr); _progressTextPtr = IntPtr.Zero; + + Marshal.FreeHGlobal(_progressMaximumPtr); _progressMaximumPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_progressCurrentPtr); _progressCurrentPtr = IntPtr.Zero; } else @@ -576,14 +577,14 @@ await ModService.InstallGit // Free all the pointers allocated. - Marshal.FreeHGlobal(_secondTextPtr); - Marshal.FreeHGlobal(_firstTextPtr); + Marshal.FreeHGlobal(_secondTextPtr); _secondTextPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_firstTextPtr); _firstTextPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_secondMaximumPtr); - Marshal.FreeHGlobal(_secondCurrentPtr); + Marshal.FreeHGlobal(_secondMaximumPtr); _secondMaximumPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_secondCurrentPtr); _secondCurrentPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstMaximumPtr); - Marshal.FreeHGlobal(_firstCurrentPtr); + Marshal.FreeHGlobal(_firstMaximumPtr); _firstCurrentPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_firstCurrentPtr); _firstCurrentPtr = IntPtr.Zero; // Return the result. @@ -1061,14 +1062,14 @@ private async Task Build() // Allocate all the pointers to use for the progress bars. - IntPtr _firstCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _firstMaximumPtr = Marshal.AllocHGlobal(4); - - IntPtr _secondCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _secondMaximumPtr = Marshal.AllocHGlobal(4); - - IntPtr _firstTextPtr = Marshal.AllocHGlobal(256); - IntPtr _secondTextPtr = Marshal.AllocHGlobal(256); + var _firstCurrentPtr = Marshal.AllocHGlobal(4); + var _firstMaximumPtr = Marshal.AllocHGlobal(4); + + var _secondCurrentPtr = Marshal.AllocHGlobal(4); + var _secondMaximumPtr = Marshal.AllocHGlobal(4); + + var _firstTextPtr = Marshal.AllocHGlobal(256); + var _secondTextPtr = Marshal.AllocHGlobal(256); // Write the default strings to the pointers. @@ -1128,14 +1129,14 @@ await ModService.Build // Free all the pointers allocated. - Marshal.FreeHGlobal(_secondTextPtr); - Marshal.FreeHGlobal(_firstTextPtr); + Marshal.FreeHGlobal(_secondTextPtr); _secondTextPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_firstTextPtr); _firstTextPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_secondMaximumPtr); - Marshal.FreeHGlobal(_secondCurrentPtr); + Marshal.FreeHGlobal(_secondMaximumPtr); _secondMaximumPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_secondCurrentPtr); _secondCurrentPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstMaximumPtr); - Marshal.FreeHGlobal(_firstCurrentPtr); + Marshal.FreeHGlobal(_firstMaximumPtr); _firstCurrentPtr = IntPtr.Zero; + Marshal.FreeHGlobal(_firstCurrentPtr); _firstCurrentPtr = IntPtr.Zero; if (_buildResult == 0x00) return true; From f5283c07c0d6ea0a21dbcb98e998c431ded26c28 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Fri, 18 Sep 2026 04:32:53 +0200 Subject: [PATCH 133/175] [Mod Manager] - Preset Implementation [WIP] --- OpenKh.Patcher/Metadata.cs | 2 +- OpenKh.Tools.ModManager/Classes/Config.cs | 8 +- OpenKh.Tools.ModManager/Models/PresetModel.cs | 15 ++ .../Services/PathService.cs | 60 +++++- .../ViewModels/MainViewModel.cs | 183 +++++++++++++++++- OpenKh.Tools.ModManager/Views/MainView.axaml | 1 + 6 files changed, 254 insertions(+), 15 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Models/PresetModel.cs diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index 45f3b1f6d..5fe7e4d21 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -75,7 +75,7 @@ public class Metadata // A single mod should not be able to patch a file more than once. // Ignore any and all duplicates that target the same file. - var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => new { x.Name, x.Platform }); + var _fetchAssetUnique = _fetchSerial.Assets.DistinctBy(x => new { x.Name, x.Platform, x.Condition }); _fetchSerial.Assets = _fetchAssetUnique.ToList(); return _fetchSerial; diff --git a/OpenKh.Tools.ModManager/Classes/Config.cs b/OpenKh.Tools.ModManager/Classes/Config.cs index 3e3e2db61..ea3b5df59 100644 --- a/OpenKh.Tools.ModManager/Classes/Config.cs +++ b/OpenKh.Tools.ModManager/Classes/Config.cs @@ -49,13 +49,15 @@ public class Frontend public string? BuildPath { get; set; } + public string[]? TargetPreset { get; set; } + + public string? PresetPath { get; set; } + public bool UpdateMods { get; set; } public string? DataPath { get; set; } - public string[]? GamePath { - get; - set; } + public string[]? GamePath { get; set; } } // Panacea Configuration diff --git a/OpenKh.Tools.ModManager/Models/PresetModel.cs b/OpenKh.Tools.ModManager/Models/PresetModel.cs new file mode 100644 index 000000000..cfb004e70 --- /dev/null +++ b/OpenKh.Tools.ModManager/Models/PresetModel.cs @@ -0,0 +1,15 @@ +using OpenKh.Tools.ModManager.Classes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.Models +{ + public class PresetModel + { + public string? PresetName { get; set; } + public Game PresetGame { get; set; } + } +} diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index 4efaf41a9..81c2e6eab 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -17,22 +17,41 @@ public static class PathService public static string ResolveMod(Config input, bool barePath = false) { var _fetchGameTarget = barePath ? "" : Config.GameShorthand[input.Frontend.TargetGame]; + var _fetchGamePreset = (barePath || input.Frontend.TargetPreset == null) ? "" : input.Frontend.TargetPreset[(int)input.Frontend.TargetGame]; - var _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, "mods", _fetchGameTarget); - - if (!String.IsNullOrEmpty(input.Frontend.ModPath)) + if (String.IsNullOrEmpty(_fetchGamePreset)) { - if (!Path.IsPathFullyQualified(input.Frontend.ModPath)) - _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, input.Frontend.ModPath, _fetchGameTarget); + var _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, "mods", _fetchGameTarget); - else - _fetchTargetPath = Path.Combine(input.Frontend.ModPath, _fetchGameTarget); + if (!String.IsNullOrEmpty(input.Frontend.ModPath)) + { + if (!Path.IsPathFullyQualified(input.Frontend.ModPath)) + _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, input.Frontend.ModPath, _fetchGameTarget); + + else + _fetchTargetPath = Path.Combine(input.Frontend.ModPath, _fetchGameTarget); + } + + if (!Directory.Exists(_fetchTargetPath)) + Directory.CreateDirectory(_fetchTargetPath); + + return _fetchTargetPath; } - if (!Directory.Exists(_fetchTargetPath)) - Directory.CreateDirectory(_fetchTargetPath); + else + { + var _fetchNormalStr = _fetchGamePreset.ToLower().Replace(" ", "_"); - return _fetchTargetPath; + foreach (var _fetchChar in Path.GetInvalidFileNameChars()) + _fetchNormalStr = _fetchNormalStr.Replace(_fetchChar, '-'); + + var _fetchFinalPath = Path.Combine(ResolvePreset(input, false), _fetchNormalStr); + + if (!Directory.Exists(_fetchFinalPath)) + Directory.CreateDirectory(_fetchFinalPath); + + return _fetchFinalPath; + } } public static string ResolveBuild(Config input, bool barePath = false) @@ -56,6 +75,27 @@ public static string ResolveBuild(Config input, bool barePath = false) return _fetchTargetPath; } + public static string ResolvePreset(Config input, bool barePath = false) + { + var _fetchGameTarget = barePath ? "" : Config.GameShorthand[input.Frontend.TargetGame]; + + var _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, "preset", _fetchGameTarget); + + if (!String.IsNullOrEmpty(input.Frontend.PresetPath)) + { + if (!Path.IsPathFullyQualified(input.Frontend.PresetPath)) + _fetchTargetPath = Path.Combine(AppContext.BaseDirectory, input.Frontend.PresetPath, _fetchGameTarget); + + else + _fetchTargetPath = Path.Combine(input.Frontend.PresetPath, _fetchGameTarget); + } + + if (!Directory.Exists(_fetchTargetPath)) + Directory.CreateDirectory(_fetchTargetPath); + + return _fetchTargetPath; + } + public static string? ResolveData(Config input, bool barePath = false) { var _fetchGameTarget = barePath ? "" : Config.GameShorthand[input.Frontend.TargetGame]; diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index e274bcc83..7177f689c 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -7,6 +7,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Platform.Storage; +using Avalonia.Input; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; @@ -60,6 +61,9 @@ public partial class MainViewModel : ViewModelBase [ObservableProperty] private bool _hasModsInstalled = false; + [ObservableProperty] + private ObservableCollection _presetItems; + /// /// Activates whenever InstalledMods has a property change. /// It commits said changes to the targeted game's mod memory. @@ -156,7 +160,8 @@ public void InitializeView() GamePath = new string[2], TargetPlatform = Platform.STEAM, ModBuildType = BuildType.PANACEA, - TargetGame = Game.KINGDOM_HEARTS_II + TargetGame = Game.KINGDOM_HEARTS_II, + TargetPreset = new string[5] }, Emulator = new Emulator() @@ -172,6 +177,87 @@ public void InitializeView() ConfigurationValid = CurrentConfig.IsValid(); + // === Preset Parsing and Verification === // + + PresetItems = new ObservableCollection(); + + PresetItems.Add(new MenuItem() + { + Header = "Create New Preset...", + Command = CreatePresetCommand, + InputGesture = new KeyGesture(Key.N, KeyModifiers.Alt) + }); + + PresetItems.Add(new Separator()); + + var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; + var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + + var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + + if (File.Exists(_fetchPresetPath)) + { + var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); + var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); + + if (_fetchPresetList.Count > 0) + { + var _fetchKeyIndex = 34; + + PresetItems.Add(new MenuItem() + { + Header = "Default", + ToggleType = MenuItemToggleType.Radio, + IsChecked = String.IsNullOrEmpty(_fetchPreset), + Command = SwitchPresetCommand, + CommandParameter = "", + InputGesture = new KeyGesture(Key.D, KeyModifiers.Alt) + }); + + foreach (var _preset in _fetchPresetList) + { + var _fetchKey = (Key)_fetchKeyIndex; + + var _fetchMenuItem = new MenuItem() + { + Header = _preset.PresetName, + ToggleType = MenuItemToggleType.Radio, + IsChecked = _fetchPreset == _preset.PresetName, + Command = SwitchPresetCommand, + CommandParameter = _preset.PresetName, + InputGesture = new KeyGesture(_fetchKey, KeyModifiers.Alt) + }; + + PresetItems.Add(_fetchMenuItem); + + _fetchKeyIndex++; + } + } + + if (CurrentConfig.Frontend.TargetPreset != null) + { + var _tryFetchPreset = _fetchPresetList.FirstOrDefault(x => x.PresetName == _fetchPreset); + + if (_tryFetchPreset == null) + CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; + } + } + + else + File.WriteAllText(_fetchPresetPath, "[]"); + + if (PresetItems.Count == 0x02) + { + PresetItems.Add(new MenuItem() + { + Header = "No Presets Available.", + IsEnabled = false + }); + + if (CurrentConfig.Frontend.TargetPreset != null) + CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; + } + // === Mod Parsing and Verification === // // Construct the mod folder path for the specified game. @@ -1305,6 +1391,101 @@ private async Task PromptArguments() return false; } + [RelayCommand] + private async Task CreatePreset() + { + if (CurrentConfig != null) + { + var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; + var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Create New Preset", "Please enter a name for the new preset.", "OK", "Ex. \"Some Very Cool Preset\""); + + if (!String.IsNullOrEmpty(_fetchResult)) + { + var _isPresetPresent = PresetItems.OfType().FirstOrDefault(x => x.Header as string == "Default") != null; + + if (!_isPresetPresent) + { + PresetItems.RemoveAt(0x02); + + PresetItems.Add(new MenuItem() + { + Header = "Default", + ToggleType = MenuItemToggleType.Radio, + IsChecked = true, + Command = SwitchPresetCommand, + CommandParameter = "", + InputGesture = new KeyGesture(Key.D, KeyModifiers.Alt) + }); + + PresetItems.Add(new MenuItem() + { + Header = _fetchResult, + ToggleType = MenuItemToggleType.Radio, + IsChecked = false, + Command = SwitchPresetCommand, + CommandParameter = _fetchResult, + InputGesture = new KeyGesture(Key.D0, KeyModifiers.Alt) + }); + } + + else + { + PresetItems.Add(new MenuItem() + { + Header = _fetchResult, + ToggleType = MenuItemToggleType.Radio, + IsChecked = false, + Command = SwitchPresetCommand, + CommandParameter = _fetchResult, + InputGesture = new KeyGesture((Key)(34 + PresetItems.Count - 0x03), KeyModifiers.Alt) + }); + } + + var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + + var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); + var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); + + _fetchPresetList.Add(new PresetModel + { + PresetName = _fetchResult, + PresetGame = CurrentConfig.Frontend.TargetGame + }); + + _fetchPresetRAW = YamlSerializer.Serialize(_fetchPresetList); + File.WriteAllText(_fetchPresetPath, _fetchPresetRAW); + } + } + + return false; + } + + [RelayCommand] + private async Task SwitchPreset(string input) + { + if (CurrentConfig != null) + { + var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; + var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + + if (_fetchPreset == input) + return false; + + CurrentConfig.Frontend.TargetPreset[_fetchGame] = input; + InitializeView(); + + return true; + } + + return false; + } } #pragma warning restore CS4014 diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 7a54057ad..a10dc14c2 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -93,6 +93,7 @@ + From 5375778a8a1cd791a5705f61940b385a71951536 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sat, 19 Sep 2026 02:40:29 +0200 Subject: [PATCH 134/175] [Mod Manager] - Move all pointer operations to SafePtr, dependency and game parsing for mods. --- OpenKh.Patcher/Metadata.cs | 9 +- OpenKh.Tools.ModManager/Classes/SafePtr.cs | 96 ++++++ OpenKh.Tools.ModManager/Models/ModModel.cs | 2 + .../Services/DialogService.cs | 86 ++--- .../Services/ModService.cs | 4 +- .../ViewModels/MainViewModel.cs | 296 ++++++++++++------ OpenKh.Tools.ModManager/Views/MainView.axaml | 2 +- 7 files changed, 324 insertions(+), 171 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Classes/SafePtr.cs diff --git a/OpenKh.Patcher/Metadata.cs b/OpenKh.Patcher/Metadata.cs index 5fe7e4d21..b4f910868 100644 --- a/OpenKh.Patcher/Metadata.cs +++ b/OpenKh.Patcher/Metadata.cs @@ -15,11 +15,6 @@ namespace OpenKh.Patcher { [YamlSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = YamlIgnoreCondition.WhenWritingDefault)] - public class Dependency - { - public string Name { get; set; } - } - public class Preference { public string Title { get; set; } @@ -45,9 +40,9 @@ public class Metadata public List? Preferences { get; set; } public string Game { get; set; } public int Specifications { get; set; } - public List? Dependencies { get; set; } + public List? Dependencies { get; set; } public bool IsCollection { get; set; } - public List CollectionGames { get; set; } + public List? CollectionGames { get; set; } public List Assets { get; set; } [YamlIgnore] diff --git a/OpenKh.Tools.ModManager/Classes/SafePtr.cs b/OpenKh.Tools.ModManager/Classes/SafePtr.cs new file mode 100644 index 000000000..317d8a090 --- /dev/null +++ b/OpenKh.Tools.ModManager/Classes/SafePtr.cs @@ -0,0 +1,96 @@ +using Avalonia.Xaml.Interactions.Custom; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.Classes +{ + public class SafePtr : IDisposable + { + protected IntPtr _internalPtr; + protected int _internalSize; + + public SafePtr(int size) + { + _internalSize = size; + _internalPtr = Marshal.AllocHGlobal(_internalSize); + + if (_internalSize == 0x04) + Marshal.WriteInt32(_internalPtr, 0x00); + + else if (_internalSize == 0xFF) + Marshal.Copy(new byte[0xFF], 0, _internalPtr, 0xFF); + + else + throw new InvalidCastException("Invalid pointer length! Supported values are 0x04 [Int] and 0xFF [String]"); + } + + public object? GetValue() + { + if (_internalPtr == IntPtr.Zero) + return null; + + switch (_internalSize) + { + case 0x04: + return Marshal.ReadInt32(_internalPtr); + + case 0xFF: + { + var _fetchValue = new byte[0xFF]; + Marshal.Copy(_internalPtr, _fetchValue, 0, 0xFF); + return Encoding.Default.GetString(_fetchValue, 0x00, _fetchValue.IndexOf(0x00)); + } + + default: + return null; + } + } + + public bool WriteValue(object value) + { + if (_internalPtr == IntPtr.Zero) + return false; + + switch (_internalSize) + { + case 0x04: + { + if (value is int) + { + Marshal.WriteInt32(_internalPtr, (int)value); + return true; + } + + else + return false; + } + + case 0xFF: + { + if (value is string) + { + var _fetchTextBytes = Encoding.Default.GetBytes((value as string) + "\x00"); + Marshal.Copy(_fetchTextBytes, 0, _internalPtr, _fetchTextBytes.Length); + return true; + } + + else + return false; + } + + default: + return false; + } + } + + public void Dispose() + { + Marshal.FreeHGlobal(_internalPtr); + _internalPtr = IntPtr.Zero; + } + } +} diff --git a/OpenKh.Tools.ModManager/Models/ModModel.cs b/OpenKh.Tools.ModManager/Models/ModModel.cs index 05d230ffc..eb736348f 100644 --- a/OpenKh.Tools.ModManager/Models/ModModel.cs +++ b/OpenKh.Tools.ModManager/Models/ModModel.cs @@ -14,9 +14,11 @@ public class ModModel { public string? ModTitle { get; set; } public string? ModAuthor { get; set; } + public string? ModGitAddress { get; set; } public string? ModPlatform { get; set; } public List? ModPreferences { get; set; } public string? ModDescription { get; set; } + public string[]? ModDependencies { get; set; } public string[]? ModFilesList { get; set; } public Bitmap? ModIcon { get; set; } public string? ModPath { get; set; } diff --git a/OpenKh.Tools.ModManager/Services/DialogService.cs b/OpenKh.Tools.ModManager/Services/DialogService.cs index 1d2109d08..6637d2c74 100644 --- a/OpenKh.Tools.ModManager/Services/DialogService.cs +++ b/OpenKh.Tools.ModManager/Services/DialogService.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Threading; using Octokit; +using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Dialogs; using System; using System.Collections.Generic; @@ -17,7 +18,7 @@ namespace OpenKh.Tools.ModManager.Services { public static class DialogService { - public async static Task ShowProgress(Window? owner, string title, string message, CancellationTokenSource cancelToken, IntPtr firstProgCurr, IntPtr firstProgMax, IntPtr? firstProgText = null, IntPtr? secondProgCurr = null, IntPtr? secondProgMax = null, IntPtr? secondProgText = null) + public async static Task ShowProgress(Window? owner, string title, string message, CancellationTokenSource cancelToken, SafePtr firstProgCurr, SafePtr firstProgMax, SafePtr? firstProgText = null, SafePtr? secondProgCurr = null, SafePtr? secondProgMax = null, SafePtr? secondProgText = null) { var _fetchDialog = new ProgressDialog { @@ -29,78 +30,41 @@ public async static Task ShowProgress(Window? owner, string title, string { while (true) { - var _fetchCurrent = Marshal.ReadInt32(firstProgCurr); - var _fetchMaximum = Marshal.ReadInt32(firstProgMax); + var _fetchFirstCurrent = firstProgCurr.GetValue() as int?; + var _fetchFirstMaximum = firstProgMax.GetValue() as int?; - Dispatcher.UIThread.Post(() => + if (_fetchFirstCurrent == null || _fetchFirstMaximum == null) { - _fetchDialog.MainProgress.Maximum = _fetchMaximum; - _fetchDialog.MainProgress.Value = _fetchCurrent; - - if (firstProgText != null) - { - var _fetchValue = new byte[0x100]; - Marshal.Copy(firstProgText.Value, _fetchValue, 0, 0x100); - - _fetchDialog.MainProgress.ShowProgressText = true; - _fetchDialog.MainProgress.ProgressTextFormat = Encoding.Default.GetString(_fetchValue, 0x00, _fetchValue.IndexOf(0x00)); - } - - else - _fetchDialog.MainProgress.ShowProgressText = false; - - if (secondProgCurr != null && secondProgMax != null) + Dispatcher.UIThread.Post(() => { - var _fetchSecondCurrent = Marshal.ReadInt32(secondProgCurr.Value); - var _fetchSecondMaximum = Marshal.ReadInt32(secondProgMax.Value); - - _fetchDialog.MiscProgress.Maximum = _fetchSecondMaximum; - _fetchDialog.MiscProgress.Value = _fetchSecondCurrent; - - _fetchDialog.MiscProgress.IsVisible = true; - } + _fetchDialog.Close(_fetchDialog.Result = true); + }); - else - _fetchDialog.MiscProgress.IsVisible = false; + break; + } - if (secondProgText != null) - { - var _fetchValue = new byte[0x100]; - Marshal.Copy(secondProgText.Value, _fetchValue, 0, 0x100); + var _fetchSecondCurrent = secondProgCurr != null ? secondProgCurr.GetValue() as int? : null; + var _fetchSecondMaximum = secondProgMax != null ? secondProgMax.GetValue() as int? : null; - _fetchDialog.MiscProgress.ShowProgressText = true; - _fetchDialog.MiscProgress.ProgressTextFormat = Encoding.Default.GetString(_fetchValue, 0x00, _fetchValue.IndexOf(0x00)); - } - }); + var _fetchFirstString = firstProgText != null ? firstProgText.GetValue() as string : null; + var _fetchSecondString = secondProgText != null ? secondProgText.GetValue() as string : null; - if ((_fetchCurrent == _fetchMaximum && _fetchMaximum != 0) || cancelToken.IsCancellationRequested) + Dispatcher.UIThread.Post(() => { - if ((secondProgCurr != null && secondProgMax != null)) - { - var _fetchSecondCurrent = Marshal.ReadInt32(secondProgCurr.Value); - var _fetchSecondMaximum = Marshal.ReadInt32(secondProgMax.Value); + _fetchDialog.MainProgress.Maximum = _fetchFirstMaximum.Value; + _fetchDialog.MainProgress.Value = _fetchFirstCurrent.Value; - if ((_fetchSecondCurrent == _fetchSecondMaximum && _fetchSecondMaximum != 0) || cancelToken.IsCancellationRequested) - { - Dispatcher.UIThread.Post(() => - { - _fetchDialog.Close(_fetchDialog.Result = true); - }); + _fetchDialog.MainProgress.ShowProgressText = _fetchFirstString != null; + _fetchDialog.MainProgress.ProgressTextFormat = _fetchFirstString ?? ""; - break; - } - } + _fetchDialog.MiscProgress.Maximum = _fetchSecondMaximum ?? 0; + _fetchDialog.MiscProgress.Value = _fetchSecondCurrent ?? 0; - else - { - Dispatcher.UIThread.Post(() => - { - _fetchDialog.Close(_fetchDialog.Result = true); - }); + _fetchDialog.MiscProgress.ShowProgressText = _fetchSecondString != null; + _fetchDialog.MiscProgress.ProgressTextFormat = _fetchSecondString ?? ""; - break; - } - } + _fetchDialog.MiscProgress.IsVisible = _fetchSecondCurrent != null && _fetchSecondMaximum != null; + }); await Task.Delay(5, CancellationToken.None); } diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 003151db9..3c6f55e58 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -545,7 +545,7 @@ await Task.Run(() => if (reportProgress != null) { // Feedback to the progress and see the result. - var _fetchProgress = reportProgress(i, _fetchArchive.Entries.Count); + var _fetchProgress = reportProgress(i + 1, _fetchArchive.Entries.Count); // If the result is false, meaning cancellation requested, break out immediately. if (!_fetchProgress) @@ -659,7 +659,7 @@ await Task.Run(() => _fetchEntry.ExtractToFile(_fetchFileTarget, true); - var _fetchProgress = reportProgress(i, _fetchArchive.Entries.Count); + var _fetchProgress = reportProgress(i + 1, _fetchArchive.Entries.Count); if (!_fetchProgress) break; diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 7177f689c..d0781ef27 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -461,7 +461,7 @@ private async Task Install(string? inputParameter) if (CurrentConfig != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchTopLevel = FetchTopLevel() as MainView; if (_fetchTopLevel == null) return false; @@ -497,20 +497,13 @@ private async Task Install(string? inputParameter) if (_fetchFileInfo.Exists) { - IntPtr _progressCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _progressMaximumPtr = Marshal.AllocHGlobal(4); - - IntPtr _progressTextPtr = Marshal.AllocHGlobal(256); - - // Write the default strings to the pointers. + var _progressCurrentPtr = new SafePtr(0x04); + var _progressMaximumPtr = new SafePtr(0x04); + + var _progressTextPtr = new SafePtr(0xFF); - var _fetchProgressBytes = Encoding.Default.GetBytes("Processing Local Files: {0} / {3}" + "\x00"); - Marshal.Copy(_fetchProgressBytes, 0, _progressTextPtr, _fetchProgressBytes.Length); - - // Write the default ints to the pointers. - - Marshal.WriteInt32(_progressCurrentPtr, 0x00); - Marshal.WriteInt32(_progressMaximumPtr, 0x00); + // Write the default string to the pointer. + _progressTextPtr.WriteValue("Processing Local Files: {0} / {3}"); var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); @@ -521,8 +514,8 @@ await ModService.InstallLocal CurrentConfig, (int processed, int total) => { - Marshal.WriteInt32(_progressCurrentPtr, processed); - Marshal.WriteInt32(_progressMaximumPtr, total); + _progressCurrentPtr.WriteValue(processed); + _progressMaximumPtr.WriteValue(total); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -533,10 +526,10 @@ await ModService.InstallLocal // Free all the pointers allocated. - Marshal.FreeHGlobal(_progressTextPtr); + _progressTextPtr.Dispose(); - Marshal.FreeHGlobal(_progressMaximumPtr); - Marshal.FreeHGlobal(_progressCurrentPtr); + _progressMaximumPtr.Dispose(); + _progressCurrentPtr.Dispose(); } else @@ -547,21 +540,13 @@ await ModService.InstallLocal { // Allocate the pointers necessary. - IntPtr _progressCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _progressMaximumPtr = Marshal.AllocHGlobal(4); + var _progressCurrentPtr = new SafePtr(0x04); + var _progressMaximumPtr = new SafePtr(0x04); - IntPtr _progressTextPtr = Marshal.AllocHGlobal(256); - - // Write the default strings to the pointers. - - var _fetchProgressBytes = Encoding.Default.GetBytes("Receiving Git Objects: {0} / {3}" + "\x00"); - Marshal.Copy(_fetchProgressBytes, 0, _progressTextPtr, _fetchProgressBytes.Length); - - // Write the default ints to the pointers. - - Marshal.WriteInt32(_progressCurrentPtr, 0x00); - Marshal.WriteInt32(_progressMaximumPtr, 0x00); + var _progressTextPtr = new SafePtr(0xFF); + // Write the default string to the pointer. + _progressTextPtr.WriteValue("Receiving Git Objects: {0} / {3}"); var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); @@ -572,8 +557,8 @@ await ModService.InstallGit CurrentConfig, new TransferProgressHandler((progress) => { - Marshal.WriteInt32(_progressCurrentPtr, progress.ReceivedObjects); - Marshal.WriteInt32(_progressMaximumPtr, progress.TotalObjects); + _progressCurrentPtr.WriteValue(progress.ReceivedObjects); + _progressMaximumPtr.WriteValue(progress.TotalObjects); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -584,41 +569,29 @@ await ModService.InstallGit // Free all the pointers allocated. + _progressTextPtr.Dispose(); - Marshal.FreeHGlobal(_progressTextPtr); _progressTextPtr = IntPtr.Zero; - - Marshal.FreeHGlobal(_progressMaximumPtr); _progressMaximumPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_progressCurrentPtr); _progressCurrentPtr = IntPtr.Zero; + _progressMaximumPtr.Dispose(); + _progressCurrentPtr.Dispose(); } else { // Allocate all the pointers to use for the progress bars. - IntPtr _firstCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _firstMaximumPtr = Marshal.AllocHGlobal(4); + var _firstCurrentPtr = new SafePtr(0x04); + var _firstMaximumPtr = new SafePtr(0x04); - IntPtr _secondCurrentPtr = Marshal.AllocHGlobal(4); - IntPtr _secondMaximumPtr = Marshal.AllocHGlobal(4); + var _secondCurrentPtr = new SafePtr(0x04); + var _secondMaximumPtr = new SafePtr(0x04); - IntPtr _firstTextPtr = Marshal.AllocHGlobal(256); - IntPtr _secondTextPtr = Marshal.AllocHGlobal(256); + var _firstTextPtr = new SafePtr(0xFF); + var _secondTextPtr = new SafePtr(0xFF); // Write the default strings to the pointers. - var _fetchFirstBytes = Encoding.Default.GetBytes($"Processing Mod: N/A" + "\x00"); - var _fetchSecondBytes = Encoding.Default.GetBytes("Receiving Git Objects: {0} / {3}" + "\x00"); - - Marshal.Copy(_fetchFirstBytes, 0, _firstTextPtr, _fetchFirstBytes.Length); - Marshal.Copy(_fetchSecondBytes, 0, _secondTextPtr, _fetchSecondBytes.Length); - - // Write the default ints to the pointers. - - Marshal.WriteInt32(_firstCurrentPtr, 0x00); - Marshal.WriteInt32(_firstMaximumPtr, 0x00); - - Marshal.WriteInt32(_secondCurrentPtr, 0x00); - Marshal.WriteInt32(_secondMaximumPtr, 0x00); + _firstTextPtr.WriteValue($"Processing Mod: N/A"); + _secondTextPtr.WriteValue("Receiving Git Objects: {0} / {3}"); // Show the dialog. @@ -635,14 +608,13 @@ await ModService.InstallGit CurrentConfig, new TransferProgressHandler((progress) => { - var _fetchFirstBytes = Encoding.Default.GetBytes($"Processing Mod: {_fetchMod}" + "\x00"); - Marshal.Copy(_fetchFirstBytes, 0, _firstTextPtr, _fetchFirstBytes.Length); + _firstTextPtr.WriteValue($"Processing Mod: {_fetchMod}"); - Marshal.WriteInt32(_firstCurrentPtr, i + 0x01); - Marshal.WriteInt32(_firstMaximumPtr, _fetchMultiInstall.Length); + _firstCurrentPtr.WriteValue(i + 0x01); + _firstMaximumPtr.WriteValue(_fetchMultiInstall.Length); - Marshal.WriteInt32(_secondCurrentPtr, progress.ReceivedObjects); - Marshal.WriteInt32(_secondMaximumPtr, progress.TotalObjects); + _secondCurrentPtr.WriteValue( progress.ReceivedObjects); + _secondMaximumPtr.WriteValue( progress.TotalObjects); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -663,14 +635,14 @@ await ModService.InstallGit // Free all the pointers allocated. - Marshal.FreeHGlobal(_secondTextPtr); _secondTextPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstTextPtr); _firstTextPtr = IntPtr.Zero; + _secondTextPtr.Dispose(); + _firstTextPtr.Dispose(); - Marshal.FreeHGlobal(_secondMaximumPtr); _secondMaximumPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_secondCurrentPtr); _secondCurrentPtr = IntPtr.Zero; + _secondMaximumPtr.Dispose(); + _secondCurrentPtr.Dispose(); - Marshal.FreeHGlobal(_firstMaximumPtr); _firstCurrentPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstCurrentPtr); _firstCurrentPtr = IntPtr.Zero; + _firstMaximumPtr.Dispose(); + _firstCurrentPtr.Dispose(); // Return the result. @@ -681,6 +653,10 @@ await ModService.InstallGit if (ModService.CancelToken.IsCancellationRequested || _fetchInstallResult == 0x03) return false; + var _fetchGameNames = _fetchTopLevel.GameBox.Items.OfType() + .Select(x => x.Content as string) + .ToList(); + switch (_fetchInstallResult) { case 0x00: @@ -690,6 +666,8 @@ await ModService.InstallGit var _fetchModAuthor = ""; var _fetchModName = ""; + var _fetchGitMod = false; + if (_fetchFileInfo.Exists) _fetchModFolder = Path.Combine(_fetchModPath, $"local/{Path.GetFileNameWithoutExtension(_fetchResult)}"); @@ -702,6 +680,7 @@ await ModService.InstallGit _fetchModName = _fetchModName.Split('@').First(); _fetchModFolder = Path.Combine(_fetchModPath, _fetchModAuthor, _fetchModName); + _fetchGitMod = true; } var _fetchPathGit = Path.Combine(_fetchModFolder, ".git"); @@ -710,12 +689,74 @@ await ModService.InstallGit var _fetchMetadata = Metadata.Read(_fetchYamlName); + if (_fetchMetadata.Game != null) + { + var _fetchMetadataGame = Config.GameShorthand.FirstOrDefault(x => x.Value == _fetchMetadata.Game).Key; + + var _fullNameMetadata = _fetchGameNames[(int)_fetchMetadataGame]; + var _fullNameCurrent = _fetchGameNames[(int)CurrentConfig.Frontend.TargetGame]; + + if (_fetchMetadataGame != CurrentConfig.Frontend.TargetGame) + { + if (Directory.Exists(_fetchModFolder)) + Directory.Delete(_fetchModFolder, true); + + await DialogService.ShowMessage(_fetchTopLevel, "Unsupported Game", $"This mod is made for {_fullNameMetadata} but you are trying to install it for {_fullNameCurrent}!\nPlease check to make sure the selected game matches the mod's requirements.", MessageType.ERROR); + + return false; + } + } + + if (_fetchMetadata.Dependencies != null) + { + var _fetchDependencies = _fetchMetadata.Dependencies; + var _fetchMissingDeps = new List(); + + foreach (var _fetchDependency in _fetchDependencies) + { + var _doesHaveDependency = InstalledMods.FirstOrDefault(x => x.ModGitAddress != null && x.ModGitAddress.ToLower() == _fetchDependency.ToLower()); + + if (_doesHaveDependency == null) + _fetchMissingDeps.Add(_fetchDependency); + } + + if (_fetchMissingDeps.Count > 0) + { + var _fetchDepString = String.Join("\n- ", _fetchMissingDeps); + var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchTopLevel, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); + + if (!_fetchDepQuestion) + { + if (Directory.Exists(_fetchModFolder)) + Directory.Delete(_fetchModFolder, true); + + return false; + } + + else + { + var _fetchInstallStr = String.Join(";", _fetchMissingDeps); + + var _fetchInstall = await Install(_fetchInstallStr); + + if (!_fetchInstall) + { + if (Directory.Exists(_fetchModFolder)) + Directory.Delete(_fetchModFolder, true); + + return false; + } + } + } + } + if (_fetchMetadata.IsValid) { var _modModel = new ModModel { ModTitle = !String.IsNullOrEmpty(_fetchMetadata.Title) ? _fetchMetadata.Title : _fetchModName, ModAuthor = !String.IsNullOrEmpty(_fetchMetadata.OriginalAuthor) ? _fetchMetadata.OriginalAuthor : _fetchModAuthor, + ModGitAddress = _fetchGitMod ? _fetchResult : null, ModDescription = !String.IsNullOrEmpty(_fetchMetadata.Description) ? _fetchMetadata.Description : "This mod does not have a description, but we believe it's pretty cool.", ModPath = _fetchModFolder, ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), @@ -819,6 +860,9 @@ await ModService.InstallGit case 0x02: { + var _fetchIncompatibleList = new List>(); + var _fetchNoDependencyList = new List(); + foreach (var _fetchSuccess in _fetchSuccessList) { var _fetchModAuthor = _fetchSuccess.Split('/').First(); @@ -836,6 +880,69 @@ await ModService.InstallGit var _fetchMetadata = Metadata.Read(_fetchYamlName); + if (_fetchMetadata.Game != null) + { + var _fetchMetadataGame = Config.GameShorthand.FirstOrDefault(x => x.Value == _fetchMetadata.Game).Key; + + var _fullNameMetadata = _fetchGameNames[(int)_fetchMetadataGame]; + var _fullNameCurrent = _fetchGameNames[(int)CurrentConfig.Frontend.TargetGame]; + + if (_fetchMetadataGame != CurrentConfig.Frontend.TargetGame) + { + if (Directory.Exists(_fetchCurrentPath)) + Directory.Delete(_fetchCurrentPath, true); + + var _fetchGameTuple = new Tuple(_fetchSuccess, _fullNameMetadata); + _fetchIncompatibleList.Add(_fetchGameTuple); + + continue; + } + } + + if (_fetchMetadata.Dependencies != null) + { + var _fetchDependencies = _fetchMetadata.Dependencies; + var _fetchMissingDeps = new List(); + + foreach (var _fetchDependency in _fetchDependencies) + { + var _doesHaveDependency = InstalledMods.FirstOrDefault(x => x.ModGitAddress != null && x.ModGitAddress.ToLower() == _fetchDependency.ToLower()); + + if (_doesHaveDependency == null) + _fetchMissingDeps.Add(_fetchDependency); + } + + if (_fetchMissingDeps.Count > 0) + { + var _fetchDepString = String.Join("\n- ", _fetchMissingDeps); + var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchTopLevel, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); + + if (!_fetchDepQuestion) + { + if (Directory.Exists(_fetchCurrentPath)) + Directory.Delete(_fetchCurrentPath, true); + + _fetchNoDependencyList.Add(_fetchSuccess); + continue; + } + + else + { + var _fetchInstallStr = String.Join(";", _fetchMissingDeps); + var _fetchInstall = await Install(_fetchInstallStr); + + if (!_fetchInstall) + { + if (Directory.Exists(_fetchCurrentPath)) + Directory.Delete(_fetchCurrentPath, true); + + _fetchNoDependencyList.Add(_fetchSuccess); + continue; + } + } + } + } + if (_fetchMetadata.IsValid) { var _modModel = new ModModel @@ -843,6 +950,7 @@ await ModService.InstallGit ModTitle = _fetchMetadata.Title, ModAuthor = _fetchMetadata.OriginalAuthor, ModDescription = _fetchMetadata.Description, + ModGitAddress = _fetchSuccess, ModPath = _fetchCurrentPath, ModFilesList = _fetchMetadata.Assets.Select(x => x.Name).ToArray(), ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, @@ -1148,30 +1256,19 @@ private async Task Build() // Allocate all the pointers to use for the progress bars. - var _firstCurrentPtr = Marshal.AllocHGlobal(4); - var _firstMaximumPtr = Marshal.AllocHGlobal(4); + var _firstCurrentPtr = new SafePtr(0x04); + var _firstMaximumPtr = new SafePtr(0x04); - var _secondCurrentPtr = Marshal.AllocHGlobal(4); - var _secondMaximumPtr = Marshal.AllocHGlobal(4); + var _secondCurrentPtr = new SafePtr(0x04); + var _secondMaximumPtr = new SafePtr(0x04); - var _firstTextPtr = Marshal.AllocHGlobal(256); - var _secondTextPtr = Marshal.AllocHGlobal(256); + var _firstTextPtr = new SafePtr(0xFF); + var _secondTextPtr = new SafePtr(0xFF); // Write the default strings to the pointers. - var _fetchFirstBytes = Encoding.Default.GetBytes("Currently building: N/A" + "\x00"); - var _fetchSecondBytes = Encoding.Default.GetBytes("Processing Files: {0} / {3}" + "\x00"); - - Marshal.Copy(_fetchFirstBytes, 0, _firstTextPtr, _fetchFirstBytes.Length); - Marshal.Copy(_fetchSecondBytes, 0, _secondTextPtr, _fetchSecondBytes.Length); - - // Write the default ints to the pointers. - - Marshal.WriteInt32(_firstCurrentPtr, 0x00); - Marshal.WriteInt32(_firstMaximumPtr, 0x00); - - Marshal.WriteInt32(_secondCurrentPtr, 0x00); - Marshal.WriteInt32(_secondMaximumPtr, 0x00); + _firstTextPtr.WriteValue("Currently building: N/A"); + _secondTextPtr.WriteValue("Processing Files: {0} / {3}"); // Show the dialog. @@ -1189,11 +1286,10 @@ await ModService.Build { _currentModName = currModName; - var _fetchFirstBytes = Encoding.Default.GetBytes($"Currently building: {currModName}" + "\x00"); - Marshal.Copy(_fetchFirstBytes, 0, _firstTextPtr, _fetchFirstBytes.Length); + _firstTextPtr.WriteValue($"Currently building: {currModName}"); - Marshal.WriteInt32(_firstCurrentPtr, procMod); - Marshal.WriteInt32(_firstMaximumPtr, totalMod); + _firstCurrentPtr.WriteValue(procMod); + _firstMaximumPtr.WriteValue(totalMod); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1203,8 +1299,8 @@ await ModService.Build (int processed, int total) => { - Marshal.WriteInt32(_secondCurrentPtr, processed); - Marshal.WriteInt32(_secondMaximumPtr, total); + _secondCurrentPtr.WriteValue(processed); + _secondMaximumPtr.WriteValue(total); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1215,14 +1311,14 @@ await ModService.Build // Free all the pointers allocated. - Marshal.FreeHGlobal(_secondTextPtr); _secondTextPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstTextPtr); _firstTextPtr = IntPtr.Zero; + _secondTextPtr.Dispose(); + _firstTextPtr.Dispose(); - Marshal.FreeHGlobal(_secondMaximumPtr); _secondMaximumPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_secondCurrentPtr); _secondCurrentPtr = IntPtr.Zero; + _secondMaximumPtr.Dispose(); + _secondCurrentPtr.Dispose(); - Marshal.FreeHGlobal(_firstMaximumPtr); _firstCurrentPtr = IntPtr.Zero; - Marshal.FreeHGlobal(_firstCurrentPtr); _firstCurrentPtr = IntPtr.Zero; + _firstMaximumPtr.Dispose(); + _firstCurrentPtr.Dispose(); if (_buildResult == 0x00) return true; diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index a10dc14c2..2f24132b2 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -97,7 +97,7 @@ - + From c8cc60058b8c08eacbaadfb5e17363e7e0fb2a4a Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 20 Sep 2026 20:22:46 +0200 Subject: [PATCH 135/175] [Mod Manager] - Make SafePtr a bit easier to work on, preset context testing. --- OpenKh.Tools.ModManager/Classes/SafePtr.cs | 101 +++++------ .../Services/DialogService.cs | 12 +- .../ViewModels/MainViewModel.cs | 168 +++++++++++++----- OpenKh.Tools.ModManager/Views/MainView.axaml | 3 +- 4 files changed, 169 insertions(+), 115 deletions(-) diff --git a/OpenKh.Tools.ModManager/Classes/SafePtr.cs b/OpenKh.Tools.ModManager/Classes/SafePtr.cs index 317d8a090..c49be8289 100644 --- a/OpenKh.Tools.ModManager/Classes/SafePtr.cs +++ b/OpenKh.Tools.ModManager/Classes/SafePtr.cs @@ -11,80 +11,63 @@ namespace OpenKh.Tools.ModManager.Classes public class SafePtr : IDisposable { protected IntPtr _internalPtr; - protected int _internalSize; + protected Type _internalType; - public SafePtr(int size) + public SafePtr(object value) { - _internalSize = size; - _internalPtr = Marshal.AllocHGlobal(_internalSize); + _internalType = value.GetType(); + _internalPtr = Marshal.AllocHGlobal(value is string ? 255 : Marshal.SizeOf(_internalType)); - if (_internalSize == 0x04) - Marshal.WriteInt32(_internalPtr, 0x00); + if (value is byte) + Marshal.WriteByte(_internalPtr, (byte)value); - else if (_internalSize == 0xFF) - Marshal.Copy(new byte[0xFF], 0, _internalPtr, 0xFF); + else if (value is short) + Marshal.WriteInt16(_internalPtr, (short)value); - else - throw new InvalidCastException("Invalid pointer length! Supported values are 0x04 [Int] and 0xFF [String]"); - } + else if (value is int) + Marshal.WriteInt32(_internalPtr, (int)value); - public object? GetValue() - { - if (_internalPtr == IntPtr.Zero) - return null; + else if (value is long) + Marshal.WriteInt64(_internalPtr, (long)value); - switch (_internalSize) + else if (value is string) { - case 0x04: - return Marshal.ReadInt32(_internalPtr); - - case 0xFF: - { - var _fetchValue = new byte[0xFF]; - Marshal.Copy(_internalPtr, _fetchValue, 0, 0xFF); - return Encoding.Default.GetString(_fetchValue, 0x00, _fetchValue.IndexOf(0x00)); - } - - default: - return null; + var _fetchString = (value as string) + "\x00"; + var _fetchEncoding = Encoding.Default.GetBytes(_fetchString); + Marshal.Copy(_fetchEncoding, 0, _internalPtr, _fetchEncoding.Length); } } - public bool WriteValue(object value) + public static implicit operator SafePtr(byte value) => new SafePtr(value); + public static implicit operator SafePtr(short value) => new SafePtr(value); + public static implicit operator SafePtr(int value) => new SafePtr(value); + public static implicit operator SafePtr(long value) => new SafePtr(value); + public static implicit operator SafePtr(string value) => new SafePtr(value); + + public void operator /= (byte value) => Marshal.WriteByte(_internalPtr, value); + public void operator /= (short value) => Marshal.WriteInt16(_internalPtr, value); + public void operator /= (int value) => Marshal.WriteInt32(_internalPtr, value); + public void operator /= (long value) => Marshal.WriteInt64(_internalPtr, value); + public void operator /= (string value) { - if (_internalPtr == IntPtr.Zero) - return false; - - switch (_internalSize) - { - case 0x04: - { - if (value is int) - { - Marshal.WriteInt32(_internalPtr, (int)value); - return true; - } - - else - return false; - } + var _fetchString = value + "\x00"; + var _fetchEncoding = Encoding.Default.GetBytes(_fetchString); + Marshal.Copy(_fetchEncoding, 0, _internalPtr, _fetchEncoding.Length); + } - case 0xFF: - { - if (value is string) - { - var _fetchTextBytes = Encoding.Default.GetBytes((value as string) + "\x00"); - Marshal.Copy(_fetchTextBytes, 0, _internalPtr, _fetchTextBytes.Length); - return true; - } + public static implicit operator byte?(SafePtr value) => value == null || value._internalPtr == IntPtr.Zero ? null : Marshal.ReadByte(value._internalPtr); + public static implicit operator short?(SafePtr value) => value == null || value._internalPtr == IntPtr.Zero ? null : Marshal.ReadInt16(value._internalPtr); + public static implicit operator int?(SafePtr value) => value == null || value._internalPtr == IntPtr.Zero ? null : Marshal.ReadInt32(value._internalPtr); + public static implicit operator long?(SafePtr value) => value == null || value._internalPtr == IntPtr.Zero ? null : Marshal.ReadInt64(value._internalPtr); + public static implicit operator string?(SafePtr value) + { + var _fetchValue = new byte[0xFF]; - else - return false; - } + if (value == null || value._internalPtr == IntPtr.Zero) + return null; - default: - return false; - } + Marshal.Copy(value._internalPtr, _fetchValue, 0, 0xFF); + return Encoding.Default.GetString(_fetchValue, 0x00, _fetchValue.IndexOf(0x00)); } public void Dispose() diff --git a/OpenKh.Tools.ModManager/Services/DialogService.cs b/OpenKh.Tools.ModManager/Services/DialogService.cs index 6637d2c74..1522fb3f0 100644 --- a/OpenKh.Tools.ModManager/Services/DialogService.cs +++ b/OpenKh.Tools.ModManager/Services/DialogService.cs @@ -30,8 +30,8 @@ public async static Task ShowProgress(Window? owner, string title, string { while (true) { - var _fetchFirstCurrent = firstProgCurr.GetValue() as int?; - var _fetchFirstMaximum = firstProgMax.GetValue() as int?; + int? _fetchFirstCurrent = firstProgCurr; + int? _fetchFirstMaximum = firstProgMax; if (_fetchFirstCurrent == null || _fetchFirstMaximum == null) { @@ -43,11 +43,11 @@ public async static Task ShowProgress(Window? owner, string title, string break; } - var _fetchSecondCurrent = secondProgCurr != null ? secondProgCurr.GetValue() as int? : null; - var _fetchSecondMaximum = secondProgMax != null ? secondProgMax.GetValue() as int? : null; + int? _fetchSecondCurrent = secondProgCurr != null ? secondProgCurr : null; + int? _fetchSecondMaximum = secondProgMax != null ? secondProgMax : null; - var _fetchFirstString = firstProgText != null ? firstProgText.GetValue() as string : null; - var _fetchSecondString = secondProgText != null ? secondProgText.GetValue() as string : null; + string? _fetchFirstString = firstProgText != null ? firstProgText : null; + string? _fetchSecondString = secondProgText != null ? secondProgText : null; Dispatcher.UIThread.Post(() => { diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index d0781ef27..b0b7bb7af 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -3,16 +3,17 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media.Imaging; using Avalonia.Platform; using Avalonia.Platform.Storage; -using Avalonia.Input; using Avalonia.Threading; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using LibGit2Sharp; using LibGit2Sharp.Handlers; +using OpenKh.Bbs.SystemData; using OpenKh.Patcher; using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Dialogs; @@ -225,6 +226,25 @@ public void InitializeView() IsChecked = _fetchPreset == _preset.PresetName, Command = SwitchPresetCommand, CommandParameter = _preset.PresetName, + ContextMenu = new ContextMenu + { + ItemsSource = new[] + { + new MenuItem + { + Header = "Rename Preset...", + Command = RenamePresetCommand, + CommandParameter = _preset.PresetName + }, + + new MenuItem + { + Header = "Delete Preset...", + Command = DeletePresetCommand, + CommandParameter = _preset.PresetName + } + } + }, InputGesture = new KeyGesture(_fetchKey, KeyModifiers.Alt) }; @@ -497,13 +517,10 @@ private async Task Install(string? inputParameter) if (_fetchFileInfo.Exists) { - var _progressCurrentPtr = new SafePtr(0x04); - var _progressMaximumPtr = new SafePtr(0x04); - - var _progressTextPtr = new SafePtr(0xFF); + SafePtr _progressCurrentPtr = 0x00; + SafePtr _progressMaximumPtr = 0x00; - // Write the default string to the pointer. - _progressTextPtr.WriteValue("Processing Local Files: {0} / {3}"); + SafePtr _progressTextPtr = "Processing Local Files: {0} / {3}"; var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); @@ -514,8 +531,8 @@ await ModService.InstallLocal CurrentConfig, (int processed, int total) => { - _progressCurrentPtr.WriteValue(processed); - _progressMaximumPtr.WriteValue(total); + _progressCurrentPtr /= processed; + _progressMaximumPtr /= total; if (ModService.CancelToken.IsCancellationRequested) return false; @@ -540,13 +557,10 @@ await ModService.InstallLocal { // Allocate the pointers necessary. - var _progressCurrentPtr = new SafePtr(0x04); - var _progressMaximumPtr = new SafePtr(0x04); - - var _progressTextPtr = new SafePtr(0xFF); + SafePtr _progressCurrentPtr = 0x00; + SafePtr _progressMaximumPtr = 0x00; - // Write the default string to the pointer. - _progressTextPtr.WriteValue("Receiving Git Objects: {0} / {3}"); + SafePtr _progressTextPtr = "Receiving Git Objects: {0} / {3}"; var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); @@ -557,8 +571,8 @@ await ModService.InstallGit CurrentConfig, new TransferProgressHandler((progress) => { - _progressCurrentPtr.WriteValue(progress.ReceivedObjects); - _progressMaximumPtr.WriteValue(progress.TotalObjects); + _progressCurrentPtr /= progress.ReceivedObjects; + _progressMaximumPtr /= progress.TotalObjects; if (ModService.CancelToken.IsCancellationRequested) return false; @@ -579,19 +593,14 @@ await ModService.InstallGit { // Allocate all the pointers to use for the progress bars. - var _firstCurrentPtr = new SafePtr(0x04); - var _firstMaximumPtr = new SafePtr(0x04); + SafePtr _firstCurrentPtr = 0x00; + SafePtr _firstMaximumPtr = 0x00; - var _secondCurrentPtr = new SafePtr(0x04); - var _secondMaximumPtr = new SafePtr(0x04); + SafePtr _secondCurrentPtr = 0x00; + SafePtr _secondMaximumPtr = 0x00; - var _firstTextPtr = new SafePtr(0xFF); - var _secondTextPtr = new SafePtr(0xFF); - - // Write the default strings to the pointers. - - _firstTextPtr.WriteValue($"Processing Mod: N/A"); - _secondTextPtr.WriteValue("Receiving Git Objects: {0} / {3}"); + SafePtr _firstTextPtr = $"Processing Mod: N/A"; + SafePtr _secondTextPtr = "Receiving Git Objects: {0} / {3}"; // Show the dialog. @@ -608,13 +617,13 @@ await ModService.InstallGit CurrentConfig, new TransferProgressHandler((progress) => { - _firstTextPtr.WriteValue($"Processing Mod: {_fetchMod}"); + _firstTextPtr /= $"Processing Mod: {_fetchMod}"; - _firstCurrentPtr.WriteValue(i + 0x01); - _firstMaximumPtr.WriteValue(_fetchMultiInstall.Length); + _firstCurrentPtr /= i + 0x01; + _firstMaximumPtr /= _fetchMultiInstall.Length; - _secondCurrentPtr.WriteValue( progress.ReceivedObjects); - _secondMaximumPtr.WriteValue( progress.TotalObjects); + _secondCurrentPtr /= progress.ReceivedObjects; + _secondMaximumPtr /= progress.TotalObjects; if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1256,19 +1265,14 @@ private async Task Build() // Allocate all the pointers to use for the progress bars. - var _firstCurrentPtr = new SafePtr(0x04); - var _firstMaximumPtr = new SafePtr(0x04); - - var _secondCurrentPtr = new SafePtr(0x04); - var _secondMaximumPtr = new SafePtr(0x04); - - var _firstTextPtr = new SafePtr(0xFF); - var _secondTextPtr = new SafePtr(0xFF); + SafePtr _firstCurrentPtr = 0x00; + SafePtr _firstMaximumPtr = 0x00; - // Write the default strings to the pointers. + SafePtr _secondCurrentPtr = 0x00; + SafePtr _secondMaximumPtr = 0x00; - _firstTextPtr.WriteValue("Currently building: N/A"); - _secondTextPtr.WriteValue("Processing Files: {0} / {3}"); + SafePtr _firstTextPtr = "Currently building: N/A"; + SafePtr _secondTextPtr = "Processing Files: {0} / {3}"; // Show the dialog. @@ -1286,10 +1290,10 @@ await ModService.Build { _currentModName = currModName; - _firstTextPtr.WriteValue($"Currently building: {currModName}"); + _firstTextPtr /= $"Currently building: {currModName}"; - _firstCurrentPtr.WriteValue(procMod); - _firstMaximumPtr.WriteValue(totalMod); + _firstCurrentPtr /= procMod; + _firstMaximumPtr /= totalMod; if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1299,8 +1303,8 @@ await ModService.Build (int processed, int total) => { - _secondCurrentPtr.WriteValue(processed); - _secondMaximumPtr.WriteValue(total); + _secondCurrentPtr /= processed; + _secondMaximumPtr /= total; if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1527,6 +1531,25 @@ private async Task CreatePreset() IsChecked = false, Command = SwitchPresetCommand, CommandParameter = _fetchResult, + ContextMenu = new ContextMenu + { + ItemsSource = new[] + { + new MenuItem + { + Header = "Rename Preset...", + Command = RenamePresetCommand, + CommandParameter = _fetchResult + }, + + new MenuItem + { + Header = "Delete Preset...", + Command = DeletePresetCommand, + CommandParameter = _fetchResult + } + } + }, InputGesture = new KeyGesture(Key.D0, KeyModifiers.Alt) }); } @@ -1540,6 +1563,25 @@ private async Task CreatePreset() IsChecked = false, Command = SwitchPresetCommand, CommandParameter = _fetchResult, + ContextMenu = new ContextMenu + { + ItemsSource = new[] + { + new MenuItem + { + Header = "Rename Preset...", + Command = RenamePresetCommand, + CommandParameter = _fetchResult + }, + + new MenuItem + { + Header = "Delete Preset...", + Command = DeletePresetCommand, + CommandParameter = _fetchResult + } + } + }, InputGesture = new KeyGesture((Key)(34 + PresetItems.Count - 0x03), KeyModifiers.Alt) }); } @@ -1582,6 +1624,34 @@ private async Task SwitchPreset(string input) return false; } + + [RelayCommand] + private async Task DeletePreset(string input) + { + if (CurrentConfig != null) + { + var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; + var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + + return true; + } + + return false; + } + + [RelayCommand] + private async Task RenamePreset(string input) + { + if (CurrentConfig != null) + { + var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; + var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + + return true; + } + + return false; + } } #pragma warning restore CS4014 diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 2f24132b2..31145627a 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -93,7 +93,8 @@ - + + From 86a2841dc186a463ddf02ba5ef7d58678592d4bc Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 20 Sep 2026 20:23:44 +0200 Subject: [PATCH 136/175] [Mod Manager] - Just in case. --- OpenKh.Tools.ModManager/Classes/SafePtr.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/OpenKh.Tools.ModManager/Classes/SafePtr.cs b/OpenKh.Tools.ModManager/Classes/SafePtr.cs index c49be8289..b8dbaae33 100644 --- a/OpenKh.Tools.ModManager/Classes/SafePtr.cs +++ b/OpenKh.Tools.ModManager/Classes/SafePtr.cs @@ -36,6 +36,9 @@ public SafePtr(object value) var _fetchEncoding = Encoding.Default.GetBytes(_fetchString); Marshal.Copy(_fetchEncoding, 0, _internalPtr, _fetchEncoding.Length); } + + else + throw new InvalidCastException("SafePtr does not support this type!"); } public static implicit operator SafePtr(byte value) => new SafePtr(value); From 4536adc49e3858ff3ddef143009b27a3803d7f97 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 20 Sep 2026 20:31:09 +0200 Subject: [PATCH 137/175] [Mod Manager] - Separate mod initialization from view. --- .../ViewModels/MainViewModel.cs | 391 +++++++++--------- 1 file changed, 200 insertions(+), 191 deletions(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index b0b7bb7af..1637634cd 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -122,6 +122,204 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent public MainViewModel() => InitializeView(); + public void InitializeMods() + { + if (CurrentConfig != null) + { + InstalledMods = null; + HasModsInstalled = false; + + // Construct the mod folder path for the specified game. + + var _fetchMods = new ObservableCollection(); + var _fetchModsPath = PathService.ResolveMod(CurrentConfig); + + // For each directory that exists in the mod folder: + + foreach (var _fetchDirectory in Directory.EnumerateDirectories(_fetchModsPath)) + { + foreach (var _fetchChild in Directory.EnumerateDirectories(_fetchDirectory)) + { + // Construct the paths to YAML and PNG files. + var _fetchPathYaml = Path.Combine(_fetchChild, "mod.yml"); + var _fetchPathIcon = Path.Combine(_fetchChild, "icon.png"); + + var _fetchPathGit = Path.Combine(_fetchChild, ".git"); + + // YAML don't do it? Don't do it! + if (!File.Exists(_fetchPathYaml)) + continue; + + // Fetch and read the YAML to parse the metadata. + var _metadata = Metadata.Read(_fetchPathYaml); + + // If the metadata is valid, parse the mod and push it to the ViewModel. + + if (_metadata.IsValid) + { + var _modModel = new ModModel + { + ModTitle = _metadata.Title, + ModAuthor = _metadata.OriginalAuthor, + ModDescription = _metadata.Description, + ModPath = _fetchChild, + ModFilesList = _metadata.Assets.Select(x => x.Name).ToArray(), + ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, + ModActive = true, + ModValid = true + }; + + if (_metadata.Preferences != null) + { + _modModel.ModPreferences = new List(); + + foreach (var _preference in _metadata.Preferences) + { + var _fetchPref = new PreferenceModel + { + Title = _preference.Title, + Key = _preference.Key, + Description = _preference.Description, + Type = _preference.Type, + Options = _preference.Options, + Value = _preference.Value + }; + + _modModel.ModPreferences.Add(_fetchPref); + } + } + + // We have found a Git Repository, let's see what's up. + + Task.Run(() => + { + if (Directory.Exists(_fetchPathGit)) + { + if (Repository.IsValid(_fetchPathGit)) + { + var _fetchGit = new Repository(_fetchPathGit); + + try + { + if (!_fetchGit.Info.IsHeadDetached) + { + var _fetchRemote = _fetchGit.Network.Remotes["origin"]; + + _modModel.ModSource = new Uri(_fetchRemote.Url); + _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); + + _modModel.ModPlatform = _modModel.ModSource.Host; + + Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); + + var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; + _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; + + } + } + + catch (LibGit2SharpException) { } + + _fetchGit.Dispose(); + var _fetchGitDir = new DirectoryInfo(_fetchPathGit); + + foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) + { + try + { + if (_fetchFile.Exists) + _fetchFile.Attributes &= ~FileAttributes.ReadOnly; + } + + catch (Exception) { } + } + } + } + }); + + _fetchMods.Add(_modModel); + } + + // Otherwise, make it known that the mod sucks ASS and is no good for us, but still push it to the ViewModel so we know about it :D + + else + { + var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); + + var _modModel = new ModModel + { + ModTitle = _metadata.Title, + ModAuthor = "This mod is invalid!", + ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", + ModIcon = new Bitmap(AssetLoader.Open(uri)), + ModPath = _fetchChild, + ModActive = false, + ModValid = false + }; + + _fetchMods.Add(_modModel); + } + } + } + + // When all mods are loaded, start processing the "mod memory" for the targeted game.\ + // If the mod memory does not exist, assume default order and mark all active. + + var _fetchModMemoryPath = Path.Combine(_fetchModsPath, "mod_memory.yml"); + + if (File.Exists(_fetchModMemoryPath)) + { + // Fetch the raw YAML data and deserialize it. + var _fetchRawYaml = File.ReadAllText(_fetchModMemoryPath); + var _fetchModMemory = YamlSerializer.Deserialize>(_fetchRawYaml); + + // Make a temporary array for us to order the mods. + // And a temporary list for us to commit the mods. + + var _tempModArray = new ModModel[_fetchMods.Count]; + var _tempModList = new List(); + + foreach (var _fetchMemory in _fetchModMemory) + { + var _fetchMod = _fetchMods.FirstOrDefault(x => ModService.ResolveMD5(x, CurrentConfig) == _fetchMemory.ModHash); + + if (_fetchMod == null || !_fetchMod.ModValid) + continue; + + _fetchMod.ModActive = _fetchMemory.ModActive; + _tempModArray[_fetchMemory.ModIndex] = _fetchMod; + + _fetchMods.Remove(_fetchMod); + } + + // Add all the existing mods from the array. + _tempModList.AddRange(_tempModArray.Where(x => x != null)); + + // Add all the valid mods from the original list which didn't exist on the mod memory. + _tempModList.AddRange(_fetchMods.Where(x => x.ModValid)); + + // Add all the invalid mods from the original list which didn't exist on the mod memory. + _tempModList.AddRange(_fetchMods.Where(x => !x.ModValid)); + + // Sync to the actual installed mods collection. + InstalledMods = new ObservableCollection(_tempModList); + } + + else + InstalledMods = _fetchMods; + + // Register mod property event to handle mod memory. + InstalledMods.CollectionChanged += OnModPropertyChanged; + + // If there is at least one mod, select the first mod and declare we have mods. + if (InstalledMods.Count > 0) + { + HasModsInstalled = true; + CurrentMod = InstalledMods.First(); + } + } + } + public void InitializeView() { Initialized = false; @@ -278,196 +476,7 @@ public void InitializeView() CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; } - // === Mod Parsing and Verification === // - - // Construct the mod folder path for the specified game. - - var _fetchMods = new ObservableCollection(); - var _fetchModsPath = PathService.ResolveMod(CurrentConfig); - - // For each directory that exists in the mod folder: - - foreach (var _fetchDirectory in Directory.EnumerateDirectories(_fetchModsPath)) - { - foreach (var _fetchChild in Directory.EnumerateDirectories(_fetchDirectory)) - { - // Construct the paths to YAML and PNG files. - var _fetchPathYaml = Path.Combine(_fetchChild, "mod.yml"); - var _fetchPathIcon = Path.Combine(_fetchChild, "icon.png"); - - var _fetchPathGit = Path.Combine(_fetchChild, ".git"); - - // YAML don't do it? Don't do it! - if (!File.Exists(_fetchPathYaml)) - continue; - - // Fetch and read the YAML to parse the metadata. - var _metadata = Metadata.Read(_fetchPathYaml); - - // If the metadata is valid, parse the mod and push it to the ViewModel. - - if (_metadata.IsValid) - { - var _modModel = new ModModel - { - ModTitle = _metadata.Title, - ModAuthor = _metadata.OriginalAuthor, - ModDescription = _metadata.Description, - ModPath = _fetchChild, - ModFilesList = _metadata.Assets.Select(x => x.Name).ToArray(), - ModIcon = File.Exists(_fetchPathIcon) ? new Bitmap(_fetchPathIcon) : null, - ModActive = true, - ModValid = true - }; - - if (_metadata.Preferences != null) - { - _modModel.ModPreferences = new List(); - - foreach (var _preference in _metadata.Preferences) - { - var _fetchPref = new PreferenceModel - { - Title = _preference.Title, - Key = _preference.Key, - Description = _preference.Description, - Type = _preference.Type, - Options = _preference.Options, - Value = _preference.Value - }; - - _modModel.ModPreferences.Add(_fetchPref); - } - } - - // We have found a Git Repository, let's see what's up. - - Task.Run(() => - { - if (Directory.Exists(_fetchPathGit)) - { - if (Repository.IsValid(_fetchPathGit)) - { - var _fetchGit = new Repository(_fetchPathGit); - - try - { - if (!_fetchGit.Info.IsHeadDetached) - { - var _fetchRemote = _fetchGit.Network.Remotes["origin"]; - - _modModel.ModSource = new Uri(_fetchRemote.Url); - _modModel.ModIssues = new Uri(_fetchRemote.Url + "/issues"); - - _modModel.ModPlatform = _modModel.ModSource.Host; - - Commands.Fetch(_fetchGit, _fetchRemote.Name, Array.Empty(), null, null); - - var _fetchBehind = _fetchGit.Head.TrackingDetails.BehindBy; - _modModel.ModBehindBy = _fetchBehind != null ? _fetchBehind.Value : 0; - - } - } - - catch (LibGit2SharpException) { } - - _fetchGit.Dispose(); - var _fetchGitDir = new DirectoryInfo(_fetchPathGit); - - foreach (var _fetchFile in _fetchGitDir.GetFiles("*", SearchOption.AllDirectories)) - { - try - { - if (_fetchFile.Exists) - _fetchFile.Attributes &= ~FileAttributes.ReadOnly; - } - - catch (Exception) { } - } - } - } - }); - - _fetchMods.Add(_modModel); - } - - // Otherwise, make it known that the mod sucks ASS and is no good for us, but still push it to the ViewModel so we know about it :D - - else - { - var uri = new Uri("avares://OpenKh.Tools.ModManager/Assets/invalid_mod.png"); - - var _modModel = new ModModel - { - ModTitle = _metadata.Title, - ModAuthor = "This mod is invalid!", - ModDescription = "This mod contains errors within its YAML file. Please check the formatting!", - ModIcon = new Bitmap(AssetLoader.Open(uri)), - ModPath = _fetchChild, - ModActive = false, - ModValid = false - }; - - _fetchMods.Add(_modModel); - } - } - } - - // When all mods are loaded, start processing the "mod memory" for the targeted game.\ - // If the mod memory does not exist, assume default order and mark all active. - - var _fetchModMemoryPath = Path.Combine(_fetchModsPath, "mod_memory.yml"); - - if (File.Exists(_fetchModMemoryPath)) - { - // Fetch the raw YAML data and deserialize it. - var _fetchRawYaml = File.ReadAllText(_fetchModMemoryPath); - var _fetchModMemory = YamlSerializer.Deserialize>(_fetchRawYaml); - - // Make a temporary array for us to order the mods. - // And a temporary list for us to commit the mods. - - var _tempModArray = new ModModel[_fetchMods.Count]; - var _tempModList = new List(); - - foreach (var _fetchMemory in _fetchModMemory) - { - var _fetchMod = _fetchMods.FirstOrDefault(x => ModService.ResolveMD5(x, CurrentConfig) == _fetchMemory.ModHash); - - if (_fetchMod == null || !_fetchMod.ModValid) - continue; - - _fetchMod.ModActive = _fetchMemory.ModActive; - _tempModArray[_fetchMemory.ModIndex] = _fetchMod; - - _fetchMods.Remove(_fetchMod); - } - - // Add all the existing mods from the array. - _tempModList.AddRange(_tempModArray.Where(x => x != null)); - - // Add all the valid mods from the original list which didn't exist on the mod memory. - _tempModList.AddRange(_fetchMods.Where(x => x.ModValid)); - - // Add all the invalid mods from the original list which didn't exist on the mod memory. - _tempModList.AddRange(_fetchMods.Where(x => !x.ModValid)); - - // Sync to the actual installed mods collection. - InstalledMods = new ObservableCollection(_tempModList); - } - - else - InstalledMods = _fetchMods; - - // Register mod property event to handle mod memory. - InstalledMods.CollectionChanged += OnModPropertyChanged; - - // If there is at least one mod, select the first mod and declare we have mods. - if (InstalledMods.Count > 0) - { - HasModsInstalled = true; - CurrentMod = InstalledMods.First(); - } + InitializeMods(); // Initialization is complete. Initialized = true; @@ -1617,7 +1626,7 @@ private async Task SwitchPreset(string input) return false; CurrentConfig.Frontend.TargetPreset[_fetchGame] = input; - InitializeView(); + InitializeMods(); return true; } From a669c69e5e3f8589302c75ad79d1b65658996bbb Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 20 Sep 2026 21:02:31 +0200 Subject: [PATCH 138/175] [Mod Manager] - Admittedly sloppy Preset work, will adjust. --- .../ViewModels/MainViewModel.cs | 80 ++++++++++++++++++- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 1637634cd..604a8b409 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -34,8 +34,6 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Xe.BinaryMapper; -using static OpenKh.Kh2.Ard.AreaDataScript; -using static System.Net.Mime.MediaTypeNames; namespace OpenKh.Tools.ModManager.ViewModels; @@ -1642,6 +1640,72 @@ private async Task DeletePreset(string input) var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchResult = await DialogService.ShowQuestion(_fetchTopLevel, "Delete this Preset?", $"Are you sure you want to delete the preset \"{input}\"?\nIf it is currently active, the Default preset will be loaded."); + + if (_fetchResult) + { + if (_fetchPreset == input) + { + CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; + (PresetItems[0x02] as MenuItem).IsChecked = true; + } + + var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + + if (File.Exists(_fetchPresetPath)) + { + var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); + var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); + + var _fetchPresetItem = _fetchPresetList.FirstOrDefault(x => x.PresetName == input); + var _fetchPresetMenu = PresetItems.OfType().FirstOrDefault(x => x.Header == input); + + _fetchPresetList.Remove(_fetchPresetItem); + PresetItems.Remove(_fetchPresetMenu); + + if (PresetItems.Count > 3) + { + var _fetchKeyIndex = 34; + + for (int i = 0x03; i < PresetItems.Count; i++) + (PresetItems[i] as MenuItem).InputGesture = new KeyGesture((Key)(_fetchKeyIndex++), KeyModifiers.Alt); + } + + else + { + PresetItems[0x02] = new MenuItem() + { + Header = "No Presets Available.", + IsEnabled = false + }; + } + + Task.Run(() => + { + var _fetchNormalStr = input.ToLower().Replace(" ", "_"); + + foreach (var _fetchChar in Path.GetInvalidFileNameChars()) + _fetchNormalStr = _fetchNormalStr.Replace(_fetchChar, '-'); + + var _fetchFinalPath = Path.Combine(PathService.ResolvePreset(CurrentConfig, false), _fetchNormalStr); + + if (Directory.Exists(_fetchFinalPath)) + Directory.Delete(_fetchFinalPath, true); + + }); + + var _fetchSerial = YamlSerializer.Serialize(_fetchPresetList); + File.WriteAllText(_fetchPresetPath, _fetchSerial); + + InitializeMods(); + } + } + return true; } @@ -1656,7 +1720,17 @@ private async Task RenamePreset(string input) var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; - return true; + var _fetchTopLevel = FetchTopLevel() as Window; + + if (_fetchTopLevel == null) + return false; + + var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Rename Preset", "Please enter a new name for this preset.", "Rename", "", input); + + if (_fetchResult != null) + { + return true; + } } return false; From 1722401b8d16a2041c81d0de57cc4cb718b6d987 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 21 Sep 2026 01:12:10 +0200 Subject: [PATCH 139/175] [Mod Manager] - Handle presets PROPERLY. --- OpenKh.Tools.ModManager/Models/PresetModel.cs | 25 +- .../ViewModels/MainViewModel.cs | 813 +++++++++--------- OpenKh.Tools.ModManager/Views/MainView.axaml | 2 +- .../Views/MainView.axaml.cs | 4 +- 4 files changed, 433 insertions(+), 411 deletions(-) diff --git a/OpenKh.Tools.ModManager/Models/PresetModel.cs b/OpenKh.Tools.ModManager/Models/PresetModel.cs index cfb004e70..3b28d2f44 100644 --- a/OpenKh.Tools.ModManager/Models/PresetModel.cs +++ b/OpenKh.Tools.ModManager/Models/PresetModel.cs @@ -1,6 +1,7 @@ using OpenKh.Tools.ModManager.Classes; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -9,7 +10,27 @@ namespace OpenKh.Tools.ModManager.Models { public class PresetModel { - public string? PresetName { get; set; } - public Game PresetGame { get; set; } + private string NAME; + private string FOLDER_NAME; + + public string Name + { + get => NAME; + set + { + NAME = value; + + var _fetchNormalStr = value.ToLower().Replace(" ", "_"); + + foreach (var _fetchChar in Path.GetInvalidFileNameChars()) + _fetchNormalStr = _fetchNormalStr.Replace(_fetchChar, '-'); + + FOLDER_NAME = _fetchNormalStr; + } + } + + public string FolderName { get => FOLDER_NAME; set => FOLDER_NAME = value; } + + public Game TargetGame { get; set; } } } diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 604a8b409..c7fc1758c 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -39,34 +39,8 @@ namespace OpenKh.Tools.ModManager.ViewModels; public partial class MainViewModel : ViewModelBase { - [ObservableProperty] - private bool _initialized = false; - - [ObservableProperty] - private ModModel? _currentMod = null; - - [ObservableProperty] - private ObservableCollection? _installedMods = null; - - [ObservableProperty] - private bool _configurationValid = true; - - [ObservableProperty] - private Config? _currentConfig = null; - - [ObservableProperty] - private bool _doesConfigExist = false; - - [ObservableProperty] - private bool _hasModsInstalled = false; + #region EVENT HANDLERS - [ObservableProperty] - private ObservableCollection _presetItems; - - /// - /// Activates whenever InstalledMods has a property change. - /// It commits said changes to the targeted game's mod memory. - /// protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEventArgs e) { // Fetch the modlist from the sender. @@ -105,7 +79,149 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent } } - public TopLevel? FetchTopLevel() + protected void OnPresetChanged(object? sender, NotifyCollectionChangedEventArgs? e) + { + if (CurrentConfig != null) + { + var _fetchTargetGame = CurrentConfig.Frontend.TargetGame; + var _fetchPresetConfig = CurrentConfig.Frontend.TargetPreset; + + if (_fetchPresetConfig != null) + { + var _fetchCurrent = _fetchPresetConfig[(int)_fetchTargetGame]; + + var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + + var _fetchPresetList = sender as ObservableCollection; + var _fetchGameList = _fetchPresetList.Where(x => x.TargetGame == CurrentConfig.Frontend.TargetGame); + + if (PresetMenu == null) + { + PresetMenu = new ObservableCollection() + { + new MenuItem() + { + Header = "Create New Preset...", + Command = CreatePresetCommand, + InputGesture = new KeyGesture(Key.N, KeyModifiers.Alt) + }, + + new Separator(), + + new MenuItem() + { + Header = "No Presets Available", + IsEnabled = false + }, + }; + } + + PresetMenu = new ObservableCollection(PresetMenu.Take(0x03)); + + var _fetchMenu = PresetMenu.OfType(); + var _doesDefaultExist = _fetchMenu.FirstOrDefault(x => x.Header as string == "Default") != null; + + if (_fetchGameList.Count() > 0x00) + { + if (!_doesDefaultExist) + { + PresetMenu[0x02] = new MenuItem + { + Header = "Default", + ToggleType = MenuItemToggleType.Radio, + IsChecked = _fetchCurrent == "", + Command = SwitchPresetCommand, + CommandParameter = null, + InputGesture = new KeyGesture(Key.D, KeyModifiers.Alt) + }; + } + + for (int i = 0; i < _fetchGameList.Count(); i++) + { + var _fetchPreset = _fetchGameList.ElementAt(i); + + PresetMenu.Add(new MenuItem + { + Header = _fetchPreset.Name, + ToggleType = MenuItemToggleType.Radio, + Command = SwitchPresetCommand, + IsChecked = _fetchCurrent == _fetchPreset.Name, + CommandParameter = _fetchPreset, + InputGesture = new KeyGesture(Key.D0 + i, KeyModifiers.Alt), + ContextMenu = new ContextMenu + { + ItemsSource = new[] + { + new MenuItem + { + Header = "Rename Preset...", + Command = RenamePresetCommand, + CommandParameter = _fetchPreset + }, + + new MenuItem + { + Header = "Delete Preset...", + Command = DeletePresetCommand, + CommandParameter = _fetchPreset + } + } + } + }); + } + } + + else + { + PresetMenu[0x02] = new MenuItem + { + Header = "No Presets Available", + IsEnabled = false + }; + } + + var _fetchSerial = YamlSerializer.Serialize(_fetchPresetList); + File.WriteAllText(_fetchPresetPath, _fetchSerial); + } + } + } + + #endregion + + #region PROPERTIES + + [ObservableProperty] + private bool _initialized = false; + + [ObservableProperty] + private ModModel? _currentMod = null; + + [ObservableProperty] + private ObservableCollection? _installedMods = null; + + [ObservableProperty] + private bool _configurationValid = true; + + [ObservableProperty] + private Config? _currentConfig = null; + + [ObservableProperty] + private bool _doesConfigExist = false; + + [ObservableProperty] + private bool _hasModsInstalled = false; + + [ObservableProperty] + private ObservableCollection? _presetMenu; + + [ObservableProperty] + private ObservableCollection? _presetMemory; + + #endregion + + #region HELPER FUNCTIONS + + public MainView? FetchMainWindow() { var _fetchApplication = Avalonia.Application.Current; @@ -115,12 +231,127 @@ protected void OnModPropertyChanged(object? sender, NotifyCollectionChangedEvent var _fetchLifetime = _fetchApplication.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime; var _fetchMainView = _fetchLifetime != null ? _fetchLifetime.MainWindow : null; - return _fetchMainView; + return _fetchMainView as MainView; + } + + #endregion + + #region VIEWMODEL INITIALIZATION + + public MainViewModel() => InitViewModel(); + + public void InitViewModel() + { + Initialized = false; + + CurrentMod = null; + InstalledMods = null; + HasModsInstalled = false; + ConfigurationValid = true; + + PresetMenu = null; + PresetMemory = null; + + // === Configuration Parsing and Verification === // + + // If the config is null, parse it. + // If it does not exist, pop-up the setup wizard. + + if (CurrentConfig == null) + { + var CurrentConfigFile = Path.Combine(AppContext.BaseDirectory, "config.yml"); + + if (File.Exists(CurrentConfigFile)) + { + var CurrentConfigRAW = File.ReadAllText(CurrentConfigFile); + + DoesConfigExist = true; + CurrentConfig = Config.Load(); + } + + else + { + DoesConfigExist = false; + + CurrentConfig = new Config + { + Panacea = new Panacea(), + + Frontend = new Frontend() + { + GamePath = new string[2], + TargetPlatform = Platform.STEAM, + ModBuildType = BuildType.PANACEA, + TargetGame = Game.KINGDOM_HEARTS_II, + TargetPreset = new string[5] + }, + + Emulator = new Emulator() + { + EmuPath = new string[1], + RomPath = new string[3], + } + }; + + CurrentConfig.Commit(); + } + } + + ConfigurationValid = CurrentConfig.IsValid(); + + // === Preset Parsing and Verification === // + + var _fetchPresetConfig = CurrentConfig.Frontend.TargetPreset; + + if (_fetchPresetConfig != null) + { + var _fetchTargetGame = CurrentConfig.Frontend.TargetGame; + var _fetchTargetPreset = _fetchPresetConfig[(int)_fetchTargetGame]; + + var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + + if (File.Exists(_fetchPresetPath)) + { + var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); + var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); + + if (_fetchPresetList != null) + { + PresetMemory = new ObservableCollection(); + PresetMemory.CollectionChanged += OnPresetChanged; + + OnPresetChanged(PresetMemory, null); + + foreach (var _fetchPreset in _fetchPresetList) + PresetMemory.Add(_fetchPreset); + + if (_fetchTargetPreset != null && PresetMenu != null) + { + var _fetchMenu = PresetMenu.OfType(); + var _fetchPresetItem = _fetchMenu.FirstOrDefault(x => x.Header as string == _fetchTargetPreset); + + if (_fetchPresetItem == null) + _fetchPresetConfig[(int)_fetchTargetGame] = ""; + } + } + } + + else + File.WriteAllText(_fetchPresetPath, "[]"); + } + + IterateModlist(); + + // Initialization is complete. + Initialized = true; } - public MainViewModel() => InitializeView(); + #endregion - public void InitializeMods() + #region MOD COMMANDS + + [RelayCommand] + public async Task IterateModlist() { if (CurrentConfig != null) { @@ -315,169 +546,11 @@ public void InitializeMods() HasModsInstalled = true; CurrentMod = InstalledMods.First(); } - } - } - - public void InitializeView() - { - Initialized = false; - - CurrentMod = null; - InstalledMods = null; - HasModsInstalled = false; - ConfigurationValid = true; - - // === Configuration Parsing and Verification === // - - // If the config is null, parse it. - // TODO: If it does not exist, pop-up the setup wizard. - - if (CurrentConfig == null) - { - var CurrentConfigFile = Path.Combine(AppContext.BaseDirectory, "config.yml"); - - if (File.Exists(CurrentConfigFile)) - { - var CurrentConfigRAW = File.ReadAllText(CurrentConfigFile); - - DoesConfigExist = true; - CurrentConfig = Config.Load(); - } - - else - { - DoesConfigExist = false; - - CurrentConfig = new Config - { - Panacea = new Panacea(), - - Frontend = new Frontend() - { - GamePath = new string[2], - TargetPlatform = Platform.STEAM, - ModBuildType = BuildType.PANACEA, - TargetGame = Game.KINGDOM_HEARTS_II, - TargetPreset = new string[5] - }, - - Emulator = new Emulator() - { - EmuPath = new string[1], - RomPath = new string[3], - } - }; - - CurrentConfig.Commit(); - } - } - - ConfigurationValid = CurrentConfig.IsValid(); - - // === Preset Parsing and Verification === // - - PresetItems = new ObservableCollection(); - - PresetItems.Add(new MenuItem() - { - Header = "Create New Preset...", - Command = CreatePresetCommand, - InputGesture = new KeyGesture(Key.N, KeyModifiers.Alt) - }); - - PresetItems.Add(new Separator()); - - var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; - var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; - - var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); - - if (File.Exists(_fetchPresetPath)) - { - var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); - var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); - - if (_fetchPresetList.Count > 0) - { - var _fetchKeyIndex = 34; - - PresetItems.Add(new MenuItem() - { - Header = "Default", - ToggleType = MenuItemToggleType.Radio, - IsChecked = String.IsNullOrEmpty(_fetchPreset), - Command = SwitchPresetCommand, - CommandParameter = "", - InputGesture = new KeyGesture(Key.D, KeyModifiers.Alt) - }); - - foreach (var _preset in _fetchPresetList) - { - var _fetchKey = (Key)_fetchKeyIndex; - - var _fetchMenuItem = new MenuItem() - { - Header = _preset.PresetName, - ToggleType = MenuItemToggleType.Radio, - IsChecked = _fetchPreset == _preset.PresetName, - Command = SwitchPresetCommand, - CommandParameter = _preset.PresetName, - ContextMenu = new ContextMenu - { - ItemsSource = new[] - { - new MenuItem - { - Header = "Rename Preset...", - Command = RenamePresetCommand, - CommandParameter = _preset.PresetName - }, - - new MenuItem - { - Header = "Delete Preset...", - Command = DeletePresetCommand, - CommandParameter = _preset.PresetName - } - } - }, - InputGesture = new KeyGesture(_fetchKey, KeyModifiers.Alt) - }; - - PresetItems.Add(_fetchMenuItem); - - _fetchKeyIndex++; - } - } - - if (CurrentConfig.Frontend.TargetPreset != null) - { - var _tryFetchPreset = _fetchPresetList.FirstOrDefault(x => x.PresetName == _fetchPreset); - - if (_tryFetchPreset == null) - CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; - } - } - - else - File.WriteAllText(_fetchPresetPath, "[]"); - if (PresetItems.Count == 0x02) - { - PresetItems.Add(new MenuItem() - { - Header = "No Presets Available.", - IsEnabled = false - }); - - if (CurrentConfig.Frontend.TargetPreset != null) - CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; + return true; } - InitializeMods(); - - // Initialization is complete. - Initialized = true; + return false; } [RelayCommand] @@ -488,14 +561,14 @@ private async Task Install(string? inputParameter) if (CurrentConfig != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as MainView; + var _fetchMainView = FetchMainWindow(); - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; - var _fetchResult = inputParameter ?? await DialogService.ShowInput(_fetchTopLevel, "Install a new Mod", "Enter the name of the repository to install.", "Install", "Ex. OpenKH/a-very-cool-mod@github.com", null, "Select and Install an Archive or Script", async (inputParent) => + var _fetchResult = inputParameter ?? await DialogService.ShowInput(_fetchMainView, "Install a new Mod", "Enter the name of the repository to install.", "Install", "Ex. OpenKH/a-very-cool-mod@github.com", null, "Select and Install an Archive or Script", async (inputParent) => { - var _fetchFiles = await _fetchTopLevel.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + var _fetchFiles = await _fetchMainView.StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { Title = "Select an Archive or Script File...", AllowMultiple = false, @@ -529,7 +602,7 @@ private async Task Install(string? inputParameter) SafePtr _progressTextPtr = "Processing Local Files: {0} / {3}"; - var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); + var _singleProgress = DialogService.ShowProgress(_fetchMainView, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); _fetchInstallResult = await ModService.InstallLocal @@ -569,7 +642,7 @@ await ModService.InstallLocal SafePtr _progressTextPtr = "Receiving Git Objects: {0} / {3}"; - var _singleProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); + var _singleProgress = DialogService.ShowProgress(_fetchMainView, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _progressCurrentPtr, _progressMaximumPtr, _progressTextPtr); _fetchInstallResult = await ModService.InstallGit @@ -611,7 +684,7 @@ await ModService.InstallGit // Show the dialog. - var _multiProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _firstCurrentPtr, _firstMaximumPtr, _firstTextPtr, _secondCurrentPtr, _secondMaximumPtr, _secondTextPtr); + var _multiProgress = DialogService.ShowProgress(_fetchMainView, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _firstCurrentPtr, _firstMaximumPtr, _firstTextPtr, _secondCurrentPtr, _secondMaximumPtr, _secondTextPtr); for (int i = 0; i < _fetchMultiInstall.Length; i++) { @@ -669,7 +742,7 @@ await ModService.InstallGit if (ModService.CancelToken.IsCancellationRequested || _fetchInstallResult == 0x03) return false; - var _fetchGameNames = _fetchTopLevel.GameBox.Items.OfType() + var _fetchGameNames = _fetchMainView.GameBox.Items.OfType() .Select(x => x.Content as string) .ToList(); @@ -717,7 +790,7 @@ await ModService.InstallGit if (Directory.Exists(_fetchModFolder)) Directory.Delete(_fetchModFolder, true); - await DialogService.ShowMessage(_fetchTopLevel, "Unsupported Game", $"This mod is made for {_fullNameMetadata} but you are trying to install it for {_fullNameCurrent}!\nPlease check to make sure the selected game matches the mod's requirements.", MessageType.ERROR); + await DialogService.ShowMessage(_fetchMainView, "Unsupported Game", $"This mod is made for {_fullNameMetadata} but you are trying to install it for {_fullNameCurrent}!\nPlease check to make sure the selected game matches the mod's requirements.", MessageType.ERROR); return false; } @@ -739,7 +812,7 @@ await ModService.InstallGit if (_fetchMissingDeps.Count > 0) { var _fetchDepString = String.Join("\n- ", _fetchMissingDeps); - var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchTopLevel, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); + var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchMainView, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); if (!_fetchDepQuestion) { @@ -871,7 +944,7 @@ await ModService.InstallGit } case 0x01: - await DialogService.ShowMessage(_fetchTopLevel, "ERROR - Invalid Mod", "This is NOT a valid/compliant Mod Manager Mod. Please make sure it exists and it is valid.", MessageType.ERROR); + await DialogService.ShowMessage(_fetchMainView, "ERROR - Invalid Mod", "This is NOT a valid/compliant Mod Manager Mod. Please make sure it exists and it is valid.", MessageType.ERROR); return false; case 0x02: @@ -931,7 +1004,7 @@ await ModService.InstallGit if (_fetchMissingDeps.Count > 0) { var _fetchDepString = String.Join("\n- ", _fetchMissingDeps); - var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchTopLevel, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); + var _fetchDepQuestion = await DialogService.ShowQuestion(_fetchMainView, "Missing Dependencies", $"This Mod has some dependencies that are not installed.\nTo proceed, all the following dependencies must be installed:\n\n- {_fetchDepString}\n\nDo you want to proceed? [Not doing so will cancel this mod's install.]"); if (!_fetchDepQuestion) { @@ -1064,7 +1137,7 @@ await ModService.InstallGit if (_fetchErroredList.Count > 0) { var _fetchError = String.Join('\n', _fetchErroredList); - await DialogService.ShowMessage(_fetchTopLevel, "Some Mods were invalid!", $"The following mods were invalid and thus were not installed:\n {_fetchError}", MessageType.WARNING); + await DialogService.ShowMessage(_fetchMainView, "Some Mods were invalid!", $"The following mods were invalid and thus were not installed:\n {_fetchError}", MessageType.WARNING); } return true; @@ -1088,12 +1161,12 @@ private async Task Remove() { if (CurrentMod != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; - bool? _fetchResult = await DialogService.ShowQuestion(_fetchTopLevel, "Remove Mod?", "Are you sure you want to remove the selected mod?"); + bool? _fetchResult = await DialogService.ShowQuestion(_fetchMainView, "Remove Mod?", "Are you sure you want to remove the selected mod?"); // If they indeed do: @@ -1177,7 +1250,6 @@ private void Toggle(Tuple inputParameter) } } - [RelayCommand] private void MoveUtmost() { @@ -1225,15 +1297,15 @@ private async Task OpenFolder() { if (CurrentMod != null) { - var _fetchTopLevel = FetchTopLevel(); + var _fetchMainView = FetchMainWindow(); - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; var _fetchDirectoryInfo = new DirectoryInfo(CurrentMod.ModPath); - if (_fetchTopLevel?.Launcher != null) - await _fetchTopLevel.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); + if (_fetchMainView?.Launcher != null) + await _fetchMainView.Launcher.LaunchDirectoryInfoAsync(_fetchDirectoryInfo); return true; } @@ -1242,18 +1314,21 @@ private async Task OpenFolder() return false; } + #endregion + + #region BUILD COMMANDS [RelayCommand] private async Task Run() { if (CurrentConfig != null) { - var _fetchTopLevel = FetchTopLevel(); + var _fetchMainView = FetchMainWindow(); - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; - return await ModService.Run(CurrentConfig, _fetchTopLevel); + return await ModService.Run(CurrentConfig, _fetchMainView); } else @@ -1265,9 +1340,9 @@ private async Task Build() { if (CurrentConfig != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; // Allocate all the pointers to use for the progress bars. @@ -1283,7 +1358,7 @@ private async Task Build() // Show the dialog. - var _multiProgress = DialogService.ShowProgress(_fetchTopLevel, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _firstCurrentPtr, _firstMaximumPtr, _firstTextPtr, _secondCurrentPtr, _secondMaximumPtr, _secondTextPtr); + var _multiProgress = DialogService.ShowProgress(_fetchMainView, "Installing Mod...", "Installing declared Mod... Please be patient...", ModService.CancelTokenSource, _firstCurrentPtr, _firstMaximumPtr, _firstTextPtr, _secondCurrentPtr, _secondMaximumPtr, _secondTextPtr); string _currentModName = "N/A"; @@ -1335,7 +1410,7 @@ await ModService.Build return true; else if (_buildResult == 0x01) - await DialogService.ShowMessage(_fetchTopLevel, "ERROR - Failed to build Mod", string.Format("Building failed on this Mod: {0}\nPlease re-install the Mod and try again!", _currentModName), MessageType.ERROR); + await DialogService.ShowMessage(_fetchMainView, "ERROR - Failed to build Mod", string.Format("Building failed on this Mod: {0}\nPlease re-install the Mod and try again!", _currentModName), MessageType.ERROR); return false; } @@ -1348,15 +1423,15 @@ private async Task Restore() { if (CurrentConfig != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; var _fetchBuildDir = PathService.ResolveBuild(CurrentConfig); Directory.Delete(_fetchBuildDir, true); - await DialogService.ShowMessage(_fetchTopLevel, "Restore Completed!", "Restoration complete! The game should run as if it isn't modded!", MessageType.INFO); + await DialogService.ShowMessage(_fetchMainView, "Restore Completed!", "Restoration complete! The game should run as if it isn't modded!", MessageType.INFO); return true; } @@ -1387,21 +1462,24 @@ private async Task BuildRun() return false; } + #endregion + + #region ASSEMBLY COMMANDS [RelayCommand] private async Task InstallPanacea() { if (CurrentConfig != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; PackageService.InstallPanacea(CurrentConfig); ConfigurationValid = CurrentConfig.IsValid(); - await DialogService.ShowMessage(_fetchTopLevel, "Panacea Installed!", "Panacea has been installed in accordance to current settings.", MessageType.INFO); + await DialogService.ShowMessage(_fetchMainView, "Panacea Installed!", "Panacea has been installed in accordance to current settings.", MessageType.INFO); return true; } @@ -1414,15 +1492,15 @@ private async Task InstallBackend() { if (CurrentConfig != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; PackageService.InstallBackend(CurrentConfig); ConfigurationValid = CurrentConfig.IsValid(); - await DialogService.ShowMessage(_fetchTopLevel, "LuaBackend Installed!", "LuaBackend has been installed in accordance to current settings.", MessageType.INFO); + await DialogService.ShowMessage(_fetchMainView, "LuaBackend Installed!", "LuaBackend has been installed in accordance to current settings.", MessageType.INFO); return true; } @@ -1430,18 +1508,21 @@ private async Task InstallBackend() return false; } + #endregion + + #region POPUP COMMANDS [RelayCommand] private async Task LaunchSetup() { if (CurrentConfig != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; - await new SetupWizardView().ShowDialog(_fetchTopLevel); + await new SetupWizardView().ShowDialog(_fetchMainView); return true; } @@ -1454,13 +1535,13 @@ private async Task LaunchCatalog() { if (CurrentConfig != null && InstalledMods != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; var _catalogWindow = new CatalogView(); - var _fetchResult = await _catalogWindow.ShowDialog(_fetchTopLevel); + var _fetchResult = await _catalogWindow.ShowDialog(_fetchMainView); if (!String.IsNullOrEmpty(_fetchResult) && InstallCommand.CanExecute(_fetchResult)) { @@ -1481,12 +1562,12 @@ private async Task PromptArguments() { if (CurrentConfig != null) { - var _fetchTopLevel = FetchTopLevel() as Window; + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchTopLevel == null) + if (_fetchMainView == null) return false; - var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Declare Launch Aruguments", "Enter the launch arguments to use when launching on Steam.", "Done", "Ex. -fastboot -noaspect", CurrentConfig.Frontend.LaunchArguments); + var _fetchResult = await DialogService.ShowInput(_fetchMainView, "Declare Launch Aruguments", "Enter the launch arguments to use when launching on Steam.", "Done", "Ex. -fastboot -noaspect", CurrentConfig.Frontend.LaunchArguments); if (!String.IsNullOrEmpty(_fetchResult)) CurrentConfig.Frontend.LaunchArguments = _fetchResult; @@ -1498,114 +1579,34 @@ private async Task PromptArguments() return false; } + #endregion + + #region PRESET COMMANDS + [RelayCommand] private async Task CreatePreset() { if (CurrentConfig != null) { - var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; - var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; - - var _fetchTopLevel = FetchTopLevel() as Window; - - if (_fetchTopLevel == null) - return false; - - var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Create New Preset", "Please enter a name for the new preset.", "OK", "Ex. \"Some Very Cool Preset\""); + var _fetchTargetGame = (int)CurrentConfig.Frontend.TargetGame; - if (!String.IsNullOrEmpty(_fetchResult)) + if (PresetMemory != null) { - var _isPresetPresent = PresetItems.OfType().FirstOrDefault(x => x.Header as string == "Default") != null; - - if (!_isPresetPresent) - { - PresetItems.RemoveAt(0x02); - - PresetItems.Add(new MenuItem() - { - Header = "Default", - ToggleType = MenuItemToggleType.Radio, - IsChecked = true, - Command = SwitchPresetCommand, - CommandParameter = "", - InputGesture = new KeyGesture(Key.D, KeyModifiers.Alt) - }); + var _fetchMainView = FetchMainWindow() as Window; - PresetItems.Add(new MenuItem() - { - Header = _fetchResult, - ToggleType = MenuItemToggleType.Radio, - IsChecked = false, - Command = SwitchPresetCommand, - CommandParameter = _fetchResult, - ContextMenu = new ContextMenu - { - ItemsSource = new[] - { - new MenuItem - { - Header = "Rename Preset...", - Command = RenamePresetCommand, - CommandParameter = _fetchResult - }, + if (_fetchMainView == null) + return false; - new MenuItem - { - Header = "Delete Preset...", - Command = DeletePresetCommand, - CommandParameter = _fetchResult - } - } - }, - InputGesture = new KeyGesture(Key.D0, KeyModifiers.Alt) - }); - } + var _fetchResult = await DialogService.ShowInput(_fetchMainView, "Create New Preset", "Please enter a name for the new preset.", "OK", "Ex. \"Some Very Cool Preset\""); - else + if (_fetchResult != null) { - PresetItems.Add(new MenuItem() + PresetMemory.Add(new PresetModel { - Header = _fetchResult, - ToggleType = MenuItemToggleType.Radio, - IsChecked = false, - Command = SwitchPresetCommand, - CommandParameter = _fetchResult, - ContextMenu = new ContextMenu - { - ItemsSource = new[] - { - new MenuItem - { - Header = "Rename Preset...", - Command = RenamePresetCommand, - CommandParameter = _fetchResult - }, - - new MenuItem - { - Header = "Delete Preset...", - Command = DeletePresetCommand, - CommandParameter = _fetchResult - } - } - }, - InputGesture = new KeyGesture((Key)(34 + PresetItems.Count - 0x03), KeyModifiers.Alt) + Name = _fetchResult, + TargetGame = CurrentConfig.Frontend.TargetGame }); } - - var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); - - var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); - var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); - - _fetchPresetList.Add(new PresetModel - { - PresetName = _fetchResult, - PresetGame = CurrentConfig.Frontend.TargetGame - }); - - _fetchPresetRAW = YamlSerializer.Serialize(_fetchPresetList); - File.WriteAllText(_fetchPresetPath, _fetchPresetRAW); } } @@ -1613,128 +1614,128 @@ private async Task CreatePreset() } [RelayCommand] - private async Task SwitchPreset(string input) + private async Task SwitchPreset(PresetModel input) { if (CurrentConfig != null) { - var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; - var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; - - if (_fetchPreset == input) - return false; + var _fetchTargetGame = CurrentConfig.Frontend.TargetGame; + var _fetchPresetList = CurrentConfig.Frontend.TargetPreset; - CurrentConfig.Frontend.TargetPreset[_fetchGame] = input; - InitializeMods(); + if (_fetchPresetList != null) + { + var _fetchPreset = _fetchPresetList[(int)_fetchTargetGame]; + var _fetchName = input == null ? "" : input.Name; - return true; + if (_fetchPreset != _fetchName) + { + _fetchPresetList[(int)_fetchTargetGame] = _fetchName; + IterateModlist(); + } + } } return false; } [RelayCommand] - private async Task DeletePreset(string input) + private async Task DeletePreset(PresetModel input) { - if (CurrentConfig != null) + if (CurrentConfig != null && PresetMemory != null) { - var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; - var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; - - var _fetchTopLevel = FetchTopLevel() as Window; - - if (_fetchTopLevel == null) - return false; + var _fetchTargetGame = CurrentConfig.Frontend.TargetGame; + var _fetchPresetList = CurrentConfig.Frontend.TargetPreset; - var _fetchResult = await DialogService.ShowQuestion(_fetchTopLevel, "Delete this Preset?", $"Are you sure you want to delete the preset \"{input}\"?\nIf it is currently active, the Default preset will be loaded."); - - if (_fetchResult) + if (_fetchPresetList != null) { - if (_fetchPreset == input) - { - CurrentConfig.Frontend.TargetPreset[_fetchGame] = ""; - (PresetItems[0x02] as MenuItem).IsChecked = true; - } + var _fetchPreset = _fetchPresetList[(int)_fetchTargetGame]; + var _fetchName = input == null ? "" : input.Name; - var _fetchPresetPath = Path.Combine(AppContext.BaseDirectory, "preset.yml"); + var _fetchMainView = FetchMainWindow() as Window; - if (File.Exists(_fetchPresetPath)) - { - var _fetchPresetRAW = File.ReadAllText(_fetchPresetPath); - var _fetchPresetList = YamlSerializer.Deserialize>(_fetchPresetRAW); - - var _fetchPresetItem = _fetchPresetList.FirstOrDefault(x => x.PresetName == input); - var _fetchPresetMenu = PresetItems.OfType().FirstOrDefault(x => x.Header == input); + if (_fetchMainView == null) + return false; - _fetchPresetList.Remove(_fetchPresetItem); - PresetItems.Remove(_fetchPresetMenu); + var _fetchResult = await DialogService.ShowQuestion(_fetchMainView, "Delete this Preset?", $"Are you sure you want to delete the preset \"{_fetchName}\"?\n" + + $"If it is currently active, the default preset will be loaded."); - if (PresetItems.Count > 3) + if (_fetchResult) + { + if (_fetchPreset == _fetchName) { - var _fetchKeyIndex = 34; - - for (int i = 0x03; i < PresetItems.Count; i++) - (PresetItems[i] as MenuItem).InputGesture = new KeyGesture((Key)(_fetchKeyIndex++), KeyModifiers.Alt); + _fetchPresetList[(int)_fetchTargetGame] = ""; + IterateModlist(); } - else - { - PresetItems[0x02] = new MenuItem() - { - Header = "No Presets Available.", - IsEnabled = false - }; - } + PresetMemory.Remove(input); Task.Run(() => { - var _fetchNormalStr = input.ToLower().Replace(" ", "_"); - - foreach (var _fetchChar in Path.GetInvalidFileNameChars()) - _fetchNormalStr = _fetchNormalStr.Replace(_fetchChar, '-'); - - var _fetchFinalPath = Path.Combine(PathService.ResolvePreset(CurrentConfig, false), _fetchNormalStr); - - if (Directory.Exists(_fetchFinalPath)) - Directory.Delete(_fetchFinalPath, true); + var _fetchPresetPath = PathService.ResolvePreset(CurrentConfig); + var _fetchTargetFolder = Path.Combine(_fetchPresetPath, input.FolderName); + if (Directory.Exists(_fetchTargetFolder)) + Directory.Delete(_fetchTargetFolder, true); }); - var _fetchSerial = YamlSerializer.Serialize(_fetchPresetList); - File.WriteAllText(_fetchPresetPath, _fetchSerial); - - InitializeMods(); + return true; } } - - return true; } return false; } [RelayCommand] - private async Task RenamePreset(string input) + private async Task RenamePreset(PresetModel input) { - if (CurrentConfig != null) + if (CurrentConfig != null && PresetMemory != null) { - var _fetchGame = (int)CurrentConfig.Frontend.TargetGame; - var _fetchPreset = CurrentConfig.Frontend.TargetPreset[_fetchGame]; + var _fetchTargetGame = CurrentConfig.Frontend.TargetGame; + var _fetchPresetList = CurrentConfig.Frontend.TargetPreset; - var _fetchTopLevel = FetchTopLevel() as Window; + if (_fetchPresetList != null) + { + var _fetchPreset = _fetchPresetList[(int)_fetchTargetGame]; - if (_fetchTopLevel == null) - return false; + var _fetchName = input.Name; + var _fetchFolder = input.FolderName; - var _fetchResult = await DialogService.ShowInput(_fetchTopLevel, "Rename Preset", "Please enter a new name for this preset.", "Rename", "", input); + var _fetchMainView = FetchMainWindow() as Window; - if (_fetchResult != null) - { - return true; + if (_fetchMainView == null) + return false; + + var _fetchResult = await DialogService.ShowInput(_fetchMainView, "Rename Preset", "Please enter a new name for this preset.", "Rename", $"Current Name: \"{_fetchName}\""); + + if (_fetchResult != null) + { + var _fetchIndex = PresetMemory.IndexOf(input); + PresetMemory.Remove(input); + + if (_fetchPreset == _fetchName) + _fetchPresetList[(int)_fetchTargetGame] = _fetchResult; + + input.Name = _fetchResult; + PresetMemory.Insert(_fetchIndex, input); + + Task.Run(() => + { + var _fetchPresetPath = PathService.ResolvePreset(CurrentConfig); + + var _fetchFolderOLD = Path.Combine(_fetchPresetPath, _fetchFolder); + var _fetchFolderNEW = Path.Combine(_fetchPresetPath, input.FolderName); + + if (Directory.Exists(_fetchFolderOLD)) + Directory.Move(_fetchFolderOLD, _fetchFolderNEW); + }); + } } } return false; } + + #endregion } #pragma warning restore CS4014 diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml b/OpenKh.Tools.ModManager/Views/MainView.axaml index 31145627a..8e58b49e4 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml @@ -93,7 +93,7 @@ - + diff --git a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs index 4c155362a..ab1e4aad1 100644 --- a/OpenKh.Tools.ModManager/Views/MainView.axaml.cs +++ b/OpenKh.Tools.ModManager/Views/MainView.axaml.cs @@ -18,8 +18,8 @@ private void OnTargetGameChanged(object? sender, SelectionChangedEventArgs e) if (_fetchContext == null) return; - // Re-Initialize the view. - _fetchContext.InitializeView(); + // Re-Initialize the viewmodel. + _fetchContext.InitViewModel(); } public MainView() From bbfdea562ba9e15647d35d371d8f26e2287aaf7f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 21 Sep 2026 05:34:06 +0200 Subject: [PATCH 140/175] [Mod Manager] - Presets can now link mods instead of having to hard-install. --- .../Controls/ModListControl.axaml | 14 +- .../Dialogs/InputDialog.axaml.cs | 7 +- .../Dialogs/OptionsDialog.axaml | 15 + .../Dialogs/OptionsDialog.axaml.cs | 52 ++ OpenKh.Tools.ModManager/Models/ModModel.cs | 1 + .../Services/DialogService.cs | 26 +- .../Services/ModService.cs | 6 +- .../ViewModels/MainViewModel.cs | 555 +++++++++++++++++- 8 files changed, 654 insertions(+), 22 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Dialogs/OptionsDialog.axaml create mode 100644 OpenKh.Tools.ModManager/Dialogs/OptionsDialog.axaml.cs diff --git a/OpenKh.Tools.ModManager/Controls/ModListControl.axaml b/OpenKh.Tools.ModManager/Controls/ModListControl.axaml index 8d43772bc..e001a0f5a 100644 --- a/OpenKh.Tools.ModManager/Controls/ModListControl.axaml +++ b/OpenKh.Tools.ModManager/Controls/ModListControl.axaml @@ -11,8 +11,7 @@ x:Class="OpenKh.Tools.ModManager.Views.ModListView"> - M18 13C18.1678 13 18.3344 13.0083 18.498 13.0244C18.7956 13.0538 19.0252 13.2867 19.0878 13.5791L19.3544 14.8223C19.4237 15.146 19.7582 15.339 20.0732 15.2373L21.2822 14.8457C21.5662 14.754 21.882 14.837 22.0566 15.0791C22.2496 15.3471 22.417 15.6353 22.5546 15.9395C22.6778 16.2119 22.591 16.5271 22.3691 16.7275L21.4257 17.5801C21.1801 17.802 21.1801 18.1883 21.4257 18.4102L22.373 19.2646C22.5948 19.4648 22.6812 19.7804 22.5585 20.0527C22.4217 20.3565 22.2555 20.6443 22.0634 20.9121C21.889 21.1551 21.5717 21.2386 21.2871 21.1465L20.0732 20.7529C19.7582 20.6512 19.4237 20.8442 19.3544 21.168L19.0859 22.4199C19.0233 22.7124 18.7937 22.9457 18.496 22.9756C18.3331 22.9919 18.1672 23 18 23C17.8341 23 17.6695 22.9915 17.5078 22.9756C17.2099 22.946 16.9786 22.7136 16.916 22.4209L16.6494 21.168C16.5801 20.8442 16.2457 20.6511 15.9306 20.7529L14.7119 21.1465C14.4274 21.2382 14.1107 21.155 13.9365 20.9121C13.745 20.6451 13.5789 20.3584 13.4423 20.0557C13.3196 19.7834 13.4062 19.4678 13.6279 19.2676L14.5781 18.4102C14.8238 18.1883 14.8238 17.802 14.5781 17.5801L13.6308 16.7246C13.4088 16.5241 13.3218 16.208 13.4453 15.9355C13.5828 15.6321 13.7505 15.3445 13.9433 15.0771C14.1179 14.8353 14.4339 14.753 14.7177 14.8447L15.9306 15.2373C16.2457 15.3392 16.5801 15.1461 16.6494 14.8223L16.914 13.5791C16.9765 13.2865 17.2072 13.0541 17.5048 13.0244C17.6674 13.0084 17.8333 13 18 13ZM18 16.5C17.1719 16.5004 16.5 17.1718 16.5 18C16.5 18.8282 17.1719 19.4996 18 19.5C18.8284 19.5 19.5 18.8284 19.5 18C19.5 17.1716 18.8284 16.5 18 16.5ZM9.00776 1.49805C9.34737 1.49806 9.68098 1.52151 10.0068 1.56543L10.33 1.61621L10.4394 1.64453C10.6888 1.72927 10.8786 1.94064 10.9326 2.2041L11.2978 3.98633L11.3173 4.06055C11.4334 4.42205 11.8259 4.62709 12.1953 4.50391L13.9228 3.92773L14.0322 3.89941C14.2913 3.8546 14.5589 3.94943 14.7324 4.1543C15.3037 4.82893 15.7593 5.60646 16.0654 6.45605C16.1691 6.74458 16.0867 7.06796 15.8574 7.27148L14.4921 8.48242C14.1819 8.75784 14.1809 9.24216 14.4912 9.51758L15.8554 10.7266C16.0851 10.9302 16.1676 11.2542 16.0634 11.543C15.7567 12.3932 15.3011 13.1705 14.7294 13.8447C14.5313 14.0783 14.2105 14.168 13.9199 14.0713L12.1953 13.4961C11.8259 13.3729 11.4334 13.5779 11.3173 13.9395L11.2978 14.0137L10.9335 15.791C10.8719 16.0918 10.6332 16.3248 10.331 16.3789C9.90421 16.4549 9.46182 16.498 9.00776 16.498C8.54901 16.498 8.10153 16.455 7.6689 16.377C7.36709 16.3225 7.12887 16.0895 7.06733 15.7891L6.70503 14.0137C6.62183 13.6069 6.20142 13.3649 5.80757 13.4961L4.09175 14.0674C3.80095 14.1642 3.48023 14.0737 3.28218 13.8398C2.7102 13.1641 2.25332 12.3856 1.94722 11.5332C1.84354 11.2444 1.92649 10.9212 2.1562 10.7178L3.51167 9.51758C3.8217 9.24218 3.8217 8.75782 3.51167 8.48242L2.15522 7.28027C1.92584 7.07698 1.84288 6.75435 1.94624 6.46582C2.25177 5.61404 2.70754 4.83434 3.27925 4.1582L3.35835 4.07715C3.55642 3.90439 3.83422 3.8458 4.08882 3.93066L5.80757 4.50391C6.20142 4.63511 6.62183 4.39309 6.70503 3.98633L7.06831 2.20605L7.09858 2.09668C7.18953 1.85006 7.4057 1.66578 7.66987 1.61816C8.10313 1.5401 8.55035 1.49805 9.00776 1.49805ZM8.99995 6.75C7.75731 6.75 6.74995 7.75736 6.74995 9C6.74995 10.2426 7.75731 11.25 8.99995 11.25C10.2425 11.2499 11.25 10.2426 11.25 9C11.25 7.75739 10.2425 6.75005 8.99995 6.75Z - + M6.5 4C3.46243 4 1 6.46243 1 9.5C1 11.8359 2.45624 13.8317 4.51041 14.6291C4.54977 13.9293 4.69984 13.2591 4.94339 12.6357C3.79151 12.0627 3 10.8738 3 9.5C3 7.567 4.567 6 6.5 6H12.5C14.433 6 16 7.567 16 9.5C16 11.433 14.433 13 12.5 13H12C11.4477 13 11 13.4477 11 14C11 14.5523 11.4477 15 12 15H12.5C15.5376 15 18 12.5376 18 9.5C18 6.46243 15.5376 4 12.5 4H6.5ZM19.1135 12.2134C20.219 12.6556 21 13.7366 21 15C21 16.6569 19.6569 18 18 18H11C9.34315 18 8 16.6569 8 15C8 13.3431 9.34315 12 11 12H12C12.5523 12 13 11.5523 13 11C13 10.4477 12.5523 10 12.0001 10H11C8.23858 10 6 12.2386 6 15C6 17.7614 8.23858 20 11 20H18C20.7614 20 23 17.7614 23 15C23 12.7598 21.5267 10.8636 19.4962 10.2277C19.4722 10.9229 19.3391 11.5903 19.1135 12.2134Z @@ -26,10 +25,13 @@ - - diff --git a/OpenKh.Tools.ModManager/Dialogs/InputDialog.axaml.cs b/OpenKh.Tools.ModManager/Dialogs/InputDialog.axaml.cs index 51159f9fa..0e48ca6d9 100644 --- a/OpenKh.Tools.ModManager/Dialogs/InputDialog.axaml.cs +++ b/OpenKh.Tools.ModManager/Dialogs/InputDialog.axaml.cs @@ -14,6 +14,7 @@ public partial class InputDialog : Window public string? Result { get; set; } public string? CurrentText { get; set; } + public string? MiscButtonText { get; set; } public InputDialog() { @@ -28,7 +29,8 @@ private void OnViewLoaded(object? sender, RoutedEventArgs e) InputText.MaxWidth = Bounds.Width; InputText.Text = CurrentText; - MiscButton.IsVisible = MiscButtonCallback == null ? false : true; + MiscButton.Content = MiscButtonText; + MiscButton.IsVisible = MiscButtonText == null ? false : true; } private void OnAcceptClicked(object? sender, Avalonia.Interactivity.RoutedEventArgs e) => Close(Result = InputText.Text); @@ -42,6 +44,9 @@ private async void OnMiscClicked(object? sender, Avalonia.Interactivity.RoutedEv if (!String.IsNullOrEmpty(_fetchResult)) Close(Result = _fetchResult); } + + else + Close(Result = null); } } } diff --git a/OpenKh.Tools.ModManager/Dialogs/OptionsDialog.axaml b/OpenKh.Tools.ModManager/Dialogs/OptionsDialog.axaml new file mode 100644 index 000000000..2895c7fd3 --- /dev/null +++ b/OpenKh.Tools.ModManager/Dialogs/OptionsDialog.axaml @@ -0,0 +1,15 @@ + + + + + + + + Enter path to the folder containing your + ISO Files: + + + + + + + + + - NOTE: Only PCSX2 versions up to 1.6.0 and PCSX2-EX are supported. + You should always use legally acquired ROMs. Using pirated copies will make you ineligible for support. From cb4ead18078ab28c8aab3c021b0c6cb548cf70d9 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 16:55:58 +0200 Subject: [PATCH 163/175] [Mod Manager] - Fix improper building progress bars. --- .../ViewModels/MainViewModel.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs index 560f22cce..cd688320f 100644 --- a/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs +++ b/OpenKh.Tools.ModManager/ViewModels/MainViewModel.cs @@ -1662,11 +1662,11 @@ private async Task Build() // Allocate all the pointers to use for the progress bars. - SafePtr _firstCurrentPtr = 0x00; - SafePtr _firstMaximumPtr = 0x00; + SafePtr _firstCurrentPtr = 0.00; + SafePtr _firstMaximumPtr = 0.00; - SafePtr _secondCurrentPtr = 0x00; - SafePtr _secondMaximumPtr = 0x00; + SafePtr _secondCurrentPtr = 0.00; + SafePtr _secondMaximumPtr = 0.00; SafePtr _firstTextPtr = "Currently building: N/A"; SafePtr _secondTextPtr = "Processing Files: {0} / {3}"; @@ -1689,8 +1689,8 @@ await ModService.Build _firstTextPtr /= $"Currently building: {currModName}"; - _firstCurrentPtr /= procMod; - _firstMaximumPtr /= totalMod; + _firstCurrentPtr /= Convert.ToDouble(procMod); + _firstMaximumPtr /= Convert.ToDouble(totalMod); if (ModService.CancelToken.IsCancellationRequested) return false; @@ -1700,8 +1700,8 @@ await ModService.Build (int processed, int total) => { - _secondCurrentPtr /= processed; - _secondMaximumPtr /= total; + _secondCurrentPtr /= Convert.ToDouble(processed); + _secondMaximumPtr /= Convert.ToDouble(total); if (ModService.CancelToken.IsCancellationRequested) return false; From 8602918c959d9a2edbe04055df4a99e48a731b8f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 16:56:39 +0200 Subject: [PATCH 164/175] [Mod Manager] - Preliminary PS2 Support on Linux, utilizing the PINE protocol. --- OpenKh.Tools.ModManager/Classes/PINEStream.cs | 444 +++++++++++++ .../Services/InjectorService.cs | 627 ++++++------------ .../Services/PathService.cs | 20 +- 3 files changed, 653 insertions(+), 438 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Classes/PINEStream.cs diff --git a/OpenKh.Tools.ModManager/Classes/PINEStream.cs b/OpenKh.Tools.ModManager/Classes/PINEStream.cs new file mode 100644 index 000000000..1327174bb --- /dev/null +++ b/OpenKh.Tools.ModManager/Classes/PINEStream.cs @@ -0,0 +1,444 @@ +using System; +using System.IO; +using System.Net.Sockets; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Linq; +using System.Net; + +namespace OpenKh.Tools.ModManager.Classes +{ + public sealed class PINEStream : Stream + { + struct PINE_MESSAGE + { + public PINE_COMMAND Command; + public uint Address; + public object? Data; + + public uint Length => 0x05 + (Data == null ? 0x00 : (uint)Marshal.SizeOf(Data)); + + public byte[] ToArray() + { + var _fetchData = new byte[Length]; + var _fetchSpan = _fetchData.AsSpan(0x05, Data == null ? 0x00 : Marshal.SizeOf(Data)); + + _fetchData[0] = (byte)Command; + + BinaryPrimitives.WriteUInt32LittleEndian(_fetchData.AsSpan(0x01, 0x04), Address); + + switch (Data) + { + case null: + break; + + case byte value: + _fetchData[0x05] = value; + break; + + case ushort value: + BinaryPrimitives.WriteUInt16LittleEndian( + _fetchSpan, + value); + break; + + case uint value: + BinaryPrimitives.WriteUInt32LittleEndian( + _fetchSpan, + value); + break; + + case ulong value: + BinaryPrimitives.WriteUInt64LittleEndian( + _fetchSpan, + value); + break; + + default: + throw new NotSupportedException( + $"Data type {Data?.GetType().Name} is not supported."); + } + + return _fetchData.ToArray(); + } + } + + enum PINE_COMMAND : byte + { + READ_INT08, + READ_INT16, + READ_INT32, + READ_INT64, + + WRITE_INT08, + WRITE_INT16, + WRITE_INT32, + WRITE_INT64 + }; + + enum PINE_RESPONSE : byte + { + IPC_OK, + IPC_FAIL = 0xFF + }; + + const int MAX_PACKET_LENGTH = 640000; + + readonly object _syncLock = new(); + readonly string _socketPath; + + Socket? _mainSocket; + + uint _currPosition; + + bool _isValid; + bool _hasDisposed; + + public PINEStream(int slot = 28011) + { + if (slot < 1 || slot > 65535) + throw new ArgumentOutOfRangeException(nameof(slot)); + + var _fetchRuntimePath = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); + + _socketPath = String.IsNullOrEmpty(_fetchRuntimePath) ? "/tmp/pcsx2.sock" : Path.Combine(_fetchRuntimePath, "pcsx2.sock"); + + if (slot != 28011) + _socketPath += "." + slot; + + var _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + + try + { + var _fetchEndPoint = new UnixDomainSocketEndPoint(_socketPath); + _fetchSocket.Connect(_fetchEndPoint); + + _mainSocket = _fetchSocket; + } + + catch + { + _fetchSocket.Dispose(); + _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + + _socketPath = "/tmp/pcsx2.sock"; + + if (slot != 28011) + _socketPath += "." + slot; + + try + { + var _fetchEndPoint = new UnixDomainSocketEndPoint(_socketPath); + _fetchSocket.Connect(_fetchEndPoint); + + _mainSocket = _fetchSocket; + } + + catch { _fetchSocket.Dispose(); } + } + + _isValid = _mainSocket != null; + } + + public override bool CanRead => !_hasDisposed && _isValid; + public override bool CanSeek => !_hasDisposed && _isValid; + public override bool CanWrite => !_hasDisposed && _isValid; + + public override long Length => UInt32.MaxValue + 1L; + + public override long Position + { + get + { + lock (_syncLock) + return _currPosition; + } + + set => Seek(value, SeekOrigin.Begin); + } + + public override long Seek(long offset, SeekOrigin origin) + { + lock (_syncLock) + { + if (origin == SeekOrigin.End) + throw new ArgumentException("Invalid seek origin.", nameof(origin)); + + var _calcPosition = origin == SeekOrigin.Begin ? (uint)offset : (uint)(_currPosition + offset); + + if (_calcPosition >= UInt32.MaxValue || _calcPosition < 0x00) + throw new ArgumentOutOfRangeException(); + + return _currPosition = _calcPosition; + } + } + + public override int Read(byte[] buffer, int offset, int count) + { + lock (_syncLock) + { + if (_hasDisposed) + throw new ObjectDisposedException(nameof(PINEStream)); + + if (!_isValid) + throw new InvalidOperationException("PINE stream is not valid."); + + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + if (offset < 0 || count <= 0 || offset > buffer.Length - count || count > Length - _currPosition) + throw new ArgumentOutOfRangeException(); + + var _makePacket = new List(); + var _messageList = new List(); + + for (int i = 0; i < count;) + { + for (uint z = 0, s = 0; s < MAX_PACKET_LENGTH - 0x04 && z < i + count;) + { + var _byteRemain = count - i - z; + + var _makeMessage = new PINE_MESSAGE + { + Command = _byteRemain >= 0x08 ? PINE_COMMAND.READ_INT64 : + _byteRemain >= 0x04 ? PINE_COMMAND.READ_INT32 : + _byteRemain >= 0x02 ? PINE_COMMAND.READ_INT16 : PINE_COMMAND.READ_INT08, + + Address = _currPosition + z + }; + + _messageList.Add(_makeMessage); + _makePacket.AddRange(_makeMessage.ToArray()); + + z += _byteRemain >= 0x08 ? 0x08U : + _byteRemain >= 0x04 ? 0x04U : + _byteRemain >= 0x02 ? 0x02U : 0x01U; + + s += _makeMessage.Length; + } + + var _finalSize = _makePacket.Count + 0x04; + _makePacket.InsertRange(0x00, BitConverter.GetBytes(_finalSize)); + + int _fetchSend = _mainSocket.Send(_makePacket.ToArray(), SocketFlags.None); + + var _fetchHeaderResponse = new byte[0x04]; + int _fetchResponse = _mainSocket.Receive(_fetchHeaderResponse, SocketFlags.None); + + if (_fetchSend <= 0x00 || _fetchResponse <= 0x00) + throw new IOException("PINE socket closed."); + + var _lengthResponse = BinaryPrimitives.ReadUInt32LittleEndian(_fetchHeaderResponse); + + if (_lengthResponse < 0x05 || _lengthResponse > 0x6DDD0) + throw new IOException("Invalid PINE response."); + + var _fetchDataResponse = new byte[_lengthResponse]; + int _fetchResult = _mainSocket.Receive(_fetchDataResponse, SocketFlags.None); + + if (_fetchResult <= 0) + throw new IOException("PINE socket closed."); + + var _fetchCodeResponse = _fetchDataResponse[0x00]; + + if (_fetchCodeResponse == (byte)PINE_RESPONSE.IPC_FAIL) + return i; + + if (_fetchCodeResponse != (byte)PINE_RESPONSE.IPC_OK) + throw new IOException($"Unknown PINE response: 0x{_fetchCodeResponse.ToString("X2")}"); + + for (int s = 0; s < _fetchDataResponse.Length - 0x05;) + { + var _byteRemain = _fetchDataResponse.Length - 0x05 - s; + + var _readLength = _byteRemain >= 0x08 ? 0x08 : + _byteRemain >= 0x04 ? 0x04 : + _byteRemain >= 0x02 ? 0x02 : 0x01; + + var _fetchSpanRead = _fetchDataResponse.AsSpan(0x01 + s, _readLength); + var _fetchSpanWrite = buffer.AsSpan(offset + i + s, _readLength); + + switch (_readLength) + { + case 0x08: + { + var _fetchData = BinaryPrimitives.ReadUInt64LittleEndian(_fetchSpanRead); + BinaryPrimitives.WriteUInt64LittleEndian(_fetchSpanWrite, _fetchData); + } + break; + + case 0x04: + { + var _fetchData = BinaryPrimitives.ReadUInt32LittleEndian(_fetchSpanRead); + BinaryPrimitives.WriteUInt32LittleEndian(_fetchSpanWrite, _fetchData); + } + break; + + case 0x02: + { + var _fetchData = BinaryPrimitives.ReadUInt16LittleEndian(_fetchSpanRead); + BinaryPrimitives.WriteUInt16LittleEndian(_fetchSpanWrite, _fetchData); + } + break; + + default: + buffer[offset + i] = _fetchDataResponse[0x05]; + break; + } + + s += _readLength; + } + + i += _finalSize - 0x05; + _currPosition += (uint)_finalSize - 0x05; + } + + return count; + } + } + + public override void Write(byte[] buffer, int offset, int count) + { + lock (_syncLock) + { + uint _initialPosition = _currPosition; + string _fileName = "fileDebug_" + count + ".dbg"; + + if (_hasDisposed || !_isValid) + throw new ObjectDisposedException(nameof(PINEStream)); + + if (buffer == null) + throw new ArgumentNullException(); + + if (offset < 0 || count <= 0 || offset > buffer.Length - count || count > Length - _initialPosition) + throw new ArgumentOutOfRangeException(); + + for (uint _packageLength = 0x00, _packageBytes = 0x00; _packageBytes < count;) + { + var _makePacket = new List(); + var _messageList = new List(); + + var _fetchData = new List(); + + for (; _packageLength < MAX_PACKET_LENGTH - 0x04 && _packageBytes < count;) + { + var _makeMessage = new PINE_MESSAGE(); + + var _fetchRemain = count - _packageBytes; + var _fetchOffset = (int) (offset + _packageBytes); + + _makeMessage.Address = _initialPosition + _packageBytes; + + if (_fetchRemain >= 0x08) + { + _makeMessage.Command = PINE_COMMAND.WRITE_INT64; + + var _fetchSpan = buffer.AsSpan(_fetchOffset, 0x08); + _makeMessage.Data = BinaryPrimitives.ReadUInt64LittleEndian(_fetchSpan); + + _packageBytes += 0x08; + } + + else if (_fetchRemain >= 0x04) + { + _makeMessage.Command = PINE_COMMAND.WRITE_INT32; + + var _fetchSpan = buffer.AsSpan(_fetchOffset, 0x04); + _makeMessage.Data = BinaryPrimitives.ReadUInt32LittleEndian(_fetchSpan); + + _packageBytes += 0x04; + } + + else if (_fetchRemain >= 0x02) + { + _makeMessage.Command = PINE_COMMAND.WRITE_INT16; + + var _fetchSpan = buffer.AsSpan(_fetchOffset, 0x02); + _makeMessage.Data = BinaryPrimitives.ReadUInt16LittleEndian(_fetchSpan); + + + _packageBytes += 0x02; + } + + else + { + _makeMessage.Command = PINE_COMMAND.WRITE_INT08; + _makeMessage.Data = buffer[_fetchOffset]; + + _packageBytes++; + } + + _messageList.Add(_makeMessage); + _makePacket.AddRange(_makeMessage.ToArray()); + + _packageLength += _makeMessage.Length; + } + + var _finalSize = _makePacket.Count + 0x04; + _makePacket.InsertRange(0x00, BitConverter.GetBytes(_finalSize)); + + int _fetchSend = _mainSocket.Send(_makePacket.ToArray(), SocketFlags.None); + + var _fetchHeaderResponse = new byte[0x04]; + int _fetchResponse = _mainSocket.Receive(_fetchHeaderResponse, SocketFlags.None); + + if (_fetchSend <= 0x00 || _fetchResponse <= 0x00) + throw new IOException("PINE socket closed."); + + var _lengthResponse = BinaryPrimitives.ReadUInt32LittleEndian(_fetchHeaderResponse); + + if (_lengthResponse < 0x05 || _lengthResponse > 0x6DDD0) + throw new IOException("Invalid PINE response."); + + var _fetchDataResponse = new byte[_lengthResponse]; + int _fetchResult = _mainSocket.Receive(_fetchDataResponse, SocketFlags.None); + + if (_fetchResult <= 0) + throw new IOException("PINE socket closed."); + + var _fetchCodeResponse = _fetchDataResponse[0x04]; + + if (_fetchCodeResponse == (byte)PINE_RESPONSE.IPC_FAIL) + return; + + if (_fetchCodeResponse != (byte)PINE_RESPONSE.IPC_OK) + throw new IOException($"Unknown PINE response: 0x{_fetchCodeResponse.ToString("X2")}"); + + _currPosition = _initialPosition + _packageBytes; + _packageLength = 0x00; + } + } + } + + public override void Flush() { } + + public override void SetLength(long value) => throw new NotSupportedException(); + + protected override void Dispose(bool disposing) + { + if (!_hasDisposed) + { + lock (_syncLock) + { + var _fetchSocket = _mainSocket; + _mainSocket = null; + + if (_fetchSocket != null) + { + try + { _fetchSocket.Shutdown(SocketShutdown.Both); } + catch { } + + _fetchSocket.Dispose(); + } + + _hasDisposed = true; + _isValid = false; + } + } + + base.Dispose(disposing); + } + } +} diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index 62f19d083..03346c8c8 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -2,8 +2,10 @@ using OpenKh.Common; using OpenKh.Tools.Common; using OpenKh.Tools.ModManager.Classes; +using SkiaSharp; using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; @@ -17,268 +19,13 @@ using Xe.BinaryMapper; using static Antlr4.Runtime.Atn.SemanticContext; using static OpenKh.Kh2.Ard.AreaDataScript; +using static OpenKh.Kh2.Ard.Event.Light; using static OpenKh.Kh2.Constants; namespace OpenKh.Tools.ModManager.Services { public class InjectorService { - public static class Hooks - { - public static readonly uint[] LoadFileHook = - [ - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.A0, MIPS.T6, -0x08), - MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V0, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 0x02), - MIPS.NOP(), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 4), - MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), - MIPS.SD(MIPS.T4, MIPS.SP, 0x08), - MIPS.SD(MIPS.S0, MIPS.SP, 0x00), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] GetFileSizeHook = - [ - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.A0, MIPS.T6, -0x08), - MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), - MIPS.SW(MIPS.A2, MIPS.T6, -0x10), - MIPS.SW(MIPS.A3, MIPS.T6, -0x14), - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V0, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.JR(MIPS.T4), - MIPS.NOP(), - MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), - MIPS.SD(MIPS.T4, MIPS.SP, 0x08), - MIPS.SD(MIPS.S0, MIPS.SP, 0x00), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] LoadFileTaskHook = - [ - // Input: - // S0 DstPtr - // S1 Filename - // T4 return program counter - // T5 Operation - // V0 IdxFilePtr - // - // Work: - // MIPS.T6 Hook stack - // V0 Return value - // - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.S1, MIPS.T6, -0x08), - // Filename - MIPS.SW(MIPS.S0, MIPS.T6, -0x0C), - // DstPtr - MIPS.SW(MIPS.V0, MIPS.T6, -0x10), - // LoadFileTask - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V1, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V1, MIPS.Zero, 3), - MIPS.MOVE(MIPS.S2, MIPS.V0), - MIPS.BEQ(MIPS.Zero, MIPS.Zero, 2), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 0x98), - // skip the remainder of the function - MIPS.LI(MIPS.V0, -1), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] LoadFileTaskHookVanilla = - [ - // Input: - // S0 DstPtr - // S1 Filename - // T4 return program counter - // T5 Operation - // V0 IdxFilePtr - // - // Work: - // MIPS.T6 Hook stack - // V0 Return value - // - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.S1, MIPS.T6, -0x08), - // Filename - MIPS.SW(MIPS.S0, MIPS.T6, -0x0C), - // DstPtr - MIPS.SW(MIPS.V0, MIPS.T6, -0x10), - // LoadFileTask - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V1, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V1, MIPS.Zero, 3), - MIPS.MOVE(MIPS.S2, MIPS.V0), - MIPS.BEQ(MIPS.Zero, MIPS.Zero, 2), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 0x64), - // skip the remainder of the function - MIPS.LI(MIPS.V0, -1), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] LoadFileAsyncHook = - [ - // Input: - // - // Work: - // - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.LW(MIPS.T2, MIPS.V0, -0x2A10), - MIPS.LW(MIPS.T3, MIPS.V0, -0x2A24), - MIPS.ADDI(MIPS.T4, MIPS.V0, -0x29F8), - MIPS.LW(MIPS.T0, MIPS.T4, 0), - MIPS.ADDIU(MIPS.T1, MIPS.T3, 8), - MIPS.LW(MIPS.T4, MIPS.T3, 0x38), - MIPS.LW(MIPS.T4, MIPS.T4, 0), - MIPS.SW(MIPS.T0, MIPS.T6, -0x08), - // FileDirID - MIPS.BEQ(MIPS.T2, MIPS.T4, 9), - MIPS.SW(MIPS.T1, MIPS.T6, -0x0C), - // FileNamePtr - MIPS.ADDIU(MIPS.T4, MIPS.Zero, 0x03), - MIPS.SW(MIPS.T4, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T4, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T4, 0x00, -2), - MIPS.LW(MIPS.T4, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.T4, MIPS.Zero, 7), - MIPS.NOP(), - MIPS.BEQ(MIPS.Zero, MIPS.Zero, 8), - MIPS.SW(MIPS.T2, MIPS.T6, -0x10), - // MemDstPtr - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.T4, MIPS.T6, -0x08), - MIPS.LUI(MIPS.V1, 0x5B), - // For Fallback - MIPS.BEQ(MIPS.T4, MIPS.Zero, 5), - MIPS.LW(MIPS.A2, MIPS.V0, -0x29F4), - MIPS.ADD(MIPS.T2, MIPS.T2, MIPS.T4), - MIPS.SW(MIPS.T2, MIPS.V0, -0x2A10), - MIPS.ADDIU(MIPS.V0, MIPS.Zero, 1), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 0x64), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] GetFileSizeRecomHook = - [ - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.LUI(MIPS.V1, 0x5C), - MIPS.ADDI(MIPS.T4, MIPS.V1, -0x29F8), - MIPS.LW(MIPS.T4, MIPS.T4, 0), - MIPS.SW(MIPS.T4, MIPS.T6, -0x08), - // FileDirID - MIPS.SW(MIPS.S2, MIPS.T6, -0x0C), - // FileNamePtr - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V1, MIPS.T6, -0x08), - - // For Fallback - MIPS.BNE(MIPS.V1, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.LW(MIPS.V1, MIPS.S2, 0x18), - MIPS.JR(MIPS.RA), - MIPS.SW(MIPS.V1, MIPS.S1, 0x28), - ]; - - public static readonly uint[] LoadFileAsyncHookJp = - [ - // Input: - // - // Work: - // - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.LW(MIPS.T2, MIPS.V0, 0x18B0), - MIPS.LW(MIPS.T3, MIPS.V0, 0x189C), - MIPS.ADDI(MIPS.T4, MIPS.V0, 0x18C8), - MIPS.LW(MIPS.T0, MIPS.T4, 0), - MIPS.ADDIU(MIPS.T1, MIPS.T3, 8), - MIPS.LW(MIPS.T4, MIPS.T3, 0x38), - MIPS.LW(MIPS.T4, MIPS.T4, 0), - MIPS.SW(MIPS.T0, MIPS.T6, -0x08), - // FileDirID - MIPS.BEQ(MIPS.T2, MIPS.T4, 9), - MIPS.SW(MIPS.T1, MIPS.T6, -0x0C), - // FileNamePtr - MIPS.ADDIU(MIPS.T4, MIPS.Zero, 0x03), - MIPS.SW(MIPS.T4, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T4, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T4, 0x00, -2), - MIPS.LW(MIPS.T4, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.T4, MIPS.Zero, 7), - MIPS.NOP(), - MIPS.BEQ(MIPS.Zero, MIPS.Zero, 8), - MIPS.SW(MIPS.T2, MIPS.T6, -0x10), - // MemDstPtr - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.T4, MIPS.T6, -0x08), - MIPS.LUI(MIPS.V1, 0x5B), - // For Fallback - MIPS.BEQ(MIPS.T4, MIPS.Zero, 5), - MIPS.LW(MIPS.A2, MIPS.V0, 0x18CC), - MIPS.ADD(MIPS.T2, MIPS.T2, MIPS.T4), - MIPS.SW(MIPS.T2, MIPS.V0, 0x18B0), - MIPS.ADDIU(MIPS.V0, MIPS.Zero, 1), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 0x64), - MIPS.JR(MIPS.RA), - MIPS.NOP(), - ]; - - public static readonly uint[] GetFileSizeRecomHookJp = - [ - MIPS.LUI(MIPS.T6, 0x0F), - MIPS.LUI(MIPS.V1, 0x5C), - MIPS.ADDI(MIPS.T4, MIPS.V1, 0x18C8), - MIPS.LW(MIPS.T4, MIPS.T4, 0), - MIPS.SW(MIPS.T4, MIPS.T6, -0x08), - // FileDirID - MIPS.SW(MIPS.S2, MIPS.T6, -0x0C), - // FileNamePtr - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - // Operation - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V1, MIPS.T6, -0x08), - - // For Fallback - MIPS.BNE(MIPS.V1, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.LW(MIPS.V1, MIPS.S2, 0x18), - MIPS.JR(MIPS.RA), - MIPS.SW(MIPS.V1, MIPS.S1, 0x28), - ]; - } - public static Dictionary ISO_NAMES = new Dictionary { { Game.KINGDOM_HEARTS, "KHFM.iso"}, @@ -310,15 +57,11 @@ public static class Hooks Process _targetProcess; - uint _hookPtr; - uint _nextHookPtr = 0xFFF00; - - byte[] _loadFuncArr = Hooks.LoadFileHook.SelectMany(BitConverter.GetBytes).ToArray(); - byte[] _getSizeFuncArr = Hooks.GetFileSizeHook.SelectMany(BitConverter.GetBytes).ToArray(); - CancellationTokenSource _cancelSource; CancellationToken _cancelToken; + long _memoryOffset = 0x00; + uint _loadFileFunc = 0x1682b8; uint _getFileSizeFunc = 0x1AE1B0; uint _subFileSizeFunc = 0x1AE460; @@ -327,12 +70,15 @@ public static class Hooks uint _regionPtr = 0x33CAF0; uint _languagePtr = 0x33CAF4; + uint _hookPtrLoad = 0xFFF00; + uint _hookPtrSize = 0xFFF00; + + bool _functionsWritten = false; + public Config TargetConfig { get; set; } - bool ResolvePath(string input, out string? finalPath) + byte ResolvePath(string input, out string? finalPath) { - Console.WriteLine($"Resolving file: {input}..."); - var _fetchBuildPath = PathService.ResolveBuild(TargetConfig); var _fetchDataPath = PathService.ResolveData(TargetConfig); @@ -346,10 +92,8 @@ bool ResolvePath(string input, out string? finalPath) if (DENY_FILES.Contains(input)) { - Console.WriteLine($"Cannot resolve file \"{input}\" as it is on the forbidden list! Skipping..."); - finalPath = null; - return false; + return 0x00; } var _fetchBuildFile = Path.Combine(_fetchBuildPath, input); @@ -358,13 +102,13 @@ bool ResolvePath(string input, out string? finalPath) if (File.Exists(_fetchBuildFile)) { finalPath = _fetchBuildFile; - return true; + return 0x01; } else if (File.Exists(_fetchBuildFileRegion)) { finalPath = _fetchBuildFileRegion; - return true; + return 0x01; } var _fetchDataFile = Path.Combine(_fetchDataPath, input); @@ -373,24 +117,24 @@ bool ResolvePath(string input, out string? finalPath) if (File.Exists(_fetchDataFile)) { finalPath = _fetchDataFile; - return true; + return 0x02; } else if (File.Exists(_fetchDataFileRegion)) { finalPath = _fetchDataFileRegion; - return true; + return 0x02; } - Console.WriteLine($"Cannot resolve file \"{input}\" as it does not exist! Skipping..."); - finalPath = null; - return false; + return 0x03; } - + public int ResolveSize(string input) { - if (ResolvePath(input, out var _filePath)) + var _couldResolve = ResolvePath(input, out var _filePath); + + if (_couldResolve != 0x00 && _couldResolve != 0x03) { var _fetchInfo = new FileInfo(_filePath); return (int)_fetchInfo.Length; @@ -399,24 +143,6 @@ public int ResolveSize(string input) return -1; } - int WriteFile(Stream targetStream, string inputName) - { - var _couldResolve = ResolvePath(inputName, out var _filePath); - - if (_couldResolve) - { - Console.WriteLine($"Loading file from path: {_filePath}"); - - return File.OpenRead(_filePath).Using(x => - { - x.CopyTo(targetStream, 512 * 1024); - return (int)x.Length; - }); - } - - return 0x00; - } - public InjectorService(Config _targetConfig) { TargetConfig = _targetConfig; @@ -427,18 +153,6 @@ public InjectorService(Config _targetConfig) Stream? _fetchStream = null; var _fetchPathEmu = PathService.ResolveEmulator(TargetConfig); - if (OperatingSystem.IsWindows()) - { - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - static extern bool AllocConsole(); - - AllocConsole(); - } - - Console.WriteLine("=== OpenKH - Mod Manager | Dynamic Loader ==="); - Console.WriteLine(""); - _targetProcess = new Process { StartInfo = new ProcessStartInfo(_fetchPathEmu) @@ -454,192 +168,243 @@ public InjectorService(Config _targetConfig) if (_targetProcess.Start()) { - if (!_fetchPathEmu.Contains("qt")) + if (OperatingSystem.IsLinux()) { - Console.WriteLine("Detected PCSX2 Build: Before v1.7.0 - Using Legacy Method."); + var _fetchProcessID = _targetProcess.Id; - _fetchStream = new ProcessStream(_targetProcess, 0x20000000, 0x20000000); + Thread.Sleep(500); - Console.WriteLine("Latching to legacy build on 0x20000000..."); + _fetchStream = new PINEStream(); } else { - Console.WriteLine("Detected PCSX2 Build: After 1.7.0 - Using Current Method."); - - var _fetchProcessID = _targetProcess.Id; - var _fetchMemoryName = $"pcsx2_{_fetchProcessID}"; - - Console.WriteLine($"Searching for MemoryMap: {_fetchMemoryName}."); + try + { + var _fetchProcessID = _targetProcess.Id; + var _fetchMemoryName = $"pcsx2_{_fetchProcessID}"; - Thread.Sleep(1000); + Thread.Sleep(1000); - var _fetchMemoryMap = MemoryMappedFile.OpenExisting(_fetchMemoryName); + var _fetchMemoryMap = MemoryMappedFile.OpenExisting(_fetchMemoryName); - if (_fetchMemoryMap == null) - { - Console.WriteLine("MemoryMap not detected! Operation cannot continue! Aborting..."); + if (_fetchMemoryMap == null) + { + _targetProcess.Kill(); + return; + } - _targetProcess.Kill(); - return; + _fetchStream = _fetchMemoryMap.CreateViewStream(); } - Console.WriteLine("MemoryMap latched! Creating stream..."); - _fetchStream = _fetchMemoryMap.CreateViewStream(); + catch (FileNotFoundException) { _fetchStream = new ProcessStream(_targetProcess, 0x20000000, 0x20000000); } } + if (_fetchStream == null) { - Console.WriteLine("Couldn't create stream! Operation cannot continue! Aborting..."); - _targetProcess.Kill(); return; } - Console.WriteLine("Stream created... Executing main loop..."); - Console.WriteLine(""); - - Task.Run(async () => + Task.Run(() => { - while (!_cancelToken.IsCancellationRequested) - { - _fetchStream.SetPosition(_loadFileFunc); + LOOP_START: - if (_fetchStream.ReadUInt32() == 0x00) - continue; + if (_cancelToken.IsCancellationRequested) + return; - _nextHookPtr = 0xFFF00; + _fetchStream.SetPosition(_hookPtrLoad); - if (_loadFileFunc > 0) - { - _hookPtr = _nextHookPtr; - if (_hookPtr != 0) - { - _fetchStream.SetPosition(_hookPtr); + uint[] _fetchHookLoad = + [ + MIPS.LUI(MIPS.T6, 0x0F), + MIPS.SW(MIPS.A0, MIPS.T6, -0x08), + MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), + MIPS.SW(MIPS.T5, MIPS.T6, -0x04), + MIPS.LW(MIPS.T5, MIPS.T6, -0x04), + MIPS.BNE(MIPS.T5, 0x00, -2), + MIPS.LW(MIPS.V0, MIPS.T6, -0x08), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 0x02), + MIPS.NOP(), + MIPS.ADDIU(MIPS.RA, MIPS.RA, 4), + MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), + MIPS.SD(MIPS.T4, MIPS.SP, 0x08), + MIPS.SD(MIPS.S0, MIPS.SP, 0x00), + MIPS.JR(MIPS.RA), + MIPS.NOP() + ]; + + uint[] _fetchHookSize = + [ + MIPS.LUI(MIPS.T6, 0x0F), + MIPS.SW(MIPS.A0, MIPS.T6, -0x08), + MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), + MIPS.SW(MIPS.A2, MIPS.T6, -0x10), + MIPS.SW(MIPS.A3, MIPS.T6, -0x14), + MIPS.SW(MIPS.T5, MIPS.T6, -0x04), + MIPS.LW(MIPS.T5, MIPS.T6, -0x04), + MIPS.BNE(MIPS.T5, 0x00, -2), + MIPS.LW(MIPS.V0, MIPS.T6, -0x08), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), + MIPS.NOP(), + MIPS.JR(MIPS.T4), + MIPS.NOP(), + MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), + MIPS.SD(MIPS.T4, MIPS.SP, 0x08), + MIPS.SD(MIPS.S0, MIPS.SP, 0x00), + MIPS.JR(MIPS.RA), + MIPS.NOP() + ]; - if (_fetchStream.ReadUInt32() == 0x00) - { - _fetchStream.SetPosition(_hookPtr); - _fetchStream.Write(_loadFuncArr); - } + if (_fetchStream.ReadUInt32() == 0x00) + { + byte[] _fetchHookArray = new byte[_fetchHookLoad.Length * sizeof(uint) + _fetchHookSize.Length * sizeof(uint)]; - _nextHookPtr += (uint)_loadFuncArr.Length; - } + Buffer.BlockCopy(_fetchHookLoad, 0, _fetchHookArray, 0, _fetchHookLoad.Length * sizeof(uint)); + Buffer.BlockCopy(_fetchHookSize, 0, _fetchHookArray, _fetchHookLoad.Length * sizeof(uint), _fetchHookSize.Length * sizeof(uint)); - _fetchStream.SetPosition(_loadFileFunc); + _fetchStream.SetPosition(_hookPtrLoad); + _fetchStream.Write(_fetchHookArray); - uint[] _fetchFunction = - [ - MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), - MIPS.JAL(_hookPtr), - MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x01) - ]; + _hookPtrSize = _hookPtrLoad + (uint)_fetchHookLoad.Length * sizeof(uint); + } - foreach (var _fetchInst in _fetchFunction) - _fetchStream.Write(_fetchInst); - } + if (_loadFileFunc > 0) + { + uint[] _fetchFunction = + [ + MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), + MIPS.JAL(_hookPtrLoad), + MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x01) + ]; - if (_getFileSizeFunc > 0) - { - _hookPtr = _nextHookPtr; + byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; + Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); - if (_hookPtr != 0) - { - _fetchStream.SetPosition(_hookPtr); + _fetchStream.SetPosition(_loadFileFunc); + _fetchStream.Write(_fetchFunctionArray); + } - if (_fetchStream.ReadUInt32() == 0x00) - { - _fetchStream.SetPosition(_hookPtr); - _fetchStream.Write(_getSizeFuncArr); - } + if (_getFileSizeFunc > 0) + { + uint[] _fetchFunction = + [ + MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), + MIPS.JAL(_hookPtrSize), + MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), + MIPS.JAL(_subFileSizeFunc), + MIPS.NOP(), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), + MIPS.NOP(), + MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), + MIPS.LD(MIPS.RA, MIPS.SP, 0x08), + MIPS.JR(MIPS.RA), + MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), + ]; - _nextHookPtr += (uint)_getSizeFuncArr.Length; - } + byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; + Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); - _fetchStream.SetPosition(_getFileSizeFunc); + _fetchStream.SetPosition(_getFileSizeFunc); + _fetchStream.Write(_fetchFunctionArray); + } - uint[] _fetchFunction = - [ - MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), - MIPS.JAL(_hookPtr), - MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), - MIPS.JAL(_subFileSizeFunc), - MIPS.NOP(), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), - MIPS.LD(MIPS.RA, MIPS.SP, 0x08), - MIPS.JR(MIPS.RA), - MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), - ]; + _fetchStream.Flush(); - foreach (var _fetchInst in _fetchFunction) - _fetchStream.Write(_fetchInst); - } + ///////////////////////////////////////////////////////////////////// + + START_PROC: - _fetchStream.Flush(); + var _fetchOpcodeAddr = (0x0F << 0x10) - 0x04; - var _fetchOpcodeAddr = (0x0F << 0x10) - 0x04; - var _fetchOpcode = _fetchStream.SetPosition(_fetchOpcodeAddr).ReadInt32(); + _fetchStream.SetPosition(_fetchOpcodeAddr); + var _fetchOpcode = _fetchStream.ReadInt32(); - if (_fetchStream.Position == _fetchOpcode || _targetProcess.HasExited) - break; + if (_fetchStream.Position == _fetchOpcode || _targetProcess.HasExited) + return; - switch (_fetchOpcode) + switch (_fetchOpcode) + { + case 0x01: { - case 0x01: - { - _fetchStream.SetPosition(_fetchOpcodeAddr - 0x08); + _fetchStream.SetPosition(_fetchOpcodeAddr - 0x08); - var _destinationPtr = _fetchStream.ReadInt32(); - var _fileNamePtr = _fetchStream.ReadInt32(); + var _destinationPtr = _fetchStream.ReadInt32(); + var _fileNamePtr = _fetchStream.ReadInt32(); - _fetchStream.SetPosition(_fileNamePtr); - var _fetchName = Encoding.UTF8.GetString(_fetchStream.ReadBytes(0x30)); + _fetchStream.SetPosition(_fileNamePtr); - _fetchName = _fetchName.Substring(0x00, _fetchName.IndexOf('\x00')); + var _fetchSize = 0x00; + var _fetchName = _fetchStream.ReadString(0x30, Encoding.ASCII); - if (_fetchName.Length < 2 || !char.IsLetterOrDigit(_fetchName[0]) || !char.IsLetterOrDigit(_fetchName[1])) - continue; + if (String.IsNullOrEmpty(_fetchName)) + { + Debug.WriteLine("CAUGHT NO-NAME FILE!"); + goto LOOP_START; + } - _fetchStream.SetPosition(_destinationPtr); - var _writeFile = WriteFile(_fetchStream, _fetchName); + Debug.WriteLine(_fetchName); - _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); - _fetchStream.Write(_writeFile); + var _couldResolve = ResolvePath(_fetchName, out var _filePath); + if (_couldResolve != 0x00 && _couldResolve != 0x03) + { + using (var mmf = MemoryMappedFile.CreateFromFile(_filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read)) + { + using (var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) + { + _fetchSize = (int) accessor.Capacity; + var _fetchData = new byte[_fetchSize]; + + accessor.ReadArray(0x00, _fetchData, 0x00, _fetchSize); + + _fetchStream.SetPosition(_destinationPtr); + _fetchStream.Write(_fetchData); + } + } } - break; - case 0x02: - { - _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); + _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); + _fetchStream.Write(_fetchSize); - var _fileNamePtr = _fetchStream.ReadInt32(); + _fetchStream.Flush(); + } + break; - _fetchStream.SetPosition(_fileNamePtr); - var _fetchName = Encoding.UTF8.GetString(_fetchStream.ReadBytes(0x30)); + case 0x02: + { + _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); - _fetchName = _fetchName.Substring(0x00, _fetchName.IndexOf('\x00')); + var _fileNamePtr = _fetchStream.ReadInt32(); - if (_fetchName.Length < 2 || !char.IsLetterOrDigit(_fetchName[0]) || !char.IsLetterOrDigit(_fetchName[1])) - continue; + _fetchStream.SetPosition(_fileNamePtr); + var _fetchName = _fetchStream.ReadString(0x30, Encoding.ASCII); - var _fetchFileSize = ResolveSize(_fetchName); + if (String.IsNullOrEmpty(_fetchName)) + goto LOOP_START; - _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); - _fetchStream.Write(_fetchFileSize); - } - break; + var _fetchFileSize = ResolveSize(_fetchName); + + _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); + _fetchStream.Write(_fetchFileSize); - case 0x00: - Thread.Sleep(1); - continue; + _fetchStream.Flush(); } + break; - _fetchStream.SetPosition((0x0F << 0x10) - 0x04).Write(0x00); + case 0x00: + Thread.Sleep(5); + goto LOOP_START; } + + _fetchStream.SetPosition(_fetchOpcodeAddr); + _fetchStream.Write(0x00); + + Thread.Sleep(5); + + goto LOOP_START; }, _cancelToken); } } diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index 8b31f9039..b96c5e4b5 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -125,17 +125,23 @@ public static string ResolvePreset(Config input, bool barePath = false) if (String.IsNullOrEmpty(input.Emulator.EmuPath[0])) return null; - var _fetchEmulatorOLD = Path.Combine(input.Emulator.EmuPath[0], "pcsx2.exe"); - var _fetchEmulatorNEW = Path.Combine(input.Emulator.EmuPath[0], "pcsx2-qt.exe"); + if (OperatingSystem.IsWindows()) + { + var _fetchEmulatorOLD = Path.Combine(input.Emulator.EmuPath[0], "pcsx2.exe"); + var _fetchEmulatorNEW = Path.Combine(input.Emulator.EmuPath[0], "pcsx2-qt.exe"); + + if (File.Exists(_fetchEmulatorOLD)) + return _fetchEmulatorOLD; - if (File.Exists(_fetchEmulatorOLD)) - return _fetchEmulatorOLD; + else if (File.Exists(_fetchEmulatorNEW)) + return _fetchEmulatorNEW; - else if (File.Exists(_fetchEmulatorNEW)) - return _fetchEmulatorNEW; + else + return null; + } else - return null; + return Path.Combine(input.Emulator.EmuPath[0], "pcsx2.appimage"); } From 0ae0e29ba99a0be3c200b79fc28a288161074fb4 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 22:37:08 +0200 Subject: [PATCH 165/175] [Mod Manager] - Change some wizards stuffs, WIP. --- .../Services/PathService.cs | 17 +---- .../Wizard/WizardGameSetup.axaml | 10 +-- .../Wizard/WizardGameSetup.axaml.cs | 74 +++++++++++++++++-- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/PathService.cs b/OpenKh.Tools.ModManager/Services/PathService.cs index b96c5e4b5..3454facb5 100644 --- a/OpenKh.Tools.ModManager/Services/PathService.cs +++ b/OpenKh.Tools.ModManager/Services/PathService.cs @@ -125,23 +125,8 @@ public static string ResolvePreset(Config input, bool barePath = false) if (String.IsNullOrEmpty(input.Emulator.EmuPath[0])) return null; - if (OperatingSystem.IsWindows()) - { - var _fetchEmulatorOLD = Path.Combine(input.Emulator.EmuPath[0], "pcsx2.exe"); - var _fetchEmulatorNEW = Path.Combine(input.Emulator.EmuPath[0], "pcsx2-qt.exe"); - - if (File.Exists(_fetchEmulatorOLD)) - return _fetchEmulatorOLD; - - else if (File.Exists(_fetchEmulatorNEW)) - return _fetchEmulatorNEW; - - else - return null; - } - else - return Path.Combine(input.Emulator.EmuPath[0], "pcsx2.appimage"); + return input.Emulator.EmuPath[0]; } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml index 116a26219..49240dfb8 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml @@ -109,7 +109,7 @@ - NOTE: Please be advised that all Lua related features will only with with PCSX2-EX. + NOTE: Please be advised that all Lua related features will only with with PCSX2-EX. On Linux, only the AppImage release is supported. Enter path for the @@ -124,8 +124,8 @@ Text="{ReflectionBinding CurrentConfig.Emulator.EmuPath[0]}" HorizontalAlignment="Stretch" PlaceholderText="Path goes here..." Margin="0, 0, 10, 0" /> - @@ -141,8 +141,8 @@ Text="{ReflectionBinding CurrentConfig.Emulator.RomPath[0]}" HorizontalAlignment="Stretch" PlaceholderText="Path goes here..." Margin="0, 0, 10, 0" /> - diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index 8a8bdc309..5731b121e 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -73,6 +73,46 @@ private async void OnFolderClickSecond(object? sender, Avalonia.Interactivity.Ro } } + private async void OnFolderClickEmuFirst(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; + + if (_storageProvider != null) + { + var _fetchFolder = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = "Select a PCSX2 Installation...", + AllowMultiple = false, + FileTypeFilter = + [ + OperatingSystem.IsWindows() ? new FilePickerFileType("Executable File") { Patterns = ["*.exe"] }: new FilePickerFileType("AppImage Archive") { Patterns = ["*.AppImage"] } + ] + }); + + if (_fetchFolder.Count >= 1) + PathEmulator.Text = _fetchFolder[0].Path.LocalPath; + } + } + + private async void OnFolderClickEmuSecond(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; + + if (_storageProvider != null) + { + var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + { + Title = "Select a Folder for the ISO Files...", + AllowMultiple = false + }); + + if (_fetchFolder.Count >= 1) + PathRoms.Text = _fetchFolder[0].Path.LocalPath; + } + } + private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel; @@ -214,20 +254,38 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv } private void OnPathChanged(object? sender, TextChangedEventArgs e) - { + { if (PlatformBox.SelectedItem != null) { - var _fetchPath1525 = PathCollectionFirst.Text; - var _fetchPath28 = PathCollectionSecond.Text; + if (PlatformBox.SelectedIndex == 0x00) + { + var _fetchPathEmulator = PathEmulator.Text; + var _fetchPathRoms = PathRoms.Text; - var _isValid1525 = !String.IsNullOrEmpty(_fetchPath1525) && File.Exists(Path.Combine(_fetchPath1525, "KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe")); - var _isValid28 = !String.IsNullOrEmpty(_fetchPath28) && File.Exists(Path.Combine(_fetchPath28, "KINGDOM HEARTS HD 2.8 Final Chapter Prologue.exe")); + var _isValidEmulator = !String.IsNullOrEmpty(_fetchPathEmulator) && File.Exists(_fetchPathEmulator); + var _isValidRoms = !String.IsNullOrEmpty(_fetchPathRoms) && Directory.Exists(_fetchPathRoms); - if (!_isValid1525 && !_isValid28) - WeakReferenceMessenger.Default.Send(new BlockNextRequest()); + if (!_isValidEmulator && !_isValidRoms) + WeakReferenceMessenger.Default.Send(new BlockNextRequest()); + + else + WeakReferenceMessenger.Default.Send(new UnblockNextRequest()); + } else - WeakReferenceMessenger.Default.Send(new UnblockNextRequest()); + { + var _fetchPath1525 = PathCollectionFirst.Text; + var _fetchPath28 = PathCollectionSecond.Text; + + var _isValid1525 = !String.IsNullOrEmpty(_fetchPath1525) && File.Exists(Path.Combine(_fetchPath1525, "KINGDOM HEARTS HD 1.5+2.5 ReMIX.exe")); + var _isValid28 = !String.IsNullOrEmpty(_fetchPath28) && File.Exists(Path.Combine(_fetchPath28, "KINGDOM HEARTS HD 2.8 Final Chapter Prologue.exe")); + + if (!_isValid1525 && !_isValid28) + WeakReferenceMessenger.Default.Send(new BlockNextRequest()); + + else + WeakReferenceMessenger.Default.Send(new UnblockNextRequest()); + } } } } From 4b0eccf3efc9c4eef884e5a2e3d8345453b2607f Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 22:37:17 +0200 Subject: [PATCH 166/175] [Mod Manager] - Remove debug. --- OpenKh.Tools.ModManager/Services/InjectorService.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index 03346c8c8..1f0b92903 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -199,7 +199,6 @@ public InjectorService(Config _targetConfig) catch (FileNotFoundException) { _fetchStream = new ProcessStream(_targetProcess, 0x20000000, 0x20000000); } } - if (_fetchStream == null) { @@ -216,7 +215,6 @@ public InjectorService(Config _targetConfig) _fetchStream.SetPosition(_hookPtrLoad); - uint[] _fetchHookLoad = [ MIPS.LUI(MIPS.T6, 0x0F), @@ -315,8 +313,6 @@ public InjectorService(Config _targetConfig) ///////////////////////////////////////////////////////////////////// - START_PROC: - var _fetchOpcodeAddr = (0x0F << 0x10) - 0x04; _fetchStream.SetPosition(_fetchOpcodeAddr); @@ -340,10 +336,7 @@ public InjectorService(Config _targetConfig) var _fetchName = _fetchStream.ReadString(0x30, Encoding.ASCII); if (String.IsNullOrEmpty(_fetchName)) - { - Debug.WriteLine("CAUGHT NO-NAME FILE!"); goto LOOP_START; - } Debug.WriteLine(_fetchName); From 8c930f5d08baa0b84bae4cd910c39bb44d240284 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 22:37:35 +0200 Subject: [PATCH 167/175] [Mod Manager] - Fix PINEStream READ. --- OpenKh.Tools.ModManager/Classes/PINEStream.cs | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/OpenKh.Tools.ModManager/Classes/PINEStream.cs b/OpenKh.Tools.ModManager/Classes/PINEStream.cs index 1327174bb..5be5b9e2d 100644 --- a/OpenKh.Tools.ModManager/Classes/PINEStream.cs +++ b/OpenKh.Tools.ModManager/Classes/PINEStream.cs @@ -83,7 +83,8 @@ enum PINE_RESPONSE : byte IPC_FAIL = 0xFF }; - const int MAX_PACKET_LENGTH = 640000; + const int MAX_SEND_LENGTH = 640000; + const int MAX_RECEIVE_LENGTH = 440000; readonly object _syncLock = new(); readonly string _socketPath; @@ -178,6 +179,9 @@ public override int Read(byte[] buffer, int offset, int count) { lock (_syncLock) { + uint _initialPosition = _currPosition; + uint _pastByteLength = 0x00; + if (_hasDisposed) throw new ObjectDisposedException(nameof(PINEStream)); @@ -190,14 +194,14 @@ public override int Read(byte[] buffer, int offset, int count) if (offset < 0 || count <= 0 || offset > buffer.Length - count || count > Length - _currPosition) throw new ArgumentOutOfRangeException(); - var _makePacket = new List(); - var _messageList = new List(); - - for (int i = 0; i < count;) + for (uint _packageLength = 0x00, _packageBytes = 0x00, _packageReceive = 0x00; _packageBytes < count;) { - for (uint z = 0, s = 0; s < MAX_PACKET_LENGTH - 0x04 && z < i + count;) + var _makePacket = new List(); + var _messageList = new List(); + + for (; _packageLength < MAX_SEND_LENGTH - 0x04 && _packageReceive < MAX_RECEIVE_LENGTH - 0x04 && _packageBytes < count;) { - var _byteRemain = count - i - z; + var _byteRemain = count - _packageBytes; var _makeMessage = new PINE_MESSAGE { @@ -205,17 +209,20 @@ public override int Read(byte[] buffer, int offset, int count) _byteRemain >= 0x04 ? PINE_COMMAND.READ_INT32 : _byteRemain >= 0x02 ? PINE_COMMAND.READ_INT16 : PINE_COMMAND.READ_INT08, - Address = _currPosition + z + Address = _initialPosition + _packageBytes }; _messageList.Add(_makeMessage); _makePacket.AddRange(_makeMessage.ToArray()); - z += _byteRemain >= 0x08 ? 0x08U : - _byteRemain >= 0x04 ? 0x04U : - _byteRemain >= 0x02 ? 0x02U : 0x01U; + var _currLength = _byteRemain >= 0x08 ? 0x08U : + _byteRemain >= 0x04 ? 0x04U : + _byteRemain >= 0x02 ? 0x02U : 0x01U; - s += _makeMessage.Length; + _packageBytes += _currLength; + _packageReceive += _currLength; + + _packageLength += _makeMessage.Length; } var _finalSize = _makePacket.Count + 0x04; @@ -243,13 +250,14 @@ public override int Read(byte[] buffer, int offset, int count) var _fetchCodeResponse = _fetchDataResponse[0x00]; if (_fetchCodeResponse == (byte)PINE_RESPONSE.IPC_FAIL) - return i; + return (int) _packageBytes; if (_fetchCodeResponse != (byte)PINE_RESPONSE.IPC_OK) throw new IOException($"Unknown PINE response: 0x{_fetchCodeResponse.ToString("X2")}"); for (int s = 0; s < _fetchDataResponse.Length - 0x05;) { + var _fetchOffset = offset + s + (int)_pastByteLength; var _byteRemain = _fetchDataResponse.Length - 0x05 - s; var _readLength = _byteRemain >= 0x08 ? 0x08 : @@ -257,7 +265,7 @@ public override int Read(byte[] buffer, int offset, int count) _byteRemain >= 0x02 ? 0x02 : 0x01; var _fetchSpanRead = _fetchDataResponse.AsSpan(0x01 + s, _readLength); - var _fetchSpanWrite = buffer.AsSpan(offset + i + s, _readLength); + var _fetchSpanWrite = buffer.AsSpan(_fetchOffset, _readLength); switch (_readLength) { @@ -283,15 +291,18 @@ public override int Read(byte[] buffer, int offset, int count) break; default: - buffer[offset + i] = _fetchDataResponse[0x05]; + buffer[_fetchOffset] = _fetchDataResponse[0x05]; break; } s += _readLength; } - i += _finalSize - 0x05; - _currPosition += (uint)_finalSize - 0x05; + _pastByteLength = _packageBytes; + _currPosition = _initialPosition + _packageBytes; + + _packageReceive = 0x00; + _packageLength = 0x00; } return count; @@ -303,7 +314,6 @@ public override void Write(byte[] buffer, int offset, int count) lock (_syncLock) { uint _initialPosition = _currPosition; - string _fileName = "fileDebug_" + count + ".dbg"; if (_hasDisposed || !_isValid) throw new ObjectDisposedException(nameof(PINEStream)); @@ -319,9 +329,7 @@ public override void Write(byte[] buffer, int offset, int count) var _makePacket = new List(); var _messageList = new List(); - var _fetchData = new List(); - - for (; _packageLength < MAX_PACKET_LENGTH - 0x04 && _packageBytes < count;) + for (; _packageLength < MAX_SEND_LENGTH - 0x04 && _packageBytes < count;) { var _makeMessage = new PINE_MESSAGE(); From 3cac9c0d8644222f428b464d20d003202d21e141 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 22:51:06 +0200 Subject: [PATCH 168/175] [Mod Manager] Add Windows Support for PINE (Only Temporary) --- OpenKh.Tools.ModManager/Classes/PINEStream.cs | 76 +++++++++++++------ 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/OpenKh.Tools.ModManager/Classes/PINEStream.cs b/OpenKh.Tools.ModManager/Classes/PINEStream.cs index 5be5b9e2d..f248cbf4d 100644 --- a/OpenKh.Tools.ModManager/Classes/PINEStream.cs +++ b/OpenKh.Tools.ModManager/Classes/PINEStream.cs @@ -98,36 +98,20 @@ enum PINE_RESPONSE : byte public PINEStream(int slot = 28011) { - if (slot < 1 || slot > 65535) - throw new ArgumentOutOfRangeException(nameof(slot)); - - var _fetchRuntimePath = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); - - _socketPath = String.IsNullOrEmpty(_fetchRuntimePath) ? "/tmp/pcsx2.sock" : Path.Combine(_fetchRuntimePath, "pcsx2.sock"); - - if (slot != 28011) - _socketPath += "." + slot; - - var _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - - try + if (OperatingSystem.IsLinux()) { - var _fetchEndPoint = new UnixDomainSocketEndPoint(_socketPath); - _fetchSocket.Connect(_fetchEndPoint); - - _mainSocket = _fetchSocket; - } + if (slot < 1 || slot > 65535) + throw new ArgumentOutOfRangeException(nameof(slot)); - catch - { - _fetchSocket.Dispose(); - _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + var _fetchRuntimePath = Environment.GetEnvironmentVariable("XDG_RUNTIME_DIR"); - _socketPath = "/tmp/pcsx2.sock"; + _socketPath = String.IsNullOrEmpty(_fetchRuntimePath) ? "/tmp/pcsx2.sock" : Path.Combine(_fetchRuntimePath, "pcsx2.sock"); if (slot != 28011) _socketPath += "." + slot; + var _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + try { var _fetchEndPoint = new UnixDomainSocketEndPoint(_socketPath); @@ -136,7 +120,51 @@ public PINEStream(int slot = 28011) _mainSocket = _fetchSocket; } - catch { _fetchSocket.Dispose(); } + catch + { + _fetchSocket.Dispose(); + _fetchSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + + _socketPath = "/tmp/pcsx2.sock"; + + if (slot != 28011) + _socketPath += "." + slot; + + try + { + var _fetchEndPoint = new UnixDomainSocketEndPoint(_socketPath); + _fetchSocket.Connect(_fetchEndPoint); + + _mainSocket = _fetchSocket; + } + + catch { _fetchSocket.Dispose(); } + } + } + + else + { + if (slot < 1 || slot > 65535) + throw new ArgumentOutOfRangeException(nameof(slot)); + + try + { + var socket = new Socket( + AddressFamily.InterNetwork, + SocketType.Stream, + ProtocolType.Tcp + ); + + socket.Connect(IPAddress.Loopback, slot); + + _mainSocket = socket; + _isValid = true; + } + catch (SocketException) + { + _mainSocket = null; + _isValid = false; + } } _isValid = _mainSocket != null; From f03266c1a1771abfe9b0a3492c26c518c83b3414 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 22:51:21 +0200 Subject: [PATCH 169/175] [Mod Manager] Apparently, old and simple methods are often best. --- .../Services/InjectorService.cs | 28 ++++--------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index 1f0b92903..7ba33ec79 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -1,26 +1,16 @@ -using LibGit2Sharp; using OpenKh.Common; using OpenKh.Tools.Common; using OpenKh.Tools.ModManager.Classes; -using SkiaSharp; using System; using System.Buffers; -using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Drawing; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; -using Xe.BinaryMapper; -using static Antlr4.Runtime.Atn.SemanticContext; -using static OpenKh.Kh2.Ard.AreaDataScript; -using static OpenKh.Kh2.Ard.Event.Light; -using static OpenKh.Kh2.Constants; namespace OpenKh.Tools.ModManager.Services { @@ -344,19 +334,11 @@ public InjectorService(Config _targetConfig) if (_couldResolve != 0x00 && _couldResolve != 0x03) { - using (var mmf = MemoryMappedFile.CreateFromFile(_filePath, FileMode.Open, null, 0, MemoryMappedFileAccess.Read)) - { - using (var accessor = mmf.CreateViewAccessor(0, 0, MemoryMappedFileAccess.Read)) - { - _fetchSize = (int) accessor.Capacity; - var _fetchData = new byte[_fetchSize]; - - accessor.ReadArray(0x00, _fetchData, 0x00, _fetchSize); - - _fetchStream.SetPosition(_destinationPtr); - _fetchStream.Write(_fetchData); - } - } + var _fetchData = File.ReadAllBytes(_filePath); + _fetchSize = _fetchData.Length; + + _fetchStream.SetPosition(_destinationPtr); + _fetchStream.Write(_fetchData); } _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); From 3d1dca628c31c65d0d07bc246ae83364a6a76c0a Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Sun, 27 Sep 2026 23:18:06 +0200 Subject: [PATCH 170/175] [Mod Manager] - Brute-force a connect with PINE on Linux. --- OpenKh.Tools.ModManager/Classes/PINEStream.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/OpenKh.Tools.ModManager/Classes/PINEStream.cs b/OpenKh.Tools.ModManager/Classes/PINEStream.cs index f248cbf4d..933babe0d 100644 --- a/OpenKh.Tools.ModManager/Classes/PINEStream.cs +++ b/OpenKh.Tools.ModManager/Classes/PINEStream.cs @@ -6,6 +6,7 @@ using System.Runtime.InteropServices; using System.Linq; using System.Net; +using System.Diagnostics; namespace OpenKh.Tools.ModManager.Classes { @@ -100,6 +101,8 @@ public PINEStream(int slot = 28011) { if (OperatingSystem.IsLinux()) { + TRY_SOCK_CONNECT: + if (slot < 1 || slot > 65535) throw new ArgumentOutOfRangeException(nameof(slot)); @@ -138,7 +141,11 @@ public PINEStream(int slot = 28011) _mainSocket = _fetchSocket; } - catch { _fetchSocket.Dispose(); } + catch + { + _fetchSocket.Dispose(); + goto TRY_SOCK_CONNECT; + } } } From 6609cb97d678b64ebc37b972b3e6004733dee12d Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 28 Sep 2026 06:31:20 +0200 Subject: [PATCH 171/175] [Mod Manager] - Make PS2 load stuff without extraction. --- .../Services/InjectorService.cs | 128 ++++++++---------- 1 file changed, 54 insertions(+), 74 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index 7ba33ec79..ab24ee5f2 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -1,4 +1,5 @@ using OpenKh.Common; +using OpenKh.Engine.Input; using OpenKh.Tools.Common; using OpenKh.Tools.ModManager.Classes; using System; @@ -101,37 +102,9 @@ byte ResolvePath(string input, out string? finalPath) return 0x01; } - var _fetchDataFile = Path.Combine(_fetchDataPath, input); - var _fetchDataFileRegion = Path.Combine(_fetchDataPath, _fetchRegionInput); - - if (File.Exists(_fetchDataFile)) - { - finalPath = _fetchDataFile; - return 0x02; - } - - else if (File.Exists(_fetchDataFileRegion)) - { - finalPath = _fetchDataFileRegion; - return 0x02; - } - finalPath = null; return 0x03; } - - public int ResolveSize(string input) - { - var _couldResolve = ResolvePath(input, out var _filePath); - - if (_couldResolve != 0x00 && _couldResolve != 0x03) - { - var _fetchInfo = new FileInfo(_filePath); - return (int)_fetchInfo.Length; - } - - return -1; - } public InjectorService(Config _targetConfig) { @@ -200,7 +173,7 @@ public InjectorService(Config _targetConfig) { LOOP_START: - if (_cancelToken.IsCancellationRequested) + if (_cancelToken.IsCancellationRequested || _targetProcess.HasExited) return; _fetchStream.SetPosition(_hookPtrLoad); @@ -208,42 +181,42 @@ public InjectorService(Config _targetConfig) uint[] _fetchHookLoad = [ MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.A0, MIPS.T6, -0x08), - MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V0, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 0x02), - MIPS.NOP(), - MIPS.ADDIU(MIPS.RA, MIPS.RA, 4), - MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), - MIPS.SD(MIPS.T4, MIPS.SP, 0x08), - MIPS.SD(MIPS.S0, MIPS.SP, 0x00), - MIPS.JR(MIPS.RA), - MIPS.NOP() + MIPS.SW(MIPS.A0, MIPS.T6, -0x08), + MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), + MIPS.SW(MIPS.T5, MIPS.T6, -0x04), + MIPS.LW(MIPS.T5, MIPS.T6, -0x04), + MIPS.BNE(MIPS.T5, 0x00, -2), + MIPS.LW(MIPS.V0, MIPS.T6, -0x08), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 0x02), + MIPS.NOP(), + MIPS.ADDIU(MIPS.RA, MIPS.RA, 4), + MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), + MIPS.SD(MIPS.T4, MIPS.SP, 0x08), + MIPS.SD(MIPS.S0, MIPS.SP, 0x00), + MIPS.JR(MIPS.RA), + MIPS.NOP() ]; uint[] _fetchHookSize = [ MIPS.LUI(MIPS.T6, 0x0F), - MIPS.SW(MIPS.A0, MIPS.T6, -0x08), - MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), - MIPS.SW(MIPS.A2, MIPS.T6, -0x10), - MIPS.SW(MIPS.A3, MIPS.T6, -0x14), - MIPS.SW(MIPS.T5, MIPS.T6, -0x04), - MIPS.LW(MIPS.T5, MIPS.T6, -0x04), - MIPS.BNE(MIPS.T5, 0x00, -2), - MIPS.LW(MIPS.V0, MIPS.T6, -0x08), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.JR(MIPS.T4), - MIPS.NOP(), - MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), - MIPS.SD(MIPS.T4, MIPS.SP, 0x08), - MIPS.SD(MIPS.S0, MIPS.SP, 0x00), - MIPS.JR(MIPS.RA), - MIPS.NOP() + MIPS.SW(MIPS.A0, MIPS.T6, -0x08), + MIPS.SW(MIPS.A1, MIPS.T6, -0x0C), + MIPS.SW(MIPS.A2, MIPS.T6, -0x10), + MIPS.SW(MIPS.A3, MIPS.T6, -0x14), + MIPS.SW(MIPS.T5, MIPS.T6, -0x04), + MIPS.LW(MIPS.T5, MIPS.T6, -0x04), + MIPS.BNE(MIPS.T5, 0x00, -2), + MIPS.LW(MIPS.V0, MIPS.T6, -0x08), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 0x03), + MIPS.NOP(), + MIPS.JR(MIPS.T4), + MIPS.NOP(), + MIPS.ADDIU(MIPS.SP, MIPS.SP, -0x10), + MIPS.SD(MIPS.T4, MIPS.SP, 0x08), + MIPS.SD(MIPS.S0, MIPS.SP, 0x00), + MIPS.JR(MIPS.RA), + MIPS.NOP() ]; if (_fetchStream.ReadUInt32() == 0x00) @@ -280,17 +253,17 @@ public InjectorService(Config _targetConfig) uint[] _fetchFunction = [ MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), - MIPS.JAL(_hookPtrSize), - MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), - MIPS.JAL(_subFileSizeFunc), - MIPS.NOP(), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), - MIPS.LD(MIPS.RA, MIPS.SP, 0x08), - MIPS.JR(MIPS.RA), - MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), - ]; + MIPS.JAL(_hookPtrSize), + MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), + MIPS.JAL(_subFileSizeFunc), + MIPS.NOP(), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), + MIPS.NOP(), + MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), + MIPS.LD(MIPS.RA, MIPS.SP, 0x08), + MIPS.JR(MIPS.RA), + MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), + ]; byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); @@ -332,7 +305,7 @@ public InjectorService(Config _targetConfig) var _couldResolve = ResolvePath(_fetchName, out var _filePath); - if (_couldResolve != 0x00 && _couldResolve != 0x03) + if (_couldResolve == 0x01) { var _fetchData = File.ReadAllBytes(_filePath); _fetchSize = _fetchData.Length; @@ -360,10 +333,17 @@ public InjectorService(Config _targetConfig) if (String.IsNullOrEmpty(_fetchName)) goto LOOP_START; - var _fetchFileSize = ResolveSize(_fetchName); + var _fetchSize = 0x00; + var _couldResolve = ResolvePath(_fetchName, out var _filePath); + + if (_couldResolve == 0x01) + { + var _fetchInfo = new FileInfo(_filePath); + _fetchSize = (int)_fetchInfo.Length; + } _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); - _fetchStream.Write(_fetchFileSize); + _fetchStream.Write(_fetchSize); _fetchStream.Flush(); } From b61b18b2e557582e7f4052393cf622f4a2657794 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 28 Sep 2026 07:22:51 +0200 Subject: [PATCH 172/175] [Mod Manager] - Preliminary logging (Very early, subject to change.) --- .../Services/InjectorService.cs | 99 ++++++++++++------- .../Services/LogService.cs | 76 ++++++++++++++ OpenKh.Tools.ModManager/Views/LogView.axaml | 12 +++ .../Views/LogView.axaml.cs | 14 +++ 4 files changed, 164 insertions(+), 37 deletions(-) create mode 100644 OpenKh.Tools.ModManager/Services/LogService.cs create mode 100644 OpenKh.Tools.ModManager/Views/LogView.axaml create mode 100644 OpenKh.Tools.ModManager/Views/LogView.axaml.cs diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index ab24ee5f2..4f560c716 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -51,8 +51,6 @@ public class InjectorService CancellationTokenSource _cancelSource; CancellationToken _cancelToken; - long _memoryOffset = 0x00; - uint _loadFileFunc = 0x1682b8; uint _getFileSizeFunc = 0x1AE1B0; uint _subFileSizeFunc = 0x1AE460; @@ -64,8 +62,6 @@ public class InjectorService uint _hookPtrLoad = 0xFFF00; uint _hookPtrSize = 0xFFF00; - bool _functionsWritten = false; - public Config TargetConfig { get; set; } byte ResolvePath(string input, out string? finalPath) @@ -108,6 +104,10 @@ byte ResolvePath(string input, out string? finalPath) public InjectorService(Config _targetConfig) { + LogService.Show(); + + LogService.Log("Welcome to the Dynamic Mod Loader - v0.01!", 0x00); + TargetConfig = _targetConfig; _cancelSource = new CancellationTokenSource(); @@ -174,7 +174,10 @@ public InjectorService(Config _targetConfig) LOOP_START: if (_cancelToken.IsCancellationRequested || _targetProcess.HasExited) + { + LogService.Dismiss(); return; + } _fetchStream.SetPosition(_hookPtrLoad); @@ -221,6 +224,8 @@ public InjectorService(Config _targetConfig) if (_fetchStream.ReadUInt32() == 0x00) { + LogService.Log("Injecting hooks for LoadFile and GetFileSize...", 0x00); + byte[] _fetchHookArray = new byte[_fetchHookLoad.Length * sizeof(uint) + _fetchHookSize.Length * sizeof(uint)]; Buffer.BlockCopy(_fetchHookLoad, 0, _fetchHookArray, 0, _fetchHookLoad.Length * sizeof(uint)); @@ -230,46 +235,58 @@ public InjectorService(Config _targetConfig) _fetchStream.Write(_fetchHookArray); _hookPtrSize = _hookPtrLoad + (uint)_fetchHookLoad.Length * sizeof(uint); + + LogService.Log("Hooks successfully connected!", 0x00); } if (_loadFileFunc > 0) { - uint[] _fetchFunction = - [ - MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), - MIPS.JAL(_hookPtrLoad), - MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x01) - ]; - - byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; - Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); - - _fetchStream.SetPosition(_loadFileFunc); - _fetchStream.Write(_fetchFunctionArray); + _fetchStream.SetPosition(_loadFileFunc + 0x04); + + if (_fetchStream.ReadUInt32() != MIPS.JAL(_hookPtrLoad)) + { + uint[] _fetchFunction = + [ + MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), + MIPS.JAL(_hookPtrLoad), + MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x01) + ]; + + byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; + Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); + + _fetchStream.SetPosition(_loadFileFunc); + _fetchStream.Write(_fetchFunctionArray); + } } if (_getFileSizeFunc > 0) { - uint[] _fetchFunction = - [ - MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), - MIPS.JAL(_hookPtrSize), - MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), - MIPS.JAL(_subFileSizeFunc), - MIPS.NOP(), - MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), - MIPS.NOP(), - MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), - MIPS.LD(MIPS.RA, MIPS.SP, 0x08), - MIPS.JR(MIPS.RA), - MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), - ]; - - byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; - Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); - - _fetchStream.SetPosition(_getFileSizeFunc); - _fetchStream.Write(_fetchFunctionArray); + _fetchStream.SetPosition(_getFileSizeFunc + 0x04); + + if (_fetchStream.ReadUInt32() != MIPS.JAL(_hookPtrLoad)) + { + uint[] _fetchFunction = + [ + MIPS.ADDIU(MIPS.T4, MIPS.RA, 0), + MIPS.JAL(_hookPtrSize), + MIPS.ADDIU(MIPS.T5, MIPS.Zero, 0x02), + MIPS.JAL(_subFileSizeFunc), + MIPS.NOP(), + MIPS.BEQ(MIPS.V0, MIPS.Zero, 2), + MIPS.NOP(), + MIPS.LW(MIPS.V0, MIPS.V0, 0x0C), + MIPS.LD(MIPS.RA, MIPS.SP, 0x08), + MIPS.JR(MIPS.RA), + MIPS.ADDIU(MIPS.SP, MIPS.SP, 0x10), + ]; + + byte[] _fetchFunctionArray = new byte[_fetchFunction.Length * sizeof(uint)]; + Buffer.BlockCopy(_fetchFunction, 0, _fetchFunctionArray, 0, _fetchFunctionArray.Length); + + _fetchStream.SetPosition(_getFileSizeFunc); + _fetchStream.Write(_fetchFunctionArray); + } } _fetchStream.Flush(); @@ -281,8 +298,11 @@ public InjectorService(Config _targetConfig) _fetchStream.SetPosition(_fetchOpcodeAddr); var _fetchOpcode = _fetchStream.ReadInt32(); - if (_fetchStream.Position == _fetchOpcode || _targetProcess.HasExited) + if (_fetchStream.Position == _fetchOpcode) + { + LogService.Dismiss(); return; + } switch (_fetchOpcode) { @@ -307,6 +327,8 @@ public InjectorService(Config _targetConfig) if (_couldResolve == 0x01) { + LogService.Log($"Redirecting file \"{_fetchName}\" to the build path...", 0x00); + var _fetchData = File.ReadAllBytes(_filePath); _fetchSize = _fetchData.Length; @@ -314,6 +336,9 @@ public InjectorService(Config _targetConfig) _fetchStream.Write(_fetchData); } + else + LogService.Log($"Loading file \"{_fetchName}\" from the ISO...", 0x00); + _fetchStream.SetPosition(_fetchOpcodeAddr - 0x04); _fetchStream.Write(_fetchSize); diff --git a/OpenKh.Tools.ModManager/Services/LogService.cs b/OpenKh.Tools.ModManager/Services/LogService.cs new file mode 100644 index 000000000..4a85b8578 --- /dev/null +++ b/OpenKh.Tools.ModManager/Services/LogService.cs @@ -0,0 +1,76 @@ +using Avalonia.Controls.Documents; +using Avalonia.Threading; +using OpenKh.Tools.ModManager.Views; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpenKh.Tools.ModManager.Services +{ + public static class LogService + { + static LogView? _logView = new LogView(); + static readonly object _lockObj = new object(); + + public static void Show() + { + Dispatcher.UIThread.Post(() => + { + _logView.Loggers.Inlines.Clear(); + _logView.Show(); + }); + } + + public static void Dismiss() + { + Dispatcher.UIThread.Post(() => + { + _logView.Loggers.Inlines.Clear(); + _logView.Hide(); + }); + } + + public static void Log(string message, byte type) + { + lock (_lockObj) + { + var _stringFormat = "[{0}] - {1}\n"; + + var _fetchLog = ""; + var _colorHex = ""; + + switch (type) + { + case 0x00: + _fetchLog = String.Format(_stringFormat, DateTime.Now, message); + _colorHex = "#4CFCFC"; + break; + case 0x01: + _fetchLog = String.Format(_stringFormat, DateTime.Now, message); + _colorHex = "#F0F040"; + break; + case 0x02: + _fetchLog = String.Format(_stringFormat, DateTime.Now, message); + _colorHex = "#F04040"; + break; + } + + Dispatcher.UIThread.Post(() => + { + Run _fetchInline = new Run + { + Text = _fetchLog, + Foreground = Avalonia.Media.Brush.Parse(_colorHex) + }; + + _logView.Loggers.Inlines.Add(_fetchInline); + _logView.Scroller.ScrollToEnd(); + }); + } + } + } +} diff --git a/OpenKh.Tools.ModManager/Views/LogView.axaml b/OpenKh.Tools.ModManager/Views/LogView.axaml new file mode 100644 index 000000000..218278a74 --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/LogView.axaml @@ -0,0 +1,12 @@ + + + + + + diff --git a/OpenKh.Tools.ModManager/Views/LogView.axaml.cs b/OpenKh.Tools.ModManager/Views/LogView.axaml.cs new file mode 100644 index 000000000..1ac97ab3a --- /dev/null +++ b/OpenKh.Tools.ModManager/Views/LogView.axaml.cs @@ -0,0 +1,14 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace OpenKh.Tools.ModManager.Views +{ + public partial class LogView : Window + { + public LogView() + { + InitializeComponent(); + } + } +} From 5a16a9f3aed1dc615c1814a313b24d0eaeb13cb3 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Mon, 28 Sep 2026 12:32:57 +0200 Subject: [PATCH 173/175] [Mod Manager] - Ability to choose distinct ISOs for PS2, better logging. --- .../Services/InjectorService.cs | 13 +-- .../Services/LogService.cs | 33 ++---- OpenKh.Tools.ModManager/Views/LogView.axaml | 4 +- .../Wizard/WizardGameSetup.axaml | 29 +++-- .../Wizard/WizardGameSetup.axaml.cs | 100 +++++++++++++++--- 5 files changed, 120 insertions(+), 59 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/InjectorService.cs b/OpenKh.Tools.ModManager/Services/InjectorService.cs index 4f560c716..4d1c9a27f 100644 --- a/OpenKh.Tools.ModManager/Services/InjectorService.cs +++ b/OpenKh.Tools.ModManager/Services/InjectorService.cs @@ -17,12 +17,6 @@ namespace OpenKh.Tools.ModManager.Services { public class InjectorService { - public static Dictionary ISO_NAMES = new Dictionary - { - { Game.KINGDOM_HEARTS, "KHFM.iso"}, - { Game.KINGDOM_HEARTS_II, "KHIIFM.iso"}, - }; - private static readonly HashSet DENY_FILES = new HashSet { "dkmovie.x", @@ -105,7 +99,6 @@ byte ResolvePath(string input, out string? finalPath) public InjectorService(Config _targetConfig) { LogService.Show(); - LogService.Log("Welcome to the Dynamic Mod Loader - v0.01!", 0x00); TargetConfig = _targetConfig; @@ -116,11 +109,13 @@ public InjectorService(Config _targetConfig) Stream? _fetchStream = null; var _fetchPathEmu = PathService.ResolveEmulator(TargetConfig); + var _fetchPathGame = _targetConfig.Frontend.TargetGame == Game.KINGDOM_HEARTS ? TargetConfig.Emulator.RomPath[0] : TargetConfig.Emulator.RomPath[1]; + _targetProcess = new Process { StartInfo = new ProcessStartInfo(_fetchPathEmu) { - Arguments = Path.Combine(TargetConfig.Emulator.RomPath[0], ISO_NAMES[TargetConfig.Frontend.TargetGame]), + Arguments = $"\"{_fetchPathGame}\"", WorkingDirectory = Path.GetDirectoryName(_fetchPathEmu), RedirectStandardInput = true, RedirectStandardOutput = true, @@ -321,8 +316,6 @@ public InjectorService(Config _targetConfig) if (String.IsNullOrEmpty(_fetchName)) goto LOOP_START; - Debug.WriteLine(_fetchName); - var _couldResolve = ResolvePath(_fetchName, out var _filePath); if (_couldResolve == 0x01) diff --git a/OpenKh.Tools.ModManager/Services/LogService.cs b/OpenKh.Tools.ModManager/Services/LogService.cs index 4a85b8578..e0302e969 100644 --- a/OpenKh.Tools.ModManager/Services/LogService.cs +++ b/OpenKh.Tools.ModManager/Services/LogService.cs @@ -1,13 +1,9 @@ -using Avalonia.Controls.Documents; +using System; + using Avalonia.Threading; +using Avalonia.Controls.Documents; + using OpenKh.Tools.ModManager.Views; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace OpenKh.Tools.ModManager.Services { @@ -39,25 +35,10 @@ public static void Log(string message, byte type) lock (_lockObj) { var _stringFormat = "[{0}] - {1}\n"; + var _fetchDate = DateTime.Now.ToString("HH:mm:ss"); - var _fetchLog = ""; - var _colorHex = ""; - - switch (type) - { - case 0x00: - _fetchLog = String.Format(_stringFormat, DateTime.Now, message); - _colorHex = "#4CFCFC"; - break; - case 0x01: - _fetchLog = String.Format(_stringFormat, DateTime.Now, message); - _colorHex = "#F0F040"; - break; - case 0x02: - _fetchLog = String.Format(_stringFormat, DateTime.Now, message); - _colorHex = "#F04040"; - break; - } + var _fetchLog = String.Format(_stringFormat, _fetchDate, message); + var _colorHex = type == 0x00 ? "#4CCCCC" : (type == 0x01 ? "#CCCC4C" : "#CC4C4C"); Dispatcher.UIThread.Post(() => { diff --git a/OpenKh.Tools.ModManager/Views/LogView.axaml b/OpenKh.Tools.ModManager/Views/LogView.axaml index 218278a74..c35e9ebf5 100644 --- a/OpenKh.Tools.ModManager/Views/LogView.axaml +++ b/OpenKh.Tools.ModManager/Views/LogView.axaml @@ -4,9 +4,9 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Width="650" Height="350" x:Class="OpenKh.Tools.ModManager.Views.LogView" - Title="Mod Manager - Debug Log" Background="DimGray"> + Title="Mod Manager - Debug Log" Background="#404040"> - + diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml index 49240dfb8..3f496fa6b 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml @@ -17,6 +17,8 @@ 7.78033C8.51836 7.92098 8.32759 8 8.12868 8H2ZM2 9.5V17.75C2 19.5449 3.45507 21 5.25 21H18.75C20.5449 21 22 19.5449 22 17.75V8.75C22 6.95507 20.5449 5.5 18.75 5.5H13.0607L9.71967 8.84099C9.29771 9.26295 8.72542 9.5 8.12868 9.5H2Z + M3 6.25C3 4.45507 4.45507 3 6.25 3H17.75C19.5449 3 21 4.45507 21 6.25V17.75C21 19.5449 19.5449 21 17.75 21H6.25C4.45507 21 3 19.5449 3 17.75V6.25ZM6.25 4.5C5.2835 4.5 4.5 5.2835 4.5 6.25V17.75C4.5 18.7165 5.2835 19.5 6.25 19.5H17.75C18.7165 19.5 19.5 18.7165 19.5 17.75V6.25C19.5 5.2835 18.7165 4.5 17.75 4.5H6.25Z + M6.25 3C4.45507 3 3 4.45507 3 6.25V17.75C3 19.5449 4.45507 21 6.25 21H17.75C19.5449 21 21 19.5449 21 17.75V6.25C21 4.45507 19.5449 3 17.75 3H6.25ZM4.5 6.25C4.5 5.2835 5.2835 4.5 6.25 4.5H17.75C18.7165 4.5 19.5 5.2835 19.5 6.25V17.75C19.5 18.7165 18.7165 19.5 17.75 19.5H6.25C5.2835 19.5 4.5 18.7165 4.5 17.75V6.25ZM17.28 9.28064C17.5731 8.98791 17.5734 8.51304 17.2806 8.21998C16.9879 7.92691 16.513 7.92664 16.22 8.21936L9.99658 14.4356L7.78084 12.2197C7.48795 11.9268 7.01308 11.9268 6.72018 12.2196C6.42727 12.5125 6.42726 12.9874 6.72014 13.2803L9.46591 16.0262C9.75868 16.319 10.2333 16.3192 10.5263 16.0266L17.28 9.28064Z @@ -109,7 +111,7 @@ - NOTE: Please be advised that all Lua related features will only with with PCSX2-EX. On Linux, only the AppImage release is supported. + NOTE: Please be advised that all Lua related features will only work with PCSX2-EX. On Linux, only the AppImage release is supported. Enter path for the @@ -129,7 +131,7 @@ - Enter path to the folder containing your + Click the appropriate buttons to choose your ISO Files: @@ -137,13 +139,22 @@ - - + + + + You should always use legally acquired ROMs. Using pirated copies will make you ineligible for support. diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index 5731b121e..dfb2a55dd 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -6,6 +6,7 @@ using Avalonia.Platform.Storage; using CommunityToolkit.Mvvm.Messaging; using Microsoft.Win32; +using OpenKh.Common; using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Services; using OpenKh.Tools.ModManager.ViewModels; @@ -95,21 +96,91 @@ private async void OnFolderClickEmuFirst(object? sender, Avalonia.Interactivity. } } - private async void OnFolderClickEmuSecond(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + private async void OnFileClickKH1(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { + var _fetchContext = DataContext as MainViewModel; + var _fetchConfig = _fetchContext.CurrentConfig; + var _fetchTopLevel = TopLevel.GetTopLevel(this); var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; if (_storageProvider != null) { - var _fetchFolder = await _storageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions + var _fetchFile = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions { - Title = "Select a Folder for the ISO Files...", - AllowMultiple = false + Title = "Select ISO File for Kingdom Hearts...", + AllowMultiple = false, + FileTypeFilter = [new FilePickerFileType("ISO Image") { Patterns = ["*.iso"] }] }); - if (_fetchFolder.Count >= 1) - PathRoms.Text = _fetchFolder[0].Path.LocalPath; + if (_fetchFile.Count >= 1) + { + using var _fetchStream = new FileStream(_fetchFile[0x00].Path.LocalPath, FileMode.Open); + var _fetchIDX = IsoUtility.GetFileOffset(_fetchStream, "KINGDOM.IDX;1"); + + if (_fetchIDX == -1) + { + _fetchConfig.Emulator.RomPath[0x00] = null; + await DialogService.ShowMessage(_fetchTopLevel as Window, "Invalid ISO for Kingdom Hearts!", "This file does not seem to be an ISO for Kingdom Hearts. Please check the file and try again.", Dialogs.MessageType.ERROR); + + IconMissingKH1.IsVisible = true; + IconFoundKH1.IsVisible = false; + } + + else + { + _fetchConfig.Emulator.RomPath[0x00] = _fetchFile[0x00].Path.LocalPath; + + IconMissingKH1.IsVisible = false; + IconFoundKH1.IsVisible = true; + } + + OnPathChanged(null, null); + } + } + } + + private async void OnFileClickKH2(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchConfig = _fetchContext.CurrentConfig; + + var _fetchTopLevel = TopLevel.GetTopLevel(this); + var _storageProvider = _fetchTopLevel != null ? _fetchTopLevel.StorageProvider : null; + + if (_storageProvider != null) + { + var _fetchFile = await _storageProvider.OpenFilePickerAsync(new FilePickerOpenOptions + { + Title = "Select ISO File for Kingdom Hearts II...", + AllowMultiple = false, + FileTypeFilter = [ new FilePickerFileType("ISO Image") { Patterns = ["*.iso"] } ] + }); + + if (_fetchFile.Count >= 1) + { + using var _fetchStream = new FileStream(_fetchFile[0x00].Path.LocalPath, FileMode.Open); + var _fetchIDX = IsoUtility.GetFileOffset(_fetchStream, "KH2.IDX;1"); + + if (_fetchIDX == -1) + { + _fetchConfig.Emulator.RomPath[0x01] = null; + await DialogService.ShowMessage(_fetchTopLevel as Window, "Invalid ISO for Kingdom Hearts II!", "This file does not seem to be an ISO for Kingdom Hearts II. Please check the file and try again.", Dialogs.MessageType.ERROR); + + IconMissingKH2.IsVisible = true; + IconFoundKH2.IsVisible = false; + } + + else + { + _fetchConfig.Emulator.RomPath[0x01] = _fetchFile[0x00].Path.LocalPath; + + IconMissingKH2.IsVisible = false; + IconFoundKH2.IsVisible = true; + } + + OnPathChanged(null, null); + } } } @@ -253,19 +324,24 @@ private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEv } } - private void OnPathChanged(object? sender, TextChangedEventArgs e) - { - if (PlatformBox.SelectedItem != null) + private void OnPathChanged(object? sender, TextChangedEventArgs? e) + { + var _fetchContext = DataContext as MainViewModel; + var _fetchConfig = _fetchContext != null ? _fetchContext.CurrentConfig : null; + + if (PlatformBox.SelectedItem != null && _fetchConfig != null) { if (PlatformBox.SelectedIndex == 0x00) { var _fetchPathEmulator = PathEmulator.Text; - var _fetchPathRoms = PathRoms.Text; + + var _fetchPathKH1 = _fetchConfig.Emulator.RomPath[0x00]; + var _fetchPathKH2 = _fetchConfig.Emulator.RomPath[0x01]; var _isValidEmulator = !String.IsNullOrEmpty(_fetchPathEmulator) && File.Exists(_fetchPathEmulator); - var _isValidRoms = !String.IsNullOrEmpty(_fetchPathRoms) && Directory.Exists(_fetchPathRoms); + var _isValidIsoFile = (!String.IsNullOrEmpty(_fetchPathKH1) || !String.IsNullOrEmpty(_fetchPathKH2)) && (File.Exists(_fetchPathKH1) || File.Exists(_fetchPathKH2)); - if (!_isValidEmulator && !_isValidRoms) + if (!_isValidEmulator || !_isValidIsoFile) WeakReferenceMessenger.Default.Send(new BlockNextRequest()); else From 57f54438531916cb27a63851ccdeb17f561f1c9a Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Tue, 29 Sep 2026 06:02:52 +0200 Subject: [PATCH 174/175] [Mod Manager] - Preliminary extractionless PS2 KH2. --- OpenKh.Patcher/PatcherProcessor.cs | 123 ++++++++++++------ .../Services/ModService.cs | 12 +- 2 files changed, 90 insertions(+), 45 deletions(-) diff --git a/OpenKh.Patcher/PatcherProcessor.cs b/OpenKh.Patcher/PatcherProcessor.cs index d67719971..2a294a2fe 100644 --- a/OpenKh.Patcher/PatcherProcessor.cs +++ b/OpenKh.Patcher/PatcherProcessor.cs @@ -4,6 +4,7 @@ using OpenKh.Common.Archives; using OpenKh.Egs; using OpenKh.Imaging; +using OpenKh.Kh1; using OpenKh.Kh2; using OpenKh.Kh2.Bdx.Models; using OpenKh.Kh2.Bdx.Utils; @@ -11,15 +12,18 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; +using System.Net.Security; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; +using Xe.IO; using YamlDotNet.Serialization; using static OpenKh.Kh2.Dpd; using static OpenKh.Patcher.PatcherProcessor; @@ -124,11 +128,6 @@ public async Task Patch( // A lot of this class relies on this existing. I am in no mood to rewrite that much code yet. var _fetchContext = new Context(modMetadata, extractDataPath, modFilesPath, buildOutputPath); - // If the targeted platform is PS2 and an extraction doesn't exist, throw an exception. - // We cannot do extractionless on PS2. - if (!_isExtraction && targetPlatform == 0x00) - throw new InvalidDataException("The PlayStation 2 Platform requires an extraction to be present for builds."); - // If the mod has a game declared and it doesn't match the current game, don't build it. if (modMetadata.Game != null && modMetadata.Game.ToLower() != _fetchGameId) return; @@ -145,7 +144,7 @@ public async Task Patch( var _assetTotalCount = modMetadata.Assets.Count; // If the build is not extraction-based: - if (!_isExtraction) + if (!_isExtraction && targetPlatform != 0x00) { // Fetch a list of all header files for the targeted game. var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); @@ -328,60 +327,100 @@ public async Task Patch( // If after the above the target file STILL does not exists, try to fetch it from the game data. if (!File.Exists(_fetchTargetPath)) { - var _fetchTarget = _isAssetPatchable ? _fetchName : _fetchAsset.Source[0].Name; + if (targetPlatform != 0x00) + { + var _fetchTarget = _isAssetPatchable ? _fetchName : _fetchAsset.Source[0].Name; - var _fetchParent = ""; - var _fetchChild = ""; + var _fetchParent = ""; + var _fetchChild = ""; - if (_isFileRAW) - _fetchTarget = _fetchTarget.Replace("raw/", ""); + if (_isFileRAW) + _fetchTarget = _fetchTarget.Replace("raw/", ""); - if (_isFileRemastered) - { - var _fetchMatch = Regex.Match(_fetchTarget, "[\\w,-]+\\.[a-zA-Z0-9]{2,4}", RegexOptions.None, TimeSpan.FromMilliseconds(500)); + if (_isFileRemastered) + { + var _fetchMatch = Regex.Match(_fetchTarget, "[\\w,-]+\\.[a-zA-Z0-9]{2,4}", RegexOptions.None, TimeSpan.FromMilliseconds(500)); - var _fetchIndex = _fetchTarget.IndexOf(_fetchMatch.Value); - var _fetchEndPoint = _fetchIndex + _fetchMatch.Value.Length; + var _fetchIndex = _fetchTarget.IndexOf(_fetchMatch.Value); + var _fetchEndPoint = _fetchIndex + _fetchMatch.Value.Length; - var _fetchSubFirst = _fetchTarget.Substring(0, _fetchEndPoint); - var _fetchSubSecond = _fetchTarget.Substring(_fetchEndPoint, _fetchTarget.Length - _fetchEndPoint); + var _fetchSubFirst = _fetchTarget.Substring(0, _fetchEndPoint); + var _fetchSubSecond = _fetchTarget.Substring(_fetchEndPoint, _fetchTarget.Length - _fetchEndPoint); - _fetchParent = _fetchSubFirst.Replace("remastered/", ""); - _fetchChild = _fetchSubSecond.Trim('/'); - } + _fetchParent = _fetchSubFirst.Replace("remastered/", ""); + _fetchChild = _fetchSubSecond.Trim('/'); + } + + var _fetchNameHash = Egs.Helpers.CreateMD5(_isFileRemastered ? _fetchParent : _fetchTarget); + var _fetchKeyExists = _fetchFileDictionary.ContainsKey(_fetchNameHash); + + if (_fetchKeyExists) + { + var _fetchFilePack = _fetchFileDictionary[_fetchNameHash]; + + if (!String.IsNullOrEmpty(_fetchFilePack)) + { + var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); + + var _fetchHeaderName = Path.Combine(_fetchDataPath, _fetchFilePack + ".hed"); + var _fetchPackageName = Path.Combine(_fetchDataPath, _fetchFilePack + ".pkg"); + + using (var _fetchHeaderStream = File.OpenRead(_fetchHeaderName)) + { + var _fetchEntries = Hed.Read(_fetchHeaderStream); + var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Convert.ToHexString(x.MD5) == _fetchNameHash); - var _fetchNameHash = Egs.Helpers.CreateMD5(_isFileRemastered ? _fetchParent : _fetchTarget); - var _fetchKeyExists = _fetchFileDictionary.ContainsKey(_fetchNameHash); + using (var _fetchPackageStream = File.OpenRead(_fetchPackageName)) + { + var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); - if (_fetchKeyExists) + if (_isFileRemastered) + _fetchTargetAsset.RemasteredAssetsDecompressedData.TryGetValue(_fetchChild, out _fetchAssetData); + + else + _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; + + if (_fetchAssetData != null) + await File.WriteAllBytesAsync(_fetchTargetPath, _fetchAssetData, CancellationToken.None); + } + } + } + } + } + + else { - var _fetchFilePack = _fetchFileDictionary[_fetchNameHash]; + var _fetchTarget = _isAssetPatchable ? _fetchName : _fetchAsset.Source[0].Name; - if (!String.IsNullOrEmpty(_fetchFilePack)) + using (var _fetchStreamISO = new FileStream(gameFilesPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { - var _fetchDataPath = Path.Combine(gameFilesPath, "Image", targetPlatform == 0x01 ? "dt" : (isJapanese ? "jp" : "en")); + switch (targetGame) + { + case 0x00: + break; + case 0x01: + { + var _fetchOffsetIDX = IsoUtility.GetFileOffset(_fetchStreamISO, "KH2.IDX;1"); + var _fetchOffsetIMG = IsoUtility.GetFileOffset(_fetchStreamISO, "KH2.IMG;1"); - var _fetchHeaderName = Path.Combine(_fetchDataPath, _fetchFilePack + ".hed"); - var _fetchPackageName = Path.Combine(_fetchDataPath, _fetchFilePack + ".pkg"); + var _fetchSubStrIDX = new SubStream(_fetchStreamISO, _fetchOffsetIDX * 0x800, _fetchStreamISO.Length - (_fetchOffsetIDX * 0x800)); + var _fetchSubStrIMG = new SubStream(_fetchStreamISO, _fetchOffsetIMG * 0x800, _fetchStreamISO.Length - (_fetchOffsetIMG * 0x800)); - using (var _fetchHeaderStream = File.OpenRead(_fetchHeaderName)) - { - var _fetchEntries = Hed.Read(_fetchHeaderStream); - var _fetchTargetEntry = _fetchEntries.FirstOrDefault(x => Convert.ToHexString(x.MD5) == _fetchNameHash); + var _fetchIDX = Idx.Read(_fetchSubStrIDX); + var _fetchIMG = new Img(_fetchSubStrIMG, _fetchIDX, true); - using (var _fetchPackageStream = File.OpenRead(_fetchPackageName)) - { - var _fetchTargetAsset = new EgsHdAsset(_fetchPackageStream.SetPosition(_fetchTargetEntry.Offset)); + var _fetchEntry = _fetchIMG.Entries.FirstOrDefault(x => IdxName.Lookup(x) == _fetchTarget); - if (_isFileRemastered) - _fetchTargetAsset.RemasteredAssetsDecompressedData.TryGetValue(_fetchChild, out _fetchAssetData); + if (_fetchEntry != null) + { + using (var _fetchStreamTarget = _fetchIMG.FileOpen(_fetchEntry)) + File.Create(_fetchTargetPath).Using(_tempStr => _fetchStreamTarget.CopyTo(_tempStr)); + } else - _fetchAssetData = _isFileRAW ? _fetchTargetAsset.OriginalRawData : _fetchTargetAsset.OriginalData; - - if (_fetchAssetData != null) - await File.WriteAllBytesAsync(_fetchTargetPath, _fetchAssetData, CancellationToken.None); + Debug.WriteLine("Entry not found: " + _fetchTarget); } + break; } } } diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 8598f3805..6368ee8b2 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -837,6 +837,12 @@ await Task.Run(async () => _fetchMetadata.Preferences = _fetchListMetadata; } + var _fetchTargetPlatform = (int)currentConfig.Frontend.TargetPlatform; + var _fetchTargetGame = (int)currentConfig.Frontend.TargetGame; + var _finalGamePath = _fetchTargetPlatform == 0x00 ? currentConfig.Emulator.RomPath[_fetchTargetGame] : _fetchGamePath; + + _fetchMetadata.Assets = _fetchMetadata.Assets.Where(x => x.Platform == (_fetchTargetPlatform == 0x00 ? "ps2" : "pc") || String.IsNullOrEmpty(x.Platform)).ToList(); + // Await the patcher process for this mod. await _fetchPatcher.Patch ( @@ -844,9 +850,9 @@ await _fetchPatcher.Patch _fetchBuildPath, _fetchMetadata, _fetchMod.ModPath, - _fetchGamePath, - (int)currentConfig.Frontend.TargetPlatform, - (int)currentConfig.Frontend.TargetGame, + _finalGamePath, + _fetchTargetPlatform, + _fetchTargetGame, _fetchJapanese, CancelToken, _fetchPackageMap, From ee42b716f25f7bbf63d68ce47b2dd84ab8c7e788 Mon Sep 17 00:00:00 2001 From: TopazTK <250328010.b@uni-prizren.com> Date: Tue, 29 Sep 2026 09:46:37 +0200 Subject: [PATCH 175/175] [Mod Manager] - Extraction Menu for Emulated ROMs, extraction for KH2 PS2. --- .../Services/ModService.cs | 102 +++++++++++++++--- .../Wizard/WizardExtractSetup.axaml | 39 +++++-- .../Wizard/WizardExtractSetup.axaml.cs | 10 +- .../Wizard/WizardGameSetup.axaml | 35 +++--- .../Wizard/WizardGameSetup.axaml.cs | 5 + 5 files changed, 153 insertions(+), 38 deletions(-) diff --git a/OpenKh.Tools.ModManager/Services/ModService.cs b/OpenKh.Tools.ModManager/Services/ModService.cs index 6368ee8b2..5d9f033cf 100644 --- a/OpenKh.Tools.ModManager/Services/ModService.cs +++ b/OpenKh.Tools.ModManager/Services/ModService.cs @@ -2,30 +2,30 @@ #pragma warning disable CS4014 // Disabled because this class contains non-awaited tasks. using Avalonia.Controls; - -using OpenKh.Egs; +using LibGit2Sharp; +using LibGit2Sharp.Handlers; using OpenKh.Common; +using OpenKh.Egs; +using OpenKh.Kh1; +using OpenKh.Kh2; using OpenKh.Patcher; using OpenKh.Tools.ModManager.Classes; using OpenKh.Tools.ModManager.Models; - -using LibGit2Sharp; -using LibGit2Sharp.Handlers; - +using SharpYaml; using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; using System.IO; -using System.Net; +using System.IO.Compression; using System.Linq; -using System.Text; +using System.Net; using System.Net.Http; +using System.Security.Cryptography; +using System.Text; using System.Threading; -using System.Diagnostics; -using System.IO.Compression; using System.Threading.Tasks; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Collections.Concurrent; -using SharpYaml; +using Xe.IO; namespace OpenKh.Tools.ModManager.Services { @@ -248,6 +248,80 @@ await Task.Run(() => return 0x03; } + else + { + for (int i = 0x00; i < 0x05; i++) + { + if (!extractGames[i]) + continue; + + var _fetchPath = currentConfig.Emulator.RomPath[i]; + + await Task.Run(async () => + { + var _fetchDataPath = PathService.ResolveData(currentConfig, true); + + // If the data folder is null, resort to the default of /modmanager/extract. + if (String.IsNullOrEmpty(_fetchDataPath)) + { + currentConfig.Frontend.DataPath = Path.Combine(AppContext.BaseDirectory, "extract"); + _fetchDataPath = currentConfig.Frontend.DataPath; + } + + using (var _fetchStreamISO = new FileStream(_fetchPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + switch (i) + { + case 0x00: + break; + case 0x01: + { + var _fetchOffsetIDX = IsoUtility.GetFileOffset(_fetchStreamISO, "KH2.IDX;1"); + var _fetchOffsetIMG = IsoUtility.GetFileOffset(_fetchStreamISO, "KH2.IMG;1"); + + var _fetchSubStrIDX = new SubStream(_fetchStreamISO, _fetchOffsetIDX * 0x800, _fetchStreamISO.Length - (_fetchOffsetIDX * 0x800)); + var _fetchSubStrIMG = new SubStream(_fetchStreamISO, _fetchOffsetIMG * 0x800, _fetchStreamISO.Length - (_fetchOffsetIMG * 0x800)); + + var _fetchIDX = Idx.Read(_fetchSubStrIDX); + var _fetchIMG = new Img(_fetchSubStrIMG, _fetchIDX, true); + + var _fetchCount = _fetchIMG.Entries.Count; + var _fetchProcessed = 0x00; + + Parallel.ForEach(_fetchIMG.Entries.AsParallel(), (_fetchEntry, _fetchToken) => + { + var _fetchTargetName = IdxName.Lookup(_fetchEntry) ?? $"@{_fetchEntry.Hash32:08X}_{_fetchEntry.Hash16:04X}"; + + using (var _fetchStreamFILE = _fetchIMG.FileOpen(_fetchEntry)) + { + var _targetFilePath = Path.Combine(_fetchDataPath, "kh2", _fetchTargetName); + var _targetFileDirectory = Path.GetDirectoryName(_targetFilePath); + + if (!Directory.Exists(_targetFileDirectory)) + Directory.CreateDirectory(_targetFileDirectory); + + var _fetchData = _fetchStreamFILE.ReadAllBytes(); + File.WriteAllBytes(_targetFilePath, _fetchData); + } + + _fetchProcessed++; + + if (reportProgress != null) + { + var _fetchProgress = reportProgress(_fetchProcessed, _fetchCount); + + if (!_fetchProgress || CancelToken.IsCancellationRequested) + _fetchToken.Stop(); + } + }); + } + break; + } + } + }, CancelToken); + } + } + // If the function made it this far, it is a surefire success. return 0x00; } diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml index d3b0fdab7..e4ae56b0f 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml @@ -2,7 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="510" + mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="510" + xmlns:convert="using:OpenKh.Tools.ModManager.Converters" + xmlns:class="using:OpenKh.Tools.ModManager.Classes" Width="500" Height="510" x:Class="OpenKh.Tools.ModManager.Wizard.WizardExtractSetup"> @@ -12,6 +14,8 @@ 7.78033C8.51836 7.92098 8.32759 8 8.12868 8H2ZM2 9.5V17.75C2 19.5449 3.45507 21 5.25 21H18.75C20.5449 21 22 19.5449 22 17.75V8.75C22 6.95507 20.5449 5.5 18.75 5.5H13.0607L9.71967 8.84099C9.29771 9.26295 8.72542 9.5 8.12868 9.5H2Z + + @@ -38,15 +42,38 @@ Please choose what games you want to extract from below: + + + + + + - - + + - - + + - + + + + + + + + + + + + + + + + + + Please declare the path you want to extract to below, if you do not, it will default to the "extract" folder relative to the Mod Manager: diff --git a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs index d8b7e66d1..e545b941c 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardExtractSetup.axaml.cs @@ -100,11 +100,11 @@ private async void OnExtractStart(object? sender, Avalonia.Interactivity.RoutedE var _fetchExtractList = new List() { - ExtractKH1.IsChecked ?? false, - ExtractKH2.IsChecked ?? false, - ExtractCOM.IsChecked ?? false, - ExtractBBS.IsChecked ?? false, - ExtractDDD.IsChecked ?? false + (ExtractPC1.IsChecked ?? false) || (ExtractPS1.IsChecked ?? false), + (ExtractPC2.IsChecked ?? false) || (ExtractPS2.IsChecked ?? false), + (ExtractPC3.IsChecked ?? false) || (ExtractPS3.IsChecked ?? false), + (ExtractPC4.IsChecked ?? false) || (ExtractPS4.IsChecked ?? false), + (ExtractPC5.IsChecked ?? false) || (ExtractPS5.IsChecked ?? false) }; WeakReferenceMessenger.Default.Send(new BlockAllRequest()); diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml index 3f496fa6b..246945be0 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml @@ -133,25 +133,34 @@ Click the appropriate buttons to choose your ISO Files: - + - - - + + + diff --git a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs index dfb2a55dd..91959f9e0 100644 --- a/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs +++ b/OpenKh.Tools.ModManager/Wizard/WizardGameSetup.axaml.cs @@ -184,6 +184,11 @@ private async void OnFileClickKH2(object? sender, Avalonia.Interactivity.RoutedE } } + private async void OnFileClickCOM(object? sender, Avalonia.Interactivity.RoutedEventArgs e) + { + + } + private async void OnDetectClick(object? sender, Avalonia.Interactivity.RoutedEventArgs e) { var _fetchContext = DataContext as MainViewModel;