int main( int argc, char **argv ) { if( argc != 2 ) { Usage(); } memset( g_NumBonesInLOD, 0, sizeof( int ) * MAX_NUM_LODS ); FILE *fp; fp = fopen( argv[1], "rb" ); if( !fp ) { fprintf( stderr, "Can't open: %s\n", argv[1] ); Usage(); } fseek( fp, 0, SEEK_END ); int len = ftell( fp ); rewind( fp ); studiohdr_t *pStudioHdr = ( studiohdr_t * )malloc( len ); fread( pStudioHdr, 1, len, fp ); Studio_ConvertStudioHdrToNewVersion( pStudioHdr ); if( pStudioHdr->version != STUDIO_VERSION ) { fprintf( stderr, "Wrong version (%d != %d)\n", ( int )pStudioHdr->version, ( int )STUDIO_VERSION ); Usage(); } int i; for( i = 0; i < pStudioHdr->numbones; i++ ) { mstudiobone_t *pBone = pStudioHdr->pBone( i ); if( pBone->parent == -1 ) { PrintBoneGraphRecursive( pStudioHdr, i, 0 ); } } fclose( fp ); for( i = 0; i < MAX_NUM_LODS; i++ ) { printf( "%d bones used in lod %d\n", g_NumBonesInLOD[i], i ); } return 0; }
//----------------------------------------------------------------------------- // Load studio model vertex data from a file... //----------------------------------------------------------------------------- bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf ) { if ( !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) ) return false; // Check that it's valid if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) { return false; } studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); Studio_ConvertStudioHdrToNewVersion( pHdr ); if (pHdr->version != STUDIO_VERSION) { return false; } isstaticprop_ret isStaticProp = IsStaticProp(pHdr); if ( isStaticProp != RET_VALID ) { if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP ) { Warning("Error! To use model \"%s\"\n" " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType ); } else if ( isStaticProp == RET_FAIL_DYNAMIC ) { Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName ); } return false; } // ensure reset pHdr->pVertexBase = NULL; pHdr->pIndexBase = NULL; return true; }
bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ) { // No luck, gotta build it // Construct the file name... if (!LoadFile( pModelName, buf )) { Warning("Error! Unable to load model \"%s\"\n", pModelName ); return false; } // Check that it's valid if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) { Warning("Error! Invalid model file \"%s\"\n", pModelName ); return false; } studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); Studio_ConvertStudioHdrToNewVersion( pHdr ); if (pHdr->version != STUDIO_VERSION) { Warning("Error! Invalid model version \"%s\"\n", pModelName ); return false; } if (!IsStaticProp(pHdr)) { Warning("Error! To use model \"%s\"\n" " as a static prop, it must be compiled with $staticprop!\n", pModelName ); return false; } // ensure reset pHdr->pVertexBase = NULL; pHdr->pIndexBase = NULL; return true; }
//---------------------------------------------------------------------- // Get list of files that a model requires. //---------------------------------------------------------------------- bool GetDependants_MDL( const char *pModelName, CUtlVector< CUtlString > *pList ) { if ( !g_bModPathIsValid ) { Msg( "Indeterminate mod path, Cannot perform BSP conversion\n" ); return false; } CUtlBuffer sourceBuf; if ( !g_pFullFileSystem->ReadFile( pModelName, "GAME", sourceBuf ) ) { Msg( "Error! Couldn't open file '%s'!\n", pModelName ); return false; } studiohdr_t *pStudioHdr = (studiohdr_t *)sourceBuf.Base(); Studio_ConvertStudioHdrToNewVersion( pStudioHdr ); if ( pStudioHdr->version != STUDIO_VERSION ) { Msg( "Error! Bad Model '%s', Expecting Version (%d), got (%d)\n", pModelName, STUDIO_VERSION, pStudioHdr->version ); return false; } char szOutName[MAX_PATH]; if ( pStudioHdr->flags & STUDIOHDR_FLAGS_OBSOLETE ) { V_strncpy( szOutName, "materials/sprites/obsolete.vmt", sizeof( szOutName ) ); V_FixSlashes( szOutName ); pList->AddToTail( szOutName ); } else if ( pStudioHdr->textureindex != 0 ) { // iterate each texture int i; int j; for ( i = 0; i < pStudioHdr->numtextures; i++ ) { // iterate through all directories until a valid material is found bool bFound = false; for ( j = 0; j < pStudioHdr->numcdtextures; j++ ) { char szPath[MAX_PATH]; V_ComposeFileName( "materials", pStudioHdr->pCdtexture( j ), szPath, sizeof( szPath ) ); // should have been fixed in studiomdl // some mdls are ending up with double slashes, borking loads int len = strlen( szPath ); if ( len > 2 && szPath[len-2] == '\\' && szPath[len-1] == '\\' ) { szPath[len-1] = '\0'; } V_ComposeFileName( szPath, pStudioHdr->pTexture( i )->pszName(), szOutName, sizeof( szOutName ) ); V_SetExtension( szOutName, ".vmt", sizeof( szOutName ) ); if ( g_pFullFileSystem->FileExists( szOutName, "GAME" ) ) { bFound = true; break; } } if ( bFound ) { pList->AddToTail( szOutName ); } } } return true; }
//----------------------------------------------------------------------------- // Rebuilds all of a MDL's components. //----------------------------------------------------------------------------- static bool GenerateModelFiles( const char *pMdlFilename ) { CUtlBuffer tempBuffer; int fileSize; int paddedSize; int swappedSize; // .mdl CUtlBuffer mdlBuffer; if ( !scriptlib->ReadFileToBuffer( pMdlFilename, mdlBuffer ) ) { return false; } if ( !Studio_ConvertStudioHdrToNewVersion( (studiohdr_t *)mdlBuffer.Base() )) { Msg("%s needs to be recompiled\n", pMdlFilename ); } // .vtx char szVtxFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szVtxFilename, sizeof( szVtxFilename ) ); V_strncat( szVtxFilename, ".dx90.vtx", sizeof( szVtxFilename ) ); CUtlBuffer vtxBuffer; bool bHasVtx = ReadFileToBuffer( szVtxFilename, vtxBuffer, false, true ); // .vvd char szVvdFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szVvdFilename, sizeof( szVvdFilename ) ); V_strncat( szVvdFilename, ".vvd", sizeof( szVvdFilename ) ); CUtlBuffer vvdBuffer; bool bHasVvd = ReadFileToBuffer( szVvdFilename, vvdBuffer, false, true ); if ( bHasVtx != bHasVvd ) { // paired resources, either mandates the other return false; } // a .mdl file that has .vtx/.vvd gets re-processed to cull lod data if ( bHasVtx && bHasVvd ) { // cull lod if needed IMdlStripInfo *pStripInfo = NULL; bool bResult = mdllib->StripModelBuffers( mdlBuffer, vvdBuffer, vtxBuffer, &pStripInfo ); if ( !bResult ) { return false; } if ( pStripInfo ) { // .vsi CUtlBuffer vsiBuffer; pStripInfo->Serialize( vsiBuffer ); pStripInfo->DeleteThis(); // save strip info for later processing char szVsiFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szVsiFilename, sizeof( szVsiFilename ) ); V_strncat( szVsiFilename, ".vsi", sizeof( szVsiFilename ) ); WriteBufferToFile( szVsiFilename, vsiBuffer, false, WRITE_TO_DISK_ALWAYS ); } } // .ani processing may further update .mdl buffer char szAniFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szAniFilename, sizeof( szAniFilename ) ); V_strncat( szAniFilename, ".ani", sizeof( szAniFilename ) ); CUtlBuffer aniBuffer; bool bHasAni = ReadFileToBuffer( szAniFilename, aniBuffer, false, true ); if ( bHasAni ) { // Some vestigal .ani files exist in the tree, only process valid .ani if ( ((studiohdr_t*)mdlBuffer.Base())->numanimblocks != 0 ) { // .ani processing modifies .mdl buffer fileSize = aniBuffer.TellPut(); paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; aniBuffer.EnsureCapacity( paddedSize ); tempBuffer.EnsureCapacity( paddedSize ); V_StripExtension( pMdlFilename, szAniFilename, sizeof( szAniFilename ) ); V_strncat( szAniFilename, ".360.ani", sizeof( szAniFilename ) ); swappedSize = StudioByteSwap::ByteswapStudioFile( szAniFilename, tempBuffer.Base(), aniBuffer.PeekGet(), fileSize, (studiohdr_t*)mdlBuffer.Base(), CompressFunc ); if ( swappedSize > 0 ) { // .ani buffer is replaced with swapped data aniBuffer.Purge(); aniBuffer.Put( tempBuffer.Base(), swappedSize ); WriteBufferToFile( szAniFilename, aniBuffer, false, WRITE_TO_DISK_ALWAYS ); } else { return false; } } } // .phy char szPhyFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szPhyFilename, sizeof( szPhyFilename ) ); V_strncat( szPhyFilename, ".phy", sizeof( szPhyFilename ) ); CUtlBuffer phyBuffer; bool bHasPhy = ReadFileToBuffer( szPhyFilename, phyBuffer, false, true ); if ( bHasPhy ) { fileSize = phyBuffer.TellPut(); paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; phyBuffer.EnsureCapacity( paddedSize ); tempBuffer.EnsureCapacity( paddedSize ); V_StripExtension( pMdlFilename, szPhyFilename, sizeof( szPhyFilename ) ); V_strncat( szPhyFilename, ".360.phy", sizeof( szPhyFilename ) ); swappedSize = StudioByteSwap::ByteswapStudioFile( szPhyFilename, tempBuffer.Base(), phyBuffer.PeekGet(), fileSize, (studiohdr_t*)mdlBuffer.Base(), CompressFunc ); if ( swappedSize > 0 ) { // .phy buffer is replaced with swapped data phyBuffer.Purge(); phyBuffer.Put( tempBuffer.Base(), swappedSize ); WriteBufferToFile( szPhyFilename, phyBuffer, false, WRITE_TO_DISK_ALWAYS ); } else { return false; } } if ( bHasVtx ) { fileSize = vtxBuffer.TellPut(); paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; vtxBuffer.EnsureCapacity( paddedSize ); tempBuffer.EnsureCapacity( paddedSize ); V_StripExtension( pMdlFilename, szVtxFilename, sizeof( szVtxFilename ) ); V_strncat( szVtxFilename, ".dx90.360.vtx", sizeof( szVtxFilename ) ); swappedSize = StudioByteSwap::ByteswapStudioFile( szVtxFilename, tempBuffer.Base(), vtxBuffer.PeekGet(), fileSize, (studiohdr_t*)mdlBuffer.Base(), CompressFunc ); if ( swappedSize > 0 ) { // .vtx buffer is replaced with swapped data vtxBuffer.Purge(); vtxBuffer.Put( tempBuffer.Base(), swappedSize ); WriteBufferToFile( szVtxFilename, vtxBuffer, false, WRITE_TO_DISK_ALWAYS ); } else { return false; } } if ( bHasVvd ) { fileSize = vvdBuffer.TellPut(); paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; vvdBuffer.EnsureCapacity( paddedSize ); tempBuffer.EnsureCapacity( paddedSize ); V_StripExtension( pMdlFilename, szVvdFilename, sizeof( szVvdFilename ) ); V_strncat( szVvdFilename, ".360.vvd", sizeof( szVvdFilename ) ); swappedSize = StudioByteSwap::ByteswapStudioFile( szVvdFilename, tempBuffer.Base(), vvdBuffer.PeekGet(), fileSize, (studiohdr_t*)mdlBuffer.Base(), CompressFunc ); if ( swappedSize > 0 ) { // .vvd buffer is replaced with swapped data vvdBuffer.Purge(); vvdBuffer.Put( tempBuffer.Base(), swappedSize ); WriteBufferToFile( szVvdFilename, vvdBuffer, false, WRITE_TO_DISK_ALWAYS ); } else { return false; } } // swap and write final .mdl fileSize = mdlBuffer.TellPut(); paddedSize = fileSize + BYTESWAP_ALIGNMENT_PADDING; mdlBuffer.EnsureCapacity( paddedSize ); tempBuffer.EnsureCapacity( paddedSize ); char szMdlFilename[MAX_PATH]; V_StripExtension( pMdlFilename, szMdlFilename, sizeof( szMdlFilename ) ); V_strncat( szMdlFilename, ".360.mdl", sizeof( szMdlFilename ) ); swappedSize = StudioByteSwap::ByteswapStudioFile( szMdlFilename, tempBuffer.Base(), mdlBuffer.PeekGet(), fileSize, NULL, CompressFunc ); if ( swappedSize > 0 ) { // .mdl buffer is replaced with swapped data mdlBuffer.Purge(); mdlBuffer.Put( tempBuffer.Base(), swappedSize ); WriteBufferToFile( szMdlFilename, mdlBuffer, false, WRITE_TO_DISK_ALWAYS ); } else { return false; } return true; }
bool ValidateModelFile( char const *modelname, int offset ) { studiohdr_t *pHdr; FILE *fp; pHdr = (studiohdr_t *)buffer; fp = fopen( modelname, "rb" ); if ( !fp ) { vprint( 0, "Unable to open .mdl file %s\n", modelname ); return false; } // See if there's a .qc which builds this model char shortname[ MAX_PATH ]; strcpy( shortname, &modelname[ offset ] ); Q_FixSlashes( shortname ); fread( buffer, sizeof( buffer ), 1, fp ); fclose( fp ); if ( pHdr->id != IDSTUDIOHEADER ) { vprint( 0, "Bogus studiomdl header for %s, expecting 'IDST' four cc code\n", shortname ); return false; } bool valid = true; bool needsrecompile = false; int toobig = 0; // previous version is compatible if ( pHdr->version < 44 || pHdr->version > STUDIO_VERSION ) { vprint( 0, "Outdated model %s (ver %i != %i)\n", shortname, pHdr->version, STUDIO_VERSION ); valid = false; } if (!Studio_ConvertStudioHdrToNewVersion( pHdr )) { // vprint( 0, "%s needs to be recompiled\n", pHdr->pszName() ); needsrecompile = true; } if (checkani) { // HACK: since the sequence data is written after the animation data, this is rough way to determine how much anim data there really is int totalanimsize = pHdr->localseqindex - pHdr->localanimindex - pHdr->numlocalanim * sizeof( mstudioanimdesc_t ); if (pHdr->pLocalAnimdesc( 0 )->animblock == 0 && totalanimsize > 1024 * 64) { toobig = totalanimsize; } } int idx = g_Analysis.models.Find( shortname ); if ( idx == g_Analysis.models.InvalidIndex() ) { vprint( 0, "Couldn't find a .qc which builds %s\n", shortname ); valid = false; } else { g_Analysis.models[idx].version = pHdr->version; g_Analysis.models[idx].needsrecompile = needsrecompile; g_Analysis.models[idx].toobig = toobig; } return valid; }