godot icon indicating copy to clipboard operation
godot copied to clipboard

Fix "res://" being replaced by resource packs in the editor and on Android

Open tracefree opened this issue 1 year ago • 28 comments

The problem

When using ProjectSettings.load_resource_pack and launching the game from the editor or on Android, DirAccess no longer sees files and directories outside of loaded packs, which is unexpected behavior and makes it difficult to work on modding support for example.

My solution

The simplest way I could find to solve this was to add a new kind of PackSource: PackedSourceDirectory (working title). It uses a resource directory as the source of the "pack" rather than a file, and returns regular FileAccess references. The first time a resource pack is loaded, the entire "res://" directory gets added to PackedData.

I adapted the MRP of #48563 to Godot 4.x: MRPResourcePack.zip

I am new to Godot's codebase and C++ in general, so there is probably a better solution. I'd be happy about any feedback or suggestions on how to solve this problem properly.

Edit: This should also solve replace_files == false not doing anything in non-exported projects, see #89299.

  • Bugsquad edit, fixes: #48563
  • Bugsquad edit, fixes: #19815
  • fixes #87274
  • fixes #16798
  • fixes #89299

tracefree avatar Apr 09 '24 09:04 tracefree

One alternative approach I could imagine might be preferable is to change the behavior of DirAccess::make_default, but I think that would require more substantial changes and I'm not sure I understand the codebase well enough to make them myself.

tracefree avatar Apr 09 '24 09:04 tracefree

Similar problems also exist on Android, because on Android, "res://" is also a real local directory.

scgm0 avatar Apr 09 '24 11:04 scgm0

Similar problems also exist on Android, because on Android, "res://" is also a real local directory.

Is there an issue documenting the problem on Android? I'm not sure my fix would resolve that, but I could try it out.

By the way, this may go beyond the scope of a bugfix, but I think my approach could easily be adapted to allow users to load any directory on the filesystem as a "pack", not just the resource folder. This could be very useful to modders, who would otherwise need to pack all assets into a .pck or .zip file every time they want to test changes to their mod while developing it. This would work by e.g. ProjectSettings.load_resource_pack("path/to/mod/directory").

tracefree avatar Apr 09 '24 16:04 tracefree

Similar problems also exist on Android, because on Android, "res://" is also a real local directory.

Is there an issue documenting the problem on Android? I'm not sure my fix would resolve that, but I could try it out.

By the way, this may go beyond the scope of a bugfix, but I think my approach could easily be adapted to allow users to load any directory on the filesystem as a "pack", not just the resource folder. This could be very useful to modders, who would otherwise need to pack all assets into a .pck or .zip file every time they want to test changes to their mod while developing it. This would work by e.g. ProjectSettings.load_resource_pack("path/to/mod/directory").

https://github.com/godotengine/godot/issues/87274 Although I didn’t mention Android in the introduction, I did initially discover this problem on Android.

scgm0 avatar Apr 09 '24 17:04 scgm0

#87274 Although I didn’t mention Android in the introduction, I did initially discover this problem on Android.

Ah yes, that does appear to be the same problem and my PR should fix it (unless there are additional complications due to Android, I'll try to test that out). Adding it to the list of relevant issues, thank you.

tracefree avatar Apr 09 '24 17:04 tracefree

I just realized there is a problem in my code: After loading a resource pack, you can only see files that existed in res:// at the time the first pack was loaded. Meaning any files that were created in the resource folder at runtime after loading the pack won't be visible. This is still better than the current behavior, where you can't see any files not belonging to datapacks in the resource folder at all, but ideally there shouldn't be any unexpected side effects to loading a resource pack. I'll try to see if there's a simple way to keep the contents of PackedData synced to changes in the filesystem. This would probably have to involve adding something like "remove_path" to PackedData to support cases where resource files were removed at runtime.

tracefree avatar Apr 10 '24 06:04 tracefree

I just realized there is a problem in my code: After loading a resource pack, you can only see files that existed in res:// at the time the first pack was loaded. Meaning any files that were created in the resource folder at runtime after loading the pack won't be visible. This is still better than the current behavior, where you can't see any files not belonging to datapacks in the resource folder at all, but ideally there shouldn't be any unexpected side effects to loading a resource pack. I'll try to see if there's a simple way to keep the contents of PackedData synced to changes in the filesystem. This would probably have to involve adding something like "remove_path" to PackedData to support cases where resource files were removed at runtime.

It feels like the current fix is the usable version, res:// is not supposed to be modified at runtime, so synchronizing only the first time is good enough.

scgm0 avatar Apr 10 '24 21:04 scgm0

It feels like the current fix is the usable version, res:// is not supposed to be modified at runtime, so synchronizing only the first time is good enough.

I tend to agree, but then the behavior of this edge case should be documented. Fact is that it is currently possible to modify res:// at runtime in the editor, and while it doesn't make sense to do so in exported builds I could imagine there are legitimate use cases for it during development of a project. Perhaps I could add a short sentence to the documentation of load_resource_pack?

tracefree avatar Apr 11 '24 07:04 tracefree

The last thing I need clarity on is whether and how to expose PackedSourceDirectory to users. At the moment, due to how I implemented the fix, users can call load_resource_pack("res://") by themselves, and they can even use subdirectories of res://, which would be like loading a pack that has the contents of that directory. I see four options:

  1. Do nothing. Then there would be undocumented / possibly undefined behavior of load_resource_pack.
  2. Leave it as is but document this relatively useless behavior.
  3. Somehow try to prevent users from doing this manually.
  4. Make it into a useful feature by allowing to load any directory on the filesystem as a pack, not just ones in res://, and document it alongside .pck and .zip files as a fully supported third kind of pack. This would be my personal preference, but I'd like to hear some more opinions before I work on including this in the PR.

tracefree avatar Apr 11 '24 09:04 tracefree

I don't think there is any need to expose PackedSourceDirectory at the moment. Let this PR only fix bugs. 4 can be considered for completion in subsequent PRs.

scgm0 avatar Apr 11 '24 12:04 scgm0

I don't think there is any need to expose PackedSourceDirectory at the moment. Let this PR only fix bugs. 4 can be considered for completion in subsequent PRs.

Makes sense. Would you say this PR is ready to be merged as is then, or should I include a check to prevent users from accessing the new internal functionality manually from GDScript until it's fleshed out and documented later? I'm thinking of adding something like if (p_pack == "res://") { return false } to _load_resource_pack.

tracefree avatar Apr 11 '24 13:04 tracefree

Reasonable, before the function is determined, user calls should indeed be prevented from producing unpredictable results.

scgm0 avatar Apr 11 '24 13:04 scgm0

By doing some more tests I realized setting the argument replace_files to false does nothing when loading the first pack, even on the master branch. It is documented here: #89299. From looking at the source code it seems the setting only allows not overriding files of previously loaded packs, not the resource folder if it is on the filesystem instead of a main pack. The reason for it is the same as for the other bugs. I was able to fix this with the following changes to the PR: it now includes hidden files and directories (crucially, this includes the .godot/imported folder) and uses the correct value of p_replace_files when loading res:// instead of always setting it to false.

tracefree avatar Apr 11 '24 15:04 tracefree

As discussed earlier, I added a line to the documentation of load_resource_pack explaining the remaining limitation to DirAccess. The wording may be a bit awkward, I'd appreciate any feedback if the same thing can be said in a more straightforward way.

tracefree avatar Apr 11 '24 15:04 tracefree

I'm failing a unit test for the macOS editor. Unfortunately I don't have access to any Apple computers... does anyone have an idea for what could be causing this? I assume it might have something to do with how I'm iterating over hidden directories?

tracefree avatar Apr 11 '24 16:04 tracefree

That one happens randomly on macOS, we're still unsure why, I'll just run it again and it should fix itself

AThousandShips avatar Apr 11 '24 16:04 AThousandShips

How is the progress of this PR? Is it possible to merge in 4.3?

scgm0 avatar Apr 19 '24 02:04 scgm0

How is the progress of this PR? Is it possible to merge in 4.3?

It's ready from my side. Since it's a bug-fix I assume it could even be cherrypicked for 4.2.x / 4.1.x?

tracefree avatar Apr 19 '24 07:04 tracefree

Please squash your commits into one, see here

AThousandShips avatar Apr 26 '24 09:04 AThousandShips

I've been thinking, maybe PackedSourceFileSystem is a better name than PackedSourceDirectory?

Edit: Or PackedSourceVirtual, since the source isn't actually packed at all, but the name might be less clear.

tracefree avatar May 15 '24 10:05 tracefree

Just a quick status update. This looks promising (and appears to solve a lot of important issues), but from a quick chat with @reduzio it seemed to us that there is a deeper design issue with the resource pack loading, which would need to be addressed properly instead of worked around like done here.

So this needs further research (likely from the @godotengine/core team), which may or may not lead to merging this solution as is, or a more involved one. Either case, since we're in feature freeze for 4.3 now and this is relatively sensitive code, we'll postpone this to 4.4 (and consider a 4.3 cherry-pick if the solution we end up merging seems suitable for it).

akien-mga avatar May 22 '24 09:05 akien-mga

Understood, thank you for the update! Please let me know if there's any way I can help.

tracefree avatar May 22 '24 17:05 tracefree

I hope don't forget about godot 3.6 all bugs reported for the 3.x branch are sent to 4.x ...

dannygaray60 avatar May 24 '24 15:05 dannygaray60

I hope don't forget about godot 3.6 all bugs reported for the 3.x branch are sent to 4.x ...

If there isn't a dedicated 3.x report for this please open one, the claim that all bugs are just sent to 4.x isn't true if they're specific to 3.x

AThousandShips avatar May 24 '24 15:05 AThousandShips

@AThousandShips is an issue already reported for 3.x ... #54917

Screenshot 2024-05-24 091957

I say this because even I had reported a problem in 3.x #67953 and the solution was only for the 4.x and they closed the issue ...

dannygaray60 avatar May 24 '24 15:05 dannygaray60

The other issue is off topic for this PR, but the 3.6 marked issue is now not linked to this so it can be tracked and discussed, not all fixes can be applied to 3.x because of compatibility and complexity

AThousandShips avatar May 24 '24 15:05 AThousandShips

For what it's worth, I think my fix would easily transfer to 3.x as well. I can look into it once we have an agreed upon solution for 4.x.

tracefree avatar May 25 '24 14:05 tracefree

I've found an issue with this fix (because it doesn't fix something crucial). When you load a resource pack with replace files enabled, it will only replace files if they haven't been referenced before loading the pck/zip. For example, in the MRPResourcePack project, if the icon.svg is used in another scene as an image, the ProjectSettings.load_resource_pack function won't replace the icon.svg with replace files enabled. I'm not talking about the image not updating in the other scene (even though that's broken too). When a file has been referenced before in any kind (with preload, in other scenes, in other resources) and you load a resource pack and then try to load the file with load("res://path-to-file"), you get the old one

ThatFinnDev avatar Aug 17 '24 13:08 ThatFinnDev

adding on to this, this fix is mostly what id need, however trying to replace files using load_resource_pack is hopeless. either checking which files, resources and scenes etc are loaded before the pck gets loaded, then unloading them. or just giving us a way to see what resources are already loaded would be incredibly helpful!

heck, resource loader has a mode which allows us to replace files upon loading a resource, it would be amazing if it could just reload existing resources, upon the pck file being loaded, thatd make mods SOOO much more flexible!

JHDev2006 avatar Aug 28 '24 13:08 JHDev2006

Hi! It's been a while since I worked on this, but IIRC replacing files that have been previously loaded is not a trivial fix due to how the resource system works, and I think it would be out of scope for this specific PR. And as akien said earlier, the whole resource pack system has design flaws and will have to be fundamentally changed to properly fix all its various issues, so I am waiting on updates on those plans before I can build on this PR.

tracefree avatar Aug 30 '24 12:08 tracefree