2010-09-19 01:04:39 +02:00
|
|
|
#include "nandtitle.h"
|
|
|
|
#include "gecko.h"
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
extern s32 MagicPatches( s32 );
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static u8 tmd_buf[ MAX_SIGNED_TMD_SIZE ] ATTRIBUTE_ALIGN( 32 );
|
|
|
|
|
|
|
|
NandTitle::NandTitle()
|
|
|
|
{
|
|
|
|
numTitles = 0;
|
|
|
|
currentIndex = 0;
|
|
|
|
currentType = 0;
|
|
|
|
list = NULL;
|
|
|
|
nameList = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
NandTitle::~NandTitle()
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( list )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( list );
|
|
|
|
list = NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( nameList )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( nameList );
|
|
|
|
nameList = NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 NandTitle::Get()
|
|
|
|
{
|
|
|
|
s32 ret;
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( list )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( list );
|
|
|
|
list = NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( nameList )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( nameList );
|
|
|
|
nameList = NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = ES_GetNumTitles( &numTitles );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ret < 0 )
|
|
|
|
return WII_EINTERNAL;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
list = ( u64* )memalign( 32, numTitles * sizeof( u64 ) );
|
|
|
|
if ( !list )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
return -1;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ret = ES_GetTitles( list, numTitles );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ret < 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( list );
|
|
|
|
return WII_EINTERNAL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
nameList = ( char* )malloc( IMET_MAX_NAME_LEN * numTitles );
|
|
|
|
if ( !nameList )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( nameList );
|
|
|
|
return -2;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
memset( nameList, 0, IMET_MAX_NAME_LEN * numTitles );
|
|
|
|
|
|
|
|
MagicPatches( 1 );
|
|
|
|
int language = CONF_GetLanguage();
|
|
|
|
ISFS_Initialize();
|
|
|
|
|
|
|
|
wchar_t name[ IMET_MAX_NAME_LEN ];
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( u32 i = 0; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
bool r = GetName( list[ i ], language, name );
|
|
|
|
if ( r )
|
|
|
|
{
|
|
|
|
wString *wsname = new wString( name );
|
|
|
|
memcpy( nameList + ( IMET_MAX_NAME_LEN * i ), ( wsname->toUTF8() ).c_str(), strlen( ( wsname->toUTF8() ).c_str() ) );
|
|
|
|
//gprintf("adding: %s\n", (wsname->toUTF8()).c_str() );
|
|
|
|
delete wsname;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ISFS_Deinitialize();
|
|
|
|
MagicPatches( 0 );
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
tmd* NandTitle::GetTMD( u64 tid )
|
|
|
|
{
|
|
|
|
//gprintf("GetTMD( %016llx ): ", tid );
|
2010-09-19 01:16:05 +02:00
|
|
|
signed_blob *s_tmd = ( signed_blob * )tmd_buf;
|
2010-09-19 01:04:39 +02:00
|
|
|
u32 tmd_size;
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ES_GetStoredTMDSize( tid, &tmd_size ) < 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
//gprintf("!size\n");
|
|
|
|
return NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
s32 ret = ES_GetStoredTMD( tid, s_tmd, tmd_size );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ret < 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
//gprintf("!tmd - %04x\n", ret );
|
|
|
|
return NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
tmd *t = ( tmd* )SIGNATURE_PAYLOAD( s_tmd );
|
2010-09-19 01:04:39 +02:00
|
|
|
//gprintf("ok\n");
|
|
|
|
|
|
|
|
return t;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NandTitle::GetName( u64 tid, int language, wchar_t* name )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( TITLE_UPPER( tid ) != 0x10001 && TITLE_UPPER( tid ) != 0x10002 && TITLE_UPPER( tid ) != 0x10004 )
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
//gprintf("GetName( %016llx ): ", tid );
|
|
|
|
char app[ ISFS_MAXPATH ];
|
2010-09-19 01:16:05 +02:00
|
|
|
IMET *imet = ( IMET* )memalign( 32, sizeof( IMET ) );
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
tmd* titleTmd = GetTMD( tid );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !titleTmd )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
//gprintf("no TMD\n");
|
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
u16 i;
|
|
|
|
bool ok = false;
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( i = 0; i < titleTmd->num_contents; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !titleTmd->contents[ i ].index )
|
|
|
|
{
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !ok )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
snprintf( app, sizeof( app ), "/title/%08x/%08x/content/%08x.app", TITLE_UPPER( tid ), TITLE_LOWER( tid ), titleTmd->contents[ i ].cid );
|
|
|
|
//gprintf("%s\n", app );
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( language > CONF_LANG_KOREAN )
|
|
|
|
language = CONF_LANG_ENGLISH;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
s32 fd = ISFS_Open( app, ISFS_OPEN_READ );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( fd < 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
//gprintf("fd: %d\n", fd );
|
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ISFS_Seek( fd, IMET_OFFSET, SEEK_SET ) != IMET_OFFSET )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
ISFS_Close( fd );
|
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( ISFS_Read( fd, imet, sizeof( IMET ) ) != sizeof( IMET ) )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
ISFS_Close( fd );
|
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ISFS_Close( fd );
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( imet->sig != IMET_SIGNATURE )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
free( imet );
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( imet->name_japanese[ language * IMET_MAX_NAME_LEN ] == 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
// channel name is not available in system language
|
|
|
|
if ( imet->name_english[ 0 ] != 0 )
|
|
|
|
{
|
|
|
|
language = CONF_LANG_ENGLISH;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// channel name is also not available on english, get ascii name
|
|
|
|
for ( int i = 0; i < 4; i++ )
|
|
|
|
{
|
|
|
|
name[ i ] = ( TITLE_LOWER( tid ) >> ( 24 - i * 8 ) ) & 0xFF;
|
|
|
|
}
|
|
|
|
name[ 4 ] = 0;
|
|
|
|
free( imet );
|
|
|
|
return true;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// retrieve channel name in system language or on english
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( int i = 0; i < IMET_MAX_NAME_LEN; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
name[ i ] = imet->name_japanese[ i + ( language * IMET_MAX_NAME_LEN ) ];
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
free( imet );
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NandTitle::Exists( u64 tid )
|
|
|
|
{
|
|
|
|
char app[ ISFS_MAXPATH ];
|
|
|
|
tmd* titleTmd = GetTMD( tid );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !titleTmd )
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
u16 i;
|
|
|
|
bool ok = false;
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( i = 0; i < titleTmd->num_contents; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !titleTmd->contents[ i ].index )
|
|
|
|
{
|
|
|
|
ok = true;
|
|
|
|
break;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !ok )
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
|
|
|
|
snprintf( app, sizeof( app ), "/title/%08x/%08x/content/%08x.app", TITLE_UPPER( tid ), TITLE_LOWER( tid ), titleTmd->contents[ i ].cid );
|
|
|
|
s32 fd = ISFS_Open( app, ISFS_OPEN_READ );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( fd >= 0 )
|
|
|
|
ISFS_Close( fd );
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
//gprintf(" fd: %d\n", fd );
|
|
|
|
return fd >= 0 || fd == -102; //102 means it exists, but we dont have permission to open it
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool NandTitle::ExistsFromIndex( u32 i )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( i > numTitles || i < 0 )
|
|
|
|
return false;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
return Exists( list[ i ] );
|
|
|
|
}
|
|
|
|
|
|
|
|
u64 NandTitle::At( u32 i )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( i > numTitles || i < 0 )
|
|
|
|
return 0;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
return list[ i ];
|
|
|
|
}
|
|
|
|
|
|
|
|
int NandTitle::IndexOf( u64 tid )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( u32 i = 0; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( list[ i ] == tid )
|
|
|
|
return i;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return WII_EINSTALL;
|
|
|
|
}
|
|
|
|
|
|
|
|
char* NandTitle::NameOf( u64 tid )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( u32 i = 0; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( list[ i ] == tid )
|
|
|
|
{
|
|
|
|
if ( !nameList[ IMET_MAX_NAME_LEN * i ] )
|
|
|
|
return NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
return nameList + ( IMET_MAX_NAME_LEN * i );
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
char* NandTitle::NameFromIndex( u32 i )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( i > numTitles || i < 0 )
|
|
|
|
return NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !nameList[ IMET_MAX_NAME_LEN * i ] )
|
|
|
|
return NULL;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
return nameList + ( IMET_MAX_NAME_LEN * i );
|
|
|
|
}
|
|
|
|
|
|
|
|
u16 NandTitle::VersionOf( u64 tid )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( u32 i = 0; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( list[ i ] == tid )
|
|
|
|
{
|
|
|
|
tmd* Tmd = GetTMD( tid );
|
|
|
|
if ( !Tmd )
|
|
|
|
break;
|
|
|
|
|
|
|
|
return Tmd->title_version;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
u16 NandTitle::VersionFromIndex( u32 i )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( i > numTitles || i < 0 )
|
|
|
|
return 0;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
tmd* Tmd = GetTMD( list[ i ] );
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( !Tmd )
|
|
|
|
return 0;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
return Tmd->title_version;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 NandTitle::CountType( u32 type )
|
|
|
|
{
|
|
|
|
u32 ret = 0;
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( u32 i = 0; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( TITLE_UPPER( list[ i ] ) == type )
|
|
|
|
{
|
|
|
|
ret++;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 NandTitle::SetType( u32 upper )
|
|
|
|
{
|
|
|
|
currentType = upper;
|
|
|
|
currentIndex = 0;
|
|
|
|
|
|
|
|
return CountType( upper );
|
|
|
|
}
|
|
|
|
|
|
|
|
u64 NandTitle::Next()
|
|
|
|
{
|
|
|
|
u64 ret = 0;
|
|
|
|
//gprintf("Next( %08x, %u )\n", currentType, currentIndex );
|
|
|
|
u32 i;
|
2010-09-19 01:16:05 +02:00
|
|
|
for ( i = currentIndex; i < numTitles; i++ )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( currentType )
|
|
|
|
{
|
|
|
|
if ( currentType == TITLE_UPPER( list[ i ] ) )
|
|
|
|
{
|
|
|
|
ret = list[ i ];
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ret = list[ i ];
|
|
|
|
break;
|
|
|
|
}
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
currentIndex = i + 1;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NandTitle::ResetCounter()
|
|
|
|
{
|
|
|
|
currentIndex = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void NandTitle::AsciiTID( u64 tid, char* out )
|
|
|
|
{
|
|
|
|
//gprintf("AsciiTID( %016llx ): ");
|
|
|
|
out[ 0 ] = ascii( TITLE_3( tid ) );
|
|
|
|
out[ 1 ] = ascii( TITLE_2( tid ) );
|
|
|
|
out[ 2 ] = ascii( TITLE_1( tid ) );
|
2010-09-19 01:16:05 +02:00
|
|
|
out[ 3 ] = ascii( ( u8 )( tid ) );
|
2010-09-19 01:04:39 +02:00
|
|
|
out[ 4 ] = 0;
|
|
|
|
//gprintf("%s\n", out );
|
|
|
|
}
|
|
|
|
|
|
|
|
void NandTitle::AsciiFromIndex( u32 i, char* out )
|
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( i > numTitles || i < 0 )
|
2010-09-19 01:04:39 +02:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
out[ 0 ] = 0;
|
|
|
|
return;
|
2010-09-19 01:04:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
AsciiTID( list[ i ], out );
|
|
|
|
}
|
|
|
|
|
|
|
|
s32 NandTitle::GetTicketViews( u64 tid, tikview **outbuf, u32 *outlen )
|
|
|
|
{
|
|
|
|
tikview *views = NULL;
|
|
|
|
|
|
|
|
u32 nb_views;
|
|
|
|
s32 ret;
|
|
|
|
|
|
|
|
/* Get number of ticket views */
|
2010-09-19 01:16:05 +02:00
|
|
|
ret = ES_GetNumTicketViews( tid, &nb_views );
|
|
|
|
if ( ret < 0 )
|
|
|
|
return ret;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
/* Allocate memory */
|
2010-09-19 01:16:05 +02:00
|
|
|
views = ( tikview * )memalign( 32, sizeof( tikview ) * nb_views );
|
|
|
|
if ( !views )
|
|
|
|
return -1;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
/* Get ticket views */
|
2010-09-19 01:16:05 +02:00
|
|
|
ret = ES_GetTicketViews( tid, views, nb_views );
|
|
|
|
if ( ret < 0 )
|
|
|
|
goto err;
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
/* Set values */
|
|
|
|
*outbuf = views;
|
|
|
|
*outlen = nb_views;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err:
|
|
|
|
/* Free memory */
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( views )
|
|
|
|
free( views );
|
2010-09-19 01:04:39 +02:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|