Support for loading songs directly out of game containers

This commit is contained in:
Jack Andersen 2016-05-30 12:46:43 -10:00
parent 13a55764a0
commit 127aa283e9
3 changed files with 374 additions and 30 deletions

View File

@ -71,7 +71,7 @@ struct AppCallback : boo::IApplicationCallback
int8_t m_octave = 4; int8_t m_octave = 4;
int8_t m_velocity = 64; int8_t m_velocity = 64;
std::shared_ptr<amuse::Sequencer> m_seq; std::shared_ptr<amuse::Sequencer> m_seq;
std::unique_ptr<uint8_t[]> m_arrData; amuse::ContainerRegistry::SongData* m_arrData = nullptr;
/* SFX playback selection */ /* SFX playback selection */
int m_sfxId = -1; int m_sfxId = -1;
@ -116,7 +116,7 @@ struct AppCallback : boo::IApplicationCallback
m_seq->setVolume(m_volume); m_seq->setVolume(m_volume);
if (m_arrData) if (m_arrData)
m_seq->playSong(m_arrData.get(), false); m_seq->playSong(m_arrData->m_data.get(), false);
UpdateSongDisplay(); UpdateSongDisplay();
} }
@ -604,6 +604,40 @@ struct AppCallback : boo::IApplicationCallback
allSFXGroups[it->first] = std::make_pair(&grp, &it->second); allSFXGroups[it->first] = std::make_pair(&grp, &it->second);
} }
/* Attempt loading song */
std::vector<std::pair<std::string, amuse::ContainerRegistry::SongData>> songs;
if (m_argc > 2)
{
songs = amuse::ContainerRegistry::LoadSongs(m_argv[2]);
/* Get song selection from user */
if (songs.size() > 1)
{
/* Ask user to specify which song */
printf("Multiple Songs discovered:\n");
int idx = 0;
for (const auto& pair : songs)
{
printf(" %d %s (Group %d, Setup %d)\n", idx++,
pair.first.c_str(), pair.second.m_groupId, pair.second.m_setupId);
}
int userSel = 0;
printf("Enter Song Number: ");
if (scanf("%d", &userSel) <= 0)
Log.report(logvisor::Fatal, "unable to parse prompt");
if (userSel < songs.size())
{
m_arrData = &songs[userSel].second;
m_groupId = m_arrData->m_groupId;
m_setupId = m_arrData->m_setupId;
}
else
Log.report(logvisor::Fatal, "unable to find Song %d", userSel);
}
}
/* Get group selection from user */ /* Get group selection from user */
if (m_groupId != -1) if (m_groupId != -1)
{ {
@ -618,7 +652,7 @@ struct AppCallback : boo::IApplicationCallback
{ {
/* Ask user to specify which group in project */ /* Ask user to specify which group in project */
printf("Multiple Audio Groups discovered:\n"); printf("Multiple Audio Groups discovered:\n");
for (const auto& pair :allSFXGroups) for (const auto& pair : allSFXGroups)
{ {
printf(" %d %s (SFXGroup) %" PRISize " sfx-entries\n", printf(" %d %s (SFXGroup) %" PRISize " sfx-entries\n",
pair.first, pair.second.first->first.c_str(), pair.first, pair.second.first->first.c_str(),
@ -691,33 +725,6 @@ struct AppCallback : boo::IApplicationCallback
if (!selData) if (!selData)
Log.report(logvisor::Fatal, "unable to select audio group data"); Log.report(logvisor::Fatal, "unable to select audio group data");
/* Attempt loading song */
if (m_argc > 2)
{
std::experimental::optional<athena::io::FileReader> r;
r.emplace(m_argv[2], 32 * 1024, false);
if (!r->hasError())
{
uint32_t version = r->readUint32Big();
if (version == 0x18)
{
/* Raw SON data */
r->seek(0, athena::SeekOrigin::Begin);
m_arrData = r->readUBytes(r->length());
}
else if (version == 0x2)
{
/* Retro CSNG data */
m_setupId = r->readUint32Big();
m_groupId = r->readUint32Big();
r->readUint32Big();
uint32_t sonLength = r->readUint32Big();
m_arrData = r->readUBytes(sonLength);
}
}
}
/* Build voice engine */ /* Build voice engine */
std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewAudioVoiceEngine(); std::unique_ptr<boo::IAudioVoiceEngine> voxEngine = boo::NewAudioVoiceEngine();
amuse::BooBackendVoiceAllocator booBackend(*voxEngine); amuse::BooBackendVoiceAllocator booBackend(*voxEngine);

View File

@ -4,6 +4,7 @@
#include "AudioGroupData.hpp" #include "AudioGroupData.hpp"
#include <string> #include <string>
#include <vector> #include <vector>
#include <memory>
namespace amuse namespace amuse
{ {
@ -24,9 +25,19 @@ public:
RogueSquadron2, RogueSquadron2,
RogueSquadron3 RogueSquadron3
}; };
struct SongData
{
std::unique_ptr<uint8_t[]> m_data;
size_t m_size;
int16_t m_groupId;
int16_t m_setupId;
SongData(std::unique_ptr<uint8_t[]>&& data, size_t size, int16_t groupId, int16_t setupId)
: m_data(std::move(data)), m_size(size), m_groupId(groupId), m_setupId(setupId) {}
};
static const char* TypeToName(Type tp); static const char* TypeToName(Type tp);
static Type DetectContainerType(const char* path); static Type DetectContainerType(const char* path);
static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadContainer(const char* path); static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadContainer(const char* path);
static std::vector<std::pair<std::string, SongData>> LoadSongs(const char* path);
}; };
} }

View File

@ -4,6 +4,7 @@
#include <string.h> #include <string.h>
#include <string> #include <string>
#include <memory> #include <memory>
#include <unordered_map>
#include <zlib.h> #include <zlib.h>
#if _WIN32 #if _WIN32
@ -100,6 +101,21 @@ static bool IsChunkExtension(const char* path, const char*& dotOut)
return false; return false;
} }
static bool IsSongExtension(const char* path, const char*& dotOut)
{
const char* ext = strrchr(path, '.');
if (ext)
{
if (!CompareCaseInsensitive(ext, ".son") ||
!CompareCaseInsensitive(ext, ".sng"))
{
dotOut = ext;
return true;
}
}
return false;
}
static bool ValidateMP1(FILE* fp) static bool ValidateMP1(FILE* fp)
{ {
FileLength(fp); FileLength(fp);
@ -244,6 +260,147 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadMP1(FILE
return ret; return ret;
} }
static bool ValidateMP1Songs(FILE* fp)
{
FileLength(fp);
uint32_t magic;
fread(&magic, 1, 4, fp);
magic = SBig(magic);
if (magic == 0x00030005)
{
FSeek(fp, 8, SEEK_SET);
uint32_t nameCount;
fread(&nameCount, 1, 4, fp);
nameCount = SBig(nameCount);
for (uint32_t i=0 ; i<nameCount ; ++i)
{
FSeek(fp, 8, SEEK_CUR);
uint32_t nameLen;
fread(&nameLen, 1, 4, fp);
nameLen = SBig(nameLen);
FSeek(fp, nameLen, SEEK_CUR);
}
uint32_t resCount;
fread(&resCount, 1, 4, fp);
resCount = SBig(resCount);
for (uint32_t i=0 ; i<resCount ; ++i)
{
FSeek(fp, 4, SEEK_CUR);
uint32_t type;
fread(&type, 1, 4, fp);
type = SBig(type);
FSeek(fp, 8, SEEK_CUR);
uint32_t offset;
fread(&offset, 1, 4, fp);
offset = SBig(offset);
if (type == 0x43534E47)
return true;
}
}
return false;
}
static std::vector<std::pair<std::string, ContainerRegistry::SongData>> LoadMP1Songs(FILE* fp)
{
std::vector<std::pair<std::string, ContainerRegistry::SongData>> ret;
FileLength(fp);
uint32_t magic;
fread(&magic, 1, 4, fp);
magic = SBig(magic);
if (magic == 0x00030005)
{
FSeek(fp, 8, SEEK_SET);
uint32_t nameCount;
fread(&nameCount, 1, 4, fp);
nameCount = SBig(nameCount);
std::unordered_map<uint32_t, std::string> names;
names.reserve(nameCount);
for (uint32_t i=0 ; i<nameCount ; ++i)
{
FSeek(fp, 4, SEEK_CUR);
uint32_t id;
fread(&id, 1, 4, fp);
id = SBig(id);
uint32_t nameLen;
fread(&nameLen, 1, 4, fp);
nameLen = SBig(nameLen);
std::string str(nameLen, '\0');
fread(&str[0], 1, nameLen, fp);
names[id] = std::move(str);
}
uint32_t resCount;
fread(&resCount, 1, 4, fp);
resCount = SBig(resCount);
ret.reserve(resCount);
for (uint32_t i=0 ; i<resCount ; ++i)
{
FSeek(fp, 4, SEEK_CUR);
uint32_t type;
fread(&type, 1, 4, fp);
type = SBig(type);
uint32_t id;
fread(&id, 1, 4, fp);
id = SBig(id);
uint32_t size;
fread(&size, 1, 4, fp);
size = SBig(size);
uint32_t offset;
fread(&offset, 1, 4, fp);
offset = SBig(offset);
if (type == 0x43534E47)
{
int64_t origPos = FTell(fp);
FSeek(fp, offset + 4, SEEK_SET);
uint32_t midiSetup;
fread(&midiSetup, 1, 4, fp);
midiSetup = SBig(midiSetup);
uint32_t groupId;
fread(&groupId, 1, 4, fp);
groupId = SBig(groupId);
FSeek(fp, 4, SEEK_CUR);
uint32_t sonLength;
fread(&sonLength, 1, 4, fp);
sonLength = SBig(sonLength);
std::unique_ptr<uint8_t[]> song(new uint8_t[sonLength]);
fread(song.get(), 1, sonLength, fp);
auto search = names.find(id);
if (search != names.end())
ret.emplace_back(std::move(search->second),
ContainerRegistry::SongData(std::move(song), sonLength, groupId, midiSetup));
else
{
char name[128];
snprintf(name, 128, "%08X", id);
ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), sonLength, groupId, midiSetup));
}
FSeek(fp, origPos, SEEK_SET);
}
}
}
return ret;
}
static bool ValidateMP2(FILE* fp) static bool ValidateMP2(FILE* fp)
{ {
FileLength(fp); FileLength(fp);
@ -962,6 +1119,13 @@ struct RS23GroupHead
uint32_t sampOff; uint32_t sampOff;
uint32_t sampLen; uint32_t sampLen;
uint32_t unkOff;
uint32_t unkLen;
uint32_t sonCount;
uint32_t sonIdxBeginOff;
uint32_t sonIdxEndOff;
void swapBig() void swapBig()
{ {
projOff = SBig(projOff); projOff = SBig(projOff);
@ -972,6 +1136,27 @@ struct RS23GroupHead
sdirLen = SBig(sdirLen); sdirLen = SBig(sdirLen);
sampOff = SBig(sampOff); sampOff = SBig(sampOff);
sampLen = SBig(sampLen); sampLen = SBig(sampLen);
unkOff = SBig(unkOff);
unkLen = SBig(unkLen);
sonCount = SBig(sonCount);
sonIdxBeginOff = SBig(sonIdxBeginOff);
sonIdxEndOff = SBig(sonIdxEndOff);
}
};
struct RS23SONHead
{
uint32_t offset;
uint32_t length;
uint16_t groupId;
uint16_t setupId;
void swapBig()
{
offset = SBig(offset);
length = SBig(length);
groupId = SBig(groupId);
setupId = SBig(setupId);
} }
}; };
@ -1066,6 +1251,68 @@ static std::vector<std::pair<std::string, IntrusiveAudioGroupData>> LoadRS2(FILE
return ret; return ret;
} }
static std::vector<std::pair<std::string, ContainerRegistry::SongData>> LoadRS2Songs(FILE* fp)
{
std::vector<std::pair<std::string, ContainerRegistry::SongData>> ret;
size_t endPos = FileLength(fp);
uint64_t fstOff;
fread(&fstOff, 1, 8, fp);
fstOff = SBig(fstOff);
uint64_t fstSz;
fread(&fstSz, 1, 8, fp);
fstSz = SBig(fstSz);
if (fstOff + fstSz > endPos)
return ret;
FSeek(fp, int64_t(fstOff), SEEK_SET);
for (size_t i=0 ; i<fstSz/64 ; ++i)
{
RS2FSTEntry entry;
fread(&entry, 1, 64, fp);
entry.swapBig();
if (!strncmp("data", entry.name, 32))
{
FSeek(fp, int64_t(entry.offset), SEEK_SET);
std::unique_ptr<uint8_t[]> audData(new uint8_t[entry.decompSz]);
fread(audData.get(), 1, entry.decompSz, fp);
uint32_t indexOff = SBig(*reinterpret_cast<uint32_t*>(audData.get() + 4));
uint32_t groupCount = SBig(*reinterpret_cast<uint32_t*>(audData.get() + indexOff));
const uint32_t* groupOffs = reinterpret_cast<const uint32_t*>(audData.get() + indexOff + 4);
for (uint32_t j=0 ; j<groupCount ; ++j)
{
const uint8_t* groupData = audData.get() + SBig(groupOffs[j]);
RS23GroupHead head = *reinterpret_cast<const RS23GroupHead*>(groupData);
head.swapBig();
if (!head.sonCount)
continue;
const RS23SONHead* sonData = reinterpret_cast<const RS23SONHead*>(audData.get() + head.sonIdxBeginOff);
for (int s=0 ; s<head.sonCount ; ++s)
{
RS23SONHead sonHead = sonData[s];
sonHead.swapBig();
char name[128];
snprintf(name, 128, "GroupFile%u-%u", j, s);
std::unique_ptr<uint8_t[]> song(new uint8_t[sonHead.length]);
memcpy(song.get(), audData.get() + sonHead.offset, sonHead.length);
ret.emplace_back(name, ContainerRegistry::SongData(std::move(song), sonHead.length,
sonHead.groupId, sonHead.setupId));
}
}
break;
}
}
return ret;
}
struct RS3FSTEntry struct RS3FSTEntry
{ {
uint64_t offset; uint64_t offset;
@ -1467,4 +1714,83 @@ ContainerRegistry::LoadContainer(const char* path)
return {}; return {};
} }
std::vector<std::pair<std::string, ContainerRegistry::SongData>>
ContainerRegistry::LoadSongs(const char* path)
{
FILE* fp;
/* See if provided file is a raw song */
const char* dot = nullptr;
if (IsSongExtension(path, dot))
{
fp = fopen(path, "rb");
size_t fLen = FileLength(fp);
if (!fLen)
{
fclose(fp);
return {};
}
std::unique_ptr<uint8_t[]> song(new uint8_t[fLen]);
fread(song.get(), 1, fLen, fp);
fclose(fp);
std::vector<std::pair<std::string, SongData>> ret;
ret.emplace_back("Song", SongData(std::move(song), fLen, -1, -1));
return ret;
}
/* Now attempt archive-file case */
fp = fopen(path, "rb");
if (fp)
{
if (ValidateMP1Songs(fp))
{
auto ret = LoadMP1Songs(fp);
fclose(fp);
return ret;
}
#if 0
if (ValidateRS1PCSongs(fp))
{
auto ret = LoadRS1PCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateRS1N64Songs(fp))
{
auto ret = LoadRS1N64Songs(fp);
fclose(fp);
return ret;
}
if (ValidateBFNPCSongs(fp))
{
auto ret = LoadBFNPCSongs(fp);
fclose(fp);
return ret;
}
if (ValidateBFNN64Songs(fp))
{
auto ret = LoadBFNN64Songs(fp);
fclose(fp);
return ret;
}
#endif
if (ValidateRS2(fp))
{
auto ret = LoadRS2Songs(fp);
fclose(fp);
return ret;
}
fclose(fp);
}
return {};
}
} }