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/>.
8 /** @file music.cpp The songs that OpenTTD knows. */
13 /** The type of set we're replacing */
14 #define SET_TYPE "music"
15 #include "base_media_func.h"
17 #include "safeguards.h"
18 #include "random_access_file_type.h"
22 * Read the name of a music CAT file entry.
23 * @param filename Name of CAT file to read from
24 * @param entrynum Index of entry whose name to read
25 * @return Pointer to string, caller is responsible for freeing memory,
26 * nullptr if entrynum does not exist.
28 char *GetMusicCatEntryName(const char *filename
, size_t entrynum
)
30 if (!FioCheckFileExists(filename
, BASESET_DIR
)) return nullptr;
32 RandomAccessFile
file(filename
, BASESET_DIR
);
33 uint32 ofs
= file
.ReadDword();
34 size_t entry_count
= ofs
/ 8;
35 if (entrynum
< entry_count
) {
36 file
.SeekTo(entrynum
* 8, SEEK_SET
);
37 file
.SeekTo(file
.ReadDword(), SEEK_SET
);
38 byte namelen
= file
.ReadByte();
39 char *name
= MallocT
<char>(namelen
+ 1);
40 file
.ReadBlock(name
, namelen
);
48 * Read the full data of a music CAT file entry.
49 * @param filename Name of CAT file to read from.
50 * @param entrynum Index of entry to read
51 * @param[out] entrylen Receives length of data read
52 * @return Pointer to buffer with data read, caller is responsible for freeind memory,
53 * nullptr if entrynum does not exist.
55 byte
*GetMusicCatEntryData(const char *filename
, size_t entrynum
, size_t &entrylen
)
58 if (!FioCheckFileExists(filename
, BASESET_DIR
)) return nullptr;
60 RandomAccessFile
file(filename
, BASESET_DIR
);
61 uint32 ofs
= file
.ReadDword();
62 size_t entry_count
= ofs
/ 8;
63 if (entrynum
< entry_count
) {
64 file
.SeekTo(entrynum
* 8, SEEK_SET
);
65 size_t entrypos
= file
.ReadDword();
66 entrylen
= file
.ReadDword();
67 file
.SeekTo(entrypos
, SEEK_SET
);
68 file
.SkipBytes(file
.ReadByte());
69 byte
*data
= MallocT
<byte
>(entrylen
);
70 file
.ReadBlock(data
, entrylen
);
76 INSTANTIATE_BASE_MEDIA_METHODS(BaseMedia
<MusicSet
>, MusicSet
)
78 /** Names corresponding to the music set's files */
79 static const char * const _music_file_names
[] = {
81 "old_0", "old_1", "old_2", "old_3", "old_4", "old_5", "old_6", "old_7", "old_8", "old_9",
82 "new_0", "new_1", "new_2", "new_3", "new_4", "new_5", "new_6", "new_7", "new_8", "new_9",
83 "ezy_0", "ezy_1", "ezy_2", "ezy_3", "ezy_4", "ezy_5", "ezy_6", "ezy_7", "ezy_8", "ezy_9",
85 /** Make sure we aren't messing things up. */
86 static_assert(lengthof(_music_file_names
) == NUM_SONGS_AVAILABLE
);
88 template <class T
, size_t Tnum_files
, bool Tsearch_in_tars
>
89 /* static */ const char * const *BaseSet
<T
, Tnum_files
, Tsearch_in_tars
>::file_names
= _music_file_names
;
91 template <class Tbase_set
>
92 /* static */ const char *BaseMedia
<Tbase_set
>::GetExtension()
94 return ".obm"; // OpenTTD Base Music
97 template <class Tbase_set
>
98 /* static */ bool BaseMedia
<Tbase_set
>::DetermineBestSet()
100 if (BaseMedia
<Tbase_set
>::used_set
!= nullptr) return true;
102 const Tbase_set
*best
= nullptr;
103 for (const Tbase_set
*c
= BaseMedia
<Tbase_set
>::available_sets
; c
!= nullptr; c
= c
->next
) {
104 if (c
->GetNumMissing() != 0) continue;
106 if (best
== nullptr ||
107 (best
->fallback
&& !c
->fallback
) ||
108 best
->valid_files
< c
->valid_files
||
109 (best
->valid_files
== c
->valid_files
&&
110 (best
->shortname
== c
->shortname
&& best
->version
< c
->version
))) {
115 BaseMedia
<Tbase_set
>::used_set
= best
;
116 return BaseMedia
<Tbase_set
>::used_set
!= nullptr;
119 bool MusicSet::FillSetDetails(IniFile
*ini
, const char *path
, const char *full_filename
)
121 bool ret
= this->BaseSet
<MusicSet
, NUM_SONGS_AVAILABLE
, false>::FillSetDetails(ini
, path
, full_filename
);
123 this->num_available
= 0;
124 IniGroup
*names
= ini
->GetGroup("names");
125 IniGroup
*catindex
= ini
->GetGroup("catindex");
126 IniGroup
*timingtrim
= ini
->GetGroup("timingtrim");
128 for (uint i
= 0; i
< lengthof(this->songinfo
); i
++) {
129 const char *filename
= this->files
[i
].filename
;
130 if (names
== nullptr || StrEmpty(filename
) || this->files
[i
].check_result
== MD5File::CR_NO_FILE
) {
131 this->songinfo
[i
].songname
[0] = '\0';
135 this->songinfo
[i
].filename
= filename
; // non-owned pointer
137 IniItem
*item
= catindex
->GetItem(_music_file_names
[i
], false);
138 if (item
!= nullptr && item
->value
.has_value() && !item
->value
->empty()) {
139 /* Song has a CAT file index, assume it's MPS MIDI format */
140 this->songinfo
[i
].filetype
= MTT_MPSMIDI
;
141 this->songinfo
[i
].cat_index
= atoi(item
->value
->c_str());
142 char *songname
= GetMusicCatEntryName(filename
, this->songinfo
[i
].cat_index
);
143 if (songname
== nullptr) {
144 Debug(grf
, 0, "Base music set song missing from CAT file: {}/{}", filename
, this->songinfo
[i
].cat_index
);
145 this->songinfo
[i
].songname
[0] = '\0';
148 strecpy(this->songinfo
[i
].songname
, songname
, lastof(this->songinfo
[i
].songname
));
151 this->songinfo
[i
].filetype
= MTT_STANDARDMIDI
;
154 const char *trimmed_filename
= filename
;
155 /* As we possibly add a path to the filename and we compare
156 * on the filename with the path as in the .obm, we need to
157 * keep stripping path elements until we find a match. */
158 for (; trimmed_filename
!= nullptr; trimmed_filename
= strchr(trimmed_filename
, PATHSEPCHAR
)) {
159 /* Remove possible double path separator characters from
160 * the beginning, so we don't start reading e.g. root. */
161 while (*trimmed_filename
== PATHSEPCHAR
) trimmed_filename
++;
163 item
= names
->GetItem(trimmed_filename
, false);
164 if (item
!= nullptr && item
->value
.has_value() && !item
->value
->empty()) break;
167 if (this->songinfo
[i
].filetype
== MTT_STANDARDMIDI
) {
168 if (item
!= nullptr && item
->value
.has_value() && !item
->value
->empty()) {
169 strecpy(this->songinfo
[i
].songname
, item
->value
->c_str(), lastof(this->songinfo
[i
].songname
));
171 Debug(grf
, 0, "Base music set song name missing: {}", filename
);
175 this->num_available
++;
177 /* Number the theme song (if any) track 0, rest are normal */
179 this->songinfo
[i
].tracknr
= 0;
181 this->songinfo
[i
].tracknr
= tracknr
++;
184 item
= trimmed_filename
!= nullptr ? timingtrim
->GetItem(trimmed_filename
, false) : nullptr;
185 if (item
!= nullptr && item
->value
.has_value() && !item
->value
->empty()) {
186 auto endpos
= item
->value
->find(':');
187 if (endpos
!= std::string::npos
) {
188 this->songinfo
[i
].override_start
= atoi(item
->value
->c_str());
189 this->songinfo
[i
].override_end
= atoi(item
->value
->c_str() + endpos
+ 1);