usbloadergx/source/utils/U8Archive.cpp
2020-11-08 21:28:26 +00:00

555 lines
11 KiB
C++

/****************************************************************************
* Copyright (C) 2012 Dimok and giantpune
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
#include <malloc.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "ash.h"
#include "lz77.h"
#include "gecko.h"
#include "U8Archive.h"
#include "uncompress.h"
#define RU(N, S) ((((N) + (S) - 1) / (S)) * (S))
U8Archive::U8Archive(const u8 *stuff, u32 len )
: fst( NULL ),
name_table( NULL ),
data( NULL )
{
if( stuff )
{
SetData( stuff, len );
}
}
void U8Archive::SetData( const u8 *stuff, u32 len )
{
fst = NULL;
name_table = NULL;
data = NULL;
if( !stuff || len < 0x40 )
{
gprintf( "SetData(): !stuff || len < 0x40 %p %08x\n", stuff, len );
return;
}
stuff = FindU8Tag( stuff, len );
if( !stuff )
{
gprintf( "U8 tag not found\n" );
return;
}
const U8Header * binHdr = (const U8Header *)stuff;
const u8* fst_buffer = stuff + binHdr->rootNodeOffset;
fst = (FstEntry *)fst_buffer;
u32 name_table_offset = fst->filelen * 0xC;
name_table = (char *)( fst_buffer + name_table_offset );
data = (u8*)stuff;
}
const u8 *U8Archive::FindU8Tag( const u8* stuff, u32 len )
{
if( !stuff || len < 0x20 )
{
return NULL;
}
if( *(u32*)( stuff ) == 0x55AA382D )
{
return stuff;
}
if( len > 0x620 && *(u32*)( stuff + 0x600 ) == 0x55AA382D )
{
return stuff + 0x600;
}
if( len > 0x660 && *(u32*)( stuff + 0x640 ) == 0x55AA382D )
{
return stuff + 0x640;
}
return NULL;
}
u8 *U8Archive::GetFile( const std::string &path, u32 *size ) const
{
return GetFile( path.c_str(), size );
}
u8 *U8Archive::GetFile( const char *path, u32 *size ) const
{
if( !path || !fst || !name_table )
{
return NULL;
}
s32 entryNo = EntryFromPath( path );
if( entryNo < 1 )
{
//gprintf( "U8: entry wasn\'t found in the archive \"%s\"\n", path );
return NULL;
}
if( fst[ entryNo ].filetype )
{
gprintf( "U8: \"%s\" is a folder\n", path );
return NULL;
}
if( size )
{
*size = fst[ entryNo ].filelen;
}
return data + fst[ entryNo ].fileoffset;
}
u8 *U8Archive::GetFile( u32 fstIdx, u32 *size ) const
{
if( !fst || !name_table )
{
return NULL;
}
if( fstIdx >= fst[0].filelen || fst[ fstIdx ].filetype )
{
gprintf( "%i is a folder\n", fstIdx );
return NULL;
}
if( size )
{
*size = fst[ fstIdx ].filelen;
}
return data + fst[ fstIdx ].fileoffset;
}
u8* U8Archive::GetFileAllocated( const std::string &path, u32 *size ) const
{
return GetFileAllocated( path.c_str(), size );
}
u8 *U8Archive::DecompressCopy( const u8 * stuff, u32 len, u32 *size ) const
{
// check for IMD5 header and skip it
if( len > 0x40 && *(u32*)stuff == 0x494d4435 )// IMD5
{
stuff += 0x20;
len -= 0x20;
}
u8* ret = NULL;
// determine if it needs to be decompressed
if( IsAshCompressed( stuff, len ) )
{
//u32 len2 = len;
// ASH0
ret = DecompressAsh( stuff, len );
if( !ret )
{
gprintf( "out of memory\n" );
return NULL;
}
}
else if( (len > 8) && *(u32*)( stuff ) == 0x59617a30 )// Yaz0
{
// Yaz0 with a magic word
Yaz0_Header *header = (Yaz0_Header *) stuff;
// set decompress length
len = header->decompressed_size;
// allocate memory
ret = (u8*) memalign(32, ALIGN32(len));
if(!ret)
{
gprintf("out of memory\n");
return NULL;
}
// function can not fail at this point
uncompressYaz0(stuff, ret, len);
}
else if( isLZ77compressed( stuff ) )
{
// LZ77 with no magic word
if( decompressLZ77content( stuff, len, &ret, &len ) )
{
return NULL;
}
}
else if( *(u32*)( stuff ) == 0x4C5A3737 )// LZ77
{
// LZ77 with a magic word
if( decompressLZ77content( stuff + 4, len - 4, &ret, &len ) )
{
return NULL;
}
}
else
{
// just copy the data out of the archive
ret = (u8*)memalign( 32, ALIGN32( len ) );
if( !ret )
{
gprintf( "out of memory\n" );
return NULL;
}
memcpy( ret, stuff, len );
}
if( size )
{
*size = len;
}
// flush the cache so if there are any textures in this data, it will be ready for the GX
DCFlushRange( ret, len );
return ret;
}
u8* U8Archive::GetFileAllocated( const char *path, u32 *size ) const
{
u32 len;
const u8 *stuff = GetFile( path, &len );
if( !stuff )
{
return NULL;
}
return DecompressCopy( stuff, len, size );
}
u8* U8Archive::GetFileAllocated( u32 fstIdx, u32 *size ) const
{
u32 len;
const u8 *stuff = GetFile( fstIdx, &len );
if( !stuff )
{
return NULL;
}
return DecompressCopy( stuff, len, size );
}
u32 U8Archive::FileDescriptor( const char *path ) const
{
int ret = EntryFromPath( path );
if( ret < 1 )
{
return 0;
}
return ret;
}
u8* U8Archive::GetFileFromFd( u32 fd, u32 *size )const
{
if( !fst || !name_table || fd >= fst[ 0 ].filelen )
{
return NULL;
}
if( fst[ fd ].filetype )
{
gprintf( "U8: \"%s\" is a folder\n", FstName( &fst[ fd ] ) );
return NULL;
}
if( size )
{
*size = fst[ fd ].filelen;
}
return data + fst[ fd ].fileoffset;
}
char *U8Archive::FstName( const FstEntry *entry ) const
{
if( entry == &fst[ 0 ] )
{
return NULL;
}
return (char*)name_table + ( *((u32 *)entry) & 0x00ffffff );
}
int U8Archive::strcasecmp_slash( const char *s1, const char *s2 )
{
while( toupper( *s1 ) == toupper( *s2++ ) )
{
if( *s1++ == 0 || *s1 == '/' )
{
return 0;
}
}
return( *(const unsigned char*)s1 - *(const unsigned char *)( s2 - 1 ) );
}
int U8Archive::strlen_slash( const char *s )
{
int ret = 0;
while( *s && *s++ != '/' )
{
ret++;
}
return ret;
}
u32 U8Archive::NextEntryInFolder( u32 current, u32 directory ) const
{
u32 next = ( fst[ current ].filetype ? fst[ current ].filelen : current + 1 );
if( next < fst[ directory ].filelen )
return next;
return 0;
}
s32 U8Archive::EntryFromPath( const char *path, int d ) const
{
//gprintf( "EntryFromPath( \"%s\", %d )\n", path, d );
while( *path == '/' )
{
path++;
}
if( !fst[ d ].filetype )
{
gprintf("ERROR!! %s is not a directory\n", FstName( &fst[ d ] ) );
return -1;
}
u32 next = d + 1;
FstEntry *entry = &fst[ next ];
while( next )
{
//does this entry match.
//strlen_slash is used because if looking for "dvd:/gameboy/" it would return a false positive if it hit "dvd:/gameboy advance/" first
if( !strcasecmp_slash( path, FstName( entry ) ) && ( strlen( FstName( entry ) ) == (u32)strlen_slash( path ) ) )
{
char *slash = strchr( path, '/' );
if( slash && *( slash + 1 ) )
{
//we are looking for a file somewhere in this folder
return EntryFromPath( slash + 1, next );
}
//this is the actual entry we were looking for
return next;
}
//find the next entry in this folder
next = NextEntryInFolder( next, d );
entry = &fst[ next ];
}
//no entry with the given path was found
return -1;
}
U8NandArchive::U8NandArchive( const char* nandPath )
: U8Archive( NULL, 0 ),
fd( -1 ),
dataOffset( 0 )
{
if( nandPath )
{
SetFile( nandPath );
}
}
U8NandArchive::~U8NandArchive()
{
if( fd >= 0 )
{
ISFS_Close( fd );
}
free( fst );
}
bool U8NandArchive::SetFile( const char* nandPath )
{
if(fst)
{
free(fst);
fst = NULL;
}
if(name_table)
{
free(name_table);
name_table = NULL;
}
CloseFile();
// open file
if( (fd = ISFS_Open( nandPath, ISFS_OPEN_READ ) ) < 0 )
{
gprintf( "U8NandArchive: ISFS_Open( \"%s\" ) failed\n", nandPath );
return false;
}
// get file size
fstats stats __attribute__(( aligned( 32 ) ));
int ret = ISFS_GetFileStats( fd, &stats );
if( ret < 0 )
{
CloseFile();
gprintf( "U8NandArchive: ISFS_GetFileStats( \"%s\" ) failed\n", nandPath );
return false;
}
// buffer for reading the header and stuff
u8* buffer = (u8*)memalign( 32, 0x800 );
if( !buffer )
{
CloseFile();
gprintf( "U8NandArchive: enomem\n" );
return false;
}
// read a chunk big enough that it should contain the U8 header if there is going to be one
if( (ret = ISFS_Read( fd, buffer, 0x800 )) != 0x800 )
{
free( buffer );
CloseFile();
gprintf( "U8NandArchive: ISFS_Read( 0x800 ) = %i\n", ret );
return false;
}
// find the start of the U8 data
U8Header* tagStart = (U8Header*)FindU8Tag( buffer, ret );
if( !tagStart )
{
free( buffer );
CloseFile();
gprintf( "U8NandArchive: didn't see a U8 tag\n" );
return false;
}
// remember where in the file the U8 starts
dataOffset = ( (u8*)tagStart - buffer );
// allocate memory and read the fst
if( !(fst = (FstEntry *)memalign( 32, RU( tagStart->dataOffset - dataOffset, 32 ) ) )
|| ( ISFS_Seek( fd, dataOffset + tagStart->rootNodeOffset, SEEK_SET ) != (s32)( dataOffset + tagStart->rootNodeOffset ) )
|| ( ISFS_Read( fd, fst, tagStart->dataOffset - dataOffset ) != (s32)( tagStart->dataOffset - dataOffset ) )
|| ( fst->filelen * 0xC > tagStart->dataOffset ) )
{
dataOffset = 0;
free( buffer );
if( fst )
{
free( fst );
fst = NULL;
}
CloseFile();
gprintf( "U8NandArchive: error reading fst\n" );
return false;
}
// set name table pointer
u32 name_table_offset = fst->filelen * 0xC;
name_table = ((char *)fst) + name_table_offset;
free( buffer );
return true;
}
u8* U8NandArchive::GetFileAllocated( const char *path, u32 *size ) const
{
//gprintf( "U8NandArchive::GetFileAllocated( %s )\n" );
if( !path || !fst )
{
return NULL;
}
// find file
int f = EntryFromPath( path, 0 );
if( f < 1 || f >= (int)fst[ 0 ].filelen )
{
gprintf( "U8: \"%s\" wasn't found in the archive.\n", path );
return NULL;
}
if( fst[ f ].filetype )
{
gprintf( "U8: \"%s\" is a folder\n", path );
return NULL;
}
// create a buffer
u8* ret = (u8*)memalign( 32, RU( fst[ f ].filelen, 32 ) );
if( !ret )
{
gprintf( "U8: out of memory\n" );
return NULL;
}
// seek and read
if( ISFS_Seek( fd, dataOffset + fst[ f ].fileoffset, SEEK_SET ) != (s32)( dataOffset + fst[ f ].fileoffset )
|| ISFS_Read( fd, ret, fst[ f ].filelen ) != (s32)fst[ f ].filelen )
{
free( ret );
gprintf( "U8: error reading data from nand\n" );
gprintf( "fd: %i fst[ fd ].filelen: %08x\n", fd, fst[ f ].filelen );
return NULL;
}
u32 len = fst[ f ].filelen;
u8* ret2;
// determine if it needs to be decompressed
if( IsAshCompressed( ret, len ) )
{
// ASH0
ret2 = DecompressAsh( ret, len );
if( !ret2 )
{
free( ret );
gprintf( "out of memory\n" );
return NULL;
}
free( ret );
}
else if( isLZ77compressed( ret ) )
{
// LZ77 with no magic word
if( decompressLZ77content( ret, len, &ret2, &len ) )
{
free( ret );
return NULL;
}
free( ret );
}
else if( *(u32*)( ret ) == 0x4C5A3737 )// LZ77
{
// LZ77 with a magic word
if( decompressLZ77content( ret + 4, len - 4, &ret2, &len ) )
{
free( ret );
return NULL;
}
free( ret );
}
else
{
// already got what we are after
ret2 = ret;
}
if( size )
{
*size = len;
}
// flush the cache so if there are any textures in this data, it will be ready for the GX
DCFlushRange( ret2, len );
return ret2;
}