421 lines
16 KiB
C++
421 lines
16 KiB
C++
//===== Copyright © 1996-2010, Valve Corporation, All rights reserved. ======//
|
||
//
|
||
// Purpose: Utility class for discovering and caching path info on the PS3.
|
||
//
|
||
// $NoKeywords: $
|
||
//===========================================================================//
|
||
|
||
|
||
#ifndef SN_TARGET_PS3
|
||
#error You're compiling this file on the wrong platform!
|
||
#endif
|
||
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <cell/sysmodule.h>
|
||
#include "../public/ps3_pathinfo.h"
|
||
#include <sys/tty.h>
|
||
#include "errorrenderloop.h"
|
||
#include <np.h>
|
||
|
||
// statically defined because not available in LauncherMain:
|
||
#ifndef DBG_H
|
||
static void LocalError( const char *fmt, ... )
|
||
{
|
||
va_list args;
|
||
va_start(args, fmt);
|
||
vprintf( fmt, args );
|
||
ErrorRenderLoop loop;
|
||
loop.Run();
|
||
exit(1);
|
||
}
|
||
static void AssertMsg( bool cond, const char *complaint )
|
||
{
|
||
if (!cond)
|
||
{
|
||
LocalError(complaint);
|
||
}
|
||
}
|
||
#else
|
||
#define LocalError Error
|
||
#endif
|
||
|
||
#define CheckError( x, str ) if ( x < 0 ) { LocalError( "%s: %s\n", str, GetSonyErrorString( x ) ); return x; }
|
||
|
||
#ifndef _CERT
|
||
#define DiagnosticStringMode 1
|
||
#define DiagnosticString( x ) do { unsigned int dummy; sys_tty_write( SYS_TTYP15, x, strlen( x ), &dummy ); } while(0)
|
||
#else
|
||
#define DiagnosticString( x ) ((void)0)
|
||
#endif
|
||
|
||
|
||
CPs3ContentPathInfo g_Ps3GameDataPathInfo;
|
||
|
||
CPs3ContentPathInfo::CPs3ContentPathInfo() :
|
||
m_bInitialized(false),
|
||
m_nHDDFreeSizeKb( 0 ),
|
||
m_nBootType( 0 ),
|
||
m_nBootAttribs( 0 ),
|
||
m_gameParentalLevel( 0 ),
|
||
m_gameResolution( 0 ),
|
||
m_gameSoundFormat( 0 )
|
||
{
|
||
#define GAME_INIT( x ) memset( x, 0, sizeof( x ) )
|
||
GAME_INIT( m_gameTitle );
|
||
GAME_INIT( m_gameTitleID );
|
||
GAME_INIT( m_gameAppVer );
|
||
GAME_INIT( m_gamePatchAppVer );
|
||
GAME_INIT( m_gameContentPath );
|
||
GAME_INIT( m_gamePatchContentPath );
|
||
GAME_INIT( m_gameBasePath );
|
||
GAME_INIT( m_gamePatchBasePath );
|
||
GAME_INIT( m_gameExesPath );
|
||
GAME_INIT( m_gameHDDataPath );
|
||
GAME_INIT( m_gameImageDataPath );
|
||
GAME_INIT( m_gameSystemCachePath );
|
||
GAME_INIT( m_gameSavesShadowPath );
|
||
#undef GAME_INIT
|
||
}
|
||
|
||
const char *GetSonyErrorString( int errorcode ) ; /// return a description for a CELL_GAME_ERROR code
|
||
int CPs3ContentPathInfo::Init( unsigned int uiFlagsMask )
|
||
{
|
||
AssertMsg( !m_bInitialized, "CPs3ContentPathInfo is being initialized twice!\n" );
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// load sysutil NP
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
// we'll need to haul libsysutil into memory ( CELL_SYSMODULE_SYSUTIL_NP )
|
||
{
|
||
int suc = cellSysmoduleLoadModule( CELL_SYSMODULE_SYSUTIL_NP );
|
||
if ( suc != CELL_OK )
|
||
{
|
||
LocalError( "Failed to load sysutil_np: %s\n", GetSonyErrorString(suc) );
|
||
return suc;
|
||
}
|
||
}
|
||
|
||
|
||
/////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// load sysutil GAME
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
// we'll need to haul libsysutil into memory ( CELL_SYSMODULE_SYSUTIL_GAME )
|
||
bool bSysModuleIsLoaded = cellSysmoduleIsLoaded( CELL_SYSMODULE_SYSUTIL_GAME ) == CELL_SYSMODULE_LOADED ;
|
||
// if this assert trips, then:
|
||
// 1) look at where the sysutil_game module is loaded to make sure it still needs to be loaded at this point (maybe you can dump it to save memory)
|
||
// 2) if it's being taken care of somewhere else, we don't need to load the module here.
|
||
AssertMsg( !bSysModuleIsLoaded, "The SYSUTIL_GAME module is already loaded -- revist load order logic in CPs3ContentPathInfo::Init()\n");
|
||
if ( !bSysModuleIsLoaded )
|
||
{
|
||
int suc = cellSysmoduleLoadModule( CELL_SYSMODULE_SYSUTIL_GAME );
|
||
if ( suc != CELL_OK )
|
||
{
|
||
LocalError( "Failed to load sysutil_game: %s\n", GetSonyErrorString(suc) );
|
||
return suc;
|
||
}
|
||
}
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// cellGameBootCheck
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
// get the base to the content directory.
|
||
CellGameContentSize size; // For game content of a disc boot game, sizeKB and sysSizeKB take no meaning – please do not use them
|
||
memset(&size, 0, sizeof(CellGameContentSize));
|
||
char bootdir[CELL_GAME_DIRNAME_SIZE] = {0};
|
||
|
||
int success = cellGameBootCheck( &m_nBootType, &m_nBootAttribs, &size, bootdir );
|
||
if ( success != CELL_GAME_RET_OK )
|
||
{
|
||
LocalError("cellGameBootCheck failed (line %d, code %d): %s\n", __LINE__, success, GetSonyErrorString(success) );
|
||
return success;
|
||
}
|
||
|
||
#if DiagnosticStringMode
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_DEBUG ) { DiagnosticString( "GAME BOOT: DEBUG MODE\n" ); }
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_APP_HOME ) { DiagnosticString( "GAME BOOT: HOSTFS MODE (app_home)\n" ); }
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_PATCH ) { DiagnosticString( "GAME BOOT: PATCH MODE\n" ); }
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_INVITE_MESSAGE ) { DiagnosticString( "GAME BOOT: INVITE MESSAGE\n" ); }
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_CUSTOM_DATA_MESSAGE ) { DiagnosticString( "GAME BOOT: CUSTOM DATA MESSAGE\n" ); }
|
||
DiagnosticString( "BOOT DIR " ); DiagnosticString( bootdir ); DiagnosticString( "\n" );
|
||
#endif
|
||
|
||
m_bInitialized = true;
|
||
m_nHDDFreeSizeKb = size.hddFreeSizeKB;
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// cellGameContentPermit
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
if ( !( uiFlagsMask & INIT_RETAIL_MODE ) )
|
||
{
|
||
DiagnosticString( "BOOT INFO USING NON-RETAIL BOOT\n" );
|
||
//
|
||
// BOOT MODE required: PARAM.SFO
|
||
//
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_TITLE, m_gameTitle, sizeof( m_gameTitle ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_TITLE )" );
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_TITLE_ID, m_gameTitleID, sizeof( m_gameTitleID ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_TITLE_ID )" );
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_APP_VER, m_gameAppVer, sizeof( m_gameAppVer ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_APP_VER )" );
|
||
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_PARENTAL_LEVEL, &m_gameParentalLevel ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_PARENTAL_LEVEL )" );
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_RESOLUTION, &m_gameResolution ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_RESOLUTION )" );
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_SOUND_FORMAT, &m_gameSoundFormat ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_SOUND_FORMAT )" );
|
||
|
||
// Access /app_home/...
|
||
success = cellGameContentPermit( m_gameContentPath, m_gameBasePath ) ;
|
||
DiagnosticString( "BOOT ROOT " ); DiagnosticString( m_gameContentPath ); DiagnosticString( "\n" );
|
||
DiagnosticString( "BOOT USR " ); DiagnosticString( m_gameBasePath ); DiagnosticString( "\n" );
|
||
// when running the game from the debugger, the data path returned by ContentPermit contains
|
||
// HOSTFS formatted path like /app_home/D:\perforce\...
|
||
// Perform the fixup to conform to disk image layout
|
||
if ( (m_nBootAttribs & CELL_GAME_ATTRIBUTE_DEBUG) && !strncmp( m_gameBasePath, "/app_home", sizeof( "/app_home" ) - 1 ) )
|
||
{
|
||
snprintf( m_gameContentPath, sizeof( m_gameContentPath ) - 1, "/app_home/PS3_GAME" );
|
||
snprintf( m_gameBasePath, sizeof( m_gameBasePath ) - 1, "/app_home/PS3_GAME/USRDIR" );
|
||
DiagnosticString( "BOOT ROOT/ " ); DiagnosticString( m_gameContentPath ); DiagnosticString( "\n" );
|
||
DiagnosticString( "BOOT USR// " ); DiagnosticString( m_gameBasePath ); DiagnosticString( "\n" );
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if ( m_nBootType != CELL_GAME_GAMETYPE_DISC )
|
||
{
|
||
LocalError("Only disk boot is supported in RETAIL mode! (bootmode=%d)\n", m_nBootType );
|
||
return -1;
|
||
}
|
||
|
||
// Finish access to boot executable
|
||
{
|
||
char tmp_contentInfoPath[CELL_GAME_PATH_MAX] = {0};
|
||
char tmp_usrdirPath[CELL_GAME_PATH_MAX] = {0};
|
||
success = cellGameContentPermit( tmp_contentInfoPath, tmp_usrdirPath ); // must call this to allow mounting of BDVD
|
||
DiagnosticString( "BOOT ROOT " ); DiagnosticString( tmp_contentInfoPath ); DiagnosticString( "\n" );
|
||
DiagnosticString( "BOOT USR " ); DiagnosticString( tmp_usrdirPath ); DiagnosticString( "\n" );
|
||
}
|
||
|
||
// in RETAIL mode we always have our assets on BDVD
|
||
success = cellGameDataCheck( m_nBootType, NULL, &size);
|
||
if ( success == CELL_GAME_RET_OK )
|
||
{
|
||
//
|
||
// BOOT MODE required: PARAM.SFO
|
||
//
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_TITLE, m_gameTitle, sizeof( m_gameTitle ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_TITLE )" );
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_TITLE_ID, m_gameTitleID, sizeof( m_gameTitleID ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_TITLE_ID )" );
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_APP_VER, m_gameAppVer, sizeof( m_gameAppVer ) ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_APP_VER )" );
|
||
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_PARENTAL_LEVEL, &m_gameParentalLevel ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_PARENTAL_LEVEL )" );
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_RESOLUTION, &m_gameResolution ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_RESOLUTION )" );
|
||
success = cellGameGetParamInt( CELL_GAME_PARAMID_SOUND_FORMAT, &m_gameSoundFormat ); CheckError( success, "PARAM.SFO getParam( CELL_GAME_PARAMID_SOUND_FORMAT )" );
|
||
|
||
// Access BDVD:
|
||
success = cellGameContentPermit( m_gameContentPath, m_gameBasePath ) ;
|
||
}
|
||
else
|
||
{
|
||
LocalError("cellGameDataCheck failed (line %d, code %d): %s\n", __LINE__, success, GetSonyErrorString(success) );
|
||
return success;
|
||
}
|
||
}
|
||
|
||
DiagnosticString( "-----------PARAM.SFO----------" );
|
||
DiagnosticString( "\nTITLE " ); DiagnosticString( m_gameTitle );
|
||
DiagnosticString( "\nTITLE ID " ); DiagnosticString( m_gameTitleID );
|
||
DiagnosticString( "\nAPP_VER " ); DiagnosticString( m_gameAppVer );
|
||
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_PATCH )
|
||
{
|
||
CellGameContentSize cgcs;
|
||
memset( &cgcs, 0, sizeof(CellGameContentSize) );
|
||
success = cellGamePatchCheck( &cgcs, NULL );
|
||
if ( success == CELL_GAME_RET_OK )
|
||
{
|
||
success = cellGameGetParamString( CELL_GAME_PARAMID_APP_VER, m_gamePatchAppVer, sizeof( m_gamePatchAppVer ) ); CheckError( success, "PARAM.SFO PATCH getParam( CELL_GAME_PARAMID_APP_VER )" );
|
||
DiagnosticString( "\nAPP_VER****" ); DiagnosticString( m_gamePatchAppVer );
|
||
success = cellGameContentPermit( m_gamePatchContentPath, m_gamePatchBasePath ) ;
|
||
}
|
||
else
|
||
{
|
||
LocalError("cellGamePatchCheck failed (line %d, code %d): %s\n", __LINE__, success, GetSonyErrorString(success) );
|
||
return success;
|
||
}
|
||
}
|
||
DiagnosticString( "\n------------------------------\n" );
|
||
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// filesystem path setup
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
|
||
DiagnosticString( "----------FILESYSTEM----------" );
|
||
DiagnosticString( "\nPS3_GAME " ); DiagnosticString( m_gameContentPath );
|
||
DiagnosticString( "\nUSRDIR " ); DiagnosticString( m_gameBasePath );
|
||
if ( m_nBootAttribs & CELL_GAME_ATTRIBUTE_PATCH )
|
||
{
|
||
DiagnosticString( "\nPS3_GAME***" ); DiagnosticString( m_gamePatchContentPath );
|
||
DiagnosticString( "\nUSRDIR*****" ); DiagnosticString( m_gamePatchBasePath );
|
||
}
|
||
|
||
#if 0
|
||
// Get the game data directory on the hard disk.
|
||
success = cellGameDataCheck( CELL_GAME_GAMETYPE_GAMEDATA, m_gameTitleID, &size );
|
||
if ( success == CELL_GAME_RET_NONE )
|
||
{
|
||
CellGameSetInitParams init; memset( &init, 0, sizeof( init ) );
|
||
memcpy( init.title, m_gameTitle, sizeof( m_gameTitle ) );
|
||
memcpy( init.titleId, m_gameTitleID, sizeof( m_gameTitleID ) );
|
||
memcpy( init.version, m_gameAppVer, sizeof( m_gameAppVer ) );
|
||
|
||
char tmp_contentInfoPath[CELL_GAME_PATH_MAX] = {0};
|
||
char tmp_usrdirPath[CELL_GAME_PATH_MAX] = {0};
|
||
|
||
success = cellGameCreateGameData( &init, tmp_contentInfoPath, tmp_usrdirPath );
|
||
DiagnosticString( "\nTMP_GAME " ); DiagnosticString( tmp_contentInfoPath );
|
||
DiagnosticString( "\nTMP_USRD " ); DiagnosticString( tmp_usrdirPath );
|
||
}
|
||
|
||
char contentInfoPath[256];
|
||
if ( success == CELL_GAME_RET_OK )
|
||
{
|
||
success = cellGameContentPermit( contentInfoPath, m_gameHDDataPath );
|
||
}
|
||
#else
|
||
snprintf( m_gameHDDataPath, sizeof( m_gameHDDataPath ), "/dev_hdd0/game/NPUB30589/USRDIR" );
|
||
//snprintf( m_gameHDDataPath, sizeof( m_gameHDDataPath ), "/dev_hdd0/game/BLUS30732/USRDIR" );
|
||
#endif
|
||
DiagnosticString( "\nHDD_PATH " ); DiagnosticString( m_gameHDDataPath );
|
||
|
||
// Mount system cache
|
||
if ( success >= CELL_GAME_RET_OK )
|
||
{
|
||
CellSysCacheParam sysCacheParams;
|
||
memset( &sysCacheParams, 0, sizeof( CellSysCacheParam ) );
|
||
memcpy( sysCacheParams.cacheId, GetWWMASTER_TitleID(), 10 );
|
||
success = cellSysCacheMount( &sysCacheParams );
|
||
if ( success >= CELL_GAME_RET_OK )
|
||
{
|
||
memcpy( m_gameSystemCachePath, sysCacheParams.getCachePath, sizeof( m_gameSystemCachePath ) );
|
||
if ( uiFlagsMask & INIT_SYS_CACHE_CLEAR )
|
||
cellSysCacheClear();
|
||
}
|
||
}
|
||
DiagnosticString( "\nSYS_CACH " ); DiagnosticString( m_gameSystemCachePath );
|
||
|
||
// Determine where image files (maps, zips, etc.) are located:
|
||
snprintf( m_gameImageDataPath, sizeof( m_gameImageDataPath ), m_gameBasePath );
|
||
if ( uiFlagsMask & INIT_IMAGE_APP_HOME )
|
||
snprintf( m_gameImageDataPath, sizeof( m_gameImageDataPath ), "/app_home/PS3_GAME/USRDIR" );
|
||
else if ( uiFlagsMask & INIT_IMAGE_ON_HDD )
|
||
snprintf( m_gameImageDataPath, sizeof( m_gameImageDataPath ), m_gameHDDataPath );
|
||
else if ( uiFlagsMask & INIT_IMAGE_ON_BDVD )
|
||
snprintf( m_gameImageDataPath, sizeof( m_gameImageDataPath ), "/dev_bdvd/PS3_GAME/USRDIR" );
|
||
DiagnosticString( "\nIMAGE_PATH " ); DiagnosticString( m_gameImageDataPath );
|
||
|
||
// Determine where PRX files are located:
|
||
snprintf( m_gameExesPath, sizeof( m_gameExesPath ), "%s/bin", m_gameBasePath );
|
||
if ( uiFlagsMask & INIT_PRX_APP_HOME )
|
||
snprintf( m_gameExesPath, sizeof( m_gameExesPath ), "/app_home/PS3_GAME/USRDIR/bin" );
|
||
else if ( uiFlagsMask & INIT_PRX_ON_HDD )
|
||
snprintf( m_gameExesPath, sizeof( m_gameExesPath ), "%s/bin", m_gameHDDataPath );
|
||
else if ( uiFlagsMask & INIT_PRX_ON_BDVD )
|
||
snprintf( m_gameExesPath, sizeof( m_gameExesPath ), "/dev_bdvd/PS3_GAME/USRDIR/bin" );
|
||
DiagnosticString( "\nPRX_PATH " ); DiagnosticString( m_gameExesPath );
|
||
|
||
DiagnosticString( "\n------------------------------\n" );
|
||
|
||
// we cache the saves to a local directory -- keep that info here so it's in a uniform
|
||
// place accessible from everywhere.
|
||
strncpy( m_gameSavesShadowPath, m_gameSystemCachePath, sizeof(m_gameSavesShadowPath) );
|
||
strncat( m_gameSavesShadowPath, "/tempsave/", sizeof(m_gameSavesShadowPath) );
|
||
|
||
|
||
|
||
//////////////////////////////////////////////////////////////////////////
|
||
//
|
||
// finished
|
||
//
|
||
//////////////////////////////////////////////////////////////////////////
|
||
|
||
if ( !bSysModuleIsLoaded ) // actually this means it wasn't loaded when we got into the function
|
||
{
|
||
cellSysmoduleUnloadModule( CELL_SYSMODULE_SYSUTIL_GAME );
|
||
}
|
||
|
||
|
||
return success;
|
||
}
|
||
|
||
|
||
|
||
const char *GetSonyErrorString( int errorcode )
|
||
{
|
||
switch( errorcode )
|
||
{
|
||
case CELL_GAME_RET_OK:
|
||
return "CELL_GAME_RET_OK";
|
||
|
||
case CELL_GAME_ERROR_ACCESS_ERROR:
|
||
return "HDD access error";
|
||
|
||
case CELL_GAME_ERROR_BUSY:
|
||
return "The call of an access preparing function was repeated";
|
||
|
||
case CELL_GAME_ERROR_IN_SHUTDOWN:
|
||
return "Processing cannot be executed because application termination is being processed";
|
||
|
||
case CELL_GAME_ERROR_INTERNAL:
|
||
return "Fatal error occurred in the utility";
|
||
|
||
case CELL_GAME_ERROR_PARAM:
|
||
return "There is an error in the argument (application bug)";
|
||
|
||
case CELL_GAME_ERROR_BOOTPATH:
|
||
return "Pathname of booted program file is too long" ;
|
||
|
||
case CELL_SYSMODULE_ERROR_UNKNOWN:
|
||
return "Tried to load an unknown PRX";
|
||
|
||
case CELL_SYSMODULE_ERROR_FATAL:
|
||
return "Sysmodule PRX load failed";
|
||
|
||
case CELL_GAME_ERROR_BROKEN:
|
||
return "The specified game content is corrupted";
|
||
|
||
|
||
|
||
default:
|
||
return "Unknown error code";
|
||
}
|
||
}
|
||
|
||
/* The boot attributes member of CPs3ContentPathInfo is some combination of:
|
||
CELL_GAME_ATTRIBUTE_PATCH Booted from a patch (only for a disc boot game)
|
||
|
||
CELL_GAME_ATTRIBUTE_APP_HOME Booted from the host machine (development machine only)
|
||
|
||
CELL_GAME_ATTRIBUTE_DEBUG Booted from the debugger (development machine only)
|
||
|
||
CELL_GAME_ATTRIBUTE_XMBBUY Rebooted from the game purchasing feature of the NP DRM utility
|
||
|
||
CELL_GAME_ATTRIBUTE_COMMERCE2_BROWSER Rebooted from the store browsing feature of the NP IN-GAME commerce 2 utility
|
||
|
||
CELL_GAME_ATTRIBUTE_INVITE_MESSAGE Booted from the game boot invitation message of the NP basic utility
|
||
|
||
CELL_GAME_ATTRIBUTE_CUSTOM_DATA_MESSAGE Booted from a message with a custom data attachment of the NP basic utility
|
||
|
||
CELL_GAME_ATTRIBUTE_WEB_BROWSER Booted from the full browser feature of the web browser utility
|
||
*/ |