CNA-BUG-041: A relative ContentManager root still follows the working directory on the non-SDL3 platforms, and the loose tier cannot see Android packaged assets

CNA snapshot c1c316b9  ·  Known Issues › Current bugs  ·  source links pinned to c1c316b9

✓

Evidence basis: source-verified at the pinned commit; tests exist (not executed for this page). Claims on this page were checked by reading the CNA source at commit c1c316b9; unless a sentence says otherwise, nothing here was built or executed. Nothing on this page was executed unless the Evidence section says so.

ContentManager now roots a relative RootDirectory under TitleLocation and reads .cnb through the platform on Android, but TitleLocation is the working directory on the non-SDL3 platforms and the loose tier still gates on std::filesystem::exists.

Identifier
CNA-BUG-041
Category
Bug
Subsystem
Content & XNB/CNB/CNJ
Status
Narrowed (partially fixed; describes only what survives)
Verified against
CNA c1c316b9 (c1c316b9c7a846ce8002809c151fcd1af14942c9)
Severity
Medium (a triage suggestion, not a project priority)
Evidence basis
Source-verified: read at TARGET, not executed
Tests touching this area
Yes: see Current tests
Affected contract
ContentManager::RootDirectory and ContentManager::Load path resolution (XNA resolves through TitleContainer.OpenStream, relative to the title location)

Expected behaviour

XNA 4.0's ContentManager.OpenStream combines RootDirectory with the asset name and opens it through TitleContainer.OpenStream, which resolves a relative path against the title location (the executable's directory), independent of the working directory. CNA's own TitleContainer::ResolveRealPath (TitleContainer.cpp) resolves through TitleLocation::getPathProperty(), which is the platform's GetBasePath(): on the SDL3 platform that is SDL_GetBasePath() (Sdl3SystemServices.cpp), the executable's directory, so there TitleContainer follows XNA's rule; CNA's docs/apple-platforms.md states that "Content is loaded relative to SDL_GetBasePath()". On Android every content tier should read the APK's packaged assets.

Actual behaviour at TARGET

BuildAssetPath joins TitleRootedDirectory() and the asset name. Off Android, that function passes the root through CNA::Internal::ResolveContentRoot (ContentRoot.cpp): a relative root is used under the title path (TitleLocation's path, else the platform's GetBasePath()) when that directory exists, and is otherwise left relative to the working directory. On the SDL3 platform the title path is the executable directory. On the headless and terminal platforms it is the working directory, because StandardFileSystem::GetBasePath (StandardFileSystem.cpp) returns current_path(). A game started from another directory therefore still misses its default Content root there.

On Android the root is used as given, and TryReadAssetBytes forwards a relative path to the platform file system. That reaches packaged assets for the .xnb tier and, since BL-19, for both .cnb tiers. ResolveAssetPath (ContentManager.hpp) still tests the literal path, the .cnj candidate and every reader extension with std::filesystem::exists, which cannot see APK assets, and the built-in Texture2D loose reader opens through ImageLoader::Load's std::ifstream.

Fixed since first recorded: a relative root resolves under the title (CBIND-140, 9976f4909) and the .cnb tiers read through TryReadAssetBytes (BL-19, 5d275f556). Earlier fixes: TitleContainer and TitleLocation have in-tree consumers (CnaCApiGameProperties.cpp), and the .xnb tier's Android route goes through Sdl3FileSystem::TryLoadFileIgnoringCase.

Source locations

Evidence

Checked by reading at c1c316b9; not run on Android or an Apple device. Whether SDL_LoadFile (reached by TryReadAssetBytes' platform fallback) resolves a relative name inside an Apple app bundle depends on SDL's own I/O layer and was not checked. The site's ContentManager guide documents the root rule, and the content-runtime internals page hedges the Android question; this entry supplies the reading for both.

Independent re-verification: one audit run at 009d40f5 confirmed the central claim as it stood then: with the headless platform, TitleLocation set by hand to the directory holding Content and the working directory changed to /, TitleContainer::OpenStream found the asset while ContentManager with the relative root "Content" threw ContentLoadException. CBIND-140 (9976f4909) has since made ContentManager resolve a relative root under the title path, so that run no longer describes the code; this entry keeps only what survives, by reading.

Re-verified at b0e97bb1: read TitleRootedDirectory, ResolveContentRoot, ResolveAssetPath, ImageLoader::Load and StandardFileSystem::GetBasePath; not executed.

Focused reproduction

Illustrative; not run (the audit run at 009d40f5 is described under Evidence).

# A game built for the headless or terminal platform, installed as /opt/mygame/mygame with /opt/mygame/Content/ beside it.
cd /opt/mygame && ./mygame        # content loads: the working directory is the executable directory
cd / && /opt/mygame/mygame        # Load<T> throws ContentLoadException for every asset: the title path is the
                                 # working directory (/), which has no Content directory

Current tests

ContentRootTests.cpp pins ResolveContentRoot (a root under the title, a missing title directory, an absolute root, no title). UnicodeContentRootTests.cpp and ContentPathContainmentTests.cpp use absolute roots. No Android content-path test runs in CnaTests, and no test loads through a non-SDL3 platform from another working directory.

Regression test

On Android, one device test per tier (.cnj, a loose PNG, a loose WAV) loaded from the APK with the relative root "Content". On a platform backed by StandardFileSystem, a Load<T> with the working directory changed away from the executable's directory.

Blast radius

Games on the headless or terminal platforms launched from another directory with a relative root. On Android, every .cnj and loose (non-compiled) asset shipped inside the APK. The SDL3 platform on desktop, absolute roots, and .xnb and .cnb content on Android are unaffected.

Workaround

On the platforms whose base path is the working directory, build an absolute root from the executable's own directory by host means, or start the game from its directory. On Android, ship run-time content as .xnb or .cnb.

The same subject is explained at several altitudes. These are the neighbouring pages at each one.

Known issues
Bug index