2010-12-10 04:50:08 +01:00
# include "u8.h"
# include "tools.h"
2011-01-13 17:43:49 +01:00
//#include "md5.h"
2010-12-10 04:50:08 +01:00
# include "ash.h"
# define U8_HEADER_ALIGNMENT 0x20 //wiibrew says 0x40, but wii.cs is using 0x20 and i havent heard of any bricks because of it
static quint32 swap24 ( quint32 i )
{
# if Q_BYTE_ORDER == Q_BIG_ENDIAN
return i & 0xffffff ;
# else
int r = qFromBigEndian ( i ) ;
return r > > 8 ;
# endif
}
U8 : : U8 ( bool initialize , int type , const QStringList & names )
{
ok = false ;
2012-01-11 06:29:59 +01:00
//isLz77 = false;
lz77Type = LZ77 : : None ;
2011-07-11 07:47:33 +02:00
wii_cs_error = false ;
2010-12-10 04:50:08 +01:00
paths . clear ( ) ;
nestedU8s . clear ( ) ;
fst = NULL ;
fstSize = 0 ;
headerType = type ;
imetNames = names ;
if ( ! initialize )
2011-01-29 09:44:22 +01:00
return ;
2010-12-10 04:50:08 +01:00
//headerType = type;
//imetNames = names;
ok = CreateEmptyData ( ) ;
}
bool U8 : : CreateEmptyData ( )
{
2012-01-11 06:29:59 +01:00
//isLz77 = false;
lz77Type = LZ77 : : None ;
2010-12-10 04:50:08 +01:00
nestedU8s . clear ( ) ;
data = QByteArray ( 0x20 , ' \0 ' ) ;
//create a generic u8 with no real info in it. only a fst with a root node
QBuffer buf ( & data ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " Can't create buffer " ;
data . clear ( ) ;
return false ;
2010-12-10 04:50:08 +01:00
}
buf . putChar ( ' U ' ) ;
buf . putChar ( ' \xAA ' ) ;
buf . putChar ( ' 8 ' ) ;
buf . putChar ( ' \x2D ' ) ;
rootnode_offset = 0x00000020 ;
quint32 tmp = qFromBigEndian ( rootnode_offset ) ;
buf . write ( ( const char * ) & tmp , 4 ) ;
fstSize = 0x0000000D ;
tmp = qFromBigEndian ( fstSize ) ;
buf . write ( ( const char * ) & tmp , 4 ) ;
data_offset = qFromBigEndian ( 0x00000040 ) ;
tmp = qFromBigEndian ( data_offset ) ;
buf . write ( ( const char * ) & tmp , 4 ) ;
buf . close ( ) ;
//create the new fst
QByteArray fstArray = QByteArray ( 0x20 , ' \0 ' ) ;
fst = ( FEntry * ) ( fstArray . data ( ) ) ;
fst [ 0 ] . Type = 1 ;
fst [ 0 ] . FileLength = qFromBigEndian ( 0x00000001 ) ;
NameOff = 0x0C ;
data . append ( fstArray ) ;
//point the fst pointer to the new data
fst = ( FEntry * ) ( data . data ( ) + rootnode_offset ) ;
ok = true ;
return true ;
}
bool U8 : : RenameEntry ( const QString & path , const QString & newName )
{
qDebug ( ) < < " U8::RenameEntry( " < < path < < " , " < < newName < < " ) " ;
if ( ! ok )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RenameEntry -> archive has no data " ;
return false ;
2010-12-10 04:50:08 +01:00
}
if ( newName . contains ( " / " ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RenameEntry -> newName cannot contain \' / \' " ;
return false ;
2010-12-10 04:50:08 +01:00
}
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
2011-01-29 09:44:22 +01:00
if ( path . startsWith ( i . key ( ) ) & & path ! = i . key ( ) )
{
QString subPath = path ;
subPath . remove ( 0 , i . key ( ) . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
bool ret = i . value ( ) . RenameEntry ( subPath , newName ) ;
if ( ! ret )
{
qWarning ( ) < < " U8::RenameEntry -> error replacing data in child U8 archive " ;
return false ;
}
//qDebug() << "child entry updated, now replacing child archive in this one";
return ReplaceEntry ( i . key ( ) , i . value ( ) . GetData ( ) ) ;
//NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug
}
2011-05-18 19:24:51 +02:00
+ + i ;
2010-12-10 04:50:08 +01:00
}
//make sure the new filename doesnt already exist
QString parentPath = path ;
while ( parentPath . startsWith ( " / " ) ) //remove leading "/"s
2011-01-29 09:44:22 +01:00
parentPath . remove ( 0 , 1 ) ;
2010-12-10 04:50:08 +01:00
while ( parentPath . endsWith ( " / " ) ) //remove trailing "/"s
2011-01-29 09:44:22 +01:00
parentPath . resize ( parentPath . size ( ) - 1 ) ;
2010-12-10 04:50:08 +01:00
int slash = parentPath . lastIndexOf ( " / " ) ;
if ( slash ! = - 1 )
{
2011-01-29 09:44:22 +01:00
parentPath . chop ( ( parentPath . size ( ) - slash ) ) ;
parentPath + = " / " + newName ;
2010-12-10 04:50:08 +01:00
}
else
2011-01-29 09:44:22 +01:00
parentPath = newName ;
2010-12-10 04:50:08 +01:00
if ( FindEntry ( parentPath ) ! = - 1 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RenameEntry -> " < < parentPath < < " already exists in the archive " ;
return false ;
2010-12-10 04:50:08 +01:00
}
//find the entry to rename
int entryToRename = FindEntry ( path ) ;
if ( entryToRename = = - 1 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RenameEntry " < < path < < " doesn \' t exists in the archive " ;
qWarning ( ) < < " choices are " < < paths ;
return false ;
2010-12-10 04:50:08 +01:00
}
quint32 newNameLen = newName . size ( ) ;
quint32 oldNameLen = FstName ( entryToRename ) . size ( ) ;
int difference = newNameLen - oldNameLen ;
quint32 nFstSize = fstSize + difference ;
int dataAdjustment = 0 ;
2011-01-29 09:44:22 +01:00
if ( RU ( nFstSize , U8_HEADER_ALIGNMENT ) < RU ( fstSize , U8_HEADER_ALIGNMENT ) )
dataAdjustment = - RU ( ( fstSize - nFstSize ) , U8_HEADER_ALIGNMENT ) ;
2010-12-10 04:50:08 +01:00
2011-01-29 09:44:22 +01:00
else if ( RU ( nFstSize , U8_HEADER_ALIGNMENT ) > RU ( fstSize , U8_HEADER_ALIGNMENT ) )
dataAdjustment = RU ( ( nFstSize - fstSize ) , U8_HEADER_ALIGNMENT ) ;
2010-12-10 04:50:08 +01:00
2024-07-20 13:36:42 +02:00
qDebug ( ) < < " old size: " < < Qt : : hex < < oldNameLen \
< < " new size: " < < Qt : : hex < < newNameLen \
< < " difference: " < < Qt : : hex < < difference
< < " dataAdjustment: " < < Qt : : hex < < dataAdjustment ;
2010-12-10 04:50:08 +01:00
QByteArray nFstData ( ( qFromBigEndian ( fst [ 0 ] . FileLength ) ) * 0xc , ' \0 ' ) ;
FEntry * nfst = ( FEntry * ) ( nFstData . data ( ) ) ;
//make the new root entry
nfst [ 0 ] = fst [ 0 ] ;
//the new data payload ( just copy the existing one )
QByteArray nPayload = data . right ( data . size ( ) - data_offset ) ;
QByteArray nNameTable ;
nNameTable . append ( ' \0 ' ) ; //add a null byte for the root name
quint32 cnt = qFromBigEndian ( fst [ 0 ] . FileLength ) ; //the number of entries
for ( quint32 i = 1 ; i < cnt ; i + + )
{
2011-01-29 09:44:22 +01:00
FEntry * e = & fst [ i ] ; //old entry
FEntry * ne = & nfst [ i ] ; //new entry
ne - > NameOffset = swap24 ( nNameTable . size ( ) ) ; //offset to the new entry's name in the string table
//add the name to the new name table
if ( i = = ( quint32 ) entryToRename )
{
nNameTable . append ( newName . toLatin1 ( ) ) ;
}
else
nNameTable . append ( FstName ( e ) . toLatin1 ( ) ) ;
nNameTable . append ( ' \0 ' ) ;
if ( e - > Type ) //nothing special to change for directories except the name offset
{
ne - > Type = 1 ;
ne - > ParentOffset = e - > ParentOffset ;
ne - > NextOffset = e - > NextOffset ;
}
else //only need to change the name offset & the data offset for files
{
ne - > Type = 0 ;
ne - > FileOffset = qFromBigEndian ( qFromBigEndian ( e - > FileOffset ) + dataAdjustment ) ; // + qFromBigEndian( dataAdjustment );
//qFromBigEndian( (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ) );
ne - > FileLength = e - > FileLength ;
2024-07-20 13:36:42 +02:00
qDebug ( ) < < " old offset " < < Qt : : hex < < qFromBigEndian ( e - > FileOffset ) < < " new offset " < < Qt : : hex < < qFromBigEndian ( e - > FileOffset ) + dataAdjustment ;
2011-01-29 09:44:22 +01:00
}
2010-12-10 04:50:08 +01:00
}
//now put all the parts together to make a new U8 archive
nFstData . append ( nNameTable ) ;
data = QByteArray ( 0x20 , ' \0 ' ) ;
QBuffer buf ( & data ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::AddEntry -> Can't create buffer " ;
return - 1 ;
2010-12-10 04:50:08 +01:00
}
buf . putChar ( ' U ' ) ;
buf . putChar ( ' \xAA ' ) ;
buf . putChar ( ' 8 ' ) ;
buf . putChar ( ' \x2D ' ) ;
rootnode_offset = 0x00000020 ;
quint32 t = qFromBigEndian ( rootnode_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
fstSize = nFstData . size ( ) ;
t = qFromBigEndian ( fstSize ) ;
buf . write ( ( const char * ) & t , 4 ) ;
2011-01-29 09:44:22 +01:00
data_offset = RU ( ( 0x20 + fstSize ) , U8_HEADER_ALIGNMENT ) ;
2010-12-10 04:50:08 +01:00
t = qFromBigEndian ( data_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
buf . close ( ) ;
data . append ( nFstData ) ; //add the fst
int padding = data_offset - data . size ( ) ; //pad to 'data_offset' after the fst
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
data . append ( nPayload ) ; //add the actual file data
2011-01-29 09:44:22 +01:00
padding = RU ( data . size ( ) , 0x20 ) - data . size ( ) ; //pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary
2010-12-10 04:50:08 +01:00
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
CreateEntryList ( ) ;
//hexdump( data, 0, 0x100 );
//hexdump( data );
return true ;
}
bool U8 : : ReplaceEntry ( const QString & path , const QByteArray & nba , bool autoCompress )
{
//qDebug() << "U8::ReplaceEntry(" << path << ")";
if ( ! ok )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::ReplaceEntry -> archive has no data " ;
return false ;
2010-12-10 04:50:08 +01:00
}
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
2011-01-29 09:44:22 +01:00
if ( path . startsWith ( i . key ( ) ) & & path ! = i . key ( ) )
{
QString subPath = path ;
subPath . remove ( 0 , i . key ( ) . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
//qDebug() << "replacing file" << subPath << "in child U8" << i.key();
U8 ch = i . value ( ) ;
QString chPath = i . key ( ) ;
//qDebug() << "replacing" << subPath << "in child archive";
bool ret = ch . ReplaceEntry ( subPath , nba , autoCompress ) ;
if ( ! ret )
{
qWarning ( ) < < " U8::ReplaceEntry -> error replacing data in child U8 archive " ;
return false ;
}
//qDebug() << "child entry updated, now replacing child archive in this one";
return ReplaceEntry ( chPath , ch . GetData ( ) ) ;
//NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug
}
2011-05-18 19:24:51 +02:00
+ + i ;
2010-12-10 04:50:08 +01:00
}
//find the entry to replace
int entryToReplace = FindEntry ( path ) ;
if ( entryToReplace = = - 1 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::ReplaceEntry " < < path < < " doesn \' t exists in the archive " ;
qWarning ( ) < < " choices are " < < paths ;
return false ;
2010-12-10 04:50:08 +01:00
}
if ( qFromBigEndian ( fst [ entryToReplace ] . Type ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::ReplaceEntry -> replacing a directory is not supported " < < path ;
return false ;
2010-12-10 04:50:08 +01:00
}
2024-07-20 13:36:42 +02:00
/*qDebug() << "old size:" << Qt::hex << oldSizePadded\
< < " new size: " < < Qt : : hex < < newSizePadded \
< < " difference: " < < Qt : : hex < < difference ; */
2010-12-10 04:50:08 +01:00
QByteArray newData = nba ;
if ( autoCompress )
{
2011-01-29 09:44:22 +01:00
QByteArray oldData = data . mid ( qFromBigEndian ( fst [ entryToReplace ] . FileOffset ) , qFromBigEndian ( fst [ entryToReplace ] . FileLength ) ) ;
2012-01-11 06:29:59 +01:00
LZ77 : : CompressionType ct = LZ77 : : GetCompressedType ( oldData ) ;
bool oldCompressed = ( ct ! = LZ77 : : None | | IsAshCompressed ( oldData ) ) ;
if ( oldCompressed & & LZ77 : : GetCompressedType ( newData ) = = LZ77 : : None & & ! IsAshCompressed ( newData ) )
2011-01-29 09:44:22 +01:00
{
2012-01-11 06:29:59 +01:00
if ( ct = = LZ77 : : None )
{
ct = LZ77 : : v10 ;
}
newData = LZ77 : : Compress ( newData , ct ) ;
2011-01-29 09:44:22 +01:00
}
2010-12-10 04:50:08 +01:00
}
2011-01-29 09:44:22 +01:00
quint32 newSizePadded = RU ( newData . size ( ) , 0x20 ) ;
quint32 oldSizePadded = RU ( qFromBigEndian ( fst [ entryToReplace ] . FileLength ) , 0x20 ) ;
2010-12-10 04:50:08 +01:00
int difference = newSizePadded - oldSizePadded ;
data . remove ( qFromBigEndian ( fst [ entryToReplace ] . FileOffset ) , oldSizePadded ) ;
QByteArray newPaddedData = newData ;
if ( newSizePadded > ( quint32 ) newPaddedData . size ( ) )
{
2011-01-29 09:44:22 +01:00
newPaddedData . append ( QByteArray ( newSizePadded - newPaddedData . size ( ) , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
data . insert ( qFromBigEndian ( fst [ entryToReplace ] . FileOffset ) , newPaddedData ) ;
if ( newSizePadded = = oldSizePadded ) //the sized match, so nothing else to change
2011-01-29 09:44:22 +01:00
return true ;
2010-12-10 04:50:08 +01:00
//point the fst to the new data
fst = ( FEntry * ) ( data . data ( ) + rootnode_offset ) ;
//edit the changed size
FEntry * e = & fst [ entryToReplace ] ;
e - > FileLength = qFromBigEndian ( newData . size ( ) ) ;
//hexdump( (const void*)e, sizeof( FEntry ) );
//edit all the file offsets after the changed item
quint32 cnt = qFromBigEndian ( fst [ 0 ] . FileLength ) ;
for ( quint32 i = entryToReplace + 1 ; i < cnt ; i + + )
{
2011-01-29 09:44:22 +01:00
FEntry * e = & fst [ i ] ; //old entry
if ( e - > Type ) //nothing changes for directories
continue ;
2010-12-10 04:50:08 +01:00
2024-07-20 13:36:42 +02:00
//qDebug() << "changed" << FstName( e ) << "offset from" << Qt::hex << qFromBigEndian( fst[ i ].FileOffset ) << "to" << qFromBigEndian( fst[ i ].FileOffset ) + difference;
2011-01-29 09:44:22 +01:00
e - > FileOffset = qFromBigEndian ( qFromBigEndian ( fst [ i ] . FileOffset ) + difference ) ;
2010-12-10 04:50:08 +01:00
}
CreateEntryList ( ) ;
//hexdump( data, 0, 0x100 );
//hexdump( data );
return true ;
}
bool U8 : : RemoveEntry ( const QString & path )
{
//qDebug() << "U8::RemoveEntry(" << path << ")";
if ( ! ok )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RemoveEntry -> archive has no data " ;
return false ;
2010-12-10 04:50:08 +01:00
}
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
2011-01-29 09:44:22 +01:00
if ( path . startsWith ( i . key ( ) ) & & path ! = i . key ( ) )
{
QString subPath = path ;
subPath . remove ( 0 , i . key ( ) . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
U8 ch = i . value ( ) ;
QString chPath = i . key ( ) ;
//qDebug() << "deleting" << subPath << "in child archive";
bool ret = ch . RemoveEntry ( subPath ) ;
if ( ! ret )
{
qWarning ( ) < < " U8::RemoveEntry -> error replacing data in child U8 archive " ;
return false ;
}
return ReplaceEntry ( chPath , ch . GetData ( ) ) ; //insert the new nested archive in this one
//NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug
}
2011-05-18 19:24:51 +02:00
+ + i ;
2010-12-10 04:50:08 +01:00
}
//find the entry to delete
int entryToDelete = FindEntry ( path ) ;
if ( entryToDelete = = - 1 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::RemoveEntry " < < path < < " doesn \' t exists in the archive " ;
return false ;
2010-12-10 04:50:08 +01:00
}
//create a list of all the directory indexes that my have their size changed
QList < int > parents ;
int parent ;
quint32 numDeletedEntries ;
quint32 fstLenToDelete ;
if ( fst [ entryToDelete ] . Type ) //deleting a directory
{
2011-01-29 09:44:22 +01:00
parent = qFromBigEndian ( fst [ entryToDelete ] . ParentOffset ) ;
while ( parent )
{
parents < < parent ;
parent = qFromBigEndian ( fst [ parent ] . ParentOffset ) ;
}
numDeletedEntries = qFromBigEndian ( fst [ entryToDelete ] . NextOffset ) - entryToDelete ;
fstLenToDelete = 0 ;
for ( quint32 i = entryToDelete ; i < qFromBigEndian ( fst [ entryToDelete ] . NextOffset ) ; i + + )
{
fstLenToDelete + = FstName ( i ) . size ( ) + 1 + 0xc ;
}
2010-12-10 04:50:08 +01:00
}
else //deleting a file
{
2011-01-29 09:44:22 +01:00
parent = entryToDelete - 1 ;
while ( parent )
{
if ( fst [ parent ] . Type & & qFromBigEndian ( fst [ parent ] . NextOffset ) > ( quint32 ) entryToDelete )
{
parents < < parent ;
parent = qFromBigEndian ( fst [ parent ] . ParentOffset ) ;
}
else
parent - - ;
}
numDeletedEntries = 1 ;
fstLenToDelete = FstName ( entryToDelete ) . size ( ) + 1 + 0xc ;
2010-12-10 04:50:08 +01:00
}
//qDebug() << "will delete" << numDeletedEntries << "entries";
quint32 nFstSize = fstSize - fstLenToDelete ; //old fst size - 0xc bytes for each deleted entry and the length of its name + 1 null byte
//qDebug() << "nFstSize" << nFstSize;
QByteArray nFstData ( ( qFromBigEndian ( fst [ 0 ] . FileLength ) - numDeletedEntries ) * 0xc , ' \0 ' ) ;
FEntry * nfst = ( FEntry * ) ( nFstData . data ( ) ) ;
//the new data payload ( all the files )
QByteArray nPayload ;
QByteArray nNameTable ;
nNameTable . append ( ' \0 ' ) ; //add a null byte for the root name
quint32 cnt = qFromBigEndian ( fst [ 0 ] . FileLength ) ; //the number of entries + 1 for the new one
//make the new root entry
nfst [ 0 ] = fst [ 0 ] ;
nfst [ 0 ] . FileLength = qFromBigEndian ( cnt - numDeletedEntries ) ;
QList < int > movedDirs ; //keep a list of the directories that are shifted back so their children can be shifted too
for ( quint32 i = 1 ; i < cnt ; i + + )
{
2011-01-29 09:44:22 +01:00
if ( i > = ( quint32 ) entryToDelete & & i < ( quint32 ) ( entryToDelete + numDeletedEntries ) ) //skip deleted entries
continue ;
//copy old entries and adjust as needed
quint8 adj = i < ( quint32 ) entryToDelete ? 0 : numDeletedEntries ;
quint32 ni = i - adj ;
2024-07-20 13:36:42 +02:00
//qDebug() << "keeping" << FstName( i ) << "in the new archive ( moved from" << Qt::hex << i << "to" << Qt::hex << ni << ")";
2011-01-29 09:44:22 +01:00
//if( parents.contains( i ) )
//qDebug() << "\tthis is a parent of the deleted item";
FEntry * e = & fst [ i ] ; //old entry
FEntry * ne = & nfst [ ni ] ; //new entry
nfst [ ni ] . NameOffset = swap24 ( nNameTable . size ( ) ) ; //offset to the new entry's name in the string table
//add the name to the new name table
nNameTable . append ( FstName ( e ) . toLatin1 ( ) ) ;
nNameTable . append ( ' \0 ' ) ;
if ( e - > Type ) //directory
{
ne - > Type = 1 ;
if ( ! adj ) //directories before the new entry
{
ne - > ParentOffset = e - > ParentOffset ;
ne - > NextOffset = qFromBigEndian ( qFromBigEndian ( e - > NextOffset ) \
- ( parents . contains ( i ) ? numDeletedEntries : 0 ) ) ;
}
else //directories after the new entry
{
ne - > ParentOffset = qFromBigEndian ( qFromBigEndian ( e - > ParentOffset ) \
- ( movedDirs . contains ( qFromBigEndian ( e - > ParentOffset ) ) ? numDeletedEntries : 0 ) ) ;
ne - > NextOffset = qFromBigEndian ( qFromBigEndian ( e - > NextOffset ) - numDeletedEntries ) ;
movedDirs < < i ;
2024-07-20 13:36:42 +02:00
//qDebug() << "e.parent:" << Qt::hex << qFromBigEndian( e->ParentOffset ) << "movedDirs:" << movedDirs;
2011-01-29 09:44:22 +01:00
//hexdump( (const void*)ne, sizeof( FEntry) );
}
}
else //file
{
ne - > Type = 0 ;
ne - > FileOffset = \
qFromBigEndian ( ( quint32 ) ( 0x20 + RU ( nFstSize , U8_HEADER_ALIGNMENT ) + nPayload . size ( ) ) ) ;
ne - > FileLength = e - > FileLength ;
nPayload . append ( data . mid ( qFromBigEndian ( e - > FileOffset ) , qFromBigEndian ( e - > FileLength ) ) ) ;
int padding = RU ( nPayload . size ( ) , 0x20 ) - nPayload . size ( ) ; //pad to 0x20 bytes between files
if ( padding )
{
nPayload . append ( QByteArray ( padding , ' \0 ' ) ) ;
}
2024-07-20 13:36:42 +02:00
//qDebug() << "writing fileOffset of" << Qt::hex << ni << Qt::hex << (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() );
2011-01-29 09:44:22 +01:00
}
//hexdump( (const void*)ne, sizeof( FEntry) );
2010-12-10 04:50:08 +01:00
}
//hexdump( nFstData );
//now put all the parts together to make a new U8 archive
nFstData . append ( nNameTable ) ;
data = QByteArray ( 0x20 , ' \0 ' ) ;
QBuffer buf ( & data ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::AddEntry -> Can't create buffer " ;
return - 1 ;
2010-12-10 04:50:08 +01:00
}
buf . putChar ( ' U ' ) ;
buf . putChar ( ' \xAA ' ) ;
buf . putChar ( ' 8 ' ) ;
buf . putChar ( ' \x2D ' ) ;
rootnode_offset = 0x00000020 ;
quint32 t = qFromBigEndian ( rootnode_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
fstSize = nFstData . size ( ) ;
t = qFromBigEndian ( fstSize ) ;
buf . write ( ( const char * ) & t , 4 ) ;
2011-01-29 09:44:22 +01:00
data_offset = RU ( ( 0x20 + fstSize ) , U8_HEADER_ALIGNMENT ) ;
2010-12-10 04:50:08 +01:00
t = qFromBigEndian ( data_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
buf . close ( ) ;
data . append ( nFstData ) ; //add the fst
int padding = data_offset - data . size ( ) ; //pad to 'data_offset' after the fst
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
data . append ( nPayload ) ; //add the actual file data
2011-01-29 09:44:22 +01:00
padding = RU ( data . size ( ) , 0x20 ) - data . size ( ) ; //pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary
2010-12-10 04:50:08 +01:00
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
//make sure the pirvate variables are correct
fst = ( FEntry * ) ( data . data ( ) + rootnode_offset ) ;
NameOff = 0x0C * qFromBigEndian ( fst [ 0 ] . FileLength ) ;
CreateEntryList ( ) ;
//hexdump( data );
2024-07-20 13:36:42 +02:00
//qDebug() << "dataSize after removal:" << Qt::hex << data.size();
2010-12-10 04:50:08 +01:00
return true ;
}
int U8 : : AddEntry ( const QString & path , int type , const QByteArray & newData )
{
2024-07-20 13:36:42 +02:00
//qDebug() << "U8::AddEntry(" << path << "," << type << "," << Qt::hex << newData.size() << ")";
2010-12-10 04:50:08 +01:00
//make sure there is actually data to manipulate
if ( ! ok & & ! CreateEmptyData ( ) )
{
2011-01-29 09:44:22 +01:00
return - 1 ;
2010-12-10 04:50:08 +01:00
}
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
2011-01-29 09:44:22 +01:00
if ( path . startsWith ( i . key ( ) ) & & path ! = i . key ( ) )
{
QString subPath = path ;
//make a copy of the string, as "i" will become invalid on replace entry, and create a crash that is a bitch to track down
QString thisPath = i . key ( ) ;
U8 chVal = i . value ( ) ;
//qDebug() << "adding entry" << subPath << "in child U8" << thisPath;
subPath . remove ( 0 , thisPath . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
bool ret = chVal . AddEntry ( subPath , type , newData ) ;
if ( ! ret )
{
qWarning ( ) < < " U8::AddEntry -> error replacing data in child U8 archive " ;
return false ;
}
if ( ! ReplaceEntry ( thisPath , chVal . GetData ( ) ) ) //insert the new nested archive in this one
return - 1 ;
//qDebug() << "done replacing the child entry in this archive, finding the index to return (" << thisPath << ")";
return FindEntry ( thisPath ) ; //just return the index of the nested archive
}
2011-05-18 19:24:51 +02:00
+ + i ;
2010-12-10 04:50:08 +01:00
}
//make sure this path doesnt already exist
if ( FindEntry ( path ) ! = - 1 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::AddEntry " < < path < < " already exists in the archive " ;
return - 1 ;
2010-12-10 04:50:08 +01:00
}
//find the parent for this new entry
QString parentPath = path ;
while ( parentPath . startsWith ( " / " ) ) //remove leading "/"s
2011-01-29 09:44:22 +01:00
parentPath . remove ( 0 , 1 ) ;
2010-12-10 04:50:08 +01:00
while ( parentPath . endsWith ( " / " ) ) //remove trailing "/"s
2011-01-29 09:44:22 +01:00
parentPath . resize ( parentPath . size ( ) - 1 ) ;
2010-12-10 04:50:08 +01:00
int parent = 0 ;
int slash = parentPath . lastIndexOf ( " / " ) ;
if ( slash ! = - 1 )
{
2011-01-29 09:44:22 +01:00
//parentPath.resize( ( parentPath.size() - slash ) - 2 );
parentPath . chop ( ( parentPath . size ( ) - slash ) ) ;
parent = FindEntry ( parentPath ) ;
if ( parent = = - 1 ) //need to create subfolders for this fucker
{
parent = AddEntry ( parentPath , 1 ) ;
if ( parent = = - 1 )
{
qWarning ( ) < < " U8::AddEntry -> could not create subfolders for " < < path ;
return - 1 ;
}
}
if ( ! fst [ parent ] . Type ) //parent maps to a file, not folder
{
qWarning ( ) < < " U8::AddEntry -> can't add child entry to a file " ;
return - 1 ;
}
2010-12-10 04:50:08 +01:00
}
//qDebug() << "adding entry to" << FstName( parent );
//find the index to insert the new path
int newEntry = qFromBigEndian ( fst [ parent ] . NextOffset ) ;
//create a list of all the directory indexes that my have their size changed to hold the new entry
QList < int > parents ;
while ( parentPath . contains ( " / " ) )
{
2011-01-29 09:44:22 +01:00
parents < < FindEntry ( parentPath ) ;
int slash = parentPath . lastIndexOf ( " / " ) ;
parentPath . chop ( ( parentPath . size ( ) - slash ) ) ;
2010-12-10 04:50:08 +01:00
}
parents < < FindEntry ( parentPath ) ;
//qDebug() << "parents" << parents;
//create the new data
//the new fst
QString name = path ; //get the actual name of this entry
int sl = name . lastIndexOf ( " / " ) ;
if ( sl ! = - 1 )
{
2011-01-29 09:44:22 +01:00
name . remove ( 0 , sl + 1 ) ;
2010-12-10 04:50:08 +01:00
}
quint32 nFstSize = fstSize + 0x0C + name . size ( ) + 1 ; //old fst size + 0xc bytes for the new entry + room for the new name in the string table
QByteArray nFstData ( ( qFromBigEndian ( fst [ 0 ] . FileLength ) + 1 ) * 0xc , ' \0 ' ) ;
FEntry * nfst = ( FEntry * ) ( nFstData . data ( ) ) ;
//the new data payload ( all the files )
QByteArray nPayload ;
QByteArray nNameTable ;
nNameTable . append ( ' \0 ' ) ; //add a null byte for the root name
quint32 cnt = qFromBigEndian ( fst [ 0 ] . FileLength ) + 1 ; //the number of entries + 1 for the new one
//make the new root entry
nfst [ 0 ] = fst [ 0 ] ;
nfst [ 0 ] . FileLength = qFromBigEndian ( cnt ) ;
QList < int > movedDirs ; //keep a list of the directories that are shifted back so their children can be shifted too
for ( quint32 i = 1 ; i < cnt ; i + + )
{
2011-01-29 09:44:22 +01:00
nfst [ i ] . NameOffset = swap24 ( nNameTable . size ( ) ) ; //offset to the new entry's name in the string table
if ( i = = ( quint32 ) newEntry ) //add the new entry
{
FEntry * ne = & nfst [ i ] ;
if ( type )
{
ne - > Type = 1 ;
ne - > ParentOffset = qFromBigEndian ( parent ) ;
ne - > NextOffset = qFromBigEndian ( i + 1 ) ;
}
else
{
ne - > Type = 0 ;
ne - > FileLength = qFromBigEndian ( ( quint32 ) newData . size ( ) ) ;
ne - > FileOffset = \
qFromBigEndian ( ( quint32 ) ( 0x20 + RU ( nFstSize , U8_HEADER_ALIGNMENT ) + nPayload . size ( ) ) ) ;
nPayload . append ( newData ) ;
int padding = RU ( nPayload . size ( ) , 0x20 ) - nPayload . size ( ) ; //pad to 0x20 bytes between files
if ( padding )
{
nPayload . append ( QByteArray ( padding , ' \0 ' ) ) ;
}
}
nNameTable . append ( name . toLatin1 ( ) ) ; //add this entry's name to the table
nNameTable . append ( ' \0 ' ) ;
continue ;
}
//copy old entries and adjust as needed
quint8 adj = i < ( quint32 ) newEntry ? 0 : 1 ;
quint32 ni = i - adj ;
FEntry * e = & fst [ ni ] ; //old entry
FEntry * ne = & nfst [ i ] ; //new entry
//add the name to the new name table
nNameTable . append ( FstName ( e ) . toLatin1 ( ) ) ;
nNameTable . append ( ' \0 ' ) ;
if ( e - > Type )
{
ne - > Type = 1 ;
if ( ! adj ) //directories before the new entry
{
ne - > ParentOffset = e - > ParentOffset ;
ne - > NextOffset = qFromBigEndian ( qFromBigEndian ( e - > NextOffset ) \
+ ( parents . contains ( i ) ? 1 : 0 ) ) ;
}
else //directories after the new entry
{
movedDirs < < ni ;
ne - > ParentOffset = qFromBigEndian ( qFromBigEndian ( e - > ParentOffset ) \
+ ( movedDirs . contains ( qFromBigEndian ( e - > ParentOffset ) ) ? 1 : 0 ) ) ;
ne - > NextOffset = qFromBigEndian ( qFromBigEndian ( e - > NextOffset ) + 1 ) ;
}
}
else //all file entries
{
ne - > Type = 0 ;
ne - > FileOffset = \
qFromBigEndian ( ( quint32 ) ( 0x20 + RU ( nFstSize , U8_HEADER_ALIGNMENT ) + nPayload . size ( ) ) ) ;
ne - > FileLength = e - > FileLength ;
nPayload . append ( data . mid ( qFromBigEndian ( e - > FileOffset ) , qFromBigEndian ( e - > FileLength ) ) ) ;
int padding = RU ( nPayload . size ( ) , 0x20 ) - nPayload . size ( ) ; //pad to 0x20 bytes between files
if ( padding )
{
nPayload . append ( QByteArray ( padding , ' \0 ' ) ) ;
}
}
2010-12-10 04:50:08 +01:00
}
//now put all the parts together to make a new U8 archive
nFstData . append ( nNameTable ) ;
data = QByteArray ( 0x20 , ' \0 ' ) ;
QBuffer buf ( & data ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::AddEntry -> Can't create buffer " ;
return - 1 ;
2010-12-10 04:50:08 +01:00
}
buf . putChar ( ' U ' ) ;
buf . putChar ( ' \xAA ' ) ;
buf . putChar ( ' 8 ' ) ;
buf . putChar ( ' \x2D ' ) ;
rootnode_offset = 0x00000020 ;
quint32 t = qFromBigEndian ( rootnode_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
fstSize = nFstData . size ( ) ;
t = qFromBigEndian ( fstSize ) ;
buf . write ( ( const char * ) & t , 4 ) ;
2011-01-29 09:44:22 +01:00
data_offset = RU ( ( 0x20 + fstSize ) , U8_HEADER_ALIGNMENT ) ;
2010-12-10 04:50:08 +01:00
t = qFromBigEndian ( data_offset ) ;
buf . write ( ( const char * ) & t , 4 ) ;
buf . close ( ) ;
data . append ( nFstData ) ; //add the fst
int padding = data_offset - data . size ( ) ; //pad to 'data_offset' after the fst
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
data . append ( nPayload ) ; //add the actual file data
2011-01-29 09:44:22 +01:00
padding = RU ( data . size ( ) , 0x20 ) - data . size ( ) ; //pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary
2010-12-10 04:50:08 +01:00
if ( padding )
{
2011-01-29 09:44:22 +01:00
data . append ( QByteArray ( padding , ' \0 ' ) ) ;
2010-12-10 04:50:08 +01:00
}
//make sure the pirvate variables are correct
fst = ( FEntry * ) ( data . data ( ) + rootnode_offset ) ;
NameOff = 0x0C * qFromBigEndian ( fst [ 0 ] . FileLength ) ;
//qDebug() << "done adding the entry";
CreateEntryList ( ) ;
//hexdump( data );
return newEntry ;
}
void U8 : : CreateEntryList ( )
{
//qDebug() << "U8::CreateEntryList";
paths . clear ( ) ;
nestedU8s . clear ( ) ;
fst = ( FEntry * ) ( data . data ( ) + rootnode_offset ) ;
quint32 cnt = qFromBigEndian ( fst [ 0 ] . FileLength ) ;
if ( ( cnt * 0x0d ) + rootnode_offset > ( quint32 ) data . size ( ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::CreateEntryList -> invalid archive " ;
ok = false ;
data . clear ( ) ;
paths . clear ( ) ;
fst = NULL ;
return ;
2010-12-10 04:50:08 +01:00
}
NameOff = cnt * 0x0C ;
bool fixWarn = false ; //ony print the warning 1 time
2024-07-20 13:36:42 +02:00
//qDebug() << "cnt" << Qt::hex << cnt;
2010-12-10 04:50:08 +01:00
for ( quint32 i = 1 ; i < cnt ; + + i ) //this is not the most effecient way to do things, but it seems to work ok and these archives are small enough that it happens fast anyways
{
2011-01-29 09:44:22 +01:00
//start at the beginning of the fst and enter every directory whos "nextoffset" is greater than this index,
//adding the names along the way and hopefully finding "i"
QString path ;
quint32 current = 1 ;
quint32 folder = 0 ;
while ( 1 )
{
if ( ! current )
{
qWarning ( ) < < " U8::CreateEntryList -> error parsing the archive " ;
break ;
}
if ( current = = i )
{
path + = FstName ( i ) ;
if ( fst [ current ] . Type )
{
//sanity check: make sure the parent offset is correct
if ( folder ! = qFromBigEndian ( fst [ current ] . ParentOffset ) )
{
qWarning ( ) < < " U8::CreateEntryList -> error parsing the archive - recursion mismatch in "
2024-07-20 13:36:42 +02:00
< < path < < " expected: " < < Qt : : hex < < folder < < " got: " < < Qt : : hex < < qFromBigEndian ( fst [ current ] . ParentOffset ) \
2011-01-29 09:44:22 +01:00
< < " ( " < < FstName ( qFromBigEndian ( fst [ current ] . ParentOffset ) ) < < " ) " ;
//some tools use "recursion" instead of "parent offset".
//it just coincidentally works for archives such as the opening.bnr and .app since they dont have many folders to mess up
if ( qFromBigEndian ( fst [ current ] . ParentOffset ) = = ( quint32 ) path . count ( " / " ) )
{
if ( ! fixWarn )
{
qWarning ( ) < < " This archive was made by a broken tool such as U8Mii. I'm trying to fix it, but I can't make any promises " ;
fixWarn = true ;
2011-07-11 07:47:33 +02:00
wii_cs_error = true ;
2011-01-29 09:44:22 +01:00
}
2011-07-11 07:47:33 +02:00
# ifndef U8_DONT_FIX_RECURSION_ERRORS
fst [ current ] . ParentOffset = qFromBigEndian ( folder ) ;
# endif
2011-01-29 09:44:22 +01:00
}
2011-07-11 07:47:33 +02:00
//#ifndef U8_DONT_FIX_RECURSION_ERRORS
// fst[ current ].ParentOffset = qFromBigEndian( folder );
//#endif
2011-01-29 09:44:22 +01:00
}
2011-07-11 07:47:33 +02:00
path + = ' / ' ;
2011-01-29 09:44:22 +01:00
paths < < path ;
}
else
{
//add this file to the list of entries
paths < < path ;
//check if this file is really a U8 archive, and add it to the list of children
QByteArray chData = data . mid ( qFromBigEndian ( fst [ current ] . FileOffset ) , qFromBigEndian ( fst [ current ] . FileLength ) ) ;
if ( IsU8 ( chData ) )
{
U8 child ( chData ) ;
if ( child . IsOK ( ) )
{
nestedU8s . insert ( path , child ) ;
2011-05-17 23:18:45 +02:00
foreach ( const QString & chPath , child . Entries ( ) )
2011-01-29 09:44:22 +01:00
{
QString newPath = path + " / " + chPath ;
paths < < newPath ;
}
}
}
}
break ;
}
if ( fst [ current ] . Type & & i < qFromBigEndian ( fst [ current ] . NextOffset ) )
{
path + = FstName ( current ) + " / " ;
folder = current ;
current + + ;
continue ;
}
current = NextEntryInFolder ( current , folder ) ;
}
//paths << path;
2010-12-10 04:50:08 +01:00
}
//qDebug() << "done";
//qDebug() << "paths" << paths;
}
U8 : : U8 ( const QByteArray & ba )
{
2011-07-11 07:47:33 +02:00
wii_cs_error = false ;
2024-07-20 13:36:42 +02:00
//qDebug() << "U8::U8 dataSize:" << Qt::hex << ba.size();
2010-12-10 04:50:08 +01:00
Load ( ba ) ;
}
void U8 : : Load ( const QByteArray & ba )
{
ok = false ;
2011-07-11 07:47:33 +02:00
wii_cs_error = false ;
2012-01-11 06:29:59 +01:00
//isLz77 = false;
lz77Type = LZ77 : : None ;
2010-12-10 04:50:08 +01:00
headerType = U8_Hdr_none ;
paths . clear ( ) ;
imetNames . clear ( ) ;
/*if( ba.size() < 0x80 )
{
2024-07-20 13:36:42 +02:00
//qWarning() << "U8::Load:" << Qt::hex << ba.size();
2011-01-29 09:44:22 +01:00
//qWarning() << "U8::Load -> where is the rest of the data?";
return ;
2010-12-10 04:50:08 +01:00
} */
data = ba ;
if ( IsAshCompressed ( data ) ) //decrypt ASH0 files
2011-01-29 09:44:22 +01:00
data = DecryptAsh ( data ) ;
2010-12-10 04:50:08 +01:00
quint32 tmp ;
2012-01-11 06:29:59 +01:00
int off ;
int off2 ;
LZ77 : : CompressionType type = LZ77 : : GetCompressedType ( data , & off ) ;
if ( type = = LZ77 : : v10 )
{
lz77Type = type ;
data = LZ77 : : Decompress_v10 ( data , 0 ) ;
}
else if ( type = = LZ77 : : v11 )
{
lz77Type = type ;
data = LZ77_11 : : Decompress ( data ) ;
}
else if ( type = = LZ77 : : v10_w_magic )
{
off2 = GetU8Offset ( data ) ;
if ( off2 > = 0 & & off < off2 )
{
lz77Type = LZ77 : : v10_w_magic ;
data = LZ77 : : Decompress_v10 ( data , off + 4 ) ;
}
}
off2 = GetU8Offset ( data ) ;
//int off = LZ77::GetLz77Offset( data );
/*int off2 = GetU8Offset( data );
2010-12-10 04:50:08 +01:00
if ( off ! = - 1 & & ( off2 = = - 1 | | ( off2 ! = - 1 & & off < off2 ) ) )
{
2012-01-11 06:29:59 +01:00
//isLz77 = true;
lz77Type = LZ77 : : v10_w_magic ;
//data = LZ77::Decompress( data );
data = LZ77 : : Decompress_v10 ( data , off + 4 ) ;
2011-01-29 09:44:22 +01:00
off2 = GetU8Offset ( data ) ;
2012-01-11 06:29:59 +01:00
} */
2010-12-10 04:50:08 +01:00
if ( off2 = = - 1 )
{
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " This isnt a U8 " ;
return ;
2010-12-10 04:50:08 +01:00
}
data = data . mid ( off2 ) ;
QBuffer buf ( & data ) ;
if ( ! buf . open ( QBuffer : : ReadOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " Can't create buffer " ;
return ;
2010-12-10 04:50:08 +01:00
}
buf . seek ( 4 ) ;
buf . read ( ( char * ) & tmp , 4 ) ;
rootnode_offset = qFromBigEndian ( tmp ) ;
if ( rootnode_offset ! = 0x20 )
{
2024-07-20 13:36:42 +02:00
qWarning ( ) < < " rootnodeOffset " < < Qt : : hex < < rootnode_offset ;
qWarning ( ) < < Qt : : hex < < data . size ( ) ;
2011-01-29 09:44:22 +01:00
hexdump ( data ) ;
2010-12-10 04:50:08 +01:00
}
buf . read ( ( char * ) & tmp , 4 ) ;
fstSize = qFromBigEndian ( tmp ) ;
buf . read ( ( char * ) & tmp , 4 ) ;
data_offset = qFromBigEndian ( tmp ) ;
buf . close ( ) ;
CreateEntryList ( ) ;
ReadHeader ( ba ) ;
ok = true ;
//qDebug() << "loaded" << paths;
}
bool U8 : : IsOK ( )
{
return ok ;
}
QString U8 : : FstName ( const FEntry * entry )
{
//qDebug() << "U8::FstName";
if ( entry = = & fst [ 0 ] )
2011-01-29 09:44:22 +01:00
return QString ( ) ;
2010-12-10 04:50:08 +01:00
int nameStart = swap24 ( entry - > NameOffset ) + NameOff + rootnode_offset ;
return data . mid ( nameStart , nameStart + 0x100 ) ;
}
QString U8 : : FstName ( quint32 i )
{
if ( i > fst [ 0 ] . FileLength )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::FstName -> index is out of range " ;
return QString ( ) ;
2010-12-10 04:50:08 +01:00
}
return FstName ( & fst [ i ] ) ;
}
quint32 U8 : : NextEntryInFolder ( quint32 current , quint32 directory )
{
2024-07-20 13:36:42 +02:00
//qDebug() << "U8::NextEntryInFolder(" << Qt::hex << current << "," << Qt::hex << directory << ")";
2010-12-10 04:50:08 +01:00
quint32 next = ( fst [ current ] . Type ? qFromBigEndian ( fst [ current ] . FileLength ) : current + 1 ) ;
2024-07-20 13:36:42 +02:00
//qDebug() << "next" << next << "len" << Qt::hex << qFromBigEndian( fst[ directory ].FileLength );
2010-12-10 04:50:08 +01:00
if ( next < qFromBigEndian ( fst [ directory ] . FileLength ) )
2011-01-29 09:44:22 +01:00
return next ;
2010-12-10 04:50:08 +01:00
return 0 ;
}
int U8 : : FindEntry ( const QString & str , int d )
{
//qDebug() << "U8::FindEntry(" << str << "," << d << ")";
if ( str . isEmpty ( ) )
2011-01-29 09:44:22 +01:00
return 0 ;
2010-12-10 04:50:08 +01:00
if ( str . startsWith ( " / " ) )
{
2011-01-29 09:44:22 +01:00
QString r = str ;
r . remove ( 0 , 1 ) ;
return FindEntry ( r , d ) ;
2010-12-10 04:50:08 +01:00
}
if ( ! fst [ d ] . Type )
{
2011-01-29 09:44:22 +01:00
qDebug ( ) < < " ERROR!! " < < FstName ( & fst [ d ] ) < < " is not a directory " ;
return - 1 ;
2010-12-10 04:50:08 +01:00
}
int next = d + 1 ;
FEntry * entry = & fst [ next ] ;
while ( next )
{
2011-01-29 09:44:22 +01:00
QString item ;
int sl = str . indexOf ( " / " ) ;
if ( sl > - 1 )
item = str . left ( sl ) ;
else
item = str ;
2011-07-11 07:47:33 +02:00
if ( ! FstName ( entry ) . compare ( item , Qt : : CaseInsensitive ) )
//if( FstName( entry ) == item )
2011-01-29 09:44:22 +01:00
{
if ( item = = str | | item + " / " = = str ) //this is the item we are looking for
{
return next ;
}
//this item is a parent folder of one we are looking for
return FindEntry ( str . right ( ( str . size ( ) - sl ) - 1 ) , next ) ;
}
//find the next entry in this folder
next = NextEntryInFolder ( next , d ) ;
entry = & fst [ next ] ;
2010-12-10 04:50:08 +01:00
}
//no entry with the given path was found
return - 1 ;
}
const QByteArray U8 : : GetData ( const QString & str , bool onlyPayload )
{
//qDebug() << "U8::GetData(" << str << ")";
if ( str . isEmpty ( ) ) //give the data for this whole archive
{
2011-01-29 09:44:22 +01:00
if ( onlyPayload )
return data ;
//qDebug() << "U8::GetData -> returning all the data for this archive. headerType:" << headerType;
QByteArray ret = data ;
switch ( headerType )
{
case U8_Hdr_IMET_app :
case U8_Hdr_IMET_bnr :
ret = AddIMET ( headerType ) ; //data is lz77 compressed in this function if it needs to be
break ;
case U8_Hdr_IMD5 :
{
2012-01-11 06:29:59 +01:00
//if( isLz77 )
if ( lz77Type = = LZ77 : : v10_w_magic )
ret = LZ77 : : Compress_v10 ( ret ) ;
2011-01-29 09:44:22 +01:00
ret = AddIMD5 ( ret ) ;
//hexdump( ret, 0, 0x40 );
}
break ;
default :
break ;
}
return ret ;
2010-12-10 04:50:08 +01:00
}
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
2011-01-29 09:44:22 +01:00
if ( str . startsWith ( i . key ( ) ) & & str ! = i . key ( ) )
{
QString subPath = str ;
subPath . remove ( 0 , i . key ( ) . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
return i . value ( ) . GetData ( subPath , onlyPayload ) ;
}
2011-05-18 19:24:51 +02:00
+ + i ;
2010-12-10 04:50:08 +01:00
}
int index = FindEntry ( str ) ;
if ( index < 0 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::GetData " < < str < < " was not found in the archive " ;
return QByteArray ( ) ;
2010-12-10 04:50:08 +01:00
}
if ( fst [ index ] . Type )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::GetData " < < str < < " is a directory " ;
return QByteArray ( ) ;
2010-12-10 04:50:08 +01:00
}
QByteArray ret = data . mid ( qFromBigEndian ( fst [ index ] . FileOffset ) , qFromBigEndian ( fst [ index ] . FileLength ) ) ;
//hexdump( ret, 0, 0x40 );
if ( onlyPayload )
{
2012-01-11 06:29:59 +01:00
LZ77 : : CompressionType ct ;
ret = LZ77 : : Decompress ( ret , & ct ) ;
if ( ct = = LZ77 : : None & & IsAshCompressed ( ret ) )
{
ret = DecryptAsh ( ret ) ;
}
//if( LZ77::GetLz77Offset( ret ) != -1 )
// ret = LZ77::Decompress( ret );
//else if( IsAshCompressed( ret ) )
// ret = DecryptAsh( ret );
2010-12-10 04:50:08 +01:00
}
return ret ;
}
quint32 U8 : : GetSize ( const QString & str )
{
if ( str . isEmpty ( ) )
{
2011-01-29 09:44:22 +01:00
return data . size ( ) ;
2010-12-10 04:50:08 +01:00
}
2012-01-11 06:29:59 +01:00
//check if this is a path to a file in a nested archive
QMap < QString , U8 > : : iterator i = nestedU8s . begin ( ) ;
while ( i ! = nestedU8s . constEnd ( ) )
{
if ( str . startsWith ( i . key ( ) ) & & str ! = i . key ( ) )
{
QString subPath = str ;
subPath . remove ( 0 , i . key ( ) . size ( ) + 1 ) ; //remove the path of the archive itself + the slash
return i . value ( ) . GetSize ( subPath ) ;
}
+ + i ;
}
2010-12-10 04:50:08 +01:00
int index = FindEntry ( str ) ;
if ( index < 0 )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::GetSize " < < str < < " was not found in the archive " ;
return 0 ;
2010-12-10 04:50:08 +01:00
}
if ( fst [ index ] . Type )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::GetSize " < < str < < " is a directory " ;
return 0 ;
2010-12-10 04:50:08 +01:00
}
return qFromBigEndian ( fst [ index ] . FileLength ) ;
}
const QStringList U8 : : Entries ( )
{
return paths ;
}
int U8 : : GetU8Offset ( const QByteArray & ba )
{
QByteArray start = ba . left ( 5000 ) ;
return start . indexOf ( " U \xAA \x38 \x2d " ) ;
}
bool U8 : : IsU8 ( const QByteArray & ba )
{
QByteArray data = ba ;
if ( IsAshCompressed ( data ) ) //decrypt ASH0 files
2011-01-29 09:44:22 +01:00
data = DecryptAsh ( data ) ;
2010-12-10 04:50:08 +01:00
2012-01-11 06:29:59 +01:00
data = LZ77 : : Decompress ( data ) ;
2010-12-10 04:50:08 +01:00
QByteArray start = data . left ( 5000 ) ;
return start . indexOf ( " U \xAA \x38 \x2d " ) ! = - 1 ;
}
# define IMET_MAX_NAME_LEN 0x2a
typedef struct
{
2011-01-29 09:44:22 +01:00
quint32 sig ; // "IMET"
quint32 unk1 ;
quint32 unk2 ;
quint32 filesizes [ 3 ] ;
quint32 unk3 ;
quint16 names [ 10 ] [ IMET_MAX_NAME_LEN ] ;
quint8 zeroes2 [ 0x24c ] ;
quint8 md5 [ 0x10 ] ;
2010-12-10 04:50:08 +01:00
} IMET ;
void U8 : : ReadHeader ( const QByteArray & ba )
{
2024-07-20 13:36:42 +02:00
//qDebug() << "U8::ReadHeader(" << Qt::hex << ba.size() << ")";
2010-12-10 04:50:08 +01:00
//hexdump( ba );
headerType = U8_Hdr_none ;
imetNames . clear ( ) ;
if ( ba . startsWith ( " IMD5 " ) ) //dont bother to read any more data
{
2011-01-29 09:44:22 +01:00
//qDebug() << "IMD5 header";
headerType = U8_Hdr_IMD5 ;
return ;
2010-12-10 04:50:08 +01:00
}
QByteArray start = ba . left ( sizeof ( IMET ) + 0x80 ) ;
QBuffer buf ( & start ) ;
if ( ! buf . open ( QBuffer : : ReadOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::ReadHeader Can't create buffer " ;
return ;
2010-12-10 04:50:08 +01:00
}
int off = start . indexOf ( " IMET " ) ;
2024-07-20 13:36:42 +02:00
//qDebug() << "imet offset" << Qt::hex << off << "u8 offset" << Qt::hex << GetU8Offset( ba );
2010-12-10 04:50:08 +01:00
if ( off = = 0x40 | | off = = 0x80 ) //read imet header
{
2011-01-29 09:44:22 +01:00
if ( off > GetU8Offset ( ba ) ) //in case somebody wants to put a IMET archive inside another U8 for whatever reason
return ;
if ( off = = 0x40 )
headerType = U8_Hdr_IMET_bnr ;
else
headerType = U8_Hdr_IMET_app ;
buf . seek ( off ) ;
IMET imet ;
buf . read ( ( char * ) & imet , sizeof ( IMET ) ) ;
buf . close ( ) ;
for ( int h = 0 ; h < 10 ; h + + )
{
QString name ;
for ( int i = 0 ; i < IMET_MAX_NAME_LEN & & imet . names [ h ] [ i ] ! = 0 ; i + + ) //no need to switch endian, 0x0000 is a palendrome
name + = QChar ( qFromBigEndian ( imet . names [ h ] [ i ] ) ) ;
imetNames < < name ;
}
//done
return ;
2010-12-10 04:50:08 +01:00
}
}
QByteArray U8 : : AddIMD5 ( QByteArray ba )
{
quint32 size = ba . size ( ) ;
2011-01-29 09:44:22 +01:00
/*MD5 hash;
2010-12-10 04:50:08 +01:00
hash . update ( ba . data ( ) , size ) ;
2011-01-29 09:44:22 +01:00
hash . finalize ( ) ; */
2010-12-10 04:50:08 +01:00
size = qFromBigEndian ( size ) ;
QByteArray imd5 ;
QBuffer buf ( & imd5 ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::AddIMD5 Can't create buffer " ;
return QByteArray ( ) ;
2010-12-10 04:50:08 +01:00
}
buf . putChar ( ' I ' ) ;
buf . putChar ( ' M ' ) ;
buf . putChar ( ' D ' ) ;
buf . putChar ( ' 5 ' ) ;
buf . write ( ( const char * ) & size , 4 ) ;
buf . seek ( 0x10 ) ;
2011-01-29 09:44:22 +01:00
buf . write ( GetMd5 ( ba ) ) ;
//buf.write( (const char*)hash.hexdigestChar(), 16 );
2010-12-10 04:50:08 +01:00
buf . close ( ) ;
//qDebug() << hash.hexdigest().c_str();
//hexdump( (void*)imd5.data(), 0x20 );
ba . prepend ( imd5 ) ;
return ba ;
}
const QByteArray U8 : : AddIMD5 ( )
{
QByteArray ba = data ;
ba = AddIMD5 ( ba ) ;
return ba ;
}
QByteArray U8 : : GetIMET ( const QStringList & names , int paddingType , quint32 iconSize , quint32 bannerSize , quint32 soundSize )
{
QByteArray ret = QByteArray ( sizeof ( IMET ) + 0x40 , ' \0 ' ) ;
QBuffer buf ( & ret ) ;
if ( ! buf . open ( QBuffer : : WriteOnly ) )
{
2011-01-29 09:44:22 +01:00
qWarning ( ) < < " U8::GetIMET Can't create buffer " ;
return QByteArray ( ) ;
2010-12-10 04:50:08 +01:00
}
buf . seek ( 0 + 0x40 ) ;
buf . putChar ( ' I ' ) ;
buf . putChar ( ' M ' ) ;
buf . putChar ( ' E ' ) ;
buf . putChar ( ' T ' ) ;
buf . seek ( 6 + 0x40 ) ;
buf . putChar ( ' \x6 ' ) ;
buf . seek ( 11 + 0x40 ) ;
buf . putChar ( ' \x3 ' ) ;
quint32 tmp = qFromBigEndian ( iconSize ) ;
buf . write ( ( char * ) & tmp , 4 ) ;
tmp = qFromBigEndian ( bannerSize ) ;
buf . write ( ( char * ) & tmp , 4 ) ;
tmp = qFromBigEndian ( soundSize ) ;
buf . write ( ( char * ) & tmp , 4 ) ;
int nameOffset = 0x1c + 0x40 ;
int numNames = names . size ( ) ;
for ( int i = 0 ; i < numNames ; i + + )
{
2011-01-29 09:44:22 +01:00
QString name = names . at ( i ) ;
int nameLen = name . size ( ) ;
buf . seek ( nameOffset + ( IMET_MAX_NAME_LEN * i * 2 ) ) ;
for ( int j = 0 ; j < nameLen ; j + + )
{
quint16 letter = qFromBigEndian ( name . at ( j ) . unicode ( ) ) ;
buf . write ( ( const char * ) & letter , 2 ) ;
}
2010-12-10 04:50:08 +01:00
}
2011-01-29 09:44:22 +01:00
/*MD5 hash;
2010-12-10 04:50:08 +01:00
hash . update ( ret . data ( ) , sizeof ( IMET ) + 0x40 ) ;
2011-01-29 09:44:22 +01:00
hash . finalize ( ) ; */
2010-12-10 04:50:08 +01:00
buf . seek ( 0x5f0 ) ;
2011-01-29 09:44:22 +01:00
buf . write ( GetMd5 ( ret ) ) ;
//buf.write( (const char*)hash.hexdigestChar(), 16 );
2010-12-10 04:50:08 +01:00
buf . close ( ) ;
switch ( paddingType )
{
case U8_Hdr_IMET_app :
2011-01-29 09:44:22 +01:00
ret . prepend ( QByteArray ( 0x40 , ' \0 ' ) ) ;
break ;
2010-12-10 04:50:08 +01:00
case U8_Hdr_IMET_bnr :
2011-01-29 09:44:22 +01:00
break ;
2010-12-10 04:50:08 +01:00
default :
2011-01-29 09:44:22 +01:00
ret . remove ( 0 , 0x40 ) ;
break ;
2010-12-10 04:50:08 +01:00
}
return ret ;
}
2011-05-18 19:34:45 +02:00
const QStringList & U8 : : IMETNames ( )
2010-12-10 04:50:08 +01:00
{
return imetNames ;
}
void U8 : : SetImetNames ( const QStringList & names )
{
imetNames . clear ( ) ;
imetNames = names ;
}
const QByteArray U8 : : AddIMET ( int paddingType )
{
QByteArray ret = GetData ( " meta/icon.bin " ) ;
quint32 iconSize ;
quint32 bannerSize ;
quint32 soundSize ;
//if these are lz77 compressed, write the uncompressed size to the header ?bannerbomb?
//otherwise write the size - 0x20 ( imd5 header size )
if ( LZ77 : : GetLz77Offset ( ret ) ! = - 1 )
2011-01-29 09:44:22 +01:00
iconSize = LZ77 : : GetDecompressedSize ( ret ) ;
2010-12-10 04:50:08 +01:00
else
2011-01-29 09:44:22 +01:00
iconSize = ret . size ( ) - 0x20 ;
2010-12-10 04:50:08 +01:00
ret = GetData ( " meta/banner.bin " ) ;
if ( LZ77 : : GetLz77Offset ( ret ) ! = - 1 )
2011-01-29 09:44:22 +01:00
bannerSize = LZ77 : : GetDecompressedSize ( ret ) ;
2010-12-10 04:50:08 +01:00
else
2011-01-29 09:44:22 +01:00
bannerSize = ret . size ( ) - 0x20 ;
2010-12-10 04:50:08 +01:00
ret = GetData ( " meta/sound.bin " ) ;
if ( LZ77 : : GetLz77Offset ( ret ) ! = - 1 )
2011-01-29 09:44:22 +01:00
soundSize = LZ77 : : GetDecompressedSize ( ret ) ;
2010-12-10 04:50:08 +01:00
else
2011-01-29 09:44:22 +01:00
soundSize = ret . size ( ) - 0x20 ;
2010-12-10 04:50:08 +01:00
ret = GetIMET ( imetNames , paddingType , iconSize , bannerSize , soundSize ) ;
2012-01-11 06:29:59 +01:00
//if( isLz77 )//really? can the entire banner be lz77 compressed?
// ret += LZ77::Compress( data );
//else
// ret += data;
ret + = LZ77 : : Compress ( data , lz77Type ) ;
2010-12-10 04:50:08 +01:00
return ret ;
}
int U8 : : GetHeaderType ( )
{
return headerType ;
}