Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/async_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ FileRequestAsync* AsyncHandler::RequestFile(std::string_view file_name) {
return RequestFile(".", file_name);
}

FileRequestAsync* AsyncHandler::RequestFile(std::string_view preferred_dir, std::string_view fallback_dir, std::string_view file_name) {
auto* request = RequestFile(preferred_dir, file_name);
request->SetFallbackDirectory(fallback_dir);
return request;
}

bool AsyncHandler::IsFilePending(bool important, bool graphic) {
for (auto& ap: async_requests) {
FileRequestAsync& request = ap.second;
Expand Down Expand Up @@ -439,6 +445,20 @@ void FileRequestAsync::DownloadDone(bool success) {
success = state == State_DoneSuccess;
}

if (!success && !fallback_directory.empty()) {
// Not found in the current directory. On platforms without
// synchronous file access (Emscripten) this is the only way to
// learn that, since the file isn't present locally until it has
// actually been downloaded. Retry once in the fallback directory
// before giving up.
directory = std::move(fallback_directory);
fallback_directory.clear();
path = FileFinder::MakePath(directory, file);
state = State_WaitForStart;
Start();
return;
}

if (success) {
#ifdef __EMSCRIPTEN__
if (state == State_Pending) {
Expand Down
35 changes: 35 additions & 0 deletions src/async_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,25 @@ namespace AsyncHandler {
*/
FileRequestAsync* RequestFile(std::string_view file_name);

/**
* Creates a request to a file that may reside in one of two possible
* directories, e.g. because a project can store it in a preferred
* subfolder or (for compatibility) in the project root.
* The file is requested from preferred_dir first. On platforms with
* synchronous file access (i.e. everywhere except Emscripten) whether
* the file actually exists there is already known at this point.
* On platforms without synchronous access, existence can only be
* determined by attempting the download: if that attempt fails, a
* fallback request for fallback_dir is issued automatically, and the
* request is only considered finished once that also completes.
*
* @param preferred_dir directory to try first
* @param fallback_dir directory to fall back to when the file isn't in preferred_dir
* @param file_name Name of the requested file requested.
* @return The async request.
*/
FileRequestAsync* RequestFile(std::string_view preferred_dir, std::string_view fallback_dir, std::string_view file_name);

/**
* Checks if any file with important-flag hasn't finished downloading yet.
*
Expand Down Expand Up @@ -159,6 +178,17 @@ class FileRequestAsync {
*/
void SetGraphicFile(bool graphic);

/**
* Sets a fallback directory to retry the request in when it fails in
* its current directory (see AsyncHandler::RequestFile with a
* preferred/fallback directory pair). Only one fallback attempt is
* made.
* This must be set before Start() is invoked.
*
* @param dir directory to retry the request in on failure.
*/
void SetFallbackDirectory(std::string_view dir);

/**
* Starts the async requests.
* When the request was already started earlier and is pending this call
Expand Down Expand Up @@ -218,6 +248,7 @@ class FileRequestAsync {
std::string directory;
std::string file;
std::string path;
std::string fallback_directory;
int state = State_DoneFailure;
bool important = false;
bool graphic = false;
Expand Down Expand Up @@ -255,6 +286,10 @@ inline void FileRequestAsync::SetImportantFile(bool important) {
this->important = important;
}

inline void FileRequestAsync::SetFallbackDirectory(std::string_view dir) {
fallback_directory = ToString(dir);
}

inline bool FileRequestAsync::IsGraphicFile() const {
return graphic;
}
Expand Down
41 changes: 29 additions & 12 deletions src/fileext_guesser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,17 +67,10 @@ FileExtGuesser::RPG2KNonStandardFilenameGuesser FileExtGuesser::GetRPG2kProjectW
return RPG2KNonStandardFilenameGuesser();
}

void FileExtGuesser::GuessAndAddLmuExtension(const FilesystemView& fs, Meta const& meta, RPG2KFileExtRemap& mapping)
{
// If metadata is provided, rely on that.
std::string metaLmu = meta.GetLmuAlias();
if (!metaLmu.empty()) {
mapping.extMap[SUFFIX_LMU] = metaLmu;
Output::Debug("Metadata-provided non-standard extension for LMU({})", metaLmu);
} else {
// Try to rescue and determine file extensions.
// Without metadata, scan for matching files. Stop after you find a few;
// we can't just pick the first since there may be some backup files on disk.
namespace {
// Scans a single directory for "mapXXXX.???" files and guesses the LMU
// extension from whichever non-standard extension occurs most often.
void GuessLmuExtensionIn(const FilesystemView& fs, FileExtGuesser::RPG2KFileExtRemap& mapping) {
std::unordered_map<std::string, int> extCounts; // ext => count

const auto* entries = fs.ListDirectory();
Expand All @@ -94,14 +87,38 @@ void FileExtGuesser::GuessAndAddLmuExtension(const FilesystemView& fs, Meta cons
if (extCounts[ext] >= 5) {
mapping.extMap[SUFFIX_LMU] = ext;
Output::Debug("Guessing non-standard extension for LMU({})", ext);
break;
return;
}
}
}
}
}
}

void FileExtGuesser::GuessAndAddLmuExtension(const FilesystemView& fs, Meta const& meta, RPG2KFileExtRemap& mapping)
{
// If metadata is provided, rely on that.
std::string metaLmu = meta.GetLmuAlias();
if (!metaLmu.empty()) {
mapping.extMap[SUFFIX_LMU] = metaLmu;
Output::Debug("Metadata-provided non-standard extension for LMU({})", metaLmu);
return;
}

// Try to rescue and determine file extensions.
// Without metadata, scan for matching files. Stop after you find a few;
// we can't just pick the first since there may be some backup files on disk.
// Maps are preferably stored in the "Map" subfolder, but fall back to
// the project root for games that keep their maps there.
FilesystemView maps_fs = fs.Subtree(MAP_DIR_NAME);
if (maps_fs) {
GuessLmuExtensionIn(maps_fs, mapping);
}
if (mapping.extMap.find(SUFFIX_LMU) == mapping.extMap.end()) {
GuessLmuExtensionIn(fs, mapping);
}
}

FileExtGuesser::RPG2KFileExtRemap FileExtGuesser::RPG2KNonStandardFilenameGuesser::guessExtensions(Meta& meta)
{
RPG2KFileExtRemap res;
Expand Down
25 changes: 22 additions & 3 deletions src/game_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,11 +330,11 @@ std::unique_ptr<lcf::rpg::Map> Game_Map::LoadMapFile(int map_id) {
// FIXME: Assert map was cached for async platforms
bool map_is_easyrpg_file = Player::player_config.prefer_easyrpg_map_files.Get();
std::string map_name = Game_Map::ConstructMapName(map_id, map_is_easyrpg_file);
std::string map_file = FileFinder::Game().FindFile(map_name);
std::string map_file = Game_Map::FindMapFile(map_name);
if (map_file.empty()) {
map_is_easyrpg_file = !map_is_easyrpg_file;
map_name = Game_Map::ConstructMapName(map_id, map_is_easyrpg_file);
map_file = FileFinder::Game().FindFile(map_name);
map_file = Game_Map::FindMapFile(map_name);

if (map_file.empty()) {
Output::Error("Loading of Map {} failed.\nThe map was not found.", map_name);
Expand Down Expand Up @@ -2017,12 +2017,31 @@ std::string Game_Map::ConstructMapName(int map_id, bool is_easyrpg) {
}
}

std::string Game_Map::FindMapFile(std::string_view map_name) {
// Maps are preferably stored in the "Map" subfolder, but fall back to
// the game's root directory for compatibility with games that keep
// their maps there.
std::string map_file = FileFinder::Game().FindFile(MAP_DIR_NAME, map_name);
if (map_file.empty()) {
map_file = FileFinder::Game().FindFile(map_name);
}
return map_file;
}

FileRequestAsync* Game_Map::RequestMap(int map_id) {
#ifdef __EMSCRIPTEN__
Player::translation.RequestAndAddMap(map_id);
#endif

auto* request = AsyncHandler::RequestFile(Game_Map::ConstructMapName(map_id, false));
std::string map_name = Game_Map::ConstructMapName(map_id, false);

// On platforms with synchronous file access, FindMapFile() above could
// tell us upfront which directory the map is in. But on platforms like
// Emscripten a file isn't available locally until it has actually been
// downloaded, so existence can't be probed in advance: request the
// preferred directory and let AsyncHandler fall back to the root
// directory automatically if that attempt fails.
auto* request = AsyncHandler::RequestFile(MAP_DIR_NAME, ".", map_name);
request->SetImportantFile(true);
return request;
}
Expand Down
10 changes: 10 additions & 0 deletions src/game_map.h
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,16 @@ namespace Game_Map {
*/
std::string ConstructMapName(int map_id, bool is_easyrpg);

/**
* Searches for a map file on disk. Maps are preferably stored in the
* "Map" subfolder, but the game's root directory is also searched for
* compatibility with existing games that were never reorganized.
*
* @param map_name The filename to search for, as returned by ConstructMapName
* @return Path to the found file, or an empty string when not found
*/
std::string FindMapFile(std::string_view map_name);

FileRequestAsync* RequestMap(int map_id);

namespace Caching {
Expand Down
4 changes: 4 additions & 0 deletions src/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@
#define TREEMAP_NAME RPG_RT_PREFIX "." SUFFIX_LMT
#define TREEMAP_NAME_EASYRPG EASY_RT_PREFIX "." SUFFIX_EMT

/** Subfolder that map files (.lmu/.emu) are preferably stored in.
* Games that still keep them in the project root are also supported. */
#define MAP_DIR_NAME "Map"

/** File name for additional metadata, such as multi-game save imports. */
#define META_NAME "Meta.ini"

Expand Down
4 changes: 2 additions & 2 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -995,8 +995,8 @@ static bool DefaultLmuStartFileExists(const FilesystemView& fs) {
int map_id = Player::start_map_id == -1 ? lcf::Data::treemap.start.party_map_id : Player::start_map_id;
std::string mapName = Game_Map::ConstructMapName(map_id, false);

// Now see if the file exists.
return !fs.FindFile(mapName).empty();
// Now see if the file exists, preferring the "Map" subfolder.
return !fs.FindFile(MAP_DIR_NAME, mapName).empty() || !fs.FindFile(mapName).empty();
}

void Player::GuessNonStandardExtensions() {
Expand Down
Loading