OpenTTD Source 20250205-master-gfd85ab1e2c
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
28std::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
52std::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
75static 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
84template <class T, size_t Tnum_files, bool Tsearch_in_tars>
86
87template <class Tbase_set>
88/* static */ const char *BaseMedia<Tbase_set>::GetExtension()
89{
90 return ".obm"; // OpenTTD Base Music
91}
92
93template <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
115bool 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}
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
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.
Base for all base media (graphics, sounds)
static bool DetermineBestSet()
Determine the graphics pack that has to be used.
Definition gfxinit.cpp:479
static const char * GetExtension()
Get the extension that is used to identify this set.
Definition gfxinit.cpp:503
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:122
@ BASESET_DIR
Subdirectory for all base data (base sets, intro game)
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
static const char *const _music_file_names[]
Names corresponding to the music set's files.
Definition music.cpp:75
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:277
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
ChecksumResult check_result
cached result of md5 check
@ CR_NO_FILE
The file did not exist.
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)