Added support for Prime 3 package cooking
This commit is contained in:
parent
560706d285
commit
7f18a33fae
|
@ -226,7 +226,8 @@ HEADERS += \
|
||||||
Resource/CAudioMacro.h \
|
Resource/CAudioMacro.h \
|
||||||
CompressionUtil.h \
|
CompressionUtil.h \
|
||||||
Resource/Animation/CSourceAnimData.h \
|
Resource/Animation/CSourceAnimData.h \
|
||||||
Resource/CMapArea.h
|
Resource/CMapArea.h \
|
||||||
|
Resource/CSavedStateID.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
|
|
@ -77,7 +77,19 @@ void CPackage::Cook()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: MP3/DKCR pak format support
|
EGame Game = mpProject->Game();
|
||||||
|
u32 Alignment = (Game <= eCorruptionProto ? 0x20 : 0x40);
|
||||||
|
u32 AlignmentMinusOne = Alignment - 1;
|
||||||
|
|
||||||
|
u32 TocOffset = 0;
|
||||||
|
u32 NamesSize = 0;
|
||||||
|
u32 ResTableOffset = 0;
|
||||||
|
u32 ResTableSize = 0;
|
||||||
|
u32 ResDataSize = 0;
|
||||||
|
|
||||||
|
// Write MP1 pak header
|
||||||
|
if (Game <= eCorruptionProto)
|
||||||
|
{
|
||||||
Pak.WriteLong(0x00030005); // Major/Minor Version
|
Pak.WriteLong(0x00030005); // Major/Minor Version
|
||||||
Pak.WriteLong(0); // Unknown
|
Pak.WriteLong(0); // Unknown
|
||||||
|
|
||||||
|
@ -91,34 +103,68 @@ void CPackage::Cook()
|
||||||
rkRes.ID.Write(Pak);
|
rkRes.ID.Write(Pak);
|
||||||
Pak.WriteSizedString(rkRes.Name);
|
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());
|
Pak.WriteLong(AssetList.size());
|
||||||
u32 TocOffset = Pak.Tell();
|
CAssetID Dummy = CAssetID::InvalidID(Game);
|
||||||
|
|
||||||
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
|
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
|
||||||
{
|
{
|
||||||
Pak.WriteLongLong(0);
|
Pak.WriteLongLong(0);
|
||||||
|
Dummy.Write(Pak);
|
||||||
Pak.WriteLongLong(0);
|
Pak.WriteLongLong(0);
|
||||||
Pak.WriteLong(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Pak.WriteToBoundary(32, 0);
|
Pak.WriteToBoundary(Alignment, 0);
|
||||||
|
ResTableSize = Pak.Tell() - ResTableOffset;
|
||||||
|
|
||||||
// Start writing resources
|
// Start writing resources
|
||||||
struct SResourceTocInfo
|
struct SResourceTableInfo
|
||||||
{
|
{
|
||||||
CResourceEntry *pEntry;
|
CResourceEntry *pEntry;
|
||||||
u32 Offset;
|
u32 Offset;
|
||||||
u32 Size;
|
u32 Size;
|
||||||
bool Compressed;
|
bool Compressed;
|
||||||
};
|
};
|
||||||
std::vector<SResourceTocInfo> ResourceTocData(AssetList.size());
|
std::vector<SResourceTableInfo> ResourceTableData(AssetList.size());
|
||||||
u32 ResIdx = 0;
|
u32 ResIdx = 0;
|
||||||
|
u32 ResDataOffset = Pak.Tell();
|
||||||
|
|
||||||
for (auto Iter = AssetList.begin(); Iter != AssetList.end(); Iter++, ResIdx++)
|
for (auto Iter = AssetList.begin(); Iter != AssetList.end(); Iter++, ResIdx++)
|
||||||
{
|
{
|
||||||
// Initialize entry, recook assets if needed
|
// Initialize entry, recook assets if needed
|
||||||
|
u32 AssetOffset = Pak.Tell();
|
||||||
CAssetID ID = *Iter;
|
CAssetID ID = *Iter;
|
||||||
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
CResourceEntry *pEntry = gpResourceStore->FindEntry(ID);
|
||||||
ASSERT(pEntry != nullptr);
|
ASSERT(pEntry != nullptr);
|
||||||
|
@ -126,9 +172,9 @@ void CPackage::Cook()
|
||||||
if (pEntry->NeedsRecook())
|
if (pEntry->NeedsRecook())
|
||||||
pEntry->Cook();
|
pEntry->Cook();
|
||||||
|
|
||||||
SResourceTocInfo& rTocInfo = ResourceTocData[ResIdx];
|
SResourceTableInfo& rTableInfo = ResourceTableData[ResIdx];
|
||||||
rTocInfo.pEntry = pEntry;
|
rTableInfo.pEntry = pEntry;
|
||||||
rTocInfo.Offset = Pak.Tell();
|
rTableInfo.Offset = (Game <= eEchoes ? AssetOffset : AssetOffset - ResDataOffset);
|
||||||
|
|
||||||
// Load resource data
|
// Load resource data
|
||||||
CFileInStream CookedAsset(pEntry->CookedAssetPath(), IOUtil::eBigEndian);
|
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
|
// 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
|
// always compressed, and some types that are compressed if they're over a certain size
|
||||||
EResType Type = pEntry->ResourceType();
|
EResType Type = pEntry->ResourceType();
|
||||||
|
u32 CompressThreshold = (Game <= eCorruptionProto ? 0x400 : 0x80);
|
||||||
|
|
||||||
bool ShouldAlwaysCompress = (Type == eTexture || Type == eModel || Type == eSkin ||
|
bool ShouldAlwaysCompress = (Type == eTexture || Type == eModel || Type == eSkin ||
|
||||||
Type == eAnimSet || Type == eAnimation || Type == eFont);
|
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 &&
|
bool ShouldCompressConditional = !ShouldAlwaysCompress &&
|
||||||
(Type == eParticle || Type == eParticleElectric || Type == eParticleSwoosh ||
|
(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
|
// Write resource data to pak
|
||||||
if (!ShouldCompress)
|
if (!ShouldCompress)
|
||||||
{
|
{
|
||||||
Pak.WriteBytes(ResourceData.data(), ResourceSize);
|
Pak.WriteBytes(ResourceData.data(), ResourceSize);
|
||||||
rTocInfo.Compressed = false;
|
rTableInfo.Compressed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -164,7 +220,7 @@ void CPackage::Cook()
|
||||||
std::vector<u8> CompressedData(ResourceData.size() * 2);
|
std::vector<u8> CompressedData(ResourceData.size() * 2);
|
||||||
bool Success = false;
|
bool Success = false;
|
||||||
|
|
||||||
if (mpProject->Game() <= eEchoesDemo)
|
if (Game <= eEchoesDemo || Game == eReturns)
|
||||||
Success = CompressionUtil::CompressZlib(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedData.size(), CompressedSize);
|
Success = CompressionUtil::CompressZlib(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedData.size(), CompressedSize);
|
||||||
else
|
else
|
||||||
Success = CompressionUtil::CompressLZOSegmented(ResourceData.data(), ResourceData.size(), CompressedData.data(), CompressedSize, false);
|
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
|
// Make sure that the compressed data is actually smaller, accounting for padding + uncompressed size value
|
||||||
if (Success)
|
if (Success)
|
||||||
{
|
{
|
||||||
u32 PaddedUncompressedSize = (ResourceSize + 0x1F) & ~0x1F;
|
u32 CompressionHeaderSize = (Game <= eCorruptionProto ? 4 : 0x10);
|
||||||
u32 PaddedCompressedSize = (CompressedSize + 4 + 0x1F) & ~0x1F;
|
u32 PaddedUncompressedSize = (ResourceSize + AlignmentMinusOne) & ~AlignmentMinusOne;
|
||||||
|
u32 PaddedCompressedSize = (CompressedSize + CompressionHeaderSize + AlignmentMinusOne) & ~AlignmentMinusOne;
|
||||||
Success = (PaddedCompressedSize < PaddedUncompressedSize);
|
Success = (PaddedCompressedSize < PaddedUncompressedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write file to pak
|
// Write file to pak
|
||||||
if (Success)
|
if (Success)
|
||||||
|
{
|
||||||
|
// Write MP1/2 compressed asset
|
||||||
|
if (Game <= eCorruptionProto)
|
||||||
{
|
{
|
||||||
Pak.WriteLong(ResourceSize);
|
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);
|
Pak.WriteBytes(CompressedData.data(), CompressedSize);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
Pak.WriteBytes(ResourceData.data(), ResourceSize);
|
Pak.WriteBytes(ResourceData.data(), ResourceSize);
|
||||||
|
|
||||||
rTocInfo.Compressed = Success;
|
rTableInfo.Compressed = Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pak.WriteToBoundary(32, 0xFF);
|
Pak.WriteToBoundary(Alignment, 0xFF);
|
||||||
rTocInfo.Size = Pak.Tell() - rTocInfo.Offset;
|
rTableInfo.Size = Pak.Tell() - AssetOffset;
|
||||||
}
|
}
|
||||||
|
ResDataSize = Pak.Tell() - ResDataOffset;
|
||||||
|
|
||||||
// Write table of contents for real
|
// Write table of contents for real
|
||||||
|
if (Game >= eCorruption)
|
||||||
|
{
|
||||||
Pak.Seek(TocOffset, SEEK_SET);
|
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++)
|
for (u32 iRes = 0; iRes < AssetList.size(); iRes++)
|
||||||
{
|
{
|
||||||
const SResourceTocInfo& rkTocInfo = ResourceTocData[iRes];
|
const SResourceTableInfo& rkInfo = ResourceTableData[iRes];
|
||||||
CResourceEntry *pEntry = rkTocInfo.pEntry;
|
CResourceEntry *pEntry = rkInfo.pEntry;
|
||||||
|
|
||||||
Pak.WriteLong( rkTocInfo.Compressed ? 1 : 0 );
|
Pak.WriteLong( rkInfo.Compressed ? 1 : 0 );
|
||||||
pEntry->CookedExtension().Write(Pak);
|
pEntry->CookedExtension().Write(Pak);
|
||||||
pEntry->ID().Write(Pak);
|
pEntry->ID().Write(Pak);
|
||||||
Pak.WriteLong(rkTocInfo.Size);
|
Pak.WriteLong(rkInfo.Size);
|
||||||
Pak.WriteLong(rkTocInfo.Offset);
|
Pak.WriteLong(rkInfo.Offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear recook flag
|
||||||
mNeedsRecook = false;
|
mNeedsRecook = false;
|
||||||
Save();
|
Save();
|
||||||
|
|
||||||
Log::Write("Finished writing " + PakPath);
|
Log::Write("Finished writing " + PakPath);
|
||||||
|
|
||||||
// Update resource store in case we recooked any assets
|
// Update resource store in case we recooked any assets
|
||||||
|
|
|
@ -461,6 +461,10 @@ void CAreaDependencyListBuilder::AddDependency(const CAssetID& rkID, std::list<C
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is an audio stream, skip
|
||||||
|
if (ResType == eStreamedAudio)
|
||||||
|
return;
|
||||||
|
|
||||||
// Check to ensure this is a valid/new dependency
|
// Check to ensure this is a valid/new dependency
|
||||||
if (ResType == eWorld || ResType == eArea)
|
if (ResType == eWorld || ResType == eArea)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -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
|
|
@ -94,12 +94,25 @@ void CWorld::Serialize(IArchive& rArc)
|
||||||
if (rArc.Game() >= eEchoesDemo && rArc.Game() <= eCorruption)
|
if (rArc.Game() >= eEchoesDemo && rArc.Game() <= eCorruption)
|
||||||
rArc << SERIAL("TempleKeyWorldIndex", mTempleKeyWorldIndex);
|
rArc << SERIAL("TempleKeyWorldIndex", mTempleKeyWorldIndex);
|
||||||
|
|
||||||
|
if (rArc.Game() == eReturns)
|
||||||
|
rArc << SERIAL("TimeAttackData", mTimeAttackData);
|
||||||
|
|
||||||
if (rArc.Game() == ePrime)
|
if (rArc.Game() == ePrime)
|
||||||
rArc << SERIAL_CONTAINER("MemoryRelays", mMemoryRelays, "MemoryRelay");
|
rArc << SERIAL_CONTAINER("MemoryRelays", mMemoryRelays, "MemoryRelay");
|
||||||
|
|
||||||
rArc << SERIAL_CONTAINER("Areas", mAreas, "Area");
|
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)
|
void Serialize(IArchive& rArc, CWorld::SMemoryRelay& rMemRelay)
|
||||||
{
|
{
|
||||||
rArc << SERIAL_HEX("MemoryRelayID", rMemRelay.InstanceID)
|
rArc << SERIAL_HEX("MemoryRelayID", rMemRelay.InstanceID)
|
||||||
|
@ -140,6 +153,11 @@ void Serialize(IArchive& rArc, CWorld::SArea::SLayer& rLayer)
|
||||||
{
|
{
|
||||||
rArc << SERIAL("Name", rLayer.LayerName)
|
rArc << SERIAL("Name", rLayer.LayerName)
|
||||||
<< SERIAL("Active", rLayer.Active);
|
<< SERIAL("Active", rLayer.Active);
|
||||||
|
|
||||||
|
if (rArc.Game() >= eCorruption)
|
||||||
|
{
|
||||||
|
rArc << SERIAL("StateID", rLayer.LayerStateID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Serialize(IArchive& rArc, CWorld::SAudioGrp& rAudioGrp)
|
void Serialize(IArchive& rArc, CWorld::SAudioGrp& rAudioGrp)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#define CWORLD_H
|
#define CWORLD_H
|
||||||
|
|
||||||
#include "CResource.h"
|
#include "CResource.h"
|
||||||
|
#include "CSavedStateID.h"
|
||||||
#include "CStringTable.h"
|
#include "CStringTable.h"
|
||||||
#include "Core/Resource/Area/CGameArea.h"
|
#include "Core/Resource/Area/CGameArea.h"
|
||||||
#include "Core/Resource/Model/CModel.h"
|
#include "Core/Resource/Model/CModel.h"
|
||||||
|
@ -22,6 +23,16 @@ class CWorld : public CResource
|
||||||
TResPtr<CResource> mpMapWorld;
|
TResPtr<CResource> mpMapWorld;
|
||||||
u32 mTempleKeyWorldIndex;
|
u32 mTempleKeyWorldIndex;
|
||||||
|
|
||||||
|
struct STimeAttackData
|
||||||
|
{
|
||||||
|
bool HasTimeAttack;
|
||||||
|
TString ActNumber;
|
||||||
|
float BronzeTime;
|
||||||
|
float SilverTime;
|
||||||
|
float GoldTime;
|
||||||
|
float ShinyGoldTime;
|
||||||
|
} mTimeAttackData;
|
||||||
|
|
||||||
struct SAudioGrp
|
struct SAudioGrp
|
||||||
{
|
{
|
||||||
CAssetID ResID;
|
CAssetID ResID;
|
||||||
|
@ -69,7 +80,7 @@ class CWorld : public CResource
|
||||||
{
|
{
|
||||||
TString LayerName;
|
TString LayerName;
|
||||||
bool Active;
|
bool Active;
|
||||||
u8 LayerID[16];
|
CSavedStateID LayerStateID;
|
||||||
};
|
};
|
||||||
std::vector<SLayer> Layers;
|
std::vector<SLayer> Layers;
|
||||||
};
|
};
|
||||||
|
@ -86,6 +97,7 @@ public:
|
||||||
|
|
||||||
// Serialization
|
// Serialization
|
||||||
virtual void Serialize(IArchive& rArc);
|
virtual void Serialize(IArchive& rArc);
|
||||||
|
friend void Serialize(IArchive& rArc, STimeAttackData& rTimeAttackData);
|
||||||
friend void Serialize(IArchive& rArc, SMemoryRelay& rMemRelay);
|
friend void Serialize(IArchive& rArc, SMemoryRelay& rMemRelay);
|
||||||
friend void Serialize(IArchive& rArc, SArea& rArea);
|
friend void Serialize(IArchive& rArc, SArea& rArea);
|
||||||
friend void Serialize(IArchive& rArc, SArea::SDock& rDock);
|
friend void Serialize(IArchive& rArc, SArea::SDock& rDock);
|
||||||
|
|
|
@ -1,11 +1,26 @@
|
||||||
#include "CAreaCooker.h"
|
#include "CAreaCooker.h"
|
||||||
#include "CScriptCooker.h"
|
#include "CScriptCooker.h"
|
||||||
#include "Core/CompressionUtil.h"
|
#include "Core/CompressionUtil.h"
|
||||||
|
#include "Core/GameProject/DependencyListBuilders.h"
|
||||||
#include <Common/Log.h>
|
#include <Common/Log.h>
|
||||||
|
|
||||||
const bool gkForceDisableCompression = false;
|
const bool gkForceDisableCompression = true;
|
||||||
|
|
||||||
CAreaCooker::CAreaCooker()
|
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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +86,8 @@ void CAreaCooker::DetermineSectionNumbersCorruption()
|
||||||
CGameArea::SSectionNumber& rNum = mpArea->mSectionNumbers[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 == "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);
|
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 ************
|
// ************ SECTION MANAGEMENT ************
|
||||||
void CAreaCooker::AddSectionToBlock()
|
void CAreaCooker::AddSectionToBlock()
|
||||||
{
|
{
|
||||||
|
@ -321,10 +389,16 @@ bool CAreaCooker::CookMREA(CGameArea *pArea, IOutputStream& rOut)
|
||||||
|
|
||||||
// Write pre-SCLY data sections
|
// Write pre-SCLY data sections
|
||||||
for (u32 iSec = 0; iSec < Cooker.mSCLYSecNum; iSec++)
|
for (u32 iSec = 0; iSec < Cooker.mSCLYSecNum; iSec++)
|
||||||
|
{
|
||||||
|
if (iSec == Cooker.mDepsSecNum)
|
||||||
|
Cooker.WriteDependencies(Cooker.mSectionData);
|
||||||
|
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
|
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
|
||||||
Cooker.FinishSection(false);
|
Cooker.FinishSection(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write SCLY
|
// Write SCLY
|
||||||
if (Cooker.mVersion <= eEchoesDemo)
|
if (Cooker.mVersion <= eEchoesDemo)
|
||||||
|
@ -335,10 +409,16 @@ bool CAreaCooker::CookMREA(CGameArea *pArea, IOutputStream& rOut)
|
||||||
// Write post-SCLY data sections
|
// Write post-SCLY data sections
|
||||||
u32 PostSCLY = (Cooker.mVersion <= ePrime ? Cooker.mSCLYSecNum + 1 : Cooker.mSCGNSecNum + 1);
|
u32 PostSCLY = (Cooker.mVersion <= ePrime ? Cooker.mSCLYSecNum + 1 : Cooker.mSCGNSecNum + 1);
|
||||||
for (u32 iSec = PostSCLY; iSec < pArea->mSectionDataBuffers.size(); iSec++)
|
for (u32 iSec = PostSCLY; iSec < pArea->mSectionDataBuffers.size(); iSec++)
|
||||||
|
{
|
||||||
|
if (iSec == Cooker.mModulesSecNum)
|
||||||
|
Cooker.WriteModules(Cooker.mSectionData);
|
||||||
|
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
|
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
|
||||||
Cooker.FinishSection(false);
|
Cooker.FinishSection(false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Cooker.FinishBlock();
|
Cooker.FinishBlock();
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,8 @@ class CAreaCooker
|
||||||
u32 mFFFFSecNum;
|
u32 mFFFFSecNum;
|
||||||
u32 mPTLASecNum;
|
u32 mPTLASecNum;
|
||||||
u32 mEGMCSecNum;
|
u32 mEGMCSecNum;
|
||||||
|
u32 mDepsSecNum;
|
||||||
|
u32 mModulesSecNum;
|
||||||
|
|
||||||
struct SCompressedBlock
|
struct SCompressedBlock
|
||||||
{
|
{
|
||||||
|
@ -57,6 +59,10 @@ class CAreaCooker
|
||||||
void WritePrimeSCLY(IOutputStream& rOut);
|
void WritePrimeSCLY(IOutputStream& rOut);
|
||||||
void WriteEchoesSCLY(IOutputStream& rOut);
|
void WriteEchoesSCLY(IOutputStream& rOut);
|
||||||
|
|
||||||
|
// Other Sections
|
||||||
|
void WriteDependencies(IOutputStream& rOut);
|
||||||
|
void WriteModules(IOutputStream& rOut);
|
||||||
|
|
||||||
// Section Management
|
// Section Management
|
||||||
void AddSectionToBlock();
|
void AddSectionToBlock();
|
||||||
void FinishSection(bool ForceFinishBlock);
|
void FinishSection(bool ForceFinishBlock);
|
||||||
|
|
|
@ -25,7 +25,24 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
|
||||||
if (Game == eEchoesDemo || Game == eEchoes)
|
if (Game == eEchoesDemo || Game == eEchoes)
|
||||||
{
|
{
|
||||||
DarkWorldNameID.Write(rMLVL);
|
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);
|
SaveWorldID.Write(rMLVL);
|
||||||
|
@ -142,6 +159,10 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
|
||||||
rMLVL.WriteLong(ModuleLayerOffsets[iOff]);
|
rMLVL.WriteLong(ModuleLayerOffsets[iOff]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unknown
|
||||||
|
if (Game == eReturns)
|
||||||
|
rMLVL.WriteLong(0);
|
||||||
|
|
||||||
// Internal Name
|
// Internal Name
|
||||||
if (Game >= eEchoesDemo)
|
if (Game >= eEchoesDemo)
|
||||||
rMLVL.WriteString(rArea.InternalName);
|
rMLVL.WriteString(rArea.InternalName);
|
||||||
|
@ -191,6 +212,7 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
|
||||||
// Layers
|
// Layers
|
||||||
rMLVL.WriteLong(pWorld->mAreas.size());
|
rMLVL.WriteLong(pWorld->mAreas.size());
|
||||||
std::vector<TString> LayerNames;
|
std::vector<TString> LayerNames;
|
||||||
|
std::vector<CSavedStateID> LayerStateIDs;
|
||||||
std::vector<u32> LayerNameOffsets;
|
std::vector<u32> LayerNameOffsets;
|
||||||
|
|
||||||
// Layer Flags
|
// Layer Flags
|
||||||
|
@ -209,6 +231,7 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
|
||||||
LayerActiveFlags &= ~(1 << iLyr);
|
LayerActiveFlags &= ~(1 << iLyr);
|
||||||
|
|
||||||
LayerNames.push_back(rLayer.LayerName);
|
LayerNames.push_back(rLayer.LayerName);
|
||||||
|
LayerStateIDs.push_back(rLayer.LayerStateID);
|
||||||
}
|
}
|
||||||
|
|
||||||
rMLVL.WriteLongLong(LayerActiveFlags);
|
rMLVL.WriteLongLong(LayerActiveFlags);
|
||||||
|
@ -220,9 +243,13 @@ bool CWorldCooker::CookMLVL(CWorld *pWorld, IOutputStream& rMLVL)
|
||||||
for (u32 iLyr = 0; iLyr < LayerNames.size(); iLyr++)
|
for (u32 iLyr = 0; iLyr < LayerNames.size(); iLyr++)
|
||||||
rMLVL.WriteString(LayerNames[iLyr]);
|
rMLVL.WriteString(LayerNames[iLyr]);
|
||||||
|
|
||||||
// todo: Layer Saved State IDs go here for MP3/DKCR; need support for saved state IDs to implement
|
// Layer Saved State IDs
|
||||||
if (Game == eCorruption || Game == eReturns)
|
if (Game >= eCorruption)
|
||||||
{
|
{
|
||||||
|
rMLVL.WriteLong(LayerStateIDs.size());
|
||||||
|
|
||||||
|
for (u32 iLyr = 0; iLyr < LayerStateIDs.size(); iLyr++)
|
||||||
|
LayerStateIDs[iLyr].Write(rMLVL);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer Name Offsets
|
// Layer Name Offsets
|
||||||
|
|
|
@ -167,19 +167,37 @@ void CWorldLoader::LoadPrimeMLVL(IInputStream& rMLVL)
|
||||||
pArea->Layers[iLayer].LayerName = rMLVL.ReadString();
|
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
|
// 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)
|
void CWorldLoader::LoadReturnsMLVL(IInputStream& rMLVL)
|
||||||
{
|
{
|
||||||
mpWorld->mpWorldName = gpResourceStore->LoadResource<CStringTable>(rMLVL.ReadLongLong());
|
mpWorld->mpWorldName = gpResourceStore->LoadResource<CStringTable>(rMLVL.ReadLongLong());
|
||||||
|
|
||||||
bool Check = (rMLVL.ReadByte() != 0);
|
CWorld::STimeAttackData& rData = mpWorld->mTimeAttackData;
|
||||||
if (Check)
|
rData.HasTimeAttack = rMLVL.ReadBool();
|
||||||
|
|
||||||
|
if (rData.HasTimeAttack)
|
||||||
{
|
{
|
||||||
rMLVL.ReadString();
|
rData.ActNumber = rMLVL.ReadString();
|
||||||
rMLVL.Seek(0x10, SEEK_CUR);
|
rData.BronzeTime = rMLVL.ReadFloat();
|
||||||
|
rData.SilverTime = rMLVL.ReadFloat();
|
||||||
|
rData.GoldTime = rMLVL.ReadFloat();
|
||||||
|
rData.ShinyGoldTime = rMLVL.ReadFloat();
|
||||||
}
|
}
|
||||||
|
|
||||||
mpWorld->mpSaveWorld = gpResourceStore->LoadResource(rMLVL.ReadLongLong(), eSaveWorld);
|
mpWorld->mpSaveWorld = gpResourceStore->LoadResource(rMLVL.ReadLongLong(), eSaveWorld);
|
||||||
|
@ -229,8 +247,18 @@ void CWorldLoader::LoadReturnsMLVL(IInputStream& rMLVL)
|
||||||
pArea->Layers[iLayer].LayerName = rMLVL.ReadString();
|
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
|
// Last part of the file is layer name offsets, but we don't need it
|
||||||
// todo: Layer ID support
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWorldLoader::GenerateEditorData()
|
void CWorldLoader::GenerateEditorData()
|
||||||
|
|
Loading…
Reference in New Issue