342 lines
12 KiB
C++
342 lines
12 KiB
C++
#include "CWorldLoader.h"
|
|
#include <Core/CResCache.h>
|
|
#include <Core/Log.h>
|
|
#include <iostream>
|
|
|
|
CWorldLoader::CWorldLoader()
|
|
{
|
|
}
|
|
|
|
void CWorldLoader::LoadPrimeMLVL(CInputStream& MLVL)
|
|
{
|
|
/**
|
|
* This function loads MLVL files from Prime 1/2
|
|
* Corruption isn't too different, but having to check for it on every file ID is obnoxious
|
|
* We start immediately after the "version" value (0x8 in the file)
|
|
*/
|
|
// Header
|
|
if (mVersion < eCorruptionProto)
|
|
{
|
|
mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG");
|
|
if (mVersion == eEchoes) mpWorld->mpDarkWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG");
|
|
if (mVersion > ePrime) mpWorld->mUnknown1 = MLVL.ReadLong();
|
|
if (mVersion >= ePrime) mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLong(), "SAVW");
|
|
mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLong(), "CMDL");
|
|
}
|
|
|
|
else
|
|
{
|
|
mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG");
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping unknown value
|
|
mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLongLong(), "SAVW");
|
|
mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLongLong(), "CMDL");
|
|
}
|
|
|
|
// Memory relays - only in MP1
|
|
if (mVersion == ePrime)
|
|
{
|
|
u32 NumMemoryRelays = MLVL.ReadLong();
|
|
mpWorld->mMemoryRelays.reserve(NumMemoryRelays);
|
|
|
|
for (u32 iMem = 0; iMem < NumMemoryRelays; iMem++)
|
|
{
|
|
CWorld::SMemoryRelay MemRelay;
|
|
MemRelay.InstanceID = MLVL.ReadLong();
|
|
MemRelay.TargetID = MLVL.ReadLong();
|
|
MemRelay.Message = MLVL.ReadShort();
|
|
MemRelay.Unknown = MLVL.ReadByte();
|
|
mpWorld->mMemoryRelays.push_back(MemRelay);
|
|
}
|
|
}
|
|
|
|
// Areas - here's the real meat of the file
|
|
u32 NumAreas = MLVL.ReadLong();
|
|
if (mVersion == ePrime) mpWorld->mUnknownAreas = MLVL.ReadLong();
|
|
mpWorld->mAreas.resize(NumAreas);
|
|
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
// Area header
|
|
CWorld::SArea *pArea = &mpWorld->mAreas[iArea];
|
|
|
|
if (mVersion < eCorruptionProto)
|
|
pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLong(), "STRG");
|
|
else
|
|
pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG");
|
|
|
|
pArea->AreaNameToken = CToken(pArea->pAreaName);
|
|
pArea->Transform = CTransform4f(MLVL);
|
|
pArea->AetherBox = CAABox(MLVL);
|
|
|
|
if (mVersion < eCorruptionProto)
|
|
{
|
|
pArea->FileID = MLVL.ReadLong() & 0xFFFFFFFF; // This is the MREA ID; not actually loading it for obvious reasons
|
|
pArea->AreaID = MLVL.ReadLong() & 0xFFFFFFFF;
|
|
}
|
|
|
|
else
|
|
{
|
|
pArea->FileID = MLVL.ReadLongLong();
|
|
pArea->AreaID = MLVL.ReadLongLong();
|
|
}
|
|
|
|
// Attached areas
|
|
u32 NumAttachedAreas = MLVL.ReadLong();
|
|
pArea->AttachedAreaIDs.reserve(NumAttachedAreas);
|
|
for (u32 iAttached = 0; iAttached < NumAttachedAreas; iAttached++)
|
|
pArea->AttachedAreaIDs.push_back( MLVL.ReadShort() );
|
|
|
|
if (mVersion < eCorruptionProto)
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping unknown value (always 0)
|
|
|
|
// Depedencies
|
|
if (mVersion < eCorruptionProto)
|
|
{
|
|
u32 NumDependencies = MLVL.ReadLong();
|
|
pArea->Dependencies.reserve(NumDependencies);
|
|
|
|
for (u32 iDep = 0; iDep < NumDependencies; iDep++)
|
|
{
|
|
SDependency Dependency;
|
|
Dependency.ResID = MLVL.ReadLong() & 0xFFFFFFFF;
|
|
Dependency.ResType = MLVL.ReadLong();
|
|
pArea->Dependencies.push_back(Dependency);
|
|
}
|
|
|
|
/**
|
|
* Dependency offsets - indicates an offset into the dependency list where each layer's dependencies start
|
|
* The count is the layer count + 1 because the last offset is for common dependencies, like terrain textures
|
|
*/
|
|
u32 NumDependencyOffsets = MLVL.ReadLong();
|
|
pArea->Layers.resize(NumDependencyOffsets - 1);
|
|
|
|
for (u32 iOff = 0; iOff < NumDependencyOffsets; iOff++)
|
|
{
|
|
u32 *Target;
|
|
if (iOff == NumDependencyOffsets - 1) Target = &pArea->CommonDependenciesStart;
|
|
else Target = &pArea->Layers[iOff].LayerDependenciesStart;
|
|
|
|
*Target = MLVL.ReadLong();
|
|
}
|
|
}
|
|
|
|
// Docks
|
|
u32 NumDocks = MLVL.ReadLong();
|
|
pArea->Docks.resize(NumDocks);
|
|
|
|
for (u32 iDock = 0; iDock < NumDocks; iDock++)
|
|
{
|
|
u32 NumConnectingDocks = MLVL.ReadLong();
|
|
|
|
CWorld::SArea::SDock* pDock = &pArea->Docks[iDock];
|
|
pDock->ConnectingDocks.reserve(NumConnectingDocks);
|
|
|
|
for (u32 iConnect = 0; iConnect < NumConnectingDocks; iConnect++)
|
|
{
|
|
CWorld::SArea::SDock::SConnectingDock ConnectingDock;
|
|
ConnectingDock.AreaIndex = MLVL.ReadLong();
|
|
ConnectingDock.DockIndex = MLVL.ReadLong();
|
|
pDock->ConnectingDocks.push_back(ConnectingDock);
|
|
}
|
|
|
|
u32 NumCoordinates = MLVL.ReadLong();
|
|
if (NumCoordinates != 4) std::cout << "\rError: Dock coordinate count not 4\n";
|
|
|
|
for (u32 iCoord = 0; iCoord < NumCoordinates; iCoord++)
|
|
pDock->DockCoordinates[iCoord] = CVector3f(MLVL);
|
|
}
|
|
|
|
// Rels
|
|
if (mVersion == eEchoes)
|
|
{
|
|
u32 NumRels = MLVL.ReadLong();
|
|
pArea->RelFilenames.resize(NumRels);
|
|
|
|
for (u32 iRel = 0; iRel < NumRels; iRel++)
|
|
pArea->RelFilenames[iRel] = MLVL.ReadString();
|
|
|
|
u32 NumRelOffsets = MLVL.ReadLong(); // Don't know what these offsets correspond to
|
|
pArea->RelOffsets.resize(NumRelOffsets);
|
|
|
|
for (u32 iOff = 0; iOff < NumRelOffsets; iOff++)
|
|
pArea->RelOffsets[iOff] = MLVL.ReadLong();
|
|
}
|
|
|
|
// Footer
|
|
if (mVersion >= eEchoes)
|
|
pArea->InternalName = MLVL.ReadString();
|
|
}
|
|
|
|
// MapWorld
|
|
if (mVersion < eCorruptionProto)
|
|
mpWorld->mpMapWorld = gResCache.GetResource(MLVL.ReadLong(), "MAPW");
|
|
else
|
|
mpWorld->mpMapWorld = gResCache.GetResource(MLVL.ReadLongLong(), "MAPW");
|
|
MLVL.Seek(0x5, SEEK_CUR); // Unknown values which are always 0
|
|
|
|
// AudioGrps
|
|
if (mVersion == ePrime)
|
|
{
|
|
u32 NumAudioGrps = MLVL.ReadLong();
|
|
mpWorld->mAudioGrps.reserve(NumAudioGrps);
|
|
|
|
for (u32 iGrp = 0; iGrp < NumAudioGrps; iGrp++)
|
|
{
|
|
CWorld::SAudioGrp AudioGrp;
|
|
AudioGrp.Unknown = MLVL.ReadLong();
|
|
AudioGrp.ResID = MLVL.ReadLong() & 0xFFFFFFFF;
|
|
mpWorld->mAudioGrps.push_back(AudioGrp);
|
|
}
|
|
|
|
MLVL.Seek(0x1, SEEK_CUR); // Unknown values which are always 0
|
|
}
|
|
|
|
// Layer flags
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant area count
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
CWorld::SArea* pArea = &mpWorld->mAreas[iArea];
|
|
u32 NumLayers = MLVL.ReadLong();
|
|
if (NumLayers != pArea->Layers.size()) pArea->Layers.resize(NumLayers);
|
|
|
|
u64 LayerFlags = MLVL.ReadLongLong();
|
|
for (u32 iLayer = 0; iLayer < NumLayers; iLayer++)
|
|
pArea->Layers[iLayer].EnabledByDefault = (((LayerFlags >> iLayer) & 0x1) == 1);
|
|
}
|
|
|
|
// Layer names
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant layer count
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
CWorld::SArea* pArea = &mpWorld->mAreas[iArea];
|
|
u32 NumLayers = pArea->Layers.size();
|
|
|
|
for (u32 iLayer = 0; iLayer < NumLayers; iLayer++)
|
|
pArea->Layers[iLayer].LayerName = MLVL.ReadString();
|
|
}
|
|
|
|
// Last part of the file is layer name offsets, but we don't need it
|
|
// todo: Layer ID support for MP3
|
|
mpWorld->mResTokens[0] = CToken(mpWorld->mpWorldName);
|
|
mpWorld->mResTokens[1] = CToken(mpWorld->mpDarkWorldName);
|
|
mpWorld->mResTokens[2] = CToken(mpWorld->mpSaveWorld);
|
|
mpWorld->mResTokens[3] = CToken(mpWorld->mpDefaultSkybox);
|
|
mpWorld->mResTokens[4] = CToken(mpWorld->mpMapWorld);
|
|
}
|
|
|
|
void CWorldLoader::LoadReturnsMLVL(CInputStream& MLVL)
|
|
{
|
|
mpWorld->mpWorldName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG");
|
|
|
|
bool Check = MLVL.ReadByte();
|
|
if (Check)
|
|
{
|
|
MLVL.ReadString();
|
|
MLVL.Seek(0x10, SEEK_CUR);
|
|
}
|
|
|
|
mpWorld->mpSaveWorld = gResCache.GetResource(MLVL.ReadLongLong(), "SAVW");
|
|
mpWorld->mpDefaultSkybox = (CModel*) gResCache.GetResource(MLVL.ReadLongLong(), "CMDL");
|
|
|
|
// Areas
|
|
u32 NumAreas = MLVL.ReadLong();
|
|
mpWorld->mAreas.resize(NumAreas);
|
|
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
// Area header
|
|
CWorld::SArea *pArea = &mpWorld->mAreas[iArea];
|
|
|
|
pArea->pAreaName = (CStringTable*) gResCache.GetResource(MLVL.ReadLongLong(), "STRG");
|
|
pArea->Transform = CTransform4f(MLVL);
|
|
pArea->AetherBox = CAABox(MLVL);
|
|
pArea->FileID = MLVL.ReadLongLong();
|
|
pArea->AreaID = MLVL.ReadLongLong();
|
|
pArea->AreaNameToken = CToken(pArea->pAreaName);
|
|
|
|
MLVL.Seek(0x4, SEEK_CUR);
|
|
pArea->InternalName = MLVL.ReadString();
|
|
}
|
|
|
|
// Layer flags
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant area count
|
|
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
CWorld::SArea* pArea = &mpWorld->mAreas[iArea];
|
|
u32 NumLayers = MLVL.ReadLong();
|
|
pArea->Layers.resize(NumLayers);
|
|
|
|
u64 LayerFlags = MLVL.ReadLongLong();
|
|
for (u32 iLayer = 0; iLayer < NumLayers; iLayer++)
|
|
pArea->Layers[iLayer].EnabledByDefault = (((LayerFlags >> iLayer) & 0x1) == 1);
|
|
}
|
|
|
|
// Layer names
|
|
MLVL.Seek(0x4, SEEK_CUR); // Skipping redundant layer count
|
|
for (u32 iArea = 0; iArea < NumAreas; iArea++)
|
|
{
|
|
CWorld::SArea* pArea = &mpWorld->mAreas[iArea];
|
|
u32 NumLayers = pArea->Layers.size();
|
|
|
|
for (u32 iLayer = 0; iLayer < NumLayers; iLayer++)
|
|
pArea->Layers[iLayer].LayerName = MLVL.ReadString();
|
|
}
|
|
|
|
// Last part of the file is layer name offsets, but we don't need it
|
|
// todo: Layer ID support
|
|
mpWorld->mResTokens[0] = CToken(mpWorld->mpWorldName);
|
|
mpWorld->mResTokens[1] = CToken(mpWorld->mpDarkWorldName);
|
|
mpWorld->mResTokens[2] = CToken(mpWorld->mpSaveWorld);
|
|
mpWorld->mResTokens[3] = CToken(mpWorld->mpDefaultSkybox);
|
|
mpWorld->mResTokens[4] = CToken(mpWorld->mpMapWorld);
|
|
}
|
|
|
|
CWorld* CWorldLoader::LoadMLVL(CInputStream& MLVL)
|
|
{
|
|
if (!MLVL.IsValid()) return nullptr;
|
|
Log::Write("Loading " + MLVL.GetSourceString());
|
|
|
|
u32 Magic = MLVL.ReadLong();
|
|
if (Magic != 0xDEAFBABE)
|
|
{
|
|
Log::FileError(MLVL.GetSourceString(), "Invalid MLVL magic: " + StringUtil::ToHexString(Magic));
|
|
return nullptr;
|
|
}
|
|
|
|
u32 FileVersion = MLVL.ReadLong();
|
|
EGame Version = GetFormatVersion(FileVersion);
|
|
if (Version == eUnknownVersion)
|
|
{
|
|
Log::FileError(MLVL.GetSourceString(), "Unsupported MLVL version: " + StringUtil::ToHexString(FileVersion));
|
|
return nullptr;
|
|
}
|
|
|
|
// Filestream is valid, magic+version are valid; everything seems good!
|
|
CWorldLoader Loader;
|
|
Loader.mpWorld = new CWorld();
|
|
Loader.mpWorld->mWorldVersion = Version;
|
|
Loader.mVersion = Version;
|
|
|
|
if (Version != eReturns)
|
|
Loader.LoadPrimeMLVL(MLVL);
|
|
else
|
|
Loader.LoadReturnsMLVL(MLVL);
|
|
|
|
return Loader.mpWorld;
|
|
}
|
|
|
|
EGame CWorldLoader::GetFormatVersion(u32 Version)
|
|
{
|
|
switch (Version)
|
|
{
|
|
case 0xD: return ePrimeKioskDemo;
|
|
case 0x11: return ePrime;
|
|
case 0x14: return eEchoesDemo;
|
|
case 0x17: return eEchoes;
|
|
case 0x19: return eCorruption;
|
|
case 0x1B: return eReturns;
|
|
default: return eUnknownVersion;
|
|
}
|
|
}
|