Compare commits

...

3 Commits

Author SHA1 Message Date
7038a902c3 Automatically remove invalid dlc and updates as part of auto load (#42)
* Automatically remove invalid dlc and updates as part of auto load
Fixed some minor label spacing issues in options dialog
Removal of unused variable in input view model

* Fixed missing french message for AutoloadDlcAddedMessage
2024-10-27 06:32:32 -05:00
6be8838043 ci moment 2024-10-26 09:00:35 -05:00
033ea86c1b Disable appimage build (for now) 2024-10-26 08:56:27 -05:00
11 changed files with 170 additions and 102 deletions

View File

@ -74,36 +74,36 @@ jobs:
chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh chmod +x ./publish_sdl2_headless/Ryujinx.Headless.SDL2 ./publish_sdl2_headless/Ryujinx.sh
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
- name: Build AppImage #- name: Build AppImage
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' # if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
run: | # run: |
PLATFORM_NAME="${{ matrix.platform.name }}" # PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt install -y zsync desktop-file-utils appstream # sudo apt install -y zsync desktop-file-utils appstream
mkdir -p tools # mkdir -p tools
export PATH="$PATH:$(readlink -f tools)" # export PATH="$PATH:$(readlink -f tools)"
# Setup appimagetool # # Setup appimagetool
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" # wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x tools/appimagetool # chmod +x tools/appimagetool
chmod +x distribution/linux/appimage/build-appimage.sh # chmod +x distribution/linux/appimage/build-appimage.sh
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name) # Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
if [ "$PLATFORM_NAME" = "linux-x64" ]; then # if [ "$PLATFORM_NAME" = "linux-x64" ]; then
ARCH_NAME=x64 # ARCH_NAME=x64
export ARCH=x86_64 # export ARCH=x86_64
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then # elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
ARCH_NAME=arm64 # ARCH_NAME=arm64
export ARCH=aarch64 # export ARCH=aarch64
else # else
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME"" # echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
exit 1 # exit 1
fi # fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync" # export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh # BUILDDIR=publish OUTDIR=publish_appimage distribution/linux/appimage/build-appimage.sh
shell: bash # shell: bash
- name: Upload Ryujinx artifact - name: Upload Ryujinx artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
@ -112,12 +112,12 @@ jobs:
path: publish path: publish
if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13' if: github.event_name == 'pull_request' && matrix.platform.os != 'macos-13'
- name: Upload Ryujinx (AppImage) artifact #- name: Upload Ryujinx (AppImage) artifact
uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v4
if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest' # if: github.event_name == 'pull_request' && matrix.platform.os == 'ubuntu-latest'
with: # with:
name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage # name: ryujinx-${{ matrix.configuration }}-${{ env.RYUJINX_BASE_VERSION }}+${{ steps.git_short_hash.outputs.result }}-${{ matrix.platform.zip_os_name }}-AppImage
path: publish_appimage # path: publish_appimage
- name: Upload Ryujinx.Headless.SDL2 artifact - name: Upload Ryujinx.Headless.SDL2 artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4

View File

@ -130,49 +130,50 @@ jobs:
popd popd
shell: bash shell: bash
- name: Build AppImage (Linux) #- name: Build AppImage (Linux)
if: matrix.platform.os == 'ubuntu-latest' # if: matrix.platform.os == 'ubuntu-latest'
run: | # run: |
BUILD_VERSION="${{ steps.version_info.outputs.build_version }}" # BUILD_VERSION="${{ steps.version_info.outputs.build_version }}"
PLATFORM_NAME="${{ matrix.platform.name }}" # PLATFORM_NAME="${{ matrix.platform.name }}"
sudo apt install -y zsync desktop-file-utils appstream # sudo apt install -y zsync desktop-file-utils appstream
mkdir -p tools # mkdir -p tools
export PATH="$PATH:$(readlink -f tools)" # export PATH="$PATH:$(readlink -f tools)"
# Setup appimagetool # Setup appimagetool
wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" # wget -q -O tools/appimagetool "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
chmod +x tools/appimagetool # chmod +x tools/appimagetool
chmod +x distribution/linux/appimage/build-appimage.sh # chmod +x distribution/linux/appimage/build-appimage.sh
# Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name) # Explicitly set $ARCH for appimagetool ($ARCH_NAME is for the file name)
if [ "$PLATFORM_NAME" = "linux-x64" ]; then # if [ "$PLATFORM_NAME" = "linux-x64" ]; then
ARCH_NAME=x64 # ARCH_NAME=x64
export ARCH=x86_64 # export ARCH=x86_64
elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then # elif [ "$PLATFORM_NAME" = "linux-arm64" ]; then
ARCH_NAME=arm64 # ARCH_NAME=arm64
export ARCH=aarch64 # export ARCH=aarch64
else # else
echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME"" # echo "Unexpected PLATFORM_NAME "$PLATFORM_NAME""
exit 1 # exit 1
fi # fi
export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync" # export UFLAG="gh-releases-zsync|${{ github.repository_owner }}|${{ github.event.repository.name }}|latest|*-$ARCH_NAME.AppImage.zsync"
BUILDDIR=publish_ava OUTDIR=publish_ava_appimage distribution/linux/appimage/build-appimage.sh # BUILDDIR=publish_ava OUTDIR=publish_ava_appimage distribution/linux/appimage/build-appimage.sh
# Add to release output # Add to release output
pushd publish_ava_appimage # pushd publish_ava_appimage
mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage # mv Ryujinx.AppImage ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage
mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync # mv Ryujinx.AppImage.zsync ../release_output/ryujinx-$BUILD_VERSION-$ARCH_NAME.AppImage.zsync
popd # popd
shell: bash # shell: bash
- name: Pushing new release - name: Pushing new release
uses: ncipollo/release-action@v1 uses: ncipollo/release-action@v1
with: with:
name: ${{ steps.version_info.outputs.build_version }} name: ${{ steps.version_info.outputs.build_version }}
artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*" artifacts: "release_output/*.tar.gz,release_output/*.zip"
#artifacts: "release_output/*.tar.gz,release_output/*.zip/*AppImage*"
tag: ${{ steps.version_info.outputs.build_version }} tag: ${{ steps.version_info.outputs.build_version }}
body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}" body: "**Full Changelog**: https://github.com/${{ github.repository }}/compare/${{ steps.version_info.outputs.prev_build_version }}...${{ steps.version_info.outputs.build_version }}"
omitBodyDuringUpdate: true omitBodyDuringUpdate: true

View File

@ -1,5 +1,6 @@
using DynamicData; using DynamicData;
using DynamicData.Kernel; using DynamicData.Kernel;
using Gommon;
using LibHac; using LibHac;
using LibHac.Common; using LibHac.Common;
using LibHac.Fs; using LibHac.Fs;
@ -801,17 +802,31 @@ namespace Ryujinx.UI.App.Common
// Searches the provided directories for DLC NSP files that are _valid for the currently detected games in the // Searches the provided directories for DLC NSP files that are _valid for the currently detected games in the
// library_, and then enables those DLC. // library_, and then enables those DLC.
public int AutoLoadDownloadableContents(List<string> appDirs) public int AutoLoadDownloadableContents(List<string> appDirs, out int numDlcRemoved)
{ {
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
List<string> dlcPaths = new(); List<string> dlcPaths = new();
int newDlcLoaded = 0; int newDlcLoaded = 0;
numDlcRemoved = 0;
try try
{ {
// Remove any downloadable content which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title DLCs");
var dlcToRemove = _downloadableContents.Items
.Where(dlc => !File.Exists(dlc.Dlc.ContainerPath))
.ToList();
dlcToRemove.ForEach(dlc =>
Logger.Warning?.Print(LogClass.Application, $"Title DLC removed: {dlc.Dlc.ContainerPath}")
);
numDlcRemoved += dlcToRemove.Distinct().Count();
_downloadableContents.RemoveKeys(dlcToRemove.Select(dlc => dlc.Dlc));
foreach (string appDir in appDirs) foreach (string appDir in appDirs)
{ {
Logger.Notice.Print(LogClass.Application, $"Auto loading DLC from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested) if (_cancellationToken.Token.IsCancellationRequested)
{ {
return newDlcLoaded; return newDlcLoaded;
@ -900,17 +915,37 @@ namespace Ryujinx.UI.App.Common
// Searches the provided directories for update NSP files that are _valid for the currently detected games in the // Searches the provided directories for update NSP files that are _valid for the currently detected games in the
// library_, and then applies those updates. If a newly-detected update is a newer version than the currently // library_, and then applies those updates. If a newly-detected update is a newer version than the currently
// selected update (or if no update is currently selected), then that update will be selected. // selected update (or if no update is currently selected), then that update will be selected.
public int AutoLoadTitleUpdates(List<string> appDirs) public int AutoLoadTitleUpdates(List<string> appDirs, out int numUpdatesRemoved)
{ {
_cancellationToken = new CancellationTokenSource(); _cancellationToken = new CancellationTokenSource();
List<string> updatePaths = new(); List<string> updatePaths = new();
int numUpdatesLoaded = 0; int numUpdatesLoaded = 0;
numUpdatesRemoved = 0;
try try
{ {
var titleIdsToSave = new HashSet<ulong>();
var titleIdsToRefresh = new HashSet<ulong>();
// Remove any updates which can no longer be located on disk
Logger.Notice.Print(LogClass.Application, $"Removing non-existing Title Updates");
var updatesToRemove = _titleUpdates.Items
.Where(it => !File.Exists(it.TitleUpdate.Path))
.ToList();
numUpdatesRemoved += updatesToRemove.Select(it => it.TitleUpdate).Distinct().Count();
updatesToRemove.ForEach(ti =>
Logger.Warning?.Print(LogClass.Application, $"Title update removed: {ti.TitleUpdate.Path}")
);
_titleUpdates.RemoveKeys(updatesToRemove.Select(it => it.TitleUpdate));
titleIdsToSave.UnionWith(updatesToRemove.Select(it => it.TitleUpdate.TitleIdBase));
titleIdsToRefresh.UnionWith(updatesToRemove.Where(it => it.IsSelected).Select(update => update.TitleUpdate.TitleIdBase));
foreach (string appDir in appDirs) foreach (string appDir in appDirs)
{ {
Logger.Notice.Print(LogClass.Application, $"Auto loading updates from: {appDir}");
if (_cancellationToken.Token.IsCancellationRequested) if (_cancellationToken.Token.IsCancellationRequested)
{ {
return numUpdatesLoaded; return numUpdatesLoaded;
@ -979,27 +1014,21 @@ namespace Ryujinx.UI.App.Common
{ {
if (!_titleUpdates.Lookup(update).HasValue) if (!_titleUpdates.Lookup(update).HasValue)
{ {
var currentlySelected = TitleUpdates.Items.FirstOrOptional(it => bool shouldSelect = AddAndAutoSelectUpdate(update);
it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected); titleIdsToSave.Add(update.TitleIdBase);
var shouldSelect = !currentlySelected.HasValue ||
currentlySelected.Value.TitleUpdate.Version < update.Version;
_titleUpdates.AddOrUpdate((update, shouldSelect));
if (currentlySelected.HasValue && shouldSelect)
_titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false));
SaveTitleUpdatesForGame(update.TitleIdBase);
numUpdatesLoaded++; numUpdatesLoaded++;
if (shouldSelect) if (shouldSelect)
{ {
RefreshApplicationInfo(update.TitleIdBase); titleIdsToRefresh.Add(update.TitleIdBase);
} }
} }
} }
} }
} }
titleIdsToSave.ForEach(titleId => SaveTitleUpdatesForGame(titleId));
titleIdsToRefresh.ForEach(titleId => RefreshApplicationInfo(titleId));
} }
finally finally
{ {
@ -1010,6 +1039,24 @@ namespace Ryujinx.UI.App.Common
return numUpdatesLoaded; return numUpdatesLoaded;
} }
private bool AddAndAutoSelectUpdate(TitleUpdateModel update)
{
var currentlySelected = TitleUpdates.Items.FirstOrOptional(it =>
it.TitleUpdate.TitleIdBase == update.TitleIdBase && it.IsSelected);
var shouldSelect = !currentlySelected.HasValue ||
currentlySelected.Value.TitleUpdate.Version < update.Version;
_titleUpdates.AddOrUpdate((update, shouldSelect));
if (currentlySelected.HasValue && shouldSelect)
{
_titleUpdates.AddOrUpdate((currentlySelected.Value.TitleUpdate, false));
}
return shouldSelect;
}
protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e) protected void OnApplicationCountUpdated(ApplicationCountUpdatedEventArgs e)
{ {
ApplicationCountUpdated?.Invoke(null, e); ApplicationCountUpdated?.Invoke(null, e);
@ -1394,8 +1441,8 @@ namespace Ryujinx.UI.App.Common
if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates)) if (TryGetTitleUpdatesFromFile(application.Path, out var bundledUpdates))
{ {
var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet(); var savedUpdateLookup = savedUpdates.Select(update => update.Item1).ToHashSet();
bool updatesChanged = false;
bool addedNewUpdate = false;
foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version)) foreach (var update in bundledUpdates.OrderByDescending(bundled => bundled.Version))
{ {
if (!savedUpdateLookup.Contains(update)) if (!savedUpdateLookup.Contains(update))
@ -1404,17 +1451,19 @@ namespace Ryujinx.UI.App.Common
if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version) if (!selectedUpdate.HasValue || selectedUpdate.Value.Item1.Version < update.Version)
{ {
shouldSelect = true; shouldSelect = true;
selectedUpdate = Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true)); if (selectedUpdate.HasValue)
_titleUpdates.AddOrUpdate((selectedUpdate.Value.Item1, false));
selectedUpdate = DynamicData.Kernel.Optional<(TitleUpdateModel, bool IsSelected)>.Create((update, true));
} }
modifiedVersion = modifiedVersion || shouldSelect; modifiedVersion = modifiedVersion || shouldSelect;
it.AddOrUpdate((update, shouldSelect)); it.AddOrUpdate((update, shouldSelect));
addedNewUpdate = true; updatesChanged = true;
} }
} }
if (addedNewUpdate) if (updatesChanged)
{ {
var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList(); var gameUpdates = it.Items.Where(update => update.TitleUpdate.TitleIdBase == application.IdBase).ToList();
TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates); TitleUpdatesHelper.SaveTitleUpdatesJson(_virtualFileSystem, application.IdBase, gameUpdates);

View File

@ -106,6 +106,7 @@
"SettingsTabGeneralHideCursorAlways": "Always", "SettingsTabGeneralHideCursorAlways": "Always",
"SettingsTabGeneralGameDirectories": "Game Directories", "SettingsTabGeneralGameDirectories": "Game Directories",
"SettingsTabGeneralAutoloadDirectories": "Autoload DLC/Updates Directories", "SettingsTabGeneralAutoloadDirectories": "Autoload DLC/Updates Directories",
"SettingsTabGeneralAutoloadNote": "DLC and Updates which refer to missing files will be unloaded automatically",
"SettingsTabGeneralAdd": "Add", "SettingsTabGeneralAdd": "Add",
"SettingsTabGeneralRemove": "Remove", "SettingsTabGeneralRemove": "Remove",
"SettingsTabSystem": "System", "SettingsTabSystem": "System",
@ -733,8 +734,9 @@
"DlcWindowHeading": "{0} Downloadable Content(s)", "DlcWindowHeading": "{0} Downloadable Content(s)",
"DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added", "DlcWindowDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcAddedMessage": "{0} new downloadable content(s) added", "AutoloadDlcAddedMessage": "{0} new downloadable content(s) added",
"AutoloadDlcRemovedMessage": "{0} missing downloadable content(s) removed",
"AutoloadUpdateAddedMessage": "{0} new update(s) added", "AutoloadUpdateAddedMessage": "{0} new update(s) added",
"AutoloadDlcAndUpdateAddedMessage": "{0} new downloadable content(s) and {1} new update(s) added", "AutoloadUpdateRemovedMessage": "{0} missing update(s) removed",
"ModWindowHeading": "{0} Mod(s)", "ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Edit Selected", "UserProfilesEditProfile": "Edit Selected",
"Cancel": "Cancel", "Cancel": "Cancel",

View File

@ -106,6 +106,7 @@
"SettingsTabGeneralHideCursorAlways": "Toujours", "SettingsTabGeneralHideCursorAlways": "Toujours",
"SettingsTabGeneralGameDirectories": "Dossiers des jeux", "SettingsTabGeneralGameDirectories": "Dossiers des jeux",
"SettingsTabGeneralAutoloadDirectories": "Dossiers des mises à jour/DLC", "SettingsTabGeneralAutoloadDirectories": "Dossiers des mises à jour/DLC",
"SettingsTabGeneralAutoloadNote": "Les DLC et les mises à jour faisant référence aux fichiers manquants seront automatiquement déchargés.",
"SettingsTabGeneralAdd": "Ajouter", "SettingsTabGeneralAdd": "Ajouter",
"SettingsTabGeneralRemove": "Retirer", "SettingsTabGeneralRemove": "Retirer",
"SettingsTabSystem": "Système", "SettingsTabSystem": "Système",
@ -733,8 +734,9 @@
"DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)", "DlcWindowHeading": "{0} Contenu(s) téléchargeable(s)",
"DlcWindowDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "DlcWindowDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)", "AutoloadDlcAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) ajouté(s)",
"AutoloadDlcRemovedMessage": "{0} contenu(s) téléchargeable(s) manquant(s) supprimé(s)",
"AutoloadUpdateAddedMessage": "{0} nouvelle(s) mise(s) à jour ajoutée(s)", "AutoloadUpdateAddedMessage": "{0} nouvelle(s) mise(s) à jour ajoutée(s)",
"AutoloadDlcAndUpdateAddedMessage": "{0} nouveau(x) contenu(s) téléchargeable(s) et {1} nouvelle(s) mise(s) à jour ajouté(s)", "AutoloadUpdateRemovedMessage": "{0} mises à jour manquantes supprimées",
"ModWindowHeading": "{0} Mod(s)", "ModWindowHeading": "{0} Mod(s)",
"UserProfilesEditProfile": "Éditer la sélection", "UserProfilesEditProfile": "Éditer la sélection",
"Cancel": "Annuler", "Cancel": "Annuler",

View File

@ -47,6 +47,7 @@ namespace Ryujinx.Ava
{ {
Version = ReleaseInformation.Version; Version = ReleaseInformation.Version;
if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134)) if (OperatingSystem.IsWindows() && !OperatingSystem.IsWindowsVersionAtLeast(10, 0, 17134))
{ {
_ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning); _ = MessageBoxA(nint.Zero, "You are running an outdated version of Windows.\n\nRyujinx supports Windows 10 version 1803 and newer.\n", $"Ryujinx {Version}", MbIconwarning);

View File

@ -53,6 +53,7 @@ namespace Ryujinx.Ava.UI.ViewModels
public class MainWindowViewModel : BaseModel public class MainWindowViewModel : BaseModel
{ {
private const int HotKeyPressDelayMs = 500; private const int HotKeyPressDelayMs = 500;
private delegate int LoadContentFromFolderDelegate(List<string> dirs, out int numRemoved);
private ObservableCollectionExtended<ApplicationData> _applications; private ObservableCollectionExtended<ApplicationData> _applications;
private string _aspectStatusText; private string _aspectStatusText;
@ -1280,7 +1281,7 @@ namespace Ryujinx.Ava.UI.ViewModels
_rendererWaitEvent.Set(); _rendererWaitEvent.Set();
} }
private async Task LoadContentFromFolder(LocaleKeys localeMessageKey, Func<List<string>, int> onDirsSelected) private async Task LoadContentFromFolder(LocaleKeys localeMessageAddedKey, LocaleKeys localeMessageRemovedKey, LoadContentFromFolderDelegate onDirsSelected)
{ {
var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions
{ {
@ -1291,14 +1292,17 @@ namespace Ryujinx.Ava.UI.ViewModels
if (result.Count > 0) if (result.Count > 0)
{ {
var dirs = result.Select(it => it.Path.LocalPath).ToList(); var dirs = result.Select(it => it.Path.LocalPath).ToList();
var numAdded = onDirsSelected(dirs); var numAdded = onDirsSelected(dirs, out int numRemoved);
var msg = string.Format(LocaleManager.Instance[localeMessageKey], numAdded); var msg = String.Join("\r\n", new string[] {
string.Format(LocaleManager.Instance[localeMessageRemovedKey], numRemoved),
string.Format(LocaleManager.Instance[localeMessageAddedKey], numAdded)
});
await Dispatcher.UIThread.InvokeAsync(async () => await Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.ShowTextDialog( await ContentDialogHelper.ShowTextDialog(
LocaleManager.Instance[numAdded > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo], LocaleManager.Instance[numAdded > 0 || numRemoved > 0 ? LocaleKeys.RyujinxConfirm : LocaleKeys.RyujinxInfo],
msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark); msg, "", "", "", LocaleManager.Instance[LocaleKeys.InputDialogOk], (int)Symbol.Checkmark);
}); });
} }
@ -1554,12 +1558,18 @@ namespace Ryujinx.Ava.UI.ViewModels
public async Task LoadDlcFromFolder() public async Task LoadDlcFromFolder()
{ {
await LoadContentFromFolder(LocaleKeys.AutoloadDlcAddedMessage, ApplicationLibrary.AutoLoadDownloadableContents); await LoadContentFromFolder(
LocaleKeys.AutoloadDlcAddedMessage,
LocaleKeys.AutoloadDlcRemovedMessage,
ApplicationLibrary.AutoLoadDownloadableContents);
} }
public async Task LoadTitleUpdatesFromFolder() public async Task LoadTitleUpdatesFromFolder()
{ {
await LoadContentFromFolder(LocaleKeys.AutoloadUpdateAddedMessage, ApplicationLibrary.AutoLoadTitleUpdates); await LoadContentFromFolder(
LocaleKeys.AutoloadUpdateAddedMessage,
LocaleKeys.AutoloadUpdateRemovedMessage,
ApplicationLibrary.AutoLoadTitleUpdates);
} }
public async Task OpenFolder() public async Task OpenFolder()

View File

@ -52,7 +52,7 @@
</CheckBox> </CheckBox>
</StackPanel> </StackPanel>
<Separator Height="1" /> <Separator Height="1" />
<StackPanel Orientation="Vertical" Spacing="2"> <StackPanel Orientation="Vertical" Spacing="5">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" /> <TextBlock Classes="h1" Text="{locale:Locale SettingsTabLoggingDeveloperOptions}" />
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" /> <TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabLoggingDeveloperOptionsNote}" />
</StackPanel> </StackPanel>

View File

@ -195,7 +195,7 @@
<Separator Height="1" /> <Separator Height="1" />
<StackPanel <StackPanel
Orientation="Vertical" Orientation="Vertical"
Spacing="2"> Spacing="5">
<TextBlock <TextBlock
Classes="h1" Classes="h1"
Text="{locale:Locale SettingsTabSystemHacks}" /> Text="{locale:Locale SettingsTabSystemHacks}" />

View File

@ -129,7 +129,10 @@
</Grid> </Grid>
</StackPanel> </StackPanel>
<Separator Height="1" /> <Separator Height="1" />
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralAutoloadDirectories}" /> <StackPanel Orientation="Vertical" Spacing="5">
<TextBlock Classes="h1" Text="{locale:Locale SettingsTabGeneralAutoloadDirectories}" />
<TextBlock Foreground="{DynamicResource SecondaryTextColor}" Text="{locale:Locale SettingsTabGeneralAutoloadNote}" />
</StackPanel>
<StackPanel <StackPanel
Margin="10,0,0,0" Margin="10,0,0,0"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
@ -137,7 +140,7 @@
Spacing="10"> Spacing="10">
<ListBox <ListBox
Name="AutoloadDirsList" Name="AutoloadDirsList"
MinHeight="120" MinHeight="100"
ItemsSource="{Binding AutoloadDirectories}"> ItemsSource="{Binding AutoloadDirectories}">
<ListBox.Styles> <ListBox.Styles>
<Style Selector="ListBoxItem"> <Style Selector="ListBoxItem">

View File

@ -633,10 +633,10 @@ namespace Ryujinx.Ava.UI.Windows
var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value; var autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
if (autoloadDirs.Count > 0) if (autoloadDirs.Count > 0)
{ {
var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs); var updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);
var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs); var dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs, out int dlcRemoved);
ShowNewContentAddedDialog(dlcLoaded, updatesLoaded); ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
} }
_isLoading = false; _isLoading = false;
@ -648,20 +648,20 @@ namespace Ryujinx.Ava.UI.Windows
applicationLibraryThread.Start(); applicationLibraryThread.Start();
} }
private void ShowNewContentAddedDialog(int numDlcAdded, int numUpdatesAdded) private void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved)
{ {
string msg = numDlcAdded > 0 && numUpdatesAdded > 0 string[] messages = {
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAndUpdateAddedMessage], numDlcAdded, numUpdatesAdded) numDlcRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved): null,
: numDlcAdded > 0 numDlcAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded): null,
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded) numUpdatesRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved): null,
: numUpdatesAdded > 0 numUpdatesAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) : null
? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) };
: null;
if (msg is null) string msg = String.Join("\r\n", messages);
if (String.IsNullOrWhiteSpace(msg))
return; return;
Dispatcher.UIThread.InvokeAsync(async () => Dispatcher.UIThread.InvokeAsync(async () =>
{ {
await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle], await ContentDialogHelper.ShowTextDialog(LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],