Skip to content

Fix tutorials double Scenarios in path#3659

Merged
AaronVanGeffen merged 4 commits intoOpenLoco:masterfrom
GoGoOtaku:master
Mar 8, 2026
Merged

Fix tutorials double Scenarios in path#3659
AaronVanGeffen merged 4 commits intoOpenLoco:masterfrom
GoGoOtaku:master

Conversation

@GoGoOtaku
Copy link
Copy Markdown
Contributor

Clicking on any tutorial mission from the main menu crashes the game with:

Exception: Exception 'Failed to open 'Scenarios/Scenarios/Boulder Breakers.SC5' for writing', thrown at 'FileStream' - src/Core/src/FileStream.cpp:82

This is because in src/OpenLoco/src/Scenario/Scenario.cpp in Scenarios::load it puzzles the path together like this:

auto scenarioPath = Environment::getPath(Environment::PathId::scenarios);
fullPath = scenarioPath / path;

scenarioPath is "Scenarios" and path is the given path which is set to Environment::PathId::boulderBreakers in src/OpenLoco/src/Tutorial.cpp which is "Scenarios/Boulder Breakers.SC5"

@AaronVanGeffen
Copy link
Copy Markdown
Member

Oddly enough, it seems to be working fine for me as-is. What platform are you testing with?

@GoGoOtaku
Copy link
Copy Markdown
Contributor Author

GoGoOtaku commented Mar 7, 2026

I am using Gentoo Linux x86_64. I used the GOG version of the game (installed with Wine but that makes no difference here, as I did not use Wine to run OpenLoco)
I compiled version 25.12, 26.02 and current master.
All of them had this issue.

I compiled OpenLoco using

cmake -B build
cd build
ninja

So nothing fancy here. I default to GCC which is up to date on version 15.2.1_p20260214.

I did not copy OpenLoco into my installation folder but rather ran the executable from said folder.
Since this is an oddly obvious bug I tried to make sure that it wasn't on my side but looking into the code it seemed like a bug.

Since you said you do not experience this I wrote this short test program:

#include <filesystem>
#include <iostream>
#include <ostream>

int main(int argc, char **argv)
{
    std::cout << "Current Folder: " << std::filesystem::current_path() << std::endl;
    std::cout << "Current Root Path: " << std::filesystem::current_path().root_path() << std::endl;
    std::cout << "Has Current Root Path: " << std::filesystem::current_path().has_root_path() << std::endl;
    std::cout << "Scenario File String: "  << fs::path("Scenarios/Boulder Breakers.SC5") << std::endl;
    std::cout << "Scenario Root Path: " << std::filesystem::path("Scenarios/Boulder Breakers.SC5").root_path() << std::endl;
    std::cout << "Has Scenario Root Path: " << std::filesystem::path("Scenarios/Boulder Breakers.SC5").has_root_path() << std::endl;
    return 0;
}

Which using g++ or clang++ gives me the output:

Current Folder: "/home/gogo/src/localhost/test/cpp/fs"
Current Root Path: "/"
Has Current Root Path: 1
Scenario File String: Scenarios/Boulder Breakers.SC5
Scenario Root Path: ""
Has Scenario Root Path: 0

and MSVC running in wine (tbf this is a bit running on fumes but it is the real MSVC from Microsoft just not running on Windows; A test on Windows runtime might still yield different results tbf):

Current Folder: "Z:\\home\\gogo\\src\\localhost\\test\\cpp\\fs"
Current Root Path: "Z:\\"
Has Current Root Path: 1
Scenario File String: "Scenarios/Boulder Breakers.SC5"
Scenario Root Path: ""
Has Scenario Root Path: 0

Which again would cause the if clause to trigger and add Scenario to Scenario/Boulder[...]

// 0x00442837
    bool load(const fs::path& path)
    {
        WindowManager::closeConstructionWindows();
        WindowManager::closeAllFloatingWindows();

        // Resolve filenames to base game scenario directory
        fs::path fullPath = path;
        if (!fullPath.has_root_path())
        {
            auto scenarioPath = Environment::getPath(Environment::PathId::scenarios);
            fullPath = scenarioPath / path;
        }

        Audio::pauseSound();

        auto result = S5::importSaveToGameState(fullPath, S5::LoadFlags::scenario);
        Audio::unpauseSound();
        return result;
    }

As stated above: I am aware that this seems like a too obvious bug so I am absolutely open to this being on me. If so I would like to know what I am doing wrong/what is different about my system.

@AaronVanGeffen
Copy link
Copy Markdown
Member

Appreciate the testing. I'm generally on macOS myself, so this could just be a platform issue, hence the question.

I'll do some digging on my Linux setup when I'm home.

@AaronVanGeffen
Copy link
Copy Markdown
Member

Tutorials are working on master (67a1ff0) on Arch Linux for me, similar to macOS.

Very odd that this change does make it work for you on Gentoo Linux. Not sure what's going wrong there, or indeed what to suggest.

@GoGoOtaku
Copy link
Copy Markdown
Contributor Author

I am very sorry for taking your time.
I have found the issue: When I first launched the game it told me that it couldn't find the installation path.
Since I already was in the installation path I just put in a single dot for current directory.
This works as long the working directory is always the game directory. The issue is that trying to get the base path does not work. C++ will not auto convert to absolute paths but instead keeping the path relative. This is why it decided that it needed the Scenario addition.

I have added a simple line that will now convert relative inputs to absolute inputs. Now I understand this was rather silly on my part but I didn't really want to input the long path. Now in my slight defense since I always launched through Lutris or the terminal I always had the working directory set to the installation directory so I didn't notice that inputing a dot was a bad idea.

@AaronVanGeffen
Copy link
Copy Markdown
Member

Right, that'll do it 😄 Thanks for the deep dive and writing up your findings. It'll make for a nice story in the dev log in the next release.

Ultimately, we should make the Linux/POSIX version of the game a little user friendlier than it is currently, what with it just prompting for a path using stdin. Perhaps we could leverage Zenity and/or KDialog, similar to what our sister project OpenRCT2 does.

Anyway, let me just add a changelog entry and merge this.

@AaronVanGeffen AaronVanGeffen added this to the v26.02+ milestone Mar 8, 2026
@AaronVanGeffen
Copy link
Copy Markdown
Member

Did some more testing and discovered that while fs::absolute does make the problem go away by making the path refer to an absolute location, it doesn't canonicalise it. For example, the input ../../../../../../Games/Locomotion leads to 'absolute' path /home/aaron/Projects/OpenLoco/OpenLoco/build/posix/Debug/../../../../../../Games/Locomotion on my end.

Switching to fs::canonical makes it so the input ../../../../../../Games/Locomotion is rewritten to the more expected absolute path /home/aaron/Games/Locomotion.

@AaronVanGeffen AaronVanGeffen enabled auto-merge (squash) March 8, 2026 18:08
@AaronVanGeffen AaronVanGeffen merged commit aad082c into OpenLoco:master Mar 8, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants