OpenTTD Source 20250524-master-gc366e6a48e
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#include "base_media_func.h"
13#include "base_media_music.h"
16
17#include "safeguards.h"
18
19
26std::optional<std::string> GetMusicCatEntryName(const std::string &filename, size_t entrynum)
27{
28 if (!FioCheckFileExists(filename, BASESET_DIR)) return std::nullopt;
29
30 RandomAccessFile file(filename, BASESET_DIR);
31 uint32_t ofs = file.ReadDword();
32 size_t entry_count = ofs / 8;
33 if (entrynum >= entry_count) return std::nullopt;
34
35 file.SeekTo(entrynum * 8, SEEK_SET);
36 file.SeekTo(file.ReadDword(), SEEK_SET);
37 uint8_t namelen = file.ReadByte();
38
39 std::string name(namelen, '\0');
40 file.ReadBlock(name.data(), namelen);
41 return StrMakeValid(name);
42}
43
50std::optional<std::vector<uint8_t>> GetMusicCatEntryData(const std::string &filename, size_t entrynum)
51{
52 if (!FioCheckFileExists(filename, BASESET_DIR)) return std::nullopt;
53
54 RandomAccessFile file(filename, BASESET_DIR);
55 uint32_t ofs = file.ReadDword();
56 size_t entry_count = ofs / 8;
57 if (entrynum >= entry_count) return std::nullopt;
58
59 file.SeekTo(entrynum * 8, SEEK_SET);
60 size_t entrypos = file.ReadDword();
61 size_t entrylen = file.ReadDword();
62 file.SeekTo(entrypos, SEEK_SET);
63 file.SkipBytes(file.ReadByte());
64
65 std::vector<uint8_t> data(entrylen);
66 file.ReadBlock(data.data(), entrylen);
67 return data;
68}
69
71static const std::string_view _music_file_names[] = {
72 "theme",
73 "old_0", "old_1", "old_2", "old_3", "old_4", "old_5", "old_6", "old_7", "old_8", "old_9",
74 "new_0", "new_1", "new_2", "new_3", "new_4", "new_5", "new_6", "new_7", "new_8", "new_9",
75 "ezy_0", "ezy_1", "ezy_2", "ezy_3", "ezy_4", "ezy_5", "ezy_6", "ezy_7", "ezy_8", "ezy_9",
76};
79
80template <>
81/* static */ std::span<const std::string_view> BaseSet<MusicSet>::GetFilenames()
82{
83 return _music_file_names;
84}
85
86template <>
87/* static */ std::string_view BaseMedia<MusicSet>::GetExtension()
88{
89 return ".obm"; // OpenTTD Base Music
90}
91
92template <>
94{
95 if (BaseMedia<MusicSet>::used_set != nullptr) return true;
96
97 const MusicSet *best = nullptr;
98 for (const MusicSet *c = BaseMedia<MusicSet>::available_sets; c != nullptr; c = c->next) {
99 if (c->GetNumMissing() != 0) continue;
100
101 if (best == nullptr ||
102 (best->fallback && !c->fallback) ||
103 best->valid_files < c->valid_files ||
104 (best->valid_files == c->valid_files &&
105 (best->shortname == c->shortname && best->version < c->version))) {
106 best = c;
107 }
108 }
109
111 return BaseMedia<MusicSet>::used_set != nullptr;
112}
113
114template class BaseMedia<MusicSet>;
115
116bool MusicSet::FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename)
117{
118 bool ret = this->BaseSet<MusicSet>::FillSetDetails(ini, path, full_filename);
119 if (ret) {
120 this->num_available = 0;
121 const IniGroup *names = ini.GetGroup("names");
122 const IniGroup *catindex = ini.GetGroup("catindex");
123 const IniGroup *timingtrim = ini.GetGroup("timingtrim");
124 uint tracknr = 1;
125 for (uint i = 0; i < lengthof(this->songinfo); i++) {
126 const std::string &filename = this->files[i].filename;
127 if (filename.empty() || this->files[i].check_result == MD5File::CR_NO_FILE) {
128 continue;
129 }
130
131 this->songinfo[i].filename = filename; // non-owned pointer
132
133 const IniItem *item = catindex != nullptr ? catindex->GetItem(_music_file_names[i]) : nullptr;
134 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
135 /* Song has a CAT file index, assume it's MPS MIDI format */
136 this->songinfo[i].filetype = MTT_MPSMIDI;
137 auto value = ParseInteger(*item->value);
138 if (!value.has_value()) {
139 Debug(grf, 0, "Invalid base music set song index: {}/{}", filename, *item->value);
140 continue;
141 }
142 this->songinfo[i].cat_index = *value;
143 auto songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
144 if (!songname.has_value()) {
145 Debug(grf, 0, "Base music set song missing from CAT file: {}/{}", filename, this->songinfo[i].cat_index);
146 continue;
147 }
148 this->songinfo[i].songname = *songname;
149 } else {
151 }
152
153 std::string_view trimmed_filename{filename};
154 /* As we possibly add a path to the filename and we compare
155 * on the filename with the path as in the .obm, we need to
156 * keep stripping path elements until we find a match. */
157 while (!trimmed_filename.empty()) {
158 /* Remove possible double path separator characters from
159 * the beginning, so we don't start reading e.g. root. */
160 while (trimmed_filename.starts_with(PATHSEPCHAR)) trimmed_filename.remove_prefix(1);
161
162 item = names != nullptr ? names->GetItem(trimmed_filename) : nullptr;
163 if (item != nullptr && item->value.has_value() && !item->value->empty()) break;
164
165 auto next = trimmed_filename.find(PATHSEPCHAR);
166 if (next == std::string_view::npos) {
167 trimmed_filename = {};
168 } else {
169 trimmed_filename.remove_prefix(next);
170 }
171 }
172
173 if (this->songinfo[i].filetype == MTT_STANDARDMIDI) {
174 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
175 this->songinfo[i].songname = item->value.value();
176 } else {
177 Debug(grf, 0, "Base music set song name missing: {}", filename);
178 return false;
179 }
180 }
181 this->num_available++;
182
183 /* Number the theme song (if any) track 0, rest are normal */
184 if (i == 0) {
185 this->songinfo[i].tracknr = 0;
186 } else {
187 this->songinfo[i].tracknr = tracknr++;
188 }
189
190 item = !trimmed_filename.empty() && timingtrim != nullptr ? timingtrim->GetItem(trimmed_filename) : nullptr;
191 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
192 StringConsumer consumer{*item->value};
193 auto start = consumer.TryReadIntegerBase<uint>(10);
194 auto valid = consumer.ReadIf(":");
195 auto end = consumer.TryReadIntegerBase<uint>(10);
196 if (start.has_value() && valid && end.has_value() && !consumer.AnyBytesLeft()) {
197 this->songinfo[i].override_start = *start;
198 this->songinfo[i].override_end = *end;
199 }
200 }
201 }
202 }
203 return ret;
204}
Generic function implementations for base data (graphics, sounds).
Generic functions for replacing base music data.
std::optional< std::string > GetMusicCatEntryName(const std::string &filename, size_t entrynum)
Read the name of a music CAT file entry.
Definition music.cpp:26
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.
Base for all base media (graphics, sounds)
static bool DetermineBestSet()
Determine the graphics pack that has to be used.
static std::string_view GetExtension()
Get the extension that is used to identify this set.
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.
Parse data from a string / buffer.
#define Debug(category, level, format_string,...)
Output a line of debugging information.
Definition debug.h:37
bool FioCheckFileExists(std::string_view 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:95
std::optional< std::string > GetMusicCatEntryName(const std::string &filename, size_t entrynum)
Read the name of a music CAT file entry.
Definition music.cpp:26
static const std::string_view _music_file_names[]
Names corresponding to the music set's files.
Definition music.cpp:71
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:50
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:271
static void StrMakeValid(Builder &builder, StringConsumer &consumer, StringValidationSettings settings)
Copies the valid (UTF-8) characters from consumer to the builder.
Definition string.cpp:117
Parse strings.
static std::optional< T > ParseInteger(std::string_view arg, int base=10, bool clamp=false)
Change a string into its number representation.
Functions related to low-level strings.
std::array< MD5File, BaseSet< MusicSet >::NUM_FILES > files
All files part of this set.
uint valid_files
Number of the files that could be found and are valid.
static std::span< const std::string_view > GetFilenames()
Get the internal names of the files in this set.
bool fallback
This set is a fallback set, i.e. it should be used only as last resort.
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.
MusicSet * next
The next base set in this list.
std::vector< uint32_t > version
The version of this base set.
uint32_t shortname
Four letter short variant of the name.
Ini file that supports both loading and saving.
Definition ini_type.h:86
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:51
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:118
@ CR_NO_FILE
The file did not exist.
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)