Added area cooking support for MP2 and MP3. DKCR support has been started but is currently broken.

This commit is contained in:
parax0 2016-02-22 11:47:47 -07:00
parent 200918671b
commit 9f2c4d75bf
15 changed files with 638 additions and 189 deletions

View File

@ -1,71 +1,71 @@
#include "CAnimationParameters.h"
#include "CAnimSet.h"
#include "CResCache.h"
#include "CResourceInfo.h"
#include <Common/Log.h>
#include <iostream>
CAnimationParameters::CAnimationParameters()
{
mGame = ePrime;
mpCharSet = nullptr;
mNodeIndex = 0;
mUnknown1 = 0;
mUnknown2 = 0;
mUnknown3 = 0;
mUnknown4 = 0;
}
CAnimationParameters::CAnimationParameters(IInputStream& SCLY, EGame Game)
{
mGame = Game;
mpCharSet = nullptr;
mNodeIndex = 0;
mUnknown1 = 0;
mUnknown2 = 0;
mUnknown3 = 0;
mUnknown4 = 0;
if (Game <= eEchoes)
{
u32 AnimSetID = SCLY.ReadLong();
mCharacter = CResourceInfo(SCLY.ReadLong(), "ANCS");
mNodeIndex = SCLY.ReadLong();
mUnknown1 = SCLY.ReadLong();
mpCharSet = gResCache.GetResource(AnimSetID, "ANCS");
}
else if (Game <= eCorruption)
{
u64 CharID = SCLY.ReadLongLong();
mCharacter = CResourceInfo(SCLY.ReadLongLong(), "CHAR");
mUnknown1 = SCLY.ReadLong();
mpCharSet = gResCache.GetResource(CharID, "CHAR");
}
else if (Game == eReturns)
{
SCLY.Seek(-6, SEEK_CUR);
u32 Offset = SCLY.Tell();
u32 PropID = SCLY.ReadLong();
SCLY.Seek(2, SEEK_CUR);
u8 Flags = SCLY.ReadByte();
mUnknown1 = (u32) SCLY.ReadByte();
mUnknown1 &= 0xFF;
if (mUnknown1 == 0x60)
// 0x80 - CharacterAnimationSet is empty.
if (Flags & 0x80)
{
u64 charID = SCLY.ReadLongLong();
mUnknown2 = SCLY.ReadLong();
mUnknown3 = SCLY.ReadLong();
mUnknown4 = SCLY.ReadLong();
mpCharSet = gResCache.GetResource(charID, "CHAR");
mUnknown1 = -1;
mUnknown2 = 0;
mUnknown3 = 0;
return;
}
else if (mUnknown1 != 0x80)
mCharacter = CResourceInfo(SCLY.ReadLongLong(), "CHAR");
// 0x20 - Default Anim is present
if (Flags & 0x20)
mUnknown1 = SCLY.ReadLong();
else
mUnknown1 = -1;
// 0x40 - Two-value struct is present
if (Flags & 0x40)
{
Log::FileError(SCLY.GetSourceString(), Offset,
"Unexpected AnimationParameters byte: " + TString::HexString(mUnknown1, true, true, 2) + " (property " + TString::HexString(PropID, true, true, 8) + ")");
mUnknown2 = SCLY.ReadLong();
mUnknown3 = SCLY.ReadLong();
}
else
{
mUnknown2 = 0;
mUnknown3 = 0;
}
}
}
@ -74,9 +74,9 @@ void CAnimationParameters::Write(IOutputStream& rSCLY)
{
if (mGame <= eEchoes)
{
if (mpCharSet)
if (mCharacter.IsValid())
{
rSCLY.WriteLong(AnimSet()->ResID().ToLong());
rSCLY.WriteLong(mCharacter.ID().ToLong());
rSCLY.WriteLong(mNodeIndex);
rSCLY.WriteLong(mUnknown1);
}
@ -87,26 +87,72 @@ void CAnimationParameters::Write(IOutputStream& rSCLY)
rSCLY.WriteLong(0xFFFFFFFF);
}
}
else if (mGame <= eCorruption)
{
if (mCharacter.IsValid())
{
rSCLY.WriteLongLong(mCharacter.ID().ToLongLong());
rSCLY.WriteLong(mUnknown1);
}
else
{
rSCLY.WriteLongLong(CUniqueID::skInvalidID64.ToLongLong());
rSCLY.WriteLong(0xFFFFFFFF);
}
}
else
{
if (!mCharacter.IsValid())
rSCLY.WriteByte((u8) 0x80);
else
{
u8 Flag = 0;
if (mUnknown1 != -1) Flag |= 0x20;
if (mUnknown2 != 0 || mUnknown3 != 0) Flag |= 0x40;
rSCLY.WriteByte(Flag);
rSCLY.WriteLongLong(mCharacter.ID().ToLongLong());
if (Flag & 0x20)
rSCLY.WriteLong(mUnknown1);
if (Flag & 0x40)
{
rSCLY.WriteLong(mUnknown2);
rSCLY.WriteLong(mUnknown3);
}
}
}
}
CModel* CAnimationParameters::GetCurrentModel(s32 NodeIndex /*= -1*/)
{
if (!mpCharSet) return nullptr;
if (mpCharSet->Type() != eAnimSet) return nullptr;
if (!mCharacter.IsValid()) return nullptr;
CAnimSet *pSet = (CAnimSet*) mCharacter.Load();
if (!pSet) return nullptr;
if (pSet->Type() != eAnimSet) return nullptr;
if (NodeIndex == -1) NodeIndex = mNodeIndex;
if (mpCharSet->getNodeCount() <= (u32) NodeIndex) return nullptr;
return mpCharSet->getNodeModel(NodeIndex);
if (pSet->getNodeCount() <= (u32) NodeIndex) return nullptr;
return pSet->getNodeModel(NodeIndex);
}
TString CAnimationParameters::GetCurrentCharacterName(s32 NodeIndex /*= -1*/)
{
if (!mpCharSet) return "";
if (mpCharSet->Type() != eAnimSet) return "";
if (!mCharacter.IsValid()) return "";
CAnimSet *pSet = (CAnimSet*) mCharacter.Load();
if (!pSet) return "";
if (pSet->Type() != eAnimSet) return "";
if (NodeIndex == -1) NodeIndex = mNodeIndex;
if (mpCharSet->getNodeCount() <= (u32) NodeIndex) return "";
return mpCharSet->getNodeName((u32) NodeIndex);
if (pSet->getNodeCount() <= (u32) NodeIndex) return "";
return pSet->getNodeName((u32) NodeIndex);
}
// ************ GETTERS ************
@ -117,7 +163,7 @@ EGame CAnimationParameters::Version()
CAnimSet* CAnimationParameters::AnimSet()
{
return mpCharSet;
return (CAnimSet*) mCharacter.Load();
}
u32 CAnimationParameters::CharacterIndex()
@ -132,21 +178,20 @@ u32 CAnimationParameters::Unknown(u32 Index)
case 0: return mUnknown1;
case 1: return mUnknown2;
case 2: return mUnknown3;
case 3: return mUnknown4;
default: return 0;
}
}
// ************ SETTERS ************
void CAnimationParameters::SetResource(CResource *pRes)
void CAnimationParameters::SetResource(CResourceInfo Res)
{
if (!pRes || (pRes->Type() == eAnimSet) || (pRes->Type() == eCharacter))
if (Res.Type() == "ANCS" || Res.Type() == "CHAR")
{
mpCharSet = pRes;
mCharacter = Res;
mNodeIndex = 0;
}
else
Log::Error("Resource with invalid type passed to CAnimationParameters: " + pRes->Source());
Log::Error("Resource with invalid type passed to CAnimationParameters: " + Res.ToString());
}
void CAnimationParameters::SetNodeIndex(u32 Index)
@ -161,6 +206,5 @@ void CAnimationParameters::SetUnknown(u32 Index, u32 Value)
case 0: mUnknown1 = Value;
case 1: mUnknown2 = Value;
case 2: mUnknown3 = Value;
case 3: mUnknown4 = Value;
}
}

View File

@ -2,6 +2,7 @@
#define CANIMATIONPARAMETERS_H
#include "CAnimSet.h"
#include "CResourceInfo.h"
#include "EGame.h"
#include "TResPtr.h"
#include "Core/Resource/Model/CModel.h"
@ -9,13 +10,12 @@
class CAnimationParameters
{
EGame mGame;
TResPtr<CAnimSet> mpCharSet;
CResourceInfo mCharacter;
u32 mNodeIndex;
u32 mUnknown1;
u32 mUnknown2;
u32 mUnknown3;
u32 mUnknown4;
public:
CAnimationParameters();
@ -32,7 +32,7 @@ public:
u32 Unknown(u32 index);
// Setters
void SetResource(CResource *pRes);
void SetResource(CResourceInfo Res);
void SetNodeIndex(u32 Index);
void SetUnknown(u32 Index, u32 Value);
@ -40,12 +40,11 @@ public:
inline bool operator==(const CAnimationParameters& rkOther) const
{
return ( (mGame == rkOther.mGame) &&
(mpCharSet == rkOther.mpCharSet) &&
(mCharacter == rkOther.mCharacter) &&
(mNodeIndex == rkOther.mNodeIndex) &&
(mUnknown1 == rkOther.mUnknown1) &&
(mUnknown2 == rkOther.mUnknown2) &&
(mUnknown3 == rkOther.mUnknown3) &&
(mUnknown4 == rkOther.mUnknown4) );
(mUnknown3 == rkOther.mUnknown3) );
}
};

View File

@ -7,6 +7,8 @@ CGameArea::CGameArea() : CResource()
mVertexCount = 0;
mTriangleCount = 0;
mTerrainMerged = false;
mOriginalWorldMeshCount = 0;
mUsesCompression = false;
mMaterialSet = nullptr;
mpGeneratorLayer = nullptr;
mCollision = nullptr;

View File

@ -29,8 +29,18 @@ class CGameArea : public CResource
CTransform4f mTransform;
CAABox mAABox;
// Section data buffers; this is used to avoid having to regenerate the entire contents of the file on cook
// Data saved from the original file to help on recook
std::vector<std::vector<u8>> mSectionDataBuffers;
u32 mOriginalWorldMeshCount;
bool mUsesCompression;
struct SSectionNumber
{
CFourCC SectionID;
u32 Index;
};
std::vector<SSectionNumber> mSectionNumbers;
// Geometry
CMaterialSet *mMaterialSet;
std::vector<CModel*> mTerrainModels; // TerrainModels is the original version of each model; this is currently mainly used in the POI map editor
@ -39,7 +49,6 @@ class CGameArea : public CResource
std::vector<CScriptLayer*> mScriptLayers;
CScriptLayer *mpGeneratorLayer;
std::unordered_map<u32, CScriptObject*> mObjectMap;
// Collision
CCollisionMeshGroup *mCollision;
// Lights

View File

@ -25,6 +25,7 @@ void CWorld::SetAreaLayerInfo(CGameArea *pArea, u32 AreaIndex)
for (u32 iLyr = 0; iLyr < pArea->GetScriptLayerCount(); iLyr++)
{
if (AreaInfo.Layers.size() <= iLyr) break;
CScriptLayer *pLayer = pArea->GetScriptLayer(iLyr);
SArea::SLayer& LayerInfo = AreaInfo.Layers[iLyr];

View File

@ -1,80 +1,190 @@
#include "CAreaCooker.h"
#include "CScriptCooker.h"
#include <Common/CompressionUtil.h>
#include <Common/Log.h>
const bool gkForceDisableCompression = false;
CAreaCooker::CAreaCooker()
{
}
void CAreaCooker::DetermineSectionNumbers()
void CAreaCooker::DetermineSectionNumbersPrime()
{
mGeometrySecNum = 0;
// Determine how many sections are taken up by geometry...
u32 GeometrySections = 1; // Starting at 1 to account for materials
// Each world mesh has 7-9 sections (depending on game) plus one section per surface.
u32 GeometrySections = 0;
u32 OriginalMeshCount = mpArea->mOriginalWorldMeshCount;
// Each world mesh has (7 + surface count) sections
// header, verts, normals, colors, float UVs, short UVs, surface offsets + surfaces
for (u32 iMesh = 0; iMesh < mpArea->mTerrainModels.size(); iMesh++)
switch (mVersion)
{
CModel *pModel = mpArea->mTerrainModels[iMesh];
GeometrySections += (7 + pModel->GetSurfaceCount());
case ePrimeDemo:
case ePrime:
GeometrySections = 1 + (7 * OriginalMeshCount); // Accounting for materials
break;
case eEchoesDemo:
GeometrySections = 2 + (9 * OriginalMeshCount); // Account for materials + AROT
break;
case eEchoes:
GeometrySections = 3 + (9 * OriginalMeshCount); // Acount for materials + AROT + an unknown section
break;
}
for (u32 iMesh = 0; iMesh < mpArea->mTerrainModels.size(); iMesh++)
GeometrySections += mpArea->mTerrainModels[iMesh]->GetSurfaceCount();
// Set section numbers
mArotSecNum = mGeometrySecNum + GeometrySections;
mSclySecNum = mArotSecNum + 1;
mCollisionSecNum = mSclySecNum + 1;
mUnknownSecNum = mCollisionSecNum + 1;
mLightsSecNum = mUnknownSecNum + 1;
mVisiSecNum = mLightsSecNum + 1;
mPathSecNum = mVisiSecNum + 1;
u32 SecNum = GeometrySections;
if (mVersion <= ePrime) mAROTSecNum = SecNum++;
if (mVersion >= eEchoesDemo) mFFFFSecNum = SecNum++;
if (mVersion >= eEchoesDemo)
{
mSCLYSecNum = SecNum;
SecNum += (mVersion >= eEchoes ? mpArea->mScriptLayers.size() : 1);
mSCGNSecNum = SecNum++;
}
else
mSCLYSecNum = SecNum++;
mCollisionSecNum = SecNum++;
mUnknownSecNum = SecNum++;
mLightsSecNum = SecNum++;
mVISISecNum = SecNum++;
mPATHSecNum = SecNum++;
if (mVersion >= eEchoesDemo)
{
mPTLASecNum = SecNum++;
mEGMCSecNum = SecNum++;
}
}
void CAreaCooker::DetermineSectionNumbersCorruption()
{
// Because we're copying these from the original file (because not all the numbers
// are present in every file), we don't care about any of these except SCLY and SCGN.
for (u32 iNum = 0; iNum < mpArea->mSectionNumbers.size(); iNum++)
{
CGameArea::SSectionNumber& rNum = mpArea->mSectionNumbers[iNum];
if (rNum.SectionID == "SOBJ") mSCLYSecNum = rNum.Index;
else if (rNum.SectionID == "SGEN") mSCGNSecNum = rNum.Index;
}
}
// ************ HEADER ************
void CAreaCooker::WritePrimeHeader(IOutputStream& rOut)
{
rOut.WriteLong(0xDEADBEEF);
rOut.WriteLong(GetMREAVersion(mVersion));
mpArea->mTransform.Write(rOut);
rOut.WriteLong(mpArea->mTerrainModels.size());
rOut.WriteLong(mpArea->mOriginalWorldMeshCount);
if (mVersion >= eEchoes) rOut.WriteLong(mpArea->mScriptLayers.size());
rOut.WriteLong(mpArea->mSectionDataBuffers.size());
rOut.WriteLong(mGeometrySecNum);
rOut.WriteLong(mSclySecNum);
rOut.WriteLong(mSCLYSecNum);
if (mVersion >= eEchoesDemo) rOut.WriteLong(mSCGNSecNum);
rOut.WriteLong(mCollisionSecNum);
rOut.WriteLong(mUnknownSecNum);
rOut.WriteLong(mLightsSecNum);
rOut.WriteLong(mVisiSecNum);
rOut.WriteLong(mPathSecNum);
rOut.WriteLong(mArotSecNum);
rOut.WriteLong(mVISISecNum);
rOut.WriteLong(mPATHSecNum);
if (mVersion <= ePrime) rOut.WriteLong(mAROTSecNum);
mSectionSizesOffset = rOut.Tell();
for (u32 iSec = 0; iSec < mpArea->mSectionDataBuffers.size(); iSec++)
rOut.WriteLong(0);
else
{
rOut.WriteLong(mFFFFSecNum);
rOut.WriteLong(mPTLASecNum);
rOut.WriteLong(mEGMCSecNum);
}
if (mVersion >= eEchoesDemo)
{
if (mVersion >= eEchoes) rOut.WriteLong(mCompressedBlocks.size());
rOut.WriteToBoundary(32, 0);
}
for (u32 iSec = 0; iSec < mSectionSizes.size(); iSec++)
rOut.WriteLong(mSectionSizes[iSec]);
rOut.WriteToBoundary(32, 0);
if (mVersion >= eEchoes)
WriteCompressionHeader(rOut);
}
void CAreaCooker::WriteCorruptionHeader(IOutputStream& rOut)
{
rOut.WriteLong(0xDEADBEEF);
rOut.WriteLong(GetMREAVersion(mVersion));
mpArea->mTransform.Write(rOut);
rOut.WriteLong(mpArea->mOriginalWorldMeshCount);
rOut.WriteLong(mpArea->mScriptLayers.size());
rOut.WriteLong(mpArea->mSectionDataBuffers.size());
rOut.WriteLong(mCompressedBlocks.size());
rOut.WriteLong(mpArea->mSectionNumbers.size());
rOut.WriteToBoundary(32, 0);
for (u32 iSec = 0; iSec < mSectionSizes.size(); iSec++)
rOut.WriteLong(mSectionSizes[iSec]);
rOut.WriteToBoundary(32, 0);
mSectionMgr.SetSectionCount(mpArea->mSectionDataBuffers.size());
mSectionMgr.Init(rOut);
WriteCompressionHeader(rOut);
for (u32 iNum = 0; iNum < mpArea->mSectionNumbers.size(); iNum++)
{
CGameArea::SSectionNumber& rNum = mpArea->mSectionNumbers[iNum];
rOut.WriteLong(rNum.SectionID.ToLong());
rOut.WriteLong(rNum.Index);
}
rOut.WriteToBoundary(32, 0);
}
void CAreaCooker::WriteCompressionHeader(IOutputStream& rOut)
{
for (u32 iCmp = 0; iCmp < mCompressedBlocks.size(); iCmp++)
{
SCompressedBlock& rBlock = mCompressedBlocks[iCmp];
bool IsCompressed = (rBlock.CompressedSize != 0);
rOut.WriteLong(IsCompressed ? rBlock.DecompressedSize + 0x120 : rBlock.DecompressedSize);
rOut.WriteLong(rBlock.DecompressedSize);
rOut.WriteLong(rBlock.CompressedSize);
rOut.WriteLong(rBlock.NumSections);
}
rOut.WriteToBoundary(32, 0);
}
void CAreaCooker::WriteAreaData(IOutputStream& rOut)
{
rOut.WriteBytes(mAreaData.Data(), mAreaData.Size());
rOut.WriteToBoundary(32, 0);
}
// ************ SCLY ************
void CAreaCooker::WritePrimeSCLY(IOutputStream& rOut)
{
u32 NumLayers = mpArea->mScriptLayers.size();
rOut.WriteString("SCLY", 4);
rOut.WriteLong(0x1); // Unknown value, but it's always 1
mVersion <= ePrime ? rOut.WriteLong(1) : rOut.WriteByte(1);
u32 NumLayers = mpArea->mScriptLayers.size();
rOut.WriteLong(NumLayers);
u32 LayerSizesStart = rOut.Tell();
for (u32 iLyr = 0; iLyr < NumLayers; iLyr++)
rOut.WriteLong(0);
// SCLY
std::vector<u32> LayerSizes(NumLayers);
for (u32 iLyr = 0; iLyr < mpArea->mScriptLayers.size(); iLyr++)
for (u32 iLyr = 0; iLyr < NumLayers; iLyr++)
{
u32 LayerStart = rOut.Tell();
CScriptCooker::WriteLayer(mpArea->Version(), mpArea->mScriptLayers[iLyr], rOut);
CScriptCooker::WriteLayer(mVersion, mpArea->mScriptLayers[iLyr], rOut);
LayerSizes[iLyr] = rOut.Tell() - LayerStart;
}
@ -85,7 +195,114 @@ void CAreaCooker::WritePrimeSCLY(IOutputStream& rOut)
rOut.WriteLong(LayerSizes[iLyr]);
rOut.Seek(LayersEnd, SEEK_SET);
rOut.WriteToBoundary(32, 0);
FinishSection(false);
// SCGN
if (mVersion == eEchoesDemo)
{
rOut.WriteString("SCGN", 4);
rOut.WriteByte(1);
CScriptCooker::WriteLayer(mVersion, mpArea->mpGeneratorLayer, rOut);
FinishSection(false);
}
}
void CAreaCooker::WriteEchoesSCLY(IOutputStream& rOut)
{
// SCLY
for (u32 iLyr = 0; iLyr < mpArea->mScriptLayers.size(); iLyr++)
{
rOut.WriteString("SCLY", 4);
rOut.WriteByte(0x1);
rOut.WriteLong(iLyr);
CScriptCooker::WriteLayer(mVersion, mpArea->mScriptLayers[iLyr], rOut);
FinishSection(true);
}
// SCGN
rOut.WriteString("SCGN", 4);
rOut.WriteByte(0x1);
CScriptCooker::WriteLayer(mVersion, mpArea->mpGeneratorLayer, rOut);
FinishSection(true);
}
// ************ SECTION MANAGEMENT ************
void CAreaCooker::AddSectionToBlock()
{
mCompressedData.WriteBytes(mSectionData.Data(), mSectionData.Size());
mCompressedData.WriteToBoundary(32, 0);
mCurBlock.DecompressedSize += mSectionData.Size();
mCurBlock.NumSections++;
}
void CAreaCooker::FinishSection(bool SingleSectionBlock)
{
// Our section data is now finished in mSection...
const u32 kSizeThreshold = 0x20000;
mSectionData.WriteToBoundary(32, 0);
u32 SecSize = mSectionData.Size();
mSectionSizes.push_back(SecSize);
// Only track compressed blocks for MP2+. Write everything to one block for MP1.
if (mVersion >= eEchoes)
{
// Finish the current block if this is a single section block OR if the new section would push the block over the size limit.
if (mCurBlock.NumSections > 0 && (mCurBlock.DecompressedSize + SecSize > kSizeThreshold || SingleSectionBlock))
FinishBlock();
AddSectionToBlock();
// And finally for a single section block, finish the new block.
if (SingleSectionBlock)
FinishBlock();
}
else AddSectionToBlock();
mSectionData.Clear();
}
void CAreaCooker::FinishBlock()
{
if (mCurBlock.NumSections == 0) return;
std::vector<u8> CompressedBuf(mCompressedData.Size() * 2);
bool EnableCompression = (mVersion >= eEchoes) && mpArea->mUsesCompression && !gkForceDisableCompression;
bool UseZlib = (mVersion == eReturns);
u32 CompressedSize = 0;
bool WriteCompressedData = false;
if (EnableCompression)
{
bool Success = CompressionUtil::CompressSegmentedData((u8*) mCompressedData.Data(), mCompressedData.Size(), CompressedBuf.data(), CompressedSize, UseZlib);
u32 PadBytes = (32 - (CompressedSize % 32)) & 0x3F;
WriteCompressedData = Success && (CompressedSize + PadBytes < (u32) mCompressedData.Size());
}
if (WriteCompressedData)
{
u32 PadBytes = 32 - (CompressedSize % 32);
PadBytes &= 0x3F;
for (u32 iPad = 0; iPad < PadBytes; iPad++)
mAreaData.WriteByte(0);
mAreaData.WriteBytes(CompressedBuf.data(), CompressedSize);
mCurBlock.CompressedSize = CompressedSize;
}
else
{
mAreaData.WriteBytes(mCompressedData.Data(), mCompressedData.Size());
mAreaData.WriteToBoundary(32, 0);
mCurBlock.CompressedSize = 0;
}
mCompressedData.Clear();
mCompressedBlocks.push_back(mCurBlock);
mCurBlock = SCompressedBlock();
}
// ************ STATIC ************
@ -95,39 +312,41 @@ void CAreaCooker::WriteCookedArea(CGameArea *pArea, IOutputStream& rOut)
Cooker.mpArea = pArea;
Cooker.mVersion = pArea->Version();
if (Cooker.mVersion > ePrime)
{
Log::Error("Area cooking is not supported for games other than Metroid Prime");
return;
}
// Write header
Cooker.DetermineSectionNumbers();
Cooker.WritePrimeHeader(rOut);
if (Cooker.mVersion <= eEchoes)
Cooker.DetermineSectionNumbersPrime();
else
Cooker.DetermineSectionNumbersCorruption();
// Write pre-SCLY data sections
for (u32 iSec = 0; iSec < Cooker.mSclySecNum; iSec++)
for (u32 iSec = 0; iSec < Cooker.mSCLYSecNum; iSec++)
{
rOut.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.mSectionMgr.AddSize(rOut);
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
}
// Write SCLY
Cooker.WritePrimeSCLY(rOut);
Cooker.mSectionMgr.AddSize(rOut);
if (Cooker.mVersion <= eEchoesDemo)
Cooker.WritePrimeSCLY(Cooker.mSectionData);
else
Cooker.WriteEchoesSCLY(Cooker.mSectionData);
// Write post-SCLY data sections
for (u32 iSec = Cooker.mSclySecNum + 1; iSec < pArea->mSectionDataBuffers.size(); iSec++)
u32 PostSCLY = (Cooker.mVersion <= ePrime ? Cooker.mSCLYSecNum + 1 : Cooker.mSCGNSecNum + 1);
for (u32 iSec = PostSCLY; iSec < pArea->mSectionDataBuffers.size(); iSec++)
{
rOut.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.mSectionMgr.AddSize(rOut);
Cooker.mSectionData.WriteBytes(pArea->mSectionDataBuffers[iSec].data(), pArea->mSectionDataBuffers[iSec].size());
Cooker.FinishSection(false);
}
// Write section sizes
u32 AreaEnd = rOut.Tell();
rOut.Seek(Cooker.mSectionSizesOffset, SEEK_SET);
Cooker.mSectionMgr.WriteSizes(rOut);
rOut.Seek(AreaEnd, SEEK_SET);
Cooker.FinishBlock();
// Write to actual file
if (Cooker.mVersion <= eEchoes)
Cooker.WritePrimeHeader(rOut);
else
Cooker.WriteCorruptionHeader(rOut);
Cooker.WriteAreaData(rOut);
}
u32 CAreaCooker::GetMREAVersion(EGame version)

View File

@ -11,22 +11,56 @@ class CAreaCooker
TResPtr<CGameArea> mpArea;
EGame mVersion;
CSectionMgrOut mSectionMgr;
u32 mSectionSizesOffset;
std::vector<u32> mSectionSizes;
u32 mGeometrySecNum;
u32 mSclySecNum;
u32 mSCLYSecNum;
u32 mSCGNSecNum;
u32 mCollisionSecNum;
u32 mUnknownSecNum;
u32 mLightsSecNum;
u32 mVisiSecNum;
u32 mPathSecNum;
u32 mArotSecNum;
u32 mVISISecNum;
u32 mPATHSecNum;
u32 mAROTSecNum;
u32 mFFFFSecNum;
u32 mPTLASecNum;
u32 mEGMCSecNum;
struct SCompressedBlock
{
u32 CompressedSize;
u32 DecompressedSize;
u32 NumSections;
SCompressedBlock()
: CompressedSize(0), DecompressedSize(0), NumSections(0) {}
};
SCompressedBlock mCurBlock;
CVectorOutStream mSectionData;
CVectorOutStream mCompressedData;
CVectorOutStream mAreaData;
std::vector<SCompressedBlock> mCompressedBlocks;
CAreaCooker();
void DetermineSectionNumbers();
void DetermineSectionNumbersPrime();
void DetermineSectionNumbersCorruption();
// Header
void WritePrimeHeader(IOutputStream& rOut);
void WriteCorruptionHeader(IOutputStream& rOut);
void WriteCompressionHeader(IOutputStream& rOut);
void WriteAreaData(IOutputStream& rOut);
// SCLY
void WritePrimeSCLY(IOutputStream& rOut);
void WriteEchoesSCLY(IOutputStream& rOut);
// Section Management
void AddSectionToBlock();
void FinishSection(bool ForceFinishBlock);
void FinishBlock();
public:
static void WriteCookedArea(CGameArea *pArea, IOutputStream& rOut);

View File

@ -1,7 +1,17 @@
#include "CScriptCooker.h"
void CScriptCooker::WriteProperty(IProperty *pProp)
void CScriptCooker::WriteProperty(IProperty *pProp, bool InSingleStruct)
{
u32 SizeOffset = 0, PropStart = 0;
if (mVersion >= eEchoesDemo && !InSingleStruct)
{
mpSCLY->WriteLong(pProp->ID());
SizeOffset = mpSCLY->Tell();
mpSCLY->WriteShort(0x0);
PropStart = mpSCLY->Tell();
}
switch (pProp->Type())
{
@ -96,25 +106,81 @@ void CScriptCooker::WriteProperty(IProperty *pProp)
{
TMayaSplineProperty *pSplineCast = static_cast<TMayaSplineProperty*>(pProp);
std::vector<u8> Buffer = pSplineCast->Get();
mpSCLY->WriteBytes(Buffer.data(), Buffer.size());
if (!Buffer.empty()) mpSCLY->WriteBytes(Buffer.data(), Buffer.size());
else
{
if (mVersion < eReturns)
{
mpSCLY->WriteShort(0);
mpSCLY->WriteLong(0);
mpSCLY->WriteByte(1);
mpSCLY->WriteFloat(0);
mpSCLY->WriteFloat(1);
}
else
{
mpSCLY->WriteLong(0);
mpSCLY->WriteFloat(0);
mpSCLY->WriteFloat(1);
mpSCLY->WriteShort(0);
mpSCLY->WriteByte(1);
}
}
break;
}
case eStructProperty:
case eArrayProperty:
{
CPropertyStruct *pStruct = static_cast<CPropertyStruct*>(pProp);
CStructTemplate *pTemp = static_cast<CStructTemplate*>(pStruct->Template());
if (!pTemp->IsSingleProperty() || pProp->Type() == eArrayProperty)
mpSCLY->WriteLong(pStruct->Count());
std::vector<IProperty*> PropertiesToWrite;
for (u32 iProp = 0; iProp < pStruct->Count(); iProp++)
WriteProperty(pStruct->PropertyByIndex(iProp));
{
IProperty *pSubProp = pStruct->PropertyByIndex(iProp);
ECookPreference Pref = pSubProp->Template()->CookPreference();
if (Pref == eNeverCook) continue;
if (mVersion < eReturns || pTemp->IsSingleProperty() || Pref == eAlwaysCook || !pSubProp->MatchesDefault())
PropertiesToWrite.push_back(pSubProp);
}
if (!pTemp->IsSingleProperty())
{
if (mVersion <= ePrime)
mpSCLY->WriteLong(PropertiesToWrite.size());
else
mpSCLY->WriteShort((u16) PropertiesToWrite.size());
}
for (u32 iProp = 0; iProp < PropertiesToWrite.size(); iProp++)
WriteProperty(PropertiesToWrite[iProp], pTemp->IsSingleProperty());
break;
}
case eArrayProperty:
{
CArrayProperty *pArray = static_cast<CArrayProperty*>(pProp);
mpSCLY->WriteLong(pArray->Count());
for (u32 iProp = 0; iProp < pArray->Count(); iProp++)
WriteProperty(pArray->PropertyByIndex(iProp), true);
break;
}
}
if (SizeOffset != 0)
{
u32 PropEnd = mpSCLY->Tell();
mpSCLY->Seek(SizeOffset, SEEK_SET);
mpSCLY->WriteShort((u16) (PropEnd - PropStart));
mpSCLY->Seek(PropEnd, SEEK_SET);
}
}
@ -157,12 +223,61 @@ void CScriptCooker::WriteInstanceMP1(CScriptObject *pInstance)
mpSCLY->WriteLong(rkLink.ObjectID);
}
WriteProperty(pInstance->Properties());
WriteProperty(pInstance->Properties(), false);
u32 InstanceEnd = mpSCLY->Tell();
u32 InstanceSize = InstanceEnd - InstanceStart;
mpSCLY->Seek(SizeOffset, SEEK_SET);
mpSCLY->WriteLong(InstanceSize);
mpSCLY->WriteLong(InstanceEnd - InstanceStart);
mpSCLY->Seek(InstanceEnd, SEEK_SET);
}
void CScriptCooker::WriteLayerMP2(CScriptLayer *pLayer)
{
u32 LayerStart = mpSCLY->Tell();
mpSCLY->WriteByte(0x1);
mpSCLY->WriteLong(pLayer->NumInstances());
for (u32 iInst = 0; iInst < pLayer->NumInstances(); iInst++)
{
CScriptObject *pInstance = pLayer->InstanceByIndex(iInst);
WriteInstanceMP2(pInstance);
}
if (mVersion == eEchoesDemo)
{
u32 LayerSize = mpSCLY->Tell() - LayerStart;
u32 NumPadBytes = 32 - (LayerSize % 32);
if (NumPadBytes == 32) NumPadBytes = 0;
for (u32 iPad = 0; iPad < NumPadBytes; iPad++)
mpSCLY->WriteByte(0);
}
}
void CScriptCooker::WriteInstanceMP2(CScriptObject *pInstance)
{
mpSCLY->WriteLong(pInstance->ObjectTypeID());
u32 SizeOffset = mpSCLY->Tell();
mpSCLY->WriteShort(0);
u32 InstanceStart = mpSCLY->Tell();
mpSCLY->WriteLong(pInstance->InstanceID());
mpSCLY->WriteShort((u16) pInstance->NumOutLinks());
for (u32 iLink = 0; iLink < pInstance->NumOutLinks(); iLink++)
{
const SLink& rkLink = pInstance->OutLink(iLink);
mpSCLY->WriteLong(rkLink.State);
mpSCLY->WriteLong(rkLink.Message);
mpSCLY->WriteLong(rkLink.ObjectID);
}
WriteProperty(pInstance->Properties(), false);
u32 InstanceEnd = mpSCLY->Tell();
mpSCLY->Seek(SizeOffset, SEEK_SET);
mpSCLY->WriteShort((u16) (InstanceEnd - InstanceStart));
mpSCLY->Seek(InstanceEnd, SEEK_SET);
}
@ -172,5 +287,9 @@ void CScriptCooker::WriteLayer(EGame Game, CScriptLayer *pLayer, IOutputStream&
CScriptCooker Cooker;
Cooker.mpSCLY = &rOut;
Cooker.mVersion = Game;
Cooker.WriteLayerMP1(pLayer);
if (Game <= ePrime)
Cooker.WriteLayerMP1(pLayer);
else
Cooker.WriteLayerMP2(pLayer);
}

View File

@ -12,9 +12,11 @@ class CScriptCooker
EGame mVersion;
CScriptCooker() {}
void WriteProperty(IProperty *pProp);
void WriteProperty(IProperty *pProp, bool InSingleStruct);
void WriteLayerMP1(CScriptLayer *pLayer);
void WriteInstanceMP1(CScriptObject *pInstance);
void WriteLayerMP2(CScriptLayer *pLayer);
void WriteInstanceMP2(CScriptObject *pInstance);
public:
static void WriteLayer(EGame Game, CScriptLayer *pLayer, IOutputStream& rOut);

View File

@ -63,6 +63,8 @@ void CAreaLoader::ReadHeaderPrime()
mpMREA->SeekToBoundary(32);
mpSectionMgr->Init();
LoadSectionDataBuffers();
mpArea->mOriginalWorldMeshCount = mNumMeshes;
}
void CAreaLoader::ReadGeometryPrime()
@ -116,39 +118,57 @@ void CAreaLoader::ReadGeometryPrime()
void CAreaLoader::ReadSCLYPrime()
{
// Prime, Echoes Demo
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP1)");
mpSectionMgr->ToSection(mScriptLayerBlockNum);
CFourCC SCLY(*mpMREA);
if (SCLY != "SCLY")
{
Log::Error(mpMREA->GetSourceString() + " - Invalid SCLY magic: " + SCLY.ToString());
Log::FileError(mpMREA->GetSourceString(), mpMREA->Tell() - 4, "Invalid SCLY magic: " + SCLY.ToString());
return;
}
mpMREA->Seek(0x4, SEEK_CUR); // Skipping unknown value which is always 4
if (mVersion <= ePrime)
mpMREA->Seek(0x4, SEEK_CUR);
else
mpMREA->Seek(0x1, SEEK_CUR);
// Read layer sizes
mNumLayers = mpMREA->ReadLong();
mpArea->mScriptLayers.reserve(mNumLayers);
mpArea->mScriptLayers.resize(mNumLayers);
std::vector<u32> LayerSizes(mNumLayers);
for (u32 iLayer = 0; iLayer < mNumLayers; iLayer++)
LayerSizes[iLayer] = mpMREA->ReadLong();
for (u32 iLayer = 0; iLayer < mNumLayers; iLayer++)
for (u32 iLyr = 0; iLyr < mNumLayers; iLyr++)
LayerSizes[iLyr] = mpMREA->ReadLong();
// SCLY
for (u32 iLyr = 0; iLyr < mNumLayers; iLyr++)
{
u32 Next = mpMREA->Tell() + LayerSizes[iLayer];
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
if (pLayer)
mpArea->mScriptLayers.push_back(pLayer);
u32 Next = mpMREA->Tell() + LayerSizes[iLyr];
mpArea->mScriptLayers[iLyr] = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
mpMREA->Seek(Next, SEEK_SET);
}
// SCGN
if (mVersion == eEchoesDemo)
{
mpSectionMgr->ToSection(mScriptGeneratorBlockNum);
CFourCC SCGN(*mpMREA);
if (SCGN != "SCGN")
Log::FileError(mpMREA->GetSourceString(), mpMREA->Tell() - 4, "Invalid SCGN magic: " + SCGN.ToString());
else
{
mpMREA->Seek(0x1, SEEK_CUR);
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
if (pLayer)
{
mpArea->mpGeneratorLayer = pLayer;
pLayer->SetName("Generated Objects");
pLayer->SetActive(true);
}
}
}
SetUpObjects();
}
@ -270,30 +290,49 @@ void CAreaLoader::ReadHeaderEchoes()
mpSectionMgr->Init();
LoadSectionDataBuffers();
mpArea->mOriginalWorldMeshCount = mNumMeshes;
}
void CAreaLoader::ReadSCLYEchoes()
{
// MP2, MP3 Proto, MP3, DKCR
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP2/MP3/DKCR)");
mpSectionMgr->ToSection(mScriptLayerBlockNum);
mpArea->mScriptLayers.resize(mNumLayers);
// SCLY
for (u32 l = 0; l < mNumLayers; l++)
for (u32 iLyr = 0; iLyr < mNumLayers; iLyr++)
{
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
if (pLayer)
mpArea->mScriptLayers.push_back(pLayer);
CFourCC SCLY(*mpMREA);
if (SCLY != "SCLY")
{
Log::FileError(mpMREA->GetSourceString(), mpMREA->Tell() - 4, "Layer " + TString::FromInt32(iLyr, 0, 10) + " - Invalid SCLY magic: " + SCLY.ToString());
mpSectionMgr->ToNextSection();
continue;
}
mpMREA->Seek(0x5, SEEK_CUR); // Skipping unknown + layer index
mpArea->mScriptLayers[iLyr] = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
mpSectionMgr->ToNextSection();
}
// SCGN
mpSectionMgr->ToSection(mScriptGeneratorBlockNum);
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
CFourCC SCGN(*mpMREA);
if (SCGN != "SCGN")
{
Log::FileError(mpMREA->GetSourceString(), mpMREA->Tell() - 4, "Invalid SCGN magic: " + SCGN.ToString());
return;
}
if (pLayer)
mpArea->mpGeneratorLayer = pLayer;
mpMREA->Seek(0x1, SEEK_CUR); // Skipping unknown
mpArea->mpGeneratorLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
if (mpArea->mpGeneratorLayer)
{
mpArea->mpGeneratorLayer->SetName("Generated Objects");
mpArea->mpGeneratorLayer->SetActive(true);
}
SetUpObjects();
}
@ -334,12 +373,19 @@ void CAreaLoader::ReadHeaderCorruption()
else if (Type == "SOBJ") mScriptLayerBlockNum = Num;
else if (Type == "SGEN") mScriptGeneratorBlockNum = Num;
else if (Type == "WOBJ") mGeometryBlockNum = Num; // note WOBJ can show up multiple times, but is always 0
CGameArea::SSectionNumber SecNum;
SecNum.SectionID = Type;
SecNum.Index = Num;
mpArea->mSectionNumbers.push_back(SecNum);
}
mpMREA->SeekToBoundary(32);
Decompress();
mpSectionMgr->Init();
LoadSectionDataBuffers();
mpArea->mOriginalWorldMeshCount = mNumMeshes;
}
void CAreaLoader::ReadGeometryCorruption()
@ -478,6 +524,8 @@ void CAreaLoader::ReadCompressedBlocks()
mClusters[c].CompressedSize = mpMREA->ReadLong();
mClusters[c].NumSections = mpMREA->ReadLong();
mTotalDecmpSize += mClusters[c].DecompressedSize;
if (mClusters[c].CompressedSize != 0) mpArea->mUsesCompression = true;
}
mpMREA->SeekToBoundary(32);

View File

@ -367,32 +367,7 @@ CScriptObject* CScriptLoader::LoadObjectMP2(IInputStream& SCLY)
CScriptLayer* CScriptLoader::LoadLayerMP2(IInputStream& SCLY)
{
bool IsSCGN = false;
if (mVersion >= eEchoes)
{
CFourCC SCLY_Magic(SCLY);
if (SCLY_Magic == "SCLY")
{
SCLY.Seek(0x6, SEEK_CUR);
}
else if (SCLY_Magic == "SCGN")
{
SCLY.Seek(0x2, SEEK_CUR);
IsSCGN = true;
}
else
{
Log::FileError(SCLY.GetSourceString(), SCLY.Tell() - 4, "Invalid script layer magic: " + TString::HexString((u32) SCLY_Magic.ToLong()));
return nullptr;
}
}
else
{
SCLY.Seek(0x1, SEEK_CUR);
}
SCLY.Seek(0x1, SEEK_CUR); // Skipping version. todo: verify this?
u32 NumObjects = SCLY.ReadLong();
mpLayer = new CScriptLayer();
@ -405,11 +380,6 @@ CScriptLayer* CScriptLoader::LoadLayerMP2(IInputStream& SCLY)
mpLayer->AddInstance(pObj);
}
if (IsSCGN)
{
mpLayer->SetName("Generated");
mpLayer->SetActive(true);
}
return mpLayer;
}

View File

@ -160,7 +160,7 @@ public:
TMayaSplineProperty(IPropertyTemplate *pTemp, CPropertyStruct *pParent, const std::vector<u8>& v)
: TTypedProperty(pTemp, pParent, v) {}
virtual bool MatchesDefault() { return true; }
virtual bool MatchesDefault() { return Get().empty(); }
};
/*

View File

@ -608,7 +608,7 @@ void CPropertyDelegate::SetCharacterEditorData(QWidget *pEditor, const QModelInd
if (Type == eFileProperty)
{
static_cast<WResourceSelector*>(pEditor)->SetResource(Params.AnimSet());
static_cast<WResourceSelector*>(pEditor)->SetResource(Params.AnimSet());
}
else if (Type == eEnumProperty)
@ -632,10 +632,10 @@ void CPropertyDelegate::SetCharacterModelData(QWidget *pEditor, const QModelInde
if (Type == eFileProperty)
{
Params.SetResource( static_cast<WResourceSelector*>(pEditor)->GetResource() );
Params.SetResource( static_cast<WResourceSelector*>(pEditor)->GetResourceInfo() );
// Reset all other parameters to 0
Params.SetNodeIndex(0);
for (u32 iUnk = 0; iUnk < 4; iUnk++)
for (u32 iUnk = 0; iUnk < 3; iUnk++)
Params.SetUnknown(iUnk, 0);
}
@ -677,7 +677,7 @@ EPropertyType CPropertyDelegate::DetermineCharacterPropType(EGame Game, const QM
else
{
if (rkIndex.row() == 0) return eFileProperty;
else if (rkIndex.row() <= 3) return eLongProperty;
else if (rkIndex.row() <= 2) return eLongProperty;
}
return eUnknownProperty;
}

View File

@ -113,7 +113,7 @@ int CPropertyModel::rowCount(const QModelIndex& rkParent) const
CAnimationParameters Params = static_cast<TCharacterProperty*>(pProp)->Get();
if (Params.Version() <= eEchoes) return 3;
if (Params.Version() <= eCorruption) return 2;
return 5;
return 4;
}
default:
@ -241,7 +241,8 @@ QVariant CPropertyModel::data(const QModelIndex& rkIndex, int Role) const
if (rkIndex.column() == 0)
{
if (rkIndex.row() == 0) return "Character";
else return "Unknown " + QString::number(rkIndex.row());
else if (rkIndex.row() == 1) return "Default Anim";
else return "Unknown " + QString::number(rkIndex.row() - 1);
}
if (rkIndex.column() == 1 && rkIndex.row() > 0)

View File

@ -2,6 +2,7 @@
#include "Editor/UICommon.h"
#include <Core/Resource/CAnimSet.h>
#include <Core/Resource/CResCache.h>
#include <Core/Resource/CResourceInfo.h>
WAnimParamsEditor::WAnimParamsEditor(QWidget *pParent)
: QWidget(pParent),
@ -66,10 +67,10 @@ void WAnimParamsEditor::SetParameters(const CAnimationParameters& params)
// ************ PRIVATE SLOTS ************
void WAnimParamsEditor::OnResourceChanged(QString path)
{
CResource *pRes = gResCache.GetResource(path.toStdString());
if (pRes && pRes->Type() != eAnimSet) pRes = nullptr;
CResourceInfo ResInfo(path.toStdString());
if (ResInfo.Type() != "ANCS" && ResInfo.Type() != "CHAR") ResInfo = CResourceInfo();
mParams.SetResource(pRes);
mParams.SetResource(ResInfo);
emit ParametersChanged(mParams);
}
@ -217,7 +218,7 @@ void WAnimParamsEditor::SetupUI()
connect(mpSpinBoxes[0], SIGNAL(valueChanged(int)), this, SLOT(OnUnknownChanged()));
// Create unknown spin box B/C/D
for (u32 iBox = 1; iBox < 4; iBox++)
for (u32 iBox = 1; iBox < 3; iBox++)
{
mpSpinBoxes[iBox] = new WIntegralSpinBox(this);
mpSpinBoxes[iBox]->setRange(INT32_MIN, INT32_MAX);