diff options
Diffstat (limited to 'utils/xbox/MakeGameData')
20 files changed, 6956 insertions, 0 deletions
diff --git a/utils/xbox/MakeGameData/MakeGameData.cpp b/utils/xbox/MakeGameData/MakeGameData.cpp new file mode 100644 index 0000000..91857b1 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeGameData.cpp @@ -0,0 +1,1174 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Generates a file list based on command line wildcard spec and drives +// conversion routines based on file extension. The conversion routines should be +// !!!elsewhere!!! in libraries that the game also uses at runtime to convert data. +// This tool as spec'd should just be file iteration. +// +//=====================================================================================// + +#include "MakeGameData.h" + +// MAKESCENESIMAGE is defined for the external tool. In general, it only +// supports the -pcscenes option. This gets built into MakeScenesImage.exe. + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +class MakeGameDataApp : public CDefaultAppSystemGroup< CSteamAppSystemGroup > +{ +public: + // Methods of IApplication + virtual bool Create(); + virtual bool PreInit( ); + virtual int Main(); + virtual void PostShutdown(); +}; + +DEFINE_CONSOLE_STEAM_APPLICATION_OBJECT( MakeGameDataApp ); + +char g_szSourcePath[MAX_PATH]; +char g_targetPath[MAX_PATH]; +char g_zipPath[MAX_PATH]; +bool g_bForce; +bool g_bTest; +bool g_bMakeZip; +CXZipTool g_MasterXZip; +DiskWriteMode_t g_WriteModeForConversions; +char g_szGamePath[MAX_PATH]; +char g_szModPath[MAX_PATH]; +bool g_bModPathIsValid; +bool g_bQuiet; +bool g_bMakeScenes; +bool g_bMakeScenesPC; +bool g_bIsPlatformZip; +bool g_bUseMapList; + +IPhysicsCollision *g_pPhysicsCollision; +CSysModule *g_pPhysicsModule; + +CUtlVector< CUtlString > g_ValidMapList; +CUtlVector< errorList_t > g_errorList; + +const char *g_GameNames[] = +{ + "ep2", + "episodic", + "hl2", + "portal", + "platform", + "tf", + NULL +}; + +// all known languages +const char *g_pLanguageSuffixes[] = +{ + "_dannish", + "_dutch", + "_english", + "_finnish", + "_french", + "_german", + "_italian", + "_japanese", + "_korean", + "_koreana", + "_norwegian", + "_polish", + "_portuguese", + "_russian", + "_russion_buka", + "_schinese", + "_spanish", + "_swedish", + "_tchinese", + "_thai", +}; + +// 360 is shipping with support for only these languages +const char *g_pTargetLanguageSuffixes[] = +{ + "_english.", + "_french.", + "_german.", +}; + +// Master list of files that can go into the zip +static char *s_AllowedExtensionsInZip[] = +{ + // Explicitly lacking from this list, thus excluded from the zip + // .ain - AINs are external to the zip + // .bsp - BSPs are external to the zip + // .mp3 - MP3s aren't supported + + // Extensions with conversions + // Purposely using .360 encoding to ensure pc versions don't leak in + ".360.wav", + ".360.vtf", + ".360.mdl", + ".360.ani", + ".dx90.360.vtx", + ".360.vvd", + ".360.phy", + ".360.dat", + ".360.lst", + ".360.vcs", + ".360.image", + ".360.pcf", + + // Extensions without conversions (taken as is) + ".rc", + ".txt", + ".cfg", + ".res", + ".vfe", + ".vbf", + ".vmt", + ".raw", + ".lst", + ".bns", +}; + + + +//----------------------------------------------------------------------------- +// Determine game path +//----------------------------------------------------------------------------- +void GetGamePath() +{ + GetCurrentDirectory( sizeof( g_szGamePath ), g_szGamePath ); + + char szFullPath[MAX_PATH]; + if ( _fullpath( szFullPath, g_szGamePath, sizeof( szFullPath ) ) ) + { + strcpy( g_szGamePath, szFullPath ); + } + V_AppendSlash( g_szGamePath, sizeof( g_szGamePath ) ); + + char *pGameDir = V_stristr( g_szGamePath, "game\\" ); + if ( !pGameDir ) + { + Msg( "ERROR: Failed to determine game directory from current path. Expecting 'game' in current path." ); + exit( 1 ); + } + + // kill any trailing dirs + pGameDir[4] = '\0'; +} + +//----------------------------------------------------------------------------- +// Determine mod path +//----------------------------------------------------------------------------- +bool GetModPath() +{ + char szDirectory[MAX_PATH]; + char szLastDirectory[MAX_PATH]; + + // non destructively determine the mod directory + bool bFound = false; + szLastDirectory[0] = '\0'; + GetCurrentDirectory( sizeof( szDirectory ), szDirectory ); + while ( 1 ) + { + V_ComposeFileName( szDirectory, "gameinfo.txt", g_szModPath, sizeof( g_szModPath ) ); + struct _stat statBuf; + if ( _stat( g_szModPath, &statBuf ) != -1 ) + { + bFound = true; + V_strncpy( g_szModPath, szDirectory, sizeof( g_szModPath ) ); + break; + } + + // previous dir + V_ComposeFileName( szDirectory, "..", g_szModPath, sizeof( g_szModPath ) ); + + char fullPath[MAX_PATH]; + if ( _fullpath( fullPath, g_szModPath, sizeof( fullPath ) ) ) + { + strcpy( szDirectory, fullPath ); + } + + if ( !V_stricmp( szDirectory, szLastDirectory ) ) + { + // can back up no further + break; + } + strcpy( szLastDirectory, szDirectory ); + } + + if ( !bFound ) + { + // use current directory instead + GetCurrentDirectory( sizeof( g_szModPath ), g_szModPath ); + } + + return bFound; +} + +//----------------------------------------------------------------------------- +// Setup File system and search paths +//----------------------------------------------------------------------------- +bool SetupFileSystem() +{ + if ( g_bModPathIsValid ) + { + CFSSteamSetupInfo steamInfo; + steamInfo.m_pDirectoryName = g_szModPath; + steamInfo.m_bOnlyUseDirectoryName = true; + steamInfo.m_bToolsMode = true; + steamInfo.m_bSetSteamDLLPath = true; + steamInfo.m_bSteam = false; + if ( FileSystem_SetupSteamEnvironment( steamInfo ) != FS_OK ) + return false; + + CFSMountContentInfo fsInfo; + fsInfo.m_pFileSystem = g_pFullFileSystem; + fsInfo.m_bToolsMode = true; + fsInfo.m_pDirectoryName = steamInfo.m_GameInfoPath; + if ( FileSystem_MountContent( fsInfo ) != FS_OK ) + return false; + + // Finally, load the search paths for the "GAME" path. + CFSSearchPathsInit searchPathsInit; + searchPathsInit.m_pDirectoryName = steamInfo.m_GameInfoPath; + searchPathsInit.m_pFileSystem = fsInfo.m_pFileSystem; + if ( FileSystem_LoadSearchPaths( searchPathsInit ) != FS_OK ) + return false; + + char platform[MAX_PATH]; + Q_strncpy( platform, steamInfo.m_GameInfoPath, MAX_PATH ); + Q_StripTrailingSlash( platform ); + Q_strncat( platform, "/../platform", MAX_PATH, MAX_PATH ); + fsInfo.m_pFileSystem->AddSearchPath( platform, "PLATFORM" ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, read file into buffer +//----------------------------------------------------------------------------- +bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning ) +{ + return scriptlib->ReadFileToBuffer( pSourceName, buffer, bText, bNoOpenFailureWarning ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper utility, Write buffer to file +//----------------------------------------------------------------------------- +bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, bool bWriteToZip, DiskWriteMode_t writeMode ) +{ + if ( g_bTest ) + return true; + + bool bSuccess = scriptlib->WriteBufferToFile( pTargetName, buffer, writeMode ); + bool bZipSuccess = true; + + if ( bSuccess && g_bMakeZip && !g_bTest && bWriteToZip ) + { + if ( !g_MasterXZip.AddBuffer( pTargetName, buffer, true ) ) + { + Msg( "WriteBufferToFile(): Error adding file %s\n", pTargetName ); + bZipSuccess = false; + } + } + + return bSuccess && bZipSuccess; +} + +//----------------------------------------------------------------------------- +// Compress data +//----------------------------------------------------------------------------- +bool CompressCallback( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ) +{ + if ( !inputBuffer.TellPut() ) + { + // nothing to do + return false; + } + + unsigned int compressedSize; + unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)inputBuffer.Base() + inputBuffer.TellGet(), inputBuffer.TellPut() - inputBuffer.TellGet(), &compressedSize ); + if ( pCompressedOutput ) + { + outputBuffer.EnsureCapacity( compressedSize ); + outputBuffer.Put( pCompressedOutput, compressedSize ); + free( pCompressedOutput ); + return true; + } + + return false; +} + + + +//----------------------------------------------------------------------------- +// Some converters need to run a final pass. +//----------------------------------------------------------------------------- +void DoPostProcessingFunctions( bool bWriteToZip ) +{ + if ( g_bMakeScenes || g_bMakeScenesPC ) + { + // scenes are converted and aggregated into one image + CreateSceneImageFile( g_szModPath, bWriteToZip, g_bMakeScenesPC, g_bQuiet, g_WriteModeForConversions ); + } + + ProcessDXSupportConfig( bWriteToZip ); +} + +//----------------------------------------------------------------------------- +// Startup any conversion modules. +//----------------------------------------------------------------------------- +bool InitConversionModules() +{ + return true; +} + +//----------------------------------------------------------------------------- +// Shutdown and cleanup any conversion modules. +//----------------------------------------------------------------------------- +void ShutdownConversionModules() +{ +} + +//----------------------------------------------------------------------------- +// Purpose: Distribute to worker function +//----------------------------------------------------------------------------- +bool CreateTargetFile( const char *pSourceName, const char *pTargetName, fileType_e fileType, bool bWriteToZip ) +{ + // resolve relative source to absolute path + // same workers use subdir name to determine conversion parameters + char fullSourcePath[MAX_PATH]; + if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) ) + { + pSourceName = fullSourcePath; + } + + // distribute to actual worker + // workers can expect exact final decorated filenames + bool bSuccess = false; + switch ( fileType ) + { + case FILETYPE_UNKNOWN: + break; + + case FILETYPE_VTF: + bSuccess = CreateTargetFile_VTF( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_WAV: + bSuccess = CreateTargetFile_WAV( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_MDL: + case FILETYPE_ANI: + case FILETYPE_VTX: + case FILETYPE_VVD: + case FILETYPE_PHY: + bSuccess = CreateTargetFile_Model( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_BSP: + bSuccess = CreateTargetFile_BSP( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_AIN: + bSuccess = CreateTargetFile_AIN( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_CCDAT: + bSuccess = CreateTargetFile_CCDAT( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_MP3: + bSuccess = CreateTargetFile_MP3( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_RESLST: + bSuccess = CreateTargetFile_RESLST( pSourceName, pTargetName, bWriteToZip ); + break; + + case FILETYPE_PCF: + bSuccess = CreateTargetFile_PCF( pSourceName, pTargetName, bWriteToZip ); + break; + + // others... + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: Determine file type based on source extension. Generate .360.xxx +// target name. +//----------------------------------------------------------------------------- +fileType_e ResolveFileType( const char *pSourceName, char *pTargetName, int targetNameSize ) +{ + char szFullSourcePath[MAX_PATH]; + _fullpath( szFullSourcePath, pSourceName, sizeof( szFullSourcePath ) ); + + char sourceExtension[MAX_PATH]; + V_ExtractFileExtension( pSourceName, sourceExtension, sizeof( sourceExtension ) ); + + // default unrecognized + fileType_e fileType = FILETYPE_UNKNOWN; + + if ( !V_stricmp( sourceExtension, "wav" ) ) + { + fileType = FILETYPE_WAV; + } + else if ( !V_stricmp( sourceExtension, "vtf" ) ) + { + fileType = FILETYPE_VTF; + } + else if ( !V_stricmp( sourceExtension, "mdl" ) ) + { + fileType = FILETYPE_MDL; + } + else if ( !V_stricmp( sourceExtension, "ani" ) ) + { + fileType = FILETYPE_ANI; + } + else if ( !V_stricmp( sourceExtension, "vvd" ) ) + { + fileType = FILETYPE_VVD; + } + else if ( !V_stricmp( sourceExtension, "phy" ) ) + { + fileType = FILETYPE_PHY; + } + else if ( !V_stricmp( sourceExtension, "bsp" ) ) + { + fileType = FILETYPE_BSP; + } + else if ( !V_stricmp( sourceExtension, "ain" ) ) + { + fileType = FILETYPE_AIN; + } + else if ( !V_stricmp( sourceExtension, "dat" ) ) + { + if ( V_stristr( pSourceName, "closecaption_" ) ) + { + // only want closecaption dat files, ignore all others + fileType = FILETYPE_CCDAT; + } + } + else if ( !V_stricmp( sourceExtension, "vtx" ) ) + { + if ( V_stristr( pSourceName, ".dx90" ) ) + { + // only want dx90 version, ignore all others + fileType = FILETYPE_VTX; + } + } + else if ( !V_stricmp( sourceExtension, "mp3" ) ) + { + // mp3's are already pre-converted into .360.wav + // slam the expected name here to simplify the external logic which will do the right thing + V_StripExtension( pSourceName, pTargetName, targetNameSize ); + V_strcat( pTargetName, ".360.wav", targetNameSize ); + return FILETYPE_MP3; + } + else if ( !V_stricmp( sourceExtension, "lst" ) ) + { + if ( V_stristr( szFullSourcePath, "reslists_xbox\\" ) ) + { + // only want reslists map versions, due to special processing, ignore all others + fileType = FILETYPE_RESLST; + } + } + else if ( !V_stricmp( sourceExtension, "pcf" ) ) + { + fileType = FILETYPE_PCF; + } + else if ( !V_stricmp( sourceExtension, "image" ) ) + { + if ( V_stristr( szFullSourcePath, "scenes\\" ) ) + { + // only want scene image, ignore all others + fileType = FILETYPE_SCENEIMAGE; + } + } + + if ( fileType != FILETYPE_UNKNOWN && !V_stristr( pSourceName, ".360." ) ) + { + char targetExtension[MAX_PATH]; + sprintf( targetExtension, ".360.%s", sourceExtension ); + + V_StripExtension( pSourceName, pTargetName, targetNameSize ); + V_strcat( pTargetName, targetExtension, targetNameSize ); + } + else + { + // unknown or already converted, target is same as input + V_strncpy( pTargetName, pSourceName, targetNameSize ); + } + + return fileType; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns TRUE if file is a known localized file. +//----------------------------------------------------------------------------- +bool IsLocalizedFile( const char *pFileName ) +{ + for ( int i = 0; i<ARRAYSIZE( g_pLanguageSuffixes ); i++ ) + { + if ( V_stristr( pFileName, g_pLanguageSuffixes[i] ) ) + { + // a localized file + return true; + } + } + + // not a known localized file + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Returns TRUE if file is a supported localization. +//----------------------------------------------------------------------------- +bool IsLocalizedFileValid( const char *pFileName, const char *pLanguageSuffix ) +{ + // file is a localized version + if ( pLanguageSuffix ) + { + if ( V_stristr( pFileName, pLanguageSuffix ) ) + { + // allow it + return true; + } + + return false; + } + + // must match the target supported languages + for ( int i = 0; i < ARRAYSIZE( g_pTargetLanguageSuffixes ); i++ ) + { + if ( V_stristr( pFileName, g_pTargetLanguageSuffixes[i] ) ) + { + // allow it + return true; + } + } + + // does not match a target language, not allowed + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Check against a list of allowed filetypes for inclusion in the zip +//----------------------------------------------------------------------------- +bool IncludeInZip( const char *pSourceName ) +{ + if ( g_bIsPlatformZip ) + { + // only allow known valid platform directories + if ( !V_stristr( pSourceName, "materials\\" ) && + !V_stristr( pSourceName, "resource\\" ) && + !V_stristr( pSourceName, "vgui\\" ) ) + { + // exclude it + return false; + } + } + + for ( int i = 0; i < ARRAYSIZE( s_AllowedExtensionsInZip ); ++i ) + { + const char *pAllowedExtension = s_AllowedExtensionsInZip[i]; + if ( !V_stristr( pSourceName, pAllowedExtension ) ) + { + continue; + } + + if ( !V_stricmp( pAllowedExtension, ".lst" ) ) + { + // only want ???_exclude.lst files + if ( V_stristr( pSourceName, "_exclude.lst" ) ) + { + return true; + } + return false; + } + + if ( !V_stricmp( pAllowedExtension, ".txt" ) || !V_stricmp( pAllowedExtension, ".360.dat" ) ) + { + if ( IsLocalizedFile( pSourceName ) && !IsLocalizedFileValid( pSourceName ) ) + { + // exclude unsupported languages + return false; + } + + if ( !V_stricmp( pAllowedExtension, ".txt" ) && V_stristr( pSourceName, "closecaption_" ) ) + { + // exclude all the closecaption_<language>.txt files + return false; + } + } + + return true; + } + + // exclude it + return false; +} + +//----------------------------------------------------------------------------- +// Returns true if map is in list, otherwise false +//----------------------------------------------------------------------------- +bool IsMapNameInList( const char *pMapName, CUtlVector< CUtlString > &mapList ) +{ + char szBaseName[MAX_PATH]; + + V_FileBase( pMapName, szBaseName, sizeof( szBaseName ) ); + V_strlower( szBaseName ); + + if ( mapList.Find( szBaseName ) != mapList.InvalidIndex() ) + { + // found + return true; + } + + // not found + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the list of valid BSPs +//----------------------------------------------------------------------------- +void BuildValidMapList( CUtlVector< CUtlString > &mapList ) +{ + char szFilename[MAX_PATH]; + V_ComposeFileName( g_szModPath, "maplist.txt", szFilename, sizeof( szFilename ) ); + + CUtlBuffer buffer; + if ( !ReadFileToBuffer( szFilename, buffer, true, true ) ) + { + return; + } + + characterset_t breakSet; + CharacterSetBuild( &breakSet, "" ); + + char szToken[MAX_PATH]; + char szMapName[MAX_PATH]; + for ( ;; ) + { + int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + // reslists are pc built, filenames can be sloppy + V_FileBase( szToken, szMapName, sizeof( szMapName ) ); + V_strlower( szMapName ); + + mapList.AddToTail( szMapName ); + } +} + +#define DO_UPDATE 0x01 +#define DO_ZIP 0x02 + +//----------------------------------------------------------------------------- +// Purpose: drive the file creation +//----------------------------------------------------------------------------- +void GenerateTargetFiles( CUtlVector<fileList_t> &fileList ) +{ + char sourcePath[MAX_PATH]; + char sourceFile[MAX_PATH]; + char targetFile[MAX_PATH]; + struct _stat sourceStatBuf; + struct _stat targetStatBuf; + + strcpy( sourcePath, g_szSourcePath ); + V_StripFilename( sourcePath ); + if ( !sourcePath[0] ) + strcpy( sourcePath, "." ); + V_AppendSlash( sourcePath, sizeof( sourcePath ) ); + + // default is to update and zip + CUtlVector< int > updateList; + updateList.AddMultipleToTail( fileList.Count() ); + for ( int i=0; i<fileList.Count(); i++ ) + { + updateList[i] = DO_UPDATE|DO_ZIP; + } + int numMatches = 0; + + // build update list + for ( int i=0; i<fileList.Count(); i++ ) + { + if ( fileList[i].fileName.IsEmpty() ) + { + // ignore entries that have been culled + updateList[i] = 0; + continue; + } + + const char *ptr = fileList[i].fileName.String(); + if ( !strnicmp( ptr, ".\\", 2 ) ) + ptr += 2; + else if ( !strnicmp( ptr, sourcePath, strlen( sourcePath ) ) ) + ptr += strlen( sourcePath ); + + strcpy( sourceFile, sourcePath ); + strcat( sourceFile, ptr ); + strcpy( targetFile, g_targetPath ); + strcat( targetFile, ptr ); + + fileType_e fileType = ResolveFileType( sourceFile, targetFile, sizeof( targetFile ) ); + + // check the target name for inclusion due to extension modifications + if ( !IncludeInZip( targetFile ) ) + { + // exclude from zip + updateList[i] &= ~DO_ZIP; + } + + if ( fileType == FILETYPE_UNKNOWN ) + { + // No conversion function, can't do anything + updateList[i] &= ~DO_UPDATE; + continue; + } + else + { + // known filetype, which has a conversion + // the wildcard match may catch existing converted files, which need to be rejected + // cull exisiting conversions from the work list + // the non-converted filename is part of the same wildcard match, gets caught and resolved + // the non-converted filename will then go through the proper conversion path + if ( V_stristr( sourceFile, ".360." ) ) + { + // cull completely + updateList[i] = 0; + continue; + } + } + + if ( fileType == FILETYPE_BSP || fileType == FILETYPE_AIN || fileType == FILETYPE_RESLST ) + { + if ( g_ValidMapList.Count() && !IsMapNameInList( sourceFile, g_ValidMapList ) ) + { + // cull completely + updateList[i] = 0; + continue; + } + } + + int retVal = _stat( sourceFile, &sourceStatBuf ); + if ( retVal != 0 ) + { + // couldn't get source, skip update or zip + updateList[i] = 0; + continue; + } + + retVal = _stat( targetFile, &targetStatBuf ); + if ( retVal != 0 ) + { + // target doesn't exit, update is required + continue; + } + + // track valid candidates + numMatches++; + + if ( fileType == FILETYPE_MDL || fileType == FILETYPE_ANI || fileType == FILETYPE_VTX || fileType == FILETYPE_VVD || fileType == FILETYPE_PHY ) + { + // models are converted in a pre-pass + // let the update logic run and catch the conversions for zipping + continue; + } + + if ( !g_bForce ) + { + if ( difftime( sourceStatBuf.st_mtime, targetStatBuf.st_mtime ) <= 0 ) + { + // target is same or older, no update + updateList[i] &= ~DO_UPDATE; + } + } + } + + // cleanse and determine totals, makes succeeding logic simpler + int numWorkItems = 0; + int numFilesToUpdate = 0; + int numFilesToZip = 0; + for ( int i=0; i<fileList.Count(); i++ ) + { + if ( updateList[i] & DO_UPDATE ) + { + numFilesToUpdate++; + } + if ( g_bMakeZip && ( updateList[i] & DO_ZIP ) ) + { + numFilesToZip++; + } + else + { + updateList[i] &= ~DO_ZIP; + } + if ( updateList[i] ) + { + numWorkItems++; + } + } + + Msg( "\n" ); + Msg( "Matched %d/%d files.\n", numMatches, fileList.Count() ); + Msg( "Creating or Updating %d files.\n", numFilesToUpdate ); + if ( g_bMakeZip ) + { + Msg( "Zipping %d files.\n", numFilesToZip ); + } + + InitConversionModules(); + + if ( numFilesToZip && !g_bTest ) + { + Msg( "Creating Zip: %s\n", g_zipPath ); + if ( !g_MasterXZip.Begin( g_zipPath, XBOX_DVD_SECTORSIZE ) ) + { + Msg( "ERROR: Failed to open \"%s\" for writing.\n", g_zipPath ); + return; + } + else + { + SetupCriticalPreloadScript( g_szModPath ); + } + } + + // iterate work list + int progress = 0; + for ( int i=0; i<fileList.Count(); i++ ) + { + if ( !updateList[i] ) + { + // no update or zip needed, skip + continue; + } + + const char *ptr = fileList[i].fileName.String(); + if ( !strnicmp( ptr, ".\\", 2 ) ) + ptr += 2; + else if ( !strnicmp( ptr, sourcePath, strlen( sourcePath ) ) ) + ptr += strlen( sourcePath ); + + strcpy( sourceFile, sourcePath ); + strcat( sourceFile, ptr ); + strcpy( targetFile, g_targetPath ); + strcat( targetFile, ptr ); + + fileType_e fileType = ResolveFileType( sourceFile, targetFile, sizeof( targetFile ) ); + + if ( !g_bQuiet ) + { + Msg( "%d/%d:%s -> %s\n", progress+1, numWorkItems, sourceFile, targetFile ); + } + + bool bSuccess = true; + if ( updateList[i] & DO_UPDATE ) + { + // generate target file (and optionally zip output) + bSuccess = CreateTargetFile( sourceFile, targetFile, fileType, (updateList[i] & DO_ZIP) != 0 ); + if ( !bSuccess ) + { + // add to error list + int error = g_errorList.AddToTail(); + g_errorList[error].result = false; + g_errorList[error].fileName.Set( sourceFile ); + } + } + else if ( updateList[i] & DO_ZIP ) + { + // existing target file is zipped + CUtlBuffer targetBuffer; + bSuccess = scriptlib->ReadFileToBuffer( targetFile, targetBuffer ); + if ( bSuccess ) + { + if ( !g_bTest ) + { + bSuccess = g_MasterXZip.AddBuffer( targetFile, targetBuffer, true ); + } + } + if ( !bSuccess ) + { + // add to error list + int error = g_errorList.AddToTail(); + g_errorList[error].result = false; + g_errorList[error].fileName.Set( targetFile ); + } + } + + progress++; + } + + DoPostProcessingFunctions( !g_bTest ); + + ShutdownConversionModules(); + + if ( numFilesToZip && !g_bTest ) + { + g_MasterXZip.End(); + } + + // iterate error list + Msg( "\n" ); + for ( int i = 0; i < g_errorList.Count(); i++ ) + { + Msg( "ERROR: could not process %s\n", g_errorList[i].fileName.String() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Spew Usage +//----------------------------------------------------------------------------- +void Usage() +{ + Msg( "usage: MakeGameData [filemask] [options]\n" ); + Msg( "options:\n" ); + Msg( "[-v] Version\n" ); + Msg( "[-q] Quiet (critical spew only)\n" ); + Msg( "[-h] [-help] [-?] Help\n" ); + Msg( "[-t targetPath] Alternate output path, will generate output at target\n" ); + Msg( "[-r] [-recurse] Recurse into source directory\n" ); + Msg( "[-f] [-force] Force update, otherwise checks timestamps\n" ); + Msg( "[-test] Skip writing to disk\n" ); + Msg( "[-z <zipname>] Generate zip file AND create or update stale conversions\n" ); + Msg( "[-zo <zipname>] Generate zip file ONLY (existing stale conversions get updated, no new conversions are written)\n" ); + Msg( "[-preloadinfo] Spew contents of preload section in zip\n" ); + Msg( "[-xmaquality <quality>] XMA Encoding quality override, [0-100]\n" ); + Msg( "[-scenes] Make 360 scene image cache.\n" ); + Msg( "[-pcscenes] Make PC scene image cache.\n" ); + Msg( "[-usemaplist] For BSP related conversions, restricts to maplist.txt.\n" ); + + exit( 1 ); +} + +//----------------------------------------------------------------------------- +// Purpose: default output func +//----------------------------------------------------------------------------- +SpewRetval_t OutputFunc( SpewType_t spewType, char const *pMsg ) +{ + printf( pMsg ); + + if ( spewType == SPEW_ERROR ) + { + return SPEW_ABORT; + } + return ( spewType == SPEW_ASSERT ) ? SPEW_DEBUGGER : SPEW_CONTINUE; +} + +//----------------------------------------------------------------------------- +// The application object +//----------------------------------------------------------------------------- +bool MakeGameDataApp::Create() +{ + SpewOutputFunc( OutputFunc ); + + AppSystemInfo_t appSystems[] = + { + { "mdllib.dll", MDLLIB_INTERFACE_VERSION }, + { "", "" } // Required to terminate the list + }; + + AddSystem( g_pDataModel, VDATAMODEL_INTERFACE_VERSION ); + AddSystem( g_pDmSerializers, DMSERIALIZERS_INTERFACE_VERSION ); + + // Load vphysics.dll + if ( !Sys_LoadInterface( "vphysics.dll", VPHYSICS_COLLISION_INTERFACE_VERSION, &g_pPhysicsModule, (void**)&g_pPhysicsCollision ) ) + { + Msg( "Failed to load vphysics interface\n" ); + return false; + } + + bool bOk = AddSystems( appSystems ); + if ( !bOk ) + return false; + + return true; +} + +bool MakeGameDataApp::PreInit() +{ + CreateInterfaceFn factory = GetFactory(); + + ConnectTier1Libraries( &factory, 1 ); + ConnectTier2Libraries( &factory, 1 ); + + if ( !g_pFullFileSystem || !g_pDataModel || !g_pPhysicsCollision || !mdllib ) + { + Warning( "MakeGameData is missing a required interface!\n" ); + return false; + } + + return true; +} + +void MakeGameDataApp::PostShutdown() +{ + if ( g_pPhysicsModule ) + { + Sys_UnloadModule( g_pPhysicsModule ); + g_pPhysicsModule = NULL; + g_pPhysicsCollision = NULL; + } + + DisconnectTier2Libraries(); + DisconnectTier1Libraries(); +} + +//----------------------------------------------------------------------------- +// main +// +//----------------------------------------------------------------------------- +int MakeGameDataApp::Main() +{ + int argnum; + + // set the valve library printer + SpewOutputFunc( OutputFunc ); + + Msg( "\nMAKEGAMEDATA - Valve Xbox 360 Game Data Compiler (Build: %s %s)\n", __DATE__, __TIME__ ); + Msg( "(C) Copyright 1996-2006, Valve Corporation, All rights reserved.\n\n" ); + + if ( CommandLine()->FindParm( "-v" ) || CommandLine()->FindParm( "-version" ) ) + { + // spew just the version, used by batches for logging + return 0; + } + +#ifndef MAKESCENESIMAGE + if ( CommandLine()->ParmCount() < 2 || CommandLine()->FindParm( "?" ) || CommandLine()->FindParm( "-h" ) || CommandLine()->FindParm( "-help" ) ) + { + Usage(); + } +#endif // MAKESCENESIMAGE + + bool bHasFileMask = false; + + const char *pFirstArg = CommandLine()->GetParm( 1 ); + if ( pFirstArg[0] == '-' ) + { + // first arg is an option, assume *.* + strcpy( g_szSourcePath, "*.*" ); + } + else + { + strcpy( g_szSourcePath, pFirstArg ); + bHasFileMask = true; + } + + bool bRecurse = false; +#ifndef MAKESCENESIMAGE + bRecurse = CommandLine()->FindParm( "-recurse" ) || CommandLine()->FindParm( "-r" ); + g_bForce = CommandLine()->FindParm( "-force" ) || CommandLine()->FindParm( "-f" ); + g_bTest = CommandLine()->FindParm( "-test" ) != 0; + g_bQuiet = CommandLine()->FindParm( "-quiet" ) || CommandLine()->FindParm( "-q" ); + g_bMakeScenes = CommandLine()->FindParm( "-scenes" ) != 0; + g_bMakeScenesPC = CommandLine()->FindParm( "-pcscenes" ) != 0; +#else + g_bMakeScenesPC = true; +#endif // MAKESCENESIMAGE + +#ifndef MAKESCENESIMAGE + g_bUseMapList = CommandLine()->FindParm( "-usemaplist" ) != 0; +#endif // MAKESCENESIMAGE + + // Set up zip file options + g_WriteModeForConversions = WRITE_TO_DISK_ALWAYS; + argnum = CommandLine()->FindParm( "-z" ); + if ( argnum ) + { + strcpy( g_szSourcePath, "*.*" ); + g_bMakeZip = true; + g_bMakeScenes = true; + bRecurse = true; + } + else + { + argnum = CommandLine()->FindParm( "-zo" ); + if ( argnum ) + { + strcpy( g_szSourcePath, "*.*" ); + g_bMakeZip = true; + g_bMakeScenes = true; + g_WriteModeForConversions = WRITE_TO_DISK_UPDATE; + bRecurse = true; + } + } + if ( g_bMakeZip ) + { + strcat( g_zipPath, CommandLine()->GetParm( argnum + 1 ) ); + } + + // default target path is source + strcpy( g_targetPath, g_szSourcePath ); + V_StripFilename( g_targetPath ); + if ( !g_targetPath[0] ) + { + strcpy( g_targetPath, "." ); + } + + // override via command line + argnum = CommandLine()->FindParm( "-t" ); + if ( argnum ) + { + V_strcpy_safe( g_targetPath, CommandLine()->GetParm( argnum + 1 ) ); + } + V_AppendSlash( g_targetPath, sizeof( g_targetPath ) ); + + if ( CommandLine()->FindParm( "-preloadinfo" ) ) + { + g_MasterXZip.SpewPreloadInfo( g_szSourcePath ); + return 0; + } + +#ifndef MAKESCENESIMAGE + GetGamePath(); +#endif // MAKESCENESIMAGE + + g_bModPathIsValid = GetModPath(); + if ( !SetupFileSystem() ) + { + Msg( "ERROR: Failed to setup file system.\n" ); + exit( 1 ); + } + + // data model initialization + g_pDataModel->SetUndoEnabled( false ); + g_pDataModel->OnlyCreateUntypedElements( true ); + g_pDataModel->SetDefaultElementFactory( NULL ); + + g_bIsPlatformZip = g_bMakeZip && ( V_stristr( g_szModPath, "\\platform" ) != NULL ); + + // cleanup any zombie temp files left from a possible prior abort or error + scriptlib->DeleteTemporaryFiles( "mgd_*.tmp" ); + + if ( g_bMakeZip || g_bUseMapList ) + { + // zips use the map list to narrow the bsp conversion to actual shipping maps + BuildValidMapList( g_ValidMapList ); + } + + CUtlVector<fileList_t> fileList; + if ( bHasFileMask || g_bMakeZip ) + { + scriptlib->FindFiles( g_szSourcePath, bRecurse, fileList ); + } + + // model conversions require seperate pre-processing to achieve grouping + if ( !PreprocessModelFiles( fileList ) ) + { + return 0; + } + + GenerateTargetFiles( fileList ); + + return 0; +} diff --git a/utils/xbox/MakeGameData/MakeGameData.h b/utils/xbox/MakeGameData/MakeGameData.h new file mode 100644 index 0000000..c2f2004 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeGameData.h @@ -0,0 +1,165 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#pragma once + +#include <windows.h> +#include <mmreg.h> +#include <stdio.h> +#include <stdlib.h> +#include <direct.h> +#include <io.h> +#include <time.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/utime.h> +#include "tier0/dbg.h" +#include "tier0/icommandline.h" +#include "appframework/appframework.h" +#include "characterset.h" +#include "tier1/strtools.h" +#include "tier1/UtlVector.h" +#include "tier1/UtlBuffer.h" +#include "tier1/UtlString.h" +#include "tier1/UtlRBTree.h" +#include "tier1/UtlDict.h" +#include "tier1/UtlSortVector.h" +#include "tier1/UtlStringMap.h" +#include "tier1/UtlSymbol.h" +#include "tier1/KeyValues.h" +#include "tier1/lzss.h" +#include "lzma/lzma.h" +#include "datamap.h" +#include "XZipTool.h" +#include "../../common/scriplib.h" +#include "../../common/cmdlib.h" +#include "tier2/tier2.h" +#include "dmserializers/idmserializers.h" +#include "datamodel/dmattribute.h" +#include "datamodel/dmelement.h" +#include "studiobyteswap.h" +#include "studio.h" +#include "vphysics_interface.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/hardwareverts.h" +#include "optimize.h" +#include "soundchars.h" +#include "mdllib/mdllib.h" + +enum fileType_e +{ + FILETYPE_UNKNOWN = 0, + FILETYPE_WAV, + FILETYPE_VTF, + FILETYPE_MDL, + FILETYPE_ANI, + FILETYPE_VTX, + FILETYPE_VVD, + FILETYPE_PHY, + FILETYPE_BSP, + FILETYPE_AIN, + FILETYPE_CCDAT, + FILETYPE_MP3, + FILETYPE_RESLST, + FILETYPE_PCF, + FILETYPE_SCENEIMAGE +}; + +typedef struct +{ + int result; + CUtlString fileName; +} errorList_t; + +//----------------------------------------------------------------------------- +// MakeGameData.cpp +//----------------------------------------------------------------------------- + + +bool IsLocalizedFile( const char *pFileName ); +bool IsLocalizedFileValid( const char *pFileName, const char *pLanguageSuffix = NULL ); +bool CompressCallback( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ); +bool ReadFileToBuffer( const char *pSourceName, CUtlBuffer &buffer, bool bText, bool bNoOpenFailureWarning ); +bool WriteBufferToFile( const char *pTargetName, CUtlBuffer &buffer, bool bWriteToZip, DiskWriteMode_t writeMode ); +fileType_e ResolveFileType( const char *pSourceName, char *pTargetName, int targetNameSize ); + +extern DiskWriteMode_t g_WriteModeForConversions; +extern CXZipTool g_MasterXZip; +extern char g_szGamePath[]; +extern char g_szModPath[]; +extern bool g_bModPathIsValid; +extern const char *g_GameNames[]; +extern bool g_bForce; +extern bool g_bQuiet; +extern bool g_bMakeZip; +extern bool g_bIsPlatformZip; +extern IPhysicsCollision *g_pPhysicsCollision; +extern char g_szSourcePath[]; +extern CUtlVector< errorList_t > g_errorList; + +//----------------------------------------------------------------------------- +// MakeTextures.cpp +//----------------------------------------------------------------------------- +bool CreateTargetFile_VTF( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool GetPreloadData_VTF( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); + +//----------------------------------------------------------------------------- +// MakeScenes.cpp +//----------------------------------------------------------------------------- +bool CreateSceneImageFile( char const *pchModPath, bool bWriteToZip, bool bLittleEndian, bool bQuiet, DiskWriteMode_t eWriteModeForConversions ); + +//----------------------------------------------------------------------------- +// MakeSounds.cpp +//----------------------------------------------------------------------------- +bool CreateTargetFile_WAV( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool CreateTargetFile_MP3( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool GetPreloadData_WAV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); + +//----------------------------------------------------------------------------- +// MakeMaps.cpp +//----------------------------------------------------------------------------- +bool CreateTargetFile_BSP( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool CreateTargetFile_AIN( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool GetDependants_BSP( const char *pBspName, CUtlVector< CUtlString > *pList ); + +//----------------------------------------------------------------------------- +// MakeResources.cpp +//----------------------------------------------------------------------------- +bool CreateTargetFile_CCDAT( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool CreateTargetFile_RESLST( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); + +//----------------------------------------------------------------------------- +// MakeModels.cpp +//----------------------------------------------------------------------------- +bool InitStudioByteSwap( void ); +void ShutdownStudioByteSwap( void ); +bool CreateTargetFile_Model( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); +bool GetDependants_MDL( const char *pModelName, CUtlVector< CUtlString > *pList ); +bool GetPreloadData_VHV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); +bool GetPreloadData_VTX( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); +bool GetPreloadData_VVD( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); +bool PreprocessModelFiles( CUtlVector<fileList_t> &fileList ); + +//----------------------------------------------------------------------------- +// MakeShaders.cpp +//----------------------------------------------------------------------------- +bool GetPreloadData_VCS( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ); + +//----------------------------------------------------------------------------- +// MakeMisc.cpp +//----------------------------------------------------------------------------- +bool ProcessDXSupportConfig( bool bWriteToZip ); + +//----------------------------------------------------------------------------- +// MakeResources.cpp +//----------------------------------------------------------------------------- +bool CreateTargetFile_PCF( const char *pSourceName, const char *pTargetName, bool bWriteToZip ); + +//----------------------------------------------------------------------------- +// MakeZip.cpp +//----------------------------------------------------------------------------- +void SetupCriticalPreloadScript( const char *pModPath ); diff --git a/utils/xbox/MakeGameData/MakeGameData.vpc b/utils/xbox/MakeGameData/MakeGameData.vpc new file mode 100644 index 0000000..315333c --- /dev/null +++ b/utils/xbox/MakeGameData/MakeGameData.vpc @@ -0,0 +1,111 @@ +//----------------------------------------------------------------------------- +// MAKEGAMEDATA.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE;$SRCDIR\x360xdk\include\win32\vs2005;$SRCDIR\game\shared" + $PreprocessorDefinitions "$BASE;NO_X360_XDK" [$VS2015] + } + + $Linker + { + $AdditionalDependencies "$BASE xgraphics.lib d3d9.lib legacy_stdio_definitions.lib" + $AdditionalDependencies "$BASE xmaencoder.lib" [!$VS2015] + $AdditionalLibraryDirectories "$BASE;$SRCDIR\x360xdk\lib\win32\vs2005" + } +} + +$Project "MakeGameData" +{ + $Folder "Source Files" + { + $File "MakeGameData.cpp" + $File "MakeMaps.cpp" + $File "MakeMisc.cpp" + $File "MakeModels.cpp" + $File "MakeParticles.cpp" + $File "MakeResources.cpp" + $File "MakeScenes.cpp" + $File "MakeShaders.cpp" + $File "MakeSounds.cpp" + $File "MakeTextures.cpp" + $File "MakeZip.cpp" + + $Folder "Audio" + { + $File "imaadpcm.cpp" + $File "sound_io.cpp" + $File "resample.cpp" + } + + $Folder "Public Modules" + { + $File "$SRCDIR\common\compiledcaptionswap.cpp" + $file "$SRCDIR\common\studiobyteswap.cpp" + $File "$SRCDIR\utils\common\scriplib.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + $File "$SRCDIR\public\sentence.cpp" + $File "$SRCDIR\utils\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "$SRCDIR\utils\common\filesystem_tools.cpp" + $File "$SRCDIR\public\interpolatortypes.cpp" + } + } + + $Folder "Header Files" + { + $File "MakeGameData.h" + $File "XZipTool.h" + $File "imaadpcm.h" + $File "resample.h" + $File "$SRCDIR\public\captioncompiler.h" + $File "$SRCDIR\common\studiobyteswap.h" + $File "$SRCDIR\utils\common\scriplib.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\ibsppack.h" + $File "$SRCDIR\public\sentence.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\xwvfile.h" + $File "$SRCDIR\public\zip_utils.h" + $File "$SRCDIR\game\shared\choreoscene.h" + $File "$SRCDIR\game\shared\choreoactor.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\filesystem_init.h" + $File "$SRCDIR\utils\common\filesystem_tools.h" + $File "$SRCDIR\public\interpolatortypes.h" + $File "$SRCDIR\utils\common\cmdlib.h" + $File "$SRCDIR\game\shared\choreochannel.h" + $File "$SRCDIR\game\shared\choreoevent.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier2\tier2.h" + $File "$SRCDIR\common\lzma\lzma.h" + } + + $Folder "Link Libraries" + { + $Lib appframework + $Lib mathlib + $Lib vtf + $Lib tier2 + $Lib choreoobjects + $Lib bitmap + $Lib datamodel + $Lib dmserializers + $Lib $LIBCOMMON\lzma + } +} diff --git a/utils/xbox/MakeGameData/MakeMaps.cpp b/utils/xbox/MakeGameData/MakeMaps.cpp new file mode 100644 index 0000000..618e5f6 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeMaps.cpp @@ -0,0 +1,383 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 Creation for all studiomdl generated files (mdl, vvd, vtx, ani, phy) +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "filesystem.h" +#include "../../common/bsplib.h" +#include "ibsppack.h" +#include "vtf/vtf.h" +#include "../../game/server/ai_hull.h" +#include "zip_utils.h" + +#define AINET_VERSION_NUMBER 37 +#define MAX_NODES 1500 + +bool ReadBSPHeader( const char *pFilename, dheader_t *pHeader ) +{ + V_memset( pHeader, 0, sizeof( dheader_t ) ); + + int handle = _open( pFilename, _O_RDONLY|_O_BINARY ); + if ( handle == -1 ) + { + return false; + } + + _read( handle, pHeader, sizeof( dheader_t ) ); + close( handle ); + + return true; +} + +//----------------------------------------------------------------------------- +// Run possible lod culling fixup +//----------------------------------------------------------------------------- +bool ConvertVHV( const char *pVhvFilename, const char *pModelName, CUtlBuffer &sourceBuffer, CUtlBuffer &targetBuffer ) +{ + // find strip info from model + char vsiFilename[MAX_PATH]; + V_strncpy( vsiFilename, pModelName, sizeof( vsiFilename ) ); + V_SetExtension( vsiFilename, ".vsi", sizeof( vsiFilename ) ); + + CUtlBuffer vsiBuffer; + if ( !g_pFullFileSystem->ReadFile( vsiFilename, NULL, vsiBuffer ) ) + { + // cannot convert bsp's without model converions + Msg( "Error! Missing expected model conversion file '%s'. Cannot perform VHV fixup.\n", vsiFilename ); + return false; + } + + IMdlStripInfo *pMdlStripInfo = NULL; + if ( !mdllib->CreateNewStripInfo( &pMdlStripInfo ) ) + { + Msg( "Error! Failed to allocate strip info object\n" ); + return false; + } + + if ( !pMdlStripInfo->UnSerialize( vsiBuffer ) ) + { + Msg( "Error! Failed to unserialize strip info object '%s'\n", vsiFilename ); + pMdlStripInfo->DeleteThis(); + return false; + } + + long originalChecksum = 0; + long newChecksum = 0; + if ( !pMdlStripInfo->GetCheckSum( &originalChecksum, &newChecksum ) ) + { + Msg( "Error! Failed to get checksums from '%s'\n", vsiFilename ); + pMdlStripInfo->DeleteThis(); + return false; + } + + HardwareVerts::FileHeader_t *pVHVhdr = (HardwareVerts::FileHeader_t*)sourceBuffer.Base(); + if ( pVHVhdr->m_nChecksum != originalChecksum ) + { + // vhv file should have matching original checksums + Msg( "Error! Mismatched checksums from '%s' and '%s'\n", vsiFilename, pVhvFilename ); + pMdlStripInfo->DeleteThis(); + return false; + } + + targetBuffer.EnsureCapacity( sourceBuffer.TellMaxPut() ); + targetBuffer.Put( sourceBuffer.Base(), sourceBuffer.TellMaxPut() ); + if ( !pMdlStripInfo->StripHardwareVertsBuffer( targetBuffer ) ) + { + pMdlStripInfo->DeleteThis(); + return false; + } + + // success + pMdlStripInfo->DeleteThis(); + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate .360 bsp +//----------------------------------------------------------------------------- +bool CreateTargetFile_BSP( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + CUtlBuffer targetBuffer; + CUtlBuffer zipBuffer; + CUtlBuffer fileBuffer; + CUtlBuffer tempBuffer; + char tempZipName[MAX_PATH]; + char tempSwapName[MAX_PATH]; + + tempZipName[0] = '\0'; + tempSwapName[0] = '\0'; + void *pPakData = NULL; + int pakSize = 0; + CXZipTool *pNewXZip = NULL; + + if ( !g_bModPathIsValid ) + { + Msg( "Indeterminate mod path, Cannot perform BSP conversion\n" ); + return false; + } + + // Load bsppack.dll + void *pBSPPack; + CSysModule *pBSPModule; + if ( !Sys_LoadInterface( "bsppack.dll", IBSPPACK_VERSION_STRING, &pBSPModule, &pBSPPack ) ) + { + Msg( "Failed to load bsppack interface\n" ); + return false; + } + + scriptlib->MakeTemporaryFilename( g_szModPath, tempSwapName, sizeof( tempSwapName ) ); + + // Swaps the bsp directly to disk + bool bOK = ((IBSPPack*)pBSPPack)->SwapBSPFile( g_pFullFileSystem, pSourceName, tempSwapName, false, ConvertVTFTo360Format, ConvertVHV, CompressCallback ); + if ( !bOK ) + { + goto cleanUp; + } + + // get the pak file from the swapped bsp + bOK = ((IBSPPack*)pBSPPack)->GetPakFileLump( g_pFullFileSystem, tempSwapName, &pPakData, &pakSize ); + if ( !bOK ) + { + goto cleanUp; + } + + // build an xzip version of the pak file + if ( pPakData && pakSize ) + { + // mount current pak file + IZip *pOldZip = IZip::CreateZip( false, true ); + pOldZip->ParseFromBuffer( pPakData, pakSize ); + + // start a new xzip version + scriptlib->MakeTemporaryFilename( g_szModPath, tempZipName, sizeof( tempZipName ) ); + + pNewXZip = new CXZipTool; + pNewXZip->Begin( tempZipName, XBOX_DVD_SECTORSIZE ); + + // iterate each file in existing zip, add to new zip + int zipIndex = -1; + for ( ;; ) + { + char filename[MAX_PATH]; + filename[0] = '\0'; + int fileSize = 0; + zipIndex = pOldZip->GetNextFilename( zipIndex, filename, sizeof( filename ), fileSize ); + if ( zipIndex == -1 ) + { + break; + } + + fileBuffer.Purge(); + bOK = pOldZip->ReadFileFromZip( filename, false, fileBuffer ); + if ( !bOK ) + { + goto cleanUp; + } + + bOK = pNewXZip->AddBuffer( filename, fileBuffer, true ); + if ( !bOK ) + { + goto cleanUp; + } + } + + IZip::ReleaseZip( pOldZip ); + pNewXZip->End(); + + // read the new zip into memory + bOK = scriptlib->ReadFileToBuffer( tempZipName, zipBuffer ); + if ( !bOK ) + { + goto cleanUp; + } + + // replace old pak lump with new zip + bOK = ((IBSPPack*)pBSPPack)->SetPakFileLump( g_pFullFileSystem, tempSwapName, tempSwapName, zipBuffer.Base(), zipBuffer.TellMaxPut() ); + if ( !bOK ) + { + goto cleanUp; + } + } + + bOK = scriptlib->ReadFileToBuffer( tempSwapName, targetBuffer ); + if ( !bOK ) + { + goto cleanUp; + } + + // never zip, always write local file + bOK = WriteBufferToFile( pTargetName, targetBuffer, false, WRITE_TO_DISK_ALWAYS ); + +cleanUp: + if ( tempZipName[0] ) + { + _unlink( tempZipName ); + } + if ( tempSwapName[0] ) + { + _unlink( tempSwapName ); + } + + Sys_UnloadModule( pBSPModule ); + + if ( pPakData ) + { + free( pPakData ); + } + + delete pNewXZip; + + return bOK; +} + + +//----------------------------------------------------------------------------- +// Purpose: Generate .360 node graphs +//----------------------------------------------------------------------------- +bool CreateTargetFile_AIN( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + CUtlBuffer sourceBuf; + if ( !scriptlib->ReadFileToBuffer( pSourceName, sourceBuf ) ) + { + return false; + } + + // the pc ain is tied to the pc bsp and should have been generated after the bsp + char szBspName[MAX_PATH]; + char szBspPath[MAX_PATH]; + V_FileBase( pSourceName, szBspName, sizeof( szBspName ) ); + V_ExtractFilePath( pSourceName, szBspPath, sizeof( szBspPath ) ); + V_AppendSlash( szBspPath, sizeof( szBspPath ) ); + V_strncat( szBspPath, "..\\", sizeof( szBspPath ) ); + V_strncat( szBspPath, szBspName, sizeof( szBspPath ) ); + V_strncat( szBspPath, ".bsp", sizeof( szBspPath ) ); + if ( scriptlib->CompareFileTime( pSourceName, szBspPath ) < 0 ) + { + // ain has a smaller filetime, thus older than bsp + Msg( "%s: Need to regenerate PC nodegraph (stale)\n", pSourceName ); + if ( !g_bForce ) + { + return false; + } + } + + // Check the version + if ( sourceBuf.GetChar() == 'V' && sourceBuf.GetChar() == 'e' && sourceBuf.GetChar() == 'r' ) + { + Msg( "%s: Need to regenerate PC nodegraph (bad format)\n", pSourceName ); + return false; + } + + // reset + sourceBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + + // Version number + int version = sourceBuf.GetInt(); + if ( version != AINET_VERSION_NUMBER ) + { + Msg( "%s: Need to regenerate PC nodegraph (got version '%d', expected '%d')\n", pSourceName, version, AINET_VERSION_NUMBER ); + return false; + } + + // check the map revision + int mapVersion = sourceBuf.GetInt(); + dheader_t bspHeader; + if ( ReadBSPHeader( szBspPath, &bspHeader ) ) + { + if ( mapVersion != bspHeader.mapRevision ) + { + Msg( "%s: Need to regenerate PC nodegraph (ai revision '%d' does not match bsp revision '%d')\n", pSourceName, mapVersion, bspHeader.mapRevision ); + return false; + } + } + else + { + Msg( "%s: Could not find expected bsp '%s'\n", pSourceName, szBspPath ); + } + + // Nodes + int nodeCt = sourceBuf.GetInt(); + if ( nodeCt > MAX_NODES || nodeCt < 0 ) + { + Msg( "%s: Need to regenerate PC nodegraph (corrupt)\n", pSourceName ); + return false; + } + + CUtlBuffer targetBuf; + targetBuf.ActivateByteSwapping( true ); + + CByteswap swap; + swap.ActivateByteSwapping( true ); + + targetBuf.PutInt( version ); + targetBuf.PutInt( mapVersion ); + targetBuf.PutInt( nodeCt ); + + int numFloats = NUM_HULLS + 4; + for ( int node = 0; node < nodeCt; ++node ) + { + targetBuf.EnsureCapacity( targetBuf.TellPut() + numFloats * sizeof( float ) ); + swap.SwapBufferToTargetEndian<float>( (float*)targetBuf.PeekPut(), (float*)sourceBuf.PeekGet(), numFloats ); + sourceBuf.SeekGet( CUtlBuffer::SEEK_CURRENT, numFloats * sizeof( float ) ); + targetBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, numFloats * sizeof( float ) ); + + targetBuf.PutChar( sourceBuf.GetChar() ); + + // Align the remaining data + targetBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, 3 ); + + targetBuf.PutUnsignedShort( sourceBuf.GetUnsignedShort() ); + targetBuf.PutShort( sourceBuf.GetShort() ); + } + + // Node links + int totalNumLinks = sourceBuf.GetInt(); + targetBuf.PutInt( totalNumLinks ); + + for ( int link = 0; link < totalNumLinks; ++link ) + { + targetBuf.PutShort( sourceBuf.GetShort() ); + targetBuf.PutShort( sourceBuf.GetShort() ); + targetBuf.Put( sourceBuf.PeekGet(), NUM_HULLS ); + sourceBuf.SeekGet( CUtlBuffer::SEEK_CURRENT, NUM_HULLS ); + } + + // WC lookup table + targetBuf.EnsureCapacity( targetBuf.TellPut() + nodeCt * sizeof( int ) ); + swap.SwapBufferToTargetEndian<int>( (int*)targetBuf.PeekPut(), (int*)sourceBuf.PeekGet(), nodeCt ); + targetBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, nodeCt * sizeof( int ) ); + + // Write the file out + return WriteBufferToFile( pTargetName, targetBuf, bWriteToZip, WRITE_TO_DISK_ALWAYS ); +} + +bool GetDependants_BSP( const char *pBspName, CUtlVector< CUtlString > *pList ) +{ + if ( !g_bModPathIsValid ) + { + Msg( "Indeterminate mod path, Cannot perform BSP conversion\n" ); + return false; + } + + // Load bsppack.dll + void *pBSPPack; + CSysModule *pBSPModule; + if ( !Sys_LoadInterface( "bsppack.dll", IBSPPACK_VERSION_STRING, &pBSPModule, &pBSPPack ) ) + { + Msg( "Failed to load bsppack interface\n" ); + return false; + } + + // 360 builds a more complete reslist that includes bsp internal files + // build full path to bsp file + char szBspFilename[MAX_PATH]; + V_ComposeFileName( g_szGamePath, pBspName, szBspFilename, sizeof( szBspFilename ) ); + + bool bOK = ((IBSPPack*)pBSPPack)->GetBSPDependants( g_pFullFileSystem, szBspFilename, pList ); + + Sys_UnloadModule( pBSPModule ); + + return bOK; +} diff --git a/utils/xbox/MakeGameData/MakeMisc.cpp b/utils/xbox/MakeGameData/MakeMisc.cpp new file mode 100644 index 0000000..d5e88de --- /dev/null +++ b/utils/xbox/MakeGameData/MakeMisc.cpp @@ -0,0 +1,86 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Conversion for general wierd files. +// +//=====================================================================================// + +#include "MakeGameData.h" + +//----------------------------------------------------------------------------- +// The DX Support file is a very fat expensive KV file, causes a run-time startup slowdown. +// Becauase it normally lives in the game\bin directory, it can't be in the zip or preloaded. +// Thus, it gets reprocessed into just the trivial 360 portion and placed into the platform.zip +// Yes, it's evil. +//----------------------------------------------------------------------------- +bool ProcessDXSupportConfig( bool bWriteToZip ) +{ + if ( !g_bIsPlatformZip ) + { + // only relevant when building platform zip, otherwise no-op + return false; + } + + const char *pConfigName = "dxsupport.cfg"; + char szTempPath[MAX_PATH]; + char szSourcePath[MAX_PATH]; + V_ComposeFileName( g_szModPath, "../bin", szTempPath, sizeof( szTempPath ) ); + V_ComposeFileName( szTempPath, pConfigName, szSourcePath, sizeof( szSourcePath ) ); + + CUtlBuffer sourceBuf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + if ( !g_pFullFileSystem->ReadFile( szSourcePath, NULL, sourceBuf ) ) + { + Msg( "Error! Couldn't open file '%s'!\n", pConfigName ); + return false; + } + + KeyValues *pKV = new KeyValues( "" ); + if ( !pKV->LoadFromBuffer( "dxsupport.cfg", sourceBuf ) ) + { + Msg( "Error! Couldn't parse config file '%s'!\n", pConfigName ); + pKV->deleteThis(); + return false; + } + + // only care about the xbox specific dxlevel 98 block + KeyValues *pXboxSubKey = NULL; + for ( KeyValues *pSubKey = pKV->GetFirstSubKey(); pSubKey != NULL && pXboxSubKey == NULL; pSubKey = pSubKey->GetNextKey() ) + { + // descend each sub block + for ( KeyValues *pKey = pSubKey->GetFirstSubKey(); pKey != NULL && pXboxSubKey == NULL; pKey = pKey->GetNextKey() ) + { + if ( !V_stricmp( pKey->GetName(), "name" ) && pKey->GetInt( (const char *)NULL ) == 98 ) + { + pXboxSubKey = pSubKey; + } + } + } + + if ( !pXboxSubKey ) + { + Msg( "Error! Couldn't find expected dxlevel 98 in config file '%s'!\n", pConfigName ); + pKV->deleteThis(); + return false; + } + + CUtlBuffer kvBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); + kvBuffer.Printf( "\"dxsupport\"\n" ); + kvBuffer.Printf( "{\n" ); + kvBuffer.Printf( "\t\"0\"\n" ); + kvBuffer.Printf( "\t{\n" ); + for ( KeyValues *pKey = pXboxSubKey->GetFirstSubKey(); pKey != NULL; pKey = pKey->GetNextKey() ) + { + kvBuffer.Printf( "\t\t\"%s\" \"%s\"\n", pKey->GetName(), pKey->GetString( (const char *)NULL ) ); + } + kvBuffer.Printf( "\t}\n" ); + kvBuffer.Printf( "}\n" ); + + CUtlBuffer targetBuf( 0, 0, CUtlBuffer::TEXT_BUFFER | CUtlBuffer::CONTAINS_CRLF ); + kvBuffer.ConvertCRLF( targetBuf ); + + // only appears in zip file + bool bSuccess = WriteBufferToFile( pConfigName, targetBuf, bWriteToZip, WRITE_TO_DISK_NEVER ); + + pKV->deleteThis(); + + return bSuccess; +}
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/MakeModels.cpp b/utils/xbox/MakeGameData/MakeModels.cpp new file mode 100644 index 0000000..21a14dc --- /dev/null +++ b/utils/xbox/MakeGameData/MakeModels.cpp @@ -0,0 +1,614 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 Creation for all studiomdl generated files (mdl, vvd, vtx, ani, phy) +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "studiobyteswap.h" +#include "studio.h" +#include "vphysics_interface.h" +#include "materialsystem/IMaterial.h" +#include "materialsystem/hardwareverts.h" +#include "optimize.h" + +//----------------------------------------------------------------------------- +// Models are already converted in a pre-pass. +//----------------------------------------------------------------------------- +bool CreateTargetFile_Model( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + // model component should be present + CUtlBuffer targetBuffer; + if ( !scriptlib->ReadFileToBuffer( pTargetName, targetBuffer ) ) + { + return false; + } + + // no conversion to write, but possibly zipped + bool bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, WRITE_TO_DISK_NEVER ); + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Load necessary dlls +//----------------------------------------------------------------------------- +bool InitStudioByteSwap( void ) +{ + StudioByteSwap::SetVerbose( false ); + StudioByteSwap::ActivateByteSwapping( true ); + StudioByteSwap::SetCollisionInterface( g_pPhysicsCollision ); + + 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; +} + +//----------------------------------------------------------------------------- +// Get the preload data for a vhv file +//----------------------------------------------------------------------------- +bool GetPreloadData_VHV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + HardwareVerts::FileHeader_t *pHeader = (HardwareVerts::FileHeader_t *)fileBufferIn.Base(); + + unsigned int version = BigLong( pHeader->m_nVersion ); + + // ensure caller's buffer is clean + // caller determines preload size, via TellMaxPut() + preloadBufferOut.Purge(); + + if ( version != VHV_VERSION ) + { + // bad version + Msg( "Can't preload: '%s', expecting version %d got version %d\n", pFilename, VHV_VERSION, version ); + return false; + } + + unsigned int nPreloadSize = sizeof( HardwareVerts::FileHeader_t ); + + preloadBufferOut.Put( fileBufferIn.Base(), nPreloadSize ); + + return true; +} + +//----------------------------------------------------------------------------- +// Get the preload data for a vtx file +//----------------------------------------------------------------------------- +bool GetPreloadData_VTX( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + OptimizedModel::FileHeader_t *pHeader = (OptimizedModel::FileHeader_t *)fileBufferIn.Base(); + + unsigned int version = BigLong( pHeader->version ); + + // ensure caller's buffer is clean + // caller determines preload size, via TellMaxPut() + preloadBufferOut.Purge(); + + if ( version != OPTIMIZED_MODEL_FILE_VERSION ) + { + // bad version + Msg( "Can't preload: '%s', expecting version %d got version %d\n", pFilename, OPTIMIZED_MODEL_FILE_VERSION, version ); + return false; + } + + unsigned int nPreloadSize = sizeof( OptimizedModel::FileHeader_t ); + + preloadBufferOut.Put( fileBufferIn.Base(), nPreloadSize ); + + return true; +} + +//----------------------------------------------------------------------------- +// Get the preload data for a vvd file +//----------------------------------------------------------------------------- +bool GetPreloadData_VVD( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + vertexFileHeader_t *pHeader = (vertexFileHeader_t *)fileBufferIn.Base(); + + unsigned int id = BigLong( pHeader->id ); + unsigned int version = BigLong( pHeader->version ); + + // ensure caller's buffer is clean + // caller determines preload size, via TellMaxPut() + preloadBufferOut.Purge(); + + if ( id != MODEL_VERTEX_FILE_ID ) + { + // bad version + Msg( "Can't preload: '%s', expecting id %d got id %d\n", pFilename, MODEL_VERTEX_FILE_ID, id ); + return false; + } + + if ( version != MODEL_VERTEX_FILE_VERSION ) + { + // bad version + Msg( "Can't preload: '%s', expecting version %d got version %d\n", pFilename, MODEL_VERTEX_FILE_VERSION, version ); + return false; + } + + unsigned int nPreloadSize = sizeof( vertexFileHeader_t ); + + preloadBufferOut.Put( fileBufferIn.Base(), nPreloadSize ); + + return true; +} + +bool CompressFunc( const void *pInput, int inputSize, void **pOutput, int *pOutputSize ) +{ + *pOutput = NULL; + *pOutputSize = 0; + + if ( !inputSize ) + { + // nothing to do + return false; + } + + unsigned int compressedSize; + unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)pInput, inputSize, &compressedSize ); + if ( pCompressedOutput ) + { + *pOutput = pCompressedOutput; + *pOutputSize = compressedSize; + return true; + } + + return false; + +} + +//----------------------------------------------------------------------------- +// 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; +} + +//----------------------------------------------------------------------------- +// Returns true if specified model path has a dirty sub-component, and requires +// update. +//----------------------------------------------------------------------------- +static bool ModelNeedsUpdate( const char *pMdlSourcePath ) +{ + struct ModelExtensions_t + { + const char *pSourceExtension; + const char *pTargetExtension; + bool bSourceMustExist; // if source exists, so must target + }; + ModelExtensions_t pExtensions[] = + { + { ".mdl", ".360.mdl", true }, + { ".dx90.vtx", ".dx90.360.vtx", false }, + { ".vvd", ".360.vvd", false }, + { ".phy", ".360.phy", false }, + { ".ani", ".360.ani", false }, + // vtx/vvd generate a vsi, vsi must be fresher to be valid + { ".dx90.vtx", ".vsi", false }, + { ".vvd", ".vsi", false }, + }; + + if ( g_bForce ) + { + return true; + } + + for ( int i = 0; i < ARRAYSIZE( pExtensions ); i++ ) + { + char szSourcePath[MAX_PATH]; + struct _stat sourceStatBuf; + V_strncpy( szSourcePath, pMdlSourcePath, sizeof( szSourcePath ) ); + V_SetExtension( szSourcePath, pExtensions[i].pSourceExtension, sizeof( szSourcePath ) ); + int retVal = _stat( szSourcePath, &sourceStatBuf ); + if ( retVal != 0 ) + { + // couldn't get source + if ( pExtensions[i].bSourceMustExist ) + { + return true; + } + else + { + // source is optional + continue; + } + } + + char szTargetPath[MAX_PATH]; + struct _stat targetStatBuf; + V_strncpy( szTargetPath, pMdlSourcePath, sizeof( szTargetPath ) ); + V_SetExtension( szTargetPath, pExtensions[i].pTargetExtension, sizeof( szTargetPath ) ); + if ( _stat( szTargetPath, &targetStatBuf ) != 0 ) + { + // target doesn't exist + return true; + } + + if ( difftime( sourceStatBuf.st_mtime, targetStatBuf.st_mtime ) > 0 ) + { + // source is older (thus newer), update required + return true; + } + } + + return false; +} + +static bool ModelNamesLessFunc( CUtlString const &pLHS, CUtlString const &pRHS ) +{ + return CaselessStringLessThan( pLHS.Get(), pRHS.Get() ); +} + +//----------------------------------------------------------------------------- +// Models require specialized group handling to generate intermediate lod culled +// versions that are then used as the the source for target conversion. +//----------------------------------------------------------------------------- +bool PreprocessModelFiles( CUtlVector<fileList_t> &fileList ) +{ + if ( !InitStudioByteSwap() ) + { + return false; + } + + CUtlVector< CUtlString > updateList; + CUtlRBTree< CUtlString, int > visitedModels( 0, 0, ModelNamesLessFunc ); + + char szSourcePath[MAX_PATH]; + strcpy( szSourcePath, g_szSourcePath ); + V_StripFilename( szSourcePath ); + if ( !szSourcePath[0] ) + strcpy( szSourcePath, "." ); + V_AppendSlash( szSourcePath, sizeof( szSourcePath ) ); + + char szModelName[MAX_PATH]; + for ( int i=0; i<fileList.Count(); i++ ) + { + V_strncpy( szModelName, fileList[i].fileName.String(), sizeof( szModelName ) ); + + if ( V_stristr( szModelName, ".360." ) ) + { + // must ignore any target files + continue; + } + + // want only model related files + char *pExtension = V_stristr( szModelName, ".mdl" ); + if ( !pExtension ) + { + pExtension = V_stristr( szModelName, ".dx90.vtx" ); + if ( !pExtension ) + { + pExtension = V_stristr( szModelName, ".vvd" ); + if ( !pExtension ) + { + pExtension = V_stristr( szModelName, ".ani" ); + if ( !pExtension ) + { + pExtension = V_stristr( szModelName, ".phy" ); + if ( !pExtension ) + { + pExtension = V_stristr( szModelName, ".vsi" ); + if ( !pExtension ) + { + continue; + } + } + } + } + } + } + + *pExtension = '\0'; + V_strncat( szModelName, ".mdl", sizeof( szModelName ) ); + + if ( visitedModels.Find( szModelName ) != visitedModels.InvalidIndex() ) + { + // already processed + continue; + } + visitedModels.Insert( szModelName ); + + // resolve to full source path + const char *ptr = szModelName; + if ( !strnicmp( ptr, ".\\", 2 ) ) + ptr += 2; + else if ( !strnicmp( ptr, szSourcePath, strlen( szSourcePath ) ) ) + ptr += strlen( szSourcePath ); + char szCleanName[MAX_PATH]; + strcpy( szCleanName, szSourcePath ); + strcat( szCleanName, ptr ); + char szFullSourcePath[MAX_PATH]; + _fullpath( szFullSourcePath, szCleanName, sizeof( szFullSourcePath ) ); + + // any one dirty component generates the set of all expected files + if ( ModelNeedsUpdate( szFullSourcePath ) ) + { + int index = updateList.AddToTail(); + updateList[index].Set( szFullSourcePath ); + } + } + + Msg( "\n" ); + Msg( "Model Pre Pass: Updating %d Models.\n", updateList.Count() ); + for ( int i = 0; i < updateList.Count(); i++ ) + { + if ( !GenerateModelFiles( updateList[i].String() ) ) + { + int error = g_errorList.AddToTail(); + g_errorList[error].result = false; + g_errorList[error].fileName.Set( updateList[i].String() ); + } + } + + // iterate error list + if ( g_errorList.Count() ) + { + Msg( "\n" ); + for ( int i = 0; i < g_errorList.Count(); i++ ) + { + Msg( "ERROR: could not pre-process model: %s\n", g_errorList[i].fileName.String() ); + } + } + + return true; +} diff --git a/utils/xbox/MakeGameData/MakeParticles.cpp b/utils/xbox/MakeGameData/MakeParticles.cpp new file mode 100644 index 0000000..7e6a5f1 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeParticles.cpp @@ -0,0 +1,37 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 file creation of PCF files +// +//=====================================================================================// + +#include "MakeGameData.h" + +bool CreateTargetFile_PCF( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + DmxHeader_t header; + CDmElement *pRoot; + if ( g_pDataModel->RestoreFromFile( pSourceName, NULL, NULL, &pRoot, CR_DELETE_NEW, &header ) == DMFILEID_INVALID ) + { + Msg( "CreateTargetFile_PCF: Error reading file \"%s\"!\n", pSourceName ); + return false; + } + + const char *pOutFormat = header.formatName; + if ( !g_pDataModel->FindFormatUpdater( pOutFormat ) ) + { + pOutFormat = "dmx"; + } + + CUtlBuffer binaryBuffer; + if ( !g_pDataModel->Serialize( binaryBuffer, "binary", pOutFormat, pRoot->GetHandle() ) ) + { + Msg( "CreateTargetFile_PCF: Error writing buffer\n" ); + return false; + } + + g_pDataModel->RemoveFileId( pRoot->GetFileId() ); + + WriteBufferToFile( pTargetName, binaryBuffer, bWriteToZip, g_WriteModeForConversions ); + + return true; +} diff --git a/utils/xbox/MakeGameData/MakeResources.cpp b/utils/xbox/MakeGameData/MakeResources.cpp new file mode 100644 index 0000000..3a411c3 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeResources.cpp @@ -0,0 +1,343 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 file creation of miscellaneous resource and data files +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "captioncompiler.h" + +//----------------------------------------------------------------------------- +// Purpose: Generate .360 compiled caption files +//----------------------------------------------------------------------------- +bool CreateTargetFile_CCDAT( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + CUtlBuffer targetBuffer; + bool bOk = false; + + if ( !scriptlib->ReadFileToBuffer( pSourceName, targetBuffer ) ) + { + return false; + } + + if ( SwapClosecaptionFile( targetBuffer.Base() ) ) + { + bOk = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, g_WriteModeForConversions ); + } + + return bOk; +} + +static bool ReslistLessFunc( CUtlString const &pLHS, CUtlString const &pRHS ) +{ + return CaselessStringLessThan( pLHS.Get(), pRHS.Get() ); +} + +//----------------------------------------------------------------------------- +// Find or Add, prevents duplicates. Returns TRUE if found. +//----------------------------------------------------------------------------- +bool FindOrAddFileToResourceList( const char *pFilename, CUtlRBTree< CUtlString, int > *pTree, bool bAdd = true ) +{ + char szOutName[MAX_PATH]; + char *pOutName; + V_strncpy( szOutName, pFilename, sizeof( szOutName ) ); + V_FixSlashes( szOutName ); + V_RemoveDotSlashes( szOutName ); + V_strlower( szOutName ); + pOutName = szOutName; + + // strip any prefixed game name + for ( int i = 0; g_GameNames[i] != NULL; i++ ) + { + size_t len = strlen( g_GameNames[i] ); + if ( !V_strnicmp( pOutName, g_GameNames[i], len ) && pOutName[len] == '\\' ) + { + // skip past game name and slash + pOutName += len+1; + break; + } + } + + if ( pTree->Find( pOutName ) != pTree->InvalidIndex() ) + { + // found + return true; + } + + if ( bAdd ) + { + pTree->Insert( pOutName ); + } + + return false; +} + +//----------------------------------------------------------------------------- +// Remove entry from dictionary, Returns TRUE if removed. +//----------------------------------------------------------------------------- +bool RemoveFileFromResourceList( const char *pFilename, CUtlRBTree< CUtlString, int > *pTree ) +{ + char szOutName[MAX_PATH]; + char *pOutName; + V_strncpy( szOutName, pFilename, sizeof( szOutName ) ); + V_FixSlashes( szOutName ); + V_RemoveDotSlashes( szOutName ); + V_strlower( szOutName ); + pOutName = szOutName; + + // strip any prefixed game name + for ( int i = 0; g_GameNames[i] != NULL; i++ ) + { + size_t len = strlen( g_GameNames[i] ); + if ( !V_strnicmp( pOutName, g_GameNames[i], len ) && pOutName[len] == '\\' ) + { + // skip past game name and slash + pOutName += len+1; + break; + } + } + + if ( pTree->Find( pOutName ) != pTree->InvalidIndex() ) + { + pTree->Remove( pOutName ); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Generate a tree containing files from a reslist. Returns TRUE if successful. +//----------------------------------------------------------------------------- +bool LoadReslist( const char *pReslistName, CUtlRBTree< CUtlString, int > *pTree ) +{ + CUtlBuffer buffer; + if ( !scriptlib->ReadFileToBuffer( pReslistName, buffer, true ) ) + { + return false; + } + + char szBasename[MAX_PATH]; + V_FileBase( pReslistName, szBasename, sizeof( szBasename ) ); + + characterset_t breakSet; + CharacterSetBuild( &breakSet, "" ); + + // parse reslist + char szToken[MAX_PATH]; + char szBspName[MAX_PATH]; + szBspName[0] = '\0'; + for ( ;; ) + { + int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + // reslists are pc built, filenames can be sloppy + V_strlower( szToken ); + V_FixSlashes( szToken ); + V_RemoveDotSlashes( szToken ); + + // can safely cull filetypes that are ignored by queued loader at runtime + bool bKeep = false; + const char *pExt = V_GetFileExtension( szToken ); + if ( !pExt ) + { + // unknown + continue; + } + else if ( !V_stricmp( pExt, "vmt" ) || + !V_stricmp( pExt, "vhv" ) || + !V_stricmp( pExt, "mdl" ) || + !V_stricmp( pExt, "raw" ) || + !V_stricmp( pExt, "wav" ) ) + { + bKeep = true; + } + else if ( !V_stricmp( pExt, "mp3" ) ) + { + // change to .wav + V_SetExtension( szToken, ".wav", sizeof( szToken ) ); + bKeep = true; + } + else if ( !V_stricmp( pExt, "bsp" ) ) + { + // reslists erroneously have multiple bsps + if ( !V_stristr( szToken, szBasename ) ) + { + // wrong one, cull it + continue; + } + else + { + // right one, save it + strcpy( szBspName, szToken ); + bKeep = true; + } + } + + if ( bKeep ) + { + FindOrAddFileToResourceList( szToken, pTree ); + } + } + + if ( !szBspName[0] ) + { + // reslist is not bsp derived, nothing more to do + return true; + } + + CUtlVector< CUtlString > bspList; + bool bOK = GetDependants_BSP( szBspName, &bspList ); + if ( !bOK ) + { + return false; + } + + // add all the bsp dependants to the resource list + for ( int i=0; i<bspList.Count(); i++ ) + { + FindOrAddFileToResourceList( bspList[i].String(), pTree ); + } + + // iterate all the models in the resource list, get all their dependents + CUtlVector< CUtlString > modelList; + for ( int i = pTree->FirstInorder(); i != pTree->InvalidIndex(); i = pTree->NextInorder( i ) ) + { + const char *pExt = V_GetFileExtension( pTree->Element( i ).String() ); + if ( !pExt || V_stricmp( pExt, "mdl" ) ) + { + continue; + } + + if ( !GetDependants_MDL( pTree->Element( i ).String(), &modelList ) ) + { + return false; + } + } + + // add all the model dependents to the resource list + for ( int i=0; i<modelList.Count(); i++ ) + { + FindOrAddFileToResourceList( modelList[i].String(), pTree ); + } + + // check for optional commentary, include wav dependencies + char szCommentaryName[MAX_PATH]; + V_ComposeFileName( g_szGamePath, szBspName, szCommentaryName, sizeof( szCommentaryName ) ); + V_StripExtension( szCommentaryName, szCommentaryName, sizeof( szCommentaryName ) ); + V_strncat( szCommentaryName, "_commentary.txt", sizeof( szCommentaryName ) ); + CUtlBuffer commentaryBuffer; + if ( ReadFileToBuffer( szCommentaryName, commentaryBuffer, true, true ) ) + { + // any single token may be quite large to due to text + char szCommentaryToken[8192]; + for ( ;; ) + { + int nTokenSize = commentaryBuffer.ParseToken( &breakSet, szCommentaryToken, sizeof( szCommentaryToken ) ); + if ( nTokenSize < 0 ) + { + break; + } + if ( nTokenSize > 0 && !V_stricmp( szCommentaryToken, "commentaryfile" ) ) + { + // get the commentary file + nTokenSize = commentaryBuffer.ParseToken( &breakSet, szCommentaryToken, sizeof( szCommentaryToken ) ); + if ( nTokenSize > 0 ) + { + // skip past sound chars + char *pName = szCommentaryToken; + while ( *pName && IsSoundChar( *pName ) ) + { + pName++; + } + char szWavFile[MAX_PATH]; + V_snprintf( szWavFile, sizeof( szWavFile ), "sound/%s", pName ); + FindOrAddFileToResourceList( szWavFile, pTree ); + } + } + } + } + + // check for optional blacklist + char szBlacklist[MAX_PATH]; + V_ComposeFileName( g_szGamePath, "reslistfixes_xbox.xsc", szBlacklist, sizeof( szBlacklist ) ); + CUtlBuffer blacklistBuffer; + if ( ReadFileToBuffer( szBlacklist, blacklistBuffer, true, true ) ) + { + for ( ;; ) + { + int nTokenSize = blacklistBuffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + bool bAdd; + if ( !V_stricmp( szToken, "-" ) ) + { + bAdd = false; + } + else if ( !V_stricmp( szToken, "+" ) ) + { + bAdd = true; + } + else + { + // bad syntax, skip line + Msg( "Bad Syntax, expecting '+' or '-' as first token in reslist fixup file '%s'.\n", szBlacklist ); + continue; + } + + // get entry + nTokenSize = blacklistBuffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + if ( bAdd ) + { + FindOrAddFileToResourceList( szToken, pTree ); + } + else + { + RemoveFileFromResourceList( szToken, pTree ); + } + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Generate .360 compiled reslist files +// Reslist files are processed for the unique consumption of Queued Loading. +//----------------------------------------------------------------------------- +bool CreateTargetFile_RESLST( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + bool bOK = false; + + // parse reslist + CUtlRBTree< CUtlString, int > rbTree( 0, 0, ReslistLessFunc ); + if ( !LoadReslist( pSourceName, &rbTree ) ) + { + return false; + } + + CUtlBuffer targetBuffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); + for ( int iIndex = rbTree.FirstInorder(); iIndex != rbTree.InvalidIndex(); iIndex = rbTree.NextInorder( iIndex ) ) + { + targetBuffer.PutChar( '\"' ); + targetBuffer.PutString( rbTree[iIndex].String() ); + targetBuffer.PutChar( '\"' ); + targetBuffer.PutString( "\n" ); + } + + bOK = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, g_WriteModeForConversions ); + + return bOK; +}
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/MakeScenes.cpp b/utils/xbox/MakeGameData/MakeScenes.cpp new file mode 100644 index 0000000..4077978 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeScenes.cpp @@ -0,0 +1,40 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 file creation of Choreo VCD files +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "sceneimage.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +class CDefaultStatus : public ISceneCompileStatus +{ +public: + virtual void UpdateStatus( char const *pchSceneName, bool bQuiet, int nIndex, int nCount ) + { + if ( !bQuiet ) + { + Msg( "Scenes: Compiling: %s\n", pchSceneName ); + } + } +}; + +bool CreateSceneImageFile( char const *pchModPath, bool bWriteToZip, bool bLittleEndian, bool bQuiet, DiskWriteMode_t eWriteModeForConversions ) +{ + CUtlBuffer targetBuffer; + + const char *pFilename = bLittleEndian ? "scenes/scenes.image" : "scenes/scenes.360.image"; + + CDefaultStatus statusHelper; + + bool bSuccess = g_pSceneImage->CreateSceneImageFile( targetBuffer, pchModPath, bLittleEndian, bQuiet, &statusHelper ); + if ( bSuccess ) + { + bSuccess = WriteBufferToFile( pFilename, targetBuffer, bWriteToZip, eWriteModeForConversions ); + } + + return bSuccess; +}
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/MakeScenesImage.vpc b/utils/xbox/MakeGameData/MakeScenesImage.vpc new file mode 100644 index 0000000..0361be6 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeScenesImage.vpc @@ -0,0 +1,133 @@ +//----------------------------------------------------------------------------- +// MAKESCENESIMAGE.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_exe_con_base.vpc" + +$Macro GAMENAME "MakeScenesImage" + +$Configuration "Debug" +{ + $General + { + $OutputDirectory ".\Debug_$GAMENAME" + $IntermediateDirectory ".\Debug_$GAMENAME" + } +} + +$Configuration "Release" +{ + $General + { + $OutputDirectory ".\Release_$GAMENAME" + $IntermediateDirectory ".\Release_$GAMENAME" + } +} + +$Configuration +{ + $Compiler + { + $PreprocessorDefinitions "$BASE;MAKESCENESIMAGE" + $PreprocessorDefinitions "$BASE;NO_X360_XDK" [$VS2015] + $AdditionalIncludeDirectories "$BASE;$SRCDIR\x360xdk\include\win32\vs2005;$SRCDIR\game\shared" + } + + $Linker + { + $AdditionalDependencies "$BASE xgraphics.lib d3d9.lib legacy_stdio_definitions.lib" + $AdditionalDependencies "$BASE xmaencoder.lib" [!$VS2015] + $AdditionalLibraryDirectories "$BASE;$SRCDIR\x360xdk\lib\win32\vs2005" + } +} + +$Project "MakeScenesImage" +{ + $Folder "Source Files" + { + -$File "$SRCDIR\public\tier0\memoverride.cpp" + $File "MakeGameData.cpp" + $File "MakeMaps.cpp" + $File "MakeMisc.cpp" + $File "MakeModels.cpp" + $File "MakeParticles.cpp" + $File "MakeResources.cpp" + $File "MakeScenes.cpp" + $File "MakeShaders.cpp" + $File "MakeSounds.cpp" + $File "MakeTextures.cpp" + $File "MakeZip.cpp" + + $Folder "Audio" + { + $File "imaadpcm.cpp" + $File "sound_io.cpp" + $File "resample.cpp" + } + + $Folder "Public Modules" + { + $File "$SRCDIR\common\compiledcaptionswap.cpp" + $file "$SRCDIR\common\studiobyteswap.cpp" + $File "$SRCDIR\utils\common\scriplib.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + $File "$SRCDIR\public\sentence.cpp" + $File "$SRCDIR\utils\common\cmdlib.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\filesystem_init.cpp" + $File "$SRCDIR\utils\common\filesystem_tools.cpp" + $File "$SRCDIR\public\interpolatortypes.cpp" + } + } + + $Folder "Header Files" + { + $File "MakeGameData.h" + $File "XZipTool.h" + $File "imaadpcm.h" + $File "resample.h" + $File "$SRCDIR\public\captioncompiler.h" + $File "$SRCDIR\common\studiobyteswap.h" + $File "$SRCDIR\utils\common\scriplib.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\ibsppack.h" + $File "$SRCDIR\public\sentence.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\xwvfile.h" + $File "$SRCDIR\public\zip_utils.h" + $File "$SRCDIR\game\shared\choreoscene.h" + $File "$SRCDIR\game\shared\choreoactor.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\filesystem_init.h" + $File "$SRCDIR\utils\common\filesystem_tools.h" + $File "$SRCDIR\public\interpolatortypes.h" + $File "$SRCDIR\utils\common\cmdlib.h" + $File "$SRCDIR\game\shared\choreochannel.h" + $File "$SRCDIR\game\shared\choreoevent.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier2\tier2.h" + $File "$SRCDIR\common\lzma\lzma.h" + } + + $Folder "Link Libraries" + { + $Lib appframework + $Lib mathlib + $Lib vtf + $Lib tier2 + $Lib choreoobjects + $Lib bitmap + $Lib datamodel + $Lib dmserializers + $Lib $LIBCOMMON\lzma + } +} diff --git a/utils/xbox/MakeGameData/MakeShaders.cpp b/utils/xbox/MakeGameData/MakeShaders.cpp new file mode 100644 index 0000000..25f51b4 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeShaders.cpp @@ -0,0 +1,52 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360 file creation of shaders +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "materialsystem/shader_vcs_version.h" + +#define SHADER_FILE_THRESHOLD 32*1024 + +//----------------------------------------------------------------------------- +// Get the preload data for a vcs file +//----------------------------------------------------------------------------- +bool GetPreloadData_VCS( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + ShaderHeader_t *pHeader = (ShaderHeader_t *)fileBufferIn.Base(); + + unsigned int version = BigLong( pHeader->m_nVersion ); + + // ensure caller's buffer is clean + // caller determines preload size, via TellMaxPut() + preloadBufferOut.Purge(); + + unsigned int nPreloadSize; + if ( fileBufferIn.TellMaxPut() <= SHADER_FILE_THRESHOLD ) + { + // include the whole file + nPreloadSize = fileBufferIn.TellMaxPut(); + } + else + { + if ( version < SHADER_VCS_VERSION_NUMBER ) + { + // not supporting old versions + return false; + } + + if ( version != SHADER_VCS_VERSION_NUMBER ) + { + // bad version + Msg( "Can't preload: '%s', expecting version %d got version %d\n", pFilename, SHADER_VCS_VERSION_NUMBER, version ); + return false; + } + + nPreloadSize = sizeof( ShaderHeader_t ) + BigLong( pHeader->m_nNumStaticCombos ) * sizeof( StaticComboRecord_t ); + } + + preloadBufferOut.Put( fileBufferIn.Base(), nPreloadSize ); + + return true; +}
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/MakeSounds.cpp b/utils/xbox/MakeGameData/MakeSounds.cpp new file mode 100644 index 0000000..f0913ce --- /dev/null +++ b/utils/xbox/MakeGameData/MakeSounds.cpp @@ -0,0 +1,968 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360.WAV Creation +// +//=====================================================================================// + +#include "MakeGameData.h" +#ifndef NO_X360_XDK +#include <XMAEncoder.h> +#endif +#include "datamap.h" +#include "sentence.h" +#include "tier2/riff.h" +#include "resample.h" +#include "xwvfile.h" + +// all files are built for streaming compliance +// allows for fastest runtime loading path +// actual streaming or static state is determined by engine +#define XBOX_DVD_SECTORSIZE 2048 +#define XMA_BLOCK_SIZE 2048 // must be aligned to 1024 +#define MAX_CHUNKS 256 + +// [0,100] +#define XMA_HIGH_QUALITY 90 +#define XMA_DEFAULT_QUALITY 75 +#define XMA_MEDIUM_QUALITY 50 +#define XMA_LOW_QUALITY 25 + +typedef struct +{ + unsigned int id; + int size; + byte *pData; +} chunk_t; + +struct conversion_t +{ + const char *pSubDir; + int quality; + bool bForceTo22K; +}; + +// default conversion rules +conversion_t g_defaultConversionRules[] = +{ + // subdir quality 22Khz + { "", XMA_DEFAULT_QUALITY, false }, // default settings + { "weapons", XMA_DEFAULT_QUALITY, false }, + { "music", XMA_DEFAULT_QUALITY, false }, + { "vo", XMA_MEDIUM_QUALITY, false }, + { "npc", XMA_MEDIUM_QUALITY, false }, + { "ambient", XMA_DEFAULT_QUALITY, false }, + { "commentary", XMA_LOW_QUALITY, true }, + { NULL }, +}; + +// portal conversion rules +conversion_t g_portalConversionRules[] = +{ + // subdir quality 22Khz + { "", XMA_DEFAULT_QUALITY, false }, // default settings + { "commentary", XMA_LOW_QUALITY, true }, + { NULL }, +}; + +chunk_t g_chunks[MAX_CHUNKS]; +int g_numChunks; + +extern IFileReadBinary *g_pSndIO; + +//----------------------------------------------------------------------------- +// Purpose: chunk printer +//----------------------------------------------------------------------------- +void PrintChunk( unsigned int chunkName, int size ) +{ + char c[4]; + + for ( int i=0; i<4; i++ ) + { + c[i] = ( chunkName >> i*8 ) & 0xFF; + if ( !c[i] ) + c[i] = ' '; + } + + Msg( "%c%c%c%c: %d bytes\n", c[0], c[1], c[2], c[3], size ); +} + +//----------------------------------------------------------------------------- +// Purpose: which chunks are supported, false to ignore +//----------------------------------------------------------------------------- +bool IsValidChunk( unsigned int chunkName ) +{ + switch ( chunkName ) + { + case WAVE_DATA: + case WAVE_CUE: + case WAVE_SAMPLER: + case WAVE_VALVEDATA: + case WAVE_FMT: + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Purpose: align buffer +//----------------------------------------------------------------------------- +int AlignToBoundary( CUtlBuffer &buf, int alignment ) +{ + int curPosition; + int newPosition; + byte padByte = 0; + + buf.SeekPut( CUtlBuffer::SEEK_TAIL, 0 ); + curPosition = buf.TellPut(); + + if ( alignment <= 1 ) + return curPosition; + + // advance to aligned position + newPosition = AlignValue( curPosition, alignment ); + buf.EnsureCapacity( newPosition ); + + // write empty + for ( int i=0; i<newPosition-curPosition; i++ ) + { + buf.Put( &padByte, 1 ); + } + + return newPosition; +} + +//-------------------------------------------------------------------------------------- +// SampleToXMABlockOffset +// +// Description: converts from a sample index to a block index + the number of samples +// to offset from the beginning of the block. +// +// Parameters: +// dwSampleIndex: sample index to convert +// pdwSeekTable: pointer to the file's XMA2 seek table +// nEntries: number of DWORD entries in the seek table +// out_pBlockIndex: index of block where the desired sample lives +// out_pOffset: number of samples in the block before the desired sample +//-------------------------------------------------------------------------------------- +bool SampleToXMABlockOffset( DWORD dwSampleIndex, const DWORD *pdwSeekTable, DWORD nEntries, DWORD *out_pBlockIndex, DWORD *out_pOffset ) +{ + // Run through the seek table to find the block closest to the desired sample. + // Each seek table entry is the index (counting from the beginning of the file) + // of the first sample in the corresponding block, but there's no entry for the + // first block (since the index would always be zero). + bool bFound = false; + for ( DWORD i = 0; !bFound && i < nEntries; ++i ) + { + if ( dwSampleIndex < BigLong( pdwSeekTable[i] ) ) + { + *out_pBlockIndex = i; + bFound = true; + } + } + + // Calculate the sample offset by figuring out what the sample index of the first sample + // in the block is, then subtracting that from dwSampleIndex. + if ( bFound ) + { + DWORD dwStartOfBlock = (*out_pBlockIndex == 0) ? 0 : BigLong( pdwSeekTable[*out_pBlockIndex - 1] ); + *out_pOffset = dwSampleIndex - dwStartOfBlock; + } + + return bFound; +} + +//----------------------------------------------------------------------------- +// Compile and compress vdat +//----------------------------------------------------------------------------- +bool CompressVDAT( chunk_t *pChunk ) +{ + CSentence *pSentence; + + CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); + + buf.EnsureCapacity( pChunk->size ); + memcpy( buf.Base(), pChunk->pData, pChunk->size ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, pChunk->size ); + + pSentence = new CSentence(); + + // Make binary version of VDAT + // Throws all phonemes into one word, discards sentence memory, etc. + pSentence->InitFromDataChunk( buf.Base(), buf.TellPut() ); + pSentence->MakeRuntimeOnly(); + CUtlBuffer binaryBuffer( 0, 0, 0 ); + binaryBuffer.SetBigEndian( true ); + pSentence->CacheSaveToBuffer( binaryBuffer, CACHED_SENTENCE_VERSION_ALIGNED ); + delete pSentence; + + unsigned int compressedSize = 0; + unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)binaryBuffer.Base(), + binaryBuffer.TellPut(), &compressedSize ); + if ( pCompressedOutput ) + { + if ( !g_bQuiet ) + { + Msg( "CompressVDAT: Compressed %d to %d\n", binaryBuffer.TellPut(), compressedSize ); + } + + free( pChunk->pData ); + pChunk->size = compressedSize; + pChunk->pData = pCompressedOutput; + } + else + { + // save binary VDAT as-is + free( pChunk->pData ); + pChunk->size = binaryBuffer.TellPut(); + pChunk->pData = (byte *)malloc( pChunk->size ); + memcpy( pChunk->pData, binaryBuffer.Base(), pChunk->size ); + } + + // success + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: read chunks into provided array +//----------------------------------------------------------------------------- +bool ReadChunks( const char *pFileName, int &numChunks, chunk_t chunks[MAX_CHUNKS] ) +{ + numChunks = 0; + + InFileRIFF riff( pFileName, *g_pSndIO ); + if ( riff.RIFFName() != RIFF_WAVE ) + { + return false; + } + + IterateRIFF walk( riff, riff.RIFFSize() ); + + while ( walk.ChunkAvailable() ) + { + chunks[numChunks].id = walk.ChunkName(); + chunks[numChunks].size = walk.ChunkSize(); + + int size = chunks[numChunks].size; + if ( walk.ChunkName() == WAVE_FMT && size < sizeof( WAVEFORMATEXTENSIBLE ) ) + { + // format chunks are variable and cast to different structures + // ensure the data footprint is at least the structure we want to manipulate + size = sizeof( WAVEFORMATEXTENSIBLE ); + } + + chunks[numChunks].pData = (byte *)malloc( size ); + memset( chunks[numChunks].pData, 0, size ); + + walk.ChunkRead( chunks[numChunks].pData ); + + numChunks++; + if ( numChunks >= MAX_CHUNKS ) + return false; + + walk.ChunkNext(); + } + + // success + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: promote pcm 8 bit to 16 bit pcm +//----------------------------------------------------------------------------- +void ConvertPCMDataChunk8To16( chunk_t *pFormatChunk, chunk_t *pDataChunk ) +{ + WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; + + int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3; + int sampleCount = pDataChunk->size / sampleSize; + int outputSize = sizeof( short ) * ( sampleCount * pFormat->nChannels ); + short *pOut = (short *)malloc( outputSize ); + + // in-place convert data from 8-bits to 16-bits + Convert8To16( pDataChunk->pData, pOut, sampleCount, pFormat->nChannels ); + + free( pDataChunk->pData ); + pDataChunk->pData = (byte *)pOut; + pDataChunk->size = outputSize; + + pFormat->wFormatTag = WAVE_FORMAT_PCM; + pFormat->nBlockAlign = 2 * pFormat->nChannels; + pFormat->wBitsPerSample = 16; + pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; +} + +//----------------------------------------------------------------------------- +// Purpose: convert adpcm to 16 bit pcm +//----------------------------------------------------------------------------- +void ConvertADPCMDataChunkTo16( chunk_t *pFormatChunk, chunk_t *pDataChunk ) +{ + WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; + + int sampleCount = ADPCMSampleCount( (byte *)pFormat, pDataChunk->pData, pDataChunk->size ); + int outputSize = sizeof( short ) * sampleCount * pFormat->nChannels; + short *pOut = (short *)malloc( outputSize ); + + // convert to PCM 16bit format + DecompressADPCMSamples( (byte*)pFormat, (byte*)pDataChunk->pData, pDataChunk->size, pOut ); + + free( pDataChunk->pData ); + pDataChunk->pData = (byte *)pOut; + pDataChunk->size = outputSize; + + pFormat->wFormatTag = WAVE_FORMAT_PCM; + pFormat->nBlockAlign = 2 * pFormat->nChannels; + pFormat->wBitsPerSample = 16; + pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; + + pFormatChunk->size = 16; +} + +//----------------------------------------------------------------------------- +// Purpose: Decimate to 22K +//----------------------------------------------------------------------------- +void ConvertPCMDataChunk16To22K( chunk_t *pFormatChunk, chunk_t *pDataChunk ) +{ + WAVEFORMATEX *pFormat = (WAVEFORMATEX*)pFormatChunk->pData; + + if ( pFormat->nSamplesPerSec != 44100 || pFormat->wBitsPerSample != 16 || pFormat->wFormatTag != WAVE_FORMAT_PCM ) + { + // not in expected format + return; + } + + int sampleSize = ( pFormat->nChannels * pFormat->wBitsPerSample ) >> 3; + int sampleCount = pDataChunk->size / sampleSize; + short *pOut = (short *)malloc( sizeof( short ) * ( sampleCount * pFormat->nChannels ) ); + + DecimateSampleRateBy2_16( (short *)pDataChunk->pData, pOut, sampleCount, pFormat->nChannels ); + + free( pDataChunk->pData ); + pDataChunk->pData = (byte *)pOut; + pDataChunk->size = sizeof( short ) * ( sampleCount/2 * pFormat->nChannels ); + + pFormat->nSamplesPerSec = 22050; + pFormat->nBlockAlign = 2 * pFormat->nChannels; + pFormat->nAvgBytesPerSec = 2 * pFormat->nSamplesPerSec * pFormat->nChannels; +} + +//----------------------------------------------------------------------------- +// Purpose: determine loop start +//----------------------------------------------------------------------------- +int FindLoopStart( int samplerChunk, int cueChunk ) +{ + int loopStartFromCue = -1; + int loopStartFromSampler = -1; + + if ( cueChunk != -1 ) + { + struct cuechunk_t + { + unsigned int dwName; + unsigned int dwPosition; + unsigned int fccChunk; + unsigned int dwChunkStart; + unsigned int dwBlockStart; + unsigned int dwSampleOffset; + }; + struct cueRIFF_t + { + int cueCount; + cuechunk_t cues[1]; + }; + + cueRIFF_t *pCue = (cueRIFF_t *)g_chunks[cueChunk].pData; + if ( pCue->cueCount > 0 ) + { + loopStartFromCue = pCue->cues[0].dwSampleOffset; + } + } + + if ( samplerChunk != -1 ) + { + struct SampleLoop + { + unsigned int dwIdentifier; + unsigned int dwType; + unsigned int dwStart; + unsigned int dwEnd; + unsigned int dwFraction; + unsigned int dwPlayCount; + }; + + struct samplerchunk_t + { + unsigned int dwManufacturer; + unsigned int dwProduct; + unsigned int dwSamplePeriod; + unsigned int dwMIDIUnityNote; + unsigned int dwMIDIPitchFraction; + unsigned int dwSMPTEFormat; + unsigned int dwSMPTEOffset; + unsigned int cSampleLoops; + unsigned int cbSamplerData; + struct SampleLoop Loops[1]; + }; + + // assume that the loop end is the sample end + // assume that only the first loop is relevant + samplerchunk_t *pSampler = (samplerchunk_t *)g_chunks[samplerChunk].pData; + if ( pSampler->cSampleLoops > 0 ) + { + // only support normal forward loops + if ( pSampler->Loops[0].dwType == 0 ) + { + loopStartFromSampler = pSampler->Loops[0].dwStart; + } + } + } + + return ( max( loopStartFromCue, loopStartFromSampler ) ); +} + +//----------------------------------------------------------------------------- +// Purpose: returns chunk, -1 if not found +//----------------------------------------------------------------------------- +int FindChunk( unsigned int id ) +{ + int i; + for ( i=0; i<g_numChunks; i++ ) + { + if ( g_chunks[i].id == id ) + { + return i; + } + } + + // not found + return - 1; +} + +bool EncodeAsXMA( const char *pDebugName, CUtlBuffer &targetBuff, int quality, bool bIsVoiceOver ) +{ +#ifdef NO_X360_XDK + return false; +#else + int formatChunk = FindChunk( WAVE_FMT ); + int dataChunk = FindChunk( WAVE_DATA ); + if ( formatChunk == -1 || dataChunk == -1 ) + { + // huh? these should have been pre-validated + return false; + } + + int vdatSize = 0; + int vdatChunk = FindChunk( WAVE_VALVEDATA ); + if ( vdatChunk != -1 ) + { + vdatSize = g_chunks[vdatChunk].size; + } + + int loopStart = FindLoopStart( FindChunk( WAVE_SAMPLER ), FindChunk( WAVE_CUE ) ); + + // format structure must be expected 16 bit PCM, otherwise encoder crashes + WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; + pFormat->nAvgBytesPerSec = pFormat->nSamplesPerSec * pFormat->nChannels * 2; + pFormat->nBlockAlign = 2 * pFormat->nChannels; + pFormat->cbSize = 0; + + XMAENCODERSTREAM inputStream = { 0 }; + + WAVEFORMATEXTENSIBLE wfx; + Assert( g_chunks[formatChunk].size <= sizeof( WAVEFORMATEXTENSIBLE ) ); + memcpy( &wfx, g_chunks[formatChunk].pData, g_chunks[formatChunk].size ); + if ( g_chunks[formatChunk].size < sizeof( WAVEFORMATEXTENSIBLE ) ) + { + memset( (unsigned char*)&wfx + g_chunks[formatChunk].size, 0, sizeof( WAVEFORMATEXTENSIBLE ) - g_chunks[formatChunk].size ); + } + + memcpy( &inputStream.Format, &wfx, sizeof( WAVEFORMATEX ) ); + inputStream.pBuffer = g_chunks[dataChunk].pData; + inputStream.BufferSize = g_chunks[dataChunk].size; + if ( loopStart != -1 ) + { + // can only support a single loop point until end of file + inputStream.LoopStart = loopStart; + inputStream.LoopLength = inputStream.BufferSize / ( pFormat->nChannels * sizeof( short ) ) - loopStart; + } + + void *pXMAData = NULL; + DWORD XMADataSize = 0; + XMA2WAVEFORMAT *pXMA2Format = NULL; + DWORD XMA2FormatSize = 0; + DWORD *pXMASeekTable = NULL; + DWORD XMASeekTableSize = 0; + HRESULT hr = S_OK; + + DWORD xmaFlags = XMAENCODER_NOFILTER; + if ( loopStart != -1 ) + { + xmaFlags |= XMAENCODER_LOOP; + } + + int numAttempts = 1; + while ( numAttempts < 10 ) + { + hr = XMA2InMemoryEncoder( 1, &inputStream, quality, xmaFlags, XMA_BLOCK_SIZE/1024, &pXMAData, &XMADataSize, &pXMA2Format, &XMA2FormatSize, &pXMASeekTable, &XMASeekTableSize ); + if ( !FAILED( hr ) ) + break; + + // make small jumps + quality += 5; + if ( quality > 100 ) + quality = 100; + if ( !g_bQuiet ) + { + Msg( "XMA Encoding Error on '%s', Attempting increasing quality to %d\n", pDebugName, quality ); + } + + numAttempts++; + + pXMAData = NULL; + XMADataSize = 0; + pXMA2Format = NULL; + XMA2FormatSize = 0; + pXMASeekTable = NULL; + XMASeekTableSize = 0; + } + + if ( FAILED( hr ) ) + { + // unrecoverable + return false; + } + else if ( numAttempts > 1 ) + { + if ( !g_bQuiet ) + { + Msg( "XMA Encoding Success on '%s' at quality %d\n", pDebugName, quality ); + } + } + + DWORD loopBlock = 0; + DWORD numLeadingSamples = 0; + DWORD numTrailingSamples = 0; + + if ( loopStart != -1 ) + { + // calculate start block/offset + DWORD loopBlockStartIndex = 0; + DWORD loopBlockStartOffset = 0; + + if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopBegin ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockStartIndex, &loopBlockStartOffset ) ) + { + // could not determine loop point, out of range of encoded samples + Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart ); + return false; + } + + loopBlock = loopBlockStartIndex; + numLeadingSamples = loopBlockStartOffset; + + if ( BigLong( pXMA2Format->LoopEnd ) < BigLong( pXMA2Format->SamplesEncoded ) ) + { + // calculate end block/offset + DWORD loopBlockEndIndex = 0; + DWORD loopBlockEndOffset = 0; + + if ( !SampleToXMABlockOffset( BigLong( pXMA2Format->LoopEnd ), pXMASeekTable, XMASeekTableSize/sizeof( DWORD ), &loopBlockEndIndex, &loopBlockEndOffset ) ) + { + // could not determine loop point, out of range of encoded samples + Msg( "XMA Loop Encoding Error on '%s', loop %d\n", pDebugName, loopStart ); + return false; + } + + if ( loopBlockEndIndex != BigLong( pXMA2Format->BlockCount ) - 1 ) + { + // end block MUST be last block + Msg( "XMA Loop Encoding Error on '%s', block end is %d/%d\n", pDebugName, loopBlockEndOffset, BigLong( pXMA2Format->BlockCount ) ); + return false; + } + + numTrailingSamples = BigLong( pXMA2Format->SamplesEncoded ) - BigLong( pXMA2Format->LoopEnd ); + } + + // check for proper encoding range + if ( loopBlock > 32767 ) + { + Msg( "XMA Loop Encoding Error on '%s', loop block exceeds 16 bits %d\n", pDebugName, loopBlock ); + return false; + } + if ( numLeadingSamples > 32767 ) + { + Msg( "XMA Loop Encoding Error on '%s', leading samples exceeds 16 bits %d\n", pDebugName, numLeadingSamples ); + return false; + } + if ( numTrailingSamples > 32767 ) + { + Msg( "XMA Loop Encoding Error on '%s', trailing samples exceeds 16 bits %d\n", pDebugName, numTrailingSamples ); + return false; + } + } + + xwvHeader_t header; + memset( &header, 0, sizeof( xwvHeader_t ) ); + + int seekTableSize = 0; + if ( vdatSize || bIsVoiceOver ) + { + // save the optional seek table only for vdat or vo + // the seek table size is expected to be derived by this calculation + seekTableSize = ( XMADataSize / XMA_BYTES_PER_PACKET ) * sizeof( int ); + if ( seekTableSize != XMASeekTableSize ) + { + Msg( "XMA Error: Unexpected seek table calculation in '%s'!", pDebugName ); + return false; + } + } + + if ( loopStart != -1 && ( vdatSize || bIsVoiceOver ) ) + { + Msg( "XMA Warning: Unexpected loop in vo data '%s'!", pDebugName ); + + // do not write the seek table for looping sounds + seekTableSize = 0; + } + + header.id = BigLong( XWV_ID ); + header.version = BigLong( XWV_VERSION ); + header.headerSize = BigLong( sizeof( xwvHeader_t ) ); + header.staticDataSize = BigLong( seekTableSize + vdatSize ); + header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + seekTableSize + vdatSize, XBOX_DVD_SECTORSIZE ) ); + header.dataSize = BigLong( XMADataSize ); + + // track the XMA number of samples that will get decoded + // which is NOT the same as what the source actually encoded + header.numDecodedSamples = pXMA2Format->SamplesEncoded; + + if ( loopStart != -1 ) + { + // the loop start is in source space (now meaningless), need the loop in XMA decoding sample space + header.loopStart = pXMA2Format->LoopBegin; + } + else + { + header.loopStart = BigLong( -1 ); + } + header.loopBlock = BigShort( (unsigned short)loopBlock ); + header.numLeadingSamples = BigShort( (unsigned short)numLeadingSamples ); + header.numTrailingSamples = BigShort( (unsigned short)numTrailingSamples ); + + header.vdatSize = BigShort( (short)vdatSize ); + header.format = XWV_FORMAT_XMA; + header.bitsPerSample = 16; + header.SetSampleRate( pFormat->nSamplesPerSec ); + header.SetChannels( pFormat->nChannels ); + header.quality = quality; + header.bHasSeekTable = ( seekTableSize != 0 ); + + // output header + targetBuff.Put( &header, sizeof( xwvHeader_t ) ); + + // output optional seek table + if ( seekTableSize ) + { + // seek table is already in big-endian format + targetBuff.Put( pXMASeekTable, seekTableSize ); + } + + // output vdat + if ( vdatSize ) + { + targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size ); + } + + AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); + + // write data + targetBuff.Put( pXMAData, XMADataSize ); + + // pad to EOF + AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); + + free( pXMAData ); + free( pXMA2Format ); + free( pXMASeekTable ); + + // xma encoder leaves its temporary files, we'll delete + scriptlib->DeleteTemporaryFiles( "LoopStrm*" ); + scriptlib->DeleteTemporaryFiles( "EncStrm*" ); + + return true; +#endif +} + +bool EncodeAsPCM( const char *pTargetName, CUtlBuffer &targetBuff ) +{ + int formatChunk = FindChunk( WAVE_FMT ); + int dataChunk = FindChunk( WAVE_DATA ); + if ( formatChunk == -1 || dataChunk == -1 ) + { + // huh? these should have been pre-validated + return false; + } + + WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; + if ( pFormat->wBitsPerSample != 16 ) + { + // huh? the input is expeted to be 16 bit PCM + return false; + } + + int vdatSize = 0; + int vdatChunk = FindChunk( WAVE_VALVEDATA ); + if ( vdatChunk != -1 ) + { + vdatSize = g_chunks[vdatChunk].size; + } + + chunk_t *pDataChunk = &g_chunks[dataChunk]; + + xwvHeader_t header; + memset( &header, 0, sizeof( xwvHeader_t ) ); + + int sampleSize = pFormat->nChannels * sizeof( short ); + int sampleCount = pDataChunk->size / sampleSize; + + header.id = BigLong( XWV_ID ); + header.version = BigLong( XWV_VERSION ); + header.headerSize = BigLong( sizeof( xwvHeader_t ) ); + header.staticDataSize = BigLong( vdatSize ); + header.dataOffset = BigLong( AlignValue( sizeof( xwvHeader_t) + vdatSize, XBOX_DVD_SECTORSIZE ) ); + header.dataSize = BigLong( pDataChunk->size ); + header.numDecodedSamples = BigLong( sampleCount ); + header.loopStart = BigLong( -1 ); + header.loopBlock = 0; + header.numLeadingSamples = 0; + header.numTrailingSamples = 0; + header.vdatSize = BigShort( (short)vdatSize ); + header.format = XWV_FORMAT_PCM; + header.bitsPerSample = 16; + header.SetSampleRate( pFormat->nSamplesPerSec ); + header.SetChannels( pFormat->nChannels ); + header.quality = 100; + + // output header + targetBuff.Put( &header, sizeof( xwvHeader_t ) ); + + // output vdat + if ( vdatSize ) + { + targetBuff.Put( g_chunks[vdatChunk].pData, g_chunks[vdatChunk].size ); + } + + AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); + + for ( int i = 0; i < sampleCount * pFormat->nChannels; i++ ) + { + ((short *)pDataChunk->pData)[i] = BigShort( ((short *)pDataChunk->pData)[i] ); + } + + // write data + targetBuff.Put( pDataChunk->pData, pDataChunk->size ); + + // pad to EOF + AlignToBoundary( targetBuff, XBOX_DVD_SECTORSIZE ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: read source, do work, and write to target +//----------------------------------------------------------------------------- +bool CreateTargetFile_WAV( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + g_numChunks = 0; + + // resolve relative source to absolute path + char fullSourcePath[MAX_PATH]; + if ( _fullpath( fullSourcePath, pSourceName, sizeof( fullSourcePath ) ) ) + { + pSourceName = fullSourcePath; + } + + if ( !ReadChunks( pSourceName, g_numChunks, g_chunks ) ) + { + Msg( "No RIFF Chunks on '%s'\n", pSourceName ); + return false; + } + + int formatChunk = FindChunk( WAVE_FMT ); + if ( formatChunk == -1 ) + { + Msg( "RIFF Format Chunk not found on '%s'\n", pSourceName ); + return false; + } + + int dataChunk = FindChunk( WAVE_DATA ); + if ( dataChunk == -1 ) + { + Msg( "RIFF Data Chunk not found on '%s'\n", pSourceName ); + return false; + } + + // get the conversion rules + conversion_t *pConversion = g_defaultConversionRules; + if ( V_stristr( g_szModPath, "\\portal" ) ) + { + pConversion = g_portalConversionRules; + } + + // conversion rules are based on matching subdir + for ( int i=1; ;i++ ) + { + char subString[MAX_PATH]; + if ( !pConversion[i].pSubDir ) + { + // end of list + break; + } + + sprintf( subString, "\\%s\\", pConversion[i].pSubDir ); + if ( V_stristr( pSourceName, subString ) ) + { + // use matched conversion rules + pConversion = &pConversion[i]; + break; + } + } + + bool bForceTo22K = pConversion->bForceTo22K; + int quality = pConversion->quality; + + // cannot trust the localization depots to have matched their sources + // cannot allow 44K + if ( IsLocalizedFile( pSourceName ) ) + { + bForceTo22K = true; + } + + // classify strict vo from /sound/vo only + bool bIsVoiceOver = V_stristr( pSourceName, "\\sound\\vo\\" ) != NULL; + + // can override default settings + quality = CommandLine()->ParmValue( "-xmaquality", quality ); + if ( quality < 0 ) + quality = 0; + else if ( quality > 100 ) + quality = 100; + if ( !g_bQuiet ) + { + Msg( "Encoding quality: %d on '%s'\n", quality, pSourceName ); + } + + int vdatSize = 0; + int vdatChunk = FindChunk( WAVE_VALVEDATA ); + if ( vdatChunk != -1 ) + { + // compile to optimal block + if ( !CompressVDAT( &g_chunks[vdatChunk] ) ) + { + Msg( "Compress VDAT Error on '%s'\n", pSourceName ); + return false; + } + vdatSize = g_chunks[vdatChunk].size; + } + + // for safety (not trusting their decoding) and simplicity convert all data to 16 bit PCM before encoding + WAVEFORMATEX *pFormat = (WAVEFORMATEX *)g_chunks[formatChunk].pData; + if ( ( pFormat->wFormatTag == WAVE_FORMAT_PCM ) ) + { + if ( pFormat->wBitsPerSample == 8 ) + { + ConvertPCMDataChunk8To16( &g_chunks[formatChunk], &g_chunks[dataChunk] ); + } + } + else if ( pFormat->wFormatTag == WAVE_FORMAT_ADPCM ) + { + ConvertADPCMDataChunkTo16( &g_chunks[formatChunk], &g_chunks[dataChunk] ); + } + else + { + Msg( "Unknown RIFF Format on '%s'\n", pSourceName ); + return false; + } + + // optionally decimate to 22K + if ( pFormat->nSamplesPerSec == 44100 && bForceTo22K ) + { + if ( !g_bQuiet ) + { + Msg( "Converting to 22K '%s'\n", pSourceName ); + } + ConvertPCMDataChunk16To22K( &g_chunks[formatChunk], &g_chunks[dataChunk] ); + } + + CUtlBuffer targetBuff; + bool bSuccess; + + bSuccess = EncodeAsXMA( pSourceName, targetBuff, quality, bIsVoiceOver ); + if ( bSuccess ) + { + WriteBufferToFile( pTargetName, targetBuff, bWriteToZip, g_WriteModeForConversions ); + } + + // release data + for ( int i = 0; i < g_numChunks; i++ ) + { + free( g_chunks[i].pData ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Purpose: MP3's are already pre-converted into .360.wav +//----------------------------------------------------------------------------- +bool CreateTargetFile_MP3( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + CUtlBuffer targetBuffer; + + // ignore the .mp3 source, the .360.wav target should have been pre-converted, checked in, and exist + // use the expected target as the source + if ( !scriptlib->ReadFileToBuffer( pTargetName, targetBuffer ) ) + { + // the .360.wav target does not exist + // try again using a .wav version and convert from that + char wavFilename[MAX_PATH]; + V_StripExtension( pSourceName, wavFilename, sizeof( wavFilename ) ); + V_SetExtension( wavFilename, ".wav", sizeof( wavFilename ) ); + if ( scriptlib->DoesFileExist( wavFilename ) ) + { + if ( CreateTargetFile_WAV( wavFilename, pTargetName, bWriteToZip ) ) + { + return true; + } + } + + return false; + } + + // no conversion to write, but possibly zipped + bool bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, WRITE_TO_DISK_NEVER ); + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Get the preload data for a wav file +//----------------------------------------------------------------------------- +bool GetPreloadData_WAV( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + xwvHeader_t *pHeader = ( xwvHeader_t * )fileBufferIn.Base(); + if ( pHeader->id != ( unsigned int )BigLong( XWV_ID ) || + pHeader->version != ( unsigned int )BigLong( XWV_VERSION ) || + pHeader->headerSize != BigLong( sizeof( xwvHeader_t ) ) ) + { + // bad version + Msg( "Can't preload: '%s', has bad version\n", pFilename ); + return false; + } + + // ensure caller's buffer is clean + // caller determines preload size, via TellMaxPut() + preloadBufferOut.Purge(); + unsigned int preloadSize = BigLong( pHeader->headerSize ) + BigLong( pHeader->staticDataSize ); + preloadBufferOut.Put( fileBufferIn.Base(), preloadSize ); + + return true; +} diff --git a/utils/xbox/MakeGameData/MakeTextures.cpp b/utils/xbox/MakeGameData/MakeTextures.cpp new file mode 100644 index 0000000..f267367 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeTextures.cpp @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: .360.VTF Creation +// +//=====================================================================================// + +#include "MakeGameData.h" +#include "vtf/vtf.h" + +//----------------------------------------------------------------------------- +// Purpose: Create the 360 VTF, use the library! +//----------------------------------------------------------------------------- +bool CreateTargetFile_VTF( const char *pSourceName, const char *pTargetName, bool bWriteToZip ) +{ + CUtlBuffer sourceBuffer; + CUtlBuffer targetBuffer; + + if ( !scriptlib->ReadFileToBuffer( pSourceName, sourceBuffer ) ) + { + return false; + } + + // using library conversion routine + bool bSuccess = ConvertVTFTo360Format( pSourceName, sourceBuffer, targetBuffer, CompressCallback ); + if ( bSuccess ) + { + bSuccess = WriteBufferToFile( pTargetName, targetBuffer, bWriteToZip, g_WriteModeForConversions ); + } + + return bSuccess; +} + +//----------------------------------------------------------------------------- +// Get the preload data for a vtf file +//----------------------------------------------------------------------------- +bool GetPreloadData_VTF( const char *pFilename, CUtlBuffer &fileBufferIn, CUtlBuffer &preloadBufferOut ) +{ + if ( !GetVTFPreload360Data( pFilename, fileBufferIn, preloadBufferOut ) ) + { + Msg( "Can't preload: '%s', has bad version\n", pFilename ); + return false; + } + + return true; +}
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/MakeZip.cpp b/utils/xbox/MakeGameData/MakeZip.cpp new file mode 100644 index 0000000..69950b0 --- /dev/null +++ b/utils/xbox/MakeGameData/MakeZip.cpp @@ -0,0 +1,593 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#include "MakeGameData.h" + +static CUtlSymbolTable g_CriticalPreloadTable( 0, 32, true ); + +//----------------------------------------------------------------------------- +// Purpose: Compute preload data by file type. Calls into appropriate libraries +// to get the preload info. Libraries would use filename to generate +// the preload if there is a compilation step, otherwise the file buffer +// is a buffer loaded filename. +//----------------------------------------------------------------------------- +static bool GetPreloadBuffer( const char *pFilename, CUtlBuffer &fileBuffer, CUtlBuffer &preloadBuffer ) +{ + char fileExtension[MAX_PATH]; + Q_ExtractFileExtension( pFilename, fileExtension, sizeof( fileExtension ) ); + + // adding an entire file IS ONLY for files that are expected to be read by the game as a single read + // NOT for files that have any seek pattern + bool bAddEntireFile = false; + + // trivial small files, always add + if ( !Q_stricmp( fileExtension, "txt" ) || + !Q_stricmp( fileExtension, "dat" ) || + !Q_stricmp( fileExtension, "lst" ) || + !Q_stricmp( fileExtension, "res" ) || + !Q_stricmp( fileExtension, "vmt" ) || + !Q_stricmp( fileExtension, "cfg" ) || + !Q_stricmp( fileExtension, "bnf" ) || + !Q_stricmp( fileExtension, "rc" ) || + !Q_stricmp( fileExtension, "vbf" ) || + !Q_stricmp( fileExtension, "vfe" ) || + !Q_stricmp( fileExtension, "pcf" ) || + !Q_stricmp( fileExtension, "inf" ) ) + { + bAddEntireFile = true; + } + + // critical resources get blindly added to the preload + if ( !bAddEntireFile && ( g_CriticalPreloadTable.Find( pFilename ) != UTL_INVAL_SYMBOL ) ) + { + bAddEntireFile = true; + } + + if ( bAddEntireFile && LZMA_IsCompressed( (unsigned char *)fileBuffer.Base() ) ) + { + // sorry, not allowed to add entirely to preload if already compressed + // breaks the run-time filesystem due to inability to deliver file as-is + bAddEntireFile = false; + } + + if ( bAddEntireFile ) + { + if ( fileBuffer.TellMaxPut() >= 1*1024 ) + { + // only compress preload files of reasonable size + unsigned int compressedSize = 0; + unsigned char *pCompressedOutput = LZMA_OpportunisticCompress( (unsigned char *)fileBuffer.Base(), + fileBuffer.TellMaxPut(), &compressedSize ); + if ( pCompressedOutput ) + { + // add as compressed + preloadBuffer.EnsureCapacity( compressedSize ); + preloadBuffer.Put( pCompressedOutput, compressedSize ); + free( pCompressedOutput ); + return true; + } + } + + // add entire file to preload section + preloadBuffer.EnsureCapacity( fileBuffer.TellMaxPut() ); + preloadBuffer.Put( fileBuffer.Base(), fileBuffer.TellMaxPut() ); + return true; + } + + // Each library will fetch the optional preload data into caller's buffer + if ( !Q_stricmp( fileExtension, "wav" ) ) + { + return GetPreloadData_WAV( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vtf" ) ) + { + return GetPreloadData_VTF( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vcs" ) ) + { + return GetPreloadData_VCS( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vhv" ) ) + { + return GetPreloadData_VHV( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vtx" ) ) + { + return GetPreloadData_VTX( pFilename, fileBuffer, preloadBuffer ); + } + else if ( !Q_stricmp( fileExtension, "vvd" ) ) + { + return GetPreloadData_VVD( pFilename, fileBuffer, preloadBuffer ); + } + + // others... + return false; +} + +void SetupCriticalPreloadScript( const char *pModPath ) +{ + characterset_t breakSet; + CharacterSetBuild( &breakSet, "" ); + + // purge any prior entries + g_CriticalPreloadTable.RemoveAll(); + + // populate table + char szCriticaList[MAX_PATH]; + char szToken[MAX_PATH]; + V_ComposeFileName( pModPath, "scripts\\preload_xbox.xsc", szCriticaList, sizeof( szCriticaList ) ); + CUtlBuffer criticalListBuffer; + if ( ReadFileToBuffer( szCriticaList, criticalListBuffer, true, true ) ) + { + for ( ;; ) + { + int nTokenSize = criticalListBuffer.ParseToken( &breakSet, szToken, sizeof( szToken ) ); + if ( nTokenSize <= 0 ) + { + break; + } + + V_strlower( szToken ); + V_FixSlashes( szToken, CORRECT_PATH_SEPARATOR ); + if ( UTL_INVAL_SYMBOL == g_CriticalPreloadTable.Find( szToken ) ) + { + g_CriticalPreloadTable.AddString( szToken ); + } + } + } +} + +CXZipTool::CXZipTool() +{ + m_pZip = NULL; + m_hPreloadFile = INVALID_HANDLE_VALUE; + m_hOutputZipFile = INVALID_HANDLE_VALUE; + m_PreloadFilename[0] = '\0'; +} + +CXZipTool::~CXZipTool() +{ + Reset(); +} + +void CXZipTool::Reset() +{ + if ( m_hOutputZipFile != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hOutputZipFile ); + m_hOutputZipFile = INVALID_HANDLE_VALUE; + } + + if ( m_hPreloadFile != INVALID_HANDLE_VALUE ) + { + CloseHandle( m_hPreloadFile ); + m_hPreloadFile = INVALID_HANDLE_VALUE; + } + + if ( m_PreloadFilename[0] ) + { + DeleteFile( m_PreloadFilename ); + m_PreloadFilename[0] = '\0'; + } + + if ( m_pZip ) + { + IZip::ReleaseZip( m_pZip ); + m_pZip = NULL; + } + + m_ZipPreloadDirectoryEntries.Purge(); + m_ZipCRCList.Purge(); + m_ZipPreloadRemapEntries.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add a file buffer to the zip +//----------------------------------------------------------------------------- +bool CXZipTool::AddBuffer( const char *pFilename, CUtlBuffer &fileBuffer, bool bDoPreload ) +{ + if ( !m_pZip ) + { + return false; + } + + // safely strip unecessary prefix, otherise pollutes CRC + if ( !strnicmp( pFilename, ".\\", 2 ) ) + { + pFilename += 2; + } + + // scan for CRC collision now, not at runtime + CRCEntry_t crcEntry; + crcEntry.fileNameCRC = HashStringCaselessConventional( pFilename ); + crcEntry.filename = pFilename; + int idx = m_ZipCRCList.Find( crcEntry ); + if ( -1 != idx ) + { + if ( !V_stricmp( pFilename, m_ZipCRCList[idx].filename.String() ) ) + { + // file has already been added, ignore as succesful + return true; + } + + Msg( "ERROR: CRC Collision: '%s' with '%s'\n", pFilename, m_ZipCRCList[idx].filename.String() ); + return false; + } + else + { + // add unique entry to lists + // must track filenames in a non-sort manner + m_ZipCRCList.Insert( crcEntry ); + } + + // default, no preload entry + unsigned short preloadDir = INVALID_PRELOAD_ENTRY; + + if ( bDoPreload ) + { + CUtlBuffer preloadBuffer; + bool bHasPreload = GetPreloadBuffer( pFilename, fileBuffer, preloadBuffer ); + int preloadSize = preloadBuffer.TellMaxPut(); + if ( bHasPreload && preloadSize > 0 ) + { + if ( m_ZipPreloadDirectoryEntries.Count() >= 65534 ) + { + Msg( "ERROR: Preload section FULL!, skipping %s\n", pFilename ); + return FALSE; + } + + // Initialize the entry header + ZIP_PreloadDirectoryEntry entry; + memset( &entry, 0, sizeof( entry ) ); + + entry.Length = preloadSize; + entry.DataOffset = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT ); + + // Add the directory entry to the preload table + preloadDir = m_ZipPreloadDirectoryEntries.AddToTail( entry ); + + // Append the preload data to the preload file + DWORD numBytesWritten; + BOOL bOK = WriteFile( m_hPreloadFile, preloadBuffer.Base(), preloadSize, &numBytesWritten, NULL ); + if ( !bOK || preloadSize != numBytesWritten ) + { + Msg( "ERROR: writing %d preload bytes of '%s'\n", preloadSize, pFilename ); + return false; + } + + if ( !g_bQuiet ) + { + // Spew it + if ( LZMA_IsCompressed( (unsigned char *)preloadBuffer.Base() ) ) + { + unsigned int actualSize = LZMA_GetActualSize( (unsigned char *)preloadBuffer.Base() ); + Msg( "Preload: '%s': Compressed:%u Actual:%u\n", pFilename, preloadSize, actualSize ); + } + else + { + Msg( "Preload: '%s': Length:%u\n", pFilename, preloadSize ); + } + } + } + } + + unsigned int fileSize = fileBuffer.TellMaxPut(); + if ( fileSize > 0 ) + { + // order in zip is sorted, not sequential + m_pZip->AddBufferToZip( pFilename, fileBuffer.Base(), fileSize, false ); + + // track the file in the zip directory to it's preload entry + // order in preload is sequential as buffers are added + preloadRemap_t remap; + remap.filename = pFilename; + remap.preloadDirIndex = preloadDir; + m_ZipPreloadRemapEntries.AddToTail( remap ); + + if ( !g_bQuiet ) + { + Msg( "File: '%s': Length:%u\n", pFilename, fileSize ); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Load a file and add it to the zip +//----------------------------------------------------------------------------- +bool CXZipTool::AddFile( const char *pFilename, bool bDoPreload ) +{ + if ( !m_pZip ) + { + return false; + } + + FILE* pFile = fopen( pFilename, "rb" ); + if( !pFile ) + { + Msg( "ERROR: failed to open file: '%s'\n", pFilename ); + return false; + } + + // Get the length of the file + fseek( pFile, 0, SEEK_END ); + unsigned fileSize = ftell( pFile ); + fseek( pFile, 0, SEEK_SET); + + // read file to buffer + CUtlBuffer fileBuffer; + fileBuffer.EnsureCapacity( fileSize ); + fread( fileBuffer.Base(), fileSize, 1, pFile ); + fclose( pFile ); + fileBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, fileSize ); + + return AddBuffer( pFilename, fileBuffer, bDoPreload ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add the preload section and save out the zip file +//----------------------------------------------------------------------------- +bool CXZipTool::End() +{ + if ( !m_pZip ) + { + return false; + } + + // Add the preload section to the zip + if ( m_ZipPreloadDirectoryEntries.Count() ) + { + CUtlBuffer sectionBuffer; + CByteswap byteSwap; + + // pc tools write 360 native data + byteSwap.ActivateByteSwapping( IsPC() ); + + // determine the preload data footprint + unsigned int preloadDataSize = SetFilePointer( m_hPreloadFile, 0, NULL, FILE_CURRENT ); + + // finalize header + m_ZipPreloadHeader.DirectoryEntries = m_ZipPreloadRemapEntries.Count(); + m_ZipPreloadHeader.PreloadDirectoryEntries = m_ZipPreloadDirectoryEntries.Count(); + + // determine the total section size ( treated as a single file inside zip ) + unsigned int sectionSize = sizeof( ZIP_PreloadHeader ) + + m_ZipPreloadHeader.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) + + m_ZipPreloadHeader.DirectoryEntries * sizeof( unsigned short ) + + preloadDataSize; + sectionSize = AlignValue( sectionSize, m_ZipPreloadHeader.Alignment ); + sectionBuffer.EnsureCapacity( sectionSize ); + + // save data in order + // save the header + byteSwap.SwapFieldsToTargetEndian( &m_ZipPreloadHeader ); + sectionBuffer.Put( &m_ZipPreloadHeader, sizeof( ZIP_PreloadHeader ) ); + + // fixup and save the preload directory + for ( int i=0; i<m_ZipPreloadDirectoryEntries.Count(); i++ ) + { + ZIP_PreloadDirectoryEntry entry = m_ZipPreloadDirectoryEntries[i]; + byteSwap.SwapFieldsToTargetEndian( &entry ); + sectionBuffer.Put( &entry, sizeof( ZIP_PreloadDirectoryEntry ) ); + } + + // generate remap table + char fileName[MAX_PATH]; + int fileSize; + int zipIndex = -1; + unsigned short *pRemapTable = (unsigned short *)malloc( m_ZipPreloadRemapEntries.Count() * sizeof( unsigned short ) ); + for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ ) + { + // zip files get iterated in the same order they are serialized to disk + fileName[0] = '\0'; + fileSize = 0; + zipIndex = m_pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize ); + + // find the file in the preload remap table + bool bFound = false; + int j; + for ( j=0; j<m_ZipPreloadRemapEntries.Count(); j++ ) + { + if ( !Q_stricmp( fileName, m_ZipPreloadRemapEntries[j].filename.String() ) ) + { + bFound = true; + break; + } + } + if ( !bFound ) + { + // shouldn't happen, every file in the zip has a matching preload remap entry, that is valid or marked invalid + Msg( "ERROR: file '%s' was expected to have an entry in preload table\n", fileName ); + } + + // the remap table is used to go find a file's preload data (if available) + pRemapTable[i] = m_ZipPreloadRemapEntries[j].preloadDirIndex; + } + for ( int i=0; i<m_ZipPreloadRemapEntries.Count(); i++ ) + { + unsigned short s = pRemapTable[i]; + sectionBuffer.PutShort( BigShort( s ) ); + } + free( pRemapTable ); + + // get and save preload data + void *pPreloadData = malloc( preloadDataSize ); + SetFilePointer( m_hPreloadFile, 0, NULL, FILE_BEGIN ); + DWORD numBytesRead; + BOOL bOK = ReadFile( m_hPreloadFile, pPreloadData, preloadDataSize, &numBytesRead, NULL ); + if ( !bOK || numBytesRead != preloadDataSize ) + { + Msg( "ERROR: failed to read %d bytes from temporary preload file\n", preloadDataSize ); + } + CloseHandle( m_hPreloadFile ); + m_hPreloadFile = INVALID_HANDLE_VALUE; + + sectionBuffer.Put( pPreloadData, preloadDataSize ); + free( pPreloadData ); + + // cannot have written more than was pre-calced, unless code was broken + Assert( (unsigned int)sectionBuffer.TellMaxPut() <= sectionSize ); + while( (unsigned int)sectionBuffer.TellMaxPut() < sectionSize ) + { + // pad to final alignment + sectionBuffer.PutChar( 0 ); + } + + m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, sectionBuffer.Base(), sectionBuffer.TellMaxPut(), false ); + } + else + { + // Clear the preload section placeholder + m_pZip->RemoveFileFromZip( PRELOAD_SECTION_NAME ); + } + + m_pZip->SaveToDisk( m_hOutputZipFile ); + + Reset(); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Create the zip file +//----------------------------------------------------------------------------- +bool CXZipTool::Begin( const char *pZipFileName, unsigned int alignment ) +{ + // get the volume of the target zip + char drivePath[MAX_PATH]; + _splitpath( pZipFileName, drivePath, NULL, NULL, NULL ); + + m_pZip = IZip::CreateZip( drivePath, true ); + + if ( alignment ) + { + // making an aligned zip that uses an optimized (but incompatible) format + m_pZip->ForceAlignment( true, false, alignment ); + } + + // Open the output file + m_hOutputZipFile = CreateFile( pZipFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( m_hOutputZipFile == INVALID_HANDLE_VALUE ) + { + Msg( "ERROR: failed to create zip file '%s'\n", pZipFileName ); + return false; + } + + // Create a temporary file for storing the preloaded data + scriptlib->MakeTemporaryFilename( g_szModPath, m_PreloadFilename, sizeof( m_PreloadFilename ) ); + m_hPreloadFile = CreateFile( m_PreloadFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); + if ( m_hPreloadFile == INVALID_HANDLE_VALUE ) + { + Msg( "ERROR: failed to create temporary file '%s' for preload data\n", m_PreloadFilename ); + CloseHandle( m_hOutputZipFile ); + m_hOutputZipFile = INVALID_HANDLE_VALUE; + return false; + } + memset( &m_ZipPreloadHeader, 0, sizeof( ZIP_PreloadHeader ) ); + + m_ZipPreloadHeader.Version = PRELOAD_HDR_VERSION; + m_ZipPreloadHeader.Alignment = alignment; + + // Add a placeholder for the preload section + m_pZip->AddBufferToZip( PRELOAD_SECTION_NAME, NULL, 0, false ); + preloadRemap_t remap; + remap.filename = PRELOAD_SECTION_NAME; + remap.preloadDirIndex = INVALID_PRELOAD_ENTRY; + m_ZipPreloadRemapEntries.AddToTail( remap ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Dump the preload contents +//----------------------------------------------------------------------------- +void CXZipTool::SpewPreloadInfo( const char *pZipName ) +{ + IZip *pZip = IZip::CreateZip( NULL, true ); + + HANDLE hZipFile = pZip->ParseFromDisk( pZipName ); + if ( !hZipFile ) + { + Msg( "Bad or missing zip file, failed to open '%s'\n", pZipName ); + return; + } + + CUtlBuffer preloadBuffer; + if ( !pZip->ReadFileFromZip( hZipFile, PRELOAD_SECTION_NAME, false, preloadBuffer ) ) + { + Msg( "No preload info for '%s'\n", pZipName ); + return; + } + + preloadBuffer.ActivateByteSwapping( IsPC() ); + + ZIP_PreloadHeader header; + preloadBuffer.GetObjects( &header ); + + // get the dir table + ZIP_PreloadDirectoryEntry *pDir = (ZIP_PreloadDirectoryEntry *)malloc( header.PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) ); + preloadBuffer.GetObjects( pDir, header.PreloadDirectoryEntries ); + + // get the remap table + unsigned short *pRemap = (unsigned short *)malloc( header.DirectoryEntries * sizeof( unsigned short ) ); + for ( unsigned int i = 0; i < header.DirectoryEntries; i++ ) + { + pRemap[i] = preloadBuffer.GetShort(); + } + + int zipIndex = -1; + int fileSize; + char fileName[MAX_PATH]; + + // iterate preload entries sequentially + CUtlDict< unsigned int, int > sizes( true ); + for ( unsigned int i = 0; i < header.DirectoryEntries; i++ ) + { + fileName[0] = '\0'; + fileSize = 0; + zipIndex = pZip->GetNextFilename( zipIndex, fileName, sizeof( fileName ), fileSize ); + + unsigned short zipPreloadDirIndex = pRemap[i]; + if ( zipPreloadDirIndex == INVALID_PRELOAD_ENTRY ) + { + continue; + } + + Msg( "Offset: 0x%8.8x Length: %5d %s (%d)\n", pDir[zipPreloadDirIndex].DataOffset, pDir[zipPreloadDirIndex].Length, fileName, fileSize ); + + // total preload sizes by extension + const char *pExt = V_GetFileExtension( fileName ); + if ( !pExt ) + { + pExt = "???"; + } + int iIndex = sizes.Find( pExt ); + if ( iIndex == sizes.InvalidIndex() ) + { + iIndex = sizes.Insert( pExt ); + sizes[iIndex] = 0; + } + sizes[iIndex] += pDir[zipPreloadDirIndex].Length; + } + + Msg( "\n" ); + Msg( "Preload Size: %.2f MB\n", (float)preloadBuffer.TellMaxPut()/(1024.0f * 1024.0f) ); + Msg( "Zip Entries: %d\n", header.DirectoryEntries ); + Msg( "Preload Entries: %d\n", header.PreloadDirectoryEntries ); + + // dump each extension's total size, necessary for debugging who is the largest contributor + for ( int i = 0; i < sizes.Count(); i++ ) + { + Msg( "Extension: '%3s' %d bytes (%.2f%s)\n", sizes.GetElementName( i ), sizes[i], (float)sizes[i]/(float)preloadBuffer.TellMaxPut() * 100.0f, "%%" ); + } + Msg( "\n" ); + + free( pRemap ); + free( pDir ); + + IZip::ReleaseZip( pZip ); +} diff --git a/utils/xbox/MakeGameData/XZipTool.h b/utils/xbox/MakeGameData/XZipTool.h new file mode 100644 index 0000000..41713cf --- /dev/null +++ b/utils/xbox/MakeGameData/XZipTool.h @@ -0,0 +1,61 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#pragma once + +#include <stdio.h> +#include "utlbuffer.h" +#include "zip_uncompressed.h" +#include "generichash.h" +#include "zip_utils.h" +#include "byteswap.h" +#include "tier1/UtlVector.h" +#include "UtlSortVector.h" + +struct CRCEntry_t +{ + unsigned int fileNameCRC; + CUtlString filename; +}; + +struct preloadRemap_t +{ + CUtlString filename; + unsigned short preloadDirIndex; +}; + +class CZipCRCLessFunc +{ +public: + bool Less( CRCEntry_t const& src1, CRCEntry_t const& src2, void *pCtx ) + { + return ( src1.fileNameCRC < src2.fileNameCRC ); + } +}; + +class CXZipTool +{ +public: + CXZipTool(); + ~CXZipTool(); + + void Reset(); + bool Begin( const char* pFileName, unsigned int alignment = 0 ); + bool End(); + bool AddBuffer( const char* pFileName, CUtlBuffer &buffer, bool bPreload = true ); + bool AddFile( const char* pFileName, bool bPreload = true ); + void SpewPreloadInfo( const char *pZipName ); + +private: + IZip *m_pZip; + char m_PreloadFilename[MAX_PATH]; + HANDLE m_hPreloadFile; + HANDLE m_hOutputZipFile; + ZIP_PreloadHeader m_ZipPreloadHeader; + CUtlVector< ZIP_PreloadDirectoryEntry > m_ZipPreloadDirectoryEntries; + CUtlSortVector< CRCEntry_t, CZipCRCLessFunc > m_ZipCRCList; + CUtlVector< preloadRemap_t > m_ZipPreloadRemapEntries; +};
\ No newline at end of file diff --git a/utils/xbox/MakeGameData/imaadpcm.cpp b/utils/xbox/MakeGameData/imaadpcm.cpp new file mode 100644 index 0000000..6648669 --- /dev/null +++ b/utils/xbox/MakeGameData/imaadpcm.cpp @@ -0,0 +1,1531 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +/*************************************************************************** + * + * Copyright (C) 2001 Microsoft Corporation. All Rights Reserved. + * + * File: imaadpcm.cpp + * Content: IMA ADPCM CODEC. + * History: + * Date By Reason + * ==== == ====== + * 04/29/01 dereks Created. + * 06/12/01 jharding Adapted for command-line encode + * + ****************************************************************************/ + +#include <stdio.h> +#include <wtypes.h> +#include <assert.h> +#include "imaadpcm.h" + +// 1/2 the range of the searchable step indices +// for a particular block when optimizing on a +// per-block basis. Widening or narrowing this +// range may produce better/worse encodings. +// Experimentation may be necessary. Higher values +// cause each block to be encoded better, but may +// produce popping in particularly fast attacks across +// blocks, while smaller values limit the number +// of encodings you consider +#define STEPINDEXSEARCHRANGE (24) + +/**************************************************************************** + * + * CImaAdpcmCodec + * + * Description: + * Object constructor. + * + * Arguments: + * (void) + * + * Returns: + * (void) + * + ****************************************************************************/ + +// +// This array is used by NextStepIndex to determine the next step index to use. +// The step index is an index to the m_asStep[] array, below. +// + +const short CImaAdpcmCodec::m_asNextStep[16] = +{ + -1, -1, -1, -1, 2, 4, 6, 8, + -1, -1, -1, -1, 2, 4, 6, 8 +}; + +// +// This array contains the array of step sizes used to encode the ADPCM +// samples. The step index in each ADPCM block is an index to this array. +// + +const short CImaAdpcmCodec::m_asStep[89] = +{ + 7, 8, 9, 10, 11, 12, 13, + 14, 16, 17, 19, 21, 23, 25, + 28, 31, 34, 37, 41, 45, 50, + 55, 60, 66, 73, 80, 88, 97, + 107, 118, 130, 143, 157, 173, 190, + 209, 230, 253, 279, 307, 337, 371, + 408, 449, 494, 544, 598, 658, 724, + 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, + 3024, 3327, 3660, 4026, 4428, 4871, 5358, + 5894, 6484, 7132, 7845, 8630, 9493, 10442, + 11487, 12635, 13899, 15289, 16818, 18500, 20350, + 22385, 24623, 27086, 29794, 32767 +}; + +CImaAdpcmCodec::CImaAdpcmCodec +( + void +) +{ +} + + +/**************************************************************************** + * + * ~CImaAdpcmCodec + * + * Description: + * Object destructor. + * + * Arguments: + * (void) + * + * Returns: + * (void) + * + ****************************************************************************/ + +CImaAdpcmCodec::~CImaAdpcmCodec +( + void +) +{ +} + + +/**************************************************************************** + * + * Initialize + * + * Description: + * Initializes the object. + * + * Arguments: + * LPCIMAADPCMWAVEFORMAT [in]: encoded data format. + * BOOL [in]: TRUE to initialize the object as an encoder. + * + * Returns: + * BOOL: TRUE on success. + * + ****************************************************************************/ + +BOOL +CImaAdpcmCodec::Initialize +( + LPCIMAADPCMWAVEFORMAT pwfxEncode, + CODEC_MODE cmCodecMode +) +{ + static const LPFNIMAADPCMCONVERT apfnConvert[2][2] = + { + { + DecodeM16, + DecodeS16 + }, + { + EncodeM16, + EncodeS16 + } + }; + + if(!IsValidImaAdpcmFormat(pwfxEncode)) + { + return FALSE; + } + + // + // Save the format data + // + + m_wfxEncode = *pwfxEncode; + m_cmCodecMode = cmCodecMode; + + // + // Set up the conversion function + // + + m_pfnConvert = apfnConvert[!(m_cmCodecMode == CODEC_MODE_DECODE)][m_wfxEncode.wfx.nChannels - 1]; + + // + // Initialize the stepping indices + // + + m_nStepIndexL = m_nStepIndexR = 0; + + return TRUE; +} + + +/**************************************************************************** + * + * Convert + * + * Description: + * Converts data from the source to destination format. + * + * Arguments: + * LPCVOID [in]: source buffer. + * LPVOID [out]: destination buffer. + * UINT [in]: block count. + * + * Returns: + * BOOL: TRUE on success. + * + ****************************************************************************/ + +BOOL +CImaAdpcmCodec::Convert +( + LPCVOID pvSrc, + LPVOID pvDst, + UINT cBlocks +) +{ + // Array of decoders + static const LPFNIMAADPCMCONVERT apfnDecoders[2] = + { + DecodeM16, + DecodeS16 + }; + + // Both destination and source block sizes + DWORD dwSrcBlockSize = m_wfxEncode.wfx.nChannels * m_wfxEncode.wSamplesPerBlock * sizeof(short); + DWORD dwDstBlockSize = m_wfxEncode.wfx.nBlockAlign; + + // Zero out the output + ZeroMemory( pvDst, cBlocks * dwDstBlockSize ); + + switch( m_cmCodecMode ) + { + case CODEC_MODE_DECODE: + // If we are decoding, just do it + return m_pfnConvert( (LPBYTE)pvSrc, (LPBYTE)pvDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ); + + case CODEC_MODE_ENCODE_NORMAL: + // Normal encode + // We have some output right now, so this becomes a separate case. + // Otherwise, it would be the same as CODEC_MODE_DECODE + { + printf("Using normal encoding...\n"); + + // Allocate temporary buffers + LPBYTE pvDecoded = new BYTE[cBlocks * dwSrcBlockSize]; + if( !pvDecoded ) + return FALSE; + + // Find the decoder + LPFNIMAADPCMCONVERT pfnOppConvert; + pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1]; + + // Encode the stream + if( !m_pfnConvert( (LPBYTE)pvSrc, (LPBYTE)pvDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) ) + { + delete[] pvDecoded; + return FALSE; + } + + // Decode it back + if( !pfnOppConvert( (LPBYTE)pvDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) ) + { + delete[] pvDecoded; + return FALSE; + } + + // Report the normal difference + printf( "Difference between original and decoded streams: 0x%I64x\n", + CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize ) ); + + delete[] pvDecoded; + } + break; + + case CODEC_MODE_ENCODE_OPTIMIZE_WHOLE_FILE: + // Optimize whole file encode + // Encode the file with each possible starting step index + // and pick the best one + { + printf("Using whole file encoding...\n"); + + // Allocate temporary buffers + LPBYTE pvTempDst = new BYTE[cBlocks * dwDstBlockSize]; + if(!pvTempDst) + return FALSE; + + LPBYTE pvDecoded = new BYTE[cBlocks * dwSrcBlockSize]; + if(!pvDecoded) + { + delete[] pvTempDst; + return FALSE; + } + + // Find the decoder + LPFNIMAADPCMCONVERT pfnOppConvert; + pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1]; + + // Keep track of the best encoding, as well as the chosen step index + ULONGLONG ullLeastDiff = (ULONGLONG)-1; + UINT uChosen = (UINT)-1; + + // Encode the entire stream with each step index and choose the best one + for( UINT i = 0; i < ARRAYSIZE(m_asStep); ++i ) + { + ZeroMemory( pvTempDst, cBlocks * dwDstBlockSize ); + ZeroMemory( pvDecoded, cBlocks * dwSrcBlockSize ); + + // Encode + m_nStepIndexL = m_nStepIndexR = i; + if( !m_pfnConvert( (LPBYTE)pvSrc, pvTempDst, cBlocks, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) ) + continue; + + // Decode + if( !pfnOppConvert( pvTempDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) ) + continue; + + // Diff + ULONGLONG ullDiff = CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize ); + if( ullDiff < ullLeastDiff ) + { + ullLeastDiff = ullDiff; + uChosen = i; + CopyMemory( (LPBYTE)pvDst, pvTempDst, cBlocks * dwDstBlockSize ); + } + } + + // Report the optimized difference + printf( "Difference between original and decoded streams: 0x%I64x\n", ullLeastDiff ); + printf( "Step index chosen: %d\n", uChosen ); + + delete[] pvTempDst; + delete[] pvDecoded; + } + break; + + case CODEC_MODE_ENCODE_OPTIMIZE_EACH_BLOCK: + // Optimize per block encode + // Encode each block within the file with each + // possible starting step index and pick the + // best one for each block + { + printf("Using per-block encoding\n\n"); + + // Allocate temporary buffers + LPBYTE pvTempDst = new BYTE[dwDstBlockSize]; + if( !pvTempDst ) + return FALSE; + + LPBYTE pvDecoded = new BYTE[dwSrcBlockSize]; + if( !pvDecoded ) + { + delete[] pvTempDst; + return FALSE; + } + + // Find the decoder + LPFNIMAADPCMCONVERT pfnOppConvert; + pfnOppConvert = apfnDecoders[m_wfxEncode.wfx.nChannels - 1]; + + // We keep track of the best step index of the previous block + // This enables us to search a small range of values close to + // this value (the size of 2*STEPINDEXSEARCHRANGE) + // To begin, the previous block's best step index was -1. + INT iPreviousBestStepIndex = -1; + + for( UINT c = 0; c < cBlocks; ++c ) + { + ULONGLONG ullLeastDiff = (ULONGLONG)-1; + INT iThisBestStepIndex = -1; + INT iStartIndex, iStopIndex; + + // Setup the start/stop indices properly + if( iPreviousBestStepIndex == -1 ) + { + // If the previous best step index is -1, + // then we haven't yet encoded a block. Search + // through the entire range of step indices, + // rather than just in a limited range + iStartIndex = 0; + iStopIndex = ARRAYSIZE( m_asStep ); + } + else + { + // Keep the range of indices to search limited + // to around the previously chosen step index + iStartIndex = iPreviousBestStepIndex - STEPINDEXSEARCHRANGE; + iStopIndex = iPreviousBestStepIndex + STEPINDEXSEARCHRANGE + 1; + } + + // Try each step index in the searchable range and choose the best one + // for this block + for( INT i = iStartIndex; i < iStopIndex; ++i ) + { + // Don't consider anything out of range + if( i < 0 || i >= ARRAYSIZE( m_asStep ) ) + continue; + + // Zero out the temporary buffers + ZeroMemory( pvTempDst, dwDstBlockSize ); + ZeroMemory( pvDecoded, dwSrcBlockSize ); + + // Encode + m_nStepIndexL = m_nStepIndexR = i; + if( !m_pfnConvert( (LPBYTE)pvSrc + c*dwSrcBlockSize, pvTempDst, 1, m_wfxEncode.wfx.nBlockAlign, m_wfxEncode.wSamplesPerBlock, &m_nStepIndexL, &m_nStepIndexR ) ) + continue; + + // Decode + if( !pfnOppConvert( pvTempDst, pvDecoded, 1, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ) ) + continue; + + // Diff + ULONGLONG ullDiff = CalcDifference( (LPBYTE)pvSrc + c*dwSrcBlockSize, pvDecoded, 1, cBlocks, dwSrcBlockSize ); + if( ullDiff < ullLeastDiff ) + { + ullLeastDiff = ullDiff; + iThisBestStepIndex = i; + CopyMemory( (LPBYTE)pvDst + c*dwDstBlockSize, (LPBYTE)pvTempDst, dwDstBlockSize ); + } + } + + // Save the best step index for this block + iPreviousBestStepIndex = iThisBestStepIndex; + } + + delete[] pvTempDst; + delete[] pvDecoded; + + // Report on the optimized difference + pvDecoded = new BYTE[cBlocks * dwSrcBlockSize]; + pfnOppConvert( (LPBYTE)pvDst, pvDecoded, cBlocks, m_wfxEncode.wfx.nBlockAlign, XBOX_ADPCM_SAMPLES_PER_BLOCK, &m_nStepIndexL, &m_nStepIndexR ); + printf( "Difference between original and decoded streams: 0x%I64x\n", CalcDifference( (LPBYTE)pvSrc, pvDecoded, cBlocks, cBlocks, dwSrcBlockSize ) ); + + delete[] pvDecoded; + } + break; + } + + return TRUE; +} + + +/**************************************************************************** + * + * Reset + * + * Description: + * Resets the conversion operation. + * + * Arguments: + * (void) + * + * Returns: + * (void) + * + ****************************************************************************/ + +void +CImaAdpcmCodec::Reset +( + void +) +{ + // + // Reset the stepping indices + // + + m_nStepIndexL = m_nStepIndexR = 0; +} + + +/**************************************************************************** + * + * GetEncodeAlignment + * + * Description: + * Gets the alignment of an encoded buffer. + * + * Arguments: + * (void) + * + * Returns: + * WORD: alignment, in bytes. + * + ****************************************************************************/ + +WORD +CImaAdpcmCodec::GetEncodeAlignment +( + void +) +{ + return m_wfxEncode.wfx.nBlockAlign; +} + + +/**************************************************************************** + * + * GetDecodeAlignment + * + * Description: + * Gets the alignment of a decoded buffer. + * + * Arguments: + * (void) + * + * Returns: + * DWORD: alignment, in bytes. + * + ****************************************************************************/ + +WORD +CImaAdpcmCodec::GetDecodeAlignment +( + void +) +{ + return m_wfxEncode.wSamplesPerBlock * m_wfxEncode.wfx.nChannels * IMAADPCM_PCM_BITS_PER_SAMPLE / 8; +} + + +/**************************************************************************** + * + * CalculateEncodeAlignment + * + * Description: + * Calculates an encoded data block alignment based on a PCM sample + * count and an alignment multiplier. + * + * Arguments: + * WORD [in]: channel count. + * WORD [in]: PCM samples per block. + * + * Returns: + * WORD: alignment, in bytes. + * + ****************************************************************************/ + +WORD +CImaAdpcmCodec::CalculateEncodeAlignment +( + WORD nChannels, + WORD nSamplesPerBlock +) +{ + const WORD nEncodedSampleBits = nChannels * IMAADPCM_BITS_PER_SAMPLE; + const WORD nHeaderBytes = nChannels * IMAADPCM_HEADER_LENGTH; + INT nBlockAlign; + + // + // Calculate the raw block alignment that nSamplesPerBlock dictates. This + // value may include a partial encoded sample, so be sure to round up. + // + // Start with the samples-per-block, minus 1. The first sample is actually + // stored in the header. + // + + nBlockAlign = nSamplesPerBlock - 1; + + // + // Convert to encoded sample size + // + + nBlockAlign *= nEncodedSampleBits; + nBlockAlign += 7; + nBlockAlign /= 8; + + // + // The stereo encoder requires that there be at least two DWORDs to process + // + + nBlockAlign += 7; + nBlockAlign /= 8; + nBlockAlign *= 8; + + // + // Add the header + // + + nBlockAlign += nHeaderBytes; + + // We used an INT temporarily, but the final result should fit into a WORD + assert( nBlockAlign < 0xFFFF ); + return (WORD)nBlockAlign; +} + + +/**************************************************************************** + * + * CreatePcmFormat + * + * Description: + * Creates a PCM format descriptor. + * + * Arguments: + * WORD [in]: channel count. + * DWORD [in]: sampling rate. + * LPWAVEFORMATEX [out]: format descriptor. + * + * Returns: + * (void) + * + ****************************************************************************/ + +void +CImaAdpcmCodec::CreatePcmFormat +( + WORD nChannels, + DWORD nSamplesPerSec, + LPWAVEFORMATEX pwfx +) +{ + pwfx->wFormatTag = WAVE_FORMAT_PCM; + pwfx->nChannels = nChannels; + pwfx->nSamplesPerSec = nSamplesPerSec; + pwfx->nBlockAlign = nChannels * IMAADPCM_PCM_BITS_PER_SAMPLE / 8; + pwfx->nAvgBytesPerSec = pwfx->nBlockAlign * pwfx->nSamplesPerSec; + pwfx->wBitsPerSample = IMAADPCM_PCM_BITS_PER_SAMPLE; +} + + +/**************************************************************************** + * + * CreateImaAdpcmFormat + * + * Description: + * Creates an IMA ADPCM format descriptor. + * + * Arguments: + * WORD [in]: channel count. + * DWORD [in]: sampling rate. + * LPIMAADPCMWAVEFORMAT [out]: format descriptor. + * + * Returns: + * (void) + * + ****************************************************************************/ + +void +CImaAdpcmCodec::CreateImaAdpcmFormat +( + WORD nChannels, + DWORD nSamplesPerSec, + WORD nSamplesPerBlock, + LPIMAADPCMWAVEFORMAT pwfx +) +{ + pwfx->wfx.wFormatTag = WAVE_FORMAT_XBOX_ADPCM; + pwfx->wfx.nChannels = nChannels; + pwfx->wfx.nSamplesPerSec = nSamplesPerSec; + pwfx->wfx.nBlockAlign = CalculateEncodeAlignment(nChannels, nSamplesPerBlock); + pwfx->wfx.nAvgBytesPerSec = nSamplesPerSec * pwfx->wfx.nBlockAlign / nSamplesPerBlock; + pwfx->wfx.wBitsPerSample = IMAADPCM_BITS_PER_SAMPLE; + pwfx->wfx.cbSize = sizeof(*pwfx) - sizeof(pwfx->wfx); + pwfx->wSamplesPerBlock = nSamplesPerBlock; +} + + +/**************************************************************************** + * + * IsValidPcmFormat + * + * Description: + * Validates a format structure. + * + * Arguments: + * LPCWAVEFORMATEX [in]: format. + * + * Returns: + * BOOL: TRUE on success. + * + ****************************************************************************/ + +BOOL +CImaAdpcmCodec::IsValidPcmFormat +( + LPCWAVEFORMATEX pwfx +) +{ + if(WAVE_FORMAT_PCM != pwfx->wFormatTag) + { + return FALSE; + } + + if((pwfx->nChannels < 1) || (pwfx->nChannels > IMAADPCM_MAX_CHANNELS)) + { + return FALSE; + } + + if(IMAADPCM_PCM_BITS_PER_SAMPLE != pwfx->wBitsPerSample) + { + return FALSE; + } + + if(pwfx->nChannels * pwfx->wBitsPerSample / 8 != pwfx->nBlockAlign) + { + return FALSE; + } + + if(pwfx->nBlockAlign * pwfx->nSamplesPerSec != pwfx->nAvgBytesPerSec) + { + return FALSE; + } + + return TRUE; +} + + +/**************************************************************************** + * + * IsValidXboxAdpcmFormat + * + * Description: + * Validates a format structure. + * + * Arguments: + * LPCIMAADPCMWAVEFORMAT [in]: format. + * + * Returns: + * BOOL: TRUE on success. + * + ****************************************************************************/ + +BOOL +CImaAdpcmCodec::IsValidImaAdpcmFormat +( + LPCIMAADPCMWAVEFORMAT pwfx +) +{ + if(WAVE_FORMAT_XBOX_ADPCM != pwfx->wfx.wFormatTag) + { + return FALSE; + } + + if(sizeof(*pwfx) - sizeof(pwfx->wfx) != pwfx->wfx.cbSize) + { + return FALSE; + } + + if((pwfx->wfx.nChannels < 1) || (pwfx->wfx.nChannels > IMAADPCM_MAX_CHANNELS)) + { + return FALSE; + } + + if(IMAADPCM_BITS_PER_SAMPLE != pwfx->wfx.wBitsPerSample) + { + return FALSE; + } + + if(CalculateEncodeAlignment(pwfx->wfx.nChannels, pwfx->wSamplesPerBlock) != pwfx->wfx.nBlockAlign) + { + return FALSE; + } + + return TRUE; +} + + +/**************************************************************************** + * + * CalcDifference + * + * Description: + * Calculates the error between two audio buffers. The error is clamped + * at (ULONGLONG)-1. Also, the error of a block acts as a percentage of + * the maximum possible contribution of a block. + * + * Arguments: + * LPBYTE [in]: First buffer + * LPBYTE [in]: Second buffer + * UINT [in]: Number of blocks-worth to compare + * UINT [in]: Total number of blocks being converted + * DWORD [in]: Size of a single block in bytes + * + * Returns: + * ULONGLONG: Difference of the two buffers + * + ****************************************************************************/ +ULONGLONG CImaAdpcmCodec::CalcDifference(LPBYTE pvBuffer1, LPBYTE pvBuffer2, UINT cBlocks, UINT cTotalBlocks, DWORD dwBlockSize) +{ + ULONGLONG ullDiff = 0; + + // Each block worth of error can contribute a maximum of this value + const ULONGLONG ullMaxBlockContribution = ( (ULONGLONG)-1 / cTotalBlocks ); + + // The maximum error in a block is + // (2^16)^2 * m_wfxEncode.wSamplesPerBlock + // = ( 1 << 32 ) * m_wfxEncode.wSamplesPerBlock + const ULONGLONG ullMaxBlockDiff = ( (ULONGLONG)1 << 32 ) * m_wfxEncode.wSamplesPerBlock; + + // Now we go through the buffers sample by sample and find the difference + // on a block-by-block basis. The factored difference of a block is + // ullBlockDiff / ullMaxBlockDiff * ullMaxBlockContribution + for( UINT i = 0; i < cBlocks; ++i ) + { + PSHORT pSamples1 = (PSHORT)(pvBuffer1 + i * dwBlockSize); + PSHORT pSamples2 = (PSHORT)(pvBuffer2 + i * dwBlockSize); + ULONGLONG ullBlockDiff = 0; + + // Find the block difference + for( UINT j = 0; j < m_wfxEncode.wSamplesPerBlock; ++j ) + { + ULONGLONG ullSampleDiff = (ULONGLONG)(pSamples2[j]) - (ULONGLONG)(pSamples1[j]); + ullBlockDiff += ( ullSampleDiff * ullSampleDiff ); + } + + // Assert that we didn't go over the maximum possible + assert( ullBlockDiff <= ullMaxBlockDiff ); + + // Add the contribution of this block to the error + ullDiff += (ULONGLONG)( ( (DOUBLE)ullBlockDiff / (DOUBLE)ullMaxBlockDiff ) * ullMaxBlockContribution ); + } + + assert( ullDiff <= cBlocks * ullMaxBlockContribution ); + + return ullDiff; +} + + +/**************************************************************************** + * + * EncodeSample + * + * Description: + * Encodes a sample. + * + * Arguments: + * int [in]: the sample to be encoded. + * LPINT [in/out]: the predicted value of the sample. + * int [in]: the quantization step size used to encode the sample. + * + * Returns: + * int: the encoded ADPCM sample. + * + ****************************************************************************/ + +int +CImaAdpcmCodec::EncodeSample +( + int nInputSample, + LPINT pnPredictedSample, + int nStepSize +) +{ + int nPredictedSample; + LONG lDifference; + int nEncodedSample; + + nPredictedSample = *pnPredictedSample; + + lDifference = nInputSample - nPredictedSample; + nEncodedSample = 0; + + if(lDifference < 0) + { + nEncodedSample = 8; + lDifference = -lDifference; + } + + if(lDifference >= nStepSize) + { + nEncodedSample |= 4; + lDifference -= nStepSize; + } + + nStepSize >>= 1; + + if(lDifference >= nStepSize) + { + nEncodedSample |= 2; + lDifference -= nStepSize; + } + + nStepSize >>= 1; + + if(lDifference >= nStepSize) + { + nEncodedSample |= 1; + lDifference -= nStepSize; + } + + if(nEncodedSample & 8) + { + nPredictedSample = nInputSample + lDifference - (nStepSize >> 1); + } + else + { + nPredictedSample = nInputSample - lDifference + (nStepSize >> 1); + } + + if(nPredictedSample > 32767) + { + nPredictedSample = 32767; + } + else if(nPredictedSample < -32768) + { + nPredictedSample = -32768; + } + + *pnPredictedSample = nPredictedSample; + + return nEncodedSample; +} + + +/**************************************************************************** + * + * DecodeSample + * + * Description: + * Decodes an encoded sample. + * + * Arguments: + * int [in]: the sample to be decoded. + * int [in]: the predicted value of the sample. + * int [i]: the quantization step size used to encode the sample. + * + * Returns: + * int: the decoded PCM sample. + * + ****************************************************************************/ + +int +CImaAdpcmCodec::DecodeSample +( + int nEncodedSample, + int nPredictedSample, + int nStepSize +) +{ + LONG lDifference; + LONG lNewSample; + + lDifference = nStepSize >> 3; + + if(nEncodedSample & 4) + { + lDifference += nStepSize; + } + + if(nEncodedSample & 2) + { + lDifference += nStepSize >> 1; + } + + if(nEncodedSample & 1) + { + lDifference += nStepSize >> 2; + } + + if(nEncodedSample & 8) + { + lDifference = -lDifference; + } + + lNewSample = nPredictedSample + lDifference; + + if((LONG)(short)lNewSample != lNewSample) + { + if(lNewSample < -32768) + { + lNewSample = -32768; + } + else + { + lNewSample = 32767; + } + } + + return (int)lNewSample; +} + + +/**************************************************************************** + * + * Conversion Routines + * + * Description: + * Converts a PCM buffer to ADPCM, or the reverse. + * + * Arguments: + * LPBYTE [in]: source buffer. + * LPBYTE [out]: destination buffer. + * UINT [in]: block count. + * UINT [in]: block alignment of the ADPCM data, in bytes. + * UINT [in]: the number of samples in each ADPCM block (not used in + * decoding). + * LPINT [in/out]: left-channel stepping index. + * LPINT [in/out]: right-channel stepping index. + * + * Returns: + * BOOL: TRUE on success. + * + ****************************************************************************/ + +BOOL +CImaAdpcmCodec::EncodeM16 +( + LPBYTE pbSrc, + LPBYTE pbDst, + UINT cBlocks, + UINT nBlockAlignment, + UINT cSamplesPerBlock, + LPINT pnStepIndexL, + LPINT +) +{ + LPBYTE pbBlock; + UINT cSamples; + int nSample; + int nStepSize; + int nEncSample1; + int nEncSample2; + int nPredSample; + int nStepIndex; + + // + // Save a local copy of the step index so we're not constantly + // dereferencing a pointer. + // + + nStepIndex = *pnStepIndexL; + + // + // Enter the main loop + // + + while(cBlocks--) + { + pbBlock = pbDst; + cSamples = cSamplesPerBlock - 1; + + // + // Block header + // + + nPredSample = *(short *)pbSrc; + pbSrc += sizeof(short); + + *(LONG *)pbBlock = MAKELONG(nPredSample, nStepIndex); + pbBlock += sizeof(LONG); + + // + // We have written the header for this block--now write the data + // chunk (which consists of a bunch of encoded nibbles). Note + // that if we don't have enough data to fill a complete byte, then + // we add a 0 nibble on the end. + // + + while(cSamples) + { + // + // Sample 1 + // + + nSample = *(short *)pbSrc; + pbSrc += sizeof(short); + cSamples--; + + nStepSize = m_asStep[nStepIndex]; + nEncSample1 = EncodeSample(nSample, &nPredSample, nStepSize); + nStepIndex = NextStepIndex(nEncSample1, nStepIndex); + + // + // Sample 2 + // + + if(cSamples) + { + nSample = *(short *)pbSrc; + pbSrc += sizeof(short); + cSamples--; + + nStepSize = m_asStep[nStepIndex]; + nEncSample2 = EncodeSample(nSample, &nPredSample, nStepSize); + nStepIndex = NextStepIndex(nEncSample2, nStepIndex); + } + else + { + nEncSample2 = 0; + } + + // + // Write out encoded byte. + // + + *pbBlock++ = (BYTE)(nEncSample1 | (nEncSample2 << 4)); + } + + // + // Skip padding + // + + pbDst += nBlockAlignment; + } + + // + // Restore the value of the step index to be used on the next buffer. + // + + *pnStepIndexL = nStepIndex; + + return TRUE; +} + + +BOOL +CImaAdpcmCodec::EncodeS16 +( + LPBYTE pbSrc, + LPBYTE pbDst, + UINT cBlocks, + UINT nBlockAlignment, + UINT cSamplesPerBlock, + LPINT pnStepIndexL, + LPINT pnStepIndexR +) +{ + LPBYTE pbBlock; + UINT cSamples; + UINT cSubSamples; + int nSample; + int nStepSize; + DWORD dwLeft; + DWORD dwRight; + int nEncSampleL; + int nPredSampleL; + int nStepIndexL; + int nEncSampleR; + int nPredSampleR; + int nStepIndexR; + UINT i; + + // + // Save a local copy of the step indices so we're not constantly + // dereferencing a pointer. + // + + nStepIndexL = *pnStepIndexL; + nStepIndexR = *pnStepIndexR; + + // + // Enter the main loop + // + + while(cBlocks--) + { + pbBlock = pbDst; + cSamples = cSamplesPerBlock - 1; + + // + // LEFT channel block header + // + + nPredSampleL = *(short *)pbSrc; + pbSrc += sizeof(short); + + *(LONG *)pbBlock = MAKELONG(nPredSampleL, nStepIndexL); + pbBlock += sizeof(LONG); + + // + // RIGHT channel block header + // + + nPredSampleR = *(short *)pbSrc; + pbSrc += sizeof(short); + + *(LONG *)pbBlock = MAKELONG(nPredSampleR, nStepIndexR); + pbBlock += sizeof(LONG); + + // + // We have written the header for this block--now write the data + // chunk. This consists of 8 left samples (one DWORD of output) + // followed by 8 right samples (also one DWORD). Since the input + // samples are interleaved, we create the left and right DWORDs + // sample by sample, and then write them both out. + // + + while(cSamples) + { + dwLeft = 0; + dwRight = 0; + + cSubSamples = min(cSamples, 8); + + for(i = 0; i < cSubSamples; i++) + { + // + // LEFT channel + // + + nSample = *(short *)pbSrc; + pbSrc += sizeof(short); + + nStepSize = m_asStep[nStepIndexL]; + + nEncSampleL = EncodeSample(nSample, &nPredSampleL, nStepSize); + + nStepIndexL = NextStepIndex(nEncSampleL, nStepIndexL); + dwLeft |= (DWORD)nEncSampleL << (4 * i); + + // + // RIGHT channel + // + + nSample = *(short *)pbSrc; + pbSrc += sizeof(short); + + nStepSize = m_asStep[nStepIndexR]; + + nEncSampleR = EncodeSample(nSample, &nPredSampleR, nStepSize); + + nStepIndexR = NextStepIndex(nEncSampleR, nStepIndexR); + dwRight |= (DWORD)nEncSampleR << (4 * i); + } + + // + // Write out encoded DWORDs. + // + + *(LPDWORD)pbBlock = dwLeft; + pbBlock += sizeof(DWORD); + + *(LPDWORD)pbBlock = dwRight; + pbBlock += sizeof(DWORD); + + cSamples -= cSubSamples; + } + + // + // Skip padding + // + + pbDst += nBlockAlignment; + } + + // + // Restore the value of the step index to be used on the next buffer. + // + + *pnStepIndexL = nStepIndexL; + *pnStepIndexR = nStepIndexR; + + return TRUE; + +} + + +BOOL +CImaAdpcmCodec::DecodeM16 +( + LPBYTE pbSrc, + LPBYTE pbDst, + UINT cBlocks, + UINT nBlockAlignment, + UINT cSamplesPerBlock, + LPINT, + LPINT +) +{ + BOOL fSuccess = TRUE; + LPBYTE pbBlock; + UINT cSamples; + BYTE bSample; + int nStepSize; + int nEncSample; + int nPredSample; + int nStepIndex; + DWORD dwHeader; + + // + // Enter the main loop + // + + while(cBlocks--) + { + pbBlock = pbSrc; + cSamples = cSamplesPerBlock - 1; + + // + // Block header + // + + dwHeader = *(LPDWORD)pbBlock; + pbBlock += sizeof(DWORD); + + nPredSample = (int)(short)LOWORD(dwHeader); + nStepIndex = (int)(BYTE)HIWORD(dwHeader); + + if(!ValidStepIndex(nStepIndex)) + { + // + // The step index is out of range - this is considered a fatal + // error as the input stream is corrupted. We fail by returning + // zero bytes converted. + // + + fSuccess = FALSE; + break; + } + + // + // Write out first sample + // + + *(short *)pbDst = (short)nPredSample; + pbDst += sizeof(short); + + // + // Enter the block loop + // + + while(cSamples) + { + bSample = *pbBlock++; + + // + // Sample 1 + // + + nEncSample = (bSample & (BYTE)0x0F); + nStepSize = m_asStep[nStepIndex]; + nPredSample = DecodeSample(nEncSample, nPredSample, nStepSize); + nStepIndex = NextStepIndex(nEncSample, nStepIndex); + + *(short *)pbDst = (short)nPredSample; + pbDst += sizeof(short); + + cSamples--; + + // + // Sample 2 + // + + if(cSamples) + { + nEncSample = (bSample >> 4); + nStepSize = m_asStep[nStepIndex]; + nPredSample = DecodeSample(nEncSample, nPredSample, nStepSize); + nStepIndex = NextStepIndex(nEncSample, nStepIndex); + + *(short *)pbDst = (short)nPredSample; + pbDst += sizeof(short); + + cSamples--; + } + } + + // + // Skip padding + // + + pbSrc += nBlockAlignment; + } + + return fSuccess; +} + + +BOOL +CImaAdpcmCodec::DecodeS16 +( + LPBYTE pbSrc, + LPBYTE pbDst, + UINT cBlocks, + UINT nBlockAlignment, + UINT cSamplesPerBlock, + LPINT, + LPINT +) +{ + BOOL fSuccess = TRUE; + LPBYTE pbBlock; + UINT cSamples; + UINT cSubSamples; + int nStepSize; + DWORD dwHeader; + DWORD dwLeft; + DWORD dwRight; + int nEncSampleL; + int nPredSampleL; + int nStepIndexL; + int nEncSampleR; + int nPredSampleR; + int nStepIndexR; + UINT i; + + // + // Enter the main loop + // + + while(cBlocks--) + { + pbBlock = pbSrc; + cSamples = cSamplesPerBlock - 1; + + // + // LEFT channel header + // + + dwHeader = *(LPDWORD)pbBlock; + pbBlock += sizeof(DWORD); + + nPredSampleL = (int)(short)LOWORD(dwHeader); + nStepIndexL = (int)(BYTE)HIWORD(dwHeader); + + if(!ValidStepIndex(nStepIndexL)) + { + // + // The step index is out of range - this is considered a fatal + // error as the input stream is corrupted. We fail by returning + // zero bytes converted. + // + + fSuccess = FALSE; + break; + } + + // + // RIGHT channel header + // + + dwHeader = *(LPDWORD)pbBlock; + pbBlock += sizeof(DWORD); + + nPredSampleR = (int)(short)LOWORD(dwHeader); + nStepIndexR = (int)(BYTE)HIWORD(dwHeader); + + if(!ValidStepIndex(nStepIndexR)) + { + // + // The step index is out of range - this is considered a fatal + // error as the input stream is corrupted. We fail by returning + // zero bytes converted. + // + + fSuccess = FALSE; + break; + } + + // + // Write out first sample + // + + *(LPDWORD)pbDst = MAKELONG(nPredSampleL, nPredSampleR); + pbDst += sizeof(DWORD); + + // + // The first DWORD contains 4 left samples, the second DWORD + // contains 4 right samples. We process the source in 8-byte + // chunks to make it easy to interleave the output correctly. + // + + while(cSamples) + { + dwLeft = *(LPDWORD)pbBlock; + pbBlock += sizeof(DWORD); + dwRight = *(LPDWORD)pbBlock; + pbBlock += sizeof(DWORD); + + cSubSamples = min(cSamples, 8); + + for(i = 0; i < cSubSamples; i++) + { + // + // LEFT channel + // + + nEncSampleL = (dwLeft & 0x0F); + nStepSize = m_asStep[nStepIndexL]; + nPredSampleL = DecodeSample(nEncSampleL, nPredSampleL, nStepSize); + nStepIndexL = NextStepIndex(nEncSampleL, nStepIndexL); + + // + // RIGHT channel + // + + nEncSampleR = (dwRight & 0x0F); + nStepSize = m_asStep[nStepIndexR]; + nPredSampleR = DecodeSample(nEncSampleR, nPredSampleR, nStepSize); + nStepIndexR = NextStepIndex(nEncSampleR, nStepIndexR); + + // + // Write out sample + // + + *(LPDWORD)pbDst = MAKELONG(nPredSampleL, nPredSampleR); + pbDst += sizeof(DWORD); + + // + // Shift the next input sample into the low-order 4 bits. + // + + dwLeft >>= 4; + dwRight >>= 4; + } + + cSamples -= cSubSamples; + } + + // + // Skip padding + // + + pbSrc += nBlockAlignment; + } + + return fSuccess; +} + + +int XboxADPCMSize( int sampleCount, int channelCount, int sampleRate ) +{ + CImaAdpcmCodec codec; + IMAADPCMWAVEFORMAT wfxEncode; + + // Create an APDCM format structure based off the source format + codec.CreateImaAdpcmFormat( (WORD)channelCount, sampleRate, XBOX_ADPCM_SAMPLES_PER_BLOCK, &wfxEncode ); + + // Calculate number of ADPCM blocks and length of ADPCM data + DWORD dwDestBlocks = sampleCount / XBOX_ADPCM_SAMPLES_PER_BLOCK; + DWORD dwDestLength = dwDestBlocks * wfxEncode.wfx.nBlockAlign; + + return dwDestLength; +} + +void Convert16ToXboxADPCM( const short *pInputBuffer, byte *pOutputBuffer, byte *pOutFormat, int sampleCount, int channelCount, int sampleRate ) +{ + CImaAdpcmCodec codec; + IMAADPCMWAVEFORMAT wfxEncode; + + // Create an APDCM format structure based off the source format + codec.CreateImaAdpcmFormat( (WORD)channelCount, sampleRate, XBOX_ADPCM_SAMPLES_PER_BLOCK, &wfxEncode ); + if ( pOutFormat ) + { + memcpy( pOutFormat, &wfxEncode, sizeof(wfxEncode) ); + } + + // Initialize the codec + if ( FALSE == codec.Initialize( &wfxEncode, CODEC_MODE_ENCODE_OPTIMIZE_EACH_BLOCK ) ) + { + printf( "Couldn't initialize codec.\n" ); + return; + } + + // Convert the data + DWORD dwDestBlocks = sampleCount / XBOX_ADPCM_SAMPLES_PER_BLOCK; + if ( FALSE == codec.Convert( (const byte *)pInputBuffer, pOutputBuffer, dwDestBlocks ) ) + return; +} diff --git a/utils/xbox/MakeGameData/imaadpcm.h b/utils/xbox/MakeGameData/imaadpcm.h new file mode 100644 index 0000000..a9fdd08 --- /dev/null +++ b/utils/xbox/MakeGameData/imaadpcm.h @@ -0,0 +1,153 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +/*************************************************************************** + * + * Copyright (C) 2001 Microsoft Corporation. All Rights Reserved. + * + * File: imaadpcm.h + * Content: IMA ADPCM CODEC. + * History: + * Date By Reason + * ==== == ====== + * 04/29/01 dereks Created. + * + ****************************************************************************/ + +#ifndef __IMAADPCM_H__ +#define __IMAADPCM_H__ + +#include <windows.h> +#include <windowsx.h> +#include <mmsystem.h> +#include <ctype.h> +#include <mmreg.h> +#include <msacm.h> + +#define XBOX_ADPCM_SAMPLES_PER_BLOCK 64 + +#define WAVE_FORMAT_XBOX_ADPCM 0x0069 + +#define IMAADPCM_BITS_PER_SAMPLE 4 +#define IMAADPCM_HEADER_LENGTH 4 + +#define IMAADPCM_MAX_CHANNELS 2 +#define IMAADPCM_PCM_BITS_PER_SAMPLE 16 + +#define NUMELMS(a) (sizeof(a) / sizeof(a[0])) + +typedef const WAVEFORMATEX *LPCWAVEFORMATEX; +typedef const IMAADPCMWAVEFORMAT *LPCIMAADPCMWAVEFORMAT; + +#ifdef __cplusplus + +// +// IMA ADPCM encoder function prototype +// + +typedef BOOL (*LPFNIMAADPCMCONVERT)(LPBYTE pbSrc, LPBYTE pbDst, UINT cBlocks, UINT nBlockAlignment, UINT cSamplesPerBlock, LPINT pnStepIndexL, LPINT pnStepIndexR); + +// +// Codec mode +// + +enum CODEC_MODE +{ + CODEC_MODE_DECODE, + CODEC_MODE_ENCODE_NORMAL, + CODEC_MODE_ENCODE_OPTIMIZE_WHOLE_FILE, + CODEC_MODE_ENCODE_OPTIMIZE_EACH_BLOCK, +}; + +// +// IMA ADPCM CODEC +// + +class CImaAdpcmCodec +{ +private: + static const short m_asNextStep[16]; // Step increment array + static const short m_asStep[89]; // Step value array + IMAADPCMWAVEFORMAT m_wfxEncode; // Encoded format description + CODEC_MODE m_cmCodecMode; // Codec mode + int m_nStepIndexL; // Left-channel stepping index + int m_nStepIndexR; // Right-channel stepping index + LPFNIMAADPCMCONVERT m_pfnConvert; // Conversion function + +public: + CImaAdpcmCodec(void); + ~CImaAdpcmCodec(void); + +public: + // Initialization + BOOL Initialize(LPCIMAADPCMWAVEFORMAT pwfxEncode, CODEC_MODE cmCodecMode); + + // Size conversions + WORD GetEncodeAlignment(void); + WORD GetDecodeAlignment(void); + WORD GetSourceAlignment(void); + WORD GetDestinationAlignment(void); + + // Data conversions + BOOL Convert(LPCVOID pvSrc, LPVOID pvDst, UINT cBlocks); + void Reset(void); + + // Format descriptions + static void CreatePcmFormat(WORD nChannels, DWORD nSamplesPerSec, LPWAVEFORMATEX pwfxFormat); + static void CreateImaAdpcmFormat(WORD nChannels, DWORD nSamplesPerSec, WORD nSamplesPerBlock, LPIMAADPCMWAVEFORMAT pwfxFormat); + + static BOOL IsValidPcmFormat(LPCWAVEFORMATEX pwfxFormat); + static BOOL IsValidImaAdpcmFormat(LPCIMAADPCMWAVEFORMAT pwfxFormat); + +private: + // En/decoded data alignment + static WORD CalculateEncodeAlignment(WORD nSamplesPerBlock, WORD nChannels); + + // Data conversion functions + static BOOL EncodeM16(LPBYTE pbSrc, LPBYTE pbDst, UINT cBlocks, UINT nBlockAlignment, UINT cSamplesPerBlock, LPINT pnStepIndexL, LPINT pnStepIndexR); + static BOOL EncodeS16(LPBYTE pbSrc, LPBYTE pbDst, UINT cBlocks, UINT nBlockAlignment, UINT cSamplesPerBlock, LPINT pnStepIndexL, LPINT pnStepIndexR); + static BOOL DecodeM16(LPBYTE pbSrc, LPBYTE pbDst, UINT cBlocks, UINT nBlockAlignment, UINT cSamplesPerBlock, LPINT pnStepIndexL, LPINT pnStepIndexR); + static BOOL DecodeS16(LPBYTE pbSrc, LPBYTE pbDst, UINT cBlocks, UINT nBlockAlignment, UINT cSamplesPerBlock, LPINT pnStepIndexL, LPINT pnStepIndexR); + + static int EncodeSample(int nInputSample, int *nPredictedSample, int nStepSize); + static int DecodeSample(int nInputSample, int nPredictedSample, int nStepSize); + + static int NextStepIndex(int nEncodedSample, int nStepIndex); + static BOOL ValidStepIndex(int nStepIndex); + + /*static ULONGLONG CalcDifference(LPDWORD pvBuffer1, LPDWORD pvBuffer2, DWORD dwLength);*/ + ULONGLONG CalcDifference(LPBYTE pvBuffer1, LPBYTE pvBuffer2, UINT cBlocks, UINT cTotalBlocks, DWORD dwBlockSize); +}; + +__inline WORD CImaAdpcmCodec::GetSourceAlignment(void) +{ + return ( m_cmCodecMode == CODEC_MODE_DECODE ) ? GetEncodeAlignment() : GetDecodeAlignment(); +} + +__inline WORD CImaAdpcmCodec::GetDestinationAlignment(void) +{ + return ( m_cmCodecMode == CODEC_MODE_DECODE ) ? GetDecodeAlignment() : GetEncodeAlignment(); +} + +__inline int CImaAdpcmCodec::NextStepIndex(int nEncodedSample, int nStepIndex) +{ + nStepIndex += m_asNextStep[nEncodedSample]; + + if(nStepIndex < 0) + { + nStepIndex = 0; + } + else if(nStepIndex >= NUMELMS(m_asStep)) + { + nStepIndex = NUMELMS(m_asStep) - 1; + } + + return nStepIndex; +} + +__inline BOOL CImaAdpcmCodec::ValidStepIndex(int nStepIndex) +{ + return (nStepIndex >= 0) && (nStepIndex < NUMELMS(m_asStep)); +} + +#endif // __cplusplus + +#endif // __IMAADPCM_H__ diff --git a/utils/xbox/MakeGameData/resample.cpp b/utils/xbox/MakeGameData/resample.cpp new file mode 100644 index 0000000..a0ce0a7 --- /dev/null +++ b/utils/xbox/MakeGameData/resample.cpp @@ -0,0 +1,381 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// + +#include <windows.h> +#include <mmreg.h> +#include "../toollib/toollib.h" +#include "tier1/strtools.h" +#include "resample.h" + +#define clamp(a,b,c) ( (a) > (c) ? (c) : ( (a) < (b) ? (b) : (a) ) ) + +const int NUM_COEFFS = 7; +static const float g_ResampleCoefficients[NUM_COEFFS] = +{ + 0.0457281f, 0.168088f, 0.332501f, 0.504486f, 0.663202f, 0.803781f, 0.933856f +}; + + +// generates 1 output sample for 2 input samples +inline float DecimateSamplePair(float input0, float input1, const float pCoefficients[7], float xState[2], float yState[7] ) +{ + float tmp_0 = xState[0]; + float tmp_1 = xState[1]; + + xState[0] = input0; + xState[1] = input1; + + input0 = (input0 - yState[0]) * pCoefficients[0] + tmp_0; + input1 = (input1 - yState[1]) * pCoefficients[1] + tmp_1; + tmp_0 = yState[0]; + tmp_1 = yState[1]; + yState[0] = input0; + yState[1] = input1; + + input0 = (input0 - yState[2]) * pCoefficients[2] + tmp_0; + input1 = (input1 - yState[3]) * pCoefficients[3] + tmp_1; + tmp_0 = yState[2]; + tmp_1 = yState[3]; + yState[2] = input0; + yState[3] = input1; + + input0 = (input0 - yState[4]) * pCoefficients[4] + tmp_0; + input1 = (input1 - yState[5]) * pCoefficients[5] + tmp_1; + tmp_0 = yState[4]; + yState[4] = input0; + yState[5] = input1; + + input0 = (input0 - yState[6]) * pCoefficients[6] + tmp_0; + yState[6] = input0; + + return (input0 + input1); +} + +static void ExtractFloatSamples( float *pOut, const short *pInputBuffer, int sampleCount, int stride ) +{ + for ( int i = 0; i < sampleCount; i++ ) + { + pOut[i] = pInputBuffer[0] * 1.0f / 32768.0f; + pInputBuffer += stride; + } +} + +static void ExtractShortSamples( short *pOut, const float *pInputBuffer, float scale, int sampleCount, int stride ) +{ + for ( int i = 0; i < sampleCount; i++ ) + { + int sampleOut = (int)(pInputBuffer[i] * scale); + sampleOut = clamp( sampleOut, -32768, 32767 ); + + pOut[0] = (short)(sampleOut); + pOut += stride; + } +} + +struct decimatestate_t +{ + float xState[2]; + float yState[7]; +}; +void DecimateSampleBlock( float *pInOut, int sampleCount ) +{ + decimatestate_t block; + int outCount = sampleCount >> 1; + int pos = 0; + memset( &block, 0, sizeof(block) ); + do + { + float input0 = pInOut[pos*2+0]; + float input1 = pInOut[pos*2+1]; + pInOut[pos] = DecimateSamplePair( input0, input1, g_ResampleCoefficients, block.xState, block.yState ); + pos++; + } while( pos < outCount ); +} + +void DecimateSampleRateBy2_16( const short *pInputBuffer, short *pOutputBuffer, int sampleCount, int channelCount ) +{ + float *pTmpBuf = new float[sampleCount]; + for ( int i = 0; i < channelCount; i++ ) + { + ExtractFloatSamples( pTmpBuf, pInputBuffer+i, sampleCount, channelCount ); + DecimateSampleBlock( pTmpBuf, sampleCount ); + ExtractShortSamples( pOutputBuffer+i, pTmpBuf, 0.5f * 32768.0f, sampleCount>>1, channelCount ); + } + delete [] pTmpBuf; +} + + +struct adpcmstate_t +{ + const ADPCMWAVEFORMAT *pFormat; + const ADPCMCOEFSET *pCoefficients; + int blockSize; +}; + +static int error_sign_lut[] = { 0, 1, 2, 3, 4, 5, 6, 7, -8, -7, -6, -5, -4, -3, -2, -1 }; +static int error_coefficients_lut[] = { 230, 230, 230, 230, 307, 409, 512, 614, 768, 614, 512, 409, 307, 230, 230, 230 }; + +void ParseADPCM( adpcmstate_t &out, const byte *pFormatChunk ) +{ + out.pFormat = (const ADPCMWAVEFORMAT *)pFormatChunk; + if ( out.pFormat ) + { + out.pCoefficients = out.pFormat->aCoef; + + // number of bytes for samples + out.blockSize = ((out.pFormat->wSamplesPerBlock - 2) * out.pFormat->wfx.nChannels ) / 2; + // size of channel header + out.blockSize += 7 * out.pFormat->wfx.nChannels; + } +} + +void DecompressBlockMono( const adpcmstate_t &state, short *pOut, const char *pIn, int count ) +{ + int pred = *pIn++; + int co1 = state.pCoefficients[pred].iCoef1; + int co2 = state.pCoefficients[pred].iCoef2; + + // read initial delta + int delta = *((short *)pIn); + pIn += 2; + + // read initial samples for prediction + int samp1 = *((short *)pIn); + pIn += 2; + + int samp2 = *((short *)pIn); + pIn += 2; + + // write out the initial samples (stored in reverse order) + *pOut++ = (short)samp2; + *pOut++ = (short)samp1; + + // subtract the 2 samples in the header + count -= 2; + + // this is a toggle to read nibbles, first nibble is high + int high = 1; + + int error, sample=0; + + // now process the block + while ( count ) + { + // read the error nibble from the input stream + if ( high ) + { + sample = (unsigned char) (*pIn++); + // high nibble + error = sample >> 4; + // cache low nibble for next read + sample = sample & 0xf; + // Next read is from cache, not stream + high = 0; + } + else + { + // stored in previous read (low nibble) + error = sample; + // next read is from stream + high = 1; + } + // convert to signed with LUT + int errorSign = error_sign_lut[error]; + + // interpolate the new sample + int predSample = (samp1 * co1) + (samp2 * co2); + // coefficients are fixed point 8-bit, so shift back to 16-bit integer + predSample >>= 8; + + // Add in current error estimate + predSample += (errorSign * delta); + + // Correct error estimate + delta = (delta * error_coefficients_lut[error]) >> 8; + // Clamp error estimate + if ( delta < 16 ) + delta = 16; + + // clamp + if ( predSample > 32767L ) + predSample = 32767L; + else if ( predSample < -32768L ) + predSample = -32768L; + + // output + *pOut++ = (short)predSample; + // move samples over + samp2 = samp1; + samp1 = predSample; + + count--; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Decode a single block of stereo ADPCM audio +// Input : *pOut - 16-bit output buffer +// *pIn - ADPCM encoded block data +// count - number of sample pairs to decode +//----------------------------------------------------------------------------- +void DecompressBlockStereo( const adpcmstate_t &state, short *pOut, const char *pIn, int count ) +{ + int pred[2], co1[2], co2[2]; + int i; + + for ( i = 0; i < 2; i++ ) + { + pred[i] = *pIn++; + co1[i] = state.pCoefficients[pred[i]].iCoef1; + co2[i] = state.pCoefficients[pred[i]].iCoef2; + } + + int delta[2], samp1[2], samp2[2]; + + for ( i = 0; i < 2; i++, pIn += 2 ) + { + // read initial delta + delta[i] = *((short *)pIn); + } + + // read initial samples for prediction + for ( i = 0; i < 2; i++, pIn += 2 ) + { + samp1[i] = *((short *)pIn); + } + for ( i = 0; i < 2; i++, pIn += 2 ) + { + samp2[i] = *((short *)pIn); + } + + // write out the initial samples (stored in reverse order) + *pOut++ = (short)samp2[0]; // left + *pOut++ = (short)samp2[1]; // right + *pOut++ = (short)samp1[0]; // left + *pOut++ = (short)samp1[1]; // right + + // subtract the 2 samples in the header + count -= 2; + + // this is a toggle to read nibbles, first nibble is high + int high = 1; + + int error, sample=0; + + // now process the block + while ( count ) + { + for ( i = 0; i < 2; i++ ) + { + // read the error nibble from the input stream + if ( high ) + { + sample = (unsigned char) (*pIn++); + // high nibble + error = sample >> 4; + // cache low nibble for next read + sample = sample & 0xf; + // Next read is from cache, not stream + high = 0; + } + else + { + // stored in previous read (low nibble) + error = sample; + // next read is from stream + high = 1; + } + // convert to signed with LUT + int errorSign = error_sign_lut[error]; + + // interpolate the new sample + int predSample = (samp1[i] * co1[i]) + (samp2[i] * co2[i]); + // coefficients are fixed point 8-bit, so shift back to 16-bit integer + predSample >>= 8; + + // Add in current error estimate + predSample += (errorSign * delta[i]); + + // Correct error estimate + delta[i] = (delta[i] * error_coefficients_lut[error]) >> 8; + // Clamp error estimate + if ( delta[i] < 16 ) + delta[i] = 16; + + // clamp + if ( predSample > 32767L ) + predSample = 32767L; + else if ( predSample < -32768L ) + predSample = -32768L; + + // output + *pOut++ = (short)predSample; + // move samples over + samp2[i] = samp1[i]; + samp1[i] = predSample; + } + count--; + } +} + +int ADPCMSampleCountShortBlock( const adpcmstate_t &state, int shortBlockSize ) +{ + if ( shortBlockSize < 8 ) + return 0; + + int sampleCount = state.pFormat->wSamplesPerBlock; + + // short block?, fixup sample count (2 samples per byte, divided by number of channels per sample set) + sampleCount -= ((state.blockSize - shortBlockSize) * 2) / state.pFormat->wfx.nChannels; + return sampleCount; +} + +int ADPCMSampleCount( const byte *pFormatChunk, const byte *pDataChunk, int dataSize ) +{ + adpcmstate_t state; + ParseADPCM( state, pFormatChunk ); + int numBlocks = dataSize / state.blockSize; + int mod = dataSize % state.blockSize; + return numBlocks * state.pFormat->wSamplesPerBlock + ADPCMSampleCountShortBlock(state, mod); +} + +void DecompressADPCMSamples( const byte *pFormatChunk, const byte *pDataChunk, int dataSize, short *pOutputBuffer ) +{ + adpcmstate_t state; + ParseADPCM( state, pFormatChunk ); + + while ( dataSize > 0 ) + { + int block = dataSize; + int sampleCount = state.pFormat->wSamplesPerBlock; + if ( block > state.blockSize ) + { + block = state.blockSize; + } + else + { + sampleCount = ADPCMSampleCountShortBlock( state, block ); + } + if ( state.pFormat->wfx.nChannels == 1 ) + { + DecompressBlockMono( state, pOutputBuffer, (const char *)pDataChunk, sampleCount ); + } + else + { + DecompressBlockStereo( state, pOutputBuffer, (const char *)pDataChunk, sampleCount ); + } + pOutputBuffer += sampleCount * state.pFormat->wfx.nChannels; + dataSize -= block; + pDataChunk += block; + } +} + +void Convert8To16( const byte *pInputBuffer, short *pOutputBuffer, int sampleCount, int channelCount ) +{ + for ( int i = 0; i < sampleCount*channelCount; i++ ) + { + unsigned short signedSample = (byte)((int)((unsigned)pInputBuffer[i]) - 128); + pOutputBuffer[i] = (short) (signedSample | (signedSample<<8)); + } +} + diff --git a/utils/xbox/MakeGameData/resample.h b/utils/xbox/MakeGameData/resample.h new file mode 100644 index 0000000..8382d7d --- /dev/null +++ b/utils/xbox/MakeGameData/resample.h @@ -0,0 +1,18 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//============================================================================= + +#ifndef RESAMPLE_H +#define RESAMPLE_H +#ifdef _WIN32 +#pragma once +#endif + +void DecimateSampleRateBy2_16( const short *pInputBuffer, short *pOutputBuffer, int sampleCount, int channelCount ); +void DecompressADPCMSamples( const byte *pFormatChunk, const byte *pDataChunk, int dataSize, short *pOutputBuffer ); +int ADPCMSampleCount( const byte *pFormatChunk, const byte *pDataChunk, int dataSize ); +void Convert8To16( const byte *pInputBuffer, short *pOutputBuffer, int sampleCount, int channelCount ); + +#endif // RESAMPLE_H diff --git a/utils/xbox/MakeGameData/sound_io.cpp b/utils/xbox/MakeGameData/sound_io.cpp new file mode 100644 index 0000000..44e0323 --- /dev/null +++ b/utils/xbox/MakeGameData/sound_io.cpp @@ -0,0 +1,68 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +/***************************************************************************** + SOUND_IO.CPP + + IO class for RIFF +*****************************************************************************/ +#include "../toollib/toollib.h" +#include "tier2/riff.h" + +//----------------------------------------------------------------------------- +// Purpose: Implements Audio IO on the engine's COMMON filesystem +//----------------------------------------------------------------------------- +class COM_IOReadBinary : public IFileReadBinary +{ +public: + int open( const char *pFileName ); + int read( void *pOutput, int size, int file ); + void seek( int file, int pos ); + unsigned int tell( int file ); + unsigned int size( int file ); + void close( int file ); +}; + + +int COM_IOReadBinary::open( const char *pFileName ) +{ + int hFile = -1; + + _sopen_s( &hFile, pFileName, _O_RDONLY|_O_BINARY, _SH_DENYWR, _S_IREAD ); + + return hFile; +} + +int COM_IOReadBinary::read( void *pOutput, int size, int file ) +{ + return _read( file, pOutput, size ); +} + +void COM_IOReadBinary::seek( int file, int pos ) +{ + _lseek( file, pos, SEEK_SET ); +} + +unsigned int COM_IOReadBinary::tell( int file ) +{ + return _lseek( file, 0, SEEK_CUR ); +} + +unsigned int COM_IOReadBinary::size( int file ) +{ + long pos; + long length; + + pos = _lseek( file, 0, SEEK_CUR ); + length = _lseek( file, 0, SEEK_END ); + _lseek( file, pos, SEEK_SET ); + + return length; +} + +void COM_IOReadBinary::close( int file ) +{ + _close( file ); +} + +static COM_IOReadBinary io; +IFileReadBinary *g_pSndIO = &io; + |