beeradmoore / dlss-swapper

GNU General Public License v3.0
1.73k stars 62 forks source link

[BUG] Can't find installed games #66

Closed tamodolo closed 2 years ago

tamodolo commented 2 years ago

Describe the bug Can't find any installed game.

To Reproduce Start the app

Expected behavior Find the games

Screenshots image

Additional context I don't know... it's the first time I try to install it.

beeradmoore commented 2 years ago

Hey @tamodolo , at the moment DLSS Swapper only detects games installed from Steam. Do you have Steam and DLSS enabled games from Stream installed?

tamodolo commented 2 years ago

Yes, quite a lot to be honest.

It's just a sugestion but this would be more strait forward if this program just scan the selected folder after supported games instead of locking it to a service. (See how GSM - Game Save Manager - do things for a reference).

edit: you can also list all known games and just let the user point to it's location.

beeradmoore commented 2 years ago

Are you saying something that has to download a community curated/supported/updated list of thousands of games for the user to manually scroll through. That a user then has to point each game at the local install folder is more straight forward than launching an app and auto-detects your installed games and just shows them?

DLSS Swapper is doing this fine for thousands of people, but apparently there is an issue with your setup that I'd love to diagnose.

Your screenshot shows a loading wheel. Does that stay there? Is it still animating?

Is your steam install just standard default install to C drive or did you do a custom install? How about games, same drive as steam or across multiple?

How many games are installed with steam? If there is A LOT then loading could be slow, I have an open issue for dealing with this sometime in the future. But if we think that could be it I can bump it's priority.

tamodolo commented 2 years ago

You're right. I don't know how you detect dlss inside games now so it's hard to tell if there is a better way. Sugestions will be sugestions I think hehe. Let's change focus.

About diagnose it, I'm here for any info you need. I'll start answering your questions:

Your screenshot shows a loading wheel. Does that stay there? Is it still animating?

The animation stops after a short while. I monitored HDD to see what it's doing and there is a 100% load when the program opens but after less than 10 seconds it returns to idle.

Is your steam install just standard default install to C drive or did you do a custom install? How about games, same drive as steam or across multiple?

Steam is installed to the same drive as windows (C:) at default location. Games are splited over drives I SSDs are a limited resourse so I often move them using steam over mechanical HDD to SSDs and back to it as needed.

How many games are installed with steam? If there is A LOT then loading could be slow, I have an open issue for dealing with this sometime in the future. But if we think that could be it I can bump it's priority.

About 200 games are installed.

If you need more info please tell me.

beeradmoore commented 2 years ago

When you say there was high load, do you mean of the HDD resources shown in task manager or high CPU load in there? Does the loading wheel go away after that and is the app responsive if you resize it?

It's very possible that it broke somewhere or is somehow getting into a deadlock with all the threads it's dealing with and adding them to a list. If scanning/threads are to blame here we could limit scan on launch with some sort of game cache.

This will improve startup speed. But also means is it scans say, Forza Horizon 4 and doesn't find DLSS that it won't scan it again next launch (or possibly ever again).

It would mean if the game updated and DID get support then it wouldn't show, so we'd need a way to either cache game versions, or have a button to clear the cache.

beeradmoore commented 2 years ago

I should also add this for context.

On launch it finds where steam is installed. Reads the steam data to see where steam libraries are across multiple disks. For each steam library we go to the folder and look for the game metadata file (this is where we get the game install folder, name, and cover image) For each game we find we search that directory for nvngx_dlss.dll which is what DLSS is always called. This in itself is a lot of folders to search through, let alone this x200. We either report if a game has DLSS or doesn't. For each nvngx_dlss.dll we load its file info to see what version it is. I forget if we are also calling the WinTrust API at this point to make sure it is indeed a signed, safe and unaltered DLL from nvidia. If we do this would also slow things.

So from all that you could see why a cache could improve things 😂

Updating to .NET 6 in the near future could improve performance of this scan, but there may also be some improvements with doing some lower level windows searching. But also, just do a cache.

tamodolo commented 2 years ago

When you say there was high load, do you mean of the HDD resources shown in task manager or high CPU load in there? Does the loading wheel go away after that and is the app responsive if you resize it?

High HDD usage. Not CPU. I opened now and the load was for just 2 seconds, then idle. The circle is still spinning

edit: the circle animation stops if you change screens inside the program. There is no indication the program is doing anything at all... PC is idle.

It's very possible that it broke somewhere or is somehow getting into a deadlock with all the threads it's dealing with and adding them to a list. If scanning/threads are to blame here we could limit scan on launch with some sort of game cache.

This will improve startup speed. But also means is it scans say, Forza Horizon 4 and doesn't find DLSS that it won't scan it again next launch (or possibly ever again).

It would mean if the game updated and DID get support then it wouldn't show, so we'd need a way to either cache game versions, or have a button to clear the cache.

Maybe you can store location of just the DLSS DLL to check if it's updated for games already found. If there the game is unstalled then you remove that entry as the DLL won't be there anymore.

tamodolo commented 2 years ago

I should also add this for context.

On launch it finds where steam is installed. Reads the steam data to see where steam libraries are across multiple disks. For each steam library we go to the folder and look for the game metadata file (this is where we get the game install folder, name, and cover image) For each game we find we search that directory for nvngx_dlss.dll which is what DLSS is always called. This in itself is a lot of folders to search through, let alone this x200. We either report if a game has DLSS or doesn't. For each nvngx_dlss.dll we load its file info to see what version it is. I forget if we are also calling the WinTrust API at this point to make sure it is indeed a signed, safe and unaltered DLL from nvidia. If we do this would also slow things.

So from all that you could see why a cache could improve things 😂

Updating to .NET 6 in the near future could improve performance of this scan, but there may also be some improvements with doing some lower level windows searching. But also, just do a cache.

Considering this, you can build a community db over time if users agree to send you their founds. As you can offer the download of the db to feed the program where the dll is for each game.

edit: this can be done in the program directly if you have a place to receave it. When the program finds the metadata it can cross check with the db to see where the dll is and avoid all the time to search it. If not found it'll search and then sent the relative location for you.

You also can store the ones not found and put a time it'll wait for it to search again. Or let user force search all again.

It's a sugestion.

beeradmoore commented 2 years ago

Cross checking a DB for relative paths of known DLSS positions can be funky because it relies on the game being the same version as it could (although unlikely) change in the future. It also means that DB would need to be updated which adds more complexity to the whole thing.

You also can store the ones not found and put a time it'll wait for it to search again. Or let user force search all again. Yep I think this. A single local cache might give a big (the current) scan time for the first opens the tool it should help subsequent openings. If a user manually hits refresh on the game list screen (which may be their first reaction if their new installed/update game isn't appearing correctly) we can do a full scan.

I am wondering if another problem is also HDD access speed. A SSD may be fine with me throwing lots of tasks at it but when we do the same to a HDD it just bogs down. If this is the case there should still be activity on your HDD even if the throughput is minimal.

A future solution to this could be to detect what drive type we have and if its HDD then we do things sequentially and if its SSD we add more parallelism.

Playing with it this morning I did find a new way to search for the DLSS dlls which in early testing is faster, but without knowing where the hefty loads are coming from it could be hard to optimize. I'll see if I have a HDD somewhere that I can plug into a USB2.0 external enclosure and install a bunch of small games onto. It should give me the worst possible performance to help diagnose what the issue is here.

But for now pushing for Windows Store release is priority as it allows easier discoverability and easier install for more users rather than having to jump through loops and import certificates and stuff.

EDIT: I forgot to mention I did look at if I can quickly add a cache, but the way the games are loaded it will require some refactoring to change the flow a bit. Because of the refactoring required is why I am pushing that to post Windows Store release. But I think a cache is required for general performance when opening the app so it will probably be priority after WS release.

tamodolo commented 2 years ago

While reading I was wondering if it's easy to implement a status like info to show users what the program is doing. The way it is now you just wait for it without knowing if it just hang. Yesterday I leave it working for more than 30 minutes without any signal it was indeed working. All system was idle including all storage drives.

beeradmoore commented 2 years ago

It should only be doing work while there is the loading wheel. If the loading wheel goes away and there is no games and everything else is idle then it's probable something died in the middle and returned 0 items.

I think it's unlikely that the thing that builds the UI died after it was told to draw 200 games but it is still a possibility.

I don't suppose you have any developer experience and want to install Visual Studio and run it from source? If not I can try get a custom build which will output stuff into a log that may help us pinpoint the issue.

Status indicator for when cache is being updated in the background could be useful, but it needs to be intuitive to the end user.

I'm not a designer in anyway so it's been a challenge trying to keep things to make sense to the user from the very basic just got their first gaming PC all the way to people who are making games themselves while at the same time not getting in their way of doing things.

When it's time to do cache I'll re-evaluate some sort of status indicator.

tamodolo commented 2 years ago

I can understand code to some extent. Learn it some math algorithm code in college. But I fall a bit short when UI or object oriented code is included. I'll try anyway. I'll let you know when I'm able to compile it.

tamodolo commented 2 years ago

It returns this error when trying to compile:

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs0433?f1url=%3FappId%3Droslyn%26k%3Dk(CS0433)

Ambiguity is caused by:

image

beeradmoore commented 2 years ago

Hmm. Not entirely sure what is the deal there. Up the top of Visual Studio there is a drop down that lets you select project to build. The one to build is DLSS Swapper (package) not just the plain DLSS Swapper.

Aside from that I am not sure why there is a few errors.

I just noticed your computer is in another language. Does that language use other characters for numbers (eg, French will use 3,14 instead of 3.14 when trying to display a decimal number), or does it have any special characters. Maybe special characters in any of the drive paths?

I'll look at how I can get you a custom build with additional logging and I'll make it output a bunch of actions so we can see what it is doing and where it may be stopping.

tamodolo commented 2 years ago

Both package and plain result the same error.

Yes, it's Brazilian portuguese, my native language. We use comma for decimals instead of dot but for programing languages that isn't a problem as It uses their own conventions.

The visual Studio was installed in vanilla state. Only detected dependencies were installed. Maybe there is something missing here.

We do use ansi for text format a lot but by now If you use utf than it's probably fine. I don't remember changing folders name but maybe it's a good Idea testing folders with ç and ã to check that.

beeradmoore commented 2 years ago

I am terrible at instructions sorry. According to the docs for WindowsAppSDK it requires the following packages are installed.

It also needs Windows 10 SDK (10.0.19041.0) but that should be selected already.

VS2022 also needs the latest WindowsAppSDK extension installed which you can get here.

(I really need to setup the contribution docs to add all this stuff in)

Regarding the language specific stuff. Going through below it appears it probably isn't a number issue as none of the things loaded are trying to deserialise a number. Although I am wondering if it is a lounge issue, like are steam config files in english even for a brazilian portuguese install. I would imagine yes, but we can double check.

The first place it checks is <steam install dir>/steamapps/libraryfolders.vdf, this contains data about where your steam libraries are located across multiple directories. DLSS Swapper goes through line by line looking for "path" "<some disk path here>". If that word is not in english then it'll not detect any steam libraries.

For each path found it looks for <library path>/steamapps/appmanifest_*.acf where * is a number (no decimals here). This is the manifest data for each game you have installed.

For each line in these we are looking for "name" "<some name>" "installdir" "<some install dir>" "appid" "<some number>" If any of these are not found it shouldn't error, it should just not load that specific game.

If any of these names don't match to what is listed here then it won't detect either the libraries or the individual games. This would perfectly explain both why no games are loading and the system goes idle. But on the flip side I know people have used this tool in Chinese with no issues. At least it is something we can tick off.

Thanks again for helping diagnose these issues.

tamodolo commented 2 years ago

Ah, I don't mind. I'll help you with this.

  • Universal Windows Platform development
  • .NET Desktop Development
  • Desktop development with C++

Hum... C++ was missing... weird. But also didn't fix only by installing it.

It also needs Windows 10 SDK (10.0.19041.0) but that should be selected already.

Yes, it's already installed.

VS2022 also needs the latest WindowsAppSDK extension installed which you can get here.

Just installed but still with that compile error...

(I really need to setup the contribution docs to add all this stuff in)

Regarding the language specific stuff. Going through below it appears it probably isn't a number issue as none of the things loaded are trying to deserialise a number. Although I am wondering if it is a lounge issue, like are steam config files in english even for a brazilian portuguese install. I would imagine yes, but we can double check.

The first place it checks is <steam install dir>/steamapps/libraryfolders.vdf, this contains data about where your steam libraries are located across multiple directories. DLSS Swapper goes through line by line looking for "path" "<some disk path here>". If that word is not in english then it'll not detect any steam libraries.

Checked just in case. This file is on C:\Program Files (x86)\Steam\steamapps\ (yeah, windows references to this in english but show me in portuguese. Clever.

For each path found it looks for <library path>/steamapps/appmanifest_*.acf where * is a number (no decimals here). This is the manifest data for each game you have installed.

For each line in these we are looking for "name" "<some name>" "installdir" "<some install dir>" "appid" "<some number>" If any of these are not found it shouldn't error, it should just not load that specific game.

This files are present in the right location. So it seems it's not that.

If any of these names don't match to what is listed here then it won't detect either the libraries or the individual games. This would perfectly explain both why no games are loading and the system goes idle. But on the flip side I know people have used this tool in Chinese with no issues. At least it is something we can tick off.

Thanks again for helping diagnose these issues.

You're welcome. I'll translate to you the error bellow:

image

The type "function name" exists in "the duplicate location and other info" and "second duplicate location and other info"

Columns are Code | Description | Project | File | line | Supression state

All errors is the same.

tamodolo commented 2 years ago

Ah, I just found what the issue is. You're trying to declare a variable with the same name as a language command and that's not possible at all. It's like I declare a var called cells in VBA. It'll crash.

image

Edit: No, nevermind that. Changing the var name didn't fix it...

tamodolo commented 2 years ago

Update: Decided to use the bult-in github support of VS22 and it's running now. The behavior is different from the release version as HDD was active for some time. No games were found... Here is the output data.

output.txt

beeradmoore commented 2 years ago

Interesting.

The only thing that seems out of place is

Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x800710DD : 'The WinUI Desktop Window object has already been closed.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(1) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.
Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x80000013 : 'The given object has already been closed / disposed and may no longer be used.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(2) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.
Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x80000013 : 'The given object has already been closed / disposed and may no longer be used.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(3) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.

Online people are saying its when you try close a window while its still debugging. Is that what you may have done?

I did start working on a custom build with logger to output more stuff, but now you have it working I'll commit the changes to the main branch and let you know so you can do a git pull in the VS source control and we can see if there is some more things happening.

If you wanted to try debug locally before I can get that out you can also check what data was loaded by adding these Debug.WriteLine statements under the relevant lines of source in Data/SteamLibrary.cs

libraryFolders = libraryFolders.Distinct().ToList();
System.Diagnostics.Debug.WriteLine($"libraryFolders.Count: {libraryFolders.Count}");
games.Sort();
System.Diagnostics.Debug.WriteLine($"games.Sort: {games.Count}");

That should let us know how many steam libraries were found, and how many games we loaded. If neither are in the log we never got to them. If either are 0 we somehow are not parsing Steam libraries correctly. If both are > 0 then the issue is somewhere else, possibly in the display code (eg, maying when that view is told to render 200 games it just dies silently.

tamodolo commented 2 years ago

Interesting.

The only thing that seems out of place is

Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x800710DD : 'The WinUI Desktop Window object has already been closed.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(1) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.
Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x80000013 : 'The given object has already been closed / disposed and may no longer be used.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(2) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.
Exceção gerada em 0x00007FF873324F69 (KernelBase.dll) em DLSS Swapper.exe: WinRT originate error - 0x80000013 : 'The given object has already been closed / disposed and may no longer be used.'.
onecore\com\combase\winrt\error\restrictederror.cpp(1016)\combase.dll!00007FF874E4C363: (caller: 00007FF874E56049) ReturnHr(3) tid(50a8) 8007007E Não foi possível encontrar o módulo especificado.

Online people are saying its when you try close a window while its still debugging. Is that what you may have done?

No. I didn't touch the program until code 0 was returned in the end of the output file. That was because I closed the program manualy.

I did start working on a custom build with logger to output more stuff, but now you have it working I'll commit the changes to the main branch and let you know so you can do a git pull in the VS source control and we can see if there is some more things happening.

If you wanted to try debug locally before I can get that out you can also check what data was loaded by adding these Debug.WriteLine statements under the relevant lines of source in Data/SteamLibrary.cs

libraryFolders = libraryFolders.Distinct().ToList();
System.Diagnostics.Debug.WriteLine($"libraryFolders.Count: {libraryFolders.Count}");
games.Sort();
System.Diagnostics.Debug.WriteLine($"games.Sort: {games.Count}");

That should let us know how many steam libraries were found, and how many games we loaded. If neither are in the log we never got to them. If either are 0 we somehow are not parsing Steam libraries correctly. If both are > 0 then the issue is somewhere else, possibly in the display code (eg, maying when that view is told to render 200 games it just dies silently.

I'll look at the code to include these lines and see what happens. I'll return the output file later.

tamodolo commented 2 years ago

Some updates:

I added the new lines where you pointed. It is able to count steam libraries that is 2. This is acurate to what I have so this is ok.

image

About five minutes later and I have a lot of messages closing threads. When I pause the debug run it says this is the next instruction to run after the current thread:

image

I'll leave running for some hours to see if this changes but that's it for now.

beeradmoore commented 2 years ago

Lots of threads closing makes sense, we do a lot of stuff off the main thread to keep the UI responsive rather than lock up the program. The fact that it never got to output the games count sounds concerning.

Where it loads the games,

var game = GetGameFromAppManifest(appManifest);
if (game != null)
{
    games.Add(game);
}

if we add a break in there,

var game = GetGameFromAppManifest(appManifest);
if (game != null)
{
    games.Add(game);
    break;
}

it should only attempt to load 1 game per library. Those games may show up until you hit the filter button and untick "Hide non-DLSS games"

tamodolo commented 2 years ago

After 2hrs running the program did nothing.

But that entry did something. It actually loaded 2 games. And it was in an instant.

image

tamodolo commented 2 years ago

update:

I put the game.sort below game.add inside the if. It was very fast until game 178. After that it gave up. There is a total of 205 manifest files inside the second steam library.

image

output.txt

beeradmoore commented 2 years ago

If you add in this output before we call GetGameFromAppManifest we can see where it is halting. The next thing is to see if the problem is that we managed to get up to so many manifests we try load that it dies OR is it this specific one that there is a problem with.

var appManifests = Directory.GetFiles(libraryFolder, "appmanifest_*.acf");
foreach (var appManifest in appManifests)
{
    System.Diagnostics.Debug.WriteLine($"Before GetGameFromAppManifest for {appManifest}");
    var game = GetGameFromAppManifest(appManifest);
    System.Diagnostics.Debug.WriteLine($"After GetGameFromAppManifest for {appManifest}")

If there is a specific one that fails to load we can exclude it like so

foreach (var appManifest in appManifests)
{
    if (appManifest.Contains("appmanifest_123456789.acf")
    {
        continue;
    }
    System.Diagnostics.Debug.WriteLine($"Before GetGameFromAppManifest for {appManifest}");
    var game = GetGameFromAppManifest(appManifest);
    System.Diagnostics.Debug.WriteLine($"After GetGameFromAppManifest for {appManifest}")

where appmanifest_123456789.acf is the name of the one that is failing. You don't need to include the full path, just the filename itself.

Hopefully its just this single entry that is failing and we can investigate it further as to what is wrong.

EDIT: You can also go open that appmanifest_123456789.acf and you should be able to see what game it is (assuming its the game and not the thread/disk flooding issue)

tamodolo commented 2 years ago

Found the one! It's Nier Automata (damn it Yoko Taro!!! haha)

Skiping it makes the program works:

image

You said that special characters could cause the problem. There is a ™ in the name... I'll send you the acf for you to test if you don't have the game.

appmanifest_524220.zip

Edit: The search time is very fast. It took around 3 seconds to parse all 205 games stored on mechanical HDD.

beeradmoore commented 2 years ago

Great to hear scan is relatively fast on such a huge library even on a mechanical HDD (means I can bump down the cache priority and focus on adding other libraries after Windows Store release)

I added ™ into a game name of mine and it didn't cause a problem. I am curious if there is something in the directory causing problems, maybe a something like a symbolic linked folder which points to another folder so the system is getting into an infinite loop.

I'll download the game and see if something appears off.

If you wanted to check something now you could delete the if statement that makes it skip this game, and then look in Game.cs and around where we scan for dlls you could try

System.Diagnostics.Debug.WriteLine($"Before first Directory.GetFiles for {Title}");
var dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);
System.Diagnostics.Debug.WriteLine($"After first Directory.GetFiles for {Title}");

If this is breaking the last thing that is output to the log would be

Before first Directory.GetFiles for NieR:Automata™

If it does indeed output the second one

After first Directory.GetFiles for NieR:Automata™

then issue is somewhere else.

We're getting close!

When this is done I'd like to get you to commit something so GitHub has you on the record as a contributor because after all this work from your side you deserve to be on the list.

EDIT: The game scanned fine for me :|

I'll start it up and play through to create a save point and see if having save data helps. Do you have any mods installed for the game?

With the

    if (appManifest.Contains("appmanifest_123456789.acf"))
    {
        continue;
    }

line you added yo could change it to

    if (appManifest.Contains("appmanifest_123456789.acf") == false)
    {
        continue;
    }

so we skip over every game EXCEPT this game. That way we can rule out this game + your install being the problem or if its also something to do with many games + this game. I can also setup a virtual machine in Brazilian portuguese and see if that helps me replicate it on my end. If you do this you'll need to hit the filter button and untick Hide non-dlss games so we can see that it did load in the list.

tamodolo commented 2 years ago

Great to hear scan is relatively fast on such a huge library even on a mechanical HDD (means I can bump down the cache priority and focus on adding other libraries after Windows Store release)

Running after a reboot and with before/after code take somewhat 2 min to end scan. Reasonable fast.

I added ™ into a game name of mine and it didn't cause a problem. I am curious if there is something in the directory causing problems, maybe a something like a symbolic linked folder which points to another folder so the system is getting into an infinite loop.

There is the one created by Special K. A tool to tweak some bad behaviors of the game. This need some test but it's probably the cause. A code to skip that folder should fix this if that's the case.

If this is breaking the last thing that is output to the log would be

Before first Directory.GetFiles for NieR:Automata™

If it does indeed output the second one

After first Directory.GetFiles for NieR:Automata™

then issue is somewhere else.

We're getting close!

And this is the output: image

When this is done I'd like to get you to commit something so GitHub has you on the record as a contributor because after all this work from your side you deserve to be on the list.

Oh! Thanks for that! I apreciate it gadly!

EDIT: The game scanned fine for me :|

I'll start it up and play through to create a save point and see if having save data helps. Do you have any mods installed for the game?

Just Special K that creates that symlink. It's like this:

image

With the

    if (appManifest.Contains("appmanifest_123456789.acf"))
    {
        continue;
    }

line you added yo could change it to

    if (appManifest.Contains("appmanifest_123456789.acf") == false)
    {
        continue;
    }

so we skip over every game EXCEPT this game. That way we can rule out this game + your install being the problem or if its also something to do with many games + this game. I can also setup a virtual machine in Brazilian portuguese and see if that helps me replicate it on my end. If you do this you'll need to hit the filter button and untick Hide non-dlss games so we can see that it did load in the list.

Leaving the code there makes it only look for nier automata and hangs there.

image

I can confirm that it is indeed that link that is making the program hangs as removing it solves the problem:

image

I also tryed to add some filters to getfile with no success at all...

The code:

var dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.TopDirectoryOnly).Where(d => !d.Contains("SpecialK")).ToArray();
beeradmoore commented 2 years ago

This is looking like it could be the culprit. I had a look if there were any options that were part of Directory.GetFiles to ignore symbolic links but I couldn't see anything.

Where we did

System.Diagnostics.Debug.WriteLine($"Before first Directory.GetFiles for {Title}");
var dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);
System.Diagnostics.Debug.WriteLine($"After first Directory.GetFiles for {Title}");

if you could you try

var dlssDlls = new string[];
try
{
    System.Diagnostics.Debug.WriteLine($"Before first Directory.GetFiles for {Title}");
    dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);
    System.Diagnostics.Debug.WriteLine($"After first Directory.GetFiles for {Title}");
}
catch (Exception err)
{
    System.Diagnostics.Debug.WriteLine($"Exception in Directory.GetFiles for {Title} - {err.Message}");
}

I am curious of the Directory.GetFiles is looping internally or straight up crashing. Ideally it outputs Exception in Directory.GetFiles... because then we can catch that (or change an option to ignore permission errors) and move past it. If it doesn't output anything after Before first Directory.GetFiles then it means that call itself is freezing up and we'd have to look to replace it.

Replacing it isn't too bad, it's just a matter of removing SearchOption.AllDirectories and then implementing our own recursive function which will do essentially that but but also try detect symbolic links.

There was a function I found over here does essentially that, and somehow does it 10x faster (from my initial testing) 🤯 . I had put this in initially in the game_cache branch as a performance improvement. If you can stash/reset your changes (or pull this branch into its own completely seperate directory) we can see if scanning this way can ignore symbolic links.

Over the weekend I'll also see if I can replicate all of this with Special K.

EDIT: I had a play with SpecialK, I have no idea how to use it or how to make it make this symbolic linked directory. I did try manually create them with windows terminal but I was not able to replicate the issue you are receiving.

Pointing it to a folder on another drive worked fine. Deleting the target on the other end of the symbolic linked directory worked fine. Pointing it at a protected folder (eg. WindowsApps) made the game not display but it still allowed my other games to load.

Curious how your SpecialK one is different. What happens if you try navigate to it in windows explorer?

tamodolo commented 2 years ago

This is looking like it could be the culprit. I had a look if there were any options that were part of Directory.GetFiles to ignore symbolic links but I couldn't see anything.

Where we did

System.Diagnostics.Debug.WriteLine($"Before first Directory.GetFiles for {Title}");
var dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);
System.Diagnostics.Debug.WriteLine($"After first Directory.GetFiles for {Title}");

if you could you try

var dlssDlls = new string[];
try
{
    System.Diagnostics.Debug.WriteLine($"Before first Directory.GetFiles for {Title}");
    dlssDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);
    System.Diagnostics.Debug.WriteLine($"After first Directory.GetFiles for {Title}");
}
catch (Exception err)
{
    System.Diagnostics.Debug.WriteLine($"Exception in Directory.GetFiles for {Title} - {err.Message}");
}

I am curious of the Directory.GetFiles is looping internally or straight up crashing. Ideally it outputs Exception in Directory.GetFiles... because then we can catch that (or change an option to ignore permission errors) and move past it. If it doesn't output anything after Before first Directory.GetFiles then it means that call itself is freezing up and we'd have to look to replace it.

I read that searchoptions.alldirectories can result in infinite loop if there is a cross reference using links. Well... specialK did just that... #facepalm

image

edit: forgot to mention. As it results in infinit loop then no error will actually happen...

Replacing it isn't too bad, it's just a matter of removing SearchOption.AllDirectories and then implementing our own recursive function which will do essentially that but but also try detect symbolic links.

There was a function I found over here does essentially that, and somehow does it 10x faster (from my initial testing) 🤯 . I had put this in initially in the game_cache branch as a performance improvement. If you can stash/reset your changes (or pull this branch into its own completely seperate directory) we can see if scanning this way can ignore symbolic links.

I'll do the pull and test it. I'll bring the results when testing it.

Over the weekend I'll also see if I can replicate all of this with Special K.

EDIT: I had a play with SpecialK, I have no idea how to use it or how to make it make this symbolic linked directory. I did try manually create them with windows terminal but I was not able to replicate the issue you are receiving.

Pointing it to a folder on another drive worked fine. Deleting the target on the other end of the symbolic linked directory worked fine. Pointing it at a protected folder (eg. WindowsApps) made the game not display but it still allowed my other games to load.

Curious how your SpecialK one is different. What happens if you try navigate to it in windows explorer?

The Special K from github is very old. You need to download it from their discord channel. You can find it here: https://discord.gg/QgUQRaT8

After installing it you'll get something like this:

image

You need to start the service before opening the game. You'll know that worked because a header will be shown inside the game for some seconds. When this happens it creates that folder for it to be able to configure itself. This is a tool I often use when I have some problem as it's quite awesome as workarround. For example: Trails of Cold Steel have a very bad frame pacer that can't comunicate with gsync. This tool fix this just by opening before the game and freesync will just work.

For Nier Automata, it unlocks fps above 60 and fix the frame pacer and aleviate stuttering. It is needed form injecting texture mods that I don't have installed.

tamodolo commented 2 years ago

I did run the game-cache brench and the behavior is the same. I saw the code for bettergetfile but I don't really know how to edit it to check links.

The most close code I found was

private bool IsSymbolic(string path)
{
    FileInfo pathInfo = new FileInfo(path);
    return pathInfo.Attributes.HasFlag(FileAttributes.ReparsePoint);
}

source: https://stackoverflow.com/questions/1485155/check-if-a-file-is-real-or-a-symbolic-link

I don't have the knoledge on how to use it with getfile.

Edit: I read that this can give false positive as real files can have reparse points. But I don't think this will be relevant to any location DLSS DLL will be. I think it'll work anyway.

beeradmoore commented 2 years ago

Thanks for the info about SpecialK. For the most part that's what I did when testing it but it never made the symbolic link. I didn't go install it again but I was able to replicate this with a recursive symbolic link

mklink /D "S:\SteamLibrary\steamapps\common\NieRAutomata\data\BackToRootDirectory" "S:\SteamLibrary\steamapps\common\NieRAutomata\"

Great catch on reparse files. I was going through the docs and somehow missed this. I think for our use case it is ok to skip any directory that is a reparse directory. What I think they are getting at is that if you mount a network drive as C:\MyNetworkDrive\ then it is a reparse file. But for us I don't think a game will have these (although Windows Store/Xbox App games may... I'll double check). I also don't think a game will have the nvngx_dlss.dll as a reparse file but who knows.

Turns out that game-cache branch is broken from the start (and I may delete it to start again). Turns out that code I found that was stupidly fast was only stupidly fast because it was scanning a max depth of 1 folder. Of course it will be fast if we don't scan all the folders 🤦‍♂️

To test this works on your end I'll need you to open Game.cs and anywhere there is a Directory.GetFiles(..., "...", SearchOption.AllDirectories); we'll update it to one that replaces the SearchOption.AllDirectories with an EnumerationOptions object.

So this,

var foundDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);

becomes

var enumerationOptions = new EnumerationOptions();
enumerationOptions.RecurseSubdirectories = true;
enumerationOptions.AttributesToSkip |= FileAttributes.ReparsePoint;
var foundDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", enumerationOptions);

There should be 4 of them to update (although the first two can use the same enumerationOptions object as they are both in the same method)

RecurseSubdirectories will do what SearchOption.AllDirectories was doing. AttributesToSkip is letting us skip things with ReparsePoint. It also has the defaults of FileAttributes.System and FileAttributes.Hidden so we'll also skip those. I don't think a game will have hidden files/folders and I have no idea what system means so this sounds good to me.

We also won't update other parts of the system because it is possible that someone has a small SSD for their C drive, they added a larger SSD for E drive and they mounted steamapps/common/ to point to E:/SteamApps/Common. I used to do this to my iTunes backup folder so I can keep iTunes on my SSD but move my backups to my mechanical drive where I have more space.

If this solves it then we can go about getting this code in, your name on the list and the build out for everyone.

Commit to this you'll need to fork the repository. I don't know if you can do this via the git clients, I always do it via github in the browser. After you have forked it you will have your own tamodolo/dlss-swapper repository. Pull that to your computer, make the changes, test it, commit it back with some message like "This fixes games with recursive symlinks"

After that you can open a pull request (I also do this in the browser) to merge the changes from main branch on tamodolo/dlss-swapper to main branch on beeradmoore/dlss-swapper. Normally I'd have you do it in another branch like bugfix/symboliclink_scanning but this is already enough hoops to jump through. Then I should be able to review it on my end and merge it in.

tamodolo commented 2 years ago

Thanks for the info about SpecialK. For the most part that's what I did when testing it but it never made the symbolic link. I didn't go install it again but I was able to replicate this with a recursive symbolic link

mklink /D "S:\SteamLibrary\steamapps\common\NieRAutomata\data\BackToRootDirectory" "S:\SteamLibrary\steamapps\common\NieRAutomata\"

Great catch on reparse files. I was going through the docs and somehow missed this. I think for our use case it is ok to skip any directory that is a reparse directory. What I think they are getting at is that if you mount a network drive as C:\MyNetworkDrive\ then it is a reparse file. But for us I don't think a game will have these (although Windows Store/Xbox App games may... I'll double check). I also don't think a game will have the nvngx_dlss.dll as a reparse file but who knows.

Turns out that game-cache branch is broken from the start (and I may delete it to start again). Turns out that code I found that was stupidly fast was only stupidly fast because it was scanning a max depth of 1 folder. Of course it will be fast if we don't scan all the folders 🤦‍♂️

I think this method can't be speed up while using getfiles as it is a fixed function. Also don't think it's needed at all as 200 games was read under 30 seconds.

To test this works on your end I'll need you to open Game.cs and anywhere there is a Directory.GetFiles(..., "...", SearchOption.AllDirectories); we'll update it to one that replaces the SearchOption.AllDirectories with an EnumerationOptions object.

So this,

var foundDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", SearchOption.AllDirectories);

becomes

var enumerationOptions = new EnumerationOptions();
enumerationOptions.RecurseSubdirectories = true;
enumerationOptions.AttributesToSkip |= FileAttributes.ReparsePoint;
var foundDlls = Directory.GetFiles(InstallPath, "nvngx_dlss.dll", enumerationOptions);

Yes! This worked!

There should be 4 of them to update (although the first two can use the same enumerationOptions object as they are both in the same method)

Also updated.

RecurseSubdirectories will do what SearchOption.AllDirectories was doing. AttributesToSkip is letting us skip things with ReparsePoint. It also has the defaults of FileAttributes.System and FileAttributes.Hidden so we'll also skip those. I don't think a game will have hidden files/folders and I have no idea what system means so this sounds good to me.

We also won't update other parts of the system because it is possible that someone has a small SSD for their C drive, they added a larger SSD for E drive and they mounted steamapps/common/ to point to E:/SteamApps/Common. I used to do this to my iTunes backup folder so I can keep iTunes on my SSD but move my backups to my mechanical drive where I have more space.

I used this method when steam didn't support many libraries. Now that does (and allows swapping games around) I never felt the need to ever use that again.

If this solves it then we can go about getting this code in, your name on the list and the build out for everyone.

Commit to this you'll need to fork the repository. I don't know if you can do this via the git clients, I always do it via github in the browser. After you have forked it you will have your own tamodolo/dlss-swapper repository. Pull that to your computer, make the changes, test it, commit it back with some message like "This fixes games with recursive symlinks"

After that you can open a pull request (I also do this in the browser) to merge the changes from main branch on tamodolo/dlss-swapper to main branch on beeradmoore/dlss-swapper. Normally I'd have you do it in another branch like bugfix/symboliclink_scanning but this is already enough hoops to jump through. Then I should be able to review it on my end and merge it in.

I think this bug is solved. I'll commit for you shortly.

About the Special K folder, indeed just opening the game for the first time don't create it anymore. Probably it's created when you change something on the special k in-game overlay. I remember doing that with nier automata to fix the frame pacer and unlock fps as it isn't a default behavior. These options works great with high fps displays that suports any kind of VRR tech.

Also, nier automata is a special case with specific fixed for it. There are some other games that also are special cases within special k like dark souls, FFX and Tales of Berseria.

beeradmoore commented 2 years ago

I think this method can't be speed up while using getfiles as it is a fixed function. Also don't think it's needed at all as 200 games was read under 30 seconds.

Yeah, I think the game-cache branch is going to instead focus on the games install ID, or update ID or something. And then just not re-scan if that ID hadn't changed since we last scanned.

I think what solving this issue has shown is that it could be a good idea to enable some sort of logging. Probably off by default but enable it via command line argument or in the settings. It's also kinda hard because we want info that could be the problem (eg. with TM in the name), but also we don't really want to have people post a log which shows all the different games they have that they may not want to be public. Nothing is ever easy 😂

Thanks again for all your work. I'll get this build out tomorrow.

tamodolo commented 2 years ago

Thank you! I helped just with testing and some ideas and the hard part was yours. I'm more familiar with vb and C so C# was a bit complex when I tried to write myself.

beeradmoore commented 2 years ago

v0.9.8.0 was just released. Thanks again for not only bringing it up, but helping diagnose the problem.