/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include "common/memstream.h"
#include "ags/lib/std/algorithm.h"
#include "ags/lib/std/utility.h"
#include "ags/shared/core/platform.h"
#include "ags/shared/core/asset_manager.h"
#include "ags/shared/util/directory.h"
#include "ags/shared/util/multi_file_lib.h"
#include "ags/shared/util/path.h"
#include "ags/shared/util/string_utils.h" // cbuf_to_string_and_free
namespace AGS3 {
namespace AGS {
namespace Shared {
inline static bool IsAssetLibDir(const AssetLibInfo *lib) {
return lib->BaseFileName.IsEmpty();
}
bool AssetManager::AssetLibEx::TestFilter(const String &filter) const {
return filter == "*" ||
(std::find(Filters.begin(), Filters.end(), filter) != Filters.end());
}
bool AssetManager::LibsByPriority::operator()(const AssetLibInfo *lib1, const AssetLibInfo *lib2) const {
const bool lib1dir = IsAssetLibDir(lib1);
const bool lib2dir = IsAssetLibDir(lib2);
if (lib1dir == lib2dir)
return false; // both are equal, none is less
if (Priority == kAssetPriorityLib)
return !lib1dir; // first element is less if it's library file
if (Priority == kAssetPriorityDir)
return lib1dir; // first element is less if it's directory
return false; // unknown priority, just fail
}
/* static */ bool AssetManager::IsDataFile(const String &data_file) {
Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (in) {
MFLUtil::MFLError err = MFLUtil::TestIsMFL(in, true);
delete in;
return err == MFLUtil::kMFLNoError;
}
return false;
}
/* static */ AssetError AssetManager::ReadDataFileTOC(const String &data_file, AssetLibInfo &lib) {
Stream *in = File::OpenFileCI(data_file.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (in) {
MFLUtil::MFLError err = MFLUtil::ReadHeader(lib, in);
delete in;
return (err != MFLUtil::kMFLNoError) ? kAssetErrLibParse : kAssetNoError;
}
return kAssetErrNoLibFile;
}
AssetManager::AssetManager() {
_libsByPriority.Priority = kAssetPriorityDir;
}
void AssetManager::SetSearchPriority(AssetSearchPriority priority) {
_libsByPriority.Priority = priority;
std::sort(_activeLibs.begin(), _activeLibs.end(), _libsByPriority);
}
AssetSearchPriority AssetManager::GetSearchPriority() const {
return _libsByPriority.Priority;
}
AssetError AssetManager::AddLibrary(const String &path, const AssetLibInfo **out_lib) {
return AddLibrary(path, "", out_lib);
}
AssetError AssetManager::AddLibrary(const String &path, const String &filters, const AssetLibInfo **out_lib) {
if (path.IsEmpty())
return kAssetErrNoLibFile;
for (const auto &lib : _libs) {
if (Path::ComparePaths(lib->BasePath, path) == 0) {
// already present, only assign new filters
lib->Filters = filters.Split(',');
if (out_lib)
*out_lib = lib;
return kAssetNoError;
}
}
AssetLibEx *lib;
AssetError err = RegisterAssetLib(path, lib);
if (err != kAssetNoError)
return err;
lib->Filters = filters.Split(',');
auto place = std::upper_bound(_activeLibs.begin(), _activeLibs.end(), lib, _libsByPriority);
_activeLibs.insert(place, lib);
if (out_lib)
*out_lib = lib;
return kAssetNoError;
}
void AssetManager::RemoveLibrary(const String &path) {
int idx = 0;
for (auto it = _libs.begin(); it != _libs.end(); ++it, ++idx) {
if (Path::ComparePaths((*it)->BasePath, path) == 0) {
_libs.remove_at(idx);
_activeLibs.remove(*it);
return;
}
}
}
void AssetManager::RemoveAllLibraries() {
for (uint i = 0; i < _libs.size(); ++i)
delete _libs[i];
_libs.clear();
_activeLibs.clear();
}
size_t AssetManager::GetLibraryCount() const {
return _libs.size();
}
const AssetLibInfo *AssetManager::GetLibraryInfo(size_t index) const {
return index < _libs.size() ? _libs[index] : nullptr;
}
bool AssetManager::DoesAssetExist(const String &asset_name, const String &filter) const {
for (const auto &lib : _activeLibs) {
if (!lib->TestFilter(filter))
continue; // filter does not match
if (IsAssetLibDir(lib)) {
String filename = File::FindFileCI(lib->BaseDir, asset_name);
if (!filename.IsEmpty() && File::IsFile(filename)) return true;
} else {
for (const auto &a : lib->AssetInfos) {
if (a.FileName.CompareNoCase(asset_name) == 0) return true;
}
}
}
return false;
}
void AssetManager::FindAssets(std::vector &assets, const String &wildcard,
const String &filter) const {
String pattern = StrUtil::WildcardToRegex(wildcard);
for (const auto *lib : _activeLibs) {
auto match = std::find(lib->Filters.begin(), lib->Filters.end(), filter);
if (match == lib->Filters.end())
continue; // filter does not match
if (IsAssetLibDir(lib)) {
for (FindFile ff = FindFile::OpenFiles(lib->BaseDir, wildcard);
!ff.AtEnd(); ff.Next())
assets.push_back(ff.Current());
} else {
for (const auto &a : lib->AssetInfos) {
if (pattern == "*" || (*pattern.GetCStr() &&
Common::String(a.FileName.GetCStr()).hasSuffixIgnoreCase(pattern.GetCStr() + 1)))
assets.push_back(a.FileName);
}
}
}
// Sort and remove duplicates
std::sort(assets.begin(), assets.end());
assets.erase(std::unique(assets.begin(), assets.end()), assets.end());
}
AssetError AssetManager::RegisterAssetLib(const String &path, AssetLibEx *&out_lib) {
// Test for a directory
std::unique_ptr lib;
if (File::IsDirectory(path)) {
lib.reset(new AssetLibEx());
lib->BasePath = Path::MakeAbsolutePath(path);
lib->BaseDir = Path::GetDirectoryPath(lib->BasePath);
// TODO: maybe parse directory for the file reference? idk if needed
}
// ...else try open a data library
else {
Stream *in = File::OpenFileCI(path.GetCStr(), Shared::kFile_Open, Shared::kFile_Read);
if (!in)
return kAssetErrNoLibFile; // can't be opened, return error code
lib.reset(new AssetLibEx());
MFLUtil::MFLError mfl_err = MFLUtil::ReadHeader(*lib, in);
delete in;
if (mfl_err != MFLUtil::kMFLNoError)
return kAssetErrLibParse;
lib->BasePath = Path::MakeAbsolutePath(path);
lib->BaseDir = Path::GetDirectoryPath(lib->BasePath);
lib->BaseFileName = Path::GetFilename(lib->BasePath);
lib->LibFileNames[0] = lib->BaseFileName;
// Find out real library files in the current filesystem and save them
for (size_t i = 0; i < lib->LibFileNames.size(); ++i) {
lib->RealLibFiles.push_back(File::FindFileCI(lib->BaseDir, lib->LibFileNames[i]));
}
}
out_lib = lib.release();
_libs.push_back(out_lib);
return kAssetNoError;
}
Stream *AssetManager::OpenAsset(const String &asset_name, const String &filter) const {
for (const auto *lib : _activeLibs) {
if (!lib->TestFilter(filter)) continue; // filter does not match
Stream *s = nullptr;
if (IsAssetLibDir(lib))
s = OpenAssetFromDir(lib, asset_name);
else
s = OpenAssetFromLib(lib, asset_name);
if (s)
return s;
}
return nullptr;
}
Stream *AssetManager::OpenAssetFromLib(const AssetLibEx *lib, const String &asset_name) const {
for (const auto &a : lib->AssetInfos) {
if (a.FileName.CompareNoCase(asset_name) == 0) {
String libfile = lib->RealLibFiles[a.LibUid];
if (libfile.IsEmpty())
return nullptr;
return File::OpenFile(libfile, a.Offset, a.Offset + a.Size);
}
}
return nullptr;
}
Stream *AssetManager::OpenAssetFromDir(const AssetLibEx *lib, const String &file_name) const {
String found_file = File::FindFileCI(lib->BaseDir, file_name);
if (found_file.IsEmpty())
return nullptr;
return File::OpenFileRead(found_file);
}
Stream *AssetManager::OpenAsset(const String &asset_name) const {
return OpenAsset(asset_name, "");
}
Common::SeekableReadStream *AssetManager::OpenAssetStream(const String &asset_name) const {
return OpenAssetStream(asset_name, "");
}
Common::SeekableReadStream *AssetManager::OpenAssetStream(const String &asset_name, const String &filter) const {
Stream *stream = OpenAsset(asset_name, filter);
if (!stream)
return nullptr;
// Get the contents of the asset
size_t assetSize = stream->GetLength();
byte *data = (byte *)malloc(assetSize);
stream->Read(data, assetSize);
delete stream;
return new Common::MemoryReadStream(data, assetSize, DisposeAfterUse::YES);
}
String GetAssetErrorText(AssetError err) {
switch (err) {
case kAssetNoError:
return "No error.";
case kAssetErrNoLibFile:
return "Asset library file not found or could not be opened.";
case kAssetErrLibParse:
return "Not an asset library or unsupported format.";
case kAssetErrNoManager:
return "Asset manager is not initialized.";
}
return "Unknown error.";
}
} // namespace Shared
} // namespace AGS
} // namespace AGS3