Displaying: model_ff11.cpp
#include "stdafx.h"
#include "model_ff11.h"
#include "model_ff11_decrypt.h"
template<typename T>
static const T *get_and_incr_offset(const unsigned char *pBuffer, int &bufferOfs, const int count = 1)
{
const T *pCmd = (const T *)(pBuffer + bufferOfs);
bufferOfs += sizeof(T) * count;
return pCmd;
}
static void align_offset(int &bufferOfs, const int alignment)
{ const int alignmentMinusOne = alignment - 1;
bufferOfs = ((bufferOfs + alignmentMinusOne) & ~alignmentMinusOne);
}
class CFFXITextureHandler : public CFFXIChunkHandler
{
public:
static const int skTexNameLength = 16;
static const int skMaterialNamePad = 32;
static const char *skpShinySuffix;
static const char *skpSoftBlendSuffix;
static const char *skpNoBlendSuffix;
struct STexHeader
{
unsigned char mType;
char mName[skTexNameLength];
unsigned int mVer; int mWidth;
int mHeight;
unsigned int mUnknown[6];
unsigned int mBitsPerPalClr; };
CFFXITextureHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_Texture)
, mFlatNormalIndex(-1)
, mFlatSpecIndex(-1)
{
}
static const int skDefaultColorFixShift = 0;
static const int skDefaultAlphaFixShift = 2;
static const int skTextureVersion = 40; static const int skTextureType_DXT = 0xA1;
static const int skTextureType_Pal = 0x91;
static const int skTextureType_Pal2 = 0x01; static const int skTextureType_PalCombo = 0x81; static const int skTextureType_PalLeadingInt = 0xB1;
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize <= sizeof(STexHeader))
{
return CFFXIDat::kVCR_Invalid;
}
const STexHeader *pTexHdr = (const STexHeader *)pChunkData;
if (pTexHdr->mWidth <= 0 || pTexHdr->mWidth > 4096 ||
pTexHdr->mHeight <= 0 || pTexHdr->mHeight > 4096 ||
pTexHdr->mVer != skTextureVersion || pTexHdr->mUnknown[0] == 0 ||
pTexHdr->mUnknown[1] != 0 || pTexHdr->mUnknown[2] != 0 || pTexHdr->mUnknown[3] != 0 ||
pTexHdr->mUnknown[4] != 0 || pTexHdr->mUnknown[5] != 0)
{
return CFFXIDat::kVCR_Invalid;
}
else if (pTexHdr->mType != skTextureType_DXT && pTexHdr->mType != skTextureType_Pal && pTexHdr->mType != skTextureType_Pal2 &&
pTexHdr->mType != skTextureType_PalCombo && pTexHdr->mType != skTextureType_PalLeadingInt)
{
return CFFXIDat::kVCR_Invalid;
}
return CFFXIDat::kVCR_Supported;
}
static unsigned char *CreateRgbaFromPaletted(noeRAPI_t *pRapi, const unsigned char *pRawPalData, const unsigned char *pPixelData,
const int width, const int height, const int bitsPerColor)
{ const int palSize = 256 * (bitsPerColor / 8);
unsigned int *pPalData = (unsigned int *)pRapi->Noesis_ImageDecodeRaw(const_cast<unsigned char *>(pRawPalData), palSize, 256, 1,
(bitsPerColor == 32) ? "b8g8r8a8" : "b5g5r5a1");
unsigned int *pDst = (unsigned int *)pRapi->Noesis_UnpooledAlloc(width * height * 4);
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
const int palIndex = pPixelData[(height - y - 1) * width + x];
pDst[y * width + x] = pPalData[palIndex];
}
}
pRapi->Noesis_UnpooledFree(pPalData);
return (unsigned char *)pDst;
}
static void ShiftRgbaData(unsigned char *pTexData, const int width, const int height, const int texColorShift, const int texAlphaShift)
{
for (int pixelIndex = 0; pixelIndex < width * height; ++pixelIndex)
{
unsigned char *pPix = pTexData + pixelIndex * 4;
if (texColorShift)
{
pPix[0] = std::min<int>((int)pPix[0] << texColorShift, 255);
pPix[1] = std::min<int>((int)pPix[1] << texColorShift, 255);
pPix[2] = std::min<int>((int)pPix[2] << texColorShift, 255);
}
if (texAlphaShift)
{
pPix[3] = std::min<int>((int)pPix[3] << texAlphaShift, 255);
}
}
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{ const bool preferDxtToPalette = false;
const int texColorShift = (gpFF11Opts && gpFF11Opts->explicitColorShift) ? gpFF11Opts->fixColorShift : skDefaultColorFixShift;
const int texAlphaShift = (gpFF11Opts && gpFF11Opts->explicitAlphaShift) ? gpFF11Opts->fixAlphaShift : skDefaultAlphaFixShift;
const bool fixAlphaOrColor = (texColorShift || texAlphaShift);
const STexHeader *pTexHdr = (const STexHeader *)pChunkData;
bool copyFromSource = false;
unsigned char *pSrcData = (unsigned char *)(pTexHdr + 1);
int srcDataSize = dataSize - sizeof(STexHeader);
unsigned char *pTexData = NULL;
int texDataSize = 0;
noesisTexType_e texType = NOESISTEX_UNKNOWN;
noeRAPI_t *pRapi = dat.GetRAPI();
const int palSize = 256 * (pTexHdr->mBitsPerPalClr / 8);
switch (pTexHdr->mType)
{
case skTextureType_PalCombo:
if (!preferDxtToPalette)
{
goto PickPalOverDXT; }
else
{ const int palTextureSize = palSize + pTexHdr->mWidth * pTexHdr->mHeight;
pSrcData += palTextureSize;
srcDataSize -= palTextureSize;
} case skTextureType_DXT:
{
const int dxtType = *(const int *)pSrcData;
switch (dxtType)
{
case 'DXT1':
texType = NOESISTEX_DXT1;
break;
case 'DXT3':
texType = NOESISTEX_DXT3;
break;
case 'DXT5':
texType = NOESISTEX_DXT5;
break;
default:
break;
}
if (texType != NOESISTEX_UNKNOWN)
{
if (fixAlphaOrColor)
{ pTexData = pRapi->Noesis_ConvertDXT(pTexHdr->mWidth, pTexHdr->mHeight, const_cast<unsigned char *>(pSrcData) + 12, texType);
ShiftRgbaData(pTexData, pTexHdr->mWidth, pTexHdr->mHeight, texColorShift, texAlphaShift);
texType = NOESISTEX_RGBA32;
}
else
{
copyFromSource = true;
pTexData = const_cast<unsigned char *>(pSrcData) + 12;
texDataSize = srcDataSize - 12;
}
}
else
{
pRapi->LogOutput("WARNING: Unknown DXT texture type.\n");
}
}
break;
case skTextureType_PalLeadingInt: pSrcData += sizeof(int);
srcDataSize -= sizeof(int); case skTextureType_Pal:
case skTextureType_Pal2:
PickPalOverDXT: pTexData = CreateRgbaFromPaletted(pRapi, pSrcData, pSrcData + palSize, pTexHdr->mWidth, pTexHdr->mHeight, pTexHdr->mBitsPerPalClr);
if (fixAlphaOrColor)
{
ShiftRgbaData(pTexData, pTexHdr->mWidth, pTexHdr->mHeight, texColorShift, texAlphaShift);
}
texDataSize = pTexHdr->mWidth * pTexHdr->mHeight * 4;
texType = NOESISTEX_RGBA32;
break;
}
if (!pTexData)
{ texDataSize = pTexHdr->mWidth * pTexHdr->mHeight * 4;
pTexData = (unsigned char *)pRapi->Noesis_UnpooledAlloc(texDataSize);
memset(pTexData, 0, texDataSize);
texType = NOESISTEX_RGBA32;
}
else if (copyFromSource)
{
NoeAssert(texDataSize > 0);
const unsigned char *pSourceData = pTexData;
pTexData = (unsigned char *)pRapi->Noesis_UnpooledAlloc(texDataSize);
memcpy(pTexData, pSourceData, texDataSize);
}
char texName[skTexNameLength + skMaterialNamePad];
memcpy(texName, pTexHdr->mName, skTexNameLength);
texName[skTexNameLength] = 0; if (mFlatNormalIndex == -1 && (!gpFF11Opts || !gpFF11Opts->noShinyMaterials))
{
NoeAssert(mFlatSpecIndex == -1);
unsigned char *pFlatNormalData = (unsigned char *)pRapi->Noesis_UnpooledAlloc(4 * 4 * 4);
for (int pixelIndex = 0; pixelIndex < 4 * 4; ++pixelIndex)
{
unsigned char *pPixel = pFlatNormalData + pixelIndex * 4;
pPixel[0] = 127;
pPixel[1] = 127;
pPixel[2] = 255;
pPixel[3] = 255;
}
noesisTex_t *pFlatNormalTex = pRapi->Noesis_TextureAlloc("__flat_normal", 4, 4, pFlatNormalData, NOESISTEX_RGBA32);
mFlatNormalIndex = mTextures.Num();
pFlatNormalTex->shouldFreeData = true;
mTextures.Append(pFlatNormalTex);
unsigned char *pFlatSpecData = (unsigned char *)pRapi->Noesis_UnpooledAlloc(4 * 4 * 4);
memset(pFlatSpecData, 0xFF, 4 * 4 * 4);
noesisTex_t *pFlatSpecTex = pRapi->Noesis_TextureAlloc("__flat_spec", 4, 4, pFlatSpecData, NOESISTEX_RGBA32);
mFlatSpecIndex = mTextures.Num();
pFlatSpecTex->shouldFreeData = true;
mTextures.Append(pFlatSpecTex);
}
noesisTex_t *pTex = pRapi->Noesis_TextureAllocEx(texName, pTexHdr->mWidth, pTexHdr->mHeight, pTexData, texDataSize, texType, 0, 0);
pTex->shouldFreeData = true;
const int defaultMtlFlags = (gpFF11Opts && gpFF11Opts->forceCull) ? 0 : NMATFLAG_TWOSIDED;
noesisMaterial_t *pMat = pRapi->Noesis_GetMaterialList(1, true);
pMat->name = pRapi->Noesis_PooledString(texName);
pMat->texIdx = mTextures.Num();
pMat->flags |= defaultMtlFlags; pMat->noDefaultBlend = true;
pMat->alphaTest = 0.5f;
char mtlVariantName[skTexNameLength + skMaterialNamePad]; if (mFlatNormalIndex >= 0 && mFlatSpecIndex >= 0)
{
noesisMaterial_t *pMatShiny = pRapi->Noesis_GetMaterialList(1, true);
sprintf_s(mtlVariantName, "%s%s", texName, skpShinySuffix);
pMatShiny->name = pRapi->Noesis_PooledString(mtlVariantName);
pMatShiny->texIdx = mTextures.Num();
pMatShiny->flags |= defaultMtlFlags;
pMatShiny->noDefaultBlend = true;
pMatShiny->alphaTest = 0.5f;
pMatShiny->normalTexIdx = mFlatNormalIndex;
pMatShiny->specularTexIdx = mFlatSpecIndex;
pMatShiny->specular[0] = 0.5f;
pMatShiny->specular[1] = 0.5f;
pMatShiny->specular[2] = 0.5f;
pMatShiny->specular[3] = 64.0f;
mMaterials.Append(pMatShiny);
} noesisMaterial_t *pMatSoftBlend = pRapi->Noesis_GetMaterialList(1, true);
sprintf_s(mtlVariantName, "%s%s", texName, skpSoftBlendSuffix);
pMatSoftBlend->name = pRapi->Noesis_PooledString(mtlVariantName);
pMatSoftBlend->texIdx = mTextures.Num();
pMatSoftBlend->flags |= defaultMtlFlags;
pMatSoftBlend->noDefaultBlend = false;
mMaterials.Append(pMatSoftBlend);
noesisMaterial_t *pMatNoBlend = pRapi->Noesis_GetMaterialList(1, true);
sprintf_s(mtlVariantName, "%s%s", texName, skpNoBlendSuffix);
pMatNoBlend->name = pRapi->Noesis_PooledString(mtlVariantName);
pMatNoBlend->texIdx = mTextures.Num();
pMatNoBlend->flags |= defaultMtlFlags;
pMatNoBlend->noDefaultBlend = true;
mMaterials.Append(pMatNoBlend);
mTextures.Append(pTex);
mMaterials.Append(pMat);
return true;
}
CArrayList<noesisTex_t *> &Textures() { return mTextures; }
CArrayList<noesisMaterial_t *> &Materials() { return mMaterials; }
protected:
CArrayList<noesisTex_t *> mTextures;
CArrayList<noesisMaterial_t *> mMaterials;
int mFlatNormalIndex;
int mFlatSpecIndex;
};
const char *CFFXITextureHandler::skpShinySuffix = "_explicitshiny";
const char *CFFXITextureHandler::skpSoftBlendSuffix = "_explicitsoftblend";
const char *CFFXITextureHandler::skpNoBlendSuffix = "_explicitnoblend";
class CFFXISkelHandler : public CFFXIChunkHandler
{
public:
struct SSkelHeader
{
short mUnknown;
short mBoneCount;
};
struct SSkelBone
{
unsigned char mParentIndex;
unsigned char mUnknown;
RichQuat mQuat;
RichVec3 mTran;
};
struct SInterpretedSkel
{
explicit SInterpretedSkel(modelBone_t *pBones, const int boneCount)
: mpBones(pBones)
, mBoneCount(boneCount)
{
}
modelBone_t *mpBones;
int mBoneCount;
};
typedef std::vector<SInterpretedSkel> TSInterpretedSkelList;
CFFXISkelHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_Skeleton)
{
}
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize <= sizeof(SSkelHeader))
{
return CFFXIDat::kVCR_Invalid;
}
const SSkelHeader *pSkelHdr = (const SSkelHeader *)pChunkData;
if (pSkelHdr->mBoneCount <= 0)
{
return CFFXIDat::kVCR_Invalid;
}
const int expectedBoneEndOfs = sizeof(SSkelHeader) + sizeof(SSkelBone) * pSkelHdr->mBoneCount;
if (expectedBoneEndOfs <= 0 || expectedBoneEndOfs > dataSize)
{
return CFFXIDat::kVCR_Invalid;
}
return CFFXIDat::kVCR_Supported;
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{
const SSkelHeader *pSkelHdr = (const SSkelHeader *)pChunkData;
const SSkelBone *pSkelBones = (const SSkelBone *)(pSkelHdr + 1);
noeRAPI_t *pRapi = dat.GetRAPI();
modelBone_t *pBones = pRapi->Noesis_AllocBones(pSkelHdr->mBoneCount);
for (int boneIndex = 0; boneIndex < pSkelHdr->mBoneCount; ++boneIndex)
{
const SSkelBone *pSkelBone = pSkelBones + boneIndex;
modelBone_t *pBone = pBones + boneIndex;
RichMat43 &boneMat = (RichMat43 &)pBone->mat;
pBone->index = boneIndex;
sprintf_s(pBone->name, "bone%04i", boneIndex);
boneMat = pSkelBone->mQuat.ToMat43(true);
boneMat[3] = pSkelBone->mTran;
pBone->eData.parent = (pSkelBone->mParentIndex != boneIndex) ? pBones + pSkelBone->mParentIndex : NULL;
}
pRapi->rpgMultiplyBones(pBones, pSkelHdr->mBoneCount);
mSkeletons.push_back(SInterpretedSkel(pBones, pSkelHdr->mBoneCount));
return true;
}
TSInterpretedSkelList &Skeletons() { return mSkeletons; }
protected:
TSInterpretedSkelList mSkeletons;
};
class CFFXIAnimHandler : public CFFXIChunkHandler
{
public:
struct SAnimHeader
{
unsigned short mUnknown;
unsigned short mElemCount;
unsigned short mFrameCount;
float mSpeedScale; }; struct SAnimElemHeader
{
int mBoneIndex;
int mQuatIndex[4];
RichQuat mQuatBase;
int mTranIndex[3];
RichVec3 mTranBase;
int mScaleIndex[3];
RichVec3 mScaleBase;
};
struct SAnimHeaderData
{
explicit SAnimHeaderData(const SAnimHeader *pAnimHdr, const int animDataSize, const char *pName)
: mpAnimHdr(pAnimHdr)
, mAnimDataSize(animDataSize)
{
memcpy(mName, pName, 4);
mName[4] = 0;
}
const SAnimHeader *mpAnimHdr;
int mAnimDataSize;
char mName[8];
};
typedef std::vector<SAnimHeaderData> TAnimHeaderList;
CFFXIAnimHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_Animation)
{
}
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize <= sizeof(SAnimHeader))
{
return CFFXIDat::kVCR_Invalid;
}
const SAnimHeader *pAnimHdr = (const SAnimHeader *)pChunkData;
const int expectedElemEndOfs = sizeof(SAnimHeader) + sizeof(SAnimElemHeader) * pAnimHdr->mElemCount;
if (expectedElemEndOfs <= 0 || expectedElemEndOfs > dataSize)
{
return CFFXIDat::kVCR_Invalid;
} return CFFXIDat::kVCR_Supported;
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{
const SAnimHeader *pAnimHdr = (const SAnimHeader *)pChunkData;
mAnimHeaderList.push_back(SAnimHeaderData(pAnimHdr, dataSize, chunk.mName));
return true;
}
noesisAnim_t *ConstructAnimations(noeRAPI_t *pRapi, const CFFXISkelHandler::SInterpretedSkel *pSkel) const
{
CArrayList<noesisAnim_t *> anims;
for (TAnimHeaderList::const_iterator it = mAnimHeaderList.begin(); it != mAnimHeaderList.end(); ++it)
{
const SAnimHeaderData &animHeaderData = *it;
const SAnimHeader *pAnimHdr = animHeaderData.mpAnimHdr;
if (pAnimHdr->mFrameCount > 0 && pAnimHdr->mElemCount > 0)
{
const int transformCount = pSkel->mBoneCount * pAnimHdr->mFrameCount; RichMat43 *pBaseMats = (RichMat43 *)pRapi->Noesis_UnpooledAlloc(sizeof(RichMat43) * pSkel->mBoneCount);
for (int boneIndex = 0; boneIndex < pSkel->mBoneCount; ++boneIndex)
{
const modelBone_t *pBone = pSkel->mpBones + boneIndex;
const RichMat43 &boneMat = (const RichMat43 &)pBone->mat;
if (!pBone->eData.parent)
{
pBaseMats[boneIndex] = boneMat;
}
else
{
const RichMat43 &parentBoneMat = (const RichMat43 &)pBone->eData.parent->mat;
pBaseMats[boneIndex] = boneMat * parentBoneMat.GetInverse();
}
} RichMat43 *pMats = (RichMat43 *)pRapi->Noesis_UnpooledAlloc(sizeof(RichMat43) * transformCount);
for (int frameIndex = 0; frameIndex < pAnimHdr->mFrameCount; ++frameIndex)
{
memcpy(pMats + frameIndex * pSkel->mBoneCount, pBaseMats, sizeof(RichMat43) * pSkel->mBoneCount);
} const SAnimElemHeader *pElems = (const SAnimElemHeader *)(pAnimHdr + 1);
const float *pAnimData = (const float *)pElems;
RichQuat frameQ;
RichVec3 frameT;
RichVec3 frameS;
for (int elemIndex = 0; elemIndex < pAnimHdr->mElemCount; ++elemIndex)
{
const SAnimElemHeader *pElem = pElems + elemIndex;
if (pElem->mBoneIndex < 0 || pElem->mBoneIndex >= pSkel->mBoneCount)
{
pRapi->LogOutput("WARNING: Out of range animation element! (index %i, but only %i bones)\n",
pElem->mBoneIndex, pSkel->mBoneCount);
continue;
}
for (int frameIndex = 0; frameIndex < pAnimHdr->mFrameCount; ++frameIndex)
{
RichMat43 *pFrameMats = pMats + pSkel->mBoneCount * frameIndex;
RichMat43 &mat = pFrameMats[pElem->mBoneIndex];
if (pElem->mQuatIndex[0] < 0 || pElem->mQuatIndex[1] < 0 || pElem->mQuatIndex[2] < 0 || pElem->mQuatIndex[3] < 0)
{ mat = pBaseMats[pElem->mBoneIndex];
}
else
{
frameQ[0] = (pElem->mQuatIndex[0] > 0) ? pAnimData[pElem->mQuatIndex[0] + frameIndex] : pElem->mQuatBase[0];
frameQ[1] = (pElem->mQuatIndex[1] > 0) ? pAnimData[pElem->mQuatIndex[1] + frameIndex] : pElem->mQuatBase[1];
frameQ[2] = (pElem->mQuatIndex[2] > 0) ? pAnimData[pElem->mQuatIndex[2] + frameIndex] : pElem->mQuatBase[2];
frameQ[3] = (pElem->mQuatIndex[3] > 0) ? pAnimData[pElem->mQuatIndex[3] + frameIndex] : pElem->mQuatBase[3];
frameT[0] = (pElem->mTranIndex[0] > 0) ? pAnimData[pElem->mTranIndex[0] + frameIndex] : pElem->mTranBase[0];
frameT[1] = (pElem->mTranIndex[1] > 0) ? pAnimData[pElem->mTranIndex[1] + frameIndex] : pElem->mTranBase[1];
frameT[2] = (pElem->mTranIndex[2] > 0) ? pAnimData[pElem->mTranIndex[2] + frameIndex] : pElem->mTranBase[2];
frameS[0] = (pElem->mScaleIndex[0] > 0) ? pAnimData[pElem->mScaleIndex[0] + frameIndex] : pElem->mScaleBase[0];
frameS[1] = (pElem->mScaleIndex[1] > 0) ? pAnimData[pElem->mScaleIndex[1] + frameIndex] : pElem->mScaleBase[1];
frameS[2] = (pElem->mScaleIndex[2] > 0) ? pAnimData[pElem->mScaleIndex[2] + frameIndex] : pElem->mScaleBase[2];
const RichVec3 ¢erPoint = pBaseMats[pElem->mBoneIndex][3]; mat = pBaseMats[pElem->mBoneIndex];
mat.TransformQST(¢erPoint, &frameQ, &frameS, ¢erPoint, &frameQ, &frameT);
}
}
}
noesisAnim_t *pAnim = pRapi->rpgAnimFromBonesAndMatsFinish(pSkel->mpBones, pSkel->mBoneCount, (modelMatrix_t *)pMats,
pAnimHdr->mFrameCount, pAnimHdr->mSpeedScale * 30.0f);
pRapi->Noesis_UnpooledFree(pBaseMats);
pRapi->Noesis_UnpooledFree(pMats);
if (pAnim)
{
pAnim->filename = pRapi->Noesis_PooledString(const_cast<char *>(animHeaderData.mName));
pAnim->flags |= NANIMFLAG_FILENAMETOSEQ;
anims.Append(pAnim);
}
}
}
return (anims.Num() > 0) ? pRapi->Noesis_AnimFromAnimsList(anims, anims.Num()) : NULL;
}
bool AnimDataIsPresent() const { return mAnimHeaderList.size() > 0; }
protected:
TAnimHeaderList mAnimHeaderList;
};
class CFFXIGeoHandler : public CFFXIChunkHandler
{
public:
struct SGeoHeader
{ short mUnknown1;
unsigned short mVertAndBoneRefFlag;
unsigned short mMirror;
int mDrawDataOfs;
unsigned short mDrawDataSize;
int mBoneRefOfs;
unsigned short mBoneRefCount; int mWeightedVertCountOfs;
unsigned short mMaxWeightsPerVertex; int mWeightDataOfs;
unsigned short mWeightDataCount;
int mVertOfs;
unsigned short mVertDataSize;
int mUnknown2Ofs; unsigned short mUnknown3;
unsigned short mUnknown4;
unsigned short mUnknown5;
int mUnknown6Ofs; unsigned short mUnknown7;
int mUnknown8;
int mUnknown9;
unsigned short mUnknown10;
unsigned short mUnknown11;
};
struct SGeoDrawState
{
SGeoDrawState()
{
mEnableShinyMat = false;
mMaterialName[0] = 0;
}
bool mEnableShinyMat;
char mMaterialName[CFFXITextureHandler::skTexNameLength + 1];
};
struct SGeoTriPrim
{
unsigned short mIndices[3];
RichVec2 mUVs[3];
};
struct SGeoStripPrim
{
unsigned short mIndex;
RichVec2 mUV;
};
struct SGeoWeightData
{
unsigned short mBoneIndexPass0 : 7; unsigned short mBoneIndexPass1 : 7; unsigned short mMirrorAxis : 2; };
struct SGeoNativeDrawState
{
unsigned char mColor[4]; float mUnknown1[2];
unsigned int mUnknown3;
float mUnknown4[4];
unsigned int mUnknown5;
float mSpecular[2];
};
struct SGeoHeaderData
{
explicit SGeoHeaderData(const SGeoHeader *pGeoHdr, const int geoDataSize, const char *pName)
: mpGeoHdr(pGeoHdr)
, mGeoDataSize(geoDataSize)
{
memcpy(mName, pName, 4);
mName[4] = 0;
}
const SGeoHeader *mpGeoHdr;
int mGeoDataSize;
char mName[8];
};
typedef std::vector<SGeoHeaderData> TGeoHeaderList;
static const int skVertFlag_NoNormals = 0x7F; static const int skVertFlag_UseBoneRefs = 0x80;
static const int skDrawCmd_SetMaterial = 0x8000;
static const int skDrawCmd_TriList = 0x0054;
static const int skDrawCmd_TriStrip = 0x5453;
static const int skDrawCmd_DrawState = 0x8010;
static const int skDrawCmd_Unknown2 = 0x4353;
static const int skDrawCmd_Unknown3 = 0x0043;
static const int skDrawCmd_End = 0xFFFF;
static const int skTriCWIdx[3];
static const int skTriCCWIdx[3];
static const RichMat44 skMirrorTransforms[3];
CFFXIGeoHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_Geo)
{
}
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize <= sizeof(SGeoHeader))
{
return CFFXIDat::kVCR_Invalid;
}
const SGeoHeader *pGeoHdr = (const SGeoHeader *)pChunkData;
if (pGeoHdr->mDrawDataOfs <= 0 || (pGeoHdr->mDrawDataOfs * 2) >= dataSize ||
pGeoHdr->mVertOfs <= 0 || (pGeoHdr->mVertOfs * 2) >= dataSize ||
(pGeoHdr->mBoneRefOfs * 2) >= dataSize ||
(pGeoHdr->mWeightedVertCountOfs * 2) >= dataSize ||
(pGeoHdr->mWeightDataOfs * 2) >= dataSize ||
pGeoHdr->mDrawDataSize == 0 || pGeoHdr->mVertDataSize == 0)
{
return CFFXIDat::kVCR_Invalid;
}
else if (pGeoHdr->mWeightedVertCountOfs > 0 && pGeoHdr->mMaxWeightsPerVertex != 2)
{ return CFFXIDat::kVCR_Invalid;
}
return CFFXIDat::kVCR_Supported;
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{
const SGeoHeader *pGeoHdr = (const SGeoHeader *)pChunkData;
mGeoHeaderList.push_back(SGeoHeaderData(pGeoHdr, dataSize, chunk.mName));
return true;
}
void RenderGeoData(noeRAPI_t *pRapi, const CFFXISkelHandler::SInterpretedSkel *pSkel) const
{
for (TGeoHeaderList::const_iterator it = mGeoHeaderList.begin(); it != mGeoHeaderList.end(); ++it)
{
const SGeoHeaderData &geoHeaderData = *it;
const SGeoHeader *pGeoHdr = geoHeaderData.mpGeoHdr;
const unsigned short *pSData = (const unsigned short *)pGeoHdr;
pRapi->rpgSetName(const_cast<char *>(geoHeaderData.mName));
SGeoDrawState drawState;
const unsigned char *pDrawCommands = (const unsigned char *)(pSData + pGeoHdr->mDrawDataOfs);
const int drawCommandEndOfs = (pGeoHdr->mDrawDataSize << 1);
const int drawCommandLastCommandOfs = drawCommandEndOfs - sizeof(unsigned short);
const int passCount = (pGeoHdr->mMirror) ? 2 : 1;
for (int passIndex = 0; passIndex < passCount; ++passIndex)
{
const bool isMirroring = (passIndex > 0);
int drawCommandOfs = 0;
while (drawCommandOfs <= drawCommandLastCommandOfs)
{
const unsigned short cmdType = *get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs);
switch (cmdType)
{
case skDrawCmd_DrawState:
{ const SGeoNativeDrawState *pNativeState = get_and_incr_offset<SGeoNativeDrawState>(pDrawCommands, drawCommandOfs); drawState.mEnableShinyMat = (pNativeState->mSpecular[0] < 128.0f || pNativeState->mSpecular[1] != 0.0f);
UpdateDrawState(pRapi, drawState);
}
break;
case skDrawCmd_SetMaterial:
{
const char *pMatNameData = get_and_incr_offset<char>(pDrawCommands, drawCommandOfs, CFFXITextureHandler::skTexNameLength);
memcpy(drawState.mMaterialName, pMatNameData, CFFXITextureHandler::skTexNameLength);
drawState.mMaterialName[CFFXITextureHandler::skTexNameLength] = 0;
UpdateDrawState(pRapi, drawState);
}
break;
case skDrawCmd_TriList:
{
const unsigned short primCount = *get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs);
const int primDataSize = sizeof(SGeoTriPrim) * primCount;
const SGeoTriPrim *pTris = get_and_incr_offset<SGeoTriPrim>(pDrawCommands, drawCommandOfs, primCount);
pRapi->rpgBegin(RPGEO_TRIANGLE);
const int *pTriWindIdx = (isMirroring) ? skTriCCWIdx : skTriCWIdx;
for (int triIdx = 0; triIdx < primCount; ++triIdx)
{
const SGeoTriPrim *pTri = pTris + triIdx;
for (int triVertIdx = 0; triVertIdx < 3; ++triVertIdx)
{
const int windIdx = pTriWindIdx[triVertIdx]; pRapi->rpgVertUV2f(const_cast<float *>(pTri->mUVs[windIdx].v), 0);
PlotVertex(pRapi, pTri->mIndices[windIdx], isMirroring, pGeoHdr, pSkel);
}
}
pRapi->rpgEnd();
}
break;
case skDrawCmd_TriStrip:
{
const unsigned short primCount = *get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs);
const int stripVertCount = primCount - 1;
const SGeoTriPrim *pFirstTri = get_and_incr_offset<SGeoTriPrim>(pDrawCommands, drawCommandOfs);
const SGeoStripPrim *pStripVerts = get_and_incr_offset<SGeoStripPrim>(pDrawCommands, drawCommandOfs, stripVertCount);
const rpgeoPrimType_e stripType = (isMirroring) ? RPGEO_TRIANGLE_STRIP_FLIPPED : RPGEO_TRIANGLE_STRIP;
pRapi->rpgBegin(stripType);
for (int triVertIdx = 0; triVertIdx < 3; ++triVertIdx)
{
pRapi->rpgVertUV2f(const_cast<float *>(pFirstTri->mUVs[triVertIdx].v), 0);
PlotVertex(pRapi, pFirstTri->mIndices[triVertIdx], isMirroring, pGeoHdr, pSkel);
}
for (int stripIdx = 0; stripIdx < stripVertCount; ++stripIdx)
{
const SGeoStripPrim *pStripVert = pStripVerts + stripIdx;
pRapi->rpgVertUV2f(const_cast<float *>(pStripVert->mUV.v), 0);
PlotVertex(pRapi, pStripVert->mIndex, isMirroring, pGeoHdr, pSkel);
}
pRapi->rpgEnd();
}
break;
case skDrawCmd_Unknown2:
{
const unsigned short primCount = *get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs);
get_and_incr_offset<unsigned char>(pDrawCommands, drawCommandOfs, 8);
get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs, primCount);
}
break;
case skDrawCmd_Unknown3:
{
const unsigned short primCount = *get_and_incr_offset<unsigned short>(pDrawCommands, drawCommandOfs);
get_and_incr_offset<unsigned char>(pDrawCommands, drawCommandOfs, primCount * 10);
}
break;
default:
{
NoeAssert(cmdType == skDrawCmd_End);
drawCommandOfs = drawCommandEndOfs;
}
break;
}
}
}
}
if (pSkel)
{
pRapi->rpgSetExData_Bones(pSkel->mpBones, pSkel->mBoneCount);
}
}
bool GeoDataIsPresent() const { return mGeoHeaderList.size() > 0; }
protected:
static void PlotVertex(noeRAPI_t *pRapi, const int index, const bool isMirroring,
const SGeoHeader *pGeoHdr, const CFFXISkelHandler::SInterpretedSkel *pSkel)
{
const unsigned short *pSData = (const unsigned short *)pGeoHdr;
const bool isSkinned = (pSkel && pGeoHdr->mWeightedVertCountOfs > 0 && pGeoHdr->mWeightDataOfs > 0);
const bool noNormals = (pGeoHdr->mVertAndBoneRefFlag & skVertFlag_NoNormals) != 0;
int oneWeightVertCount;
if (pGeoHdr->mWeightedVertCountOfs > 0)
{
const unsigned short *pWeightedCounts = pSData + pGeoHdr->mWeightedVertCountOfs;
oneWeightVertCount = pWeightedCounts[0];
}
else
{ oneWeightVertCount = 0x10000;
}
const int weightCount = (index < oneWeightVertCount) ? 1 : 2;
const int oneWeightVertSize = (noNormals) ? sizeof(float) * 3 : sizeof(float) * 6;
const int twoWeightVertSize = oneWeightVertSize * 2 + sizeof(float) * 2;
const int oneWeightVertElemCount = (oneWeightVertSize >> 2);
const int twoWeightVertElemCount = (twoWeightVertSize >> 2);
const int firstTwoWeightElemIndex = oneWeightVertCount * oneWeightVertElemCount;
const float *pVertElems = (const float *)(pSData + pGeoHdr->mVertOfs);
const float *pVertData = (weightCount == 1) ? pVertElems + index * oneWeightVertElemCount :
pVertElems + firstTwoWeightElemIndex + (index - oneWeightVertCount) * twoWeightVertElemCount;
const float *pNrmVertData = (noNormals) ? NULL : pVertData + 3 * weightCount + ((weightCount > 1) ? weightCount : 0); if (!isSkinned)
{
pRapi->rpgVertBoneIndexI(NULL, 0);
pRapi->rpgVertBoneWeightF(NULL, 0);
if (pNrmVertData)
{
const RichVec3 nrm(pNrmVertData[0 * weightCount], pNrmVertData[1 * weightCount], pNrmVertData[2 * weightCount]);
pRapi->rpgVertNormal3f(const_cast<float *>(nrm.v));
}
else
{
pRapi->rpgVertNormal3f(NULL);
}
const RichVec3 pos(pVertData[0 * weightCount], pVertData[1 * weightCount], pVertData[2 * weightCount]);
pRapi->rpgVertex3f(const_cast<float *>(pos.v));
}
else
{
const unsigned short *pBoneRefs = ((pGeoHdr->mVertAndBoneRefFlag & skVertFlag_UseBoneRefs) && pGeoHdr->mBoneRefOfs > 0) ?
pSData + pGeoHdr->mBoneRefOfs : NULL;
static const float skDefaultWeights[2] = { 1.0f, 0.0f };
const SGeoWeightData *pWeightDatas = (const SGeoWeightData *)(pSData + pGeoHdr->mWeightDataOfs) + index * 2;
int boneIndices[2] = { 0, 0 };
RichMat44 skinMats[2]; for (int weightIndex = 0; weightIndex < weightCount; ++weightIndex)
{
const SGeoWeightData *pWeightData = pWeightDatas + weightIndex;
int &boneIndex = boneIndices[weightIndex];
boneIndex = (isMirroring) ? pWeightData->mBoneIndexPass1 : pWeightData->mBoneIndexPass0;
NoeAssert(!pBoneRefs || boneIndex < pGeoHdr->mBoneRefCount);
if (pBoneRefs && boneIndex < pGeoHdr->mBoneRefCount)
{
boneIndex = pBoneRefs[boneIndex];
}
NoeAssert(boneIndex >= 0 && boneIndex < pSkel->mBoneCount);
skinMats[weightIndex] = ((RichMat43 *)&pSkel->mpBones[boneIndex].mat)->ToMat44();
if (isMirroring && pWeightData->mMirrorAxis)
{
skinMats[weightIndex] = skMirrorTransforms[pWeightData->mMirrorAxis - 1] * skinMats[weightIndex];
}
}
const float *pWeightValues = (weightCount > 1) ? pVertData + 3 * weightCount : skDefaultWeights; pRapi->rpgVertBoneIndexI(const_cast<int *>(boneIndices), 2);
pRapi->rpgVertBoneWeightF(const_cast<float *>(pWeightValues), 2); RichVec4 transformedPos;
RichVec3 transformedNrm;
for (int weightIndex = 0; weightIndex < weightCount; ++weightIndex)
{
const RichMat44 &skinMat = skinMats[weightIndex];
const RichVec4 pos(
pVertData[weightIndex + 0 * weightCount],
pVertData[weightIndex + 1 * weightCount],
pVertData[weightIndex + 2 * weightCount], pWeightValues[weightIndex]
);
transformedPos += skinMat.TransformVec4(pos);
if (pNrmVertData)
{
const RichVec3 nrm(
pNrmVertData[weightIndex + 0 * weightCount],
pNrmVertData[weightIndex + 1 * weightCount],
pNrmVertData[weightIndex + 2 * weightCount]
); transformedNrm += skinMat.TransformNormal(nrm) * pWeightValues[weightIndex];
}
}
if (pNrmVertData)
{
transformedNrm.Normalize();
pRapi->rpgVertNormal3f(transformedNrm.v);
}
else
{
pRapi->rpgVertNormal3f(NULL);
}
pRapi->rpgVertex3f(transformedPos.v);
}
}
static void UpdateDrawState(noeRAPI_t *pRapi, SGeoDrawState &drawState)
{
if (drawState.mEnableShinyMat && (!gpFF11Opts || !gpFF11Opts->noShinyMaterials))
{
char materialName[CFFXITextureHandler::skTexNameLength + CFFXITextureHandler::skMaterialNamePad];
sprintf_s(materialName, "%s%s", drawState.mMaterialName, CFFXITextureHandler::skpShinySuffix);
pRapi->rpgSetMaterial(materialName);
}
else
{
pRapi->rpgSetMaterial(drawState.mMaterialName);
}
}
TGeoHeaderList mGeoHeaderList;
};
const RichMat44 CFFXIGeoHandler::skMirrorTransforms[3] =
{
RichMat44(
-RichVec4(g_identityMatrix4x4.c1),
RichVec4(g_identityMatrix4x4.c2),
RichVec4(g_identityMatrix4x4.c3),
RichVec4(g_identityMatrix4x4.c4)
),
RichMat44(
RichVec4(g_identityMatrix4x4.c1),
-RichVec4(g_identityMatrix4x4.c2),
RichVec4(g_identityMatrix4x4.c3),
RichVec4(g_identityMatrix4x4.c4)
),
RichMat44(
RichVec4(g_identityMatrix4x4.c1),
RichVec4(g_identityMatrix4x4.c2),
-RichVec4(g_identityMatrix4x4.c3),
RichVec4(g_identityMatrix4x4.c4)
)
};
const int CFFXIGeoHandler::skTriCWIdx[3] = { 0, 1, 2 };
const int CFFXIGeoHandler::skTriCCWIdx[3] = { 2, 1, 0 };
class CFFXIMapHandler : public CFFXIChunkHandler
{
public:
struct SMapHeader
{
unsigned char mHeaderData[4];
unsigned int mObjectCount : 24;
unsigned int mUnknown1 : 8;
unsigned int mUnknown2[6];
};
static const int skObjectNameLength = 16;
struct SMapObject
{
char mObjectName[skObjectNameLength];
RichVec3 mTrans;
float mRawAngles[3];
RichVec3 mScale;
RichVec4 mVec;
int mData2[8];
};
struct SInterpretedMapObject
{
explicit SInterpretedMapObject(const SMapObject *pMapObject)
{
mTransform = RichAngles(pMapObject->mRawAngles, true).ToMat43_XYZ();
mTransform[3] = pMapObject->mTrans;
mTransform[0][0] *= pMapObject->mScale[0];
mTransform[1][0] *= pMapObject->mScale[0];
mTransform[2][0] *= pMapObject->mScale[0];
mTransform[0][1] *= pMapObject->mScale[1];
mTransform[1][1] *= pMapObject->mScale[1];
mTransform[2][1] *= pMapObject->mScale[1];
mTransform[0][2] *= pMapObject->mScale[2];
mTransform[1][2] *= pMapObject->mScale[2];
mTransform[2][2] *= pMapObject->mScale[2];
mBackwardWinding = (pMapObject->mScale[0] * pMapObject->mScale[1] * pMapObject->mScale[2]) < 0.0f;
memcpy(mObjectName, pMapObject->mObjectName, skObjectNameLength);
mObjectFlags[0] = pMapObject->mData2[0];
mObjectFlags[1] = pMapObject->mData2[4];
mVec = pMapObject->mVec;
};
char mObjectName[skObjectNameLength];
RichMat43 mTransform;
bool mBackwardWinding;
unsigned int mObjectFlags[2];
RichVec4 mVec;
};
typedef std::vector<SInterpretedMapObject> TMapObjectList;
CFFXIMapHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_Map)
{
}
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize < 16)
{
return CFFXIDat::kVCR_Invalid;
}
else if (pChunkData[3] < 0x1B)
{
if ((*(unsigned int *)pChunkData & 0xFFFFFF) != 0x00425a4d) {
return CFFXIDat::kVCR_Invalid;
}
}
else
{
const int dataLen = ((*(const unsigned int *)pChunkData) & 0xFFFFFF);
if (dataLen > dataSize ||
(dataLen + 16) < dataSize)
{
return CFFXIDat::kVCR_Invalid;
}
}
return CFFXIDat::kVCR_Supported;
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{
noeRAPI_t *pRapi = dat.GetRAPI();
unsigned char *pDecrypted = (unsigned char *)pRapi->Noesis_UnpooledAlloc(dataSize);
memcpy(pDecrypted, pChunkData, dataSize);
Model_FF11_DecryptMZB(pDecrypted, dataSize);
const SMapHeader *pMapHdr = (const SMapHeader *)pDecrypted;
const SMapObject *pMapObjects = (const SMapObject *)(pMapHdr + 1);
mMapObjects.reserve(mMapObjects.size() + pMapHdr->mObjectCount);
for (unsigned int objectIndex = 0; objectIndex < pMapHdr->mObjectCount; ++objectIndex)
{
const SMapObject *pMapObject = pMapObjects + objectIndex;
mMapObjects.push_back(SInterpretedMapObject(pMapObject));
}
pRapi->Noesis_UnpooledFree(pDecrypted);
return true;
}
TMapObjectList &MapObjects() { return mMapObjects; }
protected:
TMapObjectList mMapObjects;
};
class CFFXIMapGeoHandler : public CFFXIChunkHandler
{
public:
struct SMapGeoHeader
{
unsigned char mHeaderData[4];
unsigned int mUnknown1;
char mUnknownName[8];
char mObjectName[CFFXIMapHandler::skObjectNameLength];
};
struct SMapGeoData
{
explicit SMapGeoData(const SMapGeoHeader *pMapGeoHdr, noeRAPI_t *pAllocatingInstance, const int dataSize)
: mpMapGeoHdr(pMapGeoHdr)
, mpAllocatingInstance(pAllocatingInstance)
, mDataSize(dataSize)
{
}
const SMapGeoHeader *mpMapGeoHdr;
noeRAPI_t *mpAllocatingInstance;
int mDataSize;
};
typedef std::vector<SMapGeoData> TMapGeoList;
struct SDrawHeader
{
int mSegCount;
float mBounds[6];
int mFlag; };
static const int skMapGeoFlag_BlendHardAlpha = 0x2000;
static const int skMapGeoFlag_BlendTerrain = 0x8000;
static const int skDefaultVertColorFixShift = 1;
static const int skDefaultVertAlphaFixShift = 1;
CFFXIMapGeoHandler()
: CFFXIChunkHandler(CFFXIDat::skChunkType_MapGeo)
, mMapGeoHash(CFFXIMapHandler::skObjectNameLength)
{
}
virtual ~CFFXIMapGeoHandler()
{
for (TMapGeoList::iterator it = mMapGeoList.begin(); it != mMapGeoList.end(); ++it)
{
SMapGeoData &geoData = *it;
geoData.mpAllocatingInstance->Noesis_UnpooledFree((void *)geoData.mpMapGeoHdr);
}
}
virtual CFFXIDat::EValidateChunkResult ValidateChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize) const
{
if (dataSize < 16)
{
return CFFXIDat::kVCR_Invalid;
}
else if (pChunkData[3] < 5)
{
if ((*(unsigned int *)pChunkData & 0xFFFFFF) != 0x00424d4d) {
return CFFXIDat::kVCR_Invalid;
}
}
else
{
const int dataLen = ((*(const unsigned int *)pChunkData) & 0xFFFFFF);
if (dataLen > dataSize ||
(dataLen + 16) < dataSize)
{
return CFFXIDat::kVCR_Invalid;
}
}
return CFFXIDat::kVCR_Supported;
}
virtual bool HandleChunk(const CFFXIDat &dat, const CFFXIDat::SChunk &chunk,
const unsigned char *pChunkData, const int dataSize)
{
noeRAPI_t *pRapi = dat.GetRAPI();
unsigned char *pDecrypted = (unsigned char *)pRapi->Noesis_UnpooledAlloc(dataSize);
memcpy(pDecrypted, pChunkData, dataSize);
Model_FF11_DecryptMMB(pDecrypted, dataSize);
const SMapGeoHeader *pMapGeoHdr = (const SMapGeoHeader *)pDecrypted;
const int index = mMapGeoList.size();
mMapGeoList.push_back(SMapGeoData(pMapGeoHdr, pRapi, dataSize));
mMapGeoHash.FindOrAddResource(pMapGeoHdr->mObjectName, index, true);
return true;
}
void RenderMapObjectGeo(noeRAPI_t *pRapi, const int index, const RichMat43 &transform, const bool backwardWinding,
const CFFXIMapHandler::SInterpretedMapObject *pMapObject)
{
SMapGeoData &geoData = mMapGeoList[index];
const SMapGeoHeader *pMapGeoHdr = geoData.mpMapGeoHdr;
const unsigned char *pDrawData = (const unsigned char *)pMapGeoHdr;
const int endDrawOfs = geoData.mDataSize - sizeof(SDrawHeader);
const unsigned int objectFlags0 = (pMapObject) ? pMapObject->mObjectFlags[0] : 0;
const unsigned int objectFlags1 = (pMapObject) ? pMapObject->mObjectFlags[1] : 0;
#if !defined(_DEBUG_MAP_MESHES)
if (gpFF11Opts && gpFF11Opts->keepNames)
{
char nameString[CFFXIMapHandler::skObjectNameLength + 1];
memcpy(nameString, pMapGeoHdr->mObjectName, CFFXIMapHandler::skObjectNameLength);
nameString[CFFXIMapHandler::skObjectNameLength] = 0;
pRapi->rpgSetName(nameString);
}
#endif
pRapi->rpgSetTransform(const_cast<modelMatrix_t *>(&transform.m));
char matName[CFFXITextureHandler::skTexNameLength + CFFXITextureHandler::skMaterialNamePad];
int drawOfs = sizeof(SMapGeoHeader);
while (drawOfs <= endDrawOfs)
{
const SDrawHeader *pSuperHeader = get_and_incr_offset<SDrawHeader>(pDrawData, drawOfs);
for (int superIndex = 0; superIndex < pSuperHeader->mSegCount && drawOfs <= endDrawOfs; ++superIndex)
{
const SDrawHeader *pSubHeader = get_and_incr_offset<SDrawHeader>(pDrawData, drawOfs);
for (int subIndex = 0; subIndex < pSubHeader->mSegCount && drawOfs <= endDrawOfs; ++subIndex)
{
NoeAssert((drawOfs & 3) == 0);
const char *pMatName = get_and_incr_offset<char>(pDrawData, drawOfs, CFFXITextureHandler::skTexNameLength);
const unsigned short vertCount = *get_and_incr_offset<unsigned short>(pDrawData, drawOfs);
const unsigned short blendFlags = *get_and_incr_offset<unsigned short>(pDrawData, drawOfs);
const int vertStride = (pSubHeader->mFlag == 0) ? 48 : 36;
const unsigned char *pVertData = get_and_incr_offset<unsigned char>(pDrawData, drawOfs, vertStride * vertCount);
const unsigned short indexCount = *get_and_incr_offset<unsigned short>(pDrawData, drawOfs);
const unsigned short flags2 = *get_and_incr_offset<unsigned short>(pDrawData, drawOfs);
const unsigned short *pIndexData = get_and_incr_offset<unsigned short>(pDrawData, drawOfs, indexCount);
align_offset(drawOfs, 4);
#if defined(_DEBUG_MAP_MESHES)
char nameString[CFFXIMapHandler::skObjectNameLength + 128];
memcpy(nameString, pMapGeoHdr->mObjectName, CFFXIMapHandler::skObjectNameLength);
NoeAssert(pMapObject); sprintf_s(&nameString[CFFXIMapHandler::skObjectNameLength], 128, "_fl%08x_x%.02fy%.02fz%.02fw%.02f",
objectFlags0, pMapObject->mVec[0], pMapObject->mVec[1], pMapObject->mVec[2], pMapObject->mVec[3]);
pRapi->rpgSetName(nameString);
#endif
memcpy(matName, pMatName, CFFXITextureHandler::skTexNameLength);
matName[CFFXITextureHandler::skTexNameLength] = 0;
int candidateBlendFlags = skMapGeoFlag_BlendTerrain; const bool explicitObjectTransparency = (objectFlags0 & 0x01000000) != 0;
if (explicitObjectTransparency)
{
candidateBlendFlags |= skMapGeoFlag_BlendHardAlpha;
} const bool shouldBlend = ((explicitObjectTransparency && vertStride == 48) || (blendFlags & candidateBlendFlags) ||
(pMapObject && objectFlags0 == 0x01000000 && pMapObject->mVec[3] < 1000.0f));
const char *pBlendSuffix = (shouldBlend) ?
CFFXITextureHandler::skpSoftBlendSuffix : CFFXITextureHandler::skpNoBlendSuffix;
strcpy_s(&matName[CFFXITextureHandler::skTexNameLength], CFFXITextureHandler::skMaterialNamePad, pBlendSuffix);
pRapi->rpgSetMaterial(matName);
if (vertCount == 0 || indexCount < 3)
{
pRapi->LogOutput("WARNING: Unexpected vert/index count.\n");
break;
}
else if (drawOfs > geoData.mDataSize)
{
pRapi->LogOutput("WARNING: Ran off end of MapGeo.\n");
break;
}
int posOfs, nrmOfs, clrOfs, uvOfs;
switch (vertStride)
{
case 48:
posOfs = 0;
nrmOfs = 24;
clrOfs = 36;
uvOfs = 40;
break;
default:
NoeAssert(vertStride == 36);
posOfs = 0;
nrmOfs = 12;
clrOfs = 24;
uvOfs = 28;
break;
}
const int colorShift = (gpFF11Opts && gpFF11Opts->explicitVertColorShift) ?
gpFF11Opts->fixVertColorShift : skDefaultVertColorFixShift;
const int alphaShift = (gpFF11Opts && gpFF11Opts->explicitVertAlphaShift) ?
gpFF11Opts->fixVertAlphaShift : skDefaultVertAlphaFixShift;
const unsigned int debugSeedBase = 0;
if (pSubHeader->mFlag == 0)
{ const int *pTriWindIdx = (backwardWinding) ? CFFXIGeoHandler::skTriCCWIdx : CFFXIGeoHandler::skTriCWIdx;
pRapi->rpgBegin(RPGEO_TRIANGLE);
for (int index = 0; index < indexCount; index += 3)
{
for (int triIndex = 0; triIndex < 3; ++triIndex)
{
const int vertIndex = pIndexData[index + pTriWindIdx[triIndex]];
const unsigned char *pVert = pVertData + vertIndex * vertStride;
PlotMapVertex(pRapi, pVert, posOfs, nrmOfs, clrOfs, uvOfs, colorShift, alphaShift, debugSeedBase);
}
}
pRapi->rpgEnd();
}
else
{ const rpgeoPrimType_e primType = (backwardWinding) ? RPGEO_TRIANGLE_STRIP_FLIPPED : RPGEO_TRIANGLE_STRIP;
pRapi->rpgBegin(primType);
for (int index = 0; index < indexCount; ++index)
{
const int vertIndex = pIndexData[index];
const unsigned char *pVert = pVertData + vertIndex * vertStride;
PlotMapVertex(pRapi, pVert, posOfs, nrmOfs, clrOfs, uvOfs, colorShift, alphaShift, debugSeedBase);
}
pRapi->rpgEnd();
}
}
}
}
pRapi->rpgSetTransform(NULL);
}
void RenderMapObjectGeoForMapObject(noeRAPI_t *pRapi, const CFFXIMapHandler::SInterpretedMapObject &mapObject)
{
const int index = mMapGeoHash.FindOrAddResource(mapObject.mObjectName, -1);
if (index < 0)
{
pRapi->LogOutput("WARNING: Could not find object in resource hash, skipping.\n");
}
else
{
RenderMapObjectGeo(pRapi, index, mapObject.mTransform, mapObject.mBackwardWinding, &mapObject);
}
}
const TMapGeoList &GetMapGeoList() const { return mMapGeoList; }
protected:
void PlotMapVertex(noeRAPI_t *pRapi, const unsigned char *pVert, const int posOfs, const int nrmOfs, const int clrOfs, const int uvOfs,
const int colorShift, const int alphaShift, const unsigned int debugSeedBase)
{
pRapi->rpgVertNormal3f((float *)(pVert + nrmOfs));
if (!debugSeedBase)
{
pRapi->rpgVertUV2f((float *)(pVert + uvOfs), 0);
if (!gpFF11Opts || !gpFF11Opts->noVertColors)
{
if (colorShift || alphaShift)
{ unsigned char clr[4];
memcpy(clr, pVert + clrOfs, 4);
clr[0] = (unsigned char)std::min<int>((int)clr[0] << colorShift, 255);
clr[1] = (unsigned char)std::min<int>((int)clr[1] << colorShift, 255);
clr[2] = (unsigned char)std::min<int>((int)clr[2] << colorShift, 255);
clr[3] = (unsigned char)std::min<int>((int)clr[3] << alphaShift, 255);
pRapi->rpgVertColor4ub(clr);
}
else
{
pRapi->rpgVertColor4ub(const_cast<unsigned char *>(pVert + clrOfs));
}
}
}
else
{
unsigned int debugSeed = debugSeedBase;
pRapi->rpgVertUV2f(NULL, 0);
const float debugColor[4] =
{
g_mfn->Math_RandFloatOnSeed(0.5f, 1.0f, debugSeed),
g_mfn->Math_RandFloatOnSeed(0.5f, 1.0f, debugSeed),
g_mfn->Math_RandFloatOnSeed(0.5f, 1.0f, debugSeed),
1.0f
};
pRapi->rpgVertColor4f(const_cast<float *>(debugColor));
}
pRapi->rpgVertex3f((float *)(pVert + posOfs));
}
TMapGeoList mMapGeoList;
CLocalResHash mMapGeoHash;
};
bool Model_FF11_CheckDAT(BYTE *fileBuffer, int bufferLen, noeRAPI_t *rapi)
{
CFFXIDat dat(fileBuffer, bufferLen, rapi);
CFFXIDefaultHandlerSet datHandlers(&dat);
return dat.ParseChunksOfInterest();
}
static noesisModel_t *Model_FF11_ConstructModelFromHandlerSet(noeRAPI_t *pRapi, CFFXIDefaultHandlerSet &datHandlers, bool promptForExternalSkel)
{
noesisMatData_t *pMd = NULL;
CFFXITextureHandler *pTextureHandler = datHandlers.TextureHandler();
if (pTextureHandler->Textures().Num() > 0)
{
pMd = pRapi->Noesis_GetMatDataFromLists(pTextureHandler->Materials(), pTextureHandler->Textures());
}
noesisModel_t *pMdl = NULL; const CFFXISkelHandler::SInterpretedSkel *pSkel = NULL;
CFFXISkelHandler *pSkelHandler = datHandlers.SkelHandler();
if (pSkelHandler->Skeletons().size() > 0)
{ pSkel = &pSkelHandler->Skeletons()[0];
}
noesisAnim_t *pAnim = NULL;
const CFFXIAnimHandler *pAnimHandler = datHandlers.AnimHandler();
const CFFXIGeoHandler *pGeoHandler = datHandlers.GeoHandler();
CFFXIDat *pSkelDat = NULL;
unsigned char *pSkelDatBuffer = NULL;
if (promptForExternalSkel &&
!pSkel &&
(pAnimHandler->AnimDataIsPresent() || pGeoHandler->GeoDataIsPresent()))
{ int skelDatSize = 0;
pSkelDatBuffer = pRapi->Noesis_LoadPairedFile("FFXI Skeleton DAT", ".dat", skelDatSize, NULL);
if (pSkelDatBuffer)
{
pSkelDat = new CFFXIDat(pSkelDatBuffer, skelDatSize, pRapi); datHandlers.RegisterHandlersWithDat(*pSkelDat);
if (pSkelDat->ParseChunksOfInterest() &&
pSkelDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Skeleton))
{
if (pSkelHandler->Skeletons().size() > 0)
{
pSkel = &pSkelHandler->Skeletons()[0];
if (!pAnimHandler->AnimDataIsPresent())
{ pSkelDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Animation);
}
}
}
}
}
if (pAnimHandler->AnimDataIsPresent())
{
if (!pSkel)
{
pRapi->LogOutput("WARNING: Discarding animation data, because no skeleton is present.\n");
}
else
{
pAnim = pAnimHandler->ConstructAnimations(pRapi, pSkel);
}
}
CFFXIMapHandler *pMapHandler = datHandlers.MapHandler();
CFFXIMapGeoHandler *pMapGeoHandler = datHandlers.MapGeoHandler();
const CFFXIMapGeoHandler::TMapGeoList &mapGeoList = pMapGeoHandler->GetMapGeoList();
const int mapGeoCount = mapGeoList.size();
const bool anyGeoDataIsPresent = (mapGeoCount > 0 || pGeoHandler->GeoDataIsPresent());
void *pCtx = NULL;
if (anyGeoDataIsPresent)
{
pCtx = pRapi->rpgCreateContext();
pRapi->rpgSetOption(RPGOPT_TRIWINDBACKWARD, true);
} if (pGeoHandler->GeoDataIsPresent())
{
pGeoHandler->RenderGeoData(pRapi, pSkel);
} if (mapGeoCount > 0)
{
CFFXIMapHandler::TMapObjectList &mapObjects = pMapHandler->MapObjects();
if (mapObjects.size() > 0)
{
for (CFFXIMapHandler::TMapObjectList::const_iterator it = mapObjects.begin(); it != mapObjects.end(); ++it)
{
const CFFXIMapHandler::SInterpretedMapObject &mapObject = *it;
pMapGeoHandler->RenderMapObjectGeoForMapObject(pRapi, mapObject);
}
if (gpFF11Opts && gpFF11Opts->renderUnreferenced)
{
RichMat43 unreferencedTransform; for (int mapGeoIndex = 0; mapGeoIndex < mapGeoCount; ++mapGeoIndex)
{ const CFFXIMapGeoHandler::SMapGeoData &mapGeoData = mapGeoList[mapGeoIndex];
bool isReferenced = false;
for (CFFXIMapHandler::TMapObjectList::const_iterator it = mapObjects.begin(); it != mapObjects.end(); ++it)
{
const CFFXIMapHandler::SInterpretedMapObject &mapObject = *it;
if (memcmp(mapGeoData.mpMapGeoHdr->mObjectName, mapObject.mObjectName, CFFXIMapHandler::skObjectNameLength) == 0)
{
isReferenced = true;
break;
}
}
if (!isReferenced)
{
pMapGeoHandler->RenderMapObjectGeo(pRapi, mapGeoIndex, unreferencedTransform, false, NULL);
}
}
}
}
else
{ RichMat43 defaultTransform;
for (int mapGeoIndex = 0; mapGeoIndex < mapGeoCount; ++mapGeoIndex)
{
pMapGeoHandler->RenderMapObjectGeo(pRapi, mapGeoIndex, defaultTransform, false, NULL);
}
}
} if (pCtx)
{
if (pAnim)
{
pRapi->rpgSetExData_Anims(pAnim);
}
if (pMd)
{
pRapi->rpgSetExData_Materials(pMd);
}
NoeAssert(anyGeoDataIsPresent);
if (gpFF11Opts && gpFF11Opts->optimizeGeo)
{
pRapi->rpgOptimize();
pMdl = pRapi->rpgConstructModel();
}
else
{
pMdl = pRapi->rpgConstructModelAndSort();
}
pRapi->rpgDestroyContext(pCtx);
} if ((pAnim || pMd) && !pMdl)
{
pMdl = pRapi->Noesis_AllocModelContainer(pMd, pAnim, (pAnim) ? 1 : 0);
} if (pSkelDatBuffer)
{
pRapi->Noesis_UnpooledFree(pSkelDatBuffer);
if (pSkelDat)
{
delete pSkelDat;
}
}
return pMdl;
}
static void Model_FF11_SetPreviewOffset(noeRAPI_t *pRapi)
{
float mdlAngOfs[3] = { 0.0f, 180.0f, 270.0f };
pRapi->SetPreviewAngOfs(mdlAngOfs);
}
noesisModel_t *Model_FF11_LoadDAT(BYTE *fileBuffer, int bufferLen, int &numMdl, noeRAPI_t *rapi)
{
CFFXIDat dat(fileBuffer, bufferLen, rapi);
CFFXIDefaultHandlerSet datHandlers(&dat);
dat.ParseChunksOfInterest();
if (!dat.RunChunkHandlersForChunksOfInterest())
{
rapi->LogOutput("Error: Unrecoverable error during chunk handling.\n");
return NULL;
}
noesisModel_t *pMdl = Model_FF11_ConstructModelFromHandlerSet(rapi, datHandlers, true);
Model_FF11_SetPreviewOffset(rapi);
numMdl = (pMdl) ? 1 : 0;
return pMdl;
}
ff11Opts_t *gpFF11Opts = NULL;
#define FF11_LOCAL_DECL_OPTS(argRequired) \
ff11Opts_t *pOpts = (ff11Opts_t *)store; \
NoeAssert(storeSize == sizeof(ff11Opts_t)); \
if (argRequired && !arg) \
{ \
return false; \
}
bool Model_FF11_ShiftColorHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(true);
pOpts->fixColorShift = atoi(arg);
pOpts->explicitColorShift = true;
return true;
}
bool Model_FF11_ShiftAlphaHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(true);
pOpts->fixAlphaShift = atoi(arg);
pOpts->explicitAlphaShift = true;
return true;
}
bool Model_FF11_ShiftVertColorHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(true);
pOpts->fixVertColorShift = atoi(arg);
pOpts->explicitVertColorShift = true;
return true;
}
bool Model_FF11_ShiftVertAlphaHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(true);
pOpts->fixVertAlphaShift = atoi(arg);
pOpts->explicitVertAlphaShift = true;
return true;
}
bool Model_FF11_NoShinyHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->noShinyMaterials = true;
return true;
}
bool Model_FF11_NoVertColorHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->noVertColors = true;
return true;
}
bool Model_FF11_ForceCullHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->forceCull = true;
return true;
}
bool Model_FF11_RenderUnreferencedHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->renderUnreferenced = true;
return true;
}
bool Model_FF11_KeepNamesHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->keepNames = true;
return true;
}
bool Model_FF11_OptimizeGeoHandler(const char *arg, unsigned char *store, int storeSize)
{
FF11_LOCAL_DECL_OPTS(false);
pOpts->optimizeGeo = true;
return true;
}
static const char *skpDatSetHeader = "NOESIS_FF11_DAT_SET";
static const int skDatSetHeaderSize = strlen(skpDatSetHeader);
bool Model_FF11_CheckDATSet(BYTE *fileBuffer, int bufferLen, noeRAPI_t *rapi)
{
if (bufferLen <= skDatSetHeaderSize || memcmp(fileBuffer, skpDatSetHeader, skDatSetHeaderSize) != 0)
{
return false;
}
return true;
}
noesisModel_t *Model_FF11_LoadDATSet(BYTE *fileBuffer, int bufferLen, int &numMdl, noeRAPI_t *rapi)
{
std::vector<CFFXIDat *> dats;
CFFXIDefaultHandlerSet datHandlers;
char basePath[MAX_NOESIS_PATH];
rapi->Noesis_GetDirForFilePath(basePath, rapi->Noesis_GetLastCheckedName());
char loadPath[MAX_NOESIS_PATH];
strcpy_s(loadPath, basePath);
char currentDatName[MAX_NOESIS_PATH];
char currentDatFilename[MAX_NOESIS_PATH];
textParser_t *pParser = rapi->Parse_InitParser((char *)fileBuffer);
parseToken_t tok;
while (rapi->Parse_GetNextToken(pParser, &tok))
{
if (!stricmp(tok.text, "setPathKey"))
{
HKEY baseKey;
rapi->Parse_GetNextToken(pParser, &tok);
if (!stricmp(tok.text, "HKEY_LOCAL_MACHINE"))
{
baseKey = HKEY_LOCAL_MACHINE;
}
else if (!stricmp(tok.text, "HKEY_CURRENT_USER"))
{
baseKey = HKEY_CURRENT_USER;
}
else if (!stricmp(tok.text, "HKEY_CURRENT_CONFIG"))
{
baseKey = HKEY_CURRENT_CONFIG;
}
else if (!stricmp(tok.text, "HKEY_USERS"))
{
baseKey = HKEY_USERS;
}
else if (!stricmp(tok.text, "HKEY_CLASSES_ROOT"))
{
baseKey = HKEY_CLASSES_ROOT;
}
else
{ baseKey = HKEY_LOCAL_MACHINE;
} rapi->Parse_GetNextToken(pParser, &tok);
HKEY key;
if (RegOpenKeyExA(baseKey, tok.text, 0, KEY_READ, &key) == ERROR_SUCCESS)
{ rapi->Parse_GetNextToken(pParser, &tok);
char keyData[MAX_NOESIS_PATH];
DWORD keyDataSize = MAX_NOESIS_PATH;
DWORD keyType;
if (RegQueryValueExA(key, tok.text, NULL, &keyType, (LPBYTE)keyData, &keyDataSize) == ERROR_SUCCESS)
{
strcpy_s(loadPath, keyData);
}
RegCloseKey(key);
}
}
else if (!stricmp(tok.text, "setPathRel"))
{ rapi->Parse_GetNextToken(pParser, &tok);
sprintf_s(loadPath, "%s%s", basePath, tok.text);
}
else if (!stricmp(tok.text, "setPathAbs"))
{ rapi->Parse_GetNextToken(pParser, &tok);
strcpy_s(loadPath, tok.text);
}
else if (!stricmp(tok.text, "dat"))
{
rapi->Parse_GetNextToken(pParser, &tok);
strcpy_s(currentDatName, tok.text); rapi->Parse_GetNextToken(pParser, &tok);
sprintf_s(currentDatFilename, "%s%s", loadPath, tok.text);
int datBufferSize;
unsigned char *pDatBuffer = rapi->Noesis_ReadFile(currentDatFilename, &datBufferSize);
if (pDatBuffer)
{
CFFXIDat *pDat = new CFFXIDat(pDatBuffer, datBufferSize, rapi);
datHandlers.RegisterHandlersWithDat(*pDat);
pDat->ParseChunksOfInterest();
dats.push_back(pDat);
if (!stricmp(currentDatName, "__skeleton"))
{
pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Skeleton);
}
else if (!stricmp(currentDatName, "__animation"))
{
pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Animation);
}
else
{ pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Texture);
pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Geo);
pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_Map);
pDat->RunChunkHandlersForChunksOfInterest(CFFXIDat::skChunkType_MapGeo);
}
}
else
{
rapi->LogOutput("Failed to load file: '%s'\n", currentDatFilename);
}
}
}
rapi->Parse_FreeParser(pParser);
if (!datHandlers.TextureHandler())
{ rapi->LogOutput("Error: No relevant data was loaded.\n");
return NULL;
}
noesisModel_t *pMdl = Model_FF11_ConstructModelFromHandlerSet(rapi, datHandlers, false);
Model_FF11_SetPreviewOffset(rapi);
for (std::vector<CFFXIDat *>::iterator it = dats.begin(); it != dats.end(); ++it)
{
CFFXIDat *pDat = *it;
rapi->Noesis_UnpooledFree((void *)pDat->GetData());
delete pDat;
}
numMdl = (pMdl) ? 1 : 0;
return pMdl;
}
CFFXIDat::EValidateChunkResult CFFXIDat::ValidateChunk(const CFFXIDat::SChunk &chunk) const
{
TChunkHandlerContainer::const_iterator it = mChunkHandlers.find(chunk.mType);
if (it == mChunkHandlers.end())
{
return kVCR_NotSupported;
}
const int dataSize = chunk.mSize - skBinaryChunkSize;
return it->second->ValidateChunk(*this, chunk, mpData + chunk.mDataOffset, dataSize);
}
bool CFFXIDat::ParseChunksOfInterest()
{
mChunks.clear();
int ofs = 0;
while (ofs <= (mDataSize - skBinaryChunkSize))
{
SChunk chunk(mpData + ofs, ofs);
if ((ofs + chunk.mSize) > mDataSize)
{ return false;
} if (chunk.mSize <= 0)
{
break;
}
const EValidateChunkResult validateChunkResult = ValidateChunk(chunk);
if (validateChunkResult == kVCR_Supported)
{
mChunks.push_back(chunk);
}
else if (validateChunkResult == kVCR_Invalid)
{ return false;
}
ofs += chunk.mSize;
}
return mChunks.size() > 0;
}
bool CFFXIDat::RunChunkHandlersForChunksOfInterest(const int forChunkType) const
{ for (TChunkList::const_iterator it = mChunks.begin(); it != mChunks.end(); ++it)
{
const SChunk &chunk = *it;
if (forChunkType >= 0 && chunk.mType != forChunkType)
{
continue;
}
TChunkHandlerContainer::const_iterator itHandler = mChunkHandlers.find(chunk.mType);
if (itHandler != mChunkHandlers.end())
{
const int dataSize = chunk.mSize - skBinaryChunkSize;
if (!itHandler->second->HandleChunk(*this, chunk, mpData + chunk.mDataOffset, dataSize))
{
mpRapi->LogOutput("WARNING: Chunk handler failed on chunk type %i at %i.\n", chunk.mType, chunk.mDataOffset);
}
}
}
return true;
}
void CFFXIDat::RegisterChunkHandler(CFFXIChunkHandler *pChunkHandler)
{
const int chunkType = pChunkHandler->GetChunkType();
TChunkHandlerContainer::iterator it = mChunkHandlers.lower_bound(chunkType);
if (it != mChunkHandlers.end() && it->first == chunkType)
{
NoeAssert(!"Handler already registered to chunk type!");
return;
}
mChunkHandlers.insert(it, TChunkHandlerContainer::value_type(chunkType, pChunkHandler));
}
#define FFXI_CREATE_AND_REGISTER_HANDLER(dat, handlerPointer, handlerType, ...) \
if (!handlerPointer) \
{ \
handlerPointer = new handlerType(__VA_ARGS__); \
} \
dat.RegisterChunkHandler(handlerPointer);
CFFXIDefaultHandlerSet::CFFXIDefaultHandlerSet(CFFXIDat *pDat)
{
if (pDat)
{
RegisterHandlersWithDat(*pDat);
}
}
void CFFXIDefaultHandlerSet::RegisterHandlersWithDat(CFFXIDat &dat)
{
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpTextureHandler, CFFXITextureHandler);
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpSkelHandler, CFFXISkelHandler);
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpAnimHandler, CFFXIAnimHandler);
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpGeoHandler, CFFXIGeoHandler);
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpMapHandler, CFFXIMapHandler);
FFXI_CREATE_AND_REGISTER_HANDLER(dat, mpMapGeoHandler, CFFXIMapGeoHandler);
}