diff --git a/engine/console.cpp b/engine/console.cpp index 7c1a7bb..ad9ba7a 100644 --- a/engine/console.cpp +++ b/engine/console.cpp @@ -1,1330 +1,1330 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -//=====================================================================================// - -#include "client_pch.h" -#include -#include "console.h" -#include "ivideomode.h" -#include "zone.h" -#include "sv_main.h" -#include "server.h" -#include "MapReslistGenerator.h" -#include "tier0/vcrmode.h" -#if defined( _X360 ) -#include "xbox/xbox_console.h" -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -#if !defined( _X360 ) -#define MAXPRINTMSG 4096 -#else -#define MAXPRINTMSG 1024 -#endif - -bool con_debuglog = false; -bool con_initialized = false; -bool con_debuglogmapprefixed = false; - -CThreadFastMutex g_AsyncNotifyTextMutex; - -static ConVar con_timestamp( "con_timestamp", "0", 0, "Prefix console.log entries with timestamps" ); - -// In order to avoid excessive opening and closing of the console log file -// we wrap it in an object and keep the handle open. This is necessary -// because of the sometimes considerable cost of opening and closing files -// on Windows. Opening and closing files on Windows is always moderately -// expensive, but profiling may dramatically underestimate the true cost -// because some anti-virus software can make closing a file handle take -// 20-90 ms! -class ConsoleLogManager -{ -public: - ConsoleLogManager(); - ~ConsoleLogManager(); - - void RemoveConsoleLogFile(); - bool ReadConsoleLogFile( CUtlBuffer& buf ); - FileHandle_t GetConsoleLogFileHandleForAppend(); - void CloseFileIfOpen(); - -private: - FileHandle_t m_fh; - - const char *GetConsoleLogFilename() const; -}; - -// Wrap the ConsoleLogManager in a function to ensure that the object is always -// constructed before it is used. -ConsoleLogManager& GetConsoleLogManager() -{ - static ConsoleLogManager object; - return object; -} - -void ConsoleLogFileCallback( IConVar *var, const char *pOldValue, float flOldValue ) -{ - ConVarRef con_logfile( var->GetName() ); - const char *logFile = con_logfile.GetString(); - // close any existing file, because we have changed the name - GetConsoleLogManager().CloseFileIfOpen(); - - // validate the path and the .log/.txt extensions - if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) - { - ConMsg( "invalid log filename\n" ); - con_logfile.SetValue( "console.log" ); - return; - } - else - { - const char *extension = Q_GetFileExtension( logFile ); - if ( !extension || ( Q_strcasecmp( extension, "log" ) && Q_strcasecmp( extension, "txt" ) ) ) - { - char szTemp[MAX_PATH]; - V_sprintf_safe( szTemp, "%s.log", logFile ); - con_logfile.SetValue( szTemp ); - return; - } - } - - if ( !COM_IsValidPath( logFile ) ) - { - con_debuglog = CommandLine()->FindParm( "-condebug" ) != 0; - } - else - { - con_debuglog = true; - } -} - -ConVar con_logfile( "con_logfile", "", 0, "Console output gets written to this file", false, 0.0f, false, 0.0f, ConsoleLogFileCallback ); - -static const char *GetTimestampString( void ) -{ - static char string[128]; - tm today; - VCRHook_LocalTime( &today ); - Q_snprintf( string, sizeof( string ), "%02i/%02i/%04i - %02i:%02i:%02i", - today.tm_mon+1, today.tm_mday, 1900 + today.tm_year, - today.tm_hour, today.tm_min, today.tm_sec ); - return string; -} - -#ifndef SWDS - -static ConVar con_trace( "con_trace", "0", FCVAR_MATERIAL_SYSTEM_THREAD, "Print console text to low level printout." ); -static ConVar con_notifytime( "con_notifytime","8", FCVAR_MATERIAL_SYSTEM_THREAD, "How long to display recent console text to the upper part of the game window" ); -static ConVar con_times("contimes", "8", FCVAR_MATERIAL_SYSTEM_THREAD, "Number of console lines to overlay for debugging." ); -static ConVar con_drawnotify( "con_drawnotify", "1", 0, "Disables drawing of notification area (for taking screenshots)." ); -static ConVar con_enable("con_enable", "0", FCVAR_ARCHIVE, "Allows the console to be activated."); -static ConVar con_filter_enable ( "con_filter_enable","0", FCVAR_MATERIAL_SYSTEM_THREAD, "Filters console output based on the setting of con_filter_text. 1 filters completely, 2 displays filtered text brighter than other text." ); -static ConVar con_filter_text ( "con_filter_text","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter console spew. Set con_filter_enable 1 or 2 to activate." ); -static ConVar con_filter_text_out ( "con_filter_text_out","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter OUT of console spew. Set con_filter_enable 1 or 2 to activate." ); - - - -//----------------------------------------------------------------------------- -// Purpose: Implements the console using VGUI -//----------------------------------------------------------------------------- -class CConPanel : public CBasePanel -{ - typedef CBasePanel BaseClass; - -public: - enum - { - MAX_NOTIFY_TEXT_LINE = 256 - }; - - CConPanel( vgui::Panel *parent ); - virtual ~CConPanel( void ); - - virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); - - // Draws the text - virtual void Paint(); - // Draws the background image - virtual void PaintBackground(); - - // Draw notify area - virtual void DrawNotify( void ); - // Draws debug ( Con_NXPrintf ) areas - virtual void DrawDebugAreas( void ); - - int ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw ); - - // Draw helpers - virtual int DrawText( vgui::HFont font, int x, int y, wchar_t *data ); - - virtual bool ShouldDraw( void ); - - void Con_NPrintf( int idx, const char *msg ); - void Con_NXPrintf( const struct con_nprint_s *info, const char *msg ); - - void AddToNotify( const Color& clr, char const *msg ); - void ClearNotify(); - -private: - // Console font - vgui::HFont m_hFont; - vgui::HFont m_hFontFixed; - - struct CNotifyText - { - Color clr; - float liferemaining; - wchar_t text[MAX_NOTIFY_TEXT_LINE]; - }; - - CCopyableUtlVector< CNotifyText > m_NotifyText; - - enum - { - MAX_DBG_NOTIFY = 128, - DBG_NOTIFY_TIMEOUT = 4, - }; - - float da_default_color[3]; - - typedef struct - { - wchar_t szNotify[MAX_NOTIFY_TEXT_LINE]; - float expire; - float color[3]; - bool fixed_width_font; - } da_notify_t; - - da_notify_t da_notify[MAX_DBG_NOTIFY]; - bool m_bDrawDebugAreas; -}; - -static CConPanel *g_pConPanel = NULL; - -/* -================ -Con_HideConsole_f - -================ -*/ -void Con_HideConsole_f( void ) -{ - if ( IsX360() ) - return; - - if ( EngineVGui()->IsConsoleVisible() ) - { - // hide the console - EngineVGui()->HideConsole(); - } -} - -/* -================ -Con_ShowConsole_f -================ -*/ -void Con_ShowConsole_f( void ) -{ - if ( IsX360() ) - return; - - if ( vgui::input()->GetAppModalSurface() ) - { - // If a dialog has modal, it probably has grabbed keyboard focus, so showing - // the console would be a bad idea. - return; - } - - if ( !g_ClientDLL->ShouldAllowConsole() ) - return; - - // make sure we're allowed to see the console - if ( con_enable.GetBool() || developer.GetInt() || CommandLine()->CheckParm("-console") || CommandLine()->CheckParm("-rpt") ) - { - // show the console - EngineVGui()->ShowConsole(); - - // remove any loading screen - SCR_EndLoadingPlaque(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: toggles the console -//----------------------------------------------------------------------------- -void Con_ToggleConsole_f( void ) -{ - if ( IsX360() ) - return; - - if (EngineVGui()->IsConsoleVisible()) - { - Con_HideConsole_f(); - - // If we hide the console, we also hide the game UI - EngineVGui()->HideGameUI(); - } - else - { - Con_ShowConsole_f(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Clears the console -//----------------------------------------------------------------------------- -void Con_Clear_f( void ) -{ - if ( IsX360() ) - return; - - EngineVGui()->ClearConsole(); - Con_ClearNotify(); -} - -/* -================ -Con_ClearNotify -================ -*/ -void Con_ClearNotify (void) -{ - if ( g_pConPanel ) - { - g_pConPanel->ClearNotify(); - } -} - -#endif // SWDS - - -ConsoleLogManager::ConsoleLogManager() -{ - m_fh = FILESYSTEM_INVALID_HANDLE; -} - -ConsoleLogManager::~ConsoleLogManager() -{ - // This fails because of destructor order problems. The file - // system has already been shut down by the time this runs. - // We'll have to count on the OS to close the file for us. - //CloseFileIfOpen(); -} - -void ConsoleLogManager::RemoveConsoleLogFile() -{ - // Make sure the log file is closed before we try deleting it. - CloseFileIfOpen(); - g_pFileSystem->RemoveFile( GetConsoleLogFilename(), "GAME" ); -} - -bool ConsoleLogManager::ReadConsoleLogFile( CUtlBuffer& buf ) -{ - // Make sure the log file is closed before we try reading it. - CloseFileIfOpen(); - const char *pLogFile = GetConsoleLogFilename(); - if ( g_pFullFileSystem->ReadFile( pLogFile, "GAME", buf ) ) - return true; - - return false; -} - -FileHandle_t ConsoleLogManager::GetConsoleLogFileHandleForAppend() -{ - if ( m_fh == FILESYSTEM_INVALID_HANDLE ) - { - const char* file = GetConsoleLogFilename(); - m_fh = g_pFileSystem->Open( file, "a" ); - } - - return m_fh; -} - -void ConsoleLogManager::CloseFileIfOpen() -{ - if ( m_fh != FILESYSTEM_INVALID_HANDLE ) - { - g_pFileSystem->Close( m_fh ); - m_fh = FILESYSTEM_INVALID_HANDLE; - } -} - -const char *ConsoleLogManager::GetConsoleLogFilename() const -{ - const char *logFile = con_logfile.GetString(); - if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) - { - return "console.log"; - } - return logFile; -} - - -/* -================ -Con_Init -================ -*/ -void Con_Init (void) -{ -#ifdef DEDICATED - con_debuglog = false; // the dedicated server's console will handle this - con_debuglogmapprefixed = false; - - // Check -consolelog arg and set con_logfile if it's present. This gets some messages logged - // that we would otherwise miss due to con_logfile being set in the .cfg file. - const char *filename = NULL; - if ( CommandLine()->CheckParm( "-consolelog", &filename ) && filename && filename[ 0 ] ) - { - con_logfile.SetValue( filename ); - } -#else - bool bRPTClient = ( CommandLine()->FindParm( "-rpt" ) != 0 ); - con_debuglog = bRPTClient || ( CommandLine()->FindParm( "-condebug" ) != 0 ); - con_debuglogmapprefixed = CommandLine()->FindParm( "-makereslists" ) != 0; - if ( con_debuglog ) - { - con_logfile.SetValue( "console.log" ); - if ( bRPTClient || ( CommandLine()->FindParm( "-conclearlog" ) ) ) - { - GetConsoleLogManager().RemoveConsoleLogFile(); - } - } -#endif // !DEDICATED - - con_initialized = true; -} - -/* -================ -Con_Shutdown -================ -*/ -void Con_Shutdown (void) -{ - con_initialized = false; -} - -/* -================ -Read the console log from disk and return it in 'buf'. Buf should come -in as an empty TEXT_BUFFER CUtlBuffer. -Returns true if the log file is successfully read. -================ -*/ -bool GetConsoleLogFileData( CUtlBuffer& buf ) -{ - return GetConsoleLogManager().ReadConsoleLogFile( buf ); -} - -/* -================ -Con_DebugLog -================ -*/ -void Con_DebugLog( const char *fmt, ...) -{ - va_list argptr; - char data[MAXPRINTMSG]; - - va_start(argptr, fmt); - Q_vsnprintf(data, sizeof(data), fmt, argptr); - va_end(argptr); - - FileHandle_t fh = GetConsoleLogManager().GetConsoleLogFileHandleForAppend(); - if (fh != FILESYSTEM_INVALID_HANDLE ) - { - if ( con_debuglogmapprefixed ) - { - char const *prefix = MapReslistGenerator().LogPrefix(); - if ( prefix ) - { - g_pFileSystem->Write( prefix, strlen(prefix), fh ); - } - } - - if ( con_timestamp.GetBool() ) - { - static bool needTimestamp = true; // Start the first line with a timestamp - if ( needTimestamp ) - { - const char *timestamp = GetTimestampString(); - g_pFileSystem->Write( timestamp, strlen( timestamp ), fh ); - g_pFileSystem->Write( ": ", 2, fh ); - } - needTimestamp = V_stristr( data, "\n" ) != 0; - } - - g_pFileSystem->Write( data, strlen(data), fh ); - // Now that we don't close the file we need to flush it in order - // to make sure that the data makes it to the file system. - g_pFileSystem->Flush( fh ); - } -} - -static bool g_fIsDebugPrint = false; - -#ifndef SWDS -/* -================ -Con_Printf - -Handles cursor positioning, line wrapping, etc -================ -*/ -static bool g_fColorPrintf = false; -static bool g_bInColorPrint = false; -extern CThreadLocalInt<> g_bInSpew; - -void Con_Printf( const char *fmt, ... ); - -extern ConVar spew_consolelog_to_debugstring; - -void Con_ColorPrint( const Color& clr, char const *msg ) -{ - if ( IsPC() ) - { - if ( g_bInColorPrint ) - return; - - int nCon_Filter_Enable = con_filter_enable.GetInt(); - if ( nCon_Filter_Enable > 0 ) - { - const char *pszText = con_filter_text.GetString(); - const char *pszIgnoreText = con_filter_text_out.GetString(); - - switch( nCon_Filter_Enable ) - { - case 1: - // if line does not contain keyword do not print the line - if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) - return; - if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) - return; - break; - - case 2: - if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) - return; - // if line does not contain keyword print it in a darker color - if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) - { - Color mycolor(200, 200, 200, 150 ); - g_pCVar->ConsoleColorPrintf( mycolor, "%s", msg ); - return; - } - break; - - default: - // by default do no filtering - break; - } - } - - g_bInColorPrint = true; - - // also echo to debugging console - if ( Plat_IsInDebugSession() && !con_trace.GetInt() && !spew_consolelog_to_debugstring.GetBool() ) - { - Sys_OutputDebugString(msg); - } - - if ( sv.IsDedicated() ) - { - g_bInColorPrint = false; - return; // no graphics mode - } - - bool convisible = Con_IsVisible(); - bool indeveloper = ( developer.GetInt() > 0 ); - bool debugprint = g_fIsDebugPrint; - - if ( g_fColorPrintf ) - { - g_pCVar->ConsoleColorPrintf( clr, "%s", msg ); - } - else - { - // write it out to the vgui console no matter what - if ( g_fIsDebugPrint ) - { - // Don't spew debug stuff to actual console once in game, unless console isn't up - if ( !cl.IsActive() || !convisible ) - { - g_pCVar->ConsoleDPrintf( "%s", msg ); - } - } - else - { - g_pCVar->ConsolePrintf( "%s", msg ); - } - } - - // Make sure we "spew" if this wan't generated from the spew system - if ( !g_bInSpew ) - { - Msg( "%s", msg ); - } - - // Only write to notify if it's non-debug or we are running with developer set > 0 - // Buf it it's debug then make sure we don't have the console down - if ( ( !debugprint || indeveloper ) && !( debugprint && convisible ) ) - { - if ( g_pConPanel ) - { - g_pConPanel->AddToNotify( clr, msg ); - } - } - g_bInColorPrint = false; - } - -#if defined( _X360 ) - int r,g,b,a; - char buffer[MAXPRINTMSG]; - const char *pFrom; - char *pTo; - - clr.GetColor(r, g, b, a); - - // fixup percent printers - pFrom = msg; - pTo = buffer; - while ( *pFrom && pTo < buffer+sizeof(buffer)-1 ) - { - *pTo = *pFrom++; - if ( *pTo++ == '%' ) - *pTo++ = '%'; - } - *pTo = '\0'; - - XBX_DebugString( XMAKECOLOR(r,g,b), buffer ); -#endif -} -#endif - -// returns false if the print function shouldn't continue -bool HandleRedirectAndDebugLog( const char *msg ) -{ - // Add to redirected message - if ( SV_RedirectActive() ) - { - SV_RedirectAddText( msg ); - return false; - } - - // log all messages to file - if ( con_debuglog ) - Con_DebugLog( "%s", msg ); - - if (!con_initialized) - { - return false; - } - return true; -} - -void Con_Print( const char *msg ) -{ - if ( !msg || !msg[0] ) - return; - - if ( !HandleRedirectAndDebugLog( msg ) ) - { - return; - } - -#ifdef SWDS - Msg( "%s", msg ); -#else - if ( sv.IsDedicated() ) - { - Msg( "%s", msg ); - } - else - { -#if !defined( _X360 ) - Color clr( 255, 255, 255, 255 ); -#else - Color clr( 0, 0, 0, 255 ); -#endif - Con_ColorPrint( clr, msg ); - } -#endif -} - -void Con_Printf( const char *fmt, ... ) -{ - va_list argptr; - char msg[MAXPRINTMSG]; - static bool inupdate; - - va_start( argptr, fmt ); - Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); - va_end( argptr ); - -#ifndef NO_VCR - // Normally, we shouldn't need to write this data to the file, but it can help catch - // out-of-sync errors earlier. - if ( vcr_verbose.GetInt() ) - { - VCRGenericString( "Con_Printf", msg ); - } -#endif - - if ( !HandleRedirectAndDebugLog( msg ) ) - { - return; - } - -#ifdef SWDS - Msg( "%s", msg ); -#else - if ( sv.IsDedicated() ) - { - Msg( "%s", msg ); - } - else - { -#if !defined( _X360 ) - Color clr( 255, 255, 255, 255 ); -#else - Color clr( 0, 0, 0, 255 ); -#endif - Con_ColorPrint( clr, msg ); - } -#endif -} - -#ifndef SWDS -//----------------------------------------------------------------------------- -// Purpose: -// Input : clr - -// *fmt - -// ... - -//----------------------------------------------------------------------------- -void Con_ColorPrintf( const Color& clr, const char *fmt, ... ) -{ - va_list argptr; - char msg[MAXPRINTMSG]; - - va_start (argptr,fmt); - Q_vsnprintf (msg,sizeof( msg ), fmt,argptr); - va_end (argptr); - - AUTO_LOCK( g_AsyncNotifyTextMutex ); - if ( !HandleRedirectAndDebugLog( msg ) ) - { - return; - } - - g_fColorPrintf = true; - Con_ColorPrint( clr, msg ); - g_fColorPrintf = false; -} -#endif - -/* -================ -Con_DPrintf - -A Con_Printf that only shows up if the "developer" cvar is set -================ -*/ -void Con_DPrintf (const char *fmt, ...) -{ - va_list argptr; - char msg[MAXPRINTMSG]; - - va_start (argptr,fmt); - Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); - va_end (argptr); - - g_fIsDebugPrint = true; - -#ifdef SWDS - DevMsg( "%s", msg ); -#else - if ( sv.IsDedicated() ) - { - DevMsg( "%s", msg ); - } - else - { - Color clr( 196, 181, 80, 255 ); - Con_ColorPrint ( clr, msg ); - } -#endif - - g_fIsDebugPrint = false; -} - - -/* -================== -Con_SafePrintf - -Okay to call even when the screen can't be updated -================== -*/ -void Con_SafePrintf (const char *fmt, ...) -{ - va_list argptr; - char msg[MAXPRINTMSG]; - - va_start (argptr,fmt); - Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); - va_end (argptr); - -#ifndef SWDS - bool temp; - temp = scr_disabled_for_loading; - scr_disabled_for_loading = true; -#endif - g_fIsDebugPrint = true; - Con_Printf ("%s", msg); - g_fIsDebugPrint = false; -#ifndef SWDS - scr_disabled_for_loading = temp; -#endif -} - -#ifndef SWDS -bool Con_IsVisible() -{ - return (EngineVGui()->IsConsoleVisible()); -} - -void Con_NPrintf( int idx, const char *fmt, ... ) -{ - va_list argptr; - char outtext[MAXPRINTMSG]; - - va_start(argptr, fmt); - Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); - va_end(argptr); - - if ( IsPC() ) - { - g_pConPanel->Con_NPrintf( idx, outtext ); - } - else - { - Con_Printf( outtext ); - } -} - -void Con_NXPrintf( const struct con_nprint_s *info, const char *fmt, ... ) -{ - va_list argptr; - char outtext[MAXPRINTMSG]; - - va_start(argptr, fmt); - Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); - va_end(argptr); - - if ( IsPC() ) - { - g_pConPanel->Con_NXPrintf( info, outtext ); - } - else - { - Con_Printf( outtext ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Creates the console panel -// Input : *parent - -//----------------------------------------------------------------------------- -CConPanel::CConPanel( vgui::Panel *parent ) : CBasePanel( parent, "CConPanel" ) -{ - // Full screen assumed - SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() ); - SetPos( 0, 0 ); - SetVisible( true ); - SetMouseInputEnabled( false ); - SetKeyBoardInputEnabled( false ); - - da_default_color[0] = 1.0; - da_default_color[1] = 1.0; - da_default_color[2] = 1.0; - - m_bDrawDebugAreas = false; - - g_pConPanel = this; - memset( da_notify, 0, sizeof(da_notify) ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CConPanel::~CConPanel( void ) -{ -} - -void CConPanel::Con_NPrintf( int idx, const char *msg ) -{ - if ( idx < 0 || idx >= MAX_DBG_NOTIFY ) - return; - -#ifdef WIN32 - _snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); -#else - _snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); -#endif - da_notify[idx].szNotify[ sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0'; - - // Reset values - da_notify[idx].expire = realtime + DBG_NOTIFY_TIMEOUT; - VectorCopy( da_default_color, da_notify[idx].color ); - da_notify[idx].fixed_width_font = false; - m_bDrawDebugAreas = true; -} - -void CConPanel::Con_NXPrintf( const struct con_nprint_s *info, const char *msg ) -{ - if ( !info ) - return; - - if ( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) - return; - -#ifdef WIN32 - _snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); -#else - _snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); -#endif - da_notify[info->index].szNotify[ sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0'; - - // Reset values - if ( info->time_to_live == -1 ) - da_notify[ info->index ].expire = -1; // special marker means to just draw it once - else - da_notify[ info->index ].expire = realtime + info->time_to_live; - VectorCopy( info->color, da_notify[ info->index ].color ); - da_notify[ info->index ].fixed_width_font = info->fixed_width_font; - m_bDrawDebugAreas = true; -} - -static void safestrncat( wchar_t *text, int maxCharactersWithNullTerminator, wchar_t const *add, int addchars ) -{ - int maxCharactersWithoutTerminator = maxCharactersWithNullTerminator - 1; - - int curlen = wcslen( text ); - if ( curlen >= maxCharactersWithoutTerminator ) - return; - - wchar_t *p = text + curlen; - while ( curlen++ < maxCharactersWithoutTerminator && - --addchars >= 0 ) - { - *p++ = *add++; - } - *p = 0; -} - -void CConPanel::AddToNotify( const Color& clr, char const *msg ) -{ - if ( !host_initialized ) - return; - - // notify area only ever draws in developer mode - it should never be used for game messages - if ( !developer.GetBool() ) - return; - - // skip any special characters - if ( msg[0] == 1 || - msg[0] == 2 ) - { - msg++; - } - - // Nothing left - if ( !msg[0] ) - return; - - // Protect against background modifications to m_NotifyText. - AUTO_LOCK( g_AsyncNotifyTextMutex ); - - CNotifyText *current = NULL; - - int slot = m_NotifyText.Count() - 1; - if ( slot < 0 ) - { - slot = m_NotifyText.AddToTail(); - current = &m_NotifyText[ slot ]; - current->clr = clr; - current->text[ 0 ] = 0; - current->liferemaining = con_notifytime.GetFloat();; - } - else - { - current = &m_NotifyText[ slot ]; - current->clr = clr; - } - - Assert( current ); - - wchar_t unicode[ 1024 ]; - g_pVGuiLocalize->ConvertANSIToUnicode( msg, unicode, sizeof( unicode ) ); - - wchar_t const *p = unicode; - while ( *p ) - { - const wchar_t *nextreturn = wcsstr( p, L"\n" ); - if ( nextreturn != NULL ) - { - int copysize = nextreturn - p + 1; - safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, copysize ); - - // Add a new notify, but don't add a new one if the previous one was empty... - if ( current->text[0] && current->text[0] != L'\n' ) - { - slot = m_NotifyText.AddToTail(); - current = &m_NotifyText[ slot ]; - } - // Clear it - current->clr = clr; - current->text[ 0 ] = 0; - current->liferemaining = con_notifytime.GetFloat(); - // Skip return character - p += copysize; - continue; - } - - // Append it - safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, wcslen( p ) ); - current->clr = clr; - current->liferemaining = con_notifytime.GetFloat(); - break; - } - - while ( m_NotifyText.Count() > 0 && - ( m_NotifyText.Count() >= con_times.GetInt() ) ) - { - m_NotifyText.Remove( 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CConPanel::ClearNotify() -{ - // Protect against background modifications to m_NotifyText. - AUTO_LOCK( g_AsyncNotifyTextMutex ); - - m_NotifyText.RemoveAll(); -} - -void CConPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) -{ - BaseClass::ApplySchemeSettings( pScheme ); - - // Console font - m_hFont = pScheme->GetFont( "DefaultSmallDropShadow", false ); - m_hFontFixed = pScheme->GetFont( "DefaultFixedDropShadow", false ); -} - -int CConPanel::DrawText( vgui::HFont font, int x, int y, wchar_t *data ) -{ - int len = DrawColoredText( font, - x, - y, - 255, - 255, - 255, - 255, - data ); - - return len; -} - - -//----------------------------------------------------------------------------- -// called when we're ticked... -//----------------------------------------------------------------------------- -bool CConPanel::ShouldDraw() -{ - bool bVisible = false; - - if ( m_bDrawDebugAreas ) - { - bVisible = true; - } - - // Should be invisible if there's no notifys and the console is up. - // and if the launcher isn't active - if ( !Con_IsVisible() ) - { - // Protect against background modifications to m_NotifyText. - AUTO_LOCK( g_AsyncNotifyTextMutex ); - - int i; - int c = m_NotifyText.Count(); - for ( i = c - 1; i >= 0; i-- ) - { - CNotifyText *notify = &m_NotifyText[ i ]; - - notify->liferemaining -= host_frametime; - - if ( notify->liferemaining <= 0.0f ) - { - m_NotifyText.Remove( i ); - continue; - } - - bVisible = true; - } - } - else - { - bVisible = true; - } - - return bVisible; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CConPanel::DrawNotify( void ) -{ - int x = 8; - int y = 5; - - if ( !m_hFontFixed ) - return; - - // notify area only draws in developer mode - if ( !developer.GetBool() ) - return; - - // don't render notify area into movies, either - if ( cl_movieinfo.IsRecording( ) ) - { - return; - } - - vgui::surface()->DrawSetTextFont( m_hFontFixed ); - - int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1; - - // Protect against background modifications to m_NotifyText. - // DEADLOCK WARNING: Cannot call DrawColoredText while holding g_AsyncNotifyTextMutex or - // deadlock can occur while MatQueue0 holds material system lock and attempts to add text - // to m_NotifyText. - CUtlVector< CNotifyText > textToDraw; - { - AUTO_LOCK( g_AsyncNotifyTextMutex ); - textToDraw = m_NotifyText; - } - - int c = textToDraw.Count(); - for ( int i = 0; i < c; i++ ) - { - CNotifyText *notify = &textToDraw[ i ]; - - float timeleft = notify->liferemaining; - - Color clr = notify->clr; - - if ( timeleft < .5f ) - { - float f = clamp( timeleft, 0.0f, .5f ) / .5f; - - clr[3] = (int)( f * 255.0f ); - - if ( i == 0 && f < 0.2f ) - { - y -= fontTall * ( 1.0f - f / 0.2f ); - } - } - else - { - clr[3] = 255; - } - - DrawColoredText( m_hFontFixed, x, y, clr[0], clr[1], clr[2], clr[3], notify->text ); - - y += fontTall; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -ConVar con_nprint_bgalpha( "con_nprint_bgalpha", "50", 0, "Con_NPrint background alpha." ); -ConVar con_nprint_bgborder( "con_nprint_bgborder", "5", 0, "Con_NPrint border size." ); - -void CConPanel::DrawDebugAreas( void ) -{ - if ( !m_bDrawDebugAreas ) - return; - - // Find the top and bottom of all the nprint text so we can draw a box behind it. - int left=99999, top=99999, right=-99999, bottom=-99999; - if ( con_nprint_bgalpha.GetInt() ) - { - // First, figure out the bounds of all the con_nprint text. - if ( ProcessNotifyLines( left, top, right, bottom, false ) ) - { - int b = con_nprint_bgborder.GetInt(); - - // Now draw a box behind it. - vgui::surface()->DrawSetColor( 0, 0, 0, con_nprint_bgalpha.GetInt() ); - vgui::surface()->DrawFilledRect( left-b, top-b, right+b, bottom+b ); - } - } - - // Now draw the text. - if ( ProcessNotifyLines( left, top, right, bottom, true ) == 0 ) - { - // Have all notifies expired? - m_bDrawDebugAreas = false; - } -} - -int CConPanel::ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw ) -{ - int count = 0; - int y = 20; - - for ( int i = 0; i < MAX_DBG_NOTIFY; i++ ) - { - if ( realtime < da_notify[i].expire || da_notify[i].expire == -1 ) - { - // If it's marked this way, only draw it once. - if ( da_notify[i].expire == -1 && bDraw ) - { - da_notify[i].expire = realtime - 1; - } - - int len; - int x; - - vgui::HFont font = da_notify[i].fixed_width_font ? m_hFontFixed : m_hFont ; - - int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1; - - len = DrawTextLen( font, da_notify[i].szNotify ); - x = videomode->GetModeStereoWidth() - 10 - len; - - if ( y + fontTall > videomode->GetModeStereoHeight() - 20 ) - return count; - - count++; - y = 20 + 10 * i; - - if ( bDraw ) - { - DrawColoredText( font, x, y, - da_notify[i].color[0] * 255, - da_notify[i].color[1] * 255, - da_notify[i].color[2] * 255, - 255, - da_notify[i].szNotify ); - } - - if ( da_notify[i].szNotify[0] ) - { - // Extend the bounds. - left = min( left, x ); - top = min( top, y ); - right = max( right, x+len ); - bottom = max( bottom, y+fontTall ); - } - - y += fontTall; - } - } - - return count; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CConPanel::Paint() -{ - VPROF( "CConPanel::Paint" ); - -#if !defined( SWDS ) && !defined( DEDICATED ) - if ( IsPC() && !g_ClientDLL->ShouldDrawDropdownConsole() ) - return; -#endif - - DrawDebugAreas(); - - DrawNotify(); // only draw notify in game -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CConPanel::PaintBackground() -{ - if ( !Con_IsVisible() ) - return; - - int wide = GetWide(); - char ver[ 100 ]; - Q_snprintf(ver, sizeof( ver ), "CoolSource Engine %i (build %d)", PROTOCOL_VERSION, build_number() ); - wchar_t unicode[ 200 ]; - g_pVGuiLocalize->ConvertANSIToUnicode( ver, unicode, sizeof( unicode ) ); - - vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); - int x = wide - DrawTextLen( m_hFont, unicode ) - 2; - DrawText( m_hFont, x, 0, unicode ); - - if ( cl.IsActive() ) - { - if ( cl.m_NetChannel->IsLoopback() ) - { - Q_snprintf(ver, sizeof( ver ), "Map '%s'", cl.m_szLevelBaseName ); - } - else - { - Q_snprintf(ver, sizeof( ver ), "Server '%s' Map '%s'", cl.m_NetChannel->GetRemoteAddress().ToString(), cl.m_szLevelBaseName ); - } - wchar_t wUnicode[ 200 ]; - g_pVGuiLocalize->ConvertANSIToUnicode( ver, wUnicode, sizeof( wUnicode ) ); - - int tall = vgui::surface()->GetFontTall( m_hFont ); - - x = wide - DrawTextLen( m_hFont, wUnicode ) - 2; - DrawText( m_hFont, x, tall + 1, wUnicode ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: Creates the Console VGUI object -//----------------------------------------------------------------------------- -static CConPanel *conPanel = NULL; - -void Con_CreateConsolePanel( vgui::Panel *parent ) -{ - conPanel = new CConPanel( parent ); - if (conPanel) - { - conPanel->SetVisible(false); - } -} - -vgui::Panel* Con_GetConsolePanel() -{ - return conPanel; -} - -static ConCommand toggleconsole("toggleconsole", Con_ToggleConsole_f, "Show/hide the console.", FCVAR_DONTRECORD ); -static ConCommand hideconsole("hideconsole", Con_HideConsole_f, "Hide the console.", FCVAR_DONTRECORD ); -static ConCommand showconsole("showconsole", Con_ShowConsole_f, "Show the console.", FCVAR_DONTRECORD ); -static ConCommand clear("clear", Con_Clear_f, "Clear all console output.", FCVAR_DONTRECORD ); - -#endif // SWDS +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=====================================================================================// + +#include "client_pch.h" +#include +#include "console.h" +#include "ivideomode.h" +#include "zone.h" +#include "sv_main.h" +#include "server.h" +#include "MapReslistGenerator.h" +#include "tier0/vcrmode.h" +#if defined( _X360 ) +#include "xbox/xbox_console.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +#if !defined( _X360 ) +#define MAXPRINTMSG 4096 +#else +#define MAXPRINTMSG 1024 +#endif + +bool con_debuglog = false; +bool con_initialized = false; +bool con_debuglogmapprefixed = false; + +CThreadFastMutex g_AsyncNotifyTextMutex; + +static ConVar con_timestamp( "con_timestamp", "0", 0, "Prefix console.log entries with timestamps" ); + +// In order to avoid excessive opening and closing of the console log file +// we wrap it in an object and keep the handle open. This is necessary +// because of the sometimes considerable cost of opening and closing files +// on Windows. Opening and closing files on Windows is always moderately +// expensive, but profiling may dramatically underestimate the true cost +// because some anti-virus software can make closing a file handle take +// 20-90 ms! +class ConsoleLogManager +{ +public: + ConsoleLogManager(); + ~ConsoleLogManager(); + + void RemoveConsoleLogFile(); + bool ReadConsoleLogFile( CUtlBuffer& buf ); + FileHandle_t GetConsoleLogFileHandleForAppend(); + void CloseFileIfOpen(); + +private: + FileHandle_t m_fh; + + const char *GetConsoleLogFilename() const; +}; + +// Wrap the ConsoleLogManager in a function to ensure that the object is always +// constructed before it is used. +ConsoleLogManager& GetConsoleLogManager() +{ + static ConsoleLogManager object; + return object; +} + +void ConsoleLogFileCallback( IConVar *var, const char *pOldValue, float flOldValue ) +{ + ConVarRef con_logfile( var->GetName() ); + const char *logFile = con_logfile.GetString(); + // close any existing file, because we have changed the name + GetConsoleLogManager().CloseFileIfOpen(); + + // validate the path and the .log/.txt extensions + if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) + { + ConMsg( "invalid log filename\n" ); + con_logfile.SetValue( "console.log" ); + return; + } + else + { + const char *extension = Q_GetFileExtension( logFile ); + if ( !extension || ( Q_strcasecmp( extension, "log" ) && Q_strcasecmp( extension, "txt" ) ) ) + { + char szTemp[MAX_PATH]; + V_sprintf_safe( szTemp, "%s.log", logFile ); + con_logfile.SetValue( szTemp ); + return; + } + } + + if ( !COM_IsValidPath( logFile ) ) + { + con_debuglog = CommandLine()->FindParm( "-condebug" ) != 0; + } + else + { + con_debuglog = true; + } +} + +ConVar con_logfile( "con_logfile", "", 0, "Console output gets written to this file", false, 0.0f, false, 0.0f, ConsoleLogFileCallback ); + +static const char *GetTimestampString( void ) +{ + static char string[128]; + tm today; + VCRHook_LocalTime( &today ); + Q_snprintf( string, sizeof( string ), "%02i/%02i/%04i - %02i:%02i:%02i", + today.tm_mon+1, today.tm_mday, 1900 + today.tm_year, + today.tm_hour, today.tm_min, today.tm_sec ); + return string; +} + +#ifndef SWDS + +static ConVar con_trace( "con_trace", "0", FCVAR_MATERIAL_SYSTEM_THREAD, "Print console text to low level printout." ); +static ConVar con_notifytime( "con_notifytime","8", FCVAR_MATERIAL_SYSTEM_THREAD, "How long to display recent console text to the upper part of the game window" ); +static ConVar con_times("contimes", "8", FCVAR_MATERIAL_SYSTEM_THREAD, "Number of console lines to overlay for debugging." ); +static ConVar con_drawnotify( "con_drawnotify", "1", 0, "Disables drawing of notification area (for taking screenshots)." ); +static ConVar con_enable("con_enable", "0", FCVAR_ARCHIVE, "Allows the console to be activated."); +static ConVar con_filter_enable ( "con_filter_enable","0", FCVAR_MATERIAL_SYSTEM_THREAD, "Filters console output based on the setting of con_filter_text. 1 filters completely, 2 displays filtered text brighter than other text." ); +static ConVar con_filter_text ( "con_filter_text","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter console spew. Set con_filter_enable 1 or 2 to activate." ); +static ConVar con_filter_text_out ( "con_filter_text_out","", FCVAR_MATERIAL_SYSTEM_THREAD, "Text with which to filter OUT of console spew. Set con_filter_enable 1 or 2 to activate." ); + + + +//----------------------------------------------------------------------------- +// Purpose: Implements the console using VGUI +//----------------------------------------------------------------------------- +class CConPanel : public CBasePanel +{ + typedef CBasePanel BaseClass; + +public: + enum + { + MAX_NOTIFY_TEXT_LINE = 256 + }; + + CConPanel( vgui::Panel *parent ); + virtual ~CConPanel( void ); + + virtual void ApplySchemeSettings( vgui::IScheme *pScheme ); + + // Draws the text + virtual void Paint(); + // Draws the background image + virtual void PaintBackground(); + + // Draw notify area + virtual void DrawNotify( void ); + // Draws debug ( Con_NXPrintf ) areas + virtual void DrawDebugAreas( void ); + + int ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw ); + + // Draw helpers + virtual int DrawText( vgui::HFont font, int x, int y, wchar_t *data ); + + virtual bool ShouldDraw( void ); + + void Con_NPrintf( int idx, const char *msg ); + void Con_NXPrintf( const struct con_nprint_s *info, const char *msg ); + + void AddToNotify( const Color& clr, char const *msg ); + void ClearNotify(); + +private: + // Console font + vgui::HFont m_hFont; + vgui::HFont m_hFontFixed; + + struct CNotifyText + { + Color clr; + float liferemaining; + wchar_t text[MAX_NOTIFY_TEXT_LINE]; + }; + + CCopyableUtlVector< CNotifyText > m_NotifyText; + + enum + { + MAX_DBG_NOTIFY = 128, + DBG_NOTIFY_TIMEOUT = 4, + }; + + float da_default_color[3]; + + typedef struct + { + wchar_t szNotify[MAX_NOTIFY_TEXT_LINE]; + float expire; + float color[3]; + bool fixed_width_font; + } da_notify_t; + + da_notify_t da_notify[MAX_DBG_NOTIFY]; + bool m_bDrawDebugAreas; +}; + +static CConPanel *g_pConPanel = NULL; + +/* +================ +Con_HideConsole_f + +================ +*/ +void Con_HideConsole_f( void ) +{ + if ( IsX360() ) + return; + + if ( EngineVGui()->IsConsoleVisible() ) + { + // hide the console + EngineVGui()->HideConsole(); + } +} + +/* +================ +Con_ShowConsole_f +================ +*/ +void Con_ShowConsole_f( void ) +{ + if ( IsX360() ) + return; + + if ( vgui::input()->GetAppModalSurface() ) + { + // If a dialog has modal, it probably has grabbed keyboard focus, so showing + // the console would be a bad idea. + return; + } + + if ( !g_ClientDLL->ShouldAllowConsole() ) + return; + + // make sure we're allowed to see the console + if ( con_enable.GetBool() || developer.GetInt() || CommandLine()->CheckParm("-console") || CommandLine()->CheckParm("-rpt") ) + { + // show the console + EngineVGui()->ShowConsole(); + + // remove any loading screen + SCR_EndLoadingPlaque(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: toggles the console +//----------------------------------------------------------------------------- +void Con_ToggleConsole_f( void ) +{ + if ( IsX360() ) + return; + + if (EngineVGui()->IsConsoleVisible()) + { + Con_HideConsole_f(); + + // If we hide the console, we also hide the game UI + EngineVGui()->HideGameUI(); + } + else + { + Con_ShowConsole_f(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Clears the console +//----------------------------------------------------------------------------- +void Con_Clear_f( void ) +{ + if ( IsX360() ) + return; + + EngineVGui()->ClearConsole(); + Con_ClearNotify(); +} + +/* +================ +Con_ClearNotify +================ +*/ +void Con_ClearNotify (void) +{ + if ( g_pConPanel ) + { + g_pConPanel->ClearNotify(); + } +} + +#endif // SWDS + + +ConsoleLogManager::ConsoleLogManager() +{ + m_fh = FILESYSTEM_INVALID_HANDLE; +} + +ConsoleLogManager::~ConsoleLogManager() +{ + // This fails because of destructor order problems. The file + // system has already been shut down by the time this runs. + // We'll have to count on the OS to close the file for us. + //CloseFileIfOpen(); +} + +void ConsoleLogManager::RemoveConsoleLogFile() +{ + // Make sure the log file is closed before we try deleting it. + CloseFileIfOpen(); + g_pFileSystem->RemoveFile( GetConsoleLogFilename(), "GAME" ); +} + +bool ConsoleLogManager::ReadConsoleLogFile( CUtlBuffer& buf ) +{ + // Make sure the log file is closed before we try reading it. + CloseFileIfOpen(); + const char *pLogFile = GetConsoleLogFilename(); + if ( g_pFullFileSystem->ReadFile( pLogFile, "GAME", buf ) ) + return true; + + return false; +} + +FileHandle_t ConsoleLogManager::GetConsoleLogFileHandleForAppend() +{ + if ( m_fh == FILESYSTEM_INVALID_HANDLE ) + { + const char* file = GetConsoleLogFilename(); + m_fh = g_pFileSystem->Open( file, "a" ); + } + + return m_fh; +} + +void ConsoleLogManager::CloseFileIfOpen() +{ + if ( m_fh != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( m_fh ); + m_fh = FILESYSTEM_INVALID_HANDLE; + } +} + +const char *ConsoleLogManager::GetConsoleLogFilename() const +{ + const char *logFile = con_logfile.GetString(); + if ( !COM_IsValidPath( logFile ) || !COM_IsValidLogFilename( logFile ) ) + { + return "console.log"; + } + return logFile; +} + + +/* +================ +Con_Init +================ +*/ +void Con_Init (void) +{ +#ifdef DEDICATED + con_debuglog = false; // the dedicated server's console will handle this + con_debuglogmapprefixed = false; + + // Check -consolelog arg and set con_logfile if it's present. This gets some messages logged + // that we would otherwise miss due to con_logfile being set in the .cfg file. + const char *filename = NULL; + if ( CommandLine()->CheckParm( "-consolelog", &filename ) && filename && filename[ 0 ] ) + { + con_logfile.SetValue( filename ); + } +#else + bool bRPTClient = ( CommandLine()->FindParm( "-rpt" ) != 0 ); + con_debuglog = bRPTClient || ( CommandLine()->FindParm( "-condebug" ) != 0 ); + con_debuglogmapprefixed = CommandLine()->FindParm( "-makereslists" ) != 0; + if ( con_debuglog ) + { + con_logfile.SetValue( "console.log" ); + if ( bRPTClient || ( CommandLine()->FindParm( "-conclearlog" ) ) ) + { + GetConsoleLogManager().RemoveConsoleLogFile(); + } + } +#endif // !DEDICATED + + con_initialized = true; +} + +/* +================ +Con_Shutdown +================ +*/ +void Con_Shutdown (void) +{ + con_initialized = false; +} + +/* +================ +Read the console log from disk and return it in 'buf'. Buf should come +in as an empty TEXT_BUFFER CUtlBuffer. +Returns true if the log file is successfully read. +================ +*/ +bool GetConsoleLogFileData( CUtlBuffer& buf ) +{ + return GetConsoleLogManager().ReadConsoleLogFile( buf ); +} + +/* +================ +Con_DebugLog +================ +*/ +void Con_DebugLog( const char *fmt, ...) +{ + va_list argptr; + char data[MAXPRINTMSG]; + + va_start(argptr, fmt); + Q_vsnprintf(data, sizeof(data), fmt, argptr); + va_end(argptr); + + FileHandle_t fh = GetConsoleLogManager().GetConsoleLogFileHandleForAppend(); + if (fh != FILESYSTEM_INVALID_HANDLE ) + { + if ( con_debuglogmapprefixed ) + { + char const *prefix = MapReslistGenerator().LogPrefix(); + if ( prefix ) + { + g_pFileSystem->Write( prefix, strlen(prefix), fh ); + } + } + + if ( con_timestamp.GetBool() ) + { + static bool needTimestamp = true; // Start the first line with a timestamp + if ( needTimestamp ) + { + const char *timestamp = GetTimestampString(); + g_pFileSystem->Write( timestamp, strlen( timestamp ), fh ); + g_pFileSystem->Write( ": ", 2, fh ); + } + needTimestamp = V_stristr( data, "\n" ) != 0; + } + + g_pFileSystem->Write( data, strlen(data), fh ); + // Now that we don't close the file we need to flush it in order + // to make sure that the data makes it to the file system. + g_pFileSystem->Flush( fh ); + } +} + +static bool g_fIsDebugPrint = false; + +#ifndef SWDS +/* +================ +Con_Printf + +Handles cursor positioning, line wrapping, etc +================ +*/ +static bool g_fColorPrintf = false; +static bool g_bInColorPrint = false; +extern CThreadLocalInt<> g_bInSpew; + +void Con_Printf( const char *fmt, ... ); + +extern ConVar spew_consolelog_to_debugstring; + +void Con_ColorPrint( const Color& clr, char const *msg ) +{ + if ( IsPC() ) + { + if ( g_bInColorPrint ) + return; + + int nCon_Filter_Enable = con_filter_enable.GetInt(); + if ( nCon_Filter_Enable > 0 ) + { + const char *pszText = con_filter_text.GetString(); + const char *pszIgnoreText = con_filter_text_out.GetString(); + + switch( nCon_Filter_Enable ) + { + case 1: + // if line does not contain keyword do not print the line + if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) + return; + if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) + return; + break; + + case 2: + if ( pszIgnoreText && *pszIgnoreText && ( Q_stristr( msg, pszIgnoreText ) != NULL ) ) + return; + // if line does not contain keyword print it in a darker color + if ( pszText && ( *pszText != '\0' ) && ( Q_stristr( msg, pszText ) == NULL )) + { + Color mycolor(200, 200, 200, 150 ); + g_pCVar->ConsoleColorPrintf( mycolor, "%s", msg ); + return; + } + break; + + default: + // by default do no filtering + break; + } + } + + g_bInColorPrint = true; + + // also echo to debugging console + if ( Plat_IsInDebugSession() && !con_trace.GetInt() && !spew_consolelog_to_debugstring.GetBool() ) + { + Sys_OutputDebugString(msg); + } + + if ( sv.IsDedicated() ) + { + g_bInColorPrint = false; + return; // no graphics mode + } + + bool convisible = Con_IsVisible(); + bool indeveloper = ( developer.GetInt() > 0 ); + bool debugprint = g_fIsDebugPrint; + + if ( g_fColorPrintf ) + { + g_pCVar->ConsoleColorPrintf( clr, "%s", msg ); + } + else + { + // write it out to the vgui console no matter what + if ( g_fIsDebugPrint ) + { + // Don't spew debug stuff to actual console once in game, unless console isn't up + if ( !cl.IsActive() || !convisible ) + { + g_pCVar->ConsoleDPrintf( "%s", msg ); + } + } + else + { + g_pCVar->ConsolePrintf( "%s", msg ); + } + } + + // Make sure we "spew" if this wan't generated from the spew system + if ( !g_bInSpew ) + { + Msg( "%s", msg ); + } + + // Only write to notify if it's non-debug or we are running with developer set > 0 + // Buf it it's debug then make sure we don't have the console down + if ( ( !debugprint || indeveloper ) && !( debugprint && convisible ) ) + { + if ( g_pConPanel ) + { + g_pConPanel->AddToNotify( clr, msg ); + } + } + g_bInColorPrint = false; + } + +#if defined( _X360 ) + int r,g,b,a; + char buffer[MAXPRINTMSG]; + const char *pFrom; + char *pTo; + + clr.GetColor(r, g, b, a); + + // fixup percent printers + pFrom = msg; + pTo = buffer; + while ( *pFrom && pTo < buffer+sizeof(buffer)-1 ) + { + *pTo = *pFrom++; + if ( *pTo++ == '%' ) + *pTo++ = '%'; + } + *pTo = '\0'; + + XBX_DebugString( XMAKECOLOR(r,g,b), buffer ); +#endif +} +#endif + +// returns false if the print function shouldn't continue +bool HandleRedirectAndDebugLog( const char *msg ) +{ + // Add to redirected message + if ( SV_RedirectActive() ) + { + SV_RedirectAddText( msg ); + return false; + } + + // log all messages to file + if ( con_debuglog ) + Con_DebugLog( "%s", msg ); + + if (!con_initialized) + { + return false; + } + return true; +} + +void Con_Print( const char *msg ) +{ + if ( !msg || !msg[0] ) + return; + + if ( !HandleRedirectAndDebugLog( msg ) ) + { + return; + } + +#ifdef SWDS + Msg( "%s", msg ); +#else + if ( sv.IsDedicated() ) + { + Msg( "%s", msg ); + } + else + { +#if !defined( _X360 ) + Color clr( 255, 255, 255, 255 ); +#else + Color clr( 0, 0, 0, 255 ); +#endif + Con_ColorPrint( clr, msg ); + } +#endif +} + +void Con_Printf( const char *fmt, ... ) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + static bool inupdate; + + va_start( argptr, fmt ); + Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ); + va_end( argptr ); + +#ifndef NO_VCR + // Normally, we shouldn't need to write this data to the file, but it can help catch + // out-of-sync errors earlier. + if ( vcr_verbose.GetInt() ) + { + VCRGenericString( "Con_Printf", msg ); + } +#endif + + if ( !HandleRedirectAndDebugLog( msg ) ) + { + return; + } + +#ifdef SWDS + Msg( "%s", msg ); +#else + if ( sv.IsDedicated() ) + { + Msg( "%s", msg ); + } + else + { +#if !defined( _X360 ) + Color clr( 255, 255, 255, 255 ); +#else + Color clr( 0, 0, 0, 255 ); +#endif + Con_ColorPrint( clr, msg ); + } +#endif +} + +#ifndef SWDS +//----------------------------------------------------------------------------- +// Purpose: +// Input : clr - +// *fmt - +// ... - +//----------------------------------------------------------------------------- +void Con_ColorPrintf( const Color& clr, const char *fmt, ... ) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf (msg,sizeof( msg ), fmt,argptr); + va_end (argptr); + + AUTO_LOCK( g_AsyncNotifyTextMutex ); + if ( !HandleRedirectAndDebugLog( msg ) ) + { + return; + } + + g_fColorPrintf = true; + Con_ColorPrint( clr, msg ); + g_fColorPrintf = false; +} +#endif + +/* +================ +Con_DPrintf + +A Con_Printf that only shows up if the "developer" cvar is set +================ +*/ +void Con_DPrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); + va_end (argptr); + + g_fIsDebugPrint = true; + +#ifdef SWDS + DevMsg( "%s", msg ); +#else + if ( sv.IsDedicated() ) + { + DevMsg( "%s", msg ); + } + else + { + Color clr( 196, 181, 80, 255 ); + Con_ColorPrint ( clr, msg ); + } +#endif + + g_fIsDebugPrint = false; +} + + +/* +================== +Con_SafePrintf + +Okay to call even when the screen can't be updated +================== +*/ +void Con_SafePrintf (const char *fmt, ...) +{ + va_list argptr; + char msg[MAXPRINTMSG]; + + va_start (argptr,fmt); + Q_vsnprintf(msg,sizeof( msg ), fmt,argptr); + va_end (argptr); + +#ifndef SWDS + bool temp; + temp = scr_disabled_for_loading; + scr_disabled_for_loading = true; +#endif + g_fIsDebugPrint = true; + Con_Printf ("%s", msg); + g_fIsDebugPrint = false; +#ifndef SWDS + scr_disabled_for_loading = temp; +#endif +} + +#ifndef SWDS +bool Con_IsVisible() +{ + return (EngineVGui()->IsConsoleVisible()); +} + +void Con_NPrintf( int idx, const char *fmt, ... ) +{ + va_list argptr; + char outtext[MAXPRINTMSG]; + + va_start(argptr, fmt); + Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); + va_end(argptr); + + if ( IsPC() ) + { + g_pConPanel->Con_NPrintf( idx, outtext ); + } + else + { + Con_Printf( outtext ); + } +} + +void Con_NXPrintf( const struct con_nprint_s *info, const char *fmt, ... ) +{ + va_list argptr; + char outtext[MAXPRINTMSG]; + + va_start(argptr, fmt); + Q_vsnprintf( outtext, sizeof( outtext ), fmt, argptr); + va_end(argptr); + + if ( IsPC() ) + { + g_pConPanel->Con_NXPrintf( info, outtext ); + } + else + { + Con_Printf( outtext ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Creates the console panel +// Input : *parent - +//----------------------------------------------------------------------------- +CConPanel::CConPanel( vgui::Panel *parent ) : CBasePanel( parent, "CConPanel" ) +{ + // Full screen assumed + SetSize( videomode->GetModeStereoWidth(), videomode->GetModeStereoHeight() ); + SetPos( 0, 0 ); + SetVisible( true ); + SetMouseInputEnabled( false ); + SetKeyBoardInputEnabled( false ); + + da_default_color[0] = 1.0; + da_default_color[1] = 1.0; + da_default_color[2] = 1.0; + + m_bDrawDebugAreas = false; + + g_pConPanel = this; + memset( da_notify, 0, sizeof(da_notify) ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CConPanel::~CConPanel( void ) +{ +} + +void CConPanel::Con_NPrintf( int idx, const char *msg ) +{ + if ( idx < 0 || idx >= MAX_DBG_NOTIFY ) + return; + +#ifdef WIN32 + _snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); +#else + _snwprintf( da_notify[idx].szNotify, sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); +#endif + da_notify[idx].szNotify[ sizeof( da_notify[idx].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0'; + + // Reset values + da_notify[idx].expire = realtime + DBG_NOTIFY_TIMEOUT; + VectorCopy( da_default_color, da_notify[idx].color ); + da_notify[idx].fixed_width_font = false; + m_bDrawDebugAreas = true; +} + +void CConPanel::Con_NXPrintf( const struct con_nprint_s *info, const char *msg ) +{ + if ( !info ) + return; + + if ( info->index < 0 || info->index >= MAX_DBG_NOTIFY ) + return; + +#ifdef WIN32 + _snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%S", msg ); +#else + _snwprintf( da_notify[info->index].szNotify, sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1, L"%s", msg ); +#endif + da_notify[info->index].szNotify[ sizeof( da_notify[info->index].szNotify ) / sizeof( wchar_t ) - 1 ] = L'\0'; + + // Reset values + if ( info->time_to_live == -1 ) + da_notify[ info->index ].expire = -1; // special marker means to just draw it once + else + da_notify[ info->index ].expire = realtime + info->time_to_live; + VectorCopy( info->color, da_notify[ info->index ].color ); + da_notify[ info->index ].fixed_width_font = info->fixed_width_font; + m_bDrawDebugAreas = true; +} + +static void safestrncat( wchar_t *text, int maxCharactersWithNullTerminator, wchar_t const *add, int addchars ) +{ + int maxCharactersWithoutTerminator = maxCharactersWithNullTerminator - 1; + + int curlen = wcslen( text ); + if ( curlen >= maxCharactersWithoutTerminator ) + return; + + wchar_t *p = text + curlen; + while ( curlen++ < maxCharactersWithoutTerminator && + --addchars >= 0 ) + { + *p++ = *add++; + } + *p = 0; +} + +void CConPanel::AddToNotify( const Color& clr, char const *msg ) +{ + if ( !host_initialized ) + return; + + // notify area only ever draws in developer mode - it should never be used for game messages + if ( !developer.GetBool() ) + return; + + // skip any special characters + if ( msg[0] == 1 || + msg[0] == 2 ) + { + msg++; + } + + // Nothing left + if ( !msg[0] ) + return; + + // Protect against background modifications to m_NotifyText. + AUTO_LOCK( g_AsyncNotifyTextMutex ); + + CNotifyText *current = NULL; + + int slot = m_NotifyText.Count() - 1; + if ( slot < 0 ) + { + slot = m_NotifyText.AddToTail(); + current = &m_NotifyText[ slot ]; + current->clr = clr; + current->text[ 0 ] = 0; + current->liferemaining = con_notifytime.GetFloat();; + } + else + { + current = &m_NotifyText[ slot ]; + current->clr = clr; + } + + Assert( current ); + + wchar_t unicode[ 1024 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( msg, unicode, sizeof( unicode ) ); + + wchar_t const *p = unicode; + while ( *p ) + { + const wchar_t *nextreturn = wcsstr( p, L"\n" ); + if ( nextreturn != NULL ) + { + int copysize = nextreturn - p + 1; + safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, copysize ); + + // Add a new notify, but don't add a new one if the previous one was empty... + if ( current->text[0] && current->text[0] != L'\n' ) + { + slot = m_NotifyText.AddToTail(); + current = &m_NotifyText[ slot ]; + } + // Clear it + current->clr = clr; + current->text[ 0 ] = 0; + current->liferemaining = con_notifytime.GetFloat(); + // Skip return character + p += copysize; + continue; + } + + // Append it + safestrncat( current->text, MAX_NOTIFY_TEXT_LINE, p, wcslen( p ) ); + current->clr = clr; + current->liferemaining = con_notifytime.GetFloat(); + break; + } + + while ( m_NotifyText.Count() > 0 && + ( m_NotifyText.Count() >= con_times.GetInt() ) ) + { + m_NotifyText.Remove( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CConPanel::ClearNotify() +{ + // Protect against background modifications to m_NotifyText. + AUTO_LOCK( g_AsyncNotifyTextMutex ); + + m_NotifyText.RemoveAll(); +} + +void CConPanel::ApplySchemeSettings( vgui::IScheme *pScheme ) +{ + BaseClass::ApplySchemeSettings( pScheme ); + + // Console font + m_hFont = pScheme->GetFont( "DefaultSmallDropShadow", false ); + m_hFontFixed = pScheme->GetFont( "DefaultFixedDropShadow", false ); +} + +int CConPanel::DrawText( vgui::HFont font, int x, int y, wchar_t *data ) +{ + int len = DrawColoredText( font, + x, + y, + 255, + 255, + 255, + 255, + data ); + + return len; +} + + +//----------------------------------------------------------------------------- +// called when we're ticked... +//----------------------------------------------------------------------------- +bool CConPanel::ShouldDraw() +{ + bool bVisible = false; + + if ( m_bDrawDebugAreas ) + { + bVisible = true; + } + + // Should be invisible if there's no notifys and the console is up. + // and if the launcher isn't active + if ( !Con_IsVisible() ) + { + // Protect against background modifications to m_NotifyText. + AUTO_LOCK( g_AsyncNotifyTextMutex ); + + int i; + int c = m_NotifyText.Count(); + for ( i = c - 1; i >= 0; i-- ) + { + CNotifyText *notify = &m_NotifyText[ i ]; + + notify->liferemaining -= host_frametime; + + if ( notify->liferemaining <= 0.0f ) + { + m_NotifyText.Remove( i ); + continue; + } + + bVisible = true; + } + } + else + { + bVisible = true; + } + + return bVisible; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CConPanel::DrawNotify( void ) +{ + int x = 8; + int y = 5; + + if ( !m_hFontFixed ) + return; + + // notify area only draws in developer mode + if ( !developer.GetBool() ) + return; + + // don't render notify area into movies, either + if ( cl_movieinfo.IsRecording( ) ) + { + return; + } + + vgui::surface()->DrawSetTextFont( m_hFontFixed ); + + int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1; + + // Protect against background modifications to m_NotifyText. + // DEADLOCK WARNING: Cannot call DrawColoredText while holding g_AsyncNotifyTextMutex or + // deadlock can occur while MatQueue0 holds material system lock and attempts to add text + // to m_NotifyText. + CUtlVector< CNotifyText > textToDraw; + { + AUTO_LOCK( g_AsyncNotifyTextMutex ); + textToDraw = m_NotifyText; + } + + int c = textToDraw.Count(); + for ( int i = 0; i < c; i++ ) + { + CNotifyText *notify = &textToDraw[ i ]; + + float timeleft = notify->liferemaining; + + Color clr = notify->clr; + + if ( timeleft < .5f ) + { + float f = clamp( timeleft, 0.0f, .5f ) / .5f; + + clr[3] = (int)( f * 255.0f ); + + if ( i == 0 && f < 0.2f ) + { + y -= fontTall * ( 1.0f - f / 0.2f ); + } + } + else + { + clr[3] = 255; + } + + DrawColoredText( m_hFontFixed, x, y, clr[0], clr[1], clr[2], clr[3], notify->text ); + + y += fontTall; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +ConVar con_nprint_bgalpha( "con_nprint_bgalpha", "50", 0, "Con_NPrint background alpha." ); +ConVar con_nprint_bgborder( "con_nprint_bgborder", "5", 0, "Con_NPrint border size." ); + +void CConPanel::DrawDebugAreas( void ) +{ + if ( !m_bDrawDebugAreas ) + return; + + // Find the top and bottom of all the nprint text so we can draw a box behind it. + int left=99999, top=99999, right=-99999, bottom=-99999; + if ( con_nprint_bgalpha.GetInt() ) + { + // First, figure out the bounds of all the con_nprint text. + if ( ProcessNotifyLines( left, top, right, bottom, false ) ) + { + int b = con_nprint_bgborder.GetInt(); + + // Now draw a box behind it. + vgui::surface()->DrawSetColor( 0, 0, 0, con_nprint_bgalpha.GetInt() ); + vgui::surface()->DrawFilledRect( left-b, top-b, right+b, bottom+b ); + } + } + + // Now draw the text. + if ( ProcessNotifyLines( left, top, right, bottom, true ) == 0 ) + { + // Have all notifies expired? + m_bDrawDebugAreas = false; + } +} + +int CConPanel::ProcessNotifyLines( int &left, int &top, int &right, int &bottom, bool bDraw ) +{ + int count = 0; + int y = 20; + + for ( int i = 0; i < MAX_DBG_NOTIFY; i++ ) + { + if ( realtime < da_notify[i].expire || da_notify[i].expire == -1 ) + { + // If it's marked this way, only draw it once. + if ( da_notify[i].expire == -1 && bDraw ) + { + da_notify[i].expire = realtime - 1; + } + + int len; + int x; + + vgui::HFont font = da_notify[i].fixed_width_font ? m_hFontFixed : m_hFont ; + + int fontTall = vgui::surface()->GetFontTall( m_hFontFixed ) + 1; + + len = DrawTextLen( font, da_notify[i].szNotify ); + x = videomode->GetModeStereoWidth() - 10 - len; + + if ( y + fontTall > videomode->GetModeStereoHeight() - 20 ) + return count; + + count++; + y = 20 + 10 * i; + + if ( bDraw ) + { + DrawColoredText( font, x, y, + da_notify[i].color[0] * 255, + da_notify[i].color[1] * 255, + da_notify[i].color[2] * 255, + 255, + da_notify[i].szNotify ); + } + + if ( da_notify[i].szNotify[0] ) + { + // Extend the bounds. + left = min( left, x ); + top = min( top, y ); + right = max( right, x+len ); + bottom = max( bottom, y+fontTall ); + } + + y += fontTall; + } + } + + return count; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CConPanel::Paint() +{ + VPROF( "CConPanel::Paint" ); + +#if !defined( SWDS ) && !defined( DEDICATED ) + if ( IsPC() && !g_ClientDLL->ShouldDrawDropdownConsole() ) + return; +#endif + + DrawDebugAreas(); + + DrawNotify(); // only draw notify in game +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CConPanel::PaintBackground() +{ + if ( !Con_IsVisible() ) + return; + + int wide = GetWide(); + char ver[ 100 ]; + Q_snprintf(ver, sizeof( ver ), "CoolSource Engine %i (%s)", PROTOCOL_VERSION, __TIMESTAMP__ ); + wchar_t unicode[ 200 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( ver, unicode, sizeof( unicode ) ); + + vgui::surface()->DrawSetTextColor( Color( 255, 255, 255, 255 ) ); + int x = wide - DrawTextLen( m_hFont, unicode ) - 2; + DrawText( m_hFont, x, 0, unicode ); + + if ( cl.IsActive() ) + { + if ( cl.m_NetChannel->IsLoopback() ) + { + Q_snprintf(ver, sizeof( ver ), "Map '%s'", cl.m_szLevelBaseName ); + } + else + { + Q_snprintf(ver, sizeof( ver ), "Server '%s' Map '%s'", cl.m_NetChannel->GetRemoteAddress().ToString(), cl.m_szLevelBaseName ); + } + wchar_t wUnicode[ 200 ]; + g_pVGuiLocalize->ConvertANSIToUnicode( ver, wUnicode, sizeof( wUnicode ) ); + + int tall = vgui::surface()->GetFontTall( m_hFont ); + + x = wide - DrawTextLen( m_hFont, wUnicode ) - 2; + DrawText( m_hFont, x, tall + 1, wUnicode ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Creates the Console VGUI object +//----------------------------------------------------------------------------- +static CConPanel *conPanel = NULL; + +void Con_CreateConsolePanel( vgui::Panel *parent ) +{ + conPanel = new CConPanel( parent ); + if (conPanel) + { + conPanel->SetVisible(false); + } +} + +vgui::Panel* Con_GetConsolePanel() +{ + return conPanel; +} + +static ConCommand toggleconsole("toggleconsole", Con_ToggleConsole_f, "Show/hide the console.", FCVAR_DONTRECORD ); +static ConCommand hideconsole("hideconsole", Con_HideConsole_f, "Hide the console.", FCVAR_DONTRECORD ); +static ConCommand showconsole("showconsole", Con_ShowConsole_f, "Show the console.", FCVAR_DONTRECORD ); +static ConCommand clear("clear", Con_Clear_f, "Clear all console output.", FCVAR_DONTRECORD ); + +#endif // SWDS diff --git a/engine/modelloader.cpp b/engine/modelloader.cpp index ceb1dac..76d511d 100644 --- a/engine/modelloader.cpp +++ b/engine/modelloader.cpp @@ -1,6281 +1,6283 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: Model loading / unloading interface -// -// $NoKeywords: $ -//===========================================================================// - -#include "render_pch.h" -#include "common.h" -#include "modelloader.h" -#include "sysexternal.h" -#include "cmd.h" -#include "istudiorender.h" -#include "engine/ivmodelinfo.h" -#include "draw.h" -#include "zone.h" -#include "edict.h" -#include "cmodel_engine.h" -#include "cdll_engine_int.h" -#include "iscratchpad3d.h" -#include "materialsystem/imaterialsystemhardwareconfig.h" -#include "materialsystem/materialsystem_config.h" -#include "gl_rsurf.h" -#include "video/ivideoservices.h" -#include "materialsystem/itexture.h" -#include "Overlay.h" -#include "utldict.h" -#include "mempool.h" -#include "r_decal.h" -#include "l_studio.h" -#include "gl_drawlights.h" -#include "tier0/icommandline.h" -#include "MapReslistGenerator.h" -#ifndef SWDS -#include "vgui_baseui_interface.h" -#endif -#include "engine/ivmodelrender.h" -#include "host.h" -#include "datacache/idatacache.h" -#include "sys_dll.h" -#include "datacache/imdlcache.h" -#include "gl_cvars.h" -#include "vphysics_interface.h" -#include "filesystem/IQueuedLoader.h" -#include "tier2/tier2.h" -#include "lightcache.h" -#include "lumpfiles.h" -#include "tier2/fileutils.h" -#include "UtlSortVector.h" -#include "utlhashtable.h" -#include "tier1/lzmaDecoder.h" -#include "eiface.h" -#include "server.h" -#include "ifilelist.h" -#include "LoadScreenUpdate.h" -#include "optimize.h" -#include "networkstringtable.h" -#include "tier1/callqueue.h" - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -ConVar mat_loadtextures( "mat_loadtextures", "1", FCVAR_CHEAT ); - -// OS X and Linux are blowing up right now due to this. Benefits vs possible regressions on DX less clear. -#if defined( DX_TO_GL_ABSTRACTION ) || defined( STAGING_ONLY ) - #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "1" -#else - #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "0" -#endif -static ConVar mod_offline_hdr_switch( "mod_offline_hdr_switch", CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH, 0, - "Re-order the HDR/LDR mode switch to do most of the material system " - "reloading with the device offline. This reduces unnecessary device " - "resource uploads and may drastically reduce load time and memory pressure " - "on certain drivers, but may trigger bugs in some very old source engine " - "pathways." ); -static ConVar mod_touchalldata( "mod_touchalldata", "1", 0, "Touch model data during level startup" ); -static ConVar mod_forcetouchdata( "mod_forcetouchdata", "1", 0, "Forces all model file data into cache on model load." ); -ConVar mat_excludetextures( "mat_excludetextures", "0", FCVAR_CHEAT ); - -ConVar r_unloadlightmaps( "r_unloadlightmaps", "0", FCVAR_CHEAT ); -ConVar r_hunkalloclightmaps( "r_hunkalloclightmaps", "1" ); -extern ConVar r_lightcache_zbuffercache; - - -static ConVar mod_dynamicunloadtime( "mod_dynamicunloadtime", "150", FCVAR_HIDDEN | FCVAR_DONTRECORD ); -static ConVar mod_dynamicunloadtextures( "mod_dynamicunloadtex", "1", FCVAR_HIDDEN | FCVAR_DONTRECORD ); -static ConVar mod_dynamicloadpause( "mod_dynamicloadpause", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); -static ConVar mod_dynamicloadthrottle( "mod_dynamicloadthrottle", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); -static ConVar mod_dynamicloadspew( "mod_dynamicloadspew", "0", FCVAR_HIDDEN | FCVAR_DONTRECORD ); - -#define DynamicModelDebugMsg(...) ( mod_dynamicloadspew.GetBool() ? Msg(__VA_ARGS__) : (void)0 ) - - -bool g_bHunkAllocLightmaps; - -extern CGlobalVars g_ServerGlobalVariables; -extern IMaterial *g_materialEmpty; -extern ConVar r_rootlod; - -bool g_bLoadedMapHasBakedPropLighting = false; -bool g_bBakedPropLightingNoSeparateHDR = false; // Some maps only have HDR lighting on props, contained in the file for non-hdr light data - -double g_flAccumulatedModelLoadTime; -double g_flAccumulatedModelLoadTimeStudio; -double g_flAccumulatedModelLoadTimeStaticMesh; -double g_flAccumulatedModelLoadTimeBrush; -double g_flAccumulatedModelLoadTimeSprite; -double g_flAccumulatedModelLoadTimeVCollideSync; -double g_flAccumulatedModelLoadTimeVCollideAsync; -double g_flAccumulatedModelLoadTimeVirtualModel; -double g_flAccumulatedModelLoadTimeMaterialNamesOnly; - -//----------------------------------------------------------------------------- -// A dictionary used to store where to find game lump data in the .bsp file -//----------------------------------------------------------------------------- - -// Extended from the on-disk struct to include uncompressed size and stop propagation of bogus signed values -struct dgamelump_internal_t -{ - dgamelump_internal_t( dgamelump_t &other, unsigned int nCompressedSize ) - : id( other.id ) - , flags( other.flags ) - , version( other.version ) - , offset( Max( other.fileofs, 0 ) ) - , uncompressedSize( Max( other.filelen, 0 ) ) - , compressedSize( nCompressedSize ) - {} - GameLumpId_t id; - unsigned short flags; - unsigned short version; - unsigned int offset; - unsigned int uncompressedSize; - unsigned int compressedSize; -}; - -static CUtlVector< dgamelump_internal_t > g_GameLumpDict; -static char g_GameLumpFilename[128] = { 0 }; - -//void NotifyHunkBeginMapLoad( const char *pszMapName ) -//{ -// Hunk_OnMapStart( 32*1024*1024 ); -//} - - -// FIXME/TODO: Right now Host_FreeToLowMark unloads all models including studio -// models that have Cache_Alloc data, too. This needs to be fixed before shipping - -BEGIN_BYTESWAP_DATADESC( lump_t ) - DEFINE_FIELD( fileofs, FIELD_INTEGER ), - DEFINE_FIELD( filelen, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dheader_t ) - DEFINE_FIELD( ident, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), - DEFINE_FIELD( mapRevision, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -bool Model_LessFunc( FileNameHandle_t const &a, FileNameHandle_t const &b ) -{ - return a < b; -} - - - -//----------------------------------------------------------------------------- -// Purpose: Implements IModelLoader -//----------------------------------------------------------------------------- -class CModelLoader : public IModelLoader -{ -// Implement IModelLoader interface -public: - CModelLoader() : m_ModelPool( sizeof( model_t ), MAX_KNOWN_MODELS, CUtlMemoryPool::GROW_FAST, "CModelLoader::m_ModelPool" ), - m_Models( 0, 0, Model_LessFunc ) - { - } - - void Init( void ); - void Shutdown( void ); - - int GetCount( void ); - model_t *GetModelForIndex( int i ); - - // Look up name for model - const char *GetName( model_t const *model ); - - // Check cache for data, reload model if needed - void *GetExtraData( model_t *model ); - - int GetModelFileSize( char const *name ); - - // Finds the model, and loads it if it isn't already present. Updates reference flags - model_t *GetModelForName( const char *name, REFERENCETYPE referencetype ); - // Mark as referenced by name - model_t *ReferenceModel( const char *name, REFERENCETYPE referencetype ); - - // Unmasks the referencetype field for the model - void UnreferenceModel( model_t *model, REFERENCETYPE referencetype ); - // Unmasks the specified reference type across all models - void UnreferenceAllModels( REFERENCETYPE referencetype ); - // Set all models to last loaded on server count -1 - void ResetModelServerCounts(); - - // For any models with referencetype blank, frees all memory associated with the model - // and frees up the models slot - void UnloadUnreferencedModels( void ); - void PurgeUnusedModels( void ); - - bool Map_GetRenderInfoAllocated( void ); - void Map_SetRenderInfoAllocated( bool allocated ); - - virtual void Map_LoadDisplacements( model_t *pModel, bool bRestoring ); - - // Validate version/header of a .bsp file - bool Map_IsValid( char const *mapname, bool bQuiet = false ); - - virtual void RecomputeSurfaceFlags( model_t *mod ); - - virtual void Studio_ReloadModels( ReloadType_t reloadType ); - - void Print( void ); - - // Is a model loaded? - virtual bool IsLoaded( const model_t *mod ); - - virtual bool LastLoadedMapHasHDRLighting(void); - - void DumpVCollideStats(); - - // Returns the map model, otherwise NULL, no load or create - model_t *FindModelNoCreate( const char *pModelName ); - - // Finds the model, builds a model entry if not present - model_t *FindModel( const char *name ); - - modtype_t GetTypeFromName( const char *pModelName ); - - // start with -1, list terminates with -1 - int FindNext( int iIndex, model_t **ppModel ); - - virtual void UnloadModel( model_t *pModel ); - - virtual void ReloadFilesInList( IFileList *pFilesToReload ); - - virtual const char *GetActiveMapName( void ); - - // Called by app system once per frame to poll and update dynamic models - virtual void UpdateDynamicModels() { InternalUpdateDynamicModels(false); } - - // Called by server and client engine code to flush unreferenced dynamic models - virtual void FlushDynamicModels() { InternalUpdateDynamicModels(true); } - - // Called by server and client to force-unload dynamic models regardless of refcount! - virtual void ForceUnloadNonClientDynamicModels(); - - // Called by client code to load dynamic models, instead of GetModelForName. - virtual model_t *GetDynamicModel( const char *name, bool bClientOnly ); - - // Called by client code to query dynamic model state - virtual bool IsDynamicModelLoading( model_t *pModel, bool bClientOnly ); - - // Called by client code to refcount dynamic models - virtual void AddRefDynamicModel( model_t *pModel, bool bClientSideRef ); - virtual void ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ); - - // Called by client code or GetDynamicModel - virtual bool RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ); - - // Called by client code or IModelLoadCallback destructor - virtual void UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ); - - virtual void Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ); - - void DebugPrintDynamicModels(); - -// Internal types -private: - // TODO, flag these and allow for UnloadUnreferencedModels to check for allocation type - // so we don't have to flush all of the studio models when we free the hunk - enum - { - FALLOC_USESHUNKALLOC = (1<<31), - FALLOC_USESCACHEALLOC = (1<<30), - }; - -// Internal methods -private: - // Set reference flags and load model if it's not present already - model_t *LoadModel( model_t *model, REFERENCETYPE *referencetype ); - // Unload models ( won't unload referenced models if checkreferences is true ) - void UnloadAllModels( bool checkreference ); - void SetupSubModels( model_t *model, CUtlVector &list ); - - // World/map - void Map_LoadModel( model_t *mod ); - void Map_UnloadModel( model_t *mod ); - void Map_UnloadCubemapSamples( model_t *mod ); - - // World loading helper - void SetWorldModel( model_t *mod ); - void ClearWorldModel( void ); - bool IsWorldModelSet( void ); - int GetNumWorldSubmodels( void ); - - // Sprites - void Sprite_LoadModel( model_t *mod ); - void Sprite_UnloadModel( model_t *mod ); - - // Studio models - void Studio_LoadModel( model_t *mod, bool bTouchAllData ); - void Studio_UnloadModel( model_t *mod ); - - // Byteswap - int UpdateOrCreate( const char *pSourceName, char *pTargetName, int maxLen, bool bForce ); - - // Dynamic load queue - class CDynamicModelInfo; - void QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); - bool CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); - void UpdateDynamicModelLoadQueue(); - - void FinishDynamicModelLoadIfReady( CDynamicModelInfo *dyn, model_t *mod ); - - void InternalUpdateDynamicModels( bool bIgnoreUpdateTime ); - - // Internal data -private: - enum - { - MAX_KNOWN_MODELS = 1024, - }; - - struct ModelEntry_t - { - model_t *modelpointer; - }; - - CUtlMap< FileNameHandle_t, ModelEntry_t > m_Models; - - CUtlMemoryPool m_ModelPool; - - CUtlVector m_InlineModels; - - model_t *m_pWorldModel; - -public: // HACKHACK - worldbrushdata_t m_worldBrushData; - -private: - // local name of current loading model - char m_szLoadName[64]; - - bool m_bMapRenderInfoLoaded; - bool m_bMapHasHDRLighting; - - char m_szActiveMapName[64]; - - // Dynamic model support: - class CDynamicModelInfo - { - public: - enum { QUEUED = 0x01, LOADING = 0x02, CLIENTREADY = 0x04, SERVERLOADING = 0x08, ALLREADY = 0x10, INVALIDFLAG = 0x20 }; // flags - CDynamicModelInfo() : m_iRefCount(0), m_iClientRefCount(0), m_nLoadFlags(INVALIDFLAG), m_uLastTouchedMS_Div256(0) { } - int16 m_iRefCount; - int16 m_iClientRefCount; // also doublecounted in m_iRefCount - uint32 m_nLoadFlags : 8; - uint32 m_uLastTouchedMS_Div256 : 24; - CUtlVector< uintptr_t > m_Callbacks; // low bit = client only - }; - - CUtlHashtable< model_t * , CDynamicModelInfo > m_DynamicModels; - CUtlHashtable< uintptr_t , int > m_RegisteredDynamicCallbacks; - - // Dynamic model load queue - CUtlVector< model_t* > m_DynamicModelLoadQueue; - bool m_bDynamicLoadQueueHeadActive; -}; - -// Expose interface -static CModelLoader g_ModelLoader; -IModelLoader *modelloader = ( IModelLoader * )&g_ModelLoader; - -//----------------------------------------------------------------------------- -// Globals used by the CMapLoadHelper -//----------------------------------------------------------------------------- -static dheader_t s_MapHeader; -static FileHandle_t s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; -static char s_szLoadName[128]; -static char s_szMapName[128]; -static worldbrushdata_t *s_pMap = NULL; -static int s_nMapLoadRecursion = 0; -static CUtlBuffer s_MapBuffer; - -// Lump files are patches for a shipped map -// List of lump files found when map was loaded. Each entry is the lump file index for that lump id. -struct lumpfiles_t -{ - FileHandle_t file; - int lumpfileindex; - lumpfileheader_t header; -}; -static lumpfiles_t s_MapLumpFiles[ HEADER_LUMPS ]; - -CON_COMMAND( mem_vcollide, "Dumps the memory used by vcollides" ) -{ - g_ModelLoader.DumpVCollideStats(); -} - -//----------------------------------------------------------------------------- -// Returns the ref count for this bsp -//----------------------------------------------------------------------------- -int CMapLoadHelper::GetRefCount() -{ - return s_nMapLoadRecursion; -} - -//----------------------------------------------------------------------------- -// Setup a BSP loading context, maintains a ref count. -//----------------------------------------------------------------------------- -void CMapLoadHelper::Init( model_t *pMapModel, const char *loadname ) -{ - if ( ++s_nMapLoadRecursion > 1 ) - { - return; - } - - s_pMap = NULL; - s_szLoadName[ 0 ] = 0; - s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; - V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); - V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); - - if ( !pMapModel ) - { - V_strcpy_safe( s_szMapName, loadname ); - } - else - { - V_strcpy_safe( s_szMapName, pMapModel->strName ); - } - - s_MapFileHandle = g_pFileSystem->OpenEx( s_szMapName, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, IsX360() ? "GAME" : NULL ); - if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) - { - Host_Error( "CMapLoadHelper::Init, unable to open %s\n", s_szMapName ); - return; - } - - g_pFileSystem->Read( &s_MapHeader, sizeof( dheader_t ), s_MapFileHandle ); - if ( s_MapHeader.ident != IDBSPHEADER ) - { - g_pFileSystem->Close( s_MapFileHandle ); - s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; - Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); - return; - } - - if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) - { - g_pFileSystem->Close( s_MapFileHandle ); - s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; - Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, - s_MapHeader.version, BSPVERSION ); - return; - } - - V_strcpy_safe( s_szLoadName, loadname ); - - // Store map version, but only do it once so that the communication between the engine and Hammer isn't broken. The map version - // is incremented whenever a Hammer to Engine session is established so resetting the global map version each time causes a problem. - if ( 0 == g_ServerGlobalVariables.mapversion ) - { - g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; - } - -#ifndef SWDS - InitDLightGlobals( s_MapHeader.version ); -#endif - - s_pMap = &g_ModelLoader.m_worldBrushData; - - // lump_t fix for l4d2 maps - if (s_MapHeader.version == 21 && s_MapHeader.lumps[0].fileofs == 0) - { - DevMsg("Detected l4d2 bsp, fixing lump struct order for compatibility\n"); - - for (int iLump = 0; iLump < HEADER_LUMPS; iLump++) - { - lump_21_t lump21; - V_memcpy(&lump21, &s_MapHeader.lumps[iLump], sizeof(lump_t)); - - s_MapHeader.lumps[iLump].version = lump21.version; - s_MapHeader.lumps[iLump].filelen = lump21.filelen; - s_MapHeader.lumps[iLump].fileofs = lump21.fileofs; - } - } - -#if 0 - // XXX(johns): There are security issues with this system currently. sv_pure doesn't handle unexpected/mismatched - // lumps, so players can create lumps for maps not using them to wallhack/etc.. Currently unused, - // disabling until we have time to make a proper security pass. - if ( IsPC() ) - { - // Now find and open our lump files, and create the master list of them. - for ( int iIndex = 0; iIndex < MAX_LUMPFILES; iIndex++ ) - { - lumpfileheader_t lumpHeader; - char lumpfilename[MAX_PATH]; - - GenerateLumpFileName( s_szMapName, lumpfilename, MAX_PATH, iIndex ); - if ( !g_pFileSystem->FileExists( lumpfilename ) ) - break; - - // Open the lump file - FileHandle_t lumpFile = g_pFileSystem->Open( lumpfilename, "rb" ); - if ( lumpFile == FILESYSTEM_INVALID_HANDLE ) - { - Host_Error( "CMapLoadHelper::Init, failed to load lump file %s\n", lumpfilename ); - return; - } - - // Read the lump header - memset( &lumpHeader, 0, sizeof( lumpHeader ) ); - g_pFileSystem->Read( &lumpHeader, sizeof( lumpfileheader_t ), lumpFile ); - - if ( lumpHeader.lumpID >= 0 && lumpHeader.lumpID < HEADER_LUMPS ) - { - // We may find multiple lump files for the same lump ID. If so, - // close the earlier lump file, because the later one overwrites it. - if ( s_MapLumpFiles[lumpHeader.lumpID].file != FILESYSTEM_INVALID_HANDLE ) - { - g_pFileSystem->Close( s_MapLumpFiles[lumpHeader.lumpID].file ); - } - - s_MapLumpFiles[lumpHeader.lumpID].file = lumpFile; - s_MapLumpFiles[lumpHeader.lumpID].lumpfileindex = iIndex; - memcpy( &(s_MapLumpFiles[lumpHeader.lumpID].header), &lumpHeader, sizeof(lumpHeader) ); - } - else - { - Warning("Found invalid lump file '%s'. Lump Id: %d\n", lumpfilename, lumpHeader.lumpID ); - } - } - } -#endif -} - -//----------------------------------------------------------------------------- -// Setup a BSP loading context from a supplied buffer -//----------------------------------------------------------------------------- -void CMapLoadHelper::InitFromMemory( model_t *pMapModel, const void *pData, int nDataSize ) -{ - // valid for 360 only - // 360 has reorganized bsp format and no external lump files - Assert( IsX360() && pData && nDataSize ); - - if ( ++s_nMapLoadRecursion > 1 ) - { - return; - } - - s_pMap = NULL; - s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; - V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); - V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); - - V_strcpy_safe( s_szMapName, pMapModel->strName ); - V_FileBase( s_szMapName, s_szLoadName, sizeof( s_szLoadName ) ); - - s_MapBuffer.SetExternalBuffer( (void *)pData, nDataSize, nDataSize ); - - V_memcpy( &s_MapHeader, pData, sizeof( dheader_t ) ); - - if ( s_MapHeader.ident != IDBSPHEADER ) - { - Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); - return; - } - - if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) - { - Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, s_MapHeader.version, BSPVERSION ); - return; - } - - // Store map version - g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; - -#ifndef SWDS - InitDLightGlobals( s_MapHeader.version ); -#endif - - s_pMap = &g_ModelLoader.m_worldBrushData; - - // lump_t fix for l4d2 maps - if (s_MapHeader.version == 21 && s_MapHeader.lumps[0].fileofs == 0) - { - DevMsg("Detected l4d2 bsp, fixing lump struct order for compatibility\n"); - - for (int iLump = 0; iLump < HEADER_LUMPS; iLump++) - { - lump_21_t lump21; - V_memcpy(&lump21, &s_MapHeader.lumps[iLump], sizeof(lump_t)); - - s_MapHeader.lumps[iLump].version = lump21.version; - s_MapHeader.lumps[iLump].filelen = lump21.filelen; - s_MapHeader.lumps[iLump].fileofs = lump21.fileofs; - } - } -} - -//----------------------------------------------------------------------------- -// Shutdown a BSP loading context. -//----------------------------------------------------------------------------- -void CMapLoadHelper::Shutdown( void ) -{ - if ( --s_nMapLoadRecursion > 0 ) - { - return; - } - - if ( s_MapFileHandle != FILESYSTEM_INVALID_HANDLE ) - { - g_pFileSystem->Close( s_MapFileHandle ); - s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; - } - - if ( IsPC() ) - { - // Close our open lump files - for ( int i = 0; i < HEADER_LUMPS; i++ ) - { - if ( s_MapLumpFiles[i].file != FILESYSTEM_INVALID_HANDLE ) - { - g_pFileSystem->Close( s_MapLumpFiles[i].file ); - } - } - V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); - } - - s_szLoadName[ 0 ] = 0; - V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); - s_pMap = NULL; - - // discard from memory - if ( s_MapBuffer.Base() ) - { - free( s_MapBuffer.Base() ); - s_MapBuffer.SetExternalBuffer( NULL, 0, 0 ); - } -} - - -//----------------------------------------------------------------------------- -// Free the lighting lump (increases free memory during loading on 360) -//----------------------------------------------------------------------------- -void CMapLoadHelper::FreeLightingLump( void ) -{ - if ( IsX360() && ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) && s_MapBuffer.Base() ) - { - int lightingLump = LumpSize( LUMP_LIGHTING_HDR ) ? LUMP_LIGHTING_HDR : LUMP_LIGHTING; - // Should never have both lighting lumps on 360 - Assert( ( lightingLump == LUMP_LIGHTING ) || ( LumpSize( LUMP_LIGHTING ) == 0 ) ); - - if ( LumpSize( lightingLump ) ) - { - // Check that the lighting lump is the last one in the BSP - int lightingOffset = LumpOffset( lightingLump ); - for ( int i = 0;i < HEADER_LUMPS; i++ ) - { - if ( ( LumpOffset( i ) > lightingOffset ) && ( i != LUMP_PAKFILE ) ) - { - Warning( "CMapLoadHelper: Cannot free lighting lump (should be last before the PAK lump). Regenerate the .360.bsp file with the latest version of makegamedata." ); - return; - } - } - - // Flag the lighting chunk as gone from the BSP (principally, this sets 'filelen' to 0) - V_memset( &s_MapHeader.lumps[ lightingLump ], 0, sizeof( lump_t ) ); - - // Shrink the buffer to free up the space that was used by the lighting lump - void * shrunkBuffer = realloc( s_MapBuffer.Base(), lightingOffset ); - Assert( shrunkBuffer == s_MapBuffer.Base() ); // A shrink would surely never move!!! - s_MapBuffer.SetExternalBuffer( shrunkBuffer, lightingOffset, lightingOffset ); - } - } -} - - -//----------------------------------------------------------------------------- -// Returns the size of a particular lump without loading it... -//----------------------------------------------------------------------------- -int CMapLoadHelper::LumpSize( int lumpId ) -{ - // If we have a lump file for this lump, return its length instead - if ( IsPC() && s_MapLumpFiles[lumpId].file != FILESYSTEM_INVALID_HANDLE ) - { - return s_MapLumpFiles[lumpId].header.lumpLength; - } - - lump_t *pLump = &s_MapHeader.lumps[ lumpId ]; - Assert( pLump ); - - // all knowledge of compression is private, they expect and get the original size - int originalSize = s_MapHeader.lumps[lumpId].uncompressedSize; - if ( originalSize != 0 ) - { - return originalSize; - } - - return pLump->filelen; -} - -//----------------------------------------------------------------------------- -// Returns the offset of a particular lump without loading it... -//----------------------------------------------------------------------------- -int CMapLoadHelper::LumpOffset( int lumpID ) -{ - // If we have a lump file for this lump, return - // the offset to move past the lump file header. - if ( IsPC() && s_MapLumpFiles[lumpID].file != FILESYSTEM_INVALID_HANDLE ) - { - return s_MapLumpFiles[lumpID].header.lumpOffset; - } - - lump_t *pLump = &s_MapHeader.lumps[ lumpID ]; - Assert( pLump ); - - return pLump->fileofs; -} - -//----------------------------------------------------------------------------- -// Loads one element in a lump. -//----------------------------------------------------------------------------- -void CMapLoadHelper::LoadLumpElement( int nElemIndex, int nElemSize, void *pData ) -{ - if ( !nElemSize || !m_nLumpSize ) - { - return; - } - - // supply from memory - if ( nElemIndex * nElemSize + nElemSize <= m_nLumpSize ) - { - V_memcpy( pData, m_pData + nElemIndex * nElemSize, nElemSize ); - } - else - { - // out of range - Assert( 0 ); - } -} - - -//----------------------------------------------------------------------------- -// Loads one element in a lump. -//----------------------------------------------------------------------------- -void CMapLoadHelper::LoadLumpData( int offset, int size, void *pData ) -{ - if ( !size || !m_nLumpSize ) - { - return; - } - - if ( offset + size <= m_nLumpSize ) - { - V_memcpy( pData, m_pData + offset, size ); - } - else - { - // out of range - Assert( 0 ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : mapfile - -// lumpToLoad - -//----------------------------------------------------------------------------- -CMapLoadHelper::CMapLoadHelper( int lumpToLoad ) -{ - if ( lumpToLoad < 0 || lumpToLoad >= HEADER_LUMPS ) - { - Sys_Error( "Can't load lump %i, range is 0 to %i!!!", lumpToLoad, HEADER_LUMPS - 1 ); - } - - m_nLumpID = lumpToLoad; - m_nLumpSize = 0; - m_nLumpOffset = -1; - m_pData = NULL; - m_pRawData = NULL; - m_pUncompressedData = NULL; - - // Load raw lump from disk - lump_t *lump = &s_MapHeader.lumps[ lumpToLoad ]; - Assert( lump ); - - m_nLumpSize = lump->filelen; - m_nLumpOffset = lump->fileofs; - m_nLumpVersion = lump->version; - - FileHandle_t fileToUse = s_MapFileHandle; - - // If we have a lump file for this lump, use it instead - if ( IsPC() && s_MapLumpFiles[lumpToLoad].file != FILESYSTEM_INVALID_HANDLE ) - { - fileToUse = s_MapLumpFiles[lumpToLoad].file; - m_nLumpSize = s_MapLumpFiles[lumpToLoad].header.lumpLength; - m_nLumpOffset = s_MapLumpFiles[lumpToLoad].header.lumpOffset; - m_nLumpVersion = s_MapLumpFiles[lumpToLoad].header.lumpVersion; - - // Store off the lump file name - GenerateLumpFileName( s_szLoadName, m_szLumpFilename, MAX_PATH, s_MapLumpFiles[lumpToLoad].lumpfileindex ); - } - - if ( !m_nLumpSize ) - { - // this lump has no data - return; - } - - if ( s_MapBuffer.Base() ) - { - // bsp is in memory - m_pData = (unsigned char*)s_MapBuffer.Base() + m_nLumpOffset; - } - else - { - if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) - { - Sys_Error( "Can't load map from invalid handle!!!" ); - } - - unsigned nOffsetAlign, nSizeAlign, nBufferAlign; - g_pFileSystem->GetOptimalIOConstraints( fileToUse, &nOffsetAlign, &nSizeAlign, &nBufferAlign ); - - bool bTryOptimal = ( m_nLumpOffset % 4 == 0 ); // Don't return badly aligned data - unsigned int alignedOffset = m_nLumpOffset; - unsigned int alignedBytesToRead = ( ( m_nLumpSize ) ? m_nLumpSize : 1 ); - - if ( bTryOptimal ) - { - alignedOffset = AlignValue( ( alignedOffset - nOffsetAlign ) + 1, nOffsetAlign ); - alignedBytesToRead = AlignValue( ( m_nLumpOffset - alignedOffset ) + alignedBytesToRead, nSizeAlign ); - } - - m_pRawData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( fileToUse, alignedBytesToRead, alignedOffset ); - if ( !m_pRawData && m_nLumpSize ) - { - Sys_Error( "Can't load lump %i, allocation of %i bytes failed!!!", lumpToLoad, m_nLumpSize + 1 ); - } - - if ( m_nLumpSize ) - { - g_pFileSystem->Seek( fileToUse, alignedOffset, FILESYSTEM_SEEK_HEAD ); - g_pFileSystem->ReadEx( m_pRawData, alignedBytesToRead, alignedBytesToRead, fileToUse ); - m_pData = m_pRawData + ( m_nLumpOffset - alignedOffset ); - } - } - - if ( lump->uncompressedSize != 0 ) - { - // Handle compressed lump -- users of the class see the uncompressed data - AssertMsg( CLZMA::IsCompressed( m_pData ), - "Lump claims to be compressed but is not recognized as LZMA" ); - - m_nLumpSize = CLZMA::GetActualSize( m_pData ); - AssertMsg( lump->uncompressedSize == m_nLumpSize, - "Lump header disagrees with lzma header for compressed lump" ); - - m_pUncompressedData = (unsigned char *)malloc( m_nLumpSize ); - CLZMA::Uncompress( m_pData, m_pUncompressedData ); - - m_pData = m_pUncompressedData; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -CMapLoadHelper::~CMapLoadHelper( void ) -{ - if ( m_pUncompressedData ) - { - free( m_pUncompressedData ); - } - - if ( m_pRawData ) - { - g_pFileSystem->FreeOptimalReadBuffer( m_pRawData ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : model_t -//----------------------------------------------------------------------------- -worldbrushdata_t *CMapLoadHelper::GetMap( void ) -{ - Assert( s_pMap ); - return s_pMap; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -const char *CMapLoadHelper::GetMapName( void ) -{ - return s_szMapName; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : const char -//----------------------------------------------------------------------------- -char *CMapLoadHelper::GetLoadName( void ) -{ - // If we have a custom lump file for the lump this helper - // is loading, return it instead. - if ( IsPC() && s_MapLumpFiles[m_nLumpID].file != FILESYSTEM_INVALID_HANDLE ) - { - return m_szLumpFilename; - } - - return s_szLoadName; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : byte -//----------------------------------------------------------------------------- -byte *CMapLoadHelper::LumpBase( void ) -{ - return m_pData; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CMapLoadHelper::LumpSize() -{ - return m_nLumpSize; -} - -int CMapLoadHelper::LumpOffset() -{ - return m_nLumpOffset; -} - -int CMapLoadHelper::LumpVersion() const -{ - return m_nLumpVersion; -} - -void EnableHDR( bool bEnable ) -{ - if ( g_pMaterialSystemHardwareConfig->GetHDREnabled() == bEnable ) - return; - - g_pMaterialSystemHardwareConfig->SetHDREnabled( bEnable ); - - if ( IsX360() ) - { - // cannot do what the pc does and ditch resources, we're loading! - // can safely do the state update only, knowing that the state change won't affect 360 resources - ((MaterialSystem_Config_t *)g_pMaterialSystemConfig)->SetFlag( MATSYS_VIDCFG_FLAGS_ENABLE_HDR, bEnable ); - return; - } - - ShutdownWellKnownRenderTargets(); - InitWellKnownRenderTargets(); - - /// XXX(JohnS): This works around part of the terribleness the comments below discuss by performing - /// UpdateMaterialSystemConfig with the device offline, removing its need to do multiple re-uploads of - /// things. I am not positive my changes to allow that won't introduce terrible regressions or awaken - /// ancient bugs, hence the kill switch. - bool bUpdateOffline = mod_offline_hdr_switch.GetBool(); -#ifndef DEDICATED - extern void V_RenderVGuiOnly(); -#endif - - if ( bUpdateOffline ) - { -#ifndef DEDICATED - V_RenderVGuiOnly(); -#endif - materials->ReleaseResources(); - } - - // Grah. This is terrible. changin mat_hdr_enabled at the commandline - // will by definition break because the release/restore methods don't call - // ShutdownWellKnownRenderTargets/InitWellKnownRenderTargets. - // Also, this forces two alt-tabs, one for InitWellKnownRenderTargets, one - // for UpdateMaterialSystemConfig. - UpdateMaterialSystemConfig(); - - // Worse, since we need to init+shutdown render targets here, we can't - // rely on UpdateMaterialSystemConfig to release + reacquire resources - // because it could be called at any time. We have to precisely control - // when hdr is changed since this is the only time the code can handle it. - if ( !bUpdateOffline ) - { - materials->ReleaseResources(); - } - materials->ReacquireResources(); -#ifndef DEDICATED - if ( bUpdateOffline ) - { - V_RenderVGuiOnly(); - } -#endif -} - -//----------------------------------------------------------------------------- -// Determine feature flags -//----------------------------------------------------------------------------- -void Map_CheckFeatureFlags() -{ - g_bLoadedMapHasBakedPropLighting = false; - g_bBakedPropLightingNoSeparateHDR = false; - - if ( CMapLoadHelper::LumpSize( LUMP_MAP_FLAGS ) > 0 ) - { - CMapLoadHelper lh( LUMP_MAP_FLAGS ); - dflagslump_t flags_lump; - flags_lump = *( (dflagslump_t *)( lh.LumpBase() ) ); - - // check if loaded map has baked static prop lighting - g_bLoadedMapHasBakedPropLighting = - ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ) != 0 || - ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) != 0; - g_bBakedPropLightingNoSeparateHDR = - ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) == 0; - } -} - -//----------------------------------------------------------------------------- -// Parse the map header for HDR ability. Returns the presence of HDR data only, -// not the HDR enable state. -//----------------------------------------------------------------------------- -bool Map_CheckForHDR( model_t *pModel, const char *pLoadName ) -{ - // parse the map header only - CMapLoadHelper::Init( pModel, pLoadName ); - - bool bHasHDR = false; - if ( IsX360() ) - { - // If this is true, the 360 MUST use HDR, because the LDR data gets stripped out. - bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0; - } - else - { - // might want to also consider the game lumps GAMELUMP_DETAIL_PROP_LIGHTING_HDR - bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 && - CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0; - // Mod_GameLumpSize( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ) > 0 // fixme - } - if ( s_MapHeader.version >= 20 && CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) == 0 ) - { - // This lump only exists in version 20 and greater, so don't bother checking for it on earlier versions. - bHasHDR = false; - } - - bool bEnableHDR = ( IsX360() && bHasHDR ) || - ( bHasHDR && ( mat_hdr_level.GetInt() >= 2 ) && - ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) ); - - EnableHDR( bEnableHDR ); - - // this data really should have been in the header, but it isn't - // establish the features now, before the real bsp load commences - Map_CheckFeatureFlags(); - - CMapLoadHelper::Shutdown(); - - return bHasHDR; -} - -//----------------------------------------------------------------------------- -// Allocates, frees lighting data -//----------------------------------------------------------------------------- -static void AllocateLightingData( worldbrushdata_t *pBrushData, int nSize ) -{ - g_bHunkAllocLightmaps = ( !r_unloadlightmaps.GetBool() && r_hunkalloclightmaps.GetBool() ); - if ( g_bHunkAllocLightmaps ) - { - pBrushData->lightdata = (ColorRGBExp32 *)Hunk_Alloc( nSize, false ); - } - else - { - // Specifically *not* adding it to the hunk. - // If this malloc changes, also change the free in CacheAndUnloadLightmapData() - pBrushData->lightdata = (ColorRGBExp32 *)malloc( nSize ); - } - pBrushData->unloadedlightmaps = false; -} - -static void DeallocateLightingData( worldbrushdata_t *pBrushData ) -{ - if ( pBrushData && pBrushData->lightdata ) - { - if ( !g_bHunkAllocLightmaps ) - { - free( pBrushData->lightdata ); - } - pBrushData->lightdata = NULL; - } -} - -static int ComputeLightmapSize( dface_t *pFace, mtexinfo_t *pTexInfo ) -{ - bool bNeedsBumpmap = false; - if( pTexInfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) - { - bNeedsBumpmap = true; - } - - int lightstyles; - for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) - { - if ( pFace->styles[lightstyles] == 255 ) - break; - } - - int nLuxels = (pFace->m_LightmapTextureSizeInLuxels[0]+1) * (pFace->m_LightmapTextureSizeInLuxels[1]+1); - if( bNeedsBumpmap ) - { - return nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); - } - - return nLuxels * 4 * lightstyles; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadLighting( CMapLoadHelper &lh ) -{ - if ( !lh.LumpSize() ) - { - lh.GetMap()->lightdata = NULL; - return; - } - - Assert( lh.LumpSize() % sizeof( ColorRGBExp32 ) == 0 ); - Assert ( lh.LumpVersion() != 0 ); - - AllocateLightingData( lh.GetMap(), lh.LumpSize() ); - memcpy( lh.GetMap()->lightdata, lh.LumpBase(), lh.LumpSize()); - - if ( IsX360() ) - { - // Free the lighting lump, to increase the amount of memory free during the rest of loading - CMapLoadHelper::FreeLightingLump(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadWorldlights( CMapLoadHelper &lh, bool bIsHDR ) -{ - lh.GetMap()->shadowzbuffers = NULL; - if (!lh.LumpSize()) - { - lh.GetMap()->numworldlights = 0; - lh.GetMap()->worldlights = NULL; - return; - } - // dworldlight_t fix for previous bsp versions - if (s_MapHeader.version < BSPVERSION) - { - DevMsg("Detected bsp version lower than 21, fixing dworldlight_t struct order for compatibility\n"); - - lh.GetMap()->numworldlights = lh.LumpSize() / (sizeof(dworldlight_t) - sizeof(Vector)); - lh.GetMap()->worldlights = (dworldlight_t*)Hunk_AllocName(lh.GetMap()->numworldlights * sizeof(dworldlight_t), va("%s [%s]", lh.GetLoadName(), "worldlights")); - for (int iLight = 0; iLight < lh.GetMap()->numworldlights; iLight++) - { - memcpy(&lh.GetMap()->worldlights[iLight], lh.LumpBase() + iLight * (sizeof(dworldlight_t) - sizeof(Vector)), sizeof(Vector) * 3); - lh.GetMap()->worldlights[iLight].shadow_cast_offset = lh.GetMap()->worldlights[iLight].origin; - memcpy((byte*)&lh.GetMap()->worldlights[iLight] + sizeof(Vector) * 4, lh.LumpBase() + iLight * (sizeof(dworldlight_t) - sizeof(Vector)) + sizeof(Vector) * 3, sizeof(dworldlight_t) - sizeof(Vector) * 4); - } - } - else - { - lh.GetMap()->numworldlights = lh.LumpSize() / sizeof(dworldlight_t); - lh.GetMap()->worldlights = (dworldlight_t*)Hunk_AllocName(lh.LumpSize(), va("%s [%s]", lh.GetLoadName(), "worldlights")); - memcpy(lh.GetMap()->worldlights, lh.LumpBase(), lh.LumpSize()); - } -#if !defined( SWDS ) - if ( r_lightcache_zbuffercache.GetInt() ) - { - size_t zbufSize = lh.GetMap()->numworldlights * sizeof( lightzbuffer_t ); - lh.GetMap()->shadowzbuffers = ( lightzbuffer_t *) Hunk_AllocName( zbufSize, va( "%s [%s]", lh.GetLoadName(), "shadowzbuffers" ) ); - memset( lh.GetMap()->shadowzbuffers, 0, zbufSize ); // mark empty - } -#endif - - // Fixup for backward compatability - for ( int i = 0; i < lh.GetMap()->numworldlights; i++ ) - { - if( lh.GetMap()->worldlights[i].type == emit_spotlight) - { - if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && - (lh.GetMap()->worldlights[i].linear_attn == 0.0) && - (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) - { - lh.GetMap()->worldlights[i].quadratic_attn = 1.0; - } - - if (lh.GetMap()->worldlights[i].exponent == 0.0) - lh.GetMap()->worldlights[i].exponent = 1.0; - } - else if( lh.GetMap()->worldlights[i].type == emit_point) - { - // To match earlier lighting, use quadratic... - if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && - (lh.GetMap()->worldlights[i].linear_attn == 0.0) && - (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) - { - lh.GetMap()->worldlights[i].quadratic_attn = 1.0; - } - } - - // I replaced the cuttoff_dot field (which took a value from 0 to 1) - // with a max light radius. Radius of less than 1 will never happen, - // so I can get away with this. When I set radius to 0, it'll - // run the old code which computed a radius - if (lh.GetMap()->worldlights[i].radius < 1) - { - lh.GetMap()->worldlights[i].radius = ComputeLightRadius( &lh.GetMap()->worldlights[i], bIsHDR ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadVertices( void ) -{ - dvertex_t *in; - mvertex_t *out; - int i, count; - - CMapLoadHelper lh( LUMP_VERTEXES ); - - in = (dvertex_t *)lh.LumpBase(); - if ( lh.LumpSize() % sizeof(*in) ) - { - Host_Error( "Mod_LoadVertices: funny lump size in %s", lh.GetMapName() ); - } - count = lh.LumpSize() / sizeof(*in); - out = (mvertex_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "vertexes" ) ); - - lh.GetMap()->vertexes = out; - lh.GetMap()->numvertexes = count; - - for ( i=0 ; iposition[0] = in->point[0]; - out->position[1] = in->point[1]; - out->position[2] = in->point[2]; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : mins - -// maxs - -// Output : float -//----------------------------------------------------------------------------- -static float RadiusFromBounds (Vector& mins, Vector& maxs) -{ - int i; - Vector corner; - - for (i=0 ; i<3 ; i++) - { - corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); - } - - return VectorLength( corner ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadSubmodels( CUtlVector &submodelList ) -{ - dmodel_t *in; - int i, j, count; - - CMapLoadHelper lh( LUMP_MODELS ); - - in = (dmodel_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error("Mod_LoadSubmodels: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - - submodelList.SetCount( count ); - lh.GetMap()->numsubmodels = count; - - for ( i=0 ; imins[j] - 1; - submodelList[i].maxs[j] = in->maxs[j] + 1; - submodelList[i].origin[j] = in->origin[j]; - } - submodelList[i].radius = RadiusFromBounds (submodelList[i].mins, submodelList[i].maxs); - submodelList[i].headnode = in->headnode; - submodelList[i].firstface = in->firstface; - submodelList[i].numfaces = in->numfaces; - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : medge_t *Mod_LoadEdges -//----------------------------------------------------------------------------- -medge_t *Mod_LoadEdges ( void ) -{ - dedge_t *in; - medge_t *out; - int i, count; - - CMapLoadHelper lh( LUMP_EDGES ); - - in = (dedge_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadEdges: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - medge_t *pedges = new medge_t[count]; - - out = pedges; - - for ( i=0 ; iv[0] = in->v[0]; - out->v[1] = in->v[1]; - } - - // delete this in the loader - return pedges; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadOcclusion( void ) -{ - CMapLoadHelper lh( LUMP_OCCLUSION ); - - worldbrushdata_t *b = lh.GetMap(); - b->numoccluders = 0; - b->occluders = NULL; - b->numoccluderpolys = 0; - b->occluderpolys = NULL; - b->numoccludervertindices = 0; - b->occludervertindices = NULL; - - if ( !lh.LumpSize() ) - { - return; - } - - CUtlBuffer buf( lh.LumpBase(), lh.LumpSize(), CUtlBuffer::READ_ONLY ); - - switch( lh.LumpVersion() ) - { - case LUMP_OCCLUSION_VERSION: - { - b->numoccluders = buf.GetInt(); - if (b->numoccluders) - { - int nSize = b->numoccluders * sizeof(doccluderdata_t); - b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); - buf.Get( b->occluders, nSize ); - } - - b->numoccluderpolys = buf.GetInt(); - if (b->numoccluderpolys) - { - int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); - b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); - buf.Get( b->occluderpolys, nSize ); - } - - b->numoccludervertindices = buf.GetInt(); - if (b->numoccludervertindices) - { - int nSize = b->numoccludervertindices * sizeof(int); - b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); - buf.Get( b->occludervertindices, nSize ); - } - } - break; - - case 1: - { - b->numoccluders = buf.GetInt(); - if (b->numoccluders) - { - int nSize = b->numoccluders * sizeof(doccluderdata_t); - b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); - - doccluderdataV1_t temp; - for ( int i = 0; i < b->numoccluders; ++i ) - { - buf.Get( &temp, sizeof(doccluderdataV1_t) ); - memcpy( &b->occluders[i], &temp, sizeof(doccluderdataV1_t) ); - b->occluders[i].area = 1; - } - } - - b->numoccluderpolys = buf.GetInt(); - if (b->numoccluderpolys) - { - int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); - b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); - buf.Get( b->occluderpolys, nSize ); - } - - b->numoccludervertindices = buf.GetInt(); - if (b->numoccludervertindices) - { - int nSize = b->numoccludervertindices * sizeof(int); - b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); - buf.Get( b->occludervertindices, nSize ); - } - } - break; - - case 0: - break; - - default: - Host_Error("Invalid occlusion lump version!\n"); - break; - } -} - - - -// UNDONE: Really, it's stored 2 times because the texture system keeps a -// copy of the name too. I guess we'll get rid of this when we have a material -// system that works without a graphics context. At that point, everyone can -// reference the name in the material, or just the material itself. -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadTexdata( void ) -{ - // Don't bother loading these again; they're already stored in the collision model - // which is guaranteed to be loaded at this point - s_pMap->numtexdata = GetCollisionBSPData()->numtextures; - s_pMap->texdata = GetCollisionBSPData()->map_surfaces.Base(); -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadTexinfo( void ) -{ - texinfo_t *in; - mtexinfo_t *out; - int i, j, count; - // UNDONE: Fix this - - CMapLoadHelper lh( LUMP_TEXINFO ); - - in = (texinfo_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadTexinfo: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mtexinfo_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "texinfo" ) ); - - s_pMap->texinfo = out; - s_pMap->numtexinfo = count; - - bool loadtextures = mat_loadtextures.GetBool(); - - for ( i=0 ; itextureVecsTexelsPerWorldUnits[j][k] = in->textureVecsTexelsPerWorldUnits[j][k]; - out->lightmapVecsLuxelsPerWorldUnits[j][k] = in->lightmapVecsLuxelsPerWorldUnits[j][k] ; - } - } - - // assume that the scale is the same on both s and t. - out->luxelsPerWorldUnit = VectorLength( out->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D() ); - out->worldUnitsPerLuxel = 1.0f / out->luxelsPerWorldUnit; - - out->flags = in->flags; - out->texinfoFlags = 0; - - if ( loadtextures ) - { - if ( in->texdata >= 0 ) - { - out->material = GL_LoadMaterial( lh.GetMap()->texdata[ in->texdata ].name, TEXTURE_GROUP_WORLD ); - } - else - { - DevMsg( "Mod_LoadTexinfo: texdata < 0 (index==%i/%i)\n", i, count ); - out->material = NULL; - } - if ( !out->material ) - { - out->material = g_materialEmpty; - g_materialEmpty->IncrementReferenceCount(); - } - } - else - { - out->material = g_materialEmpty; - g_materialEmpty->IncrementReferenceCount(); - } - } -} - -// code to scan the lightmaps for empty lightstyles -static void LinearToGamma( unsigned char *pDstRGB, const float *pSrcRGB ) -{ - pDstRGB[0] = LinearToScreenGamma( pSrcRGB[0] ); - pDstRGB[1] = LinearToScreenGamma( pSrcRGB[1] ); - pDstRGB[2] = LinearToScreenGamma( pSrcRGB[2] ); -} - -static void CheckSurfaceLighting( SurfaceHandle_t surfID, worldbrushdata_t *pBrushData ) -{ -#if !defined( SWDS ) - host_state.worldbrush = pBrushData; - msurfacelighting_t *pLighting = SurfaceLighting( surfID, pBrushData ); - - if( !pLighting->m_pSamples ) - return; - - int smax = ( pLighting->m_LightmapExtents[0] ) + 1; - int tmax = ( pLighting->m_LightmapExtents[1] ) + 1; - int offset = smax * tmax; - if ( SurfHasBumpedLightmaps( surfID ) ) - { - offset *= ( NUM_BUMP_VECTS + 1 ); - } - - - // how many lightmaps does this surface have? - int maxLightmapIndex = 0; - for (int maps = 1 ; maps < MAXLIGHTMAPS && pLighting->m_nStyles[maps] != 255 ; ++maps) - { - maxLightmapIndex = maps; - } - - if ( maxLightmapIndex < 1 ) - return; - - // iterate and test each lightmap - for ( int maps = maxLightmapIndex; maps != 0; maps-- ) - { - ColorRGBExp32 *pLightmap = pLighting->m_pSamples + (maps * offset); - float maxLen = -1; - Vector maxLight; - maxLight.Init(); - for ( int i = 0; i < offset; i++ ) - { - Vector c; - ColorRGBExp32ToVector( pLightmap[i], c ); - if ( c.Length() > maxLen ) - { - maxLight = c; - maxLen = c.Length(); - } - } - unsigned char color[4]; - LinearToGamma( color, maxLight.Base() ); - const int minLightVal = 1; - if ( color[0] <= minLightVal && color[1] <= minLightVal && color[2] <= minLightVal ) - { - // found a lightmap that is too dark, remove it and shift over the subsequent maps/styles - for ( int i = maps; i < maxLightmapIndex; i++ ) - { - ColorRGBExp32 *pLightmapOverwrite = pLighting->m_pSamples + (i * offset); - memcpy( pLightmapOverwrite, pLightmapOverwrite+offset, offset * sizeof(*pLightmapOverwrite) ); - pLighting->m_nStyles[i] = pLighting->m_nStyles[i+1]; - } - // mark end lightstyle as removed, decrement max index - pLighting->m_nStyles[maxLightmapIndex] = 255; - maxLightmapIndex--; - } - } - // we removed all of the lightstyle maps so clear the flag - if ( maxLightmapIndex == 0 ) - { - surfID->flags &= ~SURFDRAW_HASLIGHTSYTLES; - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *s - -// Output : void CalcSurfaceExtents -//----------------------------------------------------------------------------- -static void CalcSurfaceExtents ( CMapLoadHelper& lh, SurfaceHandle_t surfID ) -{ - float textureMins[2], textureMaxs[2], val; - int i,j, e; - mvertex_t *v; - mtexinfo_t *tex; - int bmins[2], bmaxs[2]; - - textureMins[0] = textureMins[1] = 999999; - textureMaxs[0] = textureMaxs[1] = -99999; - - worldbrushdata_t *pBrushData = lh.GetMap(); - tex = MSurf_TexInfo( surfID, pBrushData ); - - for (i=0 ; ivertindices[MSurf_FirstVertIndex( surfID )+i]; - v = &pBrushData->vertexes[e]; - - for (j=0 ; j<2 ; j++) - { - val = v->position[0] * tex->textureVecsTexelsPerWorldUnits[j][0] + - v->position[1] * tex->textureVecsTexelsPerWorldUnits[j][1] + - v->position[2] * tex->textureVecsTexelsPerWorldUnits[j][2] + - tex->textureVecsTexelsPerWorldUnits[j][3]; - if (val < textureMins[j]) - textureMins[j] = val; - if (val > textureMaxs[j]) - textureMaxs[j] = val; - } - } - - for (i=0 ; i<2 ; i++) - { - if( MSurf_LightmapExtents( surfID, pBrushData )[i] == 0 && !MSurf_Samples( surfID, pBrushData ) ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; - } - - bmins[i] = Float2Int( textureMins[i] ); - bmaxs[i] = Ceil2Int( textureMaxs[i] ); - MSurf_TextureMins( surfID, pBrushData )[i] = bmins[i]; - MSurf_TextureExtents( surfID, pBrushData )[i] = ( bmaxs[i] - bmins[i] ); - - if ( !(tex->flags & SURF_NOLIGHT) && MSurf_LightmapExtents( surfID, pBrushData )[i] > MSurf_MaxLightmapSizeWithBorder( surfID ) ) - { - Sys_Error ("Bad surface extents on texture %s", tex->material->GetName() ); - } - } - CheckSurfaceLighting( surfID, pBrushData ); -} - -//----------------------------------------------------------------------------- -// Input : *pModel - -// *pLump - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadVertNormals( void ) -{ - CMapLoadHelper lh( LUMP_VERTNORMALS ); - - // get a pointer to the vertex normal data. - Vector *pVertNormals = ( Vector * )lh.LumpBase(); - - // - // verify vertnormals data size - // - if( lh.LumpSize() % sizeof( *pVertNormals ) ) - Host_Error( "Mod_LoadVertNormals: funny lump size in %s!\n", lh.GetMapName() ); - - int count = lh.LumpSize() / sizeof(*pVertNormals); - Vector *out = (Vector *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormals" ) ); - memcpy( out, pVertNormals, lh.LumpSize() ); - - lh.GetMap()->vertnormals = out; - lh.GetMap()->numvertnormals = count; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadVertNormalIndices( void ) -{ - CMapLoadHelper lh( LUMP_VERTNORMALINDICES ); - - // get a pointer to the vertex normal data. - unsigned short *pIndices = ( unsigned short * )lh.LumpBase(); - - int count = lh.LumpSize() / sizeof(*pIndices); - unsigned short *out = (unsigned short *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormalindices" ) ); - memcpy( out, pIndices, lh.LumpSize() ); - - lh.GetMap()->vertnormalindices = out; - lh.GetMap()->numvertnormalindices = count; - - // OPTIMIZE: Water surfaces don't need vertex normals? - int normalIndex = 0; - for( int i = 0; i < lh.GetMap()->numsurfaces; i++ ) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, lh.GetMap() ); - MSurf_FirstVertNormal( surfID, lh.GetMap() ) = normalIndex; - normalIndex += MSurf_VertCount( surfID ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadPrimitives( void ) -{ - dprimitive_t *in; - mprimitive_t *out; - int i, count; - - CMapLoadHelper lh( LUMP_PRIMITIVES ); - - in = (dprimitive_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadPrimitives: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mprimitive_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primitives" ) ); - memset( out, 0, count * sizeof( mprimitive_t ) ); - - lh.GetMap()->primitives = out; - lh.GetMap()->numprimitives = count; - for ( i=0 ; ifirstIndex = in->firstIndex; - out->firstVert = in->firstVert; - out->indexCount = in->indexCount; - out->type = in->type; - out->vertCount = in->vertCount; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadPrimVerts( void ) -{ - dprimvert_t *in; - mprimvert_t *out; - int i, count; - - CMapLoadHelper lh( LUMP_PRIMVERTS ); - - in = (dprimvert_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadPrimVerts: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mprimvert_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primverts" ) ); - memset( out, 0, count * sizeof( mprimvert_t ) ); - - lh.GetMap()->primverts = out; - lh.GetMap()->numprimverts = count; - for ( i=0 ; ipos = in->pos; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadPrimIndices( void ) -{ - unsigned short *in; - unsigned short *out; - int count; - - CMapLoadHelper lh( LUMP_PRIMINDICES ); - - in = (unsigned short *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadPrimIndices: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va("%s [%s]", lh.GetLoadName(), "primindices" ) ); - memset( out, 0, count * sizeof( unsigned short ) ); - - lh.GetMap()->primindices = out; - lh.GetMap()->numprimindices = count; - - memcpy( out, in, count * sizeof( unsigned short ) ); -} - - -// This allocates memory for a lump and copies the lump data in. -void Mod_LoadLump( - model_t *loadmodel, - int iLump, - char *loadname, - int elementSize, - void **ppData, - int *nElements ) -{ - CMapLoadHelper lh( iLump ); - - if ( lh.LumpSize() % elementSize ) - { - Host_Error( "Mod_LoadLump: funny lump size in %s", loadmodel->strName.String() ); - } - - // How many elements? - *nElements = lh.LumpSize() / elementSize; - - // Make room for the data and copy the data in. - *ppData = Hunk_AllocName( lh.LumpSize(), loadname ); - memcpy( *ppData, lh.LumpBase(), lh.LumpSize() ); -} - - -//----------------------------------------------------------------------------- -// Sets up the msurfacelighting_t structure -//----------------------------------------------------------------------------- -bool Mod_LoadSurfaceLightingV1( msurfacelighting_t *pLighting, dface_t *in, ColorRGBExp32 *pBaseLightData ) -{ - // Get lightmap extents from the file. - pLighting->m_LightmapExtents[0] = in->m_LightmapTextureSizeInLuxels[0]; - pLighting->m_LightmapExtents[1] = in->m_LightmapTextureSizeInLuxels[1]; - pLighting->m_LightmapMins[0] = in->m_LightmapTextureMinsInLuxels[0]; - pLighting->m_LightmapMins[1] = in->m_LightmapTextureMinsInLuxels[1]; - - int i = in->lightofs; - if ( (i == -1) || (!pBaseLightData) ) - { - pLighting->m_pSamples = NULL; - - // Can't have *any* lightstyles if we have no samples.... - for ( i=0; im_nStyles[i] = 255; - } - } - else - { - pLighting->m_pSamples = (ColorRGBExp32 *)( ((byte *)pBaseLightData) + i ); - - for (i=0 ; im_nStyles[i] = in->styles[i]; - } - } - - return ((pLighting->m_nStyles[0] != 0) && (pLighting->m_nStyles[0] != 255)) || (pLighting->m_nStyles[1] != 255); -} - -void *Hunk_AllocNameAlignedClear_( int size, int alignment, const char *pHunkName ) -{ - Assert(IsPowerOfTwo(alignment)); - void *pMem = Hunk_AllocName( alignment + size, pHunkName ); - memset( pMem, 0, size + alignment ); - pMem = (void *)( ( ( ( unsigned long )pMem ) + (alignment-1) ) & ~(alignment-1) ); - - return pMem; -} - -// Allocates a block of T from the hunk. Aligns as specified and clears the memory -template< typename T > -T *Hunk_AllocNameAlignedClear( int count, int alignment, const char *pHunkName ) -{ - return (T *)Hunk_AllocNameAlignedClear_( alignment + count * sizeof(T), alignment, pHunkName ); -} -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadFaces( void ) -{ - dface_t *in; - int count, surfnum; - int planenum; - int ti, di; - - int face_lump_to_load = LUMP_FACES; - if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && - CMapLoadHelper::LumpSize( LUMP_FACES_HDR ) > 0 ) - { - face_lump_to_load = LUMP_FACES_HDR; - } - CMapLoadHelper lh( face_lump_to_load ); - - in = (dface_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadFaces: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - - // align these allocations - // If you trip one of these, you need to rethink the alignment of the struct - Assert( sizeof(msurface1_t) == 16 ); - Assert( sizeof(msurface2_t) == 32 ); - Assert( sizeof(msurfacelighting_t) == 32 ); - - msurface1_t *out1 = Hunk_AllocNameAlignedClear< msurface1_t >( count, 16, va( "%s [%s]", lh.GetLoadName(), "surface1" ) ); - msurface2_t *out2 = Hunk_AllocNameAlignedClear< msurface2_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surface2" ) ); - - msurfacelighting_t *pLighting = Hunk_AllocNameAlignedClear< msurfacelighting_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surfacelighting" ) ); - - lh.GetMap()->surfaces1 = out1; - lh.GetMap()->surfaces2 = out2; - lh.GetMap()->surfacelighting = pLighting; - lh.GetMap()->surfacenormals = Hunk_AllocNameAlignedClear< msurfacenormal_t >( count, 2, va( "%s [%s]", lh.GetLoadName(), "surfacenormal" ) ); - lh.GetMap()->numsurfaces = count; - - worldbrushdata_t *pBrushData = lh.GetMap(); - - for ( surfnum=0 ; surfnumfirstedge; - - int vertCount = in->numedges; - MSurf_Flags( surfID ) = 0; - Assert( vertCount <= 255 ); - MSurf_SetVertCount( surfID, vertCount ); - - planenum = in->planenum; - - if ( in->onNode ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NODE; - } - if ( in->side ) - { - MSurf_Flags( surfID ) |= SURFDRAW_PLANEBACK; - } - - out2->plane = lh.GetMap()->planes + planenum; - - ti = in->texinfo; - if (ti < 0 || ti >= lh.GetMap()->numtexinfo) - { - Host_Error( "Mod_LoadFaces: bad texinfo number" ); - } - surfID->texinfo = ti; - surfID->m_bDynamicShadowsEnabled = in->AreDynamicShadowsEnabled(); - mtexinfo_t *pTex = lh.GetMap()->texinfo + ti; - - // big hack! - if ( !pTex->material ) - { - pTex->material = g_materialEmpty; - g_materialEmpty->IncrementReferenceCount(); - } - - // lighting info - if ( Mod_LoadSurfaceLightingV1( pLighting, in, lh.GetMap()->lightdata ) ) - { - MSurf_Flags( surfID ) |= SURFDRAW_HASLIGHTSYTLES; - } - - // set the drawing flags flag - if ( pTex->flags & SURF_NOLIGHT ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; - } - - if ( pTex->flags & SURF_NOSHADOWS ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NOSHADOWS; - } - - if ( pTex->flags & SURF_WARP ) - { - MSurf_Flags( surfID ) |= SURFDRAW_WATERSURFACE; - } - - if ( pTex->flags & SURF_SKY ) - { - MSurf_Flags( surfID ) |= SURFDRAW_SKY; - } - - di = in->dispinfo; - out2->pDispInfo = NULL; - if( di != -1 ) - { -// out->origSurfaceID = in->origFace; - MSurf_Flags( surfID ) |= SURFDRAW_HAS_DISP; - } - else - { - // non-displacement faces shouldn't come out of VBSP if they have nodraw. - Assert( !(pTex->flags & SURF_NODRAW) ); - - out1->prims.numPrims = in->GetNumPrims(); - out1->prims.firstPrimID = in->firstPrimID; - if ( in->GetNumPrims() ) - { - MSurf_Flags( surfID ) |= SURFDRAW_HAS_PRIMS; - mprimitive_t *pPrim = &pBrushData->primitives[in->firstPrimID]; - if ( pPrim->vertCount > 0 ) - { - MSurf_Flags( surfID ) |= SURFDRAW_DYNAMIC; - } - } - } - - // No shadows on the surface to start with - out2->m_ShadowDecals = SHADOW_DECAL_HANDLE_INVALID; - out2->decals = WORLD_DECAL_HANDLE_INVALID; - - // No overlays on the surface to start with - out2->m_nFirstOverlayFragment = OVERLAY_FRAGMENT_INVALID; - - CalcSurfaceExtents( lh, surfID ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *node - -// *parent - -// Output : void Mod_SetParent -//----------------------------------------------------------------------------- -void Mod_SetParent (mnode_t *node, mnode_t *parent) -{ - node->parent = parent; - if (node->contents >= 0) - return; - Mod_SetParent (node->children[0], node); - Mod_SetParent (node->children[1], node); -} - - -//----------------------------------------------------------------------------- -// Mark an entire subtree as being too small to bother with -//----------------------------------------------------------------------------- -static void MarkSmallNode( mnode_t *node ) -{ - if (node->contents >= 0) - return; - node->contents = -2; - MarkSmallNode (node->children[0]); - MarkSmallNode (node->children[1]); -} - -static void CheckSmallVolumeDifferences( mnode_t *pNode, const Vector &parentSize ) -{ - if (pNode->contents >= 0) - return; - - Vector delta; - VectorSubtract( parentSize, pNode->m_vecHalfDiagonal, delta ); - - if ((delta.x < 5) && (delta.y < 5) && (delta.z < 5)) - { - pNode->contents = -3; - CheckSmallVolumeDifferences( pNode->children[0], parentSize ); - CheckSmallVolumeDifferences( pNode->children[1], parentSize ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadNodes( void ) -{ - Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); - int i, j, count, p; - dnode_t *in; - mnode_t *out; - - CMapLoadHelper lh( LUMP_NODES ); - - in = (dnode_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadNodes: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mnode_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "nodes" ) ); - - lh.GetMap()->nodes = out; - lh.GetMap()->numnodes = count; - - for ( i=0 ; imins[j]; - maxs[j] = in->maxs[j]; - } - - VectorAdd( mins, maxs, out->m_vecCenter ); - out->m_vecCenter *= 0.5f; - VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); - - p = in->planenum; - out->plane = lh.GetMap()->planes + p; - - out->firstsurface = in->firstface; - out->numsurfaces = in->numfaces; - out->area = in->area; - out->contents = -1; // differentiate from leafs - - for (j=0 ; j<2 ; j++) - { - p = in->children[j]; - if (p >= 0) - out->children[j] = lh.GetMap()->nodes + p; - else - out->children[j] = (mnode_t *)(lh.GetMap()->leafs + (-1 - p)); - } - } - - Mod_SetParent (lh.GetMap()->nodes, NULL); // sets nodes and leafs - - // Check for small-area parents... no culling below them... - mnode_t *pNode = lh.GetMap()->nodes; - for ( i=0 ; icontents == -1) - { - if ((pNode->m_vecHalfDiagonal.x <= 50) && (pNode->m_vecHalfDiagonal.y <= 50) && - (pNode->m_vecHalfDiagonal.z <= 50)) - { - // Mark all children as being too small to bother with... - MarkSmallNode( pNode->children[0] ); - MarkSmallNode( pNode->children[1] ); - } - else - { - CheckSmallVolumeDifferences( pNode->children[0], pNode->m_vecHalfDiagonal ); - CheckSmallVolumeDifferences( pNode->children[1], pNode->m_vecHalfDiagonal ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadLeafs_Version_0( CMapLoadHelper &lh ) -{ - Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); - dleaf_version_0_t *in; - mleaf_t *out; - int i, j, count, p; - - in = (dleaf_version_0_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); - - lh.GetMap()->leafs = out; - lh.GetMap()->numleafs = count; - - // one sample per leaf - lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); - lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); - mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; - mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; - - for ( i=0 ; imins[j]; - maxs[j] = in->maxs[j]; - } - - VectorAdd( mins, maxs, out->m_vecCenter ); - out->m_vecCenter *= 0.5f; - VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); - - pTable[i].ambientSampleCount = 1; - pTable[i].firstAmbientSample = i; - pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; - pSamples[i].pad = 0; - Q_memcpy( &pSamples[i].cube, &in->m_AmbientLighting, sizeof(pSamples[i].cube) ); - - - p = in->contents; - out->contents = p; - - out->cluster = in->cluster; - out->area = in->area; - out->flags = in->flags; -/* - out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; -*/ - out->firstmarksurface = in->firstleafface; - out->nummarksurfaces = in->numleaffaces; - out->parent = NULL; - - out->dispCount = 0; - - out->leafWaterDataID = in->leafWaterDataID; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadLeafs_Version_1( CMapLoadHelper &lh, CMapLoadHelper &ambientLightingLump, CMapLoadHelper &ambientLightingTable ) -{ - Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); - dleaf_t *in; - mleaf_t *out; - int i, j, count, p; - - in = (dleaf_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); - - lh.GetMap()->leafs = out; - lh.GetMap()->numleafs = count; - - if ( ambientLightingLump.LumpVersion() != LUMP_LEAF_AMBIENT_LIGHTING_VERSION || ambientLightingTable.LumpSize() == 0 ) - { - // convert from previous version - CompressedLightCube *inLightCubes = NULL; - if ( ambientLightingLump.LumpSize() ) - { - inLightCubes = ( CompressedLightCube * )ambientLightingLump.LumpBase(); - Assert( ambientLightingLump.LumpSize() % sizeof( CompressedLightCube ) == 0 ); - Assert( ambientLightingLump.LumpSize() / sizeof( CompressedLightCube ) == lh.LumpSize() / sizeof( dleaf_t ) ); - } - lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); - lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); - mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; - mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; - Vector gray(0.5, 0.5, 0.5); - ColorRGBExp32 grayColor; - VectorToColorRGBExp32( gray, grayColor ); - for ( i = 0; i < count; i++ ) - { - pTable[i].ambientSampleCount = 1; - pTable[i].firstAmbientSample = i; - pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; - pSamples[i].pad = 0; - if ( inLightCubes ) - { - Q_memcpy( &pSamples[i].cube, &inLightCubes[i], sizeof(pSamples[i].cube) ); - } - else - { - for ( j = 0; j < 6; j++ ) - { - pSamples[i].cube.m_Color[j] = grayColor; - } - } - } - } - else - { - Assert( ambientLightingLump.LumpSize() % sizeof( dleafambientlighting_t ) == 0 ); - Assert( ambientLightingTable.LumpSize() % sizeof( dleafambientindex_t ) == 0 ); - Assert((ambientLightingTable.LumpSize() / sizeof(dleafambientindex_t)) == (unsigned)count); // should have one of these per leaf - lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( ambientLightingTable.LumpSize(), "LeafAmbient" ); - lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( ambientLightingLump.LumpSize(), "LeafAmbientSamples" ); - Q_memcpy( lh.GetMap()->m_pLeafAmbient, ambientLightingTable.LumpBase(), ambientLightingTable.LumpSize() ); - Q_memcpy( lh.GetMap()->m_pAmbientSamples, ambientLightingLump.LumpBase(), ambientLightingLump.LumpSize() ); - } - - - for ( i=0 ; imins[j]; - maxs[j] = in->maxs[j]; - } - - VectorAdd( mins, maxs, out->m_vecCenter ); - out->m_vecCenter *= 0.5f; - VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); - - p = in->contents; - out->contents = p; - - out->cluster = in->cluster; - out->area = in->area; - out->flags = in->flags; -/* - out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; -*/ - out->firstmarksurface = in->firstleafface; - out->nummarksurfaces = in->numleaffaces; - out->parent = NULL; - - out->dispCount = 0; - - out->leafWaterDataID = in->leafWaterDataID; - } -} - -void Mod_LoadLeafs( void ) -{ - CMapLoadHelper lh( LUMP_LEAFS ); - - switch( lh.LumpVersion() ) - { - case 0: - Mod_LoadLeafs_Version_0( lh ); - break; - case 1: - if( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && - CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) > 0 ) - { - CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); - CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX_HDR ); - Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); - } - else - { - CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING ); - CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX ); - Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); - } - break; - default: - Assert( 0 ); - Error( "Unknown LUMP_LEAFS version\n" ); - break; - } - - worldbrushdata_t *pMap = lh.GetMap(); - cleaf_t *pCLeaf = GetCollisionBSPData()->map_leafs.Base(); - for ( int i = 0; i < pMap->numleafs; i++ ) - { - pMap->leafs[i].dispCount = pCLeaf[i].dispCount; - pMap->leafs[i].dispListStart = pCLeaf[i].dispListStart; - } - // HACKHACK: Copy over the shared global list here. Hunk_Alloc a copy? - pMap->m_pDispInfoReferences = GetCollisionBSPData()->map_dispList.Base(); - pMap->m_nDispInfoReferences = GetCollisionBSPData()->numdisplist; -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadLeafWaterData( void ) -{ - dleafwaterdata_t *in; - mleafwaterdata_t *out; - int count, i; - - CMapLoadHelper lh( LUMP_LEAFWATERDATA ); - - in = (dleafwaterdata_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mleafwaterdata_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafwaterdata" ) ); - - lh.GetMap()->leafwaterdata = out; - lh.GetMap()->numleafwaterdata = count; - for ( i=0 ; iminZ = in->minZ; - out->surfaceTexInfoID = in->surfaceTexInfoID; - out->surfaceZ = in->surfaceZ; - out->firstLeafIndex = -1; - } - if ( count == 1 ) - { - worldbrushdata_t *brush = lh.GetMap(); - for ( i = 0; i < brush->numleafs; i++ ) - { - if ( brush->leafs[i].leafWaterDataID >= 0 ) - { - brush->leafwaterdata[0].firstLeafIndex = i; - break; - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadCubemapSamples( void ) -{ - char textureName[512]; - char loadName[ MAX_PATH ]; - dcubemapsample_t *in; - mcubemapsample_t *out; - int count, i; - - CMapLoadHelper lh( LUMP_CUBEMAPS ); - - V_strcpy_safe( loadName, lh.GetLoadName() ); - - in = (dcubemapsample_t *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadCubemapSamples: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (mcubemapsample_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "cubemapsample" ) ); - - lh.GetMap()->m_pCubemapSamples = out; - lh.GetMap()->m_nCubemapSamples = count; - - bool bHDR = g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE; - int nCreateFlags = bHDR ? 0 : TEXTUREFLAGS_SRGB; - - // We have separate HDR versions of the textures. In order to deal with this, - // we have blahenvmap.hdr.vtf and blahenvmap.vtf. - char *pHDRExtension = ""; - if( bHDR ) - { - pHDRExtension = ".hdr"; - } - - for ( i=0 ; iorigin.Init( ( float )in->origin[0], ( float )in->origin[1], ( float )in->origin[2] ); - out->size = in->size; - Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d%s", loadName, ( int )in->origin[0], - ( int )in->origin[1], ( int )in->origin[2], pHDRExtension ); - out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); - if ( IsErrorTexture( out->pTexture ) ) - { - if ( bHDR ) - { - Warning( "Couldn't get HDR '%s' -- ", textureName ); - // try non hdr version - Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d", loadName, ( int )in->origin[0], - ( int )in->origin[1], ( int )in->origin[2]); - Warning( "Trying non HDR '%s'\n", textureName); - out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true ); - } - if ( IsErrorTexture( out->pTexture ) ) - { - Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); - out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); - if ( IsErrorTexture( out->pTexture ) ) - { - out->pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); - } - Warning( "Failed, using default cubemap '%s'\n", out->pTexture->GetName() ); - } - } - out->pTexture->IncrementReferenceCount(); - } - - CMatRenderContextPtr pRenderContext( materials ); - - if ( count ) - { - pRenderContext->BindLocalCubemap( lh.GetMap()->m_pCubemapSamples[0].pTexture ); - } - else - { - if ( CommandLine()->CheckParm( "-requirecubemaps" ) ) - { - Sys_Error( "Map \"%s\" does not have cubemaps!", lh.GetMapName() ); - } - - ITexture *pTexture; - Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); - pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); - if ( IsErrorTexture( pTexture ) ) - { - pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); - } - pTexture->IncrementReferenceCount(); - pRenderContext->BindLocalCubemap( pTexture ); - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadLeafMinDistToWater( void ) -{ - CMapLoadHelper lh( LUMP_LEAFMINDISTTOWATER ); - - unsigned short *pTmp = ( unsigned short * )lh.LumpBase(); - - int i; - bool foundOne = false; - for( i = 0; i < ( int )( lh.LumpSize() / sizeof( *pTmp ) ); i++ ) - { - if( pTmp[i] != 65535 ) // FIXME: make a marcro for this. - { - foundOne = true; - break; - } - } - - if( !foundOne || lh.LumpSize() == 0 || !g_pMaterialSystemHardwareConfig || !g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders()) - { - // We don't bother keeping this if: - // 1) there is no water in the map - // 2) we don't have this lump in the bsp file (old bsp file) - // 3) we aren't going to use it because we are on old hardware. - lh.GetMap()->m_LeafMinDistToWater = NULL; - } - else - { - int count; - unsigned short *in; - unsigned short *out; - - in = (unsigned short *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadLeafMinDistToWater: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafmindisttowater" ) ); - - memcpy( out, in, sizeof( out[0] ) * count ); - lh.GetMap()->m_LeafMinDistToWater = out; - } -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void Mod_LoadMarksurfaces( void ) -{ - int i, j, count; - unsigned short *in; - - CMapLoadHelper lh( LUMP_LEAFFACES ); - - in = (unsigned short *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - SurfaceHandle_t *tempDiskData = new SurfaceHandle_t[count]; - - worldbrushdata_t *pBrushData = lh.GetMap(); - pBrushData->marksurfaces = tempDiskData; - pBrushData->nummarksurfaces = count; - - // read in the mark surfaces, count out how many we'll actually need to store - int realCount = 0; - for ( i=0 ; i= lh.GetMap()->numsurfaces) - Host_Error ("Mod_LoadMarksurfaces: bad surface number"); - SurfaceHandle_t surfID = SurfaceHandleFromIndex( j, pBrushData ); - tempDiskData[i] = surfID; - if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) - { - realCount++; - } - } - - // now allocate the permanent list, and copy the non-terrain, non-nodraw surfs into it - SurfaceHandle_t *surfList = (SurfaceHandle_t *)Hunk_AllocName( realCount*sizeof(SurfaceHandle_t), va( "%s [%s]", lh.GetLoadName(), "surfacehandle" ) ); - - int outCount = 0; - mleaf_t *pLeaf = pBrushData->leafs; - for ( i = 0; i < pBrushData->numleafs; i++ ) - { - int firstMark = outCount; - int numMark = 0; - bool foundDetail = false; - int numMarkNode = 0; - for ( j = 0; j < pLeaf[i].nummarksurfaces; j++ ) - { - // write a new copy of the mark surfaces for this leaf, strip out the nodraw & terrain - SurfaceHandle_t surfID = tempDiskData[pLeaf[i].firstmarksurface+j]; - if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) - { - surfList[outCount++] = surfID; - numMark++; - Assert(outCount<=realCount); - if ( MSurf_Flags(surfID) & SURFDRAW_NODE ) - { - // this assert assures that all SURFDRAW_NODE surfs appear coherently - Assert( !foundDetail ); - numMarkNode++; - } - else - { - foundDetail = true; - } - } - } - // update the leaf count - pLeaf[i].nummarksurfaces = numMark; - pLeaf[i].firstmarksurface = firstMark; - pLeaf[i].nummarknodesurfaces = numMarkNode; - } - - // write out the compacted array - pBrushData->marksurfaces = surfList; - pBrushData->nummarksurfaces = realCount; - - // remove the temp copy of the disk data - delete[] tempDiskData; - - //Msg("Must check %d / %d faces\n", checkCount, pModel->brush.numsurfaces ); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *pedges - -// *loadmodel - -// *l - -// *loadname - -//----------------------------------------------------------------------------- -void Mod_LoadSurfedges( medge_t *pedges ) -{ - int i, count; - int *in; - unsigned short *out; - - CMapLoadHelper lh( LUMP_SURFEDGES ); - - in = (int *)lh.LumpBase(); - if (lh.LumpSize() % sizeof(*in)) - Host_Error ("Mod_LoadSurfedges: funny lump size in %s",lh.GetMapName()); - count = lh.LumpSize() / sizeof(*in); - if (count < 1 || count >= MAX_MAP_SURFEDGES) - Host_Error ("Mod_LoadSurfedges: bad surfedges count in %s: %i", - lh.GetMapName(), count); - out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "surfedges" ) ); - - lh.GetMap()->vertindices = out; - lh.GetMap()->numvertindices = count; - - for ( i=0 ; iplanes = GetCollisionBSPData()->map_planes.Base(); - s_pMap->numplanes = GetCollisionBSPData()->numplanes; -} - - -//----------------------------------------------------------------------------- -// Returns game lump version -//----------------------------------------------------------------------------- -int Mod_GameLumpVersion( int lumpId ) -{ - for ( int i = g_GameLumpDict.Size(); --i >= 0; ) - { - if ( g_GameLumpDict[i].id == lumpId ) - { - return g_GameLumpDict[i].version; - } - } - - return 0; -} - - -//----------------------------------------------------------------------------- -// Returns game lump size -//----------------------------------------------------------------------------- -int Mod_GameLumpSize( int lumpId ) -{ - for ( int i = g_GameLumpDict.Size(); --i >= 0; ) - { - if ( g_GameLumpDict[i].id == lumpId ) - { - return g_GameLumpDict[i].uncompressedSize; - } - } - - return 0; -} - - -//----------------------------------------------------------------------------- -// Loads game lumps -//----------------------------------------------------------------------------- -bool Mod_LoadGameLump( int lumpId, void *pOutBuffer, int size ) -{ - int i; - for ( i = g_GameLumpDict.Size(); --i >= 0; ) - { - if ( g_GameLumpDict[i].id == lumpId ) - { - break; - } - } - if ( i < 0 ) - { - // unknown - return false; - } - - byte *pData; - bool bIsCompressed = ( g_GameLumpDict[i].flags & GAMELUMPFLAG_COMPRESSED ); - int dataLength; - int outSize; - if ( bIsCompressed ) - { - // lump data length is always original uncompressed size - // compressed lump data length is determined from next dictionary entry offset - dataLength = g_GameLumpDict[i].compressedSize; - outSize = g_GameLumpDict[i].uncompressedSize; - } - else - { - dataLength = outSize = g_GameLumpDict[i].uncompressedSize; - } - - if ( size < 0 || size < outSize ) - { - // caller must supply a buffer that is large enough to hold the data - return false; - } - - if ( s_MapBuffer.Base() ) - { - // data is in memory - Assert( CMapLoadHelper::GetRefCount() ); - - if ( g_GameLumpDict[i].offset + dataLength > (unsigned int)s_MapBuffer.TellMaxPut() ) - { - // out of range - Assert( 0 ); - return false; - } - - pData = (unsigned char *)s_MapBuffer.Base() + g_GameLumpDict[i].offset; - if ( !bIsCompressed ) - { - V_memcpy( pOutBuffer, pData, outSize ); - return true; - } - } - else - { - // Load file into buffer - FileHandle_t fileHandle = g_pFileSystem->Open( g_GameLumpFilename, "rb" ); - if ( fileHandle == FILESYSTEM_INVALID_HANDLE ) - { - return false; - } - - g_pFileSystem->Seek( fileHandle, g_GameLumpDict[i].offset, FILESYSTEM_SEEK_HEAD ); - - if ( !bIsCompressed ) - { - // read directly into user's buffer - bool bOK = ( g_pFileSystem->Read( pOutBuffer, outSize, fileHandle ) > 0 ); - g_pFileSystem->Close( fileHandle ); - return bOK; - } - else - { - // data is compressed, read into temporary - pData = (byte *)malloc( dataLength ); - bool bOK = ( g_pFileSystem->Read( pData, dataLength, fileHandle ) > 0 ); - g_pFileSystem->Close( fileHandle ); - if ( !bOK ) - { - free( pData ); - return false; - } - } - } - - // We'll fall though to here through here if we're compressed - bool bResult = false; - if ( !CLZMA::IsCompressed( pData ) || CLZMA::GetActualSize( (unsigned char *)pData ) != g_GameLumpDict[i].uncompressedSize ) - { - Warning( "Failed loading game lump %i: lump claims to be compressed but metadata does not match\n", lumpId ); - } - else - { - // uncompress directly into caller's buffer - int outputLength = CLZMA::Uncompress( pData, (unsigned char *)pOutBuffer ); - bResult = ( outputLength > 0 && (unsigned int)outputLength == g_GameLumpDict[i].uncompressedSize ); - } - - if ( !s_MapBuffer.Base() ) - { - // done with temporary buffer - free( pData ); - } - - return bResult; -} - -//----------------------------------------------------------------------------- -// Loads game lump dictionary -//----------------------------------------------------------------------------- -void Mod_LoadGameLumpDict( void ) -{ - CMapLoadHelper lh( LUMP_GAME_LUMP ); - - // FIXME: This is brittle. If we ever try to load two game lumps - // (say, in multiple BSP files), the dictionary info I store here will get whacked - - g_GameLumpDict.RemoveAll(); - V_strcpy_safe( g_GameLumpFilename, lh.GetMapName() ); - - unsigned int lhSize = (unsigned int)Max( lh.LumpSize(), 0 ); - if ( lhSize >= sizeof( dgamelumpheader_t ) ) - { - dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)lh.LumpBase(); - - // Ensure (lumpsize * numlumps + headersize) doesn't overflow - const int nMaxGameLumps = ( INT_MAX - sizeof( dgamelumpheader_t ) ) / sizeof( dgamelump_t ); - if ( pGameLumpHeader->lumpCount < 0 || - pGameLumpHeader->lumpCount > nMaxGameLumps || - sizeof( dgamelumpheader_t ) + sizeof( dgamelump_t ) * pGameLumpHeader->lumpCount > lhSize ) - { - Warning( "Bogus gamelump header in map, rejecting\n" ); - } - else - { - // Load in lumps - dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); - for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) - { - if ( pGameLump[i].fileofs >= 0 && - (unsigned int)pGameLump[i].fileofs >= (unsigned int)lh.LumpOffset() && - (unsigned int)pGameLump[i].fileofs < (unsigned int)lh.LumpOffset() + lhSize && - pGameLump[i].filelen > 0 ) - { - unsigned int compressedSize = 0; - if ( i + 1 < pGameLumpHeader->lumpCount && - pGameLump[i+1].fileofs > pGameLump[i].fileofs && - pGameLump[i+1].fileofs >= 0 && - (unsigned int)pGameLump[i+1].fileofs <= (unsigned int)lh.LumpOffset() + lhSize ) - { - compressedSize = (unsigned int)pGameLump[i+1].fileofs - (unsigned int)pGameLump[i].fileofs; - } - else - { - compressedSize = (unsigned int)lh.LumpOffset() + lhSize - (unsigned int)pGameLump[i].fileofs; - } - g_GameLumpDict.AddToTail( { pGameLump[i], compressedSize } ); - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Re-Loads all of a model's peer data -//----------------------------------------------------------------------------- -void Mod_TouchAllData( model_t *pModel, int nServerCount ) -{ - double t1 = Plat_FloatTime(); - - MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); - - virtualmodel_t *pVirtualModel = g_pMDLCache->GetVirtualModel( pModel->studio ); - - double t2 = Plat_FloatTime(); - g_flAccumulatedModelLoadTimeVirtualModel += ( t2 - t1 ); - - if ( pVirtualModel && nServerCount >= 1 ) - { - // ensure all sub models get current count to avoid purge - // mark first to prevent re-entrant issues during possible reload - // skip self, start at children - for ( int i=1; im_group.Count(); ++i ) - { - MDLHandle_t childHandle = (MDLHandle_t)(int)pVirtualModel->m_group[i].cache&0xffff; - model_t *pChildModel = (model_t *)g_pMDLCache->GetUserData( childHandle ); - if ( pChildModel ) - { - // child inherits parent reference - pChildModel->nLoadFlags |= ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_REFERENCEMASK ); - pChildModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED; - pChildModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; - pChildModel->nServerCount = nServerCount; - } - } - } - - // don't touch all the data - if ( !mod_forcetouchdata.GetBool() ) - return; - - g_pMDLCache->TouchAllData( pModel->studio ); -} - -//----------------------------------------------------------------------------- -// Callbacks to get called when various data is loaded or unloaded -//----------------------------------------------------------------------------- -class CMDLCacheNotify : public IMDLCacheNotify -{ -public: - virtual void OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ); - virtual void OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ); - -private: - void ComputeModelFlags( model_t* mod, MDLHandle_t handle ); - - // Sets the bounds from the studiohdr - void SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ); -}; -static CMDLCacheNotify s_MDLCacheNotify; - -//----------------------------------------------------------------------------- -// Computes model flags -//----------------------------------------------------------------------------- -void CMDLCacheNotify::ComputeModelFlags( model_t* pModel, MDLHandle_t handle ) -{ - studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); - - // Clear out those flags we set... - pModel->flags &= ~(MODELFLAG_TRANSLUCENT_TWOPASS | MODELFLAG_VERTEXLIT | - MODELFLAG_TRANSLUCENT | MODELFLAG_MATERIALPROXY | MODELFLAG_FRAMEBUFFER_TEXTURE | - MODELFLAG_STUDIOHDR_USES_FB_TEXTURE | MODELFLAG_STUDIOHDR_USES_BUMPMAPPING | MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); - - bool bForceOpaque = (pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE) != 0; - - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS ) - { - pModel->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; - } - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_FB_TEXTURE ) - { - pModel->flags |= MODELFLAG_STUDIOHDR_USES_FB_TEXTURE; - } - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_BUMPMAPPING ) - { - pModel->flags |= MODELFLAG_STUDIOHDR_USES_BUMPMAPPING; - } - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_ENV_CUBEMAP ) - { - pModel->flags |= MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP; - } - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_AMBIENT_BOOST ) - { - pModel->flags |= MODELFLAG_STUDIOHDR_AMBIENT_BOOST; - } - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS ) - { - pModel->flags |= MODELFLAG_STUDIOHDR_DO_NOT_CAST_SHADOWS; - } - - IMaterial *pMaterials[ 128 ]; - int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); - - for ( int i = 0; i < materialCount; ++i ) - { - IMaterial *pMaterial = pMaterials[ i ]; - if ( !pMaterial ) - continue; - - if ( pMaterial->IsVertexLit() ) - { - pModel->flags |= MODELFLAG_VERTEXLIT; - } - - if ( !bForceOpaque && pMaterial->IsTranslucent() ) - { - //Msg("Translucent material %s for model %s\n", pLODData->ppMaterials[i]->GetName(), pModel->name ); - pModel->flags |= MODELFLAG_TRANSLUCENT; - } - - if ( pMaterial->HasProxy() ) - { - pModel->flags |= MODELFLAG_MATERIALPROXY; - } - - if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame - { - pModel->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; - } - } -} - -//----------------------------------------------------------------------------- -// Sets the bounds from the studiohdr -//----------------------------------------------------------------------------- -void CMDLCacheNotify::SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ) -{ - studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); - VectorCopy( pStudioHdr->hull_min, pModel->mins ); - VectorCopy( pStudioHdr->hull_max, pModel->maxs ); - pModel->radius = 0.0f; - for ( int i = 0; i < 3; i++ ) - { - if ( fabs(pModel->mins[i]) > pModel->radius ) - { - pModel->radius = fabs(pModel->mins[i]); - } - - if ( fabs(pModel->maxs[i]) > pModel->radius ) - { - pModel->radius = fabs(pModel->maxs[i]); - } - } -} - -//----------------------------------------------------------------------------- -// Callbacks to get called when various data is loaded or unloaded -//----------------------------------------------------------------------------- -void CMDLCacheNotify::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) -{ - model_t *pModel = (model_t*)g_pMDLCache->GetUserData( handle ); - - // NOTE: A NULL model can occur for dependent MDLHandle_ts (like .ani files) - if ( !pModel ) - return; - - switch( type ) - { - case MDLCACHE_STUDIOHDR: - { - // FIXME: This code only works because it assumes StudioHdr - // is loaded before VCollide. - SetBoundsFromStudioHdr( pModel, handle ); - } - break; - - case MDLCACHE_VCOLLIDE: - { - SetBoundsFromStudioHdr( pModel, handle ); - - // Expand the model bounds to enclose the collision model (should be done in studiomdl) - vcollide_t *pCollide = g_pMDLCache->GetVCollide( handle ); - if ( pCollide ) - { - Vector mins, maxs; - physcollision->CollideGetAABB( &mins, &maxs, pCollide->solids[0], vec3_origin, vec3_angle ); - AddPointToBounds( mins, pModel->mins, pModel->maxs ); - AddPointToBounds( maxs, pModel->mins, pModel->maxs ); - } - } - break; - - case MDLCACHE_STUDIOHWDATA: - ComputeModelFlags( pModel, handle ); - break; - } -} - -void CMDLCacheNotify::OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ) -{ -} - -//----------------------------------------------------------------------------- -// Hooks the cache notify into the MDL cache system -//----------------------------------------------------------------------------- -void ConnectMDLCacheNotify( ) -{ - g_pMDLCache->SetCacheNotify( &s_MDLCacheNotify ); -} - -void DisconnectMDLCacheNotify( ) -{ - g_pMDLCache->SetCacheNotify( NULL ); -} - -//----------------------------------------------------------------------------- -// Initialize studiomdl state -//----------------------------------------------------------------------------- -void InitStudioModelState( model_t *pModel ) -{ - Assert( pModel->type == mod_studio ); - - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHDR ) ) - { - s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHDR, pModel->studio ); - } - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) - { - s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHWDATA, pModel->studio ); - } - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VCOLLIDE ) ) - { - s_MDLCacheNotify.OnDataLoaded( MDLCACHE_VCOLLIDE, pModel->studio ); - } -} - -//----------------------------------------------------------------------------- -// Resource loading for models -//----------------------------------------------------------------------------- -class CResourcePreloadModel : public CResourcePreload -{ - static void QueuedLoaderMapCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) - { - if ( loaderError == LOADERERROR_NONE ) - { - // 360 mounts its bsp entirely into memory - // this data is discarded at the conclusion of the entire load process - Assert( CMapLoadHelper::GetRefCount() == 0 ); - CMapLoadHelper::InitFromMemory( (model_t *)pContext, pData, nSize ); - } - } - - virtual bool CreateResource( const char *pName ) - { - modtype_t modType = g_ModelLoader.GetTypeFromName( pName ); - - // each model type resource has entirely differnt schemes for loading/creating - if ( modType == mod_brush ) - { - // expect to be the map bsp model - MEM_ALLOC_CREDIT_( "CResourcePreloadModel(BSP)" ); - model_t *pMapModel = g_ModelLoader.FindModelNoCreate( pName ); - if ( pMapModel ) - { - Assert( CMapLoadHelper::GetRefCount() == 0 ); - - // 360 reads its specialized bsp into memory, - // up to the pack lump, which is guranateed last - char szLoadName[MAX_PATH]; - V_FileBase( pMapModel->strName, szLoadName, sizeof( szLoadName ) ); - CMapLoadHelper::Init( pMapModel, szLoadName ); - int nBytesToRead = CMapLoadHelper::LumpOffset( LUMP_PAKFILE ); - CMapLoadHelper::Shutdown(); - - // create a loader job to perform i/o operation to mount the .bsp - LoaderJob_t loaderJobBSP; - loaderJobBSP.m_pFilename = pMapModel->strName; - loaderJobBSP.m_pPathID = "GAME"; - loaderJobBSP.m_pCallback = QueuedLoaderMapCallback; - loaderJobBSP.m_pContext = (void *)pMapModel; - loaderJobBSP.m_pTargetData = malloc( nBytesToRead ); - loaderJobBSP.m_nBytesToRead = nBytesToRead; - loaderJobBSP.m_Priority = LOADERPRIORITY_DURINGPRELOAD; - g_pQueuedLoader->AddJob( &loaderJobBSP ); - - // create an anonymous job to perform i/o operation to mount the .ain - // the .ain gets claimed later - char szAINName[MAX_PATH] = { 0 }; - V_snprintf( szAINName, sizeof( szAINName ), "maps/graphs/%s.360.ain", szLoadName ); - LoaderJob_t loaderJobAIN; - loaderJobAIN.m_pFilename = szAINName; - loaderJobAIN.m_pPathID = "GAME"; - loaderJobAIN.m_Priority = LOADERPRIORITY_DURINGPRELOAD; - g_pQueuedLoader->AddJob( &loaderJobAIN ); - - return true; - } - } - else if ( modType == mod_studio ) - { - MEM_ALLOC_CREDIT_( "CResourcePreloadModel(MDL)" ); - - char szFilename[MAX_PATH]; - V_ComposeFileName( "models", pName, szFilename, sizeof( szFilename ) ); - - // find model or create empty entry - model_t *pModel = g_ModelLoader.FindModel( szFilename ); - - // mark as touched - pModel->nLoadFlags |= IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; - - if ( pModel->nLoadFlags & ( IModelLoader::FMODELLOADER_LOADED|IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD ) ) - { - // already loaded or preloaded - return true; - } - - // the model in not supposed to be in memory - Assert( pModel->type == mod_bad ); - - // set its type - pModel->type = mod_studio; - - // mark the model so that the normal studio load path can perform a final fixup - pModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; - - // setup the new entry for preload to operate - pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); - - // the model is not supposed to be in memory - // if this hits, the mdlcache is out of sync with the modelloder - // if this hits, the mdlcache has the model, but the modelloader doesn't think so - // if the refcounts go haywire, bad evil bugs will occur - Assert( g_pMDLCache->GetRef( pModel->studio ) == 1 ); - - g_pMDLCache->SetUserData( pModel->studio, pModel ); - - // get it into the cache - g_pMDLCache->PreloadModel( pModel->studio ); - - return true; - } - - // unknown - return false; - } - - //----------------------------------------------------------------------------- - // Called before queued loader i/o jobs are actually performed. Must free up memory - // to ensure i/o requests have enough memory to succeed. The models that were - // touched by the CreateResource() are the ones to keep, all others get purged. - //----------------------------------------------------------------------------- - virtual void PurgeUnreferencedResources() - { - bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; - - // purge any model that was not touched by the preload process - int iIndex = -1; - CUtlVector< model_t* > firstList; - CUtlVector< model_t* > otherList; - for ( ;; ) - { - model_t *pModel; - iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); - if ( iIndex == -1 || !pModel ) - { - // end of list - break; - } - if ( pModel->type == mod_studio ) - { - // models that were touched during the preload stay, otherwise purged - if ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD ) - { - pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; - } - else - { - if ( bSpew ) - { - Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); - } - - // Models that have virtual models have to unload first to - // ensure they properly unreference their virtual models. - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) - { - firstList.AddToTail( pModel ); - } - else - { - otherList.AddToTail( pModel ); - } - } - } - } - - for ( int i=0; iIsSameMapLoading() ) - { - g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK ); - } - } - - virtual void PurgeAll() - { - bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; - - // purge any model that was not touched by the preload process - int iIndex = -1; - CUtlVector< model_t* > firstList; - CUtlVector< model_t* > otherList; - for ( ;; ) - { - model_t *pModel; - iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); - if ( iIndex == -1 || !pModel ) - { - // end of list - break; - } - if ( pModel->type == mod_studio ) - { - pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; - if ( bSpew ) - { - Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); - } - - // Models that have virtual models have to unload first to - // ensure they properly unreference their virtual models. - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) - { - firstList.AddToTail( pModel ); - } - else - { - otherList.AddToTail( pModel ); - } - } - } - - for ( int i=0; iFlush( MDLCACHE_FLUSH_ANIMBLOCK ); - } - - virtual void OnEndMapLoading( bool bAbort ) - { - // discard the memory mounted bsp - CMapLoadHelper::Shutdown(); - Assert( CMapLoadHelper::GetRefCount() == 0 ); - } -}; -static CResourcePreloadModel s_ResourcePreloadModel; - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::Init( void ) -{ - m_Models.RemoveAll(); - m_InlineModels.Purge(); - - m_pWorldModel = NULL; - m_bMapRenderInfoLoaded = false; - m_bMapHasHDRLighting = false; - g_bLoadedMapHasBakedPropLighting = false; - - // Make sure we have physcollision and physprop interfaces - CollisionBSPData_LinkPhysics(); - - m_szActiveMapName[0] = '\0'; - - g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_MODEL, &s_ResourcePreloadModel ); -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::Shutdown( void ) -{ - m_pWorldModel = NULL; - - ForceUnloadNonClientDynamicModels(); - - UnloadAllModels( false ); - - m_ModelPool.Clear(); -} - -int CModelLoader::GetCount( void ) -{ - Assert( m_Models.Count() == m_Models.MaxElement() ); - return m_Models.Count(); -} - -model_t *CModelLoader::GetModelForIndex( int i ) -{ - if ( i < 0 || (unsigned)i >= m_Models.Count() ) - { - Assert( !m_Models.IsValidIndex( i ) ); - return NULL; - } - - Assert( m_Models.IsValidIndex( i ) ); - return m_Models[i].modelpointer; -} - -//----------------------------------------------------------------------------- -// Purpose: Look up name for model -// Input : *model - -// Output : const char -//----------------------------------------------------------------------------- -const char *CModelLoader::GetName( const model_t *pModel ) -{ - if ( pModel ) - { - return pModel->strName; - } - return NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the model, builds entry if not present, always returns a model -// Input : *name - -// referencetype - -// Output : model_t -//----------------------------------------------------------------------------- -model_t *CModelLoader::FindModel( const char *pName ) -{ - if ( !pName || !pName[0] ) - { - Sys_Error( "CModelLoader::FindModel: NULL name" ); - } - - // inline models are grabbed only from worldmodel - if ( pName[0] == '*' ) - { - int modelNum = atoi( pName + 1 ); - if ( !IsWorldModelSet() ) - { - Sys_Error( "bad inline model number %i, worldmodel not yet setup", modelNum ); - } - - if ( modelNum < 1 || modelNum >= GetNumWorldSubmodels() ) - { - Sys_Error( "bad inline model number %i", modelNum ); - } - return &m_InlineModels[modelNum]; - } - - model_t *pModel = NULL; - - // get a handle suitable to use as the model key - // handles are insensitive to case and slashes - FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName ); - - int i = m_Models.Find( fnHandle ); - if ( i == m_Models.InvalidIndex() ) - { - pModel = (model_t *)m_ModelPool.Alloc(); - Assert( pModel ); - memset( pModel, 0, sizeof( model_t ) ); - - pModel->fnHandle = fnHandle; - - // Mark that we should load from disk - pModel->nLoadFlags = FMODELLOADER_NOTLOADEDORREFERENCED; - - // Copy in name and normalize! - // Various other subsystems fetch this 'object' name to do dictionary lookups, - // which are usually case insensitive, but not to slashes or dotslashes. - pModel->strName = pName; - V_RemoveDotSlashes( pModel->strName.GetForModify(), '/' ); - - ModelEntry_t entry; - entry.modelpointer = pModel; - m_Models.Insert( fnHandle, entry ); - } - else - { - pModel = m_Models[i].modelpointer; - } - - // notify the reslist generator that this model may be referenced later in the level - // (does nothing if reslist generation is not enabled) - MapReslistGenerator().OnModelPrecached( pName ); - - Assert( pModel ); - - return pModel; -} - -//----------------------------------------------------------------------------- -// Purpose: Finds the model, and loads it if it isn't already present. Updates reference flags -// Input : *name - -// referencetype - -// Output : model_t -//----------------------------------------------------------------------------- -model_t *CModelLoader::GetModelForName( const char *name, REFERENCETYPE referencetype ) -{ - AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "GetModelForName: dynamic models must use GetDynamicModel" ); - - // find or build new entry - model_t *model = FindModel( name ); - - // touch and load if not present - model_t *retval = LoadModel( model, &referencetype ); - - return retval; -} - - -//----------------------------------------------------------------------------- -// Purpose: Add a reference to the model in question -// Input : *name - -// referencetype - -//----------------------------------------------------------------------------- -model_t *CModelLoader::ReferenceModel( const char *name, REFERENCETYPE referencetype ) -{ - AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "ReferenceModel: do not use for dynamic models" ); - - model_t *model = FindModel( name ); - - model->nLoadFlags |= referencetype; - - return model; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *entry - -// referencetype - -//----------------------------------------------------------------------------- -model_t *CModelLoader::LoadModel( model_t *mod, REFERENCETYPE *pReferencetype ) -{ - if ( pReferencetype ) - { - mod->nLoadFlags |= *pReferencetype; - } - - // during initial load mark the model with an unique session ticket - // at load end, models that have a mismatch count are considered candidates for purge - // models that get marked, touch *all* their sub data to ensure the cache is pre-populated - // and hitches less during gameplay - bool bTouchAllData = false; - int nServerCount = Host_GetServerCount(); - if ( mod->nServerCount != nServerCount ) - { - // server has changed - mod->nServerCount = nServerCount; - bTouchAllData = true; - } - - // Check if the studio model is in cache. - // The model type will not be set for first time models that need to fall through to the load path. - // A model that needs a post precache fixup will fall through to the load path. - if ( mod->type == mod_studio && !( mod->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) - { - // in cache - Verify( g_pMDLCache->GetStudioHdr( mod->studio ) != 0 ); - Assert( FMODELLOADER_LOADED & mod->nLoadFlags ); - - if ( bTouchAllData ) - { - // Touch all related .ani files and sub/dependent models - // only touches once, when server changes - Mod_TouchAllData( mod, nServerCount ); - } - - return mod; - } - - // Check if brushes or sprites are loaded - if ( FMODELLOADER_LOADED & mod->nLoadFlags ) - { - return mod; - } - - // model needs to be loaded - double st = Plat_FloatTime(); - - // Set the name of the current model we are loading - Q_FileBase( mod->strName, m_szLoadName, sizeof( m_szLoadName ) ); - - // load the file - if ( developer.GetInt() > 1 ) - { - DevMsg( "Loading: %s\n", mod->strName.String() ); - } - - mod->type = GetTypeFromName( mod->strName ); - if ( mod->type == mod_bad ) - { - mod->type = mod_studio; - } - - // finalize the model data - switch ( mod->type ) - { - case mod_sprite: - { - MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); - - double t1 = Plat_FloatTime(); - Sprite_LoadModel( mod ); - double t2 = Plat_FloatTime(); - g_flAccumulatedModelLoadTimeSprite += ( t2 - t1 ); - } - break; - - case mod_studio: - { - MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); - - double t1 = Plat_FloatTime(); - Studio_LoadModel( mod, bTouchAllData ); - double t2 = Plat_FloatTime(); - g_flAccumulatedModelLoadTimeStudio += ( t2 - t1 ); - } - break; - - case mod_brush: - { - double t1 = Plat_FloatTime(); - - // This is necessary on dedicated clients. On listen + dedicated servers, it's called twice. - // The second invocation is harmless. - // Add to file system before loading so referenced objects in map can use the filename. - g_pFileSystem->AddSearchPath( mod->strName, "GAME", PATH_ADD_TO_HEAD ); - - // the map may have explicit texture exclusion - // the texture state needs to be established before any loading work - if ( IsX360() || mat_excludetextures.GetBool() ) - { - char szExcludePath[MAX_PATH]; - sprintf( szExcludePath, "//MOD/maps/%s_exclude.lst", m_szLoadName ); - g_pMaterialSystem->SetExcludedTextures( szExcludePath ); - } - - // need this before queued loader starts, various systems use this as a cheap map changed state - V_strncpy( m_szActiveMapName, mod->strName, sizeof( m_szActiveMapName ) ); - - //NotifyHunkBeginMapLoad( m_szActiveMapName ); - - bool bQueuedLoader = false; - if ( IsX360() ) - { - // must establish the bsp feature set first to ensure proper state during queued loading - Map_CheckForHDR( mod, m_szLoadName ); - - // Do not optimize map-to-same-map loading in TF - // FIXME/HACK: this fixes a bug (when shipping Orange Box) where static props would sometimes - // disappear when a client disconnects and reconnects to the same map+server - // (static prop lighting data persists when loading map A after map A) - bool bIsTF = !V_stricmp( COM_GetModDirectory(), "tf" ); - bool bOptimizeMapReload = !bIsTF; - - // start the queued loading process - bQueuedLoader = g_pQueuedLoader->BeginMapLoading( mod->strName, g_pMaterialSystemHardwareConfig->GetHDREnabled(), bOptimizeMapReload ); - } - - // the queued loader process needs to own the actual texture update - if ( !bQueuedLoader && ( IsX360() || mat_excludetextures.GetBool() ) ) - { - g_pMaterialSystem->UpdateExcludedTextures(); - } - - BeginLoadingUpdates( MATERIAL_NON_INTERACTIVE_MODE_LEVEL_LOAD ); - g_pFileSystem->BeginMapAccess(); - Map_LoadModel( mod ); - g_pFileSystem->EndMapAccess(); - - double t2 = Plat_FloatTime(); - g_flAccumulatedModelLoadTimeBrush += (t2 - t1); - } - break; - - default: - Assert( 0 ); - break; - }; - - float dt = ( Plat_FloatTime() - st ); - COM_TimestampedLog( "Load of %s took %.3f msec", mod->strName.String(), 1000.0f * dt ); - g_flAccumulatedModelLoadTime += dt; - - return mod; -} - -//----------------------------------------------------------------------------- -// Purpose: Creates the name of the sprite -//----------------------------------------------------------------------------- -//static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsAVI, bool &bIsBIK ) -static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsVideo ) -{ - // If it's a .vmt and they put a path in there, then use the path. - // Otherwise, use the old method of prepending the sprites directory. - Assert( pName != NULL && pOut != NULL ); - - bIsVideo = false; - bool bIsVMT = false; - const char *pExt = V_GetFileExtension( pName ); - if ( pExt != NULL ) - { - bIsVMT = !Q_stricmp( pExt, "vmt" ); - if ( !bIsVMT ) - { - if ( g_pVideo ) - { - bIsVideo = ( g_pVideo->LocateVideoSystemForPlayingFile( pName ) != VideoSystem::NONE ); - } - } - } - - if ( ( bIsVideo || bIsVMT ) && ( strchr( pName, '/' ) || strchr( pName, '\\' ) ) ) - { - // The material system cannot handle a prepended "materials" dir - // Keep .avi extensions on the material to load avi-based materials - if ( bIsVMT ) - { - const char *pNameStart = pName; - if ( Q_stristr( pName, "materials/" ) == pName || - Q_stristr( pName, "materials\\" ) == pName ) - { - // skip past materials/ - pNameStart = &pName[10]; - } - Q_StripExtension( pNameStart, pOut, outLen ); - } - else - { - // name is good as is - Q_strncpy( pOut, pName, outLen ); - } - } - else - { - char szBase[MAX_PATH]; - Q_FileBase( pName, szBase, sizeof( szBase ) ); - Q_snprintf( pOut, outLen, "sprites/%s", szBase ); - } - - return; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *name - -// Output : int -//----------------------------------------------------------------------------- -int CModelLoader::GetModelFileSize( char const *name ) -{ - if ( !name || !name[ 0 ] ) - return -1; - - model_t *model = FindModel( name ); - - int size = -1; - if ( Q_stristr( model->strName, ".spr" ) || Q_stristr( model->strName, ".vmt" ) ) - { - char spritename[ MAX_PATH ]; - Q_StripExtension( va( "materials/%s", model->strName.String() ), spritename, MAX_PATH ); - Q_DefaultExtension( spritename, ".vmt", sizeof( spritename ) ); - - size = COM_FileSize( spritename ); - } - else - { - size = COM_FileSize( name ); - } - - return size; -} - -//----------------------------------------------------------------------------- -// Purpose: Unmasks the referencetype field for the model -// Input : *model - -// referencetype - -//----------------------------------------------------------------------------- -void CModelLoader::UnreferenceModel( model_t *model, REFERENCETYPE referencetype ) -{ - AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceModel: do not use for dynamic models" ); - model->nLoadFlags &= ~referencetype; -} - -//----------------------------------------------------------------------------- -// Purpose: Unmasks the specified reference type across all models -// Input : referencetype - -//----------------------------------------------------------------------------- -void CModelLoader::UnreferenceAllModels( REFERENCETYPE referencetype ) -{ - AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceAllModels: do not use for dynamic models" ); - - // UNDONE: If we ever free a studio model, write code to free the collision data - // UNDONE: Reference count collision data? - - FOR_EACH_MAP_FAST( m_Models, i ) - { - m_Models[ i ].modelpointer->nLoadFlags &= ~referencetype; - } -} - -//----------------------------------------------------------------------------- -// Purpose: When changing servers the old servercount number is bogus. This -// marks all models as loaded from -1 (e.g. a server count from the -// before time.) -//----------------------------------------------------------------------------- -void CModelLoader::ResetModelServerCounts() -{ - FOR_EACH_MAP_FAST( m_Models, i ) - { - model_t *pModel = m_Models[i].modelpointer; - pModel->nServerCount = -1; - } -} - - -void CModelLoader::ReloadFilesInList( IFileList *pFilesToReload ) -{ - FOR_EACH_MAP_FAST( m_Models, i ) - { - model_t *pModel = m_Models[i].modelpointer; - - if ( pModel->type != mod_studio ) - continue; - - if ( !IsLoaded( pModel ) ) - continue; - - if ( pModel->type != mod_studio ) - continue; - - if ( pFilesToReload->IsFileInList( pModel->strName ) ) - { - #ifdef PURE_SERVER_DEBUG_SPEW - Msg( "Reloading model %s\n", pModel->strName.String() ); - #endif - - // Flush out the model cache - // Don't flush vcollides since the vphysics system currently - // has no way of indicating they refer to vcollides - g_pMDLCache->Flush( pModel->studio, (int)(MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); - - MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); - - // Get the studiohdr into the cache - g_pMDLCache->GetStudioHdr( pModel->studio ); - -#ifndef _XBOX - // force the collision to load - g_pMDLCache->GetVCollide( pModel->studio ); -#endif - } - else - { - if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) - { - studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); - if ( pStudioHdr ) - { - // Ok, we didn't have to do a full reload, but if any of our materials changed, flush out the studiohwdata because the - // vertex format may have changed. - IMaterial *pMaterials[128]; - int nMaterials = g_pStudioRender->GetMaterialList( pStudioHdr, ARRAYSIZE( pMaterials ), &pMaterials[0] ); - - for ( int iMat=0; iMat < nMaterials; iMat++ ) - { - if ( pMaterials[iMat] && pMaterials[iMat]->WasReloadedFromWhitelist() ) - { - #ifdef PURE_SERVER_DEBUG_SPEW - Msg( "Reloading model %s because material %s was reloaded\n", pModel->strName.String(), pMaterials[iMat]->GetName() ); - #endif - g_pMDLCache->Flush( pModel->studio, MDLCACHE_FLUSH_STUDIOHWDATA ); - break; - } - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model -// and frees up the models slot -//----------------------------------------------------------------------------- -void CModelLoader::UnloadAllModels( bool bCheckReference ) -{ - model_t *model; - - FOR_EACH_MAP_FAST( m_Models, i ) - { - model = m_Models[ i ].modelpointer; - if ( bCheckReference ) - { - if ( model->nLoadFlags & FMODELLOADER_REFERENCEMASK ) - { - if ( model->type == mod_studio ) - { - g_pMDLCache->MarkAsLoaded(model->studio); - } - continue; - } - } - else - { - // Wipe current flags - model->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; - } - - if ( IsX360() && g_pQueuedLoader->IsMapLoading() && ( model->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) - { - // models preloaded by the queued loader are not initially claimed and MUST remain until the end of the load process - // unclaimed models get unloaded during the post load purge - continue; - } - - if ( model->nLoadFlags & ( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ) ) - { - UnloadModel( model ); - } - } -} - - -//----------------------------------------------------------------------------- -// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model -// and frees up the models slot -//----------------------------------------------------------------------------- -void CModelLoader::UnloadUnreferencedModels( void ) -{ - // unload all unreferenced models - UnloadAllModels( true ); -} - - -//----------------------------------------------------------------------------- -// Called at the conclusion of loading. -// Frees all memory associated with models (and their materials) that are not -// marked with the current session. -//----------------------------------------------------------------------------- -void CModelLoader::PurgeUnusedModels( void ) -{ - int nServerCount = Host_GetServerCount(); - FOR_EACH_MAP_FAST( m_Models, i ) - { - model_t *pModel = m_Models[i].modelpointer; - if ( ( pModel->nLoadFlags & FMODELLOADER_LOADED ) && ( pModel->nServerCount != nServerCount ) ) - { - // mark as unreferenced - // do not unload dynamic models - pModel->nLoadFlags &= (~FMODELLOADER_REFERENCEMASK) | FMODELLOADER_DYNAMIC; - } - } - - // flush dynamic models that have no refcount - FlushDynamicModels(); - - // unload unreferenced models only - UnloadAllModels( true ); - - // now purge unreferenced materials - materials->UncacheUnusedMaterials( true ); -} - -//----------------------------------------------------------------------------- -// Compute whether this submodel uses material proxies or not -//----------------------------------------------------------------------------- -static void Mod_ComputeBrushModelFlags( model_t *mod ) -{ - Assert( mod ); - - worldbrushdata_t *pBrushData = mod->brush.pShared; - // Clear out flags we're going to set - mod->flags &= ~(MODELFLAG_MATERIALPROXY | MODELFLAG_TRANSLUCENT | MODELFLAG_FRAMEBUFFER_TEXTURE | MODELFLAG_TRANSLUCENT_TWOPASS ); - mod->flags = MODELFLAG_HAS_DLIGHT; // force this check the first time - - int i; - int scount = mod->brush.nummodelsurfaces; - bool bHasOpaqueSurfaces = false; - bool bHasTranslucentSurfaces = false; - for ( i = 0; i < scount; ++i ) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, pBrushData ); - - // Clear out flags we're going to set - MSurf_Flags( surfID ) &= ~(SURFDRAW_NOCULL | SURFDRAW_TRANS | SURFDRAW_ALPHATEST | SURFDRAW_NODECALS); - - mtexinfo_t *pTex = MSurf_TexInfo( surfID, pBrushData ); - IMaterial* pMaterial = pTex->material; - - if ( pMaterial->HasProxy() ) - { - mod->flags |= MODELFLAG_MATERIALPROXY; - } - - if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame - { - mod->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; - } - - // Deactivate culling if the material is two sided - if ( pMaterial->IsTwoSided() ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NOCULL; - } - - if ( (pTex->flags & SURF_TRANS) || pMaterial->IsTranslucent() ) - { - mod->flags |= MODELFLAG_TRANSLUCENT; - MSurf_Flags( surfID ) |= SURFDRAW_TRANS; - bHasTranslucentSurfaces = true; - } - else - { - bHasOpaqueSurfaces = true; - } - - // Certain surfaces don't want decals at all - if ( (pTex->flags & SURF_NODECALS) || pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SUPPRESS_DECALS ) || pMaterial->IsAlphaTested() ) - { - MSurf_Flags( surfID ) |= SURFDRAW_NODECALS; - } - - if ( pMaterial->IsAlphaTested() ) - { - MSurf_Flags( surfID ) |= SURFDRAW_ALPHATEST; - } - } - - if ( bHasOpaqueSurfaces && bHasTranslucentSurfaces ) - { - mod->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; - } -} - - -//----------------------------------------------------------------------------- -// Recomputes translucency for the model... -//----------------------------------------------------------------------------- -void Mod_RecomputeTranslucency( model_t* mod, int nSkin, int nBody, void /*IClientRenderable*/ *pClientRenderable, float fInstanceAlphaModulate ) -{ - if (fInstanceAlphaModulate < 1.0f) - { - mod->flags |= MODELFLAG_TRANSLUCENT; - return; - } - - mod->flags &= ~MODELFLAG_TRANSLUCENT; - - switch( mod->type ) - { - case mod_brush: - { - for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface+i, mod->brush.pShared ); - if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) - continue; - - IMaterial* material = MSurf_TexInfo( surfID, mod->brush.pShared )->material; - if ( material->IsTranslucent() ) - { - mod->flags |= MODELFLAG_TRANSLUCENT; - break; - } - } - } - break; - - case mod_studio: - { - studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( mod->studio ); - if ( pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE ) - return; - - IMaterial *pMaterials[ 128 ]; - int materialCount = g_pStudioRender->GetMaterialListFromBodyAndSkin( mod->studio, nSkin, nBody, ARRAYSIZE( pMaterials ), pMaterials ); - for ( int i = 0; i < materialCount; i++ ) - { - if ( pMaterials[i] != NULL ) - { - // Bind material first so all material proxies execute - CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); - pRenderContext->Bind( pMaterials[i], pClientRenderable ); - bool bIsTranslucent = pMaterials[i]->IsTranslucent(); - - if ( bIsTranslucent ) - { - mod->flags |= MODELFLAG_TRANSLUCENT; - break; - } - } - } - } - break; - } -} - - -//----------------------------------------------------------------------------- -// returns the material count... -//----------------------------------------------------------------------------- -int Mod_GetMaterialCount( model_t* mod ) -{ - switch( mod->type ) - { - case mod_brush: - { - CUtlVector uniqueMaterials( 0, 32 ); - - for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, mod->brush.pShared ); - - if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) - continue; - - IMaterial* pMaterial = MSurf_TexInfo( surfID, mod->brush.pShared )->material; - - // Try to find the material in the unique list of materials - // if it's not there, then add it - if (uniqueMaterials.Find(pMaterial) < 0) - uniqueMaterials.AddToTail(pMaterial); - } - - return uniqueMaterials.Size(); - } - break; - - case mod_studio: - { - // FIXME: This should return the list of all materials - // across all LODs if we every decide to implement this - Assert(0); - } - break; - - default: - // unimplemented - Assert(0); - break; - } - - return 0; -} - -//----------------------------------------------------------------------------- -// returns the first n materials. -//----------------------------------------------------------------------------- -int Mod_GetModelMaterials( model_t* pModel, int count, IMaterial** ppMaterials ) -{ - studiohdr_t *pStudioHdr; - int found = 0; - int i; - - switch( pModel->type ) - { - case mod_brush: - { - for ( i = 0; i < pModel->brush.nummodelsurfaces; ++i) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface + i, pModel->brush.pShared ); - if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) - continue; - - IMaterial* pMaterial = MSurf_TexInfo( surfID, pModel->brush.pShared )->material; - - // Try to find the material in the unique list of materials - // if it's not there, then add it - int j = found; - while ( --j >= 0 ) - { - if ( ppMaterials[j] == pMaterial ) - break; - } - if (j < 0) - ppMaterials[found++] = pMaterial; - - // Stop when we've gotten count materials - if ( found >= count ) - return found; - } - } - break; - - case mod_studio: - if ( pModel->ppMaterials ) - { - int nMaterials = ((intptr_t*)(pModel->ppMaterials))[-1]; - found = MIN( count, nMaterials ); - memcpy( ppMaterials, pModel->ppMaterials, found * sizeof( IMaterial* ) ); - } - else - { - // Get the studiohdr into the cache - pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); - // Get the list of materials - found = g_pStudioRender->GetMaterialList( pStudioHdr, count, ppMaterials ); - } - break; - - default: - // unimplemented - Assert( 0 ); - break; - } - - return found; -} - - -void Mod_SetMaterialVarFlag( model_t *pModel, unsigned int uiFlag, bool on ) -{ - MaterialVarFlags_t flag = (MaterialVarFlags_t)uiFlag; - IMaterial *pMaterials[ 128 ]; - if ( pModel ) - { - int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); - - for ( int i = 0; i < materialCount; ++i ) - { - IMaterial *pMaterial = pMaterials[ i ]; - if ( pMaterial ) - { - pMaterial->SetMaterialVarFlag( flag, on ); - } - } - } -} - -//----------------------------------------------------------------------------- -// Used to compute which surfaces are in water or not -//----------------------------------------------------------------------------- - -static void MarkWaterSurfaces_ProcessLeafNode( mleaf_t *pLeaf ) -{ - int i; - - int flags = ( pLeaf->leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; - - SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; - - for( i = 0; i < pLeaf->nummarksurfaces; i++ ) - { - SurfaceHandle_t surfID = pHandle[i]; - ASSERT_SURF_VALID( surfID ); - if( MSurf_Flags( surfID ) & SURFDRAW_WATERSURFACE ) - continue; - - if (SurfaceHasDispInfo( surfID )) - continue; - - MSurf_Flags( surfID ) |= flags; - } - - // FIXME: This is somewhat bogus, but I can do it quickly, and it's - // not clear I need to solve the harder problem. - - // If any portion of a displacement surface hits a water surface, - // I'm going to mark it as being in water, and vice versa. - for ( i = 0; i < pLeaf->dispCount; i++ ) - { - IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); - - if ( pDispInfo ) - { - SurfaceHandle_t parentSurfID = pDispInfo->GetParent(); - MSurf_Flags( parentSurfID ) |= flags; - } - } -} - - -void MarkWaterSurfaces_r( mnode_t *node ) -{ - // no polygons in solid nodes - if (node->contents == CONTENTS_SOLID) - return; // solid - - // if a leaf node, . .mark all the polys as to whether or not they are in water. - if (node->contents >= 0) - { - MarkWaterSurfaces_ProcessLeafNode( (mleaf_t *)node ); - return; - } - - MarkWaterSurfaces_r( node->children[0] ); - MarkWaterSurfaces_r( node->children[1] ); -} - - -//----------------------------------------------------------------------------- -// Computes the sort group for a particular face -//----------------------------------------------------------------------------- -static int SurfFlagsToSortGroup( SurfaceHandle_t surfID, int flags ) -{ - // If we're on the low end, stick everything into the same sort group - if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) - return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; - - if( flags & SURFDRAW_WATERSURFACE ) - return MAT_SORT_GROUP_WATERSURFACE; - - if( ( flags & ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) == ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) - return MAT_SORT_GROUP_INTERSECTS_WATER_SURFACE; - - if( flags & SURFDRAW_UNDERWATER ) - return MAT_SORT_GROUP_STRICTLY_UNDERWATER; - - if( flags & SURFDRAW_ABOVEWATER ) - return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; - - static int warningcount = 0; - if ( ++warningcount < 10 ) - { - Vector vecCenter; - Surf_ComputeCentroid( surfID, &vecCenter ); - DevWarning( "SurfFlagsToSortGroup: unhandled flags (%X) (%s)!\n", flags, MSurf_TexInfo(surfID)->material->GetName() ); - DevWarning( "- This implies you have a surface (usually a displacement) embedded in solid.\n" ); - DevWarning( "- Look near (%.1f, %.1f, %.1f)\n", vecCenter.x, vecCenter.y, vecCenter.z ); - } - //Assert( 0 ); - return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; -} - - - -//----------------------------------------------------------------------------- -// Computes sort group -//----------------------------------------------------------------------------- -bool Mod_MarkWaterSurfaces( model_t *pModel ) -{ - bool bHasWaterSurfaces = false; - model_t *pSaveModel = host_state.worldmodel; - - // garymcthack!!!!!!!! - // host_state.worldmodel isn't set at this point, so. . . . - host_state.SetWorldModel( pModel ); - MarkWaterSurfaces_r( pModel->brush.pShared->nodes ); - for ( int i = 0; i < pModel->brush.pShared->numsurfaces; i++ ) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pModel->brush.pShared ); - - int sortGroup = SurfFlagsToSortGroup( surfID, MSurf_Flags( surfID ) ); - if ( sortGroup == MAT_SORT_GROUP_WATERSURFACE ) - { - bHasWaterSurfaces = true; - } - MSurf_SetSortGroup( surfID, sortGroup ); - } - host_state.SetWorldModel( pSaveModel ); - - return bHasWaterSurfaces; -} - - -//----------------------------------------------------------------------------- -// Marks identity brushes as being in fog volumes or not -//----------------------------------------------------------------------------- -class CBrushBSPIterator : public ISpatialLeafEnumerator -{ -public: - CBrushBSPIterator( model_t *pWorld, model_t *pBrush ) - { - m_pWorld = pWorld; - m_pBrush = pBrush; - m_pShared = pBrush->brush.pShared; - m_count = 0; - } - bool EnumerateLeaf( int leaf, int ) - { - // garymcthack - need to test identity brush models - int flags = ( m_pShared->leafs[leaf].leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; - MarkModelSurfaces( flags ); - m_count++; - return true; - } - - void MarkModelSurfaces( int flags ) - { - // Iterate over all this models surfaces - int surfaceCount = m_pBrush->brush.nummodelsurfaces; - for (int i = 0; i < surfaceCount; ++i) - { - SurfaceHandle_t surfID = SurfaceHandleFromIndex( m_pBrush->brush.firstmodelsurface + i, m_pShared ); - MSurf_Flags( surfID ) &= ~(SURFDRAW_ABOVEWATER | SURFDRAW_UNDERWATER); - MSurf_Flags( surfID ) |= flags; - } - } - - void CheckSurfaces() - { - if ( !m_count ) - { - MarkModelSurfaces( SURFDRAW_ABOVEWATER ); - } - } - - model_t* m_pWorld; - model_t* m_pBrush; - worldbrushdata_t *m_pShared; - int m_count; -}; - -static void MarkBrushModelWaterSurfaces( model_t* world, - Vector const& mins, Vector const& maxs, model_t* brush ) -{ - // HACK: This is a totally brutal hack dealing with initialization order issues. - // I want to use the same box enumeration code so I don't have multiple - // copies, but I want to use it from modelloader. host_state.worldmodel isn't - // set up at that time however, so I have to fly through these crazy hoops. - // Massive suckage. - - model_t* pTemp = host_state.worldmodel; - CBrushBSPIterator brushIterator( world, brush ); - host_state.SetWorldModel( world ); - g_pToolBSPTree->EnumerateLeavesInBox( mins, maxs, &brushIterator, (int)brush ); - brushIterator.CheckSurfaces(); - host_state.SetWorldModel( pTemp ); -} - -int g_nMapLoadCount = 0; -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mod - -// *buffer - -//----------------------------------------------------------------------------- -void CModelLoader::Map_LoadModel( model_t *mod ) -{ - ++g_nMapLoadCount; - - MEM_ALLOC_CREDIT(); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); - - COM_TimestampedLog( "Map_LoadModel: Start" ); - - double startTime = Plat_FloatTime(); - - SetWorldModel( mod ); - - // point at the shared world/brush data - mod->brush.pShared = &m_worldBrushData; - mod->brush.renderHandle = 0; - - // HDR and features must be established first - COM_TimestampedLog( " Map_CheckForHDR" ); - m_bMapHasHDRLighting = Map_CheckForHDR( mod, m_szLoadName ); - if ( IsX360() && !m_bMapHasHDRLighting ) - { - Warning( "Map '%s' lacks exepected HDR data! 360 does not support accurate LDR visuals.", m_szLoadName ); - } - - // Load the collision model - COM_TimestampedLog( " CM_LoadMap" ); - unsigned int checksum; - CM_LoadMap( mod->strName, false, &checksum ); - - // Load the map - mod->type = mod_brush; - mod->nLoadFlags |= FMODELLOADER_LOADED; - CMapLoadHelper::Init( mod, m_szLoadName ); - - COM_TimestampedLog( " Mod_LoadVertices" ); - Mod_LoadVertices(); - - COM_TimestampedLog( " Mod_LoadEdges" ); - medge_t *pedges = Mod_LoadEdges(); - - COM_TimestampedLog( " Mod_LoadSurfedges" ); - Mod_LoadSurfedges( pedges ); - - COM_TimestampedLog( " Mod_LoadPlanes" ); - Mod_LoadPlanes(); - - COM_TimestampedLog( " Mod_LoadOcclusion" ); - Mod_LoadOcclusion(); - - // texdata needs to load before texinfo - COM_TimestampedLog( " Mod_LoadTexdata" ); - Mod_LoadTexdata(); - - COM_TimestampedLog( " Mod_LoadTexinfo" ); - Mod_LoadTexinfo(); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - // Until BSP version 19, this must occur after loading texinfo - COM_TimestampedLog( " Mod_LoadLighting" ); - if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && - CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 ) - { - CMapLoadHelper mlh( LUMP_LIGHTING_HDR ); - Mod_LoadLighting( mlh ); - } - else - { - CMapLoadHelper mlh( LUMP_LIGHTING ); - Mod_LoadLighting( mlh ); - } - - COM_TimestampedLog( " Mod_LoadPrimitives" ); - Mod_LoadPrimitives(); - - COM_TimestampedLog( " Mod_LoadPrimVerts" ); - Mod_LoadPrimVerts(); - - COM_TimestampedLog( " Mod_LoadPrimIndices" ); - Mod_LoadPrimIndices(); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - // faces need to be loaded before vertnormals - COM_TimestampedLog( " Mod_LoadFaces" ); - Mod_LoadFaces(); - - COM_TimestampedLog( " Mod_LoadVertNormals" ); - Mod_LoadVertNormals(); - - COM_TimestampedLog( " Mod_LoadVertNormalIndices" ); - Mod_LoadVertNormalIndices(); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - // note leafs must load befor marksurfaces - COM_TimestampedLog( " Mod_LoadLeafs" ); - Mod_LoadLeafs(); - - COM_TimestampedLog( " Mod_LoadMarksurfaces" ); - Mod_LoadMarksurfaces(); - - COM_TimestampedLog( " Mod_LoadNodes" ); - Mod_LoadNodes(); - - COM_TimestampedLog( " Mod_LoadLeafWaterData" ); - Mod_LoadLeafWaterData(); - - COM_TimestampedLog( " Mod_LoadCubemapSamples" ); - Mod_LoadCubemapSamples(); - -#ifndef SWDS - // UNDONE: Does the cmodel need worldlights? - COM_TimestampedLog( " OverlayMgr()->LoadOverlays" ); - OverlayMgr()->LoadOverlays(); -#endif - - COM_TimestampedLog( " Mod_LoadLeafMinDistToWater" ); - Mod_LoadLeafMinDistToWater(); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - COM_TimestampedLog( " LUMP_CLIPPORTALVERTS" ); - Mod_LoadLump( mod, - LUMP_CLIPPORTALVERTS, - va( "%s [%s]", m_szLoadName, "clipportalverts" ), - sizeof(m_worldBrushData.m_pClipPortalVerts[0]), - (void**)&m_worldBrushData.m_pClipPortalVerts, - &m_worldBrushData.m_nClipPortalVerts ); - - COM_TimestampedLog( " LUMP_AREAPORTALS" ); - Mod_LoadLump( mod, - LUMP_AREAPORTALS, - va( "%s [%s]", m_szLoadName, "areaportals" ), - sizeof(m_worldBrushData.m_pAreaPortals[0]), - (void**)&m_worldBrushData.m_pAreaPortals, - &m_worldBrushData.m_nAreaPortals ); - - COM_TimestampedLog( " LUMP_AREAS" ); - Mod_LoadLump( mod, - LUMP_AREAS, - va( "%s [%s]", m_szLoadName, "areas" ), - sizeof(m_worldBrushData.m_pAreas[0]), - (void**)&m_worldBrushData.m_pAreas, - &m_worldBrushData.m_nAreas ); - - COM_TimestampedLog( " Mod_LoadWorldlights" ); - if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && - CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0 ) - { - CMapLoadHelper mlh( LUMP_WORLDLIGHTS_HDR ); - Mod_LoadWorldlights( mlh, true ); - } - else - { - CMapLoadHelper mlh( LUMP_WORLDLIGHTS ); - Mod_LoadWorldlights( mlh, false ); - } - - COM_TimestampedLog( " Mod_LoadGameLumpDict" ); - Mod_LoadGameLumpDict(); - - // load the portal information - // JAY: Disabled until we need this information. -#if 0 - Mod_LoadPortalVerts(); - Mod_LoadClusterPortals(); - Mod_LoadClusters(); - Mod_LoadPortals(); -#endif - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - COM_TimestampedLog( " Mod_LoadSubmodels" ); - CUtlVector submodelList; - Mod_LoadSubmodels( submodelList ); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - COM_TimestampedLog( " SetupSubModels" ); - SetupSubModels( mod, submodelList ); - - COM_TimestampedLog( " RecomputeSurfaceFlags" ); - RecomputeSurfaceFlags( mod ); - -#ifndef SWDS - EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); -#endif - - COM_TimestampedLog( " Map_VisClear" ); - Map_VisClear(); - - COM_TimestampedLog( " Map_SetRenderInfoAllocated" ); - Map_SetRenderInfoAllocated( false ); - - // Close map file, etc. - CMapLoadHelper::Shutdown(); - - double elapsed = Plat_FloatTime() - startTime; - COM_TimestampedLog( "Map_LoadModel: Finish - loading took %.4f seconds", elapsed ); -} - -void CModelLoader::Map_UnloadCubemapSamples( model_t *mod ) -{ - int i; - for ( i=0 ; i < mod->brush.pShared->m_nCubemapSamples ; i++ ) - { - mcubemapsample_t *pSample = &mod->brush.pShared->m_pCubemapSamples[i]; - pSample->pTexture->DecrementReferenceCount(); - } -} - - -//----------------------------------------------------------------------------- -// Recomputes surface flags -//----------------------------------------------------------------------------- -void CModelLoader::RecomputeSurfaceFlags( model_t *mod ) -{ - for (int i=0 ; ibrush.pShared->numsubmodels ; i++) - { - model_t *pSubModel = &m_InlineModels[i]; - - // Compute whether this submodel uses material proxies or not - Mod_ComputeBrushModelFlags( pSubModel ); - - // Mark if brush models are in water or not; we'll use this - // for identity brushes. If the brush is not an identity brush, - // then we'll not have to worry. - if ( i != 0 ) - { - MarkBrushModelWaterSurfaces( mod, pSubModel->mins, pSubModel->maxs, pSubModel ); - } - } -} - -//----------------------------------------------------------------------------- -// Setup sub models -//----------------------------------------------------------------------------- -void CModelLoader::SetupSubModels( model_t *mod, CUtlVector &list ) -{ - int i; - - m_InlineModels.SetCount( m_worldBrushData.numsubmodels ); - - for (i=0 ; ibrush.firstmodelsurface = bm->firstface; - starmod->brush.nummodelsurfaces = bm->numfaces; - starmod->brush.firstnode = bm->headnode; - if ( starmod->brush.firstnode >= m_worldBrushData.numnodes ) - { - Sys_Error( "Inline model %i has bad firstnode", i ); - } - - VectorCopy(bm->maxs, starmod->maxs); - VectorCopy(bm->mins, starmod->mins); - starmod->radius = bm->radius; - - if (i == 0) - { - *mod = *starmod; - } - else - { - starmod->strName.Format( "*%d", i ); - starmod->fnHandle = g_pFileSystem->FindOrAddFileName( starmod->strName ); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mod - -//----------------------------------------------------------------------------- -void CModelLoader::Map_UnloadModel( model_t *mod ) -{ - Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); - mod->nLoadFlags &= ~FMODELLOADER_LOADED; - -#ifndef SWDS - OverlayMgr()->UnloadOverlays(); -#endif - - DeallocateLightingData( &m_worldBrushData ); - -#ifndef SWDS - DispInfo_ReleaseMaterialSystemObjects( mod ); -#endif - - Map_UnloadCubemapSamples( mod ); - -#ifndef SWDS - // Free decals in displacements. - R_DecalTerm( &m_worldBrushData, true ); -#endif - - if ( m_worldBrushData.hDispInfos ) - { - DispInfo_DeleteArray( m_worldBrushData.hDispInfos ); - m_worldBrushData.hDispInfos = NULL; - } - - // Model loader loads world model materials, unload them here - for( int texinfoID = 0; texinfoID < m_worldBrushData.numtexinfo; texinfoID++ ) - { - mtexinfo_t *pTexinfo = &m_worldBrushData.texinfo[texinfoID]; - if ( pTexinfo ) - { - GL_UnloadMaterial( pTexinfo->material ); - } - } - - MaterialSystem_DestroySortinfo(); - - // Don't store any reference to it here - ClearWorldModel(); - Map_SetRenderInfoAllocated( false ); -} - - -//----------------------------------------------------------------------------- -// Computes dimensions + frame count of a material -//----------------------------------------------------------------------------- -static void GetSpriteInfo( const char *pName, bool bIsVideo, int &nWidth, int &nHeight, int &nFrameCount ) -{ - nFrameCount = 1; - nWidth = nHeight = 1; - - // FIXME: The reason we are putting logic related to AVIs here, - // logic which is duplicated in the client DLL related to loading sprites, - // is that this code gets run on dedicated servers also. - IMaterial *pMaterial = NULL; - IVideoMaterial *pVideoMaterial = NULL; - if ( bIsVideo && g_pVideo != NULL ) - { - pVideoMaterial = g_pVideo->CreateVideoMaterial( pName, pName, "GAME", VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS, VideoSystem::DETERMINE_FROM_FILE_EXTENSION, false ); - if ( pVideoMaterial ) - { - pVideoMaterial->GetVideoImageSize( &nWidth, &nHeight ); - nFrameCount = pVideoMaterial->GetFrameCount(); - pMaterial = pVideoMaterial->GetMaterial(); - - g_pVideo->DestroyVideoMaterial( pVideoMaterial ); - } - } - else - { - pMaterial = GL_LoadMaterial( pName, TEXTURE_GROUP_OTHER ); - if ( pMaterial ) - { - // Store off our source height, width, frame count - nWidth = pMaterial->GetMappingWidth(); - nHeight = pMaterial->GetMappingHeight(); - nFrameCount = pMaterial->GetNumAnimationFrames(); - } - } - - if ( pMaterial == g_materialEmpty ) - { - DevMsg( "Missing sprite material %s\n", pName ); - } - -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::Sprite_LoadModel( model_t *mod ) -{ - Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); - - mod->nLoadFlags |= FMODELLOADER_LOADED; - - // The hunk data is not used on the server - byte* pSprite = NULL; - -#ifndef SWDS - if ( g_ClientDLL ) - { - int nSize = g_ClientDLL->GetSpriteSize(); - if ( nSize ) - { - pSprite = ( byte * )new byte[ nSize ]; - } - } -#endif - - mod->type = mod_sprite; - mod->sprite.sprite = (CEngineSprite *)pSprite; - - // Fake the bounding box. We need it for PVS culling, and we don't - // know the scale at which the sprite is going to be rendered at - // when we load it - mod->mins = mod->maxs = Vector(0,0,0); - - // Figure out the real load name.. - char loadName[MAX_PATH]; - bool bIsVideo; - BuildSpriteLoadName( mod->strName, loadName, MAX_PATH, bIsVideo ); - GetSpriteInfo( loadName, bIsVideo, mod->sprite.width, mod->sprite.height, mod->sprite.numframes ); - -#ifndef SWDS - if ( g_ClientDLL && mod->sprite.sprite ) - { - g_ClientDLL->InitSprite( mod->sprite.sprite, loadName ); - } -#endif -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::Sprite_UnloadModel( model_t *mod ) -{ - Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); - mod->nLoadFlags &= ~FMODELLOADER_LOADED; - - char loadName[MAX_PATH]; - bool bIsVideo; - BuildSpriteLoadName( mod->strName, loadName, sizeof( loadName ), bIsVideo ); - - IMaterial *mat = materials->FindMaterial( loadName, TEXTURE_GROUP_OTHER ); - if ( !IsErrorMaterial( mat ) ) - { - GL_UnloadMaterial( mat ); - } - -#ifndef SWDS - if ( g_ClientDLL && mod->sprite.sprite ) - { - g_ClientDLL->ShutdownSprite( mod->sprite.sprite ); - } -#endif - - delete[] (byte *)mod->sprite.sprite; - mod->sprite.sprite = 0; - mod->sprite.numframes = 0; -} - - -//----------------------------------------------------------------------------- -// Purpose: Flush and reload models. Intended for use when lod changes. -//----------------------------------------------------------------------------- -void CModelLoader::Studio_ReloadModels( CModelLoader::ReloadType_t reloadType ) -{ -#if !defined( SWDS ) - if ( g_ClientDLL ) - g_ClientDLL->InvalidateMdlCache(); -#endif // SWDS - if ( serverGameDLL ) - serverGameDLL->InvalidateMdlCache(); - - // ensure decals have no stale references to invalid lods - modelrender->RemoveAllDecalsFromAllModels(); - - // ensure static props have no stale references to invalid lods - modelrender->ReleaseAllStaticPropColorData(); - - // Flush out the model cache - // Don't flush vcollides since the vphysics system currently - // has no way of indicating they refer to vcollides - g_pMDLCache->Flush( (MDLCacheFlush_t) (MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); - - // Load the critical pieces now - // The model cache will re-populate as models render - FOR_EACH_MAP_FAST( m_Models, i ) - { - model_t *pModel = m_Models[ i ].modelpointer; - if ( !IsLoaded( pModel ) ) - continue; - - if ( pModel->type != mod_studio ) - continue; - - MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); - - // Get the studiohdr into the cache - g_pMDLCache->GetStudioHdr( pModel->studio ); - - // force the collision to load - g_pMDLCache->GetVCollide( pModel->studio ); - } -} - -struct modelsize_t -{ - const char *pName; - int size; -}; - -class CModelsize_Less -{ -public: - bool Less( const modelsize_t& src1, const modelsize_t& src2, void *pCtx ) - { - return ( src1.size < src2.size ); - } -}; - -void CModelLoader::DumpVCollideStats() -{ - int i; - CUtlSortVector< modelsize_t, CModelsize_Less > list; - for ( i = 0; (m_Models).IsUtlMap && i < (m_Models).MaxElement(); ++i ) if ( !(m_Models).IsValidIndex( i ) ) continue; else - { - model_t *pModel = m_Models[ i ].modelpointer; - if ( pModel && pModel->type == mod_studio ) - { - int size = 0; - bool loaded = g_pMDLCache->GetVCollideSize( pModel->studio, &size ); - if ( loaded && size ) - { - modelsize_t elem; - elem.pName = pModel->strName; - elem.size = size; - list.Insert( elem ); - } - } - } - for ( i = m_InlineModels.Count(); --i >= 0; ) - { - vcollide_t *pCollide = CM_VCollideForModel( i+1, &m_InlineModels[i] ); - if ( pCollide ) - { - int size = 0; - for ( int j = 0; j < pCollide->solidCount; j++ ) - { - size += physcollision->CollideSize( pCollide->solids[j] ); - } - size += pCollide->descSize; - if ( size ) - { - modelsize_t elem; - elem.pName = m_InlineModels[i].strName; - elem.size = size; - list.Insert( elem ); - } - } - } - - Msg("VCollides loaded: %d\n", list.Count() ); - int totalVCollideMemory = 0; - for ( i = 0; i < list.Count(); i++ ) - { - Msg("%8d bytes:%s\n", list[i].size, list[i].pName); - totalVCollideMemory += list[i].size; - } - int bboxCount, bboxSize; - physcollision->GetBBoxCacheSize( &bboxSize, &bboxCount ); - Msg( "%8d bytes BBox physics: %d boxes\n", bboxSize, bboxCount ); - totalVCollideMemory += bboxSize; - Msg( "--------------\n%8d bytes total VCollide Memory\n", totalVCollideMemory ); -} - - -//----------------------------------------------------------------------------- -// Is the model loaded? -//----------------------------------------------------------------------------- -bool CModelLoader::IsLoaded( const model_t *mod ) -{ - return (mod->nLoadFlags & FMODELLOADER_LOADED) != 0; -} - -bool CModelLoader::LastLoadedMapHasHDRLighting(void) -{ - return m_bMapHasHDRLighting; -} - -//----------------------------------------------------------------------------- -// Loads a studio model -//----------------------------------------------------------------------------- -void CModelLoader::Studio_LoadModel( model_t *pModel, bool bTouchAllData ) -{ - if ( !mod_touchalldata.GetBool() ) - { - bTouchAllData = false; - } - - // a preloaded model requires specific fixup behavior - bool bPreLoaded = ( pModel->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) != 0; - - bool bLoadPhysics = true; - if ( pModel->nLoadFlags == FMODELLOADER_STATICPROP ) - { - // this is the first call in loading as a static prop (load bit not set), don't load physics yet - // the next call in causes the physics to load - bLoadPhysics = false; - } - - // mark as loaded and fixed up - pModel->nLoadFlags |= FMODELLOADER_LOADED; - pModel->nLoadFlags &= ~FMODELLOADER_LOADED_BY_PRELOAD; - - if ( !bPreLoaded ) - { - pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); - g_pMDLCache->SetUserData( pModel->studio, pModel ); - - InitStudioModelState( pModel ); - } - - // Get the studiohdr into the cache - studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); - (void) pStudioHdr; - - // a preloaded model alrady has its physics data resident - if ( bLoadPhysics && !bPreLoaded ) - { - // load the collision data now - bool bSynchronous = bTouchAllData; - double t1 = Plat_FloatTime(); - g_pMDLCache->GetVCollideEx( pModel->studio, bSynchronous ); - - double t2 = Plat_FloatTime(); - if ( bSynchronous ) - { - g_flAccumulatedModelLoadTimeVCollideSync += ( t2 - t1 ); - } - else - { - g_flAccumulatedModelLoadTimeVCollideAsync += ( t2 - t1 ); - } - } - - // this forces sync setup operations (materials/shaders) to build out now during load and not at runtime - double t1 = Plat_FloatTime(); - - // should already be NULL, but better safe than sorry - if ( pModel->ppMaterials ) - { - free( pModel->ppMaterials - 1 ); - pModel->ppMaterials = NULL; - } - - IMaterial *pMaterials[128]; - int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); - - if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) - { - // Cache the material pointers so that we don't re-scan all the VMTs on dynamic unload - COMPILE_TIME_ASSERT( sizeof( intptr_t ) == sizeof( IMaterial * ) ); - IMaterial **pMem = (IMaterial**) malloc( (1 + nMaterials) * sizeof( IMaterial* ) ); - *(intptr_t*)pMem = nMaterials; - pModel->ppMaterials = pMem + 1; - for ( int i=0; ippMaterials[i] = pMaterials[i]; - } - } - - if ( nMaterials ) - { - for ( int i=0; iIncrementReferenceCount(); - } - // track the refcount bump - pModel->nLoadFlags |= FMODELLOADER_TOUCHED_MATERIALS; - } - - double t2 = Plat_FloatTime(); - g_flAccumulatedModelLoadTimeMaterialNamesOnly += ( t2 - t1 ); - - // a preloaded model must touch its children - if ( bTouchAllData || bPreLoaded ) - { - Mod_TouchAllData( pModel, Host_GetServerCount() ); - } -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mod - -//----------------------------------------------------------------------------- -void CModelLoader::Studio_UnloadModel( model_t *pModel ) -{ - // Do not unload models that are still referenced by the dynamic system - if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) - { - return; - } - - if ( pModel->nLoadFlags & FMODELLOADER_TOUCHED_MATERIALS ) - { - IMaterial *pMaterials[128]; - int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), &pMaterials[0] ); - for ( int j=0; jDecrementReferenceCount(); - } - pModel->nLoadFlags &= ~FMODELLOADER_TOUCHED_MATERIALS; - } - - // leave these flags alone since we are going to return from alt-tab at some point. - // Assert( !( mod->needload & FMODELLOADER_REFERENCEMASK ) ); - pModel->nLoadFlags &= ~( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ); - if ( IsX360() ) - { - // 360 doesn't need to keep the reference flags, but the PC does - pModel->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; - } - -#ifdef DBGFLAG_ASSERT - int nRef = -#endif - g_pMDLCache->Release( pModel->studio ); - - // the refcounts must be as expected, or evil latent bugs will occur - Assert( InEditMode() || ( nRef == 0 ) ); - - if ( pModel->ppMaterials ) - { - free( pModel->ppMaterials - 1 ); - pModel->ppMaterials = NULL; - } - - pModel->studio = MDLHANDLE_INVALID; - pModel->type = mod_bad; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mod - -//----------------------------------------------------------------------------- -void CModelLoader::SetWorldModel( model_t *mod ) -{ - Assert( mod ); - m_pWorldModel = mod; -// host_state.SetWorldModel( mod ); // garymcthack -} - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::ClearWorldModel( void ) -{ - m_pWorldModel = NULL; - memset( &m_worldBrushData, 0, sizeof(m_worldBrushData) ); - m_InlineModels.Purge(); -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CModelLoader::IsWorldModelSet( void ) -{ - return m_pWorldModel ? true : false; -} - -//----------------------------------------------------------------------------- -// Purpose: -// Output : int -//----------------------------------------------------------------------------- -int CModelLoader::GetNumWorldSubmodels( void ) -{ - if ( !IsWorldModelSet() ) - return 0; - - return m_worldBrushData.numsubmodels; -} - -//----------------------------------------------------------------------------- -// Purpose: Check cache or union data for info, reload studio model if needed -// Input : *model - -//----------------------------------------------------------------------------- -void *CModelLoader::GetExtraData( model_t *model ) -{ - if ( !model ) - { - return NULL; - } - - switch ( model->type ) - { - case mod_sprite: - { - // sprites don't use the real cache yet - if ( model->type == mod_sprite ) - { - // The sprite got unloaded. - if ( !( FMODELLOADER_LOADED & model->nLoadFlags ) ) - { - return NULL; - } - - return model->sprite.sprite; - } - } - break; - - case mod_studio: - return g_pMDLCache->GetStudioHdr( model->studio ); - - default: - case mod_brush: - // Should never happen - Assert( 0 ); - break; - }; - - return NULL; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CModelLoader::Map_GetRenderInfoAllocated( void ) -{ - return m_bMapRenderInfoLoaded; -} - - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void CModelLoader::Map_SetRenderInfoAllocated( bool allocated ) -{ - m_bMapRenderInfoLoaded = allocated; -} - - -//----------------------------------------------------------------------------- -// Purpose: -// Input : *mod - -//----------------------------------------------------------------------------- -void CModelLoader::Map_LoadDisplacements( model_t *pModel, bool bRestoring ) -{ - if ( !pModel ) - { - Assert( false ); - return; - } - - Q_FileBase( pModel->strName, m_szLoadName, sizeof( m_szLoadName ) ); - CMapLoadHelper::Init( pModel, m_szLoadName ); - - DispInfo_LoadDisplacements( pModel, bRestoring ); - - CMapLoadHelper::Shutdown(); -} - - -//----------------------------------------------------------------------------- -// Purpose: List the model dictionary -//----------------------------------------------------------------------------- -void CModelLoader::Print( void ) -{ - ConMsg( "Models:\n" ); - FOR_EACH_MAP_FAST( m_Models, i ) - { - model_t *pModel = m_Models[i].modelpointer; - if ( pModel->type == mod_studio || pModel->type == mod_bad ) - { - // studio models have ref counts - // bad models are unloaded models which need to be listed - int refCount = ( pModel->type == mod_studio ) ? g_pMDLCache->GetRef( pModel->studio ) : 0; - ConMsg( "%4d: Flags:0x%8.8x RefCount:%2d %s\n", i, pModel->nLoadFlags, refCount, pModel->strName.String() ); - } - else - { - ConMsg( "%4d: Flags:0x%8.8x %s\n", i, pModel->nLoadFlags, pModel->strName.String() ); - } - } -} - -//----------------------------------------------------------------------------- -// Callback for UpdateOrCreate utility function - swaps a bsp. -//----------------------------------------------------------------------------- -#if defined( _X360 ) -static bool BSPCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData ) -{ - // load the bsppack dll - IBSPPack *iBSPPack = NULL; - CSysModule *pmodule = g_pFullFileSystem->LoadModule( "bsppack" ); - if ( pmodule ) - { - CreateInterfaceFn factory = Sys_GetFactory( pmodule ); - if ( factory ) - { - iBSPPack = ( IBSPPack * )factory( IBSPPACK_VERSION_STRING, NULL ); - } - } - if( !iBSPPack ) - { - Warning( "Can't load bsppack.dll - unable to swap bsp.\n" ); - return false; - } - - bool bOk = true; - if ( !iBSPPack->SwapBSPFile( g_pFileSystem, pSourceName, pTargetName, IsX360(), ConvertVTFTo360Format, NULL, NULL ) ) - { - bOk = false; - Warning( "Failed to create %s\n", pTargetName ); - } - - Sys_UnloadModule( pmodule ); - - return bOk; -} -#endif - -//----------------------------------------------------------------------------- -// Calls utility function to create .360 version of a file. -//----------------------------------------------------------------------------- -int CModelLoader::UpdateOrCreate( const char *pSourceName, char *pTargetName, int targetLen, bool bForce ) -{ -#if defined( _X360 ) - return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, NULL, BSPCreateCallback, bForce ); -#else - return UOC_NOT_CREATED; -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Determine if specified .bsp is valid -// Input : *mapname - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CModelLoader::Map_IsValid( char const *pMapFile, bool bQuiet /* = false */ ) -{ - static char s_szLastMapFile[MAX_PATH] = { 0 }; - - if ( !pMapFile || !pMapFile[0] ) - { - if ( !bQuiet ) - { - ConMsg( "CModelLoader::Map_IsValid: Empty mapname!!!\n" ); - } - return false; - } - - char szMapFile[MAX_PATH] = { 0 }; - V_strncpy( szMapFile, pMapFile, sizeof( szMapFile ) ); - - if ( IsX360() && !V_stricmp( szMapFile, s_szLastMapFile ) ) - { - // already been checked, no reason to do multiple i/o validations - return true; - } - - // Blacklist some characters - // - Don't allow characters not allowed on all supported platforms for consistency - // - Don't allow quotes or ;"' as defense-in-depth against script abuses (and, no real reason for mapnames to use these) - const char *pBaseFileName = V_UnqualifiedFileName( pMapFile ); - bool bIllegalChar = false; - for (; pBaseFileName && *pBaseFileName; pBaseFileName++ ) - { - // ASCII control characters (codepoints <= 31) illegal in windows filenames - if ( *pBaseFileName <= (char)31 ) - bIllegalChar = true; - - switch ( *pBaseFileName ) - { - // Illegal in windows filenames, don't allow on any platform - case '<': case '>': case ':': case '"': case '/': case '\\': - case '|': case '?': case '*': - bIllegalChar = true; - // Additional special characters in source engine commands, defense-in-depth against things that might be - // composing commands with map names (though they really shouldn't be) - case ';': case '\'': - bIllegalChar = true; - default: break; - } - } - - if ( bIllegalChar ) - { - Assert( !"Map with illegal characters in filename" ); - Warning( "Map with illegal characters in filename\n" ); - return false; - } - - FileHandle_t mapfile; - - if ( IsX360() ) - { - char szMapName360[MAX_PATH]; - UpdateOrCreate( szMapFile, szMapName360, sizeof( szMapName360 ), false ); - V_strcpy_safe( szMapFile, szMapName360 ); - } - - mapfile = g_pFileSystem->OpenEx( szMapFile, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, "GAME" ); - if ( mapfile != FILESYSTEM_INVALID_HANDLE ) - { - dheader_t header; - memset( &header, 0, sizeof( header ) ); - g_pFileSystem->Read( &header, sizeof( dheader_t ), mapfile ); - g_pFileSystem->Close( mapfile ); - - if ( header.ident == IDBSPHEADER ) - { - if ( header.version >= MINBSPVERSION && header.version <= BSPVERSION ) - { - V_strncpy( s_szLastMapFile, szMapFile, sizeof( s_szLastMapFile ) ); - return true; - } - else - { - if ( !bQuiet ) - { - Warning( "CModelLoader::Map_IsValid: Map '%s' bsp version %i, expecting %i\n", szMapFile, header.version, BSPVERSION ); - } - - } - } - else - { - if ( !bQuiet ) - { - Warning( "CModelLoader::Map_IsValid: '%s' is not a valid BSP file\n", szMapFile ); - } - } - } - else - { - if ( !bQuiet ) - { - Warning( "CModelLoader::Map_IsValid: No such map '%s'\n", szMapFile ); - } - } - - // Get outta here if we are checking vidmemstats. - if ( CommandLine()->CheckParm( "-dumpvidmemstats" ) ) - { - Cbuf_AddText( "quit\n" ); - } - - return false; -} - -model_t *CModelLoader::FindModelNoCreate( const char *pModelName ) -{ - FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pModelName ); - int i = m_Models.Find( fnHandle ); - if ( i != m_Models.InvalidIndex() ) - { - return m_Models[i].modelpointer; - } - - // not found - return NULL; -} - -modtype_t CModelLoader::GetTypeFromName( const char *pModelName ) -{ - // HACK HACK, force sprites to correctly - const char *pExt = V_GetFileExtension( pModelName ); - if ( pExt ) - { - if ( !V_stricmp( pExt, "spr" ) || !V_stricmp( pExt, "vmt" ) ) - { - return mod_sprite; - } - else if ( !V_stricmp( pExt, "bsp" ) ) - { - return mod_brush; - } - else if ( !V_stricmp( pExt, "mdl" ) ) - { - return mod_studio; - } - else if ( g_pVideo != NULL && g_pVideo->LocateVideoSystemForPlayingFile( pModelName) != VideoSystem::NONE ) // video sprite - { - return mod_sprite; - } - } - - return mod_bad; -} - -int CModelLoader::FindNext( int iIndex, model_t **ppModel ) -{ - if ( iIndex == -1 && m_Models.Count() ) - { - iIndex = m_Models.FirstInorder(); - } - else if ( !m_Models.Count() || !m_Models.IsValidIndex( iIndex ) ) - { - *ppModel = NULL; - return -1; - } - - *ppModel = m_Models[iIndex].modelpointer; - - iIndex = m_Models.NextInorder( iIndex ); - if ( iIndex == m_Models.InvalidIndex() ) - { - // end of list - iIndex = -1; - } - - return iIndex; -} - -void CModelLoader::UnloadModel( model_t *pModel ) -{ - switch ( pModel->type ) - { - case mod_brush: - // Let it free data or call destructors.. - Map_UnloadModel( pModel ); - - // Remove from file system - g_pFileSystem->RemoveSearchPath( pModel->strName, "GAME" ); - - m_szActiveMapName[0] = '\0'; - break; - - case mod_studio: - Studio_UnloadModel( pModel ); - break; - - case mod_sprite: - Sprite_UnloadModel( pModel ); - break; - } -} - -const char *CModelLoader::GetActiveMapName( void ) -{ - return m_szActiveMapName; -} - -model_t *CModelLoader::GetDynamicModel( const char *name, bool bClientOnly ) -{ - if ( !name || !name[0] ) - { - name = "models/empty.mdl"; - } - - Assert( V_strnicmp( name, "models/", 7 ) == 0 && V_strstr( name, ".mdl" ) != NULL ); - - model_t *pModel = FindModel( name ); - Assert( pModel ); - - CDynamicModelInfo &dyn = m_DynamicModels[ m_DynamicModels.Insert( pModel ) ]; // Insert returns existing if key is already set - if ( dyn.m_nLoadFlags == CDynamicModelInfo::INVALIDFLAG ) - { - dyn.m_nLoadFlags = 0; - DynamicModelDebugMsg( "model %p [%s] registered\n", pModel, pModel->strName.String() ); - } - dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; - - return pModel; -} - -void CModelLoader::UpdateDynamicModelLoadQueue() -{ - if ( mod_dynamicloadpause.GetBool() ) - return; - - static double s_LastDynamicLoadTime = 0.0; - if ( mod_dynamicloadthrottle.GetFloat() > 0 && Plat_FloatTime() < s_LastDynamicLoadTime + mod_dynamicloadthrottle.GetFloat() ) - return; - - if ( m_bDynamicLoadQueueHeadActive ) - { - Assert( m_DynamicModelLoadQueue.Count() >= 1 ); - MaterialLock_t matLock = g_pMaterialSystem->Lock(); // ASDFADFASFASEGAafliejsfjaslaslgsaigas - bool bComplete = g_pQueuedLoader->CompleteDynamicLoad(); - g_pMaterialSystem->Unlock(matLock); - - if ( bComplete ) - { - model_t *pModel = m_DynamicModelLoadQueue[0]; - m_DynamicModelLoadQueue.Remove(0); - m_bDynamicLoadQueueHeadActive = false; - - Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); - Assert( pModel->type == mod_bad || ( pModel->nLoadFlags & (FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD) ) ); - (void) LoadModel( pModel, NULL ); - Assert( pModel->type == mod_studio ); - - UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); - Assert( hDyn != m_DynamicModels.InvalidHandle() ); - if ( hDyn != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; - Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); - Assert( dyn.m_nLoadFlags & CDynamicModelInfo::LOADING ); - - dyn.m_nLoadFlags &= ~( CDynamicModelInfo::QUEUED | CDynamicModelInfo::LOADING ); - - g_pMDLCache->LockStudioHdr( pModel->studio ); - dyn.m_nLoadFlags |= CDynamicModelInfo::CLIENTREADY; - - dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; - - FinishDynamicModelLoadIfReady( &dyn, pModel ); - } - - // do the clean up after we're actually done - // we keep some file cache around to make sure that LoadModel doesn't do blocking load - //g_pQueuedLoader->CleanupDynamicLoad(); NOT IMPLEMENTED - - s_LastDynamicLoadTime = Plat_FloatTime(); - } - } - - // If we're not working, and we have work to do, and the queued loader is open for business... - if ( !m_bDynamicLoadQueueHeadActive && m_DynamicModelLoadQueue.Count() > 0 && g_pQueuedLoader->IsFinished() ) - { - model_t *pModel = m_DynamicModelLoadQueue[0]; - UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); - Assert( hDyn != m_DynamicModels.InvalidHandle() ); - if ( hDyn != m_DynamicModels.InvalidHandle() ) - { - m_bDynamicLoadQueueHeadActive = true; - - CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; - Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); - Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ); - Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ); - dyn.m_nLoadFlags |= CDynamicModelInfo::LOADING; - - // the queued loader is very ... particular about path names. it doesn't like leading "models/" - const char* pName = pModel->strName; - if ( V_strnicmp( pName, "models", 6 ) == 0 && ( pName[6] == '/' || pName[6] == '\\' ) ) - { - pName += 7; - } - - MaterialLock_t matLock = g_pMaterialSystem->Lock(); - g_pQueuedLoader->DynamicLoadMapResource( pName, NULL, NULL, NULL ); - g_pMaterialSystem->Unlock(matLock); - } - else - { - m_DynamicModelLoadQueue.Remove(0); - } - } -} - -void CModelLoader::FinishDynamicModelLoadIfReady( CDynamicModelInfo *pDyn, model_t *pModel ) -{ - CDynamicModelInfo &dyn = *pDyn; - if ( ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) - { - if ( !( dyn.m_nLoadFlags & CDynamicModelInfo::SERVERLOADING ) ) - { - // There ought to be a better way to plumb this through, but this should be ok... - if ( sv.GetDynamicModelsTable() ) - { - int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); - if ( netidx != INVALID_STRING_INDEX ) - { - char nIsLoaded = 1; - sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); - } - } - - DynamicModelDebugMsg( "model %p [%s] loaded\n", pModel, pModel->strName.String() ); - - dyn.m_nLoadFlags |= CDynamicModelInfo::ALLREADY; - - // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back - for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) - { - uintptr_t callbackID = dyn.m_Callbacks[ i ]; - bool bClientOnly = (bool)(callbackID & 1); - IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); - UnregisterModelLoadCallback( pModel, bClientOnly, pCallback ); - pCallback->OnModelLoadComplete( pModel ); - } - } - else - { - // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back - for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) - { - uintptr_t callbackID = dyn.m_Callbacks[ i ]; - bool bClientOnly = (bool)(callbackID & 1); - IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); - if ( bClientOnly ) - { - UnregisterModelLoadCallback( pModel, true, pCallback ); - pCallback->OnModelLoadComplete( pModel ); - } - } - } - } -} - -bool CModelLoader::RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ) -{ - UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); - Assert( hDyn != m_DynamicModels.InvalidHandle() ); - if ( hDyn == m_DynamicModels.InvalidHandle() ) - return false; - - Assert( ((uintptr_t)pCallback & 1) == 0 ); - uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; - - int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; - CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; - AssertMsg( dyn.m_iRefCount > 0, "RegisterModelLoadCallback requires non-zero model refcount" ); - if ( dyn.m_nLoadFlags & readyFlag ) - { - if ( !bCallImmediatelyIfLoaded ) - return false; - - pCallback->OnModelLoadComplete( pModel ); - } - else - { - if ( !dyn.m_Callbacks.HasElement( callbackID ) ) - { - dyn.m_Callbacks.AddToTail( callbackID ); - // Set registration count for callback pointer - m_RegisteredDynamicCallbacks[ m_RegisteredDynamicCallbacks.Insert( callbackID, 0 ) ]++; - } - } - - return true; -} - -bool CModelLoader::IsDynamicModelLoading( model_t *pModel, bool bClientOnly ) -{ - Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); - UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); - Assert( hDyn != m_DynamicModels.InvalidHandle() ); - if ( hDyn != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; - AssertMsg( dyn.m_iRefCount > 0, "dynamic model state cannot be queried with zero refcount" ); - if ( dyn.m_iRefCount > 0 ) - { - int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; - return !( dyn.m_nLoadFlags & readyFlag ); - } - } - return false; -} - -void CModelLoader::AddRefDynamicModel( model_t *pModel, bool bClientSideRef ) -{ - extern IVModelInfo* modelinfo; - - UtlHashHandle_t hDyn = m_DynamicModels.Insert( pModel ); - CDynamicModelInfo& dyn = m_DynamicModels[ hDyn ]; - dyn.m_iRefCount++; - dyn.m_iClientRefCount += ( bClientSideRef ? 1 : 0 ); - Assert( dyn.m_iRefCount > 0 ); - - DynamicModelDebugMsg( "model %p [%s] addref %d (%d)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); - - if ( !( dyn.m_nLoadFlags & ( CDynamicModelInfo::QUEUED | CDynamicModelInfo::CLIENTREADY ) ) ) - { - QueueDynamicModelLoad( &dyn, pModel ); - - // Try to kick it off asap if we aren't already busy. - if ( !m_bDynamicLoadQueueHeadActive ) - { - UpdateDynamicModelLoadQueue(); - } - } -} - -void CModelLoader::ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ) -{ - Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); - UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); - Assert( hDyn != m_DynamicModels.InvalidHandle() ); - if ( hDyn != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; - Assert( dyn.m_iRefCount > 0 ); - if ( dyn.m_iRefCount > 0 ) - { - DynamicModelDebugMsg( "model %p [%s] release %d (%dc)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); - dyn.m_iRefCount--; - dyn.m_iClientRefCount -= ( bClientSideRef ? 1 : 0 ); - Assert( dyn.m_iClientRefCount >= 0 ); - if ( dyn.m_iClientRefCount < 0 ) - dyn.m_iClientRefCount = 0; - dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; - } - } -} - -void CModelLoader::UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ) -{ - Assert( ((uintptr_t)pCallback & 1) == 0 ); - uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; - if ( int *pCallbackRegistrationCount = m_RegisteredDynamicCallbacks.GetPtr( callbackID ) ) - { - if ( pModel ) - { - UtlHashHandle_t i = m_DynamicModels.Find( pModel ); - if ( i != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[ i ]; - if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) - { - if ( dyn.m_Callbacks.Count() == 0 ) - { - dyn.m_Callbacks.Purge(); - } - if ( --(*pCallbackRegistrationCount) == 0 ) - { - m_RegisteredDynamicCallbacks.Remove( callbackID ); - return; - } - } - } - } - else - { - for ( UtlHashHandle_t i = m_DynamicModels.FirstHandle(); i != m_DynamicModels.InvalidHandle(); i = m_DynamicModels.NextHandle(i) ) - { - CDynamicModelInfo &dyn = m_DynamicModels[ i ]; - if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) - { - if ( dyn.m_Callbacks.Count() == 0 ) - { - dyn.m_Callbacks.Purge(); - } - if ( --(*pCallbackRegistrationCount) == 0 ) - { - m_RegisteredDynamicCallbacks.Remove( callbackID ); - return; - } - } - } - } - } -} - -void CModelLoader::QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) -{ - Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); - // Client-side entities have priority over server-side entities - // because they are more likely to be used in UI elements. --henryg - if ( dyn->m_iClientRefCount > 0 && m_DynamicModelLoadQueue.Count() > 1 ) - { - m_DynamicModelLoadQueue.InsertAfter( 0, mod ); - } - else - { - m_DynamicModelLoadQueue.AddToTail( mod ); - } - dyn->m_nLoadFlags |= CDynamicModelInfo::QUEUED; - mod->nLoadFlags |= ( dyn->m_iClientRefCount > 0 ? FMODELLOADER_DYNCLIENT : FMODELLOADER_DYNSERVER ); -} - -bool CModelLoader::CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) -{ - int i = m_DynamicModelLoadQueue.Find( mod ); - Assert( (i < 0) == !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); - if ( i >= 0 ) - { - if ( i == 0 && m_bDynamicLoadQueueHeadActive ) - { - Assert( dyn->m_nLoadFlags & CDynamicModelInfo::LOADING ); - // can't remove head of queue - return false; - } - else - { - Assert( dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED ); - Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::LOADING) ); - m_DynamicModelLoadQueue.Remove( i ); - dyn->m_nLoadFlags &= ~CDynamicModelInfo::QUEUED; - mod->nLoadFlags &= ~FMODELLOADER_DYNAMIC; - return true; - } - } - return false; -} - -void CModelLoader::InternalUpdateDynamicModels( bool bIgnoreTime ) -{ - const uint now = Plat_MSTime(); - const uint delay = bIgnoreTime ? 0 : (int)( clamp( mod_dynamicunloadtime.GetFloat(), 1.f, 600.f ) * 1000 ); - - UpdateDynamicModelLoadQueue(); - -#ifdef _DEBUG - extern CNetworkStringTableContainer *networkStringTableContainerServer; - bool bPrevStringTableLockState = networkStringTableContainerServer->Lock( false ); -#endif - - // Scan for models to unload. TODO: accelerate with a "models to potentially unload" list? - UtlHashHandle_t i = m_DynamicModels.FirstHandle(); - while ( i != m_DynamicModels.InvalidHandle() ) - { - model_t *pModel = m_DynamicModels.Key( i ); - CDynamicModelInfo& dyn = m_DynamicModels[ i ]; - - // UNLOAD THIS MODEL if zero refcount and not currently loading, and either timed out or never loaded - if ( dyn.m_iRefCount <= 0 && !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) && - ( ( now - (dyn.m_uLastTouchedMS_Div256 << 8) ) >= delay || !( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) ) - { - // Remove from load queue - if ( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ) - { - if ( !CancelDynamicModelLoad( &dyn, pModel ) ) - { - // Couldn't remove from queue, advance to next entry and do not remove - i = m_DynamicModels.NextHandle(i); - continue; - } - } - - // Unlock studiohdr_t - if ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) - { - g_pMDLCache->UnlockStudioHdr( pModel->studio ); - } - - // There ought to be a better way to plumb this through, but this should be ok... - if ( sv.GetDynamicModelsTable() ) - { - int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); - if ( netidx != INVALID_STRING_INDEX ) - { - char nIsLoaded = 0; - sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); - } - } - - if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) - { - pModel->nLoadFlags &= ~FMODELLOADER_DYNAMIC; - // Actually unload the model if all system references are gone - if ( pModel->nLoadFlags & FMODELLOADER_REFERENCEMASK ) - { - DynamicModelDebugMsg( "model %p [%s] unload - deferred: non-dynamic reference\n", pModel, pModel->strName.String() ); - } - else - { - DynamicModelDebugMsg( "model %p [%s] unload\n", pModel, pModel->strName.String() ); - - Studio_UnloadModel( pModel ); - - if ( mod_dynamicunloadtextures.GetBool() ) - { - materials->UncacheUnusedMaterials( false ); - } - } - } - - // Remove from table, advance to next entry - i = m_DynamicModels.RemoveAndAdvance(i); - continue; - } - - // Advance to next entry in table - i = m_DynamicModels.NextHandle(i); - } - -#ifdef _DEBUG - networkStringTableContainerServer->Lock( bPrevStringTableLockState ); -#endif -} - -void CModelLoader::Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ) -{ -#ifndef SWDS - // Listen server don't distinguish between server and client ready, never use SERVERLOADING flag - if ( sv.IsActive() ) - return; - - UtlHashHandle_t i = m_DynamicModels.Find( pModel ); - if ( i != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[i]; - if ( !bServerLoaded ) - { - if ( dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY ) - DynamicModelDebugMsg( "dynamic model [%s] loaded on client but not server! is this bad? unknown...", pModel->strName.String() ); - dyn.m_nLoadFlags &= ~CDynamicModelInfo::ALLREADY; - dyn.m_nLoadFlags |= CDynamicModelInfo::SERVERLOADING; - } - else - { - dyn.m_nLoadFlags &= ~CDynamicModelInfo::SERVERLOADING; - FinishDynamicModelLoadIfReady( &dyn, pModel ); - } - } -#endif -} - -void CModelLoader::ForceUnloadNonClientDynamicModels() -{ - UtlHashHandle_t i = m_DynamicModels.FirstHandle(); - while ( i != m_DynamicModels.InvalidHandle() ) - { - CDynamicModelInfo &dyn = m_DynamicModels[i]; - dyn.m_iRefCount = dyn.m_iClientRefCount; - i = m_DynamicModels.NextHandle( i ); - } - - // Flush everything - InternalUpdateDynamicModels( true ); -} - - -// reconstruct the ambient lighting for a leaf at the given position in worldspace -void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, int leafIndex ) -{ - for ( int i = 0; i < 6; i++ ) - { - pOut[i].Init(); - } - mleafambientindex_t *pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; - if ( !pAmbient->ambientSampleCount && pAmbient->firstAmbientSample ) - { - // this leaf references another leaf, move there (this leaf is a solid leaf so it borrows samples from a neighbor) - leafIndex = pAmbient->firstAmbientSample; - pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; - } - int count = pAmbient->ambientSampleCount; - if ( count > 0 ) - { - int start = host_state.worldbrush->m_pLeafAmbient[leafIndex].firstAmbientSample; - mleafambientlighting_t *pSamples = host_state.worldbrush->m_pAmbientSamples + start; - mleaf_t *pLeaf = &host_state.worldbrush->leafs[leafIndex]; - float totalFactor = 0; - for ( int i = 0; i < count; i++ ) - { - // do an inverse squared distance weighted average of the samples to reconstruct - // the original function - - // the sample positions are packed as leaf bounds fractions, compute - Vector samplePos = pLeaf->m_vecCenter - pLeaf->m_vecHalfDiagonal; - samplePos.x += float(pSamples[i].x) * pLeaf->m_vecHalfDiagonal.x * (2.0f / 255.0f); - samplePos.y += float(pSamples[i].y) * pLeaf->m_vecHalfDiagonal.y * (2.0f / 255.0f); - samplePos.z += float(pSamples[i].z) * pLeaf->m_vecHalfDiagonal.z * (2.0f / 255.0f); - - float dist = (samplePos - pos).LengthSqr(); - float factor = 1.0f / (dist + 1.0f); - totalFactor += factor; - for ( int j = 0; j < 6; j++ ) - { - Vector v; - ColorRGBExp32ToVector( pSamples[i].cube.m_Color[j], v ); - pOut[j] += v * factor; - } - } - for ( int i = 0; i < 6; i++ ) - { - pOut[i] *= (1.0f / totalFactor); - } - } -} - -#if defined( WIN32 ) -int ComputeSize( studiohwdata_t *hwData, int *numVerts, int *pTriCount, bool onlyTopLod = false ) -{ - unsigned size = 0; - Assert(hwData && numVerts); - int max_lod = (onlyTopLod ? 1 : hwData->m_NumLODs); - *pTriCount = 0; - for ( int i=0; i < max_lod; i++ ) - { - studioloddata_t *pLOD = &hwData->m_pLODs[i]; - for ( int j = 0; j < hwData->m_NumStudioMeshes; j++ ) - { - studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j]; - for ( int k = 0; k < pMeshData->m_NumGroup; k++ ) - { - studiomeshgroup_t *pMeshGroup = &pMeshData->m_pMeshGroup[k]; - IMesh* mesh = pMeshGroup->m_pMesh; - size += mesh->ComputeMemoryUsed(); // Size of VB and IB - size += 2*pMeshGroup->m_NumVertices; // Size of m_pGroupIndexToMeshIndex[] array - *numVerts += mesh->VertexCount(); - Assert( mesh->VertexCount() == pMeshGroup->m_NumVertices ); - for ( int l = 0; l < pMeshGroup->m_NumStrips; ++l ) - { - OptimizedModel::StripHeader_t *pStripData = &pMeshGroup->m_pStripData[l]; - *pTriCount += pStripData->numIndices / 3; - } - } - } - } - return size; -} - -// APSFIXME: needs to only do models that are resident, sizes might be wrong, i.e lacking compressed vert state? -CON_COMMAND_F( model_list, "Dump model list to file", FCVAR_CHEAT | FCVAR_DONTRECORD ) -{ - // don't run this on dedicated servers - if ( sv.IsDedicated() ) - return; - - if ( g_pFileSystem ) - { - FileHandle_t fileHandle = g_pFileSystem->Open( "model_list.csv", "wt", "GAME" ); - - if ( fileHandle ) - { - const char *substring = NULL; - if ( args.ArgC() > 1 ) - { - substring = args[1]; - } - - g_pFileSystem->FPrintf( fileHandle, "name,dataSize,numVerts,nTriCount,dataSizeLod0,numVertsLod0,nTriCountLod0,numBones,numParts,numLODs,numMeshes\n" ); - - for ( int i = 0; i < modelloader->GetCount(); i++ ) - { - const char* name = "Unknown"; - int dataSizeLod0 = 0; - int dataSize = 0; - int numParts = 0; - int numBones = 0; - int numVertsLod0 = 0; - int numVerts = 0; - int numLODs = 0; - int numMeshes = 0; - int nTriCount = 0; - int nTriCountLod0 = 0; - - model_t* model = modelloader->GetModelForIndex( i ); - if ( model ) - { - // other model types are not interesting - if ( model->type != mod_studio ) - continue; - - name = model->strName; - - if ( substring && substring[0] ) - { - if ( Q_stristr( name, substring ) == NULL ) - continue; - } - - studiohwdata_t *hwData = g_pMDLCache->GetHardwareData( model->studio ); - if ( hwData ) - { - numMeshes = hwData->m_NumStudioMeshes; - numLODs = hwData->m_NumLODs; - dataSize = ComputeSize( hwData, &numVerts, &nTriCount, false ); // Size of vertex data - dataSizeLod0 = ComputeSize( hwData, &numVertsLod0, &nTriCountLod0, true ); - } - - studiohdr_t *pStudioHdr = (studiohdr_t *)modelloader->GetExtraData( model ); - dataSize += pStudioHdr->length; // Size of MDL file - numBones = pStudioHdr->numbones; - numParts = pStudioHdr->numbodyparts; - - g_pFileSystem->FPrintf( fileHandle, "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", - name, dataSize, numVerts, nTriCount, dataSizeLod0, numVertsLod0, nTriCountLod0, numBones, numParts, numLODs, numMeshes ); - } - } - - g_pFileSystem->Close( fileHandle ); - Msg( "Created \"model_list.csv\" in the game folder.\n" ); - } - } -} -#endif // WIN32 - - - -CON_COMMAND_F( mod_dynamicmodeldebug, "debug spew for dynamic model loading", FCVAR_HIDDEN | FCVAR_DONTRECORD ) -{ - ((CModelLoader*)modelloader)->DebugPrintDynamicModels(); -} - -#include "server.h" -#ifndef SWDS -#include "client.h" -#endif -void CModelLoader::DebugPrintDynamicModels() -{ - Msg( "network table (server):\n" ); - if ( sv.GetDynamicModelsTable() ) - { - for ( int i = 0; i < sv.GetDynamicModelsTable()->GetNumStrings(); ++i ) - { - int dummy = 0; - char* data = (char*) sv.GetDynamicModelsTable()->GetStringUserData( i, &dummy ); - bool bLoadedOnServer = !(data && dummy && data[0] == 0); - Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', sv.GetDynamicModelsTable()->GetString(i) ); - } - } - -#ifndef SWDS - Msg( "\nnetwork table (client):\n" ); - if ( cl.m_pDynamicModelsTable ) - { - for ( int i = 0; i < cl.m_pDynamicModelsTable->GetNumStrings(); ++i ) - { - int dummy = 0; - char* data = (char*) cl.m_pDynamicModelsTable->GetStringUserData( i, &dummy ); - bool bLoadedOnServer = !(data && dummy && data[0] == 0); - Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', cl.m_pDynamicModelsTable->GetString(i) ); - } - } -#endif - - extern IVModelInfo *modelinfo; - extern IVModelInfoClient *modelinfoclient; - Msg( "\ndynamic models:\n" ); - for ( UtlHashHandle_t h = m_DynamicModels.FirstHandle(); h != m_DynamicModels.InvalidHandle(); h = m_DynamicModels.NextHandle(h) ) - { - CDynamicModelInfo &dyn = m_DynamicModels[h]; - int idx = modelinfo->GetModelIndex( m_DynamicModels.Key(h)->strName ); -#ifndef SWDS - if ( idx == -1 ) idx = modelinfoclient->GetModelIndex( m_DynamicModels.Key(h)->strName ); -#endif - Msg( "%d (%d%c): %s [ref: %d (%dc)] %s%s%s%s\n", idx, ((-2 - idx) >> 1), (idx & 1) ? 'c' : 's', - m_DynamicModels.Key(h)->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount, - (dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED) ? " QUEUED" : "", - (dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ? " LOADING" : "", - (dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ? " CLIENTREADY" : "", - (dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY) ? " ALLREADY" : "" ); - } -} +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Model loading / unloading interface +// +// $NoKeywords: $ +//===========================================================================// + +#include "render_pch.h" +#include "common.h" +#include "modelloader.h" +#include "sysexternal.h" +#include "cmd.h" +#include "istudiorender.h" +#include "engine/ivmodelinfo.h" +#include "draw.h" +#include "zone.h" +#include "edict.h" +#include "cmodel_engine.h" +#include "cdll_engine_int.h" +#include "iscratchpad3d.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/materialsystem_config.h" +#include "gl_rsurf.h" +#include "video/ivideoservices.h" +#include "materialsystem/itexture.h" +#include "Overlay.h" +#include "utldict.h" +#include "mempool.h" +#include "r_decal.h" +#include "l_studio.h" +#include "gl_drawlights.h" +#include "tier0/icommandline.h" +#include "MapReslistGenerator.h" +#ifndef SWDS +#include "vgui_baseui_interface.h" +#endif +#include "engine/ivmodelrender.h" +#include "host.h" +#include "datacache/idatacache.h" +#include "sys_dll.h" +#include "datacache/imdlcache.h" +#include "gl_cvars.h" +#include "vphysics_interface.h" +#include "filesystem/IQueuedLoader.h" +#include "tier2/tier2.h" +#include "lightcache.h" +#include "lumpfiles.h" +#include "tier2/fileutils.h" +#include "UtlSortVector.h" +#include "utlhashtable.h" +#include "tier1/lzmaDecoder.h" +#include "eiface.h" +#include "server.h" +#include "ifilelist.h" +#include "LoadScreenUpdate.h" +#include "optimize.h" +#include "networkstringtable.h" +#include "tier1/callqueue.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar mat_loadtextures( "mat_loadtextures", "1", FCVAR_CHEAT ); + +// OS X and Linux are blowing up right now due to this. Benefits vs possible regressions on DX less clear. +#if defined( DX_TO_GL_ABSTRACTION ) || defined( STAGING_ONLY ) + #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "1" +#else + #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "0" +#endif +static ConVar mod_offline_hdr_switch( "mod_offline_hdr_switch", CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH, 0, + "Re-order the HDR/LDR mode switch to do most of the material system " + "reloading with the device offline. This reduces unnecessary device " + "resource uploads and may drastically reduce load time and memory pressure " + "on certain drivers, but may trigger bugs in some very old source engine " + "pathways." ); +static ConVar mod_touchalldata( "mod_touchalldata", "1", 0, "Touch model data during level startup" ); +static ConVar mod_forcetouchdata( "mod_forcetouchdata", "1", 0, "Forces all model file data into cache on model load." ); +ConVar mat_excludetextures( "mat_excludetextures", "0", FCVAR_CHEAT ); + +ConVar r_unloadlightmaps( "r_unloadlightmaps", "0", FCVAR_CHEAT ); +ConVar r_hunkalloclightmaps( "r_hunkalloclightmaps", "1" ); +extern ConVar r_lightcache_zbuffercache; + + +static ConVar mod_dynamicunloadtime( "mod_dynamicunloadtime", "150", FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicunloadtextures( "mod_dynamicunloadtex", "1", FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadpause( "mod_dynamicloadpause", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadthrottle( "mod_dynamicloadthrottle", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadspew( "mod_dynamicloadspew", "0", FCVAR_HIDDEN | FCVAR_DONTRECORD ); + +#define DynamicModelDebugMsg(...) ( mod_dynamicloadspew.GetBool() ? Msg(__VA_ARGS__) : (void)0 ) + + +bool g_bHunkAllocLightmaps; + +extern CGlobalVars g_ServerGlobalVariables; +extern IMaterial *g_materialEmpty; +extern ConVar r_rootlod; + +bool g_bLoadedMapHasBakedPropLighting = false; +bool g_bBakedPropLightingNoSeparateHDR = false; // Some maps only have HDR lighting on props, contained in the file for non-hdr light data + +double g_flAccumulatedModelLoadTime; +double g_flAccumulatedModelLoadTimeStudio; +double g_flAccumulatedModelLoadTimeStaticMesh; +double g_flAccumulatedModelLoadTimeBrush; +double g_flAccumulatedModelLoadTimeSprite; +double g_flAccumulatedModelLoadTimeVCollideSync; +double g_flAccumulatedModelLoadTimeVCollideAsync; +double g_flAccumulatedModelLoadTimeVirtualModel; +double g_flAccumulatedModelLoadTimeMaterialNamesOnly; + +//----------------------------------------------------------------------------- +// A dictionary used to store where to find game lump data in the .bsp file +//----------------------------------------------------------------------------- + +// Extended from the on-disk struct to include uncompressed size and stop propagation of bogus signed values +struct dgamelump_internal_t +{ + dgamelump_internal_t( dgamelump_t &other, unsigned int nCompressedSize ) + : id( other.id ) + , flags( other.flags ) + , version( other.version ) + , offset( Max( other.fileofs, 0 ) ) + , uncompressedSize( Max( other.filelen, 0 ) ) + , compressedSize( nCompressedSize ) + {} + GameLumpId_t id; + unsigned short flags; + unsigned short version; + unsigned int offset; + unsigned int uncompressedSize; + unsigned int compressedSize; +}; + +static CUtlVector< dgamelump_internal_t > g_GameLumpDict; +static char g_GameLumpFilename[128] = { 0 }; + +//void NotifyHunkBeginMapLoad( const char *pszMapName ) +//{ +// Hunk_OnMapStart( 32*1024*1024 ); +//} + + +// FIXME/TODO: Right now Host_FreeToLowMark unloads all models including studio +// models that have Cache_Alloc data, too. This needs to be fixed before shipping + +BEGIN_BYTESWAP_DATADESC( lump_t ) + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dheader_t ) + DEFINE_FIELD( ident, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), + DEFINE_FIELD( mapRevision, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +bool Model_LessFunc( FileNameHandle_t const &a, FileNameHandle_t const &b ) +{ + return a < b; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Implements IModelLoader +//----------------------------------------------------------------------------- +class CModelLoader : public IModelLoader +{ +// Implement IModelLoader interface +public: + CModelLoader() : m_ModelPool( sizeof( model_t ), MAX_KNOWN_MODELS, CUtlMemoryPool::GROW_FAST, "CModelLoader::m_ModelPool" ), + m_Models( 0, 0, Model_LessFunc ) + { + } + + void Init( void ); + void Shutdown( void ); + + int GetCount( void ); + model_t *GetModelForIndex( int i ); + + // Look up name for model + const char *GetName( model_t const *model ); + + // Check cache for data, reload model if needed + void *GetExtraData( model_t *model ); + + int GetModelFileSize( char const *name ); + + // Finds the model, and loads it if it isn't already present. Updates reference flags + model_t *GetModelForName( const char *name, REFERENCETYPE referencetype ); + // Mark as referenced by name + model_t *ReferenceModel( const char *name, REFERENCETYPE referencetype ); + + // Unmasks the referencetype field for the model + void UnreferenceModel( model_t *model, REFERENCETYPE referencetype ); + // Unmasks the specified reference type across all models + void UnreferenceAllModels( REFERENCETYPE referencetype ); + // Set all models to last loaded on server count -1 + void ResetModelServerCounts(); + + // For any models with referencetype blank, frees all memory associated with the model + // and frees up the models slot + void UnloadUnreferencedModels( void ); + void PurgeUnusedModels( void ); + + bool Map_GetRenderInfoAllocated( void ); + void Map_SetRenderInfoAllocated( bool allocated ); + + virtual void Map_LoadDisplacements( model_t *pModel, bool bRestoring ); + + // Validate version/header of a .bsp file + bool Map_IsValid( char const *mapname, bool bQuiet = false ); + + virtual void RecomputeSurfaceFlags( model_t *mod ); + + virtual void Studio_ReloadModels( ReloadType_t reloadType ); + + void Print( void ); + + // Is a model loaded? + virtual bool IsLoaded( const model_t *mod ); + + virtual bool LastLoadedMapHasHDRLighting(void); + + void DumpVCollideStats(); + + // Returns the map model, otherwise NULL, no load or create + model_t *FindModelNoCreate( const char *pModelName ); + + // Finds the model, builds a model entry if not present + model_t *FindModel( const char *name ); + + modtype_t GetTypeFromName( const char *pModelName ); + + // start with -1, list terminates with -1 + int FindNext( int iIndex, model_t **ppModel ); + + virtual void UnloadModel( model_t *pModel ); + + virtual void ReloadFilesInList( IFileList *pFilesToReload ); + + virtual const char *GetActiveMapName( void ); + + // Called by app system once per frame to poll and update dynamic models + virtual void UpdateDynamicModels() { InternalUpdateDynamicModels(false); } + + // Called by server and client engine code to flush unreferenced dynamic models + virtual void FlushDynamicModels() { InternalUpdateDynamicModels(true); } + + // Called by server and client to force-unload dynamic models regardless of refcount! + virtual void ForceUnloadNonClientDynamicModels(); + + // Called by client code to load dynamic models, instead of GetModelForName. + virtual model_t *GetDynamicModel( const char *name, bool bClientOnly ); + + // Called by client code to query dynamic model state + virtual bool IsDynamicModelLoading( model_t *pModel, bool bClientOnly ); + + // Called by client code to refcount dynamic models + virtual void AddRefDynamicModel( model_t *pModel, bool bClientSideRef ); + virtual void ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ); + + // Called by client code or GetDynamicModel + virtual bool RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ); + + // Called by client code or IModelLoadCallback destructor + virtual void UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ); + + virtual void Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ); + + void DebugPrintDynamicModels(); + +// Internal types +private: + // TODO, flag these and allow for UnloadUnreferencedModels to check for allocation type + // so we don't have to flush all of the studio models when we free the hunk + enum + { + FALLOC_USESHUNKALLOC = (1<<31), + FALLOC_USESCACHEALLOC = (1<<30), + }; + +// Internal methods +private: + // Set reference flags and load model if it's not present already + model_t *LoadModel( model_t *model, REFERENCETYPE *referencetype ); + // Unload models ( won't unload referenced models if checkreferences is true ) + void UnloadAllModels( bool checkreference ); + void SetupSubModels( model_t *model, CUtlVector &list ); + + // World/map + void Map_LoadModel( model_t *mod ); + void Map_UnloadModel( model_t *mod ); + void Map_UnloadCubemapSamples( model_t *mod ); + + // World loading helper + void SetWorldModel( model_t *mod ); + void ClearWorldModel( void ); + bool IsWorldModelSet( void ); + int GetNumWorldSubmodels( void ); + + // Sprites + void Sprite_LoadModel( model_t *mod ); + void Sprite_UnloadModel( model_t *mod ); + + // Studio models + void Studio_LoadModel( model_t *mod, bool bTouchAllData ); + void Studio_UnloadModel( model_t *mod ); + + // Byteswap + int UpdateOrCreate( const char *pSourceName, char *pTargetName, int maxLen, bool bForce ); + + // Dynamic load queue + class CDynamicModelInfo; + void QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); + bool CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); + void UpdateDynamicModelLoadQueue(); + + void FinishDynamicModelLoadIfReady( CDynamicModelInfo *dyn, model_t *mod ); + + void InternalUpdateDynamicModels( bool bIgnoreUpdateTime ); + + // Internal data +private: + enum + { + MAX_KNOWN_MODELS = 1024, + }; + + struct ModelEntry_t + { + model_t *modelpointer; + }; + + CUtlMap< FileNameHandle_t, ModelEntry_t > m_Models; + + CUtlMemoryPool m_ModelPool; + + CUtlVector m_InlineModels; + + model_t *m_pWorldModel; + +public: // HACKHACK + worldbrushdata_t m_worldBrushData; + +private: + // local name of current loading model + char m_szLoadName[64]; + + bool m_bMapRenderInfoLoaded; + bool m_bMapHasHDRLighting; + + char m_szActiveMapName[64]; + + // Dynamic model support: + class CDynamicModelInfo + { + public: + enum { QUEUED = 0x01, LOADING = 0x02, CLIENTREADY = 0x04, SERVERLOADING = 0x08, ALLREADY = 0x10, INVALIDFLAG = 0x20 }; // flags + CDynamicModelInfo() : m_iRefCount(0), m_iClientRefCount(0), m_nLoadFlags(INVALIDFLAG), m_uLastTouchedMS_Div256(0) { } + int16 m_iRefCount; + int16 m_iClientRefCount; // also doublecounted in m_iRefCount + uint32 m_nLoadFlags : 8; + uint32 m_uLastTouchedMS_Div256 : 24; + CUtlVector< uintptr_t > m_Callbacks; // low bit = client only + }; + + CUtlHashtable< model_t * , CDynamicModelInfo > m_DynamicModels; + CUtlHashtable< uintptr_t , int > m_RegisteredDynamicCallbacks; + + // Dynamic model load queue + CUtlVector< model_t* > m_DynamicModelLoadQueue; + bool m_bDynamicLoadQueueHeadActive; +}; + +// Expose interface +static CModelLoader g_ModelLoader; +IModelLoader *modelloader = ( IModelLoader * )&g_ModelLoader; + +//----------------------------------------------------------------------------- +// Globals used by the CMapLoadHelper +//----------------------------------------------------------------------------- +static dheader_t s_MapHeader; +static FileHandle_t s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; +static char s_szLoadName[128]; +static char s_szMapName[128]; +static worldbrushdata_t *s_pMap = NULL; +static int s_nMapLoadRecursion = 0; +static CUtlBuffer s_MapBuffer; + +// Lump files are patches for a shipped map +// List of lump files found when map was loaded. Each entry is the lump file index for that lump id. +struct lumpfiles_t +{ + FileHandle_t file; + int lumpfileindex; + lumpfileheader_t header; +}; +static lumpfiles_t s_MapLumpFiles[ HEADER_LUMPS ]; + +CON_COMMAND( mem_vcollide, "Dumps the memory used by vcollides" ) +{ + g_ModelLoader.DumpVCollideStats(); +} + +//----------------------------------------------------------------------------- +// Returns the ref count for this bsp +//----------------------------------------------------------------------------- +int CMapLoadHelper::GetRefCount() +{ + return s_nMapLoadRecursion; +} + +//----------------------------------------------------------------------------- +// Setup a BSP loading context, maintains a ref count. +//----------------------------------------------------------------------------- +void CMapLoadHelper::Init( model_t *pMapModel, const char *loadname ) +{ + if ( ++s_nMapLoadRecursion > 1 ) + { + return; + } + + s_pMap = NULL; + s_szLoadName[ 0 ] = 0; + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + + if ( !pMapModel ) + { + V_strcpy_safe( s_szMapName, loadname ); + } + else + { + V_strcpy_safe( s_szMapName, pMapModel->strName ); + } + + s_MapFileHandle = g_pFileSystem->OpenEx( s_szMapName, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, IsX360() ? "GAME" : NULL ); + if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Host_Error( "CMapLoadHelper::Init, unable to open %s\n", s_szMapName ); + return; + } + + g_pFileSystem->Read( &s_MapHeader, sizeof( dheader_t ), s_MapFileHandle ); + if ( s_MapHeader.ident != IDBSPHEADER ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); + return; + } + + if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, + s_MapHeader.version, BSPVERSION ); + return; + } + + V_strcpy_safe( s_szLoadName, loadname ); + + // Store map version, but only do it once so that the communication between the engine and Hammer isn't broken. The map version + // is incremented whenever a Hammer to Engine session is established so resetting the global map version each time causes a problem. + if ( 0 == g_ServerGlobalVariables.mapversion ) + { + g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; + } + +#ifndef SWDS + InitDLightGlobals( s_MapHeader.version ); +#endif + + s_pMap = &g_ModelLoader.m_worldBrushData; + + // lump_t fix for l4d2 maps + if (s_MapHeader.version == 21 && s_MapHeader.lumps[0].fileofs == 0) + { + DevMsg("Detected l4d2 bsp, fixing lump struct order for compatibility\n"); + + for (int iLump = 0; iLump < HEADER_LUMPS; iLump++) + { + lump_21_t lump21; + V_memcpy(&lump21, &s_MapHeader.lumps[iLump], sizeof(lump_t)); + + s_MapHeader.lumps[iLump].version = lump21.version; + s_MapHeader.lumps[iLump].filelen = lump21.filelen; + s_MapHeader.lumps[iLump].fileofs = lump21.fileofs; + } + } + +#if 0 + // XXX(johns): There are security issues with this system currently. sv_pure doesn't handle unexpected/mismatched + // lumps, so players can create lumps for maps not using them to wallhack/etc.. Currently unused, + // disabling until we have time to make a proper security pass. + if ( IsPC() ) + { + // Now find and open our lump files, and create the master list of them. + for ( int iIndex = 0; iIndex < MAX_LUMPFILES; iIndex++ ) + { + lumpfileheader_t lumpHeader; + char lumpfilename[MAX_PATH]; + + GenerateLumpFileName( s_szMapName, lumpfilename, MAX_PATH, iIndex ); + if ( !g_pFileSystem->FileExists( lumpfilename ) ) + break; + + // Open the lump file + FileHandle_t lumpFile = g_pFileSystem->Open( lumpfilename, "rb" ); + if ( lumpFile == FILESYSTEM_INVALID_HANDLE ) + { + Host_Error( "CMapLoadHelper::Init, failed to load lump file %s\n", lumpfilename ); + return; + } + + // Read the lump header + memset( &lumpHeader, 0, sizeof( lumpHeader ) ); + g_pFileSystem->Read( &lumpHeader, sizeof( lumpfileheader_t ), lumpFile ); + + if ( lumpHeader.lumpID >= 0 && lumpHeader.lumpID < HEADER_LUMPS ) + { + // We may find multiple lump files for the same lump ID. If so, + // close the earlier lump file, because the later one overwrites it. + if ( s_MapLumpFiles[lumpHeader.lumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapLumpFiles[lumpHeader.lumpID].file ); + } + + s_MapLumpFiles[lumpHeader.lumpID].file = lumpFile; + s_MapLumpFiles[lumpHeader.lumpID].lumpfileindex = iIndex; + memcpy( &(s_MapLumpFiles[lumpHeader.lumpID].header), &lumpHeader, sizeof(lumpHeader) ); + } + else + { + Warning("Found invalid lump file '%s'. Lump Id: %d\n", lumpfilename, lumpHeader.lumpID ); + } + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Setup a BSP loading context from a supplied buffer +//----------------------------------------------------------------------------- +void CMapLoadHelper::InitFromMemory( model_t *pMapModel, const void *pData, int nDataSize ) +{ + // valid for 360 only + // 360 has reorganized bsp format and no external lump files + Assert( IsX360() && pData && nDataSize ); + + if ( ++s_nMapLoadRecursion > 1 ) + { + return; + } + + s_pMap = NULL; + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + + V_strcpy_safe( s_szMapName, pMapModel->strName ); + V_FileBase( s_szMapName, s_szLoadName, sizeof( s_szLoadName ) ); + + s_MapBuffer.SetExternalBuffer( (void *)pData, nDataSize, nDataSize ); + + V_memcpy( &s_MapHeader, pData, sizeof( dheader_t ) ); + + if ( s_MapHeader.ident != IDBSPHEADER ) + { + Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); + return; + } + + if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) + { + Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, s_MapHeader.version, BSPVERSION ); + return; + } + + // Store map version + g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; + +#ifndef SWDS + InitDLightGlobals( s_MapHeader.version ); +#endif + + s_pMap = &g_ModelLoader.m_worldBrushData; + + // lump_t fix for l4d2 maps + if (s_MapHeader.version == 21 && s_MapHeader.lumps[0].fileofs == 0) + { + DevMsg("Detected l4d2 bsp, fixing lump struct order for compatibility\n"); + + for (int iLump = 0; iLump < HEADER_LUMPS; iLump++) + { + lump_21_t lump21; + V_memcpy(&lump21, &s_MapHeader.lumps[iLump], sizeof(lump_t)); + + s_MapHeader.lumps[iLump].version = lump21.version; + s_MapHeader.lumps[iLump].filelen = lump21.filelen; + s_MapHeader.lumps[iLump].fileofs = lump21.fileofs; + } + } +} + +//----------------------------------------------------------------------------- +// Shutdown a BSP loading context. +//----------------------------------------------------------------------------- +void CMapLoadHelper::Shutdown( void ) +{ + if ( --s_nMapLoadRecursion > 0 ) + { + return; + } + + if ( s_MapFileHandle != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + } + + if ( IsPC() ) + { + // Close our open lump files + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + if ( s_MapLumpFiles[i].file != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapLumpFiles[i].file ); + } + } + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + } + + s_szLoadName[ 0 ] = 0; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + s_pMap = NULL; + + // discard from memory + if ( s_MapBuffer.Base() ) + { + free( s_MapBuffer.Base() ); + s_MapBuffer.SetExternalBuffer( NULL, 0, 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Free the lighting lump (increases free memory during loading on 360) +//----------------------------------------------------------------------------- +void CMapLoadHelper::FreeLightingLump( void ) +{ + if ( IsX360() && ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) && s_MapBuffer.Base() ) + { + int lightingLump = LumpSize( LUMP_LIGHTING_HDR ) ? LUMP_LIGHTING_HDR : LUMP_LIGHTING; + // Should never have both lighting lumps on 360 + Assert( ( lightingLump == LUMP_LIGHTING ) || ( LumpSize( LUMP_LIGHTING ) == 0 ) ); + + if ( LumpSize( lightingLump ) ) + { + // Check that the lighting lump is the last one in the BSP + int lightingOffset = LumpOffset( lightingLump ); + for ( int i = 0;i < HEADER_LUMPS; i++ ) + { + if ( ( LumpOffset( i ) > lightingOffset ) && ( i != LUMP_PAKFILE ) ) + { + Warning( "CMapLoadHelper: Cannot free lighting lump (should be last before the PAK lump). Regenerate the .360.bsp file with the latest version of makegamedata." ); + return; + } + } + + // Flag the lighting chunk as gone from the BSP (principally, this sets 'filelen' to 0) + V_memset( &s_MapHeader.lumps[ lightingLump ], 0, sizeof( lump_t ) ); + + // Shrink the buffer to free up the space that was used by the lighting lump + void * shrunkBuffer = realloc( s_MapBuffer.Base(), lightingOffset ); + Assert( shrunkBuffer == s_MapBuffer.Base() ); // A shrink would surely never move!!! + s_MapBuffer.SetExternalBuffer( shrunkBuffer, lightingOffset, lightingOffset ); + } + } +} + + +//----------------------------------------------------------------------------- +// Returns the size of a particular lump without loading it... +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpSize( int lumpId ) +{ + // If we have a lump file for this lump, return its length instead + if ( IsPC() && s_MapLumpFiles[lumpId].file != FILESYSTEM_INVALID_HANDLE ) + { + return s_MapLumpFiles[lumpId].header.lumpLength; + } + + lump_t *pLump = &s_MapHeader.lumps[ lumpId ]; + Assert( pLump ); + + // all knowledge of compression is private, they expect and get the original size + int originalSize = s_MapHeader.lumps[lumpId].uncompressedSize; + if ( originalSize != 0 ) + { + return originalSize; + } + + return pLump->filelen; +} + +//----------------------------------------------------------------------------- +// Returns the offset of a particular lump without loading it... +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpOffset( int lumpID ) +{ + // If we have a lump file for this lump, return + // the offset to move past the lump file header. + if ( IsPC() && s_MapLumpFiles[lumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + return s_MapLumpFiles[lumpID].header.lumpOffset; + } + + lump_t *pLump = &s_MapHeader.lumps[ lumpID ]; + Assert( pLump ); + + return pLump->fileofs; +} + +//----------------------------------------------------------------------------- +// Loads one element in a lump. +//----------------------------------------------------------------------------- +void CMapLoadHelper::LoadLumpElement( int nElemIndex, int nElemSize, void *pData ) +{ + if ( !nElemSize || !m_nLumpSize ) + { + return; + } + + // supply from memory + if ( nElemIndex * nElemSize + nElemSize <= m_nLumpSize ) + { + V_memcpy( pData, m_pData + nElemIndex * nElemSize, nElemSize ); + } + else + { + // out of range + Assert( 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Loads one element in a lump. +//----------------------------------------------------------------------------- +void CMapLoadHelper::LoadLumpData( int offset, int size, void *pData ) +{ + if ( !size || !m_nLumpSize ) + { + return; + } + + if ( offset + size <= m_nLumpSize ) + { + V_memcpy( pData, m_pData + offset, size ); + } + else + { + // out of range + Assert( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mapfile - +// lumpToLoad - +//----------------------------------------------------------------------------- +CMapLoadHelper::CMapLoadHelper( int lumpToLoad ) +{ + if ( lumpToLoad < 0 || lumpToLoad >= HEADER_LUMPS ) + { + Sys_Error( "Can't load lump %i, range is 0 to %i!!!", lumpToLoad, HEADER_LUMPS - 1 ); + } + + m_nLumpID = lumpToLoad; + m_nLumpSize = 0; + m_nLumpOffset = -1; + m_pData = NULL; + m_pRawData = NULL; + m_pUncompressedData = NULL; + + // Load raw lump from disk + lump_t *lump = &s_MapHeader.lumps[ lumpToLoad ]; + Assert( lump ); + + m_nLumpSize = lump->filelen; + m_nLumpOffset = lump->fileofs; + m_nLumpVersion = lump->version; + + FileHandle_t fileToUse = s_MapFileHandle; + + // If we have a lump file for this lump, use it instead + if ( IsPC() && s_MapLumpFiles[lumpToLoad].file != FILESYSTEM_INVALID_HANDLE ) + { + fileToUse = s_MapLumpFiles[lumpToLoad].file; + m_nLumpSize = s_MapLumpFiles[lumpToLoad].header.lumpLength; + m_nLumpOffset = s_MapLumpFiles[lumpToLoad].header.lumpOffset; + m_nLumpVersion = s_MapLumpFiles[lumpToLoad].header.lumpVersion; + + // Store off the lump file name + GenerateLumpFileName( s_szLoadName, m_szLumpFilename, MAX_PATH, s_MapLumpFiles[lumpToLoad].lumpfileindex ); + } + + if ( !m_nLumpSize ) + { + // this lump has no data + return; + } + + if ( s_MapBuffer.Base() ) + { + // bsp is in memory + m_pData = (unsigned char*)s_MapBuffer.Base() + m_nLumpOffset; + } + else + { + if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Sys_Error( "Can't load map from invalid handle!!!" ); + } + + unsigned nOffsetAlign, nSizeAlign, nBufferAlign; + g_pFileSystem->GetOptimalIOConstraints( fileToUse, &nOffsetAlign, &nSizeAlign, &nBufferAlign ); + + bool bTryOptimal = ( m_nLumpOffset % 4 == 0 ); // Don't return badly aligned data + unsigned int alignedOffset = m_nLumpOffset; + unsigned int alignedBytesToRead = ( ( m_nLumpSize ) ? m_nLumpSize : 1 ); + + if ( bTryOptimal ) + { + alignedOffset = AlignValue( ( alignedOffset - nOffsetAlign ) + 1, nOffsetAlign ); + alignedBytesToRead = AlignValue( ( m_nLumpOffset - alignedOffset ) + alignedBytesToRead, nSizeAlign ); + } + + m_pRawData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( fileToUse, alignedBytesToRead, alignedOffset ); + if ( !m_pRawData && m_nLumpSize ) + { + Sys_Error( "Can't load lump %i, allocation of %i bytes failed!!!", lumpToLoad, m_nLumpSize + 1 ); + } + + if ( m_nLumpSize ) + { + g_pFileSystem->Seek( fileToUse, alignedOffset, FILESYSTEM_SEEK_HEAD ); + g_pFileSystem->ReadEx( m_pRawData, alignedBytesToRead, alignedBytesToRead, fileToUse ); + m_pData = m_pRawData + ( m_nLumpOffset - alignedOffset ); + } + } + + if ( lump->uncompressedSize != 0 ) + { + // Handle compressed lump -- users of the class see the uncompressed data + AssertMsg( CLZMA::IsCompressed( m_pData ), + "Lump claims to be compressed but is not recognized as LZMA" ); + + m_nLumpSize = CLZMA::GetActualSize( m_pData ); + AssertMsg( lump->uncompressedSize == m_nLumpSize, + "Lump header disagrees with lzma header for compressed lump" ); + + m_pUncompressedData = (unsigned char *)malloc( m_nLumpSize ); + CLZMA::Uncompress( m_pData, m_pUncompressedData ); + + m_pData = m_pUncompressedData; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMapLoadHelper::~CMapLoadHelper( void ) +{ + if ( m_pUncompressedData ) + { + free( m_pUncompressedData ); + } + + if ( m_pRawData ) + { + g_pFileSystem->FreeOptimalReadBuffer( m_pRawData ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : model_t +//----------------------------------------------------------------------------- +worldbrushdata_t *CMapLoadHelper::GetMap( void ) +{ + Assert( s_pMap ); + return s_pMap; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CMapLoadHelper::GetMapName( void ) +{ + return s_szMapName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +char *CMapLoadHelper::GetLoadName( void ) +{ + // If we have a custom lump file for the lump this helper + // is loading, return it instead. + if ( IsPC() && s_MapLumpFiles[m_nLumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + return m_szLumpFilename; + } + + return s_szLoadName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : byte +//----------------------------------------------------------------------------- +byte *CMapLoadHelper::LumpBase( void ) +{ + return m_pData; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpSize() +{ + return m_nLumpSize; +} + +int CMapLoadHelper::LumpOffset() +{ + return m_nLumpOffset; +} + +int CMapLoadHelper::LumpVersion() const +{ + return m_nLumpVersion; +} + +void EnableHDR( bool bEnable ) +{ + if ( g_pMaterialSystemHardwareConfig->GetHDREnabled() == bEnable ) + return; + + g_pMaterialSystemHardwareConfig->SetHDREnabled( bEnable ); + + if ( IsX360() ) + { + // cannot do what the pc does and ditch resources, we're loading! + // can safely do the state update only, knowing that the state change won't affect 360 resources + ((MaterialSystem_Config_t *)g_pMaterialSystemConfig)->SetFlag( MATSYS_VIDCFG_FLAGS_ENABLE_HDR, bEnable ); + return; + } + + ShutdownWellKnownRenderTargets(); + InitWellKnownRenderTargets(); + + /// XXX(JohnS): This works around part of the terribleness the comments below discuss by performing + /// UpdateMaterialSystemConfig with the device offline, removing its need to do multiple re-uploads of + /// things. I am not positive my changes to allow that won't introduce terrible regressions or awaken + /// ancient bugs, hence the kill switch. + bool bUpdateOffline = mod_offline_hdr_switch.GetBool(); +#ifndef DEDICATED + extern void V_RenderVGuiOnly(); +#endif + + if ( bUpdateOffline ) + { +#ifndef DEDICATED + V_RenderVGuiOnly(); +#endif + materials->ReleaseResources(); + } + + // Grah. This is terrible. changin mat_hdr_enabled at the commandline + // will by definition break because the release/restore methods don't call + // ShutdownWellKnownRenderTargets/InitWellKnownRenderTargets. + // Also, this forces two alt-tabs, one for InitWellKnownRenderTargets, one + // for UpdateMaterialSystemConfig. + UpdateMaterialSystemConfig(); + + // Worse, since we need to init+shutdown render targets here, we can't + // rely on UpdateMaterialSystemConfig to release + reacquire resources + // because it could be called at any time. We have to precisely control + // when hdr is changed since this is the only time the code can handle it. + if ( !bUpdateOffline ) + { + materials->ReleaseResources(); + } + materials->ReacquireResources(); +#ifndef DEDICATED + if ( bUpdateOffline ) + { + V_RenderVGuiOnly(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Determine feature flags +//----------------------------------------------------------------------------- +void Map_CheckFeatureFlags() +{ + g_bLoadedMapHasBakedPropLighting = false; + g_bBakedPropLightingNoSeparateHDR = false; + + if ( CMapLoadHelper::LumpSize( LUMP_MAP_FLAGS ) > 0 ) + { + CMapLoadHelper lh( LUMP_MAP_FLAGS ); + dflagslump_t flags_lump; + flags_lump = *( (dflagslump_t *)( lh.LumpBase() ) ); + + // check if loaded map has baked static prop lighting + g_bLoadedMapHasBakedPropLighting = + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ) != 0 || + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) != 0; + g_bBakedPropLightingNoSeparateHDR = + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) == 0; + } +} + +//----------------------------------------------------------------------------- +// Parse the map header for HDR ability. Returns the presence of HDR data only, +// not the HDR enable state. +//----------------------------------------------------------------------------- +bool Map_CheckForHDR( model_t *pModel, const char *pLoadName ) +{ + // parse the map header only + CMapLoadHelper::Init( pModel, pLoadName ); + + bool bHasHDR = false; + if ( IsX360() ) + { + // If this is true, the 360 MUST use HDR, because the LDR data gets stripped out. + bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0; + } + else + { + // might want to also consider the game lumps GAMELUMP_DETAIL_PROP_LIGHTING_HDR + bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 && + CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0; + // Mod_GameLumpSize( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ) > 0 // fixme + } + if ( s_MapHeader.version >= 20 && CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) == 0 ) + { + // This lump only exists in version 20 and greater, so don't bother checking for it on earlier versions. + bHasHDR = false; + } + + bool bEnableHDR = ( IsX360() && bHasHDR ) || + ( bHasHDR && ( mat_hdr_level.GetInt() >= 2 ) && + ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) ); + + EnableHDR( bEnableHDR ); + + // this data really should have been in the header, but it isn't + // establish the features now, before the real bsp load commences + Map_CheckFeatureFlags(); + + CMapLoadHelper::Shutdown(); + + return bHasHDR; +} + +//----------------------------------------------------------------------------- +// Allocates, frees lighting data +//----------------------------------------------------------------------------- +static void AllocateLightingData( worldbrushdata_t *pBrushData, int nSize ) +{ + g_bHunkAllocLightmaps = ( !r_unloadlightmaps.GetBool() && r_hunkalloclightmaps.GetBool() ); + if ( g_bHunkAllocLightmaps ) + { + pBrushData->lightdata = (ColorRGBExp32 *)Hunk_Alloc( nSize, false ); + } + else + { + // Specifically *not* adding it to the hunk. + // If this malloc changes, also change the free in CacheAndUnloadLightmapData() + pBrushData->lightdata = (ColorRGBExp32 *)malloc( nSize ); + } + pBrushData->unloadedlightmaps = false; +} + +static void DeallocateLightingData( worldbrushdata_t *pBrushData ) +{ + if ( pBrushData && pBrushData->lightdata ) + { + if ( !g_bHunkAllocLightmaps ) + { + free( pBrushData->lightdata ); + } + pBrushData->lightdata = NULL; + } +} + +static int ComputeLightmapSize( dface_t *pFace, mtexinfo_t *pTexInfo ) +{ + bool bNeedsBumpmap = false; + if( pTexInfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) + { + bNeedsBumpmap = true; + } + + int lightstyles; + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( pFace->styles[lightstyles] == 255 ) + break; + } + + int nLuxels = (pFace->m_LightmapTextureSizeInLuxels[0]+1) * (pFace->m_LightmapTextureSizeInLuxels[1]+1); + if( bNeedsBumpmap ) + { + return nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); + } + + return nLuxels * 4 * lightstyles; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLighting( CMapLoadHelper &lh ) +{ + if ( !lh.LumpSize() ) + { + lh.GetMap()->lightdata = NULL; + return; + } + + Assert( lh.LumpSize() % sizeof( ColorRGBExp32 ) == 0 ); + Assert ( lh.LumpVersion() != 0 ); + + AllocateLightingData( lh.GetMap(), lh.LumpSize() ); + memcpy( lh.GetMap()->lightdata, lh.LumpBase(), lh.LumpSize()); + + if ( IsX360() ) + { + // Free the lighting lump, to increase the amount of memory free during the rest of loading + CMapLoadHelper::FreeLightingLump(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadWorldlights( CMapLoadHelper &lh, bool bIsHDR ) +{ + lh.GetMap()->shadowzbuffers = NULL; + if (!lh.LumpSize()) + { + lh.GetMap()->numworldlights = 0; + lh.GetMap()->worldlights = NULL; + return; + } + + if (s_MapHeader.version < BSPVERSION) + { + DevMsg("Detected bsp version lower than 21, fixing dworldlight_t struct order for compatibility\n"); + + lh.GetMap()->numworldlights = lh.LumpSize() / (sizeof(dworldlight_t) - sizeof(Vector)); + lh.GetMap()->worldlights = (dworldlight_t*)Hunk_AllocName(lh.GetMap()->numworldlights * sizeof(dworldlight_t), va("%s [%s]", lh.GetLoadName(), "worldlights")); + for (int iLight = 0; iLight < lh.GetMap()->numworldlights; iLight++) + { + memcpy(&lh.GetMap()->worldlights[iLight], lh.LumpBase() + iLight * (sizeof(dworldlight_t) - sizeof(Vector)), sizeof(Vector) * 3); + lh.GetMap()->worldlights[iLight].shadow_cast_offset = lh.GetMap()->worldlights[iLight].origin; + memcpy((byte*)&lh.GetMap()->worldlights[iLight] + sizeof(Vector) * 4, lh.LumpBase() + iLight * (sizeof(dworldlight_t) - sizeof(Vector)) + sizeof(Vector) * 3, sizeof(dworldlight_t) - sizeof(Vector) * 4); + } + } + else + { + // dworldlight_t fix + int nNumWorldLights = lh.LumpSize() / sizeof(dworldlight_old_t); + + lh.GetMap()->numworldlights = nNumWorldLights; + lh.GetMap()->worldlights = (dworldlight_t*)Hunk_AllocName(nNumWorldLights * sizeof(dworldlight_t), va("%s [%s]", lh.GetLoadName(), "worldlights")); + } +#if !defined( SWDS ) + if ( r_lightcache_zbuffercache.GetInt() ) + { + size_t zbufSize = lh.GetMap()->numworldlights * sizeof( lightzbuffer_t ); + lh.GetMap()->shadowzbuffers = ( lightzbuffer_t *) Hunk_AllocName( zbufSize, va( "%s [%s]", lh.GetLoadName(), "shadowzbuffers" ) ); + memset( lh.GetMap()->shadowzbuffers, 0, zbufSize ); // mark empty + } +#endif + + // Fixup for backward compatability + for ( int i = 0; i < lh.GetMap()->numworldlights; i++ ) + { + if( lh.GetMap()->worldlights[i].type == emit_spotlight) + { + if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && + (lh.GetMap()->worldlights[i].linear_attn == 0.0) && + (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) + { + lh.GetMap()->worldlights[i].quadratic_attn = 1.0; + } + + if (lh.GetMap()->worldlights[i].exponent == 0.0) + lh.GetMap()->worldlights[i].exponent = 1.0; + } + else if( lh.GetMap()->worldlights[i].type == emit_point) + { + // To match earlier lighting, use quadratic... + if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && + (lh.GetMap()->worldlights[i].linear_attn == 0.0) && + (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) + { + lh.GetMap()->worldlights[i].quadratic_attn = 1.0; + } + } + + // I replaced the cuttoff_dot field (which took a value from 0 to 1) + // with a max light radius. Radius of less than 1 will never happen, + // so I can get away with this. When I set radius to 0, it'll + // run the old code which computed a radius + if (lh.GetMap()->worldlights[i].radius < 1) + { + lh.GetMap()->worldlights[i].radius = ComputeLightRadius( &lh.GetMap()->worldlights[i], bIsHDR ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadVertices( void ) +{ + dvertex_t *in; + mvertex_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_VERTEXES ); + + in = (dvertex_t *)lh.LumpBase(); + if ( lh.LumpSize() % sizeof(*in) ) + { + Host_Error( "Mod_LoadVertices: funny lump size in %s", lh.GetMapName() ); + } + count = lh.LumpSize() / sizeof(*in); + out = (mvertex_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "vertexes" ) ); + + lh.GetMap()->vertexes = out; + lh.GetMap()->numvertexes = count; + + for ( i=0 ; iposition[0] = in->point[0]; + out->position[1] = in->point[1]; + out->position[2] = in->point[2]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mins - +// maxs - +// Output : float +//----------------------------------------------------------------------------- +static float RadiusFromBounds (Vector& mins, Vector& maxs) +{ + int i; + Vector corner; + + for (i=0 ; i<3 ; i++) + { + corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); + } + + return VectorLength( corner ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadSubmodels( CUtlVector &submodelList ) +{ + dmodel_t *in; + int i, j, count; + + CMapLoadHelper lh( LUMP_MODELS ); + + in = (dmodel_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error("Mod_LoadSubmodels: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + + submodelList.SetCount( count ); + lh.GetMap()->numsubmodels = count; + + for ( i=0 ; imins[j] - 1; + submodelList[i].maxs[j] = in->maxs[j] + 1; + submodelList[i].origin[j] = in->origin[j]; + } + submodelList[i].radius = RadiusFromBounds (submodelList[i].mins, submodelList[i].maxs); + submodelList[i].headnode = in->headnode; + submodelList[i].firstface = in->firstface; + submodelList[i].numfaces = in->numfaces; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : medge_t *Mod_LoadEdges +//----------------------------------------------------------------------------- +medge_t *Mod_LoadEdges ( void ) +{ + dedge_t *in; + medge_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_EDGES ); + + in = (dedge_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadEdges: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + medge_t *pedges = new medge_t[count]; + + out = pedges; + + for ( i=0 ; iv[0] = in->v[0]; + out->v[1] = in->v[1]; + } + + // delete this in the loader + return pedges; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadOcclusion( void ) +{ + CMapLoadHelper lh( LUMP_OCCLUSION ); + + worldbrushdata_t *b = lh.GetMap(); + b->numoccluders = 0; + b->occluders = NULL; + b->numoccluderpolys = 0; + b->occluderpolys = NULL; + b->numoccludervertindices = 0; + b->occludervertindices = NULL; + + if ( !lh.LumpSize() ) + { + return; + } + + CUtlBuffer buf( lh.LumpBase(), lh.LumpSize(), CUtlBuffer::READ_ONLY ); + + switch( lh.LumpVersion() ) + { + case LUMP_OCCLUSION_VERSION: + { + b->numoccluders = buf.GetInt(); + if (b->numoccluders) + { + int nSize = b->numoccluders * sizeof(doccluderdata_t); + b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); + buf.Get( b->occluders, nSize ); + } + + b->numoccluderpolys = buf.GetInt(); + if (b->numoccluderpolys) + { + int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); + b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); + buf.Get( b->occluderpolys, nSize ); + } + + b->numoccludervertindices = buf.GetInt(); + if (b->numoccludervertindices) + { + int nSize = b->numoccludervertindices * sizeof(int); + b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); + buf.Get( b->occludervertindices, nSize ); + } + } + break; + + case 1: + { + b->numoccluders = buf.GetInt(); + if (b->numoccluders) + { + int nSize = b->numoccluders * sizeof(doccluderdata_t); + b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); + + doccluderdataV1_t temp; + for ( int i = 0; i < b->numoccluders; ++i ) + { + buf.Get( &temp, sizeof(doccluderdataV1_t) ); + memcpy( &b->occluders[i], &temp, sizeof(doccluderdataV1_t) ); + b->occluders[i].area = 1; + } + } + + b->numoccluderpolys = buf.GetInt(); + if (b->numoccluderpolys) + { + int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); + b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); + buf.Get( b->occluderpolys, nSize ); + } + + b->numoccludervertindices = buf.GetInt(); + if (b->numoccludervertindices) + { + int nSize = b->numoccludervertindices * sizeof(int); + b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); + buf.Get( b->occludervertindices, nSize ); + } + } + break; + + case 0: + break; + + default: + Host_Error("Invalid occlusion lump version!\n"); + break; + } +} + + + +// UNDONE: Really, it's stored 2 times because the texture system keeps a +// copy of the name too. I guess we'll get rid of this when we have a material +// system that works without a graphics context. At that point, everyone can +// reference the name in the material, or just the material itself. +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadTexdata( void ) +{ + // Don't bother loading these again; they're already stored in the collision model + // which is guaranteed to be loaded at this point + s_pMap->numtexdata = GetCollisionBSPData()->numtextures; + s_pMap->texdata = GetCollisionBSPData()->map_surfaces.Base(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadTexinfo( void ) +{ + texinfo_t *in; + mtexinfo_t *out; + int i, j, count; + // UNDONE: Fix this + + CMapLoadHelper lh( LUMP_TEXINFO ); + + in = (texinfo_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadTexinfo: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mtexinfo_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "texinfo" ) ); + + s_pMap->texinfo = out; + s_pMap->numtexinfo = count; + + bool loadtextures = mat_loadtextures.GetBool(); + + for ( i=0 ; itextureVecsTexelsPerWorldUnits[j][k] = in->textureVecsTexelsPerWorldUnits[j][k]; + out->lightmapVecsLuxelsPerWorldUnits[j][k] = in->lightmapVecsLuxelsPerWorldUnits[j][k] ; + } + } + + // assume that the scale is the same on both s and t. + out->luxelsPerWorldUnit = VectorLength( out->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D() ); + out->worldUnitsPerLuxel = 1.0f / out->luxelsPerWorldUnit; + + out->flags = in->flags; + out->texinfoFlags = 0; + + if ( loadtextures ) + { + if ( in->texdata >= 0 ) + { + out->material = GL_LoadMaterial( lh.GetMap()->texdata[ in->texdata ].name, TEXTURE_GROUP_WORLD ); + } + else + { + DevMsg( "Mod_LoadTexinfo: texdata < 0 (index==%i/%i)\n", i, count ); + out->material = NULL; + } + if ( !out->material ) + { + out->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + } + else + { + out->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + } +} + +// code to scan the lightmaps for empty lightstyles +static void LinearToGamma( unsigned char *pDstRGB, const float *pSrcRGB ) +{ + pDstRGB[0] = LinearToScreenGamma( pSrcRGB[0] ); + pDstRGB[1] = LinearToScreenGamma( pSrcRGB[1] ); + pDstRGB[2] = LinearToScreenGamma( pSrcRGB[2] ); +} + +static void CheckSurfaceLighting( SurfaceHandle_t surfID, worldbrushdata_t *pBrushData ) +{ +#if !defined( SWDS ) + host_state.worldbrush = pBrushData; + msurfacelighting_t *pLighting = SurfaceLighting( surfID, pBrushData ); + + if( !pLighting->m_pSamples ) + return; + + int smax = ( pLighting->m_LightmapExtents[0] ) + 1; + int tmax = ( pLighting->m_LightmapExtents[1] ) + 1; + int offset = smax * tmax; + if ( SurfHasBumpedLightmaps( surfID ) ) + { + offset *= ( NUM_BUMP_VECTS + 1 ); + } + + + // how many lightmaps does this surface have? + int maxLightmapIndex = 0; + for (int maps = 1 ; maps < MAXLIGHTMAPS && pLighting->m_nStyles[maps] != 255 ; ++maps) + { + maxLightmapIndex = maps; + } + + if ( maxLightmapIndex < 1 ) + return; + + // iterate and test each lightmap + for ( int maps = maxLightmapIndex; maps != 0; maps-- ) + { + ColorRGBExp32 *pLightmap = pLighting->m_pSamples + (maps * offset); + float maxLen = -1; + Vector maxLight; + maxLight.Init(); + for ( int i = 0; i < offset; i++ ) + { + Vector c; + ColorRGBExp32ToVector( pLightmap[i], c ); + if ( c.Length() > maxLen ) + { + maxLight = c; + maxLen = c.Length(); + } + } + unsigned char color[4]; + LinearToGamma( color, maxLight.Base() ); + const int minLightVal = 1; + if ( color[0] <= minLightVal && color[1] <= minLightVal && color[2] <= minLightVal ) + { + // found a lightmap that is too dark, remove it and shift over the subsequent maps/styles + for ( int i = maps; i < maxLightmapIndex; i++ ) + { + ColorRGBExp32 *pLightmapOverwrite = pLighting->m_pSamples + (i * offset); + memcpy( pLightmapOverwrite, pLightmapOverwrite+offset, offset * sizeof(*pLightmapOverwrite) ); + pLighting->m_nStyles[i] = pLighting->m_nStyles[i+1]; + } + // mark end lightstyle as removed, decrement max index + pLighting->m_nStyles[maxLightmapIndex] = 255; + maxLightmapIndex--; + } + } + // we removed all of the lightstyle maps so clear the flag + if ( maxLightmapIndex == 0 ) + { + surfID->flags &= ~SURFDRAW_HASLIGHTSYTLES; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *s - +// Output : void CalcSurfaceExtents +//----------------------------------------------------------------------------- +static void CalcSurfaceExtents ( CMapLoadHelper& lh, SurfaceHandle_t surfID ) +{ + float textureMins[2], textureMaxs[2], val; + int i,j, e; + mvertex_t *v; + mtexinfo_t *tex; + int bmins[2], bmaxs[2]; + + textureMins[0] = textureMins[1] = 999999; + textureMaxs[0] = textureMaxs[1] = -99999; + + worldbrushdata_t *pBrushData = lh.GetMap(); + tex = MSurf_TexInfo( surfID, pBrushData ); + + for (i=0 ; ivertindices[MSurf_FirstVertIndex( surfID )+i]; + v = &pBrushData->vertexes[e]; + + for (j=0 ; j<2 ; j++) + { + val = v->position[0] * tex->textureVecsTexelsPerWorldUnits[j][0] + + v->position[1] * tex->textureVecsTexelsPerWorldUnits[j][1] + + v->position[2] * tex->textureVecsTexelsPerWorldUnits[j][2] + + tex->textureVecsTexelsPerWorldUnits[j][3]; + if (val < textureMins[j]) + textureMins[j] = val; + if (val > textureMaxs[j]) + textureMaxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + if( MSurf_LightmapExtents( surfID, pBrushData )[i] == 0 && !MSurf_Samples( surfID, pBrushData ) ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; + } + + bmins[i] = Float2Int( textureMins[i] ); + bmaxs[i] = Ceil2Int( textureMaxs[i] ); + MSurf_TextureMins( surfID, pBrushData )[i] = bmins[i]; + MSurf_TextureExtents( surfID, pBrushData )[i] = ( bmaxs[i] - bmins[i] ); + + if ( !(tex->flags & SURF_NOLIGHT) && MSurf_LightmapExtents( surfID, pBrushData )[i] > MSurf_MaxLightmapSizeWithBorder( surfID ) ) + { + Sys_Error ("Bad surface extents on texture %s", tex->material->GetName() ); + } + } + CheckSurfaceLighting( surfID, pBrushData ); +} + +//----------------------------------------------------------------------------- +// Input : *pModel - +// *pLump - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadVertNormals( void ) +{ + CMapLoadHelper lh( LUMP_VERTNORMALS ); + + // get a pointer to the vertex normal data. + Vector *pVertNormals = ( Vector * )lh.LumpBase(); + + // + // verify vertnormals data size + // + if( lh.LumpSize() % sizeof( *pVertNormals ) ) + Host_Error( "Mod_LoadVertNormals: funny lump size in %s!\n", lh.GetMapName() ); + + int count = lh.LumpSize() / sizeof(*pVertNormals); + Vector *out = (Vector *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormals" ) ); + memcpy( out, pVertNormals, lh.LumpSize() ); + + lh.GetMap()->vertnormals = out; + lh.GetMap()->numvertnormals = count; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadVertNormalIndices( void ) +{ + CMapLoadHelper lh( LUMP_VERTNORMALINDICES ); + + // get a pointer to the vertex normal data. + unsigned short *pIndices = ( unsigned short * )lh.LumpBase(); + + int count = lh.LumpSize() / sizeof(*pIndices); + unsigned short *out = (unsigned short *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormalindices" ) ); + memcpy( out, pIndices, lh.LumpSize() ); + + lh.GetMap()->vertnormalindices = out; + lh.GetMap()->numvertnormalindices = count; + + // OPTIMIZE: Water surfaces don't need vertex normals? + int normalIndex = 0; + for( int i = 0; i < lh.GetMap()->numsurfaces; i++ ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, lh.GetMap() ); + MSurf_FirstVertNormal( surfID, lh.GetMap() ) = normalIndex; + normalIndex += MSurf_VertCount( surfID ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimitives( void ) +{ + dprimitive_t *in; + mprimitive_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_PRIMITIVES ); + + in = (dprimitive_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimitives: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mprimitive_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primitives" ) ); + memset( out, 0, count * sizeof( mprimitive_t ) ); + + lh.GetMap()->primitives = out; + lh.GetMap()->numprimitives = count; + for ( i=0 ; ifirstIndex = in->firstIndex; + out->firstVert = in->firstVert; + out->indexCount = in->indexCount; + out->type = in->type; + out->vertCount = in->vertCount; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimVerts( void ) +{ + dprimvert_t *in; + mprimvert_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_PRIMVERTS ); + + in = (dprimvert_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimVerts: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mprimvert_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primverts" ) ); + memset( out, 0, count * sizeof( mprimvert_t ) ); + + lh.GetMap()->primverts = out; + lh.GetMap()->numprimverts = count; + for ( i=0 ; ipos = in->pos; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimIndices( void ) +{ + unsigned short *in; + unsigned short *out; + int count; + + CMapLoadHelper lh( LUMP_PRIMINDICES ); + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimIndices: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va("%s [%s]", lh.GetLoadName(), "primindices" ) ); + memset( out, 0, count * sizeof( unsigned short ) ); + + lh.GetMap()->primindices = out; + lh.GetMap()->numprimindices = count; + + memcpy( out, in, count * sizeof( unsigned short ) ); +} + + +// This allocates memory for a lump and copies the lump data in. +void Mod_LoadLump( + model_t *loadmodel, + int iLump, + char *loadname, + int elementSize, + void **ppData, + int *nElements ) +{ + CMapLoadHelper lh( iLump ); + + if ( lh.LumpSize() % elementSize ) + { + Host_Error( "Mod_LoadLump: funny lump size in %s", loadmodel->strName.String() ); + } + + // How many elements? + *nElements = lh.LumpSize() / elementSize; + + // Make room for the data and copy the data in. + *ppData = Hunk_AllocName( lh.LumpSize(), loadname ); + memcpy( *ppData, lh.LumpBase(), lh.LumpSize() ); +} + + +//----------------------------------------------------------------------------- +// Sets up the msurfacelighting_t structure +//----------------------------------------------------------------------------- +bool Mod_LoadSurfaceLightingV1( msurfacelighting_t *pLighting, dface_t *in, ColorRGBExp32 *pBaseLightData ) +{ + // Get lightmap extents from the file. + pLighting->m_LightmapExtents[0] = in->m_LightmapTextureSizeInLuxels[0]; + pLighting->m_LightmapExtents[1] = in->m_LightmapTextureSizeInLuxels[1]; + pLighting->m_LightmapMins[0] = in->m_LightmapTextureMinsInLuxels[0]; + pLighting->m_LightmapMins[1] = in->m_LightmapTextureMinsInLuxels[1]; + + int i = in->lightofs; + if ( (i == -1) || (!pBaseLightData) ) + { + pLighting->m_pSamples = NULL; + + // Can't have *any* lightstyles if we have no samples.... + for ( i=0; im_nStyles[i] = 255; + } + } + else + { + pLighting->m_pSamples = (ColorRGBExp32 *)( ((byte *)pBaseLightData) + i ); + + for (i=0 ; im_nStyles[i] = in->styles[i]; + } + } + + return ((pLighting->m_nStyles[0] != 0) && (pLighting->m_nStyles[0] != 255)) || (pLighting->m_nStyles[1] != 255); +} + +void *Hunk_AllocNameAlignedClear_( int size, int alignment, const char *pHunkName ) +{ + Assert(IsPowerOfTwo(alignment)); + void *pMem = Hunk_AllocName( alignment + size, pHunkName ); + memset( pMem, 0, size + alignment ); + pMem = (void *)( ( ( ( unsigned long )pMem ) + (alignment-1) ) & ~(alignment-1) ); + + return pMem; +} + +// Allocates a block of T from the hunk. Aligns as specified and clears the memory +template< typename T > +T *Hunk_AllocNameAlignedClear( int count, int alignment, const char *pHunkName ) +{ + return (T *)Hunk_AllocNameAlignedClear_( alignment + count * sizeof(T), alignment, pHunkName ); +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadFaces( void ) +{ + dface_t *in; + int count, surfnum; + int planenum; + int ti, di; + + int face_lump_to_load = LUMP_FACES; + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_FACES_HDR ) > 0 ) + { + face_lump_to_load = LUMP_FACES_HDR; + } + CMapLoadHelper lh( face_lump_to_load ); + + in = (dface_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadFaces: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + + // align these allocations + // If you trip one of these, you need to rethink the alignment of the struct + Assert( sizeof(msurface1_t) == 16 ); + Assert( sizeof(msurface2_t) == 32 ); + Assert( sizeof(msurfacelighting_t) == 32 ); + + msurface1_t *out1 = Hunk_AllocNameAlignedClear< msurface1_t >( count, 16, va( "%s [%s]", lh.GetLoadName(), "surface1" ) ); + msurface2_t *out2 = Hunk_AllocNameAlignedClear< msurface2_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surface2" ) ); + + msurfacelighting_t *pLighting = Hunk_AllocNameAlignedClear< msurfacelighting_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surfacelighting" ) ); + + lh.GetMap()->surfaces1 = out1; + lh.GetMap()->surfaces2 = out2; + lh.GetMap()->surfacelighting = pLighting; + lh.GetMap()->surfacenormals = Hunk_AllocNameAlignedClear< msurfacenormal_t >( count, 2, va( "%s [%s]", lh.GetLoadName(), "surfacenormal" ) ); + lh.GetMap()->numsurfaces = count; + + worldbrushdata_t *pBrushData = lh.GetMap(); + + for ( surfnum=0 ; surfnumfirstedge; + + int vertCount = in->numedges; + MSurf_Flags( surfID ) = 0; + Assert( vertCount <= 255 ); + MSurf_SetVertCount( surfID, vertCount ); + + planenum = in->planenum; + + if ( in->onNode ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NODE; + } + if ( in->side ) + { + MSurf_Flags( surfID ) |= SURFDRAW_PLANEBACK; + } + + out2->plane = lh.GetMap()->planes + planenum; + + ti = in->texinfo; + if (ti < 0 || ti >= lh.GetMap()->numtexinfo) + { + Host_Error( "Mod_LoadFaces: bad texinfo number" ); + } + surfID->texinfo = ti; + surfID->m_bDynamicShadowsEnabled = in->AreDynamicShadowsEnabled(); + mtexinfo_t *pTex = lh.GetMap()->texinfo + ti; + + // big hack! + if ( !pTex->material ) + { + pTex->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + + // lighting info + if ( Mod_LoadSurfaceLightingV1( pLighting, in, lh.GetMap()->lightdata ) ) + { + MSurf_Flags( surfID ) |= SURFDRAW_HASLIGHTSYTLES; + } + + // set the drawing flags flag + if ( pTex->flags & SURF_NOLIGHT ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; + } + + if ( pTex->flags & SURF_NOSHADOWS ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOSHADOWS; + } + + if ( pTex->flags & SURF_WARP ) + { + MSurf_Flags( surfID ) |= SURFDRAW_WATERSURFACE; + } + + if ( pTex->flags & SURF_SKY ) + { + MSurf_Flags( surfID ) |= SURFDRAW_SKY; + } + + di = in->dispinfo; + out2->pDispInfo = NULL; + if( di != -1 ) + { +// out->origSurfaceID = in->origFace; + MSurf_Flags( surfID ) |= SURFDRAW_HAS_DISP; + } + else + { + // non-displacement faces shouldn't come out of VBSP if they have nodraw. + Assert( !(pTex->flags & SURF_NODRAW) ); + + out1->prims.numPrims = in->GetNumPrims(); + out1->prims.firstPrimID = in->firstPrimID; + if ( in->GetNumPrims() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_HAS_PRIMS; + mprimitive_t *pPrim = &pBrushData->primitives[in->firstPrimID]; + if ( pPrim->vertCount > 0 ) + { + MSurf_Flags( surfID ) |= SURFDRAW_DYNAMIC; + } + } + } + + // No shadows on the surface to start with + out2->m_ShadowDecals = SHADOW_DECAL_HANDLE_INVALID; + out2->decals = WORLD_DECAL_HANDLE_INVALID; + + // No overlays on the surface to start with + out2->m_nFirstOverlayFragment = OVERLAY_FRAGMENT_INVALID; + + CalcSurfaceExtents( lh, surfID ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *node - +// *parent - +// Output : void Mod_SetParent +//----------------------------------------------------------------------------- +void Mod_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents >= 0) + return; + Mod_SetParent (node->children[0], node); + Mod_SetParent (node->children[1], node); +} + + +//----------------------------------------------------------------------------- +// Mark an entire subtree as being too small to bother with +//----------------------------------------------------------------------------- +static void MarkSmallNode( mnode_t *node ) +{ + if (node->contents >= 0) + return; + node->contents = -2; + MarkSmallNode (node->children[0]); + MarkSmallNode (node->children[1]); +} + +static void CheckSmallVolumeDifferences( mnode_t *pNode, const Vector &parentSize ) +{ + if (pNode->contents >= 0) + return; + + Vector delta; + VectorSubtract( parentSize, pNode->m_vecHalfDiagonal, delta ); + + if ((delta.x < 5) && (delta.y < 5) && (delta.z < 5)) + { + pNode->contents = -3; + CheckSmallVolumeDifferences( pNode->children[0], parentSize ); + CheckSmallVolumeDifferences( pNode->children[1], parentSize ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadNodes( void ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + int i, j, count, p; + dnode_t *in; + mnode_t *out; + + CMapLoadHelper lh( LUMP_NODES ); + + in = (dnode_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadNodes: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mnode_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "nodes" ) ); + + lh.GetMap()->nodes = out; + lh.GetMap()->numnodes = count; + + for ( i=0 ; imins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + p = in->planenum; + out->plane = lh.GetMap()->planes + p; + + out->firstsurface = in->firstface; + out->numsurfaces = in->numfaces; + out->area = in->area; + out->contents = -1; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = in->children[j]; + if (p >= 0) + out->children[j] = lh.GetMap()->nodes + p; + else + out->children[j] = (mnode_t *)(lh.GetMap()->leafs + (-1 - p)); + } + } + + Mod_SetParent (lh.GetMap()->nodes, NULL); // sets nodes and leafs + + // Check for small-area parents... no culling below them... + mnode_t *pNode = lh.GetMap()->nodes; + for ( i=0 ; icontents == -1) + { + if ((pNode->m_vecHalfDiagonal.x <= 50) && (pNode->m_vecHalfDiagonal.y <= 50) && + (pNode->m_vecHalfDiagonal.z <= 50)) + { + // Mark all children as being too small to bother with... + MarkSmallNode( pNode->children[0] ); + MarkSmallNode( pNode->children[1] ); + } + else + { + CheckSmallVolumeDifferences( pNode->children[0], pNode->m_vecHalfDiagonal ); + CheckSmallVolumeDifferences( pNode->children[1], pNode->m_vecHalfDiagonal ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadLeafs_Version_0( CMapLoadHelper &lh ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + dleaf_version_0_t *in; + mleaf_t *out; + int i, j, count, p; + + in = (dleaf_version_0_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); + + lh.GetMap()->leafs = out; + lh.GetMap()->numleafs = count; + + // one sample per leaf + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); + mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; + mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; + + for ( i=0 ; imins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + pTable[i].ambientSampleCount = 1; + pTable[i].firstAmbientSample = i; + pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; + pSamples[i].pad = 0; + Q_memcpy( &pSamples[i].cube, &in->m_AmbientLighting, sizeof(pSamples[i].cube) ); + + + p = in->contents; + out->contents = p; + + out->cluster = in->cluster; + out->area = in->area; + out->flags = in->flags; +/* + out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; +*/ + out->firstmarksurface = in->firstleafface; + out->nummarksurfaces = in->numleaffaces; + out->parent = NULL; + + out->dispCount = 0; + + out->leafWaterDataID = in->leafWaterDataID; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadLeafs_Version_1( CMapLoadHelper &lh, CMapLoadHelper &ambientLightingLump, CMapLoadHelper &ambientLightingTable ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + dleaf_t *in; + mleaf_t *out; + int i, j, count, p; + + in = (dleaf_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); + + lh.GetMap()->leafs = out; + lh.GetMap()->numleafs = count; + + if ( ambientLightingLump.LumpVersion() != LUMP_LEAF_AMBIENT_LIGHTING_VERSION || ambientLightingTable.LumpSize() == 0 ) + { + // convert from previous version + CompressedLightCube *inLightCubes = NULL; + if ( ambientLightingLump.LumpSize() ) + { + inLightCubes = ( CompressedLightCube * )ambientLightingLump.LumpBase(); + Assert( ambientLightingLump.LumpSize() % sizeof( CompressedLightCube ) == 0 ); + Assert( ambientLightingLump.LumpSize() / sizeof( CompressedLightCube ) == lh.LumpSize() / sizeof( dleaf_t ) ); + } + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); + mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; + mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; + Vector gray(0.5, 0.5, 0.5); + ColorRGBExp32 grayColor; + VectorToColorRGBExp32( gray, grayColor ); + for ( i = 0; i < count; i++ ) + { + pTable[i].ambientSampleCount = 1; + pTable[i].firstAmbientSample = i; + pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; + pSamples[i].pad = 0; + if ( inLightCubes ) + { + Q_memcpy( &pSamples[i].cube, &inLightCubes[i], sizeof(pSamples[i].cube) ); + } + else + { + for ( j = 0; j < 6; j++ ) + { + pSamples[i].cube.m_Color[j] = grayColor; + } + } + } + } + else + { + Assert( ambientLightingLump.LumpSize() % sizeof( dleafambientlighting_t ) == 0 ); + Assert( ambientLightingTable.LumpSize() % sizeof( dleafambientindex_t ) == 0 ); + Assert((ambientLightingTable.LumpSize() / sizeof(dleafambientindex_t)) == (unsigned)count); // should have one of these per leaf + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( ambientLightingTable.LumpSize(), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( ambientLightingLump.LumpSize(), "LeafAmbientSamples" ); + Q_memcpy( lh.GetMap()->m_pLeafAmbient, ambientLightingTable.LumpBase(), ambientLightingTable.LumpSize() ); + Q_memcpy( lh.GetMap()->m_pAmbientSamples, ambientLightingLump.LumpBase(), ambientLightingLump.LumpSize() ); + } + + + for ( i=0 ; imins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + p = in->contents; + out->contents = p; + + out->cluster = in->cluster; + out->area = in->area; + out->flags = in->flags; +/* + out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; +*/ + out->firstmarksurface = in->firstleafface; + out->nummarksurfaces = in->numleaffaces; + out->parent = NULL; + + out->dispCount = 0; + + out->leafWaterDataID = in->leafWaterDataID; + } +} + +void Mod_LoadLeafs( void ) +{ + CMapLoadHelper lh( LUMP_LEAFS ); + + switch( lh.LumpVersion() ) + { + case 0: + Mod_LoadLeafs_Version_0( lh ); + break; + case 1: + if( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX_HDR ); + Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); + } + else + { + CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING ); + CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX ); + Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); + } + break; + default: + Assert( 0 ); + Error( "Unknown LUMP_LEAFS version\n" ); + break; + } + + worldbrushdata_t *pMap = lh.GetMap(); + cleaf_t *pCLeaf = GetCollisionBSPData()->map_leafs.Base(); + for ( int i = 0; i < pMap->numleafs; i++ ) + { + pMap->leafs[i].dispCount = pCLeaf[i].dispCount; + pMap->leafs[i].dispListStart = pCLeaf[i].dispListStart; + } + // HACKHACK: Copy over the shared global list here. Hunk_Alloc a copy? + pMap->m_pDispInfoReferences = GetCollisionBSPData()->map_dispList.Base(); + pMap->m_nDispInfoReferences = GetCollisionBSPData()->numdisplist; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLeafWaterData( void ) +{ + dleafwaterdata_t *in; + mleafwaterdata_t *out; + int count, i; + + CMapLoadHelper lh( LUMP_LEAFWATERDATA ); + + in = (dleafwaterdata_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleafwaterdata_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafwaterdata" ) ); + + lh.GetMap()->leafwaterdata = out; + lh.GetMap()->numleafwaterdata = count; + for ( i=0 ; iminZ = in->minZ; + out->surfaceTexInfoID = in->surfaceTexInfoID; + out->surfaceZ = in->surfaceZ; + out->firstLeafIndex = -1; + } + if ( count == 1 ) + { + worldbrushdata_t *brush = lh.GetMap(); + for ( i = 0; i < brush->numleafs; i++ ) + { + if ( brush->leafs[i].leafWaterDataID >= 0 ) + { + brush->leafwaterdata[0].firstLeafIndex = i; + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadCubemapSamples( void ) +{ + char textureName[512]; + char loadName[ MAX_PATH ]; + dcubemapsample_t *in; + mcubemapsample_t *out; + int count, i; + + CMapLoadHelper lh( LUMP_CUBEMAPS ); + + V_strcpy_safe( loadName, lh.GetLoadName() ); + + in = (dcubemapsample_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadCubemapSamples: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mcubemapsample_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "cubemapsample" ) ); + + lh.GetMap()->m_pCubemapSamples = out; + lh.GetMap()->m_nCubemapSamples = count; + + bool bHDR = g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE; + int nCreateFlags = bHDR ? 0 : TEXTUREFLAGS_SRGB; + + // We have separate HDR versions of the textures. In order to deal with this, + // we have blahenvmap.hdr.vtf and blahenvmap.vtf. + char *pHDRExtension = ""; + if( bHDR ) + { + pHDRExtension = ".hdr"; + } + + for ( i=0 ; iorigin.Init( ( float )in->origin[0], ( float )in->origin[1], ( float )in->origin[2] ); + out->size = in->size; + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d%s", loadName, ( int )in->origin[0], + ( int )in->origin[1], ( int )in->origin[2], pHDRExtension ); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( out->pTexture ) ) + { + if ( bHDR ) + { + Warning( "Couldn't get HDR '%s' -- ", textureName ); + // try non hdr version + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d", loadName, ( int )in->origin[0], + ( int )in->origin[1], ( int )in->origin[2]); + Warning( "Trying non HDR '%s'\n", textureName); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true ); + } + if ( IsErrorTexture( out->pTexture ) ) + { + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( out->pTexture ) ) + { + out->pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + } + Warning( "Failed, using default cubemap '%s'\n", out->pTexture->GetName() ); + } + } + out->pTexture->IncrementReferenceCount(); + } + + CMatRenderContextPtr pRenderContext( materials ); + + if ( count ) + { + pRenderContext->BindLocalCubemap( lh.GetMap()->m_pCubemapSamples[0].pTexture ); + } + else + { + if ( CommandLine()->CheckParm( "-requirecubemaps" ) ) + { + Sys_Error( "Map \"%s\" does not have cubemaps!", lh.GetMapName() ); + } + + ITexture *pTexture; + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); + pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( pTexture ) ) + { + pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + } + pTexture->IncrementReferenceCount(); + pRenderContext->BindLocalCubemap( pTexture ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLeafMinDistToWater( void ) +{ + CMapLoadHelper lh( LUMP_LEAFMINDISTTOWATER ); + + unsigned short *pTmp = ( unsigned short * )lh.LumpBase(); + + int i; + bool foundOne = false; + for( i = 0; i < ( int )( lh.LumpSize() / sizeof( *pTmp ) ); i++ ) + { + if( pTmp[i] != 65535 ) // FIXME: make a marcro for this. + { + foundOne = true; + break; + } + } + + if( !foundOne || lh.LumpSize() == 0 || !g_pMaterialSystemHardwareConfig || !g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders()) + { + // We don't bother keeping this if: + // 1) there is no water in the map + // 2) we don't have this lump in the bsp file (old bsp file) + // 3) we aren't going to use it because we are on old hardware. + lh.GetMap()->m_LeafMinDistToWater = NULL; + } + else + { + int count; + unsigned short *in; + unsigned short *out; + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafMinDistToWater: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafmindisttowater" ) ); + + memcpy( out, in, sizeof( out[0] ) * count ); + lh.GetMap()->m_LeafMinDistToWater = out; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadMarksurfaces( void ) +{ + int i, j, count; + unsigned short *in; + + CMapLoadHelper lh( LUMP_LEAFFACES ); + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + SurfaceHandle_t *tempDiskData = new SurfaceHandle_t[count]; + + worldbrushdata_t *pBrushData = lh.GetMap(); + pBrushData->marksurfaces = tempDiskData; + pBrushData->nummarksurfaces = count; + + // read in the mark surfaces, count out how many we'll actually need to store + int realCount = 0; + for ( i=0 ; i= lh.GetMap()->numsurfaces) + Host_Error ("Mod_LoadMarksurfaces: bad surface number"); + SurfaceHandle_t surfID = SurfaceHandleFromIndex( j, pBrushData ); + tempDiskData[i] = surfID; + if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) + { + realCount++; + } + } + + // now allocate the permanent list, and copy the non-terrain, non-nodraw surfs into it + SurfaceHandle_t *surfList = (SurfaceHandle_t *)Hunk_AllocName( realCount*sizeof(SurfaceHandle_t), va( "%s [%s]", lh.GetLoadName(), "surfacehandle" ) ); + + int outCount = 0; + mleaf_t *pLeaf = pBrushData->leafs; + for ( i = 0; i < pBrushData->numleafs; i++ ) + { + int firstMark = outCount; + int numMark = 0; + bool foundDetail = false; + int numMarkNode = 0; + for ( j = 0; j < pLeaf[i].nummarksurfaces; j++ ) + { + // write a new copy of the mark surfaces for this leaf, strip out the nodraw & terrain + SurfaceHandle_t surfID = tempDiskData[pLeaf[i].firstmarksurface+j]; + if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) + { + surfList[outCount++] = surfID; + numMark++; + Assert(outCount<=realCount); + if ( MSurf_Flags(surfID) & SURFDRAW_NODE ) + { + // this assert assures that all SURFDRAW_NODE surfs appear coherently + Assert( !foundDetail ); + numMarkNode++; + } + else + { + foundDetail = true; + } + } + } + // update the leaf count + pLeaf[i].nummarksurfaces = numMark; + pLeaf[i].firstmarksurface = firstMark; + pLeaf[i].nummarknodesurfaces = numMarkNode; + } + + // write out the compacted array + pBrushData->marksurfaces = surfList; + pBrushData->nummarksurfaces = realCount; + + // remove the temp copy of the disk data + delete[] tempDiskData; + + //Msg("Must check %d / %d faces\n", checkCount, pModel->brush.numsurfaces ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pedges - +// *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadSurfedges( medge_t *pedges ) +{ + int i, count; + int *in; + unsigned short *out; + + CMapLoadHelper lh( LUMP_SURFEDGES ); + + in = (int *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadSurfedges: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + if (count < 1 || count >= MAX_MAP_SURFEDGES) + Host_Error ("Mod_LoadSurfedges: bad surfedges count in %s: %i", + lh.GetMapName(), count); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "surfedges" ) ); + + lh.GetMap()->vertindices = out; + lh.GetMap()->numvertindices = count; + + for ( i=0 ; iplanes = GetCollisionBSPData()->map_planes.Base(); + s_pMap->numplanes = GetCollisionBSPData()->numplanes; +} + + +//----------------------------------------------------------------------------- +// Returns game lump version +//----------------------------------------------------------------------------- +int Mod_GameLumpVersion( int lumpId ) +{ + for ( int i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + return g_GameLumpDict[i].version; + } + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Returns game lump size +//----------------------------------------------------------------------------- +int Mod_GameLumpSize( int lumpId ) +{ + for ( int i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + return g_GameLumpDict[i].uncompressedSize; + } + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Loads game lumps +//----------------------------------------------------------------------------- +bool Mod_LoadGameLump( int lumpId, void *pOutBuffer, int size ) +{ + int i; + for ( i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + break; + } + } + if ( i < 0 ) + { + // unknown + return false; + } + + byte *pData; + bool bIsCompressed = ( g_GameLumpDict[i].flags & GAMELUMPFLAG_COMPRESSED ); + int dataLength; + int outSize; + if ( bIsCompressed ) + { + // lump data length is always original uncompressed size + // compressed lump data length is determined from next dictionary entry offset + dataLength = g_GameLumpDict[i].compressedSize; + outSize = g_GameLumpDict[i].uncompressedSize; + } + else + { + dataLength = outSize = g_GameLumpDict[i].uncompressedSize; + } + + if ( size < 0 || size < outSize ) + { + // caller must supply a buffer that is large enough to hold the data + return false; + } + + if ( s_MapBuffer.Base() ) + { + // data is in memory + Assert( CMapLoadHelper::GetRefCount() ); + + if ( g_GameLumpDict[i].offset + dataLength > (unsigned int)s_MapBuffer.TellMaxPut() ) + { + // out of range + Assert( 0 ); + return false; + } + + pData = (unsigned char *)s_MapBuffer.Base() + g_GameLumpDict[i].offset; + if ( !bIsCompressed ) + { + V_memcpy( pOutBuffer, pData, outSize ); + return true; + } + } + else + { + // Load file into buffer + FileHandle_t fileHandle = g_pFileSystem->Open( g_GameLumpFilename, "rb" ); + if ( fileHandle == FILESYSTEM_INVALID_HANDLE ) + { + return false; + } + + g_pFileSystem->Seek( fileHandle, g_GameLumpDict[i].offset, FILESYSTEM_SEEK_HEAD ); + + if ( !bIsCompressed ) + { + // read directly into user's buffer + bool bOK = ( g_pFileSystem->Read( pOutBuffer, outSize, fileHandle ) > 0 ); + g_pFileSystem->Close( fileHandle ); + return bOK; + } + else + { + // data is compressed, read into temporary + pData = (byte *)malloc( dataLength ); + bool bOK = ( g_pFileSystem->Read( pData, dataLength, fileHandle ) > 0 ); + g_pFileSystem->Close( fileHandle ); + if ( !bOK ) + { + free( pData ); + return false; + } + } + } + + // We'll fall though to here through here if we're compressed + bool bResult = false; + if ( !CLZMA::IsCompressed( pData ) || CLZMA::GetActualSize( (unsigned char *)pData ) != g_GameLumpDict[i].uncompressedSize ) + { + Warning( "Failed loading game lump %i: lump claims to be compressed but metadata does not match\n", lumpId ); + } + else + { + // uncompress directly into caller's buffer + int outputLength = CLZMA::Uncompress( pData, (unsigned char *)pOutBuffer ); + bResult = ( outputLength > 0 && (unsigned int)outputLength == g_GameLumpDict[i].uncompressedSize ); + } + + if ( !s_MapBuffer.Base() ) + { + // done with temporary buffer + free( pData ); + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Loads game lump dictionary +//----------------------------------------------------------------------------- +void Mod_LoadGameLumpDict( void ) +{ + CMapLoadHelper lh( LUMP_GAME_LUMP ); + + // FIXME: This is brittle. If we ever try to load two game lumps + // (say, in multiple BSP files), the dictionary info I store here will get whacked + + g_GameLumpDict.RemoveAll(); + V_strcpy_safe( g_GameLumpFilename, lh.GetMapName() ); + + unsigned int lhSize = (unsigned int)Max( lh.LumpSize(), 0 ); + if ( lhSize >= sizeof( dgamelumpheader_t ) ) + { + dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)lh.LumpBase(); + + // Ensure (lumpsize * numlumps + headersize) doesn't overflow + const int nMaxGameLumps = ( INT_MAX - sizeof( dgamelumpheader_t ) ) / sizeof( dgamelump_t ); + if ( pGameLumpHeader->lumpCount < 0 || + pGameLumpHeader->lumpCount > nMaxGameLumps || + sizeof( dgamelumpheader_t ) + sizeof( dgamelump_t ) * pGameLumpHeader->lumpCount > lhSize ) + { + Warning( "Bogus gamelump header in map, rejecting\n" ); + } + else + { + // Load in lumps + dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); + for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) + { + if ( pGameLump[i].fileofs >= 0 && + (unsigned int)pGameLump[i].fileofs >= (unsigned int)lh.LumpOffset() && + (unsigned int)pGameLump[i].fileofs < (unsigned int)lh.LumpOffset() + lhSize && + pGameLump[i].filelen > 0 ) + { + unsigned int compressedSize = 0; + if ( i + 1 < pGameLumpHeader->lumpCount && + pGameLump[i+1].fileofs > pGameLump[i].fileofs && + pGameLump[i+1].fileofs >= 0 && + (unsigned int)pGameLump[i+1].fileofs <= (unsigned int)lh.LumpOffset() + lhSize ) + { + compressedSize = (unsigned int)pGameLump[i+1].fileofs - (unsigned int)pGameLump[i].fileofs; + } + else + { + compressedSize = (unsigned int)lh.LumpOffset() + lhSize - (unsigned int)pGameLump[i].fileofs; + } + g_GameLumpDict.AddToTail( { pGameLump[i], compressedSize } ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Re-Loads all of a model's peer data +//----------------------------------------------------------------------------- +void Mod_TouchAllData( model_t *pModel, int nServerCount ) +{ + double t1 = Plat_FloatTime(); + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + virtualmodel_t *pVirtualModel = g_pMDLCache->GetVirtualModel( pModel->studio ); + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeVirtualModel += ( t2 - t1 ); + + if ( pVirtualModel && nServerCount >= 1 ) + { + // ensure all sub models get current count to avoid purge + // mark first to prevent re-entrant issues during possible reload + // skip self, start at children + for ( int i=1; im_group.Count(); ++i ) + { + MDLHandle_t childHandle = (MDLHandle_t)(int)pVirtualModel->m_group[i].cache&0xffff; + model_t *pChildModel = (model_t *)g_pMDLCache->GetUserData( childHandle ); + if ( pChildModel ) + { + // child inherits parent reference + pChildModel->nLoadFlags |= ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_REFERENCEMASK ); + pChildModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED; + pChildModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; + pChildModel->nServerCount = nServerCount; + } + } + } + + // don't touch all the data + if ( !mod_forcetouchdata.GetBool() ) + return; + + g_pMDLCache->TouchAllData( pModel->studio ); +} + +//----------------------------------------------------------------------------- +// Callbacks to get called when various data is loaded or unloaded +//----------------------------------------------------------------------------- +class CMDLCacheNotify : public IMDLCacheNotify +{ +public: + virtual void OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ); + virtual void OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ); + +private: + void ComputeModelFlags( model_t* mod, MDLHandle_t handle ); + + // Sets the bounds from the studiohdr + void SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ); +}; +static CMDLCacheNotify s_MDLCacheNotify; + +//----------------------------------------------------------------------------- +// Computes model flags +//----------------------------------------------------------------------------- +void CMDLCacheNotify::ComputeModelFlags( model_t* pModel, MDLHandle_t handle ) +{ + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); + + // Clear out those flags we set... + pModel->flags &= ~(MODELFLAG_TRANSLUCENT_TWOPASS | MODELFLAG_VERTEXLIT | + MODELFLAG_TRANSLUCENT | MODELFLAG_MATERIALPROXY | MODELFLAG_FRAMEBUFFER_TEXTURE | + MODELFLAG_STUDIOHDR_USES_FB_TEXTURE | MODELFLAG_STUDIOHDR_USES_BUMPMAPPING | MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); + + bool bForceOpaque = (pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE) != 0; + + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS ) + { + pModel->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_FB_TEXTURE ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_FB_TEXTURE; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_BUMPMAPPING ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_BUMPMAPPING; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_ENV_CUBEMAP ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_AMBIENT_BOOST ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_AMBIENT_BOOST; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_DO_NOT_CAST_SHADOWS; + } + + IMaterial *pMaterials[ 128 ]; + int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + for ( int i = 0; i < materialCount; ++i ) + { + IMaterial *pMaterial = pMaterials[ i ]; + if ( !pMaterial ) + continue; + + if ( pMaterial->IsVertexLit() ) + { + pModel->flags |= MODELFLAG_VERTEXLIT; + } + + if ( !bForceOpaque && pMaterial->IsTranslucent() ) + { + //Msg("Translucent material %s for model %s\n", pLODData->ppMaterials[i]->GetName(), pModel->name ); + pModel->flags |= MODELFLAG_TRANSLUCENT; + } + + if ( pMaterial->HasProxy() ) + { + pModel->flags |= MODELFLAG_MATERIALPROXY; + } + + if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame + { + pModel->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; + } + } +} + +//----------------------------------------------------------------------------- +// Sets the bounds from the studiohdr +//----------------------------------------------------------------------------- +void CMDLCacheNotify::SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ) +{ + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); + VectorCopy( pStudioHdr->hull_min, pModel->mins ); + VectorCopy( pStudioHdr->hull_max, pModel->maxs ); + pModel->radius = 0.0f; + for ( int i = 0; i < 3; i++ ) + { + if ( fabs(pModel->mins[i]) > pModel->radius ) + { + pModel->radius = fabs(pModel->mins[i]); + } + + if ( fabs(pModel->maxs[i]) > pModel->radius ) + { + pModel->radius = fabs(pModel->maxs[i]); + } + } +} + +//----------------------------------------------------------------------------- +// Callbacks to get called when various data is loaded or unloaded +//----------------------------------------------------------------------------- +void CMDLCacheNotify::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) +{ + model_t *pModel = (model_t*)g_pMDLCache->GetUserData( handle ); + + // NOTE: A NULL model can occur for dependent MDLHandle_ts (like .ani files) + if ( !pModel ) + return; + + switch( type ) + { + case MDLCACHE_STUDIOHDR: + { + // FIXME: This code only works because it assumes StudioHdr + // is loaded before VCollide. + SetBoundsFromStudioHdr( pModel, handle ); + } + break; + + case MDLCACHE_VCOLLIDE: + { + SetBoundsFromStudioHdr( pModel, handle ); + + // Expand the model bounds to enclose the collision model (should be done in studiomdl) + vcollide_t *pCollide = g_pMDLCache->GetVCollide( handle ); + if ( pCollide ) + { + Vector mins, maxs; + physcollision->CollideGetAABB( &mins, &maxs, pCollide->solids[0], vec3_origin, vec3_angle ); + AddPointToBounds( mins, pModel->mins, pModel->maxs ); + AddPointToBounds( maxs, pModel->mins, pModel->maxs ); + } + } + break; + + case MDLCACHE_STUDIOHWDATA: + ComputeModelFlags( pModel, handle ); + break; + } +} + +void CMDLCacheNotify::OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ) +{ +} + +//----------------------------------------------------------------------------- +// Hooks the cache notify into the MDL cache system +//----------------------------------------------------------------------------- +void ConnectMDLCacheNotify( ) +{ + g_pMDLCache->SetCacheNotify( &s_MDLCacheNotify ); +} + +void DisconnectMDLCacheNotify( ) +{ + g_pMDLCache->SetCacheNotify( NULL ); +} + +//----------------------------------------------------------------------------- +// Initialize studiomdl state +//----------------------------------------------------------------------------- +void InitStudioModelState( model_t *pModel ) +{ + Assert( pModel->type == mod_studio ); + + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHDR ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHDR, pModel->studio ); + } + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHWDATA, pModel->studio ); + } + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VCOLLIDE ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_VCOLLIDE, pModel->studio ); + } +} + +//----------------------------------------------------------------------------- +// Resource loading for models +//----------------------------------------------------------------------------- +class CResourcePreloadModel : public CResourcePreload +{ + static void QueuedLoaderMapCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) + { + if ( loaderError == LOADERERROR_NONE ) + { + // 360 mounts its bsp entirely into memory + // this data is discarded at the conclusion of the entire load process + Assert( CMapLoadHelper::GetRefCount() == 0 ); + CMapLoadHelper::InitFromMemory( (model_t *)pContext, pData, nSize ); + } + } + + virtual bool CreateResource( const char *pName ) + { + modtype_t modType = g_ModelLoader.GetTypeFromName( pName ); + + // each model type resource has entirely differnt schemes for loading/creating + if ( modType == mod_brush ) + { + // expect to be the map bsp model + MEM_ALLOC_CREDIT_( "CResourcePreloadModel(BSP)" ); + model_t *pMapModel = g_ModelLoader.FindModelNoCreate( pName ); + if ( pMapModel ) + { + Assert( CMapLoadHelper::GetRefCount() == 0 ); + + // 360 reads its specialized bsp into memory, + // up to the pack lump, which is guranateed last + char szLoadName[MAX_PATH]; + V_FileBase( pMapModel->strName, szLoadName, sizeof( szLoadName ) ); + CMapLoadHelper::Init( pMapModel, szLoadName ); + int nBytesToRead = CMapLoadHelper::LumpOffset( LUMP_PAKFILE ); + CMapLoadHelper::Shutdown(); + + // create a loader job to perform i/o operation to mount the .bsp + LoaderJob_t loaderJobBSP; + loaderJobBSP.m_pFilename = pMapModel->strName; + loaderJobBSP.m_pPathID = "GAME"; + loaderJobBSP.m_pCallback = QueuedLoaderMapCallback; + loaderJobBSP.m_pContext = (void *)pMapModel; + loaderJobBSP.m_pTargetData = malloc( nBytesToRead ); + loaderJobBSP.m_nBytesToRead = nBytesToRead; + loaderJobBSP.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + g_pQueuedLoader->AddJob( &loaderJobBSP ); + + // create an anonymous job to perform i/o operation to mount the .ain + // the .ain gets claimed later + char szAINName[MAX_PATH] = { 0 }; + V_snprintf( szAINName, sizeof( szAINName ), "maps/graphs/%s.360.ain", szLoadName ); + LoaderJob_t loaderJobAIN; + loaderJobAIN.m_pFilename = szAINName; + loaderJobAIN.m_pPathID = "GAME"; + loaderJobAIN.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + g_pQueuedLoader->AddJob( &loaderJobAIN ); + + return true; + } + } + else if ( modType == mod_studio ) + { + MEM_ALLOC_CREDIT_( "CResourcePreloadModel(MDL)" ); + + char szFilename[MAX_PATH]; + V_ComposeFileName( "models", pName, szFilename, sizeof( szFilename ) ); + + // find model or create empty entry + model_t *pModel = g_ModelLoader.FindModel( szFilename ); + + // mark as touched + pModel->nLoadFlags |= IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + + if ( pModel->nLoadFlags & ( IModelLoader::FMODELLOADER_LOADED|IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // already loaded or preloaded + return true; + } + + // the model in not supposed to be in memory + Assert( pModel->type == mod_bad ); + + // set its type + pModel->type = mod_studio; + + // mark the model so that the normal studio load path can perform a final fixup + pModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; + + // setup the new entry for preload to operate + pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); + + // the model is not supposed to be in memory + // if this hits, the mdlcache is out of sync with the modelloder + // if this hits, the mdlcache has the model, but the modelloader doesn't think so + // if the refcounts go haywire, bad evil bugs will occur + Assert( g_pMDLCache->GetRef( pModel->studio ) == 1 ); + + g_pMDLCache->SetUserData( pModel->studio, pModel ); + + // get it into the cache + g_pMDLCache->PreloadModel( pModel->studio ); + + return true; + } + + // unknown + return false; + } + + //----------------------------------------------------------------------------- + // Called before queued loader i/o jobs are actually performed. Must free up memory + // to ensure i/o requests have enough memory to succeed. The models that were + // touched by the CreateResource() are the ones to keep, all others get purged. + //----------------------------------------------------------------------------- + virtual void PurgeUnreferencedResources() + { + bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; + + // purge any model that was not touched by the preload process + int iIndex = -1; + CUtlVector< model_t* > firstList; + CUtlVector< model_t* > otherList; + for ( ;; ) + { + model_t *pModel; + iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); + if ( iIndex == -1 || !pModel ) + { + // end of list + break; + } + if ( pModel->type == mod_studio ) + { + // models that were touched during the preload stay, otherwise purged + if ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD ) + { + pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + } + else + { + if ( bSpew ) + { + Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); + } + + // Models that have virtual models have to unload first to + // ensure they properly unreference their virtual models. + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) + { + firstList.AddToTail( pModel ); + } + else + { + otherList.AddToTail( pModel ); + } + } + } + } + + for ( int i=0; iIsSameMapLoading() ) + { + g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK ); + } + } + + virtual void PurgeAll() + { + bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; + + // purge any model that was not touched by the preload process + int iIndex = -1; + CUtlVector< model_t* > firstList; + CUtlVector< model_t* > otherList; + for ( ;; ) + { + model_t *pModel; + iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); + if ( iIndex == -1 || !pModel ) + { + // end of list + break; + } + if ( pModel->type == mod_studio ) + { + pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + if ( bSpew ) + { + Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); + } + + // Models that have virtual models have to unload first to + // ensure they properly unreference their virtual models. + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) + { + firstList.AddToTail( pModel ); + } + else + { + otherList.AddToTail( pModel ); + } + } + } + + for ( int i=0; iFlush( MDLCACHE_FLUSH_ANIMBLOCK ); + } + + virtual void OnEndMapLoading( bool bAbort ) + { + // discard the memory mounted bsp + CMapLoadHelper::Shutdown(); + Assert( CMapLoadHelper::GetRefCount() == 0 ); + } +}; +static CResourcePreloadModel s_ResourcePreloadModel; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Init( void ) +{ + m_Models.RemoveAll(); + m_InlineModels.Purge(); + + m_pWorldModel = NULL; + m_bMapRenderInfoLoaded = false; + m_bMapHasHDRLighting = false; + g_bLoadedMapHasBakedPropLighting = false; + + // Make sure we have physcollision and physprop interfaces + CollisionBSPData_LinkPhysics(); + + m_szActiveMapName[0] = '\0'; + + g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_MODEL, &s_ResourcePreloadModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Shutdown( void ) +{ + m_pWorldModel = NULL; + + ForceUnloadNonClientDynamicModels(); + + UnloadAllModels( false ); + + m_ModelPool.Clear(); +} + +int CModelLoader::GetCount( void ) +{ + Assert( m_Models.Count() == m_Models.MaxElement() ); + return m_Models.Count(); +} + +model_t *CModelLoader::GetModelForIndex( int i ) +{ + if ( i < 0 || (unsigned)i >= m_Models.Count() ) + { + Assert( !m_Models.IsValidIndex( i ) ); + return NULL; + } + + Assert( m_Models.IsValidIndex( i ) ); + return m_Models[i].modelpointer; +} + +//----------------------------------------------------------------------------- +// Purpose: Look up name for model +// Input : *model - +// Output : const char +//----------------------------------------------------------------------------- +const char *CModelLoader::GetName( const model_t *pModel ) +{ + if ( pModel ) + { + return pModel->strName; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the model, builds entry if not present, always returns a model +// Input : *name - +// referencetype - +// Output : model_t +//----------------------------------------------------------------------------- +model_t *CModelLoader::FindModel( const char *pName ) +{ + if ( !pName || !pName[0] ) + { + Sys_Error( "CModelLoader::FindModel: NULL name" ); + } + + // inline models are grabbed only from worldmodel + if ( pName[0] == '*' ) + { + int modelNum = atoi( pName + 1 ); + if ( !IsWorldModelSet() ) + { + Sys_Error( "bad inline model number %i, worldmodel not yet setup", modelNum ); + } + + if ( modelNum < 1 || modelNum >= GetNumWorldSubmodels() ) + { + Sys_Error( "bad inline model number %i", modelNum ); + } + return &m_InlineModels[modelNum]; + } + + model_t *pModel = NULL; + + // get a handle suitable to use as the model key + // handles are insensitive to case and slashes + FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName ); + + int i = m_Models.Find( fnHandle ); + if ( i == m_Models.InvalidIndex() ) + { + pModel = (model_t *)m_ModelPool.Alloc(); + Assert( pModel ); + memset( pModel, 0, sizeof( model_t ) ); + + pModel->fnHandle = fnHandle; + + // Mark that we should load from disk + pModel->nLoadFlags = FMODELLOADER_NOTLOADEDORREFERENCED; + + // Copy in name and normalize! + // Various other subsystems fetch this 'object' name to do dictionary lookups, + // which are usually case insensitive, but not to slashes or dotslashes. + pModel->strName = pName; + V_RemoveDotSlashes( pModel->strName.GetForModify(), '/' ); + + ModelEntry_t entry; + entry.modelpointer = pModel; + m_Models.Insert( fnHandle, entry ); + } + else + { + pModel = m_Models[i].modelpointer; + } + + // notify the reslist generator that this model may be referenced later in the level + // (does nothing if reslist generation is not enabled) + MapReslistGenerator().OnModelPrecached( pName ); + + Assert( pModel ); + + return pModel; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the model, and loads it if it isn't already present. Updates reference flags +// Input : *name - +// referencetype - +// Output : model_t +//----------------------------------------------------------------------------- +model_t *CModelLoader::GetModelForName( const char *name, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "GetModelForName: dynamic models must use GetDynamicModel" ); + + // find or build new entry + model_t *model = FindModel( name ); + + // touch and load if not present + model_t *retval = LoadModel( model, &referencetype ); + + return retval; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a reference to the model in question +// Input : *name - +// referencetype - +//----------------------------------------------------------------------------- +model_t *CModelLoader::ReferenceModel( const char *name, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "ReferenceModel: do not use for dynamic models" ); + + model_t *model = FindModel( name ); + + model->nLoadFlags |= referencetype; + + return model; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *entry - +// referencetype - +//----------------------------------------------------------------------------- +model_t *CModelLoader::LoadModel( model_t *mod, REFERENCETYPE *pReferencetype ) +{ + if ( pReferencetype ) + { + mod->nLoadFlags |= *pReferencetype; + } + + // during initial load mark the model with an unique session ticket + // at load end, models that have a mismatch count are considered candidates for purge + // models that get marked, touch *all* their sub data to ensure the cache is pre-populated + // and hitches less during gameplay + bool bTouchAllData = false; + int nServerCount = Host_GetServerCount(); + if ( mod->nServerCount != nServerCount ) + { + // server has changed + mod->nServerCount = nServerCount; + bTouchAllData = true; + } + + // Check if the studio model is in cache. + // The model type will not be set for first time models that need to fall through to the load path. + // A model that needs a post precache fixup will fall through to the load path. + if ( mod->type == mod_studio && !( mod->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // in cache + Verify( g_pMDLCache->GetStudioHdr( mod->studio ) != 0 ); + Assert( FMODELLOADER_LOADED & mod->nLoadFlags ); + + if ( bTouchAllData ) + { + // Touch all related .ani files and sub/dependent models + // only touches once, when server changes + Mod_TouchAllData( mod, nServerCount ); + } + + return mod; + } + + // Check if brushes or sprites are loaded + if ( FMODELLOADER_LOADED & mod->nLoadFlags ) + { + return mod; + } + + // model needs to be loaded + double st = Plat_FloatTime(); + + // Set the name of the current model we are loading + Q_FileBase( mod->strName, m_szLoadName, sizeof( m_szLoadName ) ); + + // load the file + if ( developer.GetInt() > 1 ) + { + DevMsg( "Loading: %s\n", mod->strName.String() ); + } + + mod->type = GetTypeFromName( mod->strName ); + if ( mod->type == mod_bad ) + { + mod->type = mod_studio; + } + + // finalize the model data + switch ( mod->type ) + { + case mod_sprite: + { + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + double t1 = Plat_FloatTime(); + Sprite_LoadModel( mod ); + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeSprite += ( t2 - t1 ); + } + break; + + case mod_studio: + { + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + double t1 = Plat_FloatTime(); + Studio_LoadModel( mod, bTouchAllData ); + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeStudio += ( t2 - t1 ); + } + break; + + case mod_brush: + { + double t1 = Plat_FloatTime(); + + // This is necessary on dedicated clients. On listen + dedicated servers, it's called twice. + // The second invocation is harmless. + // Add to file system before loading so referenced objects in map can use the filename. + g_pFileSystem->AddSearchPath( mod->strName, "GAME", PATH_ADD_TO_HEAD ); + + // the map may have explicit texture exclusion + // the texture state needs to be established before any loading work + if ( IsX360() || mat_excludetextures.GetBool() ) + { + char szExcludePath[MAX_PATH]; + sprintf( szExcludePath, "//MOD/maps/%s_exclude.lst", m_szLoadName ); + g_pMaterialSystem->SetExcludedTextures( szExcludePath ); + } + + // need this before queued loader starts, various systems use this as a cheap map changed state + V_strncpy( m_szActiveMapName, mod->strName, sizeof( m_szActiveMapName ) ); + + //NotifyHunkBeginMapLoad( m_szActiveMapName ); + + bool bQueuedLoader = false; + if ( IsX360() ) + { + // must establish the bsp feature set first to ensure proper state during queued loading + Map_CheckForHDR( mod, m_szLoadName ); + + // Do not optimize map-to-same-map loading in TF + // FIXME/HACK: this fixes a bug (when shipping Orange Box) where static props would sometimes + // disappear when a client disconnects and reconnects to the same map+server + // (static prop lighting data persists when loading map A after map A) + bool bIsTF = !V_stricmp( COM_GetModDirectory(), "tf" ); + bool bOptimizeMapReload = !bIsTF; + + // start the queued loading process + bQueuedLoader = g_pQueuedLoader->BeginMapLoading( mod->strName, g_pMaterialSystemHardwareConfig->GetHDREnabled(), bOptimizeMapReload ); + } + + // the queued loader process needs to own the actual texture update + if ( !bQueuedLoader && ( IsX360() || mat_excludetextures.GetBool() ) ) + { + g_pMaterialSystem->UpdateExcludedTextures(); + } + + BeginLoadingUpdates( MATERIAL_NON_INTERACTIVE_MODE_LEVEL_LOAD ); + g_pFileSystem->BeginMapAccess(); + Map_LoadModel( mod ); + g_pFileSystem->EndMapAccess(); + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeBrush += (t2 - t1); + } + break; + + default: + Assert( 0 ); + break; + }; + + float dt = ( Plat_FloatTime() - st ); + COM_TimestampedLog( "Load of %s took %.3f msec", mod->strName.String(), 1000.0f * dt ); + g_flAccumulatedModelLoadTime += dt; + + return mod; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates the name of the sprite +//----------------------------------------------------------------------------- +//static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsAVI, bool &bIsBIK ) +static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsVideo ) +{ + // If it's a .vmt and they put a path in there, then use the path. + // Otherwise, use the old method of prepending the sprites directory. + Assert( pName != NULL && pOut != NULL ); + + bIsVideo = false; + bool bIsVMT = false; + const char *pExt = V_GetFileExtension( pName ); + if ( pExt != NULL ) + { + bIsVMT = !Q_stricmp( pExt, "vmt" ); + if ( !bIsVMT ) + { + if ( g_pVideo ) + { + bIsVideo = ( g_pVideo->LocateVideoSystemForPlayingFile( pName ) != VideoSystem::NONE ); + } + } + } + + if ( ( bIsVideo || bIsVMT ) && ( strchr( pName, '/' ) || strchr( pName, '\\' ) ) ) + { + // The material system cannot handle a prepended "materials" dir + // Keep .avi extensions on the material to load avi-based materials + if ( bIsVMT ) + { + const char *pNameStart = pName; + if ( Q_stristr( pName, "materials/" ) == pName || + Q_stristr( pName, "materials\\" ) == pName ) + { + // skip past materials/ + pNameStart = &pName[10]; + } + Q_StripExtension( pNameStart, pOut, outLen ); + } + else + { + // name is good as is + Q_strncpy( pOut, pName, outLen ); + } + } + else + { + char szBase[MAX_PATH]; + Q_FileBase( pName, szBase, sizeof( szBase ) ); + Q_snprintf( pOut, outLen, "sprites/%s", szBase ); + } + + return; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int CModelLoader::GetModelFileSize( char const *name ) +{ + if ( !name || !name[ 0 ] ) + return -1; + + model_t *model = FindModel( name ); + + int size = -1; + if ( Q_stristr( model->strName, ".spr" ) || Q_stristr( model->strName, ".vmt" ) ) + { + char spritename[ MAX_PATH ]; + Q_StripExtension( va( "materials/%s", model->strName.String() ), spritename, MAX_PATH ); + Q_DefaultExtension( spritename, ".vmt", sizeof( spritename ) ); + + size = COM_FileSize( spritename ); + } + else + { + size = COM_FileSize( name ); + } + + return size; +} + +//----------------------------------------------------------------------------- +// Purpose: Unmasks the referencetype field for the model +// Input : *model - +// referencetype - +//----------------------------------------------------------------------------- +void CModelLoader::UnreferenceModel( model_t *model, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceModel: do not use for dynamic models" ); + model->nLoadFlags &= ~referencetype; +} + +//----------------------------------------------------------------------------- +// Purpose: Unmasks the specified reference type across all models +// Input : referencetype - +//----------------------------------------------------------------------------- +void CModelLoader::UnreferenceAllModels( REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceAllModels: do not use for dynamic models" ); + + // UNDONE: If we ever free a studio model, write code to free the collision data + // UNDONE: Reference count collision data? + + FOR_EACH_MAP_FAST( m_Models, i ) + { + m_Models[ i ].modelpointer->nLoadFlags &= ~referencetype; + } +} + +//----------------------------------------------------------------------------- +// Purpose: When changing servers the old servercount number is bogus. This +// marks all models as loaded from -1 (e.g. a server count from the +// before time.) +//----------------------------------------------------------------------------- +void CModelLoader::ResetModelServerCounts() +{ + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + pModel->nServerCount = -1; + } +} + + +void CModelLoader::ReloadFilesInList( IFileList *pFilesToReload ) +{ + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + + if ( pModel->type != mod_studio ) + continue; + + if ( !IsLoaded( pModel ) ) + continue; + + if ( pModel->type != mod_studio ) + continue; + + if ( pFilesToReload->IsFileInList( pModel->strName ) ) + { + #ifdef PURE_SERVER_DEBUG_SPEW + Msg( "Reloading model %s\n", pModel->strName.String() ); + #endif + + // Flush out the model cache + // Don't flush vcollides since the vphysics system currently + // has no way of indicating they refer to vcollides + g_pMDLCache->Flush( pModel->studio, (int)(MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + // Get the studiohdr into the cache + g_pMDLCache->GetStudioHdr( pModel->studio ); + +#ifndef _XBOX + // force the collision to load + g_pMDLCache->GetVCollide( pModel->studio ); +#endif + } + else + { + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) + { + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + if ( pStudioHdr ) + { + // Ok, we didn't have to do a full reload, but if any of our materials changed, flush out the studiohwdata because the + // vertex format may have changed. + IMaterial *pMaterials[128]; + int nMaterials = g_pStudioRender->GetMaterialList( pStudioHdr, ARRAYSIZE( pMaterials ), &pMaterials[0] ); + + for ( int iMat=0; iMat < nMaterials; iMat++ ) + { + if ( pMaterials[iMat] && pMaterials[iMat]->WasReloadedFromWhitelist() ) + { + #ifdef PURE_SERVER_DEBUG_SPEW + Msg( "Reloading model %s because material %s was reloaded\n", pModel->strName.String(), pMaterials[iMat]->GetName() ); + #endif + g_pMDLCache->Flush( pModel->studio, MDLCACHE_FLUSH_STUDIOHWDATA ); + break; + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model +// and frees up the models slot +//----------------------------------------------------------------------------- +void CModelLoader::UnloadAllModels( bool bCheckReference ) +{ + model_t *model; + + FOR_EACH_MAP_FAST( m_Models, i ) + { + model = m_Models[ i ].modelpointer; + if ( bCheckReference ) + { + if ( model->nLoadFlags & FMODELLOADER_REFERENCEMASK ) + { + if ( model->type == mod_studio ) + { + g_pMDLCache->MarkAsLoaded(model->studio); + } + continue; + } + } + else + { + // Wipe current flags + model->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; + } + + if ( IsX360() && g_pQueuedLoader->IsMapLoading() && ( model->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // models preloaded by the queued loader are not initially claimed and MUST remain until the end of the load process + // unclaimed models get unloaded during the post load purge + continue; + } + + if ( model->nLoadFlags & ( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + UnloadModel( model ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model +// and frees up the models slot +//----------------------------------------------------------------------------- +void CModelLoader::UnloadUnreferencedModels( void ) +{ + // unload all unreferenced models + UnloadAllModels( true ); +} + + +//----------------------------------------------------------------------------- +// Called at the conclusion of loading. +// Frees all memory associated with models (and their materials) that are not +// marked with the current session. +//----------------------------------------------------------------------------- +void CModelLoader::PurgeUnusedModels( void ) +{ + int nServerCount = Host_GetServerCount(); + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + if ( ( pModel->nLoadFlags & FMODELLOADER_LOADED ) && ( pModel->nServerCount != nServerCount ) ) + { + // mark as unreferenced + // do not unload dynamic models + pModel->nLoadFlags &= (~FMODELLOADER_REFERENCEMASK) | FMODELLOADER_DYNAMIC; + } + } + + // flush dynamic models that have no refcount + FlushDynamicModels(); + + // unload unreferenced models only + UnloadAllModels( true ); + + // now purge unreferenced materials + materials->UncacheUnusedMaterials( true ); +} + +//----------------------------------------------------------------------------- +// Compute whether this submodel uses material proxies or not +//----------------------------------------------------------------------------- +static void Mod_ComputeBrushModelFlags( model_t *mod ) +{ + Assert( mod ); + + worldbrushdata_t *pBrushData = mod->brush.pShared; + // Clear out flags we're going to set + mod->flags &= ~(MODELFLAG_MATERIALPROXY | MODELFLAG_TRANSLUCENT | MODELFLAG_FRAMEBUFFER_TEXTURE | MODELFLAG_TRANSLUCENT_TWOPASS ); + mod->flags = MODELFLAG_HAS_DLIGHT; // force this check the first time + + int i; + int scount = mod->brush.nummodelsurfaces; + bool bHasOpaqueSurfaces = false; + bool bHasTranslucentSurfaces = false; + for ( i = 0; i < scount; ++i ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, pBrushData ); + + // Clear out flags we're going to set + MSurf_Flags( surfID ) &= ~(SURFDRAW_NOCULL | SURFDRAW_TRANS | SURFDRAW_ALPHATEST | SURFDRAW_NODECALS); + + mtexinfo_t *pTex = MSurf_TexInfo( surfID, pBrushData ); + IMaterial* pMaterial = pTex->material; + + if ( pMaterial->HasProxy() ) + { + mod->flags |= MODELFLAG_MATERIALPROXY; + } + + if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame + { + mod->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; + } + + // Deactivate culling if the material is two sided + if ( pMaterial->IsTwoSided() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOCULL; + } + + if ( (pTex->flags & SURF_TRANS) || pMaterial->IsTranslucent() ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + MSurf_Flags( surfID ) |= SURFDRAW_TRANS; + bHasTranslucentSurfaces = true; + } + else + { + bHasOpaqueSurfaces = true; + } + + // Certain surfaces don't want decals at all + if ( (pTex->flags & SURF_NODECALS) || pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SUPPRESS_DECALS ) || pMaterial->IsAlphaTested() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NODECALS; + } + + if ( pMaterial->IsAlphaTested() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_ALPHATEST; + } + } + + if ( bHasOpaqueSurfaces && bHasTranslucentSurfaces ) + { + mod->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; + } +} + + +//----------------------------------------------------------------------------- +// Recomputes translucency for the model... +//----------------------------------------------------------------------------- +void Mod_RecomputeTranslucency( model_t* mod, int nSkin, int nBody, void /*IClientRenderable*/ *pClientRenderable, float fInstanceAlphaModulate ) +{ + if (fInstanceAlphaModulate < 1.0f) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + return; + } + + mod->flags &= ~MODELFLAG_TRANSLUCENT; + + switch( mod->type ) + { + case mod_brush: + { + for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface+i, mod->brush.pShared ); + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* material = MSurf_TexInfo( surfID, mod->brush.pShared )->material; + if ( material->IsTranslucent() ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + break; + } + } + } + break; + + case mod_studio: + { + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( mod->studio ); + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE ) + return; + + IMaterial *pMaterials[ 128 ]; + int materialCount = g_pStudioRender->GetMaterialListFromBodyAndSkin( mod->studio, nSkin, nBody, ARRAYSIZE( pMaterials ), pMaterials ); + for ( int i = 0; i < materialCount; i++ ) + { + if ( pMaterials[i] != NULL ) + { + // Bind material first so all material proxies execute + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( pMaterials[i], pClientRenderable ); + bool bIsTranslucent = pMaterials[i]->IsTranslucent(); + + if ( bIsTranslucent ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + break; + } + } + } + } + break; + } +} + + +//----------------------------------------------------------------------------- +// returns the material count... +//----------------------------------------------------------------------------- +int Mod_GetMaterialCount( model_t* mod ) +{ + switch( mod->type ) + { + case mod_brush: + { + CUtlVector uniqueMaterials( 0, 32 ); + + for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, mod->brush.pShared ); + + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* pMaterial = MSurf_TexInfo( surfID, mod->brush.pShared )->material; + + // Try to find the material in the unique list of materials + // if it's not there, then add it + if (uniqueMaterials.Find(pMaterial) < 0) + uniqueMaterials.AddToTail(pMaterial); + } + + return uniqueMaterials.Size(); + } + break; + + case mod_studio: + { + // FIXME: This should return the list of all materials + // across all LODs if we every decide to implement this + Assert(0); + } + break; + + default: + // unimplemented + Assert(0); + break; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// returns the first n materials. +//----------------------------------------------------------------------------- +int Mod_GetModelMaterials( model_t* pModel, int count, IMaterial** ppMaterials ) +{ + studiohdr_t *pStudioHdr; + int found = 0; + int i; + + switch( pModel->type ) + { + case mod_brush: + { + for ( i = 0; i < pModel->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface + i, pModel->brush.pShared ); + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* pMaterial = MSurf_TexInfo( surfID, pModel->brush.pShared )->material; + + // Try to find the material in the unique list of materials + // if it's not there, then add it + int j = found; + while ( --j >= 0 ) + { + if ( ppMaterials[j] == pMaterial ) + break; + } + if (j < 0) + ppMaterials[found++] = pMaterial; + + // Stop when we've gotten count materials + if ( found >= count ) + return found; + } + } + break; + + case mod_studio: + if ( pModel->ppMaterials ) + { + int nMaterials = ((intptr_t*)(pModel->ppMaterials))[-1]; + found = MIN( count, nMaterials ); + memcpy( ppMaterials, pModel->ppMaterials, found * sizeof( IMaterial* ) ); + } + else + { + // Get the studiohdr into the cache + pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + // Get the list of materials + found = g_pStudioRender->GetMaterialList( pStudioHdr, count, ppMaterials ); + } + break; + + default: + // unimplemented + Assert( 0 ); + break; + } + + return found; +} + + +void Mod_SetMaterialVarFlag( model_t *pModel, unsigned int uiFlag, bool on ) +{ + MaterialVarFlags_t flag = (MaterialVarFlags_t)uiFlag; + IMaterial *pMaterials[ 128 ]; + if ( pModel ) + { + int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + for ( int i = 0; i < materialCount; ++i ) + { + IMaterial *pMaterial = pMaterials[ i ]; + if ( pMaterial ) + { + pMaterial->SetMaterialVarFlag( flag, on ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Used to compute which surfaces are in water or not +//----------------------------------------------------------------------------- + +static void MarkWaterSurfaces_ProcessLeafNode( mleaf_t *pLeaf ) +{ + int i; + + int flags = ( pLeaf->leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; + + SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; + + for( i = 0; i < pLeaf->nummarksurfaces; i++ ) + { + SurfaceHandle_t surfID = pHandle[i]; + ASSERT_SURF_VALID( surfID ); + if( MSurf_Flags( surfID ) & SURFDRAW_WATERSURFACE ) + continue; + + if (SurfaceHasDispInfo( surfID )) + continue; + + MSurf_Flags( surfID ) |= flags; + } + + // FIXME: This is somewhat bogus, but I can do it quickly, and it's + // not clear I need to solve the harder problem. + + // If any portion of a displacement surface hits a water surface, + // I'm going to mark it as being in water, and vice versa. + for ( i = 0; i < pLeaf->dispCount; i++ ) + { + IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); + + if ( pDispInfo ) + { + SurfaceHandle_t parentSurfID = pDispInfo->GetParent(); + MSurf_Flags( parentSurfID ) |= flags; + } + } +} + + +void MarkWaterSurfaces_r( mnode_t *node ) +{ + // no polygons in solid nodes + if (node->contents == CONTENTS_SOLID) + return; // solid + + // if a leaf node, . .mark all the polys as to whether or not they are in water. + if (node->contents >= 0) + { + MarkWaterSurfaces_ProcessLeafNode( (mleaf_t *)node ); + return; + } + + MarkWaterSurfaces_r( node->children[0] ); + MarkWaterSurfaces_r( node->children[1] ); +} + + +//----------------------------------------------------------------------------- +// Computes the sort group for a particular face +//----------------------------------------------------------------------------- +static int SurfFlagsToSortGroup( SurfaceHandle_t surfID, int flags ) +{ + // If we're on the low end, stick everything into the same sort group + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; + + if( flags & SURFDRAW_WATERSURFACE ) + return MAT_SORT_GROUP_WATERSURFACE; + + if( ( flags & ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) == ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) + return MAT_SORT_GROUP_INTERSECTS_WATER_SURFACE; + + if( flags & SURFDRAW_UNDERWATER ) + return MAT_SORT_GROUP_STRICTLY_UNDERWATER; + + if( flags & SURFDRAW_ABOVEWATER ) + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; + + static int warningcount = 0; + if ( ++warningcount < 10 ) + { + Vector vecCenter; + Surf_ComputeCentroid( surfID, &vecCenter ); + DevWarning( "SurfFlagsToSortGroup: unhandled flags (%X) (%s)!\n", flags, MSurf_TexInfo(surfID)->material->GetName() ); + DevWarning( "- This implies you have a surface (usually a displacement) embedded in solid.\n" ); + DevWarning( "- Look near (%.1f, %.1f, %.1f)\n", vecCenter.x, vecCenter.y, vecCenter.z ); + } + //Assert( 0 ); + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; +} + + + +//----------------------------------------------------------------------------- +// Computes sort group +//----------------------------------------------------------------------------- +bool Mod_MarkWaterSurfaces( model_t *pModel ) +{ + bool bHasWaterSurfaces = false; + model_t *pSaveModel = host_state.worldmodel; + + // garymcthack!!!!!!!! + // host_state.worldmodel isn't set at this point, so. . . . + host_state.SetWorldModel( pModel ); + MarkWaterSurfaces_r( pModel->brush.pShared->nodes ); + for ( int i = 0; i < pModel->brush.pShared->numsurfaces; i++ ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pModel->brush.pShared ); + + int sortGroup = SurfFlagsToSortGroup( surfID, MSurf_Flags( surfID ) ); + if ( sortGroup == MAT_SORT_GROUP_WATERSURFACE ) + { + bHasWaterSurfaces = true; + } + MSurf_SetSortGroup( surfID, sortGroup ); + } + host_state.SetWorldModel( pSaveModel ); + + return bHasWaterSurfaces; +} + + +//----------------------------------------------------------------------------- +// Marks identity brushes as being in fog volumes or not +//----------------------------------------------------------------------------- +class CBrushBSPIterator : public ISpatialLeafEnumerator +{ +public: + CBrushBSPIterator( model_t *pWorld, model_t *pBrush ) + { + m_pWorld = pWorld; + m_pBrush = pBrush; + m_pShared = pBrush->brush.pShared; + m_count = 0; + } + bool EnumerateLeaf( int leaf, int ) + { + // garymcthack - need to test identity brush models + int flags = ( m_pShared->leafs[leaf].leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; + MarkModelSurfaces( flags ); + m_count++; + return true; + } + + void MarkModelSurfaces( int flags ) + { + // Iterate over all this models surfaces + int surfaceCount = m_pBrush->brush.nummodelsurfaces; + for (int i = 0; i < surfaceCount; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( m_pBrush->brush.firstmodelsurface + i, m_pShared ); + MSurf_Flags( surfID ) &= ~(SURFDRAW_ABOVEWATER | SURFDRAW_UNDERWATER); + MSurf_Flags( surfID ) |= flags; + } + } + + void CheckSurfaces() + { + if ( !m_count ) + { + MarkModelSurfaces( SURFDRAW_ABOVEWATER ); + } + } + + model_t* m_pWorld; + model_t* m_pBrush; + worldbrushdata_t *m_pShared; + int m_count; +}; + +static void MarkBrushModelWaterSurfaces( model_t* world, + Vector const& mins, Vector const& maxs, model_t* brush ) +{ + // HACK: This is a totally brutal hack dealing with initialization order issues. + // I want to use the same box enumeration code so I don't have multiple + // copies, but I want to use it from modelloader. host_state.worldmodel isn't + // set up at that time however, so I have to fly through these crazy hoops. + // Massive suckage. + + model_t* pTemp = host_state.worldmodel; + CBrushBSPIterator brushIterator( world, brush ); + host_state.SetWorldModel( world ); + g_pToolBSPTree->EnumerateLeavesInBox( mins, maxs, &brushIterator, (int)brush ); + brushIterator.CheckSurfaces(); + host_state.SetWorldModel( pTemp ); +} + +int g_nMapLoadCount = 0; +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +// *buffer - +//----------------------------------------------------------------------------- +void CModelLoader::Map_LoadModel( model_t *mod ) +{ + ++g_nMapLoadCount; + + MEM_ALLOC_CREDIT(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); + + COM_TimestampedLog( "Map_LoadModel: Start" ); + + double startTime = Plat_FloatTime(); + + SetWorldModel( mod ); + + // point at the shared world/brush data + mod->brush.pShared = &m_worldBrushData; + mod->brush.renderHandle = 0; + + // HDR and features must be established first + COM_TimestampedLog( " Map_CheckForHDR" ); + m_bMapHasHDRLighting = Map_CheckForHDR( mod, m_szLoadName ); + if ( IsX360() && !m_bMapHasHDRLighting ) + { + Warning( "Map '%s' lacks exepected HDR data! 360 does not support accurate LDR visuals.", m_szLoadName ); + } + + // Load the collision model + COM_TimestampedLog( " CM_LoadMap" ); + unsigned int checksum; + CM_LoadMap( mod->strName, false, &checksum ); + + // Load the map + mod->type = mod_brush; + mod->nLoadFlags |= FMODELLOADER_LOADED; + CMapLoadHelper::Init( mod, m_szLoadName ); + + COM_TimestampedLog( " Mod_LoadVertices" ); + Mod_LoadVertices(); + + COM_TimestampedLog( " Mod_LoadEdges" ); + medge_t *pedges = Mod_LoadEdges(); + + COM_TimestampedLog( " Mod_LoadSurfedges" ); + Mod_LoadSurfedges( pedges ); + + COM_TimestampedLog( " Mod_LoadPlanes" ); + Mod_LoadPlanes(); + + COM_TimestampedLog( " Mod_LoadOcclusion" ); + Mod_LoadOcclusion(); + + // texdata needs to load before texinfo + COM_TimestampedLog( " Mod_LoadTexdata" ); + Mod_LoadTexdata(); + + COM_TimestampedLog( " Mod_LoadTexinfo" ); + Mod_LoadTexinfo(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // Until BSP version 19, this must occur after loading texinfo + COM_TimestampedLog( " Mod_LoadLighting" ); + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_LIGHTING_HDR ); + Mod_LoadLighting( mlh ); + } + else + { + CMapLoadHelper mlh( LUMP_LIGHTING ); + Mod_LoadLighting( mlh ); + } + + COM_TimestampedLog( " Mod_LoadPrimitives" ); + Mod_LoadPrimitives(); + + COM_TimestampedLog( " Mod_LoadPrimVerts" ); + Mod_LoadPrimVerts(); + + COM_TimestampedLog( " Mod_LoadPrimIndices" ); + Mod_LoadPrimIndices(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // faces need to be loaded before vertnormals + COM_TimestampedLog( " Mod_LoadFaces" ); + Mod_LoadFaces(); + + COM_TimestampedLog( " Mod_LoadVertNormals" ); + Mod_LoadVertNormals(); + + COM_TimestampedLog( " Mod_LoadVertNormalIndices" ); + Mod_LoadVertNormalIndices(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // note leafs must load befor marksurfaces + COM_TimestampedLog( " Mod_LoadLeafs" ); + Mod_LoadLeafs(); + + COM_TimestampedLog( " Mod_LoadMarksurfaces" ); + Mod_LoadMarksurfaces(); + + COM_TimestampedLog( " Mod_LoadNodes" ); + Mod_LoadNodes(); + + COM_TimestampedLog( " Mod_LoadLeafWaterData" ); + Mod_LoadLeafWaterData(); + + COM_TimestampedLog( " Mod_LoadCubemapSamples" ); + Mod_LoadCubemapSamples(); + +#ifndef SWDS + // UNDONE: Does the cmodel need worldlights? + COM_TimestampedLog( " OverlayMgr()->LoadOverlays" ); + OverlayMgr()->LoadOverlays(); +#endif + + COM_TimestampedLog( " Mod_LoadLeafMinDistToWater" ); + Mod_LoadLeafMinDistToWater(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " LUMP_CLIPPORTALVERTS" ); + Mod_LoadLump( mod, + LUMP_CLIPPORTALVERTS, + va( "%s [%s]", m_szLoadName, "clipportalverts" ), + sizeof(m_worldBrushData.m_pClipPortalVerts[0]), + (void**)&m_worldBrushData.m_pClipPortalVerts, + &m_worldBrushData.m_nClipPortalVerts ); + + COM_TimestampedLog( " LUMP_AREAPORTALS" ); + Mod_LoadLump( mod, + LUMP_AREAPORTALS, + va( "%s [%s]", m_szLoadName, "areaportals" ), + sizeof(m_worldBrushData.m_pAreaPortals[0]), + (void**)&m_worldBrushData.m_pAreaPortals, + &m_worldBrushData.m_nAreaPortals ); + + COM_TimestampedLog( " LUMP_AREAS" ); + Mod_LoadLump( mod, + LUMP_AREAS, + va( "%s [%s]", m_szLoadName, "areas" ), + sizeof(m_worldBrushData.m_pAreas[0]), + (void**)&m_worldBrushData.m_pAreas, + &m_worldBrushData.m_nAreas ); + + COM_TimestampedLog( " Mod_LoadWorldlights" ); + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_WORLDLIGHTS_HDR ); + Mod_LoadWorldlights( mlh, true ); + } + else + { + CMapLoadHelper mlh( LUMP_WORLDLIGHTS ); + Mod_LoadWorldlights( mlh, false ); + } + + COM_TimestampedLog( " Mod_LoadGameLumpDict" ); + Mod_LoadGameLumpDict(); + + // load the portal information + // JAY: Disabled until we need this information. +#if 0 + Mod_LoadPortalVerts(); + Mod_LoadClusterPortals(); + Mod_LoadClusters(); + Mod_LoadPortals(); +#endif + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " Mod_LoadSubmodels" ); + CUtlVector submodelList; + Mod_LoadSubmodels( submodelList ); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " SetupSubModels" ); + SetupSubModels( mod, submodelList ); + + COM_TimestampedLog( " RecomputeSurfaceFlags" ); + RecomputeSurfaceFlags( mod ); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " Map_VisClear" ); + Map_VisClear(); + + COM_TimestampedLog( " Map_SetRenderInfoAllocated" ); + Map_SetRenderInfoAllocated( false ); + + // Close map file, etc. + CMapLoadHelper::Shutdown(); + + double elapsed = Plat_FloatTime() - startTime; + COM_TimestampedLog( "Map_LoadModel: Finish - loading took %.4f seconds", elapsed ); +} + +void CModelLoader::Map_UnloadCubemapSamples( model_t *mod ) +{ + int i; + for ( i=0 ; i < mod->brush.pShared->m_nCubemapSamples ; i++ ) + { + mcubemapsample_t *pSample = &mod->brush.pShared->m_pCubemapSamples[i]; + pSample->pTexture->DecrementReferenceCount(); + } +} + + +//----------------------------------------------------------------------------- +// Recomputes surface flags +//----------------------------------------------------------------------------- +void CModelLoader::RecomputeSurfaceFlags( model_t *mod ) +{ + for (int i=0 ; ibrush.pShared->numsubmodels ; i++) + { + model_t *pSubModel = &m_InlineModels[i]; + + // Compute whether this submodel uses material proxies or not + Mod_ComputeBrushModelFlags( pSubModel ); + + // Mark if brush models are in water or not; we'll use this + // for identity brushes. If the brush is not an identity brush, + // then we'll not have to worry. + if ( i != 0 ) + { + MarkBrushModelWaterSurfaces( mod, pSubModel->mins, pSubModel->maxs, pSubModel ); + } + } +} + +//----------------------------------------------------------------------------- +// Setup sub models +//----------------------------------------------------------------------------- +void CModelLoader::SetupSubModels( model_t *mod, CUtlVector &list ) +{ + int i; + + m_InlineModels.SetCount( m_worldBrushData.numsubmodels ); + + for (i=0 ; ibrush.firstmodelsurface = bm->firstface; + starmod->brush.nummodelsurfaces = bm->numfaces; + starmod->brush.firstnode = bm->headnode; + if ( starmod->brush.firstnode >= m_worldBrushData.numnodes ) + { + Sys_Error( "Inline model %i has bad firstnode", i ); + } + + VectorCopy(bm->maxs, starmod->maxs); + VectorCopy(bm->mins, starmod->mins); + starmod->radius = bm->radius; + + if (i == 0) + { + *mod = *starmod; + } + else + { + starmod->strName.Format( "*%d", i ); + starmod->fnHandle = g_pFileSystem->FindOrAddFileName( starmod->strName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Map_UnloadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); + mod->nLoadFlags &= ~FMODELLOADER_LOADED; + +#ifndef SWDS + OverlayMgr()->UnloadOverlays(); +#endif + + DeallocateLightingData( &m_worldBrushData ); + +#ifndef SWDS + DispInfo_ReleaseMaterialSystemObjects( mod ); +#endif + + Map_UnloadCubemapSamples( mod ); + +#ifndef SWDS + // Free decals in displacements. + R_DecalTerm( &m_worldBrushData, true ); +#endif + + if ( m_worldBrushData.hDispInfos ) + { + DispInfo_DeleteArray( m_worldBrushData.hDispInfos ); + m_worldBrushData.hDispInfos = NULL; + } + + // Model loader loads world model materials, unload them here + for( int texinfoID = 0; texinfoID < m_worldBrushData.numtexinfo; texinfoID++ ) + { + mtexinfo_t *pTexinfo = &m_worldBrushData.texinfo[texinfoID]; + if ( pTexinfo ) + { + GL_UnloadMaterial( pTexinfo->material ); + } + } + + MaterialSystem_DestroySortinfo(); + + // Don't store any reference to it here + ClearWorldModel(); + Map_SetRenderInfoAllocated( false ); +} + + +//----------------------------------------------------------------------------- +// Computes dimensions + frame count of a material +//----------------------------------------------------------------------------- +static void GetSpriteInfo( const char *pName, bool bIsVideo, int &nWidth, int &nHeight, int &nFrameCount ) +{ + nFrameCount = 1; + nWidth = nHeight = 1; + + // FIXME: The reason we are putting logic related to AVIs here, + // logic which is duplicated in the client DLL related to loading sprites, + // is that this code gets run on dedicated servers also. + IMaterial *pMaterial = NULL; + IVideoMaterial *pVideoMaterial = NULL; + if ( bIsVideo && g_pVideo != NULL ) + { + pVideoMaterial = g_pVideo->CreateVideoMaterial( pName, pName, "GAME", VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS, VideoSystem::DETERMINE_FROM_FILE_EXTENSION, false ); + if ( pVideoMaterial ) + { + pVideoMaterial->GetVideoImageSize( &nWidth, &nHeight ); + nFrameCount = pVideoMaterial->GetFrameCount(); + pMaterial = pVideoMaterial->GetMaterial(); + + g_pVideo->DestroyVideoMaterial( pVideoMaterial ); + } + } + else + { + pMaterial = GL_LoadMaterial( pName, TEXTURE_GROUP_OTHER ); + if ( pMaterial ) + { + // Store off our source height, width, frame count + nWidth = pMaterial->GetMappingWidth(); + nHeight = pMaterial->GetMappingHeight(); + nFrameCount = pMaterial->GetNumAnimationFrames(); + } + } + + if ( pMaterial == g_materialEmpty ) + { + DevMsg( "Missing sprite material %s\n", pName ); + } + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Sprite_LoadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); + + mod->nLoadFlags |= FMODELLOADER_LOADED; + + // The hunk data is not used on the server + byte* pSprite = NULL; + +#ifndef SWDS + if ( g_ClientDLL ) + { + int nSize = g_ClientDLL->GetSpriteSize(); + if ( nSize ) + { + pSprite = ( byte * )new byte[ nSize ]; + } + } +#endif + + mod->type = mod_sprite; + mod->sprite.sprite = (CEngineSprite *)pSprite; + + // Fake the bounding box. We need it for PVS culling, and we don't + // know the scale at which the sprite is going to be rendered at + // when we load it + mod->mins = mod->maxs = Vector(0,0,0); + + // Figure out the real load name.. + char loadName[MAX_PATH]; + bool bIsVideo; + BuildSpriteLoadName( mod->strName, loadName, MAX_PATH, bIsVideo ); + GetSpriteInfo( loadName, bIsVideo, mod->sprite.width, mod->sprite.height, mod->sprite.numframes ); + +#ifndef SWDS + if ( g_ClientDLL && mod->sprite.sprite ) + { + g_ClientDLL->InitSprite( mod->sprite.sprite, loadName ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Sprite_UnloadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); + mod->nLoadFlags &= ~FMODELLOADER_LOADED; + + char loadName[MAX_PATH]; + bool bIsVideo; + BuildSpriteLoadName( mod->strName, loadName, sizeof( loadName ), bIsVideo ); + + IMaterial *mat = materials->FindMaterial( loadName, TEXTURE_GROUP_OTHER ); + if ( !IsErrorMaterial( mat ) ) + { + GL_UnloadMaterial( mat ); + } + +#ifndef SWDS + if ( g_ClientDLL && mod->sprite.sprite ) + { + g_ClientDLL->ShutdownSprite( mod->sprite.sprite ); + } +#endif + + delete[] (byte *)mod->sprite.sprite; + mod->sprite.sprite = 0; + mod->sprite.numframes = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Flush and reload models. Intended for use when lod changes. +//----------------------------------------------------------------------------- +void CModelLoader::Studio_ReloadModels( CModelLoader::ReloadType_t reloadType ) +{ +#if !defined( SWDS ) + if ( g_ClientDLL ) + g_ClientDLL->InvalidateMdlCache(); +#endif // SWDS + if ( serverGameDLL ) + serverGameDLL->InvalidateMdlCache(); + + // ensure decals have no stale references to invalid lods + modelrender->RemoveAllDecalsFromAllModels(); + + // ensure static props have no stale references to invalid lods + modelrender->ReleaseAllStaticPropColorData(); + + // Flush out the model cache + // Don't flush vcollides since the vphysics system currently + // has no way of indicating they refer to vcollides + g_pMDLCache->Flush( (MDLCacheFlush_t) (MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); + + // Load the critical pieces now + // The model cache will re-populate as models render + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[ i ].modelpointer; + if ( !IsLoaded( pModel ) ) + continue; + + if ( pModel->type != mod_studio ) + continue; + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + // Get the studiohdr into the cache + g_pMDLCache->GetStudioHdr( pModel->studio ); + + // force the collision to load + g_pMDLCache->GetVCollide( pModel->studio ); + } +} + +struct modelsize_t +{ + const char *pName; + int size; +}; + +class CModelsize_Less +{ +public: + bool Less( const modelsize_t& src1, const modelsize_t& src2, void *pCtx ) + { + return ( src1.size < src2.size ); + } +}; + +void CModelLoader::DumpVCollideStats() +{ + int i; + CUtlSortVector< modelsize_t, CModelsize_Less > list; + for ( i = 0; (m_Models).IsUtlMap && i < (m_Models).MaxElement(); ++i ) if ( !(m_Models).IsValidIndex( i ) ) continue; else + { + model_t *pModel = m_Models[ i ].modelpointer; + if ( pModel && pModel->type == mod_studio ) + { + int size = 0; + bool loaded = g_pMDLCache->GetVCollideSize( pModel->studio, &size ); + if ( loaded && size ) + { + modelsize_t elem; + elem.pName = pModel->strName; + elem.size = size; + list.Insert( elem ); + } + } + } + for ( i = m_InlineModels.Count(); --i >= 0; ) + { + vcollide_t *pCollide = CM_VCollideForModel( i+1, &m_InlineModels[i] ); + if ( pCollide ) + { + int size = 0; + for ( int j = 0; j < pCollide->solidCount; j++ ) + { + size += physcollision->CollideSize( pCollide->solids[j] ); + } + size += pCollide->descSize; + if ( size ) + { + modelsize_t elem; + elem.pName = m_InlineModels[i].strName; + elem.size = size; + list.Insert( elem ); + } + } + } + + Msg("VCollides loaded: %d\n", list.Count() ); + int totalVCollideMemory = 0; + for ( i = 0; i < list.Count(); i++ ) + { + Msg("%8d bytes:%s\n", list[i].size, list[i].pName); + totalVCollideMemory += list[i].size; + } + int bboxCount, bboxSize; + physcollision->GetBBoxCacheSize( &bboxSize, &bboxCount ); + Msg( "%8d bytes BBox physics: %d boxes\n", bboxSize, bboxCount ); + totalVCollideMemory += bboxSize; + Msg( "--------------\n%8d bytes total VCollide Memory\n", totalVCollideMemory ); +} + + +//----------------------------------------------------------------------------- +// Is the model loaded? +//----------------------------------------------------------------------------- +bool CModelLoader::IsLoaded( const model_t *mod ) +{ + return (mod->nLoadFlags & FMODELLOADER_LOADED) != 0; +} + +bool CModelLoader::LastLoadedMapHasHDRLighting(void) +{ + return m_bMapHasHDRLighting; +} + +//----------------------------------------------------------------------------- +// Loads a studio model +//----------------------------------------------------------------------------- +void CModelLoader::Studio_LoadModel( model_t *pModel, bool bTouchAllData ) +{ + if ( !mod_touchalldata.GetBool() ) + { + bTouchAllData = false; + } + + // a preloaded model requires specific fixup behavior + bool bPreLoaded = ( pModel->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) != 0; + + bool bLoadPhysics = true; + if ( pModel->nLoadFlags == FMODELLOADER_STATICPROP ) + { + // this is the first call in loading as a static prop (load bit not set), don't load physics yet + // the next call in causes the physics to load + bLoadPhysics = false; + } + + // mark as loaded and fixed up + pModel->nLoadFlags |= FMODELLOADER_LOADED; + pModel->nLoadFlags &= ~FMODELLOADER_LOADED_BY_PRELOAD; + + if ( !bPreLoaded ) + { + pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); + g_pMDLCache->SetUserData( pModel->studio, pModel ); + + InitStudioModelState( pModel ); + } + + // Get the studiohdr into the cache + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + (void) pStudioHdr; + + // a preloaded model alrady has its physics data resident + if ( bLoadPhysics && !bPreLoaded ) + { + // load the collision data now + bool bSynchronous = bTouchAllData; + double t1 = Plat_FloatTime(); + g_pMDLCache->GetVCollideEx( pModel->studio, bSynchronous ); + + double t2 = Plat_FloatTime(); + if ( bSynchronous ) + { + g_flAccumulatedModelLoadTimeVCollideSync += ( t2 - t1 ); + } + else + { + g_flAccumulatedModelLoadTimeVCollideAsync += ( t2 - t1 ); + } + } + + // this forces sync setup operations (materials/shaders) to build out now during load and not at runtime + double t1 = Plat_FloatTime(); + + // should already be NULL, but better safe than sorry + if ( pModel->ppMaterials ) + { + free( pModel->ppMaterials - 1 ); + pModel->ppMaterials = NULL; + } + + IMaterial *pMaterials[128]; + int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + // Cache the material pointers so that we don't re-scan all the VMTs on dynamic unload + COMPILE_TIME_ASSERT( sizeof( intptr_t ) == sizeof( IMaterial * ) ); + IMaterial **pMem = (IMaterial**) malloc( (1 + nMaterials) * sizeof( IMaterial* ) ); + *(intptr_t*)pMem = nMaterials; + pModel->ppMaterials = pMem + 1; + for ( int i=0; ippMaterials[i] = pMaterials[i]; + } + } + + if ( nMaterials ) + { + for ( int i=0; iIncrementReferenceCount(); + } + // track the refcount bump + pModel->nLoadFlags |= FMODELLOADER_TOUCHED_MATERIALS; + } + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeMaterialNamesOnly += ( t2 - t1 ); + + // a preloaded model must touch its children + if ( bTouchAllData || bPreLoaded ) + { + Mod_TouchAllData( pModel, Host_GetServerCount() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Studio_UnloadModel( model_t *pModel ) +{ + // Do not unload models that are still referenced by the dynamic system + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + return; + } + + if ( pModel->nLoadFlags & FMODELLOADER_TOUCHED_MATERIALS ) + { + IMaterial *pMaterials[128]; + int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), &pMaterials[0] ); + for ( int j=0; jDecrementReferenceCount(); + } + pModel->nLoadFlags &= ~FMODELLOADER_TOUCHED_MATERIALS; + } + + // leave these flags alone since we are going to return from alt-tab at some point. + // Assert( !( mod->needload & FMODELLOADER_REFERENCEMASK ) ); + pModel->nLoadFlags &= ~( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ); + if ( IsX360() ) + { + // 360 doesn't need to keep the reference flags, but the PC does + pModel->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; + } + +#ifdef DBGFLAG_ASSERT + int nRef = +#endif + g_pMDLCache->Release( pModel->studio ); + + // the refcounts must be as expected, or evil latent bugs will occur + Assert( InEditMode() || ( nRef == 0 ) ); + + if ( pModel->ppMaterials ) + { + free( pModel->ppMaterials - 1 ); + pModel->ppMaterials = NULL; + } + + pModel->studio = MDLHANDLE_INVALID; + pModel->type = mod_bad; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::SetWorldModel( model_t *mod ) +{ + Assert( mod ); + m_pWorldModel = mod; +// host_state.SetWorldModel( mod ); // garymcthack +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::ClearWorldModel( void ) +{ + m_pWorldModel = NULL; + memset( &m_worldBrushData, 0, sizeof(m_worldBrushData) ); + m_InlineModels.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::IsWorldModelSet( void ) +{ + return m_pWorldModel ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CModelLoader::GetNumWorldSubmodels( void ) +{ + if ( !IsWorldModelSet() ) + return 0; + + return m_worldBrushData.numsubmodels; +} + +//----------------------------------------------------------------------------- +// Purpose: Check cache or union data for info, reload studio model if needed +// Input : *model - +//----------------------------------------------------------------------------- +void *CModelLoader::GetExtraData( model_t *model ) +{ + if ( !model ) + { + return NULL; + } + + switch ( model->type ) + { + case mod_sprite: + { + // sprites don't use the real cache yet + if ( model->type == mod_sprite ) + { + // The sprite got unloaded. + if ( !( FMODELLOADER_LOADED & model->nLoadFlags ) ) + { + return NULL; + } + + return model->sprite.sprite; + } + } + break; + + case mod_studio: + return g_pMDLCache->GetStudioHdr( model->studio ); + + default: + case mod_brush: + // Should never happen + Assert( 0 ); + break; + }; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::Map_GetRenderInfoAllocated( void ) +{ + return m_bMapRenderInfoLoaded; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Map_SetRenderInfoAllocated( bool allocated ) +{ + m_bMapRenderInfoLoaded = allocated; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Map_LoadDisplacements( model_t *pModel, bool bRestoring ) +{ + if ( !pModel ) + { + Assert( false ); + return; + } + + Q_FileBase( pModel->strName, m_szLoadName, sizeof( m_szLoadName ) ); + CMapLoadHelper::Init( pModel, m_szLoadName ); + + DispInfo_LoadDisplacements( pModel, bRestoring ); + + CMapLoadHelper::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Purpose: List the model dictionary +//----------------------------------------------------------------------------- +void CModelLoader::Print( void ) +{ + ConMsg( "Models:\n" ); + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + if ( pModel->type == mod_studio || pModel->type == mod_bad ) + { + // studio models have ref counts + // bad models are unloaded models which need to be listed + int refCount = ( pModel->type == mod_studio ) ? g_pMDLCache->GetRef( pModel->studio ) : 0; + ConMsg( "%4d: Flags:0x%8.8x RefCount:%2d %s\n", i, pModel->nLoadFlags, refCount, pModel->strName.String() ); + } + else + { + ConMsg( "%4d: Flags:0x%8.8x %s\n", i, pModel->nLoadFlags, pModel->strName.String() ); + } + } +} + +//----------------------------------------------------------------------------- +// Callback for UpdateOrCreate utility function - swaps a bsp. +//----------------------------------------------------------------------------- +#if defined( _X360 ) +static bool BSPCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData ) +{ + // load the bsppack dll + IBSPPack *iBSPPack = NULL; + CSysModule *pmodule = g_pFullFileSystem->LoadModule( "bsppack" ); + if ( pmodule ) + { + CreateInterfaceFn factory = Sys_GetFactory( pmodule ); + if ( factory ) + { + iBSPPack = ( IBSPPack * )factory( IBSPPACK_VERSION_STRING, NULL ); + } + } + if( !iBSPPack ) + { + Warning( "Can't load bsppack.dll - unable to swap bsp.\n" ); + return false; + } + + bool bOk = true; + if ( !iBSPPack->SwapBSPFile( g_pFileSystem, pSourceName, pTargetName, IsX360(), ConvertVTFTo360Format, NULL, NULL ) ) + { + bOk = false; + Warning( "Failed to create %s\n", pTargetName ); + } + + Sys_UnloadModule( pmodule ); + + return bOk; +} +#endif + +//----------------------------------------------------------------------------- +// Calls utility function to create .360 version of a file. +//----------------------------------------------------------------------------- +int CModelLoader::UpdateOrCreate( const char *pSourceName, char *pTargetName, int targetLen, bool bForce ) +{ +#if defined( _X360 ) + return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, NULL, BSPCreateCallback, bForce ); +#else + return UOC_NOT_CREATED; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Determine if specified .bsp is valid +// Input : *mapname - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::Map_IsValid( char const *pMapFile, bool bQuiet /* = false */ ) +{ + static char s_szLastMapFile[MAX_PATH] = { 0 }; + + if ( !pMapFile || !pMapFile[0] ) + { + if ( !bQuiet ) + { + ConMsg( "CModelLoader::Map_IsValid: Empty mapname!!!\n" ); + } + return false; + } + + char szMapFile[MAX_PATH] = { 0 }; + V_strncpy( szMapFile, pMapFile, sizeof( szMapFile ) ); + + if ( IsX360() && !V_stricmp( szMapFile, s_szLastMapFile ) ) + { + // already been checked, no reason to do multiple i/o validations + return true; + } + + // Blacklist some characters + // - Don't allow characters not allowed on all supported platforms for consistency + // - Don't allow quotes or ;"' as defense-in-depth against script abuses (and, no real reason for mapnames to use these) + const char *pBaseFileName = V_UnqualifiedFileName( pMapFile ); + bool bIllegalChar = false; + for (; pBaseFileName && *pBaseFileName; pBaseFileName++ ) + { + // ASCII control characters (codepoints <= 31) illegal in windows filenames + if ( *pBaseFileName <= (char)31 ) + bIllegalChar = true; + + switch ( *pBaseFileName ) + { + // Illegal in windows filenames, don't allow on any platform + case '<': case '>': case ':': case '"': case '/': case '\\': + case '|': case '?': case '*': + bIllegalChar = true; + // Additional special characters in source engine commands, defense-in-depth against things that might be + // composing commands with map names (though they really shouldn't be) + case ';': case '\'': + bIllegalChar = true; + default: break; + } + } + + if ( bIllegalChar ) + { + Assert( !"Map with illegal characters in filename" ); + Warning( "Map with illegal characters in filename\n" ); + return false; + } + + FileHandle_t mapfile; + + if ( IsX360() ) + { + char szMapName360[MAX_PATH]; + UpdateOrCreate( szMapFile, szMapName360, sizeof( szMapName360 ), false ); + V_strcpy_safe( szMapFile, szMapName360 ); + } + + mapfile = g_pFileSystem->OpenEx( szMapFile, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, "GAME" ); + if ( mapfile != FILESYSTEM_INVALID_HANDLE ) + { + dheader_t header; + memset( &header, 0, sizeof( header ) ); + g_pFileSystem->Read( &header, sizeof( dheader_t ), mapfile ); + g_pFileSystem->Close( mapfile ); + + if ( header.ident == IDBSPHEADER ) + { + if ( header.version >= MINBSPVERSION && header.version <= BSPVERSION ) + { + V_strncpy( s_szLastMapFile, szMapFile, sizeof( s_szLastMapFile ) ); + return true; + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: Map '%s' bsp version %i, expecting %i\n", szMapFile, header.version, BSPVERSION ); + } + + } + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: '%s' is not a valid BSP file\n", szMapFile ); + } + } + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: No such map '%s'\n", szMapFile ); + } + } + + // Get outta here if we are checking vidmemstats. + if ( CommandLine()->CheckParm( "-dumpvidmemstats" ) ) + { + Cbuf_AddText( "quit\n" ); + } + + return false; +} + +model_t *CModelLoader::FindModelNoCreate( const char *pModelName ) +{ + FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pModelName ); + int i = m_Models.Find( fnHandle ); + if ( i != m_Models.InvalidIndex() ) + { + return m_Models[i].modelpointer; + } + + // not found + return NULL; +} + +modtype_t CModelLoader::GetTypeFromName( const char *pModelName ) +{ + // HACK HACK, force sprites to correctly + const char *pExt = V_GetFileExtension( pModelName ); + if ( pExt ) + { + if ( !V_stricmp( pExt, "spr" ) || !V_stricmp( pExt, "vmt" ) ) + { + return mod_sprite; + } + else if ( !V_stricmp( pExt, "bsp" ) ) + { + return mod_brush; + } + else if ( !V_stricmp( pExt, "mdl" ) ) + { + return mod_studio; + } + else if ( g_pVideo != NULL && g_pVideo->LocateVideoSystemForPlayingFile( pModelName) != VideoSystem::NONE ) // video sprite + { + return mod_sprite; + } + } + + return mod_bad; +} + +int CModelLoader::FindNext( int iIndex, model_t **ppModel ) +{ + if ( iIndex == -1 && m_Models.Count() ) + { + iIndex = m_Models.FirstInorder(); + } + else if ( !m_Models.Count() || !m_Models.IsValidIndex( iIndex ) ) + { + *ppModel = NULL; + return -1; + } + + *ppModel = m_Models[iIndex].modelpointer; + + iIndex = m_Models.NextInorder( iIndex ); + if ( iIndex == m_Models.InvalidIndex() ) + { + // end of list + iIndex = -1; + } + + return iIndex; +} + +void CModelLoader::UnloadModel( model_t *pModel ) +{ + switch ( pModel->type ) + { + case mod_brush: + // Let it free data or call destructors.. + Map_UnloadModel( pModel ); + + // Remove from file system + g_pFileSystem->RemoveSearchPath( pModel->strName, "GAME" ); + + m_szActiveMapName[0] = '\0'; + break; + + case mod_studio: + Studio_UnloadModel( pModel ); + break; + + case mod_sprite: + Sprite_UnloadModel( pModel ); + break; + } +} + +const char *CModelLoader::GetActiveMapName( void ) +{ + return m_szActiveMapName; +} + +model_t *CModelLoader::GetDynamicModel( const char *name, bool bClientOnly ) +{ + if ( !name || !name[0] ) + { + name = "models/empty.mdl"; + } + + Assert( V_strnicmp( name, "models/", 7 ) == 0 && V_strstr( name, ".mdl" ) != NULL ); + + model_t *pModel = FindModel( name ); + Assert( pModel ); + + CDynamicModelInfo &dyn = m_DynamicModels[ m_DynamicModels.Insert( pModel ) ]; // Insert returns existing if key is already set + if ( dyn.m_nLoadFlags == CDynamicModelInfo::INVALIDFLAG ) + { + dyn.m_nLoadFlags = 0; + DynamicModelDebugMsg( "model %p [%s] registered\n", pModel, pModel->strName.String() ); + } + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + + return pModel; +} + +void CModelLoader::UpdateDynamicModelLoadQueue() +{ + if ( mod_dynamicloadpause.GetBool() ) + return; + + static double s_LastDynamicLoadTime = 0.0; + if ( mod_dynamicloadthrottle.GetFloat() > 0 && Plat_FloatTime() < s_LastDynamicLoadTime + mod_dynamicloadthrottle.GetFloat() ) + return; + + if ( m_bDynamicLoadQueueHeadActive ) + { + Assert( m_DynamicModelLoadQueue.Count() >= 1 ); + MaterialLock_t matLock = g_pMaterialSystem->Lock(); // ASDFADFASFASEGAafliejsfjaslaslgsaigas + bool bComplete = g_pQueuedLoader->CompleteDynamicLoad(); + g_pMaterialSystem->Unlock(matLock); + + if ( bComplete ) + { + model_t *pModel = m_DynamicModelLoadQueue[0]; + m_DynamicModelLoadQueue.Remove(0); + m_bDynamicLoadQueueHeadActive = false; + + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + Assert( pModel->type == mod_bad || ( pModel->nLoadFlags & (FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD) ) ); + (void) LoadModel( pModel, NULL ); + Assert( pModel->type == mod_studio ); + + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::LOADING ); + + dyn.m_nLoadFlags &= ~( CDynamicModelInfo::QUEUED | CDynamicModelInfo::LOADING ); + + g_pMDLCache->LockStudioHdr( pModel->studio ); + dyn.m_nLoadFlags |= CDynamicModelInfo::CLIENTREADY; + + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + + FinishDynamicModelLoadIfReady( &dyn, pModel ); + } + + // do the clean up after we're actually done + // we keep some file cache around to make sure that LoadModel doesn't do blocking load + //g_pQueuedLoader->CleanupDynamicLoad(); NOT IMPLEMENTED + + s_LastDynamicLoadTime = Plat_FloatTime(); + } + } + + // If we're not working, and we have work to do, and the queued loader is open for business... + if ( !m_bDynamicLoadQueueHeadActive && m_DynamicModelLoadQueue.Count() > 0 && g_pQueuedLoader->IsFinished() ) + { + model_t *pModel = m_DynamicModelLoadQueue[0]; + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + m_bDynamicLoadQueueHeadActive = true; + + CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ); + Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ); + dyn.m_nLoadFlags |= CDynamicModelInfo::LOADING; + + // the queued loader is very ... particular about path names. it doesn't like leading "models/" + const char* pName = pModel->strName; + if ( V_strnicmp( pName, "models", 6 ) == 0 && ( pName[6] == '/' || pName[6] == '\\' ) ) + { + pName += 7; + } + + MaterialLock_t matLock = g_pMaterialSystem->Lock(); + g_pQueuedLoader->DynamicLoadMapResource( pName, NULL, NULL, NULL ); + g_pMaterialSystem->Unlock(matLock); + } + else + { + m_DynamicModelLoadQueue.Remove(0); + } + } +} + +void CModelLoader::FinishDynamicModelLoadIfReady( CDynamicModelInfo *pDyn, model_t *pModel ) +{ + CDynamicModelInfo &dyn = *pDyn; + if ( ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) + { + if ( !( dyn.m_nLoadFlags & CDynamicModelInfo::SERVERLOADING ) ) + { + // There ought to be a better way to plumb this through, but this should be ok... + if ( sv.GetDynamicModelsTable() ) + { + int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); + if ( netidx != INVALID_STRING_INDEX ) + { + char nIsLoaded = 1; + sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); + } + } + + DynamicModelDebugMsg( "model %p [%s] loaded\n", pModel, pModel->strName.String() ); + + dyn.m_nLoadFlags |= CDynamicModelInfo::ALLREADY; + + // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back + for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) + { + uintptr_t callbackID = dyn.m_Callbacks[ i ]; + bool bClientOnly = (bool)(callbackID & 1); + IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); + UnregisterModelLoadCallback( pModel, bClientOnly, pCallback ); + pCallback->OnModelLoadComplete( pModel ); + } + } + else + { + // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back + for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) + { + uintptr_t callbackID = dyn.m_Callbacks[ i ]; + bool bClientOnly = (bool)(callbackID & 1); + IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); + if ( bClientOnly ) + { + UnregisterModelLoadCallback( pModel, true, pCallback ); + pCallback->OnModelLoadComplete( pModel ); + } + } + } + } +} + +bool CModelLoader::RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ) +{ + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn == m_DynamicModels.InvalidHandle() ) + return false; + + Assert( ((uintptr_t)pCallback & 1) == 0 ); + uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; + + int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + AssertMsg( dyn.m_iRefCount > 0, "RegisterModelLoadCallback requires non-zero model refcount" ); + if ( dyn.m_nLoadFlags & readyFlag ) + { + if ( !bCallImmediatelyIfLoaded ) + return false; + + pCallback->OnModelLoadComplete( pModel ); + } + else + { + if ( !dyn.m_Callbacks.HasElement( callbackID ) ) + { + dyn.m_Callbacks.AddToTail( callbackID ); + // Set registration count for callback pointer + m_RegisteredDynamicCallbacks[ m_RegisteredDynamicCallbacks.Insert( callbackID, 0 ) ]++; + } + } + + return true; +} + +bool CModelLoader::IsDynamicModelLoading( model_t *pModel, bool bClientOnly ) +{ + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + AssertMsg( dyn.m_iRefCount > 0, "dynamic model state cannot be queried with zero refcount" ); + if ( dyn.m_iRefCount > 0 ) + { + int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; + return !( dyn.m_nLoadFlags & readyFlag ); + } + } + return false; +} + +void CModelLoader::AddRefDynamicModel( model_t *pModel, bool bClientSideRef ) +{ + extern IVModelInfo* modelinfo; + + UtlHashHandle_t hDyn = m_DynamicModels.Insert( pModel ); + CDynamicModelInfo& dyn = m_DynamicModels[ hDyn ]; + dyn.m_iRefCount++; + dyn.m_iClientRefCount += ( bClientSideRef ? 1 : 0 ); + Assert( dyn.m_iRefCount > 0 ); + + DynamicModelDebugMsg( "model %p [%s] addref %d (%d)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); + + if ( !( dyn.m_nLoadFlags & ( CDynamicModelInfo::QUEUED | CDynamicModelInfo::CLIENTREADY ) ) ) + { + QueueDynamicModelLoad( &dyn, pModel ); + + // Try to kick it off asap if we aren't already busy. + if ( !m_bDynamicLoadQueueHeadActive ) + { + UpdateDynamicModelLoadQueue(); + } + } +} + +void CModelLoader::ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ) +{ + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + Assert( dyn.m_iRefCount > 0 ); + if ( dyn.m_iRefCount > 0 ) + { + DynamicModelDebugMsg( "model %p [%s] release %d (%dc)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); + dyn.m_iRefCount--; + dyn.m_iClientRefCount -= ( bClientSideRef ? 1 : 0 ); + Assert( dyn.m_iClientRefCount >= 0 ); + if ( dyn.m_iClientRefCount < 0 ) + dyn.m_iClientRefCount = 0; + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + } + } +} + +void CModelLoader::UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ) +{ + Assert( ((uintptr_t)pCallback & 1) == 0 ); + uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; + if ( int *pCallbackRegistrationCount = m_RegisteredDynamicCallbacks.GetPtr( callbackID ) ) + { + if ( pModel ) + { + UtlHashHandle_t i = m_DynamicModels.Find( pModel ); + if ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ i ]; + if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) + { + if ( dyn.m_Callbacks.Count() == 0 ) + { + dyn.m_Callbacks.Purge(); + } + if ( --(*pCallbackRegistrationCount) == 0 ) + { + m_RegisteredDynamicCallbacks.Remove( callbackID ); + return; + } + } + } + } + else + { + for ( UtlHashHandle_t i = m_DynamicModels.FirstHandle(); i != m_DynamicModels.InvalidHandle(); i = m_DynamicModels.NextHandle(i) ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ i ]; + if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) + { + if ( dyn.m_Callbacks.Count() == 0 ) + { + dyn.m_Callbacks.Purge(); + } + if ( --(*pCallbackRegistrationCount) == 0 ) + { + m_RegisteredDynamicCallbacks.Remove( callbackID ); + return; + } + } + } + } + } +} + +void CModelLoader::QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) +{ + Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); + // Client-side entities have priority over server-side entities + // because they are more likely to be used in UI elements. --henryg + if ( dyn->m_iClientRefCount > 0 && m_DynamicModelLoadQueue.Count() > 1 ) + { + m_DynamicModelLoadQueue.InsertAfter( 0, mod ); + } + else + { + m_DynamicModelLoadQueue.AddToTail( mod ); + } + dyn->m_nLoadFlags |= CDynamicModelInfo::QUEUED; + mod->nLoadFlags |= ( dyn->m_iClientRefCount > 0 ? FMODELLOADER_DYNCLIENT : FMODELLOADER_DYNSERVER ); +} + +bool CModelLoader::CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) +{ + int i = m_DynamicModelLoadQueue.Find( mod ); + Assert( (i < 0) == !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); + if ( i >= 0 ) + { + if ( i == 0 && m_bDynamicLoadQueueHeadActive ) + { + Assert( dyn->m_nLoadFlags & CDynamicModelInfo::LOADING ); + // can't remove head of queue + return false; + } + else + { + Assert( dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::LOADING) ); + m_DynamicModelLoadQueue.Remove( i ); + dyn->m_nLoadFlags &= ~CDynamicModelInfo::QUEUED; + mod->nLoadFlags &= ~FMODELLOADER_DYNAMIC; + return true; + } + } + return false; +} + +void CModelLoader::InternalUpdateDynamicModels( bool bIgnoreTime ) +{ + const uint now = Plat_MSTime(); + const uint delay = bIgnoreTime ? 0 : (int)( clamp( mod_dynamicunloadtime.GetFloat(), 1.f, 600.f ) * 1000 ); + + UpdateDynamicModelLoadQueue(); + +#ifdef _DEBUG + extern CNetworkStringTableContainer *networkStringTableContainerServer; + bool bPrevStringTableLockState = networkStringTableContainerServer->Lock( false ); +#endif + + // Scan for models to unload. TODO: accelerate with a "models to potentially unload" list? + UtlHashHandle_t i = m_DynamicModels.FirstHandle(); + while ( i != m_DynamicModels.InvalidHandle() ) + { + model_t *pModel = m_DynamicModels.Key( i ); + CDynamicModelInfo& dyn = m_DynamicModels[ i ]; + + // UNLOAD THIS MODEL if zero refcount and not currently loading, and either timed out or never loaded + if ( dyn.m_iRefCount <= 0 && !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) && + ( ( now - (dyn.m_uLastTouchedMS_Div256 << 8) ) >= delay || !( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) ) + { + // Remove from load queue + if ( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ) + { + if ( !CancelDynamicModelLoad( &dyn, pModel ) ) + { + // Couldn't remove from queue, advance to next entry and do not remove + i = m_DynamicModels.NextHandle(i); + continue; + } + } + + // Unlock studiohdr_t + if ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) + { + g_pMDLCache->UnlockStudioHdr( pModel->studio ); + } + + // There ought to be a better way to plumb this through, but this should be ok... + if ( sv.GetDynamicModelsTable() ) + { + int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); + if ( netidx != INVALID_STRING_INDEX ) + { + char nIsLoaded = 0; + sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); + } + } + + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + pModel->nLoadFlags &= ~FMODELLOADER_DYNAMIC; + // Actually unload the model if all system references are gone + if ( pModel->nLoadFlags & FMODELLOADER_REFERENCEMASK ) + { + DynamicModelDebugMsg( "model %p [%s] unload - deferred: non-dynamic reference\n", pModel, pModel->strName.String() ); + } + else + { + DynamicModelDebugMsg( "model %p [%s] unload\n", pModel, pModel->strName.String() ); + + Studio_UnloadModel( pModel ); + + if ( mod_dynamicunloadtextures.GetBool() ) + { + materials->UncacheUnusedMaterials( false ); + } + } + } + + // Remove from table, advance to next entry + i = m_DynamicModels.RemoveAndAdvance(i); + continue; + } + + // Advance to next entry in table + i = m_DynamicModels.NextHandle(i); + } + +#ifdef _DEBUG + networkStringTableContainerServer->Lock( bPrevStringTableLockState ); +#endif +} + +void CModelLoader::Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ) +{ +#ifndef SWDS + // Listen server don't distinguish between server and client ready, never use SERVERLOADING flag + if ( sv.IsActive() ) + return; + + UtlHashHandle_t i = m_DynamicModels.Find( pModel ); + if ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[i]; + if ( !bServerLoaded ) + { + if ( dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY ) + DynamicModelDebugMsg( "dynamic model [%s] loaded on client but not server! is this bad? unknown...", pModel->strName.String() ); + dyn.m_nLoadFlags &= ~CDynamicModelInfo::ALLREADY; + dyn.m_nLoadFlags |= CDynamicModelInfo::SERVERLOADING; + } + else + { + dyn.m_nLoadFlags &= ~CDynamicModelInfo::SERVERLOADING; + FinishDynamicModelLoadIfReady( &dyn, pModel ); + } + } +#endif +} + +void CModelLoader::ForceUnloadNonClientDynamicModels() +{ + UtlHashHandle_t i = m_DynamicModels.FirstHandle(); + while ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[i]; + dyn.m_iRefCount = dyn.m_iClientRefCount; + i = m_DynamicModels.NextHandle( i ); + } + + // Flush everything + InternalUpdateDynamicModels( true ); +} + + +// reconstruct the ambient lighting for a leaf at the given position in worldspace +void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, int leafIndex ) +{ + for ( int i = 0; i < 6; i++ ) + { + pOut[i].Init(); + } + mleafambientindex_t *pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; + if ( !pAmbient->ambientSampleCount && pAmbient->firstAmbientSample ) + { + // this leaf references another leaf, move there (this leaf is a solid leaf so it borrows samples from a neighbor) + leafIndex = pAmbient->firstAmbientSample; + pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; + } + int count = pAmbient->ambientSampleCount; + if ( count > 0 ) + { + int start = host_state.worldbrush->m_pLeafAmbient[leafIndex].firstAmbientSample; + mleafambientlighting_t *pSamples = host_state.worldbrush->m_pAmbientSamples + start; + mleaf_t *pLeaf = &host_state.worldbrush->leafs[leafIndex]; + float totalFactor = 0; + for ( int i = 0; i < count; i++ ) + { + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + + // the sample positions are packed as leaf bounds fractions, compute + Vector samplePos = pLeaf->m_vecCenter - pLeaf->m_vecHalfDiagonal; + samplePos.x += float(pSamples[i].x) * pLeaf->m_vecHalfDiagonal.x * (2.0f / 255.0f); + samplePos.y += float(pSamples[i].y) * pLeaf->m_vecHalfDiagonal.y * (2.0f / 255.0f); + samplePos.z += float(pSamples[i].z) * pLeaf->m_vecHalfDiagonal.z * (2.0f / 255.0f); + + float dist = (samplePos - pos).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + for ( int j = 0; j < 6; j++ ) + { + Vector v; + ColorRGBExp32ToVector( pSamples[i].cube.m_Color[j], v ); + pOut[j] += v * factor; + } + } + for ( int i = 0; i < 6; i++ ) + { + pOut[i] *= (1.0f / totalFactor); + } + } +} + +#if defined( WIN32 ) +int ComputeSize( studiohwdata_t *hwData, int *numVerts, int *pTriCount, bool onlyTopLod = false ) +{ + unsigned size = 0; + Assert(hwData && numVerts); + int max_lod = (onlyTopLod ? 1 : hwData->m_NumLODs); + *pTriCount = 0; + for ( int i=0; i < max_lod; i++ ) + { + studioloddata_t *pLOD = &hwData->m_pLODs[i]; + for ( int j = 0; j < hwData->m_NumStudioMeshes; j++ ) + { + studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j]; + for ( int k = 0; k < pMeshData->m_NumGroup; k++ ) + { + studiomeshgroup_t *pMeshGroup = &pMeshData->m_pMeshGroup[k]; + IMesh* mesh = pMeshGroup->m_pMesh; + size += mesh->ComputeMemoryUsed(); // Size of VB and IB + size += 2*pMeshGroup->m_NumVertices; // Size of m_pGroupIndexToMeshIndex[] array + *numVerts += mesh->VertexCount(); + Assert( mesh->VertexCount() == pMeshGroup->m_NumVertices ); + for ( int l = 0; l < pMeshGroup->m_NumStrips; ++l ) + { + OptimizedModel::StripHeader_t *pStripData = &pMeshGroup->m_pStripData[l]; + *pTriCount += pStripData->numIndices / 3; + } + } + } + } + return size; +} + +// APSFIXME: needs to only do models that are resident, sizes might be wrong, i.e lacking compressed vert state? +CON_COMMAND_F( model_list, "Dump model list to file", FCVAR_CHEAT | FCVAR_DONTRECORD ) +{ + // don't run this on dedicated servers + if ( sv.IsDedicated() ) + return; + + if ( g_pFileSystem ) + { + FileHandle_t fileHandle = g_pFileSystem->Open( "model_list.csv", "wt", "GAME" ); + + if ( fileHandle ) + { + const char *substring = NULL; + if ( args.ArgC() > 1 ) + { + substring = args[1]; + } + + g_pFileSystem->FPrintf( fileHandle, "name,dataSize,numVerts,nTriCount,dataSizeLod0,numVertsLod0,nTriCountLod0,numBones,numParts,numLODs,numMeshes\n" ); + + for ( int i = 0; i < modelloader->GetCount(); i++ ) + { + const char* name = "Unknown"; + int dataSizeLod0 = 0; + int dataSize = 0; + int numParts = 0; + int numBones = 0; + int numVertsLod0 = 0; + int numVerts = 0; + int numLODs = 0; + int numMeshes = 0; + int nTriCount = 0; + int nTriCountLod0 = 0; + + model_t* model = modelloader->GetModelForIndex( i ); + if ( model ) + { + // other model types are not interesting + if ( model->type != mod_studio ) + continue; + + name = model->strName; + + if ( substring && substring[0] ) + { + if ( Q_stristr( name, substring ) == NULL ) + continue; + } + + studiohwdata_t *hwData = g_pMDLCache->GetHardwareData( model->studio ); + if ( hwData ) + { + numMeshes = hwData->m_NumStudioMeshes; + numLODs = hwData->m_NumLODs; + dataSize = ComputeSize( hwData, &numVerts, &nTriCount, false ); // Size of vertex data + dataSizeLod0 = ComputeSize( hwData, &numVertsLod0, &nTriCountLod0, true ); + } + + studiohdr_t *pStudioHdr = (studiohdr_t *)modelloader->GetExtraData( model ); + dataSize += pStudioHdr->length; // Size of MDL file + numBones = pStudioHdr->numbones; + numParts = pStudioHdr->numbodyparts; + + g_pFileSystem->FPrintf( fileHandle, "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + name, dataSize, numVerts, nTriCount, dataSizeLod0, numVertsLod0, nTriCountLod0, numBones, numParts, numLODs, numMeshes ); + } + } + + g_pFileSystem->Close( fileHandle ); + Msg( "Created \"model_list.csv\" in the game folder.\n" ); + } + } +} +#endif // WIN32 + + + +CON_COMMAND_F( mod_dynamicmodeldebug, "debug spew for dynamic model loading", FCVAR_HIDDEN | FCVAR_DONTRECORD ) +{ + ((CModelLoader*)modelloader)->DebugPrintDynamicModels(); +} + +#include "server.h" +#ifndef SWDS +#include "client.h" +#endif +void CModelLoader::DebugPrintDynamicModels() +{ + Msg( "network table (server):\n" ); + if ( sv.GetDynamicModelsTable() ) + { + for ( int i = 0; i < sv.GetDynamicModelsTable()->GetNumStrings(); ++i ) + { + int dummy = 0; + char* data = (char*) sv.GetDynamicModelsTable()->GetStringUserData( i, &dummy ); + bool bLoadedOnServer = !(data && dummy && data[0] == 0); + Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', sv.GetDynamicModelsTable()->GetString(i) ); + } + } + +#ifndef SWDS + Msg( "\nnetwork table (client):\n" ); + if ( cl.m_pDynamicModelsTable ) + { + for ( int i = 0; i < cl.m_pDynamicModelsTable->GetNumStrings(); ++i ) + { + int dummy = 0; + char* data = (char*) cl.m_pDynamicModelsTable->GetStringUserData( i, &dummy ); + bool bLoadedOnServer = !(data && dummy && data[0] == 0); + Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', cl.m_pDynamicModelsTable->GetString(i) ); + } + } +#endif + + extern IVModelInfo *modelinfo; + extern IVModelInfoClient *modelinfoclient; + Msg( "\ndynamic models:\n" ); + for ( UtlHashHandle_t h = m_DynamicModels.FirstHandle(); h != m_DynamicModels.InvalidHandle(); h = m_DynamicModels.NextHandle(h) ) + { + CDynamicModelInfo &dyn = m_DynamicModels[h]; + int idx = modelinfo->GetModelIndex( m_DynamicModels.Key(h)->strName ); +#ifndef SWDS + if ( idx == -1 ) idx = modelinfoclient->GetModelIndex( m_DynamicModels.Key(h)->strName ); +#endif + Msg( "%d (%d%c): %s [ref: %d (%dc)] %s%s%s%s\n", idx, ((-2 - idx) >> 1), (idx & 1) ? 'c' : 's', + m_DynamicModels.Key(h)->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount, + (dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED) ? " QUEUED" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ? " LOADING" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ? " CLIENTREADY" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY) ? " ALLREADY" : "" ); + } +} diff --git a/engine/sys_dll2.cpp b/engine/sys_dll2.cpp index 30fbe4a..b8700fc 100644 --- a/engine/sys_dll2.cpp +++ b/engine/sys_dll2.cpp @@ -1,2571 +1,2572 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -//===========================================================================// -#define DISABLE_PROTECTED_THINGS - -#if defined( USE_SDL ) -#include "appframework/ilaunchermgr.h" -#endif - -#if defined( _WIN32 ) && !defined( _X360 ) -#include "winlite.h" -#include -#endif - -#if defined( OSX ) -#include -#endif - -#if defined( POSIX ) -#include -#include -#endif - -#include -#include "quakedef.h" -#include "idedicatedexports.h" -#include "engine_launcher_api.h" -#include "ivideomode.h" -#include "common.h" -#include "iregistry.h" -#include "keys.h" -#include "cdll_engine_int.h" -#include "traceinit.h" -#include "iengine.h" -#include "igame.h" -#include "tier0/etwprof.h" -#include "tier0/vcrmode.h" -#include "tier0/icommandline.h" -#include "tier0/minidump.h" -#include "engine_hlds_api.h" -#include "filesystem_engine.h" -#include "cl_main.h" -#include "client.h" -#include "tier3/tier3.h" -#include "MapReslistGenerator.h" -#include "toolframework/itoolframework.h" -#include "sourcevr/isourcevirtualreality.h" -#include "DevShotGenerator.h" -#include "gl_shader.h" -#include "l_studio.h" -#include "IHammer.h" -#include "sys_dll.h" -#include "materialsystem/materialsystem_config.h" -#include "server.h" -#include "video/ivideoservices.h" -#include "datacache/idatacache.h" -#include "vphysics_interface.h" -#include "inputsystem/iinputsystem.h" -#include "appframework/IAppSystemGroup.h" -#include "tier0/systeminformation.h" -#include "host_cmd.h" -#ifdef _WIN32 -#include "VGuiMatSurface/IMatSystemSurface.h" -#endif - -#ifdef GPROFILER -#include "gperftools/profiler.h" -#endif - -// This is here just for legacy support of older .dlls!!! -#include "SoundEmitterSystem/isoundemittersystembase.h" -#include "eiface.h" -#include "tier1/fmtstr.h" -#include "steam/steam_api.h" - -#ifndef SWDS -#include "sys_mainwind.h" -#include "vgui/ISystem.h" -#include "vgui_controls/Controls.h" -#include "IGameUIFuncs.h" -#include "cl_steamauth.h" -#endif // SWDS - -#if defined(_WIN32) -#include -#endif - -#if POSIX -#include -#endif - -#if defined( _X360 ) -#include "xbox/xbox_win32stubs.h" -#else -#include "xbox/xboxstubs.h" -#endif - -// memdbgon must be the last include file in a .cpp file!!! -#include "tier0/memdbgon.h" - -//----------------------------------------------------------------------------- -// Globals -//----------------------------------------------------------------------------- -IDedicatedExports *dedicated = NULL; -extern CreateInterfaceFn g_AppSystemFactory; -IHammer *g_pHammer = NULL; -IPhysics *g_pPhysics = NULL; -ISourceVirtualReality *g_pSourceVR = NULL; -#if defined( USE_SDL ) -ILauncherMgr *g_pLauncherMgr = NULL; -#endif - -#ifndef SWDS -extern CreateInterfaceFn g_ClientFactory; -#endif - -static SteamInfVersionInfo_t g_SteamInfIDVersionInfo; -const SteamInfVersionInfo_t& GetSteamInfIDVersionInfo() -{ - Assert( g_SteamInfIDVersionInfo.AppID != k_uAppIdInvalid ); - return g_SteamInfIDVersionInfo; -} - -int build_number( void ) -{ - return GetSteamInfIDVersionInfo().ServerVersion; -} - -//----------------------------------------------------------------------------- -// Forward declarations -//----------------------------------------------------------------------------- -void Host_GetHostInfo(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ); -const char *Key_BindingForKey( int keynum ); -void COM_ShutdownFileSystem( void ); -void COM_InitFilesystem( const char *pFullModPath ); -void Host_ReadPreStartupConfiguration(); - -//----------------------------------------------------------------------------- -// ConVars and console commands -//----------------------------------------------------------------------------- -#ifndef SWDS -//----------------------------------------------------------------------------- -// Purpose: exports an interface that can be used by the launcher to run the engine -// this is the exported function when compiled as a blob -//----------------------------------------------------------------------------- -void EXPORT F( IEngineAPI **api ) -{ - CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary to prevent the LTCG compiler from crashing. - *api = ( IEngineAPI * )(factory(VENGINE_LAUNCHER_API_VERSION, NULL)); -} -#endif // SWDS - -//----------------------------------------------------------------------------- -// Purpose: -//----------------------------------------------------------------------------- -void ClearIOStates( void ) -{ -#ifndef SWDS - if ( g_ClientDLL ) - { - g_ClientDLL->IN_ClearStates(); - } -#endif -} - -//----------------------------------------------------------------------------- -// The SDK launches the game with the full path to gameinfo.txt, so we need -// to strip off the path. -//----------------------------------------------------------------------------- -const char *GetModDirFromPath( const char *pszPath ) -{ - char *pszSlash = Q_strrchr( pszPath, '\\' ); - if ( pszSlash ) - { - return pszSlash + 1; - } - else if ( ( pszSlash = Q_strrchr( pszPath, '/' ) ) != NULL ) - { - return pszSlash + 1; - } - - // Must just be a mod directory already. - return pszPath; -} - -//----------------------------------------------------------------------------- -// Purpose: Main entry -//----------------------------------------------------------------------------- -#ifndef SWDS -#include "gl_matsysiface.h" -#endif - -//----------------------------------------------------------------------------- -// Inner loop: initialize, shutdown main systems, load steam to -//----------------------------------------------------------------------------- -class CModAppSystemGroup : public CAppSystemGroup -{ - typedef CAppSystemGroup BaseClass; -public: - // constructor - CModAppSystemGroup( bool bServerOnly, CAppSystemGroup *pParentAppSystem = NULL ) - : BaseClass( pParentAppSystem ), - m_bServerOnly( bServerOnly ) - { - } - - CreateInterfaceFn GetFactory() - { - return CAppSystemGroup::GetFactory(); - } - - // Methods of IApplication - virtual bool Create(); - virtual bool PreInit(); - virtual int Main(); - virtual void PostShutdown(); - virtual void Destroy(); - -private: - - bool IsServerOnly() const - { - return m_bServerOnly; - } - bool ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName ); - - bool AddLegacySystems(); - bool m_bServerOnly; -}; - -#if defined( STAGING_ONLY ) -CON_COMMAND( bigalloc, "huge alloc crash" ) -{ - Msg( "pre-crash %d\n", MemAlloc_MemoryAllocFailed() ); - // Alloc a bit less than UINT_MAX so there is room for heap headers in the malloc functions. - void *buf = malloc( UINT_MAX - 0x4000 ); - Msg( "post-alloc %d. buf: %p\n", MemAlloc_MemoryAllocFailed(), buf ); - *(int *)buf = 0; -} -#endif - -extern void S_ClearBuffer(); -extern char g_minidumpinfo[ 4096 ]; -extern PAGED_POOL_INFO_t g_pagedpoolinfo; -extern bool g_bUpdateMinidumpComment; -void GetSpew( char *buf, size_t buflen ); - -extern int gHostSpawnCount; -extern int g_nMapLoadCount; -extern int g_HostServerAbortCount; -extern int g_HostErrorCount; -extern int g_HostEndDemo; - -// Turn this to 1 to allow for expanded spew in minidump comments. -static ConVar sys_minidumpexpandedspew( "sys_minidumpexpandedspew", "1" ); - -#ifdef IS_WINDOWS_PC - -extern "C" void __cdecl FailSafe( unsigned int uStructuredExceptionCode, struct _EXCEPTION_POINTERS * pExceptionInfo ) -{ - // Nothing, this just catches a crash when creating the comment block -} - -#endif - -#if defined( POSIX ) - -static sigjmp_buf g_mark; -static void posix_signal_handler( int i ) -{ - siglongjmp( g_mark, -1 ); -} - -#define DO_TRY if ( sigsetjmp( g_mark, 1 ) == 0 ) -#define DO_CATCH else - -#if defined( OSX ) -#define __sighandler_t sig_t -#endif - -#else - -#define DO_TRY try -#define DO_CATCH catch ( ... ) - -#endif // POSIX - -//----------------------------------------------------------------------------- -// Purpose: Check whether any mods are loaded. -// Currently looks for metamod and sourcemod. -//----------------------------------------------------------------------------- -static bool IsSourceModLoaded() -{ -#if defined( _WIN32 ) - static const char *s_pFileNames[] = { "metamod.2.tf2.dll", "sourcemod.2.tf2.dll", "sdkhooks.ext.2.ep2v.dll", "sdkhooks.ext.2.tf2.dll" }; - - for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ ) - { - // GetModuleHandle function returns a handle to a mapped module - // without incrementing its reference count. - if ( GetModuleHandleA( s_pFileNames[ i ] ) ) - return true; - } -#else - FILE *fh = fopen( "/proc/self/maps", "r" ); - - if ( fh ) - { - char buf[ 1024 ]; - static const char *s_pFileNames[] = { "metamod.2.tf2.so", "sourcemod.2.tf2.so", "sdkhooks.ext.2.ep2v.so", "sdkhooks.ext.2.tf2.so" }; - - while ( fgets( buf, sizeof( buf ), fh ) ) - { - for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ ) - { - if ( strstr( buf, s_pFileNames[ i ] ) ) - { - fclose( fh ); - return true; - } - } - } - - fclose( fh ); - } -#endif - - return false; -} - -template< int _SIZE > -class CErrorText -{ -public: - CErrorText() : m_bIsDedicatedServer( false ) {} - ~CErrorText() {} - - void Steam_SetMiniDumpComment() - { -#if !defined( NO_STEAM ) - SteamAPI_SetMiniDumpComment( m_errorText ); -#endif - } - - void CommentCat( const char * str ) - { - V_strcat_safe( m_errorText, str ); - } - - void CommentPrintf( const char *fmt, ... ) - { - va_list args; - va_start( args, fmt ); - - size_t len = strlen( m_errorText ); - vsnprintf( m_errorText + len, sizeof( m_errorText ) - len - 1, fmt, args ); - m_errorText[ sizeof( m_errorText ) - 1 ] = 0; - - va_end( args ); - } - - void BuildComment( char const *pchSysErrorText, bool bRealCrash ) - { - // Try and detect whether this - bool bSourceModLoaded = false; - if ( m_bIsDedicatedServer ) - { - bSourceModLoaded = IsSourceModLoaded(); - if ( bSourceModLoaded ) - { - AppId_t AppId = GetSteamInfIDVersionInfo().ServerAppID; - // Bump up the number and report the crash. This should be something - // like 232251 (instead of 232250). 232251 is for the TF2 Windows client, - // but we actually report those crashes under ID 440, so this should be ok. - SteamAPI_SetBreakpadAppID( AppId + 1 ); - } - } - -#ifdef IS_WINDOWS_PC - // This warning only applies if you want to catch structured exceptions (crashes) - // using C++ exceptions. We do not want to do that so we can build with C++ exceptions - // completely disabled, and just suppress this warning. - // warning C4535: calling _set_se_translator() requires /EHa - #pragma warning( suppress : 4535 ) - _se_translator_function curfilter = _set_se_translator( &FailSafe ); -#elif defined( POSIX ) - // Only need to worry about this function crashing when we're dealing with a real crash. - __sighandler_t curfilter = bRealCrash ? signal( SIGSEGV, posix_signal_handler ) : 0; -#endif - - DO_TRY - { - Q_memset( m_errorText, 0x00, sizeof( m_errorText ) ); - - if ( pchSysErrorText ) - { - CommentCat( "Sys_Error( " ); - CommentCat( pchSysErrorText ); - - // Trim trailing \n. - int len = V_strlen( m_errorText ); - if ( len > 0 && m_errorText[ len - 1 ] == '\n' ) - m_errorText[ len - 1 ] = 0; - - CommentCat( " )\n" ); - } - else - { - CommentCat( "Crash\n" ); - } - CommentPrintf( "Uptime( %f )\n", Plat_FloatTime() ); - CommentPrintf( "SourceMod:%d,DS:%d,Crash:%d\n\n", bSourceModLoaded, m_bIsDedicatedServer, bRealCrash ); - - // Add g_minidumpinfo from CL_SetSteamCrashComment(). - CommentCat( g_minidumpinfo ); - - // Latch in case extended stuff below crashes - Steam_SetMiniDumpComment(); - - // Add Memory Status - BuildCommentMemStatus(); - - // Spew out paged pool stuff, etc. - PAGED_POOL_INFO_t ppi_info; - if ( Plat_GetPagedPoolInfo( &ppi_info ) != SYSCALL_UNSUPPORTED ) - { - CommentPrintf( "\nPaged Pool\nprev PP PAGES: used: %lu, free %lu\nfinal PP PAGES: used: %lu, free %lu\n", - g_pagedpoolinfo.numPagesUsed, g_pagedpoolinfo.numPagesFree, - ppi_info.numPagesUsed, ppi_info.numPagesFree ); - } - - CommentPrintf( "memallocfail? = %u\nActive: %s\nSpawnCount %d MapLoad Count %d\nError count %d, end demo %d, abort count %d\n", - MemAlloc_MemoryAllocFailed(), - ( game && game->IsActiveApp() ) ? "active" : "inactive", - gHostSpawnCount, - g_nMapLoadCount, - g_HostErrorCount, - g_HostEndDemo, - g_HostServerAbortCount ); - - // Latch in case extended stuff below crashes - Steam_SetMiniDumpComment(); - - // Add user comment strings. 4096 is just a large sanity number we should - // never ever reach (currently our minidump supports 32 of these.) - for( int i = 0; i < 4096; i++ ) - { - const char *pUserStreamInfo = MinidumpUserStreamInfoGet( i ); - if( !pUserStreamInfo ) - break; - - if ( pUserStreamInfo[ 0 ] ) - CommentPrintf( "%s", pUserStreamInfo ); - } - - bool bExtendedSpew = sys_minidumpexpandedspew.GetBool(); - if ( bExtendedSpew ) - { - BuildCommentExtended(); - Steam_SetMiniDumpComment(); - -#if defined( LINUX ) - if ( bRealCrash ) - { - // bRealCrash is set when we're actually making a comment for a dump or error. - AddFileToComment( "/proc/meminfo" ); - AddFileToComment( "/proc/self/status" ); - Steam_SetMiniDumpComment(); - - // Useful, but really big, so disable for now. - //$ AddFileToComment( "/proc/self/maps" ); - } -#endif - } - } - DO_CATCH - { - // Oh oh - } - -#ifdef IS_WINDOWS_PC - _set_se_translator( curfilter ); -#elif defined( POSIX ) - if ( bRealCrash ) - signal( SIGSEGV, curfilter ); -#endif - } - - void BuildCommentMemStatus() - { -#ifdef _WIN32 - const double MbDiv = 1024.0 * 1024.0; - - MEMORYSTATUSEX memStat; - ZeroMemory( &memStat, sizeof( MEMORYSTATUSEX ) ); - memStat.dwLength = sizeof( MEMORYSTATUSEX ); - - if ( GlobalMemoryStatusEx( &memStat ) ) - { - CommentPrintf( "\nMemory\nmemusage( %d %% )\ntotalPhysical Mb(%.2f)\nfreePhysical Mb(%.2f)\ntotalPaging Mb(%.2f)\nfreePaging Mb(%.2f)\ntotalVirtualMem Mb(%.2f)\nfreeVirtualMem Mb(%.2f)\nextendedVirtualFree Mb(%.2f)\n", - memStat.dwMemoryLoad, - (double)memStat.ullTotalPhys / MbDiv, - (double)memStat.ullAvailPhys / MbDiv, - (double)memStat.ullTotalPageFile / MbDiv, - (double)memStat.ullAvailPageFile / MbDiv, - (double)memStat.ullTotalVirtual / MbDiv, - (double)memStat.ullAvailVirtual / MbDiv, - (double)memStat.ullAvailExtendedVirtual / MbDiv); - } - - HINSTANCE hInst = LoadLibrary( "Psapi.dll" ); - if ( hInst ) - { - typedef BOOL (WINAPI *GetProcessMemoryInfoFn)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD); - GetProcessMemoryInfoFn fn = (GetProcessMemoryInfoFn)GetProcAddress( hInst, "GetProcessMemoryInfo" ); - if ( fn ) - { - PROCESS_MEMORY_COUNTERS counters; - - ZeroMemory( &counters, sizeof( PROCESS_MEMORY_COUNTERS ) ); - counters.cb = sizeof( PROCESS_MEMORY_COUNTERS ); - - if ( fn( GetCurrentProcess(), &counters, sizeof( PROCESS_MEMORY_COUNTERS ) ) ) - { - CommentPrintf( "\nProcess Memory\nWorkingSetSize Mb(%.2f)\nQuotaPagedPoolUsage Mb(%.2f)\nQuotaNonPagedPoolUsage: Mb(%.2f)\nPagefileUsage: Mb(%.2f)\n", - (double)counters.WorkingSetSize / MbDiv, - (double)counters.QuotaPagedPoolUsage / MbDiv, - (double)counters.QuotaNonPagedPoolUsage / MbDiv, - (double)counters.PagefileUsage / MbDiv ); - } - } - - FreeLibrary( hInst ); - } - -#elif defined( OSX ) - - static const struct - { - int ctl; - const char *name; - } s_ctl_names[] = - { - #define _XTAG( _x ) { _x, #_x } - _XTAG( HW_PHYSMEM ), - _XTAG( HW_USERMEM ), - _XTAG( HW_MEMSIZE ), - _XTAG( HW_AVAILCPU ), - #undef _XTAG - }; - - for ( size_t i = 0; i < Q_ARRAYSIZE( s_ctl_names ); i++ ) - { - uint64_t val = 0; - size_t len = sizeof( val ); - int mib[] = { CTL_HW, s_ctl_names[ i ].ctl }; - - if ( sysctl( mib, Q_ARRAYSIZE( mib ), &val, &len, NULL, 0 ) == 0 ) - { - CommentPrintf( " %s: %" PRIu64 "\n", s_ctl_names[ i ].name, val ); - } - } - -#endif - } - - void BuildCommentExtended() - { - try - { - CommentCat( "\nConVars (non-default)\n\n" ); - CommentPrintf( "%s %s %s\n", "var", "value", "default" ); - - for ( const ConCommandBase *var = g_pCVar->GetCommands() ; var ; var = var->GetNext()) - { - if ( var->IsCommand() ) - continue; - - ConVar *pCvar = ( ConVar * )var; - if ( pCvar->IsFlagSet( FCVAR_SERVER_CANNOT_QUERY | FCVAR_PROTECTED ) ) - continue; - - if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) ) - { - char var1[ MAX_OSPATH ]; - char var2[ MAX_OSPATH ]; - - Q_strncpy( var1, Host_CleanupConVarStringValue( pCvar->GetString() ), sizeof( var1 ) ); - Q_strncpy( var2, Host_CleanupConVarStringValue( pCvar->GetDefault() ), sizeof( var2 ) ); - - if ( !Q_stricmp( var1, var2 ) ) - continue; - } - else - { - if ( pCvar->GetFloat() == Q_atof( pCvar->GetDefault() ) ) - continue; - } - - if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) ) - CommentPrintf( "%s '%s' '%s'\n", pCvar->GetName(), Host_CleanupConVarStringValue( pCvar->GetString() ), pCvar->GetDefault() ); - else - CommentPrintf( "%s '%f' '%f'\n", pCvar->GetName(), pCvar->GetFloat(), Q_atof( pCvar->GetDefault() ) ); - } - - CommentCat( "\nConsole History (reversed)\n\n" ); - - // Get console - int len = V_strlen( m_errorText ); - if ( len < sizeof( m_errorText ) ) - { - GetSpew( m_errorText + len, sizeof( m_errorText ) - len - 1 ); - m_errorText[ sizeof( m_errorText ) - 1 ] = 0; - } - } - catch ( ... ) - { - CommentCat( "Exception thrown building console/convar history.\n" ); - } - } - -#if defined( LINUX ) - - void AddFileToComment( const char *filename ) - { - CommentPrintf( "\n%s:\n", filename ); - - int nStart = Q_strlen( m_errorText ); - int nMaxLen = sizeof( m_errorText ) - nStart - 1; - - if ( nMaxLen > 0 ) - { - FILE *fh = fopen( filename, "r" ); - - if ( fh ) - { - size_t ret = fread( m_errorText + nStart, 1, nMaxLen, fh ); - fclose( fh ); - - // Replace tab characters with spaces. - for ( size_t i = 0; i < ret; i++ ) - { - if ( m_errorText[ nStart + i ] == '\t' ) - m_errorText[ nStart + i ] = ' '; - } - } - - // Entire buffer should have been zeroed out, but just super sure... - m_errorText[ sizeof( m_errorText ) - 1 ] = 0; - } - } - -#endif // LINUX - -public: - char m_errorText[ _SIZE ]; - bool m_bIsDedicatedServer; -}; - -#if defined( _X360 ) -static CErrorText<3500> errorText; -#else -static CErrorText<95000> errorText; -#endif - -void BuildMinidumpComment( char const *pchSysErrorText, bool bRealCrash ) -{ -#if !defined(NO_STEAM) - /* - // Uncomment this code if you are testing max minidump comment length issues - // It allows you to asked for a dummy comment of a certain length - int nCommentLength = CommandLine()->ParmValue( "-commentlen", 0 ); - if ( nCommentLength > 0 ) - { - nCommentLength = MIN( nCommentLength, 128*1024 ); - char *cbuf = new char[ nCommentLength + 1 ]; - for ( int i = 0; i < nCommentLength; ++i ) - { - cbuf[ i ] = (char)('0' + (i % 10)); - } - cbuf[ nCommentLength ] = 0; - SteamAPI_SetMiniDumpComment( cbuf ); - delete[] cbuf; - return; - } - */ - errorText.BuildComment( pchSysErrorText, bRealCrash ); -#endif -} - -#if defined( POSIX ) - -static void PosixPreMinidumpCallback( void *context ) -{ - BuildMinidumpComment( NULL, true ); -} - -#endif - -//----------------------------------------------------------------------------- -// Purpose: Attempt to initialize appid/steam.inf/minidump information. May only return partial information if called -// before Filesystem is ready. -// -// The desire is to be able to call this ASAP to init basic minidump and AppID info, then re-call later on when -// the filesystem is setup, so full version # information and such can be propagated to the minidump system. -// (Currently, SDK mods will generally only have partial information prior to filesystem init) -//----------------------------------------------------------------------------- -// steam.inf keys. -#define VERSION_KEY "PatchVersion=" -#define PRODUCT_KEY "ProductName=" -#define SERVER_VERSION_KEY "ServerVersion=" -#define APPID_KEY "AppID=" -#define SERVER_APPID_KEY "ServerAppID=" -enum eSteamInfoInit -{ - eSteamInfo_Uninitialized, - eSteamInfo_Partial, - eSteamInfo_Initialized -}; -static eSteamInfoInit Sys_TryInitSteamInfo( void *pvAPI, SteamInfVersionInfo_t& VerInfo, const char *pchMod, const char *pchBaseDir, bool bDedicated ) -{ - static eSteamInfoInit initState = eSteamInfo_Uninitialized; - - eSteamInfoInit previousInitState = initState; - - // - // - // Initialize with some defaults. - VerInfo.ClientVersion = 0; - VerInfo.ServerVersion = 0; - V_strcpy_safe( VerInfo.szVersionString, "valve" ); - V_strcpy_safe( VerInfo.szProductString, "1.0.1.0" ); - VerInfo.AppID = k_uAppIdInvalid; - VerInfo.ServerAppID = k_uAppIdInvalid; - - // Filesystem may or may not be up - CUtlBuffer infBuf; - bool bFoundInf = false; - if ( g_pFileSystem ) - { - FileHandle_t fh; - fh = g_pFileSystem->Open( "steam.inf", "rb", "GAME" ); - bFoundInf = fh && g_pFileSystem->ReadToBuffer( fh, infBuf ); - } - - if ( !bFoundInf ) - { - // We may try to load the steam.inf BEFORE we turn on the filesystem, so use raw filesystem API's here. - char szFullPath[ MAX_PATH ] = { 0 }; - char szModSteamInfPath[ MAX_PATH ] = { 0 }; - V_ComposeFileName( pchMod, "steam.inf", szModSteamInfPath, sizeof( szModSteamInfPath ) ); - V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModSteamInfPath, pchBaseDir ); - - // Try opening steam.inf - FILE *fp = fopen( szFullPath, "rb" ); - if ( fp ) - { - // Read steam.inf data. - fseek( fp, 0, SEEK_END ); - size_t bufsize = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - - infBuf.EnsureCapacity( bufsize + 1 ); - - size_t iBytesRead = fread( infBuf.Base(), 1, bufsize, fp ); - ((char *)infBuf.Base())[iBytesRead] = 0; - infBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, iBytesRead + 1 ); - fclose( fp ); - - bFoundInf = ( iBytesRead == bufsize ); - } - } - - if ( bFoundInf ) - { - const char *pbuf = (const char*)infBuf.Base(); - while ( 1 ) - { - pbuf = COM_Parse( pbuf ); - if ( !pbuf || !com_token[ 0 ] ) - break; - - if ( !Q_strnicmp( com_token, VERSION_KEY, Q_strlen( VERSION_KEY ) ) ) - { - V_strcpy_safe( VerInfo.szVersionString, com_token + Q_strlen( VERSION_KEY ) ); - VerInfo.ClientVersion = atoi( VerInfo.szVersionString ); - } - else if ( !Q_strnicmp( com_token, PRODUCT_KEY, Q_strlen( PRODUCT_KEY ) ) ) - { - V_strcpy_safe( VerInfo.szProductString, com_token + Q_strlen( PRODUCT_KEY ) ); - } - else if ( !Q_strnicmp( com_token, SERVER_VERSION_KEY, Q_strlen( SERVER_VERSION_KEY ) ) ) - { - VerInfo.ServerVersion = atoi( com_token + Q_strlen( SERVER_VERSION_KEY ) ); - } - else if ( !Q_strnicmp( com_token, APPID_KEY, Q_strlen( APPID_KEY ) ) ) - { - VerInfo.AppID = atoi( com_token + Q_strlen( APPID_KEY ) ); - } - else if ( !Q_strnicmp( com_token, SERVER_APPID_KEY, Q_strlen( SERVER_APPID_KEY ) ) ) - { - VerInfo.ServerAppID = atoi( com_token + Q_strlen( SERVER_APPID_KEY ) ); - } - } - - // If we found a steam.inf we're as good as we're going to get, but don't tell callers we're fully initialized - // if it doesn't at least have an AppID - initState = ( VerInfo.AppID != k_uAppIdInvalid ) ? eSteamInfo_Initialized : eSteamInfo_Partial; - } - else if ( !bDedicated ) - { - // Opening steam.inf failed - try to open gameinfo.txt and read in just SteamAppId from that. - // (gameinfo.txt lacks the dedicated server steamid, so we'll just have to live until filesystem init to setup - // breakpad there when we hit this case) - char szModGameinfoPath[ MAX_PATH ] = { 0 }; - char szFullPath[ MAX_PATH ] = { 0 }; - V_ComposeFileName( pchMod, "gameinfo.txt", szModGameinfoPath, sizeof( szModGameinfoPath ) ); - V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModGameinfoPath, pchBaseDir ); - - // Try opening gameinfo.txt - FILE *fp = fopen( szFullPath, "rb" ); - if( fp ) - { - fseek( fp, 0, SEEK_END ); - size_t bufsize = ftell( fp ); - fseek( fp, 0, SEEK_SET ); - - char *buffer = ( char * )_alloca( bufsize + 1 ); - - size_t iBytesRead = fread( buffer, 1, bufsize, fp ); - buffer[ iBytesRead ] = 0; - fclose( fp ); - - KeyValuesAD pkvGameInfo( "gameinfo" ); - if ( pkvGameInfo->LoadFromBuffer( "gameinfo.txt", buffer ) ) - { - VerInfo.AppID = (AppId_t)pkvGameInfo->GetInt( "FileSystem/SteamAppId", k_uAppIdInvalid ); - } - } - - initState = eSteamInfo_Partial; - } - - // In partial state the ServerAppID might be unknown, but if we found the full steam.inf and it's not set, it shares AppID. - if ( initState == eSteamInfo_Initialized && VerInfo.ServerAppID == k_uAppIdInvalid ) - VerInfo.ServerAppID = VerInfo.AppID; - -#if !defined(_X360) - if ( VerInfo.AppID ) - { - // steamclient.dll doesn't know about steam.inf files in mod folder, - // it accepts a steam_appid.txt in the root directory if the game is - // not started through Steam. So we create one there containing the - // current AppID - FILE *fh = fopen( "steam_appid.txt", "wb" ); - if ( fh ) - { - CFmtStrN< 128 > strAppID( "%u\n", VerInfo.AppID ); - - fwrite( strAppID.Get(), strAppID.Length() + 1, 1, fh ); - fclose( fh ); - } - } -#endif // !_X360 - - // - // Update minidump info if we have more information than before - // - -#ifndef NO_STEAM - // If -nobreakpad was specified or we found metamod or sourcemod, don't register breakpad. - bool bUseBreakpad = !CommandLine()->FindParm( "-nobreakpad" ) && ( !bDedicated || !IsSourceModLoaded() ); - AppId_t BreakpadAppId = bDedicated ? VerInfo.ServerAppID : VerInfo.AppID; - Assert( BreakpadAppId != k_uAppIdInvalid || initState < eSteamInfo_Initialized ); - if ( BreakpadAppId != k_uAppIdInvalid && initState > previousInitState && bUseBreakpad ) - { - void *pvMiniDumpContext = NULL; - PFNPreMinidumpCallback pfnPreMinidumpCallback = NULL; - bool bFullMemoryDump = !bDedicated && IsWindows() && CommandLine()->FindParm( "-full_memory_dumps" ); - -#if defined( POSIX ) - // On Windows we're relying on the try/except to build the minidump comment. On Linux, we don't have that - // so we need to register the minidumpcallback handler here. - pvMiniDumpContext = pvAPI; - pfnPreMinidumpCallback = PosixPreMinidumpCallback; -#endif - - CFmtStrN<128> pchVersion( "%d", build_number() ); - Msg( "Using Breakpad minidump system. Version: %s AppID: %u\n", pchVersion.Get(), BreakpadAppId ); - - // We can filter various crash dumps differently in the Socorro backend code: - // Steam/min/web/crash_reporter/socorro/scripts/config/collectorconfig.py - SteamAPI_SetBreakpadAppID( BreakpadAppId ); - SteamAPI_UseBreakpadCrashHandler( pchVersion, __DATE__, __TIME__, bFullMemoryDump, pvMiniDumpContext, pfnPreMinidumpCallback ); - - // Tell errorText class if this is dedicated server. - errorText.m_bIsDedicatedServer = bDedicated; - } -#endif // NO_STEAM - - MinidumpUserStreamInfoSetHeader( "%sLaunching \"%s\"\n", ( bDedicated ? "DedicatedServerAPI " : "" ), CommandLine()->GetCmdLine() ); - - - return initState; -} - -#ifndef SWDS - -//----------------------------------------------------------------------------- -// -// Main engine interface exposed to launcher -// -//----------------------------------------------------------------------------- -class CEngineAPI : public CTier3AppSystem< IEngineAPI > -{ - typedef CTier3AppSystem< IEngineAPI > BaseClass; - -public: - virtual bool Connect( CreateInterfaceFn factory ); - virtual void Disconnect(); - virtual void *QueryInterface( const char *pInterfaceName ); - virtual InitReturnVal_t Init(); - virtual void Shutdown(); - - // This function must be called before init - virtual void SetStartupInfo( StartupInfo_t &info ); - - virtual int Run( ); - - // Sets the engine to run in a particular editor window - virtual void SetEngineWindow( void *hWnd ); - - // Posts a console command - virtual void PostConsoleCommand( const char *pConsoleCommand ); - - // Are we running the simulation? - virtual bool IsRunningSimulation( ) const; - - // Start/stop running the simulation - virtual void ActivateSimulation( bool bActive ); - - // Reset the map we're on - virtual void SetMap( const char *pMapName ); - - bool MainLoop(); - - int RunListenServer(); - -private: - - // Hooks a particular mod up to the registry - void SetRegistryMod( const char *pModName ); - - // One-time setup, based on the initially selected mod - // FIXME: This should move into the launcher! - bool OnStartup( void *pInstance, const char *pStartupModName ); - void OnShutdown(); - - // Initialization, shutdown of a mod. - bool ModInit( const char *pModName, const char *pGameDir ); - void ModShutdown(); - - // Initializes, shuts down the registry - bool InitRegistry( const char *pModName ); - void ShutdownRegistry(); - - // Handles there being an error setting up the video mode - InitReturnVal_t HandleSetModeError(); - - // Initializes, shuts down VR - bool InitVR(); - void ShutdownVR(); - - // Purpose: Message pump when running stand-alone - void PumpMessages(); - - // Purpose: Message pump when running with the editor - void PumpMessagesEditMode( bool &bIdle, long &lIdleCount ); - - // Activate/deactivates edit mode shaders - void ActivateEditModeShaders( bool bActive ); - -private: - void *m_hEditorHWnd; - bool m_bRunningSimulation; - bool m_bSupportsVR; - StartupInfo_t m_StartupInfo; -}; - - -//----------------------------------------------------------------------------- -// Singleton interface -//----------------------------------------------------------------------------- -static CEngineAPI s_EngineAPI; -EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineAPI, IEngineAPI, VENGINE_LAUNCHER_API_VERSION, s_EngineAPI ); - - -//----------------------------------------------------------------------------- -// Connect, disconnect -//----------------------------------------------------------------------------- -bool CEngineAPI::Connect( CreateInterfaceFn factory ) -{ - // Store off the app system factory... - g_AppSystemFactory = factory; - - if ( !BaseClass::Connect( factory ) ) - return false; - - g_pFileSystem = g_pFullFileSystem; - if ( !g_pFileSystem ) - return false; - - g_pFileSystem->SetWarningFunc( Warning ); - - if ( !Shader_Connect( true ) ) - return false; - - g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL ); - - if ( !g_pStudioRender || !g_pDataCache || !g_pPhysics || !g_pMDLCache || !g_pMatSystemSurface || !g_pInputSystem /* || !g_pVideo */ ) - { - Warning( "Engine wasn't able to acquire required interfaces!\n" ); - return false; - } - - if (!g_pStudioRender) - { - Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION ); - return false; - } - - g_pHammer = (IHammer*)factory( INTERFACEVERSION_HAMMER, NULL ); - -#if defined( USE_SDL ) - g_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL ); -#endif - - ConnectMDLCacheNotify(); - - return true; -} - -void CEngineAPI::Disconnect() -{ - DisconnectMDLCacheNotify(); - -#if !defined( SWDS ) - TRACESHUTDOWN( Steam3Client().Shutdown() ); -#endif - - g_pHammer = NULL; - g_pPhysics = NULL; - - Shader_Disconnect(); - - g_pFileSystem = NULL; - - BaseClass::Disconnect(); - - g_AppSystemFactory = NULL; -} - - -//----------------------------------------------------------------------------- -// Query interface -//----------------------------------------------------------------------------- -void *CEngineAPI::QueryInterface( const char *pInterfaceName ) -{ - // Loading the engine DLL mounts *all* engine interfaces - CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary - return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. -} - - -//----------------------------------------------------------------------------- -// Sets startup info -//----------------------------------------------------------------------------- -void CEngineAPI::SetStartupInfo( StartupInfo_t &info ) -{ - // Setup and write out steam_appid.txt before we launch - bool bDedicated = false; // Dedicated comes through CDedicatedServerAPI - eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); - - g_bTextMode = info.m_bTextMode; - - // Set up the engineparms_t which contains global information about the mod - host_parms.basedir = const_cast( info.m_pBaseDirectory ); - - // Copy off all the startup info - m_StartupInfo = info; - -#if !defined( SWDS ) - // turn on the Steam3 API early so we can query app data up front - TRACEINIT( Steam3Client().Activate(), Steam3Client().Shutdown() ); -#endif - - // Needs to be done prior to init material system config - TRACEINIT( COM_InitFilesystem( m_StartupInfo.m_pInitialMod ), COM_ShutdownFileSystem() ); - - if ( steamInfo != eSteamInfo_Initialized ) - { - // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find - // their steam.inf, due to mounting SDK search paths. - steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); - Assert( steamInfo == eSteamInfo_Initialized ); - if ( steamInfo != eSteamInfo_Initialized ) - { - Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" ); - } - } - - m_bSupportsVR = false; - if ( IsPC() ) - { - KeyValues *modinfo = new KeyValues("ModInfo"); - if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) ) - { - // Enable file tracking - client always does this in case it connects to a pure server. - // server only does this if sv_pure is set - // If it's not singleplayer_only - if ( V_stricmp( modinfo->GetString("type", "singleplayer_only"), "singleplayer_only") == 0 ) - { - DevMsg( "Disabling whitelist file tracking in filesystem...\n" ); - g_pFileSystem->EnableWhitelistFileTracking( false, false, false ); - } - else - { - DevMsg( "Enabling whitelist file tracking in filesystem...\n" ); - g_pFileSystem->EnableWhitelistFileTracking( true, false, false ); - } - - m_bSupportsVR = modinfo->GetInt( "supportsvr" ) > 0 && CommandLine()->CheckParm( "-vr" ); - if ( m_bSupportsVR ) - { - // This also has to happen before CreateGameWindow to know where to put - // the window and how big to make it - if ( InitVR() ) - { - if ( Steam3Client().SteamUtils() ) - { - if ( Steam3Client().SteamUtils()->IsSteamRunningInVR() && g_pSourceVR->IsHmdConnected() ) - { - int nForceVRAdapterIndex = g_pSourceVR->GetVRModeAdapter(); - materials->SetAdapter( nForceVRAdapterIndex, 0 ); - - g_pSourceVR->SetShouldForceVRMode(); - } - } - } - } - - } - modinfo->deleteThis(); - } -} - - -//----------------------------------------------------------------------------- -// Init, shutdown -//----------------------------------------------------------------------------- -InitReturnVal_t CEngineAPI::Init() -{ - if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 ) - { - Plat_SetBenchmarkMode( true ); - } - - InitReturnVal_t nRetVal = BaseClass::Init(); - if ( nRetVal != INIT_OK ) - return nRetVal; - - m_bRunningSimulation = false; - - // Initialize the FPU control word -#if defined(WIN32) && !defined( SWDS ) && !defined( _X360 ) - _asm - { - fninit - } -#endif - - SetupFPUControlWord(); - - // This creates the videomode singleton object, it doesn't depend on the registry - VideoMode_Create(); - - // Initialize the editor hwnd to render into - m_hEditorHWnd = NULL; - - // One-time setup - // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher - // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown - if ( !OnStartup( m_StartupInfo.m_pInstance, m_StartupInfo.m_pInitialMod ) ) - { - return HandleSetModeError(); - } - - return INIT_OK; -} - -void CEngineAPI::Shutdown() -{ - VideoMode_Destroy(); - BaseClass::Shutdown(); -} - - -//----------------------------------------------------------------------------- -// Sets the engine to run in a particular editor window -//----------------------------------------------------------------------------- -void CEngineAPI::SetEngineWindow( void *hWnd ) -{ - if ( !InEditMode() ) - return; - - // Detach input from the previous editor window - game->InputDetachFromGameWindow(); - - m_hEditorHWnd = hWnd; - videomode->SetGameWindow( m_hEditorHWnd ); -} - - -//----------------------------------------------------------------------------- -// Posts a console command -//----------------------------------------------------------------------------- -void CEngineAPI::PostConsoleCommand( const char *pCommand ) -{ - Cbuf_AddText( pCommand ); -} - - -//----------------------------------------------------------------------------- -// Is the engine currently rinning? -//----------------------------------------------------------------------------- -bool CEngineAPI::IsRunningSimulation() const -{ - return (eng->GetState() == IEngine::DLL_ACTIVE); -} - - -//----------------------------------------------------------------------------- -// Reset the map we're on -//----------------------------------------------------------------------------- -void CEngineAPI::SetMap( const char *pMapName ) -{ -// if ( !Q_stricmp( sv.mapname, pMapName ) ) -// return; - - char buf[MAX_PATH]; - Q_snprintf( buf, MAX_PATH, "map %s", pMapName ); - Cbuf_AddText( buf ); -} - - -//----------------------------------------------------------------------------- -// Start/stop running the simulation -//----------------------------------------------------------------------------- -void CEngineAPI::ActivateSimulation( bool bActive ) -{ - // FIXME: Not sure what will happen in this case - if ( ( eng->GetState() != IEngine::DLL_ACTIVE ) && - ( eng->GetState() != IEngine::DLL_PAUSED ) ) - { - return; - } - - bool bCurrentlyActive = (eng->GetState() != IEngine::DLL_PAUSED); - if ( bActive == bCurrentlyActive ) - return; - - // FIXME: Should attachment/detachment be part of the state machine in IEngine? - if ( !bActive ) - { - eng->SetNextState( IEngine::DLL_PAUSED ); - - // Detach input from the previous editor window - game->InputDetachFromGameWindow(); - } - else - { - eng->SetNextState( IEngine::DLL_ACTIVE ); - - // Start accepting input from the new window - // FIXME: What if the attachment fails? - game->InputAttachToGameWindow(); - } -} - -static void MoveConsoleWindowToFront() -{ -#ifdef _WIN32 - // Move the window to the front. - HINSTANCE hInst = LoadLibrary( "kernel32.dll" ); - if ( hInst ) - { - typedef HWND (*GetConsoleWindowFn)(); - GetConsoleWindowFn fn = (GetConsoleWindowFn)GetProcAddress( hInst, "GetConsoleWindow" ); - if ( fn ) - { - HWND hwnd = fn(); - ShowWindow( hwnd, SW_SHOW ); - UpdateWindow( hwnd ); - SetWindowPos( hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW ); - } - FreeLibrary( hInst ); - } -#endif -} - -//----------------------------------------------------------------------------- -// Purpose: Message pump when running stand-alone -//----------------------------------------------------------------------------- -void CEngineAPI::PumpMessages() -{ - // This message pumping happens in SDL if SDL is enabled. -#if defined( PLATFORM_WINDOWS ) && !defined( USE_SDL ) - MSG msg; - while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) - { - TranslateMessage( &msg ); - DispatchMessage( &msg ); - } -#endif - -#if defined( USE_SDL ) - g_pLauncherMgr->PumpWindowsMessageLoop(); -#endif - - // Get input from attached devices - g_pInputSystem->PollInputState(); - - if ( IsX360() ) - { - // handle Xbox system messages - XBX_ProcessEvents(); - } - - // NOTE: Under some implementations of Win9x, - // dispatching messages can cause the FPU control word to change - if ( IsPC() ) - { - SetupFPUControlWord(); - } - - game->DispatchAllStoredGameMessages(); - - if ( IsPC() ) - { - static bool s_bFirstRun = true; - if ( s_bFirstRun ) - { - s_bFirstRun = false; - MoveConsoleWindowToFront(); - } - } -} - -//----------------------------------------------------------------------------- -// Purpose: Message pump when running stand-alone -//----------------------------------------------------------------------------- -void CEngineAPI::PumpMessagesEditMode( bool &bIdle, long &lIdleCount ) -{ - - if ( bIdle && !g_pHammer->HammerOnIdle( lIdleCount++ ) ) - { - bIdle = false; - } - - // Get input from attached devices - g_pInputSystem->PollInputState(); - -#ifdef WIN32 - MSG msg; - while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) - { - if ( msg.message == WM_QUIT ) - { - eng->SetQuitting( IEngine::QUIT_TODESKTOP ); - break; - } - - if ( !g_pHammer->HammerPreTranslateMessage(&msg) ) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - - // Reset idle state after pumping idle message. - if ( g_pHammer->HammerIsIdleMessage(&msg) ) - { - bIdle = true; - lIdleCount = 0; - } - } -#elif defined( USE_SDL ) - Error( "Not supported" ); -#else -#error -#endif - - - // NOTE: Under some implementations of Win9x, - // dispatching messages can cause the FPU control word to change - SetupFPUControlWord(); - - game->DispatchAllStoredGameMessages(); -} - -//----------------------------------------------------------------------------- -// Activate/deactivates edit mode shaders -//----------------------------------------------------------------------------- -void CEngineAPI::ActivateEditModeShaders( bool bActive ) -{ - if ( InEditMode() && ( g_pMaterialSystemConfig->bEditMode != bActive ) ) - { - MaterialSystem_Config_t config = *g_pMaterialSystemConfig; - config.bEditMode = bActive; - OverrideMaterialSystemConfig( config ); - } -} - - -#ifdef GPROFILER -static bool g_gprofiling = false; - -CON_COMMAND( gprofilerstart, "Starts the gperftools profiler recording to the specified file." ) -{ - if ( g_gprofiling ) - { - Msg( "Profiling is already started.\n" ); - return; - } - - char buffer[500]; - const char* profname = buffer; - if ( args.ArgC() < 2 ) - { - static const char *s_pszHomeDir = getenv("HOME"); - if ( !s_pszHomeDir ) - { - Msg( "Syntax: gprofile \n" ); - return; - } - - // Use the current date and time to create a unique file name.time_t t = time(NULL); - time_t t = time(NULL); - struct tm tm = *localtime(&t); - - V_sprintf_safe( buffer, "%s/valveprofile_%4d_%02d_%02d_%02d.%02d.%02d.prof", s_pszHomeDir, - tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec ); - // profname already points to buffer. - } - else - { - profname = args[1]; - } - - int result = ProfilerStart( profname ); - if ( result ) - { - Msg( "Profiling started successfully. Recording to %s. Stop profiling with gprofilerstop.\n", profname ); - g_gprofiling = true; - } - else - { - Msg( "Profiling to %s failed to start - errno = %d.\n", profname, errno ); - } -} - -CON_COMMAND( gprofilerstop, "Stops the gperftools profiler." ) -{ - if ( g_gprofiling ) - { - ProfilerStop(); - Msg( "Stopped profiling.\n" ); - g_gprofiling = false; - } -} -#endif - - -void StopGProfiler() -{ -#ifdef GPROFILER - gprofilerstop( CCommand() ); -#endif -} - - -//----------------------------------------------------------------------------- -// Purpose: Message pump -//----------------------------------------------------------------------------- -bool CEngineAPI::MainLoop() -{ - bool bIdle = true; - long lIdleCount = 0; - - // Main message pump - while ( true ) - { - // Pump messages unless someone wants to quit - if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING ) - { - // We have to explicitly stop the profiler since otherwise symbol - // resolution doesn't work correctly. - StopGProfiler(); - if ( eng->GetQuitting() != IEngine::QUIT_TODESKTOP ) - return true; - return false; - } - - // Pump the message loop - if ( !InEditMode() ) - { - PumpMessages(); - } - else - { - PumpMessagesEditMode( bIdle, lIdleCount ); - } - - // Run engine frame + hammer frame - if ( !InEditMode() || m_hEditorHWnd ) - { - VCRSyncToken( "Frame" ); - - // Deactivate edit mode shaders - ActivateEditModeShaders( false ); - - eng->Frame(); - - // Reactivate edit mode shaders (in Edit mode only...) - ActivateEditModeShaders( true ); - } - - if ( InEditMode() ) - { - g_pHammer->RunFrame(); - } - } - - return false; -} - - -//----------------------------------------------------------------------------- -// Initializes, shuts down the registry -//----------------------------------------------------------------------------- -bool CEngineAPI::InitRegistry( const char *pModName ) -{ - if ( IsPC() ) - { - char szRegSubPath[MAX_PATH]; - Q_snprintf( szRegSubPath, sizeof(szRegSubPath), "%s\\%s", "Source", pModName ); - return registry->Init( szRegSubPath ); - } - return true; -} - -void CEngineAPI::ShutdownRegistry( ) -{ - if ( IsPC() ) - { - registry->Shutdown( ); - } -} - - -//----------------------------------------------------------------------------- -// Initializes, shuts down VR (via sourcevr.dll) -//----------------------------------------------------------------------------- -bool CEngineAPI::InitVR() -{ - if ( m_bSupportsVR ) - { - g_pSourceVR = (ISourceVirtualReality *)g_AppSystemFactory( SOURCE_VIRTUAL_REALITY_INTERFACE_VERSION, NULL ); - if ( g_pSourceVR ) - { - // make sure that the sourcevr DLL we loaded is secure. If not, don't - // let this client connect to secure servers. - if ( !Host_AllowLoadModule( "sourcevr" DLL_EXT_STRING, "EXECUTABLE_PATH", false ) ) - { - Warning( "Preventing connections to secure servers because sourcevr.dll is not signed.\n" ); - Host_DisallowSecureServers(); - } - } - } - return true; -} - - -void CEngineAPI::ShutdownVR() -{ -} - - -//----------------------------------------------------------------------------- -// One-time setup, based on the initially selected mod -// FIXME: This should move into the launcher! -//----------------------------------------------------------------------------- -bool CEngineAPI::OnStartup( void *pInstance, const char *pStartupModName ) -{ - // This fixes a bug on certain machines where the input will - // stop coming in for about 1 second when someone hits a key. - // (true means to disable priority boost) -#ifdef WIN32 - if ( IsPC() ) - { - SetThreadPriorityBoost( GetCurrentThread(), true ); - } -#endif - - // FIXME: Turn videomode + game into IAppSystems? - - // Try to create the window - COM_TimestampedLog( "game->Init" ); - - // This has to happen before CreateGameWindow to set up the instance - // for use by the code that creates the window - if ( !game->Init( pInstance ) ) - { - goto onStartupError; - } - - // Try to create the window - COM_TimestampedLog( "videomode->Init" ); - - // This needs to be after Shader_Init and registry->Init - // This way mods can have different default video settings - if ( !videomode->Init( ) ) - { - goto onStartupShutdownGame; - } - - // We need to access the registry to get various settings (specifically, - // InitMaterialSystemConfig requires it). - if ( !InitRegistry( pStartupModName ) ) - { - goto onStartupShutdownVideoMode; - } - - materials->ModInit(); - - // Setup the material system config record, CreateGameWindow depends on it - // (when we're running stand-alone) - InitMaterialSystemConfig( InEditMode() ); - -#if defined( _X360 ) - XBX_NotifyCreateListener( XNOTIFY_SYSTEM|XNOTIFY_LIVE|XNOTIFY_XMP ); -#endif - - ShutdownRegistry(); - return true; - - // Various error conditions -onStartupShutdownVideoMode: - videomode->Shutdown(); - -onStartupShutdownGame: - game->Shutdown(); - -onStartupError: - return false; -} - - -//----------------------------------------------------------------------------- -// One-time shutdown (shuts down stuff set up in OnStartup) -// FIXME: This should move into the launcher! -//----------------------------------------------------------------------------- -void CEngineAPI::OnShutdown() -{ - if ( videomode ) - { - videomode->Shutdown(); - } - - ShutdownVR(); - - // Shut down the game - game->Shutdown(); - - materials->ModShutdown(); - TRACESHUTDOWN( COM_ShutdownFileSystem() ); -} - -static bool IsValveMod( const char *pModName ) -{ - // Figure out if we're running a Valve mod or not. - return ( Q_stricmp( GetCurrentMod(), "cstrike" ) == 0 || - Q_stricmp( GetCurrentMod(), "dod" ) == 0 || - Q_stricmp( GetCurrentMod(), "hl1mp" ) == 0 || - Q_stricmp( GetCurrentMod(), "tf" ) == 0 || - Q_stricmp( GetCurrentMod(), "tf_beta" ) == 0 || - Q_stricmp( GetCurrentMod(), "hl2mp" ) == 0 ); -} - -//----------------------------------------------------------------------------- -// Initialization, shutdown of a mod. -//----------------------------------------------------------------------------- -bool CEngineAPI::ModInit( const char *pModName, const char *pGameDir ) -{ - // Set up the engineparms_t which contains global information about the mod - host_parms.mod = COM_StringCopy( GetModDirFromPath( pModName ) ); - host_parms.game = COM_StringCopy( pGameDir ); - - // By default, restrict server commands in Valve games and don't restrict them in mods. - cl.m_bRestrictServerCommands = IsValveMod( host_parms.mod ); - cl.m_bRestrictClientCommands = cl.m_bRestrictServerCommands; - - // build the registry path we're going to use for this mod - InitRegistry( pModName ); - - // This sets up the game search path, depends on host_parms - TRACEINIT( MapReslistGenerator_Init(), MapReslistGenerator_Shutdown() ); -#if !defined( _X360 ) - TRACEINIT( DevShotGenerator_Init(), DevShotGenerator_Shutdown() ); -#endif - - // Slam cvars based on mod/config.cfg - Host_ReadPreStartupConfiguration(); - - bool bWindowed = g_pMaterialSystemConfig->Windowed(); - if( g_pMaterialSystemConfig->m_nVRModeAdapter != -1 ) - { - // at init time we never want to start up full screen - bWindowed = true; - } - - // Create the game window now that we have a search path - // FIXME: Deal with initial window width + height better - if ( !videomode || !videomode->CreateGameWindow( g_pMaterialSystemConfig->m_VideoMode.m_Width, g_pMaterialSystemConfig->m_VideoMode.m_Height, bWindowed ) ) - { - return false; - } - - return true; -} - -void CEngineAPI::ModShutdown() -{ - COM_StringFree(host_parms.mod); - COM_StringFree(host_parms.game); - - // Stop accepting input from the window - game->InputDetachFromGameWindow(); - -#if !defined( _X360 ) - TRACESHUTDOWN( DevShotGenerator_Shutdown() ); -#endif - TRACESHUTDOWN( MapReslistGenerator_Shutdown() ); - - ShutdownRegistry(); -} - - -//----------------------------------------------------------------------------- -// Purpose: Handles there being an error setting up the video mode -// Output : Returns true on if the engine should restart, false if it should quit -//----------------------------------------------------------------------------- -InitReturnVal_t CEngineAPI::HandleSetModeError() -{ - // show an error, see if the user wants to restart - if ( CommandLine()->FindParm( "-safe" ) ) - { - Sys_MessageBox( "Failed to set video mode.\n\nThis game has a minimum requirement of DirectX 7.0 compatible hardware.\n", "Video mode error", false ); - return INIT_FAILED; - } - - if ( CommandLine()->FindParm( "-autoconfig" ) ) - { - if ( Sys_MessageBox( "Failed to set video mode - falling back to safe mode settings.\n\nGame will now restart with the new video settings.", "Video - safe mode fallback", true )) - { - CommandLine()->AppendParm( "-safe", NULL ); - return (InitReturnVal_t)INIT_RESTART; - } - return INIT_FAILED; - } - - if ( Sys_MessageBox( "Failed to set video mode - resetting to defaults.\n\nGame will now restart with the new video settings.", "Video mode warning", true ) ) - { - CommandLine()->AppendParm( "-autoconfig", NULL ); - return (InitReturnVal_t)INIT_RESTART; - } - - return INIT_FAILED; -} - - -//----------------------------------------------------------------------------- -// Purpose: Main loop for non-dedicated servers -//----------------------------------------------------------------------------- -int CEngineAPI::RunListenServer() -{ - // - // NOTE: Systems set up here should depend on the mod - // Systems which are mod-independent should be set up in the launcher or Init() - // - - // Innocent until proven guilty - int nRunResult = RUN_OK; - - // Happens every time we start up and shut down a mod - if ( ModInit( m_StartupInfo.m_pInitialMod, m_StartupInfo.m_pInitialGame ) ) - { - CModAppSystemGroup modAppSystemGroup( false, m_StartupInfo.m_pParentAppSystemGroup ); - - // Store off the app system factory... - g_AppSystemFactory = modAppSystemGroup.GetFactory(); - - nRunResult = modAppSystemGroup.Run(); - - g_AppSystemFactory = NULL; - - // Shuts down the mod - ModShutdown(); - - // Disconnects from the editor window - videomode->SetGameWindow( NULL ); - } - - // Closes down things that were set up in OnStartup - // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher - // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown - OnShutdown(); - - return nRunResult; -} - -static void StaticRunListenServer( void *arg ) -{ - *(int *)arg = s_EngineAPI.RunListenServer(); -} - - - -// This function is set as the crash handler for unhandled exceptions and as the minidump -// handler for to be used by all of tier0's crash recording. This function -// adds a game-specific minidump comment and ensures that the SteamAPI function is -// used to save the minidump so that crashes are uploaded. SteamAPI has previously -// been configured to use breakpad by calling SteamAPI_UseBreakpadCrashHandler. -extern "C" void __cdecl WriteSteamMiniDumpWithComment( unsigned int uStructuredExceptionCode, - struct _EXCEPTION_POINTERS * pExceptionInfo, - const char *pszFilenameSuffix ) -{ - // TODO: dynamically set the minidump comment from contextual info about the crash (i.e current VPROF node)? -#if !defined( NO_STEAM ) - - if ( g_bUpdateMinidumpComment ) - { - BuildMinidumpComment( NULL, true ); - } - - SteamAPI_WriteMiniDump( uStructuredExceptionCode, pExceptionInfo, build_number() ); - // Clear DSound Buffers so the sound doesn't loop while the game shuts down - try - { - S_ClearBuffer(); - } - catch ( ... ) - { - } -#endif -} - - -//----------------------------------------------------------------------------- -// Purpose: Main -//----------------------------------------------------------------------------- -int CEngineAPI::Run() -{ - if ( CommandLine()->FindParm( "-insecure" ) || CommandLine()->FindParm( "-textmode" ) ) - { - Host_DisallowSecureServers(); - } - -#ifdef _X360 - return RunListenServer(); // don't handle exceptions on 360 (because if we do then minidumps won't work at all) -#elif defined ( _WIN32 ) - // Ensure that we crash when we do something naughty in a callback - // such as a window proc. Otherwise on a 64-bit OS the crashes will be - // silently swallowed. - EnableCrashingOnCrashes(); - - // Set the default minidump handling function. This is necessary so that Steam - // will upload crashes, with comments. - SetMiniDumpFunction( WriteSteamMiniDumpWithComment ); - - // Catch unhandled crashes. A normal __try/__except block will not work across - // the kernel callback boundary, but this does. To be clear, __try/__except - // and try/catch will usually not catch exceptions in a WindowProc or other - // callback that is called from kernel mode because 64-bit Windows cannot handle - // throwing exceptions across that boundary. See this article for details: - // http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/ - // Note that the unhandled exception function is not called when running - // under a debugger, but that's fine because in that case we don't care about - // recording minidumps. - // The try/catch block still makes sense because it is a more reliable way - // of catching exceptions that aren't in callbacks. - // The unhandled exception filter will also catch crashes in threads that - // don't have a try/catch or __try/__except block. - bool noMinidumps = CommandLine()->FindParm( "-nominidumps"); - if ( !noMinidumps ) - MinidumpSetUnhandledExceptionFunction( WriteSteamMiniDumpWithComment ); - - if ( !Plat_IsInDebugSession() && !noMinidumps ) - { - int nRetVal = RUN_OK; - CatchAndWriteMiniDumpForVoidPtrFn( StaticRunListenServer, &nRetVal, true ); - return nRetVal; - } - else - { - return RunListenServer(); - } -#else - return RunListenServer(); -#endif -} -#endif // SWDS - -bool g_bUsingLegacyAppSystems = false; - -bool CModAppSystemGroup::AddLegacySystems() -{ - g_bUsingLegacyAppSystems = true; - - AppSystemInfo_t appSystems[] = - { - { "soundemittersystem", SOUNDEMITTERSYSTEM_INTERFACE_VERSION }, - { "", "" } // Required to terminate the list - }; - - if ( !AddSystems( appSystems ) ) - return false; - -#if !defined( DEDICATED ) -// if ( CommandLine()->FindParm( "-tools" ) ) - { - AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING ); - - if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) ) - return false; - } -#endif - - return true; -} - -//----------------------------------------------------------------------------- -// Instantiate all main libraries -//----------------------------------------------------------------------------- -bool CModAppSystemGroup::Create() -{ -#ifndef SWDS - if ( !IsServerOnly() ) -{ - if ( !ClientDLL_Load() ) - return false; -} -#endif - - if ( !ServerDLL_Load( IsServerOnly() ) ) - return false; - - IClientDLLSharedAppSystems *clientSharedSystems = 0; - -#ifndef SWDS - if ( !IsServerOnly() ) - { - clientSharedSystems = ( IClientDLLSharedAppSystems * )g_ClientFactory( CLIENT_DLL_SHARED_APPSYSTEMS, NULL ); - if ( !clientSharedSystems ) - return AddLegacySystems(); - } -#endif - - IServerDLLSharedAppSystems *serverSharedSystems = ( IServerDLLSharedAppSystems * )g_ServerFactory( SERVER_DLL_SHARED_APPSYSTEMS, NULL ); - if ( !serverSharedSystems ) - { - Assert( !"Expected both game and client .dlls to have or not have shared app systems interfaces!!!" ); - return AddLegacySystems(); - } - - // Load game and client .dlls and build list then - CUtlVector< AppSystemInfo_t > systems; - - int i; - int serverCount = serverSharedSystems->Count(); - for ( i = 0 ; i < serverCount; ++i ) - { - const char *dllName = serverSharedSystems->GetDllName( i ); - const char *interfaceName = serverSharedSystems->GetInterfaceName( i ); - - AppSystemInfo_t info; - info.m_pModuleName = dllName; - info.m_pInterfaceName = interfaceName; - - systems.AddToTail( info ); - } - - if ( !IsServerOnly() ) - { - int clientCount = clientSharedSystems->Count(); - for ( i = 0 ; i < clientCount; ++i ) - { - const char *dllName = clientSharedSystems->GetDllName( i ); - const char *interfaceName = clientSharedSystems->GetInterfaceName( i ); - - if ( ModuleAlreadyInList( systems, dllName, interfaceName ) ) - continue; - - AppSystemInfo_t info; - info.m_pModuleName = dllName; - info.m_pInterfaceName = interfaceName; - - systems.AddToTail( info ); - } - } - - AppSystemInfo_t info; - info.m_pModuleName = ""; - info.m_pInterfaceName = ""; - systems.AddToTail( info ); - - if ( !AddSystems( systems.Base() ) ) - return false; - -#if !defined( DEDICATED ) -// if ( CommandLine()->FindParm( "-tools" ) ) - { - AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING ); - - if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) ) - return false; - } -#endif - - return true; -} - -//----------------------------------------------------------------------------- -// Purpose: Fixme, we might need to verify if the interface names differ for the client versus the server -// Input : list - -// *moduleName - -// Output : Returns true on success, false on failure. -//----------------------------------------------------------------------------- -bool CModAppSystemGroup::ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName ) -{ - for ( int i = 0; i < list.Count(); ++i ) - { - if ( !Q_stricmp( list[ i ].m_pModuleName, moduleName ) ) - { - if ( Q_stricmp( list[ i ].m_pInterfaceName, interfaceName ) ) - { - Error( "Game and client .dlls requesting different versions '%s' vs. '%s' from '%s'\n", - list[ i ].m_pInterfaceName, interfaceName, moduleName ); - } - return true; - } - } - - return false; -} - -bool CModAppSystemGroup::PreInit() -{ - return true; -} - -void SV_ShutdownGameDLL(); -int CModAppSystemGroup::Main() -{ - int nRunResult = RUN_OK; - - if ( IsServerOnly() ) - { - // Start up the game engine - if ( eng->Load( true, host_parms.basedir ) ) - { - // If we're using STEAM, pass the map cycle list as resource hints... - // Dedicated server drives frame loop manually - dedicated->RunServer(); - - SV_ShutdownGameDLL(); - } - } - else - { - eng->SetQuitting( IEngine::QUIT_NOTQUITTING ); - - COM_TimestampedLog( "eng->Load" ); - - // Start up the game engine - static const char engineLoadMessage[] = "Calling CEngine::Load"; - int64 nStartTime = ETWBegin( engineLoadMessage ); - if ( eng->Load( false, host_parms.basedir ) ) - { -#if !defined(SWDS) - ETWEnd( engineLoadMessage, nStartTime ); - toolframework->ServerInit( g_ServerFactory ); - - if ( s_EngineAPI.MainLoop() ) - { - nRunResult = RUN_RESTART; - } - - // unload systems - eng->Unload(); - - toolframework->ServerShutdown(); -#endif - SV_ShutdownGameDLL(); - } - } - - return nRunResult; -} - -void CModAppSystemGroup::PostShutdown() -{ -} - -void CModAppSystemGroup::Destroy() -{ - // unload game and client .dlls - ServerDLL_Unload(); -#ifndef SWDS - if ( !IsServerOnly() ) - { - ClientDLL_Unload(); - } -#endif -} - -//----------------------------------------------------------------------------- -// -// Purpose: Expose engine interface to launcher for dedicated servers -// -//----------------------------------------------------------------------------- -class CDedicatedServerAPI : public CTier3AppSystem< IDedicatedServerAPI > -{ - typedef CTier3AppSystem< IDedicatedServerAPI > BaseClass; - -public: - CDedicatedServerAPI() : - m_pDedicatedServer( 0 ) - { - } - virtual bool Connect( CreateInterfaceFn factory ); - virtual void Disconnect(); - virtual void *QueryInterface( const char *pInterfaceName ); - - virtual bool ModInit( ModInfo_t &info ); - virtual void ModShutdown( void ); - - virtual bool RunFrame( void ); - - virtual void AddConsoleText( char *text ); - virtual void UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ); - virtual void UpdateHostname(char *pszHostname, int maxlen); - - CModAppSystemGroup *m_pDedicatedServer; -}; - -//----------------------------------------------------------------------------- -// Singleton -//----------------------------------------------------------------------------- -EXPOSE_SINGLE_INTERFACE( CDedicatedServerAPI, IDedicatedServerAPI, VENGINE_HLDS_API_VERSION ); - -#define LONG_TICK_TIME 0.12f // about 8/66ths of a second -#define MIN_TIME_BETWEEN_DUMPED_TICKS 5.0f; -#define MAX_DUMPS_PER_LONG_TICK 10 -void Sys_Sleep ( int msec ); - -bool g_bLongTickWatcherThreadEnabled = false; -bool g_bQuitLongTickWatcherThread = false; -int g_bTotalDumps = 0; - -DWORD __stdcall LongTickWatcherThread( void *voidPtr ) -{ - int nLastTick = 0; - double flWarnTickTime = 0.0f; - double flNextPossibleDumpTime = Plat_FloatTime() + MIN_TIME_BETWEEN_DUMPED_TICKS; - int nNumDumpsThisTick = 0; - - while ( eng->GetQuitting() == IEngine::QUIT_NOTQUITTING && !g_bQuitLongTickWatcherThread ) - { - if ( sv.m_State == ss_active && sv.m_bSimulatingTicks ) - { - int curTick = sv.m_nTickCount; - double curTime = Plat_FloatTime(); - if ( nLastTick > 0 && nLastTick == curTick ) - { - if ( curTime > flNextPossibleDumpTime && curTime > flWarnTickTime && nNumDumpsThisTick < MAX_DUMPS_PER_LONG_TICK ) - { - nNumDumpsThisTick++; - g_bTotalDumps++; - Warning( "Long tick after tick %i. Writing minidump #%i (%i total).\n", nLastTick, nNumDumpsThisTick, g_bTotalDumps ); - - if ( nNumDumpsThisTick == MAX_DUMPS_PER_LONG_TICK ) - { - Msg( "Not writing any more minidumps for this tick.\n" ); - } - - // If you're debugging a minidump and you ended up here, you probably want to switch to the main thread. - WriteMiniDump( "longtick" ); - } - } - - if ( nLastTick != curTick ) - { - if ( nNumDumpsThisTick ) - { - Msg( "Long tick lasted about %.1f seconds.\n", curTime - (flWarnTickTime - LONG_TICK_TIME) ); - nNumDumpsThisTick = 0; - flNextPossibleDumpTime = curTime + MIN_TIME_BETWEEN_DUMPED_TICKS; - } - - nLastTick = curTick; - flWarnTickTime = curTime + LONG_TICK_TIME; - } - } - else - { - nLastTick = 0; - } - - if ( nNumDumpsThisTick ) - { - // We'll write the next minidump 0.06 seconds from now. - Sys_Sleep( 60 ); - } - else - { - // Check tick progress every 1/100th of a second. - Sys_Sleep( 10 ); - } - } - - g_bLongTickWatcherThreadEnabled = false; - g_bQuitLongTickWatcherThread = false; - - return 0; -} - -bool EnableLongTickWatcher() -{ - bool bRet = false; - if ( !g_bLongTickWatcherThreadEnabled ) - { - g_bQuitLongTickWatcherThread = false; - g_bLongTickWatcherThreadEnabled = true; - - DWORD nThreadID; - VCRHook_CreateThread(NULL, 0, -#ifdef POSIX - (void*) -#endif - LongTickWatcherThread, NULL, 0, (unsigned long int *)&nThreadID ); - - bRet = true; - } - else if ( g_bQuitLongTickWatcherThread ) - { - Msg( "Cannot create a new long tick watcher while waiting for an old one to terminate.\n" ); - } - else - { - Msg( "The long tick watcher thread is already running.\n" ); - } - - return bRet; -} - -//----------------------------------------------------------------------------- -// Dedicated server entrypoint -//----------------------------------------------------------------------------- -bool CDedicatedServerAPI::Connect( CreateInterfaceFn factory ) -{ - if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 ) - { - Plat_SetBenchmarkMode( true ); - } - - if ( CommandLine()->FindParm( "-dumplongticks" ) ) - { - Msg( "-dumplongticks found on command line. Activating long tick watcher thread.\n" ); - EnableLongTickWatcher(); - } - - // Store off the app system factory... - g_AppSystemFactory = factory; - - if ( !BaseClass::Connect( factory ) ) - return false; - - dedicated = ( IDedicatedExports * )factory( VENGINE_DEDICATEDEXPORTS_API_VERSION, NULL ); - if ( !dedicated ) - return false; - - g_pFileSystem = g_pFullFileSystem; - g_pFileSystem->SetWarningFunc( Warning ); - - if ( !Shader_Connect( false ) ) - return false; - - if ( !g_pStudioRender ) - { - Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION ); - return false; - } - - g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL ); - - if ( !g_pDataCache || !g_pPhysics || !g_pMDLCache ) - { - Warning( "Engine wasn't able to acquire required interfaces!\n" ); - return false; - } - - ConnectMDLCacheNotify(); - return true; -} - -void CDedicatedServerAPI::Disconnect() -{ - DisconnectMDLCacheNotify(); - - g_pPhysics = NULL; - - Shader_Disconnect(); - - g_pFileSystem = NULL; - - ConVar_Unregister(); - - dedicated = NULL; - - BaseClass::Disconnect(); - - g_AppSystemFactory = NULL; -} - -//----------------------------------------------------------------------------- -// Query interface -//----------------------------------------------------------------------------- -void *CDedicatedServerAPI::QueryInterface( const char *pInterfaceName ) -{ - // Loading the engine DLL mounts *all* engine interfaces - CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary - return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. -} - -//----------------------------------------------------------------------------- -// Purpose: -// Input : type - 0 == normal, 1 == dedicated server -// *instance - -// *basedir - -// *cmdline - -// launcherFactory - -//----------------------------------------------------------------------------- -bool CDedicatedServerAPI::ModInit( ModInfo_t &info ) -{ - // Setup and write out steam_appid.txt before we launch - bool bDedicated = true; - eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); - - eng->SetQuitting( IEngine::QUIT_NOTQUITTING ); - - // Set up the engineparms_t which contains global information about the mod - host_parms.basedir = const_cast(info.m_pBaseDirectory); - host_parms.mod = const_cast(GetModDirFromPath(info.m_pInitialMod)); - host_parms.game = const_cast(info.m_pInitialGame); - - g_bTextMode = info.m_bTextMode; - - TRACEINIT( COM_InitFilesystem( info.m_pInitialMod ), COM_ShutdownFileSystem() ); - - if ( steamInfo != eSteamInfo_Initialized ) - { - // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find - // their steam.inf, due to mounting SDK search paths. - steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); - Assert( steamInfo == eSteamInfo_Initialized ); - if ( steamInfo != eSteamInfo_Initialized ) - { - Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" ); - } - } - - // set this up as early as possible, if the server isn't going to run pure, stop CRCing bits as we load them - // this happens even before the ConCommand's are processed, but we need to be sure to either CRC every file - // that is loaded, or not bother doing any - // Note that this mirrors g_sv_pure_mode from sv_main.cpp - int pure_mode = 1; // default to on, +sv_pure 0 or -sv_pure 0 will turn it off - if ( CommandLine()->CheckParm("+sv_pure") ) - pure_mode = CommandLine()->ParmValue( "+sv_pure", 1 ); - else if ( CommandLine()->CheckParm("-sv_pure") ) - pure_mode = CommandLine()->ParmValue( "-sv_pure", 1 ); - if ( pure_mode ) - g_pFullFileSystem->EnableWhitelistFileTracking( true, true, CommandLine()->FindParm( "-sv_pure_verify_hashes" ) ? true : false ); - else - g_pFullFileSystem->EnableWhitelistFileTracking( false, false, false ); - - materials->ModInit(); - - // Setup the material system config record, CreateGameWindow depends on it - // (when we're running stand-alone) -#ifndef SWDS - InitMaterialSystemConfig( true ); // !!should this be called standalone or not? -#endif - - // Initialize general game stuff and create the main window - if ( game->Init( NULL ) ) - { - m_pDedicatedServer = new CModAppSystemGroup( true, info.m_pParentAppSystemGroup ); - - // Store off the app system factory... - g_AppSystemFactory = m_pDedicatedServer->GetFactory(); - - m_pDedicatedServer->Run(); - return true; - } - - return false; -} - -void CDedicatedServerAPI::ModShutdown( void ) -{ - if ( m_pDedicatedServer ) - { - delete m_pDedicatedServer; - m_pDedicatedServer = NULL; - } - - g_AppSystemFactory = NULL; - - // Unload GL, Sound, etc. - eng->Unload(); - - // Shut down memory, etc. - game->Shutdown(); - - materials->ModShutdown(); - TRACESHUTDOWN( COM_ShutdownFileSystem() ); -} - -bool CDedicatedServerAPI::RunFrame( void ) -{ - // Bail if someone wants to quit. - if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING ) - { - return false; - } - - // Run engine frame - eng->Frame(); - return true; -} - -void CDedicatedServerAPI::AddConsoleText( char *text ) -{ - Cbuf_AddText( text ); -} - -void CDedicatedServerAPI::UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ) -{ - Host_GetHostInfo( fps, nActive, nMaxPlayers, pszMap, maxlen ); -} - -void CDedicatedServerAPI::UpdateHostname(char *pszHostname, int maxlen) -{ - if ( pszHostname && ( maxlen > 0 ) ) - { - Q_strncpy( pszHostname, sv.GetName(), maxlen ); - } -} - -#ifndef SWDS - -class CGameUIFuncs : public IGameUIFuncs -{ -public: - bool IsKeyDown( const char *keyname, bool& isdown ) - { - isdown = false; - if ( !g_ClientDLL ) - return false; - - return g_ClientDLL->IN_IsKeyDown( keyname, isdown ); - } - - const char *GetBindingForButtonCode( ButtonCode_t code ) - { - return ::Key_BindingForKey( code ); - } - - virtual ButtonCode_t GetButtonCodeForBind( const char *bind ) - { - const char *pKeyName = Key_NameForBinding( bind ); - if ( !pKeyName ) - return KEY_NONE; - return g_pInputSystem->StringToButtonCode( pKeyName ) ; - } - - void GetVideoModes( struct vmode_s **ppListStart, int *pCount ) - { - if ( videomode ) - { - *pCount = videomode->GetModeCount(); - *ppListStart = videomode->GetMode( 0 ); - } - else - { - *pCount = 0; - *ppListStart = NULL; - } - } - - void GetDesktopResolution( int &width, int &height ) - { - int refreshrate; - game->GetDesktopInfo( width, height, refreshrate ); - } - - virtual void SetFriendsID( uint friendsID, const char *friendsName ) - { - cl.SetFriendsID( friendsID, friendsName ); - } - - bool IsConnectedToVACSecureServer() - { - if ( cl.IsConnected() ) - return Steam3Client().BGSSecure(); - return false; - } -}; - -EXPOSE_SINGLE_INTERFACE( CGameUIFuncs, IGameUIFuncs, VENGINE_GAMEUIFUNCS_VERSION ); - -#endif - -CON_COMMAND( dumplongticks, "Enables generating minidumps on long ticks." ) -{ - int enable = atoi( args[1] ); - if ( args.ArgC() == 1 || enable ) - { - if ( EnableLongTickWatcher() ) - { - Msg( "Long tick watcher thread created. Use \"dumplongticks 0\" to disable.\n" ); - } - } - else - { - // disable watcher thread if enabled - if ( g_bLongTickWatcherThreadEnabled && !g_bQuitLongTickWatcherThread ) - { - Msg( "Disabling the long tick watcher.\n" ); - g_bQuitLongTickWatcherThread = true; - } - else - { - Msg( "The long tick watcher is already disabled.\n" ); - } - } -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//===========================================================================// +#define DISABLE_PROTECTED_THINGS + +#if defined( USE_SDL ) +#include "appframework/ilaunchermgr.h" +#endif + +#if defined( _WIN32 ) && !defined( _X360 ) +#include "winlite.h" +#include +#endif + +#if defined( OSX ) +#include +#endif + +#if defined( POSIX ) +#include +#include +#endif + +#include +#include "quakedef.h" +#include "idedicatedexports.h" +#include "engine_launcher_api.h" +#include "ivideomode.h" +#include "common.h" +#include "iregistry.h" +#include "keys.h" +#include "cdll_engine_int.h" +#include "traceinit.h" +#include "iengine.h" +#include "igame.h" +#include "tier0/etwprof.h" +#include "tier0/vcrmode.h" +#include "tier0/icommandline.h" +#include "tier0/minidump.h" +#include "engine_hlds_api.h" +#include "filesystem_engine.h" +#include "cl_main.h" +#include "client.h" +#include "tier3/tier3.h" +#include "MapReslistGenerator.h" +#include "toolframework/itoolframework.h" +#include "sourcevr/isourcevirtualreality.h" +#include "DevShotGenerator.h" +#include "gl_shader.h" +#include "l_studio.h" +#include "IHammer.h" +#include "sys_dll.h" +#include "materialsystem/materialsystem_config.h" +#include "server.h" +#include "video/ivideoservices.h" +#include "datacache/idatacache.h" +#include "vphysics_interface.h" +#include "inputsystem/iinputsystem.h" +#include "appframework/IAppSystemGroup.h" +#include "tier0/systeminformation.h" +#include "host_cmd.h" +#ifdef _WIN32 +#include "VGuiMatSurface/IMatSystemSurface.h" +#endif + +#ifdef GPROFILER +#include "gperftools/profiler.h" +#endif + +// This is here just for legacy support of older .dlls!!! +#include "SoundEmitterSystem/isoundemittersystembase.h" +#include "eiface.h" +#include "tier1/fmtstr.h" +#include "steam/steam_api.h" + +#ifndef SWDS +#include "sys_mainwind.h" +#include "vgui/ISystem.h" +#include "vgui_controls/Controls.h" +#include "IGameUIFuncs.h" +#include "cl_steamauth.h" +#endif // SWDS + +#if defined(_WIN32) +#include +#endif + +#if POSIX +#include +#endif + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#else +#include "xbox/xboxstubs.h" +#endif + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +IDedicatedExports *dedicated = NULL; +extern CreateInterfaceFn g_AppSystemFactory; +IHammer *g_pHammer = NULL; +IPhysics *g_pPhysics = NULL; +ISourceVirtualReality *g_pSourceVR = NULL; +#if defined( USE_SDL ) +ILauncherMgr *g_pLauncherMgr = NULL; +#endif + +#ifndef SWDS +extern CreateInterfaceFn g_ClientFactory; +#endif + +static SteamInfVersionInfo_t g_SteamInfIDVersionInfo; +const SteamInfVersionInfo_t& GetSteamInfIDVersionInfo() +{ + Assert( g_SteamInfIDVersionInfo.AppID != k_uAppIdInvalid ); + return g_SteamInfIDVersionInfo; +} + +// don't use this, it sucks +int build_number( void ) +{ + return GetSteamInfIDVersionInfo().ServerVersion; +} + +//----------------------------------------------------------------------------- +// Forward declarations +//----------------------------------------------------------------------------- +void Host_GetHostInfo(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ); +const char *Key_BindingForKey( int keynum ); +void COM_ShutdownFileSystem( void ); +void COM_InitFilesystem( const char *pFullModPath ); +void Host_ReadPreStartupConfiguration(); + +//----------------------------------------------------------------------------- +// ConVars and console commands +//----------------------------------------------------------------------------- +#ifndef SWDS +//----------------------------------------------------------------------------- +// Purpose: exports an interface that can be used by the launcher to run the engine +// this is the exported function when compiled as a blob +//----------------------------------------------------------------------------- +void EXPORT F( IEngineAPI **api ) +{ + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary to prevent the LTCG compiler from crashing. + *api = ( IEngineAPI * )(factory(VENGINE_LAUNCHER_API_VERSION, NULL)); +} +#endif // SWDS + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void ClearIOStates( void ) +{ +#ifndef SWDS + if ( g_ClientDLL ) + { + g_ClientDLL->IN_ClearStates(); + } +#endif +} + +//----------------------------------------------------------------------------- +// The SDK launches the game with the full path to gameinfo.txt, so we need +// to strip off the path. +//----------------------------------------------------------------------------- +const char *GetModDirFromPath( const char *pszPath ) +{ + char *pszSlash = Q_strrchr( pszPath, '\\' ); + if ( pszSlash ) + { + return pszSlash + 1; + } + else if ( ( pszSlash = Q_strrchr( pszPath, '/' ) ) != NULL ) + { + return pszSlash + 1; + } + + // Must just be a mod directory already. + return pszPath; +} + +//----------------------------------------------------------------------------- +// Purpose: Main entry +//----------------------------------------------------------------------------- +#ifndef SWDS +#include "gl_matsysiface.h" +#endif + +//----------------------------------------------------------------------------- +// Inner loop: initialize, shutdown main systems, load steam to +//----------------------------------------------------------------------------- +class CModAppSystemGroup : public CAppSystemGroup +{ + typedef CAppSystemGroup BaseClass; +public: + // constructor + CModAppSystemGroup( bool bServerOnly, CAppSystemGroup *pParentAppSystem = NULL ) + : BaseClass( pParentAppSystem ), + m_bServerOnly( bServerOnly ) + { + } + + CreateInterfaceFn GetFactory() + { + return CAppSystemGroup::GetFactory(); + } + + // Methods of IApplication + virtual bool Create(); + virtual bool PreInit(); + virtual int Main(); + virtual void PostShutdown(); + virtual void Destroy(); + +private: + + bool IsServerOnly() const + { + return m_bServerOnly; + } + bool ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName ); + + bool AddLegacySystems(); + bool m_bServerOnly; +}; + +#if defined( STAGING_ONLY ) +CON_COMMAND( bigalloc, "huge alloc crash" ) +{ + Msg( "pre-crash %d\n", MemAlloc_MemoryAllocFailed() ); + // Alloc a bit less than UINT_MAX so there is room for heap headers in the malloc functions. + void *buf = malloc( UINT_MAX - 0x4000 ); + Msg( "post-alloc %d. buf: %p\n", MemAlloc_MemoryAllocFailed(), buf ); + *(int *)buf = 0; +} +#endif + +extern void S_ClearBuffer(); +extern char g_minidumpinfo[ 4096 ]; +extern PAGED_POOL_INFO_t g_pagedpoolinfo; +extern bool g_bUpdateMinidumpComment; +void GetSpew( char *buf, size_t buflen ); + +extern int gHostSpawnCount; +extern int g_nMapLoadCount; +extern int g_HostServerAbortCount; +extern int g_HostErrorCount; +extern int g_HostEndDemo; + +// Turn this to 1 to allow for expanded spew in minidump comments. +static ConVar sys_minidumpexpandedspew( "sys_minidumpexpandedspew", "1" ); + +#ifdef IS_WINDOWS_PC + +extern "C" void __cdecl FailSafe( unsigned int uStructuredExceptionCode, struct _EXCEPTION_POINTERS * pExceptionInfo ) +{ + // Nothing, this just catches a crash when creating the comment block +} + +#endif + +#if defined( POSIX ) + +static sigjmp_buf g_mark; +static void posix_signal_handler( int i ) +{ + siglongjmp( g_mark, -1 ); +} + +#define DO_TRY if ( sigsetjmp( g_mark, 1 ) == 0 ) +#define DO_CATCH else + +#if defined( OSX ) +#define __sighandler_t sig_t +#endif + +#else + +#define DO_TRY try +#define DO_CATCH catch ( ... ) + +#endif // POSIX + +//----------------------------------------------------------------------------- +// Purpose: Check whether any mods are loaded. +// Currently looks for metamod and sourcemod. +//----------------------------------------------------------------------------- +static bool IsSourceModLoaded() +{ +#if defined( _WIN32 ) + static const char *s_pFileNames[] = { "metamod.2.tf2.dll", "sourcemod.2.tf2.dll", "sdkhooks.ext.2.ep2v.dll", "sdkhooks.ext.2.tf2.dll" }; + + for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ ) + { + // GetModuleHandle function returns a handle to a mapped module + // without incrementing its reference count. + if ( GetModuleHandleA( s_pFileNames[ i ] ) ) + return true; + } +#else + FILE *fh = fopen( "/proc/self/maps", "r" ); + + if ( fh ) + { + char buf[ 1024 ]; + static const char *s_pFileNames[] = { "metamod.2.tf2.so", "sourcemod.2.tf2.so", "sdkhooks.ext.2.ep2v.so", "sdkhooks.ext.2.tf2.so" }; + + while ( fgets( buf, sizeof( buf ), fh ) ) + { + for ( size_t i = 0; i < Q_ARRAYSIZE( s_pFileNames ); i++ ) + { + if ( strstr( buf, s_pFileNames[ i ] ) ) + { + fclose( fh ); + return true; + } + } + } + + fclose( fh ); + } +#endif + + return false; +} + +template< int _SIZE > +class CErrorText +{ +public: + CErrorText() : m_bIsDedicatedServer( false ) {} + ~CErrorText() {} + + void Steam_SetMiniDumpComment() + { +#if !defined( NO_STEAM ) + SteamAPI_SetMiniDumpComment( m_errorText ); +#endif + } + + void CommentCat( const char * str ) + { + V_strcat_safe( m_errorText, str ); + } + + void CommentPrintf( const char *fmt, ... ) + { + va_list args; + va_start( args, fmt ); + + size_t len = strlen( m_errorText ); + vsnprintf( m_errorText + len, sizeof( m_errorText ) - len - 1, fmt, args ); + m_errorText[ sizeof( m_errorText ) - 1 ] = 0; + + va_end( args ); + } + + void BuildComment( char const *pchSysErrorText, bool bRealCrash ) + { + // Try and detect whether this + bool bSourceModLoaded = false; + if ( m_bIsDedicatedServer ) + { + bSourceModLoaded = IsSourceModLoaded(); + if ( bSourceModLoaded ) + { + AppId_t AppId = GetSteamInfIDVersionInfo().ServerAppID; + // Bump up the number and report the crash. This should be something + // like 232251 (instead of 232250). 232251 is for the TF2 Windows client, + // but we actually report those crashes under ID 440, so this should be ok. + SteamAPI_SetBreakpadAppID( AppId + 1 ); + } + } + +#ifdef IS_WINDOWS_PC + // This warning only applies if you want to catch structured exceptions (crashes) + // using C++ exceptions. We do not want to do that so we can build with C++ exceptions + // completely disabled, and just suppress this warning. + // warning C4535: calling _set_se_translator() requires /EHa + #pragma warning( suppress : 4535 ) + _se_translator_function curfilter = _set_se_translator( &FailSafe ); +#elif defined( POSIX ) + // Only need to worry about this function crashing when we're dealing with a real crash. + __sighandler_t curfilter = bRealCrash ? signal( SIGSEGV, posix_signal_handler ) : 0; +#endif + + DO_TRY + { + Q_memset( m_errorText, 0x00, sizeof( m_errorText ) ); + + if ( pchSysErrorText ) + { + CommentCat( "Sys_Error( " ); + CommentCat( pchSysErrorText ); + + // Trim trailing \n. + int len = V_strlen( m_errorText ); + if ( len > 0 && m_errorText[ len - 1 ] == '\n' ) + m_errorText[ len - 1 ] = 0; + + CommentCat( " )\n" ); + } + else + { + CommentCat( "Crash\n" ); + } + CommentPrintf( "Uptime( %f )\n", Plat_FloatTime() ); + CommentPrintf( "SourceMod:%d,DS:%d,Crash:%d\n\n", bSourceModLoaded, m_bIsDedicatedServer, bRealCrash ); + + // Add g_minidumpinfo from CL_SetSteamCrashComment(). + CommentCat( g_minidumpinfo ); + + // Latch in case extended stuff below crashes + Steam_SetMiniDumpComment(); + + // Add Memory Status + BuildCommentMemStatus(); + + // Spew out paged pool stuff, etc. + PAGED_POOL_INFO_t ppi_info; + if ( Plat_GetPagedPoolInfo( &ppi_info ) != SYSCALL_UNSUPPORTED ) + { + CommentPrintf( "\nPaged Pool\nprev PP PAGES: used: %lu, free %lu\nfinal PP PAGES: used: %lu, free %lu\n", + g_pagedpoolinfo.numPagesUsed, g_pagedpoolinfo.numPagesFree, + ppi_info.numPagesUsed, ppi_info.numPagesFree ); + } + + CommentPrintf( "memallocfail? = %u\nActive: %s\nSpawnCount %d MapLoad Count %d\nError count %d, end demo %d, abort count %d\n", + MemAlloc_MemoryAllocFailed(), + ( game && game->IsActiveApp() ) ? "active" : "inactive", + gHostSpawnCount, + g_nMapLoadCount, + g_HostErrorCount, + g_HostEndDemo, + g_HostServerAbortCount ); + + // Latch in case extended stuff below crashes + Steam_SetMiniDumpComment(); + + // Add user comment strings. 4096 is just a large sanity number we should + // never ever reach (currently our minidump supports 32 of these.) + for( int i = 0; i < 4096; i++ ) + { + const char *pUserStreamInfo = MinidumpUserStreamInfoGet( i ); + if( !pUserStreamInfo ) + break; + + if ( pUserStreamInfo[ 0 ] ) + CommentPrintf( "%s", pUserStreamInfo ); + } + + bool bExtendedSpew = sys_minidumpexpandedspew.GetBool(); + if ( bExtendedSpew ) + { + BuildCommentExtended(); + Steam_SetMiniDumpComment(); + +#if defined( LINUX ) + if ( bRealCrash ) + { + // bRealCrash is set when we're actually making a comment for a dump or error. + AddFileToComment( "/proc/meminfo" ); + AddFileToComment( "/proc/self/status" ); + Steam_SetMiniDumpComment(); + + // Useful, but really big, so disable for now. + //$ AddFileToComment( "/proc/self/maps" ); + } +#endif + } + } + DO_CATCH + { + // Oh oh + } + +#ifdef IS_WINDOWS_PC + _set_se_translator( curfilter ); +#elif defined( POSIX ) + if ( bRealCrash ) + signal( SIGSEGV, curfilter ); +#endif + } + + void BuildCommentMemStatus() + { +#ifdef _WIN32 + const double MbDiv = 1024.0 * 1024.0; + + MEMORYSTATUSEX memStat; + ZeroMemory( &memStat, sizeof( MEMORYSTATUSEX ) ); + memStat.dwLength = sizeof( MEMORYSTATUSEX ); + + if ( GlobalMemoryStatusEx( &memStat ) ) + { + CommentPrintf( "\nMemory\nmemusage( %d %% )\ntotalPhysical Mb(%.2f)\nfreePhysical Mb(%.2f)\ntotalPaging Mb(%.2f)\nfreePaging Mb(%.2f)\ntotalVirtualMem Mb(%.2f)\nfreeVirtualMem Mb(%.2f)\nextendedVirtualFree Mb(%.2f)\n", + memStat.dwMemoryLoad, + (double)memStat.ullTotalPhys / MbDiv, + (double)memStat.ullAvailPhys / MbDiv, + (double)memStat.ullTotalPageFile / MbDiv, + (double)memStat.ullAvailPageFile / MbDiv, + (double)memStat.ullTotalVirtual / MbDiv, + (double)memStat.ullAvailVirtual / MbDiv, + (double)memStat.ullAvailExtendedVirtual / MbDiv); + } + + HINSTANCE hInst = LoadLibrary( "Psapi.dll" ); + if ( hInst ) + { + typedef BOOL (WINAPI *GetProcessMemoryInfoFn)(HANDLE, PPROCESS_MEMORY_COUNTERS, DWORD); + GetProcessMemoryInfoFn fn = (GetProcessMemoryInfoFn)GetProcAddress( hInst, "GetProcessMemoryInfo" ); + if ( fn ) + { + PROCESS_MEMORY_COUNTERS counters; + + ZeroMemory( &counters, sizeof( PROCESS_MEMORY_COUNTERS ) ); + counters.cb = sizeof( PROCESS_MEMORY_COUNTERS ); + + if ( fn( GetCurrentProcess(), &counters, sizeof( PROCESS_MEMORY_COUNTERS ) ) ) + { + CommentPrintf( "\nProcess Memory\nWorkingSetSize Mb(%.2f)\nQuotaPagedPoolUsage Mb(%.2f)\nQuotaNonPagedPoolUsage: Mb(%.2f)\nPagefileUsage: Mb(%.2f)\n", + (double)counters.WorkingSetSize / MbDiv, + (double)counters.QuotaPagedPoolUsage / MbDiv, + (double)counters.QuotaNonPagedPoolUsage / MbDiv, + (double)counters.PagefileUsage / MbDiv ); + } + } + + FreeLibrary( hInst ); + } + +#elif defined( OSX ) + + static const struct + { + int ctl; + const char *name; + } s_ctl_names[] = + { + #define _XTAG( _x ) { _x, #_x } + _XTAG( HW_PHYSMEM ), + _XTAG( HW_USERMEM ), + _XTAG( HW_MEMSIZE ), + _XTAG( HW_AVAILCPU ), + #undef _XTAG + }; + + for ( size_t i = 0; i < Q_ARRAYSIZE( s_ctl_names ); i++ ) + { + uint64_t val = 0; + size_t len = sizeof( val ); + int mib[] = { CTL_HW, s_ctl_names[ i ].ctl }; + + if ( sysctl( mib, Q_ARRAYSIZE( mib ), &val, &len, NULL, 0 ) == 0 ) + { + CommentPrintf( " %s: %" PRIu64 "\n", s_ctl_names[ i ].name, val ); + } + } + +#endif + } + + void BuildCommentExtended() + { + try + { + CommentCat( "\nConVars (non-default)\n\n" ); + CommentPrintf( "%s %s %s\n", "var", "value", "default" ); + + for ( const ConCommandBase *var = g_pCVar->GetCommands() ; var ; var = var->GetNext()) + { + if ( var->IsCommand() ) + continue; + + ConVar *pCvar = ( ConVar * )var; + if ( pCvar->IsFlagSet( FCVAR_SERVER_CANNOT_QUERY | FCVAR_PROTECTED ) ) + continue; + + if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) ) + { + char var1[ MAX_OSPATH ]; + char var2[ MAX_OSPATH ]; + + Q_strncpy( var1, Host_CleanupConVarStringValue( pCvar->GetString() ), sizeof( var1 ) ); + Q_strncpy( var2, Host_CleanupConVarStringValue( pCvar->GetDefault() ), sizeof( var2 ) ); + + if ( !Q_stricmp( var1, var2 ) ) + continue; + } + else + { + if ( pCvar->GetFloat() == Q_atof( pCvar->GetDefault() ) ) + continue; + } + + if ( !(pCvar->IsFlagSet( FCVAR_NEVER_AS_STRING ) ) ) + CommentPrintf( "%s '%s' '%s'\n", pCvar->GetName(), Host_CleanupConVarStringValue( pCvar->GetString() ), pCvar->GetDefault() ); + else + CommentPrintf( "%s '%f' '%f'\n", pCvar->GetName(), pCvar->GetFloat(), Q_atof( pCvar->GetDefault() ) ); + } + + CommentCat( "\nConsole History (reversed)\n\n" ); + + // Get console + int len = V_strlen( m_errorText ); + if ( len < sizeof( m_errorText ) ) + { + GetSpew( m_errorText + len, sizeof( m_errorText ) - len - 1 ); + m_errorText[ sizeof( m_errorText ) - 1 ] = 0; + } + } + catch ( ... ) + { + CommentCat( "Exception thrown building console/convar history.\n" ); + } + } + +#if defined( LINUX ) + + void AddFileToComment( const char *filename ) + { + CommentPrintf( "\n%s:\n", filename ); + + int nStart = Q_strlen( m_errorText ); + int nMaxLen = sizeof( m_errorText ) - nStart - 1; + + if ( nMaxLen > 0 ) + { + FILE *fh = fopen( filename, "r" ); + + if ( fh ) + { + size_t ret = fread( m_errorText + nStart, 1, nMaxLen, fh ); + fclose( fh ); + + // Replace tab characters with spaces. + for ( size_t i = 0; i < ret; i++ ) + { + if ( m_errorText[ nStart + i ] == '\t' ) + m_errorText[ nStart + i ] = ' '; + } + } + + // Entire buffer should have been zeroed out, but just super sure... + m_errorText[ sizeof( m_errorText ) - 1 ] = 0; + } + } + +#endif // LINUX + +public: + char m_errorText[ _SIZE ]; + bool m_bIsDedicatedServer; +}; + +#if defined( _X360 ) +static CErrorText<3500> errorText; +#else +static CErrorText<95000> errorText; +#endif + +void BuildMinidumpComment( char const *pchSysErrorText, bool bRealCrash ) +{ +#if !defined(NO_STEAM) + /* + // Uncomment this code if you are testing max minidump comment length issues + // It allows you to asked for a dummy comment of a certain length + int nCommentLength = CommandLine()->ParmValue( "-commentlen", 0 ); + if ( nCommentLength > 0 ) + { + nCommentLength = MIN( nCommentLength, 128*1024 ); + char *cbuf = new char[ nCommentLength + 1 ]; + for ( int i = 0; i < nCommentLength; ++i ) + { + cbuf[ i ] = (char)('0' + (i % 10)); + } + cbuf[ nCommentLength ] = 0; + SteamAPI_SetMiniDumpComment( cbuf ); + delete[] cbuf; + return; + } + */ + errorText.BuildComment( pchSysErrorText, bRealCrash ); +#endif +} + +#if defined( POSIX ) + +static void PosixPreMinidumpCallback( void *context ) +{ + BuildMinidumpComment( NULL, true ); +} + +#endif + +//----------------------------------------------------------------------------- +// Purpose: Attempt to initialize appid/steam.inf/minidump information. May only return partial information if called +// before Filesystem is ready. +// +// The desire is to be able to call this ASAP to init basic minidump and AppID info, then re-call later on when +// the filesystem is setup, so full version # information and such can be propagated to the minidump system. +// (Currently, SDK mods will generally only have partial information prior to filesystem init) +//----------------------------------------------------------------------------- +// steam.inf keys. +#define VERSION_KEY "PatchVersion=" +#define PRODUCT_KEY "ProductName=" +#define SERVER_VERSION_KEY "ServerVersion=" +#define APPID_KEY "AppID=" +#define SERVER_APPID_KEY "ServerAppID=" +enum eSteamInfoInit +{ + eSteamInfo_Uninitialized, + eSteamInfo_Partial, + eSteamInfo_Initialized +}; +static eSteamInfoInit Sys_TryInitSteamInfo( void *pvAPI, SteamInfVersionInfo_t& VerInfo, const char *pchMod, const char *pchBaseDir, bool bDedicated ) +{ + static eSteamInfoInit initState = eSteamInfo_Uninitialized; + + eSteamInfoInit previousInitState = initState; + + // + // + // Initialize with some defaults. + VerInfo.ClientVersion = 0; + VerInfo.ServerVersion = 0; + V_strcpy_safe( VerInfo.szVersionString, "valve" ); + V_strcpy_safe( VerInfo.szProductString, "1.0.1.0" ); + VerInfo.AppID = k_uAppIdInvalid; + VerInfo.ServerAppID = k_uAppIdInvalid; + + // Filesystem may or may not be up + CUtlBuffer infBuf; + bool bFoundInf = false; + if ( g_pFileSystem ) + { + FileHandle_t fh; + fh = g_pFileSystem->Open( "steam.inf", "rb", "GAME" ); + bFoundInf = fh && g_pFileSystem->ReadToBuffer( fh, infBuf ); + } + + if ( !bFoundInf ) + { + // We may try to load the steam.inf BEFORE we turn on the filesystem, so use raw filesystem API's here. + char szFullPath[ MAX_PATH ] = { 0 }; + char szModSteamInfPath[ MAX_PATH ] = { 0 }; + V_ComposeFileName( pchMod, "steam.inf", szModSteamInfPath, sizeof( szModSteamInfPath ) ); + V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModSteamInfPath, pchBaseDir ); + + // Try opening steam.inf + FILE *fp = fopen( szFullPath, "rb" ); + if ( fp ) + { + // Read steam.inf data. + fseek( fp, 0, SEEK_END ); + size_t bufsize = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + infBuf.EnsureCapacity( bufsize + 1 ); + + size_t iBytesRead = fread( infBuf.Base(), 1, bufsize, fp ); + ((char *)infBuf.Base())[iBytesRead] = 0; + infBuf.SeekPut( CUtlBuffer::SEEK_CURRENT, iBytesRead + 1 ); + fclose( fp ); + + bFoundInf = ( iBytesRead == bufsize ); + } + } + + if ( bFoundInf ) + { + const char *pbuf = (const char*)infBuf.Base(); + while ( 1 ) + { + pbuf = COM_Parse( pbuf ); + if ( !pbuf || !com_token[ 0 ] ) + break; + + if ( !Q_strnicmp( com_token, VERSION_KEY, Q_strlen( VERSION_KEY ) ) ) + { + V_strcpy_safe( VerInfo.szVersionString, com_token + Q_strlen( VERSION_KEY ) ); + VerInfo.ClientVersion = atoi( VerInfo.szVersionString ); + } + else if ( !Q_strnicmp( com_token, PRODUCT_KEY, Q_strlen( PRODUCT_KEY ) ) ) + { + V_strcpy_safe( VerInfo.szProductString, com_token + Q_strlen( PRODUCT_KEY ) ); + } + else if ( !Q_strnicmp( com_token, SERVER_VERSION_KEY, Q_strlen( SERVER_VERSION_KEY ) ) ) + { + VerInfo.ServerVersion = atoi( com_token + Q_strlen( SERVER_VERSION_KEY ) ); + } + else if ( !Q_strnicmp( com_token, APPID_KEY, Q_strlen( APPID_KEY ) ) ) + { + VerInfo.AppID = atoi( com_token + Q_strlen( APPID_KEY ) ); + } + else if ( !Q_strnicmp( com_token, SERVER_APPID_KEY, Q_strlen( SERVER_APPID_KEY ) ) ) + { + VerInfo.ServerAppID = atoi( com_token + Q_strlen( SERVER_APPID_KEY ) ); + } + } + + // If we found a steam.inf we're as good as we're going to get, but don't tell callers we're fully initialized + // if it doesn't at least have an AppID + initState = ( VerInfo.AppID != k_uAppIdInvalid ) ? eSteamInfo_Initialized : eSteamInfo_Partial; + } + else if ( !bDedicated ) + { + // Opening steam.inf failed - try to open gameinfo.txt and read in just SteamAppId from that. + // (gameinfo.txt lacks the dedicated server steamid, so we'll just have to live until filesystem init to setup + // breakpad there when we hit this case) + char szModGameinfoPath[ MAX_PATH ] = { 0 }; + char szFullPath[ MAX_PATH ] = { 0 }; + V_ComposeFileName( pchMod, "gameinfo.txt", szModGameinfoPath, sizeof( szModGameinfoPath ) ); + V_MakeAbsolutePath( szFullPath, sizeof( szFullPath ), szModGameinfoPath, pchBaseDir ); + + // Try opening gameinfo.txt + FILE *fp = fopen( szFullPath, "rb" ); + if( fp ) + { + fseek( fp, 0, SEEK_END ); + size_t bufsize = ftell( fp ); + fseek( fp, 0, SEEK_SET ); + + char *buffer = ( char * )_alloca( bufsize + 1 ); + + size_t iBytesRead = fread( buffer, 1, bufsize, fp ); + buffer[ iBytesRead ] = 0; + fclose( fp ); + + KeyValuesAD pkvGameInfo( "gameinfo" ); + if ( pkvGameInfo->LoadFromBuffer( "gameinfo.txt", buffer ) ) + { + VerInfo.AppID = (AppId_t)pkvGameInfo->GetInt( "FileSystem/SteamAppId", k_uAppIdInvalid ); + } + } + + initState = eSteamInfo_Partial; + } + + // In partial state the ServerAppID might be unknown, but if we found the full steam.inf and it's not set, it shares AppID. + if ( initState == eSteamInfo_Initialized && VerInfo.ServerAppID == k_uAppIdInvalid ) + VerInfo.ServerAppID = VerInfo.AppID; + +#if !defined(_X360) + if ( VerInfo.AppID ) + { + // steamclient.dll doesn't know about steam.inf files in mod folder, + // it accepts a steam_appid.txt in the root directory if the game is + // not started through Steam. So we create one there containing the + // current AppID + FILE *fh = fopen( "steam_appid.txt", "wb" ); + if ( fh ) + { + CFmtStrN< 128 > strAppID( "%u\n", VerInfo.AppID ); + + fwrite( strAppID.Get(), strAppID.Length() + 1, 1, fh ); + fclose( fh ); + } + } +#endif // !_X360 + + // + // Update minidump info if we have more information than before + // + +#ifndef NO_STEAM + // If -nobreakpad was specified or we found metamod or sourcemod, don't register breakpad. + bool bUseBreakpad = !CommandLine()->FindParm( "-nobreakpad" ) && ( !bDedicated || !IsSourceModLoaded() ); + AppId_t BreakpadAppId = bDedicated ? VerInfo.ServerAppID : VerInfo.AppID; + Assert( BreakpadAppId != k_uAppIdInvalid || initState < eSteamInfo_Initialized ); + if ( BreakpadAppId != k_uAppIdInvalid && initState > previousInitState && bUseBreakpad ) + { + void *pvMiniDumpContext = NULL; + PFNPreMinidumpCallback pfnPreMinidumpCallback = NULL; + bool bFullMemoryDump = !bDedicated && IsWindows() && CommandLine()->FindParm( "-full_memory_dumps" ); + +#if defined( POSIX ) + // On Windows we're relying on the try/except to build the minidump comment. On Linux, we don't have that + // so we need to register the minidumpcallback handler here. + pvMiniDumpContext = pvAPI; + pfnPreMinidumpCallback = PosixPreMinidumpCallback; +#endif + + CFmtStrN<128> pchVersion( "%d", build_number() ); + Msg( "Using Breakpad minidump system. Version: %s AppID: %u\n", pchVersion.Get(), BreakpadAppId ); + + // We can filter various crash dumps differently in the Socorro backend code: + // Steam/min/web/crash_reporter/socorro/scripts/config/collectorconfig.py + SteamAPI_SetBreakpadAppID( BreakpadAppId ); + SteamAPI_UseBreakpadCrashHandler( pchVersion, __DATE__, __TIME__, bFullMemoryDump, pvMiniDumpContext, pfnPreMinidumpCallback ); + + // Tell errorText class if this is dedicated server. + errorText.m_bIsDedicatedServer = bDedicated; + } +#endif // NO_STEAM + + MinidumpUserStreamInfoSetHeader( "%sLaunching \"%s\"\n", ( bDedicated ? "DedicatedServerAPI " : "" ), CommandLine()->GetCmdLine() ); + + + return initState; +} + +#ifndef SWDS + +//----------------------------------------------------------------------------- +// +// Main engine interface exposed to launcher +// +//----------------------------------------------------------------------------- +class CEngineAPI : public CTier3AppSystem< IEngineAPI > +{ + typedef CTier3AppSystem< IEngineAPI > BaseClass; + +public: + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + virtual InitReturnVal_t Init(); + virtual void Shutdown(); + + // This function must be called before init + virtual void SetStartupInfo( StartupInfo_t &info ); + + virtual int Run( ); + + // Sets the engine to run in a particular editor window + virtual void SetEngineWindow( void *hWnd ); + + // Posts a console command + virtual void PostConsoleCommand( const char *pConsoleCommand ); + + // Are we running the simulation? + virtual bool IsRunningSimulation( ) const; + + // Start/stop running the simulation + virtual void ActivateSimulation( bool bActive ); + + // Reset the map we're on + virtual void SetMap( const char *pMapName ); + + bool MainLoop(); + + int RunListenServer(); + +private: + + // Hooks a particular mod up to the registry + void SetRegistryMod( const char *pModName ); + + // One-time setup, based on the initially selected mod + // FIXME: This should move into the launcher! + bool OnStartup( void *pInstance, const char *pStartupModName ); + void OnShutdown(); + + // Initialization, shutdown of a mod. + bool ModInit( const char *pModName, const char *pGameDir ); + void ModShutdown(); + + // Initializes, shuts down the registry + bool InitRegistry( const char *pModName ); + void ShutdownRegistry(); + + // Handles there being an error setting up the video mode + InitReturnVal_t HandleSetModeError(); + + // Initializes, shuts down VR + bool InitVR(); + void ShutdownVR(); + + // Purpose: Message pump when running stand-alone + void PumpMessages(); + + // Purpose: Message pump when running with the editor + void PumpMessagesEditMode( bool &bIdle, long &lIdleCount ); + + // Activate/deactivates edit mode shaders + void ActivateEditModeShaders( bool bActive ); + +private: + void *m_hEditorHWnd; + bool m_bRunningSimulation; + bool m_bSupportsVR; + StartupInfo_t m_StartupInfo; +}; + + +//----------------------------------------------------------------------------- +// Singleton interface +//----------------------------------------------------------------------------- +static CEngineAPI s_EngineAPI; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CEngineAPI, IEngineAPI, VENGINE_LAUNCHER_API_VERSION, s_EngineAPI ); + + +//----------------------------------------------------------------------------- +// Connect, disconnect +//----------------------------------------------------------------------------- +bool CEngineAPI::Connect( CreateInterfaceFn factory ) +{ + // Store off the app system factory... + g_AppSystemFactory = factory; + + if ( !BaseClass::Connect( factory ) ) + return false; + + g_pFileSystem = g_pFullFileSystem; + if ( !g_pFileSystem ) + return false; + + g_pFileSystem->SetWarningFunc( Warning ); + + if ( !Shader_Connect( true ) ) + return false; + + g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL ); + + if ( !g_pStudioRender || !g_pDataCache || !g_pPhysics || !g_pMDLCache || !g_pMatSystemSurface || !g_pInputSystem /* || !g_pVideo */ ) + { + Warning( "Engine wasn't able to acquire required interfaces!\n" ); + return false; + } + + if (!g_pStudioRender) + { + Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION ); + return false; + } + + g_pHammer = (IHammer*)factory( INTERFACEVERSION_HAMMER, NULL ); + +#if defined( USE_SDL ) + g_pLauncherMgr = (ILauncherMgr *)factory( SDLMGR_INTERFACE_VERSION, NULL ); +#endif + + ConnectMDLCacheNotify(); + + return true; +} + +void CEngineAPI::Disconnect() +{ + DisconnectMDLCacheNotify(); + +#if !defined( SWDS ) + TRACESHUTDOWN( Steam3Client().Shutdown() ); +#endif + + g_pHammer = NULL; + g_pPhysics = NULL; + + Shader_Disconnect(); + + g_pFileSystem = NULL; + + BaseClass::Disconnect(); + + g_AppSystemFactory = NULL; +} + + +//----------------------------------------------------------------------------- +// Query interface +//----------------------------------------------------------------------------- +void *CEngineAPI::QueryInterface( const char *pInterfaceName ) +{ + // Loading the engine DLL mounts *all* engine interfaces + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary + return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. +} + + +//----------------------------------------------------------------------------- +// Sets startup info +//----------------------------------------------------------------------------- +void CEngineAPI::SetStartupInfo( StartupInfo_t &info ) +{ + // Setup and write out steam_appid.txt before we launch + bool bDedicated = false; // Dedicated comes through CDedicatedServerAPI + eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); + + g_bTextMode = info.m_bTextMode; + + // Set up the engineparms_t which contains global information about the mod + host_parms.basedir = const_cast( info.m_pBaseDirectory ); + + // Copy off all the startup info + m_StartupInfo = info; + +#if !defined( SWDS ) + // turn on the Steam3 API early so we can query app data up front + TRACEINIT( Steam3Client().Activate(), Steam3Client().Shutdown() ); +#endif + + // Needs to be done prior to init material system config + TRACEINIT( COM_InitFilesystem( m_StartupInfo.m_pInitialMod ), COM_ShutdownFileSystem() ); + + if ( steamInfo != eSteamInfo_Initialized ) + { + // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find + // their steam.inf, due to mounting SDK search paths. + steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); + Assert( steamInfo == eSteamInfo_Initialized ); + if ( steamInfo != eSteamInfo_Initialized ) + { + Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" ); + } + } + + m_bSupportsVR = false; + if ( IsPC() ) + { + KeyValues *modinfo = new KeyValues("ModInfo"); + if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) ) + { + // Enable file tracking - client always does this in case it connects to a pure server. + // server only does this if sv_pure is set + // If it's not singleplayer_only + if ( V_stricmp( modinfo->GetString("type", "singleplayer_only"), "singleplayer_only") == 0 ) + { + DevMsg( "Disabling whitelist file tracking in filesystem...\n" ); + g_pFileSystem->EnableWhitelistFileTracking( false, false, false ); + } + else + { + DevMsg( "Enabling whitelist file tracking in filesystem...\n" ); + g_pFileSystem->EnableWhitelistFileTracking( true, false, false ); + } + + m_bSupportsVR = modinfo->GetInt( "supportsvr" ) > 0 && CommandLine()->CheckParm( "-vr" ); + if ( m_bSupportsVR ) + { + // This also has to happen before CreateGameWindow to know where to put + // the window and how big to make it + if ( InitVR() ) + { + if ( Steam3Client().SteamUtils() ) + { + if ( Steam3Client().SteamUtils()->IsSteamRunningInVR() && g_pSourceVR->IsHmdConnected() ) + { + int nForceVRAdapterIndex = g_pSourceVR->GetVRModeAdapter(); + materials->SetAdapter( nForceVRAdapterIndex, 0 ); + + g_pSourceVR->SetShouldForceVRMode(); + } + } + } + } + + } + modinfo->deleteThis(); + } +} + + +//----------------------------------------------------------------------------- +// Init, shutdown +//----------------------------------------------------------------------------- +InitReturnVal_t CEngineAPI::Init() +{ + if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 ) + { + Plat_SetBenchmarkMode( true ); + } + + InitReturnVal_t nRetVal = BaseClass::Init(); + if ( nRetVal != INIT_OK ) + return nRetVal; + + m_bRunningSimulation = false; + + // Initialize the FPU control word +#if defined(WIN32) && !defined( SWDS ) && !defined( _X360 ) + _asm + { + fninit + } +#endif + + SetupFPUControlWord(); + + // This creates the videomode singleton object, it doesn't depend on the registry + VideoMode_Create(); + + // Initialize the editor hwnd to render into + m_hEditorHWnd = NULL; + + // One-time setup + // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher + // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown + if ( !OnStartup( m_StartupInfo.m_pInstance, m_StartupInfo.m_pInitialMod ) ) + { + return HandleSetModeError(); + } + + return INIT_OK; +} + +void CEngineAPI::Shutdown() +{ + VideoMode_Destroy(); + BaseClass::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Sets the engine to run in a particular editor window +//----------------------------------------------------------------------------- +void CEngineAPI::SetEngineWindow( void *hWnd ) +{ + if ( !InEditMode() ) + return; + + // Detach input from the previous editor window + game->InputDetachFromGameWindow(); + + m_hEditorHWnd = hWnd; + videomode->SetGameWindow( m_hEditorHWnd ); +} + + +//----------------------------------------------------------------------------- +// Posts a console command +//----------------------------------------------------------------------------- +void CEngineAPI::PostConsoleCommand( const char *pCommand ) +{ + Cbuf_AddText( pCommand ); +} + + +//----------------------------------------------------------------------------- +// Is the engine currently rinning? +//----------------------------------------------------------------------------- +bool CEngineAPI::IsRunningSimulation() const +{ + return (eng->GetState() == IEngine::DLL_ACTIVE); +} + + +//----------------------------------------------------------------------------- +// Reset the map we're on +//----------------------------------------------------------------------------- +void CEngineAPI::SetMap( const char *pMapName ) +{ +// if ( !Q_stricmp( sv.mapname, pMapName ) ) +// return; + + char buf[MAX_PATH]; + Q_snprintf( buf, MAX_PATH, "map %s", pMapName ); + Cbuf_AddText( buf ); +} + + +//----------------------------------------------------------------------------- +// Start/stop running the simulation +//----------------------------------------------------------------------------- +void CEngineAPI::ActivateSimulation( bool bActive ) +{ + // FIXME: Not sure what will happen in this case + if ( ( eng->GetState() != IEngine::DLL_ACTIVE ) && + ( eng->GetState() != IEngine::DLL_PAUSED ) ) + { + return; + } + + bool bCurrentlyActive = (eng->GetState() != IEngine::DLL_PAUSED); + if ( bActive == bCurrentlyActive ) + return; + + // FIXME: Should attachment/detachment be part of the state machine in IEngine? + if ( !bActive ) + { + eng->SetNextState( IEngine::DLL_PAUSED ); + + // Detach input from the previous editor window + game->InputDetachFromGameWindow(); + } + else + { + eng->SetNextState( IEngine::DLL_ACTIVE ); + + // Start accepting input from the new window + // FIXME: What if the attachment fails? + game->InputAttachToGameWindow(); + } +} + +static void MoveConsoleWindowToFront() +{ +#ifdef _WIN32 + // Move the window to the front. + HINSTANCE hInst = LoadLibrary( "kernel32.dll" ); + if ( hInst ) + { + typedef HWND (*GetConsoleWindowFn)(); + GetConsoleWindowFn fn = (GetConsoleWindowFn)GetProcAddress( hInst, "GetConsoleWindow" ); + if ( fn ) + { + HWND hwnd = fn(); + ShowWindow( hwnd, SW_SHOW ); + UpdateWindow( hwnd ); + SetWindowPos( hwnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW ); + } + FreeLibrary( hInst ); + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Message pump when running stand-alone +//----------------------------------------------------------------------------- +void CEngineAPI::PumpMessages() +{ + // This message pumping happens in SDL if SDL is enabled. +#if defined( PLATFORM_WINDOWS ) && !defined( USE_SDL ) + MSG msg; + while ( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) ) + { + TranslateMessage( &msg ); + DispatchMessage( &msg ); + } +#endif + +#if defined( USE_SDL ) + g_pLauncherMgr->PumpWindowsMessageLoop(); +#endif + + // Get input from attached devices + g_pInputSystem->PollInputState(); + + if ( IsX360() ) + { + // handle Xbox system messages + XBX_ProcessEvents(); + } + + // NOTE: Under some implementations of Win9x, + // dispatching messages can cause the FPU control word to change + if ( IsPC() ) + { + SetupFPUControlWord(); + } + + game->DispatchAllStoredGameMessages(); + + if ( IsPC() ) + { + static bool s_bFirstRun = true; + if ( s_bFirstRun ) + { + s_bFirstRun = false; + MoveConsoleWindowToFront(); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Message pump when running stand-alone +//----------------------------------------------------------------------------- +void CEngineAPI::PumpMessagesEditMode( bool &bIdle, long &lIdleCount ) +{ + + if ( bIdle && !g_pHammer->HammerOnIdle( lIdleCount++ ) ) + { + bIdle = false; + } + + // Get input from attached devices + g_pInputSystem->PollInputState(); + +#ifdef WIN32 + MSG msg; + while ( PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) ) + { + if ( msg.message == WM_QUIT ) + { + eng->SetQuitting( IEngine::QUIT_TODESKTOP ); + break; + } + + if ( !g_pHammer->HammerPreTranslateMessage(&msg) ) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Reset idle state after pumping idle message. + if ( g_pHammer->HammerIsIdleMessage(&msg) ) + { + bIdle = true; + lIdleCount = 0; + } + } +#elif defined( USE_SDL ) + Error( "Not supported" ); +#else +#error +#endif + + + // NOTE: Under some implementations of Win9x, + // dispatching messages can cause the FPU control word to change + SetupFPUControlWord(); + + game->DispatchAllStoredGameMessages(); +} + +//----------------------------------------------------------------------------- +// Activate/deactivates edit mode shaders +//----------------------------------------------------------------------------- +void CEngineAPI::ActivateEditModeShaders( bool bActive ) +{ + if ( InEditMode() && ( g_pMaterialSystemConfig->bEditMode != bActive ) ) + { + MaterialSystem_Config_t config = *g_pMaterialSystemConfig; + config.bEditMode = bActive; + OverrideMaterialSystemConfig( config ); + } +} + + +#ifdef GPROFILER +static bool g_gprofiling = false; + +CON_COMMAND( gprofilerstart, "Starts the gperftools profiler recording to the specified file." ) +{ + if ( g_gprofiling ) + { + Msg( "Profiling is already started.\n" ); + return; + } + + char buffer[500]; + const char* profname = buffer; + if ( args.ArgC() < 2 ) + { + static const char *s_pszHomeDir = getenv("HOME"); + if ( !s_pszHomeDir ) + { + Msg( "Syntax: gprofile \n" ); + return; + } + + // Use the current date and time to create a unique file name.time_t t = time(NULL); + time_t t = time(NULL); + struct tm tm = *localtime(&t); + + V_sprintf_safe( buffer, "%s/valveprofile_%4d_%02d_%02d_%02d.%02d.%02d.prof", s_pszHomeDir, + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec ); + // profname already points to buffer. + } + else + { + profname = args[1]; + } + + int result = ProfilerStart( profname ); + if ( result ) + { + Msg( "Profiling started successfully. Recording to %s. Stop profiling with gprofilerstop.\n", profname ); + g_gprofiling = true; + } + else + { + Msg( "Profiling to %s failed to start - errno = %d.\n", profname, errno ); + } +} + +CON_COMMAND( gprofilerstop, "Stops the gperftools profiler." ) +{ + if ( g_gprofiling ) + { + ProfilerStop(); + Msg( "Stopped profiling.\n" ); + g_gprofiling = false; + } +} +#endif + + +void StopGProfiler() +{ +#ifdef GPROFILER + gprofilerstop( CCommand() ); +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Message pump +//----------------------------------------------------------------------------- +bool CEngineAPI::MainLoop() +{ + bool bIdle = true; + long lIdleCount = 0; + + // Main message pump + while ( true ) + { + // Pump messages unless someone wants to quit + if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING ) + { + // We have to explicitly stop the profiler since otherwise symbol + // resolution doesn't work correctly. + StopGProfiler(); + if ( eng->GetQuitting() != IEngine::QUIT_TODESKTOP ) + return true; + return false; + } + + // Pump the message loop + if ( !InEditMode() ) + { + PumpMessages(); + } + else + { + PumpMessagesEditMode( bIdle, lIdleCount ); + } + + // Run engine frame + hammer frame + if ( !InEditMode() || m_hEditorHWnd ) + { + VCRSyncToken( "Frame" ); + + // Deactivate edit mode shaders + ActivateEditModeShaders( false ); + + eng->Frame(); + + // Reactivate edit mode shaders (in Edit mode only...) + ActivateEditModeShaders( true ); + } + + if ( InEditMode() ) + { + g_pHammer->RunFrame(); + } + } + + return false; +} + + +//----------------------------------------------------------------------------- +// Initializes, shuts down the registry +//----------------------------------------------------------------------------- +bool CEngineAPI::InitRegistry( const char *pModName ) +{ + if ( IsPC() ) + { + char szRegSubPath[MAX_PATH]; + Q_snprintf( szRegSubPath, sizeof(szRegSubPath), "%s\\%s", "Source", pModName ); + return registry->Init( szRegSubPath ); + } + return true; +} + +void CEngineAPI::ShutdownRegistry( ) +{ + if ( IsPC() ) + { + registry->Shutdown( ); + } +} + + +//----------------------------------------------------------------------------- +// Initializes, shuts down VR (via sourcevr.dll) +//----------------------------------------------------------------------------- +bool CEngineAPI::InitVR() +{ + if ( m_bSupportsVR ) + { + g_pSourceVR = (ISourceVirtualReality *)g_AppSystemFactory( SOURCE_VIRTUAL_REALITY_INTERFACE_VERSION, NULL ); + if ( g_pSourceVR ) + { + // make sure that the sourcevr DLL we loaded is secure. If not, don't + // let this client connect to secure servers. + if ( !Host_AllowLoadModule( "sourcevr" DLL_EXT_STRING, "EXECUTABLE_PATH", false ) ) + { + Warning( "Preventing connections to secure servers because sourcevr.dll is not signed.\n" ); + Host_DisallowSecureServers(); + } + } + } + return true; +} + + +void CEngineAPI::ShutdownVR() +{ +} + + +//----------------------------------------------------------------------------- +// One-time setup, based on the initially selected mod +// FIXME: This should move into the launcher! +//----------------------------------------------------------------------------- +bool CEngineAPI::OnStartup( void *pInstance, const char *pStartupModName ) +{ + // This fixes a bug on certain machines where the input will + // stop coming in for about 1 second when someone hits a key. + // (true means to disable priority boost) +#ifdef WIN32 + if ( IsPC() ) + { + SetThreadPriorityBoost( GetCurrentThread(), true ); + } +#endif + + // FIXME: Turn videomode + game into IAppSystems? + + // Try to create the window + COM_TimestampedLog( "game->Init" ); + + // This has to happen before CreateGameWindow to set up the instance + // for use by the code that creates the window + if ( !game->Init( pInstance ) ) + { + goto onStartupError; + } + + // Try to create the window + COM_TimestampedLog( "videomode->Init" ); + + // This needs to be after Shader_Init and registry->Init + // This way mods can have different default video settings + if ( !videomode->Init( ) ) + { + goto onStartupShutdownGame; + } + + // We need to access the registry to get various settings (specifically, + // InitMaterialSystemConfig requires it). + if ( !InitRegistry( pStartupModName ) ) + { + goto onStartupShutdownVideoMode; + } + + materials->ModInit(); + + // Setup the material system config record, CreateGameWindow depends on it + // (when we're running stand-alone) + InitMaterialSystemConfig( InEditMode() ); + +#if defined( _X360 ) + XBX_NotifyCreateListener( XNOTIFY_SYSTEM|XNOTIFY_LIVE|XNOTIFY_XMP ); +#endif + + ShutdownRegistry(); + return true; + + // Various error conditions +onStartupShutdownVideoMode: + videomode->Shutdown(); + +onStartupShutdownGame: + game->Shutdown(); + +onStartupError: + return false; +} + + +//----------------------------------------------------------------------------- +// One-time shutdown (shuts down stuff set up in OnStartup) +// FIXME: This should move into the launcher! +//----------------------------------------------------------------------------- +void CEngineAPI::OnShutdown() +{ + if ( videomode ) + { + videomode->Shutdown(); + } + + ShutdownVR(); + + // Shut down the game + game->Shutdown(); + + materials->ModShutdown(); + TRACESHUTDOWN( COM_ShutdownFileSystem() ); +} + +static bool IsValveMod( const char *pModName ) +{ + // Figure out if we're running a Valve mod or not. + return ( Q_stricmp( GetCurrentMod(), "cstrike" ) == 0 || + Q_stricmp( GetCurrentMod(), "dod" ) == 0 || + Q_stricmp( GetCurrentMod(), "hl1mp" ) == 0 || + Q_stricmp( GetCurrentMod(), "tf" ) == 0 || + Q_stricmp( GetCurrentMod(), "tf_beta" ) == 0 || + Q_stricmp( GetCurrentMod(), "hl2mp" ) == 0 ); +} + +//----------------------------------------------------------------------------- +// Initialization, shutdown of a mod. +//----------------------------------------------------------------------------- +bool CEngineAPI::ModInit( const char *pModName, const char *pGameDir ) +{ + // Set up the engineparms_t which contains global information about the mod + host_parms.mod = COM_StringCopy( GetModDirFromPath( pModName ) ); + host_parms.game = COM_StringCopy( pGameDir ); + + // By default, restrict server commands in Valve games and don't restrict them in mods. + cl.m_bRestrictServerCommands = IsValveMod( host_parms.mod ); + cl.m_bRestrictClientCommands = cl.m_bRestrictServerCommands; + + // build the registry path we're going to use for this mod + InitRegistry( pModName ); + + // This sets up the game search path, depends on host_parms + TRACEINIT( MapReslistGenerator_Init(), MapReslistGenerator_Shutdown() ); +#if !defined( _X360 ) + TRACEINIT( DevShotGenerator_Init(), DevShotGenerator_Shutdown() ); +#endif + + // Slam cvars based on mod/config.cfg + Host_ReadPreStartupConfiguration(); + + bool bWindowed = g_pMaterialSystemConfig->Windowed(); + if( g_pMaterialSystemConfig->m_nVRModeAdapter != -1 ) + { + // at init time we never want to start up full screen + bWindowed = true; + } + + // Create the game window now that we have a search path + // FIXME: Deal with initial window width + height better + if ( !videomode || !videomode->CreateGameWindow( g_pMaterialSystemConfig->m_VideoMode.m_Width, g_pMaterialSystemConfig->m_VideoMode.m_Height, bWindowed ) ) + { + return false; + } + + return true; +} + +void CEngineAPI::ModShutdown() +{ + COM_StringFree(host_parms.mod); + COM_StringFree(host_parms.game); + + // Stop accepting input from the window + game->InputDetachFromGameWindow(); + +#if !defined( _X360 ) + TRACESHUTDOWN( DevShotGenerator_Shutdown() ); +#endif + TRACESHUTDOWN( MapReslistGenerator_Shutdown() ); + + ShutdownRegistry(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Handles there being an error setting up the video mode +// Output : Returns true on if the engine should restart, false if it should quit +//----------------------------------------------------------------------------- +InitReturnVal_t CEngineAPI::HandleSetModeError() +{ + // show an error, see if the user wants to restart + if ( CommandLine()->FindParm( "-safe" ) ) + { + Sys_MessageBox( "Failed to set video mode.\n\nThis game has a minimum requirement of DirectX 7.0 compatible hardware.\n", "Video mode error", false ); + return INIT_FAILED; + } + + if ( CommandLine()->FindParm( "-autoconfig" ) ) + { + if ( Sys_MessageBox( "Failed to set video mode - falling back to safe mode settings.\n\nGame will now restart with the new video settings.", "Video - safe mode fallback", true )) + { + CommandLine()->AppendParm( "-safe", NULL ); + return (InitReturnVal_t)INIT_RESTART; + } + return INIT_FAILED; + } + + if ( Sys_MessageBox( "Failed to set video mode - resetting to defaults.\n\nGame will now restart with the new video settings.", "Video mode warning", true ) ) + { + CommandLine()->AppendParm( "-autoconfig", NULL ); + return (InitReturnVal_t)INIT_RESTART; + } + + return INIT_FAILED; +} + + +//----------------------------------------------------------------------------- +// Purpose: Main loop for non-dedicated servers +//----------------------------------------------------------------------------- +int CEngineAPI::RunListenServer() +{ + // + // NOTE: Systems set up here should depend on the mod + // Systems which are mod-independent should be set up in the launcher or Init() + // + + // Innocent until proven guilty + int nRunResult = RUN_OK; + + // Happens every time we start up and shut down a mod + if ( ModInit( m_StartupInfo.m_pInitialMod, m_StartupInfo.m_pInitialGame ) ) + { + CModAppSystemGroup modAppSystemGroup( false, m_StartupInfo.m_pParentAppSystemGroup ); + + // Store off the app system factory... + g_AppSystemFactory = modAppSystemGroup.GetFactory(); + + nRunResult = modAppSystemGroup.Run(); + + g_AppSystemFactory = NULL; + + // Shuts down the mod + ModShutdown(); + + // Disconnects from the editor window + videomode->SetGameWindow( NULL ); + } + + // Closes down things that were set up in OnStartup + // FIXME: OnStartup + OnShutdown should be removed + moved into the launcher + // or the launcher code should be merged into the engine into the code in OnStartup/OnShutdown + OnShutdown(); + + return nRunResult; +} + +static void StaticRunListenServer( void *arg ) +{ + *(int *)arg = s_EngineAPI.RunListenServer(); +} + + + +// This function is set as the crash handler for unhandled exceptions and as the minidump +// handler for to be used by all of tier0's crash recording. This function +// adds a game-specific minidump comment and ensures that the SteamAPI function is +// used to save the minidump so that crashes are uploaded. SteamAPI has previously +// been configured to use breakpad by calling SteamAPI_UseBreakpadCrashHandler. +extern "C" void __cdecl WriteSteamMiniDumpWithComment( unsigned int uStructuredExceptionCode, + struct _EXCEPTION_POINTERS * pExceptionInfo, + const char *pszFilenameSuffix ) +{ + // TODO: dynamically set the minidump comment from contextual info about the crash (i.e current VPROF node)? +#if !defined( NO_STEAM ) + + if ( g_bUpdateMinidumpComment ) + { + BuildMinidumpComment( NULL, true ); + } + + SteamAPI_WriteMiniDump( uStructuredExceptionCode, pExceptionInfo, build_number() ); + // Clear DSound Buffers so the sound doesn't loop while the game shuts down + try + { + S_ClearBuffer(); + } + catch ( ... ) + { + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: Main +//----------------------------------------------------------------------------- +int CEngineAPI::Run() +{ + if ( CommandLine()->FindParm( "-insecure" ) || CommandLine()->FindParm( "-textmode" ) ) + { + Host_DisallowSecureServers(); + } + +#ifdef _X360 + return RunListenServer(); // don't handle exceptions on 360 (because if we do then minidumps won't work at all) +#elif defined ( _WIN32 ) + // Ensure that we crash when we do something naughty in a callback + // such as a window proc. Otherwise on a 64-bit OS the crashes will be + // silently swallowed. + EnableCrashingOnCrashes(); + + // Set the default minidump handling function. This is necessary so that Steam + // will upload crashes, with comments. + SetMiniDumpFunction( WriteSteamMiniDumpWithComment ); + + // Catch unhandled crashes. A normal __try/__except block will not work across + // the kernel callback boundary, but this does. To be clear, __try/__except + // and try/catch will usually not catch exceptions in a WindowProc or other + // callback that is called from kernel mode because 64-bit Windows cannot handle + // throwing exceptions across that boundary. See this article for details: + // http://blog.paulbetts.org/index.php/2010/07/20/the-case-of-the-disappearing-onload-exception-user-mode-callback-exceptions-in-x64/ + // Note that the unhandled exception function is not called when running + // under a debugger, but that's fine because in that case we don't care about + // recording minidumps. + // The try/catch block still makes sense because it is a more reliable way + // of catching exceptions that aren't in callbacks. + // The unhandled exception filter will also catch crashes in threads that + // don't have a try/catch or __try/__except block. + bool noMinidumps = CommandLine()->FindParm( "-nominidumps"); + if ( !noMinidumps ) + MinidumpSetUnhandledExceptionFunction( WriteSteamMiniDumpWithComment ); + + if ( !Plat_IsInDebugSession() && !noMinidumps ) + { + int nRetVal = RUN_OK; + CatchAndWriteMiniDumpForVoidPtrFn( StaticRunListenServer, &nRetVal, true ); + return nRetVal; + } + else + { + return RunListenServer(); + } +#else + return RunListenServer(); +#endif +} +#endif // SWDS + +bool g_bUsingLegacyAppSystems = false; + +bool CModAppSystemGroup::AddLegacySystems() +{ + g_bUsingLegacyAppSystems = true; + + AppSystemInfo_t appSystems[] = + { + { "soundemittersystem", SOUNDEMITTERSYSTEM_INTERFACE_VERSION }, + { "", "" } // Required to terminate the list + }; + + if ( !AddSystems( appSystems ) ) + return false; + +#if !defined( DEDICATED ) +// if ( CommandLine()->FindParm( "-tools" ) ) + { + AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING ); + + if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) ) + return false; + } +#endif + + return true; +} + +//----------------------------------------------------------------------------- +// Instantiate all main libraries +//----------------------------------------------------------------------------- +bool CModAppSystemGroup::Create() +{ +#ifndef SWDS + if ( !IsServerOnly() ) +{ + if ( !ClientDLL_Load() ) + return false; +} +#endif + + if ( !ServerDLL_Load( IsServerOnly() ) ) + return false; + + IClientDLLSharedAppSystems *clientSharedSystems = 0; + +#ifndef SWDS + if ( !IsServerOnly() ) + { + clientSharedSystems = ( IClientDLLSharedAppSystems * )g_ClientFactory( CLIENT_DLL_SHARED_APPSYSTEMS, NULL ); + if ( !clientSharedSystems ) + return AddLegacySystems(); + } +#endif + + IServerDLLSharedAppSystems *serverSharedSystems = ( IServerDLLSharedAppSystems * )g_ServerFactory( SERVER_DLL_SHARED_APPSYSTEMS, NULL ); + if ( !serverSharedSystems ) + { + Assert( !"Expected both game and client .dlls to have or not have shared app systems interfaces!!!" ); + return AddLegacySystems(); + } + + // Load game and client .dlls and build list then + CUtlVector< AppSystemInfo_t > systems; + + int i; + int serverCount = serverSharedSystems->Count(); + for ( i = 0 ; i < serverCount; ++i ) + { + const char *dllName = serverSharedSystems->GetDllName( i ); + const char *interfaceName = serverSharedSystems->GetInterfaceName( i ); + + AppSystemInfo_t info; + info.m_pModuleName = dllName; + info.m_pInterfaceName = interfaceName; + + systems.AddToTail( info ); + } + + if ( !IsServerOnly() ) + { + int clientCount = clientSharedSystems->Count(); + for ( i = 0 ; i < clientCount; ++i ) + { + const char *dllName = clientSharedSystems->GetDllName( i ); + const char *interfaceName = clientSharedSystems->GetInterfaceName( i ); + + if ( ModuleAlreadyInList( systems, dllName, interfaceName ) ) + continue; + + AppSystemInfo_t info; + info.m_pModuleName = dllName; + info.m_pInterfaceName = interfaceName; + + systems.AddToTail( info ); + } + } + + AppSystemInfo_t info; + info.m_pModuleName = ""; + info.m_pInterfaceName = ""; + systems.AddToTail( info ); + + if ( !AddSystems( systems.Base() ) ) + return false; + +#if !defined( DEDICATED ) +// if ( CommandLine()->FindParm( "-tools" ) ) + { + AppModule_t toolFrameworkModule = LoadModule( "engine" DLL_EXT_STRING ); + + if ( !AddSystem( toolFrameworkModule, VTOOLFRAMEWORK_INTERFACE_VERSION ) ) + return false; + } +#endif + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: Fixme, we might need to verify if the interface names differ for the client versus the server +// Input : list - +// *moduleName - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModAppSystemGroup::ModuleAlreadyInList( CUtlVector< AppSystemInfo_t >& list, const char *moduleName, const char *interfaceName ) +{ + for ( int i = 0; i < list.Count(); ++i ) + { + if ( !Q_stricmp( list[ i ].m_pModuleName, moduleName ) ) + { + if ( Q_stricmp( list[ i ].m_pInterfaceName, interfaceName ) ) + { + Error( "Game and client .dlls requesting different versions '%s' vs. '%s' from '%s'\n", + list[ i ].m_pInterfaceName, interfaceName, moduleName ); + } + return true; + } + } + + return false; +} + +bool CModAppSystemGroup::PreInit() +{ + return true; +} + +void SV_ShutdownGameDLL(); +int CModAppSystemGroup::Main() +{ + int nRunResult = RUN_OK; + + if ( IsServerOnly() ) + { + // Start up the game engine + if ( eng->Load( true, host_parms.basedir ) ) + { + // If we're using STEAM, pass the map cycle list as resource hints... + // Dedicated server drives frame loop manually + dedicated->RunServer(); + + SV_ShutdownGameDLL(); + } + } + else + { + eng->SetQuitting( IEngine::QUIT_NOTQUITTING ); + + COM_TimestampedLog( "eng->Load" ); + + // Start up the game engine + static const char engineLoadMessage[] = "Calling CEngine::Load"; + int64 nStartTime = ETWBegin( engineLoadMessage ); + if ( eng->Load( false, host_parms.basedir ) ) + { +#if !defined(SWDS) + ETWEnd( engineLoadMessage, nStartTime ); + toolframework->ServerInit( g_ServerFactory ); + + if ( s_EngineAPI.MainLoop() ) + { + nRunResult = RUN_RESTART; + } + + // unload systems + eng->Unload(); + + toolframework->ServerShutdown(); +#endif + SV_ShutdownGameDLL(); + } + } + + return nRunResult; +} + +void CModAppSystemGroup::PostShutdown() +{ +} + +void CModAppSystemGroup::Destroy() +{ + // unload game and client .dlls + ServerDLL_Unload(); +#ifndef SWDS + if ( !IsServerOnly() ) + { + ClientDLL_Unload(); + } +#endif +} + +//----------------------------------------------------------------------------- +// +// Purpose: Expose engine interface to launcher for dedicated servers +// +//----------------------------------------------------------------------------- +class CDedicatedServerAPI : public CTier3AppSystem< IDedicatedServerAPI > +{ + typedef CTier3AppSystem< IDedicatedServerAPI > BaseClass; + +public: + CDedicatedServerAPI() : + m_pDedicatedServer( 0 ) + { + } + virtual bool Connect( CreateInterfaceFn factory ); + virtual void Disconnect(); + virtual void *QueryInterface( const char *pInterfaceName ); + + virtual bool ModInit( ModInfo_t &info ); + virtual void ModShutdown( void ); + + virtual bool RunFrame( void ); + + virtual void AddConsoleText( char *text ); + virtual void UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ); + virtual void UpdateHostname(char *pszHostname, int maxlen); + + CModAppSystemGroup *m_pDedicatedServer; +}; + +//----------------------------------------------------------------------------- +// Singleton +//----------------------------------------------------------------------------- +EXPOSE_SINGLE_INTERFACE( CDedicatedServerAPI, IDedicatedServerAPI, VENGINE_HLDS_API_VERSION ); + +#define LONG_TICK_TIME 0.12f // about 8/66ths of a second +#define MIN_TIME_BETWEEN_DUMPED_TICKS 5.0f; +#define MAX_DUMPS_PER_LONG_TICK 10 +void Sys_Sleep ( int msec ); + +bool g_bLongTickWatcherThreadEnabled = false; +bool g_bQuitLongTickWatcherThread = false; +int g_bTotalDumps = 0; + +DWORD __stdcall LongTickWatcherThread( void *voidPtr ) +{ + int nLastTick = 0; + double flWarnTickTime = 0.0f; + double flNextPossibleDumpTime = Plat_FloatTime() + MIN_TIME_BETWEEN_DUMPED_TICKS; + int nNumDumpsThisTick = 0; + + while ( eng->GetQuitting() == IEngine::QUIT_NOTQUITTING && !g_bQuitLongTickWatcherThread ) + { + if ( sv.m_State == ss_active && sv.m_bSimulatingTicks ) + { + int curTick = sv.m_nTickCount; + double curTime = Plat_FloatTime(); + if ( nLastTick > 0 && nLastTick == curTick ) + { + if ( curTime > flNextPossibleDumpTime && curTime > flWarnTickTime && nNumDumpsThisTick < MAX_DUMPS_PER_LONG_TICK ) + { + nNumDumpsThisTick++; + g_bTotalDumps++; + Warning( "Long tick after tick %i. Writing minidump #%i (%i total).\n", nLastTick, nNumDumpsThisTick, g_bTotalDumps ); + + if ( nNumDumpsThisTick == MAX_DUMPS_PER_LONG_TICK ) + { + Msg( "Not writing any more minidumps for this tick.\n" ); + } + + // If you're debugging a minidump and you ended up here, you probably want to switch to the main thread. + WriteMiniDump( "longtick" ); + } + } + + if ( nLastTick != curTick ) + { + if ( nNumDumpsThisTick ) + { + Msg( "Long tick lasted about %.1f seconds.\n", curTime - (flWarnTickTime - LONG_TICK_TIME) ); + nNumDumpsThisTick = 0; + flNextPossibleDumpTime = curTime + MIN_TIME_BETWEEN_DUMPED_TICKS; + } + + nLastTick = curTick; + flWarnTickTime = curTime + LONG_TICK_TIME; + } + } + else + { + nLastTick = 0; + } + + if ( nNumDumpsThisTick ) + { + // We'll write the next minidump 0.06 seconds from now. + Sys_Sleep( 60 ); + } + else + { + // Check tick progress every 1/100th of a second. + Sys_Sleep( 10 ); + } + } + + g_bLongTickWatcherThreadEnabled = false; + g_bQuitLongTickWatcherThread = false; + + return 0; +} + +bool EnableLongTickWatcher() +{ + bool bRet = false; + if ( !g_bLongTickWatcherThreadEnabled ) + { + g_bQuitLongTickWatcherThread = false; + g_bLongTickWatcherThreadEnabled = true; + + DWORD nThreadID; + VCRHook_CreateThread(NULL, 0, +#ifdef POSIX + (void*) +#endif + LongTickWatcherThread, NULL, 0, (unsigned long int *)&nThreadID ); + + bRet = true; + } + else if ( g_bQuitLongTickWatcherThread ) + { + Msg( "Cannot create a new long tick watcher while waiting for an old one to terminate.\n" ); + } + else + { + Msg( "The long tick watcher thread is already running.\n" ); + } + + return bRet; +} + +//----------------------------------------------------------------------------- +// Dedicated server entrypoint +//----------------------------------------------------------------------------- +bool CDedicatedServerAPI::Connect( CreateInterfaceFn factory ) +{ + if ( CommandLine()->FindParm( "-sv_benchmark" ) != 0 ) + { + Plat_SetBenchmarkMode( true ); + } + + if ( CommandLine()->FindParm( "-dumplongticks" ) ) + { + Msg( "-dumplongticks found on command line. Activating long tick watcher thread.\n" ); + EnableLongTickWatcher(); + } + + // Store off the app system factory... + g_AppSystemFactory = factory; + + if ( !BaseClass::Connect( factory ) ) + return false; + + dedicated = ( IDedicatedExports * )factory( VENGINE_DEDICATEDEXPORTS_API_VERSION, NULL ); + if ( !dedicated ) + return false; + + g_pFileSystem = g_pFullFileSystem; + g_pFileSystem->SetWarningFunc( Warning ); + + if ( !Shader_Connect( false ) ) + return false; + + if ( !g_pStudioRender ) + { + Sys_Error( "Unable to init studio render system version %s\n", STUDIO_RENDER_INTERFACE_VERSION ); + return false; + } + + g_pPhysics = (IPhysics*)factory( VPHYSICS_INTERFACE_VERSION, NULL ); + + if ( !g_pDataCache || !g_pPhysics || !g_pMDLCache ) + { + Warning( "Engine wasn't able to acquire required interfaces!\n" ); + return false; + } + + ConnectMDLCacheNotify(); + return true; +} + +void CDedicatedServerAPI::Disconnect() +{ + DisconnectMDLCacheNotify(); + + g_pPhysics = NULL; + + Shader_Disconnect(); + + g_pFileSystem = NULL; + + ConVar_Unregister(); + + dedicated = NULL; + + BaseClass::Disconnect(); + + g_AppSystemFactory = NULL; +} + +//----------------------------------------------------------------------------- +// Query interface +//----------------------------------------------------------------------------- +void *CDedicatedServerAPI::QueryInterface( const char *pInterfaceName ) +{ + // Loading the engine DLL mounts *all* engine interfaces + CreateInterfaceFn factory = Sys_GetFactoryThis(); // This silly construction is necessary + return factory( pInterfaceName, NULL ); // to prevent the LTCG compiler from crashing. +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : type - 0 == normal, 1 == dedicated server +// *instance - +// *basedir - +// *cmdline - +// launcherFactory - +//----------------------------------------------------------------------------- +bool CDedicatedServerAPI::ModInit( ModInfo_t &info ) +{ + // Setup and write out steam_appid.txt before we launch + bool bDedicated = true; + eSteamInfoInit steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); + + eng->SetQuitting( IEngine::QUIT_NOTQUITTING ); + + // Set up the engineparms_t which contains global information about the mod + host_parms.basedir = const_cast(info.m_pBaseDirectory); + host_parms.mod = const_cast(GetModDirFromPath(info.m_pInitialMod)); + host_parms.game = const_cast(info.m_pInitialGame); + + g_bTextMode = info.m_bTextMode; + + TRACEINIT( COM_InitFilesystem( info.m_pInitialMod ), COM_ShutdownFileSystem() ); + + if ( steamInfo != eSteamInfo_Initialized ) + { + // Try again with filesystem available. This is commonly needed for SDK mods which need the filesystem to find + // their steam.inf, due to mounting SDK search paths. + steamInfo = Sys_TryInitSteamInfo( this, g_SteamInfIDVersionInfo, info.m_pInitialMod, info.m_pBaseDirectory, bDedicated ); + Assert( steamInfo == eSteamInfo_Initialized ); + if ( steamInfo != eSteamInfo_Initialized ) + { + Warning( "Failed to find steam.inf or equivalent steam info. May not have proper information to connect to Steam.\n" ); + } + } + + // set this up as early as possible, if the server isn't going to run pure, stop CRCing bits as we load them + // this happens even before the ConCommand's are processed, but we need to be sure to either CRC every file + // that is loaded, or not bother doing any + // Note that this mirrors g_sv_pure_mode from sv_main.cpp + int pure_mode = 1; // default to on, +sv_pure 0 or -sv_pure 0 will turn it off + if ( CommandLine()->CheckParm("+sv_pure") ) + pure_mode = CommandLine()->ParmValue( "+sv_pure", 1 ); + else if ( CommandLine()->CheckParm("-sv_pure") ) + pure_mode = CommandLine()->ParmValue( "-sv_pure", 1 ); + if ( pure_mode ) + g_pFullFileSystem->EnableWhitelistFileTracking( true, true, CommandLine()->FindParm( "-sv_pure_verify_hashes" ) ? true : false ); + else + g_pFullFileSystem->EnableWhitelistFileTracking( false, false, false ); + + materials->ModInit(); + + // Setup the material system config record, CreateGameWindow depends on it + // (when we're running stand-alone) +#ifndef SWDS + InitMaterialSystemConfig( true ); // !!should this be called standalone or not? +#endif + + // Initialize general game stuff and create the main window + if ( game->Init( NULL ) ) + { + m_pDedicatedServer = new CModAppSystemGroup( true, info.m_pParentAppSystemGroup ); + + // Store off the app system factory... + g_AppSystemFactory = m_pDedicatedServer->GetFactory(); + + m_pDedicatedServer->Run(); + return true; + } + + return false; +} + +void CDedicatedServerAPI::ModShutdown( void ) +{ + if ( m_pDedicatedServer ) + { + delete m_pDedicatedServer; + m_pDedicatedServer = NULL; + } + + g_AppSystemFactory = NULL; + + // Unload GL, Sound, etc. + eng->Unload(); + + // Shut down memory, etc. + game->Shutdown(); + + materials->ModShutdown(); + TRACESHUTDOWN( COM_ShutdownFileSystem() ); +} + +bool CDedicatedServerAPI::RunFrame( void ) +{ + // Bail if someone wants to quit. + if ( eng->GetQuitting() != IEngine::QUIT_NOTQUITTING ) + { + return false; + } + + // Run engine frame + eng->Frame(); + return true; +} + +void CDedicatedServerAPI::AddConsoleText( char *text ) +{ + Cbuf_AddText( text ); +} + +void CDedicatedServerAPI::UpdateStatus(float *fps, int *nActive, int *nMaxPlayers, char *pszMap, int maxlen ) +{ + Host_GetHostInfo( fps, nActive, nMaxPlayers, pszMap, maxlen ); +} + +void CDedicatedServerAPI::UpdateHostname(char *pszHostname, int maxlen) +{ + if ( pszHostname && ( maxlen > 0 ) ) + { + Q_strncpy( pszHostname, sv.GetName(), maxlen ); + } +} + +#ifndef SWDS + +class CGameUIFuncs : public IGameUIFuncs +{ +public: + bool IsKeyDown( const char *keyname, bool& isdown ) + { + isdown = false; + if ( !g_ClientDLL ) + return false; + + return g_ClientDLL->IN_IsKeyDown( keyname, isdown ); + } + + const char *GetBindingForButtonCode( ButtonCode_t code ) + { + return ::Key_BindingForKey( code ); + } + + virtual ButtonCode_t GetButtonCodeForBind( const char *bind ) + { + const char *pKeyName = Key_NameForBinding( bind ); + if ( !pKeyName ) + return KEY_NONE; + return g_pInputSystem->StringToButtonCode( pKeyName ) ; + } + + void GetVideoModes( struct vmode_s **ppListStart, int *pCount ) + { + if ( videomode ) + { + *pCount = videomode->GetModeCount(); + *ppListStart = videomode->GetMode( 0 ); + } + else + { + *pCount = 0; + *ppListStart = NULL; + } + } + + void GetDesktopResolution( int &width, int &height ) + { + int refreshrate; + game->GetDesktopInfo( width, height, refreshrate ); + } + + virtual void SetFriendsID( uint friendsID, const char *friendsName ) + { + cl.SetFriendsID( friendsID, friendsName ); + } + + bool IsConnectedToVACSecureServer() + { + if ( cl.IsConnected() ) + return Steam3Client().BGSSecure(); + return false; + } +}; + +EXPOSE_SINGLE_INTERFACE( CGameUIFuncs, IGameUIFuncs, VENGINE_GAMEUIFUNCS_VERSION ); + +#endif + +CON_COMMAND( dumplongticks, "Enables generating minidumps on long ticks." ) +{ + int enable = atoi( args[1] ); + if ( args.ArgC() == 1 || enable ) + { + if ( EnableLongTickWatcher() ) + { + Msg( "Long tick watcher thread created. Use \"dumplongticks 0\" to disable.\n" ); + } + } + else + { + // disable watcher thread if enabled + if ( g_bLongTickWatcherThreadEnabled && !g_bQuitLongTickWatcherThread ) + { + Msg( "Disabling the long tick watcher.\n" ); + g_bQuitLongTickWatcherThread = true; + } + else + { + Msg( "The long tick watcher is already disabled.\n" ); + } + } +} + diff --git a/public/bspfile.h b/public/bspfile.h index 963b4c8..1648025 100644 --- a/public/bspfile.h +++ b/public/bspfile.h @@ -971,6 +971,29 @@ enum emittype_t // Flags for dworldlight_t::flags #define DWL_FLAGS_INAMBIENTCUBE 0x0001 // This says that the light was put into the per-leaf ambient cubes. +// Old version of the worldlight struct, used for backward compatibility loading. +struct dworldlight_old_t +{ + DECLARE_BYTESWAP_DATADESC(); + Vector origin; + Vector intensity; + Vector normal; // for surfaces and spotlights + int cluster; + emittype_t type; + int style; + float stopdot; // start of penumbra for emit_spotlight + float stopdot2; // end of penumbra for emit_spotlight + float exponent; // + float radius; // cutoff distance + // falloff for emit_spotlight + emit_point: + // 1 / (constant_attn + linear_attn * dist + quadratic_attn * dist^2) + float constant_attn; + float linear_attn; + float quadratic_attn; + int flags; // Uses a combination of the DWL_FLAGS_ defines. + int texinfo; // + int owner; // entity that this light it relative to +}; struct dworldlight_t { diff --git a/utils/common/bsplib.cpp b/utils/common/bsplib.cpp index cadf7af..1ccc02c 100644 --- a/utils/common/bsplib.cpp +++ b/utils/common/bsplib.cpp @@ -1,5245 +1,5245 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $Revision: $ -// $NoKeywords: $ -//=============================================================================// - -#include "cmdlib.h" -#include "mathlib/mathlib.h" -#include "bsplib.h" -#include "zip_utils.h" -#include "scriplib.h" -#include "utllinkedlist.h" -#include "bsptreedata.h" -#include "cmodel.h" -#include "gamebspfile.h" -#include "materialsystem/imaterial.h" -#include "materialsystem/hardwareverts.h" -#include "utlbuffer.h" -#include "utlrbtree.h" -#include "utlsymbol.h" -#include "utlstring.h" -#include "checksum_crc.h" -#include "physdll.h" -#include "tier0/dbg.h" -#include "lumpfiles.h" -#include "vtf/vtf.h" -#include "lzma/lzma.h" -#include "tier1/lzmaDecoder.h" - -//============================================================================= - -// Boundary each lump should be aligned to -#define LUMP_ALIGNMENT 4 - -// Data descriptions for byte swapping - only needed -// for structures that are written to file for use by the game. -BEGIN_BYTESWAP_DATADESC( dheader_t ) - DEFINE_FIELD( ident, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), - DEFINE_FIELD( mapRevision, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( lump_t ) - DEFINE_FIELD( fileofs, FIELD_INTEGER ), - DEFINE_FIELD( filelen, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_INTEGER ), - DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dflagslump_t ) - DEFINE_FIELD( m_LevelFlags, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dplane_t ) - DEFINE_FIELD( normal, FIELD_VECTOR ), - DEFINE_FIELD( dist, FIELD_FLOAT ), - DEFINE_FIELD( type, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleaf_version_0_t ) - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( cluster, FIELD_SHORT ), - DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstleafface, FIELD_SHORT ), - DEFINE_FIELD( numleaffaces, FIELD_SHORT ), - DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), - DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), - DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), - DEFINE_EMBEDDED( m_AmbientLighting ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleaf_t ) - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( cluster, FIELD_SHORT ), - DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstleafface, FIELD_SHORT ), - DEFINE_FIELD( numleaffaces, FIELD_SHORT ), - DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), - DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), - DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CompressedLightCube ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) - DEFINE_ARRAY( m_Color, FIELD_CHARACTER, 6 * sizeof(ColorRGBExp32) ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafambientindex_t ) - DEFINE_FIELD( ambientSampleCount, FIELD_SHORT ), - DEFINE_FIELD( firstAmbientSample, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafambientlighting_t ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) - DEFINE_EMBEDDED( cube ), - DEFINE_FIELD( x, FIELD_CHARACTER ), - DEFINE_FIELD( y, FIELD_CHARACTER ), - DEFINE_FIELD( z, FIELD_CHARACTER ), - DEFINE_FIELD( pad, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dvertex_t ) - DEFINE_FIELD( point, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dnode_t ) - DEFINE_FIELD( planenum, FIELD_INTEGER ), - DEFINE_ARRAY( children, FIELD_INTEGER, 2 ), - DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), - DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), - DEFINE_FIELD( firstface, FIELD_SHORT ), - DEFINE_FIELD( numfaces, FIELD_SHORT ), - DEFINE_FIELD( area, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( texinfo_t ) - DEFINE_ARRAY( textureVecsTexelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), - DEFINE_ARRAY( lightmapVecsLuxelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( texdata, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dtexdata_t ) - DEFINE_FIELD( reflectivity, FIELD_VECTOR ), - DEFINE_FIELD( nameStringTableID, FIELD_INTEGER ), - DEFINE_FIELD( width, FIELD_INTEGER ), - DEFINE_FIELD( height, FIELD_INTEGER ), - DEFINE_FIELD( view_width, FIELD_INTEGER ), - DEFINE_FIELD( view_height, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( ddispinfo_t ) - DEFINE_FIELD( startPosition, FIELD_VECTOR ), - DEFINE_FIELD( m_iDispVertStart, FIELD_INTEGER ), - DEFINE_FIELD( m_iDispTriStart, FIELD_INTEGER ), - DEFINE_FIELD( power, FIELD_INTEGER ), - DEFINE_FIELD( minTess, FIELD_INTEGER ), - DEFINE_FIELD( smoothingAngle, FIELD_FLOAT ), - DEFINE_FIELD( contents, FIELD_INTEGER ), - DEFINE_FIELD( m_iMapFace, FIELD_SHORT ), - DEFINE_FIELD( m_iLightmapAlphaStart, FIELD_INTEGER ), - DEFINE_FIELD( m_iLightmapSamplePositionStart, FIELD_INTEGER ), - DEFINE_EMBEDDED_ARRAY( m_EdgeNeighbors, 4 ), - DEFINE_EMBEDDED_ARRAY( m_CornerNeighbors, 4 ), - DEFINE_ARRAY( m_AllowedVerts, FIELD_INTEGER, ddispinfo_t::ALLOWEDVERTS_SIZE ), // unsigned long -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispNeighbor ) - DEFINE_EMBEDDED_ARRAY( m_SubNeighbors, 2 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispCornerNeighbors ) - DEFINE_ARRAY( m_Neighbors, FIELD_SHORT, MAX_DISP_CORNER_NEIGHBORS ), - DEFINE_FIELD( m_nNeighbors, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispSubNeighbor ) - DEFINE_FIELD( m_iNeighbor, FIELD_SHORT ), - DEFINE_FIELD( m_NeighborOrientation, FIELD_CHARACTER ), - DEFINE_FIELD( m_Span, FIELD_CHARACTER ), - DEFINE_FIELD( m_NeighborSpan, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispVert ) - DEFINE_FIELD( m_vVector, FIELD_VECTOR ), - DEFINE_FIELD( m_flDist, FIELD_FLOAT ), - DEFINE_FIELD( m_flAlpha, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CDispTri ) - DEFINE_FIELD( m_uiTags, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( CFaceMacroTextureInfo ) - DEFINE_FIELD( m_MacroTextureNameID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dprimitive_t ) - DEFINE_FIELD( type, FIELD_CHARACTER ), - DEFINE_FIELD( firstIndex, FIELD_SHORT ), - DEFINE_FIELD( indexCount, FIELD_SHORT ), - DEFINE_FIELD( firstVert, FIELD_SHORT ), - DEFINE_FIELD( vertCount, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dprimvert_t ) - DEFINE_FIELD( pos, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dface_t ) - DEFINE_FIELD( planenum, FIELD_SHORT ), - DEFINE_FIELD( side, FIELD_CHARACTER ), - DEFINE_FIELD( onNode, FIELD_CHARACTER ), - DEFINE_FIELD( firstedge, FIELD_INTEGER ), - DEFINE_FIELD( numedges, FIELD_SHORT ), - DEFINE_FIELD( texinfo, FIELD_SHORT ), - DEFINE_FIELD( dispinfo, FIELD_SHORT ), - DEFINE_FIELD( surfaceFogVolumeID, FIELD_SHORT ), - DEFINE_ARRAY( styles, FIELD_CHARACTER, MAXLIGHTMAPS ), - DEFINE_FIELD( lightofs, FIELD_INTEGER ), - DEFINE_FIELD( area, FIELD_FLOAT ), - DEFINE_ARRAY( m_LightmapTextureMinsInLuxels, FIELD_INTEGER, 2 ), - DEFINE_ARRAY( m_LightmapTextureSizeInLuxels, FIELD_INTEGER, 2 ), - DEFINE_FIELD( origFace, FIELD_INTEGER ), - DEFINE_FIELD( m_NumPrims, FIELD_SHORT ), - DEFINE_FIELD( firstPrimID, FIELD_SHORT ), - DEFINE_FIELD( smoothingGroups, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dfaceid_t ) - DEFINE_FIELD( hammerfaceid, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dbrush_t ) - DEFINE_FIELD( firstside, FIELD_INTEGER ), - DEFINE_FIELD( numsides, FIELD_INTEGER ), - DEFINE_FIELD( contents, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dbrushside_t ) - DEFINE_FIELD( planenum, FIELD_SHORT ), - DEFINE_FIELD( texinfo, FIELD_SHORT ), - DEFINE_FIELD( dispinfo, FIELD_SHORT ), - DEFINE_FIELD( bevel, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dedge_t ) - DEFINE_ARRAY( v, FIELD_SHORT, 2 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dmodel_t ) - DEFINE_FIELD( mins, FIELD_VECTOR ), - DEFINE_FIELD( maxs, FIELD_VECTOR ), - DEFINE_FIELD( origin, FIELD_VECTOR ), - DEFINE_FIELD( headnode, FIELD_INTEGER ), - DEFINE_FIELD( firstface, FIELD_INTEGER ), - DEFINE_FIELD( numfaces, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dphysmodel_t ) - DEFINE_FIELD( modelIndex, FIELD_INTEGER ), - DEFINE_FIELD( dataSize, FIELD_INTEGER ), - DEFINE_FIELD( keydataSize, FIELD_INTEGER ), - DEFINE_FIELD( solidCount, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dphysdisp_t ) - DEFINE_FIELD( numDisplacements, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( darea_t ) - DEFINE_FIELD( numareaportals, FIELD_INTEGER ), - DEFINE_FIELD( firstareaportal, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dareaportal_t ) - DEFINE_FIELD( m_PortalKey, FIELD_SHORT ), - DEFINE_FIELD( otherarea, FIELD_SHORT ), - DEFINE_FIELD( m_FirstClipPortalVert, FIELD_SHORT ), - DEFINE_FIELD( m_nClipPortalVerts, FIELD_SHORT ), - DEFINE_FIELD( planenum, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dworldlight_t ) - DEFINE_FIELD( origin, FIELD_VECTOR ), - DEFINE_FIELD( intensity, FIELD_VECTOR ), - DEFINE_FIELD( normal, FIELD_VECTOR ), - DEFINE_FIELD( cluster, FIELD_INTEGER ), - DEFINE_FIELD( type, FIELD_INTEGER ), // enumeration - DEFINE_FIELD( style, FIELD_INTEGER ), - DEFINE_FIELD( stopdot, FIELD_FLOAT ), - DEFINE_FIELD( stopdot2, FIELD_FLOAT ), - DEFINE_FIELD( exponent, FIELD_FLOAT ), - DEFINE_FIELD( radius, FIELD_FLOAT ), - DEFINE_FIELD( constant_attn, FIELD_FLOAT ), - DEFINE_FIELD( linear_attn, FIELD_FLOAT ), - DEFINE_FIELD( quadratic_attn, FIELD_FLOAT ), - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( texinfo, FIELD_INTEGER ), - DEFINE_FIELD( owner, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dleafwaterdata_t ) - DEFINE_FIELD( surfaceZ, FIELD_FLOAT ), - DEFINE_FIELD( minZ, FIELD_FLOAT ), - DEFINE_FIELD( surfaceTexInfoID, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doccluderdata_t ) - DEFINE_FIELD( flags, FIELD_INTEGER ), - DEFINE_FIELD( firstpoly, FIELD_INTEGER ), - DEFINE_FIELD( polycount, FIELD_INTEGER ), - DEFINE_FIELD( mins, FIELD_VECTOR ), - DEFINE_FIELD( maxs, FIELD_VECTOR ), - DEFINE_FIELD( area, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doccluderpolydata_t ) - DEFINE_FIELD( firstvertexindex, FIELD_INTEGER ), - DEFINE_FIELD( vertexcount, FIELD_INTEGER ), - DEFINE_FIELD( planenum, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dcubemapsample_t ) - DEFINE_ARRAY( origin, FIELD_INTEGER, 3 ), - DEFINE_FIELD( size, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doverlay_t ) - DEFINE_FIELD( nId, FIELD_INTEGER ), - DEFINE_FIELD( nTexInfo, FIELD_SHORT ), - DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), - DEFINE_ARRAY( aFaces, FIELD_INTEGER, OVERLAY_BSP_FACE_COUNT ), - DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), - DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), - DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dwateroverlay_t ) - DEFINE_FIELD( nId, FIELD_INTEGER ), - DEFINE_FIELD( nTexInfo, FIELD_SHORT ), - DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), - DEFINE_ARRAY( aFaces, FIELD_INTEGER, WATEROVERLAY_BSP_FACE_COUNT ), - DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), - DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), - DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), - DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( doverlayfade_t ) - DEFINE_FIELD( flFadeDistMinSq, FIELD_FLOAT ), - DEFINE_FIELD( flFadeDistMaxSq, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dgamelumpheader_t ) - DEFINE_FIELD( lumpCount, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( dgamelump_t ) - DEFINE_FIELD( id, FIELD_INTEGER ), // GameLumpId_t - DEFINE_FIELD( flags, FIELD_SHORT ), - DEFINE_FIELD( version, FIELD_SHORT ), - DEFINE_FIELD( fileofs, FIELD_INTEGER ), - DEFINE_FIELD( filelen, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -// From gamebspfile.h -BEGIN_BYTESWAP_DATADESC( StaticPropDictLump_t ) - DEFINE_ARRAY( m_Name, FIELD_CHARACTER, STATIC_PROP_NAME_LENGTH ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLump_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), - DEFINE_FIELD( m_nMinDXLevel, FIELD_SHORT ), - DEFINE_FIELD( m_nMaxDXLevel, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLumpV4_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLumpV5_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_PropType, FIELD_SHORT ), - DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), - DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), - DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), - DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), - DEFINE_FIELD( m_Skin, FIELD_INTEGER ), - DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), - DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), - DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), - DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( StaticPropLeafLump_t ) - DEFINE_FIELD( m_Leaf, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailObjectDictLump_t ) - DEFINE_ARRAY( m_Name, FIELD_CHARACTER, DETAIL_NAME_LENGTH ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailObjectLump_t ) - DEFINE_FIELD( m_Origin, FIELD_VECTOR ), - DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle - DEFINE_FIELD( m_DetailModel, FIELD_SHORT ), - DEFINE_FIELD( m_Leaf, FIELD_SHORT ), - DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 - DEFINE_FIELD( m_LightStyles, FIELD_INTEGER ), - DEFINE_FIELD( m_LightStyleCount, FIELD_CHARACTER ), - DEFINE_FIELD( m_SwayAmount, FIELD_CHARACTER ), - DEFINE_FIELD( m_ShapeAngle, FIELD_CHARACTER ), - DEFINE_FIELD( m_ShapeSize, FIELD_CHARACTER ), - DEFINE_FIELD( m_Orientation, FIELD_CHARACTER ), - DEFINE_ARRAY( m_Padding2, FIELD_CHARACTER, 3 ), - DEFINE_FIELD( m_Type, FIELD_CHARACTER ), - DEFINE_ARRAY( m_Padding3, FIELD_CHARACTER, 3 ), - DEFINE_FIELD( m_flScale, FIELD_FLOAT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailSpriteDictLump_t ) - DEFINE_FIELD( m_UL, FIELD_VECTOR2D ), - DEFINE_FIELD( m_LR, FIELD_VECTOR2D ), - DEFINE_FIELD( m_TexUL, FIELD_VECTOR2D ), - DEFINE_FIELD( m_TexLR, FIELD_VECTOR2D ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( DetailPropLightstylesLump_t ) - DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 - DEFINE_FIELD( m_Style, FIELD_CHARACTER ), -END_BYTESWAP_DATADESC() - -// From vradstaticprops.h -namespace HardwareVerts -{ -BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) - DEFINE_FIELD( m_nLod, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), - DEFINE_FIELD( m_nOffset, FIELD_INTEGER ), - DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC( FileHeader_t ) - DEFINE_FIELD( m_nVersion, FIELD_INTEGER ), - DEFINE_FIELD( m_nChecksum, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexFlags, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexSize, FIELD_INTEGER ), - DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), - DEFINE_FIELD( m_nMeshes, FIELD_INTEGER ), - DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), -END_BYTESWAP_DATADESC() -} // end namespace - -static const char *s_LumpNames[] = { - "LUMP_ENTITIES", // 0 - "LUMP_PLANES", // 1 - "LUMP_TEXDATA", // 2 - "LUMP_VERTEXES", // 3 - "LUMP_VISIBILITY", // 4 - "LUMP_NODES", // 5 - "LUMP_TEXINFO", // 6 - "LUMP_FACES", // 7 - "LUMP_LIGHTING", // 8 - "LUMP_OCCLUSION", // 9 - "LUMP_LEAFS", // 10 - "LUMP_FACEIDS", // 11 - "LUMP_EDGES", // 12 - "LUMP_SURFEDGES", // 13 - "LUMP_MODELS", // 14 - "LUMP_WORLDLIGHTS", // 15 - "LUMP_LEAFFACES", // 16 - "LUMP_LEAFBRUSHES", // 17 - "LUMP_BRUSHES", // 18 - "LUMP_BRUSHSIDES", // 19 - "LUMP_AREAS", // 20 - "LUMP_AREAPORTALS", // 21 - "LUMP_UNUSED0", // 22 - "LUMP_UNUSED1", // 23 - "LUMP_UNUSED2", // 24 - "LUMP_UNUSED3", // 25 - "LUMP_DISPINFO", // 26 - "LUMP_ORIGINALFACES", // 27 - "LUMP_PHYSDISP", // 28 - "LUMP_PHYSCOLLIDE", // 29 - "LUMP_VERTNORMALS", // 30 - "LUMP_VERTNORMALINDICES", // 31 - "LUMP_DISP_LIGHTMAP_ALPHAS", // 32 - "LUMP_DISP_VERTS", // 33 - "LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS", // 34 - "LUMP_GAME_LUMP", // 35 - "LUMP_LEAFWATERDATA", // 36 - "LUMP_PRIMITIVES", // 37 - "LUMP_PRIMVERTS", // 38 - "LUMP_PRIMINDICES", // 39 - "LUMP_PAKFILE", // 40 - "LUMP_CLIPPORTALVERTS", // 41 - "LUMP_CUBEMAPS", // 42 - "LUMP_TEXDATA_STRING_DATA", // 43 - "LUMP_TEXDATA_STRING_TABLE", // 44 - "LUMP_OVERLAYS", // 45 - "LUMP_LEAFMINDISTTOWATER", // 46 - "LUMP_FACE_MACRO_TEXTURE_INFO", // 47 - "LUMP_DISP_TRIS", // 48 - "LUMP_PHYSCOLLIDESURFACE", // 49 - "LUMP_WATEROVERLAYS", // 50 - "LUMP_LEAF_AMBIENT_INDEX_HDR", // 51 - "LUMP_LEAF_AMBIENT_INDEX", // 52 - "LUMP_LIGHTING_HDR", // 53 - "LUMP_WORLDLIGHTS_HDR", // 54 - "LUMP_LEAF_AMBIENT_LIGHTING_HDR", // 55 - "LUMP_LEAF_AMBIENT_LIGHTING", // 56 - "LUMP_XZIPPAKFILE", // 57 - "LUMP_FACES_HDR", // 58 - "LUMP_MAP_FLAGS", // 59 - "LUMP_OVERLAY_FADES", // 60 -}; - -const char *GetLumpName( unsigned int lumpnum ) -{ - if ( lumpnum >= ARRAYSIZE( s_LumpNames ) ) - { - return "UNKNOWN"; - } - return s_LumpNames[lumpnum]; -} - -// "-hdr" tells us to use the HDR fields (if present) on the light sources. Also, tells us to write -// out the HDR lumps for lightmaps, ambient leaves, and lights sources. -bool g_bHDR = false; - -// Set to true to generate Xbox360 native output files -static bool g_bSwapOnLoad = false; -static bool g_bSwapOnWrite = false; - -VTFConvertFunc_t g_pVTFConvertFunc; -VHVFixupFunc_t g_pVHVFixupFunc; -CompressFunc_t g_pCompressFunc; - -CUtlVector< CUtlString > g_StaticPropNames; -CUtlVector< int > g_StaticPropInstances; - -CByteswap g_Swap; - -uint32 g_LevelFlags = 0; - -int nummodels; -dmodel_t dmodels[MAX_MAP_MODELS]; - -int visdatasize; -byte dvisdata[MAX_MAP_VISIBILITY]; -dvis_t *dvis = (dvis_t *)dvisdata; - -CUtlVector dlightdataHDR; -CUtlVector dlightdataLDR; -CUtlVector *pdlightdata = &dlightdataLDR; - -CUtlVector dentdata; - -int numleafs; -#if !defined( BSP_USE_LESS_MEMORY ) -dleaf_t dleafs[MAX_MAP_LEAFS]; -#else -dleaf_t *dleafs; -#endif - -CUtlVector g_LeafAmbientIndexLDR; -CUtlVector g_LeafAmbientIndexHDR; -CUtlVector *g_pLeafAmbientIndex = NULL; -CUtlVector g_LeafAmbientLightingLDR; -CUtlVector g_LeafAmbientLightingHDR; -CUtlVector *g_pLeafAmbientLighting = NULL; - -unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; - -int numplanes; -dplane_t dplanes[MAX_MAP_PLANES]; - -int numvertexes; -dvertex_t dvertexes[MAX_MAP_VERTS]; - -int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. -unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; - -int g_numvertnormals; -Vector g_vertnormals[MAX_MAP_VERTNORMALS]; - -int numnodes; -dnode_t dnodes[MAX_MAP_NODES]; - -CUtlVector texinfo( MAX_MAP_TEXINFO ); - -int numtexdata; -dtexdata_t dtexdata[MAX_MAP_TEXDATA]; - -// -// displacement map bsp file info: dispinfo -// -CUtlVector g_dispinfo; -CUtlVector g_DispVerts; -CUtlVector g_DispTris; -CUtlVector g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS - -int numorigfaces; -dface_t dorigfaces[MAX_MAP_FACES]; - -int g_numprimitives = 0; -dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; - -int g_numprimverts = 0; -dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; - -int g_numprimindices = 0; -unsigned short g_primindices[MAX_MAP_PRIMINDICES]; - -int numfaces; -dface_t dfaces[MAX_MAP_FACES]; - -int numfaceids; -CUtlVector dfaceids; - -int numfaces_hdr; -dface_t dfaces_hdr[MAX_MAP_FACES]; - -int numedges; -dedge_t dedges[MAX_MAP_EDGES]; - -int numleaffaces; -unsigned short dleaffaces[MAX_MAP_LEAFFACES]; - -int numleafbrushes; -unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; - -int numsurfedges; -int dsurfedges[MAX_MAP_SURFEDGES]; - -int numbrushes; -dbrush_t dbrushes[MAX_MAP_BRUSHES]; - -int numbrushsides; -dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; - -int numareas; -darea_t dareas[MAX_MAP_AREAS]; - -int numareaportals; -dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; - -int numworldlightsLDR; -dworldlight_t dworldlightsLDR[MAX_MAP_WORLDLIGHTS]; - -int numworldlightsHDR; -dworldlight_t dworldlightsHDR[MAX_MAP_WORLDLIGHTS]; - -int *pNumworldlights = &numworldlightsLDR; -dworldlight_t *dworldlights = dworldlightsLDR; - -int numleafwaterdata = 0; -dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; - -CUtlVector g_FaceMacroTextureInfos; - -Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; -int g_nClipPortalVerts; - -dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; -int g_nCubemapSamples = 0; - -int g_nOverlayCount; -doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; -doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; - -int g_nWaterOverlayCount; -dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; - -CUtlVector g_TexDataStringData; -CUtlVector g_TexDataStringTable; - -byte *g_pPhysCollide = NULL; -int g_PhysCollideSize = 0; -int g_MapRevision = 0; - -byte *g_pPhysDisp = NULL; -int g_PhysDispSize = 0; - -CUtlVector g_OccluderData( 256, 256 ); -CUtlVector g_OccluderPolyData( 1024, 1024 ); -CUtlVector g_OccluderVertexIndices( 2048, 2048 ); - -template static void WriteData( T *pData, int count = 1 ); -template static void WriteData( int fieldType, T *pData, int count = 1 ); -template< class T > static void AddLump( int lumpnum, T *pData, int count, int version = 0 ); -template< class T > static void AddLump( int lumpnum, CUtlVector &data, int version = 0 ); - -dheader_t *g_pBSPHeader; -FileHandle_t g_hBSPFile; - -struct Lump_t -{ - void *pLumps[HEADER_LUMPS]; - int size[HEADER_LUMPS]; - bool bLumpParsed[HEADER_LUMPS]; -} g_Lumps; - -CGameLump g_GameLumps; - -static IZip *s_pakFile = 0; - -//----------------------------------------------------------------------------- -// Keep the file position aligned to an arbitrary boundary. -// Returns updated file position. -//----------------------------------------------------------------------------- -static unsigned int AlignFilePosition( FileHandle_t hFile, int alignment ) -{ - unsigned int currPosition = g_pFileSystem->Tell( hFile ); - - if ( alignment >= 2 ) - { - unsigned int newPosition = AlignValue( currPosition, alignment ); - unsigned int count = newPosition - currPosition; - if ( count ) - { - char *pBuffer; - char smallBuffer[4096]; - if ( count > sizeof( smallBuffer ) ) - { - pBuffer = (char *)malloc( count ); - } - else - { - pBuffer = smallBuffer; - } - - memset( pBuffer, 0, count ); - SafeWrite( hFile, pBuffer, count ); - - if ( pBuffer != smallBuffer ) - { - free( pBuffer ); - } - - currPosition = newPosition; - } - } - - return currPosition; -} - -//----------------------------------------------------------------------------- -// Purpose: // Get a pakfile instance -// Output : IZip* -//----------------------------------------------------------------------------- -IZip* GetPakFile( void ) -{ - if ( !s_pakFile ) - { - s_pakFile = IZip::CreateZip(); - } - return s_pakFile; -} - -//----------------------------------------------------------------------------- -// Purpose: Free the pak files -//----------------------------------------------------------------------------- -void ReleasePakFileLumps( void ) -{ - // Release the pak files - IZip::ReleaseZip( s_pakFile ); - s_pakFile = NULL; -} - -//----------------------------------------------------------------------------- -// Purpose: Set the sector alignment for all subsequent zip operations -//----------------------------------------------------------------------------- -void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ) -{ - pak->ForceAlignment( bAlign, bCompatibleFormat, alignmentSize ); -} - -//----------------------------------------------------------------------------- -// Purpose: Store data back out to .bsp file -//----------------------------------------------------------------------------- -static void WritePakFileLump( void ) -{ - CUtlBuffer buf( 0, 0 ); - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->SaveToBuffer( buf ); - - // must respect pak file alignment - // pad up and ensure lump starts on same aligned boundary - AlignFilePosition( g_hBSPFile, GetPakFile()->GetAlignment() ); - - // Now store final buffers out to file - AddLump( LUMP_PAKFILE, (byte*)buf.Base(), buf.TellPut() ); -} - -//----------------------------------------------------------------------------- -// Purpose: Remove all entries -//----------------------------------------------------------------------------- -void ClearPakFile( IZip *pak ) -{ - pak->Reset(); -} - -//----------------------------------------------------------------------------- -// Purpose: Add file from disk to .bsp PAK lump -// Input : *relativename - -// *fullpath - -//----------------------------------------------------------------------------- -void AddFileToPak( IZip *pak, const char *relativename, const char *fullpath, IZip::eCompressionType compressionType ) -{ - DevMsg( "Adding file to pakfile [ %s ]\n", fullpath ); - pak->AddFileToZip( relativename, fullpath, compressionType ); -} - -//----------------------------------------------------------------------------- -// Purpose: Add buffer to .bsp PAK lump as named file -// Input : *relativename - -// *data - -// length - -//----------------------------------------------------------------------------- -void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode, IZip::eCompressionType compressionType ) -{ - pak->AddBufferToZip( pRelativeName, data, length, bTextMode, compressionType ); -} - -//----------------------------------------------------------------------------- -// Purpose: Add entire directory to .bsp PAK lump as named file -// Input : *relativename - -// *data - -// length - -//----------------------------------------------------------------------------- -void AddDirToPak( IZip *pak, const char *pDirPath, const char *pPakPrefix ) -{ - if ( !g_pFullFileSystem->IsDirectory( pDirPath ) ) - { - Warning( "Passed non-directory to AddDirToPak [ %s ]\n", pDirPath ); - return; - } - - DevMsg( "Adding directory to pakfile [ %s ]\n", pDirPath ); - - // Enumerate dir - char szEnumerateDir[MAX_PATH] = { 0 }; - V_snprintf( szEnumerateDir, sizeof( szEnumerateDir ), "%s/*.*", pDirPath ); - V_FixSlashes( szEnumerateDir ); - - FileFindHandle_t handle; - const char *szFindResult = g_pFullFileSystem->FindFirst( szEnumerateDir, &handle ); - do - { - if ( szFindResult[0] != '.' ) - { - char szPakName[MAX_PATH] = { 0 }; - char szFullPath[MAX_PATH] = { 0 }; - if ( pPakPrefix ) - { - V_snprintf( szPakName, sizeof( szPakName ), "%s/%s", pPakPrefix, szFindResult ); - } - else - { - V_strncpy( szPakName, szFindResult, sizeof( szPakName ) ); - } - V_snprintf( szFullPath, sizeof( szFullPath ), "%s/%s", pDirPath, szFindResult ); - V_FixDoubleSlashes( szFullPath ); - V_FixDoubleSlashes( szPakName ); - - if ( g_pFullFileSystem->FindIsDirectory( handle ) ) - { - // Recurse - AddDirToPak( pak, szFullPath, szPakName ); - } - else - { - // Just add this file - AddFileToPak( pak, szPakName, szFullPath ); - } - } - szFindResult = g_pFullFileSystem->FindNext( handle ); - } while ( szFindResult); -} - -//----------------------------------------------------------------------------- -// Purpose: Check if a file already exists in the pack file. -// Input : *relativename - -//----------------------------------------------------------------------------- -bool FileExistsInPak( IZip *pak, const char *pRelativeName ) -{ - return pak->FileExistsInZip( pRelativeName ); -} - - -//----------------------------------------------------------------------------- -// Read a file from the pack file -//----------------------------------------------------------------------------- -bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) -{ - return pak->ReadFileFromZip( pRelativeName, bTextMode, buf ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Remove file from .bsp PAK lump -// Input : *relativename - -//----------------------------------------------------------------------------- -void RemoveFileFromPak( IZip *pak, const char *relativename ) -{ - pak->RemoveFileFromZip( relativename ); -} - - -//----------------------------------------------------------------------------- -// Purpose: Get next filename in directory -// Input : id, -1 to start, returns next id, or -1 at list conclusion -//----------------------------------------------------------------------------- -int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ) -{ - return pak->GetNextFilename( id, pBuffer, bufferSize, fileSize ); -} - -//----------------------------------------------------------------------------- -// Convert four-CC code to a handle + back -//----------------------------------------------------------------------------- -GameLumpHandle_t CGameLump::GetGameLumpHandle( GameLumpId_t id ) -{ - // NOTE: I'm also expecting game lump id's to be four-CC codes - Assert( id > HEADER_LUMPS ); - - FOR_EACH_LL(m_GameLumps, i) - { - if (m_GameLumps[i].m_Id == id) - return i; - } - - return InvalidGameLump(); -} - -GameLumpId_t CGameLump::GetGameLumpId( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Id; -} - -int CGameLump::GetGameLumpFlags( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Flags; -} - -int CGameLump::GetGameLumpVersion( GameLumpHandle_t handle ) -{ - return m_GameLumps[handle].m_Version; -} - - -//----------------------------------------------------------------------------- -// Game lump accessor methods -//----------------------------------------------------------------------------- - -void* CGameLump::GetGameLump( GameLumpHandle_t id ) -{ - return m_GameLumps[id].m_Memory.Base(); -} - -int CGameLump::GameLumpSize( GameLumpHandle_t id ) -{ - return m_GameLumps[id].m_Memory.NumAllocated(); -} - - -//----------------------------------------------------------------------------- -// Game lump iteration methods -//----------------------------------------------------------------------------- - -GameLumpHandle_t CGameLump::FirstGameLump() -{ - return (m_GameLumps.Count()) ? m_GameLumps.Head() : InvalidGameLump(); -} - -GameLumpHandle_t CGameLump::NextGameLump( GameLumpHandle_t handle ) -{ - - return (m_GameLumps.IsValidIndex(handle)) ? m_GameLumps.Next(handle) : InvalidGameLump(); -} - -GameLumpHandle_t CGameLump::InvalidGameLump() -{ - return 0xFFFF; -} - - -//----------------------------------------------------------------------------- -// Game lump creation/destruction method -//----------------------------------------------------------------------------- - -GameLumpHandle_t CGameLump::CreateGameLump( GameLumpId_t id, int size, int flags, int version ) -{ - Assert( GetGameLumpHandle(id) == InvalidGameLump() ); - GameLumpHandle_t handle = m_GameLumps.AddToTail(); - m_GameLumps[handle].m_Id = id; - m_GameLumps[handle].m_Flags = flags; - m_GameLumps[handle].m_Version = version; - m_GameLumps[handle].m_Memory.EnsureCapacity( size ); - return handle; -} - -void CGameLump::DestroyGameLump( GameLumpHandle_t handle ) -{ - m_GameLumps.Remove( handle ); -} - -void CGameLump::DestroyAllGameLumps() -{ - m_GameLumps.RemoveAll(); -} - -//----------------------------------------------------------------------------- -// Compute file size and clump count -//----------------------------------------------------------------------------- - -void CGameLump::ComputeGameLumpSizeAndCount( int& size, int& clumpCount ) -{ - // Figure out total size of the client lumps - size = 0; - clumpCount = 0; - GameLumpHandle_t h; - for( h = FirstGameLump(); h != InvalidGameLump(); h = NextGameLump( h ) ) - { - ++clumpCount; - size += GameLumpSize( h ); - } - - // Add on headers - size += sizeof( dgamelumpheader_t ) + clumpCount * sizeof( dgamelump_t ); -} - - -void CGameLump::SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int length ) -{ - int count = 0; - switch( id ) - { - case GAMELUMP_STATIC_PROPS: - // Swap the static prop model dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (StaticPropDictLump_t*)dest, (StaticPropDictLump_t*)src, count ); - src += sizeof(StaticPropDictLump_t) * count; - dest += sizeof(StaticPropDictLump_t) * count; - - // Swap the leaf list - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (StaticPropLeafLump_t*)dest, (StaticPropLeafLump_t*)src, count ); - src += sizeof(StaticPropLeafLump_t) * count; - dest += sizeof(StaticPropLeafLump_t) * count; - - // Swap the models - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - // The one-at-a-time swap is to compensate for these structures - // possibly being misaligned, which crashes the Xbox 360. - if ( version == 4 ) - { - StaticPropLumpV4_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLumpV4_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLumpV4_t) ); - src += sizeof( StaticPropLumpV4_t ); - dest += sizeof( StaticPropLumpV4_t ); - } - } - else if ( version == 5 ) - { - StaticPropLumpV5_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLumpV5_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLumpV5_t) ); - src += sizeof( StaticPropLumpV5_t ); - dest += sizeof( StaticPropLumpV5_t ); - } - } - else - { - if ( version != 6 ) - { - Error( "Unknown Static Prop Lump version %d didn't get swapped!\n", version ); - } - - StaticPropLump_t lump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &lump, src, sizeof(StaticPropLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); - Q_memcpy( dest, &lump, sizeof(StaticPropLump_t) ); - src += sizeof( StaticPropLump_t ); - dest += sizeof( StaticPropLump_t ); - } - } - break; - - case GAMELUMP_DETAIL_PROPS: - // Swap the detail prop model dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailObjectDictLump_t*)dest, (DetailObjectDictLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - - if ( version == 4 ) - { - // Swap the detail sprite dict - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - DetailSpriteDictLump_t spritelump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &spritelump, src, sizeof(DetailSpriteDictLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &spritelump, &spritelump ); - Q_memcpy( dest, &spritelump, sizeof(DetailSpriteDictLump_t) ); - src += sizeof(DetailSpriteDictLump_t); - dest += sizeof(DetailSpriteDictLump_t); - } - - // Swap the models - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - DetailObjectLump_t objectlump; - for ( int i = 0; i < count; ++i ) - { - Q_memcpy( &objectlump, src, sizeof(DetailObjectLump_t) ); - g_Swap.SwapFieldsToTargetEndian( &objectlump, &objectlump ); - Q_memcpy( dest, &objectlump, sizeof(DetailObjectLump_t) ); - src += sizeof(DetailObjectLump_t); - dest += sizeof(DetailObjectLump_t); - } - } - break; - - case GAMELUMP_DETAIL_PROP_LIGHTING: - // Swap the LDR light styles - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - break; - - case GAMELUMP_DETAIL_PROP_LIGHTING_HDR: - // Swap the HDR light styles - count = *(int*)src; - g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); - count = g_bSwapOnLoad ? *(int*)dest : count; - src += sizeof(int); - dest += sizeof(int); - - g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); - src += sizeof(DetailObjectDictLump_t) * count; - dest += sizeof(DetailObjectDictLump_t) * count; - break; - - default: - char idchars[5] = {0}; - Q_memcpy( idchars, &id, 4 ); - Warning( "Unknown game lump '%s' didn't get swapped!\n", idchars ); - memcpy ( dest, src, length); - break; - } -} - -//----------------------------------------------------------------------------- -// Game lump file I/O -//----------------------------------------------------------------------------- -void CGameLump::ParseGameLump( dheader_t* pHeader ) -{ - g_GameLumps.DestroyAllGameLumps(); - - g_Lumps.bLumpParsed[LUMP_GAME_LUMP] = true; - - int length = pHeader->lumps[LUMP_GAME_LUMP].filelen; - int ofs = pHeader->lumps[LUMP_GAME_LUMP].fileofs; - - if (length > 0) - { - // Read dictionary... - dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)((byte *)pHeader + ofs); - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( pGameLumpHeader ); - } - dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); - for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pGameLump[i] ); - } - - int length = pGameLump[i].filelen; - GameLumpHandle_t lump = g_GameLumps.CreateGameLump( pGameLump[i].id, length, pGameLump[i].flags, pGameLump[i].version ); - if ( g_bSwapOnLoad ) - { - SwapGameLump( pGameLump[i].id, pGameLump[i].version, (byte*)g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); - } - else - { - memcpy( g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); - } - } - } -} - - -//----------------------------------------------------------------------------- -// String table methods -//----------------------------------------------------------------------------- -const char *TexDataStringTable_GetString( int stringID ) -{ - return &g_TexDataStringData[g_TexDataStringTable[stringID]]; -} - -int TexDataStringTable_AddOrFindString( const char *pString ) -{ - int i; - // garymcthack: Make this use an RBTree! - for( i = 0; i < g_TexDataStringTable.Count(); i++ ) - { - if( stricmp( pString, &g_TexDataStringData[g_TexDataStringTable[i]] ) == 0 ) - { - return i; - } - } - - int len = strlen( pString ); - int outOffset = g_TexDataStringData.AddMultipleToTail( len+1, pString ); - int outIndex = g_TexDataStringTable.AddToTail( outOffset ); - return outIndex; -} - -//----------------------------------------------------------------------------- -// Adds all game lumps into one big block -//----------------------------------------------------------------------------- - -static void AddGameLumps( ) -{ - // Figure out total size of the client lumps - int size, clumpCount; - g_GameLumps.ComputeGameLumpSizeAndCount( size, clumpCount ); - - // Set up the main lump dictionary entry - g_Lumps.size[LUMP_GAME_LUMP] = 0; // mark it written - - lump_t* lump = &g_pBSPHeader->lumps[LUMP_GAME_LUMP]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = size; - - // write header - dgamelumpheader_t header; - header.lumpCount = clumpCount; - WriteData( &header ); - - // write dictionary - dgamelump_t dict; - int offset = lump->fileofs + sizeof(header) + clumpCount * sizeof(dgamelump_t); - GameLumpHandle_t h; - for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) - { - dict.id = g_GameLumps.GetGameLumpId(h); - dict.version = g_GameLumps.GetGameLumpVersion(h); - dict.flags = g_GameLumps.GetGameLumpFlags(h); - dict.fileofs = offset; - dict.filelen = g_GameLumps.GameLumpSize( h ); - offset += dict.filelen; - - WriteData( &dict ); - } - - // write lumps.. - for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) - { - unsigned int lumpsize = g_GameLumps.GameLumpSize(h); - if ( g_bSwapOnWrite ) - { - g_GameLumps.SwapGameLump( g_GameLumps.GetGameLumpId(h), g_GameLumps.GetGameLumpVersion(h), (byte*)g_GameLumps.GetGameLump(h), (byte*)g_GameLumps.GetGameLump(h), lumpsize ); - } - SafeWrite( g_hBSPFile, g_GameLumps.GetGameLump(h), lumpsize ); - } - - // align to doubleword - AlignFilePosition( g_hBSPFile, 4 ); -} - - -//----------------------------------------------------------------------------- -// Adds the occluder lump... -//----------------------------------------------------------------------------- -static void AddOcclusionLump( ) -{ - g_Lumps.size[LUMP_OCCLUSION] = 0; // mark it written - - int nOccluderCount = g_OccluderData.Count(); - int nOccluderPolyDataCount = g_OccluderPolyData.Count(); - int nOccluderVertexIndices = g_OccluderVertexIndices.Count(); - - int nLumpLength = nOccluderCount * sizeof(doccluderdata_t) + - nOccluderPolyDataCount * sizeof(doccluderpolydata_t) + - nOccluderVertexIndices * sizeof(int) + - 3 * sizeof(int); - - lump_t *lump = &g_pBSPHeader->lumps[LUMP_OCCLUSION]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = nLumpLength; - lump->version = LUMP_OCCLUSION_VERSION; - lump->uncompressedSize = 0; - - // Data is swapped in place, so the 'Count' variables aren't safe to use after they're written - WriteData( FIELD_INTEGER, &nOccluderCount ); - WriteData( (doccluderdata_t*)g_OccluderData.Base(), g_OccluderData.Count() ); - WriteData( FIELD_INTEGER, &nOccluderPolyDataCount ); - WriteData( (doccluderpolydata_t*)g_OccluderPolyData.Base(), g_OccluderPolyData.Count() ); - WriteData( FIELD_INTEGER, &nOccluderVertexIndices ); - WriteData( FIELD_INTEGER, (int*)g_OccluderVertexIndices.Base(), g_OccluderVertexIndices.Count() ); -} - - -//----------------------------------------------------------------------------- -// Loads the occluder lump... -//----------------------------------------------------------------------------- -static void UnserializeOcclusionLumpV2( CUtlBuffer &buf ) -{ - int nCount = buf.GetInt(); - if ( nCount ) - { - g_OccluderData.SetCount( nCount ); - buf.GetObjects( g_OccluderData.Base(), nCount ); - } - - nCount = buf.GetInt(); - if ( nCount ) - { - g_OccluderPolyData.SetCount( nCount ); - buf.GetObjects( g_OccluderPolyData.Base(), nCount ); - } - - nCount = buf.GetInt(); - if ( nCount ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( (int*)buf.PeekGet(), (int*)buf.PeekGet(), nCount ); - } - g_OccluderVertexIndices.SetCount( nCount ); - buf.Get( g_OccluderVertexIndices.Base(), nCount * sizeof(g_OccluderVertexIndices[0]) ); - } -} - - -static void LoadOcclusionLump() -{ - g_OccluderData.RemoveAll(); - g_OccluderPolyData.RemoveAll(); - g_OccluderVertexIndices.RemoveAll(); - - int length, ofs; - - g_Lumps.bLumpParsed[LUMP_OCCLUSION] = true; - - length = g_pBSPHeader->lumps[LUMP_OCCLUSION].filelen; - ofs = g_pBSPHeader->lumps[LUMP_OCCLUSION].fileofs; - - CUtlBuffer buf( (byte *)g_pBSPHeader + ofs, length, CUtlBuffer::READ_ONLY ); - buf.ActivateByteSwapping( g_bSwapOnLoad ); - switch ( g_pBSPHeader->lumps[LUMP_OCCLUSION].version ) - { - case 2: - UnserializeOcclusionLumpV2( buf ); - break; - - case 0: - break; - - default: - Error("Unknown occlusion lump version!\n"); - break; - } -} - - -/* -=============== -CompressVis - -=============== -*/ -int CompressVis (byte *vis, byte *dest) -{ - int j; - int rep; - int visrow; - byte *dest_p; - - dest_p = dest; -// visrow = (r_numvisleafs + 7)>>3; - visrow = (dvis->numclusters + 7)>>3; - - for (j=0 ; j>3; - row = (dvis->numclusters+7)>>3; - out = decompressed; - - do - { - if (*in) - { - *out++ = *in++; - continue; - } - - c = in[1]; - if (!c) - Error ("DecompressVis: 0 repeat"); - in += 2; - if ((out - decompressed) + c > row) - { - c = row - (out - decompressed); - Warning( "warning: Vis decompression overrun\n" ); - } - while (c) - { - *out++ = 0; - c--; - } - } while (out - decompressed < row); -} - -//----------------------------------------------------------------------------- -// Lump-specific swap functions -//----------------------------------------------------------------------------- -struct swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int size; - int vphysicsID; - short version; - short modelType; -}; - -struct swapcompactsurfaceheader_t : swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int surfaceSize; - Vector dragAxisAreas; - int axisMapSize; -}; - -struct swapmoppsurfaceheader_t : swapcollideheader_t -{ - DECLARE_BYTESWAP_DATADESC(); - int moppSize; -}; - -BEGIN_BYTESWAP_DATADESC( swapcollideheader_t ) - DEFINE_FIELD( size, FIELD_INTEGER ), - DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), - DEFINE_FIELD( version, FIELD_SHORT ), - DEFINE_FIELD( modelType, FIELD_SHORT ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC_( swapcompactsurfaceheader_t, swapcollideheader_t ) - DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), - DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), - DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - -BEGIN_BYTESWAP_DATADESC_( swapmoppsurfaceheader_t, swapcollideheader_t ) - DEFINE_FIELD( moppSize, FIELD_INTEGER ), -END_BYTESWAP_DATADESC() - - -static void SwapPhyscollideLump( byte *pDestBase, byte *pSrcBase, unsigned int &count ) -{ - IPhysicsCollision *physcollision = NULL; - CSysModule *pPhysicsModule = g_pFullFileSystem->LoadModule( "vphysics.dll" ); - if ( pPhysicsModule ) - { - CreateInterfaceFn physicsFactory = Sys_GetFactory( pPhysicsModule ); - if ( physicsFactory ) - { - physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); - } - } - - if ( !physcollision ) - { - Warning("!!! WARNING: Can't swap the physcollide lump!\n" ); - return; - } - - // physics data is variable length. The last physmodel is a NULL pointer - // with modelIndex -1, dataSize -1 - dphysmodel_t *pPhysModel; - byte *pSrc = pSrcBase; - - // first the src chunks have to be aligned properly - // swap increases size, allocate enough expansion room - byte *pSrcAlignedBase = (byte*)malloc( 2*count ); - byte *basePtr = pSrcAlignedBase; - byte *pSrcAligned = pSrcAlignedBase; - - do - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pSrcAligned, (dphysmodel_t*)pSrc ); - } - else - { - Q_memcpy( pSrcAligned, pSrc, sizeof(dphysmodel_t) ); - } - pPhysModel = (dphysmodel_t*)pSrcAligned; - - pSrc += sizeof(dphysmodel_t); - pSrcAligned += sizeof(dphysmodel_t); - - if ( pPhysModel->dataSize > 0 ) - { - // Align the collide headers - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - // Get data size - int size; - Q_memcpy( &size, pSrc, sizeof(int) ); - if ( g_bSwapOnLoad ) - size = SwapLong( size ); - - // Fixup size - int padBytes = 0; - if ( size % 4 != 0 ) - { - padBytes = ( 4 - size % 4 ); - count += padBytes; - pPhysModel->dataSize += padBytes; - } - - // Copy data and size into alligned buffer - int newsize = size + padBytes; - if ( g_bSwapOnLoad ) - newsize = SwapLong( newsize ); - - Q_memcpy( pSrcAligned, &newsize, sizeof(int) ); - Q_memcpy( pSrcAligned + sizeof(int), pSrc + sizeof(int), size ); - pSrcAligned += size + padBytes + sizeof(int); - pSrc += size + sizeof(int); - } - - int padBytes = 0; - int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; - Q_memcpy( pSrcAligned, pSrc, pPhysModel->keydataSize ); - pSrc += pPhysModel->keydataSize; - pSrcAligned += pPhysModel->keydataSize; - if ( dataSize % 4 != 0 ) - { - // Next chunk will be unaligned - padBytes = ( 4 - dataSize % 4 ); - pPhysModel->keydataSize += padBytes; - count += padBytes; - Q_memset( pSrcAligned, 0, padBytes ); - pSrcAligned += padBytes; - } - } - } while ( pPhysModel->dataSize > 0 ); - - // Now the data can be swapped properly - pSrcBase = pSrcAlignedBase; - pSrc = pSrcBase; - byte *pDest = pDestBase; - - do - { - // src headers are in native format - pPhysModel = (dphysmodel_t*)pSrc; - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pDest, (dphysmodel_t*)pSrc ); - } - else - { - Q_memcpy( pDest, pSrc, sizeof(dphysmodel_t) ); - } - - pSrc += sizeof(dphysmodel_t); - pDest += sizeof(dphysmodel_t); - - pSrcBase = pSrc; - pDestBase = pDest; - - if ( pPhysModel->dataSize > 0 ) - { - vcollide_t collide = {0}; - int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; - - if ( g_bSwapOnWrite ) - { - // Load the collide data - physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, false ); - } - - int *offsets = new int[ pPhysModel->solidCount ]; - - // Swap the collision data headers - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - int headerSize = 0; - swapcollideheader_t *baseHdr = (swapcollideheader_t*)pSrc; - short modelType = baseHdr->modelType; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &modelType ); - } - - if ( modelType == 0 ) // COLLIDE_POLY - { - headerSize = sizeof(swapcompactsurfaceheader_t); - swapcompactsurfaceheader_t swapHdr; - Q_memcpy( &swapHdr, pSrc, headerSize ); - g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); - Q_memcpy( pDest, &swapHdr, headerSize ); - } - else if ( modelType == 1 ) // COLLIDE_MOPP - { - // The PC still unserializes these, but we don't support them - if ( g_bSwapOnWrite ) - { - collide.solids[i] = NULL; - } - - headerSize = sizeof(swapmoppsurfaceheader_t); - swapmoppsurfaceheader_t swapHdr; - Q_memcpy( &swapHdr, pSrc, headerSize ); - g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); - Q_memcpy( pDest, &swapHdr, headerSize ); - - } - else - { - // Shouldn't happen - Assert( 0 ); - } - - if ( g_bSwapOnLoad ) - { - // src needs the native header data to load the vcollides - Q_memcpy( pSrc, pDest, headerSize ); - } - // HACK: Need either surfaceSize or moppSize - both sit at the same offset in the structure - swapmoppsurfaceheader_t *hdr = (swapmoppsurfaceheader_t*)pSrc; - pSrc += hdr->size + sizeof(int); - pDest += hdr->size + sizeof(int); - offsets[i] = hdr->size; - } - - pSrc = pSrcBase; - pDest = pDestBase; - if ( g_bSwapOnLoad ) - { - physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, true ); - } - - // Write out the ledge tree data - for ( int i = 0; i < pPhysModel->solidCount; ++i ) - { - if ( collide.solids[i] ) - { - // skip over the size member - pSrc += sizeof(int); - pDest += sizeof(int); - int offset = physcollision->CollideWrite( (char*)pDest, collide.solids[i], g_bSwapOnWrite ); - pSrc += offset; - pDest += offset; - } - else - { - pSrc += offsets[i] + sizeof(int); - pDest += offsets[i] + sizeof(int); - } - } - - // copy the keyvalues data - Q_memcpy( pDest, pSrc, pPhysModel->keydataSize ); - pDest += pPhysModel->keydataSize; - pSrc += pPhysModel->keydataSize; - - // Free the memory - physcollision->VCollideUnload( &collide ); - delete [] offsets; - } - - // avoid infinite loop on badly formed file - if ( (pSrc - basePtr) > count ) - break; - - } while ( pPhysModel->dataSize > 0 ); - - free( pSrcAlignedBase ); -} - - -// UNDONE: This code is not yet tested. -static void SwapPhysdispLump( byte *pDest, byte *pSrc, int count ) -{ - // the format of this lump is one unsigned short dispCount, then dispCount unsigned shorts of sizes - // followed by an array of variable length (each element is the length of the corresponding entry in the - // previous table) byte-stream data structure of the displacement collision models - // these byte-stream structs are endian-neutral because each element is byte-sized - unsigned short dispCount = *(unsigned short*)pSrc; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &dispCount ); - } - g_Swap.SwapBufferToTargetEndian( (unsigned short*)pDest, (unsigned short*)pSrc, dispCount + 1 ); - - const int nBytes = (dispCount + 1) * sizeof( unsigned short ); - pSrc += nBytes; - pDest += nBytes; - count -= nBytes; - - g_Swap.SwapBufferToTargetEndian( pDest, pSrc, count ); -} - - -static void SwapVisibilityLump( byte *pDest, byte *pSrc, int count ) -{ - int firstInt = *(int*)pSrc; - if ( g_bSwapOnLoad ) - { - g_Swap.SwapBufferToTargetEndian( &firstInt ); - } - int intCt = firstInt * 2 + 1; - const int hdrSize = intCt * sizeof(int); - g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, intCt ); - g_Swap.SwapBufferToTargetEndian( pDest + hdrSize, pSrc + hdrSize, count - hdrSize ); -} - -//============================================================================= -void Lumps_Init( void ) -{ - memset( &g_Lumps, 0, sizeof(g_Lumps) ); -} - -int LumpVersion( int lump ) -{ - return g_pBSPHeader->lumps[lump].version; -} - -bool HasLump( int lump ) -{ - return g_pBSPHeader->lumps[lump].filelen > 0; -} - -void ValidateLump( int lump, int length, int size, int forceVersion ) -{ - if ( length % size ) - { - Error( "ValidateLump: odd size for lump %d", lump ); - } - - if ( forceVersion >= 0 && forceVersion != g_pBSPHeader->lumps[lump].version ) - { - Error( "ValidateLump: old version for lump %d in map!", lump ); - } -} - -//----------------------------------------------------------------------------- -// Add Lumps of integral types without datadescs -//----------------------------------------------------------------------------- -template< class T > -int CopyLumpInternal( int fieldType, int lump, T *dest, int forceVersion ) -{ - g_Lumps.bLumpParsed[lump] = true; - - // Vectors are passed in as floats - int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); - unsigned int length = g_pBSPHeader->lumps[lump].filelen; - unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; - - // count must be of the integral type - unsigned int count = length / sizeof(T); - - ValidateLump( lump, length, fieldSize, forceVersion ); - - if ( g_bSwapOnLoad ) - { - switch( lump ) - { - case LUMP_VISIBILITY: - SwapVisibilityLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - break; - - case LUMP_PHYSCOLLIDE: - // SwapPhyscollideLump may change size - SwapPhyscollideLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - length = count; - break; - - case LUMP_PHYSDISP: - SwapPhysdispLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); - break; - - default: - g_Swap.SwapBufferToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); - break; - } - } - else - { - memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); - } - - // Return actual count of elements - return length / fieldSize; -} - -template< class T > -int CopyLump( int fieldType, int lump, T *dest, int forceVersion = -1 ) -{ - return CopyLumpInternal( fieldType, lump, dest, forceVersion ); -} - -template< class T > -void CopyLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - Assert( fieldType != FIELD_VECTOR ); // TODO: Support this if necessary - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); -} - -template< class T > -void CopyOptionalLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - // not fatal if not present - if ( !HasLump( lump ) ) - return; - - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); -} - -template< class T > -int CopyVariableLump( int fieldType, int lump, void **dest, int forceVersion = -1 ) -{ - int length = g_pBSPHeader->lumps[lump].filelen; - *dest = malloc( length ); - - return CopyLumpInternal( fieldType, lump, (T*)*dest, forceVersion ); -} - -//----------------------------------------------------------------------------- -// Add Lumps of object types with datadescs -//----------------------------------------------------------------------------- -template< class T > -int CopyLumpInternal( int lump, T *dest, int forceVersion ) -{ - g_Lumps.bLumpParsed[lump] = true; - - unsigned int length = g_pBSPHeader->lumps[lump].filelen; - unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; - unsigned int count = length / sizeof(T); - - ValidateLump( lump, length, sizeof(T), forceVersion ); - - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); - } - else - { - memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); - } - - return count; -} - -template< class T > -int CopyLump( int lump, T *dest, int forceVersion = -1 ) -{ - return CopyLumpInternal( lump, dest, forceVersion ); -} - -template< class T > -void CopyLump( int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( lump, dest.Base(), forceVersion ); -} - -template< class T > -void CopyOptionalLump( int lump, CUtlVector &dest, int forceVersion = -1 ) -{ - // not fatal if not present - if ( !HasLump( lump ) ) - return; - - dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); - CopyLumpInternal( lump, dest.Base(), forceVersion ); -} - -template< class T > -int CopyVariableLump( int lump, void **dest, int forceVersion = -1 ) -{ - int length = g_pBSPHeader->lumps[lump].filelen; - *dest = malloc( length ); - - return CopyLumpInternal( lump, (T*)*dest, forceVersion ); -} - -//----------------------------------------------------------------------------- -// Add/Write unknown lumps -//----------------------------------------------------------------------------- -void Lumps_Parse( void ) -{ - int i; - - for ( i = 0; i < HEADER_LUMPS; i++ ) - { - if ( !g_Lumps.bLumpParsed[i] && g_pBSPHeader->lumps[i].filelen ) - { - g_Lumps.size[i] = CopyVariableLump( FIELD_CHARACTER, i, &g_Lumps.pLumps[i], -1 ); - Msg( "Reading unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); - } - } -} - -void Lumps_Write( void ) -{ - int i; - - for ( i = 0; i < HEADER_LUMPS; i++ ) - { - if ( g_Lumps.size[i] ) - { - Msg( "Writing unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); - AddLump( i, (byte*)g_Lumps.pLumps[i], g_Lumps.size[i] ); - } - if ( g_Lumps.pLumps[i] ) - { - free( g_Lumps.pLumps[i] ); - g_Lumps.pLumps[i] = NULL; - } - } -} - -int LoadLeafs( void ) -{ -#if defined( BSP_USE_LESS_MEMORY ) - dleafs = (dleaf_t*)malloc( g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); -#endif - - switch ( LumpVersion( LUMP_LEAFS ) ) - { - case 0: - { - g_Lumps.bLumpParsed[LUMP_LEAFS] = true; - int length = g_pBSPHeader->lumps[LUMP_LEAFS].filelen; - int size = sizeof( dleaf_version_0_t ); - if ( length % size ) - { - Error( "odd size for LUMP_LEAFS\n" ); - } - int count = length / size; - - void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAFS].fileofs ); - dleaf_version_0_t *pSrc = (dleaf_version_0_t *)pSrcBase; - dleaf_t *pDst = dleafs; - - // version 0 predates HDR, build the LDR - g_LeafAmbientLightingLDR.SetCount( count ); - g_LeafAmbientIndexLDR.SetCount( count ); - - dleafambientlighting_t *pDstLeafAmbientLighting = &g_LeafAmbientLightingLDR[0]; - for ( int i = 0; i < count; i++ ) - { - g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexLDR[i].firstAmbientSample = i; - - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( pSrc ); - } - // pDst is a subset of pSrc; - *pDst = *( ( dleaf_t * )( void * )pSrc ); - pDstLeafAmbientLighting->cube = pSrc->m_AmbientLighting; - pDstLeafAmbientLighting->x = pDstLeafAmbientLighting->y = pDstLeafAmbientLighting->z = pDstLeafAmbientLighting->pad = 0; - pDst++; - pSrc++; - pDstLeafAmbientLighting++; - } - return count; - } - - case 1: - return CopyLump( LUMP_LEAFS, dleafs ); - - default: - Assert( 0 ); - Error( "Unknown LUMP_LEAFS version\n" ); - return 0; - } -} - -void LoadLeafAmbientLighting( int numLeafs ) -{ - if ( LumpVersion( LUMP_LEAFS ) == 0 ) - { - // an older leaf version already built the LDR ambient lighting on load - return; - } - - // old BSP with ambient, or new BSP with no lighting, convert ambient light to new format or create dummy ambient - if ( !HasLump( LUMP_LEAF_AMBIENT_INDEX ) ) - { - // a bunch of legacy maps, have these lumps with garbage versions - // expect them to be NOT the current version - if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING) ) - { - Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - } - if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING_HDR) ) - { - Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - } - - void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].fileofs ); - CompressedLightCube *pSrc = NULL; - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING ) ) - { - pSrc = (CompressedLightCube*)pSrcBase; - } - g_LeafAmbientIndexLDR.SetCount( numLeafs ); - g_LeafAmbientLightingLDR.SetCount( numLeafs ); - - void *pSrcBaseHDR = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].fileofs ); - CompressedLightCube *pSrcHDR = NULL; - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) - { - pSrcHDR = (CompressedLightCube*)pSrcBaseHDR; - } - g_LeafAmbientIndexHDR.SetCount( numLeafs ); - g_LeafAmbientLightingHDR.SetCount( numLeafs ); - - for ( int i = 0; i < numLeafs; i++ ) - { - g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexLDR[i].firstAmbientSample = i; - g_LeafAmbientIndexHDR[i].ambientSampleCount = 1; - g_LeafAmbientIndexHDR[i].firstAmbientSample = i; - - Q_memset( &g_LeafAmbientLightingLDR[i], 0, sizeof(g_LeafAmbientLightingLDR[i]) ); - Q_memset( &g_LeafAmbientLightingHDR[i], 0, sizeof(g_LeafAmbientLightingHDR[i]) ); - - if ( pSrc ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pSrc[i] ); - } - g_LeafAmbientLightingLDR[i].cube = pSrc[i]; - } - if ( pSrcHDR ) - { - if ( g_bSwapOnLoad ) - { - g_Swap.SwapFieldsToTargetEndian( &pSrcHDR[i] ); - } - g_LeafAmbientLightingHDR[i].cube = pSrcHDR[i]; - } - } - - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; - } - else - { - CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR ); - CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); - } -} - -void ValidateHeader( const char *filename, const dheader_t *pHeader ) -{ - if ( pHeader->ident != IDBSPHEADER ) - { - Error ("%s is not a IBSP file", filename); - } - if ( pHeader->version < MINBSPVERSION || pHeader->version > BSPVERSION ) - { - Error ("%s is version %i, not %i", filename, pHeader->version, BSPVERSION); - } -} - -//----------------------------------------------------------------------------- -// Low level BSP opener for external parsing. Parses headers, but nothing else. -// You must close the BSP, via CloseBSPFile(). -//----------------------------------------------------------------------------- -void OpenBSPFile( const char *filename ) -{ - Lumps_Init(); - - // load the file header - LoadFile( filename, (void **)&g_pBSPHeader ); - - if ( g_bSwapOnLoad ) - { - g_Swap.ActivateByteSwapping( true ); - g_Swap.SwapFieldsToTargetEndian( g_pBSPHeader ); - } - - ValidateHeader( filename, g_pBSPHeader ); - - g_MapRevision = g_pBSPHeader->mapRevision; -} - -//----------------------------------------------------------------------------- -// CloseBSPFile -//----------------------------------------------------------------------------- -void CloseBSPFile( void ) -{ - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -//----------------------------------------------------------------------------- -// LoadBSPFile -//----------------------------------------------------------------------------- -void LoadBSPFile( const char *filename ) -{ - OpenBSPFile( filename ); - - nummodels = CopyLump( LUMP_MODELS, dmodels ); - numvertexes = CopyLump( LUMP_VERTEXES, dvertexes ); - numplanes = CopyLump( LUMP_PLANES, dplanes ); - numleafs = LoadLeafs(); - numnodes = CopyLump( LUMP_NODES, dnodes ); - CopyLump( LUMP_TEXINFO, texinfo ); - numtexdata = CopyLump( LUMP_TEXDATA, dtexdata ); - - CopyLump( LUMP_DISPINFO, g_dispinfo ); - CopyLump( LUMP_DISP_VERTS, g_DispVerts ); - CopyLump( LUMP_DISP_TRIS, g_DispTris ); - CopyLump( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); - CopyLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); - - numfaces = CopyLump(LUMP_FACES, dfaces, LUMP_FACES_VERSION); - if ( HasLump( LUMP_FACES_HDR ) ) - numfaces_hdr = CopyLump( LUMP_FACES_HDR, dfaces_hdr, LUMP_FACES_VERSION ); - else - numfaces_hdr = 0; - - CopyOptionalLump( LUMP_FACEIDS, dfaceids ); - - g_numprimitives = CopyLump( LUMP_PRIMITIVES, g_primitives ); - g_numprimverts = CopyLump( LUMP_PRIMVERTS, g_primverts ); - g_numprimindices = CopyLump( FIELD_SHORT, LUMP_PRIMINDICES, g_primindices ); - numorigfaces = CopyLump( LUMP_ORIGINALFACES, dorigfaces ); // original faces - numleaffaces = CopyLump( FIELD_SHORT, LUMP_LEAFFACES, dleaffaces ); - numleafbrushes = CopyLump( FIELD_SHORT, LUMP_LEAFBRUSHES, dleafbrushes ); - numsurfedges = CopyLump( FIELD_INTEGER, LUMP_SURFEDGES, dsurfedges ); - numedges = CopyLump( LUMP_EDGES, dedges ); - numbrushes = CopyLump( LUMP_BRUSHES, dbrushes ); - numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides ); - numareas = CopyLump( LUMP_AREAS, dareas ); - numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals ); - - visdatasize = CopyLump ( FIELD_CHARACTER, LUMP_VISIBILITY, dvisdata ); - CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); - CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); - - LoadLeafAmbientLighting( numleafs ); - - CopyLump( FIELD_CHARACTER, LUMP_ENTITIES, dentdata ); - numworldlightsLDR = CopyLump( LUMP_WORLDLIGHTS, dworldlightsLDR ); - numworldlightsHDR = CopyLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR ); - - numleafwaterdata = CopyLump( LUMP_LEAFWATERDATA, dleafwaterdata ); - g_PhysCollideSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSCOLLIDE, (void**)&g_pPhysCollide ); - g_PhysDispSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSDISP, (void**)&g_pPhysDisp ); - - g_numvertnormals = CopyLump( FIELD_VECTOR, LUMP_VERTNORMALS, (float*)g_vertnormals ); - g_numvertnormalindices = CopyLump( FIELD_SHORT, LUMP_VERTNORMALINDICES, g_vertnormalindices ); - - g_nClipPortalVerts = CopyLump( FIELD_VECTOR, LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts ); - g_nCubemapSamples = CopyLump( LUMP_CUBEMAPS, g_CubemapSamples ); - - CopyLump( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); - CopyLump( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); - - g_nOverlayCount = CopyLump( LUMP_OVERLAYS, g_Overlays ); - g_nWaterOverlayCount = CopyLump( LUMP_WATEROVERLAYS, g_WaterOverlays ); - CopyLump( LUMP_OVERLAY_FADES, g_OverlayFades ); - - dflagslump_t flags_lump; - - if ( HasLump( LUMP_MAP_FLAGS ) ) - CopyLump ( LUMP_MAP_FLAGS, &flags_lump ); - else - memset( &flags_lump, 0, sizeof( flags_lump ) ); // default flags to 0 - - g_LevelFlags = flags_lump.m_LevelFlags; - - LoadOcclusionLump(); - - CopyLump( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater ); - - /* - int crap; - for( crap = 0; crap < g_nBSPStringTable; crap++ ) - { - Msg( "stringtable %d", ( int )crap ); - Msg( " %d:", ( int )g_BSPStringTable[crap] ); - puts( &g_BSPStringData[g_BSPStringTable[crap]] ); - puts( "\n" ); - } - */ - - // Load PAK file lump into appropriate data structure - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - } - else - { - GetPakFile()->Reset(); - } - - free( pakbuffer ); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - - // NOTE: Do NOT call CopyLump after Lumps_Parse() it parses all un-Copied lumps - // parse any additional lumps - Lumps_Parse(); - - // everything has been copied out - CloseBSPFile(); - - g_Swap.ActivateByteSwapping( false ); -} - -//----------------------------------------------------------------------------- -// Reset any state. -//----------------------------------------------------------------------------- -void UnloadBSPFile() -{ - nummodels = 0; - numvertexes = 0; - numplanes = 0; - - numleafs = 0; -#if defined( BSP_USE_LESS_MEMORY ) - if ( dleafs ) - { - free( dleafs ); - dleafs = NULL; - } -#endif - - numnodes = 0; - texinfo.Purge(); - numtexdata = 0; - - g_dispinfo.Purge(); - g_DispVerts.Purge(); - g_DispTris.Purge(); - - g_DispLightmapSamplePositions.Purge(); - g_FaceMacroTextureInfos.Purge(); - - numfaces = 0; - numfaces_hdr = 0; - - dfaceids.Purge(); - - g_numprimitives = 0; - g_numprimverts = 0; - g_numprimindices = 0; - numorigfaces = 0; - numleaffaces = 0; - numleafbrushes = 0; - numsurfedges = 0; - numedges = 0; - numbrushes = 0; - numbrushsides = 0; - numareas = 0; - numareaportals = 0; - - visdatasize = 0; - dlightdataLDR.Purge(); - dlightdataHDR.Purge(); - - g_LeafAmbientLightingLDR.Purge(); - g_LeafAmbientLightingHDR.Purge(); - g_LeafAmbientIndexHDR.Purge(); - g_LeafAmbientIndexLDR.Purge(); - - dentdata.Purge(); - numworldlightsLDR = 0; - numworldlightsHDR = 0; - - numleafwaterdata = 0; - - if ( g_pPhysCollide ) - { - free( g_pPhysCollide ); - g_pPhysCollide = NULL; - } - g_PhysCollideSize = 0; - - if ( g_pPhysDisp ) - { - free( g_pPhysDisp ); - g_pPhysDisp = NULL; - } - g_PhysDispSize = 0; - - g_numvertnormals = 0; - g_numvertnormalindices = 0; - - g_nClipPortalVerts = 0; - g_nCubemapSamples = 0; - - g_TexDataStringData.Purge(); - g_TexDataStringTable.Purge(); - - g_nOverlayCount = 0; - g_nWaterOverlayCount = 0; - - g_LevelFlags = 0; - - g_OccluderData.Purge(); - g_OccluderPolyData.Purge(); - g_OccluderVertexIndices.Purge(); - - g_GameLumps.DestroyAllGameLumps(); - - for ( int i = 0; i < HEADER_LUMPS; i++ ) - { - if ( g_Lumps.pLumps[i] ) - { - free( g_Lumps.pLumps[i] ); - g_Lumps.pLumps[i] = NULL; - } - } - - ReleasePakFileLumps(); -} - -//----------------------------------------------------------------------------- -// LoadBSPFileFilesystemOnly -//----------------------------------------------------------------------------- -void LoadBSPFile_FileSystemOnly( const char *filename ) -{ - Lumps_Init(); - - // - // load the file header - // - LoadFile( filename, (void **)&g_pBSPHeader ); - - ValidateHeader( filename, g_pBSPHeader ); - - // Load PAK file lump into appropriate data structure - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer, 1 ); - if ( paksize > 0 ) - { - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - } - else - { - GetPakFile()->Reset(); - } - - free( pakbuffer ); - - // everything has been copied out - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ) -{ - Lumps_Init(); - - // - // load the file header - // - LoadFile( pBSPFileName, (void **)&g_pBSPHeader); - - ValidateHeader( pBSPFileName, g_pBSPHeader ); - - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - FILE *fp; - fp = fopen( pZipFileName, "wb" ); - if( !fp ) - { - fprintf( stderr, "can't open %s\n", pZipFileName ); - return; - } - - fwrite( pakbuffer, paksize, 1, fp ); - fclose( fp ); - } - else - { - fprintf( stderr, "zip file is zero length!\n" ); - } -} - -/* -============= -LoadBSPFileTexinfo - -Only loads the texinfo lump, so qdata can scan for textures -============= -*/ -void LoadBSPFileTexinfo( const char *filename ) -{ - FILE *f; - int length, ofs; - - g_pBSPHeader = (dheader_t*)malloc( sizeof(dheader_t) ); - - f = fopen( filename, "rb" ); - fread( g_pBSPHeader, sizeof(dheader_t), 1, f); - - ValidateHeader( filename, g_pBSPHeader ); - - length = g_pBSPHeader->lumps[LUMP_TEXINFO].filelen; - ofs = g_pBSPHeader->lumps[LUMP_TEXINFO].fileofs; - - int nCount = length / sizeof(texinfo_t); - - texinfo.Purge(); - texinfo.AddMultipleToTail( nCount ); - - fseek( f, ofs, SEEK_SET ); - fread( texinfo.Base(), length, 1, f ); - fclose( f ); - - // everything has been copied out - free( g_pBSPHeader ); - g_pBSPHeader = NULL; -} - -static void AddLumpInternal( int lumpnum, void *data, int len, int version ) -{ - lump_t *lump; - - g_Lumps.size[lumpnum] = 0; // mark it written - - lump = &g_pBSPHeader->lumps[lumpnum]; - - lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); - lump->filelen = len; - lump->version = version; - lump->uncompressedSize = 0; - - SafeWrite( g_hBSPFile, data, len ); - - // pad out to the next dword - AlignFilePosition( g_hBSPFile, 4 ); -} - -template< class T > -static void SwapInPlace( T *pData, int count ) -{ - if ( !pData ) - return; - - // use the datadesc to swap the fields in place - g_Swap.SwapFieldsToTargetEndian( (T*)pData, pData, count ); -} - -template< class T > -static void SwapInPlace( int fieldType, T *pData, int count ) -{ - if ( !pData ) - return; - - // swap the data in place - g_Swap.SwapBufferToTargetEndian( (T*)pData, (T*)pData, count ); -} - -//----------------------------------------------------------------------------- -// Add raw data chunk to file (not a lump) -//----------------------------------------------------------------------------- -template< class T > -static void WriteData( int fieldType, T *pData, int count ) -{ - if ( g_bSwapOnWrite ) - { - SwapInPlace( fieldType, pData, count ); - } - SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); -} - -template< class T > -static void WriteData( T *pData, int count ) -{ - if ( g_bSwapOnWrite ) - { - SwapInPlace( pData, count ); - } - SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); -} - -//----------------------------------------------------------------------------- -// Add Lump of object types with datadescs -//----------------------------------------------------------------------------- -template< class T > -static void AddLump( int lumpnum, T *pData, int count, int version ) -{ - AddLumpInternal( lumpnum, pData, count * sizeof(T), version ); -} - -template< class T > -static void AddLump( int lumpnum, CUtlVector &data, int version ) -{ - AddLumpInternal( lumpnum, data.Base(), data.Count() * sizeof(T), version ); -} - -/* -============= -WriteBSPFile - -Swaps the bsp file in place, so it should not be referenced again -============= -*/ -void WriteBSPFile( const char *filename, char *pUnused ) -{ - if ( texinfo.Count() > MAX_MAP_TEXINFO ) - { - Error( "Map has too many texinfos (has %d, can have at most %d)\n", texinfo.Count(), MAX_MAP_TEXINFO ); - return; - } - - dheader_t outHeader; - g_pBSPHeader = &outHeader; - memset( g_pBSPHeader, 0, sizeof( dheader_t ) ); - - g_pBSPHeader->ident = IDBSPHEADER; - g_pBSPHeader->version = BSPVERSION; - g_pBSPHeader->mapRevision = g_MapRevision; - - g_hBSPFile = SafeOpenWrite( filename ); - WriteData( g_pBSPHeader ); // overwritten later - - AddLump( LUMP_PLANES, dplanes, numplanes ); - AddLump( LUMP_LEAFS, dleafs, numleafs, LUMP_LEAFS_VERSION ); - AddLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - AddLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); - AddLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); - AddLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); - - AddLump( LUMP_VERTEXES, dvertexes, numvertexes ); - AddLump( LUMP_NODES, dnodes, numnodes ); - AddLump( LUMP_TEXINFO, texinfo ); - AddLump( LUMP_TEXDATA, dtexdata, numtexdata ); - - AddLump( LUMP_DISPINFO, g_dispinfo ); - AddLump( LUMP_DISP_VERTS, g_DispVerts ); - AddLump( LUMP_DISP_TRIS, g_DispTris ); - AddLump( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); - AddLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); - - AddLump( LUMP_PRIMITIVES, g_primitives, g_numprimitives ); - AddLump( LUMP_PRIMVERTS, g_primverts, g_numprimverts ); - AddLump( LUMP_PRIMINDICES, g_primindices, g_numprimindices ); - AddLump( LUMP_FACES, dfaces, numfaces, LUMP_FACES_VERSION ); - if (numfaces_hdr) - AddLump( LUMP_FACES_HDR, dfaces_hdr, numfaces_hdr, LUMP_FACES_VERSION ); - AddLump ( LUMP_FACEIDS, dfaceids, numfaceids ); - - AddLump( LUMP_ORIGINALFACES, dorigfaces, numorigfaces ); // original faces lump - AddLump( LUMP_BRUSHES, dbrushes, numbrushes ); - AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides ); - AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces ); - AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes ); - AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges ); - AddLump( LUMP_EDGES, dedges, numedges ); - AddLump( LUMP_MODELS, dmodels, nummodels ); - AddLump( LUMP_AREAS, dareas, numareas ); - AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals ); - - AddLump( LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); - AddLump( LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); - AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); - AddLump( LUMP_ENTITIES, dentdata ); - AddLump( LUMP_WORLDLIGHTS, dworldlightsLDR, numworldlightsLDR ); - AddLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR, numworldlightsHDR ); - AddLump( LUMP_LEAFWATERDATA, dleafwaterdata, numleafwaterdata ); - - AddOcclusionLump(); - - dflagslump_t flags_lump; - flags_lump.m_LevelFlags = g_LevelFlags; - AddLump( LUMP_MAP_FLAGS, &flags_lump, 1 ); - - // NOTE: This is just for debugging, so it is disabled in release maps -#if 0 - // add the vis portals to the BSP for visualization - AddLump( LUMP_PORTALS, dportals, numportals ); - AddLump( LUMP_CLUSTERS, dclusters, numclusters ); - AddLump( LUMP_PORTALVERTS, dportalverts, numportalverts ); - AddLump( LUMP_CLUSTERPORTALS, dclusterportals, numclusterportals ); -#endif - - AddLump( LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts, g_nClipPortalVerts * 3 ); - AddLump( LUMP_CUBEMAPS, g_CubemapSamples, g_nCubemapSamples ); - AddLump( LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); - AddLump( LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); - AddLump( LUMP_OVERLAYS, g_Overlays, g_nOverlayCount ); - AddLump( LUMP_WATEROVERLAYS, g_WaterOverlays, g_nWaterOverlayCount ); - AddLump( LUMP_OVERLAY_FADES, g_OverlayFades, g_nOverlayCount ); - - if ( g_pPhysCollide ) - { - AddLump( LUMP_PHYSCOLLIDE, g_pPhysCollide, g_PhysCollideSize ); - } - - if ( g_pPhysDisp ) - { - AddLump ( LUMP_PHYSDISP, g_pPhysDisp, g_PhysDispSize ); - } - - AddLump( LUMP_VERTNORMALS, (float*)g_vertnormals, g_numvertnormals * 3 ); - AddLump( LUMP_VERTNORMALINDICES, g_vertnormalindices, g_numvertnormalindices ); - - AddLump( LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater, numleafs ); - - AddGameLumps(); - - // Write pakfile lump to disk - WritePakFileLump(); - - // NOTE: Do NOT call AddLump after Lumps_Write() it writes all un-Added lumps - // write any additional lumps - Lumps_Write(); - - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); -} - -// Generate the next clear lump filename for the bsp file -bool GenerateNextLumpFileName( const char *bspfilename, char *lumpfilename, int buffsize ) -{ - for (int i = 0; i < MAX_LUMPFILES; i++) - { - GenerateLumpFileName( bspfilename, lumpfilename, buffsize, i ); - - if ( !g_pFileSystem->FileExists( lumpfilename ) ) - return true; - } - - return false; -} - -void WriteLumpToFile( char *filename, int lump ) -{ - if ( !HasLump(lump) ) - return; - - char lumppre[MAX_PATH]; - if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) - { - Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); - return; - } - - // Open the file - FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); - if ( !lumpfile ) - { - Error ("Error opening %s! (Check for write enable)\n",filename); - return; - } - - int ofs = g_pBSPHeader->lumps[lump].fileofs; - int length = g_pBSPHeader->lumps[lump].filelen; - - // Write the header - lumpfileheader_t lumpHeader; - lumpHeader.lumpID = lump; - lumpHeader.lumpVersion = LumpVersion(lump); - lumpHeader.lumpLength = length; - lumpHeader.mapRevision = LittleLong( g_MapRevision ); - lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header - SafeWrite (lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); - - // Write the lump - SafeWrite (lumpfile, (byte *)g_pBSPHeader + ofs, length); -} - -void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ) -{ - char lumppre[MAX_PATH]; - if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) - { - Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); - return; - } - - // Open the file - FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); - if ( !lumpfile ) - { - Error ("Error opening %s! (Check for write enable)\n",filename); - return; - } - - // Write the header - lumpfileheader_t lumpHeader; - lumpHeader.lumpID = lump; - lumpHeader.lumpVersion = nLumpVersion; - lumpHeader.lumpLength = nBufLen; - lumpHeader.mapRevision = LittleLong( g_MapRevision ); - lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header - SafeWrite( lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); - - // Write the lump - SafeWrite( lumpfile, pBuffer, nBufLen ); - - g_pFileSystem->Close( lumpfile ); -} - - -//============================================================================ -#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) -#define ENTRYSIZE(a) (sizeof(*(a))) - -int ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) -{ - float percentage = maxitems ? items * 100.0 / maxitems : 0.0; - - Msg("%-17.17s %8i/%-8i %8i/%-8i (%4.1f%%) ", - szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); - if ( percentage > 80.0 ) - Msg( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - Msg( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - Msg( "SIZE OVERFLOW!!!\n" ); - else - Msg( "\n" ); - return items * itemsize; -} - -int GlobUsage( const char *szItem, int itemstorage, int maxstorage ) -{ - float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; - Msg("%-17.17s [variable] %8i/%-8i (%4.1f%%) ", - szItem, itemstorage, maxstorage, percentage ); - if ( percentage > 80.0 ) - Msg( "VERY FULL!\n" ); - else if ( percentage > 95.0 ) - Msg( "SIZE DANGER!\n" ); - else if ( percentage > 99.9 ) - Msg( "SIZE OVERFLOW!!!\n" ); - else - Msg( "\n" ); - return itemstorage; -} - -/* -============= -PrintBSPFileSizes - -Dumps info about current file -============= -*/ -void PrintBSPFileSizes (void) -{ - int totalmemory = 0; - -// if (!num_entities) -// ParseEntities (); - - Msg("\n"); - Msg( "%-17s %16s %16s %9s \n", "Object names", "Objects/Maxobjs", "Memory / Maxmem", "Fullness" ); - Msg( "%-17s %16s %16s %9s \n", "------------", "---------------", "---------------", "--------" ); - - totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); - totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES(dbrushes), ENTRYSIZE(dbrushes) ); - totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES(dbrushsides), ENTRYSIZE(dbrushsides) ); - totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); - totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); - totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); - totalmemory += ArrayUsage( "texinfos", texinfo.Count(),MAX_MAP_TEXINFO, sizeof(texinfo_t) ); - totalmemory += ArrayUsage( "texdata", numtexdata, ENTRIES(dtexdata), ENTRYSIZE(dtexdata) ); - - totalmemory += ArrayUsage( "dispinfos", g_dispinfo.Count(), 0, sizeof( ddispinfo_t ) ); - totalmemory += ArrayUsage( "disp_verts", g_DispVerts.Count(), 0, sizeof( g_DispVerts[0] ) ); - totalmemory += ArrayUsage( "disp_tris", g_DispTris.Count(), 0, sizeof( g_DispTris[0] ) ); - totalmemory += ArrayUsage( "disp_lmsamples",g_DispLightmapSamplePositions.Count(),0,sizeof( g_DispLightmapSamplePositions[0] ) ); - - totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); - totalmemory += ArrayUsage( "hdr faces", numfaces_hdr, ENTRIES(dfaces_hdr), ENTRYSIZE(dfaces_hdr) ); - totalmemory += ArrayUsage( "origfaces", numorigfaces, ENTRIES(dorigfaces), ENTRYSIZE(dorigfaces) ); // original faces - totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); - totalmemory += ArrayUsage( "leaffaces", numleaffaces, ENTRIES(dleaffaces), ENTRYSIZE(dleaffaces) ); - totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES(dleafbrushes), ENTRYSIZE(dleafbrushes) ); - totalmemory += ArrayUsage( "areas", numareas, ENTRIES(dareas), ENTRYSIZE(dareas) ); - totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); - totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); - totalmemory += ArrayUsage( "LDR worldlights", numworldlightsLDR, ENTRIES(dworldlightsLDR), ENTRYSIZE(dworldlightsLDR) ); - totalmemory += ArrayUsage( "HDR worldlights", numworldlightsHDR, ENTRIES(dworldlightsHDR), ENTRYSIZE(dworldlightsHDR) ); - - totalmemory += ArrayUsage( "leafwaterdata", numleafwaterdata,ENTRIES(dleafwaterdata), ENTRYSIZE(dleafwaterdata) ); - totalmemory += ArrayUsage( "waterstrips", g_numprimitives,ENTRIES(g_primitives), ENTRYSIZE(g_primitives) ); - totalmemory += ArrayUsage( "waterverts", g_numprimverts, ENTRIES(g_primverts), ENTRYSIZE(g_primverts) ); - totalmemory += ArrayUsage( "waterindices", g_numprimindices,ENTRIES(g_primindices),ENTRYSIZE(g_primindices) ); - totalmemory += ArrayUsage( "cubemapsamples", g_nCubemapSamples,ENTRIES(g_CubemapSamples),ENTRYSIZE(g_CubemapSamples) ); - totalmemory += ArrayUsage( "overlays", g_nOverlayCount, ENTRIES(g_Overlays), ENTRYSIZE(g_Overlays) ); - - totalmemory += GlobUsage( "LDR lightdata", dlightdataLDR.Count(), 0 ); - totalmemory += GlobUsage( "HDR lightdata", dlightdataHDR.Count(), 0 ); - totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); - totalmemory += GlobUsage( "entdata", dentdata.Count(), 384*1024 ); // goal is <384K - - totalmemory += ArrayUsage( "LDR ambient table", g_LeafAmbientIndexLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexLDR[0] ) ); - totalmemory += ArrayUsage( "HDR ambient table", g_LeafAmbientIndexHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexHDR[0] ) ); - totalmemory += ArrayUsage( "LDR leaf ambient lighting", g_LeafAmbientLightingLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingLDR[0] ) ); - totalmemory += ArrayUsage( "HDR leaf ambient lighting", g_LeafAmbientLightingHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingHDR[0] ) ); - - totalmemory += ArrayUsage( "occluders", g_OccluderData.Count(), 0, sizeof( g_OccluderData[0] ) ); - totalmemory += ArrayUsage( "occluder polygons", g_OccluderPolyData.Count(), 0, sizeof( g_OccluderPolyData[0] ) ); - totalmemory += ArrayUsage( "occluder vert ind",g_OccluderVertexIndices.Count(),0, sizeof( g_OccluderVertexIndices[0] ) ); - - GameLumpHandle_t h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "detail props", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "HDR dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); - h = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if (h != g_GameLumps.InvalidGameLump()) - totalmemory += GlobUsage( "static props", 1, g_GameLumps.GameLumpSize(h) ); - - totalmemory += GlobUsage( "pakfile", GetPakFile()->EstimateSize(), 0 ); - // HACKHACK: Set physics limit at 4MB, in reality this is totally dynamic - totalmemory += GlobUsage( "physics", g_PhysCollideSize, 4*1024*1024 ); - totalmemory += GlobUsage( "physics terrain", g_PhysDispSize, 1*1024*1024 ); - - Msg( "\nLevel flags = %x\n", g_LevelFlags ); - - Msg( "\n" ); - - int triangleCount = 0; - - for ( int i = 0; i < numfaces; i++ ) - { - // face tris = numedges - 2 - triangleCount += dfaces[i].numedges - 2; - } - Msg("Total triangle count: %d\n", triangleCount ); - - // UNDONE: - // areaportals, portals, texdata, clusters, worldlights, portalverts -} - -/* -============= -PrintBSPPackDirectory - -Dumps a list of files stored in the bsp pack. -============= -*/ -void PrintBSPPackDirectory( void ) -{ - GetPakFile()->PrintDirectory(); -} - - -//============================================ - -int num_entities; -entity_t entities[MAX_MAP_ENTITIES]; - -void StripTrailing (char *e) -{ - char *s; - - s = e + strlen(e)-1; - while (s >= e && *s <= 32) - { - *s = 0; - s--; - } -} - -/* -================= -ParseEpair -================= -*/ -epair_t *ParseEpair (void) -{ - epair_t *e; - - e = (epair_t*)malloc (sizeof(epair_t)); - memset (e, 0, sizeof(epair_t)); - - if (strlen(token) >= MAX_KEY-1) - Error ("ParseEpar: token too long"); - e->key = copystring(token); - - GetToken (false); - if (strlen(token) >= MAX_VALUE-1) - Error ("ParseEpar: token too long"); - e->value = copystring(token); - - // strip trailing spaces - StripTrailing (e->key); - StripTrailing (e->value); - - return e; -} - - -/* -================ -ParseEntity -================ -*/ -qboolean ParseEntity (void) -{ - epair_t *e; - entity_t *mapent; - - if (!GetToken (true)) - return false; - - if (Q_stricmp (token, "{") ) - Error ("ParseEntity: { not found"); - - if (num_entities == MAX_MAP_ENTITIES) - Error ("num_entities == MAX_MAP_ENTITIES"); - - mapent = &entities[num_entities]; - num_entities++; - - do - { - if (!GetToken (true)) - Error ("ParseEntity: EOF without closing brace"); - if (!Q_stricmp (token, "}") ) - break; - e = ParseEpair (); - e->next = mapent->epairs; - mapent->epairs = e; - } while (1); - - return true; -} - -/* -================ -ParseEntities - -Parses the dentdata string into entities -================ -*/ -void ParseEntities (void) -{ - num_entities = 0; - ParseFromMemory (dentdata.Base(), dentdata.Count()); - - while (ParseEntity ()) - { - } -} - - -/* -================ -UnparseEntities - -Generates the dentdata string from all the entities -================ -*/ -void UnparseEntities (void) -{ - epair_t *ep; - char line[2048]; - int i; - char key[1024], value[1024]; - - CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); - buffer.EnsureCapacity( 256 * 1024 ); - - for (i=0 ; inext) - { - strcpy (key, ep->key); - StripTrailing (key); - strcpy (value, ep->value); - StripTrailing (value); - - sprintf(line, "\"%s\" \"%s\"\n", key, value); - buffer.PutString( line ); - } - buffer.PutString("}\n"); - } - int entdatasize = buffer.TellPut()+1; - - dentdata.SetSize( entdatasize ); - memcpy( dentdata.Base(), buffer.Base(), entdatasize-1 ); - dentdata[entdatasize-1] = 0; -} - -void PrintEntity (entity_t *ent) -{ - epair_t *ep; - - Msg ("------- entity %p -------\n", ent); - for (ep=ent->epairs ; ep ; ep=ep->next) - { - Msg ("%s = %s\n", ep->key, ep->value); - } - -} - -void SetKeyValue(entity_t *ent, const char *key, const char *value) -{ - epair_t *ep; - - for (ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - { - free (ep->value); - ep->value = copystring(value); - return; - } - ep = (epair_t*)malloc (sizeof(*ep)); - ep->next = ent->epairs; - ent->epairs = ep; - ep->key = copystring(key); - ep->value = copystring(value); -} - -char *ValueForKey (entity_t *ent, char *key) -{ - for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - return ep->value; - return ""; -} - -vec_t FloatForKey (entity_t *ent, char *key) -{ - char *k = ValueForKey (ent, key); - return atof(k); -} - -vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value) -{ - for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) - if (!Q_stricmp (ep->key, key) ) - return atof( ep->value ); - return default_value; -} - - - -int IntForKey (entity_t *ent, char *key) -{ - char *k = ValueForKey (ent, key); - return atol(k); -} - -int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ) -{ - char *k = ValueForKey (ent, key); - if ( !k[0] ) - return nDefault; - return atol(k); -} - -void GetVectorForKey (entity_t *ent, char *key, Vector& vec) -{ - - char *k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - double v1, v2, v3; - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - vec[0] = v1; - vec[1] = v2; - vec[2] = v3; -} - -void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec) -{ - double v1, v2; - - char *k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = 0; - sscanf (k, "%lf %lf", &v1, &v2); - vec[0] = v1; - vec[1] = v2; -} - -void GetAnglesForKey (entity_t *ent, char *key, QAngle& angle) -{ - char *k; - double v1, v2, v3; - - k = ValueForKey (ent, key); -// scanf into doubles, then assign, so it is vec_t size independent - v1 = v2 = v3 = 0; - sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); - angle[0] = v1; - angle[1] = v2; - angle[2] = v3; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -void BuildFaceCalcWindingData( dface_t *pFace, int *points ) -{ - for( int i = 0; i < pFace->numedges; i++ ) - { - int eIndex = dsurfedges[pFace->firstedge+i]; - if( eIndex < 0 ) - { - points[i] = dedges[-eIndex].v[1]; - } - else - { - points[i] = dedges[eIndex].v[0]; - } - } -} - - -void TriStripToTriList( - unsigned short const *pTriStripIndices, - int nTriStripIndices, - unsigned short **pTriListIndices, - int *pnTriListIndices ) -{ - int nMaxTriListIndices = (nTriStripIndices - 2) * 3; - *pTriListIndices = new unsigned short[ nMaxTriListIndices ]; - *pnTriListIndices = 0; - - for( int i=0; i < nTriStripIndices - 2; i++ ) - { - if( pTriStripIndices[i] == pTriStripIndices[i+1] || - pTriStripIndices[i] == pTriStripIndices[i+2] || - pTriStripIndices[i+1] == pTriStripIndices[i+2] ) - { - } - else - { - // Flip odd numbered tris.. - if( i & 1 ) - { - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; - } - else - { - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; - (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; - } - } - } -} - - -void CalcTextureCoordsAtPoints( - float const texelsPerWorldUnits[2][4], - int const subtractOffset[2], - Vector const *pPoints, - int const nPoints, - Vector2D *pCoords ) -{ - for( int i=0; i < nPoints; i++ ) - { - for( int iCoord=0; iCoord < 2; iCoord++ ) - { - float *pDestCoord = &pCoords[i][iCoord]; - - *pDestCoord = 0; - for( int iDot=0; iDot < 3; iDot++ ) - *pDestCoord += pPoints[i][iDot] * texelsPerWorldUnits[iCoord][iDot]; - - *pDestCoord += texelsPerWorldUnits[iCoord][3]; - *pDestCoord -= subtractOffset[iCoord]; - } - } -} - - -/* -================ -CalcFaceExtents - -Fills in s->texmins[] and s->texsize[] -================ -*/ -void CalcFaceExtents(dface_t *s, int lightmapTextureMinsInLuxels[2], int lightmapTextureSizeInLuxels[2]) -{ - vec_t mins[2], maxs[2], val=0; - int i,j, e=0; - dvertex_t *v=NULL; - texinfo_t *tex=NULL; - - mins[0] = mins[1] = 1e24; - maxs[0] = maxs[1] = -1e24; - - tex = &texinfo[s->texinfo]; - - for (i=0 ; inumedges ; i++) - { - e = dsurfedges[s->firstedge+i]; - if (e >= 0) - v = dvertexes + dedges[e].v[0]; - else - v = dvertexes + dedges[-e].v[1]; - - for (j=0 ; j<2 ; j++) - { - val = v->point[0] * tex->lightmapVecsLuxelsPerWorldUnits[j][0] + - v->point[1] * tex->lightmapVecsLuxelsPerWorldUnits[j][1] + - v->point[2] * tex->lightmapVecsLuxelsPerWorldUnits[j][2] + - tex->lightmapVecsLuxelsPerWorldUnits[j][3]; - if (val < mins[j]) - mins[j] = val; - if (val > maxs[j]) - maxs[j] = val; - } - } - - int nMaxLightmapDim = (s->dispinfo == -1) ? MAX_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER; - for (i=0 ; i<2 ; i++) - { - mins[i] = ( float )floor( mins[i] ); - maxs[i] = ( float )ceil( maxs[i] ); - - lightmapTextureMinsInLuxels[i] = ( int )mins[i]; - lightmapTextureSizeInLuxels[i] = ( int )( maxs[i] - mins[i] ); - if( lightmapTextureSizeInLuxels[i] > nMaxLightmapDim + 1 ) - { - Vector point = vec3_origin; - for (int j=0 ; jnumedges ; j++) - { - e = dsurfedges[s->firstedge+j]; - v = (e<0)?dvertexes + dedges[-e].v[1] : dvertexes + dedges[e].v[0]; - point += v->point; - Warning( "Bad surface extents point: %f %f %f\n", v->point.x, v->point.y, v->point.z ); - } - point *= 1.0f/s->numedges; - Error( "Bad surface extents - surface is too big to have a lightmap\n\tmaterial %s around point (%.1f %.1f %.1f)\n\t(dimension: %d, %d>%d)\n", - TexDataStringTable_GetString( dtexdata[texinfo[s->texinfo].texdata].nameStringTableID ), - point.x, point.y, point.z, - ( int )i, - ( int )lightmapTextureSizeInLuxels[i], - ( int )( nMaxLightmapDim + 1 ) - ); - } - } -} - - -void UpdateAllFaceLightmapExtents() -{ - for( int i=0; i < numfaces; i++ ) - { - dface_t *pFace = &dfaces[i]; - - if ( texinfo[pFace->texinfo].flags & (SURF_SKY|SURF_NOLIGHT) ) - continue; // non-lit texture - - CalcFaceExtents( pFace, pFace->m_LightmapTextureMinsInLuxels, pFace->m_LightmapTextureSizeInLuxels ); - } -} - - -//----------------------------------------------------------------------------- -// -// Helper class to iterate over leaves, used by tools -// -//----------------------------------------------------------------------------- - -#define TEST_EPSILON (0.03125) - - -class CToolBSPTree : public ISpatialQuery -{ -public: - // Returns the number of leaves - int LeafCount() const; - - // Enumerates the leaves along a ray, box, etc. - bool EnumerateLeavesAtPoint( Vector const& pt, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ); - bool EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ); -}; - - -//----------------------------------------------------------------------------- -// Returns the number of leaves -//----------------------------------------------------------------------------- - -int CToolBSPTree::LeafCount() const -{ - return numleafs; -} - - -//----------------------------------------------------------------------------- -// Enumerates the leaves at a point -//----------------------------------------------------------------------------- - -bool CToolBSPTree::EnumerateLeavesAtPoint( Vector const& pt, - ISpatialLeafEnumerator* pEnum, int context ) -{ - int node = 0; - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if (DotProduct( pPlane->normal, pt ) <= pPlane->dist) - { - node = pNode->children[1]; - } - else - { - node = pNode->children[0]; - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - - -//----------------------------------------------------------------------------- -// Enumerates the leaves in a box -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesInBox_R( int node, Vector const& mins, - Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ) -{ - Vector cornermin, cornermax; - - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - // Arbitrary split plane here - for (int i = 0; i < 3; ++i) - { - if (pPlane->normal[i] >= 0) - { - cornermin[i] = mins[i]; - cornermax[i] = maxs[i]; - } - else - { - cornermin[i] = maxs[i]; - cornermax[i] = mins[i]; - } - } - - if ( (DotProduct( pPlane->normal, cornermax ) - pPlane->dist) <= -TEST_EPSILON ) - { - node = pNode->children[1]; - } - else if ( (DotProduct( pPlane->normal, cornermin ) - pPlane->dist) >= TEST_EPSILON ) - { - node = pNode->children[0]; - } - else - { - if (!EnumerateLeavesInBox_R( pNode->children[0], mins, maxs, pEnum, context )) - { - return false; - } - - return EnumerateLeavesInBox_R( pNode->children[1], mins, maxs, pEnum, context ); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, - ISpatialLeafEnumerator* pEnum, int context ) -{ - return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); -} - -//----------------------------------------------------------------------------- -// Enumerate leaves within a sphere -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesInSphere_R( int node, Vector const& origin, - float radius, ISpatialLeafEnumerator* pEnum, int context ) -{ - while( node >= 0 ) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if (DotProduct( pPlane->normal, origin ) + radius - pPlane->dist <= -TEST_EPSILON ) - { - node = pNode->children[1]; - } - else if (DotProduct( pPlane->normal, origin ) - radius - pPlane->dist >= TEST_EPSILON ) - { - node = pNode->children[0]; - } - else - { - if (!EnumerateLeavesInSphere_R( pNode->children[0], - origin, radius, pEnum, context )) - { - return false; - } - - return EnumerateLeavesInSphere_R( pNode->children[1], - origin, radius, pEnum, context ); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ) -{ - return EnumerateLeavesInSphere_R( 0, center, radius, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Enumerate leaves along a ray -//----------------------------------------------------------------------------- - -static bool EnumerateLeavesAlongRay_R( int node, Ray_t const& ray, - Vector const& start, Vector const& end, ISpatialLeafEnumerator* pEnum, int context ) -{ - float front,back; - - while (node >= 0) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if ( pPlane->type <= PLANE_Z ) - { - front = start[pPlane->type] - pPlane->dist; - back = end[pPlane->type] - pPlane->dist; - } - else - { - front = DotProduct(start, pPlane->normal) - pPlane->dist; - back = DotProduct(end, pPlane->normal) - pPlane->dist; - } - - if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) - { - node = pNode->children[1]; - } - else if (front >= TEST_EPSILON && back >= TEST_EPSILON) - { - node = pNode->children[0]; - } - else - { - // test the front side first - bool side = front < 0; - - // Compute intersection point based on the original ray - float splitfrac; - float denom = DotProduct( ray.m_Delta, pPlane->normal ); - if ( denom == 0.0f ) - { - splitfrac = 1.0f; - } - else - { - splitfrac = ( pPlane->dist - DotProduct( ray.m_Start, pPlane->normal ) ) / denom; - if (splitfrac < 0) - splitfrac = 0; - else if (splitfrac > 1) - splitfrac = 1; - } - - // Compute the split point - Vector split; - VectorMA( ray.m_Start, splitfrac, ray.m_Delta, split ); - - bool r = EnumerateLeavesAlongRay_R (pNode->children[side], ray, start, split, pEnum, context ); - if (!r) - return r; - return EnumerateLeavesAlongRay_R (pNode->children[!side], ray, split, end, pEnum, context); - } - } - - return pEnum->EnumerateLeaf( - node - 1, context ); -} - -bool CToolBSPTree::EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ) -{ - if (!ray.m_IsSwept) - { - Vector mins, maxs; - VectorAdd( ray.m_Start, ray.m_Extents, maxs ); - VectorSubtract( ray.m_Start, ray.m_Extents, mins ); - - return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); - } - - // FIXME: Extruded ray not implemented yet - Assert( ray.m_IsRay ); - - Vector end; - VectorAdd( ray.m_Start, ray.m_Delta, end ); - return EnumerateLeavesAlongRay_R( 0, ray, ray.m_Start, end, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Singleton accessor -//----------------------------------------------------------------------------- - -ISpatialQuery* ToolBSPTree() -{ - static CToolBSPTree s_ToolBSPTree; - return &s_ToolBSPTree; -} - - - -//----------------------------------------------------------------------------- -// Enumerates nodes in front to back order... -//----------------------------------------------------------------------------- - -// FIXME: Do we want this in the IBSPTree interface? - -static bool EnumerateNodesAlongRay_R( int node, Ray_t const& ray, float start, float end, - IBSPNodeEnumerator* pEnum, int context ) -{ - float front, back; - float startDotN, deltaDotN; - - while (node >= 0) - { - dnode_t* pNode = &dnodes[node]; - dplane_t* pPlane = &dplanes[pNode->planenum]; - - if ( pPlane->type <= PLANE_Z ) - { - startDotN = ray.m_Start[pPlane->type]; - deltaDotN = ray.m_Delta[pPlane->type]; - } - else - { - startDotN = DotProduct( ray.m_Start, pPlane->normal ); - deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); - } - - front = startDotN + start * deltaDotN - pPlane->dist; - back = startDotN + end * deltaDotN - pPlane->dist; - - if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) - { - node = pNode->children[1]; - } - else if (front >= TEST_EPSILON && back >= TEST_EPSILON) - { - node = pNode->children[0]; - } - else - { - // test the front side first - bool side = front < 0; - - // Compute intersection point based on the original ray - float splitfrac; - if ( deltaDotN == 0.0f ) - { - splitfrac = 1.0f; - } - else - { - splitfrac = ( pPlane->dist - startDotN ) / deltaDotN; - if (splitfrac < 0.0f) - splitfrac = 0.0f; - else if (splitfrac > 1.0f) - splitfrac = 1.0f; - } - - bool r = EnumerateNodesAlongRay_R (pNode->children[side], ray, start, splitfrac, pEnum, context ); - if (!r) - return r; - - // Visit the node... - if (!pEnum->EnumerateNode( node, ray, splitfrac, context )) - return false; - - return EnumerateNodesAlongRay_R (pNode->children[!side], ray, splitfrac, end, pEnum, context); - } - } - - // Visit the leaf... - return pEnum->EnumerateLeaf( - node - 1, ray, start, end, context ); -} - - -bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ) -{ - Vector end; - VectorAdd( ray.m_Start, ray.m_Delta, end ); - return EnumerateNodesAlongRay_R( 0, ray, 0.0f, 1.0f, pEnum, context ); -} - - -//----------------------------------------------------------------------------- -// Helps us find all leaves associated with a particular cluster -//----------------------------------------------------------------------------- -CUtlVector g_ClusterLeaves; - -void BuildClusterTable( void ) -{ - int i, j; - int leafCount; - int leafList[MAX_MAP_LEAFS]; - - g_ClusterLeaves.SetCount( dvis->numclusters ); - for ( i = 0; i < dvis->numclusters; i++ ) - { - leafCount = 0; - for ( j = 0; j < numleafs; j++ ) - { - if ( dleafs[j].cluster == i ) - { - leafList[ leafCount ] = j; - leafCount++; - } - } - - g_ClusterLeaves[i].leafCount = leafCount; - if ( leafCount ) - { - g_ClusterLeaves[i].leafs.SetCount( leafCount ); - memcpy( g_ClusterLeaves[i].leafs.Base(), leafList, sizeof(int) * leafCount ); - } - } -} - -// There's a version of this in checksum_engine.cpp!!! Make sure that they match. -static bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName) -{ - byte chunk[1024]; - lump_t *curLump; - - FileHandle_t fp = g_pFileSystem->Open( pszFileName, "rb" ); - if ( !fp ) - return false; - - // CRC across all lumps except for the Entities lump - for ( int l = 0; l < HEADER_LUMPS; ++l ) - { - if (l == LUMP_ENTITIES) - continue; - - curLump = &g_pBSPHeader->lumps[l]; - unsigned int nSize = curLump->filelen; - - g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); - - // Now read in 1K chunks - while ( nSize > 0 ) - { - int nBytesRead = 0; - - if ( nSize > 1024 ) - nBytesRead = g_pFileSystem->Read( chunk, 1024, fp ); - else - nBytesRead = g_pFileSystem->Read( chunk, nSize, fp ); - - // If any data was received, CRC it. - if ( nBytesRead > 0 ) - { - nSize -= nBytesRead; - CRC32_ProcessBuffer( crcvalue, chunk, nBytesRead ); - } - else - { - g_pFileSystem->Close( fp ); - return false; - } - } - } - - g_pFileSystem->Close( fp ); - return true; -} - - -void SetHDRMode( bool bHDR ) -{ - g_bHDR = bHDR; - if ( bHDR ) - { - pdlightdata = &dlightdataHDR; - g_pLeafAmbientLighting = &g_LeafAmbientLightingHDR; - g_pLeafAmbientIndex = &g_LeafAmbientIndexHDR; - pNumworldlights = &numworldlightsHDR; - dworldlights = dworldlightsHDR; -#ifdef VRAD - extern void VRadDetailProps_SetHDRMode( bool bHDR ); - VRadDetailProps_SetHDRMode( bHDR ); -#endif - } - else - { - pdlightdata = &dlightdataLDR; - g_pLeafAmbientLighting = &g_LeafAmbientLightingLDR; - g_pLeafAmbientIndex = &g_LeafAmbientIndexLDR; - pNumworldlights = &numworldlightsLDR; - dworldlights = dworldlightsLDR; -#ifdef VRAD - extern void VRadDetailProps_SetHDRMode( bool bHDR ); - VRadDetailProps_SetHDRMode( bHDR ); -#endif - } -} - -bool SwapVHV( void *pDestBase, void *pSrcBase ) -{ - byte *pDest = (byte*)pDestBase; - byte *pSrc = (byte*)pSrcBase; - - HardwareVerts::FileHeader_t *pHdr = (HardwareVerts::FileHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); - g_Swap.SwapFieldsToTargetEndian( (HardwareVerts::FileHeader_t*)pDest, (HardwareVerts::FileHeader_t*)pSrc ); - pSrc += sizeof(HardwareVerts::FileHeader_t); - pDest += sizeof(HardwareVerts::FileHeader_t); - - // This swap is pretty format specific - Assert( pHdr->m_nVersion == VHV_VERSION ); - if ( pHdr->m_nVersion != VHV_VERSION ) - return false; - - HardwareVerts::MeshHeader_t *pSrcMesh = (HardwareVerts::MeshHeader_t*)pSrc; - HardwareVerts::MeshHeader_t *pDestMesh = (HardwareVerts::MeshHeader_t*)pDest; - HardwareVerts::MeshHeader_t *pMesh = (HardwareVerts::MeshHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); - for ( int i = 0; i < pHdr->m_nMeshes; ++i, ++pMesh, ++pSrcMesh, ++pDestMesh ) - { - g_Swap.SwapFieldsToTargetEndian( pDestMesh, pSrcMesh ); - - pSrc = (byte*)pSrcBase + pMesh->m_nOffset; - pDest = (byte*)pDestBase + pMesh->m_nOffset; - - // Swap as a buffer of integers - // (source is bgra for an Intel swap to argb. PowerPC won't swap, so we need argb source. - g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, pMesh->m_nVertexes ); - } - return true; -} - -const char *ResolveStaticPropToModel( const char *pPropName ) -{ - // resolve back to static prop - int iProp = -1; - - // filename should be sp_???.vhv or sp_hdr_???.vhv - if ( V_strnicmp( pPropName, "sp_", 3 ) ) - { - return NULL; - } - const char *pPropNumber = V_strrchr( pPropName, '_' ); - if ( pPropNumber ) - { - sscanf( pPropNumber+1, "%d.vhv", &iProp ); - } - else - { - return NULL; - } - - // look up the prop to get to the actual model - if ( iProp < 0 || iProp >= g_StaticPropInstances.Count() ) - { - // prop out of range - return NULL; - } - int iModel = g_StaticPropInstances[iProp]; - if ( iModel < 0 || iModel >= g_StaticPropNames.Count() ) - { - // model out of range - return NULL; - } - - return g_StaticPropNames[iModel].String(); -} - -//----------------------------------------------------------------------------- -// Iterate files in pak file, distribute to converters -// pak file will be ready for serialization upon completion -//----------------------------------------------------------------------------- -void ConvertPakFileContents( const char *pInFilename ) -{ - IZip *newPakFile = IZip::CreateZip( NULL ); - - CUtlBuffer sourceBuf; - CUtlBuffer targetBuf; - bool bConverted; - CUtlVector< CUtlString > hdrFiles; - - int id = -1; - int fileSize; - while ( 1 ) - { - char relativeName[MAX_PATH]; - id = GetNextFilename( GetPakFile(), id, relativeName, sizeof( relativeName ), fileSize ); - if ( id == -1) - break; - - bConverted = false; - sourceBuf.Purge(); - targetBuf.Purge(); - - const char* pExtension = V_GetFileExtension( relativeName ); - const char* pExt = 0; - - bool bOK = ReadFileFromPak( GetPakFile(), relativeName, false, sourceBuf ); - if ( !bOK ) - { - Warning( "Failed to load '%s' from lump pak for conversion or copy in '%s'.\n", relativeName, pInFilename ); - continue; - } - - if ( pExtension && !V_stricmp( pExtension, "vtf" ) ) - { - bOK = g_pVTFConvertFunc( relativeName, sourceBuf, targetBuf, g_pCompressFunc ); - if ( !bOK ) - { - Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - - bConverted = true; - pExt = ".vtf"; - } - else if ( pExtension && !V_stricmp( pExtension, "vhv" ) ) - { - CUtlBuffer tempBuffer; - if ( g_pVHVFixupFunc ) - { - // caller supplied a fixup - const char *pModelName = ResolveStaticPropToModel( relativeName ); - if ( !pModelName ) - { - Warning( "Static Prop '%s' failed to resolve actual model in '%s'.\n", relativeName, pInFilename ); - continue; - } - - // output temp buffer may shrink, must use TellPut() to determine size - bOK = g_pVHVFixupFunc( relativeName, pModelName, sourceBuf, tempBuffer ); - if ( !bOK ) - { - Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - } - else - { - // use the source buffer as-is - tempBuffer.EnsureCapacity( sourceBuf.TellMaxPut() ); - tempBuffer.Put( sourceBuf.Base(), sourceBuf.TellMaxPut() ); - } - - // swap the VHV - targetBuf.EnsureCapacity( tempBuffer.TellPut() ); - bOK = SwapVHV( targetBuf.Base(), tempBuffer.Base() ); - if ( !bOK ) - { - Warning( "Failed to swap '%s' in '%s'.\n", relativeName, pInFilename ); - continue; - } - targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, tempBuffer.TellPut() ); - - if ( g_pCompressFunc ) - { - CUtlBuffer compressedBuffer; - targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, sizeof( HardwareVerts::FileHeader_t ) ); - bool bCompressed = g_pCompressFunc( targetBuf, compressedBuffer ); - if ( bCompressed ) - { - // copy all the header data off - CUtlBuffer headerBuffer; - headerBuffer.EnsureCapacity( sizeof( HardwareVerts::FileHeader_t ) ); - headerBuffer.Put( targetBuf.Base(), sizeof( HardwareVerts::FileHeader_t ) ); - - // reform the target with the header and then the compressed data - targetBuf.Clear(); - targetBuf.Put( headerBuffer.Base(), sizeof( HardwareVerts::FileHeader_t ) ); - targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - } - - targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); - } - - bConverted = true; - pExt = ".vhv"; - } - - if ( !bConverted ) - { - // straight copy - AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false, IZip::eCompressionType_None ); - } - else - { - // converted filename - V_StripExtension( relativeName, relativeName, sizeof( relativeName ) ); - V_strcat( relativeName, ".360", sizeof( relativeName ) ); - V_strcat( relativeName, pExt, sizeof( relativeName ) ); - AddBufferToPak( newPakFile, relativeName, targetBuf.Base(), targetBuf.TellMaxPut(), false, IZip::eCompressionType_None ); - } - - if ( V_stristr( relativeName, ".hdr" ) || V_stristr( relativeName, "_hdr" ) ) - { - hdrFiles.AddToTail( relativeName ); - } - - DevMsg( "Created '%s' in lump pak in '%s'.\n", relativeName, pInFilename ); - } - - // strip ldr version of hdr files - for ( int i=0; iRemoveFileFromZip( ldrFileName ); - } - } - - // discard old pak in favor of new pak - IZip::ReleaseZip( s_pakFile ); - s_pakFile = newPakFile; -} - -void SetAlignedLumpPosition( int lumpnum, int alignment = LUMP_ALIGNMENT ) -{ - g_pBSPHeader->lumps[lumpnum].fileofs = AlignFilePosition( g_hBSPFile, alignment ); -} - -template< class T > -int SwapLumpToDisk( int fieldType, int lumpnum ) -{ - if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 ) - return 0; - - DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); - - // lump swap may expand, allocate enough expansion room - void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); - - // CopyLumpInternal will handle the swap on load case - unsigned int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); - unsigned int count = CopyLumpInternal( fieldType, lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); - g_pBSPHeader->lumps[lumpnum].filelen = count * fieldSize; - - if ( g_bSwapOnWrite ) - { - // Swap the lump in place before writing - switch( lumpnum ) - { - case LUMP_VISIBILITY: - SwapVisibilityLump( (byte*)pBuffer, (byte*)pBuffer, count ); - break; - - case LUMP_PHYSCOLLIDE: - // SwapPhyscollideLump may change size - SwapPhyscollideLump( (byte*)pBuffer, (byte*)pBuffer, count ); - g_pBSPHeader->lumps[lumpnum].filelen = count; - break; - - case LUMP_PHYSDISP: - SwapPhysdispLump( (byte*)pBuffer, (byte*)pBuffer, count ); - break; - - default: - g_Swap.SwapBufferToTargetEndian( (T*)pBuffer, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].filelen / sizeof(T) ); - break; - } - } - - SetAlignedLumpPosition( lumpnum ); - SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); - - free( pBuffer ); - - return g_pBSPHeader->lumps[lumpnum].filelen; -} - -template< class T > -int SwapLumpToDisk( int lumpnum ) -{ - if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 || g_Lumps.bLumpParsed[lumpnum] ) - return 0; - - DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); - - // lump swap may expand, allocate enough room - void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); - - // CopyLumpInternal will handle the swap on load case - int count = CopyLumpInternal( lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); - g_pBSPHeader->lumps[lumpnum].filelen = count * sizeof(T); - - if ( g_bSwapOnWrite ) - { - // Swap the lump in place before writing - g_Swap.SwapFieldsToTargetEndian( (T*)pBuffer, (T*)pBuffer, count ); - } - - SetAlignedLumpPosition( lumpnum ); - SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); - free( pBuffer ); - - return g_pBSPHeader->lumps[lumpnum].filelen; -} - -void SwapLeafAmbientLightingLumpToDisk() -{ - if ( HasLump( LUMP_LEAF_AMBIENT_INDEX ) || HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) - { - // current version, swap in place - if ( HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) - { - // write HDR - SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); - SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX_HDR ); - - // cull LDR - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING ); - SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX ); - } - } - else - { - // older ambient lighting version (before index) - // load older ambient lighting into memory and build ambient/index - // an older leaf version would have already built the new LDR leaf ambient/index - int numLeafs = g_pBSPHeader->lumps[LUMP_LEAFS].filelen / sizeof( dleaf_t ); - LoadLeafAmbientLighting( numLeafs ); - - if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) - { - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ); - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX_HDR ) ); - - // write HDR - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingHDR.Base(), g_LeafAmbientLightingHDR.Count() ); - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexHDR.Base(), g_LeafAmbientIndexHDR.Count() ); - } - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen = g_LeafAmbientLightingHDR.Count() * sizeof( dleafambientlighting_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientLightingHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen ); - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX_HDR ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen = g_LeafAmbientIndexHDR.Count() * sizeof( dleafambientindex_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientIndexHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen ); - - // mark as processed - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; - - // cull LDR - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; - } - else - { - // no HDR, keep LDR version - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING ) ); - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX ) ); - - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingLDR.Base(), g_LeafAmbientLightingLDR.Count() ); - g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexLDR.Base(), g_LeafAmbientIndexLDR.Count() ); - } - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = g_LeafAmbientLightingLDR.Count() * sizeof( dleafambientlighting_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientLightingLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen ); - - SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX ); - g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = g_LeafAmbientIndexLDR.Count() * sizeof( dleafambientindex_t ); - SafeWrite( g_hBSPFile, g_LeafAmbientIndexLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen ); - - // mark as processed - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; - g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; - } - - g_LeafAmbientLightingLDR.Purge(); - g_LeafAmbientIndexLDR.Purge(); - g_LeafAmbientLightingHDR.Purge(); - g_LeafAmbientIndexHDR.Purge(); - } -} - -void SwapLeafLumpToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAFS ) ); - - // load the leafs - int count = LoadLeafs(); - if ( g_bSwapOnWrite ) - { - g_Swap.SwapFieldsToTargetEndian( dleafs, count ); - } - - bool bOldLeafVersion = ( LumpVersion( LUMP_LEAFS ) == 0 ); - if ( bOldLeafVersion ) - { - // version has been converted in the load process - // not updating the version ye, SwapLeafAmbientLightingLumpToDisk() can detect - g_pBSPHeader->lumps[LUMP_LEAFS].filelen = count * sizeof( dleaf_t ); - } - - SetAlignedLumpPosition( LUMP_LEAFS ); - SafeWrite( g_hBSPFile, dleafs, g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); - - SwapLeafAmbientLightingLumpToDisk(); - - if ( bOldLeafVersion ) - { - // version has been converted in the load process - // can now safely change - g_pBSPHeader->lumps[LUMP_LEAFS].version = 1; - } - -#if defined( BSP_USE_LESS_MEMORY ) - if ( dleafs ) - { - free( dleafs ); - dleafs = NULL; - } -#endif -} - -void SwapOcclusionLumpToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_OCCLUSION ) ); - - LoadOcclusionLump(); - SetAlignedLumpPosition( LUMP_OCCLUSION ); - AddOcclusionLump(); -} - -void SwapPakfileLumpToDisk( const char *pInFilename ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_PAKFILE ) ); - - byte *pakbuffer = NULL; - int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); - if ( paksize > 0 ) - { - GetPakFile()->ActivateByteSwapping( IsX360() ); - GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); - - ConvertPakFileContents( pInFilename ); - } - free( pakbuffer ); - - SetAlignedLumpPosition( LUMP_PAKFILE, XBOX_DVD_SECTORSIZE ); - WritePakFileLump(); - - ReleasePakFileLumps(); -} - -void SwapGameLumpsToDisk( void ) -{ - DevMsg( "Swapping %s\n", GetLumpName( LUMP_GAME_LUMP ) ); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - SetAlignedLumpPosition( LUMP_GAME_LUMP ); - AddGameLumps(); -} - -//----------------------------------------------------------------------------- -// Generate a table of all static props, used for resolving static prop lighting -// files back to their actual mdl. -//----------------------------------------------------------------------------- -void BuildStaticPropNameTable() -{ - g_StaticPropNames.Purge(); - g_StaticPropInstances.Purge(); - - g_GameLumps.ParseGameLump( g_pBSPHeader ); - - GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - int nVersion = g_GameLumps.GetGameLumpVersion( hGameLump ); - if ( nVersion < 4 ) - { - // old unsupported version - return; - } - - if ( nVersion != 4 && nVersion != 5 && nVersion != 6 ) - { - Error( "Unknown Static Prop Lump version %d!\n", nVersion ); - } - - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - // get the model dictionary - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; - for ( int i = 0; i < count; i++ ) - { - g_StaticPropNames.AddToTail( pStaticPropDictLump[i].m_Name ); - } - pGameLumpData += count * sizeof( StaticPropDictLump_t ); - - // skip the leaf list - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - pGameLumpData += count * sizeof( StaticPropLeafLump_t ); - - // get the instances - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - for ( int i = 0; i < count; i++ ) - { - int propType; - if ( nVersion == 4 ) - { - propType = ((StaticPropLumpV4_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLumpV4_t ); - } - else if ( nVersion == 5 ) - { - propType = ((StaticPropLumpV5_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLumpV5_t ); - } - else - { - propType = ((StaticPropLump_t *)pGameLumpData)->m_PropType; - pGameLumpData += sizeof( StaticPropLump_t ); - } - g_StaticPropInstances.AddToTail( propType ); - } - } - } - - g_GameLumps.DestroyAllGameLumps(); -} - -int AlignBuffer( CUtlBuffer &buffer, int alignment ) -{ - unsigned int newPosition = AlignValue( buffer.TellPut(), alignment ); - int padLength = newPosition - buffer.TellPut(); - for ( int i = 0; ipLump->fileofs; - int fileOffsetB = pSortedLumpB->pLump->fileofs; - - int fileSizeA = pSortedLumpA->pLump->filelen; - int fileSizeB = pSortedLumpB->pLump->filelen; - - // invalid or empty lumps get sorted together - if ( !fileSizeA ) - { - fileOffsetA = 0; - } - if ( !fileSizeB ) - { - fileOffsetB = 0; - } - - // compare by offset, want ascending - if ( fileOffsetA < fileOffsetB ) - { - return -1; - } - else if ( fileOffsetA > fileOffsetB ) - { - return 1; - } - - return 0; -} - -bool CompressGameLump( dheader_t *pInBSPHeader, dheader_t *pOutBSPHeader, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) -{ - CByteswap byteSwap; - - dgamelumpheader_t* pInGameLumpHeader = (dgamelumpheader_t*)(((byte *)pInBSPHeader) + pInBSPHeader->lumps[LUMP_GAME_LUMP].fileofs); - dgamelump_t* pInGameLump = (dgamelump_t*)(pInGameLumpHeader + 1); - - if ( IsX360() ) - { - byteSwap.ActivateByteSwapping( true ); - byteSwap.SwapFieldsToTargetEndian( pInGameLumpHeader ); - byteSwap.SwapFieldsToTargetEndian( pInGameLump, pInGameLumpHeader->lumpCount ); - } - - unsigned int newOffset = outputBuffer.TellPut(); - // Make room for gamelump header and gamelump structs, which we'll write at the end - outputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, sizeof( dgamelumpheader_t ) ); - outputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); - - // Start with input lumps, and fixup - dgamelumpheader_t sOutGameLumpHeader = *pInGameLumpHeader; - CUtlBuffer sOutGameLumpBuf; - sOutGameLumpBuf.Put( pInGameLump, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); - dgamelump_t *sOutGameLump = (dgamelump_t *)sOutGameLumpBuf.Base(); - - // add a dummy terminal gamelump - // purposely NOT updating the .filelen to reflect the compressed size, but leaving as original size - // callers use the next entry offset to determine compressed size - sOutGameLumpHeader.lumpCount++; - dgamelump_t dummyLump = { 0 }; - outputBuffer.Put( &dummyLump, sizeof( dgamelump_t ) ); - - for ( int i = 0; i < pInGameLumpHeader->lumpCount; i++ ) - { - CUtlBuffer inputBuffer; - CUtlBuffer compressedBuffer; - - sOutGameLump[i].fileofs = AlignBuffer( outputBuffer, 4 ); - - if ( pInGameLump[i].filelen ) - { - if ( pInGameLump[i].flags & GAMELUMPFLAG_COMPRESSED ) - { - byte *pCompressedLump = ((byte *)pInBSPHeader) + pInGameLump[i].fileofs; - if ( CLZMA::IsCompressed( pCompressedLump ) ) - { - inputBuffer.EnsureCapacity( CLZMA::GetActualSize( pCompressedLump ) ); - unsigned int outSize = CLZMA::Uncompress( pCompressedLump, (unsigned char *)inputBuffer.Base() ); - inputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, outSize ); - if ( outSize != CLZMA::GetActualSize( pCompressedLump ) ) - { - Warning( "Decompressed size differs from header, BSP may be corrupt\n" ); - } - } - else - { - Assert( CLZMA::IsCompressed( pCompressedLump ) ); - Warning( "Unsupported BSP: Unrecognized compressed game lump\n" ); - } - - } - else - { - inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pInGameLump[i].fileofs, - pInGameLump[i].filelen, pInGameLump[i].filelen ); - } - - bool bCompressed = pCompressFunc ? pCompressFunc( inputBuffer, compressedBuffer ) : false; - if ( bCompressed ) - { - sOutGameLump[i].flags |= GAMELUMPFLAG_COMPRESSED; - - outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - compressedBuffer.Purge(); - } - else - { - // as is, clear compression flag from input lump - sOutGameLump[i].flags &= ~GAMELUMPFLAG_COMPRESSED; - outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); - } - } - } - - // fix the dummy terminal lump - int lastLump = sOutGameLumpHeader.lumpCount-1; - sOutGameLump[lastLump].fileofs = outputBuffer.TellPut(); - - if ( IsX360() ) - { - // fix the output for 360, swapping it back - byteSwap.SwapFieldsToTargetEndian( sOutGameLump, sOutGameLumpHeader.lumpCount ); - byteSwap.SwapFieldsToTargetEndian( &sOutGameLumpHeader ); - } - - pOutBSPHeader->lumps[LUMP_GAME_LUMP].fileofs = newOffset; - pOutBSPHeader->lumps[LUMP_GAME_LUMP].filelen = outputBuffer.TellPut() - newOffset; - // We set GAMELUMPFLAG_COMPRESSED and handle compression at the sub-lump level, this whole lump is not - // decompressable as a block. - pOutBSPHeader->lumps[LUMP_GAME_LUMP].uncompressedSize = 0; - - // Rewind to start and write lump headers - unsigned int endOffset = outputBuffer.TellPut(); - outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, newOffset ); - outputBuffer.Put( &sOutGameLumpHeader, sizeof( dgamelumpheader_t ) ); - outputBuffer.Put( sOutGameLumpBuf.Base(), sOutGameLumpBuf.TellPut() ); - outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, endOffset ); - - return true; -} - -//----------------------------------------------------------------------------- -// Compress callback for RepackBSP -//----------------------------------------------------------------------------- -bool RepackBSPCallback_LZMA( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ) -{ - if ( !inputBuffer.TellPut() ) - { - // nothing to do - return false; - } - - unsigned int originalSize = inputBuffer.TellPut() - inputBuffer.TellGet(); - unsigned int compressedSize = 0; - unsigned char *pCompressedOutput = LZMA_Compress( (unsigned char *)inputBuffer.Base() + inputBuffer.TellGet(), - originalSize, &compressedSize ); - if ( pCompressedOutput ) - { - outputBuffer.Put( pCompressedOutput, compressedSize ); - DevMsg( "Compressed bsp lump %u -> %u bytes\n", originalSize, compressedSize ); - free( pCompressedOutput ); - return true; - } - - return false; -} - - -bool RepackBSP( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc, IZip::eCompressionType packfileCompression ) -{ - dheader_t *pInBSPHeader = (dheader_t *)inputBuffer.Base(); - // The 360 swaps this header to disk. For some reason. - if ( pInBSPHeader->ident != ( IsX360() ? BigLong( IDBSPHEADER ) : IDBSPHEADER ) ) - { - Warning( "RepackBSP given invalid input data\n" ); - return false; - } - - CByteswap byteSwap; - if ( IsX360() ) - { - // bsp is 360, swap the header back - byteSwap.ActivateByteSwapping( true ); - byteSwap.SwapFieldsToTargetEndian( pInBSPHeader ); - } - - unsigned int headerOffset = outputBuffer.TellPut(); - outputBuffer.Put( pInBSPHeader, sizeof( dheader_t ) ); - - // This buffer grows dynamically, don't keep pointers to it around. Write out header at end. - dheader_t sOutBSPHeader = *pInBSPHeader; - - // must adhere to input lump's offset order and process according to that, NOT lump num - // sort by offset order - CUtlVector< SortedLump_t > sortedLumps; - for ( int i = 0; i < HEADER_LUMPS; i++ ) - { - int iIndex = sortedLumps.AddToTail(); - sortedLumps[iIndex].lumpNum = i; - sortedLumps[iIndex].pLump = &pInBSPHeader->lumps[i]; - } - sortedLumps.Sort( SortLumpsByOffset ); - - // iterate in sorted order - for ( int i = 0; i < HEADER_LUMPS; ++i ) - { - SortedLump_t *pSortedLump = &sortedLumps[i]; - int lumpNum = pSortedLump->lumpNum; - - // Should be set below, don't copy over old data - sOutBSPHeader.lumps[lumpNum].fileofs = 0; - sOutBSPHeader.lumps[lumpNum].filelen = 0; - // Only set by compressed lumps - sOutBSPHeader.lumps[lumpNum].uncompressedSize = 0; - - if ( pSortedLump->pLump->filelen ) // Otherwise its degenerate - { - int alignment = 4; - if ( lumpNum == LUMP_PAKFILE ) - { - alignment = 2048; - } - unsigned int newOffset = AlignBuffer( outputBuffer, alignment ); - - CUtlBuffer inputBuffer; - if ( pSortedLump->pLump->uncompressedSize ) - { - byte *pCompressedLump = ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs; - if ( CLZMA::IsCompressed( pCompressedLump ) && pSortedLump->pLump->uncompressedSize == CLZMA::GetActualSize( pCompressedLump ) ) - { - inputBuffer.EnsureCapacity( CLZMA::GetActualSize( pCompressedLump ) ); - unsigned int outSize = CLZMA::Uncompress( pCompressedLump, (unsigned char *)inputBuffer.Base() ); - inputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, outSize ); - if ( outSize != pSortedLump->pLump->uncompressedSize ) - { - Warning( "Decompressed size differs from header, BSP may be corrupt\n" ); - } - } - else - { - Assert( CLZMA::IsCompressed( pCompressedLump ) && - pSortedLump->pLump->uncompressedSize == CLZMA::GetActualSize( pCompressedLump ) ); - Warning( "Unsupported BSP: Unrecognized compressed lump\n" ); - } - } - else - { - // Just use input - inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs, - pSortedLump->pLump->filelen, pSortedLump->pLump->filelen ); - } - - if ( lumpNum == LUMP_GAME_LUMP ) - { - // the game lump has to have each of its components individually compressed - CompressGameLump( pInBSPHeader, &sOutBSPHeader, outputBuffer, pCompressFunc ); - } - else if ( lumpNum == LUMP_PAKFILE ) - { - IZip *newPakFile = IZip::CreateZip( NULL ); - IZip *oldPakFile = IZip::CreateZip( NULL ); - oldPakFile->ParseFromBuffer( inputBuffer.Base(), inputBuffer.Size() ); - - int id = -1; - int fileSize; - while ( 1 ) - { - char relativeName[MAX_PATH]; - id = GetNextFilename( oldPakFile, id, relativeName, sizeof( relativeName ), fileSize ); - if ( id == -1 ) - break; - - CUtlBuffer sourceBuf; - CUtlBuffer targetBuf; - - bool bOK = ReadFileFromPak( oldPakFile, relativeName, false, sourceBuf ); - if ( !bOK ) - { - Error( "Failed to load '%s' from lump pak for repacking.\n", relativeName ); - continue; - } - - AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false, packfileCompression ); - - DevMsg( "Repacking BSP: Created '%s' in lump pak\n", relativeName ); - } - - // save new pack to buffer - newPakFile->SaveToBuffer( outputBuffer ); - sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; - sOutBSPHeader.lumps[lumpNum].filelen = outputBuffer.TellPut() - newOffset; - // Note that this *lump* is uncompressed, it just contains a packfile that uses compression, so we're - // not setting lumps[lumpNum].uncompressedSize - - IZip::ReleaseZip( oldPakFile ); - IZip::ReleaseZip( newPakFile ); - } - else - { - CUtlBuffer compressedBuffer; - bool bCompressed = pCompressFunc ? pCompressFunc( inputBuffer, compressedBuffer ) : false; - if ( bCompressed ) - { - sOutBSPHeader.lumps[lumpNum].uncompressedSize = inputBuffer.TellPut(); - sOutBSPHeader.lumps[lumpNum].filelen = compressedBuffer.TellPut(); - sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; - outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); - compressedBuffer.Purge(); - } - else - { - // add as is - sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; - sOutBSPHeader.lumps[lumpNum].filelen = inputBuffer.TellPut(); - outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); - } - } - } - } - - if ( IsX360() ) - { - // fix the output for 360, swapping it back - byteSwap.SetTargetBigEndian( true ); - byteSwap.SwapFieldsToTargetEndian( &sOutBSPHeader ); - } - - // Write out header - unsigned int endOffset = outputBuffer.TellPut(); - outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, headerOffset ); - outputBuffer.Put( &sOutBSPHeader, sizeof( sOutBSPHeader ) ); - outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, endOffset ); - - return true; -} - -//----------------------------------------------------------------------------- -// For all lumps in a bsp: Loads the lump from file A, swaps it, writes it to file B. -// This limits the memory used for the swap process which helps the Xbox 360. -// -// NOTE: These lumps will be written to the file in exactly the order they appear here, -// so they can be shifted around if desired for file access optimization. -//----------------------------------------------------------------------------- -bool SwapBSPFile( const char *pInFilename, const char *pOutFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ) -{ - DevMsg( "Creating %s\n", pOutFilename ); - - if ( !g_pFileSystem->FileExists( pInFilename ) ) - { - Warning( "Error! Couldn't open input file %s - BSP swap failed!\n", pInFilename ); - return false; - } - - g_hBSPFile = SafeOpenWrite( pOutFilename ); - if ( !g_hBSPFile ) - { - Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); - return false; - } - - if ( !pVTFConvertFunc ) - { - Warning( "Error! Missing VTF Conversion function\n" ); - return false; - } - g_pVTFConvertFunc = pVTFConvertFunc; - - // optional VHV fixup - g_pVHVFixupFunc = pVHVFixupFunc; - - // optional compression callback - g_pCompressFunc = pCompressFunc; - - // These must be mutually exclusive - g_bSwapOnLoad = bSwapOnLoad; - g_bSwapOnWrite = !bSwapOnLoad; - - g_Swap.ActivateByteSwapping( true ); - - OpenBSPFile( pInFilename ); - - // CRC the bsp first - CRC32_t mapCRC; - CRC32_Init(&mapCRC); - if ( !CRC_MapFile( &mapCRC, pInFilename ) ) - { - Warning( "Failed to CRC the bsp\n" ); - return false; - } - - // hold a dictionary of all the static prop names - // this is needed to properly convert any VHV files inside the pak lump - BuildStaticPropNameTable(); - - // Set the output file pointer after the header - dheader_t dummyHeader = { 0 }; - SafeWrite( g_hBSPFile, &dummyHeader, sizeof( dheader_t ) ); - - // To allow for alignment fixups, the lumps will be written to the - // output file in the order they appear in this function. - - // NOTE: Flags for 360 !!!MUST!!! be first - SwapLumpToDisk< dflagslump_t >( LUMP_MAP_FLAGS ); - - // complex lump swaps first or for later contingent data - SwapLeafLumpToDisk(); - SwapOcclusionLumpToDisk(); - SwapGameLumpsToDisk(); - - // Strip dead or non relevant lumps - g_pBSPHeader->lumps[LUMP_DISP_LIGHTMAP_ALPHAS].filelen = 0; - g_pBSPHeader->lumps[LUMP_FACEIDS].filelen = 0; - - // Strip obsolete LDR in favor of HDR - if ( SwapLumpToDisk( LUMP_FACES_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_FACES].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( LUMP_FACES ); - } - - if ( SwapLumpToDisk( LUMP_WORLDLIGHTS_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_WORLDLIGHTS].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( LUMP_WORLDLIGHTS ); - } - - // Simple lump swaps - SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSDISP ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSCOLLIDE ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_VISIBILITY ); - SwapLumpToDisk( LUMP_MODELS ); - SwapLumpToDisk( LUMP_VERTEXES ); - SwapLumpToDisk( LUMP_PLANES ); - SwapLumpToDisk( LUMP_NODES ); - SwapLumpToDisk( LUMP_TEXINFO ); - SwapLumpToDisk( LUMP_TEXDATA ); - SwapLumpToDisk( LUMP_DISPINFO ); - SwapLumpToDisk( LUMP_DISP_VERTS ); - SwapLumpToDisk( LUMP_DISP_TRIS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); - SwapLumpToDisk( LUMP_FACE_MACRO_TEXTURE_INFO ); - SwapLumpToDisk( LUMP_PRIMITIVES ); - SwapLumpToDisk( LUMP_PRIMVERTS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_PRIMINDICES ); - SwapLumpToDisk( LUMP_ORIGINALFACES ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFFACES ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFBRUSHES ); - SwapLumpToDisk( FIELD_INTEGER, LUMP_SURFEDGES ); - SwapLumpToDisk( LUMP_EDGES ); - SwapLumpToDisk( LUMP_BRUSHES ); - SwapLumpToDisk( LUMP_BRUSHSIDES ); - SwapLumpToDisk( LUMP_AREAS ); - SwapLumpToDisk( LUMP_AREAPORTALS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_ENTITIES ); - SwapLumpToDisk( LUMP_LEAFWATERDATA ); - SwapLumpToDisk( FIELD_VECTOR, LUMP_VERTNORMALS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_VERTNORMALINDICES ); - SwapLumpToDisk( FIELD_VECTOR, LUMP_CLIPPORTALVERTS ); - SwapLumpToDisk( LUMP_CUBEMAPS ); - SwapLumpToDisk( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA ); - SwapLumpToDisk( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE ); - SwapLumpToDisk( LUMP_OVERLAYS ); - SwapLumpToDisk( LUMP_WATEROVERLAYS ); - SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER ); - SwapLumpToDisk( LUMP_OVERLAY_FADES ); - - - // NOTE: this data placed at the end for the sake of 360: - { - // NOTE: lighting must be the penultimate lump - // (allows 360 to free this memory part-way through map loading) - if ( SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING_HDR ) ) - { - g_pBSPHeader->lumps[LUMP_LIGHTING].filelen = 0; - } - else - { - // no HDR, keep LDR version - SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING ); - } - // NOTE: Pakfile for 360 !!!MUST!!! be last - SwapPakfileLumpToDisk( pInFilename ); - } - - - // Store the crc in the flags lump version field - g_pBSPHeader->lumps[LUMP_MAP_FLAGS].version = mapCRC; - - // Pad out the end of the file to a sector boundary for optimal IO - AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - - // Warn of any lumps that didn't get swapped - for ( int i = 0; i < HEADER_LUMPS; ++i ) - { - if ( HasLump( i ) && !g_Lumps.bLumpParsed[i] ) - { - // a new lump got added that needs to have a swap function - Warning( "BSP: '%s', %s has no swap or copy function. Discarding!\n", pInFilename, GetLumpName(i) ); - - // the data didn't get copied, so don't reference garbage - g_pBSPHeader->lumps[i].filelen = 0; - } - } - - // Write the updated header - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); - g_hBSPFile = 0; - - // Cleanup - g_Swap.ActivateByteSwapping( false ); - - CloseBSPFile(); - - g_StaticPropNames.Purge(); - g_StaticPropInstances.Purge(); - - DevMsg( "Finished BSP Swap\n" ); - - // caller provided compress func will further compress compatible lumps - if ( pCompressFunc ) - { - CUtlBuffer inputBuffer; - if ( !g_pFileSystem->ReadFile( pOutFilename, NULL, inputBuffer ) ) - { - Warning( "Error! Couldn't read file %s - final BSP compression failed!\n", pOutFilename ); - return false; - } - - CUtlBuffer outputBuffer; - if ( !RepackBSP( inputBuffer, outputBuffer, pCompressFunc, IZip::eCompressionType_None ) ) - { - Warning( "Error! Failed to compress BSP '%s'!\n", pOutFilename ); - return false; - } - - g_hBSPFile = SafeOpenWrite( pOutFilename ); - if ( !g_hBSPFile ) - { - Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); - return false; - } - SafeWrite( g_hBSPFile, outputBuffer.Base(), outputBuffer.TellPut() ); - g_pFileSystem->Close( g_hBSPFile ); - g_hBSPFile = 0; - } - - return true; -} - -//----------------------------------------------------------------------------- -// Get the pak lump from a BSP -//----------------------------------------------------------------------------- -bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ) -{ - *pPakData = NULL; - *pPakSize = 0; - - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // determine endian nature - dheader_t *pHeader; - LoadFile( pBSPFilename, (void **)&pHeader ); - bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); - free( pHeader ); - - g_bSwapOnLoad = bSwap; - g_bSwapOnWrite = !bSwap; - - OpenBSPFile( pBSPFilename ); - - if ( g_pBSPHeader->lumps[LUMP_PAKFILE].filelen ) - { - *pPakSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, pPakData ); - } - - CloseBSPFile(); - - return true; -} - -// compare function for qsort below -static int LumpOffsetCompare( const void *pElem1, const void *pElem2 ) -{ - int lump1 = *(byte *)pElem1; - int lump2 = *(byte *)pElem2; - - if ( lump1 != lump2 ) - { - // force LUMP_MAP_FLAGS to be first, always - if ( lump1 == LUMP_MAP_FLAGS ) - { - return -1; - } - else if ( lump2 == LUMP_MAP_FLAGS ) - { - return 1; - } - - // force LUMP_PAKFILE to be last, always - if ( lump1 == LUMP_PAKFILE ) - { - return 1; - } - else if ( lump2 == LUMP_PAKFILE ) - { - return -1; - } - } - - int fileOffset1 = g_pBSPHeader->lumps[lump1].fileofs; - int fileOffset2 = g_pBSPHeader->lumps[lump2].fileofs; - - // invalid or empty lumps will get sorted together - if ( !g_pBSPHeader->lumps[lump1].filelen ) - { - fileOffset1 = 0; - } - - if ( !g_pBSPHeader->lumps[lump2].filelen ) - { - fileOffset2 = 0; - } - - // compare by offset - if ( fileOffset1 < fileOffset2 ) - { - return -1; - } - else if ( fileOffset1 > fileOffset2 ) - { - return 1; - } - return 0; -} - -//----------------------------------------------------------------------------- -// Replace the pak lump in a BSP -//----------------------------------------------------------------------------- -bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ) -{ - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // determine endian nature - dheader_t *pHeader; - LoadFile( pBSPFilename, (void **)&pHeader ); - bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); - free( pHeader ); - - g_bSwapOnLoad = bSwap; - g_bSwapOnWrite = bSwap; - - OpenBSPFile( pBSPFilename ); - - // save a copy of the old header - // generating a new bsp is a destructive operation - dheader_t oldHeader; - oldHeader = *g_pBSPHeader; - - g_hBSPFile = SafeOpenWrite( pNewFilename ); - if ( !g_hBSPFile ) - { - return false; - } - - // placeholder only, reset at conclusion - WriteData( &oldHeader ); - - // lumps must be reserialized in same relative offset order - // build sorted order table - int readOrder[HEADER_LUMPS]; - for ( int i=0; ilumps[lump].filelen; - if ( length ) - { - // save the lump data - int offset = g_pBSPHeader->lumps[lump].fileofs; - SetAlignedLumpPosition( lump ); - SafeWrite( g_hBSPFile, (byte *)g_pBSPHeader + offset, length ); - } - else - { - g_pBSPHeader->lumps[lump].fileofs = 0; - } - } - - // Always write the pak file at the end - // Pad out the end of the file to a sector boundary for optimal IO - g_pBSPHeader->lumps[LUMP_PAKFILE].fileofs = AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - g_pBSPHeader->lumps[LUMP_PAKFILE].filelen = pakSize; - SafeWrite( g_hBSPFile, pPakData, pakSize ); - - // Pad out the end of the file to a sector boundary for optimal IO - AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); - - // Write the updated header - g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); - WriteData( g_pBSPHeader ); - g_pFileSystem->Close( g_hBSPFile ); - - CloseBSPFile(); - - return true; -} - -//----------------------------------------------------------------------------- -// Build a list of files that BSP owns, world/cubemap materials, static props, etc. -//----------------------------------------------------------------------------- -bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ) -{ - if ( !g_pFileSystem->FileExists( pBSPFilename ) ) - { - Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); - return false; - } - - // must be set, but exact hdr not critical for dependant traversal - SetHDRMode( false ); - - LoadBSPFile( pBSPFilename ); - - char szBspName[MAX_PATH]; - V_FileBase( pBSPFilename, szBspName, sizeof( szBspName ) ); - V_SetExtension( szBspName, ".bsp", sizeof( szBspName ) ); - - // get embedded pak files, and internals - char szFilename[MAX_PATH]; - int fileSize; - int fileId = -1; - for ( ;; ) - { - fileId = GetPakFile()->GetNextFilename( fileId, szFilename, sizeof( szFilename ), fileSize ); - if ( fileId == -1 ) - { - break; - } - pList->AddToTail( szFilename ); - } - - // get all the world materials - for ( int i=0; iAddToTail( szFilename ); - } - - // get all the static props - GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - - StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; - for ( int i=0; iAddToTail( pStaticPropDictLump[i].m_Name ); - } - } - } - - // get all the detail props - hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); - if ( hGameLump != g_GameLumps.InvalidGameLump() ) - { - byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); - if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) - { - int count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - - DetailObjectDictLump_t *pDetailObjectDictLump = (DetailObjectDictLump_t *)pGameLumpData; - for ( int i=0; iAddToTail( pDetailObjectDictLump[i].m_Name ); - } - pGameLumpData += count * sizeof( DetailObjectDictLump_t ); - - if ( g_GameLumps.GetGameLumpVersion( hGameLump ) == 4 ) - { - count = ((int *)pGameLumpData)[0]; - pGameLumpData += sizeof( int ); - if ( count ) - { - // All detail prop sprites must lie in the material detail/detailsprites - pList->AddToTail( "materials/detail/detailsprites.vmt" ); - } - } - } - } - - UnloadBSPFile(); - - return true; -} - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +//=============================================================================// + +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "zip_utils.h" +#include "scriplib.h" +#include "utllinkedlist.h" +#include "bsptreedata.h" +#include "cmodel.h" +#include "gamebspfile.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "utlbuffer.h" +#include "utlrbtree.h" +#include "utlsymbol.h" +#include "utlstring.h" +#include "checksum_crc.h" +#include "physdll.h" +#include "tier0/dbg.h" +#include "lumpfiles.h" +#include "vtf/vtf.h" +#include "lzma/lzma.h" +#include "tier1/lzmaDecoder.h" + +//============================================================================= + +// Boundary each lump should be aligned to +#define LUMP_ALIGNMENT 4 + +// Data descriptions for byte swapping - only needed +// for structures that are written to file for use by the game. +BEGIN_BYTESWAP_DATADESC( dheader_t ) + DEFINE_FIELD( ident, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), + DEFINE_FIELD( mapRevision, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( lump_t ) + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dflagslump_t ) + DEFINE_FIELD( m_LevelFlags, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dplane_t ) + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( dist, FIELD_FLOAT ), + DEFINE_FIELD( type, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_version_0_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), + DEFINE_EMBEDDED( m_AmbientLighting ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleaf_t ) + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( cluster, FIELD_SHORT ), + DEFINE_BITFIELD( bf, FIELD_SHORT, 16 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstleafface, FIELD_SHORT ), + DEFINE_FIELD( numleaffaces, FIELD_SHORT ), + DEFINE_FIELD( firstleafbrush, FIELD_SHORT ), + DEFINE_FIELD( numleafbrushes, FIELD_SHORT ), + DEFINE_FIELD( leafWaterDataID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CompressedLightCube ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_ARRAY( m_Color, FIELD_CHARACTER, 6 * sizeof(ColorRGBExp32) ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientindex_t ) + DEFINE_FIELD( ambientSampleCount, FIELD_SHORT ), + DEFINE_FIELD( firstAmbientSample, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafambientlighting_t ) // array of 6 ColorRGBExp32 (3 bytes and 1 char) + DEFINE_EMBEDDED( cube ), + DEFINE_FIELD( x, FIELD_CHARACTER ), + DEFINE_FIELD( y, FIELD_CHARACTER ), + DEFINE_FIELD( z, FIELD_CHARACTER ), + DEFINE_FIELD( pad, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dvertex_t ) + DEFINE_FIELD( point, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dnode_t ) + DEFINE_FIELD( planenum, FIELD_INTEGER ), + DEFINE_ARRAY( children, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( mins, FIELD_SHORT, 3 ), + DEFINE_ARRAY( maxs, FIELD_SHORT, 3 ), + DEFINE_FIELD( firstface, FIELD_SHORT ), + DEFINE_FIELD( numfaces, FIELD_SHORT ), + DEFINE_FIELD( area, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( texinfo_t ) + DEFINE_ARRAY( textureVecsTexelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_ARRAY( lightmapVecsLuxelsPerWorldUnits, FIELD_FLOAT, 2 * 4 ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texdata, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dtexdata_t ) + DEFINE_FIELD( reflectivity, FIELD_VECTOR ), + DEFINE_FIELD( nameStringTableID, FIELD_INTEGER ), + DEFINE_FIELD( width, FIELD_INTEGER ), + DEFINE_FIELD( height, FIELD_INTEGER ), + DEFINE_FIELD( view_width, FIELD_INTEGER ), + DEFINE_FIELD( view_height, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( ddispinfo_t ) + DEFINE_FIELD( startPosition, FIELD_VECTOR ), + DEFINE_FIELD( m_iDispVertStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iDispTriStart, FIELD_INTEGER ), + DEFINE_FIELD( power, FIELD_INTEGER ), + DEFINE_FIELD( minTess, FIELD_INTEGER ), + DEFINE_FIELD( smoothingAngle, FIELD_FLOAT ), + DEFINE_FIELD( contents, FIELD_INTEGER ), + DEFINE_FIELD( m_iMapFace, FIELD_SHORT ), + DEFINE_FIELD( m_iLightmapAlphaStart, FIELD_INTEGER ), + DEFINE_FIELD( m_iLightmapSamplePositionStart, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( m_EdgeNeighbors, 4 ), + DEFINE_EMBEDDED_ARRAY( m_CornerNeighbors, 4 ), + DEFINE_ARRAY( m_AllowedVerts, FIELD_INTEGER, ddispinfo_t::ALLOWEDVERTS_SIZE ), // unsigned long +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispNeighbor ) + DEFINE_EMBEDDED_ARRAY( m_SubNeighbors, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispCornerNeighbors ) + DEFINE_ARRAY( m_Neighbors, FIELD_SHORT, MAX_DISP_CORNER_NEIGHBORS ), + DEFINE_FIELD( m_nNeighbors, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispSubNeighbor ) + DEFINE_FIELD( m_iNeighbor, FIELD_SHORT ), + DEFINE_FIELD( m_NeighborOrientation, FIELD_CHARACTER ), + DEFINE_FIELD( m_Span, FIELD_CHARACTER ), + DEFINE_FIELD( m_NeighborSpan, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispVert ) + DEFINE_FIELD( m_vVector, FIELD_VECTOR ), + DEFINE_FIELD( m_flDist, FIELD_FLOAT ), + DEFINE_FIELD( m_flAlpha, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CDispTri ) + DEFINE_FIELD( m_uiTags, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( CFaceMacroTextureInfo ) + DEFINE_FIELD( m_MacroTextureNameID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimitive_t ) + DEFINE_FIELD( type, FIELD_CHARACTER ), + DEFINE_FIELD( firstIndex, FIELD_SHORT ), + DEFINE_FIELD( indexCount, FIELD_SHORT ), + DEFINE_FIELD( firstVert, FIELD_SHORT ), + DEFINE_FIELD( vertCount, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dprimvert_t ) + DEFINE_FIELD( pos, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dface_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( side, FIELD_CHARACTER ), + DEFINE_FIELD( onNode, FIELD_CHARACTER ), + DEFINE_FIELD( firstedge, FIELD_INTEGER ), + DEFINE_FIELD( numedges, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( surfaceFogVolumeID, FIELD_SHORT ), + DEFINE_ARRAY( styles, FIELD_CHARACTER, MAXLIGHTMAPS ), + DEFINE_FIELD( lightofs, FIELD_INTEGER ), + DEFINE_FIELD( area, FIELD_FLOAT ), + DEFINE_ARRAY( m_LightmapTextureMinsInLuxels, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( m_LightmapTextureSizeInLuxels, FIELD_INTEGER, 2 ), + DEFINE_FIELD( origFace, FIELD_INTEGER ), + DEFINE_FIELD( m_NumPrims, FIELD_SHORT ), + DEFINE_FIELD( firstPrimID, FIELD_SHORT ), + DEFINE_FIELD( smoothingGroups, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dfaceid_t ) + DEFINE_FIELD( hammerfaceid, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrush_t ) + DEFINE_FIELD( firstside, FIELD_INTEGER ), + DEFINE_FIELD( numsides, FIELD_INTEGER ), + DEFINE_FIELD( contents, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dbrushside_t ) + DEFINE_FIELD( planenum, FIELD_SHORT ), + DEFINE_FIELD( texinfo, FIELD_SHORT ), + DEFINE_FIELD( dispinfo, FIELD_SHORT ), + DEFINE_FIELD( bevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dedge_t ) + DEFINE_ARRAY( v, FIELD_SHORT, 2 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dmodel_t ) + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( headnode, FIELD_INTEGER ), + DEFINE_FIELD( firstface, FIELD_INTEGER ), + DEFINE_FIELD( numfaces, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysmodel_t ) + DEFINE_FIELD( modelIndex, FIELD_INTEGER ), + DEFINE_FIELD( dataSize, FIELD_INTEGER ), + DEFINE_FIELD( keydataSize, FIELD_INTEGER ), + DEFINE_FIELD( solidCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dphysdisp_t ) + DEFINE_FIELD( numDisplacements, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( darea_t ) + DEFINE_FIELD( numareaportals, FIELD_INTEGER ), + DEFINE_FIELD( firstareaportal, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dareaportal_t ) + DEFINE_FIELD( m_PortalKey, FIELD_SHORT ), + DEFINE_FIELD( otherarea, FIELD_SHORT ), + DEFINE_FIELD( m_FirstClipPortalVert, FIELD_SHORT ), + DEFINE_FIELD( m_nClipPortalVerts, FIELD_SHORT ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dworldlight_t ) + DEFINE_FIELD( origin, FIELD_VECTOR ), + DEFINE_FIELD( intensity, FIELD_VECTOR ), + DEFINE_FIELD( normal, FIELD_VECTOR ), + DEFINE_FIELD( cluster, FIELD_INTEGER ), + DEFINE_FIELD( type, FIELD_INTEGER ), // enumeration + DEFINE_FIELD( style, FIELD_INTEGER ), + DEFINE_FIELD( stopdot, FIELD_FLOAT ), + DEFINE_FIELD( stopdot2, FIELD_FLOAT ), + DEFINE_FIELD( exponent, FIELD_FLOAT ), + DEFINE_FIELD( radius, FIELD_FLOAT ), + DEFINE_FIELD( constant_attn, FIELD_FLOAT ), + DEFINE_FIELD( linear_attn, FIELD_FLOAT ), + DEFINE_FIELD( quadratic_attn, FIELD_FLOAT ), + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( texinfo, FIELD_INTEGER ), + DEFINE_FIELD( owner, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dleafwaterdata_t ) + DEFINE_FIELD( surfaceZ, FIELD_FLOAT ), + DEFINE_FIELD( minZ, FIELD_FLOAT ), + DEFINE_FIELD( surfaceTexInfoID, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderdata_t ) + DEFINE_FIELD( flags, FIELD_INTEGER ), + DEFINE_FIELD( firstpoly, FIELD_INTEGER ), + DEFINE_FIELD( polycount, FIELD_INTEGER ), + DEFINE_FIELD( mins, FIELD_VECTOR ), + DEFINE_FIELD( maxs, FIELD_VECTOR ), + DEFINE_FIELD( area, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doccluderpolydata_t ) + DEFINE_FIELD( firstvertexindex, FIELD_INTEGER ), + DEFINE_FIELD( vertexcount, FIELD_INTEGER ), + DEFINE_FIELD( planenum, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dcubemapsample_t ) + DEFINE_ARRAY( origin, FIELD_INTEGER, 3 ), + DEFINE_FIELD( size, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, OVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dwateroverlay_t ) + DEFINE_FIELD( nId, FIELD_INTEGER ), + DEFINE_FIELD( nTexInfo, FIELD_SHORT ), + DEFINE_FIELD( m_nFaceCountAndRenderOrder, FIELD_SHORT ), + DEFINE_ARRAY( aFaces, FIELD_INTEGER, WATEROVERLAY_BSP_FACE_COUNT ), + DEFINE_ARRAY( flU, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( flV, FIELD_FLOAT, 2 ), + DEFINE_ARRAY( vecUVPoints, FIELD_VECTOR, 4 ), + DEFINE_FIELD( vecOrigin, FIELD_VECTOR ), + DEFINE_FIELD( vecBasisNormal, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( doverlayfade_t ) + DEFINE_FIELD( flFadeDistMinSq, FIELD_FLOAT ), + DEFINE_FIELD( flFadeDistMaxSq, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelumpheader_t ) + DEFINE_FIELD( lumpCount, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dgamelump_t ) + DEFINE_FIELD( id, FIELD_INTEGER ), // GameLumpId_t + DEFINE_FIELD( flags, FIELD_SHORT ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +// From gamebspfile.h +BEGIN_BYTESWAP_DATADESC( StaticPropDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, STATIC_PROP_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), + DEFINE_FIELD( m_nMinDXLevel, FIELD_SHORT ), + DEFINE_FIELD( m_nMaxDXLevel, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV4_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLumpV5_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_PropType, FIELD_SHORT ), + DEFINE_FIELD( m_FirstLeaf, FIELD_SHORT ), + DEFINE_FIELD( m_LeafCount, FIELD_SHORT ), + DEFINE_FIELD( m_Solid, FIELD_CHARACTER ), + DEFINE_FIELD( m_Flags, FIELD_CHARACTER ), + DEFINE_FIELD( m_Skin, FIELD_INTEGER ), + DEFINE_FIELD( m_FadeMinDist, FIELD_FLOAT ), + DEFINE_FIELD( m_FadeMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( m_LightingOrigin, FIELD_VECTOR ), + DEFINE_FIELD( m_flForcedFadeScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( StaticPropLeafLump_t ) + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectDictLump_t ) + DEFINE_ARRAY( m_Name, FIELD_CHARACTER, DETAIL_NAME_LENGTH ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailObjectLump_t ) + DEFINE_FIELD( m_Origin, FIELD_VECTOR ), + DEFINE_FIELD( m_Angles, FIELD_VECTOR ), // QAngle + DEFINE_FIELD( m_DetailModel, FIELD_SHORT ), + DEFINE_FIELD( m_Leaf, FIELD_SHORT ), + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_LightStyles, FIELD_INTEGER ), + DEFINE_FIELD( m_LightStyleCount, FIELD_CHARACTER ), + DEFINE_FIELD( m_SwayAmount, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeAngle, FIELD_CHARACTER ), + DEFINE_FIELD( m_ShapeSize, FIELD_CHARACTER ), + DEFINE_FIELD( m_Orientation, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding2, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_Type, FIELD_CHARACTER ), + DEFINE_ARRAY( m_Padding3, FIELD_CHARACTER, 3 ), + DEFINE_FIELD( m_flScale, FIELD_FLOAT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailSpriteDictLump_t ) + DEFINE_FIELD( m_UL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_LR, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexUL, FIELD_VECTOR2D ), + DEFINE_FIELD( m_TexLR, FIELD_VECTOR2D ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( DetailPropLightstylesLump_t ) + DEFINE_ARRAY( m_Lighting, FIELD_CHARACTER, 4 ), // ColorRGBExp32 + DEFINE_FIELD( m_Style, FIELD_CHARACTER ), +END_BYTESWAP_DATADESC() + +// From vradstaticprops.h +namespace HardwareVerts +{ +BEGIN_BYTESWAP_DATADESC( MeshHeader_t ) + DEFINE_FIELD( m_nLod, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nOffset, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( FileHeader_t ) + DEFINE_FIELD( m_nVersion, FIELD_INTEGER ), + DEFINE_FIELD( m_nChecksum, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexFlags, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexSize, FIELD_INTEGER ), + DEFINE_FIELD( m_nVertexes, FIELD_INTEGER ), + DEFINE_FIELD( m_nMeshes, FIELD_INTEGER ), + DEFINE_ARRAY( m_nUnused, FIELD_INTEGER, 4 ), +END_BYTESWAP_DATADESC() +} // end namespace + +static const char *s_LumpNames[] = { + "LUMP_ENTITIES", // 0 + "LUMP_PLANES", // 1 + "LUMP_TEXDATA", // 2 + "LUMP_VERTEXES", // 3 + "LUMP_VISIBILITY", // 4 + "LUMP_NODES", // 5 + "LUMP_TEXINFO", // 6 + "LUMP_FACES", // 7 + "LUMP_LIGHTING", // 8 + "LUMP_OCCLUSION", // 9 + "LUMP_LEAFS", // 10 + "LUMP_FACEIDS", // 11 + "LUMP_EDGES", // 12 + "LUMP_SURFEDGES", // 13 + "LUMP_MODELS", // 14 + "LUMP_WORLDLIGHTS", // 15 + "LUMP_LEAFFACES", // 16 + "LUMP_LEAFBRUSHES", // 17 + "LUMP_BRUSHES", // 18 + "LUMP_BRUSHSIDES", // 19 + "LUMP_AREAS", // 20 + "LUMP_AREAPORTALS", // 21 + "LUMP_UNUSED0", // 22 + "LUMP_UNUSED1", // 23 + "LUMP_UNUSED2", // 24 + "LUMP_UNUSED3", // 25 + "LUMP_DISPINFO", // 26 + "LUMP_ORIGINALFACES", // 27 + "LUMP_PHYSDISP", // 28 + "LUMP_PHYSCOLLIDE", // 29 + "LUMP_VERTNORMALS", // 30 + "LUMP_VERTNORMALINDICES", // 31 + "LUMP_DISP_LIGHTMAP_ALPHAS", // 32 + "LUMP_DISP_VERTS", // 33 + "LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS", // 34 + "LUMP_GAME_LUMP", // 35 + "LUMP_LEAFWATERDATA", // 36 + "LUMP_PRIMITIVES", // 37 + "LUMP_PRIMVERTS", // 38 + "LUMP_PRIMINDICES", // 39 + "LUMP_PAKFILE", // 40 + "LUMP_CLIPPORTALVERTS", // 41 + "LUMP_CUBEMAPS", // 42 + "LUMP_TEXDATA_STRING_DATA", // 43 + "LUMP_TEXDATA_STRING_TABLE", // 44 + "LUMP_OVERLAYS", // 45 + "LUMP_LEAFMINDISTTOWATER", // 46 + "LUMP_FACE_MACRO_TEXTURE_INFO", // 47 + "LUMP_DISP_TRIS", // 48 + "LUMP_PHYSCOLLIDESURFACE", // 49 + "LUMP_WATEROVERLAYS", // 50 + "LUMP_LEAF_AMBIENT_INDEX_HDR", // 51 + "LUMP_LEAF_AMBIENT_INDEX", // 52 + "LUMP_LIGHTING_HDR", // 53 + "LUMP_WORLDLIGHTS_HDR", // 54 + "LUMP_LEAF_AMBIENT_LIGHTING_HDR", // 55 + "LUMP_LEAF_AMBIENT_LIGHTING", // 56 + "LUMP_XZIPPAKFILE", // 57 + "LUMP_FACES_HDR", // 58 + "LUMP_MAP_FLAGS", // 59 + "LUMP_OVERLAY_FADES", // 60 +}; + +const char *GetLumpName( unsigned int lumpnum ) +{ + if ( lumpnum >= ARRAYSIZE( s_LumpNames ) ) + { + return "UNKNOWN"; + } + return s_LumpNames[lumpnum]; +} + +// "-hdr" tells us to use the HDR fields (if present) on the light sources. Also, tells us to write +// out the HDR lumps for lightmaps, ambient leaves, and lights sources. +bool g_bHDR = false; + +// Set to true to generate Xbox360 native output files +static bool g_bSwapOnLoad = false; +static bool g_bSwapOnWrite = false; + +VTFConvertFunc_t g_pVTFConvertFunc; +VHVFixupFunc_t g_pVHVFixupFunc; +CompressFunc_t g_pCompressFunc; + +CUtlVector< CUtlString > g_StaticPropNames; +CUtlVector< int > g_StaticPropInstances; + +CByteswap g_Swap; + +uint32 g_LevelFlags = 0; + +int nummodels; +dmodel_t dmodels[MAX_MAP_MODELS]; + +int visdatasize; +byte dvisdata[MAX_MAP_VISIBILITY]; +dvis_t *dvis = (dvis_t *)dvisdata; + +CUtlVector dlightdataHDR; +CUtlVector dlightdataLDR; +CUtlVector *pdlightdata = &dlightdataLDR; + +CUtlVector dentdata; + +int numleafs; +#if !defined( BSP_USE_LESS_MEMORY ) +dleaf_t dleafs[MAX_MAP_LEAFS]; +#else +dleaf_t *dleafs; +#endif + +CUtlVector g_LeafAmbientIndexLDR; +CUtlVector g_LeafAmbientIndexHDR; +CUtlVector *g_pLeafAmbientIndex = NULL; +CUtlVector g_LeafAmbientLightingLDR; +CUtlVector g_LeafAmbientLightingHDR; +CUtlVector *g_pLeafAmbientLighting = NULL; + +unsigned short g_LeafMinDistToWater[MAX_MAP_LEAFS]; + +int numplanes; +dplane_t dplanes[MAX_MAP_PLANES]; + +int numvertexes; +dvertex_t dvertexes[MAX_MAP_VERTS]; + +int g_numvertnormalindices; // dfaces reference these. These index g_vertnormals. +unsigned short g_vertnormalindices[MAX_MAP_VERTNORMALS]; + +int g_numvertnormals; +Vector g_vertnormals[MAX_MAP_VERTNORMALS]; + +int numnodes; +dnode_t dnodes[MAX_MAP_NODES]; + +CUtlVector texinfo( MAX_MAP_TEXINFO ); + +int numtexdata; +dtexdata_t dtexdata[MAX_MAP_TEXDATA]; + +// +// displacement map bsp file info: dispinfo +// +CUtlVector g_dispinfo; +CUtlVector g_DispVerts; +CUtlVector g_DispTris; +CUtlVector g_DispLightmapSamplePositions; // LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS + +int numorigfaces; +dface_t dorigfaces[MAX_MAP_FACES]; + +int g_numprimitives = 0; +dprimitive_t g_primitives[MAX_MAP_PRIMITIVES]; + +int g_numprimverts = 0; +dprimvert_t g_primverts[MAX_MAP_PRIMVERTS]; + +int g_numprimindices = 0; +unsigned short g_primindices[MAX_MAP_PRIMINDICES]; + +int numfaces; +dface_t dfaces[MAX_MAP_FACES]; + +int numfaceids; +CUtlVector dfaceids; + +int numfaces_hdr; +dface_t dfaces_hdr[MAX_MAP_FACES]; + +int numedges; +dedge_t dedges[MAX_MAP_EDGES]; + +int numleaffaces; +unsigned short dleaffaces[MAX_MAP_LEAFFACES]; + +int numleafbrushes; +unsigned short dleafbrushes[MAX_MAP_LEAFBRUSHES]; + +int numsurfedges; +int dsurfedges[MAX_MAP_SURFEDGES]; + +int numbrushes; +dbrush_t dbrushes[MAX_MAP_BRUSHES]; + +int numbrushsides; +dbrushside_t dbrushsides[MAX_MAP_BRUSHSIDES]; + +int numareas; +darea_t dareas[MAX_MAP_AREAS]; + +int numareaportals; +dareaportal_t dareaportals[MAX_MAP_AREAPORTALS]; + +int numworldlightsLDR; +dworldlight_t dworldlightsLDR[MAX_MAP_WORLDLIGHTS]; + +int numworldlightsHDR; +dworldlight_t dworldlightsHDR[MAX_MAP_WORLDLIGHTS]; + +int *pNumworldlights = &numworldlightsLDR; +dworldlight_t *dworldlights = dworldlightsLDR; + +int numleafwaterdata = 0; +dleafwaterdata_t dleafwaterdata[MAX_MAP_LEAFWATERDATA]; + +CUtlVector g_FaceMacroTextureInfos; + +Vector g_ClipPortalVerts[MAX_MAP_PORTALVERTS]; +int g_nClipPortalVerts; + +dcubemapsample_t g_CubemapSamples[MAX_MAP_CUBEMAPSAMPLES]; +int g_nCubemapSamples = 0; + +int g_nOverlayCount; +doverlay_t g_Overlays[MAX_MAP_OVERLAYS]; +doverlayfade_t g_OverlayFades[MAX_MAP_OVERLAYS]; + +int g_nWaterOverlayCount; +dwateroverlay_t g_WaterOverlays[MAX_MAP_WATEROVERLAYS]; + +CUtlVector g_TexDataStringData; +CUtlVector g_TexDataStringTable; + +byte *g_pPhysCollide = NULL; +int g_PhysCollideSize = 0; +int g_MapRevision = 0; + +byte *g_pPhysDisp = NULL; +int g_PhysDispSize = 0; + +CUtlVector g_OccluderData( 256, 256 ); +CUtlVector g_OccluderPolyData( 1024, 1024 ); +CUtlVector g_OccluderVertexIndices( 2048, 2048 ); + +template static void WriteData( T *pData, int count = 1 ); +template static void WriteData( int fieldType, T *pData, int count = 1 ); +template< class T > static void AddLump( int lumpnum, T *pData, int count, int version = 0 ); +template< class T > static void AddLump( int lumpnum, CUtlVector &data, int version = 0 ); + +dheader_t *g_pBSPHeader; +FileHandle_t g_hBSPFile; + +struct Lump_t +{ + void *pLumps[HEADER_LUMPS]; + int size[HEADER_LUMPS]; + bool bLumpParsed[HEADER_LUMPS]; +} g_Lumps; + +CGameLump g_GameLumps; + +static IZip *s_pakFile = 0; + +//----------------------------------------------------------------------------- +// Keep the file position aligned to an arbitrary boundary. +// Returns updated file position. +//----------------------------------------------------------------------------- +static unsigned int AlignFilePosition( FileHandle_t hFile, int alignment ) +{ + unsigned int currPosition = g_pFileSystem->Tell( hFile ); + + if ( alignment >= 2 ) + { + unsigned int newPosition = AlignValue( currPosition, alignment ); + unsigned int count = newPosition - currPosition; + if ( count ) + { + char *pBuffer; + char smallBuffer[4096]; + if ( count > sizeof( smallBuffer ) ) + { + pBuffer = (char *)malloc( count ); + } + else + { + pBuffer = smallBuffer; + } + + memset( pBuffer, 0, count ); + SafeWrite( hFile, pBuffer, count ); + + if ( pBuffer != smallBuffer ) + { + free( pBuffer ); + } + + currPosition = newPosition; + } + } + + return currPosition; +} + +//----------------------------------------------------------------------------- +// Purpose: // Get a pakfile instance +// Output : IZip* +//----------------------------------------------------------------------------- +IZip* GetPakFile( void ) +{ + if ( !s_pakFile ) + { + s_pakFile = IZip::CreateZip(); + } + return s_pakFile; +} + +//----------------------------------------------------------------------------- +// Purpose: Free the pak files +//----------------------------------------------------------------------------- +void ReleasePakFileLumps( void ) +{ + // Release the pak files + IZip::ReleaseZip( s_pakFile ); + s_pakFile = NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Set the sector alignment for all subsequent zip operations +//----------------------------------------------------------------------------- +void ForceAlignment( IZip *pak, bool bAlign, bool bCompatibleFormat, unsigned int alignmentSize ) +{ + pak->ForceAlignment( bAlign, bCompatibleFormat, alignmentSize ); +} + +//----------------------------------------------------------------------------- +// Purpose: Store data back out to .bsp file +//----------------------------------------------------------------------------- +static void WritePakFileLump( void ) +{ + CUtlBuffer buf( 0, 0 ); + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->SaveToBuffer( buf ); + + // must respect pak file alignment + // pad up and ensure lump starts on same aligned boundary + AlignFilePosition( g_hBSPFile, GetPakFile()->GetAlignment() ); + + // Now store final buffers out to file + AddLump( LUMP_PAKFILE, (byte*)buf.Base(), buf.TellPut() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Remove all entries +//----------------------------------------------------------------------------- +void ClearPakFile( IZip *pak ) +{ + pak->Reset(); +} + +//----------------------------------------------------------------------------- +// Purpose: Add file from disk to .bsp PAK lump +// Input : *relativename - +// *fullpath - +//----------------------------------------------------------------------------- +void AddFileToPak( IZip *pak, const char *relativename, const char *fullpath, IZip::eCompressionType compressionType ) +{ + DevMsg( "Adding file to pakfile [ %s ]\n", fullpath ); + pak->AddFileToZip( relativename, fullpath, compressionType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add buffer to .bsp PAK lump as named file +// Input : *relativename - +// *data - +// length - +//----------------------------------------------------------------------------- +void AddBufferToPak( IZip *pak, const char *pRelativeName, void *data, int length, bool bTextMode, IZip::eCompressionType compressionType ) +{ + pak->AddBufferToZip( pRelativeName, data, length, bTextMode, compressionType ); +} + +//----------------------------------------------------------------------------- +// Purpose: Add entire directory to .bsp PAK lump as named file +// Input : *relativename - +// *data - +// length - +//----------------------------------------------------------------------------- +void AddDirToPak( IZip *pak, const char *pDirPath, const char *pPakPrefix ) +{ + if ( !g_pFullFileSystem->IsDirectory( pDirPath ) ) + { + Warning( "Passed non-directory to AddDirToPak [ %s ]\n", pDirPath ); + return; + } + + DevMsg( "Adding directory to pakfile [ %s ]\n", pDirPath ); + + // Enumerate dir + char szEnumerateDir[MAX_PATH] = { 0 }; + V_snprintf( szEnumerateDir, sizeof( szEnumerateDir ), "%s/*.*", pDirPath ); + V_FixSlashes( szEnumerateDir ); + + FileFindHandle_t handle; + const char *szFindResult = g_pFullFileSystem->FindFirst( szEnumerateDir, &handle ); + do + { + if ( szFindResult[0] != '.' ) + { + char szPakName[MAX_PATH] = { 0 }; + char szFullPath[MAX_PATH] = { 0 }; + if ( pPakPrefix ) + { + V_snprintf( szPakName, sizeof( szPakName ), "%s/%s", pPakPrefix, szFindResult ); + } + else + { + V_strncpy( szPakName, szFindResult, sizeof( szPakName ) ); + } + V_snprintf( szFullPath, sizeof( szFullPath ), "%s/%s", pDirPath, szFindResult ); + V_FixDoubleSlashes( szFullPath ); + V_FixDoubleSlashes( szPakName ); + + if ( g_pFullFileSystem->FindIsDirectory( handle ) ) + { + // Recurse + AddDirToPak( pak, szFullPath, szPakName ); + } + else + { + // Just add this file + AddFileToPak( pak, szPakName, szFullPath ); + } + } + szFindResult = g_pFullFileSystem->FindNext( handle ); + } while ( szFindResult); +} + +//----------------------------------------------------------------------------- +// Purpose: Check if a file already exists in the pack file. +// Input : *relativename - +//----------------------------------------------------------------------------- +bool FileExistsInPak( IZip *pak, const char *pRelativeName ) +{ + return pak->FileExistsInZip( pRelativeName ); +} + + +//----------------------------------------------------------------------------- +// Read a file from the pack file +//----------------------------------------------------------------------------- +bool ReadFileFromPak( IZip *pak, const char *pRelativeName, bool bTextMode, CUtlBuffer &buf ) +{ + return pak->ReadFileFromZip( pRelativeName, bTextMode, buf ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Remove file from .bsp PAK lump +// Input : *relativename - +//----------------------------------------------------------------------------- +void RemoveFileFromPak( IZip *pak, const char *relativename ) +{ + pak->RemoveFileFromZip( relativename ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Get next filename in directory +// Input : id, -1 to start, returns next id, or -1 at list conclusion +//----------------------------------------------------------------------------- +int GetNextFilename( IZip *pak, int id, char *pBuffer, int bufferSize, int &fileSize ) +{ + return pak->GetNextFilename( id, pBuffer, bufferSize, fileSize ); +} + +//----------------------------------------------------------------------------- +// Convert four-CC code to a handle + back +//----------------------------------------------------------------------------- +GameLumpHandle_t CGameLump::GetGameLumpHandle( GameLumpId_t id ) +{ + // NOTE: I'm also expecting game lump id's to be four-CC codes + Assert( id > HEADER_LUMPS ); + + FOR_EACH_LL(m_GameLumps, i) + { + if (m_GameLumps[i].m_Id == id) + return i; + } + + return InvalidGameLump(); +} + +GameLumpId_t CGameLump::GetGameLumpId( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Id; +} + +int CGameLump::GetGameLumpFlags( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Flags; +} + +int CGameLump::GetGameLumpVersion( GameLumpHandle_t handle ) +{ + return m_GameLumps[handle].m_Version; +} + + +//----------------------------------------------------------------------------- +// Game lump accessor methods +//----------------------------------------------------------------------------- + +void* CGameLump::GetGameLump( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.Base(); +} + +int CGameLump::GameLumpSize( GameLumpHandle_t id ) +{ + return m_GameLumps[id].m_Memory.NumAllocated(); +} + + +//----------------------------------------------------------------------------- +// Game lump iteration methods +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::FirstGameLump() +{ + return (m_GameLumps.Count()) ? m_GameLumps.Head() : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::NextGameLump( GameLumpHandle_t handle ) +{ + + return (m_GameLumps.IsValidIndex(handle)) ? m_GameLumps.Next(handle) : InvalidGameLump(); +} + +GameLumpHandle_t CGameLump::InvalidGameLump() +{ + return 0xFFFF; +} + + +//----------------------------------------------------------------------------- +// Game lump creation/destruction method +//----------------------------------------------------------------------------- + +GameLumpHandle_t CGameLump::CreateGameLump( GameLumpId_t id, int size, int flags, int version ) +{ + Assert( GetGameLumpHandle(id) == InvalidGameLump() ); + GameLumpHandle_t handle = m_GameLumps.AddToTail(); + m_GameLumps[handle].m_Id = id; + m_GameLumps[handle].m_Flags = flags; + m_GameLumps[handle].m_Version = version; + m_GameLumps[handle].m_Memory.EnsureCapacity( size ); + return handle; +} + +void CGameLump::DestroyGameLump( GameLumpHandle_t handle ) +{ + m_GameLumps.Remove( handle ); +} + +void CGameLump::DestroyAllGameLumps() +{ + m_GameLumps.RemoveAll(); +} + +//----------------------------------------------------------------------------- +// Compute file size and clump count +//----------------------------------------------------------------------------- + +void CGameLump::ComputeGameLumpSizeAndCount( int& size, int& clumpCount ) +{ + // Figure out total size of the client lumps + size = 0; + clumpCount = 0; + GameLumpHandle_t h; + for( h = FirstGameLump(); h != InvalidGameLump(); h = NextGameLump( h ) ) + { + ++clumpCount; + size += GameLumpSize( h ); + } + + // Add on headers + size += sizeof( dgamelumpheader_t ) + clumpCount * sizeof( dgamelump_t ); +} + + +void CGameLump::SwapGameLump( GameLumpId_t id, int version, byte *dest, byte *src, int length ) +{ + int count = 0; + switch( id ) + { + case GAMELUMP_STATIC_PROPS: + // Swap the static prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropDictLump_t*)dest, (StaticPropDictLump_t*)src, count ); + src += sizeof(StaticPropDictLump_t) * count; + dest += sizeof(StaticPropDictLump_t) * count; + + // Swap the leaf list + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (StaticPropLeafLump_t*)dest, (StaticPropLeafLump_t*)src, count ); + src += sizeof(StaticPropLeafLump_t) * count; + dest += sizeof(StaticPropLeafLump_t) * count; + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + // The one-at-a-time swap is to compensate for these structures + // possibly being misaligned, which crashes the Xbox 360. + if ( version == 4 ) + { + StaticPropLumpV4_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV4_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV4_t) ); + src += sizeof( StaticPropLumpV4_t ); + dest += sizeof( StaticPropLumpV4_t ); + } + } + else if ( version == 5 ) + { + StaticPropLumpV5_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLumpV5_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLumpV5_t) ); + src += sizeof( StaticPropLumpV5_t ); + dest += sizeof( StaticPropLumpV5_t ); + } + } + else + { + if ( version != 6 ) + { + Error( "Unknown Static Prop Lump version %d didn't get swapped!\n", version ); + } + + StaticPropLump_t lump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &lump, src, sizeof(StaticPropLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &lump, &lump ); + Q_memcpy( dest, &lump, sizeof(StaticPropLump_t) ); + src += sizeof( StaticPropLump_t ); + dest += sizeof( StaticPropLump_t ); + } + } + break; + + case GAMELUMP_DETAIL_PROPS: + // Swap the detail prop model dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailObjectDictLump_t*)dest, (DetailObjectDictLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + + if ( version == 4 ) + { + // Swap the detail sprite dict + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailSpriteDictLump_t spritelump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &spritelump, src, sizeof(DetailSpriteDictLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &spritelump, &spritelump ); + Q_memcpy( dest, &spritelump, sizeof(DetailSpriteDictLump_t) ); + src += sizeof(DetailSpriteDictLump_t); + dest += sizeof(DetailSpriteDictLump_t); + } + + // Swap the models + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + DetailObjectLump_t objectlump; + for ( int i = 0; i < count; ++i ) + { + Q_memcpy( &objectlump, src, sizeof(DetailObjectLump_t) ); + g_Swap.SwapFieldsToTargetEndian( &objectlump, &objectlump ); + Q_memcpy( dest, &objectlump, sizeof(DetailObjectLump_t) ); + src += sizeof(DetailObjectLump_t); + dest += sizeof(DetailObjectLump_t); + } + } + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING: + // Swap the LDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + case GAMELUMP_DETAIL_PROP_LIGHTING_HDR: + // Swap the HDR light styles + count = *(int*)src; + g_Swap.SwapBufferToTargetEndian( (int*)dest, (int*)src ); + count = g_bSwapOnLoad ? *(int*)dest : count; + src += sizeof(int); + dest += sizeof(int); + + g_Swap.SwapFieldsToTargetEndian( (DetailPropLightstylesLump_t*)dest, (DetailPropLightstylesLump_t*)src, count ); + src += sizeof(DetailObjectDictLump_t) * count; + dest += sizeof(DetailObjectDictLump_t) * count; + break; + + default: + char idchars[5] = {0}; + Q_memcpy( idchars, &id, 4 ); + Warning( "Unknown game lump '%s' didn't get swapped!\n", idchars ); + memcpy ( dest, src, length); + break; + } +} + +//----------------------------------------------------------------------------- +// Game lump file I/O +//----------------------------------------------------------------------------- +void CGameLump::ParseGameLump( dheader_t* pHeader ) +{ + g_GameLumps.DestroyAllGameLumps(); + + g_Lumps.bLumpParsed[LUMP_GAME_LUMP] = true; + + int length = pHeader->lumps[LUMP_GAME_LUMP].filelen; + int ofs = pHeader->lumps[LUMP_GAME_LUMP].fileofs; + + if (length > 0) + { + // Read dictionary... + dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)((byte *)pHeader + ofs); + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pGameLumpHeader ); + } + dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); + for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pGameLump[i] ); + } + + int length = pGameLump[i].filelen; + GameLumpHandle_t lump = g_GameLumps.CreateGameLump( pGameLump[i].id, length, pGameLump[i].flags, pGameLump[i].version ); + if ( g_bSwapOnLoad ) + { + SwapGameLump( pGameLump[i].id, pGameLump[i].version, (byte*)g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + else + { + memcpy( g_GameLumps.GetGameLump(lump), (byte *)pHeader + pGameLump[i].fileofs, length ); + } + } + } +} + + +//----------------------------------------------------------------------------- +// String table methods +//----------------------------------------------------------------------------- +const char *TexDataStringTable_GetString( int stringID ) +{ + return &g_TexDataStringData[g_TexDataStringTable[stringID]]; +} + +int TexDataStringTable_AddOrFindString( const char *pString ) +{ + int i; + // garymcthack: Make this use an RBTree! + for( i = 0; i < g_TexDataStringTable.Count(); i++ ) + { + if( stricmp( pString, &g_TexDataStringData[g_TexDataStringTable[i]] ) == 0 ) + { + return i; + } + } + + int len = strlen( pString ); + int outOffset = g_TexDataStringData.AddMultipleToTail( len+1, pString ); + int outIndex = g_TexDataStringTable.AddToTail( outOffset ); + return outIndex; +} + +//----------------------------------------------------------------------------- +// Adds all game lumps into one big block +//----------------------------------------------------------------------------- + +static void AddGameLumps( ) +{ + // Figure out total size of the client lumps + int size, clumpCount; + g_GameLumps.ComputeGameLumpSizeAndCount( size, clumpCount ); + + // Set up the main lump dictionary entry + g_Lumps.size[LUMP_GAME_LUMP] = 0; // mark it written + + lump_t* lump = &g_pBSPHeader->lumps[LUMP_GAME_LUMP]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = size; + + // write header + dgamelumpheader_t header; + header.lumpCount = clumpCount; + WriteData( &header ); + + // write dictionary + dgamelump_t dict; + int offset = lump->fileofs + sizeof(header) + clumpCount * sizeof(dgamelump_t); + GameLumpHandle_t h; + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + dict.id = g_GameLumps.GetGameLumpId(h); + dict.version = g_GameLumps.GetGameLumpVersion(h); + dict.flags = g_GameLumps.GetGameLumpFlags(h); + dict.fileofs = offset; + dict.filelen = g_GameLumps.GameLumpSize( h ); + offset += dict.filelen; + + WriteData( &dict ); + } + + // write lumps.. + for( h = g_GameLumps.FirstGameLump(); h != g_GameLumps.InvalidGameLump(); h = g_GameLumps.NextGameLump( h ) ) + { + unsigned int lumpsize = g_GameLumps.GameLumpSize(h); + if ( g_bSwapOnWrite ) + { + g_GameLumps.SwapGameLump( g_GameLumps.GetGameLumpId(h), g_GameLumps.GetGameLumpVersion(h), (byte*)g_GameLumps.GetGameLump(h), (byte*)g_GameLumps.GetGameLump(h), lumpsize ); + } + SafeWrite( g_hBSPFile, g_GameLumps.GetGameLump(h), lumpsize ); + } + + // align to doubleword + AlignFilePosition( g_hBSPFile, 4 ); +} + + +//----------------------------------------------------------------------------- +// Adds the occluder lump... +//----------------------------------------------------------------------------- +static void AddOcclusionLump( ) +{ + g_Lumps.size[LUMP_OCCLUSION] = 0; // mark it written + + int nOccluderCount = g_OccluderData.Count(); + int nOccluderPolyDataCount = g_OccluderPolyData.Count(); + int nOccluderVertexIndices = g_OccluderVertexIndices.Count(); + + int nLumpLength = nOccluderCount * sizeof(doccluderdata_t) + + nOccluderPolyDataCount * sizeof(doccluderpolydata_t) + + nOccluderVertexIndices * sizeof(int) + + 3 * sizeof(int); + + lump_t *lump = &g_pBSPHeader->lumps[LUMP_OCCLUSION]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = nLumpLength; + lump->version = LUMP_OCCLUSION_VERSION; + lump->uncompressedSize = 0; + + // Data is swapped in place, so the 'Count' variables aren't safe to use after they're written + WriteData( FIELD_INTEGER, &nOccluderCount ); + WriteData( (doccluderdata_t*)g_OccluderData.Base(), g_OccluderData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderPolyDataCount ); + WriteData( (doccluderpolydata_t*)g_OccluderPolyData.Base(), g_OccluderPolyData.Count() ); + WriteData( FIELD_INTEGER, &nOccluderVertexIndices ); + WriteData( FIELD_INTEGER, (int*)g_OccluderVertexIndices.Base(), g_OccluderVertexIndices.Count() ); +} + + +//----------------------------------------------------------------------------- +// Loads the occluder lump... +//----------------------------------------------------------------------------- +static void UnserializeOcclusionLumpV2( CUtlBuffer &buf ) +{ + int nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderData.SetCount( nCount ); + buf.GetObjects( g_OccluderData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + g_OccluderPolyData.SetCount( nCount ); + buf.GetObjects( g_OccluderPolyData.Base(), nCount ); + } + + nCount = buf.GetInt(); + if ( nCount ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( (int*)buf.PeekGet(), (int*)buf.PeekGet(), nCount ); + } + g_OccluderVertexIndices.SetCount( nCount ); + buf.Get( g_OccluderVertexIndices.Base(), nCount * sizeof(g_OccluderVertexIndices[0]) ); + } +} + + +static void LoadOcclusionLump() +{ + g_OccluderData.RemoveAll(); + g_OccluderPolyData.RemoveAll(); + g_OccluderVertexIndices.RemoveAll(); + + int length, ofs; + + g_Lumps.bLumpParsed[LUMP_OCCLUSION] = true; + + length = g_pBSPHeader->lumps[LUMP_OCCLUSION].filelen; + ofs = g_pBSPHeader->lumps[LUMP_OCCLUSION].fileofs; + + CUtlBuffer buf( (byte *)g_pBSPHeader + ofs, length, CUtlBuffer::READ_ONLY ); + buf.ActivateByteSwapping( g_bSwapOnLoad ); + switch ( g_pBSPHeader->lumps[LUMP_OCCLUSION].version ) + { + case 2: + UnserializeOcclusionLumpV2( buf ); + break; + + case 0: + break; + + default: + Error("Unknown occlusion lump version!\n"); + break; + } +} + + +/* +=============== +CompressVis + +=============== +*/ +int CompressVis (byte *vis, byte *dest) +{ + int j; + int rep; + int visrow; + byte *dest_p; + + dest_p = dest; +// visrow = (r_numvisleafs + 7)>>3; + visrow = (dvis->numclusters + 7)>>3; + + for (j=0 ; j>3; + row = (dvis->numclusters+7)>>3; + out = decompressed; + + do + { + if (*in) + { + *out++ = *in++; + continue; + } + + c = in[1]; + if (!c) + Error ("DecompressVis: 0 repeat"); + in += 2; + if ((out - decompressed) + c > row) + { + c = row - (out - decompressed); + Warning( "warning: Vis decompression overrun\n" ); + } + while (c) + { + *out++ = 0; + c--; + } + } while (out - decompressed < row); +} + +//----------------------------------------------------------------------------- +// Lump-specific swap functions +//----------------------------------------------------------------------------- +struct swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int size; + int vphysicsID; + short version; + short modelType; +}; + +struct swapcompactsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int surfaceSize; + Vector dragAxisAreas; + int axisMapSize; +}; + +struct swapmoppsurfaceheader_t : swapcollideheader_t +{ + DECLARE_BYTESWAP_DATADESC(); + int moppSize; +}; + +BEGIN_BYTESWAP_DATADESC( swapcollideheader_t ) + DEFINE_FIELD( size, FIELD_INTEGER ), + DEFINE_FIELD( vphysicsID, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_SHORT ), + DEFINE_FIELD( modelType, FIELD_SHORT ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapcompactsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( surfaceSize, FIELD_INTEGER ), + DEFINE_FIELD( dragAxisAreas, FIELD_VECTOR ), + DEFINE_FIELD( axisMapSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC_( swapmoppsurfaceheader_t, swapcollideheader_t ) + DEFINE_FIELD( moppSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + + +static void SwapPhyscollideLump( byte *pDestBase, byte *pSrcBase, unsigned int &count ) +{ + IPhysicsCollision *physcollision = NULL; + CSysModule *pPhysicsModule = g_pFullFileSystem->LoadModule( "vphysics.dll" ); + if ( pPhysicsModule ) + { + CreateInterfaceFn physicsFactory = Sys_GetFactory( pPhysicsModule ); + if ( physicsFactory ) + { + physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + } + } + + if ( !physcollision ) + { + Warning("!!! WARNING: Can't swap the physcollide lump!\n" ); + return; + } + + // physics data is variable length. The last physmodel is a NULL pointer + // with modelIndex -1, dataSize -1 + dphysmodel_t *pPhysModel; + byte *pSrc = pSrcBase; + + // first the src chunks have to be aligned properly + // swap increases size, allocate enough expansion room + byte *pSrcAlignedBase = (byte*)malloc( 2*count ); + byte *basePtr = pSrcAlignedBase; + byte *pSrcAligned = pSrcAlignedBase; + + do + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pSrcAligned, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pSrcAligned, pSrc, sizeof(dphysmodel_t) ); + } + pPhysModel = (dphysmodel_t*)pSrcAligned; + + pSrc += sizeof(dphysmodel_t); + pSrcAligned += sizeof(dphysmodel_t); + + if ( pPhysModel->dataSize > 0 ) + { + // Align the collide headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + // Get data size + int size; + Q_memcpy( &size, pSrc, sizeof(int) ); + if ( g_bSwapOnLoad ) + size = SwapLong( size ); + + // Fixup size + int padBytes = 0; + if ( size % 4 != 0 ) + { + padBytes = ( 4 - size % 4 ); + count += padBytes; + pPhysModel->dataSize += padBytes; + } + + // Copy data and size into alligned buffer + int newsize = size + padBytes; + if ( g_bSwapOnLoad ) + newsize = SwapLong( newsize ); + + Q_memcpy( pSrcAligned, &newsize, sizeof(int) ); + Q_memcpy( pSrcAligned + sizeof(int), pSrc + sizeof(int), size ); + pSrcAligned += size + padBytes + sizeof(int); + pSrc += size + sizeof(int); + } + + int padBytes = 0; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + Q_memcpy( pSrcAligned, pSrc, pPhysModel->keydataSize ); + pSrc += pPhysModel->keydataSize; + pSrcAligned += pPhysModel->keydataSize; + if ( dataSize % 4 != 0 ) + { + // Next chunk will be unaligned + padBytes = ( 4 - dataSize % 4 ); + pPhysModel->keydataSize += padBytes; + count += padBytes; + Q_memset( pSrcAligned, 0, padBytes ); + pSrcAligned += padBytes; + } + } + } while ( pPhysModel->dataSize > 0 ); + + // Now the data can be swapped properly + pSrcBase = pSrcAlignedBase; + pSrc = pSrcBase; + byte *pDest = pDestBase; + + do + { + // src headers are in native format + pPhysModel = (dphysmodel_t*)pSrc; + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( (dphysmodel_t*)pDest, (dphysmodel_t*)pSrc ); + } + else + { + Q_memcpy( pDest, pSrc, sizeof(dphysmodel_t) ); + } + + pSrc += sizeof(dphysmodel_t); + pDest += sizeof(dphysmodel_t); + + pSrcBase = pSrc; + pDestBase = pDest; + + if ( pPhysModel->dataSize > 0 ) + { + vcollide_t collide = {0}; + int dataSize = pPhysModel->dataSize + pPhysModel->keydataSize; + + if ( g_bSwapOnWrite ) + { + // Load the collide data + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, false ); + } + + int *offsets = new int[ pPhysModel->solidCount ]; + + // Swap the collision data headers + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + int headerSize = 0; + swapcollideheader_t *baseHdr = (swapcollideheader_t*)pSrc; + short modelType = baseHdr->modelType; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &modelType ); + } + + if ( modelType == 0 ) // COLLIDE_POLY + { + headerSize = sizeof(swapcompactsurfaceheader_t); + swapcompactsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + } + else if ( modelType == 1 ) // COLLIDE_MOPP + { + // The PC still unserializes these, but we don't support them + if ( g_bSwapOnWrite ) + { + collide.solids[i] = NULL; + } + + headerSize = sizeof(swapmoppsurfaceheader_t); + swapmoppsurfaceheader_t swapHdr; + Q_memcpy( &swapHdr, pSrc, headerSize ); + g_Swap.SwapFieldsToTargetEndian( &swapHdr, &swapHdr ); + Q_memcpy( pDest, &swapHdr, headerSize ); + + } + else + { + // Shouldn't happen + Assert( 0 ); + } + + if ( g_bSwapOnLoad ) + { + // src needs the native header data to load the vcollides + Q_memcpy( pSrc, pDest, headerSize ); + } + // HACK: Need either surfaceSize or moppSize - both sit at the same offset in the structure + swapmoppsurfaceheader_t *hdr = (swapmoppsurfaceheader_t*)pSrc; + pSrc += hdr->size + sizeof(int); + pDest += hdr->size + sizeof(int); + offsets[i] = hdr->size; + } + + pSrc = pSrcBase; + pDest = pDestBase; + if ( g_bSwapOnLoad ) + { + physcollision->VCollideLoad( &collide, pPhysModel->solidCount, (const char *)pSrc, dataSize, true ); + } + + // Write out the ledge tree data + for ( int i = 0; i < pPhysModel->solidCount; ++i ) + { + if ( collide.solids[i] ) + { + // skip over the size member + pSrc += sizeof(int); + pDest += sizeof(int); + int offset = physcollision->CollideWrite( (char*)pDest, collide.solids[i], g_bSwapOnWrite ); + pSrc += offset; + pDest += offset; + } + else + { + pSrc += offsets[i] + sizeof(int); + pDest += offsets[i] + sizeof(int); + } + } + + // copy the keyvalues data + Q_memcpy( pDest, pSrc, pPhysModel->keydataSize ); + pDest += pPhysModel->keydataSize; + pSrc += pPhysModel->keydataSize; + + // Free the memory + physcollision->VCollideUnload( &collide ); + delete [] offsets; + } + + // avoid infinite loop on badly formed file + if ( (pSrc - basePtr) > count ) + break; + + } while ( pPhysModel->dataSize > 0 ); + + free( pSrcAlignedBase ); +} + + +// UNDONE: This code is not yet tested. +static void SwapPhysdispLump( byte *pDest, byte *pSrc, int count ) +{ + // the format of this lump is one unsigned short dispCount, then dispCount unsigned shorts of sizes + // followed by an array of variable length (each element is the length of the corresponding entry in the + // previous table) byte-stream data structure of the displacement collision models + // these byte-stream structs are endian-neutral because each element is byte-sized + unsigned short dispCount = *(unsigned short*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &dispCount ); + } + g_Swap.SwapBufferToTargetEndian( (unsigned short*)pDest, (unsigned short*)pSrc, dispCount + 1 ); + + const int nBytes = (dispCount + 1) * sizeof( unsigned short ); + pSrc += nBytes; + pDest += nBytes; + count -= nBytes; + + g_Swap.SwapBufferToTargetEndian( pDest, pSrc, count ); +} + + +static void SwapVisibilityLump( byte *pDest, byte *pSrc, int count ) +{ + int firstInt = *(int*)pSrc; + if ( g_bSwapOnLoad ) + { + g_Swap.SwapBufferToTargetEndian( &firstInt ); + } + int intCt = firstInt * 2 + 1; + const int hdrSize = intCt * sizeof(int); + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, intCt ); + g_Swap.SwapBufferToTargetEndian( pDest + hdrSize, pSrc + hdrSize, count - hdrSize ); +} + +//============================================================================= +void Lumps_Init( void ) +{ + memset( &g_Lumps, 0, sizeof(g_Lumps) ); +} + +int LumpVersion( int lump ) +{ + return g_pBSPHeader->lumps[lump].version; +} + +bool HasLump( int lump ) +{ + return g_pBSPHeader->lumps[lump].filelen > 0; +} + +void ValidateLump( int lump, int length, int size, int forceVersion ) +{ + if ( length % size ) + { + Error( "ValidateLump: odd size for lump %d", lump ); + } + + if ( forceVersion >= 0 && forceVersion != g_pBSPHeader->lumps[lump].version ) + { + Error( "ValidateLump: old version for lump %d in map!", lump ); + } +} + +//----------------------------------------------------------------------------- +// Add Lumps of integral types without datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int fieldType, int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + // Vectors are passed in as floats + int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + + // count must be of the integral type + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, fieldSize, forceVersion ); + + if ( g_bSwapOnLoad ) + { + switch( lump ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + length = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)dest, ((byte*)g_pBSPHeader + ofs), count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + break; + } + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + // Return actual count of elements + return length / fieldSize; +} + +template< class T > +int CopyLump( int fieldType, int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( fieldType, lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + Assert( fieldType != FIELD_VECTOR ); // TODO: Support this if necessary + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int fieldType, int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( fieldType, lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int fieldType, int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( fieldType, lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add Lumps of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +int CopyLumpInternal( int lump, T *dest, int forceVersion ) +{ + g_Lumps.bLumpParsed[lump] = true; + + unsigned int length = g_pBSPHeader->lumps[lump].filelen; + unsigned int ofs = g_pBSPHeader->lumps[lump].fileofs; + unsigned int count = length / sizeof(T); + + ValidateLump( lump, length, sizeof(T), forceVersion ); + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( dest, (T*)((byte*)g_pBSPHeader + ofs), count ); + } + else + { + memcpy( dest, (byte*)g_pBSPHeader + ofs, length ); + } + + return count; +} + +template< class T > +int CopyLump( int lump, T *dest, int forceVersion = -1 ) +{ + return CopyLumpInternal( lump, dest, forceVersion ); +} + +template< class T > +void CopyLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +void CopyOptionalLump( int lump, CUtlVector &dest, int forceVersion = -1 ) +{ + // not fatal if not present + if ( !HasLump( lump ) ) + return; + + dest.SetSize( g_pBSPHeader->lumps[lump].filelen / sizeof(T) ); + CopyLumpInternal( lump, dest.Base(), forceVersion ); +} + +template< class T > +int CopyVariableLump( int lump, void **dest, int forceVersion = -1 ) +{ + int length = g_pBSPHeader->lumps[lump].filelen; + *dest = malloc( length ); + + return CopyLumpInternal( lump, (T*)*dest, forceVersion ); +} + +//----------------------------------------------------------------------------- +// Add/Write unknown lumps +//----------------------------------------------------------------------------- +void Lumps_Parse( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( !g_Lumps.bLumpParsed[i] && g_pBSPHeader->lumps[i].filelen ) + { + g_Lumps.size[i] = CopyVariableLump( FIELD_CHARACTER, i, &g_Lumps.pLumps[i], -1 ); + Msg( "Reading unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + } + } +} + +void Lumps_Write( void ) +{ + int i; + + for ( i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.size[i] ) + { + Msg( "Writing unknown lump #%d (%d bytes)\n", i, g_Lumps.size[i] ); + AddLump( i, (byte*)g_Lumps.pLumps[i], g_Lumps.size[i] ); + } + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } +} + +int LoadLeafs( void ) +{ +#if defined( BSP_USE_LESS_MEMORY ) + dleafs = (dleaf_t*)malloc( g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); +#endif + + switch ( LumpVersion( LUMP_LEAFS ) ) + { + case 0: + { + g_Lumps.bLumpParsed[LUMP_LEAFS] = true; + int length = g_pBSPHeader->lumps[LUMP_LEAFS].filelen; + int size = sizeof( dleaf_version_0_t ); + if ( length % size ) + { + Error( "odd size for LUMP_LEAFS\n" ); + } + int count = length / size; + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAFS].fileofs ); + dleaf_version_0_t *pSrc = (dleaf_version_0_t *)pSrcBase; + dleaf_t *pDst = dleafs; + + // version 0 predates HDR, build the LDR + g_LeafAmbientLightingLDR.SetCount( count ); + g_LeafAmbientIndexLDR.SetCount( count ); + + dleafambientlighting_t *pDstLeafAmbientLighting = &g_LeafAmbientLightingLDR[0]; + for ( int i = 0; i < count; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( pSrc ); + } + // pDst is a subset of pSrc; + *pDst = *( ( dleaf_t * )( void * )pSrc ); + pDstLeafAmbientLighting->cube = pSrc->m_AmbientLighting; + pDstLeafAmbientLighting->x = pDstLeafAmbientLighting->y = pDstLeafAmbientLighting->z = pDstLeafAmbientLighting->pad = 0; + pDst++; + pSrc++; + pDstLeafAmbientLighting++; + } + return count; + } + + case 1: + return CopyLump( LUMP_LEAFS, dleafs ); + + default: + Assert( 0 ); + Error( "Unknown LUMP_LEAFS version\n" ); + return 0; + } +} + +void LoadLeafAmbientLighting( int numLeafs ) +{ + if ( LumpVersion( LUMP_LEAFS ) == 0 ) + { + // an older leaf version already built the LDR ambient lighting on load + return; + } + + // old BSP with ambient, or new BSP with no lighting, convert ambient light to new format or create dummy ambient + if ( !HasLump( LUMP_LEAF_AMBIENT_INDEX ) ) + { + // a bunch of legacy maps, have these lumps with garbage versions + // expect them to be NOT the current version + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + if ( HasLump(LUMP_LEAF_AMBIENT_LIGHTING_HDR) ) + { + Assert( LumpVersion( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) != LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + } + + void *pSrcBase = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].fileofs ); + CompressedLightCube *pSrc = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING ) ) + { + pSrc = (CompressedLightCube*)pSrcBase; + } + g_LeafAmbientIndexLDR.SetCount( numLeafs ); + g_LeafAmbientLightingLDR.SetCount( numLeafs ); + + void *pSrcBaseHDR = ( ( byte * )g_pBSPHeader + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].fileofs ); + CompressedLightCube *pSrcHDR = NULL; + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + pSrcHDR = (CompressedLightCube*)pSrcBaseHDR; + } + g_LeafAmbientIndexHDR.SetCount( numLeafs ); + g_LeafAmbientLightingHDR.SetCount( numLeafs ); + + for ( int i = 0; i < numLeafs; i++ ) + { + g_LeafAmbientIndexLDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexLDR[i].firstAmbientSample = i; + g_LeafAmbientIndexHDR[i].ambientSampleCount = 1; + g_LeafAmbientIndexHDR[i].firstAmbientSample = i; + + Q_memset( &g_LeafAmbientLightingLDR[i], 0, sizeof(g_LeafAmbientLightingLDR[i]) ); + Q_memset( &g_LeafAmbientLightingHDR[i], 0, sizeof(g_LeafAmbientLightingHDR[i]) ); + + if ( pSrc ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrc[i] ); + } + g_LeafAmbientLightingLDR[i].cube = pSrc[i]; + } + if ( pSrcHDR ) + { + if ( g_bSwapOnLoad ) + { + g_Swap.SwapFieldsToTargetEndian( &pSrcHDR[i] ); + } + g_LeafAmbientLightingHDR[i].cube = pSrcHDR[i]; + } + } + + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + } + else + { + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR ); + CopyOptionalLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + } +} + +void ValidateHeader( const char *filename, const dheader_t *pHeader ) +{ + if ( pHeader->ident != IDBSPHEADER ) + { + Error ("%s is not a IBSP file", filename); + } + if ( pHeader->version < MINBSPVERSION || pHeader->version > BSPVERSION ) + { + Error ("%s is version %i, not %i", filename, pHeader->version, BSPVERSION); + } +} + +//----------------------------------------------------------------------------- +// Low level BSP opener for external parsing. Parses headers, but nothing else. +// You must close the BSP, via CloseBSPFile(). +//----------------------------------------------------------------------------- +void OpenBSPFile( const char *filename ) +{ + Lumps_Init(); + + // load the file header + LoadFile( filename, (void **)&g_pBSPHeader ); + + if ( g_bSwapOnLoad ) + { + g_Swap.ActivateByteSwapping( true ); + g_Swap.SwapFieldsToTargetEndian( g_pBSPHeader ); + } + + ValidateHeader( filename, g_pBSPHeader ); + + g_MapRevision = g_pBSPHeader->mapRevision; +} + +//----------------------------------------------------------------------------- +// CloseBSPFile +//----------------------------------------------------------------------------- +void CloseBSPFile( void ) +{ + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +//----------------------------------------------------------------------------- +// LoadBSPFile +//----------------------------------------------------------------------------- +void LoadBSPFile( const char *filename ) +{ + OpenBSPFile( filename ); + + nummodels = CopyLump( LUMP_MODELS, dmodels ); + numvertexes = CopyLump( LUMP_VERTEXES, dvertexes ); + numplanes = CopyLump( LUMP_PLANES, dplanes ); + numleafs = LoadLeafs(); + numnodes = CopyLump( LUMP_NODES, dnodes ); + CopyLump( LUMP_TEXINFO, texinfo ); + numtexdata = CopyLump( LUMP_TEXDATA, dtexdata ); + + CopyLump( LUMP_DISPINFO, g_dispinfo ); + CopyLump( LUMP_DISP_VERTS, g_DispVerts ); + CopyLump( LUMP_DISP_TRIS, g_DispTris ); + CopyLump( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + CopyLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + numfaces = CopyLump(LUMP_FACES, dfaces, LUMP_FACES_VERSION); + if ( HasLump( LUMP_FACES_HDR ) ) + numfaces_hdr = CopyLump( LUMP_FACES_HDR, dfaces_hdr, LUMP_FACES_VERSION ); + else + numfaces_hdr = 0; + + CopyOptionalLump( LUMP_FACEIDS, dfaceids ); + + g_numprimitives = CopyLump( LUMP_PRIMITIVES, g_primitives ); + g_numprimverts = CopyLump( LUMP_PRIMVERTS, g_primverts ); + g_numprimindices = CopyLump( FIELD_SHORT, LUMP_PRIMINDICES, g_primindices ); + numorigfaces = CopyLump( LUMP_ORIGINALFACES, dorigfaces ); // original faces + numleaffaces = CopyLump( FIELD_SHORT, LUMP_LEAFFACES, dleaffaces ); + numleafbrushes = CopyLump( FIELD_SHORT, LUMP_LEAFBRUSHES, dleafbrushes ); + numsurfedges = CopyLump( FIELD_INTEGER, LUMP_SURFEDGES, dsurfedges ); + numedges = CopyLump( LUMP_EDGES, dedges ); + numbrushes = CopyLump( LUMP_BRUSHES, dbrushes ); + numbrushsides = CopyLump( LUMP_BRUSHSIDES, dbrushsides ); + numareas = CopyLump( LUMP_AREAS, dareas ); + numareaportals = CopyLump( LUMP_AREAPORTALS, dareaportals ); + + visdatasize = CopyLump ( FIELD_CHARACTER, LUMP_VISIBILITY, dvisdata ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + CopyOptionalLump( FIELD_CHARACTER, LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + + LoadLeafAmbientLighting( numleafs ); + + CopyLump( FIELD_CHARACTER, LUMP_ENTITIES, dentdata ); + numworldlightsLDR = CopyLump( LUMP_WORLDLIGHTS, dworldlightsLDR ); + numworldlightsHDR = CopyLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR ); + + numleafwaterdata = CopyLump( LUMP_LEAFWATERDATA, dleafwaterdata ); + g_PhysCollideSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSCOLLIDE, (void**)&g_pPhysCollide ); + g_PhysDispSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PHYSDISP, (void**)&g_pPhysDisp ); + + g_numvertnormals = CopyLump( FIELD_VECTOR, LUMP_VERTNORMALS, (float*)g_vertnormals ); + g_numvertnormalindices = CopyLump( FIELD_SHORT, LUMP_VERTNORMALINDICES, g_vertnormalindices ); + + g_nClipPortalVerts = CopyLump( FIELD_VECTOR, LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts ); + g_nCubemapSamples = CopyLump( LUMP_CUBEMAPS, g_CubemapSamples ); + + CopyLump( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + CopyLump( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + + g_nOverlayCount = CopyLump( LUMP_OVERLAYS, g_Overlays ); + g_nWaterOverlayCount = CopyLump( LUMP_WATEROVERLAYS, g_WaterOverlays ); + CopyLump( LUMP_OVERLAY_FADES, g_OverlayFades ); + + dflagslump_t flags_lump; + + if ( HasLump( LUMP_MAP_FLAGS ) ) + CopyLump ( LUMP_MAP_FLAGS, &flags_lump ); + else + memset( &flags_lump, 0, sizeof( flags_lump ) ); // default flags to 0 + + g_LevelFlags = flags_lump.m_LevelFlags; + + LoadOcclusionLump(); + + CopyLump( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater ); + + /* + int crap; + for( crap = 0; crap < g_nBSPStringTable; crap++ ) + { + Msg( "stringtable %d", ( int )crap ); + Msg( " %d:", ( int )g_BSPStringTable[crap] ); + puts( &g_BSPStringData[g_BSPStringTable[crap]] ); + puts( "\n" ); + } + */ + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + // NOTE: Do NOT call CopyLump after Lumps_Parse() it parses all un-Copied lumps + // parse any additional lumps + Lumps_Parse(); + + // everything has been copied out + CloseBSPFile(); + + g_Swap.ActivateByteSwapping( false ); +} + +//----------------------------------------------------------------------------- +// Reset any state. +//----------------------------------------------------------------------------- +void UnloadBSPFile() +{ + nummodels = 0; + numvertexes = 0; + numplanes = 0; + + numleafs = 0; +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif + + numnodes = 0; + texinfo.Purge(); + numtexdata = 0; + + g_dispinfo.Purge(); + g_DispVerts.Purge(); + g_DispTris.Purge(); + + g_DispLightmapSamplePositions.Purge(); + g_FaceMacroTextureInfos.Purge(); + + numfaces = 0; + numfaces_hdr = 0; + + dfaceids.Purge(); + + g_numprimitives = 0; + g_numprimverts = 0; + g_numprimindices = 0; + numorigfaces = 0; + numleaffaces = 0; + numleafbrushes = 0; + numsurfedges = 0; + numedges = 0; + numbrushes = 0; + numbrushsides = 0; + numareas = 0; + numareaportals = 0; + + visdatasize = 0; + dlightdataLDR.Purge(); + dlightdataHDR.Purge(); + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + + dentdata.Purge(); + numworldlightsLDR = 0; + numworldlightsHDR = 0; + + numleafwaterdata = 0; + + if ( g_pPhysCollide ) + { + free( g_pPhysCollide ); + g_pPhysCollide = NULL; + } + g_PhysCollideSize = 0; + + if ( g_pPhysDisp ) + { + free( g_pPhysDisp ); + g_pPhysDisp = NULL; + } + g_PhysDispSize = 0; + + g_numvertnormals = 0; + g_numvertnormalindices = 0; + + g_nClipPortalVerts = 0; + g_nCubemapSamples = 0; + + g_TexDataStringData.Purge(); + g_TexDataStringTable.Purge(); + + g_nOverlayCount = 0; + g_nWaterOverlayCount = 0; + + g_LevelFlags = 0; + + g_OccluderData.Purge(); + g_OccluderPolyData.Purge(); + g_OccluderVertexIndices.Purge(); + + g_GameLumps.DestroyAllGameLumps(); + + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + if ( g_Lumps.pLumps[i] ) + { + free( g_Lumps.pLumps[i] ); + g_Lumps.pLumps[i] = NULL; + } + } + + ReleasePakFileLumps(); +} + +//----------------------------------------------------------------------------- +// LoadBSPFileFilesystemOnly +//----------------------------------------------------------------------------- +void LoadBSPFile_FileSystemOnly( const char *filename ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( filename, (void **)&g_pBSPHeader ); + + ValidateHeader( filename, g_pBSPHeader ); + + // Load PAK file lump into appropriate data structure + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer, 1 ); + if ( paksize > 0 ) + { + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + } + else + { + GetPakFile()->Reset(); + } + + free( pakbuffer ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +void ExtractZipFileFromBSP( char *pBSPFileName, char *pZipFileName ) +{ + Lumps_Init(); + + // + // load the file header + // + LoadFile( pBSPFileName, (void **)&g_pBSPHeader); + + ValidateHeader( pBSPFileName, g_pBSPHeader ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + FILE *fp; + fp = fopen( pZipFileName, "wb" ); + if( !fp ) + { + fprintf( stderr, "can't open %s\n", pZipFileName ); + return; + } + + fwrite( pakbuffer, paksize, 1, fp ); + fclose( fp ); + } + else + { + fprintf( stderr, "zip file is zero length!\n" ); + } +} + +/* +============= +LoadBSPFileTexinfo + +Only loads the texinfo lump, so qdata can scan for textures +============= +*/ +void LoadBSPFileTexinfo( const char *filename ) +{ + FILE *f; + int length, ofs; + + g_pBSPHeader = (dheader_t*)malloc( sizeof(dheader_t) ); + + f = fopen( filename, "rb" ); + fread( g_pBSPHeader, sizeof(dheader_t), 1, f); + + ValidateHeader( filename, g_pBSPHeader ); + + length = g_pBSPHeader->lumps[LUMP_TEXINFO].filelen; + ofs = g_pBSPHeader->lumps[LUMP_TEXINFO].fileofs; + + int nCount = length / sizeof(texinfo_t); + + texinfo.Purge(); + texinfo.AddMultipleToTail( nCount ); + + fseek( f, ofs, SEEK_SET ); + fread( texinfo.Base(), length, 1, f ); + fclose( f ); + + // everything has been copied out + free( g_pBSPHeader ); + g_pBSPHeader = NULL; +} + +static void AddLumpInternal( int lumpnum, void *data, int len, int version ) +{ + lump_t *lump; + + g_Lumps.size[lumpnum] = 0; // mark it written + + lump = &g_pBSPHeader->lumps[lumpnum]; + + lump->fileofs = g_pFileSystem->Tell( g_hBSPFile ); + lump->filelen = len; + lump->version = version; + lump->uncompressedSize = 0; + + SafeWrite( g_hBSPFile, data, len ); + + // pad out to the next dword + AlignFilePosition( g_hBSPFile, 4 ); +} + +template< class T > +static void SwapInPlace( T *pData, int count ) +{ + if ( !pData ) + return; + + // use the datadesc to swap the fields in place + g_Swap.SwapFieldsToTargetEndian( (T*)pData, pData, count ); +} + +template< class T > +static void SwapInPlace( int fieldType, T *pData, int count ) +{ + if ( !pData ) + return; + + // swap the data in place + g_Swap.SwapBufferToTargetEndian( (T*)pData, (T*)pData, count ); +} + +//----------------------------------------------------------------------------- +// Add raw data chunk to file (not a lump) +//----------------------------------------------------------------------------- +template< class T > +static void WriteData( int fieldType, T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( fieldType, pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +template< class T > +static void WriteData( T *pData, int count ) +{ + if ( g_bSwapOnWrite ) + { + SwapInPlace( pData, count ); + } + SafeWrite( g_hBSPFile, pData, count * sizeof(T) ); +} + +//----------------------------------------------------------------------------- +// Add Lump of object types with datadescs +//----------------------------------------------------------------------------- +template< class T > +static void AddLump( int lumpnum, T *pData, int count, int version ) +{ + AddLumpInternal( lumpnum, pData, count * sizeof(T), version ); +} + +template< class T > +static void AddLump( int lumpnum, CUtlVector &data, int version ) +{ + AddLumpInternal( lumpnum, data.Base(), data.Count() * sizeof(T), version ); +} + +/* +============= +WriteBSPFile + +Swaps the bsp file in place, so it should not be referenced again +============= +*/ +void WriteBSPFile( const char *filename, char *pUnused ) +{ + if ( texinfo.Count() > MAX_MAP_TEXINFO ) + { + Error( "Map has too many texinfos (has %d, can have at most %d)\n", texinfo.Count(), MAX_MAP_TEXINFO ); + return; + } + + dheader_t outHeader; + g_pBSPHeader = &outHeader; + memset( g_pBSPHeader, 0, sizeof( dheader_t ) ); + + g_pBSPHeader->ident = IDBSPHEADER; + g_pBSPHeader->version = BSPVERSION; + g_pBSPHeader->mapRevision = g_MapRevision; + + g_hBSPFile = SafeOpenWrite( filename ); + WriteData( g_pBSPHeader ); // overwritten later + + AddLump( LUMP_PLANES, dplanes, numplanes ); + AddLump( LUMP_LEAFS, dleafs, numleafs, LUMP_LEAFS_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING, g_LeafAmbientLightingLDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + AddLump( LUMP_LEAF_AMBIENT_INDEX, g_LeafAmbientIndexLDR ); + AddLump( LUMP_LEAF_AMBIENT_INDEX_HDR, g_LeafAmbientIndexHDR ); + AddLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR, g_LeafAmbientLightingHDR, LUMP_LEAF_AMBIENT_LIGHTING_VERSION ); + + AddLump( LUMP_VERTEXES, dvertexes, numvertexes ); + AddLump( LUMP_NODES, dnodes, numnodes ); + AddLump( LUMP_TEXINFO, texinfo ); + AddLump( LUMP_TEXDATA, dtexdata, numtexdata ); + + AddLump( LUMP_DISPINFO, g_dispinfo ); + AddLump( LUMP_DISP_VERTS, g_DispVerts ); + AddLump( LUMP_DISP_TRIS, g_DispTris ); + AddLump( LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS, g_DispLightmapSamplePositions ); + AddLump( LUMP_FACE_MACRO_TEXTURE_INFO, g_FaceMacroTextureInfos ); + + AddLump( LUMP_PRIMITIVES, g_primitives, g_numprimitives ); + AddLump( LUMP_PRIMVERTS, g_primverts, g_numprimverts ); + AddLump( LUMP_PRIMINDICES, g_primindices, g_numprimindices ); + AddLump( LUMP_FACES, dfaces, numfaces, LUMP_FACES_VERSION ); + if (numfaces_hdr) + AddLump( LUMP_FACES_HDR, dfaces_hdr, numfaces_hdr, LUMP_FACES_VERSION ); + AddLump ( LUMP_FACEIDS, dfaceids, numfaceids ); + + AddLump( LUMP_ORIGINALFACES, dorigfaces, numorigfaces ); // original faces lump + AddLump( LUMP_BRUSHES, dbrushes, numbrushes ); + AddLump( LUMP_BRUSHSIDES, dbrushsides, numbrushsides ); + AddLump( LUMP_LEAFFACES, dleaffaces, numleaffaces ); + AddLump( LUMP_LEAFBRUSHES, dleafbrushes, numleafbrushes ); + AddLump( LUMP_SURFEDGES, dsurfedges, numsurfedges ); + AddLump( LUMP_EDGES, dedges, numedges ); + AddLump( LUMP_MODELS, dmodels, nummodels ); + AddLump( LUMP_AREAS, dareas, numareas ); + AddLump( LUMP_AREAPORTALS, dareaportals, numareaportals ); + + AddLump( LUMP_LIGHTING, dlightdataLDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_LIGHTING_HDR, dlightdataHDR, LUMP_LIGHTING_VERSION ); + AddLump( LUMP_VISIBILITY, dvisdata, visdatasize ); + AddLump( LUMP_ENTITIES, dentdata ); + AddLump( LUMP_WORLDLIGHTS, dworldlightsLDR, numworldlightsLDR ); + AddLump( LUMP_WORLDLIGHTS_HDR, dworldlightsHDR, numworldlightsHDR ); + AddLump( LUMP_LEAFWATERDATA, dleafwaterdata, numleafwaterdata ); + + AddOcclusionLump(); + + dflagslump_t flags_lump; + flags_lump.m_LevelFlags = g_LevelFlags; + AddLump( LUMP_MAP_FLAGS, &flags_lump, 1 ); + + // NOTE: This is just for debugging, so it is disabled in release maps +#if 0 + // add the vis portals to the BSP for visualization + AddLump( LUMP_PORTALS, dportals, numportals ); + AddLump( LUMP_CLUSTERS, dclusters, numclusters ); + AddLump( LUMP_PORTALVERTS, dportalverts, numportalverts ); + AddLump( LUMP_CLUSTERPORTALS, dclusterportals, numclusterportals ); +#endif + + AddLump( LUMP_CLIPPORTALVERTS, (float*)g_ClipPortalVerts, g_nClipPortalVerts * 3 ); + AddLump( LUMP_CUBEMAPS, g_CubemapSamples, g_nCubemapSamples ); + AddLump( LUMP_TEXDATA_STRING_DATA, g_TexDataStringData ); + AddLump( LUMP_TEXDATA_STRING_TABLE, g_TexDataStringTable ); + AddLump( LUMP_OVERLAYS, g_Overlays, g_nOverlayCount ); + AddLump( LUMP_WATEROVERLAYS, g_WaterOverlays, g_nWaterOverlayCount ); + AddLump( LUMP_OVERLAY_FADES, g_OverlayFades, g_nOverlayCount ); + + if ( g_pPhysCollide ) + { + AddLump( LUMP_PHYSCOLLIDE, g_pPhysCollide, g_PhysCollideSize ); + } + + if ( g_pPhysDisp ) + { + AddLump ( LUMP_PHYSDISP, g_pPhysDisp, g_PhysDispSize ); + } + + AddLump( LUMP_VERTNORMALS, (float*)g_vertnormals, g_numvertnormals * 3 ); + AddLump( LUMP_VERTNORMALINDICES, g_vertnormalindices, g_numvertnormalindices ); + + AddLump( LUMP_LEAFMINDISTTOWATER, g_LeafMinDistToWater, numleafs ); + + AddGameLumps(); + + // Write pakfile lump to disk + WritePakFileLump(); + + // NOTE: Do NOT call AddLump after Lumps_Write() it writes all un-Added lumps + // write any additional lumps + Lumps_Write(); + + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); +} + +// Generate the next clear lump filename for the bsp file +bool GenerateNextLumpFileName( const char *bspfilename, char *lumpfilename, int buffsize ) +{ + for (int i = 0; i < MAX_LUMPFILES; i++) + { + GenerateLumpFileName( bspfilename, lumpfilename, buffsize, i ); + + if ( !g_pFileSystem->FileExists( lumpfilename ) ) + return true; + } + + return false; +} + +void WriteLumpToFile( char *filename, int lump ) +{ + if ( !HasLump(lump) ) + return; + + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + int ofs = g_pBSPHeader->lumps[lump].fileofs; + int length = g_pBSPHeader->lumps[lump].filelen; + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = LumpVersion(lump); + lumpHeader.lumpLength = length; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite (lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite (lumpfile, (byte *)g_pBSPHeader + ofs, length); +} + +void WriteLumpToFile( char *filename, int lump, int nLumpVersion, void *pBuffer, size_t nBufLen ) +{ + char lumppre[MAX_PATH]; + if ( !GenerateNextLumpFileName( filename, lumppre, MAX_PATH ) ) + { + Warning( "Failed to find valid lump filename for bsp %s.\n", filename ); + return; + } + + // Open the file + FileHandle_t lumpfile = g_pFileSystem->Open(lumppre, "wb"); + if ( !lumpfile ) + { + Error ("Error opening %s! (Check for write enable)\n",filename); + return; + } + + // Write the header + lumpfileheader_t lumpHeader; + lumpHeader.lumpID = lump; + lumpHeader.lumpVersion = nLumpVersion; + lumpHeader.lumpLength = nBufLen; + lumpHeader.mapRevision = LittleLong( g_MapRevision ); + lumpHeader.lumpOffset = sizeof(lumpfileheader_t); // Lump starts after the header + SafeWrite( lumpfile, &lumpHeader, sizeof(lumpfileheader_t)); + + // Write the lump + SafeWrite( lumpfile, pBuffer, nBufLen ); + + g_pFileSystem->Close( lumpfile ); +} + + +//============================================================================ +#define ENTRIES(a) (sizeof(a)/sizeof(*(a))) +#define ENTRYSIZE(a) (sizeof(*(a))) + +int ArrayUsage( const char *szItem, int items, int maxitems, int itemsize ) +{ + float percentage = maxitems ? items * 100.0 / maxitems : 0.0; + + Msg("%-17.17s %8i/%-8i %8i/%-8i (%4.1f%%) ", + szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return items * itemsize; +} + +int GlobUsage( const char *szItem, int itemstorage, int maxstorage ) +{ + float percentage = maxstorage ? itemstorage * 100.0 / maxstorage : 0.0; + Msg("%-17.17s [variable] %8i/%-8i (%4.1f%%) ", + szItem, itemstorage, maxstorage, percentage ); + if ( percentage > 80.0 ) + Msg( "VERY FULL!\n" ); + else if ( percentage > 95.0 ) + Msg( "SIZE DANGER!\n" ); + else if ( percentage > 99.9 ) + Msg( "SIZE OVERFLOW!!!\n" ); + else + Msg( "\n" ); + return itemstorage; +} + +/* +============= +PrintBSPFileSizes + +Dumps info about current file +============= +*/ +void PrintBSPFileSizes (void) +{ + int totalmemory = 0; + +// if (!num_entities) +// ParseEntities (); + + Msg("\n"); + Msg( "%-17s %16s %16s %9s \n", "Object names", "Objects/Maxobjs", "Memory / Maxmem", "Fullness" ); + Msg( "%-17s %16s %16s %9s \n", "------------", "---------------", "---------------", "--------" ); + + totalmemory += ArrayUsage( "models", nummodels, ENTRIES(dmodels), ENTRYSIZE(dmodels) ); + totalmemory += ArrayUsage( "brushes", numbrushes, ENTRIES(dbrushes), ENTRYSIZE(dbrushes) ); + totalmemory += ArrayUsage( "brushsides", numbrushsides, ENTRIES(dbrushsides), ENTRYSIZE(dbrushsides) ); + totalmemory += ArrayUsage( "planes", numplanes, ENTRIES(dplanes), ENTRYSIZE(dplanes) ); + totalmemory += ArrayUsage( "vertexes", numvertexes, ENTRIES(dvertexes), ENTRYSIZE(dvertexes) ); + totalmemory += ArrayUsage( "nodes", numnodes, ENTRIES(dnodes), ENTRYSIZE(dnodes) ); + totalmemory += ArrayUsage( "texinfos", texinfo.Count(),MAX_MAP_TEXINFO, sizeof(texinfo_t) ); + totalmemory += ArrayUsage( "texdata", numtexdata, ENTRIES(dtexdata), ENTRYSIZE(dtexdata) ); + + totalmemory += ArrayUsage( "dispinfos", g_dispinfo.Count(), 0, sizeof( ddispinfo_t ) ); + totalmemory += ArrayUsage( "disp_verts", g_DispVerts.Count(), 0, sizeof( g_DispVerts[0] ) ); + totalmemory += ArrayUsage( "disp_tris", g_DispTris.Count(), 0, sizeof( g_DispTris[0] ) ); + totalmemory += ArrayUsage( "disp_lmsamples",g_DispLightmapSamplePositions.Count(),0,sizeof( g_DispLightmapSamplePositions[0] ) ); + + totalmemory += ArrayUsage( "faces", numfaces, ENTRIES(dfaces), ENTRYSIZE(dfaces) ); + totalmemory += ArrayUsage( "hdr faces", numfaces_hdr, ENTRIES(dfaces_hdr), ENTRYSIZE(dfaces_hdr) ); + totalmemory += ArrayUsage( "origfaces", numorigfaces, ENTRIES(dorigfaces), ENTRYSIZE(dorigfaces) ); // original faces + totalmemory += ArrayUsage( "leaves", numleafs, ENTRIES(dleafs), ENTRYSIZE(dleafs) ); + totalmemory += ArrayUsage( "leaffaces", numleaffaces, ENTRIES(dleaffaces), ENTRYSIZE(dleaffaces) ); + totalmemory += ArrayUsage( "leafbrushes", numleafbrushes, ENTRIES(dleafbrushes), ENTRYSIZE(dleafbrushes) ); + totalmemory += ArrayUsage( "areas", numareas, ENTRIES(dareas), ENTRYSIZE(dareas) ); + totalmemory += ArrayUsage( "surfedges", numsurfedges, ENTRIES(dsurfedges), ENTRYSIZE(dsurfedges) ); + totalmemory += ArrayUsage( "edges", numedges, ENTRIES(dedges), ENTRYSIZE(dedges) ); + totalmemory += ArrayUsage( "LDR worldlights", numworldlightsLDR, ENTRIES(dworldlightsLDR), ENTRYSIZE(dworldlightsLDR) ); + totalmemory += ArrayUsage( "HDR worldlights", numworldlightsHDR, ENTRIES(dworldlightsHDR), ENTRYSIZE(dworldlightsHDR) ); + + totalmemory += ArrayUsage( "leafwaterdata", numleafwaterdata,ENTRIES(dleafwaterdata), ENTRYSIZE(dleafwaterdata) ); + totalmemory += ArrayUsage( "waterstrips", g_numprimitives,ENTRIES(g_primitives), ENTRYSIZE(g_primitives) ); + totalmemory += ArrayUsage( "waterverts", g_numprimverts, ENTRIES(g_primverts), ENTRYSIZE(g_primverts) ); + totalmemory += ArrayUsage( "waterindices", g_numprimindices,ENTRIES(g_primindices),ENTRYSIZE(g_primindices) ); + totalmemory += ArrayUsage( "cubemapsamples", g_nCubemapSamples,ENTRIES(g_CubemapSamples),ENTRYSIZE(g_CubemapSamples) ); + totalmemory += ArrayUsage( "overlays", g_nOverlayCount, ENTRIES(g_Overlays), ENTRYSIZE(g_Overlays) ); + + totalmemory += GlobUsage( "LDR lightdata", dlightdataLDR.Count(), 0 ); + totalmemory += GlobUsage( "HDR lightdata", dlightdataHDR.Count(), 0 ); + totalmemory += GlobUsage( "visdata", visdatasize, sizeof(dvisdata) ); + totalmemory += GlobUsage( "entdata", dentdata.Count(), 384*1024 ); // goal is <384K + + totalmemory += ArrayUsage( "LDR ambient table", g_LeafAmbientIndexLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexLDR[0] ) ); + totalmemory += ArrayUsage( "HDR ambient table", g_LeafAmbientIndexHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientIndexHDR[0] ) ); + totalmemory += ArrayUsage( "LDR leaf ambient lighting", g_LeafAmbientLightingLDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingLDR[0] ) ); + totalmemory += ArrayUsage( "HDR leaf ambient lighting", g_LeafAmbientLightingHDR.Count(), MAX_MAP_LEAFS, sizeof( g_LeafAmbientLightingHDR[0] ) ); + + totalmemory += ArrayUsage( "occluders", g_OccluderData.Count(), 0, sizeof( g_OccluderData[0] ) ); + totalmemory += ArrayUsage( "occluder polygons", g_OccluderPolyData.Count(), 0, sizeof( g_OccluderPolyData[0] ) ); + totalmemory += ArrayUsage( "occluder vert ind",g_OccluderVertexIndices.Count(),0, sizeof( g_OccluderVertexIndices[0] ) ); + + GameLumpHandle_t h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "detail props", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "HDR dtl prp lght", 1, g_GameLumps.GameLumpSize(h) ); + h = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if (h != g_GameLumps.InvalidGameLump()) + totalmemory += GlobUsage( "static props", 1, g_GameLumps.GameLumpSize(h) ); + + totalmemory += GlobUsage( "pakfile", GetPakFile()->EstimateSize(), 0 ); + // HACKHACK: Set physics limit at 4MB, in reality this is totally dynamic + totalmemory += GlobUsage( "physics", g_PhysCollideSize, 4*1024*1024 ); + totalmemory += GlobUsage( "physics terrain", g_PhysDispSize, 1*1024*1024 ); + + Msg( "\nLevel flags = %x\n", g_LevelFlags ); + + Msg( "\n" ); + + int triangleCount = 0; + + for ( int i = 0; i < numfaces; i++ ) + { + // face tris = numedges - 2 + triangleCount += dfaces[i].numedges - 2; + } + Msg("Total triangle count: %d\n", triangleCount ); + + // UNDONE: + // areaportals, portals, texdata, clusters, worldlights, portalverts +} + +/* +============= +PrintBSPPackDirectory + +Dumps a list of files stored in the bsp pack. +============= +*/ +void PrintBSPPackDirectory( void ) +{ + GetPakFile()->PrintDirectory(); +} + + +//============================================ + +int num_entities; +entity_t entities[MAX_MAP_ENTITIES]; + +void StripTrailing (char *e) +{ + char *s; + + s = e + strlen(e)-1; + while (s >= e && *s <= 32) + { + *s = 0; + s--; + } +} + +/* +================= +ParseEpair +================= +*/ +epair_t *ParseEpair (void) +{ + epair_t *e; + + e = (epair_t*)malloc (sizeof(epair_t)); + memset (e, 0, sizeof(epair_t)); + + if (strlen(token) >= MAX_KEY-1) + Error ("ParseEpar: token too long"); + e->key = copystring(token); + + GetToken (false); + if (strlen(token) >= MAX_VALUE-1) + Error ("ParseEpar: token too long"); + e->value = copystring(token); + + // strip trailing spaces + StripTrailing (e->key); + StripTrailing (e->value); + + return e; +} + + +/* +================ +ParseEntity +================ +*/ +qboolean ParseEntity (void) +{ + epair_t *e; + entity_t *mapent; + + if (!GetToken (true)) + return false; + + if (Q_stricmp (token, "{") ) + Error ("ParseEntity: { not found"); + + if (num_entities == MAX_MAP_ENTITIES) + Error ("num_entities == MAX_MAP_ENTITIES"); + + mapent = &entities[num_entities]; + num_entities++; + + do + { + if (!GetToken (true)) + Error ("ParseEntity: EOF without closing brace"); + if (!Q_stricmp (token, "}") ) + break; + e = ParseEpair (); + e->next = mapent->epairs; + mapent->epairs = e; + } while (1); + + return true; +} + +/* +================ +ParseEntities + +Parses the dentdata string into entities +================ +*/ +void ParseEntities (void) +{ + num_entities = 0; + ParseFromMemory (dentdata.Base(), dentdata.Count()); + + while (ParseEntity ()) + { + } +} + + +/* +================ +UnparseEntities + +Generates the dentdata string from all the entities +================ +*/ +void UnparseEntities (void) +{ + epair_t *ep; + char line[2048]; + int i; + char key[1024], value[1024]; + + CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER ); + buffer.EnsureCapacity( 256 * 1024 ); + + for (i=0 ; inext) + { + strcpy (key, ep->key); + StripTrailing (key); + strcpy (value, ep->value); + StripTrailing (value); + + sprintf(line, "\"%s\" \"%s\"\n", key, value); + buffer.PutString( line ); + } + buffer.PutString("}\n"); + } + int entdatasize = buffer.TellPut()+1; + + dentdata.SetSize( entdatasize ); + memcpy( dentdata.Base(), buffer.Base(), entdatasize-1 ); + dentdata[entdatasize-1] = 0; +} + +void PrintEntity (entity_t *ent) +{ + epair_t *ep; + + Msg ("------- entity %p -------\n", ent); + for (ep=ent->epairs ; ep ; ep=ep->next) + { + Msg ("%s = %s\n", ep->key, ep->value); + } + +} + +void SetKeyValue(entity_t *ent, const char *key, const char *value) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + { + free (ep->value); + ep->value = copystring(value); + return; + } + ep = (epair_t*)malloc (sizeof(*ep)); + ep->next = ent->epairs; + ent->epairs = ep; + ep->key = copystring(key); + ep->value = copystring(value); +} + +char *ValueForKey (entity_t *ent, char *key) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return ep->value; + return ""; +} + +vec_t FloatForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atof(k); +} + +vec_t FloatForKeyWithDefault (entity_t *ent, char *key, float default_value) +{ + for (epair_t *ep=ent->epairs ; ep ; ep=ep->next) + if (!Q_stricmp (ep->key, key) ) + return atof( ep->value ); + return default_value; +} + + + +int IntForKey (entity_t *ent, char *key) +{ + char *k = ValueForKey (ent, key); + return atol(k); +} + +int IntForKeyWithDefault(entity_t *ent, char *key, int nDefault ) +{ + char *k = ValueForKey (ent, key); + if ( !k[0] ) + return nDefault; + return atol(k); +} + +void GetVectorForKey (entity_t *ent, char *key, Vector& vec) +{ + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + double v1, v2, v3; + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + vec[0] = v1; + vec[1] = v2; + vec[2] = v3; +} + +void GetVector2DForKey (entity_t *ent, char *key, Vector2D& vec) +{ + double v1, v2; + + char *k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = 0; + sscanf (k, "%lf %lf", &v1, &v2); + vec[0] = v1; + vec[1] = v2; +} + +void GetAnglesForKey (entity_t *ent, char *key, QAngle& angle) +{ + char *k; + double v1, v2, v3; + + k = ValueForKey (ent, key); +// scanf into doubles, then assign, so it is vec_t size independent + v1 = v2 = v3 = 0; + sscanf (k, "%lf %lf %lf", &v1, &v2, &v3); + angle[0] = v1; + angle[1] = v2; + angle[2] = v3; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void BuildFaceCalcWindingData( dface_t *pFace, int *points ) +{ + for( int i = 0; i < pFace->numedges; i++ ) + { + int eIndex = dsurfedges[pFace->firstedge+i]; + if( eIndex < 0 ) + { + points[i] = dedges[-eIndex].v[1]; + } + else + { + points[i] = dedges[eIndex].v[0]; + } + } +} + + +void TriStripToTriList( + unsigned short const *pTriStripIndices, + int nTriStripIndices, + unsigned short **pTriListIndices, + int *pnTriListIndices ) +{ + int nMaxTriListIndices = (nTriStripIndices - 2) * 3; + *pTriListIndices = new unsigned short[ nMaxTriListIndices ]; + *pnTriListIndices = 0; + + for( int i=0; i < nTriStripIndices - 2; i++ ) + { + if( pTriStripIndices[i] == pTriStripIndices[i+1] || + pTriStripIndices[i] == pTriStripIndices[i+2] || + pTriStripIndices[i+1] == pTriStripIndices[i+2] ) + { + } + else + { + // Flip odd numbered tris.. + if( i & 1 ) + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + } + else + { + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+1]; + (*pTriListIndices)[(*pnTriListIndices)++] = pTriStripIndices[i+2]; + } + } + } +} + + +void CalcTextureCoordsAtPoints( + float const texelsPerWorldUnits[2][4], + int const subtractOffset[2], + Vector const *pPoints, + int const nPoints, + Vector2D *pCoords ) +{ + for( int i=0; i < nPoints; i++ ) + { + for( int iCoord=0; iCoord < 2; iCoord++ ) + { + float *pDestCoord = &pCoords[i][iCoord]; + + *pDestCoord = 0; + for( int iDot=0; iDot < 3; iDot++ ) + *pDestCoord += pPoints[i][iDot] * texelsPerWorldUnits[iCoord][iDot]; + + *pDestCoord += texelsPerWorldUnits[iCoord][3]; + *pDestCoord -= subtractOffset[iCoord]; + } + } +} + + +/* +================ +CalcFaceExtents + +Fills in s->texmins[] and s->texsize[] +================ +*/ +void CalcFaceExtents(dface_t *s, int lightmapTextureMinsInLuxels[2], int lightmapTextureSizeInLuxels[2]) +{ + vec_t mins[2], maxs[2], val=0; + int i,j, e=0; + dvertex_t *v=NULL; + texinfo_t *tex=NULL; + + mins[0] = mins[1] = 1e24; + maxs[0] = maxs[1] = -1e24; + + tex = &texinfo[s->texinfo]; + + for (i=0 ; inumedges ; i++) + { + e = dsurfedges[s->firstedge+i]; + if (e >= 0) + v = dvertexes + dedges[e].v[0]; + else + v = dvertexes + dedges[-e].v[1]; + + for (j=0 ; j<2 ; j++) + { + val = v->point[0] * tex->lightmapVecsLuxelsPerWorldUnits[j][0] + + v->point[1] * tex->lightmapVecsLuxelsPerWorldUnits[j][1] + + v->point[2] * tex->lightmapVecsLuxelsPerWorldUnits[j][2] + + tex->lightmapVecsLuxelsPerWorldUnits[j][3]; + if (val < mins[j]) + mins[j] = val; + if (val > maxs[j]) + maxs[j] = val; + } + } + + int nMaxLightmapDim = (s->dispinfo == -1) ? MAX_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER; + for (i=0 ; i<2 ; i++) + { + mins[i] = ( float )floor( mins[i] ); + maxs[i] = ( float )ceil( maxs[i] ); + + lightmapTextureMinsInLuxels[i] = ( int )mins[i]; + lightmapTextureSizeInLuxels[i] = ( int )( maxs[i] - mins[i] ); + if( lightmapTextureSizeInLuxels[i] > nMaxLightmapDim + 1 ) + { + Vector point = vec3_origin; + for (int j=0 ; jnumedges ; j++) + { + e = dsurfedges[s->firstedge+j]; + v = (e<0)?dvertexes + dedges[-e].v[1] : dvertexes + dedges[e].v[0]; + point += v->point; + Warning( "Bad surface extents point: %f %f %f\n", v->point.x, v->point.y, v->point.z ); + } + point *= 1.0f/s->numedges; + Error( "Bad surface extents - surface is too big to have a lightmap\n\tmaterial %s around point (%.1f %.1f %.1f)\n\t(dimension: %d, %d>%d)\n", + TexDataStringTable_GetString( dtexdata[texinfo[s->texinfo].texdata].nameStringTableID ), + point.x, point.y, point.z, + ( int )i, + ( int )lightmapTextureSizeInLuxels[i], + ( int )( nMaxLightmapDim + 1 ) + ); + } + } +} + + +void UpdateAllFaceLightmapExtents() +{ + for( int i=0; i < numfaces; i++ ) + { + dface_t *pFace = &dfaces[i]; + + if ( texinfo[pFace->texinfo].flags & (SURF_SKY|SURF_NOLIGHT) ) + continue; // non-lit texture + + CalcFaceExtents( pFace, pFace->m_LightmapTextureMinsInLuxels, pFace->m_LightmapTextureSizeInLuxels ); + } +} + + +//----------------------------------------------------------------------------- +// +// Helper class to iterate over leaves, used by tools +// +//----------------------------------------------------------------------------- + +#define TEST_EPSILON (0.03125) + + +class CToolBSPTree : public ISpatialQuery +{ +public: + // Returns the number of leaves + int LeafCount() const; + + // Enumerates the leaves along a ray, box, etc. + bool EnumerateLeavesAtPoint( Vector const& pt, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ); + bool EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ); +}; + + +//----------------------------------------------------------------------------- +// Returns the number of leaves +//----------------------------------------------------------------------------- + +int CToolBSPTree::LeafCount() const +{ + return numleafs; +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves at a point +//----------------------------------------------------------------------------- + +bool CToolBSPTree::EnumerateLeavesAtPoint( Vector const& pt, + ISpatialLeafEnumerator* pEnum, int context ) +{ + int node = 0; + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, pt ) <= pPlane->dist) + { + node = pNode->children[1]; + } + else + { + node = pNode->children[0]; + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerates the leaves in a box +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInBox_R( int node, Vector const& mins, + Vector const& maxs, ISpatialLeafEnumerator* pEnum, int context ) +{ + Vector cornermin, cornermax; + + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + // Arbitrary split plane here + for (int i = 0; i < 3; ++i) + { + if (pPlane->normal[i] >= 0) + { + cornermin[i] = mins[i]; + cornermax[i] = maxs[i]; + } + else + { + cornermin[i] = maxs[i]; + cornermax[i] = mins[i]; + } + } + + if ( (DotProduct( pPlane->normal, cornermax ) - pPlane->dist) <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if ( (DotProduct( pPlane->normal, cornermin ) - pPlane->dist) >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInBox_R( pNode->children[0], mins, maxs, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInBox_R( pNode->children[1], mins, maxs, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInBox( Vector const& mins, Vector const& maxs, + ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); +} + +//----------------------------------------------------------------------------- +// Enumerate leaves within a sphere +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesInSphere_R( int node, Vector const& origin, + float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + while( node >= 0 ) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if (DotProduct( pPlane->normal, origin ) + radius - pPlane->dist <= -TEST_EPSILON ) + { + node = pNode->children[1]; + } + else if (DotProduct( pPlane->normal, origin ) - radius - pPlane->dist >= TEST_EPSILON ) + { + node = pNode->children[0]; + } + else + { + if (!EnumerateLeavesInSphere_R( pNode->children[0], + origin, radius, pEnum, context )) + { + return false; + } + + return EnumerateLeavesInSphere_R( pNode->children[1], + origin, radius, pEnum, context ); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesInSphere( Vector const& center, float radius, ISpatialLeafEnumerator* pEnum, int context ) +{ + return EnumerateLeavesInSphere_R( 0, center, radius, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Enumerate leaves along a ray +//----------------------------------------------------------------------------- + +static bool EnumerateLeavesAlongRay_R( int node, Ray_t const& ray, + Vector const& start, Vector const& end, ISpatialLeafEnumerator* pEnum, int context ) +{ + float front,back; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + front = start[pPlane->type] - pPlane->dist; + back = end[pPlane->type] - pPlane->dist; + } + else + { + front = DotProduct(start, pPlane->normal) - pPlane->dist; + back = DotProduct(end, pPlane->normal) - pPlane->dist; + } + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + float denom = DotProduct( ray.m_Delta, pPlane->normal ); + if ( denom == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - DotProduct( ray.m_Start, pPlane->normal ) ) / denom; + if (splitfrac < 0) + splitfrac = 0; + else if (splitfrac > 1) + splitfrac = 1; + } + + // Compute the split point + Vector split; + VectorMA( ray.m_Start, splitfrac, ray.m_Delta, split ); + + bool r = EnumerateLeavesAlongRay_R (pNode->children[side], ray, start, split, pEnum, context ); + if (!r) + return r; + return EnumerateLeavesAlongRay_R (pNode->children[!side], ray, split, end, pEnum, context); + } + } + + return pEnum->EnumerateLeaf( - node - 1, context ); +} + +bool CToolBSPTree::EnumerateLeavesAlongRay( Ray_t const& ray, ISpatialLeafEnumerator* pEnum, int context ) +{ + if (!ray.m_IsSwept) + { + Vector mins, maxs; + VectorAdd( ray.m_Start, ray.m_Extents, maxs ); + VectorSubtract( ray.m_Start, ray.m_Extents, mins ); + + return EnumerateLeavesInBox_R( 0, mins, maxs, pEnum, context ); + } + + // FIXME: Extruded ray not implemented yet + Assert( ray.m_IsRay ); + + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateLeavesAlongRay_R( 0, ray, ray.m_Start, end, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Singleton accessor +//----------------------------------------------------------------------------- + +ISpatialQuery* ToolBSPTree() +{ + static CToolBSPTree s_ToolBSPTree; + return &s_ToolBSPTree; +} + + + +//----------------------------------------------------------------------------- +// Enumerates nodes in front to back order... +//----------------------------------------------------------------------------- + +// FIXME: Do we want this in the IBSPTree interface? + +static bool EnumerateNodesAlongRay_R( int node, Ray_t const& ray, float start, float end, + IBSPNodeEnumerator* pEnum, int context ) +{ + float front, back; + float startDotN, deltaDotN; + + while (node >= 0) + { + dnode_t* pNode = &dnodes[node]; + dplane_t* pPlane = &dplanes[pNode->planenum]; + + if ( pPlane->type <= PLANE_Z ) + { + startDotN = ray.m_Start[pPlane->type]; + deltaDotN = ray.m_Delta[pPlane->type]; + } + else + { + startDotN = DotProduct( ray.m_Start, pPlane->normal ); + deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + } + + front = startDotN + start * deltaDotN - pPlane->dist; + back = startDotN + end * deltaDotN - pPlane->dist; + + if (front <= -TEST_EPSILON && back <= -TEST_EPSILON) + { + node = pNode->children[1]; + } + else if (front >= TEST_EPSILON && back >= TEST_EPSILON) + { + node = pNode->children[0]; + } + else + { + // test the front side first + bool side = front < 0; + + // Compute intersection point based on the original ray + float splitfrac; + if ( deltaDotN == 0.0f ) + { + splitfrac = 1.0f; + } + else + { + splitfrac = ( pPlane->dist - startDotN ) / deltaDotN; + if (splitfrac < 0.0f) + splitfrac = 0.0f; + else if (splitfrac > 1.0f) + splitfrac = 1.0f; + } + + bool r = EnumerateNodesAlongRay_R (pNode->children[side], ray, start, splitfrac, pEnum, context ); + if (!r) + return r; + + // Visit the node... + if (!pEnum->EnumerateNode( node, ray, splitfrac, context )) + return false; + + return EnumerateNodesAlongRay_R (pNode->children[!side], ray, splitfrac, end, pEnum, context); + } + } + + // Visit the leaf... + return pEnum->EnumerateLeaf( - node - 1, ray, start, end, context ); +} + + +bool EnumerateNodesAlongRay( Ray_t const& ray, IBSPNodeEnumerator* pEnum, int context ) +{ + Vector end; + VectorAdd( ray.m_Start, ray.m_Delta, end ); + return EnumerateNodesAlongRay_R( 0, ray, 0.0f, 1.0f, pEnum, context ); +} + + +//----------------------------------------------------------------------------- +// Helps us find all leaves associated with a particular cluster +//----------------------------------------------------------------------------- +CUtlVector g_ClusterLeaves; + +void BuildClusterTable( void ) +{ + int i, j; + int leafCount; + int leafList[MAX_MAP_LEAFS]; + + g_ClusterLeaves.SetCount( dvis->numclusters ); + for ( i = 0; i < dvis->numclusters; i++ ) + { + leafCount = 0; + for ( j = 0; j < numleafs; j++ ) + { + if ( dleafs[j].cluster == i ) + { + leafList[ leafCount ] = j; + leafCount++; + } + } + + g_ClusterLeaves[i].leafCount = leafCount; + if ( leafCount ) + { + g_ClusterLeaves[i].leafs.SetCount( leafCount ); + memcpy( g_ClusterLeaves[i].leafs.Base(), leafList, sizeof(int) * leafCount ); + } + } +} + +// There's a version of this in checksum_engine.cpp!!! Make sure that they match. +static bool CRC_MapFile(CRC32_t *crcvalue, const char *pszFileName) +{ + byte chunk[1024]; + lump_t *curLump; + + FileHandle_t fp = g_pFileSystem->Open( pszFileName, "rb" ); + if ( !fp ) + return false; + + // CRC across all lumps except for the Entities lump + for ( int l = 0; l < HEADER_LUMPS; ++l ) + { + if (l == LUMP_ENTITIES) + continue; + + curLump = &g_pBSPHeader->lumps[l]; + unsigned int nSize = curLump->filelen; + + g_pFileSystem->Seek( fp, curLump->fileofs, FILESYSTEM_SEEK_HEAD ); + + // Now read in 1K chunks + while ( nSize > 0 ) + { + int nBytesRead = 0; + + if ( nSize > 1024 ) + nBytesRead = g_pFileSystem->Read( chunk, 1024, fp ); + else + nBytesRead = g_pFileSystem->Read( chunk, nSize, fp ); + + // If any data was received, CRC it. + if ( nBytesRead > 0 ) + { + nSize -= nBytesRead; + CRC32_ProcessBuffer( crcvalue, chunk, nBytesRead ); + } + else + { + g_pFileSystem->Close( fp ); + return false; + } + } + } + + g_pFileSystem->Close( fp ); + return true; +} + + +void SetHDRMode( bool bHDR ) +{ + g_bHDR = bHDR; + if ( bHDR ) + { + pdlightdata = &dlightdataHDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingHDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexHDR; + pNumworldlights = &numworldlightsHDR; + dworldlights = dworldlightsHDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } + else + { + pdlightdata = &dlightdataLDR; + g_pLeafAmbientLighting = &g_LeafAmbientLightingLDR; + g_pLeafAmbientIndex = &g_LeafAmbientIndexLDR; + pNumworldlights = &numworldlightsLDR; + dworldlights = dworldlightsLDR; +#ifdef VRAD + extern void VRadDetailProps_SetHDRMode( bool bHDR ); + VRadDetailProps_SetHDRMode( bHDR ); +#endif + } +} + +bool SwapVHV( void *pDestBase, void *pSrcBase ) +{ + byte *pDest = (byte*)pDestBase; + byte *pSrc = (byte*)pSrcBase; + + HardwareVerts::FileHeader_t *pHdr = (HardwareVerts::FileHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + g_Swap.SwapFieldsToTargetEndian( (HardwareVerts::FileHeader_t*)pDest, (HardwareVerts::FileHeader_t*)pSrc ); + pSrc += sizeof(HardwareVerts::FileHeader_t); + pDest += sizeof(HardwareVerts::FileHeader_t); + + // This swap is pretty format specific + Assert( pHdr->m_nVersion == VHV_VERSION ); + if ( pHdr->m_nVersion != VHV_VERSION ) + return false; + + HardwareVerts::MeshHeader_t *pSrcMesh = (HardwareVerts::MeshHeader_t*)pSrc; + HardwareVerts::MeshHeader_t *pDestMesh = (HardwareVerts::MeshHeader_t*)pDest; + HardwareVerts::MeshHeader_t *pMesh = (HardwareVerts::MeshHeader_t*)( g_bSwapOnLoad ? pDest : pSrc ); + for ( int i = 0; i < pHdr->m_nMeshes; ++i, ++pMesh, ++pSrcMesh, ++pDestMesh ) + { + g_Swap.SwapFieldsToTargetEndian( pDestMesh, pSrcMesh ); + + pSrc = (byte*)pSrcBase + pMesh->m_nOffset; + pDest = (byte*)pDestBase + pMesh->m_nOffset; + + // Swap as a buffer of integers + // (source is bgra for an Intel swap to argb. PowerPC won't swap, so we need argb source. + g_Swap.SwapBufferToTargetEndian( (int*)pDest, (int*)pSrc, pMesh->m_nVertexes ); + } + return true; +} + +const char *ResolveStaticPropToModel( const char *pPropName ) +{ + // resolve back to static prop + int iProp = -1; + + // filename should be sp_???.vhv or sp_hdr_???.vhv + if ( V_strnicmp( pPropName, "sp_", 3 ) ) + { + return NULL; + } + const char *pPropNumber = V_strrchr( pPropName, '_' ); + if ( pPropNumber ) + { + sscanf( pPropNumber+1, "%d.vhv", &iProp ); + } + else + { + return NULL; + } + + // look up the prop to get to the actual model + if ( iProp < 0 || iProp >= g_StaticPropInstances.Count() ) + { + // prop out of range + return NULL; + } + int iModel = g_StaticPropInstances[iProp]; + if ( iModel < 0 || iModel >= g_StaticPropNames.Count() ) + { + // model out of range + return NULL; + } + + return g_StaticPropNames[iModel].String(); +} + +//----------------------------------------------------------------------------- +// Iterate files in pak file, distribute to converters +// pak file will be ready for serialization upon completion +//----------------------------------------------------------------------------- +void ConvertPakFileContents( const char *pInFilename ) +{ + IZip *newPakFile = IZip::CreateZip( NULL ); + + CUtlBuffer sourceBuf; + CUtlBuffer targetBuf; + bool bConverted; + CUtlVector< CUtlString > hdrFiles; + + int id = -1; + int fileSize; + while ( 1 ) + { + char relativeName[MAX_PATH]; + id = GetNextFilename( GetPakFile(), id, relativeName, sizeof( relativeName ), fileSize ); + if ( id == -1) + break; + + bConverted = false; + sourceBuf.Purge(); + targetBuf.Purge(); + + const char* pExtension = V_GetFileExtension( relativeName ); + const char* pExt = 0; + + bool bOK = ReadFileFromPak( GetPakFile(), relativeName, false, sourceBuf ); + if ( !bOK ) + { + Warning( "Failed to load '%s' from lump pak for conversion or copy in '%s'.\n", relativeName, pInFilename ); + continue; + } + + if ( pExtension && !V_stricmp( pExtension, "vtf" ) ) + { + bOK = g_pVTFConvertFunc( relativeName, sourceBuf, targetBuf, g_pCompressFunc ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + + bConverted = true; + pExt = ".vtf"; + } + else if ( pExtension && !V_stricmp( pExtension, "vhv" ) ) + { + CUtlBuffer tempBuffer; + if ( g_pVHVFixupFunc ) + { + // caller supplied a fixup + const char *pModelName = ResolveStaticPropToModel( relativeName ); + if ( !pModelName ) + { + Warning( "Static Prop '%s' failed to resolve actual model in '%s'.\n", relativeName, pInFilename ); + continue; + } + + // output temp buffer may shrink, must use TellPut() to determine size + bOK = g_pVHVFixupFunc( relativeName, pModelName, sourceBuf, tempBuffer ); + if ( !bOK ) + { + Warning( "Failed to convert '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + } + else + { + // use the source buffer as-is + tempBuffer.EnsureCapacity( sourceBuf.TellMaxPut() ); + tempBuffer.Put( sourceBuf.Base(), sourceBuf.TellMaxPut() ); + } + + // swap the VHV + targetBuf.EnsureCapacity( tempBuffer.TellPut() ); + bOK = SwapVHV( targetBuf.Base(), tempBuffer.Base() ); + if ( !bOK ) + { + Warning( "Failed to swap '%s' in '%s'.\n", relativeName, pInFilename ); + continue; + } + targetBuf.SeekPut( CUtlBuffer::SEEK_HEAD, tempBuffer.TellPut() ); + + if ( g_pCompressFunc ) + { + CUtlBuffer compressedBuffer; + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, sizeof( HardwareVerts::FileHeader_t ) ); + bool bCompressed = g_pCompressFunc( targetBuf, compressedBuffer ); + if ( bCompressed ) + { + // copy all the header data off + CUtlBuffer headerBuffer; + headerBuffer.EnsureCapacity( sizeof( HardwareVerts::FileHeader_t ) ); + headerBuffer.Put( targetBuf.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + + // reform the target with the header and then the compressed data + targetBuf.Clear(); + targetBuf.Put( headerBuffer.Base(), sizeof( HardwareVerts::FileHeader_t ) ); + targetBuf.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + } + + targetBuf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + } + + bConverted = true; + pExt = ".vhv"; + } + + if ( !bConverted ) + { + // straight copy + AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false, IZip::eCompressionType_None ); + } + else + { + // converted filename + V_StripExtension( relativeName, relativeName, sizeof( relativeName ) ); + V_strcat( relativeName, ".360", sizeof( relativeName ) ); + V_strcat( relativeName, pExt, sizeof( relativeName ) ); + AddBufferToPak( newPakFile, relativeName, targetBuf.Base(), targetBuf.TellMaxPut(), false, IZip::eCompressionType_None ); + } + + if ( V_stristr( relativeName, ".hdr" ) || V_stristr( relativeName, "_hdr" ) ) + { + hdrFiles.AddToTail( relativeName ); + } + + DevMsg( "Created '%s' in lump pak in '%s'.\n", relativeName, pInFilename ); + } + + // strip ldr version of hdr files + for ( int i=0; iRemoveFileFromZip( ldrFileName ); + } + } + + // discard old pak in favor of new pak + IZip::ReleaseZip( s_pakFile ); + s_pakFile = newPakFile; +} + +void SetAlignedLumpPosition( int lumpnum, int alignment = LUMP_ALIGNMENT ) +{ + g_pBSPHeader->lumps[lumpnum].fileofs = AlignFilePosition( g_hBSPFile, alignment ); +} + +template< class T > +int SwapLumpToDisk( int fieldType, int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough expansion room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + unsigned int fieldSize = ( fieldType == FIELD_VECTOR ) ? sizeof(Vector) : sizeof(T); + unsigned int count = CopyLumpInternal( fieldType, lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * fieldSize; + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + switch( lumpnum ) + { + case LUMP_VISIBILITY: + SwapVisibilityLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + case LUMP_PHYSCOLLIDE: + // SwapPhyscollideLump may change size + SwapPhyscollideLump( (byte*)pBuffer, (byte*)pBuffer, count ); + g_pBSPHeader->lumps[lumpnum].filelen = count; + break; + + case LUMP_PHYSDISP: + SwapPhysdispLump( (byte*)pBuffer, (byte*)pBuffer, count ); + break; + + default: + g_Swap.SwapBufferToTargetEndian( (T*)pBuffer, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].filelen / sizeof(T) ); + break; + } + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +template< class T > +int SwapLumpToDisk( int lumpnum ) +{ + if ( g_pBSPHeader->lumps[lumpnum].filelen == 0 || g_Lumps.bLumpParsed[lumpnum] ) + return 0; + + DevMsg( "Swapping %s\n", GetLumpName( lumpnum ) ); + + // lump swap may expand, allocate enough room + void *pBuffer = malloc( 2*g_pBSPHeader->lumps[lumpnum].filelen ); + + // CopyLumpInternal will handle the swap on load case + int count = CopyLumpInternal( lumpnum, (T*)pBuffer, g_pBSPHeader->lumps[lumpnum].version ); + g_pBSPHeader->lumps[lumpnum].filelen = count * sizeof(T); + + if ( g_bSwapOnWrite ) + { + // Swap the lump in place before writing + g_Swap.SwapFieldsToTargetEndian( (T*)pBuffer, (T*)pBuffer, count ); + } + + SetAlignedLumpPosition( lumpnum ); + SafeWrite( g_hBSPFile, pBuffer, g_pBSPHeader->lumps[lumpnum].filelen ); + free( pBuffer ); + + return g_pBSPHeader->lumps[lumpnum].filelen; +} + +void SwapLeafAmbientLightingLumpToDisk() +{ + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX ) || HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // current version, swap in place + if ( HasLump( LUMP_LEAF_AMBIENT_INDEX_HDR ) ) + { + // write HDR + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX_HDR ); + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk< dleafambientlighting_t >( LUMP_LEAF_AMBIENT_LIGHTING ); + SwapLumpToDisk< dleafambientindex_t >( LUMP_LEAF_AMBIENT_INDEX ); + } + } + else + { + // older ambient lighting version (before index) + // load older ambient lighting into memory and build ambient/index + // an older leaf version would have already built the new LDR leaf ambient/index + int numLeafs = g_pBSPHeader->lumps[LUMP_LEAFS].filelen / sizeof( dleaf_t ); + LoadLeafAmbientLighting( numLeafs ); + + if ( HasLump( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ) + { + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX_HDR ) ); + + // write HDR + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingHDR.Base(), g_LeafAmbientLightingHDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexHDR.Base(), g_LeafAmbientIndexHDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen = g_LeafAmbientLightingHDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING_HDR].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX_HDR ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen = g_LeafAmbientIndexHDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexHDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX_HDR].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING_HDR] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX_HDR] = true; + + // cull LDR + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = 0; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = 0; + } + else + { + // no HDR, keep LDR version + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_LIGHTING ) ); + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAF_AMBIENT_INDEX ) ); + + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientLightingLDR.Base(), g_LeafAmbientLightingLDR.Count() ); + g_Swap.SwapFieldsToTargetEndian( g_LeafAmbientIndexLDR.Base(), g_LeafAmbientIndexLDR.Count() ); + } + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_LIGHTING ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].version = LUMP_LEAF_AMBIENT_LIGHTING_VERSION; + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen = g_LeafAmbientLightingLDR.Count() * sizeof( dleafambientlighting_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientLightingLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_LIGHTING].filelen ); + + SetAlignedLumpPosition( LUMP_LEAF_AMBIENT_INDEX ); + g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen = g_LeafAmbientIndexLDR.Count() * sizeof( dleafambientindex_t ); + SafeWrite( g_hBSPFile, g_LeafAmbientIndexLDR.Base(), g_pBSPHeader->lumps[LUMP_LEAF_AMBIENT_INDEX].filelen ); + + // mark as processed + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_LIGHTING] = true; + g_Lumps.bLumpParsed[LUMP_LEAF_AMBIENT_INDEX] = true; + } + + g_LeafAmbientLightingLDR.Purge(); + g_LeafAmbientIndexLDR.Purge(); + g_LeafAmbientLightingHDR.Purge(); + g_LeafAmbientIndexHDR.Purge(); + } +} + +void SwapLeafLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_LEAFS ) ); + + // load the leafs + int count = LoadLeafs(); + if ( g_bSwapOnWrite ) + { + g_Swap.SwapFieldsToTargetEndian( dleafs, count ); + } + + bool bOldLeafVersion = ( LumpVersion( LUMP_LEAFS ) == 0 ); + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // not updating the version ye, SwapLeafAmbientLightingLumpToDisk() can detect + g_pBSPHeader->lumps[LUMP_LEAFS].filelen = count * sizeof( dleaf_t ); + } + + SetAlignedLumpPosition( LUMP_LEAFS ); + SafeWrite( g_hBSPFile, dleafs, g_pBSPHeader->lumps[LUMP_LEAFS].filelen ); + + SwapLeafAmbientLightingLumpToDisk(); + + if ( bOldLeafVersion ) + { + // version has been converted in the load process + // can now safely change + g_pBSPHeader->lumps[LUMP_LEAFS].version = 1; + } + +#if defined( BSP_USE_LESS_MEMORY ) + if ( dleafs ) + { + free( dleafs ); + dleafs = NULL; + } +#endif +} + +void SwapOcclusionLumpToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_OCCLUSION ) ); + + LoadOcclusionLump(); + SetAlignedLumpPosition( LUMP_OCCLUSION ); + AddOcclusionLump(); +} + +void SwapPakfileLumpToDisk( const char *pInFilename ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_PAKFILE ) ); + + byte *pakbuffer = NULL; + int paksize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, ( void ** )&pakbuffer ); + if ( paksize > 0 ) + { + GetPakFile()->ActivateByteSwapping( IsX360() ); + GetPakFile()->ParseFromBuffer( pakbuffer, paksize ); + + ConvertPakFileContents( pInFilename ); + } + free( pakbuffer ); + + SetAlignedLumpPosition( LUMP_PAKFILE, XBOX_DVD_SECTORSIZE ); + WritePakFileLump(); + + ReleasePakFileLumps(); +} + +void SwapGameLumpsToDisk( void ) +{ + DevMsg( "Swapping %s\n", GetLumpName( LUMP_GAME_LUMP ) ); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + SetAlignedLumpPosition( LUMP_GAME_LUMP ); + AddGameLumps(); +} + +//----------------------------------------------------------------------------- +// Generate a table of all static props, used for resolving static prop lighting +// files back to their actual mdl. +//----------------------------------------------------------------------------- +void BuildStaticPropNameTable() +{ + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + g_GameLumps.ParseGameLump( g_pBSPHeader ); + + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + int nVersion = g_GameLumps.GetGameLumpVersion( hGameLump ); + if ( nVersion < 4 ) + { + // old unsupported version + return; + } + + if ( nVersion != 4 && nVersion != 5 && nVersion != 6 ) + { + Error( "Unknown Static Prop Lump version %d!\n", nVersion ); + } + + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + // get the model dictionary + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i = 0; i < count; i++ ) + { + g_StaticPropNames.AddToTail( pStaticPropDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( StaticPropDictLump_t ); + + // skip the leaf list + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + pGameLumpData += count * sizeof( StaticPropLeafLump_t ); + + // get the instances + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + for ( int i = 0; i < count; i++ ) + { + int propType; + if ( nVersion == 4 ) + { + propType = ((StaticPropLumpV4_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV4_t ); + } + else if ( nVersion == 5 ) + { + propType = ((StaticPropLumpV5_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLumpV5_t ); + } + else + { + propType = ((StaticPropLump_t *)pGameLumpData)->m_PropType; + pGameLumpData += sizeof( StaticPropLump_t ); + } + g_StaticPropInstances.AddToTail( propType ); + } + } + } + + g_GameLumps.DestroyAllGameLumps(); +} + +int AlignBuffer( CUtlBuffer &buffer, int alignment ) +{ + unsigned int newPosition = AlignValue( buffer.TellPut(), alignment ); + int padLength = newPosition - buffer.TellPut(); + for ( int i = 0; ipLump->fileofs; + int fileOffsetB = pSortedLumpB->pLump->fileofs; + + int fileSizeA = pSortedLumpA->pLump->filelen; + int fileSizeB = pSortedLumpB->pLump->filelen; + + // invalid or empty lumps get sorted together + if ( !fileSizeA ) + { + fileOffsetA = 0; + } + if ( !fileSizeB ) + { + fileOffsetB = 0; + } + + // compare by offset, want ascending + if ( fileOffsetA < fileOffsetB ) + { + return -1; + } + else if ( fileOffsetA > fileOffsetB ) + { + return 1; + } + + return 0; +} + +bool CompressGameLump( dheader_t *pInBSPHeader, dheader_t *pOutBSPHeader, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc ) +{ + CByteswap byteSwap; + + dgamelumpheader_t* pInGameLumpHeader = (dgamelumpheader_t*)(((byte *)pInBSPHeader) + pInBSPHeader->lumps[LUMP_GAME_LUMP].fileofs); + dgamelump_t* pInGameLump = (dgamelump_t*)(pInGameLumpHeader + 1); + + if ( IsX360() ) + { + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInGameLumpHeader ); + byteSwap.SwapFieldsToTargetEndian( pInGameLump, pInGameLumpHeader->lumpCount ); + } + + unsigned int newOffset = outputBuffer.TellPut(); + // Make room for gamelump header and gamelump structs, which we'll write at the end + outputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, sizeof( dgamelumpheader_t ) ); + outputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); + + // Start with input lumps, and fixup + dgamelumpheader_t sOutGameLumpHeader = *pInGameLumpHeader; + CUtlBuffer sOutGameLumpBuf; + sOutGameLumpBuf.Put( pInGameLump, pInGameLumpHeader->lumpCount * sizeof( dgamelump_t ) ); + dgamelump_t *sOutGameLump = (dgamelump_t *)sOutGameLumpBuf.Base(); + + // add a dummy terminal gamelump + // purposely NOT updating the .filelen to reflect the compressed size, but leaving as original size + // callers use the next entry offset to determine compressed size + sOutGameLumpHeader.lumpCount++; + dgamelump_t dummyLump = { 0 }; + outputBuffer.Put( &dummyLump, sizeof( dgamelump_t ) ); + + for ( int i = 0; i < pInGameLumpHeader->lumpCount; i++ ) + { + CUtlBuffer inputBuffer; + CUtlBuffer compressedBuffer; + + sOutGameLump[i].fileofs = AlignBuffer( outputBuffer, 4 ); + + if ( pInGameLump[i].filelen ) + { + if ( pInGameLump[i].flags & GAMELUMPFLAG_COMPRESSED ) + { + byte *pCompressedLump = ((byte *)pInBSPHeader) + pInGameLump[i].fileofs; + if ( CLZMA::IsCompressed( pCompressedLump ) ) + { + inputBuffer.EnsureCapacity( CLZMA::GetActualSize( pCompressedLump ) ); + unsigned int outSize = CLZMA::Uncompress( pCompressedLump, (unsigned char *)inputBuffer.Base() ); + inputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, outSize ); + if ( outSize != CLZMA::GetActualSize( pCompressedLump ) ) + { + Warning( "Decompressed size differs from header, BSP may be corrupt\n" ); + } + } + else + { + Assert( CLZMA::IsCompressed( pCompressedLump ) ); + Warning( "Unsupported BSP: Unrecognized compressed game lump\n" ); + } + + } + else + { + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pInGameLump[i].fileofs, + pInGameLump[i].filelen, pInGameLump[i].filelen ); + } + + bool bCompressed = pCompressFunc ? pCompressFunc( inputBuffer, compressedBuffer ) : false; + if ( bCompressed ) + { + sOutGameLump[i].flags |= GAMELUMPFLAG_COMPRESSED; + + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // as is, clear compression flag from input lump + sOutGameLump[i].flags &= ~GAMELUMPFLAG_COMPRESSED; + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + + // fix the dummy terminal lump + int lastLump = sOutGameLumpHeader.lumpCount-1; + sOutGameLump[lastLump].fileofs = outputBuffer.TellPut(); + + if ( IsX360() ) + { + // fix the output for 360, swapping it back + byteSwap.SwapFieldsToTargetEndian( sOutGameLump, sOutGameLumpHeader.lumpCount ); + byteSwap.SwapFieldsToTargetEndian( &sOutGameLumpHeader ); + } + + pOutBSPHeader->lumps[LUMP_GAME_LUMP].fileofs = newOffset; + pOutBSPHeader->lumps[LUMP_GAME_LUMP].filelen = outputBuffer.TellPut() - newOffset; + // We set GAMELUMPFLAG_COMPRESSED and handle compression at the sub-lump level, this whole lump is not + // decompressable as a block. + pOutBSPHeader->lumps[LUMP_GAME_LUMP].uncompressedSize = 0; + + // Rewind to start and write lump headers + unsigned int endOffset = outputBuffer.TellPut(); + outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, newOffset ); + outputBuffer.Put( &sOutGameLumpHeader, sizeof( dgamelumpheader_t ) ); + outputBuffer.Put( sOutGameLumpBuf.Base(), sOutGameLumpBuf.TellPut() ); + outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, endOffset ); + + return true; +} + +//----------------------------------------------------------------------------- +// Compress callback for RepackBSP +//----------------------------------------------------------------------------- +bool RepackBSPCallback_LZMA( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer ) +{ + if ( !inputBuffer.TellPut() ) + { + // nothing to do + return false; + } + + unsigned int originalSize = inputBuffer.TellPut() - inputBuffer.TellGet(); + unsigned int compressedSize = 0; + unsigned char *pCompressedOutput = LZMA_Compress( (unsigned char *)inputBuffer.Base() + inputBuffer.TellGet(), + originalSize, &compressedSize ); + if ( pCompressedOutput ) + { + outputBuffer.Put( pCompressedOutput, compressedSize ); + DevMsg( "Compressed bsp lump %u -> %u bytes\n", originalSize, compressedSize ); + free( pCompressedOutput ); + return true; + } + + return false; +} + + +bool RepackBSP( CUtlBuffer &inputBuffer, CUtlBuffer &outputBuffer, CompressFunc_t pCompressFunc, IZip::eCompressionType packfileCompression ) +{ + dheader_t *pInBSPHeader = (dheader_t *)inputBuffer.Base(); + // The 360 swaps this header to disk. For some reason. + if ( pInBSPHeader->ident != ( IsX360() ? BigLong( IDBSPHEADER ) : IDBSPHEADER ) ) + { + Warning( "RepackBSP given invalid input data\n" ); + return false; + } + + CByteswap byteSwap; + if ( IsX360() ) + { + // bsp is 360, swap the header back + byteSwap.ActivateByteSwapping( true ); + byteSwap.SwapFieldsToTargetEndian( pInBSPHeader ); + } + + unsigned int headerOffset = outputBuffer.TellPut(); + outputBuffer.Put( pInBSPHeader, sizeof( dheader_t ) ); + + // This buffer grows dynamically, don't keep pointers to it around. Write out header at end. + dheader_t sOutBSPHeader = *pInBSPHeader; + + // must adhere to input lump's offset order and process according to that, NOT lump num + // sort by offset order + CUtlVector< SortedLump_t > sortedLumps; + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + int iIndex = sortedLumps.AddToTail(); + sortedLumps[iIndex].lumpNum = i; + sortedLumps[iIndex].pLump = &pInBSPHeader->lumps[i]; + } + sortedLumps.Sort( SortLumpsByOffset ); + + // iterate in sorted order + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + SortedLump_t *pSortedLump = &sortedLumps[i]; + int lumpNum = pSortedLump->lumpNum; + + // Should be set below, don't copy over old data + sOutBSPHeader.lumps[lumpNum].fileofs = 0; + sOutBSPHeader.lumps[lumpNum].filelen = 0; + // Only set by compressed lumps + sOutBSPHeader.lumps[lumpNum].uncompressedSize = 0; + + if ( pSortedLump->pLump->filelen ) // Otherwise its degenerate + { + int alignment = 4; + if ( lumpNum == LUMP_PAKFILE ) + { + alignment = 2048; + } + unsigned int newOffset = AlignBuffer( outputBuffer, alignment ); + + CUtlBuffer inputBuffer; + if ( pSortedLump->pLump->uncompressedSize ) + { + byte *pCompressedLump = ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs; + if ( CLZMA::IsCompressed( pCompressedLump ) && pSortedLump->pLump->uncompressedSize == CLZMA::GetActualSize( pCompressedLump ) ) + { + inputBuffer.EnsureCapacity( CLZMA::GetActualSize( pCompressedLump ) ); + unsigned int outSize = CLZMA::Uncompress( pCompressedLump, (unsigned char *)inputBuffer.Base() ); + inputBuffer.SeekPut( CUtlBuffer::SEEK_CURRENT, outSize ); + if ( outSize != pSortedLump->pLump->uncompressedSize ) + { + Warning( "Decompressed size differs from header, BSP may be corrupt\n" ); + } + } + else + { + Assert( CLZMA::IsCompressed( pCompressedLump ) && + pSortedLump->pLump->uncompressedSize == CLZMA::GetActualSize( pCompressedLump ) ); + Warning( "Unsupported BSP: Unrecognized compressed lump\n" ); + } + } + else + { + // Just use input + inputBuffer.SetExternalBuffer( ((byte *)pInBSPHeader) + pSortedLump->pLump->fileofs, + pSortedLump->pLump->filelen, pSortedLump->pLump->filelen ); + } + + if ( lumpNum == LUMP_GAME_LUMP ) + { + // the game lump has to have each of its components individually compressed + CompressGameLump( pInBSPHeader, &sOutBSPHeader, outputBuffer, pCompressFunc ); + } + else if ( lumpNum == LUMP_PAKFILE ) + { + IZip *newPakFile = IZip::CreateZip( NULL ); + IZip *oldPakFile = IZip::CreateZip( NULL ); + oldPakFile->ParseFromBuffer( inputBuffer.Base(), inputBuffer.Size() ); + + int id = -1; + int fileSize; + while ( 1 ) + { + char relativeName[MAX_PATH]; + id = GetNextFilename( oldPakFile, id, relativeName, sizeof( relativeName ), fileSize ); + if ( id == -1 ) + break; + + CUtlBuffer sourceBuf; + CUtlBuffer targetBuf; + + bool bOK = ReadFileFromPak( oldPakFile, relativeName, false, sourceBuf ); + if ( !bOK ) + { + Error( "Failed to load '%s' from lump pak for repacking.\n", relativeName ); + continue; + } + + AddBufferToPak( newPakFile, relativeName, sourceBuf.Base(), sourceBuf.TellMaxPut(), false, packfileCompression ); + + DevMsg( "Repacking BSP: Created '%s' in lump pak\n", relativeName ); + } + + // save new pack to buffer + newPakFile->SaveToBuffer( outputBuffer ); + sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; + sOutBSPHeader.lumps[lumpNum].filelen = outputBuffer.TellPut() - newOffset; + // Note that this *lump* is uncompressed, it just contains a packfile that uses compression, so we're + // not setting lumps[lumpNum].uncompressedSize + + IZip::ReleaseZip( oldPakFile ); + IZip::ReleaseZip( newPakFile ); + } + else + { + CUtlBuffer compressedBuffer; + bool bCompressed = pCompressFunc ? pCompressFunc( inputBuffer, compressedBuffer ) : false; + if ( bCompressed ) + { + sOutBSPHeader.lumps[lumpNum].uncompressedSize = inputBuffer.TellPut(); + sOutBSPHeader.lumps[lumpNum].filelen = compressedBuffer.TellPut(); + sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; + outputBuffer.Put( compressedBuffer.Base(), compressedBuffer.TellPut() ); + compressedBuffer.Purge(); + } + else + { + // add as is + sOutBSPHeader.lumps[lumpNum].fileofs = newOffset; + sOutBSPHeader.lumps[lumpNum].filelen = inputBuffer.TellPut(); + outputBuffer.Put( inputBuffer.Base(), inputBuffer.TellPut() ); + } + } + } + } + + if ( IsX360() ) + { + // fix the output for 360, swapping it back + byteSwap.SetTargetBigEndian( true ); + byteSwap.SwapFieldsToTargetEndian( &sOutBSPHeader ); + } + + // Write out header + unsigned int endOffset = outputBuffer.TellPut(); + outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, headerOffset ); + outputBuffer.Put( &sOutBSPHeader, sizeof( sOutBSPHeader ) ); + outputBuffer.SeekPut( CUtlBuffer::SEEK_HEAD, endOffset ); + + return true; +} + +//----------------------------------------------------------------------------- +// For all lumps in a bsp: Loads the lump from file A, swaps it, writes it to file B. +// This limits the memory used for the swap process which helps the Xbox 360. +// +// NOTE: These lumps will be written to the file in exactly the order they appear here, +// so they can be shifted around if desired for file access optimization. +//----------------------------------------------------------------------------- +bool SwapBSPFile( const char *pInFilename, const char *pOutFilename, bool bSwapOnLoad, VTFConvertFunc_t pVTFConvertFunc, VHVFixupFunc_t pVHVFixupFunc, CompressFunc_t pCompressFunc ) +{ + DevMsg( "Creating %s\n", pOutFilename ); + + if ( !g_pFileSystem->FileExists( pInFilename ) ) + { + Warning( "Error! Couldn't open input file %s - BSP swap failed!\n", pInFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + + if ( !pVTFConvertFunc ) + { + Warning( "Error! Missing VTF Conversion function\n" ); + return false; + } + g_pVTFConvertFunc = pVTFConvertFunc; + + // optional VHV fixup + g_pVHVFixupFunc = pVHVFixupFunc; + + // optional compression callback + g_pCompressFunc = pCompressFunc; + + // These must be mutually exclusive + g_bSwapOnLoad = bSwapOnLoad; + g_bSwapOnWrite = !bSwapOnLoad; + + g_Swap.ActivateByteSwapping( true ); + + OpenBSPFile( pInFilename ); + + // CRC the bsp first + CRC32_t mapCRC; + CRC32_Init(&mapCRC); + if ( !CRC_MapFile( &mapCRC, pInFilename ) ) + { + Warning( "Failed to CRC the bsp\n" ); + return false; + } + + // hold a dictionary of all the static prop names + // this is needed to properly convert any VHV files inside the pak lump + BuildStaticPropNameTable(); + + // Set the output file pointer after the header + dheader_t dummyHeader = { 0 }; + SafeWrite( g_hBSPFile, &dummyHeader, sizeof( dheader_t ) ); + + // To allow for alignment fixups, the lumps will be written to the + // output file in the order they appear in this function. + + // NOTE: Flags for 360 !!!MUST!!! be first + SwapLumpToDisk< dflagslump_t >( LUMP_MAP_FLAGS ); + + // complex lump swaps first or for later contingent data + SwapLeafLumpToDisk(); + SwapOcclusionLumpToDisk(); + SwapGameLumpsToDisk(); + + // Strip dead or non relevant lumps + g_pBSPHeader->lumps[LUMP_DISP_LIGHTMAP_ALPHAS].filelen = 0; + g_pBSPHeader->lumps[LUMP_FACEIDS].filelen = 0; + + // Strip obsolete LDR in favor of HDR + if ( SwapLumpToDisk( LUMP_FACES_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_FACES].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_FACES ); + } + + if ( SwapLumpToDisk( LUMP_WORLDLIGHTS_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_WORLDLIGHTS].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( LUMP_WORLDLIGHTS ); + } + + // Simple lump swaps + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSDISP ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_PHYSCOLLIDE ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_VISIBILITY ); + SwapLumpToDisk( LUMP_MODELS ); + SwapLumpToDisk( LUMP_VERTEXES ); + SwapLumpToDisk( LUMP_PLANES ); + SwapLumpToDisk( LUMP_NODES ); + SwapLumpToDisk( LUMP_TEXINFO ); + SwapLumpToDisk( LUMP_TEXDATA ); + SwapLumpToDisk( LUMP_DISPINFO ); + SwapLumpToDisk( LUMP_DISP_VERTS ); + SwapLumpToDisk( LUMP_DISP_TRIS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_DISP_LIGHTMAP_SAMPLE_POSITIONS ); + SwapLumpToDisk( LUMP_FACE_MACRO_TEXTURE_INFO ); + SwapLumpToDisk( LUMP_PRIMITIVES ); + SwapLumpToDisk( LUMP_PRIMVERTS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_PRIMINDICES ); + SwapLumpToDisk( LUMP_ORIGINALFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFFACES ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFBRUSHES ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_SURFEDGES ); + SwapLumpToDisk( LUMP_EDGES ); + SwapLumpToDisk( LUMP_BRUSHES ); + SwapLumpToDisk( LUMP_BRUSHSIDES ); + SwapLumpToDisk( LUMP_AREAS ); + SwapLumpToDisk( LUMP_AREAPORTALS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_ENTITIES ); + SwapLumpToDisk( LUMP_LEAFWATERDATA ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_VERTNORMALS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_VERTNORMALINDICES ); + SwapLumpToDisk( FIELD_VECTOR, LUMP_CLIPPORTALVERTS ); + SwapLumpToDisk( LUMP_CUBEMAPS ); + SwapLumpToDisk( FIELD_CHARACTER, LUMP_TEXDATA_STRING_DATA ); + SwapLumpToDisk( FIELD_INTEGER, LUMP_TEXDATA_STRING_TABLE ); + SwapLumpToDisk( LUMP_OVERLAYS ); + SwapLumpToDisk( LUMP_WATEROVERLAYS ); + SwapLumpToDisk( FIELD_SHORT, LUMP_LEAFMINDISTTOWATER ); + SwapLumpToDisk( LUMP_OVERLAY_FADES ); + + + // NOTE: this data placed at the end for the sake of 360: + { + // NOTE: lighting must be the penultimate lump + // (allows 360 to free this memory part-way through map loading) + if ( SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING_HDR ) ) + { + g_pBSPHeader->lumps[LUMP_LIGHTING].filelen = 0; + } + else + { + // no HDR, keep LDR version + SwapLumpToDisk( FIELD_CHARACTER, LUMP_LIGHTING ); + } + // NOTE: Pakfile for 360 !!!MUST!!! be last + SwapPakfileLumpToDisk( pInFilename ); + } + + + // Store the crc in the flags lump version field + g_pBSPHeader->lumps[LUMP_MAP_FLAGS].version = mapCRC; + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Warn of any lumps that didn't get swapped + for ( int i = 0; i < HEADER_LUMPS; ++i ) + { + if ( HasLump( i ) && !g_Lumps.bLumpParsed[i] ) + { + // a new lump got added that needs to have a swap function + Warning( "BSP: '%s', %s has no swap or copy function. Discarding!\n", pInFilename, GetLumpName(i) ); + + // the data didn't get copied, so don't reference garbage + g_pBSPHeader->lumps[i].filelen = 0; + } + } + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + + // Cleanup + g_Swap.ActivateByteSwapping( false ); + + CloseBSPFile(); + + g_StaticPropNames.Purge(); + g_StaticPropInstances.Purge(); + + DevMsg( "Finished BSP Swap\n" ); + + // caller provided compress func will further compress compatible lumps + if ( pCompressFunc ) + { + CUtlBuffer inputBuffer; + if ( !g_pFileSystem->ReadFile( pOutFilename, NULL, inputBuffer ) ) + { + Warning( "Error! Couldn't read file %s - final BSP compression failed!\n", pOutFilename ); + return false; + } + + CUtlBuffer outputBuffer; + if ( !RepackBSP( inputBuffer, outputBuffer, pCompressFunc, IZip::eCompressionType_None ) ) + { + Warning( "Error! Failed to compress BSP '%s'!\n", pOutFilename ); + return false; + } + + g_hBSPFile = SafeOpenWrite( pOutFilename ); + if ( !g_hBSPFile ) + { + Warning( "Error! Couldn't open output file %s - BSP swap failed!\n", pOutFilename ); + return false; + } + SafeWrite( g_hBSPFile, outputBuffer.Base(), outputBuffer.TellPut() ); + g_pFileSystem->Close( g_hBSPFile ); + g_hBSPFile = 0; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Get the pak lump from a BSP +//----------------------------------------------------------------------------- +bool GetPakFileLump( const char *pBSPFilename, void **pPakData, int *pPakSize ) +{ + *pPakData = NULL; + *pPakSize = 0; + + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = !bSwap; + + OpenBSPFile( pBSPFilename ); + + if ( g_pBSPHeader->lumps[LUMP_PAKFILE].filelen ) + { + *pPakSize = CopyVariableLump( FIELD_CHARACTER, LUMP_PAKFILE, pPakData ); + } + + CloseBSPFile(); + + return true; +} + +// compare function for qsort below +static int LumpOffsetCompare( const void *pElem1, const void *pElem2 ) +{ + int lump1 = *(byte *)pElem1; + int lump2 = *(byte *)pElem2; + + if ( lump1 != lump2 ) + { + // force LUMP_MAP_FLAGS to be first, always + if ( lump1 == LUMP_MAP_FLAGS ) + { + return -1; + } + else if ( lump2 == LUMP_MAP_FLAGS ) + { + return 1; + } + + // force LUMP_PAKFILE to be last, always + if ( lump1 == LUMP_PAKFILE ) + { + return 1; + } + else if ( lump2 == LUMP_PAKFILE ) + { + return -1; + } + } + + int fileOffset1 = g_pBSPHeader->lumps[lump1].fileofs; + int fileOffset2 = g_pBSPHeader->lumps[lump2].fileofs; + + // invalid or empty lumps will get sorted together + if ( !g_pBSPHeader->lumps[lump1].filelen ) + { + fileOffset1 = 0; + } + + if ( !g_pBSPHeader->lumps[lump2].filelen ) + { + fileOffset2 = 0; + } + + // compare by offset + if ( fileOffset1 < fileOffset2 ) + { + return -1; + } + else if ( fileOffset1 > fileOffset2 ) + { + return 1; + } + return 0; +} + +//----------------------------------------------------------------------------- +// Replace the pak lump in a BSP +//----------------------------------------------------------------------------- +bool SetPakFileLump( const char *pBSPFilename, const char *pNewFilename, void *pPakData, int pakSize ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // determine endian nature + dheader_t *pHeader; + LoadFile( pBSPFilename, (void **)&pHeader ); + bool bSwap = ( pHeader->ident == BigLong( IDBSPHEADER ) ); + free( pHeader ); + + g_bSwapOnLoad = bSwap; + g_bSwapOnWrite = bSwap; + + OpenBSPFile( pBSPFilename ); + + // save a copy of the old header + // generating a new bsp is a destructive operation + dheader_t oldHeader; + oldHeader = *g_pBSPHeader; + + g_hBSPFile = SafeOpenWrite( pNewFilename ); + if ( !g_hBSPFile ) + { + return false; + } + + // placeholder only, reset at conclusion + WriteData( &oldHeader ); + + // lumps must be reserialized in same relative offset order + // build sorted order table + int readOrder[HEADER_LUMPS]; + for ( int i=0; ilumps[lump].filelen; + if ( length ) + { + // save the lump data + int offset = g_pBSPHeader->lumps[lump].fileofs; + SetAlignedLumpPosition( lump ); + SafeWrite( g_hBSPFile, (byte *)g_pBSPHeader + offset, length ); + } + else + { + g_pBSPHeader->lumps[lump].fileofs = 0; + } + } + + // Always write the pak file at the end + // Pad out the end of the file to a sector boundary for optimal IO + g_pBSPHeader->lumps[LUMP_PAKFILE].fileofs = AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + g_pBSPHeader->lumps[LUMP_PAKFILE].filelen = pakSize; + SafeWrite( g_hBSPFile, pPakData, pakSize ); + + // Pad out the end of the file to a sector boundary for optimal IO + AlignFilePosition( g_hBSPFile, XBOX_DVD_SECTORSIZE ); + + // Write the updated header + g_pFileSystem->Seek( g_hBSPFile, 0, FILESYSTEM_SEEK_HEAD ); + WriteData( g_pBSPHeader ); + g_pFileSystem->Close( g_hBSPFile ); + + CloseBSPFile(); + + return true; +} + +//----------------------------------------------------------------------------- +// Build a list of files that BSP owns, world/cubemap materials, static props, etc. +//----------------------------------------------------------------------------- +bool GetBSPDependants( const char *pBSPFilename, CUtlVector< CUtlString > *pList ) +{ + if ( !g_pFileSystem->FileExists( pBSPFilename ) ) + { + Warning( "Error! Couldn't open file %s!\n", pBSPFilename ); + return false; + } + + // must be set, but exact hdr not critical for dependant traversal + SetHDRMode( false ); + + LoadBSPFile( pBSPFilename ); + + char szBspName[MAX_PATH]; + V_FileBase( pBSPFilename, szBspName, sizeof( szBspName ) ); + V_SetExtension( szBspName, ".bsp", sizeof( szBspName ) ); + + // get embedded pak files, and internals + char szFilename[MAX_PATH]; + int fileSize; + int fileId = -1; + for ( ;; ) + { + fileId = GetPakFile()->GetNextFilename( fileId, szFilename, sizeof( szFilename ), fileSize ); + if ( fileId == -1 ) + { + break; + } + pList->AddToTail( szFilename ); + } + + // get all the world materials + for ( int i=0; iAddToTail( szFilename ); + } + + // get all the static props + GameLumpHandle_t hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + StaticPropDictLump_t *pStaticPropDictLump = (StaticPropDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pStaticPropDictLump[i].m_Name ); + } + } + } + + // get all the detail props + hGameLump = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + if ( hGameLump != g_GameLumps.InvalidGameLump() ) + { + byte *pGameLumpData = (byte *)g_GameLumps.GetGameLump( hGameLump ); + if ( pGameLumpData && g_GameLumps.GameLumpSize( hGameLump ) ) + { + int count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + + DetailObjectDictLump_t *pDetailObjectDictLump = (DetailObjectDictLump_t *)pGameLumpData; + for ( int i=0; iAddToTail( pDetailObjectDictLump[i].m_Name ); + } + pGameLumpData += count * sizeof( DetailObjectDictLump_t ); + + if ( g_GameLumps.GetGameLumpVersion( hGameLump ) == 4 ) + { + count = ((int *)pGameLumpData)[0]; + pGameLumpData += sizeof( int ); + if ( count ) + { + // All detail prop sprites must lie in the material detail/detailsprites + pList->AddToTail( "materials/detail/detailsprites.vmt" ); + } + } + } + } + + UnloadBSPFile(); + + return true; +} + diff --git a/utils/common/cmdlib.cpp b/utils/common/cmdlib.cpp index a5f9b74..fd38c11 100644 --- a/utils/common/cmdlib.cpp +++ b/utils/common/cmdlib.cpp @@ -1,1007 +1,1011 @@ -//========= Copyright Valve Corporation, All rights reserved. ============// -// -// Purpose: -// -// $NoKeywords: $ -// -//=============================================================================// -// ----------------------- -// cmdlib.c -// ----------------------- -#include "tier0/platform.h" -#ifdef IS_WINDOWS_PC -#include -#endif -#include "cmdlib.h" -#include -#include -#include "tier1/strtools.h" -#ifdef _WIN32 -#include -#endif -#include "utlvector.h" -#include "filesystem_helpers.h" -#include "utllinkedlist.h" -#include "tier0/icommandline.h" -#include "KeyValues.h" -#include "filesystem_tools.h" - -#if defined( MPI ) - - #include "vmpi.h" - #include "vmpi_tools_shared.h" - -#endif - - -#if defined( _WIN32 ) || defined( WIN32 ) -#include -#endif - -#if defined( _X360 ) -#include "xbox/xbox_win32stubs.h" -#endif - -// set these before calling CheckParm -int myargc; -char **myargv; - -char com_token[1024]; - -qboolean archive; -char archivedir[1024]; - -FileHandle_t g_pLogFile = 0; - -CUtlLinkedList g_CleanupFunctions; -CUtlLinkedList g_ExtraSpewHooks; - -bool g_bStopOnExit = false; -void (*g_ExtraSpewHook)(const char*) = NULL; - -#if defined( _WIN32 ) || defined( WIN32 ) - -void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) -{ - static CUtlVector buf; - if ( buf.Count() == 0 ) - buf.SetCount( 1024 ); - - va_list marker; - va_start( marker, pFormat ); - - while ( 1 ) - { - int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); - if ( ret >= 0 ) - { - // Write the string. - g_pFileSystem->Write( buf.Base(), ret, hFile ); - - break; - } - else - { - // Make the buffer larger. - int newSize = buf.Count() * 2; - buf.SetCount( newSize ); - if ( buf.Count() != newSize ) - { - Error( "CmdLib_FPrintf: can't allocate space for text." ); - } - } - } - - va_end( marker ); -} - -char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) -{ - int iCur=0; - for ( ; iCur < (outSize-1); iCur++ ) - { - char c; - if ( !g_pFileSystem->Read( &c, 1, hFile ) ) - { - if ( iCur == 0 ) - return NULL; - else - break; - } - - pOut[iCur] = c; - if ( c == '\n' ) - break; - - if ( c == EOF ) - { - if ( iCur == 0 ) - return NULL; - else - break; - } - } - - pOut[iCur] = 0; - return pOut; -} - -#if !defined( _X360 ) -#include -#endif - -// This pauses before exiting if they use -StopOnExit. Useful for debugging. -class CExitStopper -{ -public: - ~CExitStopper() - { - if ( g_bStopOnExit ) - { - Warning( "\nPress any key to quit.\n" ); - getch(); - } - } -} g_ExitStopper; - - -static unsigned short g_InitialColor = 0xFFFF; -static unsigned short g_LastColor = 0xFFFF; -static unsigned short g_BadColor = 0xFFFF; -static WORD g_BackgroundFlags = 0xFFFF; -static void GetInitialColors( ) -{ -#if !defined( _X360 ) - // Get the old background attributes. - CONSOLE_SCREEN_BUFFER_INFO oldInfo; - GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); - g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); - g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); - - g_BadColor = 0; - if (g_BackgroundFlags & BACKGROUND_RED) - g_BadColor |= FOREGROUND_RED; - if (g_BackgroundFlags & BACKGROUND_GREEN) - g_BadColor |= FOREGROUND_GREEN; - if (g_BackgroundFlags & BACKGROUND_BLUE) - g_BadColor |= FOREGROUND_BLUE; - if (g_BackgroundFlags & BACKGROUND_INTENSITY) - g_BadColor |= FOREGROUND_INTENSITY; -#endif -} - -WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) -{ - WORD ret = g_LastColor; -#if !defined( _X360 ) - - g_LastColor = 0; - if( red ) g_LastColor |= FOREGROUND_RED; - if( green ) g_LastColor |= FOREGROUND_GREEN; - if( blue ) g_LastColor |= FOREGROUND_BLUE; - if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; - - // Just use the initial color if there's a match... - if (g_LastColor == g_BadColor) - g_LastColor = g_InitialColor; - - SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); -#endif - return ret; -} - -void RestoreConsoleTextColor( WORD color ) -{ -#if !defined( _X360 ) - SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); - g_LastColor = color; -#endif -} - - -#if defined( CMDLIB_NODBGLIB ) - -// This can go away when everything is in bin. -void Error( char const *pMsg, ... ) -{ - va_list marker; - va_start( marker, pMsg ); - vprintf( pMsg, marker ); - va_end( marker ); - - exit( -1 ); -} - -#else - -CRITICAL_SECTION g_SpewCS; -bool g_bSpewCSInitted = false; -bool g_bSuppressPrintfOutput = false; - -SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) -{ - // Hopefully two threads won't call this simultaneously right at the start! - if ( !g_bSpewCSInitted ) - { - InitializeCriticalSection( &g_SpewCS ); - g_bSpewCSInitted = true; - } - - WORD old; - SpewRetval_t retVal; - - EnterCriticalSection( &g_SpewCS ); - { - if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) - { - Color c = *GetSpewOutputColor(); - if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) - { - // custom color - old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); - } - else - { - old = SetConsoleTextColor( 1, 1, 1, 0 ); - } - retVal = SPEW_CONTINUE; - } - else if( type == SPEW_WARNING ) - { - old = SetConsoleTextColor( 1, 1, 0, 1 ); - retVal = SPEW_CONTINUE; - } - else if( type == SPEW_ASSERT ) - { - old = SetConsoleTextColor( 1, 0, 0, 1 ); - retVal = SPEW_DEBUGGER; - -#ifdef MPI - // VMPI workers don't want to bring up dialogs and suchlike. - // They need to have a special function installed to handle - // the exceptions and write the minidumps. - // Install the function after VMPI_Init with a call: - // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); - if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) - { - // Generating an exception and letting the - // installed handler handle it - ::RaiseException - ( - 0, // dwExceptionCode - EXCEPTION_NONCONTINUABLE, // dwExceptionFlags - 0, // nNumberOfArguments, - NULL // const ULONG_PTR* lpArguments - ); - - // Never get here (non-continuable exception) - - VMPI_HandleCrash( pMsg, NULL, true ); - exit( 0 ); - } -#endif - } - else if( type == SPEW_ERROR ) - { - old = SetConsoleTextColor( 1, 0, 0, 1 ); - retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). - } - else - { - old = SetConsoleTextColor( 1, 1, 1, 1 ); - retVal = SPEW_CONTINUE; - } - - if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) - printf( "%s", pMsg ); - - OutputDebugString( pMsg ); - - if ( type == SPEW_ERROR ) - { - printf( "\n" ); - OutputDebugString( "\n" ); - } - - if( g_pLogFile ) - { - CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); - g_pFileSystem->Flush( g_pLogFile ); - } - - // Dispatch to other spew hooks. - FOR_EACH_LL( g_ExtraSpewHooks, i ) - g_ExtraSpewHooks[i]( pMsg ); - - RestoreConsoleTextColor( old ); - } - LeaveCriticalSection( &g_SpewCS ); - - if ( type == SPEW_ERROR ) - { - CmdLib_Exit( 1 ); - } - - return retVal; -} - - -void InstallSpewFunction() -{ - setvbuf( stdout, NULL, _IONBF, 0 ); - setvbuf( stderr, NULL, _IONBF, 0 ); - - SpewOutputFunc( CmdLib_SpewOutputFunc ); - GetInitialColors(); -} - - -void InstallExtraSpewHook( SpewHookFn pFn ) -{ - g_ExtraSpewHooks.AddToTail( pFn ); -} - -#if 0 -void CmdLib_AllocError( unsigned long size ) -{ - Error( "Error trying to allocate %d bytes.\n", size ); -} - - -int CmdLib_NewHandler( size_t size ) -{ - CmdLib_AllocError( size ); - return 0; -} -#endif - -void InstallAllocationFunctions() -{ -// _set_new_mode( 1 ); // so if malloc() fails, we exit. -// _set_new_handler( CmdLib_NewHandler ); -} - -void SetSpewFunctionLogFile( char const *pFilename ) -{ - Assert( (!g_pLogFile) ); - g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); - - Assert( g_pLogFile ); - if (!g_pLogFile) - Error("Can't create LogFile:\"%s\"\n", pFilename ); - - CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); -} - - -void CloseSpewFunctionLogFile() -{ - if ( g_pFileSystem && g_pLogFile ) - { - g_pFileSystem->Close( g_pLogFile ); - g_pLogFile = FILESYSTEM_INVALID_HANDLE; - } -} - - -void CmdLib_AtCleanup( CleanupFn pFn ) -{ - g_CleanupFunctions.AddToTail( pFn ); -} - - -void CmdLib_Cleanup() -{ - CloseSpewFunctionLogFile(); - - CmdLib_TermFileSystem(); - - FOR_EACH_LL( g_CleanupFunctions, i ) - g_CleanupFunctions[i](); - -#if defined( MPI ) - // Unfortunately, when you call exit(), even if you have things registered with atexit(), - // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE - // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup - // everything that uses threads before exiting. - VMPI_Finalize(); -#endif -} - - -void CmdLib_Exit( int exitCode ) -{ - TerminateProcess( GetCurrentProcess(), 1 ); -} - - - -#endif - -#endif - - - - -/* -=================== -ExpandWildcards - -Mimic unix command line expansion -=================== -*/ -#define MAX_EX_ARGC 1024 -int ex_argc; -char *ex_argv[MAX_EX_ARGC]; -#if defined( _WIN32 ) && !defined( _X360 ) -#include "io.h" -void ExpandWildcards (int *argc, char ***argv) -{ - struct _finddata_t fileinfo; - int handle; - int i; - char filename[1024]; - char filebase[1024]; - char *path; - - ex_argc = 0; - for (i=0 ; i<*argc ; i++) - { - path = (*argv)[i]; - if ( path[0] == '-' - || ( !strstr(path, "*") && !strstr(path, "?") ) ) - { - ex_argv[ex_argc++] = path; - continue; - } - - handle = _findfirst (path, &fileinfo); - if (handle == -1) - return; - - Q_ExtractFilePath (path, filebase, sizeof( filebase )); - - do - { - sprintf (filename, "%s%s", filebase, fileinfo.name); - ex_argv[ex_argc++] = copystring (filename); - } while (_findnext( handle, &fileinfo ) != -1); - - _findclose (handle); - } - - *argc = ex_argc; - *argv = ex_argv; -} -#else -void ExpandWildcards (int *argc, char ***argv) -{ -} -#endif - - -// only printf if in verbose mode -qboolean verbose = false; -void qprintf (const char *format, ...) -{ - if (!verbose) - return; - - va_list argptr; - va_start (argptr,format); - - char str[2048]; - Q_vsnprintf( str, sizeof(str), format, argptr ); - -#if defined( CMDLIB_NODBGLIB ) - printf( "%s", str ); -#else - Msg( "%s", str ); -#endif - - va_end (argptr); -} - - -// ---------------------------------------------------------------------------------------------------- // -// Helpers. -// ---------------------------------------------------------------------------------------------------- // - -static void CmdLib_getwd( char *out, int outSize ) -{ -#if defined( _WIN32 ) || defined( WIN32 ) - _getcwd( out, outSize ); - Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); -#else - getcwd(out, outSize); - strcat(out, "/"); -#endif - Q_FixSlashes( out ); -} - -char *ExpandArg (char *path) -{ - static char full[1024]; - - if (path[0] != '/' && path[0] != '\\' && path[1] != ':') - { - CmdLib_getwd (full, sizeof( full )); - Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); - } - else - Q_strncpy (full, path, sizeof( full )); - return full; -} - - -char *ExpandPath (char *path) -{ - static char full[1024]; - if (path[0] == '/' || path[0] == '\\' || path[1] == ':') - return path; - sprintf (full, "%s%s", qdir, path); - return full; -} - - - -char *copystring(const char *s) -{ - char *b; - b = (char *)malloc(strlen(s)+1); - strcpy (b, s); - return b; -} - - -void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) -{ -} - - -void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) -{ - int nMinutes = nInputSeconds / 60; - int nSeconds = nInputSeconds - nMinutes * 60; - int nHours = nMinutes / 60; - nMinutes -= nHours * 60; - - const char *extra[2] = { "", "s" }; - - if ( nHours > 0 ) - Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); - else if ( nMinutes > 0 ) - Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); - else - Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); -} - - -void Q_mkdir (char *path) -{ -#if defined( _WIN32 ) || defined( WIN32 ) - if (_mkdir (path) != -1) - return; -#else - if (mkdir (path, 0777) != -1) - return; -#endif -// if (errno != EEXIST) - Error ("mkdir failed %s\n", path ); -} - -void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) -{ - FileSystem_Init( pFilename, maxMemoryUsage ); - if ( !g_pFileSystem ) - Error( "CmdLib_InitFileSystem failed." ); -} - -void CmdLib_TermFileSystem() -{ - FileSystem_Term(); -} - -CreateInterfaceFn CmdLib_GetFileSystemFactory() -{ - return FileSystem_GetFactory(); -} - - -/* -============ -FileTime - -returns -1 if not present -============ -*/ -int FileTime (char *path) -{ - struct stat buf; - - if (stat (path,&buf) == -1) - return -1; - - return buf.st_mtime; -} - - - -/* -============== -COM_Parse - -Parse a token out of a string -============== -*/ -char *COM_Parse (char *data) -{ - return (char*)ParseFile( data, com_token, NULL ); -} - - -/* -============================================================================= - - MISC FUNCTIONS - -============================================================================= -*/ - - -/* -================= -CheckParm - -Checks for the given parameter in the program's command line arguments -Returns the argument number (1 to argc-1) or 0 if not present -================= -*/ -int CheckParm (char *check) -{ - int i; - - for (i = 1;iSize( f ); -} - - -FileHandle_t SafeOpenWrite ( const char *filename ) -{ - FileHandle_t f = g_pFileSystem->Open(filename, "wb"); - - if (!f) - { - //Error ("Error opening %s: %s",filename,strerror(errno)); - // BUGBUG: No way to get equivalent of errno from IFileSystem! - Error ("Error opening %s! (Check for write enable)\n",filename); - } - - return f; -} - -#define MAX_CMDLIB_BASE_PATHS 10 -static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; -static int g_NumBasePaths = 0; - -void CmdLib_AddBasePath( const char *pPath ) -{ -// printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); - if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) - { - Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); - Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); - g_NumBasePaths++; - } - else - { - Assert( 0 ); - } -} - -bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) -{ - char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); - strcpy( pFileName, pFileName_ ); - Q_FixSlashes( pFileName ); - pathLength = 0; - int i; - for( i = 0; i < g_NumBasePaths; i++ ) - { - // see if we can rip the base off of the filename. - if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) - { - pathLength = strlen( g_pBasePaths[i] ); - return true; - } - } - return false; -} - -int CmdLib_GetNumBasePaths( void ) -{ - return g_NumBasePaths; -} - -const char *CmdLib_GetBasePath( int i ) -{ - Assert( i >= 0 && i < g_NumBasePaths ); - return g_pBasePaths[i]; -} - - -//----------------------------------------------------------------------------- -// Like ExpandPath but expands the path for each base path like SafeOpenRead -//----------------------------------------------------------------------------- -int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) -{ - int nPathLength = 0; - - pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things - - if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) - { - pszPath = pszPath + nPathLength; - for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) - { - CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; - expandedPath += pszPath; - } - } - else - { - expandedPathList.AddToTail( pszPath ); - } - - return expandedPathList.Count(); -} - - -FileHandle_t SafeOpenRead( const char *filename ) -{ - int pathLength; - FileHandle_t f = 0; - if( CmdLib_HasBasePath( filename, pathLength ) ) - { - filename = filename + pathLength; - int i; - for( i = 0; i < g_NumBasePaths; i++ ) - { - char tmp[MAX_PATH]; - strcpy( tmp, g_pBasePaths[i] ); - strcat( tmp, filename ); - f = g_pFileSystem->Open( tmp, "rb" ); - if( f ) - { - return f; - } - } - Error ("Error opening %s\n",filename ); - return f; - } - else - { - f = g_pFileSystem->Open( filename, "rb" ); - if ( !f ) - Error ("Error opening %s",filename ); - - return f; - } -} - -void SafeRead( FileHandle_t f, void *buffer, int count) -{ - if ( g_pFileSystem->Read (buffer, count, f) != (size_t)count) - Error ("File read failure"); -} - - -void SafeWrite ( FileHandle_t f, void *buffer, int count) -{ - if (g_pFileSystem->Write (buffer, count, f) != (size_t)count) - Error ("File write failure"); -} - - -/* -============== -FileExists -============== -*/ -qboolean FileExists ( const char *filename ) -{ - FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); - if ( hFile == FILESYSTEM_INVALID_HANDLE ) - { - return false; - } - else - { - g_pFileSystem->Close( hFile ); - return true; - } -} - -/* -============== -LoadFile -============== -*/ -int LoadFile ( const char *filename, void **bufferptr ) -{ - int length = 0; - void *buffer; - - FileHandle_t f = SafeOpenRead (filename); - if ( FILESYSTEM_INVALID_HANDLE != f ) - { - length = Q_filelength (f); - buffer = malloc (length+1); - ((char *)buffer)[length] = 0; - SafeRead (f, buffer, length); - g_pFileSystem->Close (f); - *bufferptr = buffer; - } - else - { - *bufferptr = NULL; - } - return length; -} - - - -/* -============== -SaveFile -============== -*/ -void SaveFile ( const char *filename, void *buffer, int count ) -{ - FileHandle_t f = SafeOpenWrite (filename); - SafeWrite (f, buffer, count); - g_pFileSystem->Close (f); -} - -/* -==================== -Extract file parts -==================== -*/ -// FIXME: should include the slash, otherwise -// backing to an empty path will be wrong when appending a slash - - - -/* -============== -ParseNum / ParseHex -============== -*/ -int ParseHex (char *hex) -{ - char *str; - int num; - - num = 0; - str = hex; - - while (*str) - { - num <<= 4; - if (*str >= '0' && *str <= '9') - num += *str-'0'; - else if (*str >= 'a' && *str <= 'f') - num += 10 + *str-'a'; - else if (*str >= 'A' && *str <= 'F') - num += 10 + *str-'A'; - else - Error ("Bad hex number: %s",hex); - str++; - } - - return num; -} - - -int ParseNum (char *str) -{ - if (str[0] == '$') - return ParseHex (str+1); - if (str[0] == '0' && str[1] == 'x') - return ParseHex (str+2); - return atol (str); -} - -/* -============ -CreatePath -============ -*/ -void CreatePath (char *path) -{ - char *ofs, c; - - // strip the drive - if (path[1] == ':') - path += 2; - - for (ofs = path+1 ; *ofs ; ofs++) - { - c = *ofs; - if (c == '/' || c == '\\') - { // create the directory - *ofs = 0; - Q_mkdir (path); - *ofs = c; - } - } -} - -//----------------------------------------------------------------------------- -// Creates a path, path may already exist -//----------------------------------------------------------------------------- -#if defined( _WIN32 ) || defined( WIN32 ) -void SafeCreatePath( char *path ) -{ - char *ptr; - - // skip past the drive path, but don't strip - if ( path[1] == ':' ) - { - ptr = strchr( path, '\\' ); - } - else - { - ptr = path; - } - while ( ptr ) - { - ptr = strchr( ptr+1, '\\' ); - if ( ptr ) - { - *ptr = '\0'; - _mkdir( path ); - *ptr = '\\'; - } - } -} -#endif - -/* -============ -QCopyFile - - Used to archive source files -============ -*/ -void QCopyFile (char *from, char *to) -{ - void *buffer; - int length; - - length = LoadFile (from, &buffer); - CreatePath (to); - SaveFile (to, buffer, length); - free (buffer); -} - - - +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// ----------------------- +// cmdlib.c +// ----------------------- +#include "tier0/platform.h" +#ifdef IS_WINDOWS_PC +#include +#endif +#include "cmdlib.h" +#include +#include +#include "tier1/strtools.h" +#ifdef _WIN32 +#include +#endif +#include "utlvector.h" +#include "filesystem_helpers.h" +#include "utllinkedlist.h" +#include "tier0/icommandline.h" +#include "KeyValues.h" +#include "filesystem_tools.h" + +#if defined( MPI ) + + #include "vmpi.h" + #include "vmpi_tools_shared.h" + +#endif + + +#if defined( _WIN32 ) || defined( WIN32 ) +#include +#endif + +#if defined( _X360 ) +#include "xbox/xbox_win32stubs.h" +#endif + +// set these before calling CheckParm +int myargc; +char **myargv; + +char com_token[1024]; + +qboolean archive; +char archivedir[1024]; + +FileHandle_t g_pLogFile = 0; + +CUtlLinkedList g_CleanupFunctions; +CUtlLinkedList g_ExtraSpewHooks; + +bool g_bStopOnExit = false; +void (*g_ExtraSpewHook)(const char*) = NULL; + +#if defined( _WIN32 ) || defined( WIN32 ) + +void CmdLib_FPrintf( FileHandle_t hFile, const char *pFormat, ... ) +{ + static CUtlVector buf; + if ( buf.Count() == 0 ) + buf.SetCount( 1024 ); + + va_list marker; + va_start( marker, pFormat ); + + while ( 1 ) + { + int ret = Q_vsnprintf( buf.Base(), buf.Count(), pFormat, marker ); + if ( ret >= 0 ) + { + // Write the string. + g_pFileSystem->Write( buf.Base(), ret, hFile ); + + break; + } + else + { + // Make the buffer larger. + int newSize = buf.Count() * 2; + buf.SetCount( newSize ); + if ( buf.Count() != newSize ) + { + Error( "CmdLib_FPrintf: can't allocate space for text." ); + } + } + } + + va_end( marker ); +} + +char* CmdLib_FGets( char *pOut, int outSize, FileHandle_t hFile ) +{ + int iCur=0; + for ( ; iCur < (outSize-1); iCur++ ) + { + char c; + if ( !g_pFileSystem->Read( &c, 1, hFile ) ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + + pOut[iCur] = c; + if ( c == '\n' ) + break; + + if ( c == EOF ) + { + if ( iCur == 0 ) + return NULL; + else + break; + } + } + + pOut[iCur] = 0; + return pOut; +} + +#if !defined( _X360 ) +#include +#endif + +// This pauses before exiting if they use -StopOnExit. Useful for debugging. +class CExitStopper +{ +public: + ~CExitStopper() + { + if ( g_bStopOnExit ) + { + Warning( "\nPress any key to quit.\n" ); + getch(); + } + } +} g_ExitStopper; + + +static unsigned short g_InitialColor = 0xFFFF; +static unsigned short g_LastColor = 0xFFFF; +static unsigned short g_BadColor = 0xFFFF; +static WORD g_BackgroundFlags = 0xFFFF; +static void GetInitialColors( ) +{ +#if !defined( _X360 ) + // Get the old background attributes. + CONSOLE_SCREEN_BUFFER_INFO oldInfo; + GetConsoleScreenBufferInfo( GetStdHandle( STD_OUTPUT_HANDLE ), &oldInfo ); + g_InitialColor = g_LastColor = oldInfo.wAttributes & (FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE|FOREGROUND_INTENSITY); + g_BackgroundFlags = oldInfo.wAttributes & (BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE|BACKGROUND_INTENSITY); + + g_BadColor = 0; + if (g_BackgroundFlags & BACKGROUND_RED) + g_BadColor |= FOREGROUND_RED; + if (g_BackgroundFlags & BACKGROUND_GREEN) + g_BadColor |= FOREGROUND_GREEN; + if (g_BackgroundFlags & BACKGROUND_BLUE) + g_BadColor |= FOREGROUND_BLUE; + if (g_BackgroundFlags & BACKGROUND_INTENSITY) + g_BadColor |= FOREGROUND_INTENSITY; +#endif +} + +WORD SetConsoleTextColor( int red, int green, int blue, int intensity ) +{ + WORD ret = g_LastColor; +#if !defined( _X360 ) + + g_LastColor = 0; + if( red ) g_LastColor |= FOREGROUND_RED; + if( green ) g_LastColor |= FOREGROUND_GREEN; + if( blue ) g_LastColor |= FOREGROUND_BLUE; + if( intensity ) g_LastColor |= FOREGROUND_INTENSITY; + + // Just use the initial color if there's a match... + if (g_LastColor == g_BadColor) + g_LastColor = g_InitialColor; + + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), g_LastColor | g_BackgroundFlags ); +#endif + return ret; +} + +void RestoreConsoleTextColor( WORD color ) +{ +#if !defined( _X360 ) + SetConsoleTextAttribute( GetStdHandle( STD_OUTPUT_HANDLE ), color | g_BackgroundFlags ); + g_LastColor = color; +#endif +} + + +#if defined( CMDLIB_NODBGLIB ) + +// This can go away when everything is in bin. +void Error( char const *pMsg, ... ) +{ + va_list marker; + va_start( marker, pMsg ); + vprintf( pMsg, marker ); + va_end( marker ); + + exit( -1 ); +} + +#else + +CRITICAL_SECTION g_SpewCS; +bool g_bSpewCSInitted = false; +bool g_bSuppressPrintfOutput = false; + +SpewRetval_t CmdLib_SpewOutputFunc( SpewType_t type, char const *pMsg ) +{ + // Hopefully two threads won't call this simultaneously right at the start! + if ( !g_bSpewCSInitted ) + { + InitializeCriticalSection( &g_SpewCS ); + g_bSpewCSInitted = true; + } + + WORD old; + SpewRetval_t retVal; + + EnterCriticalSection( &g_SpewCS ); + { + if (( type == SPEW_MESSAGE ) || (type == SPEW_LOG )) + { + Color c = *GetSpewOutputColor(); + if ( c.r() != 255 || c.g() != 255 || c.b() != 255 ) + { + // custom color + old = SetConsoleTextColor( c.r(), c.g(), c.b(), c.a() ); + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 0 ); + } + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_WARNING ) + { + old = SetConsoleTextColor( 1, 1, 0, 1 ); + retVal = SPEW_CONTINUE; + } + else if( type == SPEW_ASSERT ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_DEBUGGER; + +#ifdef MPI + // VMPI workers don't want to bring up dialogs and suchlike. + // They need to have a special function installed to handle + // the exceptions and write the minidumps. + // Install the function after VMPI_Init with a call: + // SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + if ( g_bUseMPI && !g_bMPIMaster && !Plat_IsInDebugSession() ) + { + // Generating an exception and letting the + // installed handler handle it + ::RaiseException + ( + 0, // dwExceptionCode + EXCEPTION_NONCONTINUABLE, // dwExceptionFlags + 0, // nNumberOfArguments, + NULL // const ULONG_PTR* lpArguments + ); + + // Never get here (non-continuable exception) + + VMPI_HandleCrash( pMsg, NULL, true ); + exit( 0 ); + } +#endif + } + else if( type == SPEW_ERROR ) + { + old = SetConsoleTextColor( 1, 0, 0, 1 ); + retVal = SPEW_ABORT; // doesn't matter.. we exit below so we can return an errorlevel (which dbg.dll doesn't do). + } + else + { + old = SetConsoleTextColor( 1, 1, 1, 1 ); + retVal = SPEW_CONTINUE; + } + + if ( !g_bSuppressPrintfOutput || type == SPEW_ERROR ) + printf( "%s", pMsg ); + + OutputDebugString( pMsg ); + + if ( type == SPEW_ERROR ) + { + printf( "\n" ); + OutputDebugString( "\n" ); + } + + if( g_pLogFile ) + { + CmdLib_FPrintf( g_pLogFile, "%s", pMsg ); + g_pFileSystem->Flush( g_pLogFile ); + } + + // Dispatch to other spew hooks. + FOR_EACH_LL( g_ExtraSpewHooks, i ) + g_ExtraSpewHooks[i]( pMsg ); + + RestoreConsoleTextColor( old ); + } + LeaveCriticalSection( &g_SpewCS ); + + if ( type == SPEW_ERROR ) + { + CmdLib_Exit( 1 ); + } + + return retVal; +} + + +void InstallSpewFunction() +{ + setvbuf( stdout, NULL, _IONBF, 0 ); + setvbuf( stderr, NULL, _IONBF, 0 ); + + SpewOutputFunc( CmdLib_SpewOutputFunc ); + GetInitialColors(); +} + + +void InstallExtraSpewHook( SpewHookFn pFn ) +{ + g_ExtraSpewHooks.AddToTail( pFn ); +} + +#if 0 +void CmdLib_AllocError( unsigned long size ) +{ + Error( "Error trying to allocate %d bytes.\n", size ); +} + + +int CmdLib_NewHandler( size_t size ) +{ + CmdLib_AllocError( size ); + return 0; +} +#endif + +void InstallAllocationFunctions() +{ +// _set_new_mode( 1 ); // so if malloc() fails, we exit. +// _set_new_handler( CmdLib_NewHandler ); +} + +void SetSpewFunctionLogFile( char const *pFilename ) +{ + Assert( (!g_pLogFile) ); + g_pLogFile = g_pFileSystem->Open( pFilename, "a" ); + + Assert( g_pLogFile ); + if (!g_pLogFile) + Error("Can't create LogFile:\"%s\"\n", pFilename ); + + CmdLib_FPrintf( g_pLogFile, "\n\n\n" ); +} + + +void CloseSpewFunctionLogFile() +{ + if ( g_pFileSystem && g_pLogFile ) + { + g_pFileSystem->Close( g_pLogFile ); + g_pLogFile = FILESYSTEM_INVALID_HANDLE; + } +} + + +void CmdLib_AtCleanup( CleanupFn pFn ) +{ + g_CleanupFunctions.AddToTail( pFn ); +} + + +void CmdLib_Cleanup() +{ + CloseSpewFunctionLogFile(); + + CmdLib_TermFileSystem(); + + FOR_EACH_LL( g_CleanupFunctions, i ) + g_CleanupFunctions[i](); + +#if defined( MPI ) + // Unfortunately, when you call exit(), even if you have things registered with atexit(), + // threads go into a seemingly undefined state where GetExitCodeThread gives STILL_ACTIVE + // and WaitForSingleObject will stall forever on the thread. Because of this, we must cleanup + // everything that uses threads before exiting. + VMPI_Finalize(); +#endif +} + + +void CmdLib_Exit( int exitCode ) +{ + TerminateProcess( GetCurrentProcess(), 1 ); +} + + + +#endif + +#endif + + + + +/* +=================== +ExpandWildcards + +Mimic unix command line expansion +=================== +*/ +#define MAX_EX_ARGC 1024 +int ex_argc; +char *ex_argv[MAX_EX_ARGC]; +#if defined( _WIN32 ) && !defined( _X360 ) +#include "io.h" +void ExpandWildcards (int *argc, char ***argv) +{ + struct _finddata_t fileinfo; + int handle; + int i; + char filename[1024]; + char filebase[1024]; + char *path; + + ex_argc = 0; + for (i=0 ; i<*argc ; i++) + { + path = (*argv)[i]; + if ( path[0] == '-' + || ( !strstr(path, "*") && !strstr(path, "?") ) ) + { + ex_argv[ex_argc++] = path; + continue; + } + + handle = _findfirst (path, &fileinfo); + if (handle == -1) + return; + + Q_ExtractFilePath (path, filebase, sizeof( filebase )); + + do + { + sprintf (filename, "%s%s", filebase, fileinfo.name); + ex_argv[ex_argc++] = copystring (filename); + } while (_findnext( handle, &fileinfo ) != -1); + + _findclose (handle); + } + + *argc = ex_argc; + *argv = ex_argv; +} +#else +void ExpandWildcards (int *argc, char ***argv) +{ +} +#endif + + +// only printf if in verbose mode +qboolean verbose = false; +void qprintf (const char *format, ...) +{ + if (!verbose) + return; + + va_list argptr; + va_start (argptr,format); + + char str[2048]; + Q_vsnprintf( str, sizeof(str), format, argptr ); + +#if defined( CMDLIB_NODBGLIB ) + printf( "%s", str ); +#else + Msg( "%s", str ); +#endif + + va_end (argptr); +} + + +// ---------------------------------------------------------------------------------------------------- // +// Helpers. +// ---------------------------------------------------------------------------------------------------- // + +static void CmdLib_getwd( char *out, int outSize ) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + _getcwd( out, outSize ); + Q_strncat( out, "\\", outSize, COPY_ALL_CHARACTERS ); +#else + getcwd(out, outSize); + strcat(out, "/"); +#endif + Q_FixSlashes( out ); +} + +char *ExpandArg (char *path) +{ + static char full[1024]; + + if (path[0] != '/' && path[0] != '\\' && path[1] != ':') + { + CmdLib_getwd (full, sizeof( full )); + Q_strncat (full, path, sizeof( full ), COPY_ALL_CHARACTERS); + } + else + Q_strncpy (full, path, sizeof( full )); + return full; +} + + +char *ExpandPath (char *path) +{ + static char full[1024]; + if (path[0] == '/' || path[0] == '\\' || path[1] == ':') + return path; + sprintf (full, "%s%s", qdir, path); + return full; +} + + + +char *copystring(const char *s) +{ + char *b; + b = (char *)malloc(strlen(s)+1); + strcpy (b, s); + return b; +} + + +void GetHourMinuteSeconds( int nInputSeconds, int &nHours, int &nMinutes, int &nSeconds ) +{ +} + + +void GetHourMinuteSecondsString( int nInputSeconds, char *pOut, int outLen ) +{ + int nMinutes = nInputSeconds / 60; + int nSeconds = nInputSeconds - nMinutes * 60; + int nHours = nMinutes / 60; + nMinutes -= nHours * 60; + + const char *extra[2] = { "", "s" }; + + if ( nHours > 0 ) + Q_snprintf( pOut, outLen, "%d hour%s, %d minute%s, %d second%s", nHours, extra[nHours != 1], nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else if ( nMinutes > 0 ) + Q_snprintf( pOut, outLen, "%d minute%s, %d second%s", nMinutes, extra[nMinutes != 1], nSeconds, extra[nSeconds != 1] ); + else + Q_snprintf( pOut, outLen, "%d second%s", nSeconds, extra[nSeconds != 1] ); +} + + +void Q_mkdir (char *path) +{ +#if defined( _WIN32 ) || defined( WIN32 ) + if (_mkdir (path) != -1) + return; +#else + if (mkdir (path, 0777) != -1) + return; +#endif +// if (errno != EEXIST) + Error ("mkdir failed %s\n", path ); +} + +void CmdLib_InitFileSystem( const char *pFilename, int maxMemoryUsage ) +{ + FileSystem_Init( pFilename, maxMemoryUsage ); + if ( !g_pFileSystem ) + Error( "CmdLib_InitFileSystem failed." ); +} + +void CmdLib_TermFileSystem() +{ + FileSystem_Term(); +} + +CreateInterfaceFn CmdLib_GetFileSystemFactory() +{ + return FileSystem_GetFactory(); +} + + +/* +============ +FileTime + +returns -1 if not present +============ +*/ +int FileTime (char *path) +{ + struct stat buf; + + if (stat (path,&buf) == -1) + return -1; + + return buf.st_mtime; +} + + + +/* +============== +COM_Parse + +Parse a token out of a string +============== +*/ +char *COM_Parse (char *data) +{ + return (char*)ParseFile( data, com_token, NULL ); +} + + +/* +============================================================================= + + MISC FUNCTIONS + +============================================================================= +*/ + + +/* +================= +CheckParm + +Checks for the given parameter in the program's command line arguments +Returns the argument number (1 to argc-1) or 0 if not present +================= +*/ +int CheckParm (char *check) +{ + int i; + + for (i = 1;iSize( f ); +} + + +FileHandle_t SafeOpenWrite ( const char *filename ) +{ + FileHandle_t f = g_pFileSystem->Open(filename, "wb"); + + if (!f) + { + //Error ("Error opening %s: %s",filename,strerror(errno)); + // BUGBUG: No way to get equivalent of errno from IFileSystem! + Error ("Error opening %s! (Check for write enable)\n",filename); + } + + return f; +} + +#define MAX_CMDLIB_BASE_PATHS 10 +static char g_pBasePaths[MAX_CMDLIB_BASE_PATHS][MAX_PATH]; +static int g_NumBasePaths = 0; + +void CmdLib_AddBasePath( const char *pPath ) +{ +// printf( "CmdLib_AddBasePath( \"%s\" )\n", pPath ); + if( g_NumBasePaths < MAX_CMDLIB_BASE_PATHS ) + { + Q_strncpy( g_pBasePaths[g_NumBasePaths], pPath, MAX_PATH ); + Q_FixSlashes( g_pBasePaths[g_NumBasePaths] ); + g_NumBasePaths++; + } + else + { + Assert( 0 ); + } +} + +bool CmdLib_HasBasePath( const char *pFileName_, int &pathLength ) +{ + char *pFileName = ( char * )_alloca( strlen( pFileName_ ) + 1 ); + strcpy( pFileName, pFileName_ ); + Q_FixSlashes( pFileName ); + pathLength = 0; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + // see if we can rip the base off of the filename. + if( Q_strncasecmp( g_pBasePaths[i], pFileName, strlen( g_pBasePaths[i] ) ) == 0 ) + { + pathLength = strlen( g_pBasePaths[i] ); + return true; + } + } + return false; +} + +int CmdLib_GetNumBasePaths( void ) +{ + return g_NumBasePaths; +} + +const char *CmdLib_GetBasePath( int i ) +{ + Assert( i >= 0 && i < g_NumBasePaths ); + return g_pBasePaths[i]; +} + + +//----------------------------------------------------------------------------- +// Like ExpandPath but expands the path for each base path like SafeOpenRead +//----------------------------------------------------------------------------- +int CmdLib_ExpandWithBasePaths( CUtlVector< CUtlString > &expandedPathList, const char *pszPath ) +{ + int nPathLength = 0; + + pszPath = ExpandPath( const_cast< char * >( pszPath ) ); // Kind of redundant but it's how CmdLib_HasBasePath needs things + + if ( CmdLib_HasBasePath( pszPath, nPathLength ) ) + { + pszPath = pszPath + nPathLength; + for ( int i = 0; i < CmdLib_GetNumBasePaths(); ++i ) + { + CUtlString &expandedPath = expandedPathList[ expandedPathList.AddToTail( CmdLib_GetBasePath( i ) ) ]; + expandedPath += pszPath; + } + } + else + { + expandedPathList.AddToTail( pszPath ); + } + + return expandedPathList.Count(); +} + + +FileHandle_t SafeOpenRead( const char *filename ) +{ + int pathLength; + FileHandle_t f = 0; + if( CmdLib_HasBasePath( filename, pathLength ) ) + { + filename = filename + pathLength; + int i; + for( i = 0; i < g_NumBasePaths; i++ ) + { + char tmp[MAX_PATH]; + strcpy( tmp, g_pBasePaths[i] ); + strcat( tmp, filename ); + f = g_pFileSystem->Open( tmp, "rb" ); + if( f ) + { + return f; + } + } + Error ("Error opening %s\n",filename ); + return f; + } + else + { + f = g_pFileSystem->Open( filename, "rb" ); + if ( !f ) + Error ("Error opening %s",filename ); + + return f; + } +} + +void SafeRead( FileHandle_t f, void *buffer, int count) +{ + if (g_pFileSystem->Read(buffer, count, f) != (size_t)count) + { + Error("File read failure"); + } +} + + +void SafeWrite ( FileHandle_t f, void *buffer, int count) +{ + if (g_pFileSystem->Write(buffer, count, f) != (size_t)count) + { + Error("File write failure"); + } +} + + +/* +============== +FileExists +============== +*/ +qboolean FileExists ( const char *filename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( filename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + { + return false; + } + else + { + g_pFileSystem->Close( hFile ); + return true; + } +} + +/* +============== +LoadFile +============== +*/ +int LoadFile ( const char *filename, void **bufferptr ) +{ + int length = 0; + void *buffer; + + FileHandle_t f = SafeOpenRead (filename); + if ( FILESYSTEM_INVALID_HANDLE != f ) + { + length = Q_filelength (f); + buffer = malloc (length+1); + ((char *)buffer)[length] = 0; + SafeRead (f, buffer, length); + g_pFileSystem->Close (f); + *bufferptr = buffer; + } + else + { + *bufferptr = NULL; + } + return length; +} + + + +/* +============== +SaveFile +============== +*/ +void SaveFile ( const char *filename, void *buffer, int count ) +{ + FileHandle_t f = SafeOpenWrite (filename); + SafeWrite (f, buffer, count); + g_pFileSystem->Close (f); +} + +/* +==================== +Extract file parts +==================== +*/ +// FIXME: should include the slash, otherwise +// backing to an empty path will be wrong when appending a slash + + + +/* +============== +ParseNum / ParseHex +============== +*/ +int ParseHex (char *hex) +{ + char *str; + int num; + + num = 0; + str = hex; + + while (*str) + { + num <<= 4; + if (*str >= '0' && *str <= '9') + num += *str-'0'; + else if (*str >= 'a' && *str <= 'f') + num += 10 + *str-'a'; + else if (*str >= 'A' && *str <= 'F') + num += 10 + *str-'A'; + else + Error ("Bad hex number: %s",hex); + str++; + } + + return num; +} + + +int ParseNum (char *str) +{ + if (str[0] == '$') + return ParseHex (str+1); + if (str[0] == '0' && str[1] == 'x') + return ParseHex (str+2); + return atol (str); +} + +/* +============ +CreatePath +============ +*/ +void CreatePath (char *path) +{ + char *ofs, c; + + // strip the drive + if (path[1] == ':') + path += 2; + + for (ofs = path+1 ; *ofs ; ofs++) + { + c = *ofs; + if (c == '/' || c == '\\') + { // create the directory + *ofs = 0; + Q_mkdir (path); + *ofs = c; + } + } +} + +//----------------------------------------------------------------------------- +// Creates a path, path may already exist +//----------------------------------------------------------------------------- +#if defined( _WIN32 ) || defined( WIN32 ) +void SafeCreatePath( char *path ) +{ + char *ptr; + + // skip past the drive path, but don't strip + if ( path[1] == ':' ) + { + ptr = strchr( path, '\\' ); + } + else + { + ptr = path; + } + while ( ptr ) + { + ptr = strchr( ptr+1, '\\' ); + if ( ptr ) + { + *ptr = '\0'; + _mkdir( path ); + *ptr = '\\'; + } + } +} +#endif + +/* +============ +QCopyFile + + Used to archive source files +============ +*/ +void QCopyFile (char *from, char *to) +{ + void *buffer; + int length; + + length = LoadFile (from, &buffer); + CreatePath (to); + SaveFile (to, buffer, length); + free (buffer); +} + + +