OpenTTD Source 20260421-master-gc2fbc6fdeb
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 <https://www.gnu.org/licenses/old-licenses/gpl-2.0>.
6 */
7
9
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, Subdirectory::Baseset)) return std::nullopt;
29
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, Subdirectory::Baseset)) return std::nullopt;
53
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};
77
79
81template <>
82/* static */ std::span<const std::string_view> BaseSet<MusicSet>::GetFilenames()
83{
84 return _music_file_names;
85}
86
88template <>
89/* static */ std::string_view BaseMedia<MusicSet>::GetExtension()
90{
91 return ".obm"; // OpenTTD Base Music
92}
93
95template <>
97{
98 if (BaseMedia<MusicSet>::used_set != nullptr) return true;
99
100 const MusicSet *best = nullptr;
101 for (const auto &c : BaseMedia<MusicSet>::available_sets) {
102 if (c->GetNumMissing() != 0) continue;
103
104 if (best == nullptr ||
105 (best->fallback && !c->fallback) ||
106 best->valid_files < c->valid_files ||
107 (best->valid_files == c->valid_files &&
108 (best->shortname == c->shortname && best->version < c->version))) {
109 best = c.get();
110 }
111 }
112
114 return BaseMedia<MusicSet>::used_set != nullptr;
115}
116
117template class BaseMedia<MusicSet>;
118
119bool MusicSet::FillSetDetails(const IniFile &ini, const std::string &path, const std::string &full_filename)
120{
121 bool ret = this->BaseSet<MusicSet>::FillSetDetails(ini, path, full_filename);
122 if (ret) {
123 this->num_available = 0;
124 const IniGroup *names = ini.GetGroup("names");
125 const IniGroup *catindex = ini.GetGroup("catindex");
126 const IniGroup *timingtrim = ini.GetGroup("timingtrim");
127 uint tracknr = 1;
128 for (uint i = 0; i < lengthof(this->songinfo); i++) {
129 const std::string &filename = this->files[i].filename;
130 if (filename.empty() || this->files[i].check_result == MD5File::ChecksumResult::NoFile) {
131 continue;
132 }
133
134 this->songinfo[i].filename = filename; // non-owned pointer
135
136 const IniItem *item = catindex != nullptr ? catindex->GetItem(_music_file_names[i]) : nullptr;
137 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
138 /* Song has a CAT file index, assume it's MPS MIDI format */
139 this->songinfo[i].filetype = MTT_MPSMIDI;
140 auto value = ParseInteger(*item->value);
141 if (!value.has_value()) {
142 Debug(grf, 0, "Invalid base music set song index: {}/{}", filename, *item->value);
143 continue;
144 }
145 this->songinfo[i].cat_index = *value;
146 auto songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
147 if (!songname.has_value()) {
148 Debug(grf, 0, "Base music set song missing from CAT file: {}/{}", filename, this->songinfo[i].cat_index);
149 continue;
150 }
151 this->songinfo[i].songname = *songname;
152 } else {
153 this->songinfo[i].filetype = MTT_STANDARDMIDI;
154 }
155
156 std::string_view trimmed_filename{filename};
157 /* As we possibly add a path to the filename and we compare
158 * on the filename with the path as in the .obm, we need to
159 * keep stripping path elements until we find a match. */
160 while (!trimmed_filename.empty()) {
161 /* Remove possible double path separator characters from
162 * the beginning, so we don't start reading e.g. root. */
163 while (trimmed_filename.starts_with(PATHSEPCHAR)) trimmed_filename.remove_prefix(1);
164
165 item = names != nullptr ? names->GetItem(trimmed_filename) : nullptr;
166 if (item != nullptr && item->value.has_value() && !item->value->empty()) break;
167
168 auto next = trimmed_filename.find(PATHSEPCHAR);
169 if (next == std::string_view::npos) {
170 trimmed_filename = {};
171 } else {
172 trimmed_filename.remove_prefix(next);
173 }
174 }
175
176 if (this->songinfo[i].filetype == MTT_STANDARDMIDI) {
177 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
178 this->songinfo[i].songname = item->value.value();
179 } else {
180 Debug(grf, 0, "Base music set song name missing: {}", filename);
181 return false;
182 }
183 }
184 this->num_available++;
185
186 /* Number the theme song (if any) track 0, rest are normal */
187 if (i == 0) {
188 this->songinfo[i].tracknr = 0;
189 } else {
190 this->songinfo[i].tracknr = tracknr++;
191 }
192
193 item = !trimmed_filename.empty() && timingtrim != nullptr ? timingtrim->GetItem(trimmed_filename) : nullptr;
194 if (item != nullptr && item->value.has_value() && !item->value->empty()) {
195 StringConsumer consumer{*item->value};
196 auto start = consumer.TryReadIntegerBase<uint>(10);
197 auto valid = consumer.ReadIf(":");
198 auto end = consumer.TryReadIntegerBase<uint>(10);
199 if (start.has_value() && valid && end.has_value() && !consumer.AnyBytesLeft()) {
200 this->songinfo[i].override_start = *start;
201 this->songinfo[i].override_end = *end;
202 }
203 }
204 }
205 }
206 return ret;
207}
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 std::vector< std::unique_ptr< Tbase_set > > available_sets
All available sets.
static const Tbase_set * used_set
The currently used set.
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.
std::optional< T > TryReadIntegerBase(int base, bool clamp=false)
Try to read and parse an integer in number 'base', and then advance the reader.
bool AnyBytesLeft() const noexcept
Check whether any bytes left to read.
bool ReadIf(std::string_view str)
Check whether the next data matches 'str', and skip it.
#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:123
@ Baseset
Subdirectory for all base data (base sets, intro game).
Definition fileio_type.h:96
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:119
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
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.
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:87
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:50
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:117
@ NoFile
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.