Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid mixing up truckfiles when filename isn't unique. #3171

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
3 changes: 1 addition & 2 deletions source/main/GameContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,7 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq)
rq.asr_cache_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, rq.asr_filename);
}

RigDef::DocumentPtr def = m_actor_manager.FetchActorDef(
rq.asr_filename, rq.asr_origin == ActorSpawnRequest::Origin::TERRN_DEF);
RigDef::DocumentPtr def = m_actor_manager.FetchActorDef(rq);
if (def == nullptr)
{
return nullptr; // Error already reported
Expand Down
12 changes: 10 additions & 2 deletions source/main/physics/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1950,7 +1950,15 @@ void Actor::sendStreamSetup()
reg.status = 0;
reg.type = 0;
reg.time = App::GetGameContext()->GetActorManager()->GetNetTime();
strncpy(reg.name, ar_filename.c_str(), 128);

// Send the filename in "Bundle-qualified" format, i.e. "mybundle.zip:myactor.truck"
std::string bname;
std::string bpath;
Ogre::StringUtil::splitFilename(m_used_actor_entry->resource_bundle_path, bname, bpath);
std::string bq_filename = fmt::format("{}:{}", bname, ar_filename);
strncpy(reg.name, bq_filename.c_str(), 128);

// Skin and sectionconfig
if (m_used_skin_entry != nullptr)
{
strncpy(reg.skin, m_used_skin_entry->dname.c_str(), 60);
Expand Down Expand Up @@ -4413,7 +4421,7 @@ Actor::Actor(
, ar_instance_id(actor_id)
, ar_vector_index(vector_index)
, m_avg_proped_wheel_radius(0.2f)
, ar_filename(rq.asr_filename)
, ar_filename(rq.asr_cache_entry->fname)
, m_section_config(rq.asr_config)
, m_used_actor_entry(rq.asr_cache_entry)
, m_used_skin_entry(rq.asr_skin_entry)
Expand Down
64 changes: 31 additions & 33 deletions source/main/physics/ActorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,11 @@ void ActorManager::HandleActorStreamData(std::vector<RoR::NetRecvPacket> packet_
if (reg->type == 0)
{
reg->name[127] = 0;
std::string filename = SanitizeUtf8CString(reg->name);
// NOTE: The filename is by default in "Bundle-qualified" format, i.e. "mybundle.zip:myactor.truck"
std::string filename_maybe_bundlequalified = SanitizeUtf8CString(reg->name);
std::string filename;
std::string bundlename;
SplitBundleQualifiedFilename(filename_maybe_bundlequalified, /*out:*/ bundlename, /*out:*/ filename);

RoRnet::UserInfo info;
if (!App::GetNetwork()->GetUserInfo(reg->origin_sourceid, info))
Expand All @@ -398,12 +402,14 @@ void ActorManager::HandleActorStreamData(std::vector<RoR::NetRecvPacket> packet_

LOG("[RoR] Creating remote actor for " + TOSTRING(reg->origin_sourceid) + ":" + TOSTRING(reg->origin_streamid));

if (!App::GetCacheSystem()->CheckResourceLoaded(filename))
CacheEntryPtr actor_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, filename_maybe_bundlequalified);

if (!actor_entry)
{
App::GetConsole()->putMessage(
Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING,
_L("Mod not installed: ") + filename);
RoR::LogFormat("[RoR] Cannot create remote actor (not installed), filename: '%s'", filename.c_str());
RoR::LogFormat("[RoR] Cannot create remote actor (not installed), filename: '%s'", filename_maybe_bundlequalified.c_str());
AddStreamMismatch(reg->origin_sourceid, reg->origin_streamid);
reg->status = -1;
}
Expand All @@ -416,12 +422,11 @@ void ActorManager::HandleActorStreamData(std::vector<RoR::NetRecvPacket> packet_
m_stream_time_offsets[reg->origin_sourceid] = offset - 100;
}
ActorSpawnRequest* rq = new ActorSpawnRequest;
rq->asr_origin = ActorSpawnRequest::Origin::NETWORK;
// TODO: Look up cache entry early (eliminate asr_filename) and fetch skin by name+guid! ~ 03/2019
rq->asr_filename = filename;
rq->asr_origin = ActorSpawnRequest::Origin::NETWORK;
rq->asr_cache_entry = actor_entry;
if (strnlen(actor_reg->skin, 60) < 60 && actor_reg->skin[0] != '\0')
{
rq->asr_skin_entry = App::GetCacheSystem()->FetchSkinByName(actor_reg->skin);
rq->asr_skin_entry = App::GetCacheSystem()->FetchSkinByName(actor_reg->skin); // FIXME: fetch skin by name+guid! ~ 03/2019
}
if (strnlen(actor_reg->sectionconfig, 60) < 60)
{
Expand Down Expand Up @@ -873,7 +878,7 @@ void ActorManager::CleanUpSimulation() // Called after simulation finishes
{
while (m_actors.size() > 0)
{
this->DeleteActorInternal(m_actors.back());
this->DeleteActorInternal(m_actors.back()); // OK to invoke here - CleanUpSimulation() - processing `MSG_SIM_UNLOAD_TERRAIN_REQUESTED`
}

m_total_sim_time = 0.f;
Expand Down Expand Up @@ -1254,44 +1259,37 @@ void HandleErrorLoadingTruckfile(std::string filename, std::string exception_msg
HandleErrorLoadingFile("actor", filename, exception_msg);
}

RigDef::DocumentPtr ActorManager::FetchActorDef(std::string filename, bool predefined_on_terrain)
RigDef::DocumentPtr ActorManager::FetchActorDef(RoR::ActorSpawnRequest& rq)
{
// Find the user content
CacheEntryPtr cache_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial=*/false, filename);
if (cache_entry == nullptr)
// Check the actor exists in mod cache
if (rq.asr_cache_entry == nullptr)
{
HandleErrorLoadingTruckfile(filename, "Truckfile not found in ModCache (probably not installed)");
HandleErrorLoadingTruckfile(rq.asr_filename, "Truckfile not found in ModCache (probably not installed)");
return nullptr;
}

// If already parsed, re-use
if (cache_entry->actor_def != nullptr)
if (rq.asr_cache_entry->actor_def != nullptr)
{
return cache_entry->actor_def;
return rq.asr_cache_entry->actor_def;
}

// Load the 'truckfile'
try
{
Ogre::String resource_filename = filename;
Ogre::String resource_groupname;
if (!App::GetCacheSystem()->CheckResourceLoaded(resource_filename, resource_groupname)) // Validates the filename and finds resource group
{
HandleErrorLoadingTruckfile(filename, "Truckfile not found");
return nullptr;
}
Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(resource_filename, resource_groupname);
App::GetCacheSystem()->LoadResource(rq.asr_cache_entry);
Ogre::DataStreamPtr stream = Ogre::ResourceGroupManager::getSingleton().openResource(rq.asr_cache_entry->fname, rq.asr_cache_entry->resource_group);

if (stream.isNull() || !stream->isReadable())
{
HandleErrorLoadingTruckfile(filename, "Unable to open/read truckfile");
HandleErrorLoadingTruckfile(rq.asr_cache_entry->fname, "Unable to open/read truckfile");
return nullptr;
}

RoR::LogFormat("[RoR] Parsing truckfile '%s'", resource_filename.c_str());
RoR::LogFormat("[RoR] Parsing truckfile '%s'", rq.asr_cache_entry->fname.c_str());
RigDef::Parser parser;
parser.Prepare();
parser.ProcessOgreStream(stream.getPointer(), resource_groupname);
parser.ProcessOgreStream(stream.getPointer(), rq.asr_cache_entry->resource_group);
parser.Finalize();

auto def = parser.GetFile();
Expand All @@ -1302,15 +1300,15 @@ RigDef::DocumentPtr ActorManager::FetchActorDef(std::string filename, bool prede
RigDef::Validator validator;
validator.Setup(def);

if (predefined_on_terrain)
if (rq.asr_origin == ActorSpawnRequest::Origin::TERRN_DEF)
{
// Workaround: Some terrains pre-load truckfiles with special purpose:
// "soundloads" = play sound effect at certain spot
// "fixes" = structures of N/B fixed to the ground
// These files can have no beams. Possible extensions: .load or .fixed
std::string file_extension = filename.substr(filename.find_last_of('.'));
std::string file_extension = rq.asr_cache_entry->fname.substr(rq.asr_cache_entry->fname.find_last_of('.'));
Ogre::StringUtil::toLowerCase(file_extension);
if ((file_extension == ".load") | (file_extension == ".fixed"))
if ((file_extension == ".load") || (file_extension == ".fixed"))
{
validator.SetCheckBeams(false);
}
Expand All @@ -1320,22 +1318,22 @@ RigDef::DocumentPtr ActorManager::FetchActorDef(std::string filename, bool prede

def->hash = Sha1Hash(stream->getAsString());

cache_entry->actor_def = def;
rq.asr_cache_entry->actor_def = def;
return def;
}
catch (Ogre::Exception& oex)
{
HandleErrorLoadingTruckfile(filename, oex.getFullDescription().c_str());
HandleErrorLoadingTruckfile(rq.asr_cache_entry->fname, oex.getDescription().c_str());
return nullptr;
}
catch (std::exception& stex)
{
HandleErrorLoadingTruckfile(filename, stex.what());
HandleErrorLoadingTruckfile(rq.asr_cache_entry->fname, stex.what());
return nullptr;
}
catch (...)
{
HandleErrorLoadingTruckfile(filename, "<Unknown exception occurred>");
HandleErrorLoadingTruckfile(rq.asr_cache_entry->fname, "<Unknown exception occurred>");
return nullptr;
}
}
Expand Down
2 changes: 1 addition & 1 deletion source/main/physics/ActorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class ActorManager


void UpdateInputEvents(float dt);
RigDef::DocumentPtr FetchActorDef(std::string filename, bool predefined_on_terrain = false);
RigDef::DocumentPtr FetchActorDef(RoR::ActorSpawnRequest& rq);

#ifdef USE_SOCKETW
void HandleActorStreamData(std::vector<RoR::NetRecvPacket> packet);
Expand Down
4 changes: 1 addition & 3 deletions source/main/physics/ActorSpawnerFlow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ void ActorSpawner::ProcessNewActor(ActorPtr actor, ActorSpawnRequest rq, RigDef:
{
m_actor = actor;
m_file = def;
m_custom_resource_group = rq.asr_cache_entry->resource_group; // For historical/backwards-compat reasons, the instances live in the same group as the bundle.

// Under OGRE, every scenenode must have globally unique name.
m_actor_grouping_scenenode = App::GetGfxScene()->GetSceneManager()->getRootSceneNode()->createChildSceneNode(this->ComposeName("Actor"));
Expand All @@ -85,9 +86,6 @@ void ActorSpawner::ProcessNewActor(ActorPtr actor, ActorSpawnRequest rq, RigDef:
m_generate_wing_position_lights = false; // Disable aerial pos. lights for land vehicles.
}

// Get resource group name
App::GetCacheSystem()->CheckResourceLoaded(m_actor->ar_filename, m_custom_resource_group);

// Create the built-in "renderdash" material for use in meshes.
// Must be done before 'props' are processed because those traditionally use it.
// Must be always created, there is no mechanism to declare the need for it. It can be acessed from any mesh, not only dashboard-prop.
Expand Down
18 changes: 10 additions & 8 deletions source/main/physics/Savegame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,9 @@ bool ActorManager::LoadScene(Ogre::String save_filename)
std::vector<ActorPtr> x_actors = GetLocalActors();
for (rapidjson::Value& j_entry: j_doc["actors"].GetArray())
{
// NOTE: The filename is by default in "Bundle-qualified" format, i.e. "mybundle.zip:myactor.truck"
String rigdef_filename = j_entry["filename"].GetString();
if (!App::GetCacheSystem()->CheckResourceLoaded(rigdef_filename))
{
Str<600> msg; msg << _L("Error while loading scene: Missing content (probably not installed)") << " '" << rigdef_filename << "'";
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_ERROR, msg.ToCStr());
actors.push_back(nullptr);
continue;
}
CacheEntryPtr actor_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, rigdef_filename);

CacheEntryPtr skin = nullptr;
if (j_entry.HasMember("skin"))
Expand Down Expand Up @@ -483,7 +478,14 @@ bool ActorManager::SaveScene(Ogre::String filename)
{
rapidjson::Value j_entry(rapidjson::kObjectType);

j_entry.AddMember("filename", rapidjson::StringRef(actor->ar_filename.c_str()), j_doc.GetAllocator());
// Save the filename in "Bundle-qualified" format, i.e. "mybundle.zip:myactor.truck"
std::string bname;
std::string bpath;
Ogre::StringUtil::splitFilename(actor->getUsedActorEntry()->resource_bundle_path, bname, bpath);
std::string bq_filename = fmt::format("{}:{}", bname, actor->ar_filename);
rapidjson::Value j_bq_filename(bq_filename.c_str(), j_doc.GetAllocator());
j_entry.AddMember("filename", j_bq_filename, j_doc.GetAllocator());

rapidjson::Value j_actor_position(rapidjson::kArrayType);
j_actor_position.PushBack(actor->ar_nodes[0].AbsPosition.x, j_doc.GetAllocator());
j_actor_position.PushBack(actor->ar_nodes[0].AbsPosition.y, j_doc.GetAllocator());
Expand Down
2 changes: 1 addition & 1 deletion source/main/physics/SimData.h
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,7 @@ struct ActorSpawnRequest
};

CacheEntryPtr asr_cache_entry; //!< Optional, overrides 'asr_filename' and 'asr_cache_entry_num'
std::string asr_filename;
std::string asr_filename; //!< Can be in "Bundle-qualified" format, i.e. "mybundle.zip:myactor.truck"
Ogre::String asr_config;
Ogre::Vector3 asr_position = Ogre::Vector3::ZERO;
Ogre::Quaternion asr_rotation = Ogre::Quaternion::ZERO;
Expand Down
95 changes: 48 additions & 47 deletions source/main/resources/CacheSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,20 @@ void CacheSystem::LoadModCache(CacheValidity validity)
m_loaded = true;
}

CacheEntryPtr CacheSystem::FindEntryByFilename(LoaderType type, bool partial, const std::string& _filename)
CacheEntryPtr CacheSystem::FindEntryByFilename(LoaderType type, bool partial, const std::string& _filename_maybe_bundlequalified)
{
std::string filename = _filename;
// "Bundle-qualified" format also specifies the ZIP/directory in modcache, i.e. "mybundle.zip:myactor.truck"
// Like the filename, the bundle name lookup is case-insensitive.
// -------------------------------------------------------------------------------------------------

std::string filename;
std::string bundlename;
SplitBundleQualifiedFilename(_filename_maybe_bundlequalified, bundlename, filename);
StringUtil::toLowerCase(filename);
StringUtil::toLowerCase(bundlename);
size_t partial_match_length = std::numeric_limits<size_t>::max();
CacheEntryPtr partial_match = nullptr;
std::vector<CacheEntryPtr> log_candidates;
for (CacheEntryPtr& entry : m_entries)
{
if ((type == LT_Terrain) != (entry->fext == "terrn2") ||
Expand All @@ -195,20 +203,53 @@ CacheEntryPtr CacheSystem::FindEntryByFilename(LoaderType type, bool partial, co

String fname = entry->fname;
String fname_without_uid = entry->fname_without_uid;
String bname;
String _path_placeholder;
StringUtil::splitFilename(entry->resource_bundle_path, bname, _path_placeholder);
StringUtil::toLowerCase(fname);
StringUtil::toLowerCase(fname_without_uid);
StringUtil::toLowerCase(bname);
if (fname == filename || fname_without_uid == filename)
return entry;

if (partial &&
{
if (bundlename == "" || bname == bundlename)
{
return entry;
}
else
{
log_candidates.push_back(entry);
}
}
else if (partial &&
fname.length() < partial_match_length &&
fname.find(filename) != std::string::npos)
{
partial_match = entry;
partial_match_length = fname.length();
if (bundlename == "" || bname == bundlename)
{
partial_match = entry;
partial_match_length = fname.length();
}
else
{
log_candidates.push_back(entry);
}
}
}

if (log_candidates.size() > 0)
{
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
fmt::format(_LC("CacheSystem", "Mod '{}' was not found in cache; candidates ({}) are:"), _filename_maybe_bundlequalified, log_candidates.size()));
for (CacheEntryPtr& entry: log_candidates)
{
std::string bundle_name, bundle_path;
StringUtil::toLowerCase(bundle_name);
Ogre::StringUtil::splitFilename(entry->resource_bundle_path, bundle_name, bundle_path);
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_NOTICE,
fmt::format(_LC("CacheSystem", "* {}:{}"), bundle_name, entry->fname));
}
}

return (partial) ? partial_match : nullptr;
}

Expand Down Expand Up @@ -1299,46 +1340,6 @@ void CacheSystem::LoadAssetPack(CacheEntryPtr& target_entry, Ogre::String const
}
}

bool CacheSystem::CheckResourceLoaded(Ogre::String & filename)
{
Ogre::String group = "";
return CheckResourceLoaded(filename, group);
}

bool CacheSystem::CheckResourceLoaded(Ogre::String & filename, Ogre::String& group)
{
try
{
// check if we already loaded it via ogre ...
if (ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(filename))
{
group = ResourceGroupManager::getSingleton().findGroupContainingResource(filename);
return true;
}

for (auto& entry : m_entries)
{
// case insensitive comparison
String fname = entry->fname;
String fname_without_uid = entry->fname_without_uid;
StringUtil::toLowerCase(fname);
StringUtil::toLowerCase(filename);
StringUtil::toLowerCase(fname_without_uid);
if (fname == filename || fname_without_uid == filename)
{
// we found the file, load it
LoadResource(entry);
filename = entry->fname;
group = entry->resource_group;
return !group.empty() && ResourceGroupManager::getSingleton().resourceExists(group, filename);
}
}
}
catch (Ogre::Exception) {} // Already logged by OGRE

return false;
}

static bool CheckAndReplacePathIgnoreCase(const CacheEntryPtr& entry, CVar* dir, const std::string& dir_label, std::string& out_rgname)
{
// Helper for `ComposeResourceGroupName()`
Expand Down
Loading