-
-
Notifications
You must be signed in to change notification settings - Fork 104
Check Downloaded and Drag & Dropped Mods for Targetted Game and Warn If None #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…ere no compatible applications are found
|
Only works for drag and dropping right now, working on support for mods installed via the R2 protocol (it isn't going well) |
|
PR for #751 |
|
I also made these two mods to test it https://github.com/TheBestAstroNOT/psychic-happiness/releases/tag/1.0.0 |
|
@dreamsyntax are there any changes you would like to see? |
|
Ok, now I've properly tested this build. "No" instead of "Cancel" makes more sense, since the mod itself still gets installed, we just don't set the supported app. Ideally there would be three options Since this happens after a download for example though, I think its fine to just change it to -- Mod Loader Download Button
R2 Links (Gamebanana)
|
|
@dreamsyntax Can you test the mod loader's one click download button again, I'd do it myself but I don't know of any mods that I can test with. |
I'll try later today. If you want to test in interim: If you add any exe renamed as tsonic_win.exe, it will add Sonic Heroes as a gamebanana source. You'll be able to see 64 Mario skin for that game in the download manager, which has the issue. |
1 similar comment
I'll try later today. If you want to test in interim: If you add any exe renamed as tsonic_win.exe, it will add Sonic Heroes as a gamebanana source. You'll be able to see 64 Mario skin for that game in the download manager, which has the issue. |
| public XamlResourceMessageBoxOkCancel(string titleResourceName, string descriptionResourceName, string okButtonTextResourceName, string cancelButtonTextResourceName) : base(new XamlResource<string>(titleResourceName).Get(), new XamlResource<string>(descriptionResourceName).Get()) | ||
| public XamlResourceMessageBoxOkCancel(string title, string message, string okText, string cancelText) : base(title, message) | ||
| { | ||
| this.CancelBtn.Content = Lib.Static.Resources.ResourceProvider.Get<string>(cancelButtonTextResourceName); | ||
| this.OKBtn.Content = Lib.Static.Resources.ResourceProvider.Get<string>(okButtonTextResourceName); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something, wouldn't this replace every instance of Ok/Cancel to Yes/No?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uhh no? You are passing the text every time you call it. Plus other message boxes use another delegate to display their text. Only I'm using this one.
|
Tested. Feature works as expected now. @Sewer56 on any objections with the code? I left a comment about the resource, the rest looks fine. |
|
Bump |
|
Looks reasonable to me. Just doing all sorts of different things as always :p |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds validation logic to warn users when they install mods (via drag & drop or download) that don't have any supported applications configured, or when no compatible installed application exists for the mod. When such a situation is detected, users are prompted to manually select a supported application.
Changes:
- Modified
XamlResourceMessageBoxOkCancelto add a constructor overload that accepts pre-resolved text strings instead of resource keys - Added validation logic in three installation paths (drag & drop, URL download, and package download) to check for app compatibility
- Added localization strings for warning messages and Yes/No button labels
- Refactored
EditModDialogViewModelto support initialization with collection parameters
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| XamlResourceMessageBoxOkCancel.xaml.cs | Added constructor overload accepting resolved strings for title, message, and button texts |
| MainWindow.xaml.cs | Added validation logic for drag-and-dropped mods to check app compatibility |
| en-GB.xaml | Added localization strings for app compatibility warnings and Yes/No buttons |
| Resources.cs | Added static resource properties for new localization strings |
| Startup.cs | Added validation logic for downloaded mods to check app compatibility |
| DownloadPackagesViewModel.cs | Added validation logic for package downloads to check app compatibility |
| EditModDialogViewModel.cs | Added constructor overload accepting ObservableCollection parameters for flexibility |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if(item.Value.Config.IsUniversalMod || item.Value.Config.SupportedAppId.Length > 0) | ||
| { | ||
| var match = Lib.IoC.Get<ApplicationConfigService>().Items.FirstOrDefault(app => item.Value.Config.SupportedAppId.Contains(app.Config.AppId)); | ||
| if(match == null) | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(NoCompatibleAppsInConfigTitle.Get(), $"{NoCompatibleAppsInConfigDescription.Get()}\n{AppSelectionQuestion.Get()}", Yes.Get(), No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var createModDialog = new EditModDialog(new EditModDialogViewModel(item.Value, Lib.IoC.Get<ApplicationConfigService>(), modConfigService)); | ||
| createModDialog.Owner = System.Windows.Window.GetWindow(this); | ||
| createModDialog.RealViewModel.Page = EditModPage.Special; | ||
| createModDialog.ShowDialog(); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic appears incorrect. When a mod has IsUniversalMod set to true, it means the mod is compatible with all applications, so there's no need to check for a matching installed app or warn the user. The condition should be checking only if SupportedAppId.Length > 0 (mod has specific supported apps), and then check if a matching installed app exists. Universal mods should not trigger this warning path at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ngl it's got a good point
| if (item.Value.Config.IsUniversalMod || item.Value.Config.SupportedAppId.Length > 0) | ||
| { | ||
| var match = IoC.Get<ApplicationConfigService>().Items.FirstOrDefault(app => item.Value.Config.SupportedAppId.Contains(app.Config.AppId)); | ||
| if (match == null) | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel(Resources.NoCompatibleAppsInConfigTitle.Get(), $"{Resources.NoCompatibleAppsInConfigDescription.Get()}\n{Resources.AppSelectionQuestion.Get()}", Resources.Yes.Get(), Resources.No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, allApps, items); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic appears incorrect. When a mod has IsUniversalMod set to true, it means the mod is compatible with all applications, so there's no need to check for a matching installed app or warn the user. The condition should be checking only if SupportedAppId.Length > 0 (mod has specific supported apps), and then check if a matching installed app exists. Universal mods should not trigger this warning path at all.
| if (item.Value.Config.IsUniversalMod || item.Value.Config.SupportedAppId.Length > 0) | ||
| { | ||
| var match = IoC.Get<ApplicationConfigService>().Items.FirstOrDefault(app => item.Value.Config.SupportedAppId.Contains(app.Config.AppId)); | ||
| if (match == null) | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(Resources.NoCompatibleAppsInConfigTitle.Get(), $"{Resources.NoCompatibleAppsInConfigDescription.Get()}\n{Resources.AppSelectionQuestion.Get()}", Resources.Yes.Get(), Resources.No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logic appears incorrect. When a mod has IsUniversalMod set to true, it means the mod is compatible with all applications, so there's no need to check for a matching installed app or warn the user. The condition should be checking only if SupportedAppId.Length > 0 (mod has specific supported apps), and then check if a matching installed app exists. Universal mods should not trigger this warning path at all.
| if(item.Value.Config.IsUniversalMod || item.Value.Config.SupportedAppId.Length > 0) | ||
| { | ||
| var match = Lib.IoC.Get<ApplicationConfigService>().Items.FirstOrDefault(app => item.Value.Config.SupportedAppId.Contains(app.Config.AppId)); | ||
| if(match == null) | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(NoCompatibleAppsInConfigTitle.Get(), $"{NoCompatibleAppsInConfigDescription.Get()}\n{AppSelectionQuestion.Get()}", Yes.Get(), No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var createModDialog = new EditModDialog(new EditModDialogViewModel(item.Value, Lib.IoC.Get<ApplicationConfigService>(), modConfigService)); | ||
| createModDialog.Owner = System.Windows.Window.GetWindow(this); | ||
| createModDialog.RealViewModel.Page = EditModPage.Special; | ||
| createModDialog.ShowDialog(); | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(NoAppsInConfigTitle.Get(), $"{NoAppsInConfigDescription.Get()}\n{AppSelectionQuestion.Get()}" , Yes.Get(), No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var createModDialog = new EditModDialog(new EditModDialogViewModel(item.Value, Lib.IoC.Get<ApplicationConfigService>(), modConfigService)); | ||
| createModDialog.Owner = System.Windows.Window.GetWindow(this); | ||
| createModDialog.RealViewModel.Page = EditModPage.Special; | ||
| createModDialog.ShowDialog(); | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This validation logic is duplicated across three different files (MainWindow.xaml.cs, Startup.cs, and DownloadPackagesViewModel.cs). Consider extracting this logic into a shared helper method to improve maintainability and ensure consistent behavior across all mod installation paths.
| foreach (var item in modConfigService.ItemsById.ToArray()) | ||
| { | ||
| if (!modsBefore.ContainsKey(item.Key)) | ||
| { | ||
| newConfigs.Add(item.Value.Config); | ||
| if (item.Value.Config.IsUniversalMod || item.Value.Config.SupportedAppId.Length > 0) | ||
| { | ||
| var match = IoC.Get<ApplicationConfigService>().Items.FirstOrDefault(app => item.Value.Config.SupportedAppId.Contains(app.Config.AppId)); | ||
| if (match == null) | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(Resources.NoCompatibleAppsInConfigTitle.Get(), $"{Resources.NoCompatibleAppsInConfigDescription.Get()}\n{Resources.AppSelectionQuestion.Get()}", Resources.Yes.Get(), Resources.No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); | ||
| } | ||
| } | ||
| } | ||
| else | ||
| { | ||
| bool loadAppPage = Actions.DisplayResourceMessageBoxOkCancel!.Invoke(Resources.NoAppsInConfigTitle.Get(), $"{Resources.NoAppsInConfigDescription.Get()}\n{Resources.AppSelectionQuestion.Get()}", Resources.Yes.Get(), Resources.No.Get()); | ||
| if (loadAppPage) | ||
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); | ||
| } | ||
| } | ||
| } | ||
| } |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| var newConfigs = new List<ModConfig>(); | ||
| foreach (var item in modConfigService.ItemsById.ToArray()) | ||
| { | ||
| if (!modsBefore.ContainsKey(item.Key)) | ||
| { | ||
| newConfigs.Add(item.Value.Config); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The contents of this container are never accessed.
| var newConfigs = new List<ModConfig>(); | |
| foreach (var item in modConfigService.ItemsById.ToArray()) | |
| { | |
| if (!modsBefore.ContainsKey(item.Key)) | |
| { | |
| newConfigs.Add(item.Value.Config); | |
| foreach (var item in modConfigService.ItemsById.ToArray()) | |
| { | |
| if (!modsBefore.ContainsKey(item.Key)) | |
| { |
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment to createModDialog is useless, since its value is never read.
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment to createModDialog is useless, since its value is never read.
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, allApps, items); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment to createModDialog is useless, since its value is never read.
| { | ||
| var viewmodel = new EditModDialogViewModel(item.Value, allApps, items); | ||
| viewmodel.Page = EditModPage.Special; | ||
| var createModDialog = Actions.EditModDialog(viewmodel, null); |
Copilot
AI
Jan 18, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment to createModDialog is useless, since its value is never read.


No description provided.