OpenTTD Source  20241108-master-g80f628063a
music.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 "string_func.h"
12 
13 
15 #define SET_TYPE "music"
16 #include "base_media_func.h"
18 
19 #include "safeguards.h"
20 
21 
28 std::optional<std::string> GetMusicCatEntryName(const std::string &filename, size_t entrynum)
29 {
30  if (!FioCheckFileExists(filename, BASESET_DIR)) return std::nullopt;
31 
32  RandomAccessFile file(filename, BASESET_DIR);
33  uint32_t ofs = file.ReadDword();
34  size_t entry_count = ofs / 8;
35  if (entrynum >= entry_count) return std::nullopt;
36 
37  file.SeekTo(entrynum * 8, SEEK_SET);
38  file.SeekTo(file.ReadDword(), SEEK_SET);
39  uint8_t namelen = file.ReadByte();
40 
41  std::string name(namelen, '\0');
42  file.ReadBlock(name.data(), namelen);
43  return StrMakeValid(name);
44 }
45 
52 std::optional<std::vector<uint8_t>> GetMusicCatEntryData(const std::string &filename, size_t entrynum)
53 {
54  if (!FioCheckFileExists(filename, BASESET_DIR)) return std::nullopt;
55 
56  RandomAccessFile file(filename, BASESET_DIR);
57  uint32_t ofs = file.ReadDword();
58  size_t entry_count = ofs / 8;
59  if (entrynum >= entry_count) return std::nullopt;
60 
61  file.SeekTo(entrynum * 8, SEEK_SET);
62  size_t entrypos = file.ReadDword();
63  size_t entrylen = file.ReadDword();
64  file.SeekTo(entrypos, SEEK_SET);
65  file.SkipBytes(file.ReadByte());
66 
67  std::vector<uint8_t> data(entrylen);
68  file.ReadBlock(data.data(), entrylen);
69  return data;
70 }
71 
73 
74 
75 static const char * const _music_file_names[] = {
76  "theme",
77  "old_0", "old_1", "old_2", "old_3", "old_4", "old_5", "old_6", "old_7", "old_8", "old_9",
78  "new_0", "new_1", "new_2", "new_3", "new_4", "new_5", "new_6", "new_7", "new_8", "new_9",
79  "ezy_0", "ezy_1", "ezy_2", "ezy_3", "ezy_4", "ezy_5", "ezy_6", "ezy_7", "ezy_8", "ezy_9",
80 };
83 
84 template <class T, size_t Tnum_files, bool Tsearch_in_tars>
86 
87 template <class Tbase_set>
88 /* static */ const char *BaseMedia<Tbase_set>::GetExtension()
89 {
90  return ".obm"; // OpenTTD Base Music
91 }
92 
93 template <class Tbase_set>
95 {
96  if (BaseMedia<Tbase_set>::used_set != nullptr) return true;
97 
98  const Tbase_set *best = nullptr;
99  for (const Tbase_set *c = BaseMedia<Tbase_set>::available_sets; c != nullptr; c = c->next) {
100  if (c->GetNumMissing() != 0) continue;
101 
102  if (best == nullptr ||
103  (best->fallback && !c->fallback) ||
104  best->valid_files < c->valid_files ||
105  (best->valid_files == c->valid_files &&
106  (best->shortname == c->shortname && best->version < c->version))) {
107  best = c;
108  }
109  }
110 
112  return BaseMedia<Tbase_set>::used_set != nullptr;
113 }
114 
115 bool MusicSet::FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename)
116 {
117  bool ret = this->BaseSet<MusicSet, NUM_SONGS_AVAILABLE, false>::FillSetDetails(ini, path, full_filename);
118  if (ret) {
119  this->num_available = 0;
120  const IniGroup *names = ini.GetGroup("names");
121  const IniGroup *catindex = ini.GetGroup("catindex");
122  const IniGroup *timingtrim = ini.GetGroup("timingtrim");
123  uint tracknr = 1;
124  for (uint i = 0; i < lengthof(this->songinfo); i++) {
125  const std::string &filename = this->files[i].filename;
126  if (filename.empty() || this->files[i].check_result == MD5File::CR_NO_FILE) {
127  continue;
128  }
129 
130  this->songinfo[i].filename = filename; // non-owned pointer
131 
132  const IniItem *item = catindex != nullptr ? catindex->GetItem(_music_file_names[i]) : nullptr;
133  if (item != nullptr && item->value.has_value() && !item->value->empty()) {
134  /* Song has a CAT file index, assume it's MPS MIDI format */
135  this->songinfo[i].filetype = MTT_MPSMIDI;
136  this->songinfo[i].cat_index = atoi(item->value->c_str());
137  auto songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
138  if (!songname.has_value()) {
139  Debug(grf, 0, "Base music set song missing from CAT file: {}/{}", filename, this->songinfo[i].cat_index);
140  continue;
141  }
142  this->songinfo[i].songname = *songname;
143  } else {
145  }
146 
147  const char *trimmed_filename = filename.c_str();
148  /* As we possibly add a path to the filename and we compare
149  * on the filename with the path as in the .obm, we need to
150  * keep stripping path elements until we find a match. */
151  for (; trimmed_filename != nullptr; trimmed_filename = strchr(trimmed_filename, PATHSEPCHAR)) {
152  /* Remove possible double path separator characters from
153  * the beginning, so we don't start reading e.g. root. */
154  while (*trimmed_filename == PATHSEPCHAR) trimmed_filename++;
155 
156  item = names != nullptr ? names->GetItem(trimmed_filename) : nullptr;
157  if (item != nullptr && item->value.has_value() && !item->value->empty()) break;
158  }
159 
160  if (this->songinfo[i].filetype == MTT_STANDARDMIDI) {
161  if (item != nullptr && item->value.has_value() && !item->value->empty()) {
162  this->songinfo[i].songname = item->value.value();
163  } else {
164  Debug(grf, 0, "Base music set song name missing: {}", filename);
165  return false;
166  }
167  }
168  this->num_available++;
169 
170  /* Number the theme song (if any) track 0, rest are normal */
171  if (i == 0) {
172  this->songinfo[i].tracknr = 0;
173  } else {
174  this->songinfo[i].tracknr = tracknr++;
175  }
176 
177  item = trimmed_filename != nullptr && timingtrim != nullptr ? timingtrim->GetItem(trimmed_filename) : nullptr;
178  if (item != nullptr && item->value.has_value() && !item->value->empty()) {
179  auto endpos = item->value->find(':');
180  if (endpos != std::string::npos) {
181  this->songinfo[i].override_start = atoi(item->value->c_str());
182  this->songinfo[i].override_end = atoi(item->value->c_str() + endpos + 1);
183  }
184  }
185  }
186  }
187  return ret;
188 }
static const uint NUM_SONGS_AVAILABLE
Maximum number of songs in the full playlist; theme song + the classes.
@ MTT_MPSMIDI
MPS GM driver MIDI format (contained in a CAT file)
@ MTT_STANDARDMIDI
Standard MIDI file.
Generic function implementations for base data (graphics, sounds).
#define INSTANTIATE_BASE_MEDIA_METHODS(repl_type, set_type)
Force instantiation of methods so we don't get linker errors.
static bool DetermineBestSet()
Determine the graphics pack that has to be used.
Definition: gfxinit.cpp:466
static const char * GetExtension()
Get the extension that is used to identify this set.
Definition: gfxinit.cpp:490
A file from which bytes, words and double words are read in (potentially) a random order.
void ReadBlock(void *ptr, size_t size)
Read a block.
void SeekTo(size_t pos, int mode)
Seek in the current file.
uint8_t ReadByte()
Read a byte from the file.
uint32_t ReadDword()
Read a double word (32 bits) from the file (in low endian format).
void SkipBytes(size_t n)
Skip n bytes ahead in the file.
#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
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
Definition: fileio_type.h:123
static const char *const _music_file_names[]
Names corresponding to the music set's files.
Definition: music.cpp:75
std::optional< std::string > GetMusicCatEntryName(const std::string &filename, size_t entrynum)
Read the name of a music CAT file entry.
Definition: music.cpp:28
std::optional< std::vector< uint8_t > > GetMusicCatEntryData(const std::string &filename, size_t entrynum)
Read the full data of a music CAT file entry.
Definition: music.cpp:52
Class related to random access to files.
A number of safeguards to prevent using unsafe methods.
Definition of base types and functions in a cross-platform compatible way.
#define lengthof(array)
Return the length of an fixed size array.
Definition: stdafx.h:280
static void StrMakeValid(T &dst, const char *str, const char *last, StringValidationSettings settings)
Copies the valid (UTF-8) characters from str up to last to the dst.
Definition: string.cpp:107
Functions related to low-level strings.
Information about a single base set.
bool FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename, bool allow_empty_filename=true)
Read the set information from a loaded ini.
MD5File files[NUM_FILES]
All files part of this set.
Ini file that supports both loading and saving.
Definition: ini_type.h:88
A group within an ini file.
Definition: ini_type.h:34
const IniItem * GetItem(std::string_view name) const
Get the item with the given name.
Definition: ini_load.cpp:52
A single "line" in an ini file.
Definition: ini_type.h:23
std::optional< std::string > value
The value of this item.
Definition: ini_type.h:25
const IniGroup * GetGroup(std::string_view name) const
Get the group with the given name.
Definition: ini_load.cpp:119
@ CR_NO_FILE
The file did not exist.
ChecksumResult check_result
cached result of md5 check
std::string filename
filename
All data of a music set.
MusicSongInfo songinfo[NUM_SONGS_AVAILABLE]
Data about individual songs in set.
uint8_t num_available
Number of valid songs in set.
MusicTrackType filetype
decoder required for song file
std::string songname
name of song displayed in UI
uint8_t tracknr
track number of song displayed in UI
std::string filename
file on disk containing song (when used in MusicSet class)
int override_start
MIDI ticks to skip over in beginning.
int cat_index
entry index in CAT file, for filetype==MTT_MPSMIDI
int override_end
MIDI tick to end the song at (0 if no override)