#include "meta.h" #include "../util/endianness.h" #include "../layout/layout.h" #include "../util/companion_files.h" static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/); /* EA MPF/MUS combo - used in older 7th gen games for storing interactive music */ VGMSTREAM* init_vgmstream_ea_mpf_mus_eaac(STREAMFILE* sf) { VGMSTREAM* vgmstream = NULL; STREAMFILE *sf_mus = NULL; uint32_t num_tracks, track_start, track_checksum = 0, mus_sounds, mus_stream = 0, bnk_index = 0, bnk_sound_index = 0, tracks_table, samples_table, eof_offset, table_offset, entry_offset = 0, snr_offset, sns_offset; uint16_t num_subbanks, index, sub_index; uint8_t version, sub_version; segmented_layout_data* data_s = NULL; int i; int target_stream = sf->stream_index, total_streams, is_ram = 0; uint32_t(*read_u32)(off_t, STREAMFILE *); uint16_t(*read_u16)(off_t, STREAMFILE *); /* checks */ if (is_id32be(0x00, sf, "PFDx")) { read_u32 = read_u32be; read_u16 = read_u16be; } else if (is_id32le(0x00, sf, "PFDx")) { read_u32 = read_u32le; read_u16 = read_u16le; } else { return NULL; } if (!check_extensions(sf, "mpf")) return NULL; version = read_u8(0x04, sf); sub_version = read_u8(0x05, sf); if (version != 5 || sub_version < 2 || sub_version > 3) goto fail; num_tracks = read_u8(0x0d, sf); tracks_table = read_u32(0x2c, sf); samples_table = read_u32(0x34, sf); eof_offset = read_u32(0x38, sf); total_streams = (eof_offset - samples_table) / 0x08; if (target_stream == 0) target_stream = 1; if (target_stream < 0 || total_streams == 0 || target_stream > total_streams) goto fail; for (i = num_tracks - 1; i >= 0; i--) { entry_offset = read_u32(tracks_table + i * 0x04, sf) * 0x04; track_start = read_u32(entry_offset + 0x00, sf); if (track_start == 0 && i != 0) continue; /* empty track */ if (track_start <= target_stream - 1) { num_subbanks = read_u16(entry_offset + 0x04, sf); track_checksum = read_u32be(entry_offset + 0x08, sf); is_ram = (num_subbanks != 0); mus_stream = target_stream - 1 - track_start; break; } } /* open MUS file that matches this track */ sf_mus = open_mapfile_pair(sf, i);//, num_tracks if (!sf_mus) goto fail; /* sample offsets table is still there but it just holds SNS offsets, we only need it for RAM sound indexes */ /* 0x00 - offset/index, 0x04 - duration (in milliseconds) */ sns_offset = read_u32(samples_table + (target_stream - 1) * 0x08 + 0x00, sf); if (is_ram) { bnk_sound_index = (sns_offset & 0x0000FFFF); bnk_index = (sns_offset & 0xFFFF0000) >> 16; if (bnk_index != 0) { /* HACK: open proper .mus now since open_mapfile_pair doesn't let us adjust the name */ char filename[PATH_LIMIT], basename[PATH_LIMIT], ext[32]; int basename_len; STREAMFILE* sf_temp; get_streamfile_basename(sf_mus, basename, PATH_LIMIT); basename_len = strlen(basename); get_streamfile_ext(sf_mus, ext, sizeof(ext)); /* strip off 0 at the end */ basename[basename_len - 1] = '\0'; /* append bank index to the name */ snprintf(filename, PATH_LIMIT, "%s%u.%s", basename, bnk_index, ext); sf_temp = open_streamfile_by_filename(sf_mus, filename); if (!sf_temp) goto fail; close_streamfile(sf_mus); sf_mus = sf_temp; } track_checksum = read_u32be(entry_offset + 0x14 + bnk_index * 0x10, sf); if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum) goto fail; } else { if (track_checksum && read_u32be(0x00, sf_mus) != track_checksum) goto fail; } /* MUS file has a header, however */ if (sub_version == 2) { if (read_u32(0x04, sf_mus) != 0x00) goto fail; /* * 0x00: index * 0x02: sub-index * 0x04: SNR offset * 0x08: SNS offset (contains garbage for RAM sounds) */ table_offset = 0x08; if (is_ram) { int ram_segments; /* find number of parts for this node */ for (i = 0; ; i++) { entry_offset = table_offset + (bnk_sound_index + i) * 0x0c; index = read_u16(entry_offset + 0x00, sf_mus); sub_index = read_u16(entry_offset + 0x02, sf_mus); if (index == 0xffff) /* EOF check */ goto fail; entry_offset += 0x0c; if (read_u16(entry_offset + 0x00, sf_mus) != index || read_u16(entry_offset + 0x02, sf_mus) != sub_index + 1) break; } ram_segments = i + 1; /* init layout */ data_s = init_layout_segmented(ram_segments); if (!data_s) goto fail; for (i = 0; i < ram_segments; i++) { eaac_meta_t info = {0}; entry_offset = table_offset + (bnk_sound_index + i) * 0x0c; snr_offset = read_u32(entry_offset + 0x04, sf_mus); info.sf_head = sf_mus; info.head_offset = snr_offset; info.body_offset = 0x00; info.type = meta_EA_SNR_SNS; data_s->segments[i] = load_vgmstream_ea_eaac(&info); if (!data_s->segments[i]) goto fail; } /* setup segmented VGMSTREAMs */ if (!setup_layout_segmented(data_s)) goto fail; vgmstream = allocate_segmented_vgmstream(data_s, 0, 0, 0); } else { eaac_meta_t info = {0}; entry_offset = table_offset + mus_stream * 0x0c; snr_offset = read_u32(entry_offset + 0x04, sf_mus); sns_offset = read_u32(entry_offset + 0x08, sf_mus); info.sf_head = sf_mus; info.sf_body = sf_mus; info.head_offset = snr_offset; info.body_offset = sns_offset; info.type = meta_EA_SNR_SNS; vgmstream = load_vgmstream_ea_eaac(&info); } } else if (sub_version == 3) { eaac_meta_t info = {0}; /* number of samples is always little endian */ mus_sounds = read_u32le(0x04, sf_mus); if (mus_stream >= mus_sounds) goto fail; if (is_ram) { /* not seen so far */ VGM_LOG("EA EAAC MUS: found RAM track in MPF v5.3.\n"); goto fail; } /* * 0x00: checksum * 0x04: index * 0x06: sub-index * 0x08: SNR offset * 0x0c: SNS offset * 0x10: SNR size * 0x14: SNS size * 0x18: zero */ table_offset = 0x28; entry_offset = table_offset + mus_stream * 0x1c; snr_offset = read_u32(entry_offset + 0x08, sf_mus) * 0x10; sns_offset = read_u32(entry_offset + 0x0c, sf_mus) * 0x80; info.sf_head = sf_mus; info.sf_body = sf_mus; info.head_offset = snr_offset; info.body_offset = sns_offset; info.type = meta_EA_SNR_SNS; vgmstream = load_vgmstream_ea_eaac(&info); } else { goto fail; } if (!vgmstream) goto fail; vgmstream->num_streams = total_streams; get_streamfile_filename(sf_mus, vgmstream->stream_name, STREAM_NAME_SIZE); close_streamfile(sf_mus); return vgmstream; fail: close_streamfile(sf_mus); free_layout_segmented(data_s); return NULL; } //TODO remove a few lesser ones + use .txtm /* open map/mpf+mus pairs that aren't exact pairs, since EA's games can load any combo */ static STREAMFILE *open_mapfile_pair(STREAMFILE* sf, int track /*, int num_tracks*/) { static const char *const mapfile_pairs[][2] = { /* standard cases, replace map part with mus part (from the end to preserve prefixes) */ {"game.mpf", "Game_Stream.mus"}, /* Skate 1/2/3 */ {"ipod.mpf", "Ipod_Stream.mus"}, {"world.mpf", "World_Stream.mus"}, {"FreSkate.mpf", "track.mus,ram.mus"}, /* Skate It */ {"nsf_sing.mpf", "track_main.mus"}, /* Need for Speed: Nitro */ {"nsf_wii.mpf", "Track.mus"}, {"ssx_fe.mpf", "stream_1.mus,stream_2.mus"}, /* SSX 2012 */ {"ssxdd.mpf", "main_trk.mus," "trick_alaska0.mus," "trick_rockies0.mus," "trick_pata0.mus," "trick_ant0.mus," "trick_killi0.mus," "trick_cyb0.mus," "trick_hima0.mus," "trick_nz0.mus," "trick_alps0.mus," "trick_lhotse0.mus"} }; STREAMFILE *sf_mus = NULL; char file_name[PATH_LIMIT]; int pair_count = (sizeof(mapfile_pairs) / sizeof(mapfile_pairs[0])); int i, j; size_t file_len, map_len; /* try parsing TXTM if present */ sf_mus = read_filemap_file(sf, track); if (sf_mus) return sf_mus; /* if loading the first track, try opening MUS with the same name first (most common scenario) */ if (track == 0) { sf_mus = open_streamfile_by_ext(sf, "mus"); if (sf_mus) return sf_mus; } get_streamfile_filename(sf, file_name, PATH_LIMIT); file_len = strlen(file_name); for (i = 0; i < pair_count; i++) { const char *map_name = mapfile_pairs[i][0]; const char *mus_name = mapfile_pairs[i][1]; char buf[PATH_LIMIT] = { 0 }; char *pch; int use_mask = 0; map_len = strlen(map_name); /* replace map_name with expected mus_name */ if (file_len < map_len) continue; if (map_name[0] == '*') { use_mask = 1; map_name++; map_len--; if (strncmp(file_name + (file_len - map_len), map_name, map_len) != 0) continue; } else { if (strcmp(file_name, map_name) != 0) continue; } strncpy(buf, mus_name, PATH_LIMIT - 1); pch = strtok(buf, ","); //TODO: not thread safe in std C for (j = 0; j < track && pch; j++) { pch = strtok(NULL, ","); } if (!pch) continue; /* invalid track */ if (use_mask) { file_name[file_len - map_len] = '\0'; strncat(file_name, pch + 1, PATH_LIMIT - 1); } else { strncpy(file_name, pch, PATH_LIMIT - 1); } sf_mus = open_streamfile_by_filename(sf, file_name); if (sf_mus) return sf_mus; get_streamfile_filename(sf, file_name, PATH_LIMIT); /* reset for next loop */ } /* hack when when multiple maps point to the same mus, uses name before "+" * ex. ZZZTR00A.TRJ+ZTR00PGR.MAP or ZZZTR00A.TRJ+ZTR00R0A.MAP both point to ZZZTR00A.TRJ */ { char *mod_name = strchr(file_name, '+'); if (mod_name) { mod_name[0] = '\0'; sf_mus = open_streamfile_by_filename(sf, file_name); if (sf_mus) return sf_mus; } } vgm_logi("EA MPF: .mus file not found (find and put together)\n"); return NULL; }