Rigs of Rods 2023.09
Soft-body Physics Simulation
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Loading...
Searching...
No Matches
CacheSystem.cpp
Go to the documentation of this file.
1/*
2 This source file is part of Rigs of Rods
3 Copyright 2005-2012 Pierre-Michel Ricordel
4 Copyright 2007-2012 Thomas Fischer
5 Copyright 2013-2023 Petr Ohlidal
6
7 For more information, see http://www.rigsofrods.org/
8
9 Rigs of Rods is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License version 3, as
11 published by the Free Software Foundation.
12
13 Rigs of Rods is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with Rigs of Rods. If not, see <http://www.gnu.org/licenses/>.
20*/
21
25
26#include "CacheSystem.h"
27
28#include "Actor.h"
29#include "AddonPartFileFormat.h"
30#include "Application.h"
31#include "SimData.h"
32#include "ContentManager.h"
33#include "ErrorUtils.h"
34#include "GUI_LoadingWindow.h"
35#include "GUI_GameMainMenu.h"
36#include "GUIManager.h"
37#include "GenericFileFormat.h"
38#include "GfxActor.h"
39#include "GfxScene.h"
40#include "Language.h"
41#include "PlatformUtils.h"
42#include "RigDef_Parser.h"
43#include "ScriptEngine.h"
44#include "SkinFileFormat.h"
45#include "Terrain.h"
46#include "Terrn2FileFormat.h"
47#include "TuneupFileFormat.h"
48#include "Utils.h"
49
50#include <OgreException.h>
51#include <OgreFileSystem.h>
52#include <OgreFileSystemLayer.h>
53#include <rapidjson/document.h>
54#include <rapidjson/istreamwrapper.h>
55#include <rapidjson/ostreamwrapper.h>
56#include <rapidjson/writer.h>
57#include <fstream>
58
59using namespace Ogre;
60using namespace RoR;
61
63 addtimestamp(0),
64 beamcount(0),
65 categoryid(CID_None),
66 commandscount(0),
67 custom_particles(false),
68 customtach(false),
69 deleted(false),
70 driveable(NOT_DRIVEABLE),
71 enginetype('t'), // enginetype = t = truck is default
72 exhaustscount(0),
73 fileformatversion(0),
74 filetime(0),
75 fixescount(0),
76 flarescount(0),
77 flexbodiescount(0),
78 forwardcommands(false),
79 hasSubmeshs(false),
80 hydroscount(0),
81 importcommands(false),
82 loadmass(0),
83 maxrpm(0),
84 minrpm(0),
85 nodecount(0),
86 number(0),
87 numgears(0),
88 propscount(0),
89 propwheelcount(0),
90 rescuer(false),
91 rotatorscount(0),
92 shockcount(0),
93 soundsourcescount(0),
94 torque(0),
95 truckmass(0),
96 turbojetcount(0),
97 turbopropscount(0),
98 usagecounter(0),
99 version(0),
100 wheelcount(0),
101 wingscount(0)
102{
103}
104
106{
107 // Destructs `TuneupDefPtr` which is a `RefCountingObjectPtr<>` so it doesn't compile without `#include "TuneupFileFormat.h"` and thus should not be in header.
108}
109
111{
112 // Constructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
113}
114
116{
117 // Destructs `ActorPtr` - doesn't compile without `#include Actor.h` - not pretty if in header (even if auto-generated by C++).
118}
119
121 cqr_entry(entry),
122 cqr_score(score)
123{}
124
126{
127 // register the extensions
128 m_known_extensions.push_back("machine");
129 m_known_extensions.push_back("fixed");
130 m_known_extensions.push_back("terrn2");
131 m_known_extensions.push_back("truck");
132 m_known_extensions.push_back("car");
133 m_known_extensions.push_back("boat");
134 m_known_extensions.push_back("airplane");
135 m_known_extensions.push_back("trailer");
136 m_known_extensions.push_back("load");
137 m_known_extensions.push_back("train");
138 m_known_extensions.push_back("skin");
139 m_known_extensions.push_back("addonpart");
140 m_known_extensions.push_back("tuneup");
141 m_known_extensions.push_back("assetpack");
142 m_known_extensions.push_back("dashboard");
143 m_known_extensions.push_back("gadget");
144
145 // register the dirs
146 m_content_dirs.push_back("mods");
147 m_content_dirs.push_back("packs");
148 m_content_dirs.push_back("terrains");
149 m_content_dirs.push_back("vehicles");
150 m_content_dirs.push_back("projects");
151}
152
154{
155 m_resource_paths.clear();
157
158 if (validity != CacheValidity::VALID)
159 {
160 if (validity == CacheValidity::NEEDS_REBUILD)
161 {
162 RoR::Log("[RoR|ModCache] Performing rebuild ...");
163 this->ClearCache();
164 }
165 else
166 {
167 RoR::Log("[RoR|ModCache] Performing update ...");
168 this->ClearResourceGroups();
169 this->PruneCache();
170 }
171 const bool orig_echo = App::diag_log_console_echo->getBool();
176 this->DetectDuplicates();
177 this->WriteCacheFileJson();
178
179 this->LoadCacheFileJson();
180 }
181
182 RoR::Log("[RoR|ModCache] Cache loaded");
183 m_loaded = true;
184}
185
186CacheEntryPtr CacheSystem::FindEntryByFilename(LoaderType type, bool partial, const std::string& _filename_maybe_bundlequalified)
187{
188 // "Bundle-qualified" format also specifies the ZIP/directory in modcache, i.e. "mybundle.zip:myactor.truck"
189 // Like the filename, the bundle name lookup is case-insensitive.
190 // -------------------------------------------------------------------------------------------------
191
192 std::string filename;
193 std::string bundlename;
194 SplitBundleQualifiedFilename(_filename_maybe_bundlequalified, bundlename, filename);
195 StringUtil::toLowerCase(filename);
196 StringUtil::toLowerCase(bundlename);
197 size_t partial_match_length = std::numeric_limits<size_t>::max();
198 CacheEntryPtr partial_match = nullptr;
199 std::vector<CacheEntryPtr> log_candidates;
200 for (CacheEntryPtr& entry : m_entries)
201 {
202 if ((type == LT_Terrain) != (entry->fext == "terrn2") ||
203 (type == LT_DashBoard) != (entry->fext == "dashboard") ||
204 (type == LT_AllBeam && entry->fext == "skin"))
205 continue;
206
207 String fname = entry->fname;
208 String fname_without_uid = entry->fname_without_uid;
209 String bname;
210 String _path_placeholder;
211 StringUtil::splitFilename(entry->resource_bundle_path, bname, _path_placeholder);
212 StringUtil::toLowerCase(fname);
213 StringUtil::toLowerCase(fname_without_uid);
214 StringUtil::toLowerCase(bname);
215 if (fname == filename || fname_without_uid == filename)
216 {
217 if (bundlename == "" || bname == bundlename)
218 {
219 return entry;
220 }
221 else
222 {
223 log_candidates.push_back(entry);
224 }
225 }
226 else if (partial &&
227 fname.length() < partial_match_length &&
228 fname.find(filename) != std::string::npos)
229 {
230 if (bundlename == "" || bname == bundlename)
231 {
232 partial_match = entry;
233 partial_match_length = fname.length();
234 }
235 else
236 {
237 log_candidates.push_back(entry);
238 }
239 }
240 }
241
242 if (log_candidates.size() > 0)
243 {
245 fmt::format(_LC("CacheSystem", "Mod '{}' was not found in cache; candidates ({}) are:"), _filename_maybe_bundlequalified, log_candidates.size()));
246 for (CacheEntryPtr& entry: log_candidates)
247 {
248 std::string bundle_name, bundle_path;
249 StringUtil::toLowerCase(bundle_name);
250 Ogre::StringUtil::splitFilename(entry->resource_bundle_path, bundle_name, bundle_path);
252 fmt::format(_LC("CacheSystem", "* {}:{}"), bundle_name, entry->fname));
253 }
254 }
255
256 return (partial) ? partial_match : nullptr;
257}
258
260{
262
263 // Load cache file
264 CacheValidity validity = this->LoadCacheFileJson();
265
266 if (validity != CacheValidity::VALID)
267 {
268 RoR::Log("[RoR|ModCache] Cannot load cache file: wrong version, corrupted or missing.");
269 return validity;
270 }
271
272 // Compare stored hash with generated hash
274 {
275 RoR::Log("[RoR|ModCache] Cache file out of date");
277 }
278
279 for (auto& entry : m_entries)
280 {
281 std::string fn = entry->resource_bundle_path;
282 if (entry->resource_bundle_type == "FileSystem")
283 {
284 fn = PathCombine(fn, entry->fname);
285 }
286
287 if ((entry->filetime != RoR::GetFileLastModifiedTime(fn)))
288 {
290 }
291 }
292
293 RoR::Log("[RoR|ModCache] Cache valid");
295}
296
297void CacheSystem::ImportEntryFromJson(rapidjson::Value& j_entry, CacheEntryPtr & out_entry)
298{
299 // Common details
300 out_entry->usagecounter = j_entry["usagecounter"].GetInt();
301 out_entry->addtimestamp = j_entry["addtimestamp"].GetInt();
302 out_entry->resource_bundle_type = j_entry["resource_bundle_type"].GetString();
303 out_entry->resource_bundle_path = j_entry["resource_bundle_path"].GetString();
304 out_entry->fpath = j_entry["fpath"].GetString();
305 out_entry->fname = j_entry["fname"].GetString();
306 out_entry->fname_without_uid = j_entry["fname_without_uid"].GetString();
307 out_entry->fext = j_entry["fext"].GetString();
308 out_entry->filetime = j_entry["filetime"].GetInt();
309 out_entry->dname = j_entry["dname"].GetString();
310 out_entry->uniqueid = j_entry["uniqueid"].GetString();
311 out_entry->version = j_entry["version"].GetInt();
312 out_entry->filecachename = j_entry["filecachename"].GetString();
313
314 out_entry->guid = j_entry["guid"].GetString();
315 Ogre::StringUtil::trim(out_entry->guid);
316
317 // Category
318 int category_id = j_entry["categoryid"].GetInt();
319 auto category_itor = m_categories.find(category_id);
320 if (category_itor == m_categories.end() || category_id >= CID_Max)
321 {
322 category_itor = m_categories.find(CID_Unsorted);
323 }
324 out_entry->categoryname = category_itor->second;
325 out_entry->categoryid = category_itor->first;
326
327 // Common - Authors
328 for (rapidjson::Value& j_author: j_entry["authors"].GetArray())
329 {
330 AuthorInfo author;
331
332 author.type = j_author["type"].GetString();
333 author.name = j_author["name"].GetString();
334 author.email = j_author["email"].GetString();
335 author.id = j_author["id"].GetInt();
336
337 out_entry->authors.push_back(author);
338 }
339
340 // Vehicle details
341 out_entry->description = j_entry["description"].GetString();
342 out_entry->tags = j_entry["tags"].GetString();
343 out_entry->default_skin = j_entry["default_skin"].GetString();
344 out_entry->fileformatversion = j_entry["fileformatversion"].GetInt();
345 out_entry->hasSubmeshs = j_entry["hasSubmeshs"].GetBool();
346 out_entry->nodecount = j_entry["nodecount"].GetInt();
347 out_entry->beamcount = j_entry["beamcount"].GetInt();
348 out_entry->shockcount = j_entry["shockcount"].GetInt();
349 out_entry->fixescount = j_entry["fixescount"].GetInt();
350 out_entry->hydroscount = j_entry["hydroscount"].GetInt();
351 out_entry->wheelcount = j_entry["wheelcount"].GetInt();
352 out_entry->propwheelcount = j_entry["propwheelcount"].GetInt();
353 out_entry->commandscount = j_entry["commandscount"].GetInt();
354 out_entry->flarescount = j_entry["flarescount"].GetInt();
355 out_entry->propscount = j_entry["propscount"].GetInt();
356 out_entry->wingscount = j_entry["wingscount"].GetInt();
357 out_entry->turbopropscount = j_entry["turbopropscount"].GetInt();
358 out_entry->turbojetcount = j_entry["turbojetcount"].GetInt();
359 out_entry->rotatorscount = j_entry["rotatorscount"].GetInt();
360 out_entry->exhaustscount = j_entry["exhaustscount"].GetInt();
361 out_entry->flexbodiescount = j_entry["flexbodiescount"].GetInt();
362 out_entry->soundsourcescount = j_entry["soundsourcescount"].GetInt();
363 out_entry->truckmass = j_entry["truckmass"].GetFloat();
364 out_entry->loadmass = j_entry["loadmass"].GetFloat();
365 out_entry->minrpm = j_entry["minrpm"].GetFloat();
366 out_entry->maxrpm = j_entry["maxrpm"].GetFloat();
367 out_entry->torque = j_entry["torque"].GetFloat();
368 out_entry->customtach = j_entry["customtach"].GetBool();
369 out_entry->custom_particles = j_entry["custom_particles"].GetBool();
370 out_entry->forwardcommands = j_entry["forwardcommands"].GetBool();
371 out_entry->importcommands = j_entry["importcommands"].GetBool();
372 out_entry->rescuer = j_entry["rescuer"].GetBool();
373 out_entry->driveable = ActorType(j_entry["driveable"].GetInt());
374 out_entry->numgears = j_entry["numgears"].GetInt();
375 out_entry->enginetype = static_cast<char>(j_entry["enginetype"].GetInt());
376
377 // Vehicle 'section-configs' (aka Modules in RigDef namespace)
378 for (rapidjson::Value& j_module_name: j_entry["sectionconfigs"].GetArray())
379 {
380 out_entry->sectionconfigs.push_back(j_module_name.GetString());
381 }
382
383 // Addon part suggested mod guids
384 for (rapidjson::Value& j_addonguid: j_entry["addonpart_guids"].GetArray())
385 {
386 out_entry->addonpart_guids.insert(j_addonguid.GetString());
387 }
388
389 // Addon part suggested mod filenames
390 for (rapidjson::Value& j_addonfname: j_entry["addonpart_filenames"].GetArray())
391 {
392 out_entry->addonpart_filenames.insert(j_addonfname.GetString());
393 }
394
395 // Tuneup details
396 out_entry->tuneup_associated_filename = j_entry["tuneup_associated_filename"].GetString();
397}
398
400{
401 // Clear existing entries
402 m_entries.clear();
403
404 rapidjson::Document j_doc;
405 if (!App::GetContentManager()->LoadAndParseJson(CACHE_FILE, RGN_CACHE, j_doc) ||
406 !j_doc.IsObject() || !j_doc.HasMember("entries") || !j_doc["entries"].IsArray())
407 {
408 RoR::Log("[RoR|ModCache] Error, cache file still invalid after check/update, content selector will be empty.");
410 }
411
412 if (j_doc["format_version"].GetInt() != CACHE_FILE_FORMAT)
413 {
414 RoR::Log("[RoR|ModCache] Invalid cache file format");
416 }
417
418 for (rapidjson::Value& j_entry: j_doc["entries"].GetArray())
419 {
420 CacheEntryPtr entry = new CacheEntry();
421 this->ImportEntryFromJson(j_entry, entry);
422 entry->number = static_cast<int>(m_entries.size() + 1); // Let's number mods from 1
423 m_entries.push_back(entry);
424 }
425
426 m_filenames_hash_loaded = j_doc["global_hash"].GetString();
427
429}
430
432{
433 this->LoadCacheFileJson();
434
435 std::vector<String> paths;
436 for (auto& entry : m_entries)
437 {
438 std::string fn = entry->resource_bundle_path;
439 if (entry->resource_bundle_type == "FileSystem")
440 {
441 fn = PathCombine(fn, entry->fname);
442 }
443
444 if (!RoR::FileExists(fn.c_str()) || (entry->filetime != RoR::GetFileLastModifiedTime(fn)))
445 {
446 if (!entry->deleted)
447 {
448 if (std::find(paths.begin(), paths.end(), fn) == paths.end())
449 {
450 RoR::LogFormat("[RoR|ModCache] Removing '%s'", fn.c_str());
451 paths.push_back(fn);
452 }
453 this->RemoveFileCache(entry);
454 }
455 entry->deleted = true;
456 }
457 else
458 {
459 m_resource_paths.insert(fn);
460 }
461 }
462}
463
465{
466 for (auto& entry : m_entries)
467 {
468 String group = entry->resource_group;
469 if (!group.empty())
470 {
471 if (ResourceGroupManager::getSingleton().resourceGroupExists(group))
472 ResourceGroupManager::getSingleton().destroyResourceGroup(group);
473 }
474 }
475}
476
478{
479 RoR::Log("[RoR|ModCache] Searching for duplicates ...");
480 std::map<String, String> possible_duplicates;
481 for (int i=0; i<m_entries.size(); i++)
482 {
483 CacheEntryPtr entryA = m_entries[i];
484
485 if (entryA->deleted)
486 continue;
487
488 String dnameA = entryA->dname;
489 StringUtil::toLowerCase(dnameA);
490 StringUtil::trim(dnameA);
491 String dirA = entryA->resource_bundle_path;
492 StringUtil::toLowerCase(dirA);
493 String basenameA, basepathA;
494 StringUtil::splitFilename(dirA, basenameA, basepathA);
495 String filenameWUIDA = entryA->fname_without_uid;
496 StringUtil::toLowerCase(filenameWUIDA);
497
498 for (int j=i+1; j<m_entries.size(); j++)
499 {
500 CacheEntryPtr entryB = m_entries[j];
501
502 if (entryB->deleted)
503 continue;
504
505 String filenameWUIDB = entryB->fname_without_uid;
506 StringUtil::toLowerCase(filenameWUIDB);
507 if (filenameWUIDA != filenameWUIDB)
508 continue;
509
510 String dnameB = entryB->dname;
511 StringUtil::toLowerCase(dnameB);
512 StringUtil::trim(dnameB);
513 if (dnameA != dnameB)
514 continue;
515
516 String dirB = entryB->resource_bundle_path;
517 StringUtil::toLowerCase(dirB);
518 String basenameB, basepathB;
519 StringUtil::splitFilename(dirB, basenameB, basepathB);
520 basenameA = Ogre::StringUtil::replaceAll(basenameA, " ", "_");
521 basenameA = Ogre::StringUtil::replaceAll(basenameA, "-", "_");
522 basenameB = Ogre::StringUtil::replaceAll(basenameB, " ", "_");
523 basenameB = Ogre::StringUtil::replaceAll(basenameB, "-", "_");
524 if (StripSHA1fromString(basenameA) != StripSHA1fromString(basenameB))
525 continue;
526
527 if (entryA->resource_bundle_path == entryB->resource_bundle_path)
528 {
529 LOG("- duplicate: " + entryA->fpath + entryA->fname
530 + " <--> " + entryB->fpath + entryB->fname);
531 LOG(" - " + entryB->resource_bundle_path);
532 int idx = entryA->fpath.size() < entryB->fpath.size() ? i : j;
533 m_entries[idx]->deleted = true;
534 }
535 else
536 {
537 possible_duplicates[entryA->resource_bundle_path] = entryB->resource_bundle_path;
538 }
539 }
540 }
541 for (auto duplicate : possible_duplicates)
542 {
543 LOG("- possible duplicate: ");
544 LOG(" - " + duplicate.first);
545 LOG(" - " + duplicate.second);
546 }
547}
548
550{
551 for (CacheEntryPtr& entry: m_entries)
552 {
553 if (modid == entry->number)
554 return entry;
555 }
556 return 0;
557}
558
559String CacheSystem::GetPrettyName(String fname)
560{
561 for (CacheEntryPtr& entry: m_entries)
562 {
563 if (fname == entry->fname)
564 return entry->dname;
565 }
566 return "";
567}
568
570{
571 switch (driveable)
572 {
573 case ActorType::NOT_DRIVEABLE: return _LC("MainSelector", "Non-Driveable");
574 case ActorType::TRUCK: return _LC("MainSelector", "Truck");
575 case ActorType::AIRPLANE: return _LC("MainSelector", "Airplane");
576 case ActorType::BOAT: return _LC("MainSelector", "Boat");
577 case ActorType::MACHINE: return _LC("MainSelector", "Machine");
578 case ActorType::AI: return _LC("MainSelector", "A.I.");
579 default: return "";
580 };
581}
582
583void CacheSystem::ExportEntryToJson(rapidjson::Value& j_entries, rapidjson::Document& j_doc, CacheEntryPtr const & entry)
584{
585 rapidjson::Value j_entry(rapidjson::kObjectType);
586
587 // Common details
588 j_entry.AddMember("usagecounter", entry->usagecounter, j_doc.GetAllocator());
589 j_entry.AddMember("addtimestamp", static_cast<int64_t>(entry->addtimestamp), j_doc.GetAllocator());
590 j_entry.AddMember("resource_bundle_type", rapidjson::StringRef(entry->resource_bundle_type.c_str()), j_doc.GetAllocator());
591 j_entry.AddMember("resource_bundle_path", rapidjson::StringRef(entry->resource_bundle_path.c_str()), j_doc.GetAllocator());
592 j_entry.AddMember("fpath", rapidjson::StringRef(entry->fpath.c_str()), j_doc.GetAllocator());
593 j_entry.AddMember("fname", rapidjson::StringRef(entry->fname.c_str()), j_doc.GetAllocator());
594 j_entry.AddMember("fname_without_uid", rapidjson::StringRef(entry->fname_without_uid.c_str()), j_doc.GetAllocator());
595 j_entry.AddMember("fext", rapidjson::StringRef(entry->fext.c_str()), j_doc.GetAllocator());
596 j_entry.AddMember("filetime", static_cast<int64_t>(entry->filetime), j_doc.GetAllocator());
597 j_entry.AddMember("dname", rapidjson::StringRef(entry->dname.c_str()), j_doc.GetAllocator());
598 j_entry.AddMember("categoryid", entry->categoryid, j_doc.GetAllocator());
599 j_entry.AddMember("uniqueid", rapidjson::StringRef(entry->uniqueid.c_str()), j_doc.GetAllocator());
600 j_entry.AddMember("guid", rapidjson::StringRef(entry->guid.c_str()), j_doc.GetAllocator());
601 j_entry.AddMember("version", entry->version, j_doc.GetAllocator());
602 j_entry.AddMember("filecachename", rapidjson::StringRef(entry->filecachename.c_str()), j_doc.GetAllocator());
603
604 // Common - Authors
605 rapidjson::Value j_authors(rapidjson::kArrayType);
606 for (AuthorInfo const& author: entry->authors)
607 {
608 rapidjson::Value j_author(rapidjson::kObjectType);
609
610 j_author.AddMember("type", rapidjson::StringRef(author.type.c_str()), j_doc.GetAllocator());
611 j_author.AddMember("name", rapidjson::StringRef(author.name.c_str()), j_doc.GetAllocator());
612 j_author.AddMember("email", rapidjson::StringRef(author.email.c_str()), j_doc.GetAllocator());
613 j_author.AddMember("id", author.id, j_doc.GetAllocator());
614
615 j_authors.PushBack(j_author, j_doc.GetAllocator());
616 }
617 j_entry.AddMember("authors", j_authors, j_doc.GetAllocator());
618
619 // Vehicle details
620 j_entry.AddMember("description", rapidjson::StringRef(entry->description.c_str()), j_doc.GetAllocator());
621 j_entry.AddMember("tags", rapidjson::StringRef(entry->tags.c_str()), j_doc.GetAllocator());
622 j_entry.AddMember("default_skin", rapidjson::StringRef(entry->default_skin.c_str()), j_doc.GetAllocator());
623 j_entry.AddMember("fileformatversion", entry->fileformatversion, j_doc.GetAllocator());
624 j_entry.AddMember("hasSubmeshs", entry->hasSubmeshs, j_doc.GetAllocator());
625 j_entry.AddMember("nodecount", entry->nodecount, j_doc.GetAllocator());
626 j_entry.AddMember("beamcount", entry->beamcount, j_doc.GetAllocator());
627 j_entry.AddMember("shockcount", entry->shockcount, j_doc.GetAllocator());
628 j_entry.AddMember("fixescount", entry->fixescount, j_doc.GetAllocator());
629 j_entry.AddMember("hydroscount", entry->hydroscount, j_doc.GetAllocator());
630 j_entry.AddMember("wheelcount", entry->wheelcount, j_doc.GetAllocator());
631 j_entry.AddMember("propwheelcount", entry->propwheelcount, j_doc.GetAllocator());
632 j_entry.AddMember("commandscount", entry->commandscount, j_doc.GetAllocator());
633 j_entry.AddMember("flarescount", entry->flarescount, j_doc.GetAllocator());
634 j_entry.AddMember("propscount", entry->propscount, j_doc.GetAllocator());
635 j_entry.AddMember("wingscount", entry->wingscount, j_doc.GetAllocator());
636 j_entry.AddMember("turbopropscount", entry->turbopropscount, j_doc.GetAllocator());
637 j_entry.AddMember("turbojetcount", entry->turbojetcount, j_doc.GetAllocator());
638 j_entry.AddMember("rotatorscount", entry->rotatorscount, j_doc.GetAllocator());
639 j_entry.AddMember("exhaustscount", entry->exhaustscount, j_doc.GetAllocator());
640 j_entry.AddMember("flexbodiescount", entry->flexbodiescount, j_doc.GetAllocator());
641 j_entry.AddMember("soundsourcescount", entry->soundsourcescount, j_doc.GetAllocator());
642 j_entry.AddMember("truckmass", entry->truckmass, j_doc.GetAllocator());
643 j_entry.AddMember("loadmass", entry->loadmass, j_doc.GetAllocator());
644 j_entry.AddMember("minrpm", entry->minrpm, j_doc.GetAllocator());
645 j_entry.AddMember("maxrpm", entry->maxrpm, j_doc.GetAllocator());
646 j_entry.AddMember("torque", entry->torque, j_doc.GetAllocator());
647 j_entry.AddMember("customtach", entry->customtach, j_doc.GetAllocator());
648 j_entry.AddMember("custom_particles", entry->custom_particles, j_doc.GetAllocator());
649 j_entry.AddMember("forwardcommands", entry->forwardcommands, j_doc.GetAllocator());
650 j_entry.AddMember("importcommands", entry->importcommands, j_doc.GetAllocator());
651 j_entry.AddMember("rescuer", entry->rescuer, j_doc.GetAllocator());
652 j_entry.AddMember("driveable", entry->driveable, j_doc.GetAllocator());
653 j_entry.AddMember("numgears", entry->numgears, j_doc.GetAllocator());
654 j_entry.AddMember("enginetype", entry->enginetype, j_doc.GetAllocator());
655
656 // Vehicle 'section-configs' (aka Modules in RigDef namespace)
657 rapidjson::Value j_sectionconfigs(rapidjson::kArrayType);
658 for (std::string const & module_name: entry->sectionconfigs)
659 {
660 j_sectionconfigs.PushBack(rapidjson::StringRef(module_name.c_str()), j_doc.GetAllocator());
661 }
662 j_entry.AddMember("sectionconfigs", j_sectionconfigs, j_doc.GetAllocator());
663
664 // Addon part details
665 rapidjson::Value j_addonguids(rapidjson::kArrayType);
666 for (std::string const & ag: entry->addonpart_guids)
667 {
668 j_addonguids.PushBack(rapidjson::StringRef(ag.c_str()), j_doc.GetAllocator());
669 }
670 j_entry.AddMember("addonpart_guids", j_addonguids, j_doc.GetAllocator());
671
672 rapidjson::Value j_addonfnames(rapidjson::kArrayType);
673 for (std::string const & ag: entry->addonpart_filenames)
674 {
675 j_addonfnames.PushBack(rapidjson::StringRef(ag.c_str()), j_doc.GetAllocator());
676 }
677 j_entry.AddMember("addonpart_filenames", j_addonfnames, j_doc.GetAllocator());
678
679 // Tuneup details
680 j_entry.AddMember("tuneup_associated_filename", rapidjson::StringRef(entry->tuneup_associated_filename.c_str()), j_doc.GetAllocator());
681
682 // Add entry to list
683 j_entries.PushBack(j_entry, j_doc.GetAllocator());
684}
685
687{
688 // Basic file structure
689 rapidjson::Document j_doc;
690 j_doc.SetObject();
691 j_doc.AddMember("format_version", CACHE_FILE_FORMAT, j_doc.GetAllocator());
692 j_doc.AddMember("global_hash", rapidjson::StringRef(m_filenames_hash_generated.c_str()), j_doc.GetAllocator());
693
694 // Entries
695 rapidjson::Value j_entries(rapidjson::kArrayType);
696 for (CacheEntryPtr const& entry : m_entries)
697 {
698 if (!entry->deleted)
699 {
700 this->ExportEntryToJson(j_entries, j_doc, entry);
701 }
702 }
703 j_doc.AddMember("entries", j_entries, j_doc.GetAllocator());
704
705 // Write to file
706 if (App::GetContentManager()->SerializeAndWriteJson(CACHE_FILE, RGN_CACHE, j_doc)) // Logs errors
707 {
708 RoR::LogFormat("[RoR|ModCache] File '%s' written OK", CACHE_FILE);
709 }
710}
711
713{
715 for (auto& entry : m_entries)
716 {
717 String group = entry->resource_group;
718 if (!group.empty())
719 {
720 if (ResourceGroupManager::getSingleton().resourceGroupExists(group))
721 ResourceGroupManager::getSingleton().destroyResourceGroup(group);
722 }
723 this->RemoveFileCache(entry);
724 }
725 m_entries.clear();
726}
727
728Ogre::String CacheSystem::StripUIDfromString(Ogre::String uidstr)
729{
730 size_t pos = uidstr.find("-");
731 if (pos != String::npos && pos >= 3 && uidstr.substr(pos - 3, 3) == "UID")
732 return uidstr.substr(pos + 1, uidstr.length() - pos);
733 return uidstr;
734}
735
736Ogre::String CacheSystem::StripSHA1fromString(Ogre::String sha1str)
737{
738 size_t pos = sha1str.find_first_of("-_");
739 if (pos != String::npos && pos >= 20)
740 return sha1str.substr(pos + 1, sha1str.length() - pos);
741 return sha1str;
742}
743
744void CacheSystem::AddFile(String group, Ogre::FileInfo f, String ext)
745{
746 String type = f.archive ? f.archive->getType() : "FileSystem";
747 String path = f.archive ? f.archive->getName() : "";
748
749 if (std::find_if(m_entries.begin(), m_entries.end(), [&](CacheEntryPtr& entry)
750 { return !entry->deleted && entry->fname == f.filename && entry->resource_bundle_path == path; }) != m_entries.end())
751 return;
752
753 LOG(fmt::format("[RoR|ModCache] Preparing to add file '{}'", f.filename));
754
755 try
756 {
757 DataStreamPtr ds = ResourceGroupManager::getSingleton().openResource(f.filename, group);
758 // ds closes automatically, so do _not_ close it explicitly below
759
760 std::vector<CacheEntryPtr> new_entries;
761 if (ext == "terrn2")
762 {
763 CacheEntryPtr entry = new CacheEntry();
764 FillTerrainDetailInfo(entry, ds, f.filename);
765 new_entries.push_back(entry);
766 }
767 else if (ext == "skin")
768 {
769 auto new_skins = RoR::SkinParser::ParseSkins(ds);
770 for (auto skin_def: new_skins)
771 {
772 CacheEntryPtr entry = new CacheEntry();
773 FillSkinDetailInfo(entry, skin_def);
774 new_entries.push_back(entry);
775 }
776 }
777 else if (ext == "addonpart")
778 {
779 CacheEntryPtr entry = new CacheEntry();
780 FillAddonPartDetailInfo(entry, ds);
781 new_entries.push_back(entry);
782 }
783 else if (ext == "tuneup")
784 {
785 auto new_tuneups = RoR::TuneupUtil::ParseTuneups(ds);
786 for (auto tuneup_def: new_tuneups)
787 {
788 CacheEntryPtr entry = new CacheEntry();
789 FillTuneupDetailInfo(entry, tuneup_def);
790 new_entries.push_back(entry);
791 }
792 }
793 else if (ext == "assetpack")
794 {
795 CacheEntryPtr entry = new CacheEntry();
796 FillAssetPackDetailInfo(entry, ds);
797 new_entries.push_back(entry);
798 }
799 else if (ext == "dashboard")
800 {
801 CacheEntryPtr entry = new CacheEntry();
802 FillDashboardDetailInfo(entry, ds);
803 new_entries.push_back(entry);
804 }
805 else if (ext == "gadget")
806 {
807 CacheEntryPtr entry = new CacheEntry();
808 FillGadgetDetailInfo(entry, ds);
809 new_entries.push_back(entry);
810 }
811 else
812 {
813 CacheEntryPtr entry = new CacheEntry();
814 FillTruckDetailInfo(entry, ds, f.filename, group);
815 new_entries.push_back(entry);
816 }
817
818 for (auto& entry: new_entries)
819 {
820 Ogre::StringUtil::toLowerCase(entry->guid); // Important for comparsion
821 entry->fpath = f.path;
822 entry->fname = f.filename;
823 entry->fname_without_uid = StripUIDfromString(f.filename);
824 entry->fext = ext;
825 if (type == "Zip")
826 {
828 }
829 else
830 {
831 entry->filetime = RoR::GetFileLastModifiedTime(PathCombine(path, f.filename));
832 }
833 entry->resource_bundle_type = type;
834 entry->resource_bundle_path = path;
835 entry->number = static_cast<int>(m_entries.size() + 1); // Let's number mods from 1
837 this->GenerateFileCache(entry, group);
838 m_entries.push_back(entry);
839 // This isn't just for script, it also triggers retry-spawn in multiplayer, see `case MSG_SIM_SCRIPT_EVENT_TRIGGERED:` in main.cpp
841 }
842 }
843 catch (Ogre::Exception& e)
844 {
845 RoR::LogFormat("[RoR|ModCache] Error processing file '%s', message :%s",
846 f.filename.c_str(), e.getFullDescription().c_str());
847 }
848}
849
850void CacheSystem::FillTruckDetailInfo(CacheEntryPtr& entry, Ogre::DataStreamPtr stream, String file_name, String group)
851{
852 /* LOAD AND PARSE THE VEHICLE */
853 RigDef::Parser parser;
854 parser.Prepare();
855 parser.ProcessOgreStream(stream.get(), group);
856 parser.GetSequentialImporter()->Disable();
857 parser.Finalize();
858
859 /* RETRIEVE DATA */
860
861 RigDef::DocumentPtr def = parser.GetFile();
862
863 /* Name */
864 if (!def->name.empty())
865 {
866 entry->dname = def->name; // Use retrieved name
867 }
868 else
869 {
870 entry->dname = "@" + file_name; // Fallback
871 }
872
873 /* Description */
874 std::vector<Ogre::String>::iterator desc_itor = def->root_module->description.begin();
875 for (; desc_itor != def->root_module->description.end(); desc_itor++)
876 {
877 entry->description += *desc_itor + "\n";
878 }
879
880 /* Authors */
881 std::vector<RigDef::Author>::iterator author_itor = def->root_module->author.begin();
882 for (; author_itor != def->root_module->author.end(); author_itor++)
883 {
884 AuthorInfo author;
885 author.email = author_itor->email;
886 author.id = (author_itor->_has_forum_account) ? static_cast<int>(author_itor->forum_account_id) : -1;
887 author.name = author_itor->name;
888 author.type = author_itor->type;
889
890 entry->authors.push_back(author);
891 }
892
893 /* Default skin */
894 if (def->root_module->default_skin.size() > 0)
895 {
896 entry->default_skin = def->root_module->default_skin.back().skin_name;
897 }
898
899 /* Modules (previously called "sections") */
900 std::map<Ogre::String, std::shared_ptr<RigDef::Document::Module>>::iterator module_itor = def->user_modules.begin();
901 for (; module_itor != def->user_modules.end(); module_itor++)
902 {
903 entry->sectionconfigs.push_back(module_itor->second->name);
904 }
905
906 /* Engine */
907 /* TODO: Handle engines in modules */
908 if (def->root_module->engine.size() > 0)
909 {
910 RigDef::Engine& engine = def->root_module->engine[def->root_module->engine.size() - 1];
911 entry->numgears = static_cast<int>(engine.gear_ratios.size());
912 entry->minrpm = engine.shift_down_rpm;
913 entry->maxrpm = engine.shift_up_rpm;
914 entry->torque = engine.torque;
915 entry->enginetype = 't'; /* Truck (default) */
916 if (def->root_module->engoption.size() > 0)
917 {
918 entry->enginetype = (char)def->root_module->engoption[def->root_module->engoption.size() - 1].type;
919 }
920 }
921
922 /* File info */
923 if (def->root_module->fileinfo.size() > 0)
924 {
925 RigDef::Fileinfo& data = def->root_module->fileinfo[def->root_module->fileinfo.size() - 1];
926
927 entry->uniqueid = data.unique_id;
928 entry->categoryid = static_cast<int>(data.category_id);
929 entry->version = static_cast<int>(data.file_version);
930 }
931 else
932 {
933 entry->uniqueid = "-1";
934 entry->categoryid = -1;
935 entry->version = -1;
936 }
937
938 /* Vehicle type */
939 /* NOTE: RigDef::Document allows modularization of vehicle type. Cache only supports single type.
940 This is a temporary solution which has undefined results for mixed-type vehicles.
941 */
942 ActorType vehicle_type = NOT_DRIVEABLE;
943 module_itor = def->user_modules.begin();
944 for (; module_itor != def->user_modules.end(); module_itor++)
945 {
946 if (module_itor->second->engine.size() > 0)
947 {
948 vehicle_type = TRUCK;
949 }
950 else if (module_itor->second->screwprops.size() > 0)
951 {
952 vehicle_type = BOAT;
953 }
954 /* Note: Sections 'turboprops' and 'turboprops2' are unified in TruckParser2013 */
955 else if (module_itor->second->turbojets.size() > 0 || module_itor->second->pistonprops.size() > 0 || module_itor->second->turboprops2.size() > 0)
956 {
957 vehicle_type = AIRPLANE;
958 }
959 }
960 /* Root module */
961 if (def->root_module->engine.size() > 0)
962 {
963 vehicle_type = TRUCK;
964 }
965 else if (def->root_module->screwprops.size() > 0)
966 {
967 vehicle_type = BOAT;
968 }
969 /* Note: Sections 'turboprops' and 'turboprops2' are unified in TruckParser2013 */
970 else if (def->root_module->turbojets.size() > 0 || def->root_module->pistonprops.size() > 0 || def->root_module->turboprops2.size() > 0)
971 {
972 vehicle_type = AIRPLANE;
973 }
974
975 if (def->root_module->globals.size() > 0)
976 {
977 entry->truckmass = def->root_module->globals[def->root_module->globals.size() - 1].dry_mass;
978 entry->loadmass = def->root_module->globals[def->root_module->globals.size() - 1].cargo_mass;
979 }
980
981 entry->forwardcommands = def->forward_commands;
982 entry->importcommands = def->import_commands;
983 entry->rescuer = def->rescuer;
984 if (def->root_module->guid.size() > 0)
985 {
986 entry->guid = def->root_module->guid[def->root_module->guid.size() - 1].guid;
987 Ogre::StringUtil::toLowerCase(entry->guid);
988 }
989 entry->fileformatversion = 0;
990 if (def->root_module->fileformatversion.size() > 0)
991 {
992 entry->fileformatversion = def->root_module->fileformatversion[def->root_module->fileformatversion.size() - 1].version;
993 }
994 entry->hasSubmeshs = static_cast<int>(def->root_module->submeshes.size() > 0);
995 entry->nodecount = static_cast<int>(def->root_module->nodes.size());
996 entry->beamcount = static_cast<int>(def->root_module->beams.size());
997 entry->shockcount = static_cast<int>(def->root_module->shocks.size() + def->root_module->shocks2.size());
998 entry->fixescount = static_cast<int>(def->root_module->fixes.size());
999 entry->hydroscount = static_cast<int>(def->root_module->hydros.size());
1000 entry->driveable = vehicle_type;
1001 entry->commandscount = static_cast<int>(def->root_module->commands2.size());
1002 entry->flarescount = static_cast<int>(def->root_module->flares2.size());
1003 entry->propscount = static_cast<int>(def->root_module->props.size());
1004 entry->wingscount = static_cast<int>(def->root_module->wings.size());
1005 entry->turbopropscount = static_cast<int>(def->root_module->turboprops2.size());
1006 entry->rotatorscount = static_cast<int>(def->root_module->rotators.size() + def->root_module->rotators2.size());
1007 entry->exhaustscount = static_cast<int>(def->root_module->exhausts.size());
1008 entry->custom_particles = def->root_module->particles.size() > 0;
1009 entry->turbojetcount = static_cast<int>(def->root_module->turbojets.size());
1010 entry->flexbodiescount = static_cast<int>(def->root_module->flexbodies.size());
1011 entry->soundsourcescount = static_cast<int>(def->root_module->soundsources.size() + def->root_module->soundsources.size());
1012
1013 entry->wheelcount = 0;
1014 entry->propwheelcount = 0;
1015 for (const auto& w : def->root_module->wheels)
1016 {
1017 entry->wheelcount++;
1018 if (w.propulsion != WheelPropulsion::NONE)
1019 entry->propwheelcount++;
1020 }
1021 for (const auto& w : def->root_module->wheels2)
1022 {
1023 entry->wheelcount++;
1024 if (w.propulsion != WheelPropulsion::NONE)
1025 entry->propwheelcount++;
1026 }
1027 for (const auto& w : def->root_module->meshwheels)
1028 {
1029 entry->wheelcount++;
1030 if (w.propulsion != WheelPropulsion::NONE)
1031 entry->propwheelcount++;
1032 }
1033 for (const auto& w : def->root_module->meshwheels2)
1034 {
1035 entry->wheelcount++;
1036 if (w.propulsion != WheelPropulsion::NONE)
1037 entry->propwheelcount++;
1038 }
1039 for (const auto& w : def->root_module->flexbodywheels)
1040 {
1041 entry->wheelcount++;
1042 if (w.propulsion != WheelPropulsion::NONE)
1043 entry->propwheelcount++;
1044 }
1045
1046 if (!def->root_module->axles.empty())
1047 {
1048 entry->propwheelcount = static_cast<int>(def->root_module->axles.size() * 2);
1049 }
1050
1051 /* NOTE: std::shared_ptr cleans everything up. */
1052}
1053
1054Ogre::String detectMiniType(String filename, String group)
1055{
1056 if (ResourceGroupManager::getSingleton().resourceExists(group, filename + "dds"))
1057 return "dds";
1058
1059 if (ResourceGroupManager::getSingleton().resourceExists(group, filename + "png"))
1060 return "png";
1061
1062 if (ResourceGroupManager::getSingleton().resourceExists(group, filename + "jpg"))
1063 return "jpg";
1064
1065 return "";
1066}
1067
1069{
1070 if (!entry->filecachename.empty())
1071 {
1073 }
1074}
1075
1077{
1078 if (entry->fname.empty())
1079 return;
1080
1081 String bundle_basename, bundle_path;
1082 StringUtil::splitFilename(entry->resource_bundle_path, bundle_basename, bundle_path);
1083
1084 String src_path;
1085 String dst_path;
1086 if (entry->fext == "skin")
1087 {
1088 if (entry->skin_def->thumbnail.empty())
1089 return;
1090 src_path = entry->skin_def->thumbnail;
1091 String mini_fbase, minitype;
1092 StringUtil::splitBaseFilename(entry->skin_def->thumbnail, mini_fbase, minitype);
1093 dst_path = bundle_basename + "_" + mini_fbase + ".mini." + minitype;
1094 }
1095 else
1096 {
1097 String fbase, fext;
1098 StringUtil::splitBaseFilename(entry->fname, fbase, fext);
1099 String minifn = fbase + "-mini.";
1100 String minitype = detectMiniType(minifn, group);
1101 if (minitype.empty())
1102 return;
1103 src_path = minifn + minitype;
1104 dst_path = bundle_basename + "_" + entry->fname + ".mini." + minitype;
1105 }
1106
1107 try
1108 {
1109 DataStreamPtr src_ds = ResourceGroupManager::getSingleton().openResource(src_path, group);
1110 DataStreamPtr dst_ds = ResourceGroupManager::getSingleton().createResource(dst_path, RGN_CACHE, true);
1111 std::vector<char> buf(src_ds->size());
1112 size_t read = src_ds->read(buf.data(), src_ds->size());
1113 if (read > 0)
1114 {
1115 dst_ds->write(buf.data(), read);
1116 entry->filecachename = dst_path;
1117 }
1118 }
1119 catch (Ogre::Exception& e)
1120 {
1121 LOG("error while generating file cache: " + e.getFullDescription());
1122 }
1123
1124 LOG("done generating file cache!");
1125}
1126
1128{
1129 auto files = ResourceGroupManager::getSingleton().findResourceFileInfo(group, "*.zip");
1130 auto skinzips = ResourceGroupManager::getSingleton().findResourceFileInfo(group, "*.skinzip");
1131 for (const auto& skinzip : *skinzips)
1132 files->push_back(skinzip);
1133
1134 int i = 0, count = static_cast<int>(files->size());
1135 for (const auto& file : *files)
1136 {
1137 int progress = ((float)i++ / (float)count) * 100;
1138 std::string text = fmt::format("{}{}\n{}\n{}/{}",
1139 _L("Loading zips in group "), group, file.filename, i, count);
1141
1142 String path = PathCombine(file.archive->getName(), file.filename);
1143 this->ParseSingleZip(path);
1144 }
1145
1148}
1149
1151{
1152 if (std::find(m_resource_paths.begin(), m_resource_paths.end(), path) == m_resource_paths.end())
1153 {
1154 RoR::LogFormat("[RoR|ModCache] Adding archive '%s'", path.c_str());
1155 ResourceGroupManager::getSingleton().createResourceGroup(RGN_TEMP, false);
1156 try
1157 {
1158 ResourceGroupManager::getSingleton().addResourceLocation(path, "Zip", RGN_TEMP);
1160 {
1161 LOG("No usable content in: '" + path + "'");
1162 }
1163 }
1164 catch (Ogre::Exception& e)
1165 {
1166 LOG("Error while opening archive: '" + path + "': " + e.getFullDescription());
1167 }
1168 ResourceGroupManager::getSingleton().destroyResourceGroup(RGN_TEMP);
1169 m_resource_paths.insert(path);
1170 }
1171}
1172
1173bool CacheSystem::ParseKnownFiles(Ogre::String group)
1174{
1175 bool empty = true;
1176 for (auto ext : m_known_extensions)
1177 {
1178 auto files = ResourceGroupManager::getSingleton().findResourceFileInfo(group, "*." + ext);
1179 for (const auto& file : *files)
1180 {
1181 this->AddFile(group, file, ext);
1182 empty = false;
1183 }
1184 }
1185 return empty;
1186}
1187
1189{
1190 std::string filenames = App::GetContentManager()->ListAllUserContent();
1191 m_filenames_hash_generated = HashData(filenames.c_str(), static_cast<int>(filenames.size()));
1192}
1193
1194void CacheSystem::FillTerrainDetailInfo(CacheEntryPtr& entry, Ogre::DataStreamPtr ds, Ogre::String fname)
1195{
1196 Terrn2Parser parser;
1197 Terrn2DocumentPtr def = parser.LoadTerrn2(ds);
1198 if (!def)
1199 {
1201 fmt::format("Mod cache entry not populated - could not load terrain {}", ds->getName()));
1202 return;
1203 }
1204
1205 for (Terrn2Author& author : def->authors)
1206 {
1207 AuthorInfo a;
1208 a.id = -1;
1209 a.name = author.name;
1210 a.type = author.type;
1211 entry->authors.push_back(a);
1212 }
1213
1214 entry->dname = def->name;
1215 entry->categoryid = def->category_id;
1216 entry->uniqueid = def->guid;
1217 entry->version = def->version;
1218}
1219
1220void CacheSystem::FillSkinDetailInfo(CacheEntryPtr &entry, std::shared_ptr<SkinDocument>& skin_def)
1221{
1222 if (!skin_def->author_name.empty())
1223 {
1224 AuthorInfo a;
1225 a.id = skin_def->author_id;
1226 a.name = skin_def->author_name;
1227 entry->authors.push_back(a);
1228 }
1229
1230 entry->dname = skin_def->name;
1231 entry->guid = skin_def->guid;
1232 entry->description = skin_def->description;
1233 entry->categoryid = -1;
1234 entry->skin_def = skin_def; // Needed to generate preview image
1235
1236 Ogre::StringUtil::toLowerCase(entry->guid);
1237}
1238
1239void CacheSystem::FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
1240{
1243 doc->loadFromDataStream(ds, options);
1244
1246 while (!ctx->endOfFile())
1247 {
1248 if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_name")
1249 {
1250 entry->dname = ctx->getTokString(1);
1251 }
1252 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_description")
1253 {
1254 entry->description = ctx->getTokString(1);
1255 }
1256 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_guid")
1257 {
1258 std::string guid = ctx->getTokString(1);
1259 Ogre::StringUtil::toLowerCase(guid);
1260 entry->addonpart_guids.insert(guid);
1261 }
1262 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_filename")
1263 {
1264 std::string fname = ctx->getTokString(1);
1265 Ogre::StringUtil::toLowerCase(fname);
1266 entry->addonpart_filenames.insert(fname);
1267 }
1268
1269 ctx->seekNextLine();
1270 }
1271}
1272
1273void CacheSystem::FillAssetPackDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
1274{
1277 doc->loadFromDataStream(ds, options);
1278
1280 while (!ctx->endOfFile())
1281 {
1282 if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_name")
1283 {
1284 entry->dname = ctx->getTokString(1);
1285 }
1286 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_description")
1287 {
1288 entry->description = ctx->getTokString(1);
1289 }
1290 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "assetpack_author")
1291 {
1292 int n = ctx->countLineArgs();
1293 AuthorInfo author;
1294 if (n > 1) { author.type = ctx->getTokString(1); }
1295 if (n > 2) { author.id = (int)ctx->getTokFloat(2); }
1296 if (n > 3) { author.name = ctx->getTokString(3); }
1297 if (n > 4) { author.email = ctx->getTokString(4); }
1298 entry->authors.push_back(author);
1299 }
1300
1301 ctx->seekNextLine();
1302 }
1303}
1304
1305void CacheSystem::FillDashboardDetailInfo(CacheEntryPtr& entry, Ogre::DataStreamPtr ds)
1306{
1309 doc->loadFromDataStream(ds, options);
1310
1312 while (!ctx->endOfFile())
1313 {
1314 if (ctx->isTokKeyword() && ctx->getTokKeyword() == "dashboard_name" && ctx->isTokString(1))
1315 {
1316 entry->dname = ctx->getTokString(1);
1317 }
1318 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "dashboard_description" && ctx->isTokString(1))
1319 {
1320 entry->description = ctx->getTokString(1);
1321 }
1322 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "dashboard_category" && ctx->isTokInt(1))
1323 {
1324 entry->categoryid = ctx->getTokInt(1);
1325 }
1326 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "dashboard_author")
1327 {
1328 int n = ctx->countLineArgs();
1329 AuthorInfo author;
1330 if (n > 1) { author.type = ctx->getTokString(1); }
1331 if (n > 2) { author.id = ctx->getTokInt(2); }
1332 if (n > 3) { author.name = ctx->getTokString(3); }
1333 if (n > 4) { author.email = ctx->getTokString(4); }
1334 entry->authors.push_back(author);
1335 }
1336
1337 ctx->seekNextLine();
1338 }
1339
1340}
1341
1342void CacheSystem::FillGadgetDetailInfo(CacheEntryPtr& entry, Ogre::DataStreamPtr ds)
1343{
1345 BitMask_t options
1348 doc->loadFromDataStream(ds, options);
1349
1351 while (!ctx->endOfFile())
1352 {
1353 if (ctx->isTokKeyword() && ctx->getTokKeyword() == "gadget_name" && ctx->isTokString(1))
1354 {
1355 entry->dname = ctx->getTokString(1);
1356 }
1357 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "gadget_description" && ctx->isTokString(1))
1358 {
1359 if (entry->description == "")
1360 {
1361 entry->description = ctx->getTokString(1);
1362 }
1363 else
1364 {
1365 entry->description += "\n" + ctx->getTokString(1);
1366 }
1367 }
1368 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "gadget_category" && ctx->isTokInt(1))
1369 {
1370 entry->categoryid = ctx->getTokInt(1);
1371 }
1372 else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "gadget_author")
1373 {
1374 int n = ctx->countLineArgs();
1375 AuthorInfo author;
1376 if (n > 1) { author.type = ctx->getTokString(1); }
1377 if (n > 2) { author.id = ctx->getTokInt(2); }
1378 if (n > 3) { author.name = ctx->getTokString(3); }
1379 if (n > 4) { author.email = ctx->getTokString(4); }
1380 entry->authors.push_back(author);
1381 }
1382
1383 ctx->seekNextLine();
1384 }
1385
1386}
1387
1389{
1390 if (!tuneup_def->author_name.empty())
1391 {
1392 AuthorInfo a;
1393 a.id = tuneup_def->author_id;
1394 a.name = tuneup_def->author_name;
1395 entry->authors.push_back(a);
1396 }
1397
1398 entry->dname = tuneup_def->name;
1399 entry->guid = tuneup_def->guid;
1400 entry->description = tuneup_def->description;
1401 entry->categoryid = tuneup_def->category_id;
1402 entry->tuneup_def = tuneup_def; // Needed to generate preview image
1403 entry->tuneup_associated_filename = tuneup_def->filename;
1404
1405 Ogre::StringUtil::toLowerCase(entry->guid);
1406 Ogre::StringUtil::toLowerCase(entry->tuneup_associated_filename);
1407}
1408
1409void CacheSystem::LoadAssetPack(CacheEntryPtr& target_entry, Ogre::String const & assetpack_filename)
1410{
1411 // Load asset packs into the mod-bundle's resource group (quick & dirty approach).
1412 // See also `ContentManager::resourceCollision()` - we always keep the original file and dump the colliding one.
1413 // --------------------------------------------------------------------------------------------------------------
1414
1415 ROR_ASSERT(!target_entry->deleted);
1416 ROR_ASSERT(target_entry->resource_group != "");
1417 ROR_ASSERT(assetpack_filename != "");
1418
1419 CacheEntryPtr assetpack_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AssetPack, /*partial=*/false, assetpack_filename);
1420 if (assetpack_entry)
1421 {
1422 try
1423 {
1424 Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
1425 assetpack_entry->resource_bundle_path, // name (source)
1426 assetpack_entry->resource_bundle_type, // type (source)
1427 target_entry->resource_group, // resGroup (target)
1428 false, // recursive
1429 assetpack_entry->resource_bundle_type != "FileSystem"); // readOnly
1430
1431 // This is messy but there's no other way - OGRE resource groups cannot update incrementally.
1432 Ogre::ResourceGroupManager::getSingleton().clearResourceGroup(target_entry->resource_group);
1433 Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(target_entry->resource_group);
1434 }
1435 catch (std::exception const& e)
1436 {
1438 fmt::format(_L("Failed to load asset pack '{}' (requested by '{}'): {}"), assetpack_entry->fname, target_entry->fname, e.what()));
1439 }
1440 }
1441 else
1442 {
1444 fmt::format(_L("Asset pack '{}' (requested by '{}') not found"), assetpack_filename, target_entry->fname));
1445 }
1446}
1447
1448static bool CheckAndReplacePathIgnoreCase(const CacheEntryPtr& entry, CVar* dir, const std::string& dir_label, std::string& out_rgname)
1449{
1450 // Helper for `ComposeResourceGroupName()`
1451 // ---------------------------------------
1452
1453 // Sanity check - assert on Debug, minimize damage on Release
1454 ROR_ASSERT(entry->resource_bundle_path != "");
1455 if (entry->resource_bundle_path == "")
1456 {
1457 LOG(fmt::format("[RoR|ModCache] CheckAndReplacePathIgnoreCase(): INTERNAL ERROR - entry '{}' has no bundle path!", entry->fname));
1458 return false;
1459 }
1460
1461 // Lowercase everything
1462 std::string lower_bundlepath = entry->resource_bundle_path;
1463 Ogre::StringUtil::toLowerCase(lower_bundlepath);
1464
1465 std::string lower_dir = dir->getStr();
1466 Ogre::StringUtil::toLowerCase(lower_dir);
1467
1468 // Look for match and replace
1469 if (Ogre::StringUtil::startsWith(lower_bundlepath, lower_dir, /*lowercase:*/true))
1470 {
1471 // Sanity check; Should be guaranteed by the `startsWith()` check, but just to be sure...
1472 ROR_ASSERT(lower_bundlepath.size() > lower_dir.size());
1473 if (lower_bundlepath.size() > lower_dir.size())
1474 {
1475 std::string localpath = entry->resource_bundle_path.substr(lower_dir.length());
1476 out_rgname = fmt::format("{{bundle {}:{}}}", dir_label, localpath);
1477 return true;
1478 }
1479 }
1480 return false;
1481}
1482
1484{
1485 // Compose group name as "{bundle <local path>}", where 'local path' means either:
1486 // - under `sys_user_dir` (by default 'Documenst\My Games\Rigs of Rods')
1487 // - under `app_extra_mod_path` (empty by default)
1488 // - under 'sys_process_dir' (autodetected)
1489 // -------------------------------------------------------------------------------
1490
1491 std::string rg_name;
1492 if (CheckAndReplacePathIgnoreCase(entry, App::sys_user_dir, "USER", rg_name) ||
1493 CheckAndReplacePathIgnoreCase(entry, App::sys_process_dir, "BIN", rg_name) ||
1495 {
1496 return rg_name;
1497 }
1498 else
1499 {
1500 return fmt::format("{{bundle FULL:{}}}", entry->resource_bundle_path);
1501 }
1502}
1503
1505{
1506 // Because we use one resource group per bundle and multiple entries can share the same bundle,
1507 // we need to load the supplementary documents even if the bundle is already loaded.
1508 // -------------------------------------------------------------------------------------------
1509
1510 if (!entry)
1511 return;
1512
1513 ROR_ASSERT(entry->resource_group != "");
1514
1515 if (entry->fext == "skin")
1516 {
1517 this->LoadAssociatedSkinDef(entry);
1518 }
1519 else if (entry->fext == "tuneup")
1520 {
1521 this->LoadAssociatedTuneupDef(entry);
1522 }
1523}
1524
1525bool CacheSystem::IsPathContentDirRoot(const std::string& path) const
1526{
1527 // Helper for `LoadResource()` because OGRE's 'readOnly' flag, see explanation in `ContentManager::InitModCache()`
1528 // --------------------------------------------------------------------------------------------------------------
1529
1530 for (const std::string& cdir: m_content_dirs)
1531 {
1532 if (path == PathCombine(App::sys_user_dir->getStr(), cdir))
1533 {
1534 return true;
1535 }
1536 }
1537 return false;
1538}
1539
1541{
1542 if (!entry)
1543 return;
1544
1545 // Check if already loaded for this entry->
1546 if (entry->resource_group != "")
1547 {
1548 this->LoadSupplementaryDocuments(entry);
1549 return;
1550 }
1551
1552 Ogre::String group = CacheSystem::ComposeResourceGroupName(entry);
1553
1554 // Make "FileSystem" (directory) bundles writable (Default is read-only), except if it's a root directory.
1555 // See explanation of `readOnly` OGRE flag in `ContentManager::InitModCache()`.
1556 bool readonly = entry->resource_bundle_type == "Zip" || this->IsPathContentDirRoot(entry->resource_bundle_path);
1557 bool recursive = false;
1558
1559 // Load now.
1560 try
1561 {
1562 if (entry->fext == "terrn2")
1563 {
1564 // PagedGeometry is hardcoded to use `Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME`
1565 ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/true);
1566 ResourceGroupManager::getSingleton().addResourceLocation(
1567 entry->resource_bundle_path, entry->resource_bundle_type, group, recursive, readonly);
1568 }
1569 else if (entry->fext == "skin")
1570 {
1571 // This is a SkinZip bundle - use `inGlobalPool=false` to prevent resource name conflicts.
1572 // Note: this code won't execute for .skin files in vehicle-bundles because in such case the bundle is already loaded by the vehicle's Cacheentry->
1573 ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/false);
1574 ResourceGroupManager::getSingleton().addResourceLocation(
1575 entry->resource_bundle_path, entry->resource_bundle_type, group, recursive, readonly);
1577 }
1578 else if (entry->fext == "tuneup")
1579 {
1580 // This is a .tuneup bundle - use `inGlobalPool=false` to prevent resource name conflicts.
1581 ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/false);
1582 ResourceGroupManager::getSingleton().addResourceLocation(
1583 entry->resource_bundle_path, entry->resource_bundle_type, group, recursive, readonly);
1585 }
1586 else if (entry->fext == "gadget")
1587 {
1588 // This is a .gadget bundle - use `inGlobalPool=false` to prevent resource name conflicts.
1589 ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/false);
1590 ResourceGroupManager::getSingleton().addResourceLocation(
1591 entry->resource_bundle_path, entry->resource_bundle_type, group, recursive, readonly);
1593 // Allow using builtin include scripts
1595 }
1596 else
1597 {
1598 // A vehicle bundle - use `inGlobalPool=false` to prevent resource name conflicts.
1599 // See bottom 'note' at https://ogrecave.github.io/ogre/api/latest/_resource-_management.html#Resource-Groups
1600 ResourceGroupManager::getSingleton().createResourceGroup(group, /*inGlobalPool=*/false);
1601 ResourceGroupManager::getSingleton().addResourceLocation(
1602 entry->resource_bundle_path, entry->resource_bundle_type, group, recursive, readonly);
1603
1608 }
1609
1610 // Initialize resource group
1611 ResourceGroupManager::getSingleton().initialiseResourceGroup(group);
1612 entry->resource_group = group;
1613
1614 this->LoadSupplementaryDocuments(entry);
1615
1616 // Inform other entries sharing this bundle (i.e. '.skin' entries in vehicle bundles)
1617 for (CacheEntryPtr& i_entry: m_entries)
1618 {
1619 if (i_entry->resource_bundle_path == entry->resource_bundle_path)
1620 {
1621 i_entry->resource_group = group; // Mark as loaded
1622 }
1623 }
1624 }
1625 catch (Ogre::Exception& e)
1626 {
1627 RoR::LogFormat("[RoR] Error while loading '%s', message: %s",
1628 entry->resource_bundle_path.c_str(), e.getFullDescription().c_str());
1629 if (ResourceGroupManager::getSingleton().resourceGroupExists(group))
1630 {
1631 ResourceGroupManager::getSingleton().destroyResourceGroup(group);
1632 }
1633 }
1634}
1635
1637{
1638 if (entry->resource_group == "")
1639 {
1640 return; // Not loaded - nothing to do
1641 }
1642
1643 // IMPORTANT! No actors must use the bundle while reloading, use RoR::MsgType::MSG_EDI_RELOAD_BUNDLE_REQUESTED
1644
1645 this->UnLoadResource(entry);
1646 this->LoadResource(entry); // Will create the same resource group again
1647}
1648
1650{
1651 if (entry->resource_group == "")
1652 {
1653 return; // Not loaded - nothing to do
1654 }
1655
1656 // IMPORTANT! No actors must use the bundle after reloading, use RoR::MsgType::MSG_EDI_RELOAD_BUNDLE_REQUESTED
1657
1658 std::string resource_group = entry->resource_group; // Keep local copy, the CacheEntry will be blanked!
1659 for (CacheEntryPtr& i_entry: m_entries)
1660 {
1661 if (i_entry->resource_group == resource_group)
1662 {
1663 // Delete cached documents - force reload from disk
1664 i_entry->actor_def = nullptr;
1665 i_entry->tuneup_def = nullptr;
1666 i_entry->skin_def = nullptr;
1667 // Mark as unloaded
1668 i_entry->resource_group = "";
1669 }
1670 }
1671
1672 Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(resource_group);
1673}
1674
1675CacheEntryPtr CacheSystem::FetchSkinByName(std::string const & skin_name)
1676{
1677 for (CacheEntryPtr & entry: m_entries)
1678 {
1679 if (entry->dname == skin_name && entry->fext == "skin")
1680 {
1681 return entry;
1682 }
1683 }
1684 return nullptr;
1685}
1686
1688{
1689 // A .skin file defines multiple skins, so we need to locate and update all associated cache entries.
1690 // --------------------------------------------------------------------------------------------------
1691
1692 if (!cache_entry)
1693 return;
1694
1695 ROR_ASSERT(cache_entry->resource_group != ""); // Must be already loaded
1696
1697 if (cache_entry->skin_def != nullptr) // If already parsed, re-use
1698 {
1699 return;
1700 }
1701
1702 try
1703 {
1704 Ogre::DataStreamPtr ds = Ogre::ResourceGroupManager::getSingleton()
1705 .openResource(cache_entry->fname, cache_entry->resource_group);
1706
1707 auto new_skins = RoR::SkinParser::ParseSkins(ds); // Load the '.skin' file
1708 for (auto def: new_skins)
1709 {
1710 for (CacheEntryPtr& entry: m_entries)
1711 {
1712 if (entry->resource_bundle_path == cache_entry->resource_bundle_path
1713 && entry->resource_bundle_type == cache_entry->resource_bundle_type
1714 && entry->fname == cache_entry->fname
1715 && entry->dname == def->name)
1716 {
1717 entry->skin_def = def;
1718 entry->resource_group = cache_entry->resource_group;
1719 }
1720 }
1721 }
1722
1723 if (cache_entry->skin_def == nullptr)
1724 {
1725 RoR::LogFormat("Definition of skin '%s' was not found in file '%s'",
1726 cache_entry->dname.c_str(), cache_entry->fname.c_str());
1727 }
1728 }
1729 catch (Ogre::Exception& oex)
1730 {
1731 RoR::LogFormat("[RoR] Error loading skin file '%s', message: %s",
1732 cache_entry->fname.c_str(), oex.getFullDescription().c_str());
1733 }
1734}
1735
1737{
1738 // A .tuneup file defines multiple tuneups, so we need to locate and update all associated cache entries.
1739 // --------------------------------------------------------------------------------------------------
1740
1741 if (!cache_entry)
1742 return;
1743
1744 ROR_ASSERT(cache_entry->resource_group != ""); // Must be already loaded
1745
1746 if (cache_entry->tuneup_def != nullptr) // If already parsed, re-use
1747 {
1748 return;
1749 }
1750
1751 try
1752 {
1753 Ogre::DataStreamPtr ds = Ogre::ResourceGroupManager::getSingleton()
1754 .openResource(cache_entry->fname, cache_entry->resource_group);
1755
1756 auto new_tuneups = RoR::TuneupUtil::ParseTuneups(ds); // Load the '.tuneup' file
1757 for (auto def: new_tuneups)
1758 {
1759 for (CacheEntryPtr& entry: m_entries)
1760 {
1761 if (entry->resource_bundle_path == cache_entry->resource_bundle_path
1762 && entry->resource_bundle_type == cache_entry->resource_bundle_type
1763 && entry->fname == cache_entry->fname
1764 && entry->dname == def->name)
1765 {
1766 entry->tuneup_def = def;
1767 entry->resource_group = cache_entry->resource_group;
1768 }
1769 }
1770 }
1771
1772 if (cache_entry->tuneup_def == nullptr)
1773 {
1774 RoR::LogFormat("Definition of tuneup '%s' was not found in file '%s'",
1775 cache_entry->dname.c_str(), cache_entry->fname.c_str());
1776 }
1777 }
1778 catch (Ogre::Exception& oex)
1779 {
1780 RoR::LogFormat("[RoR] Error loading tuneup file '%s', message: %s",
1781 cache_entry->fname.c_str(), oex.getFullDescription().c_str());
1782 }
1783}
1784
1786{
1787
1788 // Validate the request
1789 if (!request->cpr_source_entry)
1790 {
1792 fmt::format(_LC("CacheSystem", "Cannot create project '{}' - no source mod specified!"), request->cpr_name));
1793 return nullptr;
1794 }
1795
1796 // Make sure projects folder exists
1798
1799 // Create subfolder
1800 std::string project_path = PathCombine(App::sys_projects_dir->getStr(), request->cpr_name);
1801 if (FolderExists(project_path) && !request->cpr_overwrite)
1802 {
1804 fmt::format(_LC("CacheSystem", "Project directory '{}' already exists!"), request->cpr_name));
1805 return nullptr;
1806 }
1807 CreateFolder(project_path);
1808 if (!FolderExists(project_path))
1809 {
1811 fmt::format(_LC("CacheSystem", "Project directory '{}' could not be created!"), request->cpr_name));
1812 return nullptr;
1813 }
1814
1815 // Check if a project with the same name already exists
1816 CacheEntryPtr project_entry;
1817 bool project_entry_created = false;
1818 if (request->cpr_overwrite)
1819 {
1820 project_entry = this->FindEntryByFilename(LT_Tuneup, /*partial:*/false, fmt::format("{}.tuneup", request->cpr_name));
1821 this->LoadResource(project_entry); // This fills `entry.resource_group`
1822 }
1823
1824 if (!project_entry)
1825 {
1826 // Create preliminary cache entry
1827 project_entry = new CacheEntry();
1828 project_entry_created = true;
1829
1831 {
1832 project_entry->fext = "tuneup"; // Tell modcache what it is.
1833 project_entry->categoryid = CID_Tuneups; // For display in modcache
1834 project_entry->guid = request->cpr_source_entry->guid; // For lookup of tuneups by vehicle GUID.
1835 Ogre::StringUtil::toLowerCase(project_entry->guid);
1836 project_entry->tuneup_associated_filename = request->cpr_source_entry->fname; // For additional filtering of results (GUID marks a family, not individual mod).
1837 Ogre::StringUtil::toLowerCase(project_entry->tuneup_associated_filename);
1838 }
1839 else
1840 {
1841 project_entry->fext = request->cpr_source_entry->fext; // Tell modcache what it is.
1842 project_entry->categoryid = CID_Projects; // To list projects easily from cache
1843 }
1844 project_entry->categoryname = m_categories[project_entry->categoryid];
1845 project_entry->resource_bundle_type = "FileSystem"; // Tell modcache how to load it.
1846 project_entry->resource_bundle_path = project_path; // Tell modcache where to load it from.
1847 project_entry->fname = fmt::format("{}.{}", request->cpr_name, project_entry->fext); // Compose target mod filename
1848 project_entry->dname = request->cpr_name;
1849 project_entry->description = request->cpr_description;
1850 project_entry->number = static_cast<int>(m_entries.size() + 1); // Let's number mods from 1
1851 this->LoadResource(project_entry); // This fills `entry.resource_group`
1852 }
1853
1855 {
1856 // Tuneup projects don't contain any media, just the .tuneup file which lists addonparts to use.
1857
1858 // Prepare the .tuneup document
1859 ROR_ASSERT(request->cpr_source_actor);
1861
1863 tuneup->guid = request->cpr_source_entry->guid; // For lookup of tuneups by vehicle GUID.
1864 tuneup->filename = request->cpr_source_entry->fname; // For additional filtering of results (GUID marks a family, not individual mod).
1865 tuneup->name = request->cpr_name;
1866 tuneup->description = request->cpr_description;
1867 tuneup->thumbnail = request->cpr_source_entry->filecachename;
1868 tuneup->category_id = (CacheCategoryId)project_entry->categoryid;
1869
1870 // Write out the .tuneup file.
1871 Ogre::DataStreamPtr datastream = Ogre::ResourceGroupManager::getSingleton().createResource(
1872 project_entry->fname, project_entry->resource_group, request->cpr_overwrite);
1873 TuneupUtil::ExportTuneup(datastream, tuneup);
1874
1875 // Attach the document to the entry in memory
1876 project_entry->tuneup_def = tuneup;
1877
1878 // In the likely case this was invoked from TopMenubarUI, update it.
1879 if (App::GetGuiManager()->TopMenubar.tuning_savebox_visible)
1880 {
1882 App::GetGuiManager()->TopMenubar.tuning_actor = nullptr; // Force refresh
1883 }
1884 }
1885 else
1886 {
1887
1888 // Create temporary resource group with only the data we want.
1889 std::string temp_rg = "TempProjectSourceRG";
1890 // Apart from `Resources` and resource groups, OGRE also keeps `Archives` in `ArchiveManager`
1891 // These aren't unloaded on destroying resource groups, and keep a 'readOnly' flag (defaults to true).
1892 // Upon loading/creating new resource groups, OGRE complains if the submitted flag doesn't match.
1893 // Since we want to make subdirs (with upacked mods) writable, we must purge subdir-archives now.
1894 bool readonly = request->cpr_source_entry->resource_bundle_type == "Zip";
1895 bool recursive = false;
1896 Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
1898 request->cpr_source_entry->resource_bundle_type, temp_rg, recursive, readonly);
1899 Ogre::ResourceGroupManager::getSingleton().initialiseResourceGroup(temp_rg);
1900
1901 // Copy the files, one by one
1902 Ogre::FileInfoListPtr filelist = Ogre::ResourceGroupManager::getSingleton().findResourceFileInfo(temp_rg, "*.*");
1903 for (size_t i = 0; i < filelist->size(); i++)
1904 {
1905 Ogre::FileInfo fileinfo = filelist->at(i);
1906
1908 {
1909 std::string basename, ext;
1910 Ogre::StringUtil::splitBaseFilename(fileinfo.filename, basename, ext);
1911 // Skip all actor files - the one we care about will be added manually.
1912 if (ext == "truck" || ext == "car" || ext == "load" || ext == "fixed" || ext == "boat" || ext == "airplane" || ext == "train" || ext == "trailer")
1913 {
1914 continue;
1915 }
1916 }
1917
1918 // Render a frame with a progress window on it.
1920 (i+1)/filelist->size(),
1921 fmt::format("Creating project from existing mod...\nCopying file {}/{} '{}'", i, filelist->size(), fileinfo.filename),
1922 /*render_frame:*/true);
1923
1924 // Copy one file
1925 try
1926 {
1927 DataStreamPtr src_ds = ResourceGroupManager::getSingleton().openResource(fileinfo.filename, temp_rg);
1928 DataStreamPtr dst_ds = ResourceGroupManager::getSingleton().createResource(fileinfo.filename, project_entry->resource_group);
1929 std::vector<char> buf(src_ds->size());
1930 size_t read = src_ds->read(buf.data(), src_ds->size());
1931 if (read > 0)
1932 {
1933 dst_ds->write(buf.data(), read);
1934 }
1935 }
1936 catch (Ogre::Exception& oex)
1937 {
1939 fmt::format(_LC("CacheSystem", "Could not copy file '{}' to project '{}', message: {}."),
1940 fileinfo.filename, request->cpr_name, oex.getDescription()));
1941 }
1942 }
1943
1945 Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(temp_rg);
1946 }
1947
1949 {
1950 // Load the project file to perform fixups (name & category)
1957 if (!doc->loadFromResource(request->cpr_source_entry->fname, request->cpr_source_entry->resource_group, flags))
1958 {
1960 fmt::format(_LC("CacheSystem", "Could not load project file '{}' to perform fixups."),
1961 project_entry->fname));
1962 return nullptr;
1963 }
1964
1965 // fixup the document..
1966
1968 // >> seek the name
1969 while (!ctx->isTokString())
1970 {
1971 ctx->seekNextLine();
1972 }
1973 // >> change the name
1974 if (!ctx->endOfFile())
1975 {
1976 ctx->setTokString(0, project_entry->dname);
1977 }
1978 // >> seek fileinfo
1979 while (!ctx->endOfFile() && (!ctx->isTokKeyword() || ctx->getTokKeyword() != "fileinfo"))
1980 {
1981 ctx->seekNextLine();
1982 }
1983 // change the fileinfo param #2 categoryid (if found)
1984 if (!ctx->endOfFile() && ctx->isTokKeyword() && ctx->getTokKeyword() == "fileinfo" && !ctx->endOfFile(2))
1985 {
1986 ctx->setTokFloat(2, CID_Projects);
1987 }
1988
1989 // Write the document
1990 if (!doc->saveToResource(project_entry->fname, project_entry->resource_group))
1991 {
1993 fmt::format(_LC("CacheSystem", "Could not save fixed-up project file '{}'."),
1994 project_entry->fname));
1995 return nullptr;
1996 }
1997 }
1998
1999 if (project_entry_created)
2000 {
2001 // Add the new entry to database
2002 m_entries.push_back(project_entry);
2003 }
2004
2005 // Reload the underlying OGRE resource group to properly pick up all added files.
2006 this->ReLoadResource(project_entry); // OK to call here ~ processing `MSG_EDI_CREATE_PROJECT_REQUESTED`
2007
2008 // notify script
2009 modCacheActivityType activity_type = (project_entry_created) ? MODCACHEACTIVITY_ENTRY_ADDED : MODCACHEACTIVITY_ENTRY_MODIFIED;
2011 /*ints*/ activity_type, project_entry->number, 0, 0,
2012 /*strings*/ project_entry->fname, project_entry->fext);
2013
2014 return project_entry;
2015}
2016
2018{
2019 ROR_ASSERT(request->mpr_target_actor);
2021
2022
2023 switch (request->mpr_type)
2024 {
2026 {
2028 if (request->mpr_target_actor->getWorkingTuneupDef()->use_addonparts.count(request->mpr_subject) != 0)
2029 {
2031 fmt::format(_LC("Tuning", "Addon part '{}' is already equipped."), request->mpr_subject));
2032 return; // Nothing to do!
2033 }
2034
2035 CacheEntryPtr subject_entry = this->FindEntryByFilename(LT_AddonPart, /*partial=*/false, request->mpr_subject);
2036 if (!subject_entry)
2037 {
2039 fmt::format(_LC("Tuning", "Addon part '{}' was not found in mod cache (probably not installed)."), request->mpr_subject));
2040 return; // Nothing to do!
2041 }
2042
2044 {
2045 return; // Error message box already shown
2046 }
2047 else
2048 {
2049 request->mpr_target_actor->getWorkingTuneupDef()->use_addonparts.insert(request->mpr_subject);
2050 }
2051
2052 break;
2053 }
2054
2058 break;
2059
2063 break;
2064
2068 break;
2069
2073 break;
2074
2078 break;
2079
2083 break;
2084
2088 break;
2089
2093 break;
2094
2098 break;
2099
2103 break;
2104
2108 break;
2109
2113 break;
2114
2118 break;
2119
2123 break;
2124
2128 break;
2129
2133 break;
2134
2138 break;
2139
2143 break;
2144
2148 break;
2149
2153 break;
2154
2158 break;
2159
2163 break;
2164
2168 break;
2169
2173 break;
2174
2178 break;
2179
2183 break;
2184
2188 break;
2189
2191 {
2192 // Instead of loading with the saved tuneup directly, keep the autogenerated and sync it with the save.
2193 // That way, subsequent editing doesn't modify the save until user saves again.
2194 CacheEntryPtr save_entry = App::GetCacheSystem()->FindEntryByFilename(LT_Tuneup, /*partial:*/false, request->mpr_subject);
2195 if (!save_entry)
2196 {
2198 fmt::format(_LC("CacheSystem", "Error loading tuneup: file '{}', not found in mod cache"), request->mpr_subject));
2199 return;
2200 }
2201 this->LoadResource(save_entry);
2202 ROR_ASSERT(save_entry->tuneup_def);
2203 request->mpr_target_actor->getWorkingTuneupDef() = save_entry->tuneup_def->clone();
2205 break;
2206 }
2207
2211 break;
2212
2214 {
2215 const bool actor_ok(request->mpr_target_actor && request->mpr_target_actor->ar_state != ActorState::DISPOSED);
2216 if (!actor_ok)
2217 {
2219 fmt::format(_LC("CacheSystem", "Error updating truck file: actor not found or disposed")));
2220 return;
2221 }
2223 const bool entry_ok(entry && entry->resource_bundle_type == "FileSystem");
2224 if (!entry_ok)
2225 {
2227 fmt::format(_LC("CacheSystem", "Error updating truck file: cache entry missing or not a project")));
2228 return;
2229 }
2232 break;
2233 }
2234
2235 default:
2236 break;
2237 }
2238
2239 // Create spawn request while actor still exists
2240 // Note we don't use `ActorModifyRequest::Type::RELOAD` because we don't need the bundle reloaded.
2242 srq->asr_position = Ogre::Vector3(request->mpr_target_actor->getPosition().x, request->mpr_target_actor->getMinHeight(), request->mpr_target_actor->getPosition().z);
2243 srq->asr_rotation = Ogre::Quaternion(Ogre::Degree(270) - Ogre::Radian(request->mpr_target_actor->getRotation()), Ogre::Vector3::UNIT_Y);
2244 srq->asr_config = request->mpr_target_actor->getSectionConfig();
2248 srq->asr_debugview = (int)request->mpr_target_actor->GetGfxActor()->GetDebugView();
2250
2251 // Remove the actor
2253
2254 // Load our actor again, but only after it was deleted.
2256}
2257
2259{
2260
2261 this->UnLoadResource(entry);
2262
2263 // Delete the files, one by one
2264 const std::string DELETEPROJ_TEMP_RG = "DeleteProjectTempRG";
2265 Ogre::ResourceGroupManager::getSingleton().createResourceGroup(DELETEPROJ_TEMP_RG, /*inGlobalPool=*/false);
2266 Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
2267 entry->resource_bundle_path, entry->resource_bundle_type, DELETEPROJ_TEMP_RG, /*recursive=*/false, /*readOnly=*/false);
2268 Ogre::FileInfoListPtr filelist = Ogre::ResourceGroupManager::getSingleton().findResourceFileInfo(DELETEPROJ_TEMP_RG, "*.*");
2269 LOG(fmt::format("[RoR|ModCache] Deleting project '{}' (resource group '{}'), found {} files to erase.", entry->fname, entry->resource_group, filelist->size()));
2270 for (size_t i = 0; i < filelist->size(); i++)
2271 {
2272 Ogre::FileInfo fileinfo = filelist->at(i);
2273 if (!Ogre::FileSystemLayer::removeFile(PathCombine(entry->resource_bundle_path, fileinfo.filename)))
2274 {
2276 fmt::format(_LC("CacheSystem", "Problem deleting project '{}' - could not delete file '{}'"), entry->fname, fileinfo.filename));
2277 }
2278 }
2279
2280 Ogre::ResourceGroupManager::getSingleton().destroyResourceGroup(DELETEPROJ_TEMP_RG);
2281
2282 // Delete the directory itself
2283 if (!Ogre::FileSystemLayer::removeDirectory(entry->resource_bundle_path))
2284 {
2286 fmt::format(_LC("CacheSystem", "Problem deleting project '{}' - could not delete directory '{}'"), entry->fname, entry->resource_bundle_path));
2287 }
2288
2289 // Remove the entry
2290 RoR::EraseIf(m_entries, [entry](CacheEntryPtr& e) { return e == entry; });
2291
2292 // Force update of Tuning menu in TopMenubarUI.
2294}
2295
2297{
2298 Ogre::StringUtil::toLowerCase(query.cqy_search_string);
2299 Ogre::StringUtil::toLowerCase(query.cqy_filter_guid);
2300 Ogre::StringUtil::toLowerCase(query.cqy_filter_target_filename);
2301 std::time_t cur_time = std::time(nullptr);
2302 for (CacheEntryPtr& entry: m_entries)
2303 {
2304 // Filter by GUID
2305 if (query.cqy_filter_guid != "")
2306 {
2307 // Addon parts have `guid` empty
2308 if ((entry->fext == "addonpart" && entry->addonpart_guids.count(query.cqy_filter_guid) == 0) ||
2309 (entry->fext != "addonpart" && entry->guid != query.cqy_filter_guid))
2310 {
2311 continue;
2312 }
2313 }
2314
2315 // Filter by target filename; pass items which have no target filenames listed.
2316 if (query.cqy_filter_target_filename != "")
2317 {
2318 if (entry->fext == "addonpart"
2319 && entry->addonpart_filenames.size() > 0
2320 && entry->addonpart_filenames.count(query.cqy_filter_target_filename) == 0)
2321 {
2322 continue;
2323 }
2324 else if (entry->fext == "tuneup"
2325 && entry->tuneup_associated_filename != ""
2326 && entry->tuneup_associated_filename != query.cqy_filter_target_filename)
2327 {
2328 continue;
2329 }
2330 }
2331
2332 // Filter by entry type
2333 bool add = false;
2334 if (entry->fext == "terrn2")
2335 add = (query.cqy_filter_type == LT_Terrain);
2336 if (entry->fext == "skin")
2337 add = (query.cqy_filter_type == LT_Skin);
2338 else if (entry->fext == "addonpart")
2339 add = (query.cqy_filter_type == LT_AddonPart);
2340 else if (entry->fext == "tuneup")
2341 add = (query.cqy_filter_type == LT_Tuneup);
2342 else if (entry->fext == "assetpack")
2343 add = (query.cqy_filter_type == LT_AssetPack);
2344 else if (entry->fext == "dashboard")
2345 add = (query.cqy_filter_type == LT_DashBoard);
2346 else if (entry->fext == "gadget")
2347 add = (query.cqy_filter_type == LT_Gadget);
2348 else if (entry->fext == "truck")
2349 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Vehicle || query.cqy_filter_type == LT_Truck);
2350 else if (entry->fext == "car")
2351 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Vehicle || query.cqy_filter_type == LT_Truck || query.cqy_filter_type == LT_Car);
2352 else if (entry->fext == "boat")
2353 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Boat);
2354 else if (entry->fext == "airplane")
2355 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Airplane);
2356 else if (entry->fext == "trailer")
2357 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Trailer || query.cqy_filter_type == LT_Extension);
2358 else if (entry->fext == "train")
2359 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Train);
2360 else if (entry->fext == "load")
2361 add = (query.cqy_filter_type == LT_AllBeam || query.cqy_filter_type == LT_Load || query.cqy_filter_type == LT_Extension);
2362
2363 if (!add)
2364 {
2365 continue;
2366 }
2367
2368 // Category usage stats
2369 query.cqy_res_category_usage[entry->categoryid]++;
2370
2372
2373 const bool is_fresh = (cur_time - entry->addtimestamp) < CACHE_FILE_FRESHNESS;
2374 if (is_fresh)
2376
2377 // Filter by category
2378 if ((query.cqy_filter_category_id <= CacheCategoryId::CID_Max && query.cqy_filter_category_id != entry->categoryid) ||
2379 (query.cqy_filter_category_id == CID_Fresh && !is_fresh))
2380 {
2381 continue;
2382 }
2383
2384 // Search
2385 size_t score = 0;
2386 bool match = false;
2387 Str<100> wheels_str;
2388 switch (query.cqy_search_method)
2389 {
2391 if (match = this->Match(score, entry->dname, query.cqy_search_string, 0)) { break; }
2392 if (match = this->Match(score, entry->fname, query.cqy_search_string, 100)) { break; }
2393 if (match = this->Match(score, entry->description, query.cqy_search_string, 200)) { break; }
2394 for (AuthorInfo const& author: entry->authors)
2395 {
2396 if (match = this->Match(score, author.name, query.cqy_search_string, 300)) { break; }
2397 if (match = this->Match(score, author.email, query.cqy_search_string, 400)) { break; }
2398 }
2399 break;
2400
2402 match = this->Match(score, entry->guid, query.cqy_search_string, 0);
2403 break;
2404
2406 for (AuthorInfo const& author: entry->authors)
2407 {
2408 if (match = this->Match(score, author.name, query.cqy_search_string, 0)) { break; }
2409 if (match = this->Match(score, author.email, query.cqy_search_string, 0)) { break; }
2410 }
2411 break;
2412
2414 wheels_str << entry->wheelcount << "x" << entry->propwheelcount;
2415 match = this->Match(score, wheels_str.ToCStr(), query.cqy_search_string, 0);
2416 break;
2417
2419 match = this->Match(score, entry->fname, query.cqy_search_string, 100);
2420 break;
2421
2422 default: // CacheSearchMethod::
2423 match = true;
2424 break;
2425 };
2426
2427 if (match)
2428 {
2429 query.cqy_results.emplace_back(entry, score);
2430 query.cqy_res_last_update = std::max(query.cqy_res_last_update, entry->addtimestamp);
2431 }
2432 }
2433
2434 std::sort(query.cqy_results.begin(), query.cqy_results.end());
2435 return query.cqy_results.size();
2436}
2437
2438bool CacheSystem::Match(size_t& out_score, std::string data, std::string const& query, size_t score)
2439{
2440 Ogre::StringUtil::toLowerCase(data);
2441 size_t pos = data.find(query);
2442 if (pos != std::string::npos)
2443 {
2444 out_score = score + pos;
2445 return true;
2446 }
2447 else
2448 {
2449 return false;
2450 }
2451}
2452
2454{
2455 if (cqr_score == other.cqr_score)
2456 {
2457 Ogre::String first = this->cqr_entry->dname;
2458 Ogre::String second = other.cqr_entry->dname;
2459 Ogre::StringUtil::toLowerCase(first);
2460 Ogre::StringUtil::toLowerCase(second);
2461 return first < second;
2462 }
2463
2464 return cqr_score < other.cqr_score;
2465}
2466
2467void CacheSystem::DeleteResourceBundleByFilename(const std::string& bundle_filename)
2468{
2469 std::string bundle_path;
2470 if (!this->IsRepoFileInstalled(bundle_filename, bundle_path))
2471 {
2473 fmt::format(_LC("CacheSystem", "Could not delete resource bundle '{}': not found in mod cache."), bundle_filename));
2474 return;
2475 }
2476
2477 // Delete (flag 'deleted') all individual cache entries that belong to this bundle
2478 for (const CacheEntryPtr& entry: App::GetCacheSystem()->GetEntries())
2479 {
2480 if (entry->resource_bundle_path == bundle_path)
2481 {
2482 entry->deleted = true; // The object must remain in memory until all references expire.
2484 /*ints*/ MODCACHEACTIVITY_ENTRY_DELETED, entry->number);
2486 fmt::format(_LC("CacheSystem", "Deleted {} '{}' because bundle {} is being removed."), entry->fext, entry->dname, bundle_filename));
2487 }
2488 }
2489
2490 // Erase the 'deleted' entries from memory
2491 RoR::EraseIf(m_entries, [](CacheEntryPtr& e) { return e->deleted; });
2492
2493 // Actually delete the bundle from disk
2494 try
2495 {
2496 Ogre::ArchiveManager::getSingleton().unload(bundle_path);
2497 if (!Ogre::FileSystemLayer::removeFile(bundle_path))
2498 {
2500 fmt::format(_LC("CacheSystem", "Could not delete resource bundle file '{}' from disk."), bundle_filename));
2501 return;
2502 }
2504 /*ints*/ MODCACHEACTIVITY_BUNDLE_DELETED, 0, 0, 0,
2505 /*strings*/ bundle_filename);
2506 }
2507 catch (Ogre::Exception& oex)
2508 {
2510 fmt::format(_LC("CacheSystem", "Could not delete resource bundle '{}', message: {}."), bundle_filename, oex.getDescription()));
2511 }
2512}
2513
2514bool CacheSystem::IsRepoFileInstalled(const std::string& repo_filename, std::string& out_filepath)
2515{
2516 for (const CacheEntryPtr& entry: App::GetCacheSystem()->GetEntries())
2517 {
2518 std::string path, basename;
2519 Ogre::StringUtil::splitFilename(entry->resource_bundle_path, basename, path);
2520 if (basename == repo_filename)
2521 {
2522 out_filepath = entry->resource_bundle_path;
2523 return true;
2524 }
2525 }
2526 return false;
2527}
Central state/object manager and communications hub.
#define ROR_ASSERT(_EXPR)
Definition Application.h:40
#define RGN_TEMP
Definition Application.h:45
#define RGN_CONTENT
Definition Application.h:50
void LOG(const char *msg)
Legacy alias - formerly a macro.
#define RGN_CACHE
Definition Application.h:46
uint32_t BitMask_t
Definition BitFlags.h:7
Ogre::String detectMiniType(String filename, String group)
static bool CheckAndReplacePathIgnoreCase(const CacheEntryPtr &entry, CVar *dir, const std::string &dir_label, std::string &out_rgname)
A database of user-installed content alias 'mods' (vehicles, terrains...)
#define CACHE_FILE_FRESHNESS
Definition CacheSystem.h:43
#define CACHE_FILE
Definition CacheSystem.h:41
#define CACHE_FILE_FORMAT
Definition CacheSystem.h:42
#define _L
Generic text file parser.
Manager for all visuals belonging to a single actor.
#define _LC(ctx, str)
Definition Language.h:38
Platform-specific utilities. We use narrow UTF-8 encoded strings as paths. Inspired by http://utf8eve...
Checks the rig-def file syntax and loads data to memory.
Core data structures for simulation; Everything affected by by either physics, network or user intera...
The vehicle tuning system; applies addonparts and user overrides to vehicles.
Checks the rig-def file syntax and pulls data to File object.
SequentialImporter * GetSequentialImporter()
RigDef::DocumentPtr GetFile()
void ProcessOgreStream(Ogre::DataStream *stream, Ogre::String resource_group)
CacheEntryPtr & getUsedSkinEntry()
Definition Actor.cpp:4878
GfxActor * GetGfxActor()
Definition Actor.h:309
float getRotation()
Definition Actor.cpp:355
float getMinHeight(bool skip_virtual_nodes=true)
Definition Actor.cpp:1568
void removeWorkingTuneupDef()
Deletes the working tuneup def object if it exists.
Definition Actor.cpp:4907
Ogre::Vector3 getPosition()
Definition Actor.cpp:370
void ensureWorkingTuneupDef()
Creates a working tuneup def if it doesn't exist yet.
Definition Actor.cpp:4898
ActorState ar_state
Definition Actor.h:518
void propagateNodeBeamChangesToDef()
Back-propagates changes done by N/B-utils UI to the def-document.
Ogre::String getSectionConfig()
Definition Actor.h:270
CacheEntryPtr & getUsedActorEntry()
The actor entry itself.
Definition Actor.cpp:4873
TuneupDefPtr & getWorkingTuneupDef()
Definition Actor.cpp:4893
void ExportActorDef(RigDef::DocumentPtr def, std::string filename, std::string rg_name)
static bool DoubleCheckForAddonpartConflict(ActorPtr target_actor, CacheEntryPtr addonpart_entry)
Quake-style console variable, defined in RoR.cfg or crated via Console UI and scripts.
Definition CVar.h:53
std::string const & getStr() const
Definition CVar.h:95
bool getBool() const
Definition CVar.h:98
void setVal(T val)
Definition CVar.h:72
Definition CacheSystem.h:56
float minrpm
float truckmass
int wheelcount
CacheEntryID_t number
Sequential number, assigned internally, used by Selector-GUI.
Definition CacheSystem.h:64
bool custom_particles
std::vector< Ogre::String > sectionconfigs
Ogre::String fname
filename
Definition CacheSystem.h:67
int version
file's version
Definition CacheSystem.h:78
Ogre::String description
Ogre::String fext
file's extension
Definition CacheSystem.h:69
std::set< std::string > addonpart_guids
GUIDs of all vehicles this addonpart is used with.
Definition CacheSystem.h:98
Ogre::String fpath
filepath relative to the .zip file
Definition CacheSystem.h:66
std::string tuneup_associated_filename
Value of 'filename' field in the tuneup file; always lowercase.
int categoryid
category id
Definition CacheSystem.h:72
Ogre::String dname
name parsed from the file
Definition CacheSystem.h:70
int commandscount
int propscount
int exhaustscount
std::vector< AuthorInfo > authors
authors
Definition CacheSystem.h:86
bool hasSubmeshs
int flexbodiescount
bool importcommands
int turbojetcount
SkinDocumentPtr skin_def
Cached skin info, added on first use or during cache rebuild.
Definition CacheSystem.h:92
std::time_t addtimestamp
timestamp when this file was added to the cache
Definition CacheSystem.h:75
bool forwardcommands
int numgears
bool rescuer
CacheEntry()
default constructor resets the data.
int hydroscount
bool customtach
Ogre::String tags
Ogre::String resource_group
Resource group of the loaded bundle. Empty if not loaded yet.
Definition CacheSystem.h:89
TuneupDefPtr tuneup_def
Cached tuning info, added on first use or during cache rebuild.
Definition CacheSystem.h:93
std::set< std::string > addonpart_filenames
File names of all vehicles this addonpart is used with. If empty, any filename goes.
Definition CacheSystem.h:99
int usagecounter
how much it was used already
Definition CacheSystem.h:85
Ogre::String fname_without_uid
filename
Definition CacheSystem.h:68
float torque
std::string resource_bundle_type
Archive type recognized by OGRE resource system: 'FileSystem' or 'Zip'.
Definition CacheSystem.h:80
std::string resource_bundle_path
Path of ZIP or directory which contains the media. Shared between CacheEntries, loaded only once.
Definition CacheSystem.h:81
int nodecount
int beamcount
int propwheelcount
float loadmass
int soundsourcescount
int fixescount
int shockcount
char enginetype
std::string default_skin
bool deleted
is this mod deleted?
Definition CacheSystem.h:84
Ogre::String categoryname
category name
Definition CacheSystem.h:73
ActorType driveable
int flarescount
~CacheEntry()
float maxrpm
Ogre::String filecachename
preview image filename
Definition CacheSystem.h:87
int rotatorscount
int fileformatversion
int turbopropscount
RigDef::DocumentPtr actor_def
Cached actor definition (aka truckfile) after first spawn.
Definition CacheSystem.h:91
int wingscount
std::time_t filetime
filetime
Definition CacheSystem.h:83
Ogre::String uniqueid
file's unique id
Definition CacheSystem.h:76
Ogre::String guid
global unique id; Type "addonpart" leaves this empty and uses addonpart_guids; Always lowercase.
Definition CacheSystem.h:77
void ReLoadResource(CacheEntryPtr &t)
Forces reloading the associated bundle.
void GenerateFileCache(CacheEntryPtr &entry, Ogre::String group)
void LoadSupplementaryDocuments(CacheEntryPtr &t)
Loads the associated .truck*, .skin and .tuneup files.
std::vector< std::string > m_content_dirs
the various mod directories we track in the cache system
std::vector< Ogre::String > m_known_extensions
the extensions we track in the cache system
void FillTerrainDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds, Ogre::String fname)
void UnLoadResource(CacheEntryPtr &t)
Unloads associated bundle, destroying all spawned actors.
void ParseZipArchives(Ogre::String group)
bool Match(size_t &out_score, std::string data, std::string const &query, size_t)
void RemoveFileCache(CacheEntryPtr &entry)
std::set< Ogre::String > m_resource_paths
A temporary list of existing resource paths.
void ModifyProject(ModifyProjectRequest *request)
std::time_t m_update_time
Ensures that all inserted files share the same timestamp.
const std::vector< CacheEntryPtr > & GetEntries() const
void LoadAssetPack(CacheEntryPtr &t_dest, Ogre::String const &assetpack_filename)
Adds asset pack to the requesting cache entry's resource group.
static Ogre::String StripSHA1fromString(Ogre::String sha1str)
void FillTuneupDetailInfo(CacheEntryPtr &entry, TuneupDefPtr &tuneup_def)
void FillTruckDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds, Ogre::String fname, Ogre::String group)
void FillSkinDetailInfo(CacheEntryPtr &entry, std::shared_ptr< SkinDocument > &skin_def)
std::string m_filenames_hash_loaded
hash from cachefile, for quick update detection
std::vector< CacheEntryPtr > m_entries
bool ParseKnownFiles(Ogre::String group)
void FillGadgetDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
CacheEntryPtr FindEntryByFilename(RoR::LoaderType type, bool partial, const std::string &_filename_maybe_bundlequalified)
Returns NULL if none found; "Bundle-qualified" format also specifies the ZIP/directory in modcache,...
Ogre::String GetPrettyName(Ogre::String fname)
void GenerateHashFromFilenames()
For quick detection of added/removed content.
void ImportEntryFromJson(rapidjson::Value &j_entry, CacheEntryPtr &out_entry)
void DeleteProject(CacheEntryPtr &entry)
void ParseSingleZip(Ogre::String path)
std::string ActorTypeToName(ActorType driveable)
CacheEntryPtr FetchSkinByName(std::string const &skin_name)
static Ogre::String StripUIDfromString(Ogre::String uidstr)
size_t Query(CacheQuery &query)
void LoadAssociatedTuneupDef(CacheEntryPtr &cache_entry)
Loads+parses the .tuneup file and updates all related CacheEntries.
std::map< int, Ogre::String > m_categories
void LoadAssociatedSkinDef(CacheEntryPtr &cache_entry)
Loads+parses the .skin file and updates all related CacheEntries.
void AddFile(Ogre::String group, Ogre::FileInfo f, Ogre::String ext)
CacheEntryPtr CreateProject(CreateProjectRequest *request)
Creates subdirectory in 'My Games\Rigs of Rods\projects', pre-populates it with files and adds modcac...
bool IsPathContentDirRoot(const std::string &path) const
bool IsRepoFileInstalled(const std::string &repo_filename, std::string &out_filepath)
Checks whether a ZIP archive from the online repository is installed in the local modcache.
CacheEntryPtr GetEntryByNumber(int modid)
void FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
CacheValidity EvaluateCacheValidity()
void ExportEntryToJson(rapidjson::Value &j_entries, rapidjson::Document &j_doc, CacheEntryPtr const &entry)
void FillDashboardDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
std::string m_filenames_hash_generated
stores hash over the content, for quick update detection
void LoadResource(CacheEntryPtr &t)
Loads the associated resource bundle if not already done.
static std::string ComposeResourceGroupName(const CacheEntryPtr &entry)
void FillAssetPackDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds)
void LoadModCache(CacheValidity validity)
CacheValidity LoadCacheFileJson()
void DeleteResourceBundleByFilename(const std::string &bundle_filename)
Deletes all CacheEntries which share the given resource bundle (ZIP or directory).
@ CONSOLE_MSGTYPE_ACTOR
Parsing/spawn/simulation messages for actors.
Definition Console.h:63
@ CONSOLE_MSGTYPE_INFO
Generic message.
Definition Console.h:60
@ CONSOLE_MSGTYPE_TERRN
Parsing/spawn/simulation messages for terrain.
Definition Console.h:64
void putMessage(MessageArea area, MessageType type, std::string const &msg, std::string icon="")
Definition Console.cpp:103
@ CONSOLE_SYSTEM_ERROR
Definition Console.h:52
@ CONSOLE_SYSTEM_NOTICE
Definition Console.h:51
@ CONSOLE_SYSTEM_WARNING
Definition Console.h:53
void AddResourcePack(ResourcePack const &resource_pack, std::string const &override_rgn="")
Loads resources if not already loaded.
bool DeleteDiskFile(std::string const &filename, std::string const &rg_name)
std::string ListAllUserContent()
Used by ModCache for quick detection of added/removed content.
void InitManagedMaterials(std::string const &rg_name)
void SetProgress(int _percent, const std::string &_text="", bool render_frame=true)
ActorPtr tuning_actor
Detecting actor change to update cached values.
bool tuning_savebox_visible
User pressed 'save active' to open savebox.
GUI::TopMenubar TopMenubar
Definition GUIManager.h:133
GUI::LoadingWindow LoadingWindow
Definition GUIManager.h:132
GUI::GameMainMenu GameMainMenu
Definition GUIManager.h:117
void PushMessage(Message m)
Doesn't guarantee order! Use ChainMessage() if order matters.
void ChainMessage(Message m)
Add to last pushed message's chain.
ActorManager * GetActorManager()
DebugViewType GetDebugView() const
Definition GfxActor.h:145
static std::vector< SkinDocumentPtr > ParseSkins(Ogre::DataStreamPtr &stream)
Wrapper for classic c-string (local buffer) Refresher: strlen() excludes '\0' terminator; strncat() A...
Definition Str.h:36
const char * ToCStr() const
Definition Str.h:46
Terrn2DocumentPtr LoadTerrn2(Ogre::DataStreamPtr &ds)
static void ExportTuneup(Ogre::DataStreamPtr &stream, TuneupDefPtr &tuneup)
static std::vector< TuneupDefPtr > ParseTuneups(Ogre::DataStreamPtr &stream)
std::time_t GetFileLastModifiedTime(std::string const &path)
void CreateFolder(const char *path)
Path must be UTF-8 encoded.
bool FolderExists(const char *path)
Path must be UTF-8 encoded.
std::string PathCombine(std::string a, std::string b)
bool FileExists(const char *path)
Path must be UTF-8 encoded.
ActorType
< Aka 'Driveable'
Definition SimData.h:81
@ MACHINE
its a machine
Definition SimData.h:88
@ TRUCK
its a truck (or other land vehicle)
Definition SimData.h:85
@ NOT_DRIVEABLE
not drivable at all
Definition SimData.h:84
@ BOAT
its a boat
Definition SimData.h:87
@ AIRPLANE
its an airplane
Definition SimData.h:86
@ AI
machine controlled by an Artificial Intelligence
Definition SimData.h:89
@ MSG_SIM_SPAWN_ACTOR_REQUESTED
Payload = RoR::ActorSpawnRequest* (owner)
@ MSG_SIM_DELETE_ACTOR_REQUESTED
Payload = RoR::ActorPtr* (owner)
@ DISPOSED
removed from simulation, still in memory to satisfy pointers.
void TRIGGER_EVENT_ASYNC(scriptEvents type, int arg1, int arg2ex=0, int arg3ex=0, int arg4ex=0, std::string arg5ex="", std::string arg6ex="", std::string arg7ex="", std::string arg8ex="")
Asynchronously (via MSG_SIM_SCRIPT_EVENT_TRIGGERED) invoke script function eventCallbackEx(),...
std::shared_ptr< Document > DocumentPtr
ContentManager * GetContentManager()
CVar * sys_projects_dir
GUIManager * GetGuiManager()
GameContext * GetGameContext()
CVar * sys_user_dir
Console * GetConsole()
CacheSystem * GetCacheSystem()
CVar * diag_log_console_echo
CVar * app_extra_mod_path
CVar * sys_process_dir
Ogre::String HashData(const char *key, int len)
Definition Utils.cpp:54
CacheValidity
void Log(const char *msg)
The ultimate, application-wide logging function. Adds a line (any length) in 'RoR....
LoaderType
< Search mode for ModCache::Query() & Operation mode for GUI::MainSelector
@ LT_Airplane
@ LT_Tuneup
@ LT_AssetPack
@ LT_Boat
@ LT_DashBoard
@ LT_Train
@ LT_AddonPart
@ LT_Skin
@ LT_Truck
@ LT_Gadget
@ LT_Car
@ LT_Load
@ LT_Terrain
@ LT_Trailer
@ LT_Vehicle
@ LT_Extension
@ LT_AllBeam
@ SAVE_TUNEUP
Dumps .tuneup file with CID_Tuneup from source actor, will not overwrite existing unless explicitly i...
@ ACTOR_PROJECT
Like DEFAULT but fixes up name + category in the truckfile.
@ TUNEUP_PROTECTED_WHEEL_RESET
'subject_id' is wheel ID.
@ TUNEUP_FORCED_VCAM_ROLE_RESET
'subject_id' is video camera ID.
@ TUNEUP_FORCEREMOVE_MANAGEDMAT_RESET
'subject' is managed material name.
@ TUNEUP_FORCEREMOVE_FLEXBODY_SET
'subject_id' is flexbody ID.
@ TUNEUP_PROTECTED_WHEEL_SET
'subject_id' is wheel ID.
@ TUNEUP_PROTECTED_MANAGEDMAT_SET
'subject' is managed material name.
@ TUNEUP_FORCEREMOVE_EXHAUST_SET
'subject_id' is exhaust ID.
@ TUNEUP_USE_ADDONPART_RESET
'subject' is addonpart filename.
@ TUNEUP_PROTECTED_PROP_SET
'subject_id' is prop ID.
@ TUNEUP_USE_ADDONPART_SET
'subject' is addonpart filename.
@ TUNEUP_FORCEREMOVE_PROP_SET
'subject_id' is prop ID.
@ TUNEUP_PROTECTED_EXHAUST_SET
'subject_id' is exhaust ID.
@ TUNEUP_PROTECTED_FLARE_SET
'subject_id' is flare ID.
@ TUNEUP_FORCED_WHEEL_SIDE_RESET
'subject_id' is wheel ID.
@ TUNEUP_FORCEREMOVE_MANAGEDMAT_SET
'subject' is managed material name.
@ TUNEUP_PROTECTED_FLEXBODY_RESET
'subject_id' is flexbody ID.
@ TUNEUP_PROTECTED_FLEXBODY_SET
'subject_id' is flexbody ID.
@ TUNEUP_FORCED_WHEEL_SIDE_SET
'subject_id' is wheel ID, 'value_int' is RoR::WheelSide
@ ACTOR_UPDATE_DEF_DOCUMENT
'subject' is empty; 'target_actor' is the actual subject. Propagates modifications from the live acto...
@ TUNEUP_PROTECTED_EXHAUST_RESET
'subject_id' is exhaust ID.
@ TUNEUP_PROTECTED_MANAGEDMAT_RESET
'subject' is managed material name.
@ TUNEUP_FORCED_VCAM_ROLE_SET
'subject_id' is video camera ID, 'value_int' is RoR::VideoCamRole
@ PROJECT_RESET_TUNEUP
'subject' is empty. This resets the auto-generated tuneup to orig. values.
@ TUNEUP_FORCEREMOVE_FLARE_RESET
'subject_id' is flare ID.
@ TUNEUP_PROTECTED_PROP_RESET
'subject_id' is prop ID.
@ TUNEUP_FORCEREMOVE_FLEXBODY_RESET
'subject_id' is flexbody ID.
@ TUNEUP_FORCEREMOVE_PROP_RESET
'subject_id' is prop ID.
@ TUNEUP_PROTECTED_FLARE_RESET
'subject_id' is flare ID.
@ TUNEUP_FORCEREMOVE_EXHAUST_RESET
'subject_id' is exhaust ID.
@ PROJECT_LOAD_TUNEUP
'subject' is tuneup filename. This overwrites the auto-generated tuneup with the save.
@ TUNEUP_FORCEREMOVE_FLARE_SET
'subject_id' is flare ID.
modCacheActivityType
Argument #1 of script event RoR::SE_GENERIC_MODCACHE_ACTIVITY
@ MODCACHEACTIVITY_BUNDLE_DELETED
Args: #1 type, #2 entry number.
@ MODCACHEACTIVITY_ENTRY_DELETED
Flagged as deleted, managed by shared pointers; Args: #1 type, #2 entry number, –,...
@ MODCACHEACTIVITY_ENTRY_MODIFIED
Args: #1 type, #2 entry number, –, –, #5 fname, #6 fext.
@ MODCACHEACTIVITY_ENTRY_ADDED
Args: #1 type, #2 entry number, –, –, #5 fname, #6 fext.
@ AUTHORS
Partial match in: author name/email.
@ FILENAME
Partial match in file name.
@ WHEELS
Wheel configuration, i.e. 4x4.
@ GUID
Partial match in: guid.
@ FULLTEXT
Partial match in: name, filename, description, author name/mail.
WheelSide
Used by rig-def/addonpart/tuneup formats to specify wheel rim mesh orientation.
std::shared_ptr< Terrn2Document > Terrn2DocumentPtr
VideoCamRole
void LogFormat(const char *format,...)
Improved logging utility. Uses fixed 2Kb buffer.
RefCountingObjectPtr< Actor > ActorPtr
@ SE_GENERIC_MODCACHE_ACTIVITY
Triggered when status of modcache changes, args: #1 type, #2 entry number, for other args see RoR::mo...
void EraseIf(std::vector< T, A > &c, Predicate pred)
Definition Utils.h:75
void SplitBundleQualifiedFilename(const std::string &bundleQualifiedFilename, std::string &out_bundleName, std::string &out_filename)
Definition Utils.cpp:239
CacheCategoryId
@ CID_None
@ CID_Max
SPECIAL VALUE - Maximum allowed to be present in any mod files.
@ CID_Unsorted
@ CID_Projects
For truck files under 'projects/' directory, to allow listing from editors.
@ CID_Fresh
@ CID_Tuneups
For unsorted tuneup files.
@ CID_All
std::time_t getTimeStamp()
Definition Utils.cpp:77
std::vector< float > gear_ratios
Ogre::String unique_id
Ogre::String asr_config
Definition SimData.h:836
CacheEntryPtr asr_cache_entry
Optional, overrides 'asr_filename' and 'asr_cache_entry_num'.
Definition SimData.h:834
TuneupDefPtr asr_working_tuneup
Only filled when editing tuneup via Tuning menu.
Definition SimData.h:842
Ogre::Vector3 asr_position
Definition SimData.h:837
CacheEntryPtr asr_skin_entry
Definition SimData.h:840
Ogre::Quaternion asr_rotation
Definition SimData.h:838
@ USER
Direct selection by user via GUI.
Ogre::String type
Definition CacheSystem.h:50
Ogre::String name
Definition CacheSystem.h:51
Ogre::String email
Definition CacheSystem.h:52
std::string cqy_filter_target_filename
Exact match (case-insensitive); leave empty to disable (currently only used with addonparts)
std::string cqy_filter_guid
Exact match (case-insensitive); leave empty to disable.
std::map< int, size_t > cqy_res_category_usage
Total usage (ignores search params + category filter)
std::time_t cqy_res_last_update
std::string cqy_search_string
CacheSearchMethod cqy_search_method
std::vector< CacheQueryResult > cqy_results
RoR::LoaderType cqy_filter_type
CacheQueryResult(CacheEntryPtr entry, size_t score)
CacheEntryPtr cqr_entry
bool operator<(CacheQueryResult const &other) const
static const ResourcePack TEXTURES
static const ResourcePack MESHES
static const ResourcePack SCRIPTS
static const ResourcePack MATERIALS
Creates subdirectory in 'My Games\Rigs of Rods\projects', pre-populates it with files and adds modcac...
std::string cpr_description
Optional, implemented for tuneups.
CreateProjectRequestType cpr_type
CacheEntryPtr cpr_source_entry
The original mod to copy files from.
std::string cpr_name
Directory and also the mod file (without extension).
ActorPtr cpr_source_actor
Only for type SAVE_TUNEUP
bool isTokKeyword(int offset=0) const
std::string getTokKeyword(int offset=0) const
float getTokFloat(int offset=0) const
bool endOfFile(int offset=0) const
std::string getTokString(int offset=0) const
bool isTokString(int offset=0) const
bool isTokInt(int offset=0) const
int getTokInt(int offset=0) const
bool setTokString(int offset, const std::string &str)
bool setTokFloat(int offset, float val)
static const BitMask_t OPTION_ALLOW_SLASH_COMMENTS
Allow comments starting with //.
static const BitMask_t OPTION_ALLOW_SEPARATOR_COLON
Allow ':' as separator between tokens.
virtual bool saveToResource(std::string resource_name, std::string resource_group_name)
virtual void loadFromDataStream(Ogre::DataStreamPtr datastream, BitMask_t options=0)
virtual bool loadFromResource(std::string resource_name, std::string resource_group_name, BitMask_t options=0)
static const BitMask_t OPTION_FIRST_LINE_IS_TITLE
First non-empty & non-comment line is a naked string with spaces.
static const BitMask_t OPTION_ALLOW_NAKED_STRINGS
Allow strings without quotes, for backwards compatibility.
static const BitMask_t OPTION_PARENTHESES_CAPTURE_SPACES
If non-empty NAKED string encounters '(', following spaces will be captured until matching ')' is fou...
Unified game event system - all requests and state changes are reported using a message.
Definition GameContext.h:52
ModifyProjectRequestType mpr_type
std::string description
std::string filename
target vehicle filename
std::set< WheelID_t > protected_wheels
Wheels that cannot be altered via 'addonpart_tweak_wheel'.
std::string thumbnail
std::set< FlareID_t > protected_flares
Flares which cannot be altered via 'addonpart_unwanted_flare' directive.
std::set< std::string > protected_managedmats
Managed materials which cannot be altered via 'addonpart_tweak_managedmaterial' directive.
std::set< ExhaustID_t > protected_exhausts
Exhausts which cannot be altered via 'addonpart_unwanted_exhaust' directive.
std::map< VideoCameraID_t, VideoCamRole > force_video_cam_roles
UI overrides.
std::set< FlexbodyID_t > protected_flexbodies
Flexbodies which cannot be altered via 'addonpart_tweak_flexbody' or 'addonpart_unwanted_flexbody' di...
std::set< std::string > force_remove_managedmats
User unticked an UI checkbox in Tuning menu, section Managed Materials.
TuneupDefPtr clone()
std::set< std::string > use_addonparts
Addonpart filenames.
std::set< ExhaustID_t > force_remove_exhausts
User unticked an UI checkbox in Tuning menu, section Exhausts.
CacheCategoryId category_id
std::set< PropID_t > force_remove_props
UI overrides.
std::set< FlareID_t > force_remove_flares
User unticked an UI checkbox in Tuning menu, section Flares.
std::set< PropID_t > protected_props
Props which cannot be altered via 'addonpart_tweak_prop' or 'addonpart_unwanted_prop' directive.
std::string guid
target vehicle GUID
std::set< FlexbodyID_t > force_remove_flexbodies
UI overrides.
std::string author_name
std::map< WheelID_t, WheelSide > force_wheel_sides
UI overrides.