Logo Search packages:      
Sourcecode: kamefu version File versions

kfile_snes.cpp

/*
    kfile_snes.cpp - SNES(Super Nintendo Entertainment System) Rom image KFile Plugin

    Copyright (c) 2005      by Michaƫl Larouche       <michael.larouche@kdemail.net>

    *************************************************************************
    *                                                                       *
    * 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 2 of the License, or     *
    * (at your option) any later version.                                   *
    *                                                                       *
    *************************************************************************
*/
#include "kfile_snes.h"

#include <kgenericfactory.h>
#include <kdebug.h>
#include <kmdcodec.h>

#include <qfile.h>
#include <qdatastream.h>

#include "snes_country.h"
#include <nintendomakerlist.h>

const int loRomHeaderlessLocation = 0x7fc0; // Without the 512 byte header
const int hiRomHeaderlessLocation = 0xffc0; // Without the 512 byte header
const int loRomHeaderLocation = 0x81c0; // With the 512 byte header
const int hiRomHeaderLocation = 0x101c0; // With the 512 byte header

const QString snesRomType[3] = 
{
      i18n("ROM"),
      i18n("ROM + SRAM"), 
      i18n("ROM + SRAM + Battery")
};

typedef KGenericFactory<KSnesFilePlugin> ksnesfileFactory;

K_EXPORT_COMPONENT_FACTORY(kfile_snes, ksnesfileFactory( "kfile_snes" ))

KSnesFilePlugin::KSnesFilePlugin(QObject *parent, const char *name,
                       const QStringList &args)
    : KFilePlugin(parent, name, args)
{
      //add the mimetype here:
      KFileMimeTypeInfo *info = addMimeTypeInfo( "application/x-rom-snes" );
      
      KFileMimeTypeInfo::ItemInfo *item;
      // our new group
      KFileMimeTypeInfo::GroupInfo *groupGeneral = 0L;
      groupGeneral = addGroupInfo(info, "romGeneralInfo", i18n("General"));
      
      // General Group
      addItemInfo(groupGeneral, "internalName", i18n("Internal Name"), QVariant::String);
      addItemInfo(groupGeneral, "country", i18n("Country"), QVariant::String);
      addItemInfo(groupGeneral, "editor", i18n("Developer"), QVariant::String);
      item = addItemInfo(groupGeneral, "romSize", i18n("ROM Size"), QVariant::Int);
      setSuffix(item, i18n("MBit"));
      item = addItemInfo(groupGeneral, "sramSize", i18n("SRAM Size"), QVariant::Int);
      setSuffix(item, i18n("KBit"));
      addItemInfo(groupGeneral, "md5Hash", i18n("MD5 Hash"), QVariant::String);

      // SNES Info Group
      KFileMimeTypeInfo::GroupInfo *groupSNES = 0L;
      groupSNES = addGroupInfo(info, "snesInfo", i18n("SNES Info"));
      addItemInfo(groupSNES, "snesMakeup", i18n("ROM Makeup"), QVariant::String);
      addItemInfo(groupSNES, "snesType", i18n("ROM Type"), QVariant::String);
}     

bool KSnesFilePlugin::readInfo(KFileMetaInfo& info, uint /*what*/)
{
      KFileMetaInfoGroup groupGeneral = appendGroup(info, "romGeneralInfo");
      KFileMetaInfoGroup groupSNES = appendGroup(info, "snesInfo");

      QByteArray internalName(21);
      Q_UINT8 romMakeup, romType, romSize, sramSize, countryCode, editorCode;
      QString displayMakeup, displayType, editorName(i18n("Unknown")), countryName(i18n("Unknown")), md5Hash;
      int makerCode;

      QFile snes_file(info.path());

      if(snes_file.open(IO_ReadOnly))
      {
            QDataStream stream(&snes_file);
            stream.setByteOrder(QDataStream::LittleEndian);

            // Get the best header location (header/headerless and HiROM or LoROM)
            snes_file.at( getBestHeaderLocation(snes_file, stream) );
            
            // Read the Internal name
            stream.readRawBytes(internalName.data(), internalName.size());
            
            // Read the rom Makeup
            stream >> romMakeup;
            if(romMakeup & HiRom)
            {
                  displayMakeup += QString("HiROM");
            }
            else
            {
                  displayMakeup += QString("LoROM");
            }
            displayMakeup += "/";
            if(romMakeup & FastRom)
            {
                  displayMakeup += QString("FastROM");
            }
            else
            {
                  displayMakeup += QString("SlowROM");
            }

            // Read the rom type
            stream >> romType;
            
            // Read the rom size
            stream >> romSize;
            // Get the rom size in MBit (1 << (ROM_SIZE - 7) MBits)
            romSize = 1 << (romSize - 7);
            
            // Read the SRAM size
            stream >> sramSize;
            // Get the sram size in KBit (1 << (3+SRAM_BYTE) Kbits)
            sramSize = 1 << (3+sramSize);

            // Read the countryCode
            stream >> countryCode;
            if(countryCode < 14)
                  countryName = snesCountryList[countryCode];
            
            // Read the editor
            stream >> editorCode;
            kdDebug() << k_funcinfo << "Read editorCode: " << (uint)editorCode << endl;

            if(editorCode == 0x33) // This a special code for newer ROM, the editor is in ASCII below
                  makerCode = getMakerCode(snes_file, stream);
            else if(editorCode != 0)
                  makerCode = editorCode;
            else 
                  makerCode = 0;
            makerCode = (makerCode >> 4) * 36 + (makerCode & 0x0f); // from uCON64 source.
            
            if(makerCode > 0 || makerCode <= nintendoMakerListLength)
                  editorName = nintendoMakerList[makerCode];

            kdDebug() << k_funcinfo << "Maker Code: " << (int)makerCode << endl;
            kdDebug() << k_funcinfo << "Editor Name: " << editorName << endl;

            // Do the ROM type display
            displayType += snesRomType[(romType & 7) % 3];
            if( (romType & 0xf) >= 3 )
            {
                  displayType += QString(" + ");

                  if (romType == 3 || romType == 5)
                        displayType += i18n("DSP");
                  else if(romType == 0x13)
                        displayType += i18n("SRAM + Super FX (Mario Chip 1)");
                  else if(romType == 0x1a)
                        displayType += i18n("Super FX");
                  else if(romType == 0x14 || romType == 0x15)
                  {
                        if (romSize > 8) // larger than 8 Mbit
                              displayType += i18n("Super FX 2");
                        else
                              displayType += i18n("Super FX");
                  }
                  else if(romType == 0x25)
                        displayType += i18n("OBC1");
                  else if(romType == 0x34 || romType == 0x35)
                        displayType += i18n("SA-1");
                  else if(romType == 0x43 || romType == 0x45)
                        displayType += i18n("S-DD1");
                  else if(romType == 0x55)
                        displayType += i18n("S-RTC");
                  else if(romType == 0xe3)
                        displayType += i18n("Game Boy data");
                  else if(romType == 0xf3)
                        displayType += i18n("C4");
                  else if(romType == 0xf5)
                  {
                        if (romMakeup & FastRom)
                              displayType += i18n("Seta RISC");
                        else
                              displayType += i18n("SPC7110");
                  }
                  else if (romType == 0xf6)
                        displayType += i18n("Seta DSP");
                  else if (romType == 0xf9)
                        displayType += i18n("SPC7110 + RTC");
                  else
                        displayType += i18n("Unknown");
            }

            snes_file.reset(); // Make sure that the md5 hash read the entire ROM.
            // Generate the MD5 Hash
            KMD5 context(0L);
            context.update(snes_file);
            md5Hash = context.hexDigest();

            snes_file.close();
      }
      else
      {
            return false;
      }

      // General group
      appendItem(groupGeneral, "internalName", QString(internalName));
      appendItem(groupGeneral, "country", countryName);
      appendItem(groupGeneral, "editor", editorName);
      appendItem(groupGeneral, "romSize", romSize);
      appendItem(groupGeneral, "sramSize", sramSize);
      appendItem(groupGeneral, "md5Hash", md5Hash);
      
      // SNES group
      appendItem(groupSNES, "snesMakeup", displayMakeup);
      appendItem(groupSNES, "snesType", displayType);

    return true;
}

00225 int KSnesFilePlugin::getMakerCode(QFile &file, QDataStream &stream)
{
      int oldLocation = file.at();
      int makerLocation = oldLocation - 0x2b; // Maybe 0x2a
      int result = 0;

      QByteArray makerRaw(2);
      
      file.at(makerLocation);
      stream.readRawBytes(makerRaw.data(), makerRaw.size());
      
      bool ok;
      QString sMaker(makerRaw);
      kdDebug() << k_funcinfo << "QString maker: " << sMaker << endl;
      result = sMaker.toInt(&ok, 16);

      kdDebug() << k_funcinfo << "Returned Maker Code: " << result << endl;

      // Return to the latest location.
      file.at(oldLocation);

      return result;
}

// FIXME: Make more efficent.
00250 int KSnesFilePlugin::getBestHeaderLocation(QFile &file, QDataStream &stream)
{
      int i, theBestLocation=0, tempScore=0;
      int scores[4];
      int locations[4] = {hiRomHeaderLocation, hiRomHeaderlessLocation, loRomHeaderLocation, loRomHeaderlessLocation};
      for(i=0; i<4; i++)
      {
            scores[i] = checkInformationValidity(locations[i], file, stream);
            kdDebug() << k_funcinfo << "0x" << QString::number(locations[i], 16) << " score: " << scores[i] << endl;
      }
      // Assume that the first location is the best
      tempScore = scores[0];
      theBestLocation = locations[0];
      // Find the best score
      for(i=1; i<4; i++)
      {
            if(scores[i] > tempScore)
            {
                  tempScore = scores[i];
                  theBestLocation = locations[i];
            }
      }
      
      kdDebug() << "The best header location: 0x" << QString::number(theBestLocation, 16) << endl;
      
      return theBestLocation;
}

00278 bool KSnesFilePlugin::canPrint(const QByteArray &charArray)
{
      uint i=0;
      for(i=0; i<charArray.size()-1; i++)
      {
            char temp = charArray.at(i);
            // Make sure all the characters are in printable ASCII range.
            if(temp < 0x20 || temp > 0x7E)
                  return false;
      }
      return true;
}

00291 int KSnesFilePlugin::checkInformationValidity(int location, QFile &file, QDataStream &stream)
{
      int score = 0;
      Q_UINT8 temp1, temp2;

      // Set the rom at test location.
      file.at(location);
      // Internal Name check
      QByteArray gameName(21);
      stream.readRawBytes(gameName.data(), gameName.size());
      if( canPrint(gameName) )
            score += 1;
      stream >> temp1;// Skip rom makeup
      stream >> temp1; 
      // map type
      if( (temp1 & 0xf) < 4)
            score += 2;
    // ROM size
      stream >> temp1;
      if (1 << (temp1 - 7) <= 64)
            score +=1;

      // SRAM size
      stream >> temp1;
      if(1 << temp1 <= 256)
            score += 1;
      // Country
      stream >> temp1;
      if(temp1 <= 13)
            score += 1;
      // Editor "escape code"
      stream >> temp1;
      if(temp1 == 0x33)
            score += 2;
      else
      {
            temp1 = (temp1 >> 4) * 36 + (temp1 & 0x0f);
            if( nintendoMakerList[temp1] != QString::null )
                  score += 2;
      }
      // Version
      stream >> temp1;
      if(temp1 <= 2)
            score += 2;

      Q_UINT16 checksum1, checksum2;
      // Check first checksum.
      stream >> temp1;
      stream >> temp2;
      checksum1 = temp1 + (temp2 << 8);
      // Check second checksum.
      stream >> temp1;
      stream >> temp2;
      checksum2  = temp1 + (temp2 << 8);
      
      if(checksum1 + checksum2 == 0xffff)
      {
            if( checksum1 == 0xffff || checksum2 == 0xffff )
                  score += 3;
            else
                  score += 4;
      }
      // Reset vector
      stream >> temp1;
      if(temp1 & 0x80)
            score += 3;

      return score;
}

#include "kfile_snes.moc"

Generated by  Doxygen 1.6.0   Back to index