wiiqt6/WiiQt/savebanner.cpp
2024-07-20 13:36:42 +02:00

366 lines
14 KiB
C++

#include "savebanner.h"
#include "tools.h"
static int ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height);
SaveBanner::SaveBanner()
{
ok = false;
}
SaveBanner::SaveBanner( QByteArray stuff )
{
ok = false;
QBuffer f( &stuff );
f.open( QIODevice::ReadOnly );
quint32 size = f.size();
if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image
{
qDebug() << "SaveBanner::SaveBanner -> bad filesize" << Qt::hex << size;
f.close();
return;
}
quint32 magic;
f.read( (char*)&magic, 4 );
if( qFromBigEndian( magic ) != 0x5749424e )//WIBN
{
hexdump( stuff, 0, 0x30 );
f.close();
qWarning() << "SaveBanner::SaveBanner -> bad file magic" << Qt::hex << qFromBigEndian( magic );
return;
}
f.read( (char*)&attributes, 4 );
f.read( (char*)&speeds, 2 );
attributes = qFromBigEndian( attributes );
speeds = qFromBigEndian( speeds );
f.seek( 0x20 );
quint16 name[ 0x20 ];
quint16 name2[ 0x20 ];
f.read( (char*)&name, 0x40 );
f.read( (char*)&name2, 0x40 );
//QString title;
for( int i = 0; i < 0x20 && name[ i ] != 0; i++ )
saveTitle += QChar( qFromBigEndian( name[ i ] ) );
saveTitle = saveTitle.trimmed();
QString speedStr;
// 0 = lastimage, speed range from 1 - 3
for( int i = 0; i < 8; i++ )
{
speedStr += QString::number( ( speeds >> ( 2 * ( i ) ) ) & 3 );
if( i < 7 )
{
speedStr += " ";
}
}
QString flags;
// nocopy bit
if( attributes & 1 )
{
flags += "nocopy";
}
// animation type bit
if( attributes & 0x10 )
{
if( !flags.isEmpty() )
{
flags += ", ";
}
flags += "forward and reverse";
}
else
{
if( !flags.isEmpty() )
{
flags += ", ";
}
flags += "loop";
}
flags = flags.leftJustified( 27, QChar( ' ' ) );
qDebug() << Qt::hex << //QString( "%1" ).arg( tmp, 8, 16, QChar( '0' ) ) <<
//QString( "%1" ).arg( speeds, 4, 16, QChar( '0' ) ) <<
speedStr <<
flags <<
saveTitle;
//QString title2;
for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ )
saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) );
saveTitle2 = saveTitle2.trimmed();
//get the banner
f.seek( 0xa0 );
QByteArray ban = f.read( 0x6000 );
//convert to an image we can display
bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 );
if( bannerImg.isNull() )
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> error converting banner image";
return;
}
//get the images that make up the icon
for( quint8 i = 0; i < 8 && f.pos() < size; i++ )
{
QByteArray icn = f.read( 0x1200 );
//make this texture int an image
QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 );
if( iconImg.isNull() )
break;
//add the image to the list
iconImgs << iconImg;
if( !( ( speeds >> ( 2 * i ) ) & 3 ) )// this is the last image
{
break;
}
}
qDebug() << "imgCnt:" << iconImgs.size();
f.close();
ok = true;
//qDebug() << Qt::hex << QString( "%1 %2").arg( qFromBigEndian( tmp ), 9, 16).arg( qFromBigEndian( tmp2 ), 9, 16)
//<< saveTitle.leftJustified( 0x20 ) << QString( "icons: %1").arg( iconImgs.size(), 1, 16 ) << QString( "banner size: %1" ).arg( size, 4, 16 );
}
SaveBanner::SaveBanner( const QString &bannerPath )
{
ok = false;
QFile f( bannerPath );
if( !f.exists() || !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "SaveBanner::SaveBanner -> error opening" << bannerPath;
return;
}
quint32 size = f.size();
if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image
{
qDebug() << "SaveBanner::SaveBanner -> bad filesize" << Qt::hex << size;
f.close();
return;
}
quint32 magic;
f.read( (char*)&magic, 4 );
if( qFromBigEndian( magic ) != 0x5749424e )//WIBN
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> bad file magic" << qFromBigEndian( magic );
return;
}
//get the title of the save
f.seek( 0x20 );
quint16 name[ 0x20 ];
quint16 name2[ 0x20 ];
f.read( (char*)&name, 0x40 );
f.read( (char*)&name2, 0x40 );
//QString title;
for( int i = 0; i < 0x20 && name[ i ] != 0; i++ )
saveTitle += QChar( qFromBigEndian( name[ i ] ) );
saveTitle = saveTitle.trimmed();
//QString title2;
for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ )
saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) );
saveTitle2 = saveTitle2.trimmed();
//get the banner
f.seek( 0xa0 );
QByteArray ban = f.read( 0x6000 );
//convert to an image we can display
bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 );
if( bannerImg.isNull() )
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> error converting banner image";
return;
}
//get the images that make up the icon
while( f.pos() != size )
{
QByteArray icn = f.read( 0x1200 );
//check that there is actually data. some banners use all 0x00 for some images
bool null = true;
for( int i = 0; i < 0x1200; i++ )
{
if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image
{
null = false;
break;
}
}
if( null )
{
//qDebug() << "skipping empty image";
break;
}
//make this texture int an image
QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 );
if( iconImg.isNull() )
break;
//add the image to the list
iconImgs << iconImg;
}
f.close();
ok = true;
}
QImage SaveBanner::ConvertTextureToImage( const QByteArray &ba, quint32 w, quint32 h )
{
//qDebug() << "SaveBanner::ConvertTextureToImage" << ba.size() << Qt::hex << w << h;
quint8* bitmapdata = NULL;//this will hold the converted image
int ret = ConvertRGB5A3ToBitMap( (quint8*)ba.constData(), &bitmapdata, w, h );
if( !ret )
{
qWarning() << "SaveBanner::ConvertTextureToImage -> error converting image";
return QImage();
}
QImage im = QImage( (const uchar*)bitmapdata, w, h, QImage::Format_ARGB32 );
QImage im2 = im.copy( im.rect() );//make a copy of the image so the "free" wont delete any data we still want
free( bitmapdata );
return im2;
}
static int ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height)
{
quint32 x, y;
quint32 x1, y1;
quint32 iv;
//tplpoint -= width;
*bitmapdata = (quint8*)calloc(width * height, 4);
if(*bitmapdata == NULL)
return -1;
quint32 outsz = width * height * 4;
for(iv = 0, y1 = 0; y1 < height; y1 += 4) {
for(x1 = 0; x1 < width; x1 += 4) {
for(y = y1; y < (y1 + 4); y++) {
for(x = x1; x < (x1 + 4); x++) {
quint16 oldpixel = *(quint16*)(tplbuf + ((iv++) * 2));
if((x >= width) || (y >= height))
continue;
oldpixel = qFromBigEndian(oldpixel);
if(oldpixel & (1 << 15)) {
// RGB5
quint8 b = (((oldpixel >> 10) & 0x1F) * 255) / 31;
quint8 g = (((oldpixel >> 5) & 0x1F) * 255) / 31;
quint8 r = (((oldpixel >> 0) & 0x1F) * 255) / 31;
quint8 a = 255;
quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
(*(quint32**)bitmapdata)[x + (y * width)] = rgba;
}else{
// RGB4A3
quint8 a = (((oldpixel >> 12) & 0x7) * 255) / 7;
quint8 b = (((oldpixel >> 8) & 0xF) * 255) / 15;
quint8 g = (((oldpixel >> 4) & 0xF) * 255) / 15;
quint8 r = (((oldpixel >> 0) & 0xF) * 255) / 15;
quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
(*(quint32**)bitmapdata)[x + (y * width)] = rgba;
}
}
}
}
}
return outsz;
}
/*
" 0 20000" "Data East Arcade Classics " "icons: 4" "banner size: a8a0"
" 10 20000" "Paintball 2009 " "icons: 1" "banner size: 72a0"
" 0 55570000" "Let's TAP " "icons: 8" "banner size: f0a0" //too slow
" 0 57ff0000" "Metroid: Other M " "icons: 8" "banner size: f0a0" //too fast
" 0 2aaa0000" "Championship foosball " "icons: 7" "banner size: dea0" //little too slow
" 1 aaaa0000" "Blue World " "icons: 8" "banner size: f0a0" //perfect?
" 0 55520000" "零-月蝕の仮面- " "icons: 1" "banner size: 72a0"
" 0 2e0000" "Punch-Out!! " "icons: 4" "banner size: a8a0" //just a little bit too slow
" 0 15550000" "Balls of Fury " "icons: 7" "banner size: dea0" //mine plays to slow
" 0 aaaa0000" "The Umbrella Chronicles " "icons: 8" "banner size: f0a0"
" 0 20000" "DU SUPER MARIO BROS. Wii " "icons: 1" "banner size: 72a0"
" 0 55550000" "Excite Truck " "icons: 8" "banner size: f0a0" //plays too slow
" 0 2aa0000" "Guitar Hero III " "icons: 5" "banner size: baa0" //to slow
" 0 2aa0000" "Guitar Hero III " "icons: 5" "banner size: baa0"
" 0 aa0000" "HOD 2&3 RETURN " "icons: 4" "banner size: a8a0" //static
" 10 0" "The House of the Dead " "icons: 1" "banner size: 72a0"
" 1 aaaa0000" "Quantum of Solace™ " "icons: 8" "banner size: f0a0" //close to original speed
" 10 0" "Trauma Center: Second Opinion " "icons: 1" "banner size: 72a0"
" 0 0" "Indiana Pwns, by Team Twiizers " "icons: 1" "banner size: 72a0"
" 10 55550000" "Alien Syndrome " "icons: 8" "banner size: f0a0" //should be playing forwards and then reverse 1 or 2 frames and start over?
" 10 0" "Prince of Persia " "icons: 1" "banner size: 72a0"
" 0 ffff0000" "Mad Dog McCree " "icons: 8" "banner size: f0a0" //plays too fast
" 10 30000" "Rubik's World " "icons: 1" "banner size: 72a0"
" 1 2000000" "Smash Bros. Brawl " "icons: 1" "banner size: 72a0"
" 1 2000000" "大乱闘スマッシュブラザーズX " "icons: 1" "banner size: 72a0"
" 1 2000000" "Smash Bros. Brawl " "icons: 1" "banner size: 72a0"
" 0 20000" "Wii Sports " "icons: 1" "banner size: 72a0"
" 10 aaaa0000" "Downhill Jam " "icons: 8" "banner size: f0a0"//correctish
" 1 ffff0000" "Call of Duty: WaW " "icons: 8" "banner size: f0a0" //plays too fast
" 10 ff0000" "Tony Hawk: RIDE " "icons: 4" "banner size: a8a0" //appears static
" 0 20000" "Link's Training " "icons: 1" "banner size: 72a0"
" 0 20000" "Wii Sports Resort " "icons: 1" "banner size: 72a0"
" 0 ff0000" "Iron Man™ 2 " "icons: 4" "banner size: a8a0"//too slow
" 10 55550000" "Attack of the Movies 3-D " "icons: 8" "banner size: f0a0" //appears static
" 0 30000" "SUPER MARIO GALAXY 2 " "icons: 1" "banner size: 72a0"
" 1 ffff0000" "Call of Duty: Black Ops " "icons: 8" "banner size: f0a0" //appears static
" 0 aaaa0000" "DKC Returns " "icons: 8" "banner size: f0a0"//correct
" 0 aaaa0000" "Wheel of Fortune " "icons: 8" "banner size: f0a0"//correct
" 0 0" "Scooby-Doo! " "icons: 1" "banner size: 72a0"
" 11 aaaa0000" "GoldenEye 007 " "icons: 8" "banner size: f0a0"//correctish
" 0 aaaa0000" "Jeopardy! " "icons: 8" "banner size: f0a0"//correctish
" 10 0" "All Star Karate " "icons: 1" "banner size: 72a0"
" 0 20000" "NewSuperMarioBrosWii " "icons: 1" "banner size: 72a0"
" 0 20000" "New SUPER MARIO BROS. Wii " "icons: 1" "banner size: 72a0"
" 10 20000" "Cabela's NAA " "icons: 1" "banner size: 72a0"
" 10 0" "NBA JAM " "icons: 1" "banner size: 72a0"
" 0 30000" "Spider-Man™: SD " "icons: 1" "banner size: 72a0"
" 1 ffff0000" "Tetris® Party Deluxe " "icons: 8" "banner size: f0a0"//plays too fast
" 0 2aaa0000" "GH World Tour " "icons: 7" "banner size: dea0"
" 0 aaa0000" "GH Metallica " "icons: 6" "banner size: cca0"
" 0 aaa0000" "GH Smash Hits " "icons: 6" "banner size: cca0"
" 0 2aaa0000" "GH™:Warriors of Rock " "icons: 7" "banner size: dea0"
" 1 3ff0000" "Rock Band™ 2 " "icons: 5" "banner size: baa0"
" 1 3ff0000" "Rock Band™ 3 " "icons: 5" "banner size: baa0"
" 1 20000" "Check Mii Out Ch. " "icons: 1" "banner size: 72a0"
" 10 ffff0000" "Super Smash Bros. " "icons: 8" "banner size: f0a0" //plays forward, pause, backwards, pause
" 0 20000" "Wii Fit " "icons: 1" "banner size: 72a0"
" 0 20000" "Wii Fit™ Plus " "icons: 1" "banner size: 72a0"
skipping empty image
skipping empty image
skipping empty image
skipping empty image
skipping empty image
skipping empty image
skipping empty image
" 1 20000" "Rabbids Go Home " "icons: 8" "banner size: f0a0"
" 1 20000" "Mario Kart Wii " "icons: 1" "banner size: 72a0"
" 1 20000" "Mario Kart Wii " "icons: 1" "banner size: 72a0"
0x20000 = static even if theres multiple images ( data east arcade )
*/