OpenTTD Source 20260311-master-g511d3794ce
script_scanner.cpp
Go to the documentation of this file.
1/*
2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
10#include "../stdafx.h"
11#include "../debug.h"
12#include "../string_func.h"
13#include "../settings_type.h"
14
16#include "script_scanner.hpp"
17#include "script_info.hpp"
18#include "script_fatalerror.hpp"
19
21#include "../3rdparty/md5/md5.h"
22#include "../tar_type.h"
23
24#include "../safeguards.h"
25
26bool ScriptScanner::AddFile(const std::string &filename, size_t, const std::string &tar_filename)
27{
28 this->main_script = filename;
29 this->tar_file = tar_filename;
30
31 auto p = this->main_script.find_last_of(PATHSEPCHAR);
32 this->main_script.erase(p != std::string::npos ? p + 1 : 0);
33 this->main_script += "main.nut";
34
35 if (!FioCheckFileExists(filename, this->subdir) || !FioCheckFileExists(this->main_script, this->subdir)) return false;
36
37 this->ResetEngine();
38 try {
39 this->engine->LoadScript(filename);
40 } catch (Script_FatalError &e) {
41 Debug(script, 0, "Fatal error '{}' when trying to load the script '{}'.", e.GetErrorMessage(), filename);
42 return false;
43 }
44 return true;
45}
46
47ScriptScanner::ScriptScanner() = default;
48
50{
51 this->engine->Reset();
52 this->engine->SetGlobalPointer(this);
53 this->RegisterAPI(*this->engine);
54}
55
56void ScriptScanner::Initialize(std::string_view name)
57{
58 this->engine = std::make_unique<Squirrel>(name);
59
60 this->RescanDir();
61
62 this->ResetEngine();
63}
64
65ScriptScanner::~ScriptScanner()
66{
67 this->Reset();
68}
69
71{
72 /* Forget about older scans */
73 this->Reset();
74
75 /* Scan for scripts */
76 this->Scan(this->GetFileName(), this->GetDirectory());
77}
78
80{
81 this->info_list.clear();
82 this->info_single_list.clear();
83 this->info_vector.clear();
84}
85
86void ScriptScanner::RegisterScript(std::unique_ptr<ScriptInfo> &&info)
87{
88 std::string script_original_name = this->GetScriptName(*info);
89 std::string script_name = fmt::format("{}.{}", script_original_name, info->GetVersion());
90
91 /* Check if GetShortName follows the rules */
92 if (info->GetShortName().size() != 4) {
93 Debug(script, 0, "The script '{}' returned a string from GetShortName() which is not four characters. Unable to load the script.", info->GetName());
94 return;
95 }
96
97 if (auto it = this->info_list.find(script_name); it != this->info_list.end()) {
98 /* This script was already registered */
99#ifdef _WIN32
100 /* Windows doesn't care about the case */
101 if (StrEqualsIgnoreCase(it->second->GetMainScript(), info->GetMainScript())) {
102#else
103 if (it->second->GetMainScript() == info->GetMainScript()) {
104#endif
105 return;
106 }
107
108 Debug(script, 1, "Registering two scripts with the same name and version");
109 Debug(script, 1, " 1: {}", it->second->GetMainScript());
110 Debug(script, 1, " 2: {}", info->GetMainScript());
111 Debug(script, 1, "The first is taking precedence.");
112
113 return;
114 }
115
116 ScriptInfo *script_info = this->info_vector.emplace_back(std::move(info)).get();
117 this->info_list[script_name] = script_info;
118
119 if (!script_info->IsDeveloperOnly() || _settings_client.gui.ai_developer_tools) {
120 /* Add the script to the 'unique' script list, where only the highest version
121 * of the script is registered. */
122 auto it = this->info_single_list.find(script_original_name);
123 if (it == this->info_single_list.end()) {
124 this->info_single_list[script_original_name] = script_info;
125 } else if (it->second->GetVersion() < script_info->GetVersion()) {
126 it->second = script_info;
127 }
128 }
129}
130
131void ScriptScanner::GetConsoleList(std::back_insert_iterator<std::string> &output_iterator, bool newest_only) const
132{
133 fmt::format_to(output_iterator, "List of {}:\n", this->GetScannerName());
134 const ScriptInfoList &list = newest_only ? this->info_single_list : this->info_list;
135 for (const auto &item : list) {
136 ScriptInfo *i = item.second;
137 fmt::format_to(output_iterator, "{:>10} (v{:d}): {}\n", i->GetName(), i->GetVersion(), i->GetDescription());
138 }
139 fmt::format_to(output_iterator, "\n");
140}
141
144 MD5Hash md5sum;
146
153
154 /* Add the file and calculate the md5 sum. */
155 bool AddFile(const std::string &filename, size_t, const std::string &) override
156 {
157 Md5 checksum;
158 uint8_t buffer[1024];
159 size_t len, size;
160
161 /* Open the file ... */
162 auto f = FioFOpenFile(filename, "rb", this->dir, &size);
163 if (!f.has_value()) return false;
164
165 /* ... calculate md5sum... */
166 while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
167 size -= len;
168 checksum.Append(buffer, len);
169 }
170
171 MD5Hash tmp_md5sum;
172 checksum.Finish(tmp_md5sum);
173
174 /* ... and xor it to the overall md5sum. */
175 this->md5sum ^= tmp_md5sum;
176
177 return true;
178 }
179};
180
190static bool IsSameScript(const ContentInfo &ci, bool md5sum, const ScriptInfo &info, Subdirectory dir)
191{
192 uint32_t id = 0;
193 auto str = std::string_view{info.GetShortName()}.substr(0, 4);
194 for (size_t j = 0; j < str.size(); j++) id |= static_cast<uint8_t>(str[j]) << (8 * j);
195
196 if (id != ci.unique_id) return false;
197 if (!md5sum) return true;
198
199 ScriptFileChecksumCreator checksum(dir);
200 const auto &tar_filename = info.GetTarFile();
201 TarList::iterator iter;
202 if (!tar_filename.empty() && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) {
203 /* The main script is in a tar file, so find all files that
204 * are in the same tar and add them to the MD5 checksumming. */
205 for (const auto &tar : _tar_filelist[dir]) {
206 /* Not in the same tar. */
207 if (tar.second.tar_filename != iter->first) continue;
208
209 /* Check the extension. */
210 auto ext = tar.first.rfind('.');
211 if (ext == std::string_view::npos || !StrEqualsIgnoreCase(tar.first.substr(ext), ".nut")) continue;
212
213 checksum.AddFile(tar.first, 0, tar_filename);
214 }
215 } else {
216 /* There'll always be at least 1 path separator character in a script
217 * main script name as the search algorithm requires the main script to
218 * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
219 const std::string &main_script = info.GetMainScript();
220 std::string path = main_script.substr(0, main_script.find_last_of(PATHSEPCHAR));
221 checksum.Scan(".nut", path);
222 }
223
224 return ci.md5sum == checksum.md5sum;
225}
226
227bool ScriptScanner::HasScript(const ContentInfo &ci, bool md5sum)
228{
229 for (const auto &item : this->info_list) {
230 if (IsSameScript(ci, md5sum, *item.second, this->GetDirectory())) return true;
231 }
232 return false;
233}
234
235std::optional<std::string_view> ScriptScanner::FindMainScript(const ContentInfo &ci, bool md5sum)
236{
237 for (const auto &item : this->info_list) {
238 if (IsSameScript(ci, md5sum, *item.second, this->GetDirectory())) return item.second->GetMainScript();
239 }
240 return std::nullopt;
241}
Helper for scanning for files with a given name.
Definition fileio_func.h:37
uint Scan(std::string_view extension, Subdirectory sd, bool tars=true, bool recursive=true)
Scan for files with the given extension in the given search path.
Definition fileio.cpp:1124
Subdirectory subdir
The current sub directory we are searching through.
Definition fileio_func.h:39
All static information from an Script like name, version, etc.
const std::string & GetName() const
Get the Name of the script.
const std::string & GetMainScript() const
Get the filename of the main.nut script.
const std::string & GetShortName() const
Get the 4 character long short name of the script.
int GetVersion() const
Get the version of the script.
const std::string & GetTarFile() const
Get the filename of the tar the script is in.
const std::string & GetDescription() const
Get the description of the script.
virtual bool IsDeveloperOnly() const
Can this script be selected by developers only?
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
Add a file with the given filename.
virtual std::string_view GetScannerName() const =0
Get the type of the script, in plural.
std::vector< std::unique_ptr< ScriptInfo > > info_vector
The known ScriptInfo objects.
std::string tar_file
If, which tar file the script was in.
virtual void RegisterAPI(class Squirrel &engine)=0
Register the API for this ScriptInfo.
void Reset()
Reset all allocated lists.
virtual void Initialize()=0
Initialize the scanner by scanning for scripts and creating dummies if needed.
virtual std::string GetScriptName(ScriptInfo &info)=0
Get the script name how to store the script in memory.
void RescanDir()
Rescan the script dir.
virtual Subdirectory GetDirectory() const =0
Get the directory to scan in.
std::string main_script
The full path of the script.
void GetConsoleList(std::back_insert_iterator< std::string > &output_iterator, bool newest_only) const
Get the list of registered scripts to print on the console.
std::optional< std::string_view > FindMainScript(const ContentInfo &ci, bool md5sum)
Find a script of a ContentInfo.
virtual std::string_view GetFileName() const =0
Get the filename to scan for this type of script.
ScriptInfoList info_list
The list of all script.
ScriptInfoList info_single_list
The list of all unique script. The best script (highest version) is shown.
void RegisterScript(std::unique_ptr< class ScriptInfo > &&info)
Register a ScriptInfo to the scanner.
std::unique_ptr< class Squirrel > engine
The engine we're scanning with.
bool HasScript(const struct ContentInfo &ci, bool md5sum)
Check whether we have a script with the exact characteristics as ci.
void ResetEngine()
Reset the engine to ensure a clean environment for further steps.
A throw-class that is given when the script made a fatal error.
const std::string & GetErrorMessage() const
The error message associated with the fatal error.
Functions related to debugging.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
std::optional< FileHandle > FioFOpenFile(std::string_view filename, std::string_view mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition fileio.cpp:244
bool FioCheckFileExists(std::string_view filename, Subdirectory subdir)
Check whether the given file exists.
Definition fileio.cpp:121
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition fileio_type.h:88
Part of the network protocol handling content distribution.
A number of safeguards to prevent using unsafe methods.
The definition of Script_FatalError.
ScriptInfo keeps track of all information of a script, like Author, Description, ....
static bool IsSameScript(const ContentInfo &ci, bool md5sum, const ScriptInfo &info, Subdirectory dir)
Check whether the script given in info is the same as in ci based on the shortname and md5 sum.
Declarations of the class for the script scanner.
std::map< std::string, class ScriptInfo *, CaseInsensitiveComparator > ScriptInfoList
Type for the list of scripts.
ClientSettings _settings_client
The current settings for this game.
Definition settings.cpp:60
Types related to global configuration settings.
Defines the Squirrel class.
Definition of base types and functions in a cross-platform compatible way.
bool StrEqualsIgnoreCase(std::string_view str1, std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition string.cpp:325
Functions related to low-level strings.
Container for all important information about a piece of content.
uint32_t unique_id
Unique ID; either GRF ID or shortname.
MD5Hash md5sum
The MD5 checksum.
Helper for creating a MD5sum of all files within of a script.
MD5Hash md5sum
The final md5sum.
bool AddFile(const std::string &filename, size_t, const std::string &) override
Add a file with the given filename.
Subdirectory dir
The directory to look in.
ScriptFileChecksumCreator(Subdirectory dir)
Initialise the md5sum to be all zeroes, so we can easily xor the data.
Structs, typedefs and macros used for TAR file handling.