/*
* This file is part of OpenTTD.
* 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.
* 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.
* 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 .
*/
/** @file music.cpp The songs that OpenTTD knows. */
#include "stdafx.h"
/** The type of set we're replacing */
#define SET_TYPE "music"
#include "base_media_func.h"
#include "safeguards.h"
#include "random_access_file_type.h"
/**
* Read the name of a music CAT file entry.
* @param filename Name of CAT file to read from
* @param entrynum Index of entry whose name to read
* @return Pointer to string, caller is responsible for freeing memory,
* nullptr if entrynum does not exist.
*/
char *GetMusicCatEntryName(const char *filename, size_t entrynum)
{
if (!FioCheckFileExists(filename, BASESET_DIR)) return nullptr;
RandomAccessFile file(filename, BASESET_DIR);
uint32 ofs = file.ReadDword();
size_t entry_count = ofs / 8;
if (entrynum < entry_count) {
file.SeekTo(entrynum * 8, SEEK_SET);
file.SeekTo(file.ReadDword(), SEEK_SET);
byte namelen = file.ReadByte();
char *name = MallocT(namelen + 1);
file.ReadBlock(name, namelen);
name[namelen] = '\0';
return name;
}
return nullptr;
}
/**
* Read the full data of a music CAT file entry.
* @param filename Name of CAT file to read from.
* @param entrynum Index of entry to read
* @param[out] entrylen Receives length of data read
* @return Pointer to buffer with data read, caller is responsible for freeind memory,
* nullptr if entrynum does not exist.
*/
byte *GetMusicCatEntryData(const char *filename, size_t entrynum, size_t &entrylen)
{
entrylen = 0;
if (!FioCheckFileExists(filename, BASESET_DIR)) return nullptr;
RandomAccessFile file(filename, BASESET_DIR);
uint32 ofs = file.ReadDword();
size_t entry_count = ofs / 8;
if (entrynum < entry_count) {
file.SeekTo(entrynum * 8, SEEK_SET);
size_t entrypos = file.ReadDword();
entrylen = file.ReadDword();
file.SeekTo(entrypos, SEEK_SET);
file.SkipBytes(file.ReadByte());
byte *data = MallocT(entrylen);
file.ReadBlock(data, entrylen);
return data;
}
return nullptr;
}
INSTANTIATE_BASE_MEDIA_METHODS(BaseMedia, MusicSet)
/** Names corresponding to the music set's files */
static const char * const _music_file_names[] = {
"theme",
"old_0", "old_1", "old_2", "old_3", "old_4", "old_5", "old_6", "old_7", "old_8", "old_9",
"new_0", "new_1", "new_2", "new_3", "new_4", "new_5", "new_6", "new_7", "new_8", "new_9",
"ezy_0", "ezy_1", "ezy_2", "ezy_3", "ezy_4", "ezy_5", "ezy_6", "ezy_7", "ezy_8", "ezy_9",
};
/** Make sure we aren't messing things up. */
static_assert(lengthof(_music_file_names) == NUM_SONGS_AVAILABLE);
template
/* static */ const char * const *BaseSet::file_names = _music_file_names;
template
/* static */ const char *BaseMedia::GetExtension()
{
return ".obm"; // OpenTTD Base Music
}
template
/* static */ bool BaseMedia::DetermineBestSet()
{
if (BaseMedia::used_set != nullptr) return true;
const Tbase_set *best = nullptr;
for (const Tbase_set *c = BaseMedia::available_sets; c != nullptr; c = c->next) {
if (c->GetNumMissing() != 0) continue;
if (best == nullptr ||
(best->fallback && !c->fallback) ||
best->valid_files < c->valid_files ||
(best->valid_files == c->valid_files &&
(best->shortname == c->shortname && best->version < c->version))) {
best = c;
}
}
BaseMedia::used_set = best;
return BaseMedia::used_set != nullptr;
}
bool MusicSet::FillSetDetails(IniFile *ini, const char *path, const char *full_filename)
{
bool ret = this->BaseSet::FillSetDetails(ini, path, full_filename);
if (ret) {
this->num_available = 0;
IniGroup *names = ini->GetGroup("names");
IniGroup *catindex = ini->GetGroup("catindex");
IniGroup *timingtrim = ini->GetGroup("timingtrim");
uint tracknr = 1;
for (uint i = 0; i < lengthof(this->songinfo); i++) {
const char *filename = this->files[i].filename;
if (names == nullptr || StrEmpty(filename) || this->files[i].check_result == MD5File::CR_NO_FILE) {
this->songinfo[i].songname[0] = '\0';
continue;
}
this->songinfo[i].filename = filename; // non-owned pointer
IniItem *item = catindex->GetItem(_music_file_names[i], false);
if (item != nullptr && item->value.has_value() && !item->value->empty()) {
/* Song has a CAT file index, assume it's MPS MIDI format */
this->songinfo[i].filetype = MTT_MPSMIDI;
this->songinfo[i].cat_index = atoi(item->value->c_str());
char *songname = GetMusicCatEntryName(filename, this->songinfo[i].cat_index);
if (songname == nullptr) {
Debug(grf, 0, "Base music set song missing from CAT file: {}/{}", filename, this->songinfo[i].cat_index);
this->songinfo[i].songname[0] = '\0';
continue;
}
strecpy(this->songinfo[i].songname, songname, lastof(this->songinfo[i].songname));
free(songname);
} else {
this->songinfo[i].filetype = MTT_STANDARDMIDI;
}
const char *trimmed_filename = filename;
/* As we possibly add a path to the filename and we compare
* on the filename with the path as in the .obm, we need to
* keep stripping path elements until we find a match. */
for (; trimmed_filename != nullptr; trimmed_filename = strchr(trimmed_filename, PATHSEPCHAR)) {
/* Remove possible double path separator characters from
* the beginning, so we don't start reading e.g. root. */
while (*trimmed_filename == PATHSEPCHAR) trimmed_filename++;
item = names->GetItem(trimmed_filename, false);
if (item != nullptr && item->value.has_value() && !item->value->empty()) break;
}
if (this->songinfo[i].filetype == MTT_STANDARDMIDI) {
if (item != nullptr && item->value.has_value() && !item->value->empty()) {
strecpy(this->songinfo[i].songname, item->value->c_str(), lastof(this->songinfo[i].songname));
} else {
Debug(grf, 0, "Base music set song name missing: {}", filename);
return false;
}
}
this->num_available++;
/* Number the theme song (if any) track 0, rest are normal */
if (i == 0) {
this->songinfo[i].tracknr = 0;
} else {
this->songinfo[i].tracknr = tracknr++;
}
item = trimmed_filename != nullptr ? timingtrim->GetItem(trimmed_filename, false) : nullptr;
if (item != nullptr && item->value.has_value() && !item->value->empty()) {
auto endpos = item->value->find(':');
if (endpos != std::string::npos) {
this->songinfo[i].override_start = atoi(item->value->c_str());
this->songinfo[i].override_end = atoi(item->value->c_str() + endpos + 1);
}
}
}
}
return ret;
}