Added support for Prime 3 package cooking

This commit is contained in:
Aruki 2017-05-09 21:08:52 -06:00
parent 560706d285
commit 7f18a33fae
10 changed files with 402 additions and 52 deletions

View File

@ -226,7 +226,8 @@ HEADERS += \
Resource/CAudioMacro.h \
CompressionUtil.h \
Resource/Animation/CSourceAnimData.h \
Resource/CMapArea.h
Resource/CMapArea.h \
Resource/CSavedStateID.h
# Source Files
SOURCES += \

View File

@ -77,48 +77,94 @@ void CPackage::Cook()
return;
}
// todo: MP3/DKCR pak format support
Pak.WriteLong(0x00030005); // Major/Minor Version
Pak.WriteLong(0); // Unknown
EGame Game = mpProject->Game();
u32 Alignment = (Game <= eCorruptionProto ? 0x20 : 0x40);
u32 AlignmentMinusOne = Alignment - 1;
// Named Resources
Pak.WriteLong(mResources.size());
u32 TocOffset = 0;
u32 NamesSize = 0;
u32 ResTableOffset = 0;
u32 ResTableSize = 0;
u32 ResDataSize = 0;
for (auto Iter = mResources.begin(); Iter != mResources.end(); Iter++)
// Write MP1 pak header
if (Game <= eCorruptionProto)
{
const SNamedResource& rkRes = *Iter;
rkRes.Type.Write(Pak);
rkRes.ID.Write(Pak);
Pak.WriteSizedString(rkRes.Name);
Pak.WriteLong(0x00030005); // Major/Minor Version
Pak.WriteLong(0); // Unknown
// Named Resources
Pak.WriteLong(mResources.size());
for (auto Iter = mResources.begin(); Iter != mResources.end(); Iter++)
{
const SNamedResource& rkRes = *Iter;
rkRes.Type.Write(Pak);
rkRes.ID.Write(Pak);
Pak.WriteSizedString(rkRes.Name);
}
}
// Fill in table of contents with junk, write later
// Write MP3 pak header
else
{
// Header
Pak.WriteLong(2); // Version
Pak.WriteLong(0x40); // Header size
Pak.WriteToBoundary(0x40, 0); // We don't care about the MD5 hash; the game doesn't use it
// PAK table of contents; write later
TocOffset = Pak.Tell();
Pak.WriteLong(0);
Pak.WriteToBoundary(0x40, 0);
// Named Resources
u32 NamesStart = Pak.Tell();
Pak.WriteLong(mResources.size());
for (auto Iter = mResources.begin(); Iter != mResources.end(); Iter++)
{
const SNamedResource& rkRes = *Iter;
Pak.WriteString(rkRes.Name);
rkRes.Type.Write(Pak);
rkRes.ID.Write(Pak);
}
Pak.WriteToBoundary(0x40, 0);
NamesSize = Pak.Tell() - NamesStart;
}
// Fill in resource table with junk, write later
ResTableOffset = Pak.Tell();
Pak.WriteLong(AssetList.size());
u32 TocOffset = Pak.Tell();
CAssetID Dummy = CAssetID::InvalidID(Game);
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
{
Pak.WriteLongLong(0);
Dummy.Write(Pak);
Pak.WriteLongLong(0);
Pak.WriteLong(0);
}
Pak.WriteToBoundary(32, 0);
Pak.WriteToBoundary(Alignment, 0);
ResTableSize = Pak.Tell() - ResTableOffset;
// Start writing resources
struct SResourceTocInfo
struct SResourceTableInfo
{
CResourceEntry *pEntry;
u32 Offset;
u32 Size;
bool Compressed;
};
std::vector<SResourceTocInfo> ResourceTocData(AssetList.size());
std::vector<SResourceTableInfo> ResourceTableData(AssetList.size());
u32 ResIdx = 0;
u32 ResDataOffset = Pak.Tell();
for (auto Iter = AssetList.begin(); Iter != AssetList.end(); Iter++, ResIdx++)
{
// Initialize entry, recook assets if needed
u32 AssetOffset = Pak.Tell();
CAssetID ID = *Iter;
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
ASSERT(pEntry != nullptr);
@ -126,9 +172,9 @@ void CPackage::Cook()
if (pEntry->NeedsRecook())
pEntry->Cook();
SResourceTocInfo& rTocInfo = ResourceTocData[ResIdx];
rTocInfo.pEntry = pEntry;
rTocInfo.Offset = Pak.Tell();
SResourceTableInfo& rTableInfo = ResourceTableData[ResIdx];
rTableInfo.pEntry = pEntry;
rTableInfo.Offset = (Game <= eEchoes ? AssetOffset : AssetOffset - ResDataOffset);
// Load resource data
CFileInStream CookedAsset(pEntry->CookedAssetPath(), IOUtil::eBigEndian);
@ -141,21 +187,31 @@ void CPackage::Cook()
// Check if this asset should be compressed; there are a few resource types that are
// always compressed, and some types that are compressed if they're over a certain size
EResType Type = pEntry->ResourceType();
u32 CompressThreshold = (Game <= eCorruptionProto ? 0x400 : 0x80);
bool ShouldAlwaysCompress = (Type == eTexture || Type == eModel || Type == eSkin ||
Type == eAnimSet || Type == eAnimation || Type == eFont);
if (Game >= eCorruption)
{
ShouldAlwaysCompress = ShouldAlwaysCompress ||
(Type == eCharacter || Type == eSourceAnimData || Type == eScan ||
Type == eAudioSample || Type == eStringTable || Type == eAudioAmplitudeData ||
Type == eDynamicCollision);
}
bool ShouldCompressConditional = !ShouldAlwaysCompress &&
(Type == eParticle || Type == eParticleElectric || Type == eParticleSwoosh ||
Type == eParticleWeapon || Type == eParticleDecal || Type == eParticleCollisionResponse);
Type == eParticleWeapon || Type == eParticleDecal || Type == eParticleCollisionResponse ||
Type == eParticleSpawn || Type == eParticleSorted || Type == eBurstFireData);
bool ShouldCompress = ShouldAlwaysCompress || (ShouldCompressConditional && ResourceSize >= 0x400);
bool ShouldCompress = ShouldAlwaysCompress || (ShouldCompressConditional && ResourceSize >= CompressThreshold);
// Write resource data to pak
if (!ShouldCompress)
{
Pak.WriteBytes(ResourceData.data(), ResourceSize);
rTocInfo.Compressed = false;
rTableInfo.Compressed = false;
}
else
@ -164,7 +220,7 @@ void CPackage::Cook()
std::vector<u8> CompressedData(ResourceData.size() * 2);
bool Success = false;
if (mpProject->Game() <= eEchoesDemo)
if (Game <= eEchoesDemo || Game == eReturns)
Success = CompressionUtil::CompressZlib(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedData.size(), CompressedSize);
else
Success = CompressionUtil::CompressLZOSegmented(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedSize, false);
@ -172,44 +228,77 @@ void CPackage::Cook()
// Make sure that the compressed data is actually smaller, accounting for padding + uncompressed size value
if (Success)
{
u32 PaddedUncompressedSize = (ResourceSize + 0x1F) & ~0x1F;
u32 PaddedCompressedSize = (CompressedSize + 4 + 0x1F) & ~0x1F;
u32 CompressionHeaderSize = (Game <= eCorruptionProto ? 4 : 0x10);
u32 PaddedUncompressedSize = (ResourceSize + AlignmentMinusOne) & ~AlignmentMinusOne;
u32 PaddedCompressedSize = (CompressedSize + CompressionHeaderSize + AlignmentMinusOne) & ~AlignmentMinusOne;
Success = (PaddedCompressedSize < PaddedUncompressedSize);
}
// Write file to pak
if (Success)
{
Pak.WriteLong(ResourceSize);
// Write MP1/2 compressed asset
if (Game <= eCorruptionProto)
{
Pak.WriteLong(ResourceSize);
}
// Write MP3/DKCR compressed asset
else
{
// Note: Compressed asset data can be stored in multiple blocks. Normally, the only assets that make use of this are textures,
// which can store each separate component of the file (header, palette, image data) in separate blocks. However, some textures
// are stored in one block, and I've had no luck figuring out why. The game doesn't generally seem to care whether textures use
// multiple blocks or not, so for the sake of complicity we compress everything to one block.
Pak.WriteFourCC( FOURCC('CMPD') );
Pak.WriteLong(1);
Pak.WriteLong(0xA0000000 | CompressedSize);
Pak.WriteLong(ResourceSize);
}
Pak.WriteBytes(CompressedData.data(), CompressedSize);
}
else
Pak.WriteBytes(ResourceData.data(), ResourceSize);
rTocInfo.Compressed = Success;
rTableInfo.Compressed = Success;
}
Pak.WriteToBoundary(32, 0xFF);
rTocInfo.Size = Pak.Tell() - rTocInfo.Offset;
Pak.WriteToBoundary(Alignment, 0xFF);
rTableInfo.Size = Pak.Tell() - AssetOffset;
}
ResDataSize = Pak.Tell() - ResDataOffset;
// Write table of contents for real
Pak.Seek(TocOffset, SEEK_SET);
if (Game >= eCorruption)
{
Pak.Seek(TocOffset, SEEK_SET);
Pak.WriteLong(3); // Always 3 pak sections
Pak.WriteFourCC( FOURCC('STRG') );
Pak.WriteLong(NamesSize);
Pak.WriteFourCC( FOURCC('RSHD') );
Pak.WriteLong(ResTableSize);
Pak.WriteFourCC( FOURCC('DATA') );
Pak.WriteLong(ResDataSize);
}
// Write resource table for real
Pak.Seek(ResTableOffset+4, SEEK_SET);
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
{
const SResourceTocInfo& rkTocInfo = ResourceTocData[iRes];
CResourceEntry *pEntry = rkTocInfo.pEntry;
const SResourceTableInfo& rkInfo = ResourceTableData[iRes];
CResourceEntry *pEntry = rkInfo.pEntry;
Pak.WriteLong( rkTocInfo.Compressed ? 1 : 0 );
Pak.WriteLong( rkInfo.Compressed ? 1 : 0 );
pEntry->CookedExtension().Write(Pak);
pEntry->ID().Write(Pak);
Pak.WriteLong(rkTocInfo.Size);
Pak.WriteLong(rkTocInfo.Offset);
Pak.WriteLong(rkInfo.Size);
Pak.WriteLong(rkInfo.Offset);
}
// Clear recook flag
mNeedsRecook = false;
Save();
Log::Write("Finished writing " + PakPath);
// Update resource store in case we recooked any assets

View File

@ -461,6 +461,10 @@ void CAreaDependencyListBuilder::AddDependency(const CAssetID& rkID, std::list<C
return;
}
// If this is an audio stream, skip
if (ResType == eStreamedAudio)
return;
// Check to ensure this is a valid/new dependency
if (ResType == eWorld || ResType == eArea)
return;

View File

@ -0,0 +1,85 @@
#ifndef CSAVEDSTATEID_H
#define CSAVEDSTATEID_H
#include <Common/Common.h>
// GUID representing a value stored in the save file for MP3/DKCR
class CSavedStateID
{
u64 m[2];
public:
CSavedStateID()
{
m[0] = 0;
m[1] = 0;
}
CSavedStateID(u64 Part1, u64 Part2)
{
m[0] = Part1;
m[1] = Part2;
}
CSavedStateID(IInputStream& rInput)
{
m[0] = rInput.ReadLongLong();
m[1] = rInput.ReadLongLong();
}
TString ToString()
{
u32 Part1 = (m[0] >> 32) & 0xFFFFFFFF;
u32 Part2 = (m[0] >> 16) & 0x0000FFFF;
u32 Part3 = (m[0] >> 00) & 0x0000FFFF;
u32 Part4 = (m[1] >> 48) & 0x0000FFFF;
u32 Part5 = (m[1] >> 32) & 0x0000FFFF;
u32 Part6 = (m[1] >> 00) & 0xFFFFFFFF;
return TString::Format("%08X-%04X-%04X-%04X-%04X%08X", Part1, Part2, Part3, Part4, Part5, Part6);
}
void Write(IOutputStream& rOutput)
{
rOutput.WriteLongLong(m[0]);
rOutput.WriteLongLong(m[1]);
}
void Serialize(IArchive& rArc)
{
TString Str;
if (rArc.IsWriter()) Str = ToString();
rArc.SerializePrimitive(Str);
if (rArc.IsReader()) *this = FromString(Str);
}
// Operators
inline bool operator==(const CSavedStateID& rkOther)
{
return (m[0] == rkOther.m[0] && m[1] == rkOther.m[1]);
}
inline bool operator!=(const CSavedStateID& rkOther)
{
return !(*this == rkOther);
}
inline bool operator<(const CSavedStateID& rkOther)
{
return (m[0] == rkOther.m[0] ? m[1] < rkOther.m[1] : m[0] < rkOther.m[0]);
}
// Static
static CSavedStateID FromString(TString Str)
{
Str.Remove('-');
ASSERT(Str.Size() == 32);
CSavedStateID Out;
Out.m[0] = Str.SubString(0, 16).ToInt64(16);
Out.m[1] = Str.SubString(16, 16).ToInt64(16);
return Out;
}
};
#endif // CSAVEDSTATEID_H

View File

@ -94,12 +94,25 @@ void CWorld::Serialize(IArchive& rArc)
if (rArc.Game() >= eEchoesDemo && rArc.Game() <= eCorruption)
rArc << SERIAL("TempleKeyWorldIndex", mTempleKeyWorldIndex);
if (rArc.Game() == eReturns)
rArc << SERIAL("TimeAttackData", mTimeAttackData);
if (rArc.Game() == ePrime)
rArc << SERIAL_CONTAINER("MemoryRelays", mMemoryRelays, "MemoryRelay");
rArc << SERIAL_CONTAINER("Areas", mAreas, "Area");
}
void Serialize(IArchive& rArc, CWorld::STimeAttackData& rData)
{
rArc << SERIAL("HasTimeAttack", rData.HasTimeAttack)
<< SERIAL("ActNumber", rData.ActNumber)
<< SERIAL("BronzeTime", rData.BronzeTime)
<< SERIAL("SilverTime", rData.SilverTime)
<< SERIAL("GoldTime", rData.GoldTime)
<< SERIAL("ShinyGoldTime", rData.ShinyGoldTime);
}
void Serialize(IArchive& rArc, CWorld::SMemoryRelay& rMemRelay)
{
rArc << SERIAL_HEX("MemoryRelayID", rMemRelay.InstanceID)
@ -140,6 +153,11 @@ void Serialize(IArchive& rArc, CWorld::SArea::SLayer& rLayer)
{
rArc << SERIAL("Name", rLayer.LayerName)
<< SERIAL("Active", rLayer.Active);
if (rArc.Game() >= eCorruption)
{
rArc << SERIAL("StateID", rLayer.LayerStateID);
}
}
void Serialize(IArchive& rArc, CWorld::SAudioGrp& rAudioGrp)

View File

@ -2,6 +2,7 @@
#define CWORLD_H
#include "CResource.h"
#include "CSavedStateID.h"
#include "CStringTable.h"
#include "Core/Resource/Area/CGameArea.h"
#include "Core/Resource/Model/CModel.h"
@ -22,6 +23,16 @@ class CWorld : public CResource
TResPtr<CResource> mpMapWorld;
u32 mTempleKeyWorldIndex;
struct STimeAttackData
{
bool HasTimeAttack;
TString ActNumber;
float BronzeTime;
float SilverTime;
float GoldTime;
float ShinyGoldTime;
} mTimeAttackData;
struct SAudioGrp
{
CAssetID ResID;
@ -69,7 +80,7 @@ class CWorld : public CResource
{
TString LayerName;
bool Active;
u8 LayerID[16];
CSavedStateID LayerStateID;
};
std::vector<SLayer> Layers;
};
@ -86,6 +97,7 @@ public:
// Serialization
virtual void Serialize(IArchive& rArc);
friend void Serialize(IArchive& rArc, STimeAttackData& rTimeAttackData);
friend void Serialize(IArchive& rArc, SMemoryRelay& rMemRelay);
friend void Serialize(IArchive& rArc, SArea& rArea);
friend void Serialize(IArchive& rArc, SArea::SDock& rDock);

View File

@ -1,11 +1,26 @@
#include "CAreaCooker.h"
#include "CScriptCooker.h"
#include "Core/CompressionUtil.h"
#include "Core/GameProject/DependencyListBuilders.h"
#include <Common/Log.h>
const bool gkForceDisableCompression = false;
const bool gkForceDisableCompression = true;
CAreaCooker::CAreaCooker()
: mGeometrySecNum(-1)
, mSCLYSecNum(-1)
, mSCGNSecNum(-1)
, mCollisionSecNum(-1)
, mUnknownSecNum(-1)
, mLightsSecNum(-1)
, mVISISecNum(-1)
, mPATHSecNum(-1)
, mAROTSecNum(-1)
, mFFFFSecNum(-1)
, mPTLASecNum(-1)
, mEGMCSecNum(-1)
, mDepsSecNum(-1)
, mModulesSecNum(-1)
{
}
@ -69,8 +84,10 @@ void CAreaCooker::DetermineSectionNumbersCorruption()
for (u32 iNum = 0; iNum < mpArea->mSectionNumbers.size(); iNum++)
{
CGameArea::SSectionNumber& rNum = mpArea->mSectionNumbers[iNum];
if (rNum.SectionID == "SOBJ") mSCLYSecNum = rNum.Index;
if (rNum.SectionID == "SOBJ") mSCLYSecNum = rNum.Index;
else if (rNum.SectionID == "SGEN") mSCGNSecNum = rNum.Index;
else if (rNum.SectionID == "DEPS") mDepsSecNum = rNum.Index;
else if (rNum.SectionID == "RSOS") mModulesSecNum = rNum.Index;
}
}
@ -228,6 +245,57 @@ void CAreaCooker::WriteEchoesSCLY(IOutputStream& rOut)
FinishSection(true);
}
void CAreaCooker::WriteDependencies(IOutputStream& rOut)
{
// Build dependency list
std::list<CAssetID> Dependencies;
std::list<u32> LayerOffsets;
CAreaDependencyListBuilder Builder(mpArea->Entry());
Builder.BuildDependencyList(Dependencies, LayerOffsets);
// Write
rOut.WriteLong(Dependencies.size());
for (auto Iter = Dependencies.begin(); Iter != Dependencies.end(); Iter++)
{
CAssetID ID = *Iter;
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
ID.Write(rOut);
pEntry->CookedExtension().Write(rOut);
}
rOut.WriteLong(LayerOffsets.size());
for (auto Iter = LayerOffsets.begin(); Iter != LayerOffsets.end(); Iter++)
rOut.WriteLong(*Iter);
FinishSection(false);
}
void CAreaCooker::WriteModules(IOutputStream& rOut)
{
// Build module list
std::vector<TString> ModuleNames;
std::vector<u32> LayerOffsets;
CAreaDependencyTree *pAreaDeps = static_cast<CAreaDependencyTree*>(mpArea->Entry()->Dependencies());
pAreaDeps->GetModuleDependencies(mpArea->Game(), ModuleNames, LayerOffsets);
// Write
rOut.WriteLong(ModuleNames.size());
for (u32 ModuleIdx = 0; ModuleIdx < ModuleNames.size(); ModuleIdx++)
rOut.WriteString( ModuleNames[ModuleIdx] );
rOut.WriteLong(LayerOffsets.size());
for (u32 OffsetIdx = 0; OffsetIdx < LayerOffsets.size(); OffsetIdx++)
rOut.WriteLong(LayerOffsets[OffsetIdx]);
FinishSection(false);
}
// ************ SECTION MANAGEMENT ************
void CAreaCooker::AddSectionToBlock()
{
@ -322,8 +390,14 @@ bool CAreaCooker::CookMREA(CGameArea *pArea, IOutputStream& rOut)
// Write pre-SCLY data sections
for (u32 iSec = 0; iSec < Cooker.mSCLYSecNum; iSec++)
{
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
if (iSec == Cooker.mDepsSecNum)
Cooker.WriteDependencies(Cooker.mSectionData);
else
{
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
}
}
// Write SCLY
@ -336,8 +410,14 @@ bool CAreaCooker::CookMREA(CGameArea *pArea, IOutputStream& rOut)
u32 PostSCLY = (Cooker.mVersion <= ePrime ? Cooker.mSCLYSecNum + 1 : Cooker.mSCGNSecNum + 1);
for (u32 iSec = PostSCLY; iSec < pArea->mSectionDataBuffers.size(); iSec++)
{
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
if (iSec == Cooker.mModulesSecNum)
Cooker.WriteModules(Cooker.mSectionData);
else
{
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
}
}
Cooker.FinishBlock();

View File

@ -25,6 +25,8 @@ class CAreaCooker
u32 mFFFFSecNum;
u32 mPTLASecNum;
u32 mEGMCSecNum;
u32 mDepsSecNum;
u32 mModulesSecNum;
struct SCompressedBlock
{
@ -57,6 +59,10 @@ class CAreaCooker
void WritePrimeSCLY(IOutputStream& rOut);
void WriteEchoesSCLY(IOutputStream& rOut);
// Other Sections
void WriteDependencies(IOutputStream& rOut);
void WriteModules(IOutputStream& rOut);
// Section Management
void AddSectionToBlock();
void FinishSection(bool ForceFinishBlock);

View File

@ -25,7 +25,24 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
if (Game == eEchoesDemo || Game == eEchoes)
{
DarkWorldNameID.Write(rMLVL);
rMLVL.WriteLong(0);
}
if (Game >= eEchoesDemo && Game <= eCorruption)
{
rMLVL.WriteLong(pWorld->mTempleKeyWorldIndex);
}
if (Game == eReturns)
{
const CWorld::STimeAttackData& rkData = pWorld->mTimeAttackData;
rMLVL.WriteBool(rkData.HasTimeAttack);
if (rkData.HasTimeAttack)
{
rMLVL.WriteString(rkData.ActNumber);
rMLVL.WriteFloat(rkData.BronzeTime);
rMLVL.WriteFloat(rkData.SilverTime);
rMLVL.WriteFloat(rkData.GoldTime);
rMLVL.WriteFloat(rkData.ShinyGoldTime);
}
}
SaveWorldID.Write(rMLVL);
@ -142,6 +159,10 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
rMLVL.WriteLong(ModuleLayerOffsets[iOff]);
}
// Unknown
if (Game == eReturns)
rMLVL.WriteLong(0);
// Internal Name
if (Game >= eEchoesDemo)
rMLVL.WriteString(rArea.InternalName);
@ -191,6 +212,7 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
// Layers
rMLVL.WriteLong(pWorld->mAreas.size());
std::vector<TString> LayerNames;
std::vector<CSavedStateID> LayerStateIDs;
std::vector<u32> LayerNameOffsets;
// Layer Flags
@ -209,6 +231,7 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
LayerActiveFlags &= ~(1 << iLyr);
LayerNames.push_back(rLayer.LayerName);
LayerStateIDs.push_back(rLayer.LayerStateID);
}
rMLVL.WriteLongLong(LayerActiveFlags);
@ -220,9 +243,13 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
for (u32 iLyr = 0; iLyr < LayerNames.size(); iLyr++)
rMLVL.WriteString(LayerNames[iLyr]);
// todo: Layer Saved State IDs go here for MP3/DKCR; need support for saved state IDs to implement
if (Game == eCorruption || Game == eReturns)
// Layer Saved State IDs
if (Game >= eCorruption)
{
rMLVL.WriteLong(LayerStateIDs.size());
for (u32 iLyr = 0; iLyr < LayerStateIDs.size(); iLyr++)
LayerStateIDs[iLyr].Write(rMLVL);
}
// Layer Name Offsets

View File

@ -167,19 +167,37 @@ void CWorldLoader::LoadPrimeMLVL(IInputStream& rMLVL)
pArea->Layers[iLayer].LayerName = rMLVL.ReadString();
}
// Layer state IDs
if (mVersion >= eCorruption)
{
rMLVL.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].LayerStateID = CSavedStateID(rMLVL);
}
}
// Last part of the file is layer name offsets, but we don't need it
// todo: Layer ID support for MP3
}
void CWorldLoader::LoadReturnsMLVL(IInputStream& rMLVL)
{
mpWorld->mpWorldName = gpResourceStore->LoadResource<CStringTable>(rMLVL.ReadLongLong());
bool Check = (rMLVL.ReadByte() != 0);
if (Check)
CWorld::STimeAttackData& rData = mpWorld->mTimeAttackData;
rData.HasTimeAttack = rMLVL.ReadBool();
if (rData.HasTimeAttack)
{
rMLVL.ReadString();
rMLVL.Seek(0x10, SEEK_CUR);
rData.ActNumber = rMLVL.ReadString();
rData.BronzeTime = rMLVL.ReadFloat();
rData.SilverTime = rMLVL.ReadFloat();
rData.GoldTime = rMLVL.ReadFloat();
rData.ShinyGoldTime = rMLVL.ReadFloat();
}
mpWorld->mpSaveWorld = gpResourceStore->LoadResource(rMLVL.ReadLongLong(), eSaveWorld);
@ -229,8 +247,18 @@ void CWorldLoader::LoadReturnsMLVL(IInputStream& rMLVL)
pArea->Layers[iLayer].LayerName = rMLVL.ReadString();
}
// Layer state IDs
rMLVL.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].LayerStateID = CSavedStateID(rMLVL);
}
// Last part of the file is layer name offsets, but we don't need it
// todo: Layer ID support
}
void CWorldLoader::GenerateEditorData()