OpenTTD Source  20241108-master-g80f628063a
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 <http://www.gnu.org/licenses/>.
6  */
7 
10 #include "../stdafx.h"
11 #include "../debug.h"
12 #include "../string_func.h"
13 #include "../settings_type.h"
14 
15 #include "../script/squirrel.hpp"
16 #include "script_scanner.hpp"
17 #include "script_info.hpp"
18 #include "script_fatalerror.hpp"
19 
20 #include "../network/network_content.h"
21 #include "../3rdparty/md5/md5.h"
22 #include "../tar_type.h"
23 
24 #include "../safeguards.h"
25 
26 bool 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 
47 ScriptScanner::ScriptScanner() :
48  engine(nullptr)
49 {
50 }
51 
53 {
54  this->engine->Reset();
55  this->engine->SetGlobalPointer(this);
56  this->RegisterAPI(this->engine);
57 }
58 
59 void ScriptScanner::Initialize(const char *name)
60 {
61  this->engine = new Squirrel(name);
62 
63  this->RescanDir();
64 
65  this->ResetEngine();
66 }
67 
68 ScriptScanner::~ScriptScanner()
69 {
70  this->Reset();
71 
72  delete this->engine;
73 }
74 
76 {
77  /* Forget about older scans */
78  this->Reset();
79 
80  /* Scan for scripts */
81  this->Scan(this->GetFileName(), this->GetDirectory());
82 }
83 
85 {
86  for (const auto &item : this->info_list) {
87  delete item.second;
88  }
89 
90  this->info_list.clear();
91  this->info_single_list.clear();
92 }
93 
95 {
96  std::string script_original_name = this->GetScriptName(info);
97  std::string script_name = fmt::format("{}.{}", script_original_name, info->GetVersion());
98 
99  /* Check if GetShortName follows the rules */
100  if (info->GetShortName().size() != 4) {
101  Debug(script, 0, "The script '{}' returned a string from GetShortName() which is not four characters. Unable to load the script.", info->GetName());
102  delete info;
103  return;
104  }
105 
106  if (this->info_list.find(script_name) != this->info_list.end()) {
107  /* This script was already registered */
108 #ifdef _WIN32
109  /* Windows doesn't care about the case */
110  if (StrEqualsIgnoreCase(this->info_list[script_name]->GetMainScript(), info->GetMainScript())) {
111 #else
112  if (this->info_list[script_name]->GetMainScript() == info->GetMainScript()) {
113 #endif
114  delete info;
115  return;
116  }
117 
118  Debug(script, 1, "Registering two scripts with the same name and version");
119  Debug(script, 1, " 1: {}", this->info_list[script_name]->GetMainScript());
120  Debug(script, 1, " 2: {}", info->GetMainScript());
121  Debug(script, 1, "The first is taking precedence.");
122 
123  delete info;
124  return;
125  }
126 
127  this->info_list[script_name] = info;
128 
130  /* Add the script to the 'unique' script list, where only the highest version
131  * of the script is registered. */
132  auto it = this->info_single_list.find(script_original_name);
133  if (it == this->info_single_list.end()) {
134  this->info_single_list[script_original_name] = info;
135  } else if (it->second->GetVersion() < info->GetVersion()) {
136  it->second = info;
137  }
138  }
139 }
140 
141 void ScriptScanner::GetConsoleList(std::back_insert_iterator<std::string> &output_iterator, bool newest_only) const
142 {
143  fmt::format_to(output_iterator, "List of {}:\n", this->GetScannerName());
144  const ScriptInfoList &list = newest_only ? this->info_single_list : this->info_list;
145  for (const auto &item : list) {
146  ScriptInfo *i = item.second;
147  fmt::format_to(output_iterator, "{:>10} (v{:d}): {}\n", i->GetName(), i->GetVersion(), i->GetDescription());
148  }
149  fmt::format_to(output_iterator, "\n");
150 }
151 
154  MD5Hash md5sum;
156 
162 
163  /* Add the file and calculate the md5 sum. */
164  bool AddFile(const std::string &filename, size_t, const std::string &) override
165  {
166  Md5 checksum;
167  uint8_t buffer[1024];
168  size_t len, size;
169 
170  /* Open the file ... */
171  auto f = FioFOpenFile(filename, "rb", this->dir, &size);
172  if (!f.has_value()) return false;
173 
174  /* ... calculate md5sum... */
175  while ((len = fread(buffer, 1, (size > sizeof(buffer)) ? sizeof(buffer) : size, *f)) != 0 && size != 0) {
176  size -= len;
177  checksum.Append(buffer, len);
178  }
179 
180  MD5Hash tmp_md5sum;
181  checksum.Finish(tmp_md5sum);
182 
183  /* ... and xor it to the overall md5sum. */
184  this->md5sum ^= tmp_md5sum;
185 
186  return true;
187  }
188 };
189 
198 static bool IsSameScript(const ContentInfo *ci, bool md5sum, ScriptInfo *info, Subdirectory dir)
199 {
200  uint32_t id = 0;
201  const char *str = info->GetShortName().c_str();
202  for (int j = 0; j < 4 && *str != '\0'; j++, str++) id |= *str << (8 * j);
203 
204  if (id != ci->unique_id) return false;
205  if (!md5sum) return true;
206 
207  ScriptFileChecksumCreator checksum(dir);
208  auto tar_filename = info->GetTarFile();
209  TarList::iterator iter;
210  if (!tar_filename.empty() && (iter = _tar_list[dir].find(tar_filename)) != _tar_list[dir].end()) {
211  /* The main script is in a tar file, so find all files that
212  * are in the same tar and add them to the MD5 checksumming. */
213  for (const auto &tar : _tar_filelist[dir]) {
214  /* Not in the same tar. */
215  if (tar.second.tar_filename != iter->first) continue;
216 
217  /* Check the extension. */
218  const char *ext = strrchr(tar.first.c_str(), '.');
219  if (ext == nullptr || !StrEqualsIgnoreCase(ext, ".nut")) continue;
220 
221  checksum.AddFile(tar.first, 0, tar_filename);
222  }
223  } else {
224  /* There'll always be at least 1 path separator character in a script
225  * main script name as the search algorithm requires the main script to
226  * be in a subdirectory of the script directory; so <dir>/<path>/main.nut. */
227  const std::string &main_script = info->GetMainScript();
228  std::string path = main_script.substr(0, main_script.find_last_of(PATHSEPCHAR));
229  checksum.Scan(".nut", path);
230  }
231 
232  return ci->md5sum == checksum.md5sum;
233 }
234 
235 bool ScriptScanner::HasScript(const ContentInfo *ci, bool md5sum)
236 {
237  for (const auto &item : this->info_list) {
238  if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return true;
239  }
240  return false;
241 }
242 
243 const char *ScriptScanner::FindMainScript(const ContentInfo *ci, bool md5sum)
244 {
245  for (const auto &item : this->info_list) {
246  if (IsSameScript(ci, md5sum, item.second, this->GetDirectory())) return item.second->GetMainScript().c_str();
247  }
248  return nullptr;
249 }
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:1114
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.
Definition: script_info.hpp:30
const std::string & GetShortName() const
Get the 4 character long short name of the script.
Definition: script_info.hpp:51
const std::string & GetTarFile() const
Get the filename of the tar the script is in.
Definition: script_info.hpp:86
const std::string & GetMainScript() const
Get the filename of the main.nut script.
Definition: script_info.hpp:81
int GetVersion() const
Get the version of the script.
Definition: script_info.hpp:61
const std::string & GetName() const
Get the Name of the script.
Definition: script_info.hpp:46
const std::string & GetDescription() const
Get the description of the script.
Definition: script_info.hpp:56
virtual bool IsDeveloperOnly() const
Can this script be selected by developers only?
bool HasScript(const struct ContentInfo *ci, bool md5sum)
Check whether we have a script with the exact characteristics as ci.
bool AddFile(const std::string &filename, size_t basepath_length, const std::string &tar_filename) override
Add a file with the given filename.
virtual const char * GetFileName() const =0
Get the filename to scan for this type of script.
const char * FindMainScript(const ContentInfo *ci, bool md5sum)
Find a script of a ContentInfo.
virtual const char * GetScannerName() const =0
Get the type of the script, in plural.
class Squirrel * engine
The engine we're scanning with.
std::string tar_file
If, which tar file the script was in.
void Reset()
Reset all allocated lists.
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.
virtual void RegisterAPI(class Squirrel *engine)=0
Register the API for this ScriptInfo.
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.
virtual std::string GetScriptName(ScriptInfo *info)=0
Get the script name how to store the script in memory.
std::string GetMainScript()
Get the current main script the ScanDir is currently tracking.
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(class ScriptInfo *info)
Register a ScriptInfo to the scanner.
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.
void Reset()
Completely reset the engine; start from scratch.
Definition: squirrel.cpp:753
bool LoadScript(const std::string &script)
Load a script.
Definition: squirrel.cpp:723
void SetGlobalPointer(void *ptr)
Sets a pointer in the VM that is reachable from where ever you are in SQ.
Definition: squirrel.hpp:221
#define Debug(category, level, format_string,...)
Ouptut a line of debugging information.
Definition: debug.h:37
bool FioCheckFileExists(const std::string &filename, Subdirectory subdir)
Check whether the given file exists.
Definition: fileio.cpp:121
std::optional< FileHandle > FioFOpenFile(const std::string &filename, const char *mode, Subdirectory subdir, size_t *filesize)
Opens a OpenTTD file somewhere in a personal or global directory.
Definition: fileio.cpp:242
Subdirectory
The different kinds of subdirectories OpenTTD uses.
Definition: fileio_type.h:115
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, 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:56
bool StrEqualsIgnoreCase(const std::string_view str1, const std::string_view str2)
Compares two string( view)s for equality, while ignoring the case of the characters.
Definition: string.cpp:347
GUISettings gui
settings related to the GUI
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.
bool ai_developer_tools
activate AI/GS developer tools
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.