#include "dynos.cpp.h" extern "C" { } // // Model files // enum { COMMENT_NONE = 0, COMMENT_START, // first slash COMMENT_SINGLE_LINE, // double slash, reset to COMMENT_NONE if \\n is hit COMMENT_BLOCK, // slash-star, set to comment block end if * is hit COMMENT_BLOCK_END, // slash-star-star, set to comment none if / is hit, else return to COMMENT_BLOCK }; struct IfDefPtr { const char *mPtr; u64 mSize; bool mErase; }; static IfDefPtr GetNearestIfDefPointer(char *pFileBuffer) { static const IfDefPtr sIfDefs[] = { { "#ifdef VERSION_JP", 17, true }, { "#ifndef VERSION_JP", 18, false }, { "#ifdef VERSION_EU", 17, true }, { "#ifdef TEXTURE_FIX", 18, false }, }; IfDefPtr _Nearest = { NULL, 0, false }; for (const auto &_IfDef : sIfDefs) { const char *_Ptr = strstr(pFileBuffer, _IfDef.mPtr); if (_Ptr != NULL && (_Nearest.mPtr == NULL || _Nearest.mPtr > _Ptr)) { _Nearest.mPtr = _Ptr; _Nearest.mSize = _IfDef.mSize; _Nearest.mErase = _IfDef.mErase; } } return _Nearest; } static char *LoadFileBuffer(FILE* aFile, GfxData* aGfxData) { fseek(aFile, 0, SEEK_END); s32 _Length = ftell(aFile); if (aGfxData && aGfxData->mModelIdentifier == 0) { aGfxData->mModelIdentifier = (u32) _Length; } // Remove comments rewind(aFile); char *_FileBuffer = New(_Length + 1); char *pFileBuffer = _FileBuffer; char _Previous = 0; char _Current = 0; s32 _CommentType = 0; while (fread(&_Current, 1, 1, aFile)) { if (_CommentType == COMMENT_NONE) { if (_Current == '/') { _CommentType = COMMENT_START; } else { *(pFileBuffer++) = _Current; } } else if (_CommentType == COMMENT_START) { if (_Current == '/') { _CommentType = COMMENT_SINGLE_LINE; } else if (_Current == '*') { _CommentType = COMMENT_BLOCK; } else { _CommentType = COMMENT_NONE; *(pFileBuffer++) = _Previous; *(pFileBuffer++) = _Current; } } else if (_CommentType == COMMENT_SINGLE_LINE) { if (_Current == '\n') { _CommentType = COMMENT_NONE; *(pFileBuffer++) = _Current; } } else if (_CommentType == COMMENT_BLOCK) { if (_Current == '*') { _CommentType = COMMENT_BLOCK_END; } } else if (_CommentType == COMMENT_BLOCK_END) { if (_Current == '/') { _CommentType = COMMENT_NONE; } else { _CommentType = COMMENT_BLOCK; } } _Previous = _Current; } *(pFileBuffer++) = 0; // Remove ifdef blocks // Doesn't support nested blocks for (pFileBuffer = _FileBuffer; pFileBuffer != NULL;) { IfDefPtr _IfDefPtr = GetNearestIfDefPointer(pFileBuffer); if (_IfDefPtr.mPtr) { char *pIfDef = (char *) _IfDefPtr.mPtr; char *pElse = (char *) strstr(_IfDefPtr.mPtr, "#else"); char *pEndIf = (char *) strstr(_IfDefPtr.mPtr, "#endif"); if (pElse && pElse < pEndIf) { if (_IfDefPtr.mErase) memset(pIfDef, ' ', pElse + 5 - pIfDef); else memset(pElse, ' ', pEndIf - pElse); } else { if (_IfDefPtr.mErase) memset(pIfDef, ' ', pEndIf - pIfDef); } memset(pIfDef, ' ', _IfDefPtr.mSize); memset(pEndIf, ' ', 6); pFileBuffer = pEndIf; } else { pFileBuffer = NULL; } } return _FileBuffer; } template static void AppendNewNode(GfxData *aGfxData, DataNodes &aNodes, const String &aName, String *&aDataName, Array *&aDataTokens) { DataNode *_Node = New>(); _Node->mName = aName; _Node->mModelIdentifier = aGfxData->mModelIdentifier; aNodes.Add(_Node); aDataName = &_Node->mName; aDataTokens = &_Node->mTokens; } static void ScanModelFile(GfxData *aGfxData, const SysPath &aFilename) { FILE *_File = fopen(aFilename.c_str(), "rb"); if (!_File) return; // Remember the geo layout count s32 prevGeoLayoutCount = aGfxData->mGeoLayouts.Count(); // Load file into a buffer while removing all comments char *_FileBuffer = LoadFileBuffer(_File, aGfxData); fclose(_File); // Scanning the loaded data s32 _DataType = DATA_TYPE_NONE; String* pDataName = NULL; Array *pDataTokens = NULL; char *pDataStart = NULL; bool _DataIgnore = false; // Needed to ignore the '#include "file.h"' strings String _Buffer = ""; for (char *c = _FileBuffer; *c != 0; ++c) { // Scanning data type if (_DataType == DATA_TYPE_NONE) { // Reading data type name if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9') || (*c == '_') || (*c == '\"')) { if (*c == '\"') { _DataIgnore = !_DataIgnore; } else if (!_DataIgnore) { _Buffer.Add(*c); } } // Retrieving data type else if (_Buffer.Length() != 0) { if (_Buffer == "static") { // Ignore static keyword } else if (_Buffer == "const") { // Ignore const keyword } else if (_Buffer == "inline") { // Ignore inline keyword } else if (_Buffer == "include") { // Ignore include keyword } else if (_Buffer == "ALIGNED8") { // Ignore ALIGNED8 keyword } else if (_Buffer == "UNUSED") { // Ignore UNUSED keyword } else if (_Buffer == "u64") { _DataType = DATA_TYPE_UNUSED; } else if (_Buffer == "Lights1") { _DataType = DATA_TYPE_LIGHT; } else if (_Buffer == "u8") { _DataType = DATA_TYPE_TEXTURE; } else if (_Buffer == "Texture") { _DataType = DATA_TYPE_TEXTURE; } else if (_Buffer == "Vtx") { _DataType = DATA_TYPE_VERTEX; } else if (_Buffer == "Gfx") { _DataType = DATA_TYPE_DISPLAY_LIST; } else if (_Buffer == "GeoLayout") { _DataType = DATA_TYPE_GEO_LAYOUT; } else if (_Buffer == "Collision") { _DataType = DATA_TYPE_COLLISION; } else if (_Buffer == "LevelScript") { _DataType = DATA_TYPE_LEVEL_SCRIPT; } else if (_Buffer == "MacroObject") { _DataType = DATA_TYPE_MACRO_OBJECT; } else { PrintError(" ERROR: Unknown type name: %s", _Buffer.begin()); } _Buffer.Clear(); } } // Scanning data identifier else if (!pDataTokens) { // Reading data identifier name if ((*c >= 'A' && *c <= 'Z') || (*c >= 'a' && *c <= 'z') || (*c >= '0' && *c <= '9') || (*c == '_')) { _Buffer.Add(*c); } // Adding new data node else if (_Buffer.Length() != 0) { switch (_DataType) { case DATA_TYPE_LIGHT: AppendNewNode(aGfxData, aGfxData->mLights, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_TEXTURE: AppendNewNode(aGfxData, aGfxData->mTextures, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_VERTEX: AppendNewNode(aGfxData, aGfxData->mVertices, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_DISPLAY_LIST: AppendNewNode(aGfxData, aGfxData->mDisplayLists, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_GEO_LAYOUT: AppendNewNode(aGfxData, aGfxData->mGeoLayouts, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_COLLISION: AppendNewNode(aGfxData, aGfxData->mCollisions, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_LEVEL_SCRIPT: AppendNewNode(aGfxData, aGfxData->mLevelScripts, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_MACRO_OBJECT: AppendNewNode(aGfxData, aGfxData->mMacroObjects, _Buffer, pDataName, pDataTokens); break; case DATA_TYPE_UNUSED: pDataTokens = (Array *) 1; break; } _Buffer.Clear(); } } // Looking for data else if (pDataStart == 0) { if (*c == '=') { pDataStart = c + 1; } else if (*c == ';') { PrintError(" ERROR: %s: Unexpected end of data", pDataName->begin()); } } // Data end else if (*c == ';') { if (_DataType != DATA_TYPE_UNUSED) { char* pDataEnd = &*c; String _Token = ""; for (u8 _Bracket = 0; pDataStart <= pDataEnd; pDataStart++) { if (*pDataStart == '(') _Bracket++; if (*pDataStart == ' ' || *pDataStart == '\t' || *pDataStart == '\r' || *pDataStart == '\n') continue; if (_Bracket <= 1 && (*pDataStart == '(' || *pDataStart == ')' || *pDataStart == ',' || *pDataStart == '{' || *pDataStart == '}' || *pDataStart == ';')) { if (_Token.Length() != 0) { pDataTokens->Add(_Token); _Token.Clear(); } } else { _Token.Add(*pDataStart); } if (*pDataStart == ')') _Bracket--; } } _DataType = DATA_TYPE_NONE; pDataName = NULL; pDataTokens = NULL; pDataStart = NULL; _DataIgnore = false; _Buffer = ""; } } // Figure out which geo layouts to generate s32 geoLayoutCount = aGfxData->mGeoLayouts.Count(); if (geoLayoutCount > prevGeoLayoutCount) { // find actors to generate bool foundActor = false; for (s32 i = prevGeoLayoutCount; i < geoLayoutCount; i++) { String _GeoRootName = aGfxData->mGeoLayouts[i]->mName; const void* actor = DynOS_Geo_GetActorLayoutFromName(_GeoRootName.begin()); if (actor != NULL) { foundActor = true; aGfxData->mGenerateGeoLayouts.Add(aGfxData->mGeoLayouts[i]); } } // if we haven't found an actor, just add the last geo layout found if (!foundActor) { aGfxData->mGenerateGeoLayouts.Add(aGfxData->mGeoLayouts[geoLayoutCount - 1]); } } Delete(_FileBuffer); Print("Data read from file \"%s\"", aFilename.c_str()); } // // Animation files // static void ScanAnimationDataFile(GfxData *aGfxData, const SysPath &aFilename) { FILE *_File = fopen(aFilename.c_str(), "rb"); if (!_File) { PrintError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); } // Load file into a buffer while removing all comments char *_FileBuffer = LoadFileBuffer(_File, NULL); fclose(_File); // Parse animation data u8 _DataType = DATA_TYPE_NONE; String _DataName; bool _IsData = false; Array _Data; Array _Tokens = Split(_FileBuffer, " []()=&,;\t\r\n\b"); for (const auto &_Token : _Tokens) { // Data type if (_DataType == DATA_TYPE_NONE) { if (_Token == "s16" || _Token == "u16" || _Token == "s16") { _DataType = DATA_TYPE_ANIMATION_VALUE; } else if (_Token == "Animation") { _DataType = DATA_TYPE_ANIMATION; } } // Data name else if (_DataName.Empty()) { _DataName = _Token; if (_DataType == DATA_TYPE_ANIMATION_VALUE && (_DataName.Find("index") != -1 || _DataName.Find("indices") != -1)) { _DataType = DATA_TYPE_ANIMATION_INDEX; } } // Is data? else if (!_IsData) { if (_Token == "{") { _IsData = true; } } // Data else { if (_Token == "}") { switch (_DataType) { case DATA_TYPE_ANIMATION_VALUE: { AnimBuffer *_AnimValues = New>(); _AnimValues->first = _DataName; for (const auto &_Value : _Data) { _AnimValues->second.Add(_Value.ParseInt()); } aGfxData->mAnimValues.Add(_AnimValues); } break; case DATA_TYPE_ANIMATION_INDEX: { AnimBuffer *_AnimIndices = New>(); _AnimIndices->first = _DataName; for (const auto &_Index : _Data) { _AnimIndices->second.Add(_Index.ParseInt()); } aGfxData->mAnimIndices.Add(_AnimIndices); } break; case DATA_TYPE_ANIMATION: { if (_Data.Count() < 10) { PrintError(" ERROR: %s: Not enough data", _DataName.begin()); break; } DataNode *_Node = New>(); _Node->mName = _DataName; _Node->mData = New(); _Node->mData->mFlags = (s16) _Data[0].ParseInt(); _Node->mData->mUnk02 = (s16) _Data[1].ParseInt(); _Node->mData->mUnk04 = (s16) _Data[2].ParseInt(); _Node->mData->mUnk06 = (s16) _Data[3].ParseInt(); _Node->mData->mUnk08 = (s16) _Data[4].ParseInt(); _Node->mData->mUnk0A.first = _Data[6]; // 5 is "ANIMINDEX_NUMPARTS" _Node->mData->mValues.first = _Data[7]; _Node->mData->mIndex.first = _Data[8]; _Node->mData->mLength = (u32) _Data[9].ParseInt(); aGfxData->mAnimations.Add(_Node); } break; } _DataType = DATA_TYPE_NONE; _DataName.Clear(); _IsData = false; _Data.Clear(); } else { _Data.Add(_Token); } } } Delete(_FileBuffer); } static void ScanAnimationTableFile(GfxData *aGfxData, const SysPath &aFilename) { FILE *_File = fopen(aFilename.c_str(), "rb"); if (!_File) { PrintError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); } // Load file into a buffer while removing all comments char *_FileBuffer = LoadFileBuffer(_File, NULL); fclose(_File); // Retrieve animation names bool _IsAnimName = false; Array _Tokens = Split(_FileBuffer, " =&,;\t\r\n\b"); for (const auto &_Token : _Tokens) { if (_Token == "{") { _IsAnimName = true; } else if (_Token == "}") { _IsAnimName = false; } else if (_IsAnimName) { aGfxData->mAnimationTable.Add({ _Token, NULL }); } } Delete(_FileBuffer); } static void ScanAnimationFolder(GfxData *aGfxData, const SysPath &aAnimsFolder) { DIR *_AnimsDir = opendir(aAnimsFolder.c_str()); if (!_AnimsDir) return; struct dirent *_AnimsEnt = NULL; while ((_AnimsEnt = readdir(_AnimsDir)) != NULL) { // Skip if (SysPath(_AnimsEnt->d_name) == ".") continue; if (SysPath(_AnimsEnt->d_name) == "..") continue; if (SysPath(_AnimsEnt->d_name) == "data.inc.c") continue; // Animation file SysPath _AnimsFilename = fstring("%s/%s", aAnimsFolder.c_str(), _AnimsEnt->d_name); if (fs_sys_file_exists(_AnimsFilename.c_str())) { // Table file if (SysPath(_AnimsEnt->d_name) == "table.inc.c") { ScanAnimationTableFile(aGfxData, _AnimsFilename); } // Data file else { ScanAnimationDataFile(aGfxData, _AnimsFilename); } } } closedir(_AnimsDir); } // // Read & Generate // // Free data pointers, but keep nodes and tokens intact // Delete nodes generated from GfxDynCmds template static void ClearGfxDataNodes(DataNodes &aDataNodes) { for (s32 i = aDataNodes.Count(); i != 0; --i) { Delete(aDataNodes[i - 1]->mData); } } static String GetActorFolder(const Array> &aActorsFolders, u64 aModelIdentifier) { for (const auto &_Pair : aActorsFolders) { if (_Pair.first == aModelIdentifier) { return _Pair.second; } } return String(); } static void DynOS_Col_Generate(const SysPath &aPackFolder, Array> _ActorsFolders, GfxData *_GfxData) { for (auto &_ColNode : _GfxData->mCollisions) { String _ColRootName = _ColNode->mName; // If there is an existing binary file for this collision, skip and go to the next actor SysPath _ColFilename = fstring("%s/%s.col", aPackFolder.c_str(), _ColRootName.begin()); if (fs_sys_file_exists(_ColFilename.c_str())) { continue; } // Init _GfxData->mErrorCount = 0; _GfxData->mLoadIndex = 0; // Parse data PrintNoNewLine("%s.col: Model identifier: %X - Processing... ", _ColRootName.begin(), _GfxData->mModelIdentifier); DynOS_Col_Parse(_GfxData, _ColNode, true); // Write if no error if (_GfxData->mErrorCount == 0) { DynOS_Col_WriteBinary(_ColFilename, _GfxData, _ColNode); } else { Print(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers ClearGfxDataNodes(_GfxData->mCollisions); } } static void DynOS_Actor_Generate(const SysPath &aPackFolder, Array> _ActorsFolders, GfxData *_GfxData) { for (auto &_GeoNode : _GfxData->mGenerateGeoLayouts) { String _GeoRootName = _GeoNode->mName; // If there is an existing binary file for this layout, skip and go to the next actor SysPath _BinFilename = fstring("%s/%s.bin", aPackFolder.c_str(), _GeoRootName.begin()); if (fs_sys_file_exists(_BinFilename.c_str())) { continue; } // Init _GfxData->mLoadIndex = 0; _GfxData->mErrorCount = 0; _GfxData->mModelIdentifier = _GeoNode->mModelIdentifier; _GfxData->mPackFolder = aPackFolder; _GfxData->mPointerList = { NULL }; // The NULL pointer is needed, so we add it here _GfxData->mGfxContext.mCurrentTexture = NULL; _GfxData->mGfxContext.mCurrentPalette = NULL; _GfxData->mGeoNodeStack.Clear(); // Parse data PrintNoNewLine("%s.bin: Model identifier: %X - Processing... ", _GeoRootName.begin(), _GfxData->mModelIdentifier); DynOS_Geo_Parse(_GfxData, _GeoNode, true); // Init animation data for (auto &_AnimBuffer : _GfxData->mAnimValues) Delete(_AnimBuffer); for (auto &_AnimBuffer : _GfxData->mAnimIndices) Delete(_AnimBuffer); for (auto &_AnimNode : _GfxData->mAnimations) Delete(_AnimNode); _GfxData->mAnimValues.Clear(); _GfxData->mAnimIndices.Clear(); _GfxData->mAnimations.Clear(); _GfxData->mAnimationTable.Clear(); // Scan anims folder for animation data String _ActorFolder = GetActorFolder(_ActorsFolders, _GfxData->mModelIdentifier); SysPath _AnimsFolder = fstring("%s/%s/anims", aPackFolder.c_str(), _ActorFolder.begin()); ScanAnimationFolder(_GfxData, _AnimsFolder); // Create table for player model animations if ((_GeoRootName == "mario_geo" || _GeoRootName == "luigi_geo" || _GeoRootName == "toad_player_geo" || _GeoRootName == "wario_geo" || _GeoRootName == "waluigi_geo") && !_GfxData->mAnimations.Empty()) { _GfxData->mAnimationTable.Resize(256); for (s32 i = 0; i != 256; ++i) { String _AnimName("anim_%02X", i); if (_GfxData->mAnimations.FindIf([&_AnimName](const DataNode *aNode) { return aNode->mName == _AnimName; }) != -1) { _GfxData->mAnimationTable[i] = { _AnimName, NULL }; } else { _GfxData->mAnimationTable[i] = { "NULL", NULL }; } } } // Write if no error if (_GfxData->mErrorCount == 0) { DynOS_Gfx_WriteBinary(_BinFilename, _GfxData); } else { Print(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers ClearGfxDataNodes(_GfxData->mLights); ClearGfxDataNodes(_GfxData->mTextures); ClearGfxDataNodes(_GfxData->mVertices); ClearGfxDataNodes(_GfxData->mDisplayLists); ClearGfxDataNodes(_GfxData->mGeoLayouts); ClearGfxDataNodes(_GfxData->mCollisions); } } void DynOS_Gfx_GeneratePack(const SysPath &aPackFolder) { Print("---------- Pack folder: \"%s\" ----------", aPackFolder.c_str()); Array> _ActorsFolders; GfxData *_GfxData = New(); // Read all the model.inc.c files and geo.inc.c files from the subfolders of the pack folder // Animations are processed separately DIR *aPackDir = opendir(aPackFolder.c_str()); if (aPackDir) { struct dirent *_PackEnt = NULL; while ((_PackEnt = readdir(aPackDir)) != NULL) { // Skip . and .. if (SysPath(_PackEnt->d_name) == ".") continue; if (SysPath(_PackEnt->d_name) == "..") continue; // For each subfolder, read tokens from model.inc.c and geo.inc.c SysPath _Folder = fstring("%s/%s", aPackFolder.c_str(), _PackEnt->d_name); if (fs_sys_dir_exists(_Folder.c_str())) { _GfxData->mModelIdentifier = 0; ScanModelFile(_GfxData, fstring("%s/model.inc.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/geo.inc.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/collision.inc.c", _Folder.c_str())); if (_GfxData->mModelIdentifier != 0) { _ActorsFolders.Add({ _GfxData->mModelIdentifier, String(_PackEnt->d_name) }); } } } closedir(aPackDir); } // Generate a binary file for each actor found in the GfxData DynOS_Col_Generate(aPackFolder, _ActorsFolders, _GfxData); DynOS_Actor_Generate(aPackFolder, _ActorsFolders, _GfxData); DynOS_Gfx_Free(_GfxData); } void DynOS_Lvl_GeneratePack(const SysPath &aPackFolder) { Print("---------- Level pack folder: \"%s\" ----------", aPackFolder.c_str()); Array> _ActorsFolders; GfxData *_GfxData = New(); DIR *aPackDir = opendir(aPackFolder.c_str()); if (aPackDir) { struct dirent *_PackEnt = NULL; while ((_PackEnt = readdir(aPackDir)) != NULL) { // Skip . and .. if (SysPath(_PackEnt->d_name) == ".") continue; if (SysPath(_PackEnt->d_name) == "..") continue; // For each subfolder, read tokens from script.c SysPath _Folder = fstring("%s/%s", aPackFolder.c_str(), _PackEnt->d_name); if (fs_sys_dir_exists(_Folder.c_str())) { _GfxData->mModelIdentifier = 0; ScanModelFile(_GfxData, fstring("%s/model.inc.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/area_1/collision.inc.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/area_1/geo.inc.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/script.c", _Folder.c_str())); ScanModelFile(_GfxData, fstring("%s/area_1/macro.inc.c", _Folder.c_str())); } } closedir(aPackDir); } // Generate a binary file for each actor found in the GfxData DynOS_Lvl_GeneratePack_Internal(aPackFolder, _ActorsFolders, _GfxData); DynOS_Gfx_Free(_GfxData); }