mirror of
https://github.com/AxioDL/PrimeWorldEditor.git
synced 2025-12-15 08:06:10 +00:00
Initial commit of current work on Prime World Editor
This commit is contained in:
199
Resource/factory/CAnimSetLoader.cpp
Normal file
199
Resource/factory/CAnimSetLoader.cpp
Normal file
@@ -0,0 +1,199 @@
|
||||
#include "CAnimSetLoader.h"
|
||||
#include <Core/CResCache.h>
|
||||
#include <Core/Log.h>
|
||||
|
||||
CAnimSetLoader::CAnimSetLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CAnimSet* CAnimSetLoader::LoadCorruptionCHAR(CInputStream& CHAR)
|
||||
{
|
||||
// For now, we only read enough to fetch the model
|
||||
CHAR.Seek(0x1, SEEK_CUR);
|
||||
set->nodes.resize(1);
|
||||
CAnimSet::SNode& node = set->nodes[0];
|
||||
|
||||
node.name = CHAR.ReadString();
|
||||
node.model = (CModel*) gResCache.GetResource(CHAR.ReadLongLong(), "CMDL");
|
||||
node.ModelToken = CToken(node.model);
|
||||
return set;
|
||||
}
|
||||
|
||||
CAnimSet* CAnimSetLoader::LoadReturnsCHAR(CInputStream& CHAR)
|
||||
{
|
||||
// For now, we only read enough to fetch the model
|
||||
CHAR.Seek(0x16, SEEK_CUR);
|
||||
set->nodes.resize(1);
|
||||
CAnimSet::SNode& node = set->nodes[0];
|
||||
|
||||
node.name = CHAR.ReadString();
|
||||
CHAR.Seek(0x14, SEEK_CUR);
|
||||
CHAR.ReadString();
|
||||
node.model = (CModel*) gResCache.GetResource(CHAR.ReadLongLong(), "CMDL");
|
||||
node.ModelToken = CToken(node.model);
|
||||
return set;
|
||||
}
|
||||
|
||||
void CAnimSetLoader::LoadPASDatabase(CInputStream& PAS4)
|
||||
{
|
||||
// For now, just parse the data; don't store it
|
||||
PAS4.Seek(0x4, SEEK_CUR); // Skipping PAS4 FourCC
|
||||
u32 anim_state_count = PAS4.ReadLong();
|
||||
PAS4.Seek(0x4, SEEK_CUR); // Skipping default anim state
|
||||
|
||||
for (u32 s = 0; s < anim_state_count; s++)
|
||||
{
|
||||
PAS4.Seek(0x4, SEEK_CUR); // Skipping unknown value
|
||||
u32 parm_info_count = PAS4.ReadLong();
|
||||
u32 anim_info_count = PAS4.ReadLong();
|
||||
|
||||
u32 skip = 0;
|
||||
for (u32 p = 0; p < parm_info_count; p++)
|
||||
{
|
||||
u32 type = PAS4.ReadLong();
|
||||
PAS4.Seek(0x8, SEEK_CUR);
|
||||
|
||||
switch (type) {
|
||||
case 0: // Int32
|
||||
case 1: // Uint32
|
||||
case 2: // Real32
|
||||
case 4: // Enum
|
||||
PAS4.Seek(0x8, SEEK_CUR);
|
||||
skip += 4;
|
||||
break;
|
||||
case 3: // Bool
|
||||
PAS4.Seek(0x2, SEEK_CUR);
|
||||
skip++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 a = 0; a < anim_info_count; a++)
|
||||
PAS4.Seek(0x4 + skip, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CAnimSet* CAnimSetLoader::LoadANCS(CInputStream& ANCS)
|
||||
{
|
||||
if (!ANCS.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + ANCS.GetSourceString());
|
||||
|
||||
u32 magic = ANCS.ReadLong();
|
||||
if (magic != 0x00010001)
|
||||
{
|
||||
Log::FileError(ANCS.GetSourceString(), "Invalid ANCS magic: " + StringUtil::ToHexString(magic));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CAnimSetLoader loader;
|
||||
loader.set = new CAnimSet;
|
||||
|
||||
u32 node_count = ANCS.ReadLong();
|
||||
loader.set->nodes.resize(node_count);
|
||||
|
||||
for (u32 n = 0; n < node_count; n++)
|
||||
{
|
||||
CAnimSet::SNode *node = &loader.set->nodes[n];
|
||||
|
||||
ANCS.Seek(0x4, SEEK_CUR); // Skipping node self-index
|
||||
u16 unknown1 = ANCS.ReadShort();
|
||||
if (n == 0) loader.mVersion = (unknown1 == 0xA) ? eEchoes : ePrime; // Best version indicator we know of unfortunately
|
||||
node->name = ANCS.ReadString();
|
||||
node->model = (CModel*) gResCache.GetResource(ANCS.ReadLong(), "CMDL");
|
||||
node->skinID = ANCS.ReadLong();
|
||||
node->skelID = ANCS.ReadLong();
|
||||
node->ModelToken = CToken(node->model);
|
||||
|
||||
// Unfortunately that's all that's actually supported at the moment. Hope to expand later.
|
||||
// Since there's no size value I have to actually read the rest of the node to reach the next one
|
||||
u32 anim_count = ANCS.ReadLong();
|
||||
for (u32 a = 0; a < anim_count; a++)
|
||||
{
|
||||
ANCS.Seek(0x4, SEEK_CUR);
|
||||
if (loader.mVersion == ePrime) ANCS.Seek(0x1, SEEK_CUR);
|
||||
ANCS.ReadString();
|
||||
}
|
||||
|
||||
// PAS Database
|
||||
loader.LoadPASDatabase(ANCS);
|
||||
|
||||
// Particles
|
||||
u32 particle_count = ANCS.ReadLong();
|
||||
ANCS.Seek(particle_count * 4, SEEK_CUR);
|
||||
u32 swoosh_count = ANCS.ReadLong();
|
||||
ANCS.Seek(swoosh_count * 4, SEEK_CUR);
|
||||
if (unknown1 != 5) ANCS.Seek(0x4, SEEK_CUR);
|
||||
u32 electric_count = ANCS.ReadLong();
|
||||
ANCS.Seek(electric_count * 4, SEEK_CUR);
|
||||
if (loader.mVersion == eEchoes) {
|
||||
u32 spsc_count = ANCS.ReadLong();
|
||||
ANCS.Seek(spsc_count * 4, SEEK_CUR);
|
||||
}
|
||||
ANCS.Seek(0x4, SEEK_CUR);
|
||||
if (loader.mVersion == eEchoes) ANCS.Seek(0x4, SEEK_CUR);
|
||||
|
||||
u32 anim_count2 = ANCS.ReadLong();
|
||||
for (u32 a = 0; a < anim_count2; a++)
|
||||
{
|
||||
ANCS.ReadString();
|
||||
ANCS.Seek(0x18, SEEK_CUR);
|
||||
}
|
||||
|
||||
u32 EffectGroupCount = ANCS.ReadLong();
|
||||
for (u32 g = 0; g < EffectGroupCount; g++)
|
||||
{
|
||||
ANCS.ReadString();
|
||||
u32 EffectCount = ANCS.ReadLong();
|
||||
|
||||
for (u32 e = 0; e < EffectCount; e++)
|
||||
{
|
||||
ANCS.ReadString();
|
||||
ANCS.Seek(0x8, SEEK_CUR);
|
||||
if (loader.mVersion == ePrime) ANCS.ReadString();
|
||||
if (loader.mVersion == eEchoes) ANCS.Seek(0x4, SEEK_CUR);
|
||||
ANCS.Seek(0xC, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
ANCS.Seek(0x8, SEEK_CUR);
|
||||
|
||||
u32 unknown_count = ANCS.ReadLong();
|
||||
ANCS.Seek(unknown_count * 4, SEEK_CUR);
|
||||
|
||||
if (loader.mVersion == eEchoes)
|
||||
{
|
||||
ANCS.Seek(0x5, SEEK_CUR);
|
||||
u32 unknown_count2 = ANCS.ReadLong();
|
||||
ANCS.Seek(unknown_count2 * 0x1C, SEEK_CUR);
|
||||
}
|
||||
// Lots of work for data I'm not even using x.x
|
||||
}
|
||||
|
||||
return loader.set;
|
||||
}
|
||||
|
||||
CAnimSet* CAnimSetLoader::LoadCHAR(CInputStream &CHAR)
|
||||
{
|
||||
if (!CHAR.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + CHAR.GetSourceString());
|
||||
|
||||
CAnimSetLoader loader;
|
||||
u8 check = CHAR.ReadByte();
|
||||
|
||||
if (check == 0x5)
|
||||
{
|
||||
loader.mVersion = eCorruption;
|
||||
loader.set = new CAnimSet();
|
||||
return loader.LoadCorruptionCHAR(CHAR);
|
||||
}
|
||||
|
||||
if (check == 0x59)
|
||||
{
|
||||
loader.mVersion = eReturns;
|
||||
loader.set = new CAnimSet();
|
||||
return loader.LoadReturnsCHAR(CHAR);
|
||||
}
|
||||
|
||||
Log::FileError(CHAR.GetSourceString(), "CHAR has invalid first byte: " + StringUtil::ToHexString(check));
|
||||
return nullptr;
|
||||
}
|
||||
24
Resource/factory/CAnimSetLoader.h
Normal file
24
Resource/factory/CAnimSetLoader.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef CCHARACTERLOADER_H
|
||||
#define CCHARACTERLOADER_H
|
||||
|
||||
#include "../CAnimSet.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CAnimSetLoader
|
||||
{
|
||||
CAnimSet *set;
|
||||
CResCache *mpResCache;
|
||||
EGame mVersion;
|
||||
|
||||
CAnimSetLoader();
|
||||
CAnimSet* LoadCorruptionCHAR(CInputStream& CHAR);
|
||||
CAnimSet* LoadReturnsCHAR(CInputStream& CHAR);
|
||||
void LoadPASDatabase(CInputStream& PAS4);
|
||||
|
||||
public:
|
||||
static CAnimSet* LoadANCS(CInputStream& ANCS);
|
||||
static CAnimSet* LoadCHAR(CInputStream& CHAR);
|
||||
};
|
||||
|
||||
#endif // CCHARACTERLOADER_H
|
||||
538
Resource/factory/CAreaLoader.cpp
Normal file
538
Resource/factory/CAreaLoader.cpp
Normal file
@@ -0,0 +1,538 @@
|
||||
#include "CAreaLoader.h"
|
||||
#include "CCollisionLoader.h"
|
||||
#include "CModelLoader.h"
|
||||
#include "CMaterialLoader.h"
|
||||
#include "CScriptLoader.h"
|
||||
#include <Common/CFourCC.h>
|
||||
#include <Common/CompressionUtil.h>
|
||||
#include <iostream>
|
||||
#include <Core/Log.h>
|
||||
|
||||
CAreaLoader::CAreaLoader()
|
||||
{
|
||||
mpMREA = nullptr;
|
||||
mHasDecompressedBuffer = false;
|
||||
mGeometryBlockNum = -1;
|
||||
mScriptLayerBlockNum = -1;
|
||||
mCollisionBlockNum = -1;
|
||||
mUnknownBlockNum = -1;
|
||||
mLightsBlockNum = -1;
|
||||
mEmptyBlockNum = -1;
|
||||
mPathBlockNum = -1;
|
||||
mOctreeBlockNum = -1;
|
||||
mScriptGeneratorBlockNum = -1;
|
||||
mFFFFBlockNum = -1;
|
||||
mUnknown2BlockNum = -1;
|
||||
mEGMCBlockNum = -1;
|
||||
mBoundingBoxesBlockNum = -1;
|
||||
mDependenciesBlockNum = -1;
|
||||
mGPUBlockNum = -1;
|
||||
mPVSBlockNum = -1;
|
||||
mRSOBlockNum = -1;
|
||||
}
|
||||
|
||||
CAreaLoader::~CAreaLoader()
|
||||
{
|
||||
if (mHasDecompressedBuffer)
|
||||
{
|
||||
delete mpMREA;
|
||||
delete[] mDecmpBuffer;
|
||||
}
|
||||
}
|
||||
|
||||
// ************ PRIME ************
|
||||
void CAreaLoader::ReadHeaderPrime()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP1)");
|
||||
mpArea->mTransform = CTransform4f(*mpMREA);
|
||||
mNumMeshes = mpMREA->ReadLong();
|
||||
u32 mNumBlocks = mpMREA->ReadLong();
|
||||
|
||||
mGeometryBlockNum = mpMREA->ReadLong();
|
||||
mScriptLayerBlockNum = mpMREA->ReadLong();
|
||||
mCollisionBlockNum = mpMREA->ReadLong();
|
||||
mUnknownBlockNum = mpMREA->ReadLong();
|
||||
mLightsBlockNum = mpMREA->ReadLong();
|
||||
mEmptyBlockNum = mpMREA->ReadLong();
|
||||
mPathBlockNum = mpMREA->ReadLong();
|
||||
mOctreeBlockNum = mpMREA->ReadLong();
|
||||
|
||||
mBlockMgr = new CBlockMgrIn(mNumBlocks, mpMREA);
|
||||
mpMREA->SeekToBoundary(32);
|
||||
mBlockMgr->Init();
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadGeometryPrime()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA world geometry (MP1/MP2)");
|
||||
mBlockMgr->ToBlock(mGeometryBlockNum);
|
||||
|
||||
// Materials
|
||||
mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
|
||||
mBlockMgr->ToNextBlock();
|
||||
|
||||
// Geometry
|
||||
for (u32 m = 0; m < mNumMeshes; m++) {
|
||||
std::cout << "\rLoading mesh " << std::dec << m + 1 << "/" << mNumMeshes;
|
||||
|
||||
SModelData *data = CModelLoader::LoadWorldModel(*mpMREA, *mBlockMgr, *mpArea->mMaterialSet, mVersion);
|
||||
CModel *pTerrainModel = new CModel(data, mpArea->mMaterialSet);
|
||||
mpArea->AddWorldModel(pTerrainModel);
|
||||
|
||||
if (mVersion >= eEchoes) {
|
||||
mBlockMgr->ToNextBlock();
|
||||
mBlockMgr->ToNextBlock();
|
||||
}
|
||||
}
|
||||
mpArea->MergeTerrain();
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadSCLYPrime()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP1)");
|
||||
mBlockMgr->ToBlock(mScriptLayerBlockNum);
|
||||
|
||||
CFourCC SCLY(*mpMREA);
|
||||
if (SCLY != "SCLY")
|
||||
{
|
||||
Log::Error(mpMREA->GetSourceString() + " - Invalid SCLY magic: " + SCLY.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
mpMREA->Seek(0x4, SEEK_CUR);
|
||||
mNumLayers = mpMREA->ReadLong();
|
||||
mpArea->mScriptLayers.reserve(mNumLayers);
|
||||
|
||||
std::vector<u32> LayerSizes(mNumLayers);
|
||||
for (u32 l = 0; l < mNumLayers; l++)
|
||||
LayerSizes[l] = mpMREA->ReadLong();
|
||||
|
||||
for (u32 l = 0; l < mNumLayers; l++)
|
||||
{
|
||||
u32 next = mpMREA->Tell() + LayerSizes[l];
|
||||
|
||||
CScriptLayer *layer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
|
||||
if (layer)
|
||||
mpArea->mScriptLayers.push_back(layer);
|
||||
|
||||
mpMREA->Seek(next, SEEK_SET);
|
||||
}
|
||||
|
||||
SetUpObjects();
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadLightsPrime()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA dynamic lights (MP1/MP2)");
|
||||
mBlockMgr->ToBlock(mLightsBlockNum);
|
||||
|
||||
u32 babedead = mpMREA->ReadLong();
|
||||
if (babedead != 0xbabedead) return;
|
||||
|
||||
mpArea->mLightLayers.resize(2);
|
||||
|
||||
for (u32 ly = 0; ly < 2; ly++)
|
||||
{
|
||||
u32 NumLights = mpMREA->ReadLong();
|
||||
mpArea->mLightLayers[ly].resize(NumLights);
|
||||
|
||||
for (u32 l = 0; l < NumLights; l++)
|
||||
{
|
||||
ELightType Type = ELightType(mpMREA->ReadLong());
|
||||
CVector3f Color(*mpMREA);
|
||||
CVector3f Position(*mpMREA);
|
||||
CVector3f Direction(*mpMREA);
|
||||
float Multiplier = mpMREA->ReadFloat();
|
||||
float SpotCutoff = mpMREA->ReadFloat();
|
||||
mpMREA->Seek(0x9, SEEK_CUR);
|
||||
u32 FalloffType = mpMREA->ReadLong();
|
||||
mpMREA->Seek(0x4, SEEK_CUR);
|
||||
|
||||
// Relevant data is read - now we process and form a CLight out of it
|
||||
CLight *Light;
|
||||
|
||||
CColor LightColor = CColor(Color.x, Color.y, Color.z, 0.f);
|
||||
if (Multiplier < FLT_EPSILON)
|
||||
Multiplier = FLT_EPSILON;
|
||||
|
||||
// Local Ambient
|
||||
if (Type == eLocalAmbient)
|
||||
{
|
||||
Color *= Multiplier;
|
||||
|
||||
// Clamp
|
||||
if (Color.x > 1.f) Color.x = 1.f;
|
||||
if (Color.y > 1.f) Color.y = 1.f;
|
||||
if (Color.z > 1.f) Color.z = 1.f;
|
||||
CColor MultColor(Color.x, Color.y, Color.z, 1.f);
|
||||
|
||||
Light = CLight::BuildLocalAmbient(Position, MultColor);
|
||||
}
|
||||
|
||||
// Directional
|
||||
else if (Type == eDirectional)
|
||||
{
|
||||
Light = CLight::BuildDirectional(Position, Direction, LightColor);
|
||||
}
|
||||
|
||||
// Spot
|
||||
else if (Type == eSpot)
|
||||
{
|
||||
Light = CLight::BuildSpot(Position, Direction.Normalized(), LightColor, SpotCutoff);
|
||||
|
||||
float DistAttenA = (FalloffType == 0) ? (2.f / Multiplier) : 0.f;
|
||||
float DistAttenB = (FalloffType == 1) ? (250.f / Multiplier) : 0.f;
|
||||
float DistAttenC = (FalloffType == 2) ? (25000.f / Multiplier) : 0.f;
|
||||
Light->SetDistAtten(DistAttenA, DistAttenB, DistAttenC);
|
||||
}
|
||||
|
||||
// Custom
|
||||
else
|
||||
{
|
||||
float DistAttenA = (FalloffType == 0) ? (2.f / Multiplier) : 0.f;
|
||||
float DistAttenB = (FalloffType == 1) ? (249.9998f / Multiplier) : 0.f;
|
||||
float DistAttenC = (FalloffType == 2) ? (25000.f / Multiplier) : 0.f;
|
||||
|
||||
Light = CLight::BuildCustom(Position, Direction, LightColor,
|
||||
DistAttenA, DistAttenB, DistAttenC,
|
||||
1.f, 0.f, 0.f);
|
||||
}
|
||||
|
||||
mpArea->mLightLayers[ly][l] = Light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ************ ECHOES ************
|
||||
void CAreaLoader::ReadHeaderEchoes()
|
||||
{
|
||||
// This function reads the header for Echoes and the Echoes demo disc
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP2)");
|
||||
mpArea->mTransform = CTransform4f(*mpMREA);
|
||||
mNumMeshes = mpMREA->ReadLong();
|
||||
if (mVersion == eEchoes) mNumLayers = mpMREA->ReadLong();
|
||||
u32 numBlocks = mpMREA->ReadLong();
|
||||
|
||||
mGeometryBlockNum = mpMREA->ReadLong();
|
||||
mScriptLayerBlockNum = mpMREA->ReadLong();
|
||||
mScriptGeneratorBlockNum = mpMREA->ReadLong();
|
||||
mCollisionBlockNum = mpMREA->ReadLong();
|
||||
mUnknownBlockNum = mpMREA->ReadLong();
|
||||
mLightsBlockNum = mpMREA->ReadLong();
|
||||
mEmptyBlockNum = mpMREA->ReadLong();
|
||||
mPathBlockNum = mpMREA->ReadLong();
|
||||
mFFFFBlockNum = mpMREA->ReadLong();
|
||||
mUnknown2BlockNum = mpMREA->ReadLong();
|
||||
mEGMCBlockNum = mpMREA->ReadLong();
|
||||
if (mVersion == eEchoes) mClusters.resize(mpMREA->ReadLong());
|
||||
mpMREA->SeekToBoundary(32);
|
||||
|
||||
mBlockMgr = new CBlockMgrIn(numBlocks, mpMREA);
|
||||
mpMREA->SeekToBoundary(32);
|
||||
|
||||
if (mVersion == eEchoes)
|
||||
{
|
||||
ReadCompressedBlocks();
|
||||
Decompress();
|
||||
}
|
||||
|
||||
mBlockMgr->Init();
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadSCLYEchoes()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA script layers (MP2/MP3/DKCR)");
|
||||
mBlockMgr->ToBlock(mScriptLayerBlockNum);
|
||||
|
||||
// SCLY
|
||||
for (u32 l = 0; l < mNumLayers; l++)
|
||||
{
|
||||
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
|
||||
|
||||
if (pLayer)
|
||||
mpArea->mScriptLayers.push_back(pLayer);
|
||||
|
||||
mBlockMgr->ToNextBlock();
|
||||
}
|
||||
|
||||
// SCGN
|
||||
mBlockMgr->ToBlock(mScriptGeneratorBlockNum);
|
||||
CScriptLayer *pLayer = CScriptLoader::LoadLayer(*mpMREA, mpArea, mVersion);
|
||||
|
||||
if (pLayer)
|
||||
mpArea->mpGeneratorLayer = pLayer;
|
||||
|
||||
SetUpObjects();
|
||||
}
|
||||
|
||||
// ************ CORRUPTION ************
|
||||
void CAreaLoader::ReadHeaderCorruption()
|
||||
{
|
||||
// This function reads the header for MP3, the MP3 prototype, and DKCR
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA header (MP3/DKCR)");
|
||||
mpArea->mTransform = CTransform4f(*mpMREA);
|
||||
mNumMeshes = mpMREA->ReadLong();
|
||||
mNumLayers = mpMREA->ReadLong();
|
||||
u32 NumSections = mpMREA->ReadLong();
|
||||
mClusters.resize(mpMREA->ReadLong());
|
||||
u32 SectionNumberCount = mpMREA->ReadLong();
|
||||
mpMREA->SeekToBoundary(32);
|
||||
|
||||
mBlockMgr = new CBlockMgrIn(NumSections, mpMREA);
|
||||
mpMREA->SeekToBoundary(32);
|
||||
|
||||
ReadCompressedBlocks();
|
||||
|
||||
for (u32 iNum = 0; iNum < SectionNumberCount; iNum++)
|
||||
{
|
||||
CFourCC Type(*mpMREA);
|
||||
u32 Num = mpMREA->ReadLong();
|
||||
|
||||
if (Type == "AABB") mBoundingBoxesBlockNum = Num;
|
||||
else if (Type == "COLI") mCollisionBlockNum = Num;
|
||||
else if (Type == "DEPS") mDependenciesBlockNum = Num;
|
||||
else if (Type == "EGMC") mEGMCBlockNum = Num;
|
||||
else if (Type == "GPUD") mGPUBlockNum = Num;
|
||||
else if (Type == "LITE") mLightsBlockNum = Num;
|
||||
else if (Type == "PFL2") mPathBlockNum = Num;
|
||||
else if (Type == "PVS!") mPVSBlockNum = Num;
|
||||
else if (Type == "ROCT") mOctreeBlockNum = Num;
|
||||
else if (Type == "RSOS") mRSOBlockNum = Num;
|
||||
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
|
||||
}
|
||||
|
||||
mpMREA->SeekToBoundary(32);
|
||||
Decompress();
|
||||
mBlockMgr->Init();
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadGeometryCorruption()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading MREA world geometry (MP3)");
|
||||
mBlockMgr->ToBlock(mGeometryBlockNum);
|
||||
|
||||
// Materials
|
||||
mpArea->mMaterialSet = CMaterialLoader::LoadMaterialSet(*mpMREA, mVersion);
|
||||
mBlockMgr->ToNextBlock();
|
||||
|
||||
// Geometry
|
||||
u32 CurWOBJSection = 1;
|
||||
u32 CurGPUSection = mGPUBlockNum;
|
||||
|
||||
for (u32 iMesh = 0; iMesh < mNumMeshes; iMesh++)
|
||||
{
|
||||
std::cout << "\rLoading mesh " << std::dec << iMesh + 1 << "/" << mNumMeshes;
|
||||
|
||||
SModelData *pData = CModelLoader::LoadCorruptionWorldModel(*mpMREA, *mBlockMgr, *mpArea->mMaterialSet, CurWOBJSection, CurGPUSection, mVersion);
|
||||
CModel *pWorldModel = new CModel(pData, mpArea->mMaterialSet);
|
||||
mpArea->AddWorldModel(pWorldModel);
|
||||
|
||||
CurWOBJSection += 4;
|
||||
CurGPUSection = mBlockMgr->CurrentBlock();
|
||||
}
|
||||
|
||||
mpArea->MergeTerrain();
|
||||
std::cout << "\n";
|
||||
}
|
||||
|
||||
// ************ RETURNS ************
|
||||
|
||||
// ************ COMMON ************
|
||||
void CAreaLoader::ReadCompressedBlocks()
|
||||
{
|
||||
mTotalDecmpSize = 0;
|
||||
|
||||
for (u32 c = 0; c < mClusters.size(); c++)
|
||||
{
|
||||
mClusters[c].BufferSize = mpMREA->ReadLong();
|
||||
mClusters[c].DecompressedSize = mpMREA->ReadLong();
|
||||
mClusters[c].CompressedSize = mpMREA->ReadLong();
|
||||
mClusters[c].NumSections = mpMREA->ReadLong();
|
||||
mTotalDecmpSize += mClusters[c].DecompressedSize;
|
||||
}
|
||||
|
||||
mpMREA->SeekToBoundary(32);
|
||||
}
|
||||
|
||||
void CAreaLoader::Decompress()
|
||||
{
|
||||
// This function decompresses compressed clusters into a buffer.
|
||||
// It should be called at the beginning of the first compressed cluster.
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Decompressing MREA data");
|
||||
if (mVersion < eEchoes) return;
|
||||
|
||||
// Decompress clusters
|
||||
mDecmpBuffer = new u8[mTotalDecmpSize];
|
||||
u32 Offset = 0;
|
||||
|
||||
for (u32 c = 0; c < mClusters.size(); c++)
|
||||
{
|
||||
SCompressedCluster *cc = &mClusters[c];
|
||||
|
||||
// Is it decompressed already?
|
||||
if (mClusters[c].CompressedSize == 0)
|
||||
{
|
||||
mpMREA->ReadBytes(mDecmpBuffer + Offset, cc->DecompressedSize);
|
||||
Offset += cc->DecompressedSize;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
u32 StartOffset = 32 - (mClusters[c].CompressedSize % 32); // For some reason they pad the beginning instead of the end
|
||||
if (StartOffset != 32)
|
||||
mpMREA->Seek(StartOffset, SEEK_CUR);
|
||||
|
||||
std::vector<u8> cmp(mClusters[c].CompressedSize);
|
||||
mpMREA->ReadBytes(cmp.data(), cmp.size());
|
||||
|
||||
bool Success = CompressionUtil::DecompressAreaLZO(cmp.data(), cmp.size(), mDecmpBuffer + Offset, cc->DecompressedSize);
|
||||
if (!Success)
|
||||
throw "Failed to decompress MREA!";
|
||||
|
||||
Offset += cc->DecompressedSize;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Source = mpMREA->GetSourceString();
|
||||
mpMREA = new CMemoryInStream(mDecmpBuffer, mTotalDecmpSize, IOUtil::BigEndian);
|
||||
mpMREA->SetSourceString(Source);
|
||||
mBlockMgr->SetInputStream(mpMREA);
|
||||
mHasDecompressedBuffer = true;
|
||||
}
|
||||
|
||||
void CAreaLoader::ReadCollision()
|
||||
{
|
||||
Log::FileWrite(mpMREA->GetSourceString(), "Reading collision (MP1/MP2/MP3)");
|
||||
mBlockMgr->ToBlock(mCollisionBlockNum);
|
||||
mpArea->mCollision = CCollisionLoader::LoadAreaCollision(*mpMREA);
|
||||
}
|
||||
|
||||
void CAreaLoader::SetUpObjects()
|
||||
{
|
||||
// Iterate over all objects
|
||||
for (u32 iLyr = 0; iLyr < mpArea->GetScriptLayerCount() + 1; iLyr++)
|
||||
{
|
||||
CScriptLayer *pLayer;
|
||||
if (iLyr < mpArea->GetScriptLayerCount()) pLayer = mpArea->mScriptLayers[iLyr];
|
||||
|
||||
else
|
||||
{
|
||||
pLayer = mpArea->GetGeneratorLayer();
|
||||
if (!pLayer) break;
|
||||
}
|
||||
|
||||
for (u32 iObj = 0; iObj < pLayer->GetNumObjects(); iObj++)
|
||||
{
|
||||
// Add object to object map
|
||||
CScriptObject *pObj = (*pLayer)[iObj];
|
||||
mpArea->mObjectMap[pObj->InstanceID()] = pObj;
|
||||
|
||||
// Store outgoing connections
|
||||
for (u32 iCon = 0; iCon < pObj->NumOutLinks(); iCon++)
|
||||
{
|
||||
SLink Connection = pObj->OutLink(iCon);
|
||||
|
||||
SLink NewConnection;
|
||||
NewConnection.State = Connection.State;
|
||||
NewConnection.Message = Connection.Message;
|
||||
NewConnection.ObjectID = pObj->InstanceID();
|
||||
mConnectionMap[Connection.ObjectID].push_back(NewConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store connections
|
||||
for (auto it = mpArea->mObjectMap.begin(); it != mpArea->mObjectMap.end(); it++)
|
||||
{
|
||||
u32 InstanceID = it->first;
|
||||
auto iConMap = mConnectionMap.find(InstanceID);
|
||||
|
||||
if (iConMap != mConnectionMap.end())
|
||||
{
|
||||
CScriptObject *pObj = mpArea->GetInstanceByID(InstanceID);
|
||||
pObj->mInConnections = iConMap->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CGameArea* CAreaLoader::LoadMREA(CInputStream& MREA)
|
||||
{
|
||||
CAreaLoader Loader;
|
||||
|
||||
// Validation
|
||||
if (!MREA.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + MREA.GetSourceString());
|
||||
|
||||
u32 deadbeef = MREA.ReadLong();
|
||||
if (deadbeef != 0xdeadbeef)
|
||||
{
|
||||
Log::FileError(MREA.GetSourceString(), "Invalid MREA magic: " + StringUtil::ToHexString(deadbeef));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Header
|
||||
Loader.mpArea = new CGameArea;
|
||||
u32 version = MREA.ReadLong();
|
||||
Loader.mVersion = GetFormatVersion(version);
|
||||
Loader.mpMREA = &MREA;
|
||||
|
||||
switch (Loader.mVersion)
|
||||
{
|
||||
case ePrimeKioskDemo:
|
||||
case ePrime:
|
||||
Loader.ReadHeaderPrime();
|
||||
Loader.ReadGeometryPrime();
|
||||
Loader.ReadSCLYPrime();
|
||||
Loader.ReadCollision();
|
||||
Loader.ReadLightsPrime();
|
||||
break;
|
||||
case eEchoesDemo:
|
||||
case eEchoes:
|
||||
Loader.ReadHeaderEchoes();
|
||||
Loader.ReadGeometryPrime();
|
||||
Loader.ReadSCLYEchoes();
|
||||
Loader.ReadCollision();
|
||||
Loader.ReadLightsPrime();
|
||||
break;
|
||||
case eCorruptionProto:
|
||||
Loader.ReadHeaderCorruption();
|
||||
Loader.ReadGeometryPrime();
|
||||
Loader.ReadSCLYEchoes();
|
||||
Loader.ReadCollision();
|
||||
break;
|
||||
case eCorruption:
|
||||
case eReturns:
|
||||
Loader.ReadHeaderCorruption();
|
||||
Loader.ReadGeometryCorruption();
|
||||
Loader.ReadSCLYEchoes();
|
||||
if (Loader.mVersion != eReturns) Loader.ReadCollision();
|
||||
break;
|
||||
default:
|
||||
Log::FileError(MREA.GetSourceString(), "Unsupported MREA version: " + StringUtil::ToHexString(version));
|
||||
delete Loader.mpArea;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
delete Loader.mBlockMgr;
|
||||
return Loader.mpArea;
|
||||
}
|
||||
|
||||
EGame CAreaLoader::GetFormatVersion(u32 version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case 0xC: return ePrimeKioskDemo;
|
||||
case 0xF: return ePrime;
|
||||
case 0x15: return eEchoesDemo;
|
||||
case 0x19: return eEchoes;
|
||||
case 0x1D: return eCorruptionProto;
|
||||
case 0x1E: return eCorruption;
|
||||
case 0x20: return eReturns;
|
||||
default: return eUnknownVersion;
|
||||
}
|
||||
}
|
||||
82
Resource/factory/CAreaLoader.h
Normal file
82
Resource/factory/CAreaLoader.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#ifndef CAREALOADER_H
|
||||
#define CAREALOADER_H
|
||||
|
||||
#include <FileIO/FileIO.h>
|
||||
#include "../CGameArea.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include "CBlockMgrIn.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CAreaLoader
|
||||
{
|
||||
struct SCompressedCluster;
|
||||
|
||||
// Area data
|
||||
CGameArea *mpArea;
|
||||
CInputStream *mpMREA;
|
||||
CBlockMgrIn *mBlockMgr;
|
||||
EGame mVersion;
|
||||
u32 mNumMeshes;
|
||||
u32 mNumLayers;
|
||||
|
||||
// Object connections
|
||||
std::unordered_map<u32, std::vector<SLink>> mConnectionMap;
|
||||
|
||||
// Compression
|
||||
u8 *mDecmpBuffer;
|
||||
bool mHasDecompressedBuffer;
|
||||
std::vector<SCompressedCluster> mClusters;
|
||||
u32 mTotalDecmpSize;
|
||||
|
||||
// Block numbers
|
||||
u32 mGeometryBlockNum;
|
||||
u32 mScriptLayerBlockNum;
|
||||
u32 mCollisionBlockNum;
|
||||
u32 mUnknownBlockNum;
|
||||
u32 mLightsBlockNum;
|
||||
u32 mEmptyBlockNum;
|
||||
u32 mPathBlockNum;
|
||||
u32 mOctreeBlockNum;
|
||||
u32 mScriptGeneratorBlockNum;
|
||||
u32 mFFFFBlockNum;
|
||||
u32 mUnknown2BlockNum;
|
||||
u32 mEGMCBlockNum;
|
||||
u32 mBoundingBoxesBlockNum;
|
||||
u32 mDependenciesBlockNum;
|
||||
u32 mGPUBlockNum;
|
||||
u32 mPVSBlockNum;
|
||||
u32 mRSOBlockNum;
|
||||
|
||||
struct SCompressedCluster {
|
||||
u32 BufferSize, DecompressedSize, CompressedSize, NumSections;
|
||||
};
|
||||
|
||||
CAreaLoader();
|
||||
~CAreaLoader();
|
||||
|
||||
// Prime
|
||||
void ReadHeaderPrime();
|
||||
void ReadGeometryPrime();
|
||||
void ReadSCLYPrime();
|
||||
void ReadLightsPrime();
|
||||
|
||||
// Echoes
|
||||
void ReadHeaderEchoes();
|
||||
void ReadSCLYEchoes();
|
||||
|
||||
// Corruption
|
||||
void ReadHeaderCorruption();
|
||||
void ReadGeometryCorruption();
|
||||
|
||||
// Common
|
||||
void ReadCompressedBlocks();
|
||||
void Decompress();
|
||||
void ReadCollision();
|
||||
void SetUpObjects();
|
||||
|
||||
public:
|
||||
static CGameArea* LoadMREA(CInputStream& MREA);
|
||||
static EGame GetFormatVersion(u32 version);
|
||||
};
|
||||
|
||||
#endif // CAREALOADER_H
|
||||
58
Resource/factory/CBlockMgr.cpp
Normal file
58
Resource/factory/CBlockMgr.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#include "CBlockMgrIn.h"
|
||||
|
||||
CBlockMgrIn::CBlockMgrIn(unsigned long count, CInputStream* src)
|
||||
{
|
||||
mpInputStream = src;
|
||||
mBlockCount = count;
|
||||
mBlockSizes.resize(count);
|
||||
|
||||
for (unsigned long b = 0; b < count; b++)
|
||||
mBlockSizes[b] = src->ReadLong();
|
||||
}
|
||||
|
||||
void CBlockMgrIn::Init()
|
||||
{
|
||||
// Initialize the block manager; this marks the start of the first block
|
||||
mCurBlock = 0;
|
||||
mCurBlockStart = mpInputStream->Tell();
|
||||
mBlocksStart = mpInputStream->Tell();
|
||||
}
|
||||
|
||||
void CBlockMgrIn::ToBlock(unsigned long block)
|
||||
{
|
||||
unsigned long offset = mBlocksStart;
|
||||
for (unsigned long b = 0; b < block; b++)
|
||||
offset += mBlockSizes[b];
|
||||
|
||||
mpInputStream->Seek(offset, SEEK_SET);
|
||||
|
||||
mCurBlock = block;
|
||||
mCurBlockStart = mpInputStream->Tell();
|
||||
}
|
||||
|
||||
void CBlockMgrIn::ToNextBlock()
|
||||
{
|
||||
mpInputStream->Seek(mCurBlockStart + mBlockSizes[mCurBlock], SEEK_SET);
|
||||
mCurBlock++;
|
||||
mCurBlockStart = mpInputStream->Tell();
|
||||
}
|
||||
|
||||
long CBlockMgrIn::NextOffset()
|
||||
{
|
||||
return mCurBlockStart + mBlockSizes[mCurBlock];
|
||||
}
|
||||
|
||||
long CBlockMgrIn::CurrentBlock()
|
||||
{
|
||||
return mCurBlock;
|
||||
}
|
||||
|
||||
long CBlockMgrIn::CurrentBlockSize()
|
||||
{
|
||||
return mBlockSizes[mCurBlock];
|
||||
}
|
||||
|
||||
void CBlockMgrIn::SetInputStream(CInputStream *in)
|
||||
{
|
||||
mpInputStream = in;
|
||||
}
|
||||
28
Resource/factory/CBlockMgrIn.h
Normal file
28
Resource/factory/CBlockMgrIn.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#ifndef CBLOCKMGRIN_H
|
||||
#define CBLOCKMGRIN_H
|
||||
|
||||
#include <FileIO/CInputStream.h>
|
||||
#include <vector>
|
||||
|
||||
// The purpose of this class is to keep track of data block navigation - required to read CMDL and MREA files correctly
|
||||
class CBlockMgrIn
|
||||
{
|
||||
CInputStream *mpInputStream;
|
||||
unsigned long mBlockCount;
|
||||
std::vector<unsigned long> mBlockSizes;
|
||||
unsigned long mCurBlock;
|
||||
unsigned long mCurBlockStart;
|
||||
unsigned long mBlocksStart;
|
||||
|
||||
public:
|
||||
CBlockMgrIn(unsigned long count, CInputStream* src);
|
||||
void Init();
|
||||
void ToBlock(unsigned long block);
|
||||
void ToNextBlock();
|
||||
long NextOffset();
|
||||
long CurrentBlock();
|
||||
long CurrentBlockSize();
|
||||
void SetInputStream(CInputStream *in);
|
||||
};
|
||||
|
||||
#endif // CBLOCKMGRIN_H
|
||||
136
Resource/factory/CCollisionLoader.cpp
Normal file
136
Resource/factory/CCollisionLoader.cpp
Normal file
@@ -0,0 +1,136 @@
|
||||
#include "CCollisionLoader.h"
|
||||
#include <Core/Log.h>
|
||||
#include <iostream>
|
||||
|
||||
CCollisionLoader::CCollisionLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CCollisionMesh* CCollisionLoader::LoadAreaCollision(CInputStream& MREA)
|
||||
{
|
||||
if (!MREA.IsValid()) return nullptr;
|
||||
CCollisionLoader loader;
|
||||
|
||||
MREA.Seek(0x8, SEEK_CUR);
|
||||
u32 deafbabe = MREA.ReadLong();
|
||||
if (deafbabe != 0xdeafbabe)
|
||||
{
|
||||
Log::FileError(MREA.GetSourceString(), MREA.Tell() - 4, "Invalid collision magic: " + StringUtil::ToHexString(deafbabe));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 version = MREA.ReadLong();
|
||||
loader.version = ECollisionVersion(version);
|
||||
if ((loader.version != Prime) && (loader.version != Echoes))
|
||||
{
|
||||
Log::FileError(MREA.GetSourceString(), MREA.Tell() - 4, "Unsupported collision version: " + StringUtil::ToHexString(version));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
loader.mesh = new CCollisionMesh;
|
||||
CCollisionMesh *cmesh = loader.mesh;
|
||||
|
||||
// Octree - structure is known, but not coding this right now
|
||||
cmesh->mAABox = CAABox(MREA);
|
||||
MREA.Seek(0x4, SEEK_CUR);
|
||||
u32 octreeSize = MREA.ReadLong();
|
||||
MREA.Seek(octreeSize, SEEK_CUR); // Skipping the octree for now
|
||||
cmesh->mOctreeLoaded = false;
|
||||
|
||||
// Properties
|
||||
u32 propertySetCount = MREA.ReadLong();
|
||||
for (u32 p = 0; p < propertySetCount; p++)
|
||||
loader.readPropertyFlags(MREA);
|
||||
|
||||
// Property indices for vertices/lines/faces
|
||||
u32 vtxIndexCount = MREA.ReadLong();
|
||||
std::vector<u8> vtxIndices(vtxIndexCount);
|
||||
MREA.ReadBytes(vtxIndices.data(), vtxIndices.size());
|
||||
|
||||
u32 lineIndexCount = MREA.ReadLong();
|
||||
std::vector<u8> lineIndices(lineIndexCount);
|
||||
MREA.ReadBytes(lineIndices.data(), lineIndices.size());
|
||||
|
||||
u32 faceIndexCount = MREA.ReadLong();
|
||||
std::vector<u8> faceIndices(faceIndexCount);
|
||||
MREA.ReadBytes(faceIndices.data(), faceIndices.size());
|
||||
|
||||
// Lines
|
||||
cmesh->mLineCount = MREA.ReadLong();
|
||||
cmesh->mCollisionLines.resize(cmesh->mLineCount);
|
||||
for (u32 l = 0; l < cmesh->mLineCount; l++)
|
||||
{
|
||||
CCollisionMesh::CCollisionLine *Line = &cmesh->mCollisionLines[l];
|
||||
Line->Vertices[0] = MREA.ReadShort();
|
||||
Line->Vertices[1] = MREA.ReadShort();
|
||||
Line->Properties = loader.properties[lineIndices[l]];
|
||||
}
|
||||
|
||||
// Faces
|
||||
cmesh->mFaceCount = MREA.ReadLong() / 3; // Not sure why they store it this way. It's inconsistent.
|
||||
cmesh->mCollisionFaces.resize(cmesh->mFaceCount);
|
||||
for (u32 f = 0; f < cmesh->mFaceCount; f++)
|
||||
{
|
||||
CCollisionMesh::CCollisionFace *face = &cmesh->mCollisionFaces[f];
|
||||
face->Lines[0] = MREA.ReadShort();
|
||||
face->Lines[1] = MREA.ReadShort();
|
||||
face->Lines[2] = MREA.ReadShort();
|
||||
face->Properties = loader.properties[faceIndices[f]];
|
||||
}
|
||||
|
||||
// Echoes introduces a new data chunk; don't know what it is yet, skipping for now
|
||||
if (loader.version == Echoes)
|
||||
{
|
||||
u32 unknown_count = MREA.ReadLong();
|
||||
MREA.Seek(unknown_count * 2, SEEK_CUR);
|
||||
}
|
||||
|
||||
// Vertices
|
||||
cmesh->mVertexCount = MREA.ReadLong();
|
||||
cmesh->mCollisionVertices.resize(cmesh->mVertexCount);
|
||||
for (u32 v = 0; v < cmesh->mVertexCount; v++)
|
||||
{
|
||||
CCollisionMesh::CCollisionVertex *vtx = &cmesh->mCollisionVertices[v];
|
||||
vtx->Pos = CVector3f(MREA);
|
||||
vtx->Properties = loader.properties[vtxIndices[v]];
|
||||
}
|
||||
|
||||
return cmesh;
|
||||
}
|
||||
|
||||
CCollisionMesh::CCollisionOctree* CCollisionLoader::parseOctree(CInputStream&)
|
||||
{
|
||||
// Not using: Parameter 1 (CInputStream& - src)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CCollisionMesh::CCollisionOctree::SBranch* CCollisionLoader::parseOctreeBranch(CInputStream&)
|
||||
{
|
||||
// Not using: Parameter 1 (CInputStream& - src)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CCollisionMesh::CCollisionOctree::SLeaf* CCollisionLoader::parseOctreeLeaf(CInputStream&)
|
||||
{
|
||||
// Not using: Parameter 1 (CInputStream& - src)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CCollisionLoader::readPropertyFlags(CInputStream& src)
|
||||
{
|
||||
CCollisionMesh::SCollisionProperties property;
|
||||
|
||||
if (version == Prime)
|
||||
{
|
||||
u32 flag = src.ReadLong();
|
||||
property.Invert = (flag >> 25) & 0x1;
|
||||
}
|
||||
|
||||
if (version == Echoes)
|
||||
{
|
||||
u64 flag = src.ReadLongLong();
|
||||
property.Invert = (flag >> 24) & 0x1;
|
||||
}
|
||||
|
||||
properties.push_back(property);
|
||||
}
|
||||
31
Resource/factory/CCollisionLoader.h
Normal file
31
Resource/factory/CCollisionLoader.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#ifndef CCOLLISIONLOADER_H
|
||||
#define CCOLLISIONLOADER_H
|
||||
|
||||
#include "../CCollisionMesh.h"
|
||||
|
||||
class CCollisionLoader
|
||||
{
|
||||
enum ECollisionVersion;
|
||||
|
||||
CCollisionMesh *mesh;
|
||||
ECollisionVersion version;
|
||||
std::vector<CCollisionMesh::SCollisionProperties> properties;
|
||||
|
||||
enum ECollisionVersion
|
||||
{
|
||||
Prime = 0x3,
|
||||
Echoes = 0x4,
|
||||
DonkeyKongCountryReturns = 0x5
|
||||
};
|
||||
|
||||
CCollisionLoader();
|
||||
CCollisionMesh::CCollisionOctree* parseOctree(CInputStream& src);
|
||||
CCollisionMesh::CCollisionOctree::SBranch* parseOctreeBranch(CInputStream& src);
|
||||
CCollisionMesh::CCollisionOctree::SLeaf* parseOctreeLeaf(CInputStream& src);
|
||||
void readPropertyFlags(CInputStream& src);
|
||||
|
||||
public:
|
||||
static CCollisionMesh* LoadAreaCollision(CInputStream& MREA);
|
||||
};
|
||||
|
||||
#endif // CCOLLISIONLOADER_H
|
||||
119
Resource/factory/CFontLoader.cpp
Normal file
119
Resource/factory/CFontLoader.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include "CFontLoader.h"
|
||||
#include <Core/Log.h>
|
||||
#include <iostream>
|
||||
|
||||
CFontLoader::CFontLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CFont* CFontLoader::LoadFont(CInputStream& FONT)
|
||||
{
|
||||
// If I seek past a value without reading it, then it's because I don't know what it is
|
||||
mpFont->mUnknown = FONT.ReadLong();
|
||||
mpFont->mLineHeight = FONT.ReadLong();
|
||||
mpFont->mVerticalOffset = FONT.ReadLong();
|
||||
mpFont->mLineMargin = FONT.ReadLong();
|
||||
if (mVersion > ePrimeKioskDemo) FONT.Seek(0x4, SEEK_CUR);
|
||||
FONT.Seek(0x2, SEEK_CUR);
|
||||
mpFont->mDefaultSize = FONT.ReadLong();
|
||||
mpFont->mFontName = FONT.ReadString();
|
||||
|
||||
if (mVersion <= eEchoes) mpFont->mpFontTexture = (CTexture*) gResCache.GetResource(FONT.ReadLong(), "TXTR");
|
||||
else mpFont->mpFontTexture = (CTexture*) gResCache.GetResource(FONT.ReadLongLong(), "TXTR");
|
||||
mpFont->mTextureToken = CToken(mpFont->mpFontTexture);
|
||||
|
||||
mpFont->mTextureFormat = FONT.ReadLong();
|
||||
u32 NumGlyphs = FONT.ReadLong();
|
||||
mpFont->mGlyphs.reserve(NumGlyphs);
|
||||
|
||||
for (u32 iGlyph = 0; iGlyph < NumGlyphs; iGlyph++)
|
||||
{
|
||||
CFont::SGlyph Glyph;
|
||||
Glyph.Character = FONT.ReadShort();
|
||||
|
||||
float TexCoordL = FONT.ReadFloat();
|
||||
float TexCoordU = FONT.ReadFloat();
|
||||
float TexCoordR = FONT.ReadFloat();
|
||||
float TexCoordD = FONT.ReadFloat();
|
||||
Glyph.TexCoords[0] = CVector2f(TexCoordL, TexCoordU); // Upper-left
|
||||
Glyph.TexCoords[1] = CVector2f(TexCoordR, TexCoordU); // Upper-right
|
||||
Glyph.TexCoords[2] = CVector2f(TexCoordL, TexCoordD); // Lower-left
|
||||
Glyph.TexCoords[3] = CVector2f(TexCoordR, TexCoordD); // Lower-right
|
||||
|
||||
if (mVersion <= ePrime)
|
||||
{
|
||||
Glyph.RGBAChannel = 0;
|
||||
Glyph.LeftPadding = FONT.ReadLong();
|
||||
Glyph.PrintAdvance = FONT.ReadLong();
|
||||
Glyph.RightPadding = FONT.ReadLong();
|
||||
Glyph.Width = FONT.ReadLong();
|
||||
Glyph.Height = FONT.ReadLong();
|
||||
Glyph.BaseOffset = FONT.ReadLong();
|
||||
Glyph.KerningIndex = FONT.ReadLong();
|
||||
}
|
||||
else if (mVersion >= eEchoes)
|
||||
{
|
||||
Glyph.RGBAChannel = FONT.ReadByte();
|
||||
Glyph.LeftPadding = FONT.ReadByte();
|
||||
Glyph.PrintAdvance = FONT.ReadByte();
|
||||
Glyph.RightPadding = FONT.ReadByte();
|
||||
Glyph.Width = FONT.ReadByte();
|
||||
Glyph.Height = FONT.ReadByte();
|
||||
Glyph.BaseOffset = FONT.ReadByte();
|
||||
Glyph.KerningIndex = FONT.ReadShort();
|
||||
}
|
||||
mpFont->mGlyphs[Glyph.Character] = Glyph;
|
||||
}
|
||||
|
||||
u32 NumKerningPairs = FONT.ReadLong();
|
||||
mpFont->mKerningTable.reserve(NumKerningPairs);
|
||||
|
||||
for (u32 iKern = 0; iKern < NumKerningPairs; iKern++)
|
||||
{
|
||||
CFont::SKerningPair Pair;
|
||||
Pair.CharacterA = FONT.ReadShort();
|
||||
Pair.CharacterB = FONT.ReadShort();
|
||||
Pair.Adjust = FONT.ReadLong();
|
||||
mpFont->mKerningTable.push_back(Pair);
|
||||
}
|
||||
|
||||
return mpFont;
|
||||
}
|
||||
|
||||
CFont* CFontLoader::LoadFONT(CInputStream& FONT)
|
||||
{
|
||||
if (!FONT.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + FONT.GetSourceString());
|
||||
|
||||
CFourCC Magic(FONT);
|
||||
if (Magic != "FONT")
|
||||
{
|
||||
Log::FileError(FONT.GetSourceString(), "Invalid FONT magic: " + StringUtil::ToHexString((u32) Magic.ToLong()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 FileVersion = FONT.ReadLong();
|
||||
EGame Version = GetFormatVersion(FileVersion);
|
||||
if (Version == eUnknownVersion)
|
||||
{
|
||||
Log::FileError(FONT.GetSourceString(), "Unsupported FONT version: " + StringUtil::ToHexString(FileVersion));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CFontLoader Loader;
|
||||
Loader.mpFont = new CFont();
|
||||
Loader.mVersion = Version;
|
||||
return Loader.LoadFont(FONT);
|
||||
}
|
||||
|
||||
EGame CFontLoader::GetFormatVersion(u32 Version)
|
||||
{
|
||||
switch (Version)
|
||||
{
|
||||
case 1: return ePrimeKioskDemo;
|
||||
case 2: return ePrime;
|
||||
case 4: return eEchoes;
|
||||
case 5: return eCorruption;
|
||||
default: return eUnknownVersion;
|
||||
}
|
||||
}
|
||||
21
Resource/factory/CFontLoader.h
Normal file
21
Resource/factory/CFontLoader.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef CFONTLOADER_H
|
||||
#define CFONTLOADER_H
|
||||
|
||||
#include "../CFont.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CFontLoader
|
||||
{
|
||||
CFont *mpFont;
|
||||
EGame mVersion;
|
||||
|
||||
CFontLoader();
|
||||
CFont* LoadFont(CInputStream& FONT);
|
||||
|
||||
public:
|
||||
static CFont* LoadFONT(CInputStream& FONT);
|
||||
static EGame GetFormatVersion(u32 Version);
|
||||
};
|
||||
|
||||
#endif // CFONTLOADER_H
|
||||
581
Resource/factory/CMaterialLoader.cpp
Normal file
581
Resource/factory/CMaterialLoader.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#include "CMaterialLoader.h"
|
||||
#include <Core/CResCache.h>
|
||||
#include <Core/Log.h>
|
||||
#include <OpenGL/GLCommon.h>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
CMaterialLoader::CMaterialLoader()
|
||||
{
|
||||
mCorruptionFlags = 0;
|
||||
mHasOPAC = false;
|
||||
}
|
||||
|
||||
CMaterialLoader::~CMaterialLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CMaterialSet* CMaterialLoader::LoadMaterialSet(CInputStream& Mat, EGame Version)
|
||||
{
|
||||
CMaterialLoader Loader;
|
||||
Loader.mpSet = new CMaterialSet();
|
||||
Loader.mpFile = &Mat;
|
||||
Loader.mVersion = Version;
|
||||
|
||||
if ((Version >= ePrimeKioskDemo) && (Version <= eEchoes))
|
||||
Loader.ReadPrimeMatSet();
|
||||
else
|
||||
Loader.ReadCorruptionMatSet();
|
||||
|
||||
return Loader.mpSet;
|
||||
}
|
||||
|
||||
void CMaterialLoader::ReadPrimeMatSet()
|
||||
{
|
||||
// Textures
|
||||
u32 TexCount = mpFile->ReadLong();
|
||||
mpSet->textures.resize(TexCount);
|
||||
|
||||
for (u32 t = 0; t < TexCount; t++)
|
||||
{
|
||||
u32 TextureID = mpFile->ReadLong();
|
||||
mpSet->textures[t] = (CTexture*) gResCache.GetResource(TextureID, "TXTR");
|
||||
}
|
||||
|
||||
// Materials
|
||||
u32 MatCount = mpFile->ReadLong();
|
||||
std::vector<u32> offsets(MatCount);
|
||||
for (u32 m = 0; m < MatCount; m++)
|
||||
offsets[m] = mpFile->ReadLong();
|
||||
|
||||
u32 mats_start = mpFile->Tell();
|
||||
mpSet->materials.resize(MatCount);
|
||||
for (u32 m = 0; m < MatCount; m++)
|
||||
{
|
||||
mpSet->materials[m] = ReadPrimeMaterial();
|
||||
mpSet->materials[m]->mVersion = mVersion;
|
||||
mpFile->Seek(mats_start + offsets[m], SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
CMaterial* CMaterialLoader::ReadPrimeMaterial()
|
||||
{
|
||||
CMaterial *pMat = new CMaterial();
|
||||
pMat->mEnableBloom = false;
|
||||
|
||||
// Flags
|
||||
pMat->mOptions = (CMaterial::EMaterialOptions) (mpFile->ReadLong() & CMaterial::eAllSettings);
|
||||
|
||||
// Textures
|
||||
u32 NumTextures = mpFile->ReadLong();
|
||||
std::vector<u32> TextureIndices(NumTextures);
|
||||
|
||||
for (u32 iTex = 0; iTex < NumTextures; iTex++)
|
||||
{
|
||||
u32 Index = mpFile->ReadLong();
|
||||
TextureIndices[iTex] = Index;
|
||||
}
|
||||
|
||||
// Vertex description
|
||||
pMat->mVtxDesc = (EVertexDescription) mpFile->ReadLong();
|
||||
|
||||
// Unknowns
|
||||
if (mVersion >= eEchoesDemo)
|
||||
{
|
||||
pMat->mEchoesUnknownA = mpFile->ReadLong();
|
||||
pMat->mEchoesUnknownB = mpFile->ReadLong();
|
||||
}
|
||||
mpFile->Seek(0x4, SEEK_CUR); // Skipping group index
|
||||
|
||||
// Konst
|
||||
if (pMat->mOptions & CMaterial::eKonst)
|
||||
{
|
||||
u32 KonstCount = mpFile->ReadLong();
|
||||
|
||||
for (u32 iKonst = 0; iKonst < KonstCount; iKonst++)
|
||||
{
|
||||
if (iKonst >= 4) break;
|
||||
pMat->mKonstColors[iKonst] = CColor(*mpFile);
|
||||
}
|
||||
if (KonstCount > 4) mpFile->Seek(0x4 * (KonstCount - 4), SEEK_CUR);
|
||||
}
|
||||
|
||||
// Blend mode
|
||||
pMat->mBlendDstFac = glBlendFactor[mpFile->ReadShort()];
|
||||
pMat->mBlendSrcFac = glBlendFactor[mpFile->ReadShort()];
|
||||
|
||||
// Indirect texture
|
||||
if (pMat->mOptions & CMaterial::eIndStage)
|
||||
{
|
||||
u32 IndTexIndex = mpFile->ReadLong();
|
||||
pMat->mpIndirectTexture = mpSet->textures[IndTexIndex];
|
||||
}
|
||||
|
||||
// Color channels
|
||||
u32 ChanCount = mpFile->ReadLong();
|
||||
pMat->mLightingEnabled = ((mpFile->ReadLong() & 0x1) == 1);
|
||||
mpFile->Seek((4 * ChanCount) - 4, SEEK_CUR);
|
||||
|
||||
// TEV
|
||||
u32 TevCount = mpFile->ReadLong();
|
||||
pMat->mPasses.resize(TevCount);
|
||||
|
||||
for (u32 iTev = 0; iTev < TevCount; iTev++)
|
||||
{
|
||||
CMaterialPass *pPass = new CMaterialPass(pMat);
|
||||
|
||||
u32 ColorIn = mpFile->ReadLong();
|
||||
u32 AlphaIn = mpFile->ReadLong();
|
||||
pPass->mColorOutput = (ETevOutput) ((mpFile->ReadLong() & 0x600) >> 9);
|
||||
pPass->mAlphaOutput = (ETevOutput) ((mpFile->ReadLong() & 0x600) >> 9);
|
||||
mpFile->Seek(0x1, SEEK_CUR); // Padding byte
|
||||
pPass->mKAlphaSel = (ETevKSel) mpFile->ReadByte();
|
||||
pPass->mKColorSel = (ETevKSel) mpFile->ReadByte();
|
||||
pPass->mRasSel = (ETevRasSel) (u8) mpFile->ReadByte();
|
||||
|
||||
for (u32 iInput = 0; iInput < 4; iInput++)
|
||||
{
|
||||
pPass->mColorInputs[iInput] = (ETevColorInput) ((ColorIn >> (iInput * 5)) & 0xF);
|
||||
pPass->mAlphaInputs[iInput] = (ETevAlphaInput) ((AlphaIn >> (iInput * 5)) & 0x7);
|
||||
}
|
||||
|
||||
pMat->mPasses[iTev] = pPass;
|
||||
}
|
||||
|
||||
std::vector<u8> TevCoordIndices(TevCount);
|
||||
for (u32 iTev = 0; iTev < TevCount; iTev++)
|
||||
{
|
||||
mpFile->Seek(0x2, SEEK_CUR);
|
||||
CMaterialPass *pPass = pMat->Pass(iTev);
|
||||
|
||||
u8 TexSel = mpFile->ReadByte();
|
||||
if ((TexSel == 0xFF) || (TexSel >= mpSet->textures.size()))
|
||||
{
|
||||
pPass->mpTexture = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
pPass->mpTexture = mpSet->textures[ TextureIndices[TexSel] ];
|
||||
pPass->mTexToken = CToken(pPass->mpTexture);
|
||||
}
|
||||
|
||||
TevCoordIndices[iTev] = mpFile->ReadByte();
|
||||
}
|
||||
|
||||
// TexGens
|
||||
u32 TexGenCount = mpFile->ReadLong();
|
||||
std::vector<u32> TexGens(TexGenCount);
|
||||
|
||||
for (u32 iTex = 0; iTex < TexGenCount; iTex++)
|
||||
TexGens[iTex] = mpFile->ReadLong();
|
||||
|
||||
// UV animations
|
||||
mpFile->Seek(0x4, SEEK_CUR); // Skipping UV anims size
|
||||
u32 NumAnims = mpFile->ReadLong();
|
||||
|
||||
struct SUVAnim {
|
||||
s32 Mode; float Params[4];
|
||||
};
|
||||
std::vector <SUVAnim> Anims(NumAnims);
|
||||
|
||||
for (u32 iAnim = 0; iAnim < NumAnims; iAnim++)
|
||||
{
|
||||
Anims[iAnim].Mode = mpFile->ReadLong();
|
||||
|
||||
switch (Anims[iAnim].Mode)
|
||||
{
|
||||
case 3: // Rotation
|
||||
case 7: // ???
|
||||
Anims[iAnim].Params[0] = mpFile->ReadFloat();
|
||||
Anims[iAnim].Params[1] = mpFile->ReadFloat();
|
||||
break;
|
||||
case 2: // UV Scroll
|
||||
case 4: // U Scroll
|
||||
case 5: // V Scroll
|
||||
Anims[iAnim].Params[0] = mpFile->ReadFloat();
|
||||
Anims[iAnim].Params[1] = mpFile->ReadFloat();
|
||||
Anims[iAnim].Params[2] = mpFile->ReadFloat();
|
||||
Anims[iAnim].Params[3] = mpFile->ReadFloat();
|
||||
break;
|
||||
case 0: // Inverse ModelView Matrix
|
||||
case 1: // Inverse ModelView Matrix Translated
|
||||
case 6: // Model Matrix
|
||||
break;
|
||||
default:
|
||||
Log::FileError(mpFile->GetSourceString(), mpFile->Tell() - 4, "Unsupported animation mode encountered: " + StringUtil::ToHexString((u32) Anims[iAnim].Mode));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Move TexGen and anims into passes
|
||||
for (u32 iPass = 0; iPass < pMat->mPasses.size(); iPass++)
|
||||
{
|
||||
CMaterialPass *pPass = pMat->mPasses[iPass];
|
||||
u8 TexCoordIdx = TevCoordIndices[iPass];
|
||||
|
||||
if ((TexGens.size() == 0) || (TexCoordIdx == 0xFF))
|
||||
{
|
||||
pPass->mTexCoordSource = 0xFF;
|
||||
pPass->mAnimMode = eNoUVAnim;
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pPass->mTexCoordSource = (u8) ((TexGens[TexCoordIdx] & 0x1F0) >> 4);
|
||||
|
||||
// Next step - find which animation is used by this pass
|
||||
// Texture matrix is a reliable way to tell, because every UV anim mode generates a texture matrix
|
||||
u32 TexMtxIdx = ((TexGens[TexCoordIdx] & 0x3E00) >> 9) / 3;
|
||||
|
||||
if (TexMtxIdx == 10) pPass->mAnimMode = eNoUVAnim; // 10 is identity matrix; indicates no UV anim for this pass
|
||||
|
||||
else
|
||||
{
|
||||
pPass->mAnimMode = (EUVAnimMode) Anims[TexMtxIdx].Mode;
|
||||
|
||||
for (u32 iParam = 0; iParam < 4; iParam++)
|
||||
pPass->mAnimParams[iParam] = Anims[TexMtxIdx].Params[iParam];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pMat;
|
||||
}
|
||||
|
||||
void CMaterialLoader::ReadCorruptionMatSet()
|
||||
{
|
||||
u32 NumMats = mpFile->ReadLong();
|
||||
mpSet->materials.resize(NumMats);
|
||||
|
||||
for (u32 iMat = 0; iMat < NumMats; iMat++)
|
||||
{
|
||||
u32 Size = mpFile->ReadLong();
|
||||
u32 Next = mpFile->Tell() + Size;
|
||||
mpSet->materials[iMat] = ReadCorruptionMaterial();
|
||||
mpSet->materials[iMat]->mVersion = mVersion;
|
||||
mpFile->Seek(Next, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
CMaterial* CMaterialLoader::ReadCorruptionMaterial()
|
||||
{
|
||||
CMaterial *pMat = new CMaterial();
|
||||
pMat->mOptions = CMaterial::eDepthWrite;
|
||||
pMat->mEnableBloom = true;
|
||||
|
||||
// Flags
|
||||
u32 Flags = mpFile->ReadLong();
|
||||
if (Flags & 0x8)
|
||||
{
|
||||
pMat->mBlendSrcFac = GL_SRC_ALPHA;
|
||||
pMat->mBlendDstFac = GL_ONE_MINUS_SRC_ALPHA;
|
||||
pMat->mOptions |= CMaterial::eTransparent;
|
||||
}
|
||||
else if (Flags & 0x20)
|
||||
{
|
||||
pMat->mBlendSrcFac = GL_ONE;
|
||||
pMat->mBlendDstFac = GL_ONE;
|
||||
pMat->mOptions |= CMaterial::eTransparent;
|
||||
}
|
||||
|
||||
if (Flags & 0x10) pMat->mOptions |= CMaterial::ePunchthrough;
|
||||
if (Flags & 0x100) pMat->mOptions |= CMaterial::eOccluder;
|
||||
mHas0x400 = ((Flags & 0x400) != 0);
|
||||
|
||||
mpFile->Seek(0x8, SEEK_CUR); // Don't know what any of this is
|
||||
pMat->mVtxDesc = (EVertexDescription) mpFile->ReadLong();
|
||||
mpFile->Seek(0xC, SEEK_CUR);
|
||||
|
||||
// Initialize all KColors to white
|
||||
pMat->mKonstColors[0] = CColor::skWhite;
|
||||
pMat->mKonstColors[1] = CColor::skWhite;
|
||||
pMat->mKonstColors[2] = CColor::skWhite;
|
||||
// Current usage of KColors:
|
||||
// 0 - INT OPAC (transparency)
|
||||
// 1 - CLR DIFB (lightmap multiplier)
|
||||
// 2 - CLR CLR (additive color)
|
||||
|
||||
while (true)
|
||||
{
|
||||
CFourCC Type = mpFile->ReadLong();
|
||||
|
||||
// END
|
||||
if (Type == "END ")
|
||||
break;
|
||||
|
||||
// INT
|
||||
if (Type == "INT ")
|
||||
{
|
||||
CFourCC IntType = mpFile->ReadLong();
|
||||
u8 IntVal = (u8) mpFile->ReadLong();
|
||||
|
||||
if (IntType == "OPAC")
|
||||
{
|
||||
pMat->mKonstColors[0] = CColor(1.f, 1.f, 1.f, (float) IntVal / 255);
|
||||
mHasOPAC = true;
|
||||
}
|
||||
}
|
||||
|
||||
// CLR
|
||||
if (Type == "CLR ")
|
||||
{
|
||||
CFourCC ClrType = mpFile->ReadLong();
|
||||
CColor ClrVal(*mpFile);
|
||||
|
||||
if (ClrType == "DIFB")
|
||||
{
|
||||
ClrVal.a = 0xFF;
|
||||
pMat->mKonstColors[1] = ClrVal;
|
||||
}
|
||||
|
||||
if (ClrType == "CLR ")
|
||||
{
|
||||
// I'm not sure what this does. It has a clear and obvious ingame effect
|
||||
// but I need to test it further to tell specifically what it's doing.
|
||||
// All attempts at implementing this just break things.
|
||||
}
|
||||
}
|
||||
|
||||
// PASS
|
||||
if (Type == "PASS")
|
||||
{
|
||||
CMaterialPass *pPass = new CMaterialPass(pMat);
|
||||
mPassOffsets.push_back(mpFile->Tell() - 4);
|
||||
|
||||
u32 Size = mpFile->ReadLong();
|
||||
u32 Next = Size + mpFile->Tell();
|
||||
|
||||
pPass->mPassType = mpFile->ReadLong();
|
||||
pPass->mSettings = (CMaterialPass::EPassSettings) mpFile->ReadLong();
|
||||
|
||||
u64 TextureID = mpFile->ReadLongLong();
|
||||
if (TextureID == 0xFFFFFFFFFFFFFFFF)
|
||||
{
|
||||
Log::FileWarning(mpFile->GetSourceString(), mPassOffsets.back(), "Skipping " + pPass->mPassType.ToString() + " pass with no texture");
|
||||
delete pPass;
|
||||
continue;
|
||||
}
|
||||
|
||||
CTexture *pTex = (CTexture*) gResCache.GetResource(TextureID, "TXTR");
|
||||
mpSet->textures.push_back(pTex);
|
||||
pPass->mpTexture = pTex;
|
||||
pPass->mTexToken = CToken(pTex);
|
||||
|
||||
pPass->mTexCoordSource = 4 + (u8) mpFile->ReadLong();
|
||||
u32 AnimSize = mpFile->ReadLong();
|
||||
|
||||
if (AnimSize > 0)
|
||||
{
|
||||
u16 Unknown1 = mpFile->ReadShort();
|
||||
u16 Unknown2 = mpFile->ReadShort();
|
||||
pPass->mAnimMode = (EUVAnimMode) mpFile->ReadLong();
|
||||
|
||||
switch (pPass->mAnimMode)
|
||||
{
|
||||
case 3: // Rotation
|
||||
case 7: // ???
|
||||
pPass->mAnimParams[0] = mpFile->ReadFloat();
|
||||
pPass->mAnimParams[1] = mpFile->ReadFloat();
|
||||
break;
|
||||
case 2: // UV Scroll
|
||||
case 4: // U Scroll
|
||||
case 5: // V Scroll
|
||||
pPass->mAnimParams[0] = mpFile->ReadFloat();
|
||||
pPass->mAnimParams[1] = mpFile->ReadFloat();
|
||||
pPass->mAnimParams[2] = mpFile->ReadFloat();
|
||||
pPass->mAnimParams[3] = mpFile->ReadFloat();
|
||||
break;
|
||||
case 0: // Inverse ModelView Matrix
|
||||
case 1: // Inverse ModelView Matrix Translated
|
||||
case 6: // Model Matrix
|
||||
case 10: // Yet-to-be-named
|
||||
break;
|
||||
default:
|
||||
Log::FileError(mpFile->GetSourceString(), mpFile->Tell() - 8, "Unsupported animation mode encountered: " + StringUtil::ToHexString((u32) pPass->mAnimMode));
|
||||
break;
|
||||
}
|
||||
|
||||
// Hack until the correct way to determine tex coord source is figured out
|
||||
if ((pPass->mAnimMode < 2) || (pPass->mAnimMode == 6) || (pPass->mAnimMode == 7) || (pPass->mAnimMode == 10))
|
||||
pPass->mTexCoordSource = 1;
|
||||
}
|
||||
|
||||
else pPass->mAnimMode = eNoUVAnim;
|
||||
|
||||
pMat->mPasses.push_back(pPass);
|
||||
mpFile->Seek(Next, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
CreateCorruptionPasses(pMat);
|
||||
mHasOPAC = false;
|
||||
return pMat;
|
||||
}
|
||||
|
||||
void CMaterialLoader::CreateCorruptionPasses(CMaterial *pMat)
|
||||
{
|
||||
u32 NumPass = pMat->PassCount();
|
||||
bool Lightmap = false;
|
||||
bool AlphaBlended = ((pMat->mBlendSrcFac == GL_SRC_ALPHA) && (pMat->mBlendDstFac == GL_ONE_MINUS_SRC_ALPHA));
|
||||
|
||||
for (u32 iPass = 0; iPass < NumPass; iPass++)
|
||||
{
|
||||
CMaterialPass *pPass = pMat->Pass(iPass);
|
||||
CFourCC Type = pPass->Type();
|
||||
|
||||
// Color Map (Diffuse)
|
||||
if (Type == "CLR ")
|
||||
{
|
||||
if (Lightmap)
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eColor0RGB, eTextureRGB, ePrevRGB);
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eRasRGB, eTextureRGB, ePrevRGB);
|
||||
pPass->SetRasSel(eRasColor0A0);
|
||||
}
|
||||
|
||||
|
||||
if (pMat->mOptions & CMaterial::ePunchthrough)
|
||||
{
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha);
|
||||
}
|
||||
else if (mHasOPAC)
|
||||
{
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eKonstAlpha);
|
||||
pPass->SetKColorSel(eKonst0_RGB);
|
||||
pPass->SetKAlphaSel(eKonst0_A);
|
||||
}
|
||||
else
|
||||
{
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
}
|
||||
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Lightmap
|
||||
else if (Type == "DIFF")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eKonstRGB, eTextureRGB, eZeroRGB);
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eKonstAlpha);
|
||||
pPass->SetColorOutput(eColor0Reg);
|
||||
pPass->SetAlphaOutput(eColor0Reg);
|
||||
pPass->SetKColorSel(eKonst1_RGB);
|
||||
pPass->SetKAlphaSel(eKonst1_A);
|
||||
pPass->SetRasSel(eRasColor0A0);
|
||||
Lightmap = true;
|
||||
}
|
||||
|
||||
// Bloom Lightmap
|
||||
else if (Type == "BLOL")
|
||||
{
|
||||
// Bloom maps work by writing to framebuffer alpha. Can't do this on alpha-blended mats.
|
||||
pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB);
|
||||
|
||||
if ((AlphaBlended) || (pMat->mOptions & CMaterial::ePunchthrough))
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
else
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha);
|
||||
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Rim Light Map
|
||||
else if (Type == "RIML")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eOneRGB, ePrevRGB, eTextureRGB);
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Emissive Map
|
||||
else if (Type == "INCA")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eTextureRGB, eOneRGB, ePrevRGB);
|
||||
|
||||
if ((pPass->mSettings & CMaterialPass::eEmissiveBloom) && (!AlphaBlended))
|
||||
{
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eTextureAlpha, eKonstAlpha, ePrevAlpha);
|
||||
pPass->SetKAlphaSel(eKonstOneFourth);
|
||||
}
|
||||
else
|
||||
{
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
}
|
||||
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Opacity Map
|
||||
else if (Type == "TRAN")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB);
|
||||
|
||||
if (pPass->mSettings & CMaterialPass::eInvertOpacityMap)
|
||||
pPass->SetAlphaInputs(eKonstAlpha, eZeroAlpha, eTextureAlpha, eZeroAlpha);
|
||||
else
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eKonstAlpha, eTextureAlpha, eZeroAlpha);
|
||||
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Specular Map
|
||||
else if (Type == "RFLV")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, eTextureRGB);
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
pPass->SetColorOutput(eColor2Reg);
|
||||
pPass->SetAlphaOutput(eColor2Reg);
|
||||
}
|
||||
|
||||
// Reflection Map
|
||||
else if (Type == "RFLD")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eColor2RGB, eTextureRGB, ePrevRGB);
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
if (mHas0x400) pPass->SetEnabled(false);
|
||||
}
|
||||
|
||||
// Bloom
|
||||
else if (Type == "BLOI")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, eZeroRGB, eZeroRGB, ePrevRGB);
|
||||
|
||||
// Comes out wrong every time even though this is exactly how the Dolphin shaders say this is done.
|
||||
if (AlphaBlended)
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
else
|
||||
pPass->SetAlphaInputs(eTextureAlpha, eZeroAlpha, eZeroAlpha, ePrevAlpha);
|
||||
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetAlphaOutput(ePrevReg);
|
||||
}
|
||||
|
||||
// Toon? Don't know what it's for but got TEV setup from shader dumps
|
||||
else if (Type == "TOON")
|
||||
{
|
||||
pPass->SetColorInputs(eZeroRGB, ePrevRGB, eTextureRGB, eZeroRGB);
|
||||
pPass->SetAlphaInputs(eZeroAlpha, eZeroAlpha, eZeroAlpha, eTextureAlpha);
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
pPass->SetColorOutput(ePrevReg);
|
||||
}
|
||||
|
||||
else if (Type == "CUST") {}
|
||||
|
||||
else
|
||||
{
|
||||
Log::FileError(mpFile->GetSourceString(), mPassOffsets[iPass], "Unsupported material pass type: " + Type.ToString());
|
||||
pPass->mEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Resource/factory/CMaterialLoader.h
Normal file
38
Resource/factory/CMaterialLoader.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#ifndef CMATERIALLOADER_H
|
||||
#define CMATERIALLOADER_H
|
||||
|
||||
#include <FileIO/FileIO.h>
|
||||
#include "../CMaterialSet.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CMaterialLoader
|
||||
{
|
||||
// Material data
|
||||
CMaterialSet *mpSet;
|
||||
CInputStream *mpFile;
|
||||
EGame mVersion;
|
||||
bool mHasOPAC;
|
||||
bool mHas0x400;
|
||||
|
||||
CColor mCorruptionColors[4];
|
||||
u8 mCorruptionInts[5];
|
||||
u32 mCorruptionFlags;
|
||||
std::vector<u32> mPassOffsets;
|
||||
|
||||
CMaterialLoader();
|
||||
~CMaterialLoader();
|
||||
|
||||
// Load Functions
|
||||
void ReadPrimeMatSet();
|
||||
CMaterial* ReadPrimeMaterial();
|
||||
|
||||
void ReadCorruptionMatSet();
|
||||
CMaterial* ReadCorruptionMaterial();
|
||||
void CreateCorruptionPasses(CMaterial *pMat);
|
||||
|
||||
public:
|
||||
static CMaterialSet* LoadMaterialSet(CInputStream& Mat, EGame Version);
|
||||
};
|
||||
|
||||
#endif // CMATERIALLOADER_H
|
||||
445
Resource/factory/CModelLoader.cpp
Normal file
445
Resource/factory/CModelLoader.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
#include "CModelLoader.h"
|
||||
#include "CMaterialLoader.h"
|
||||
#include <Core/Log.h>
|
||||
|
||||
CModelLoader::CModelLoader()
|
||||
{
|
||||
mFlags = eNoFlags;
|
||||
}
|
||||
|
||||
CModelLoader::~CModelLoader()
|
||||
{
|
||||
}
|
||||
|
||||
void CModelLoader::LoadWorldMeshHeader(CInputStream &Model)
|
||||
{
|
||||
// I don't really have any need for most of this data, so
|
||||
Model.Seek(0x34, SEEK_CUR);
|
||||
mAABox = CAABox(Model);
|
||||
mpBlockMgr->ToNextBlock();
|
||||
}
|
||||
|
||||
void CModelLoader::LoadAttribArrays(CInputStream& Model)
|
||||
{
|
||||
// Positions
|
||||
if (mFlags & eShortPositions) // Shorts (DKCR only)
|
||||
{
|
||||
mPositions.resize(mpBlockMgr->CurrentBlockSize() / 0x6);
|
||||
float Divisor = 8192.f; // Might be incorrect! Needs verification via size comparison.
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mPositions.size(); iVtx++)
|
||||
{
|
||||
float x = Model.ReadShort() / Divisor;
|
||||
float y = Model.ReadShort() / Divisor;
|
||||
float z = Model.ReadShort() / Divisor;
|
||||
mPositions[iVtx] = CVector3f(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
else // Floats
|
||||
{
|
||||
mPositions.resize(mpBlockMgr->CurrentBlockSize() / 0xC);
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mPositions.size(); iVtx++)
|
||||
mPositions[iVtx] = CVector3f(Model);
|
||||
}
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
|
||||
// Normals
|
||||
if (mFlags & eShortNormals) // Shorts
|
||||
{
|
||||
mNormals.resize(mpBlockMgr->CurrentBlockSize() / 0x6);
|
||||
float Divisor = (mVersion < eReturns) ? 32768.f : 16384.f;
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mNormals.size(); iVtx++)
|
||||
{
|
||||
float x = Model.ReadShort() / Divisor;
|
||||
float y = Model.ReadShort() / Divisor;
|
||||
float z = Model.ReadShort() / Divisor;
|
||||
mNormals[iVtx] = CVector3f(x, y, z);
|
||||
}
|
||||
}
|
||||
else // Floats
|
||||
{
|
||||
mNormals.resize(mpBlockMgr->CurrentBlockSize() / 0xC);
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mNormals.size(); iVtx++)
|
||||
mNormals[iVtx] = CVector3f(Model);
|
||||
}
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
|
||||
// Colors
|
||||
mColors.resize(mpBlockMgr->CurrentBlockSize() / 4);
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mColors.size(); iVtx++)
|
||||
mColors[iVtx] = CColor(Model);
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
|
||||
|
||||
// Float UVs
|
||||
mTex0.resize(mpBlockMgr->CurrentBlockSize() / 0x8);
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mTex0.size(); iVtx++)
|
||||
mTex0[iVtx] = CVector2f(Model);
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
|
||||
// Short UVs
|
||||
if (mFlags & eHasTex1)
|
||||
{
|
||||
mTex1.resize(mpBlockMgr->CurrentBlockSize() / 0x4);
|
||||
float Divisor = (mVersion < eReturns) ? 32768.f : 8192.f;
|
||||
|
||||
for (u32 iVtx = 0; iVtx < mTex1.size(); iVtx++)
|
||||
{
|
||||
float x = Model.ReadShort() / Divisor;
|
||||
float y = Model.ReadShort() / Divisor;
|
||||
mTex1[iVtx] = CVector2f(x, y);
|
||||
}
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
}
|
||||
}
|
||||
|
||||
void CModelLoader::LoadSurfaceOffsets(CInputStream& Model)
|
||||
{
|
||||
mSurfaceCount = Model.ReadLong();
|
||||
mSurfaceOffsets.resize(mSurfaceCount);
|
||||
|
||||
for (u32 iSurf = 0; iSurf < mSurfaceCount; iSurf++)
|
||||
mSurfaceOffsets[iSurf] = Model.ReadLong();
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
}
|
||||
|
||||
SModelData* CModelLoader::LoadSurfaces(CInputStream& Model)
|
||||
{
|
||||
// This function is meant to be called at the start of the first surface
|
||||
SModelData *pData = new SModelData;
|
||||
u32 Offset = Model.Tell();
|
||||
|
||||
// Surfaces
|
||||
pData->mSurfaces.resize(mSurfaceCount);
|
||||
|
||||
for (u32 iSurf = 0; iSurf < mSurfaceCount; iSurf++)
|
||||
{
|
||||
SSurface *pSurf = new SSurface;
|
||||
pData->mSurfaces[iSurf] = pSurf;
|
||||
u32 NextSurface = mpBlockMgr->NextOffset();
|
||||
|
||||
// Surface header
|
||||
if (mVersion < eReturns)
|
||||
LoadSurfaceHeaderPrime(Model, pSurf);
|
||||
else
|
||||
LoadSurfaceHeaderDKCR(Model, pSurf);
|
||||
|
||||
bool HasAABB = (pSurf->AABox != CAABox::skInfinite);
|
||||
CMaterial *pMat = mMaterials[0]->materials[pSurf->MaterialID];
|
||||
|
||||
// Primitive table
|
||||
u8 Flag = Model.ReadByte();
|
||||
|
||||
while ((Flag != 0) && ((u32) Model.Tell() < NextSurface))
|
||||
{
|
||||
SSurface::SPrimitive Prim;
|
||||
Prim.Type = EGXPrimitiveType(Flag & 0xF8);
|
||||
u16 VertexCount = Model.ReadShort();
|
||||
|
||||
for (u16 iVtx = 0; iVtx < VertexCount; iVtx++)
|
||||
{
|
||||
CVertex Vtx;
|
||||
EVertexDescription VtxDesc = pMat->VtxDesc();
|
||||
|
||||
for (u32 iMtxAttr = 0; iMtxAttr < 8; iMtxAttr++)
|
||||
if (VtxDesc & (ePosMtx << iMtxAttr)) Model.Seek(0x1, SEEK_CUR);
|
||||
|
||||
// Only thing to do here is check whether each attribute is present, and if so, read it.
|
||||
// A couple attributes have special considerations; normals can be floats or shorts, as can tex0, depending on vtxfmt.
|
||||
// tex0 can also be read from either UV buffer; depends what the material says.
|
||||
|
||||
// Position
|
||||
if (VtxDesc & ePosition)
|
||||
{
|
||||
u16 PosIndex = Model.ReadShort() & 0xFFFF;
|
||||
Vtx.Position = mPositions[PosIndex];
|
||||
Vtx.ArrayPosition = PosIndex;
|
||||
|
||||
if (!HasAABB) pSurf->AABox.ExpandBounds(Vtx.Position);
|
||||
}
|
||||
|
||||
// Normal
|
||||
if (VtxDesc & eNormal)
|
||||
Vtx.Normal = mNormals[Model.ReadShort() & 0xFFFF];
|
||||
|
||||
// Color
|
||||
for (u32 c = 0; c < 2; c++)
|
||||
if (VtxDesc & (eColor0 << (c * 2)))
|
||||
Vtx.Color[c] = mColors[Model.ReadShort() & 0xFFFF];
|
||||
|
||||
// Tex Coords - these are done a bit differently in DKCR than in the Prime series
|
||||
if (mVersion < eReturns)
|
||||
{
|
||||
// Tex0
|
||||
if (VtxDesc & eTex0)
|
||||
{
|
||||
if ((mFlags & eHasTex1) && (pMat->Options() & CMaterial::eShortTexCoord))
|
||||
Vtx.Tex[0] = mTex1[Model.ReadShort() & 0xFFFF];
|
||||
else
|
||||
Vtx.Tex[0] = mTex0[Model.ReadShort() & 0xFFFF];
|
||||
}
|
||||
|
||||
// Tex1-7
|
||||
for (u32 iTex = 1; iTex < 7; iTex++)
|
||||
if (VtxDesc & (eTex0 << (iTex * 2)))
|
||||
Vtx.Tex[iTex] = mTex0[Model.ReadShort() & 0xFFFF];
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
// Tex0-7
|
||||
for (u32 iTex = 0; iTex < 7; iTex++)
|
||||
{
|
||||
if (VtxDesc & (eTex0 << iTex * 2))
|
||||
{
|
||||
if (!mSurfaceUsingTex1)
|
||||
Vtx.Tex[iTex] = mTex0[Model.ReadShort() & 0xFFFF];
|
||||
else
|
||||
Vtx.Tex[iTex] = mTex1[Model.ReadShort() & 0xFFFF];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Prim.Vertices.push_back(Vtx);
|
||||
} // Vertex array end
|
||||
|
||||
// Update vertex/triangle count
|
||||
pSurf->VertexCount += VertexCount;
|
||||
|
||||
switch (Prim.Type)
|
||||
{
|
||||
case eGX_Triangles:
|
||||
pSurf->TriangleCount += VertexCount / 3;
|
||||
break;
|
||||
case eGX_TriangleFan:
|
||||
case eGX_TriangleStrip:
|
||||
pSurf->TriangleCount += VertexCount - 2;
|
||||
break;
|
||||
}
|
||||
|
||||
pSurf->Primitives.push_back(Prim);
|
||||
Flag = Model.ReadByte();
|
||||
} // Primitive table end
|
||||
|
||||
mpBlockMgr->ToNextBlock();
|
||||
} // Submesh table end
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
void CModelLoader::LoadSurfaceHeaderPrime(CInputStream& Model, SSurface *pSurf)
|
||||
{
|
||||
pSurf->CenterPoint = CVector3f(Model);
|
||||
pSurf->MaterialID = Model.ReadLong();
|
||||
|
||||
Model.Seek(0xC, SEEK_CUR);
|
||||
u32 ExtraSize = Model.ReadLong();
|
||||
pSurf->ReflectionDirection = CVector3f(Model);
|
||||
if (mVersion >= eEchoesDemo) Model.Seek(0x4, SEEK_CUR); // Extra values in Echoes. Not sure what they are.
|
||||
bool HasAABox = (ExtraSize >= 0x18); // MREAs have a set of bounding box coordinates here.
|
||||
|
||||
// If this surface has a bounding box, we can just read it here. Otherwise we'll fill it in manually.
|
||||
if (HasAABox)
|
||||
{
|
||||
ExtraSize -= 0x18;
|
||||
pSurf->AABox = CAABox(Model);
|
||||
}
|
||||
else
|
||||
pSurf->AABox = CAABox::skInfinite;
|
||||
|
||||
Model.Seek(ExtraSize, SEEK_CUR);
|
||||
Model.SeekToBoundary(32);
|
||||
}
|
||||
|
||||
void CModelLoader::LoadSurfaceHeaderDKCR(CInputStream& Model, SSurface *pSurf)
|
||||
{
|
||||
pSurf->CenterPoint = CVector3f(Model);
|
||||
Model.Seek(0xE, SEEK_CUR);
|
||||
pSurf->MaterialID = (u32) Model.ReadShort();
|
||||
Model.Seek(0x2, SEEK_CUR);
|
||||
mSurfaceUsingTex1 = (Model.ReadByte() == 1);
|
||||
u32 ExtraSize = Model.ReadByte();
|
||||
|
||||
if (ExtraSize > 0)
|
||||
{
|
||||
ExtraSize -= 0x18;
|
||||
pSurf->AABox = CAABox(Model);
|
||||
}
|
||||
else
|
||||
pSurf->AABox = CAABox::skInfinite;
|
||||
|
||||
Model.Seek(ExtraSize, SEEK_CUR);
|
||||
Model.SeekToBoundary(32);
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CModel* CModelLoader::LoadCMDL(CInputStream& CMDL)
|
||||
{
|
||||
CModelLoader Loader;
|
||||
Log::Write("Loading " + CMDL.GetSourceString());
|
||||
|
||||
// CMDL header - same across the three Primes, but different structure in DKCR
|
||||
u32 Magic = CMDL.ReadLong();
|
||||
|
||||
u32 Version, BlockCount, MatSetCount;
|
||||
CAABox AABox;
|
||||
|
||||
// 0xDEADBABE - Metroid Prime seres
|
||||
if (Magic == 0xDEADBABE)
|
||||
{
|
||||
Version = CMDL.ReadLong();
|
||||
u32 Flags = CMDL.ReadLong();
|
||||
AABox = CAABox(CMDL);
|
||||
BlockCount = CMDL.ReadLong();
|
||||
MatSetCount = CMDL.ReadLong();
|
||||
|
||||
if (Flags & 0x2) Loader.mFlags |= eShortNormals;
|
||||
if (Flags & 0x4) Loader.mFlags |= eHasTex1;
|
||||
}
|
||||
|
||||
// 0x9381000A - Donkey Kong Country Returns
|
||||
else if (Magic == 0x9381000A)
|
||||
{
|
||||
Version = Magic & 0xFFFF;
|
||||
u32 Flags = CMDL.ReadLong();
|
||||
AABox = CAABox(CMDL);
|
||||
BlockCount = CMDL.ReadLong();
|
||||
MatSetCount = CMDL.ReadLong();
|
||||
|
||||
// todo: unknown flags
|
||||
Loader.mFlags = eShortNormals | eHasTex1;
|
||||
if (Flags & 0x10) Loader.mFlags |= eHasVisGroups;
|
||||
if (Flags & 0x20) Loader.mFlags |= eShortPositions;
|
||||
|
||||
// Visibility group data
|
||||
// Skipping for now - should read in eventually
|
||||
if (Flags & 0x10)
|
||||
{
|
||||
CMDL.Seek(0x4, SEEK_CUR);
|
||||
u32 VisGroupCount = CMDL.ReadLong();
|
||||
|
||||
for (u32 iVis = 0; iVis < VisGroupCount; iVis++)
|
||||
{
|
||||
u32 NameLength = CMDL.ReadLong();
|
||||
CMDL.Seek(NameLength, SEEK_CUR);
|
||||
}
|
||||
|
||||
CMDL.Seek(0x14, SEEK_CUR); // no clue what any of this is!
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
Log::FileError(CMDL.GetSourceString(), "Invalid CMDL magic: " + StringUtil::ToHexString(Magic));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// The rest is common to all CMDL versions
|
||||
Loader.mVersion = GetFormatVersion(Version);
|
||||
|
||||
if (Loader.mVersion == eUnknownVersion)
|
||||
{
|
||||
Log::FileError(CMDL.GetSourceString(), "Unsupported CMDL version: " + StringUtil::ToHexString(Magic));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CModel *pModel = new CModel();
|
||||
Loader.mpModel = pModel;
|
||||
Loader.mpBlockMgr = new CBlockMgrIn(BlockCount, &CMDL);
|
||||
CMDL.SeekToBoundary(32);
|
||||
Loader.mpBlockMgr->Init();
|
||||
|
||||
// Materials
|
||||
Loader.mMaterials.resize(MatSetCount);
|
||||
for (u32 iMat = 0; iMat < MatSetCount; iMat++)
|
||||
{
|
||||
Loader.mMaterials[iMat] = CMaterialLoader::LoadMaterialSet(CMDL, Loader.mVersion);
|
||||
|
||||
if (Loader.mVersion < eCorruptionProto)
|
||||
Loader.mpBlockMgr->ToNextBlock();
|
||||
}
|
||||
|
||||
pModel->mMaterialSets = Loader.mMaterials;
|
||||
pModel->mHasOwnMaterials = true;
|
||||
if (Loader.mVersion >= eCorruptionProto) Loader.mpBlockMgr->ToNextBlock();
|
||||
|
||||
// Mesh
|
||||
Loader.LoadAttribArrays(CMDL);
|
||||
Loader.LoadSurfaceOffsets(CMDL);
|
||||
SModelData *pData = Loader.LoadSurfaces(CMDL);
|
||||
|
||||
pModel->SetData(pData);
|
||||
pModel->mAABox = AABox;
|
||||
pModel->mHasOwnSurfaces = true;
|
||||
|
||||
// Cleanup
|
||||
delete pData;
|
||||
delete Loader.mpBlockMgr;
|
||||
return pModel;
|
||||
}
|
||||
|
||||
SModelData* CModelLoader::LoadWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, EGame Version)
|
||||
{
|
||||
CModelLoader Loader;
|
||||
Loader.mpBlockMgr = &BlockMgr;
|
||||
Loader.mVersion = Version;
|
||||
Loader.mFlags = eShortNormals;
|
||||
if (Version != eCorruptionProto) Loader.mFlags |= eHasTex1;
|
||||
Loader.mMaterials.resize(1);
|
||||
Loader.mMaterials[0] = &MatSet;
|
||||
|
||||
Loader.LoadWorldMeshHeader(MREA);
|
||||
Loader.LoadAttribArrays(MREA);
|
||||
Loader.LoadSurfaceOffsets(MREA);
|
||||
SModelData *pData = Loader.LoadSurfaces(MREA);
|
||||
pData->mAABox = Loader.mAABox;
|
||||
|
||||
return pData;
|
||||
}
|
||||
|
||||
SModelData* CModelLoader::LoadCorruptionWorldModel(CInputStream &MREA, CBlockMgrIn &BlockMgr, CMaterialSet &MatSet, u32 HeaderSecNum, u32 GPUSecNum, EGame Version)
|
||||
{
|
||||
CModelLoader Loader;
|
||||
Loader.mpBlockMgr = &BlockMgr;
|
||||
Loader.mVersion = Version;
|
||||
Loader.mFlags = eShortNormals;
|
||||
Loader.mMaterials.resize(1);
|
||||
Loader.mMaterials[0] = &MatSet;
|
||||
if (Version == eReturns) Loader.mFlags |= eHasTex1;
|
||||
|
||||
// Corruption/DKCR MREAs split the mesh header and surface offsets away from the actual geometry data so I need two section numbers to read it
|
||||
BlockMgr.ToBlock(HeaderSecNum);
|
||||
Loader.LoadWorldMeshHeader(MREA);
|
||||
Loader.LoadSurfaceOffsets(MREA);
|
||||
|
||||
BlockMgr.ToBlock(GPUSecNum);
|
||||
Loader.LoadAttribArrays(MREA);
|
||||
SModelData *pData = Loader.LoadSurfaces(MREA);
|
||||
pData->mAABox = Loader.mAABox;
|
||||
return pData;
|
||||
}
|
||||
|
||||
EGame CModelLoader::GetFormatVersion(u32 Version)
|
||||
{
|
||||
switch (Version)
|
||||
{
|
||||
case 0x2: return ePrime;
|
||||
case 0x3: return eEchoesDemo;
|
||||
case 0x4: return eEchoes;
|
||||
case 0x5: return eCorruption;
|
||||
case 0xA: return eReturns;
|
||||
default: return eUnknownVersion;
|
||||
}
|
||||
}
|
||||
63
Resource/factory/CModelLoader.h
Normal file
63
Resource/factory/CModelLoader.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef CMODELLOADER_H
|
||||
#define CMODELLOADER_H
|
||||
|
||||
#include "../model/SModelData.h"
|
||||
#include "../model/CBasicModel.h"
|
||||
#include "../model/CModel.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include "CBlockMgrIn.h"
|
||||
#include <FileIO/FileIO.h>
|
||||
#include <Core/CResCache.h>
|
||||
#include <Common/EnumUtil.h>
|
||||
|
||||
class CModelLoader
|
||||
{
|
||||
public:
|
||||
enum EModelFlags
|
||||
{
|
||||
eNoFlags = 0x0,
|
||||
eShortPositions = 0x1,
|
||||
eShortNormals = 0x2,
|
||||
eHasTex1 = 0x4,
|
||||
eHasVisGroups = 0x8
|
||||
};
|
||||
|
||||
private:
|
||||
CModel *mpModel;
|
||||
std::vector<CMaterialSet*> mMaterials;
|
||||
CBlockMgrIn *mpBlockMgr;
|
||||
CAABox mAABox;
|
||||
EGame mVersion;
|
||||
|
||||
std::vector<CVector3f> mPositions;
|
||||
std::vector<CVector3f> mNormals;
|
||||
std::vector<CColor> mColors;
|
||||
std::vector<CVector2f> mTex0;
|
||||
std::vector<CVector2f> mTex1;
|
||||
bool mSurfaceUsingTex1;
|
||||
|
||||
u32 mSurfaceCount;
|
||||
std::vector<u32> mSurfaceOffsets;
|
||||
|
||||
EModelFlags mFlags;
|
||||
|
||||
CModelLoader();
|
||||
~CModelLoader();
|
||||
void LoadWorldMeshHeader(CInputStream& Model);
|
||||
void LoadAttribArrays(CInputStream& Model);
|
||||
void LoadAttribArraysDKCR(CInputStream& Model);
|
||||
void LoadSurfaceOffsets(CInputStream& Model);
|
||||
SModelData* LoadSurfaces(CInputStream& Model);
|
||||
void LoadSurfaceHeaderPrime(CInputStream& Model, SSurface *pSurf);
|
||||
void LoadSurfaceHeaderDKCR(CInputStream& Model, SSurface *pSurf);
|
||||
|
||||
public:
|
||||
static CModel* LoadCMDL(CInputStream& CMDL);
|
||||
static SModelData* LoadWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, EGame Version);
|
||||
static SModelData* LoadCorruptionWorldModel(CInputStream& MREA, CBlockMgrIn& BlockMgr, CMaterialSet& MatSet, u32 HeaderSecNum, u32 GPUSecNum, EGame Version);
|
||||
static EGame GetFormatVersion(u32 Version);
|
||||
};
|
||||
|
||||
DEFINE_ENUM_FLAGS(CModelLoader::EModelFlags)
|
||||
|
||||
#endif // CMODELLOADER_H
|
||||
150
Resource/factory/CScanLoader.cpp
Normal file
150
Resource/factory/CScanLoader.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "CScanLoader.h"
|
||||
#include <Core/CResCache.h>
|
||||
#include <Core/Log.h>
|
||||
|
||||
CScanLoader::CScanLoader()
|
||||
{
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP1(CInputStream &SCAN)
|
||||
{
|
||||
// Basic support at the moment - don't read animation/scan image data
|
||||
SCAN.Seek(0x4, SEEK_CUR); // Skip FRME ID
|
||||
mpScan->mpStringTable = (CStringTable*) gResCache.GetResource(SCAN.ReadLong(), "STRG");
|
||||
mpScan->mStringToken = CToken(mpScan->mpStringTable);
|
||||
mpScan->mIsSlow = (SCAN.ReadLong() != 0);
|
||||
mpScan->mCategory = (CScan::ELogbookCategory) SCAN.ReadLong();
|
||||
mpScan->mIsImportant = (SCAN.ReadByte() == 1);
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
CScan* CScanLoader::LoadScanMP2(CInputStream& SCAN)
|
||||
{
|
||||
// The SCAN format in MP2 embeds a SNFO object using the same format as SCLY
|
||||
// However since the contents of the file are consistent there's no need to delegate to CScriptLoader
|
||||
SCAN.Seek(0x1, SEEK_CUR);
|
||||
u32 NumInstances = SCAN.ReadLong();
|
||||
|
||||
if (NumInstances != 1) {
|
||||
Log::FileError(SCAN.GetSourceString(), "SCAN has multiple instances");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 ScanInfoStart = SCAN.Tell();
|
||||
|
||||
CFourCC SNFO(SCAN);
|
||||
if (SNFO != "SNFO") {
|
||||
Log::FileError(SCAN.GetSourceString(), ScanInfoStart, "Unrecognized SCAN object type: " + SNFO.ToString());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SCAN.Seek(0x6, SEEK_CUR);
|
||||
u16 NumConnections = SCAN.ReadShort();
|
||||
if (NumConnections > 0) {
|
||||
Log::FileWarning(SCAN.GetSourceString(), ScanInfoStart, "SNFO object in SCAN has connections");
|
||||
SCAN.Seek(NumConnections * 0xC, SEEK_CUR);
|
||||
}
|
||||
|
||||
u32 BasePropID = SCAN.ReadLong();
|
||||
if (BasePropID != 0xFFFFFFFF) {
|
||||
Log::FileError(SCAN.GetSourceString(), SCAN.Tell() - 4, "Invalid base proprty ID: " + StringUtil::ToHexString(BasePropID));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mpScan = new CScan();
|
||||
SCAN.Seek(0x2, SEEK_CUR);
|
||||
u16 NumProperties = SCAN.ReadShort();
|
||||
|
||||
switch (NumProperties)
|
||||
{
|
||||
case 0x14:
|
||||
LoadParamsMP2(SCAN);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::FileError(SCAN.GetSourceString(), SCAN.Tell() - 2, "Invalid SNFO property count: " + StringUtil::ToHexString(NumProperties));
|
||||
delete mpScan;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mpScan;
|
||||
}
|
||||
|
||||
void CScanLoader::LoadParamsMP2(CInputStream &SCAN)
|
||||
{
|
||||
// Function begins after the SNFO property count
|
||||
for (u32 iProp = 0; iProp < 20; iProp++)
|
||||
{
|
||||
u32 PropertyID = SCAN.ReadLong();
|
||||
u16 PropertySize = SCAN.ReadShort();
|
||||
u32 Next = SCAN.Tell() + PropertySize;
|
||||
|
||||
switch (PropertyID)
|
||||
{
|
||||
case 0x2F5B6423:
|
||||
mpScan->mpStringTable = (CStringTable*) gResCache.GetResource(SCAN.ReadLong(), "STRG");
|
||||
mpScan->mStringToken = CToken(mpScan->mpStringTable);
|
||||
break;
|
||||
|
||||
case 0xC308A322:
|
||||
mpScan->mIsSlow = (SCAN.ReadLong() != 0);
|
||||
break;
|
||||
|
||||
case 0x7B714814:
|
||||
mpScan->mIsImportant = (SCAN.ReadByte() != 0);
|
||||
break;
|
||||
|
||||
case 0x53336141:
|
||||
u32 TextureID = SCAN.ReadLong();
|
||||
if (TextureID != 0xFFFFFFFF)
|
||||
Log::FileWarning(SCAN.GetSourceString(), "SCAN with texture found!");
|
||||
break;
|
||||
}
|
||||
|
||||
SCAN.Seek(Next, SEEK_SET);
|
||||
}
|
||||
|
||||
mpScan->mCategory = CScan::eNone;
|
||||
}
|
||||
|
||||
// ************ STATIC/PUBLIC ************
|
||||
CScan* CScanLoader::LoadSCAN(CInputStream &SCAN)
|
||||
{
|
||||
if (!SCAN.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + SCAN.GetSourceString());
|
||||
|
||||
/* Switching to EGame enum here isn't really useful unfortunately
|
||||
* because the MP1 demo can be 1, 2, or 3, while MP1 is 5 and MP2+ is 2
|
||||
* MP1 is the only one that starts with 5 so that is a consistent check for now
|
||||
* Better version checks will be implemented when the other versions are
|
||||
* better-understood. */
|
||||
u32 fileVersion = SCAN.ReadLong();
|
||||
u32 magic = SCAN.ReadLong();
|
||||
|
||||
// Echoes+
|
||||
if (CFourCC(fileVersion) == "SCAN")
|
||||
{
|
||||
// The MP2 load function will check for MP3
|
||||
CScanLoader loader;
|
||||
loader.mVersion = eEchoes;
|
||||
return loader.LoadScanMP2(SCAN);
|
||||
}
|
||||
|
||||
if (magic != 0x0BADBEEF)
|
||||
{
|
||||
Log::FileError(SCAN.GetSourceString(), "Invalid SCAN magic: " + StringUtil::ToHexString(magic));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fileVersion != 5)
|
||||
{
|
||||
Log::FileError(SCAN.GetSourceString(), "Unsupported SCAN version: " + StringUtil::ToHexString(fileVersion));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// MP1 SCAN - read the file!
|
||||
CScanLoader loader;
|
||||
loader.mVersion = ePrime;
|
||||
loader.mpScan = new CScan();
|
||||
return loader.LoadScanMP1(SCAN);
|
||||
}
|
||||
21
Resource/factory/CScanLoader.h
Normal file
21
Resource/factory/CScanLoader.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#ifndef CSCANLOADER_H
|
||||
#define CSCANLOADER_H
|
||||
|
||||
#include "../CScan.h"
|
||||
#include "../EFormatVersion.h"
|
||||
|
||||
class CScanLoader
|
||||
{
|
||||
CScan *mpScan;
|
||||
EGame mVersion;
|
||||
|
||||
CScanLoader();
|
||||
CScan* LoadScanMP1(CInputStream& SCAN);
|
||||
CScan* LoadScanMP2(CInputStream& SCAN);
|
||||
void LoadParamsMP2(CInputStream& SCAN);
|
||||
|
||||
public:
|
||||
static CScan* LoadSCAN(CInputStream& SCAN);
|
||||
};
|
||||
|
||||
#endif // CSCANLOADER_H
|
||||
464
Resource/factory/CScriptLoader.cpp
Normal file
464
Resource/factory/CScriptLoader.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
#include "CScriptLoader.h"
|
||||
#include "../script/CMasterTemplate.h"
|
||||
#include <Core/CResCache.h>
|
||||
#include <Core/Log.h>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
CScriptLoader::CScriptLoader()
|
||||
{
|
||||
mpObj = nullptr;
|
||||
}
|
||||
|
||||
CPropertyStruct* CScriptLoader::LoadStructMP1(CInputStream& SCLY, CStructTemplate *tmp)
|
||||
{
|
||||
u32 StructStart = SCLY.Tell();
|
||||
CPropertyStruct *PropStruct = new CPropertyStruct();
|
||||
PropStruct->tmp = tmp;
|
||||
|
||||
// Verify property count
|
||||
s32 TemplatePropCount = tmp->TemplateCount();
|
||||
if (TemplatePropCount >= 0)
|
||||
{
|
||||
u32 FilePropCount = SCLY.ReadLong();
|
||||
if (TemplatePropCount != FilePropCount)
|
||||
Log::FileWarning(SCLY.GetSourceString(), StructStart, "Struct \"" + tmp->Name() + "\" template prop count doesn't match file");
|
||||
}
|
||||
|
||||
// Parse properties
|
||||
u32 PropCount = tmp->Count();
|
||||
PropStruct->Reserve(PropCount);
|
||||
|
||||
for (u32 p = 0; p < PropCount; p++)
|
||||
{
|
||||
CPropertyBase *prop = nullptr;
|
||||
CPropertyTemplate *proptmp = tmp->PropertyByIndex(p);
|
||||
EPropertyType type = proptmp->Type();
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
||||
case eBoolProperty: {
|
||||
bool v = (SCLY.ReadByte() == 1);
|
||||
prop = new CBoolProperty(v);
|
||||
break;
|
||||
}
|
||||
case eByteProperty: {
|
||||
char v = SCLY.ReadByte();
|
||||
prop = new CByteProperty(v);
|
||||
break;
|
||||
}
|
||||
case eShortProperty: {
|
||||
short v = SCLY.ReadShort();
|
||||
prop = new CShortProperty(v);
|
||||
break;
|
||||
}
|
||||
case eLongProperty: {
|
||||
long v = SCLY.ReadLong();
|
||||
prop = new CLongProperty(v);
|
||||
break;
|
||||
}
|
||||
case eFloatProperty: {
|
||||
float v = SCLY.ReadFloat();
|
||||
prop = new CFloatProperty(v);
|
||||
break;
|
||||
}
|
||||
case eStringProperty: {
|
||||
std::string v = SCLY.ReadString();
|
||||
prop = new CStringProperty(v);
|
||||
break;
|
||||
}
|
||||
case eVector3Property: {
|
||||
CVector3f v(SCLY);
|
||||
prop = new CVector3Property(v);
|
||||
break;
|
||||
}
|
||||
case eColorProperty: {
|
||||
CVector4f color(SCLY);
|
||||
CColor v(color.x, color.y, color.z, color.w);
|
||||
prop = new CColorProperty(v);
|
||||
break;
|
||||
}
|
||||
case eFileProperty: {
|
||||
u32 ResID = SCLY.ReadLong();
|
||||
const CStringList& Extensions = static_cast<CFileTemplate*>(proptmp)->Extensions();
|
||||
|
||||
CResource *pRes = nullptr;
|
||||
|
||||
for (auto it = Extensions.begin(); it != Extensions.end(); it++)
|
||||
{
|
||||
const std::string& ext = *it;
|
||||
if ((ext != "MREA") && (ext != "MLVL")) // Let's avoid recursion please
|
||||
pRes = gResCache.GetResource(ResID, ext);
|
||||
|
||||
if (pRes) break;
|
||||
}
|
||||
|
||||
prop = new CFileProperty(pRes);
|
||||
break;
|
||||
}
|
||||
case eStructProperty: {
|
||||
CStructTemplate *StructTmp = tmp->StructByIndex(p);
|
||||
prop = LoadStructMP1(SCLY, StructTmp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (prop)
|
||||
{
|
||||
prop->tmp = proptmp;
|
||||
PropStruct->Properties.push_back(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return PropStruct;
|
||||
}
|
||||
|
||||
CScriptObject* CScriptLoader::LoadObjectMP1(CInputStream& SCLY)
|
||||
{
|
||||
u32 ObjStart = SCLY.Tell();
|
||||
u8 type = SCLY.ReadByte();
|
||||
u32 size = SCLY.ReadLong();
|
||||
u32 end = SCLY.Tell() + size;
|
||||
|
||||
CScriptTemplate *tmp = mpMaster->TemplateByID((u32) type);
|
||||
if (!tmp)
|
||||
{
|
||||
// No valid template for this object; can't load
|
||||
Log::FileError(SCLY.GetSourceString(), ObjStart, "Invalid object ID encountered - " + StringUtil::ToHexString(type));
|
||||
SCLY.Seek(end, SEEK_SET);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mpObj = new CScriptObject(mpArea, mpLayer, tmp);
|
||||
mpObj->mInstanceID = SCLY.ReadLong();
|
||||
|
||||
// Load connections
|
||||
u32 numConnections = SCLY.ReadLong();
|
||||
mpObj->mOutConnections.reserve(numConnections);
|
||||
|
||||
for (u32 c = 0; c < numConnections; c++)
|
||||
{
|
||||
SLink con;
|
||||
con.State = SCLY.ReadLong();
|
||||
con.Message = SCLY.ReadLong();
|
||||
con.ObjectID = SCLY.ReadLong();
|
||||
mpObj->mOutConnections.push_back(con);
|
||||
}
|
||||
|
||||
// Load object...
|
||||
CStructTemplate *base = tmp->BaseStruct();
|
||||
mpObj->mpProperties = LoadStructMP1(SCLY, base);
|
||||
SetupAttribs();
|
||||
|
||||
// Cleanup and return
|
||||
SCLY.Seek(end, SEEK_SET);
|
||||
return mpObj;
|
||||
}
|
||||
|
||||
CScriptLayer* CScriptLoader::LoadLayerMP1(CInputStream &SCLY)
|
||||
{
|
||||
u32 LayerStart = SCLY.Tell();
|
||||
|
||||
SCLY.Seek(0x1, SEEK_CUR); // One unknown byte at the start of each layer
|
||||
u32 NumObjects = SCLY.ReadLong();
|
||||
|
||||
mpLayer = new CScriptLayer();
|
||||
mpLayer->Reserve(NumObjects);
|
||||
|
||||
for (u32 iObj = 0; iObj < NumObjects; iObj++)
|
||||
{
|
||||
CScriptObject *pObj = LoadObjectMP1(SCLY);
|
||||
if (pObj)
|
||||
mpLayer->AddObject(pObj);
|
||||
}
|
||||
|
||||
// Layer sizes are always a multiple of 32 - skip end padding before returning
|
||||
u32 remaining = 32 - ((SCLY.Tell() - LayerStart) & 0x1F);
|
||||
SCLY.Seek(remaining, SEEK_CUR);
|
||||
return mpLayer;
|
||||
}
|
||||
|
||||
void CScriptLoader::LoadStructMP2(CInputStream& SCLY, CPropertyStruct *pStruct, CStructTemplate *pTemp)
|
||||
{
|
||||
// Verify property count
|
||||
if (!pTemp->IsSingleProperty())
|
||||
{
|
||||
u16 NumProperties = SCLY.ReadShort();
|
||||
if ((pTemp->TemplateCount() >= 0) && (NumProperties != pTemp->TemplateCount()))
|
||||
Log::FileWarning(SCLY.GetSourceString(), SCLY.Tell() - 2, "Struct \"" + pTemp->Name() + "\" template property count doesn't match file");
|
||||
}
|
||||
|
||||
// Parse properties
|
||||
u32 PropCount = pTemp->Count();
|
||||
pStruct->Reserve(PropCount);
|
||||
|
||||
for (u32 p = 0; p < PropCount; p++)
|
||||
{
|
||||
CPropertyBase *pProp;
|
||||
CPropertyTemplate *pPropTemp;
|
||||
u32 PropertyStart = SCLY.Tell();
|
||||
u32 PropertyID = -1;
|
||||
u16 PropertyLength = 0;
|
||||
u32 NextProperty = 0;
|
||||
|
||||
if (pTemp->IsSingleProperty())
|
||||
{
|
||||
pProp = pStruct->PropertyByIndex(p);
|
||||
pPropTemp = pTemp->PropertyByIndex(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
PropertyID = SCLY.ReadLong();
|
||||
PropertyLength = SCLY.ReadShort();
|
||||
NextProperty = SCLY.Tell() + PropertyLength;
|
||||
|
||||
pProp = pStruct->PropertyByID(PropertyID);
|
||||
pPropTemp = pTemp->PropertyByID(PropertyID);
|
||||
}
|
||||
|
||||
if (!pPropTemp)
|
||||
Log::FileError(SCLY.GetSourceString(), PropertyStart, "Can't find template for property " + StringUtil::ToHexString(PropertyID) + " - skipping");
|
||||
|
||||
else
|
||||
{
|
||||
switch (pPropTemp->Type())
|
||||
{
|
||||
|
||||
case eBoolProperty: {
|
||||
CBoolProperty *pBoolCast = static_cast<CBoolProperty*>(pProp);
|
||||
pBoolCast->Set( (SCLY.ReadByte() != 0) );
|
||||
break;
|
||||
}
|
||||
|
||||
case eByteProperty: {
|
||||
CByteProperty *pByteCast = static_cast<CByteProperty*>(pProp);
|
||||
pByteCast->Set(SCLY.ReadByte());
|
||||
break;
|
||||
}
|
||||
|
||||
case eShortProperty: {
|
||||
CShortProperty *pShortCast = static_cast<CShortProperty*>(pProp);
|
||||
pShortCast->Set(SCLY.ReadShort());
|
||||
break;
|
||||
}
|
||||
|
||||
case eLongProperty: {
|
||||
CLongProperty *pLongCast = static_cast<CLongProperty*>(pProp);
|
||||
pLongCast->Set(SCLY.ReadLong());
|
||||
break;
|
||||
}
|
||||
|
||||
case eFloatProperty: {
|
||||
CFloatProperty *pFloatCast = static_cast<CFloatProperty*>(pProp);
|
||||
pFloatCast->Set(SCLY.ReadFloat());
|
||||
break;
|
||||
}
|
||||
|
||||
case eStringProperty: {
|
||||
CStringProperty *pStringCast = static_cast<CStringProperty*>(pProp);
|
||||
pStringCast->Set(SCLY.ReadString());
|
||||
break;
|
||||
}
|
||||
|
||||
case eVector3Property: {
|
||||
CVector3Property *pVector3Cast = static_cast<CVector3Property*>(pProp);
|
||||
pVector3Cast->Set(CVector3f(SCLY));
|
||||
break;
|
||||
}
|
||||
|
||||
case eColorProperty: {
|
||||
CColorProperty *pColorCast = static_cast<CColorProperty*>(pProp);
|
||||
CVector4f Color(SCLY);
|
||||
pColorCast->Set(CColor(Color.x, Color.y, Color.z, Color.w));
|
||||
break;
|
||||
}
|
||||
|
||||
case eFileProperty: {
|
||||
CFileProperty *pFileCast = static_cast<CFileProperty*>(pProp);
|
||||
|
||||
CUniqueID ResID = (mVersion < eCorruptionProto ? SCLY.ReadLong() : SCLY.ReadLongLong());
|
||||
const CStringList& Extensions = static_cast<CFileTemplate*>(pPropTemp)->Extensions();
|
||||
|
||||
CResource *pRes = nullptr;
|
||||
|
||||
// Check for each extension individually until we find a match
|
||||
// This could be done better with a function to fetch the extension given the resource ID
|
||||
// and a "does resource exist" function, but this will do for now
|
||||
bool hasIgnoredExt = false;
|
||||
|
||||
if (ResID.IsValid())
|
||||
{
|
||||
for (auto it = Extensions.begin(); it != Extensions.end(); it++)
|
||||
{
|
||||
const std::string& ext = *it;
|
||||
|
||||
if ((ext != "MREA") && (ext != "MLVL")) {
|
||||
pRes = gResCache.GetResource(ResID, ext);
|
||||
if (pRes) break;
|
||||
}
|
||||
|
||||
else
|
||||
hasIgnoredExt = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Property may have an incorrect extension listed - print error
|
||||
if ((!pRes) && (CUniqueID(ResID).IsValid()) && (!hasIgnoredExt))
|
||||
{
|
||||
std::string ExtList;
|
||||
for (auto it = Extensions.begin(); it != Extensions.end(); it++)
|
||||
{
|
||||
if (it != Extensions.begin()) ExtList += "/";
|
||||
ExtList += *it;
|
||||
}
|
||||
Log::FileWarning(SCLY.GetSourceString(), "Incorrect resource type? " + ExtList + " " + StringUtil::ToHexString(PropertyID));
|
||||
}
|
||||
|
||||
pFileCast->Set(pRes);
|
||||
break;
|
||||
}
|
||||
|
||||
case eUnknownProperty: {
|
||||
CUnknownProperty *pUnknownCast = static_cast<CUnknownProperty*>(pProp);
|
||||
std::vector<u8> buf(PropertyLength);
|
||||
SCLY.ReadBytes(buf.data(), buf.size());
|
||||
pUnknownCast->Set(buf);
|
||||
break;
|
||||
}
|
||||
|
||||
case eStructProperty: {
|
||||
CPropertyStruct *pStructCast = static_cast<CPropertyStruct*>(pProp);
|
||||
LoadStructMP2(SCLY, pStructCast, static_cast<CStructTemplate*>(pPropTemp));
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (NextProperty > 0)
|
||||
SCLY.Seek(NextProperty, SEEK_SET);
|
||||
}
|
||||
}
|
||||
|
||||
CScriptObject* CScriptLoader::LoadObjectMP2(CInputStream& SCLY)
|
||||
{
|
||||
u32 ObjStart = SCLY.Tell();
|
||||
u32 ObjectID = SCLY.ReadLong();
|
||||
u16 ObjectSize = SCLY.ReadShort();
|
||||
u32 ObjEnd = SCLY.Tell() + ObjectSize;
|
||||
|
||||
CScriptTemplate *pTemplate = mpMaster->TemplateByID(ObjectID);
|
||||
|
||||
if (!pTemplate)
|
||||
{
|
||||
Log::FileError(SCLY.GetSourceString(), ObjStart, "Invalid object ID encountered: " + CFourCC(ObjectID).ToString());
|
||||
SCLY.Seek(ObjEnd, SEEK_SET);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
mpObj = CScriptObject::CopyFromTemplate(pTemplate, mpArea, mpLayer);
|
||||
mpObj->mpTemplate = pTemplate;
|
||||
mpObj->mInstanceID = SCLY.ReadLong();
|
||||
|
||||
// Load connections
|
||||
u32 NumConnections = SCLY.ReadShort();
|
||||
mpObj->mOutConnections.reserve(NumConnections);
|
||||
|
||||
for (u32 iCon = 0; iCon < NumConnections; iCon++)
|
||||
{
|
||||
SLink con;
|
||||
con.State = SCLY.ReadLong();
|
||||
con.Message = SCLY.ReadLong();
|
||||
con.ObjectID = SCLY.ReadLong();
|
||||
mpObj->mOutConnections.push_back(con);
|
||||
}
|
||||
|
||||
// Load object
|
||||
CStructTemplate *pBase = pTemplate->BaseStruct();
|
||||
SCLY.Seek(0x6, SEEK_CUR); // Skip base struct ID + size
|
||||
LoadStructMP2(SCLY, mpObj->mpProperties, pBase);
|
||||
SetupAttribs();
|
||||
|
||||
SCLY.Seek(ObjEnd, SEEK_SET);
|
||||
return mpObj;
|
||||
}
|
||||
|
||||
CScriptLayer* CScriptLoader::LoadLayerMP2(CInputStream& SCLY)
|
||||
{
|
||||
CFourCC SCLY_Magic(SCLY);
|
||||
|
||||
if (SCLY_Magic == "SCLY") SCLY.Seek(0x6, SEEK_CUR);
|
||||
else if (SCLY_Magic == "SCGN") SCLY.Seek(0x2, SEEK_CUR);
|
||||
else
|
||||
{
|
||||
Log::FileError(SCLY.GetSourceString(), SCLY.Tell() - 4, "Invalid script layer magic: " + StringUtil::ToHexString((u32) SCLY_Magic.ToLong()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u32 NumObjects = SCLY.ReadLong();
|
||||
|
||||
mpLayer = new CScriptLayer();
|
||||
mpLayer->Reserve(NumObjects);
|
||||
|
||||
for (u32 iObj = 0; iObj < NumObjects; iObj++)
|
||||
{
|
||||
CScriptObject *pObj = LoadObjectMP2(SCLY);
|
||||
if (pObj)
|
||||
mpLayer->AddObject(pObj);
|
||||
}
|
||||
|
||||
if (SCLY_Magic == "SCGN")
|
||||
{
|
||||
mpLayer->SetName("Generated");
|
||||
mpLayer->SetActive(true);
|
||||
}
|
||||
return mpLayer;
|
||||
}
|
||||
|
||||
void CScriptLoader::SetupAttribs()
|
||||
{
|
||||
// Add template attributes
|
||||
u32 numAttribs = mpObj->mpTemplate->AttribCount();
|
||||
for (u32 a = 0; a < numAttribs; a++)
|
||||
{
|
||||
CAttribTemplate *AttribTmp = mpObj->mpTemplate->Attrib(a);
|
||||
CPropertyBase *prop = mpObj->PropertyByName( AttribTmp->Target() );
|
||||
|
||||
// Check for static resource
|
||||
CResource *res = nullptr;
|
||||
std::string ResStr = AttribTmp->Resource();
|
||||
if (!ResStr.empty())
|
||||
res = gResCache.GetResource(ResStr);
|
||||
|
||||
mpObj->mAttribs.emplace_back(CScriptObject::SAttrib(AttribTmp->Type(), res, AttribTmp->Settings(), prop) );
|
||||
mpObj->mAttribFlags |= AttribTmp->Type();
|
||||
}
|
||||
|
||||
// Initial attribute evaluation
|
||||
mpObj->EvaluateInstanceName();
|
||||
mpObj->EvalutateXForm();
|
||||
mpObj->EvaluateTevColor();
|
||||
mpObj->EvaluateDisplayModel();
|
||||
}
|
||||
|
||||
CScriptLayer* CScriptLoader::LoadLayer(CInputStream &SCLY, CGameArea *pArea, EGame version)
|
||||
{
|
||||
if (!SCLY.IsValid()) return nullptr;
|
||||
|
||||
CScriptLoader Loader;
|
||||
Loader.mVersion = version;
|
||||
Loader.mpMaster = CMasterTemplate::GetMasterForGame(version);
|
||||
Loader.mpArea = pArea;
|
||||
|
||||
if (!Loader.mpMaster)
|
||||
{
|
||||
Log::Write("This game doesn't have a master template; couldn't load script layer");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (version <= ePrime)
|
||||
return Loader.LoadLayerMP1(SCLY);
|
||||
else
|
||||
return Loader.LoadLayerMP2(SCLY);
|
||||
}
|
||||
34
Resource/factory/CScriptLoader.h
Normal file
34
Resource/factory/CScriptLoader.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#ifndef CSCRIPTLOADER_H
|
||||
#define CSCRIPTLOADER_H
|
||||
|
||||
#include "../script/CScriptObject.h"
|
||||
#include "../script/CScriptLayer.h"
|
||||
#include "../script/CMasterTemplate.h"
|
||||
#include "../CGameArea.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CScriptLoader
|
||||
{
|
||||
EGame mVersion;
|
||||
CScriptObject *mpObj;
|
||||
CScriptLayer *mpLayer;
|
||||
CGameArea *mpArea;
|
||||
CMasterTemplate *mpMaster;
|
||||
|
||||
CScriptLoader();
|
||||
|
||||
CPropertyStruct* LoadStructMP1(CInputStream& SCLY, CStructTemplate *tmp);
|
||||
CScriptObject* LoadObjectMP1(CInputStream& SCLY);
|
||||
CScriptLayer* LoadLayerMP1(CInputStream& SCLY);
|
||||
|
||||
void LoadStructMP2(CInputStream& SCLY, CPropertyStruct *pStruct, CStructTemplate *pTemp);
|
||||
CScriptObject* LoadObjectMP2(CInputStream& SCLY);
|
||||
CScriptLayer* LoadLayerMP2(CInputStream& SCLY);
|
||||
|
||||
void SetupAttribs();
|
||||
|
||||
public:
|
||||
static CScriptLayer* LoadLayer(CInputStream& SCLY, CGameArea *pArea, EGame version);
|
||||
};
|
||||
|
||||
#endif // CSCRIPTLOADER_H
|
||||
221
Resource/factory/CStringLoader.cpp
Normal file
221
Resource/factory/CStringLoader.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "CStringLoader.h"
|
||||
#include <Core/Log.h>
|
||||
|
||||
CStringLoader::CStringLoader()
|
||||
{
|
||||
}
|
||||
|
||||
void CStringLoader::LoadPrimeDemoSTRG(CInputStream& STRG)
|
||||
{
|
||||
// This function starts at 0x4 in the file - right after the size
|
||||
// This STRG version only supports one language per file
|
||||
mpStringTable->mLangTables.resize(1);
|
||||
CStringTable::SLangTable* Lang = &mpStringTable->mLangTables[1];
|
||||
Lang->Language = "ENGL";
|
||||
u32 TableStart = STRG.Tell();
|
||||
|
||||
// Header
|
||||
u32 NumStrings = STRG.ReadLong();
|
||||
Lang->Strings.resize(NumStrings);
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
|
||||
// String offsets (yeah, that wasn't much of a header)
|
||||
std::vector<u32> StringOffsets(NumStrings);
|
||||
for (u32 iOff = 0; iOff < StringOffsets.size(); iOff++)
|
||||
StringOffsets[iOff] = STRG.ReadLong();
|
||||
|
||||
// Strings
|
||||
for (u32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
{
|
||||
STRG.Seek(TableStart + StringOffsets[iStr], SEEK_SET);
|
||||
Lang->Strings[iStr] = STRG.ReadWString();
|
||||
}
|
||||
}
|
||||
|
||||
void CStringLoader::LoadPrimeSTRG(CInputStream& STRG)
|
||||
{
|
||||
// This function starts at 0x8 in the file, after magic/version
|
||||
// Header
|
||||
u32 NumLanguages = STRG.ReadLong();
|
||||
u32 NumStrings = STRG.ReadLong();
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
|
||||
// Language definitions
|
||||
mpStringTable->mLangTables.resize(NumLanguages);
|
||||
std::vector<u32> LangOffsets(NumLanguages);
|
||||
|
||||
for (u32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
{
|
||||
mpStringTable->mLangTables[iLang].Language = CFourCC(STRG);
|
||||
LangOffsets[iLang] = STRG.ReadLong();
|
||||
if (mVersion == eEchoes) STRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
||||
}
|
||||
|
||||
// String names
|
||||
if (mVersion == eEchoes)
|
||||
LoadNameTable(STRG);
|
||||
|
||||
// Strings
|
||||
u32 StringsStart = STRG.Tell();
|
||||
for (u32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
{
|
||||
STRG.Seek(StringsStart + LangOffsets[iLang], SEEK_SET);
|
||||
if (mVersion == ePrime) STRG.Seek(0x4, SEEK_CUR); // Skipping strings size
|
||||
|
||||
u32 LangStart = STRG.Tell();
|
||||
CStringTable::SLangTable* pLang = &mpStringTable->mLangTables[iLang];
|
||||
pLang->Strings.resize(NumStrings);
|
||||
|
||||
// Offsets
|
||||
std::vector<u32> StringOffsets(NumStrings);
|
||||
for (u32 iOff = 0; iOff < NumStrings; iOff++)
|
||||
StringOffsets[iOff] = STRG.ReadLong();
|
||||
|
||||
// The actual strings
|
||||
for (u32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
{
|
||||
STRG.Seek(LangStart + StringOffsets[iStr], SEEK_SET);
|
||||
pLang->Strings[iStr] = STRG.ReadWString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CStringLoader::LoadCorruptionSTRG(CInputStream& STRG)
|
||||
{
|
||||
// This function starts at 0x8 in the file, after magic/version
|
||||
// Header
|
||||
u32 NumLanguages = STRG.ReadLong();
|
||||
u32 NumStrings = STRG.ReadLong();
|
||||
mpStringTable->mNumStrings = NumStrings;
|
||||
|
||||
// String names
|
||||
LoadNameTable(STRG);
|
||||
|
||||
// Language definitions
|
||||
mpStringTable->mLangTables.resize(NumLanguages);
|
||||
std::vector<std::vector<u32>> LangOffsets(NumLanguages);
|
||||
|
||||
for (u32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
mpStringTable->mLangTables[iLang].Language = CFourCC(STRG);
|
||||
|
||||
for (u32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
{
|
||||
LangOffsets[iLang].resize(NumStrings);
|
||||
|
||||
STRG.Seek(0x4, SEEK_CUR); // Skipping total string size
|
||||
|
||||
for (u32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
LangOffsets[iLang][iStr] = STRG.ReadLong();
|
||||
}
|
||||
|
||||
// Strings
|
||||
u32 StringsStart = STRG.Tell();
|
||||
|
||||
for (u32 iLang = 0; iLang < NumLanguages; iLang++)
|
||||
{
|
||||
CStringTable::SLangTable *pLang = &mpStringTable->mLangTables[iLang];
|
||||
pLang->Strings.resize(NumStrings);
|
||||
|
||||
for (u32 iStr = 0; iStr < NumStrings; iStr++)
|
||||
{
|
||||
STRG.Seek(StringsStart + LangOffsets[iLang][iStr], SEEK_SET);
|
||||
STRG.Seek(0x4, SEEK_CUR); // Skipping string size
|
||||
|
||||
pLang->Strings[iStr] = StringUtil::UTF8to16(STRG.ReadString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CStringLoader::LoadNameTable(CInputStream& STRG)
|
||||
{
|
||||
// Name table header
|
||||
u32 NameCount = STRG.ReadLong();
|
||||
u32 NameTableSize = STRG.ReadLong();
|
||||
u32 NameTableStart = STRG.Tell();
|
||||
u32 NameTableEnd = NameTableStart + NameTableSize;
|
||||
|
||||
// Name definitions
|
||||
struct SNameDef {
|
||||
u32 NameOffset, StringIndex;
|
||||
};
|
||||
std::vector<SNameDef> NameDefs(NameCount);
|
||||
|
||||
for (u32 iName = 0; iName < NameCount; iName++)
|
||||
{
|
||||
NameDefs[iName].NameOffset = STRG.ReadLong() + NameTableStart;
|
||||
NameDefs[iName].StringIndex = STRG.ReadLong();
|
||||
}
|
||||
|
||||
// Name strings
|
||||
mpStringTable->mStringNames.resize(NameCount);
|
||||
for (u32 iName = 0; iName < NameCount; iName++)
|
||||
{
|
||||
SNameDef *pDef = &NameDefs[iName];
|
||||
STRG.Seek(pDef->NameOffset, SEEK_SET);
|
||||
mpStringTable->mStringNames[pDef->StringIndex] = STRG.ReadString();
|
||||
}
|
||||
STRG.Seek(NameTableEnd, SEEK_SET);
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CStringTable* CStringLoader::LoadSTRG(CInputStream& STRG)
|
||||
{
|
||||
// Verify that this is a valid STRG
|
||||
if (!STRG.IsValid()) return nullptr;
|
||||
Log::Write("Loading " + STRG.GetSourceString());
|
||||
|
||||
u32 Magic = STRG.ReadLong();
|
||||
EGame Version = eUnknownVersion;
|
||||
|
||||
if (Magic != 0x87654321)
|
||||
{
|
||||
// Check for MP1 Demo STRG format - no magic/version; the first value is actually the filesize
|
||||
// so the best I can do is verify the first value actually points to the end of the file
|
||||
if (Magic <= (u32) STRG.Size())
|
||||
{
|
||||
STRG.Seek(Magic, SEEK_SET);
|
||||
if ((STRG.EoF()) || (STRG.ReadShort() == 0xFFFF))
|
||||
Version = ePrimeKioskDemo;
|
||||
}
|
||||
|
||||
if (Version != ePrimeKioskDemo)
|
||||
{
|
||||
Log::FileError(STRG.GetSourceString(), "Invalid STRG magic: " + StringUtil::ToHexString(Magic));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
u32 FileVersion = STRG.ReadLong();
|
||||
Version = GetFormatVersion(FileVersion);
|
||||
|
||||
if (FileVersion == eUnknownVersion)
|
||||
{
|
||||
Log::FileError(STRG.GetSourceString(), "Unsupported STRG version: " + StringUtil::ToHexString(FileVersion));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Valid; now we create the loader and call the function that reads the rest of the file
|
||||
CStringLoader Loader;
|
||||
Loader.mpStringTable = new CStringTable();
|
||||
Loader.mVersion = Version;
|
||||
|
||||
if (Version == ePrimeKioskDemo) Loader.LoadPrimeDemoSTRG(STRG);
|
||||
else if (Version < eCorruption) Loader.LoadPrimeSTRG(STRG);
|
||||
else Loader.LoadCorruptionSTRG(STRG);
|
||||
|
||||
return Loader.mpStringTable;
|
||||
}
|
||||
|
||||
EGame CStringLoader::GetFormatVersion(u32 Version)
|
||||
{
|
||||
switch (Version)
|
||||
{
|
||||
case 0x0: return ePrime;
|
||||
case 0x1: return eEchoes;
|
||||
case 0x3: return eCorruption;
|
||||
default: return eUnknownVersion;
|
||||
}
|
||||
}
|
||||
24
Resource/factory/CStringLoader.h
Normal file
24
Resource/factory/CStringLoader.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#ifndef CSTRINGLOADER_H
|
||||
#define CSTRINGLOADER_H
|
||||
|
||||
#include "../CStringTable.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CStringLoader
|
||||
{
|
||||
CStringTable* mpStringTable;
|
||||
EGame mVersion;
|
||||
|
||||
CStringLoader();
|
||||
void LoadPrimeDemoSTRG(CInputStream& STRG);
|
||||
void LoadPrimeSTRG(CInputStream& STRG);
|
||||
void LoadCorruptionSTRG(CInputStream& STRG);
|
||||
void LoadNameTable(CInputStream& STRG);
|
||||
|
||||
public:
|
||||
static CStringTable* LoadSTRG(CInputStream& STRG);
|
||||
static EGame GetFormatVersion(u32 Version);
|
||||
};
|
||||
|
||||
#endif // CSTRINGLOADER_H
|
||||
644
Resource/factory/CTemplateLoader.cpp
Normal file
644
Resource/factory/CTemplateLoader.cpp
Normal file
@@ -0,0 +1,644 @@
|
||||
#include "CTemplateLoader.h"
|
||||
#include "CWorldLoader.h"
|
||||
#include "../script/EAttribType.h"
|
||||
#include <Core/Log.h>
|
||||
|
||||
// ************ PROPERTY ************
|
||||
CPropertyTemplate* CTemplateLoader::LoadPropertyTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName)
|
||||
{
|
||||
const char *pElemName = pElem->Name();
|
||||
|
||||
// Load multi-property struct
|
||||
if (strcmp(pElemName, "struct") == 0)
|
||||
{
|
||||
CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName);
|
||||
|
||||
if (pStruct)
|
||||
pStruct->mIsSingleProperty = false;
|
||||
|
||||
return pStruct;
|
||||
}
|
||||
|
||||
else if (strcmp(pElemName, "property") == 0)
|
||||
{
|
||||
// Get name, type, and ID
|
||||
std::string Name;
|
||||
EPropertyType Type;
|
||||
u32 ID;
|
||||
GetPropertyInfo(pElem, Name, Type, ID);
|
||||
|
||||
// Error check
|
||||
if (Type == eInvalidProperty)
|
||||
{
|
||||
const char *pType = pElem->Attribute("type");
|
||||
|
||||
if (pType)
|
||||
Log::Error("Invalid property type in " + TemplateName + " template: " + pType);
|
||||
else
|
||||
Log::Error("Property " + Name + " in " + TemplateName + " template has no type");
|
||||
}
|
||||
|
||||
// Load single-property struct
|
||||
if (Type == eStructProperty)
|
||||
{
|
||||
CStructTemplate *pStruct = LoadStructTemplate(pElem, TemplateName);
|
||||
pStruct->mIsSingleProperty = true;
|
||||
return pStruct;
|
||||
}
|
||||
|
||||
// Load file property
|
||||
else if (Type == eFileProperty)
|
||||
{
|
||||
// Fetch file extension
|
||||
CFileTemplate *pFile = nullptr;
|
||||
const char *pExt = pElem->Attribute("ext");
|
||||
|
||||
if (pExt)
|
||||
pFile = new CFileTemplate(Name, ID, StringUtil::Tokenize(pExt, ","));
|
||||
|
||||
else
|
||||
{
|
||||
CFileTemplate *pSrc = (CFileTemplate*) mpMaster->GetProperty(ID);
|
||||
|
||||
if (pSrc)
|
||||
pFile = new CFileTemplate(Name, ID, pSrc->Extensions());
|
||||
}
|
||||
|
||||
// Check if extensions are valid
|
||||
if (!pFile)
|
||||
{
|
||||
Log::Error("File property " + Name + " in " + TemplateName + " template has no extension");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
else
|
||||
return pFile;
|
||||
}
|
||||
|
||||
// Load regular property
|
||||
else
|
||||
{
|
||||
CPropertyTemplate *pProperty = new CPropertyTemplate(Type, Name, ID);
|
||||
return pProperty;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CStructTemplate* CTemplateLoader::LoadStructTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName)
|
||||
{
|
||||
CStructTemplate *pStruct = new CStructTemplate();
|
||||
|
||||
// Get name, type, and ID
|
||||
GetPropertyInfo(pElem, pStruct->mPropName, pStruct->mPropType, pStruct->mPropID);
|
||||
const char *pTemp = pElem->Attribute("template");
|
||||
if (!pTemp) pTemp = pElem->Attribute("target");
|
||||
|
||||
// Get source template from the master property list, if it exists
|
||||
CStructTemplate *pSrc = (CStructTemplate*) mpMaster->GetProperty(pStruct->mPropID);
|
||||
|
||||
// "IsSingleProperty" means, does the struct contain multiple properties, each with separate IDs
|
||||
// or does the entire struct as a whole count as just one property?
|
||||
if (pSrc)
|
||||
pStruct->mIsSingleProperty = pSrc->IsSingleProperty();
|
||||
else
|
||||
pStruct->mIsSingleProperty = (strcmp(pElem->Name(), "property") == 0);
|
||||
|
||||
// Read struct children. Priority is [Embedded -> Template -> Master].
|
||||
// Embedded
|
||||
if (!pElem->NoChildren())
|
||||
{
|
||||
// Get count
|
||||
const char *pCount = pElem->Attribute("count");
|
||||
|
||||
if (pCount)
|
||||
pStruct->mPropertyCount = std::stoul(pCount);
|
||||
|
||||
// Parse sub-elements
|
||||
tinyxml2::XMLElement *pChild = pElem->FirstChildElement();
|
||||
|
||||
while (pChild)
|
||||
{
|
||||
CPropertyTemplate *pProp = LoadPropertyTemplate(pChild, TemplateName);
|
||||
|
||||
if (pProp)
|
||||
pStruct->mProperties.push_back(pProp);
|
||||
|
||||
pChild = pChild->NextSiblingElement();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Template
|
||||
else if (pTemp)
|
||||
{
|
||||
// Get handle for XML
|
||||
std::string TempPath = mMasterDir + pTemp;
|
||||
|
||||
tinyxml2::XMLDocument TempXML;
|
||||
TempXML.LoadFile(TempPath.c_str());
|
||||
|
||||
if (TempXML.Error())
|
||||
Log::Error("Couldn't open struct template: " + TempPath);
|
||||
|
||||
else
|
||||
{
|
||||
tinyxml2::XMLElement *pVersionElem = TempXML.FirstChildElement()->FirstChildElement("version");
|
||||
tinyxml2::XMLElement *pPropertiesElem = TempXML.FirstChildElement()->FirstChildElement("properties");
|
||||
|
||||
if (!pVersionElem) Log::Error("Struct template has no version element: " + TempPath);
|
||||
if (!pPropertiesElem) Log::Error("Struct template has no properties element: " + TempPath);
|
||||
|
||||
if (pVersionElem && pPropertiesElem)
|
||||
{
|
||||
// Get version number
|
||||
u32 VersionNumber = std::stoul(pVersionElem->GetText());
|
||||
|
||||
// Get property count
|
||||
const char *pCount = pPropertiesElem->Attribute("count");
|
||||
|
||||
if (pCount)
|
||||
pStruct->mPropertyCount = std::stoul(pCount);
|
||||
|
||||
// Parse properties
|
||||
tinyxml2::XMLElement *pPropElem = pPropertiesElem->FirstChildElement();
|
||||
|
||||
while (pPropElem)
|
||||
{
|
||||
if (!pPropElem) break;
|
||||
|
||||
CPropertyTemplate *pProp = LoadPropertyTemplate(pPropElem, TemplateName);
|
||||
|
||||
if (pProp)
|
||||
pStruct->mProperties.push_back(pProp);
|
||||
|
||||
pPropElem = pPropElem->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Master
|
||||
else if (pSrc)
|
||||
{
|
||||
pStruct->mPropertyCount = pSrc->TemplateCount();
|
||||
|
||||
for (u32 p = 0; p < pSrc->Count(); p++)
|
||||
pStruct->mProperties.push_back(pSrc->PropertyByIndex(p));
|
||||
}
|
||||
|
||||
// If it's none of these things, then it probably has no children because it's a property list entry
|
||||
return pStruct;
|
||||
}
|
||||
|
||||
void CTemplateLoader::GetPropertyInfo(tinyxml2::XMLElement *pElem, std::string& Name, EPropertyType& Type, u32& ID)
|
||||
{
|
||||
const char *pNameStr = pElem->Attribute("name");
|
||||
const char *pTypeStr = pElem->Attribute("type");
|
||||
const char *pIDStr = pElem->Attribute("ID");
|
||||
bool IsBaseStruct = (strcmp(pElem->Name(), "properties") == 0);
|
||||
|
||||
// Fetch source template, if available
|
||||
CPropertyTemplate *pSrcTmp;
|
||||
|
||||
if (pIDStr)
|
||||
{
|
||||
ID = std::stoul(pIDStr, 0, 16);
|
||||
pSrcTmp = mpMaster->GetProperty(ID);
|
||||
}
|
||||
else
|
||||
{
|
||||
ID = 0xFFFFFFFF;
|
||||
pSrcTmp = nullptr;
|
||||
}
|
||||
|
||||
// Get name
|
||||
if (pNameStr)
|
||||
Name = pNameStr;
|
||||
else if (pSrcTmp)
|
||||
Name = pSrcTmp->Name();
|
||||
else if (IsBaseStruct)
|
||||
Name = "Base";
|
||||
else
|
||||
Name = "";
|
||||
|
||||
// Get type
|
||||
if (strcmp(pElem->Name(), "struct") == 0)
|
||||
Type = eStructProperty;
|
||||
else if (IsBaseStruct)
|
||||
Type = eStructProperty;
|
||||
else if (pTypeStr)
|
||||
Type = PropStringToPropEnum(pTypeStr);
|
||||
else if (pSrcTmp)
|
||||
Type = pSrcTmp->Type();
|
||||
else
|
||||
Type = eInvalidProperty;
|
||||
}
|
||||
|
||||
// ************ SCRIPT OBJECT ************
|
||||
CScriptTemplate* CTemplateLoader::LoadScriptTemplate(tinyxml2::XMLDocument *pDoc, const std::string& TemplateName, u32 ObjectID)
|
||||
{
|
||||
tinyxml2::XMLElement *pBaseElement = pDoc->FirstChildElement();
|
||||
|
||||
CScriptTemplate *pScript = new CScriptTemplate(mpMaster);
|
||||
pScript->mObjectID = ObjectID;
|
||||
pScript->mTemplateName = pBaseElement->Name();
|
||||
|
||||
// Properties?
|
||||
tinyxml2::XMLElement *pProperties = pBaseElement->FirstChildElement("properties");
|
||||
if (pProperties)
|
||||
{
|
||||
pScript->mpBaseStruct = LoadStructTemplate(pBaseElement->FirstChildElement("properties"), TemplateName);
|
||||
pScript->mpBaseStruct->SetName(pScript->mTemplateName);
|
||||
}
|
||||
|
||||
// Attribs?
|
||||
tinyxml2::XMLElement *pAttributes = pBaseElement->FirstChildElement("attributes");
|
||||
if (pAttributes) LoadScriptAttribs(pAttributes, pScript);
|
||||
|
||||
return pScript;
|
||||
}
|
||||
|
||||
void CTemplateLoader::LoadScriptAttribs(tinyxml2::XMLElement *pElem, CScriptTemplate *pScript)
|
||||
{
|
||||
// Parsing attribs
|
||||
tinyxml2::XMLElement *pAttrib = pElem->FirstChildElement("attrib");
|
||||
while (pAttrib)
|
||||
{
|
||||
CAttribTemplate Attrib;
|
||||
Attrib.ExtraSettings = -1;
|
||||
|
||||
const char *pType = pAttrib->Attribute("type");
|
||||
if (!pType)
|
||||
Log::Error("An attrib in " + pScript->TemplateName() + " template has no type set");
|
||||
|
||||
else
|
||||
{
|
||||
// Initialize attrib template values
|
||||
Attrib.AttribType = AttribStringToAttribEnum(pType);
|
||||
|
||||
if (Attrib.AttribType == eInvalidAttrib)
|
||||
Log::Error("An attrib in " + pScript->TemplateName() + " template has an invalid type: " + pType);
|
||||
|
||||
else
|
||||
{
|
||||
bool NoError = ParseAttribExtra(pAttrib, Attrib, pScript->TemplateName());
|
||||
|
||||
if (NoError)
|
||||
{
|
||||
Attrib.AttribTarget = pAttrib->Attribute("target");
|
||||
CPropertyTemplate *pTargetProp = nullptr;
|
||||
|
||||
if (Attrib.ResFile.empty())
|
||||
{
|
||||
pTargetProp = pScript->mpBaseStruct->PropertyByName(Attrib.AttribTarget); // Ensure target is valid if it points to a property
|
||||
|
||||
if (!pTargetProp)
|
||||
Log::Error("An attrib in " + pScript->TemplateName() + " template of type " + pType + " has an invalid target: " + Attrib.AttribTarget);
|
||||
}
|
||||
|
||||
if ((pTargetProp) || (!Attrib.ResFile.empty()))
|
||||
pScript->mAttribs.push_back(Attrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pAttrib = pAttrib->NextSiblingElement("attrib");
|
||||
}
|
||||
}
|
||||
|
||||
bool CTemplateLoader::ParseAttribExtra(tinyxml2::XMLElement *pElem, CAttribTemplate& Attrib, const std::string& TemplateName)
|
||||
{
|
||||
// This function is for parsing extra tags that some attribs have, such as "source" for models or "forcenode" for animsets
|
||||
|
||||
// AnimSet
|
||||
if (Attrib.Type() == eAnimSetAttrib)
|
||||
{
|
||||
// Check res source
|
||||
const char *pSource = pElem->Attribute("source");
|
||||
|
||||
if ((pSource) && (strcmp(pSource, "file") == 0))
|
||||
{
|
||||
const char *pFileName = pElem->Attribute("target");
|
||||
if (pFileName)
|
||||
Attrib.ResFile = std::string("../resources/") + pFileName;
|
||||
else
|
||||
{
|
||||
Log::Error("An attrib in " + TemplateName + " template of type animset has an invalid target: \"" + pFileName + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check forcenode
|
||||
const char *pForceNode = pElem->Attribute("forcenode");
|
||||
if (pForceNode)
|
||||
{
|
||||
if (!StringUtil::IsHexString(pForceNode))
|
||||
{
|
||||
Log::Error("An animset attrib in " + TemplateName + " has an invalid \"forcenode\" setting: \"" + pForceNode + "\"");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
Attrib.ExtraSettings = std::stoul(pForceNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Model
|
||||
if (Attrib.Type() == eModelAttrib)
|
||||
{
|
||||
// Check res source
|
||||
const char *pSource = pElem->Attribute("source");
|
||||
if ((pSource) && (strcmp(pSource, "file") == 0))
|
||||
{
|
||||
const char *pFileName = pElem->Attribute("target");
|
||||
if (pFileName)
|
||||
Attrib.ResFile = std::string("../resources/") + pFileName;
|
||||
else
|
||||
{
|
||||
Log::Error("An attrib in " + TemplateName + " template of type model has an invalid target: \"" + pFileName + "\"");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volume
|
||||
if (Attrib.Type() == eVolumeAttrib)
|
||||
{
|
||||
const char *pShape = pElem->Attribute("shape");
|
||||
|
||||
if (pShape)
|
||||
{
|
||||
if (strcmp(pShape, "Box") == 0)
|
||||
Attrib.ExtraSettings = 0;
|
||||
else if (strcmp(pShape, "OrientedBox") == 0)
|
||||
Attrib.ExtraSettings = 1;
|
||||
else if (strcmp(pShape, "Sphere") == 0)
|
||||
Attrib.ExtraSettings = 2;
|
||||
else
|
||||
{
|
||||
Log::Error("Volume attrib in " + TemplateName + " template has an invalid shape: " + pShape);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log::Error("Volume attrib in " + TemplateName + " template has no shape attribute");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ************ MASTER ************
|
||||
void CTemplateLoader::LoadMasterTemplate(tinyxml2::XMLDocument *pDoc)
|
||||
{
|
||||
tinyxml2::XMLNode *pNode = pDoc->FirstChild()->NextSibling()->FirstChild();
|
||||
FILE *f = fopen("a.txt", "w");
|
||||
|
||||
while (pNode)
|
||||
{
|
||||
tinyxml2::XMLElement *pElem = pNode->ToElement();
|
||||
|
||||
// Version
|
||||
if (strcmp(pElem->Name(), "version") == 0)
|
||||
{
|
||||
u32 Version = std::stoul(pElem->GetText());
|
||||
mpMaster->mVersion = Version;
|
||||
}
|
||||
|
||||
// Properties
|
||||
else if (strcmp(pElem->Name(), "properties") == 0)
|
||||
{
|
||||
std::string PropListPath = mMasterDir + pElem->GetText();
|
||||
|
||||
tinyxml2::XMLDocument PropListXML;
|
||||
PropListXML.LoadFile(PropListPath.c_str());
|
||||
|
||||
if (PropListXML.Error())
|
||||
Log::Error("Couldn't open property list: " + PropListPath);
|
||||
|
||||
else
|
||||
LoadPropertyList(&PropListXML, PropListPath);
|
||||
}
|
||||
|
||||
// Objects
|
||||
else if (strcmp(pElem->Name(), "objects") == 0)
|
||||
{
|
||||
// Iterate categories
|
||||
tinyxml2::XMLElement *pCat = pElem->FirstChildElement("category");
|
||||
|
||||
while (pCat)
|
||||
{
|
||||
CTemplateCategory Cat(pCat->Attribute("name"));
|
||||
tinyxml2::XMLElement *pObj = pCat->FirstChildElement("object");
|
||||
|
||||
while (pObj)
|
||||
{
|
||||
// ID can either be a hex number or an ASCII fourCC
|
||||
std::string StrID = pObj->Attribute("ID");
|
||||
u32 ID;
|
||||
|
||||
if (StringUtil::IsHexString(StrID, true))
|
||||
ID = std::stoul(StrID, 0, 16);
|
||||
else
|
||||
ID = CFourCC(StrID).ToLong();
|
||||
|
||||
// Load up the object
|
||||
std::string TemplateName = pObj->Attribute("template");
|
||||
std::string TemplatePath = mMasterDir + TemplateName;
|
||||
|
||||
tinyxml2::XMLDocument ObjectXML;
|
||||
ObjectXML.LoadFile(TemplatePath.c_str());
|
||||
|
||||
if (ObjectXML.Error())
|
||||
Log::Error("Couldn't open script template: " + TemplatePath);
|
||||
|
||||
else
|
||||
{
|
||||
CScriptTemplate *pTemp = LoadScriptTemplate(&ObjectXML, TemplateName, ID);
|
||||
|
||||
if (pTemp)
|
||||
{
|
||||
mpMaster->mTemplates[ID] = pTemp;
|
||||
Cat.AddTemplate(pTemp);
|
||||
|
||||
std::string ID = "| <code>" + CFourCC(pTemp->ObjectID()).ToString() + "</code>\n";
|
||||
std::string Name = "| [[" + pTemp->TemplateName() + " (Metroid Prime 2)|" + pTemp->TemplateName() + "]]\n";
|
||||
fprintf(f, ID.c_str());
|
||||
fprintf(f, Name.c_str());
|
||||
fprintf(f, "| \n");
|
||||
fprintf(f, "|-\n");
|
||||
}
|
||||
}
|
||||
|
||||
pObj = pObj->NextSiblingElement("object");
|
||||
}
|
||||
|
||||
Cat.Sort();
|
||||
mpMaster->mCategories.push_back(Cat);
|
||||
pCat = pCat->NextSiblingElement("category");
|
||||
}
|
||||
}
|
||||
|
||||
// States
|
||||
else if (strcmp(pElem->Name(), "states") == 0)
|
||||
{
|
||||
tinyxml2::XMLElement *pState = pElem->FirstChildElement("state");
|
||||
|
||||
while (pState)
|
||||
{
|
||||
std::string StrID = pState->Attribute("ID");
|
||||
u32 StateID;
|
||||
|
||||
if (StringUtil::IsHexString(StrID, true))
|
||||
StateID = std::stoul(StrID, 0, 16);
|
||||
else
|
||||
StateID = CFourCC(StrID).ToLong();
|
||||
|
||||
std::string StateName = pState->Attribute("name");
|
||||
mpMaster->mStates[StateID] = StateName;
|
||||
pState = pState->NextSiblingElement("state");
|
||||
|
||||
std::string ID = "| <code>" + StrID + "</code>\n";
|
||||
std::string Name = "| " + StateName + "\n";
|
||||
fprintf(f, ID.c_str());
|
||||
fprintf(f, Name.c_str());
|
||||
fprintf(f, "|-\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Messages
|
||||
else if (strcmp(pElem->Name(), "messages") == 0)
|
||||
{
|
||||
tinyxml2::XMLElement *pMessage = pElem->FirstChildElement("message");
|
||||
|
||||
while (pMessage)
|
||||
{
|
||||
std::string StrID = pMessage->Attribute("ID");
|
||||
u32 MessageID;
|
||||
|
||||
if (StringUtil::IsHexString(StrID, true))
|
||||
MessageID = std::stoul(StrID, 0, 16);
|
||||
else
|
||||
MessageID = CFourCC(StrID).ToLong();
|
||||
|
||||
std::string MessageName = pMessage->Attribute("name");
|
||||
mpMaster->mMessages[MessageID] = MessageName;
|
||||
pMessage = pMessage->NextSiblingElement("message");
|
||||
|
||||
std::string ID = "| <code>" + StrID + "</code>\n";
|
||||
std::string Name = "| " + MessageName + "\n";
|
||||
fprintf(f, ID.c_str());
|
||||
fprintf(f, Name.c_str());
|
||||
fprintf(f, "|-\n");
|
||||
}
|
||||
}
|
||||
|
||||
pNode = pNode->NextSibling();
|
||||
}
|
||||
}
|
||||
|
||||
void CTemplateLoader::LoadPropertyList(tinyxml2::XMLDocument *pDoc, const std::string& ListName)
|
||||
{
|
||||
tinyxml2::XMLElement *pElem = pDoc->FirstChildElement()->FirstChildElement();
|
||||
|
||||
while (pElem)
|
||||
{
|
||||
CPropertyTemplate *pProp = LoadPropertyTemplate(pElem, ListName);
|
||||
|
||||
if (pProp)
|
||||
mpMaster->mPropertyList[pProp->PropertyID()] = pProp;
|
||||
|
||||
pElem = pElem->NextSiblingElement();
|
||||
}
|
||||
}
|
||||
|
||||
CMasterTemplate* CTemplateLoader::LoadGame(tinyxml2::XMLNode *pNode)
|
||||
{
|
||||
tinyxml2::XMLElement *pGameElem = pNode->FirstChildElement();
|
||||
mpMaster = new CMasterTemplate();
|
||||
|
||||
// Parse game parameters
|
||||
while (pGameElem)
|
||||
{
|
||||
if (strcmp(pGameElem->Name(), "name") == 0)
|
||||
mpMaster->mGameName = pGameElem->GetText();
|
||||
|
||||
else if (strcmp(pGameElem->Name(), "mlvl") == 0)
|
||||
{
|
||||
u32 VersionNum = std::stoul(pGameElem->GetText(), 0, 16);
|
||||
mpMaster->mGame = CWorldLoader::GetFormatVersion(VersionNum);
|
||||
}
|
||||
|
||||
else if (strcmp(pGameElem->Name(), "master") == 0)
|
||||
{
|
||||
std::string MasterPath = mTemplatesDir + pGameElem->GetText();
|
||||
mMasterDir = StringUtil::GetFileDirectory(MasterPath);
|
||||
|
||||
tinyxml2::XMLDocument MasterXML;
|
||||
MasterXML.LoadFile(MasterPath.c_str());
|
||||
|
||||
if (MasterXML.Error())
|
||||
Log::Error("Couldn't open master template at " + MasterPath + " - error " + std::to_string(MasterXML.ErrorID()));
|
||||
else
|
||||
LoadMasterTemplate(&MasterXML);
|
||||
}
|
||||
pGameElem = pGameElem->NextSiblingElement();
|
||||
}
|
||||
|
||||
mpMaster->mFullyLoaded = true;
|
||||
return mpMaster;
|
||||
}
|
||||
|
||||
// ************ PUBLIC ************
|
||||
void CTemplateLoader::LoadGameList()
|
||||
{
|
||||
static const std::string TemplatesDir = "../templates/";
|
||||
static const std::string GameListPath = TemplatesDir + "GameList.xml";
|
||||
Log::Write("Loading game list");
|
||||
|
||||
// Load Game List XML
|
||||
tinyxml2::XMLDocument GameListXML;
|
||||
GameListXML.LoadFile(GameListPath.c_str());
|
||||
|
||||
if (GameListXML.Error())
|
||||
{
|
||||
Log::Error("Couldn't open game list at " + GameListPath + " - error " + std::to_string(GameListXML.ErrorID()));
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse
|
||||
tinyxml2::XMLNode *pNode = GameListXML.FirstChild()->NextSibling()->FirstChild();
|
||||
|
||||
while (pNode)
|
||||
{
|
||||
tinyxml2::XMLElement *pElement = pNode->ToElement();
|
||||
|
||||
// Game List version number
|
||||
if (strcmp(pElement->Name(), "version") == 0)
|
||||
{
|
||||
u32 VersionNum = std::stoul(pElement->GetText());
|
||||
CMasterTemplate::smGameListVersion = VersionNum;
|
||||
}
|
||||
|
||||
// Games
|
||||
else if (strcmp(pElement->Name(), "game") == 0)
|
||||
{
|
||||
CTemplateLoader Loader(TemplatesDir);
|
||||
CMasterTemplate *pMaster = Loader.LoadGame(pNode);
|
||||
|
||||
if (!pMaster->IsLoadedSuccessfully())
|
||||
{
|
||||
Log::Error("Master template for " + pMaster->mGameName + " couldn't be loaded");
|
||||
delete pMaster;
|
||||
}
|
||||
|
||||
else
|
||||
CMasterTemplate::smMasterMap[pMaster->mGame] = pMaster;
|
||||
}
|
||||
|
||||
pNode = pNode->NextSibling();
|
||||
}
|
||||
}
|
||||
36
Resource/factory/CTemplateLoader.h
Normal file
36
Resource/factory/CTemplateLoader.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef CTEMPLATELOADER_H
|
||||
#define CTEMPLATELOADER_H
|
||||
|
||||
#include "../script/CMasterTemplate.h"
|
||||
#include "../script/CScriptTemplate.h"
|
||||
#include <tinyxml2.h>
|
||||
|
||||
class CTemplateLoader
|
||||
{
|
||||
CMasterTemplate *mpMaster;
|
||||
std::string mTemplatesDir;
|
||||
std::string mMasterDir;
|
||||
|
||||
// Constructor
|
||||
CTemplateLoader(const std::string& TemplatesDir) : mTemplatesDir(TemplatesDir) {}
|
||||
|
||||
// Load Property
|
||||
CPropertyTemplate* LoadPropertyTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName);
|
||||
CStructTemplate* LoadStructTemplate(tinyxml2::XMLElement *pElem, const std::string& TemplateName);
|
||||
void GetPropertyInfo(tinyxml2::XMLElement *pElem, std::string& Name, EPropertyType& Type, u32& ID);
|
||||
|
||||
// Load Script Object
|
||||
CScriptTemplate* LoadScriptTemplate(tinyxml2::XMLDocument *pDoc, const std::string& TemplateName, u32 ObjectID);
|
||||
void LoadScriptAttribs(tinyxml2::XMLElement *pElem, CScriptTemplate *pScript);
|
||||
bool ParseAttribExtra(tinyxml2::XMLElement *pElem, CAttribTemplate& Attrib, const std::string& TemplateName);
|
||||
|
||||
// Load Master
|
||||
void LoadMasterTemplate(tinyxml2::XMLDocument *pDoc);
|
||||
void LoadPropertyList(tinyxml2::XMLDocument *pDoc, const std::string& ListName);
|
||||
CMasterTemplate* LoadGame(tinyxml2::XMLNode *pNode);
|
||||
|
||||
public:
|
||||
static void LoadGameList();
|
||||
};
|
||||
|
||||
#endif // CTEMPLATELOADER_H
|
||||
875
Resource/factory/CTextureDecoder.cpp
Normal file
875
Resource/factory/CTextureDecoder.cpp
Normal file
@@ -0,0 +1,875 @@
|
||||
#include <Common/CColor.h>
|
||||
#include <Core/Log.h>
|
||||
#include "CTextureDecoder.h"
|
||||
|
||||
// A cleanup is warranted at some point. Trying to support both partial + full decode ended up really messy.
|
||||
|
||||
// Number of pixels * this = number of bytes
|
||||
static const float gskPixelsToBytes[] = {
|
||||
1.f, 1.f, 2.f, 2.f, 4.f, 4.f, 0.f, 2.f, 4.f, 4.f, 0.5f
|
||||
};
|
||||
|
||||
// Bits per pixel for each GX texture format
|
||||
static const u32 gskSourceBpp[] = {
|
||||
4, 8, 8, 16, 4, 8, 16, 16, 16, 32, 4
|
||||
};
|
||||
|
||||
// Bits per pixel for each GX texture format when decoded
|
||||
static const u32 gskOutputBpp[] = {
|
||||
16, 16, 16, 16, 16, 16, 16, 16, 32, 32, 4
|
||||
};
|
||||
|
||||
// Size of one pixel in output data in bytes
|
||||
static const u32 gskOutputPixelStride[] = {
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 4, 4, 8
|
||||
};
|
||||
|
||||
// Block width for each GX texture format
|
||||
static const u32 gskBlockWidth[] = {
|
||||
8, 8, 8, 4, 8, 8, 4, 4, 4, 4, 2
|
||||
};
|
||||
|
||||
// Block height for each GX texture format
|
||||
static const u32 gskBlockHeight[] = {
|
||||
8, 4, 4, 4, 8, 4, 4, 4, 4, 4, 2
|
||||
};
|
||||
|
||||
CTextureDecoder::CTextureDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
CTextureDecoder::~CTextureDecoder()
|
||||
{
|
||||
}
|
||||
|
||||
CTexture* CTextureDecoder::CreateTexture()
|
||||
{
|
||||
CTexture *pTex = new CTexture;
|
||||
pTex->mSourceTexelFormat = mTexelFormat;
|
||||
pTex->mWidth = mWidth;
|
||||
pTex->mHeight = mHeight;
|
||||
pTex->mNumMipMaps = mNumMipMaps;
|
||||
pTex->mLinearSize = (u32) (mWidth * mHeight * gskPixelsToBytes[mTexelFormat]);
|
||||
pTex->mImgDataBuffer = mpDataBuffer;
|
||||
pTex->mImgDataSize = mDataBufferSize;
|
||||
pTex->mBufferExists = true;
|
||||
|
||||
switch (mTexelFormat) {
|
||||
case eGX_I4:
|
||||
case eGX_I8:
|
||||
case eGX_IA4:
|
||||
case eGX_IA8:
|
||||
pTex->mTexelFormat = eLuminanceAlpha;
|
||||
break;
|
||||
case eGX_RGB565:
|
||||
pTex->mTexelFormat = eRGB565;
|
||||
break;
|
||||
case eGX_C4:
|
||||
case eGX_C8:
|
||||
if (mPaletteFormat == ePalette_IA8) pTex->mTexelFormat = eLuminanceAlpha;
|
||||
if (mPaletteFormat == ePalette_RGB565) pTex->mTexelFormat = eRGB565;
|
||||
if (mPaletteFormat == ePalette_RGB5A3) pTex->mTexelFormat = eRGBA8;
|
||||
break;
|
||||
case eGX_RGB5A3:
|
||||
case eGX_RGBA8:
|
||||
pTex->mTexelFormat = eRGBA8;
|
||||
break;
|
||||
case eGX_CMPR:
|
||||
pTex->mTexelFormat = eDXT1;
|
||||
break;
|
||||
case eDXT1:
|
||||
pTex->mTexelFormat = eDXT1;
|
||||
pTex->mLinearSize = mWidth * mHeight / 2;
|
||||
break;
|
||||
default:
|
||||
pTex->mTexelFormat = mTexelFormat;
|
||||
break;
|
||||
}
|
||||
|
||||
return pTex;
|
||||
}
|
||||
|
||||
// ************ STATIC ************
|
||||
CTexture* CTextureDecoder::LoadTXTR(CInputStream& TXTR)
|
||||
{
|
||||
CTextureDecoder Decoder;
|
||||
Decoder.ReadTXTR(TXTR);
|
||||
Decoder.PartialDecodeGXTexture(TXTR);
|
||||
return Decoder.CreateTexture();
|
||||
}
|
||||
|
||||
CTexture* CTextureDecoder::DoFullDecode(CInputStream &TXTR)
|
||||
{
|
||||
CTextureDecoder Decoder;
|
||||
Decoder.ReadTXTR(TXTR);
|
||||
Decoder.FullDecodeGXTexture(TXTR);
|
||||
|
||||
CTexture *pTexture = Decoder.CreateTexture();
|
||||
pTexture->mTexelFormat = eRGBA8;
|
||||
return pTexture;
|
||||
}
|
||||
|
||||
CTexture* CTextureDecoder::LoadDDS(CInputStream &DDS)
|
||||
{
|
||||
CTextureDecoder Decoder;
|
||||
Decoder.ReadDDS(DDS);
|
||||
Decoder.DecodeDDS(DDS);
|
||||
return Decoder.CreateTexture();
|
||||
}
|
||||
|
||||
CTexture* CTextureDecoder::DoFullDecode(CTexture*)
|
||||
{
|
||||
// Not using parameter 1 (CTexture* - pTexture)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// ************ READ ************
|
||||
void CTextureDecoder::ReadTXTR(CInputStream& TXTR)
|
||||
{
|
||||
// Read TXTR header
|
||||
Log::Write("Loading " + TXTR.GetSourceString());
|
||||
mTexelFormat = ETexelFormat(TXTR.ReadLong());
|
||||
mWidth = TXTR.ReadShort();
|
||||
mHeight = TXTR.ReadShort();
|
||||
mNumMipMaps = TXTR.ReadLong();
|
||||
|
||||
// For C4 and C8 images, read palette
|
||||
if ((mTexelFormat == eGX_C4) || (mTexelFormat == eGX_C8))
|
||||
{
|
||||
mHasPalettes = true;
|
||||
mPaletteFormat = EGXPaletteFormat(TXTR.ReadLong());
|
||||
TXTR.Seek(0x4, SEEK_CUR);
|
||||
|
||||
u32 PaletteEntryCount = (mTexelFormat == eGX_C4) ? 16 : 256;
|
||||
mPalettes.resize(PaletteEntryCount * 2);
|
||||
TXTR.ReadBytes(mPalettes.data(), mPalettes.size());
|
||||
|
||||
mPaletteInput.SetData(mPalettes.data(), mPalettes.size(), IOUtil::BigEndian);
|
||||
}
|
||||
else mHasPalettes = false;
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadDDS(CInputStream& DDS)
|
||||
{
|
||||
Log::Write("Loading " + DDS.GetSourceString());
|
||||
|
||||
// Header
|
||||
CFourCC Magic(DDS);
|
||||
if (Magic != "DDS ")
|
||||
{
|
||||
Log::FileError(DDS.GetSourceString(), "Invalid DDS magic: " + StringUtil::ToHexString((u32) Magic.ToLong()));
|
||||
return;
|
||||
}
|
||||
|
||||
u32 ImageDataStart = DDS.Tell() + DDS.ReadLong();
|
||||
DDS.Seek(0x4, SEEK_CUR); // Skipping flags
|
||||
mHeight = (u16) DDS.ReadLong();
|
||||
mWidth = (u16) DDS.ReadLong();
|
||||
DDS.Seek(0x8, SEEK_CUR); // Skipping linear size + depth
|
||||
mNumMipMaps = DDS.ReadLong() + 1; // DDS doesn't seem to count the first mipmap
|
||||
DDS.Seek(0x2C, SEEK_CUR); // Skipping reserved
|
||||
|
||||
// Pixel Format
|
||||
DDS.Seek(0x4, SEEK_CUR); // Skipping size
|
||||
mDDSInfo.Flags = DDS.ReadLong();
|
||||
CFourCC Format(DDS);
|
||||
|
||||
if (Format == "DXT1") mDDSInfo.Format = SDDSInfo::DXT1;
|
||||
else if (Format == "DXT2") mDDSInfo.Format = SDDSInfo::DXT2;
|
||||
else if (Format == "DXT3") mDDSInfo.Format = SDDSInfo::DXT3;
|
||||
else if (Format == "DXT4") mDDSInfo.Format = SDDSInfo::DXT4;
|
||||
else if (Format == "DXT5") mDDSInfo.Format = SDDSInfo::DXT5;
|
||||
else
|
||||
{
|
||||
mDDSInfo.Format = SDDSInfo::RGBA;
|
||||
mDDSInfo.BitCount = DDS.ReadLong();
|
||||
mDDSInfo.RBitMask = DDS.ReadLong();
|
||||
mDDSInfo.GBitMask = DDS.ReadLong();
|
||||
mDDSInfo.BBitMask = DDS.ReadLong();
|
||||
mDDSInfo.ABitMask = DDS.ReadLong();
|
||||
mDDSInfo.RShift = CalculateShiftForMask(mDDSInfo.RBitMask);
|
||||
mDDSInfo.GShift = CalculateShiftForMask(mDDSInfo.GBitMask);
|
||||
mDDSInfo.BShift = CalculateShiftForMask(mDDSInfo.BBitMask);
|
||||
mDDSInfo.AShift = CalculateShiftForMask(mDDSInfo.ABitMask);
|
||||
mDDSInfo.RSize = CalculateMaskBitCount(mDDSInfo.RBitMask);
|
||||
mDDSInfo.GSize = CalculateMaskBitCount(mDDSInfo.GBitMask);
|
||||
mDDSInfo.BSize = CalculateMaskBitCount(mDDSInfo.BBitMask);
|
||||
mDDSInfo.ASize = CalculateMaskBitCount(mDDSInfo.ABitMask);
|
||||
}
|
||||
|
||||
// Skip the rest
|
||||
DDS.Seek(ImageDataStart, SEEK_SET);
|
||||
}
|
||||
|
||||
// ************ DECODE ************
|
||||
void CTextureDecoder::PartialDecodeGXTexture(CInputStream& TXTR)
|
||||
{
|
||||
// Get image data size, create output buffer
|
||||
u32 ImageStart = TXTR.Tell();
|
||||
TXTR.Seek(0x0, SEEK_END);
|
||||
u32 ImageSize = TXTR.Tell() - ImageStart;
|
||||
TXTR.Seek(ImageStart, SEEK_SET);
|
||||
|
||||
mDataBufferSize = ImageSize * (gskOutputBpp[mTexelFormat] / gskSourceBpp[mTexelFormat]);
|
||||
if ((mHasPalettes) && (mPaletteFormat == ePalette_RGB5A3)) mDataBufferSize *= 2;
|
||||
mpDataBuffer = new u8[mDataBufferSize];
|
||||
|
||||
CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness);
|
||||
|
||||
// Initializing more stuff before we start the mipmap loop
|
||||
u32 MipW = mWidth, MipH = mHeight;
|
||||
u32 MipOffset = 0;
|
||||
|
||||
u32 BWidth = gskBlockWidth[mTexelFormat];
|
||||
u32 BHeight = gskBlockHeight[mTexelFormat];
|
||||
|
||||
u32 PixelStride = gskOutputPixelStride[mTexelFormat];
|
||||
if (mHasPalettes && (mPaletteFormat == ePalette_RGB5A3))
|
||||
PixelStride = 4;
|
||||
|
||||
// With CMPR, we're using a little trick.
|
||||
// CMPR stores pixels in 8x8 blocks, with four 4x4 subblocks.
|
||||
// An easy way to convert it is to pretend each block is 2x2 and each subblock is one pixel.
|
||||
// So to do that we need to calculate the "new" dimensions of the image, 1/4 the size of the original.
|
||||
if (mTexelFormat == eGX_CMPR) {
|
||||
MipW /= 4;
|
||||
MipH /= 4;
|
||||
}
|
||||
|
||||
for (u32 iMip = 0; iMip < mNumMipMaps; iMip++)
|
||||
{
|
||||
for (u32 BlockY = 0; BlockY < MipH; BlockY += BHeight)
|
||||
for (u32 BlockX = 0; BlockX < MipW; BlockX += BWidth) {
|
||||
for (u32 ImgY = BlockY; ImgY < BlockY + BHeight; ImgY++) {
|
||||
for (u32 ImgX = BlockX; ImgX < BlockX + BWidth; ImgX++)
|
||||
{
|
||||
u32 DstPos = ((ImgY * MipW) + ImgX) * PixelStride;
|
||||
Out.Seek(MipOffset + DstPos, SEEK_SET);
|
||||
|
||||
if (mTexelFormat == eGX_I4) ReadPixelsI4(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_I8) ReadPixelI8(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_IA4) ReadPixelIA4(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_IA8) ReadPixelIA8(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_C4) ReadPixelsC4(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_C8) ReadPixelC8(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_RGB565) ReadPixelRGB565(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_RGB5A3) ReadPixelRGB5A3(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_RGBA8) ReadPixelRGBA8(TXTR, Out);
|
||||
else if (mTexelFormat == eGX_CMPR) ReadSubBlockCMPR(TXTR, Out);
|
||||
|
||||
// I4 and C4 have 4bpp images, so I'm forced to read two pixels at a time.
|
||||
if ((mTexelFormat == eGX_I4) || (mTexelFormat == eGX_C4)) ImgX++;
|
||||
}
|
||||
}
|
||||
if (mTexelFormat == eGX_RGBA8) TXTR.Seek(0x20, SEEK_CUR);
|
||||
}
|
||||
|
||||
u32 MipSize = (u32) (MipW * MipH * gskPixelsToBytes[mTexelFormat]);
|
||||
if (mTexelFormat == eGX_CMPR) MipSize *= 16; // Since we're pretending the image is 1/4 its actual size, we have to multiply the size by 16 to get the correct offset
|
||||
|
||||
MipOffset += MipSize;
|
||||
MipW /= 2;
|
||||
MipH /= 2;
|
||||
if (MipW < BWidth) MipW = BWidth;
|
||||
if (MipH < BHeight) MipH = BHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::FullDecodeGXTexture(CInputStream& TXTR)
|
||||
{
|
||||
// Get image data size, create output buffer
|
||||
u32 ImageStart = TXTR.Tell();
|
||||
TXTR.Seek(0x0, SEEK_END);
|
||||
u32 ImageSize = TXTR.Tell() - ImageStart;
|
||||
TXTR.Seek(ImageStart, SEEK_SET);
|
||||
|
||||
mDataBufferSize = ImageSize * (32 / gskSourceBpp[mTexelFormat]);
|
||||
mpDataBuffer = new u8[mDataBufferSize];
|
||||
|
||||
CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness);
|
||||
|
||||
// Initializing more stuff before we start the mipmap loop
|
||||
u32 MipW = mWidth, MipH = mHeight;
|
||||
u32 MipOffset = 0;
|
||||
|
||||
u32 BWidth = gskBlockWidth[mTexelFormat];
|
||||
u32 BHeight = gskBlockHeight[mTexelFormat];
|
||||
|
||||
// With CMPR, we're using a little trick.
|
||||
// CMPR stores pixels in 8x8 blocks, with four 4x4 subblocks.
|
||||
// An easy way to convert it is to pretend each block is 2x2 and each subblock is one pixel.
|
||||
// So to do that we need to calculate the "new" dimensions of the image, 1/4 the size of the original.
|
||||
if (mTexelFormat == eGX_CMPR) {
|
||||
MipW /= 4;
|
||||
MipH /= 4;
|
||||
}
|
||||
|
||||
for (u32 iMip = 0; iMip < mNumMipMaps; iMip++)
|
||||
{
|
||||
for (u32 BlockY = 0; BlockY < MipH; BlockY += BHeight)
|
||||
for (u32 BlockX = 0; BlockX < MipW; BlockX += BWidth) {
|
||||
for (u32 ImgY = BlockY; ImgY < BlockY + BHeight; ImgY++) {
|
||||
for (u32 ImgX = BlockX; ImgX < BlockX + BWidth; ImgX++)
|
||||
{
|
||||
u32 DstPos = (mTexelFormat == eGX_CMPR) ? ((ImgY * (MipW * 4)) + ImgX) * 16 : ((ImgY * MipW) + ImgX) * 4;
|
||||
Out.Seek(MipOffset + DstPos, SEEK_SET);
|
||||
|
||||
// I4/C4/CMPR require reading more than one pixel at a time
|
||||
if (mTexelFormat == eGX_I4)
|
||||
{
|
||||
u8 Byte = TXTR.ReadByte();
|
||||
Out.WriteLong( DecodePixelI4(Byte, 0).ToLongARGB() );
|
||||
Out.WriteLong( DecodePixelI4(Byte, 1).ToLongARGB() );
|
||||
}
|
||||
else if (mTexelFormat == eGX_C4)
|
||||
{
|
||||
u8 Byte = TXTR.ReadByte();
|
||||
Out.WriteLong( DecodePixelC4(Byte, 0, mPaletteInput).ToLongARGB() );
|
||||
Out.WriteLong( DecodePixelC4(Byte, 1, mPaletteInput).ToLongARGB() );
|
||||
}
|
||||
else if (mTexelFormat == eGX_CMPR) DecodeSubBlockCMPR(TXTR, Out, (u16) (MipW * 4));
|
||||
|
||||
else
|
||||
{
|
||||
CColor Pixel;
|
||||
|
||||
if (mTexelFormat == eGX_I8) Pixel = DecodePixelI8(TXTR.ReadByte());
|
||||
else if (mTexelFormat == eGX_IA4) Pixel = DecodePixelIA4(TXTR.ReadByte());
|
||||
else if (mTexelFormat == eGX_IA8) Pixel = DecodePixelIA8(TXTR.ReadShort());
|
||||
else if (mTexelFormat == eGX_C8) Pixel = DecodePixelC8(TXTR.ReadByte(), mPaletteInput);
|
||||
else if (mTexelFormat == eGX_RGB565) Pixel = DecodePixelRGB565(TXTR.ReadShort());
|
||||
else if (mTexelFormat == eGX_RGB5A3) Pixel = DecodePixelRGB5A3(TXTR.ReadShort());
|
||||
else if (mTexelFormat == eGX_RGBA8) Pixel = CColor(TXTR);
|
||||
|
||||
Out.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mTexelFormat == eGX_RGBA8) TXTR.Seek(0x20, SEEK_CUR);
|
||||
}
|
||||
|
||||
u32 MipSize = MipW * MipH * 4;
|
||||
if (mTexelFormat == eGX_CMPR) MipSize *= 16;
|
||||
|
||||
MipOffset += MipSize;
|
||||
MipW /= 2;
|
||||
MipH /= 2;
|
||||
if (MipW < BWidth) MipW = BWidth;
|
||||
if (MipH < BHeight) MipH = BHeight;
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::DecodeDDS(CInputStream &DDS)
|
||||
{
|
||||
// Get image data size, create output buffer
|
||||
u32 ImageStart = DDS.Tell();
|
||||
DDS.Seek(0x0, SEEK_END);
|
||||
u32 ImageSize = DDS.Tell() - ImageStart;
|
||||
DDS.Seek(ImageStart, SEEK_SET);
|
||||
|
||||
mDataBufferSize = ImageSize;
|
||||
if (mDDSInfo.Format == SDDSInfo::DXT1) mDataBufferSize *= 8;
|
||||
else if (mDDSInfo.Format == SDDSInfo::RGBA) mDataBufferSize *= (32 / mDDSInfo.BitCount);
|
||||
else mDataBufferSize *= 4;
|
||||
mpDataBuffer = new u8[mDataBufferSize];
|
||||
|
||||
CMemoryOutStream Out(mpDataBuffer, mDataBufferSize, IOUtil::SystemEndianness);
|
||||
|
||||
// Initializing more stuff before we start the mipmap loop
|
||||
u32 MipW = mWidth, MipH = mHeight;
|
||||
u32 MipOffset = 0;
|
||||
|
||||
u32 BPP;
|
||||
switch (mDDSInfo.Format)
|
||||
{
|
||||
case SDDSInfo::RGBA:
|
||||
BPP = mDDSInfo.BitCount;
|
||||
break;
|
||||
case SDDSInfo::DXT1:
|
||||
BPP = 4;
|
||||
break;
|
||||
case SDDSInfo::DXT2:
|
||||
case SDDSInfo::DXT3:
|
||||
case SDDSInfo::DXT4:
|
||||
case SDDSInfo::DXT5:
|
||||
BPP = 8;
|
||||
break;
|
||||
}
|
||||
|
||||
// For DXT* decodes we can use the same trick as CMPR
|
||||
if ((mDDSInfo.Format != SDDSInfo::RGBA) && (mDDSInfo.Format != SDDSInfo::DXT1))
|
||||
{
|
||||
MipW /= 4;
|
||||
MipH /= 4;
|
||||
}
|
||||
|
||||
for (u32 iMip = 0; iMip < mNumMipMaps; iMip++)
|
||||
{
|
||||
// For DXT1 we can copy the image data as-is to load it
|
||||
if (mDDSInfo.Format == SDDSInfo::DXT1)
|
||||
{
|
||||
Out.Seek(MipOffset, SEEK_SET);
|
||||
u32 MipSize = MipW * MipH / 2;
|
||||
std::vector<u8> MipBuffer(MipSize);
|
||||
DDS.ReadBytes(MipBuffer.data(), MipBuffer.size());
|
||||
Out.WriteBytes(MipBuffer.data(), MipBuffer.size());
|
||||
MipOffset += MipSize;
|
||||
|
||||
MipW /= 2;
|
||||
MipH /= 2;
|
||||
if (MipW % 4) MipW += (4 - (MipW % 4));
|
||||
if (MipH % 4) MipH += (4 - (MipH % 4));
|
||||
}
|
||||
|
||||
// Otherwise we do a full decode to RGBA8
|
||||
else
|
||||
{
|
||||
for (u32 y = 0; y < MipH; y++)
|
||||
{
|
||||
for (u32 x = 0; x < MipW; x++)
|
||||
{
|
||||
u32 OutPos = MipOffset;
|
||||
|
||||
if (mDDSInfo.Format == SDDSInfo::RGBA)
|
||||
{
|
||||
OutPos += ((y * MipW) + x) * 4;
|
||||
Out.Seek(OutPos, SEEK_SET);
|
||||
|
||||
CColor Pixel = DecodeDDSPixel(DDS);
|
||||
Out.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
|
||||
else
|
||||
{
|
||||
OutPos += ((y * (MipW * 4)) + x) * 16;
|
||||
Out.Seek(OutPos, SEEK_SET);
|
||||
|
||||
if (mDDSInfo.Format == SDDSInfo::DXT1)
|
||||
DecodeBlockBC1(DDS, Out, MipW * 4);
|
||||
else if ((mDDSInfo.Format == SDDSInfo::DXT2) || (mDDSInfo.Format == SDDSInfo::DXT3))
|
||||
DecodeBlockBC2(DDS, Out, MipW * 4);
|
||||
else if ((mDDSInfo.Format == SDDSInfo::DXT4) || (mDDSInfo.Format == SDDSInfo::DXT5))
|
||||
DecodeBlockBC3(DDS, Out, MipW * 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 MipSize = (mWidth * mHeight) * 4;
|
||||
if (mDDSInfo.Format != SDDSInfo::RGBA) MipSize *= 16;
|
||||
MipOffset += MipSize;
|
||||
|
||||
MipW /= 2;
|
||||
MipH /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (mDDSInfo.Format == SDDSInfo::DXT1)
|
||||
mTexelFormat = eDXT1;
|
||||
else
|
||||
mTexelFormat = eGX_RGBA8;
|
||||
}
|
||||
|
||||
// ************ READ PIXELS (PARTIAL DECODE) ************
|
||||
void CTextureDecoder::ReadPixelsI4(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
u8 px = src.ReadByte();
|
||||
dst.WriteByte(Extend4to8(px >> 4));
|
||||
dst.WriteByte(Extend4to8(px >> 4));
|
||||
dst.WriteByte(Extend4to8(px));
|
||||
dst.WriteByte(Extend4to8(px));
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelI8(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
u8 px = src.ReadByte();
|
||||
dst.WriteByte(px);
|
||||
dst.WriteByte(px);
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelIA4(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
// this can be left as-is for DDS conversion, but opengl doesn't support two components in one byte...
|
||||
u8 byte = src.ReadByte();
|
||||
u8 a = Extend4to8(byte >> 4);
|
||||
u8 l = Extend4to8(byte);
|
||||
dst.WriteShort((l << 8) | a);
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelIA8(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
dst.WriteShort(src.ReadShort());
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelsC4(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
// This isn't how C4 works, but due to the way Retro packed font textures (which use C4)
|
||||
// this is the only way to get them to decode correctly for now.
|
||||
// Commented-out code is proper C4 decoding. Dedicated font texture-decoding function
|
||||
// is probably going to be necessary in the future.
|
||||
u8 byte = src.ReadByte();
|
||||
u8 indices[2];
|
||||
indices[0] = (byte >> 4) & 0xF;
|
||||
indices[1] = byte & 0xF;
|
||||
|
||||
for (u32 i = 0; i < 2; i++)
|
||||
{
|
||||
u8 r, g, b, a;
|
||||
((indices[i] >> 3) & 0x1) ? r = 0xFF : r = 0x0;
|
||||
((indices[i] >> 2) & 0x1) ? g = 0xFF : g = 0x0;
|
||||
((indices[i] >> 1) & 0x1) ? b = 0xFF : b = 0x0;
|
||||
((indices[i] >> 0) & 0x1) ? a = 0xFF : a = 0x0;
|
||||
u32 rgba = (r << 24) | (g << 16) | (b << 8) | (a);
|
||||
dst.WriteLong(rgba);
|
||||
|
||||
/*paletteInput->Seek(indices[i] * 2, SEEK_SET);
|
||||
|
||||
if (paletteFormat == PaletteIA8) readPixelIA8(*paletteInput, dst);
|
||||
else if (paletteFormat == PaletteRGB565) readPixelRGB565(*paletteInput, dst);
|
||||
else if (paletteFormat == PaletteRGB5A3) readPixelRGB5A3(*paletteInput, dst);*/
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelC8(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
// DKCR fonts use C8 :|
|
||||
u8 index = src.ReadByte();
|
||||
|
||||
/*u8 r, g, b, a;
|
||||
((index >> 3) & 0x1) ? r = 0xFF : r = 0x0;
|
||||
((index >> 2) & 0x1) ? g = 0xFF : g = 0x0;
|
||||
((index >> 1) & 0x1) ? b = 0xFF : b = 0x0;
|
||||
((index >> 0) & 0x1) ? a = 0xFF : a = 0x0;
|
||||
u32 rgba = (r << 24) | (g << 16) | (b << 8) | (a);
|
||||
dst.WriteLong(rgba);*/
|
||||
|
||||
mPaletteInput.Seek(index * 2, SEEK_SET);
|
||||
|
||||
if (mPaletteFormat == ePalette_IA8) ReadPixelIA8(mPaletteInput, dst);
|
||||
else if (mPaletteFormat == ePalette_RGB565) ReadPixelRGB565(mPaletteInput, dst);
|
||||
else if (mPaletteFormat == ePalette_RGB5A3) ReadPixelRGB5A3(mPaletteInput, dst);
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelRGB565(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
// RGB565 can be used as-is.
|
||||
dst.WriteShort(src.ReadShort());
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelRGB5A3(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
u16 px = src.ReadShort();
|
||||
CColor c;
|
||||
|
||||
if (px & 0x8000) // RGB5
|
||||
{
|
||||
c.b = Extend5to8(px >> 10);
|
||||
c.g = Extend5to8(px >> 5);
|
||||
c.r = Extend5to8(px >> 0);
|
||||
c.a = 0xFF;
|
||||
}
|
||||
|
||||
else // RGB4A3
|
||||
{
|
||||
c.a = Extend3to8(px >> 12);
|
||||
c.b = Extend4to8(px >> 8);
|
||||
c.g = Extend4to8(px >> 4);
|
||||
c.r = Extend4to8(px >> 0);
|
||||
}
|
||||
|
||||
dst.WriteLong(c.ToLongARGB());
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadPixelRGBA8(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
u16 ar = src.ReadShort();
|
||||
src.Seek(0x1E, SEEK_CUR);
|
||||
u16 gb = src.ReadShort();
|
||||
src.Seek(-0x20, SEEK_CUR);
|
||||
u32 px = (ar << 16) | gb;
|
||||
dst.WriteLong(px);
|
||||
}
|
||||
|
||||
void CTextureDecoder::ReadSubBlockCMPR(CInputStream& src, COutputStream& dst)
|
||||
{
|
||||
dst.WriteShort(src.ReadShort());
|
||||
dst.WriteShort(src.ReadShort());
|
||||
|
||||
for (u32 byte = 0; byte < 4; byte++) {
|
||||
u8 b = src.ReadByte();
|
||||
b = ((b & 0x3) << 6) | ((b & 0xC) << 2) | ((b & 0x30) >> 2) | ((b & 0xC0) >> 6);
|
||||
dst.WriteByte(b);
|
||||
}
|
||||
}
|
||||
|
||||
// ************ DECODE PIXELS (FULL DECODE TO RGBA8) ************
|
||||
CColor CTextureDecoder::DecodePixelI4(u8 Byte, u8 WhichPixel)
|
||||
{
|
||||
if (WhichPixel == 1) Byte >>= 4;
|
||||
u8 px = Extend4to8(Byte);
|
||||
return CColor(px, px, px, 0xFF);
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelI8(u8 Byte)
|
||||
{
|
||||
return CColor(Byte, Byte, Byte, 0xFF);
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelIA4(u8 Byte)
|
||||
{
|
||||
u8 Alpha = Extend4to8(Byte >> 4);
|
||||
u8 Lum = Extend4to8(Byte);
|
||||
return CColor(Lum, Lum, Lum, Alpha);
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelIA8(u16 Short)
|
||||
{
|
||||
u8 Alpha = (Short >> 8) & 0xFF;
|
||||
u8 Lum = Short & 0xFF;
|
||||
return CColor(Lum, Lum, Lum, Alpha);
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelC4(u8 Byte, u8 WhichPixel, CInputStream& PaletteStream)
|
||||
{
|
||||
if (WhichPixel == 1) Byte >>= 4;
|
||||
Byte &= 0xF;
|
||||
|
||||
PaletteStream.Seek(Byte * 2, SEEK_SET);
|
||||
if (mPaletteFormat == ePalette_IA8) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else if (mPaletteFormat == ePalette_RGB565) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else if (mPaletteFormat == ePalette_RGB5A3) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else return CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelC8(u8 Byte, CInputStream& PaletteStream)
|
||||
{
|
||||
PaletteStream.Seek(Byte * 2, SEEK_SET);
|
||||
if (mPaletteFormat == ePalette_IA8) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else if (mPaletteFormat == ePalette_RGB565) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else if (mPaletteFormat == ePalette_RGB5A3) return DecodePixelIA8(PaletteStream.ReadShort());
|
||||
else return CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelRGB565(u16 Short)
|
||||
{
|
||||
u8 b = Extend5to8( (u8) (Short >> 11) );
|
||||
u8 g = Extend6to8( (u8) (Short >> 5) );
|
||||
u8 r = Extend5to8( (u8) (Short) );
|
||||
return CColor(r, g, b, 0xFF);
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodePixelRGB5A3(u16 Short)
|
||||
{
|
||||
if (Short & 0x8000) // RGB5
|
||||
{
|
||||
u8 b = Extend5to8( (u8) (Short >> 10));
|
||||
u8 g = Extend5to8( (u8) (Short >> 5));
|
||||
u8 r = Extend5to8( (u8) (Short) );
|
||||
return CColor(r, g, b, 0xFF);
|
||||
}
|
||||
|
||||
else // RGB4A3
|
||||
{
|
||||
u8 a = Extend3to8( (u8) (Short >> 12) );
|
||||
u8 b = Extend4to8( (u8) (Short >> 8) );
|
||||
u8 g = Extend4to8( (u8) (Short >> 4) );
|
||||
u8 r = Extend4to8( (u8) (Short) );
|
||||
return CColor(r, g, b, a);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::DecodeSubBlockCMPR(CInputStream& src, COutputStream& dst, u16 Width)
|
||||
{
|
||||
CColor Palettes[4];
|
||||
u16 PaletteA = src.ReadShort();
|
||||
u16 PaletteB = src.ReadShort();
|
||||
Palettes[0] = DecodePixelRGB565(PaletteA);
|
||||
Palettes[1] = DecodePixelRGB565(PaletteB);
|
||||
|
||||
if (PaletteA > PaletteB)
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f);
|
||||
Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f);
|
||||
Palettes[3] = CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < 4; y++)
|
||||
{
|
||||
u8 Byte = src.ReadByte();
|
||||
|
||||
for (u32 x = 0; x < 4; x++)
|
||||
{
|
||||
u8 Shift = (u8) (6 - (x * 2));
|
||||
u8 PaletteIndex = (Byte >> Shift) & 0x3;
|
||||
CColor Pixel = Palettes[PaletteIndex];
|
||||
dst.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
|
||||
dst.Seek((Width - 4) * 4, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::DecodeBlockBC1(CInputStream& src, COutputStream& dst, u32 Width)
|
||||
{
|
||||
// Very similar to the CMPR subblock function, but unfortunately a slight
|
||||
// difference in the order the pixel indices are read requires a separate function
|
||||
CColor Palettes[4];
|
||||
u16 PaletteA = src.ReadShort();
|
||||
u16 PaletteB = src.ReadShort();
|
||||
Palettes[0] = DecodePixelRGB565(PaletteA);
|
||||
Palettes[1] = DecodePixelRGB565(PaletteB);
|
||||
|
||||
if (PaletteA > PaletteB)
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f);
|
||||
Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f);
|
||||
Palettes[3] = CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < 4; y++)
|
||||
{
|
||||
u8 Byte = src.ReadByte();
|
||||
|
||||
for (u32 x = 0; x < 4; x++)
|
||||
{
|
||||
u8 Shift = (u8) (x * 2);
|
||||
u8 PaletteIndex = (Byte >> Shift) & 0x3;
|
||||
CColor Pixel = Palettes[PaletteIndex];
|
||||
dst.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
|
||||
dst.Seek((Width - 4) * 4, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::DecodeBlockBC2(CInputStream& src, COutputStream& dst, u32 Width)
|
||||
{
|
||||
CColor CPalettes[4];
|
||||
u16 PaletteA = src.ReadShort();
|
||||
u16 PaletteB = src.ReadShort();
|
||||
CPalettes[0] = DecodePixelRGB565(PaletteA);
|
||||
CPalettes[1] = DecodePixelRGB565(PaletteB);
|
||||
|
||||
if (PaletteA > PaletteB)
|
||||
{
|
||||
CPalettes[2] = (CPalettes[0] * 0.666666666f) + (CPalettes[1] * 0.333333333f);
|
||||
CPalettes[3] = (CPalettes[0] * 0.333333333f) + (CPalettes[1] * 0.666666666f);
|
||||
}
|
||||
else
|
||||
{
|
||||
CPalettes[2] = (CPalettes[0] * 0.5f) + (CPalettes[1] * 0.5f);
|
||||
CPalettes[3] = CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < 4; y++)
|
||||
{
|
||||
u8 Byte = src.ReadByte();
|
||||
|
||||
for (u32 x = 0; x < 4; x++)
|
||||
{
|
||||
u8 Shift = (u8) (x * 2);
|
||||
u8 PaletteIndex = (Byte >> Shift) & 0x3;
|
||||
CColor Pixel = CPalettes[PaletteIndex];
|
||||
dst.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
|
||||
dst.Seek((Width - 4) * 4, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
void CTextureDecoder::DecodeBlockBC3(CInputStream& src, COutputStream& dst, u32 Width)
|
||||
{
|
||||
CColor Palettes[4];
|
||||
u16 PaletteA = src.ReadShort();
|
||||
u16 PaletteB = src.ReadShort();
|
||||
Palettes[0] = DecodePixelRGB565(PaletteA);
|
||||
Palettes[1] = DecodePixelRGB565(PaletteB);
|
||||
|
||||
if (PaletteA > PaletteB)
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.666666666f) + (Palettes[1] * 0.333333333f);
|
||||
Palettes[3] = (Palettes[0] * 0.333333333f) + (Palettes[1] * 0.666666666f);
|
||||
}
|
||||
else
|
||||
{
|
||||
Palettes[2] = (Palettes[0] * 0.5f) + (Palettes[1] * 0.5f);
|
||||
Palettes[3] = CColor::skTransparentBlack;
|
||||
}
|
||||
|
||||
for (u32 y = 0; y < 4; y++)
|
||||
{
|
||||
u8 Byte = src.ReadByte();
|
||||
|
||||
for (u32 x = 0; x < 4; x++)
|
||||
{
|
||||
u8 Shift = (u8) (x * 2);
|
||||
u8 PaletteIndex = (Byte >> Shift) & 0x3;
|
||||
CColor Pixel = Palettes[PaletteIndex];
|
||||
dst.WriteLong(Pixel.ToLongARGB());
|
||||
}
|
||||
|
||||
dst.Seek((Width - 4) * 4, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
CColor CTextureDecoder::DecodeDDSPixel(CInputStream&)
|
||||
{
|
||||
// Not using parameter 1 (CInputStream& - DDS)
|
||||
return CColor::skWhite;
|
||||
}
|
||||
|
||||
// ************ UTILITY ************
|
||||
u8 CTextureDecoder::Extend3to8(u8 in)
|
||||
{
|
||||
in &= 0x7;
|
||||
return (in << 5) | (in << 2) | (in >> 1);
|
||||
}
|
||||
|
||||
u8 CTextureDecoder::Extend4to8(u8 in)
|
||||
{
|
||||
in &= 0xF;
|
||||
return (in << 4) | in;
|
||||
}
|
||||
|
||||
u8 CTextureDecoder::Extend5to8(u8 in)
|
||||
{
|
||||
in &= 0x1F;
|
||||
return (in << 3) | (in >> 2);
|
||||
}
|
||||
|
||||
u8 CTextureDecoder::Extend6to8(u8 in)
|
||||
{
|
||||
in &= 0x3F;
|
||||
return (in << 2) | (in >> 4);
|
||||
}
|
||||
|
||||
u32 CTextureDecoder::CalculateShiftForMask(u32 BitMask)
|
||||
{
|
||||
u32 Shift = 32;
|
||||
|
||||
while (BitMask)
|
||||
{
|
||||
BitMask <<= 1;
|
||||
Shift--;
|
||||
}
|
||||
return Shift;
|
||||
}
|
||||
|
||||
u32 CTextureDecoder::CalculateMaskBitCount(u32 BitMask)
|
||||
{
|
||||
u32 Count = 0;
|
||||
|
||||
while (BitMask)
|
||||
{
|
||||
if (BitMask & 0x1) Count++;
|
||||
BitMask >>= 1;
|
||||
}
|
||||
return Count;
|
||||
}
|
||||
93
Resource/factory/CTextureDecoder.h
Normal file
93
Resource/factory/CTextureDecoder.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#ifndef CTEXTUREDECODER_H
|
||||
#define CTEXTUREDECODER_H
|
||||
|
||||
#include <FileIO/FileIO.h>
|
||||
#include <Common/CColor.h>
|
||||
#include <Common/types.h>
|
||||
#include "../CTexture.h"
|
||||
#include "../ETexelFormat.h"
|
||||
|
||||
class CTextureDecoder
|
||||
{
|
||||
ETexelFormat mTexelFormat;
|
||||
u16 mWidth, mHeight;
|
||||
u32 mNumMipMaps;
|
||||
|
||||
bool mHasPalettes;
|
||||
EGXPaletteFormat mPaletteFormat;
|
||||
std::vector<u8> mPalettes;
|
||||
CMemoryInStream mPaletteInput;
|
||||
|
||||
struct SDDSInfo
|
||||
{
|
||||
enum { DXT1, DXT2, DXT3, DXT4, DXT5, RGBA } Format;
|
||||
u32 Flags;
|
||||
u32 BitCount;
|
||||
u32 RBitMask, GBitMask, BBitMask, ABitMask;
|
||||
u32 RShift, GShift, BShift, AShift;
|
||||
u32 RSize, GSize, BSize, ASize;
|
||||
} mDDSInfo;
|
||||
|
||||
u8 *mpDataBuffer;
|
||||
u32 mDataBufferSize;
|
||||
|
||||
// Private Functions
|
||||
CTextureDecoder();
|
||||
~CTextureDecoder();
|
||||
CTexture* CreateTexture();
|
||||
|
||||
// Read
|
||||
void ReadTXTR(CInputStream& TXTR);
|
||||
void ReadDDS(CInputStream& DDS);
|
||||
|
||||
// Decode
|
||||
void PartialDecodeGXTexture(CInputStream& TXTR);
|
||||
void FullDecodeGXTexture(CInputStream& TXTR);
|
||||
void DecodeDDS(CInputStream& DDS);
|
||||
|
||||
// Decode Pixels (preserve compression)
|
||||
void ReadPixelsI4(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelI8(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelIA4(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelIA8(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelsC4(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelC8(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelRGB565(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelRGB5A3(CInputStream& src, COutputStream& dst);
|
||||
void ReadPixelRGBA8(CInputStream& src, COutputStream& dst);
|
||||
void ReadSubBlockCMPR(CInputStream& src, COutputStream& dst);
|
||||
|
||||
// Decode Pixels (convert to RGBA8)
|
||||
CColor DecodePixelI4(u8 Byte, u8 WhichPixel);
|
||||
CColor DecodePixelI8(u8 Byte);
|
||||
CColor DecodePixelIA4(u8 Byte);
|
||||
CColor DecodePixelIA8(u16 Short);
|
||||
CColor DecodePixelC4(u8 Byte, u8 WhichPixel, CInputStream& PaletteStream);
|
||||
CColor DecodePixelC8(u8 Byte, CInputStream& PaletteStream);
|
||||
CColor DecodePixelRGB565(u16 Short);
|
||||
CColor DecodePixelRGB5A3(u16 Short);
|
||||
CColor DecodePixelRGBA8(CInputStream& src, COutputStream& dst);
|
||||
void DecodeSubBlockCMPR(CInputStream& src, COutputStream& dst, u16 Width);
|
||||
|
||||
void DecodeBlockBC1(CInputStream& src, COutputStream& dst, u32 Width);
|
||||
void DecodeBlockBC2(CInputStream& src, COutputStream& dst, u32 Width);
|
||||
void DecodeBlockBC3(CInputStream& src, COutputStream& dst, u32 Width);
|
||||
CColor DecodeDDSPixel(CInputStream& DDS);
|
||||
|
||||
// Static
|
||||
public:
|
||||
static CTexture* LoadTXTR(CInputStream& TXTR);
|
||||
static CTexture* LoadDDS(CInputStream& DDS);
|
||||
static CTexture* DoFullDecode(CInputStream& TXTR);
|
||||
static CTexture* DoFullDecode(CTexture *pTexture);
|
||||
|
||||
// Utility
|
||||
static u8 Extend3to8(u8 in);
|
||||
static u8 Extend4to8(u8 in);
|
||||
static u8 Extend5to8(u8 in);
|
||||
static u8 Extend6to8(u8 in);
|
||||
static u32 CalculateShiftForMask(u32 BitMask);
|
||||
static u32 CalculateMaskBitCount(u32 BitMask);
|
||||
};
|
||||
|
||||
#endif // CTEXTUREDECODER_H
|
||||
341
Resource/factory/CWorldLoader.cpp
Normal file
341
Resource/factory/CWorldLoader.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
#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;
|
||||
}
|
||||
}
|
||||
23
Resource/factory/CWorldLoader.h
Normal file
23
Resource/factory/CWorldLoader.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#ifndef CWORLDLOADER_H
|
||||
#define CWORLDLOADER_H
|
||||
|
||||
#include "../CWorld.h"
|
||||
#include "../EFormatVersion.h"
|
||||
#include <FileIO/FileIO.h>
|
||||
#include <Core/CResCache.h>
|
||||
|
||||
class CWorldLoader
|
||||
{
|
||||
CWorld *mpWorld;
|
||||
EGame mVersion;
|
||||
|
||||
CWorldLoader();
|
||||
void LoadPrimeMLVL(CInputStream& MLVL);
|
||||
void LoadReturnsMLVL(CInputStream& MLVL);
|
||||
|
||||
public:
|
||||
static CWorld* LoadMLVL(CInputStream& MLVL);
|
||||
static EGame GetFormatVersion(u32 Version);
|
||||
};
|
||||
|
||||
#endif // CWORLDLOADER_H
|
||||
Reference in New Issue
Block a user