Skip to content

Conversation

@TheBestAstroNOT
Copy link
Contributor

@TheBestAstroNOT TheBestAstroNOT commented Dec 10, 2025

No description provided.

@TheBestAstroNOT TheBestAstroNOT changed the title Prompt user to select supported applications for a mod in the case wh… Check Downloaded and Drag & Dropped Mods for Targetted Game and Warn If None Dec 10, 2025
@TheBestAstroNOT
Copy link
Contributor Author

Only works for drag and dropping right now, working on support for mods installed via the R2 protocol (it isn't going well)

@TheBestAstroNOT TheBestAstroNOT marked this pull request as ready for review December 10, 2025 19:39
@TheBestAstroNOT
Copy link
Contributor Author

PR for #751

@TheBestAstroNOT
Copy link
Contributor Author

I also made these two mods to test it https://github.com/TheBestAstroNOT/psychic-happiness/releases/tag/1.0.0

@TheBestAstroNOT
Copy link
Contributor Author

@dreamsyntax are there any changes you would like to see?

@dreamsyntax
Copy link
Contributor

dreamsyntax commented Dec 11, 2025

Ok, now I've properly tested this build.

Drag & Drop
image

"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
"No" -> no edit, but keeps the mod installed
"Yes" -> edit screen
"Abort/Cancel" -> does not install the mod at all

Since this happens after a download for example though, I think its fine to just change it to
"No" and "Yes" to be more clear that the mod will still be installed.

--

Mod Loader Download Button

  • Does not seem to invoke this flow at all

R2 Links (Gamebanana)

  • Invokes flow successfully

@TheBestAstroNOT
Copy link
Contributor Author

@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.

@dreamsyntax
Copy link
Contributor

@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
@dreamsyntax
Copy link
Contributor

@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.

@TheBestAstroNOT
Copy link
Contributor Author

image Ok it seems to be working

Comment on lines -13 to -16
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);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@dreamsyntax
Copy link
Contributor

Tested.

Feature works as expected now.

@Sewer56 on any objections with the code?

I left a comment about the resource, the rest looks fine.

@TheBestAstroNOT
Copy link
Contributor Author

Bump

@Sewer56
Copy link
Member

Sewer56 commented Jan 4, 2026

Looks reasonable to me. Just doing all sorts of different things as always :p

Copilot AI review requested due to automatic review settings January 18, 2026 15:13
Copy link

Copilot AI left a 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 XamlResourceMessageBoxOkCancel to 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 EditModDialogViewModel to 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.

Comment on lines 138 to 152
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();
}
}
}
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
Copy link
Member

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

Comment on lines 141 to 154
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);
}
}
}
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 319 to 332
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);
}
}
}
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 138 to 164
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();
}
}
}
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
Comment on lines 314 to 344
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);
}
}
}
}
Copy link

Copilot AI Jan 18, 2026

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(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines +313 to +318
var newConfigs = new List<ModConfig>();
foreach (var item in modConfigService.ItemsById.ToArray())
{
if (!modsBefore.ContainsKey(item.Key))
{
newConfigs.Add(item.Value.Config);
Copy link

Copilot AI Jan 18, 2026

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.

Suggested change
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))
{

Copilot uses AI. Check for mistakes.
{
var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService);
viewmodel.Page = EditModPage.Special;
var createModDialog = Actions.EditModDialog(viewmodel, null);
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
{
var viewmodel = new EditModDialogViewModel(item.Value, IoC.Get<ApplicationConfigService>(), modConfigService);
viewmodel.Page = EditModPage.Special;
var createModDialog = Actions.EditModDialog(viewmodel, null);
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
{
var viewmodel = new EditModDialogViewModel(item.Value, allApps, items);
viewmodel.Page = EditModPage.Special;
var createModDialog = Actions.EditModDialog(viewmodel, null);
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
{
var viewmodel = new EditModDialogViewModel(item.Value, allApps, items);
viewmodel.Page = EditModPage.Special;
var createModDialog = Actions.EditModDialog(viewmodel, null);
Copy link

Copilot AI Jan 18, 2026

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.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants