mirror of
https://github.com/DaedalusX64/daedalus.git
synced 2025-04-02 10:21:48 -04:00
510 lines
12 KiB
C++
510 lines
12 KiB
C++
/*
|
|
|
|
Copyright (C) 2001 StrmnNrmn
|
|
|
|
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.
|
|
|
|
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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
*/
|
|
|
|
#include "BuildOptions.h"
|
|
#include "Base/Types.h"
|
|
|
|
#include <algorithm>
|
|
#include <stdio.h>
|
|
#include <vector>
|
|
|
|
#include "Core/ROM.h"
|
|
#include "Core/ROMImage.h"
|
|
#include "Debug/DBGConsole.h"
|
|
#include "Interface/RomDB.h"
|
|
#include "Base/MathUtil.h"
|
|
#include "System/IO.h"
|
|
#include "RomFile/RomFile.h"
|
|
#include "Utility/Stream.h"
|
|
#include <filesystem>
|
|
|
|
static const u64 ROMDB_MAGIC_NO = 0x42444D5244454144LL; //DAEDRMDB // 44 41 45 44 52 4D 44 42
|
|
static const u32 ROMDB_CURRENT_VERSION = 4;
|
|
|
|
static const u32 MAX_SENSIBLE_FILES = 2048;
|
|
static const u32 MAX_SENSIBLE_DETAILS = 2048;
|
|
|
|
CRomDB::~CRomDB() {}
|
|
|
|
class IRomDB : public CRomDB
|
|
{
|
|
public:
|
|
IRomDB();
|
|
~IRomDB();
|
|
|
|
//
|
|
// CRomDB implementation
|
|
//
|
|
void Reset();
|
|
bool Commit();
|
|
|
|
void AddRomDirectory(const std::filesystem::path directory);
|
|
|
|
bool QueryByFilename( const std::filesystem::path filename, RomID * id, u32 * rom_size, ECicType * cic_type );
|
|
bool QueryByID( const RomID & id, u32 * rom_size, ECicType * cic_type ) const;
|
|
const char * QueryFilenameFromID( const RomID & id ) const;
|
|
|
|
private:
|
|
void AddRomFile(const std::filesystem::path filename);
|
|
|
|
void AddRomEntry( const std::filesystem::path filename, const RomID & id, u32 rom_size, ECicType cic_type );
|
|
bool OpenDB( const std::filesystem::path filename );
|
|
|
|
private:
|
|
|
|
// For serialisation we used a fixed size struct for ease of reading
|
|
struct RomFilesKeyValue
|
|
{
|
|
RomFilesKeyValue()
|
|
{
|
|
memset( FileName, 0, sizeof( FileName ) );
|
|
}
|
|
RomFilesKeyValue( const RomFilesKeyValue & rhs )
|
|
{
|
|
memset( FileName, 0, sizeof( FileName ) );
|
|
strcpy( FileName, rhs.FileName );
|
|
ID = rhs.ID;
|
|
}
|
|
RomFilesKeyValue & operator=( const RomFilesKeyValue & rhs )
|
|
{
|
|
if( this == &rhs )
|
|
return *this;
|
|
memset( FileName, 0, sizeof( FileName ) );
|
|
strcpy( FileName, rhs.FileName );
|
|
ID = rhs.ID;
|
|
return *this;
|
|
}
|
|
|
|
RomFilesKeyValue( const std::filesystem::path filename, const RomID & id )
|
|
{
|
|
memset( FileName, 0, sizeof( FileName ) );
|
|
strcpy( FileName, filename.c_str() );
|
|
ID = id;
|
|
}
|
|
|
|
// This is actually IO::Path::kMaxPathLen+1, but we need to ensure that it doesn't change if we ever change the kMaxPathLen constant.
|
|
static const u32 kMaxFilenameLen = 260;
|
|
char FileName[ kMaxFilenameLen + 1 ];
|
|
RomID ID;
|
|
};
|
|
|
|
struct SSortByFilename
|
|
{
|
|
bool operator()( const RomFilesKeyValue & a, const RomFilesKeyValue & b ) const
|
|
{
|
|
return strcmp( a.FileName, b.FileName ) < 0;
|
|
}
|
|
bool operator()( const char * a, const RomFilesKeyValue & b ) const
|
|
{
|
|
return strcmp( a, b.FileName ) < 0;
|
|
}
|
|
bool operator()( const RomFilesKeyValue & a, const char * b ) const
|
|
{
|
|
return strcmp( a.FileName, b ) < 0;
|
|
}
|
|
};
|
|
|
|
struct RomDetails
|
|
{
|
|
RomDetails()
|
|
: ID()
|
|
, RomSize( 0 )
|
|
, CicType( CIC_UNKNOWN )
|
|
{
|
|
}
|
|
|
|
RomDetails( const RomID & id, u32 rom_size, ECicType cic_type )
|
|
: ID( id )
|
|
, RomSize( rom_size )
|
|
, CicType( cic_type )
|
|
{
|
|
}
|
|
|
|
RomID ID;
|
|
u32 RomSize;
|
|
ECicType CicType;
|
|
};
|
|
|
|
struct SSortDetailsByID
|
|
{
|
|
bool operator()( const RomDetails & a, const RomDetails & b ) const
|
|
{
|
|
return a.ID < b.ID;
|
|
}
|
|
bool operator()( const RomID & a, const RomDetails & b ) const
|
|
{
|
|
return a < b.ID;
|
|
}
|
|
bool operator()( const RomDetails & a, const RomID & b ) const
|
|
{
|
|
return a.ID < b;
|
|
}
|
|
};
|
|
using FilenameVec = std::vector< RomFilesKeyValue >;
|
|
using DetailsVec = std::vector< RomDetails >;
|
|
|
|
IO::Filename mRomDBFileName;
|
|
FilenameVec mRomFiles;
|
|
DetailsVec mRomDetails;
|
|
bool mDirty;
|
|
};
|
|
|
|
template<> bool CSingleton< CRomDB >::Create()
|
|
{
|
|
DAEDALUS_ASSERT_Q(mpInstance == nullptr);
|
|
mpInstance = std::make_unique<IRomDB>();
|
|
|
|
std::filesystem::path p("rom.db");
|
|
const char *romdb_filename = p.c_str();
|
|
|
|
/*ret = */mpInstance->OpenDB( romdb_filename );
|
|
|
|
return true;
|
|
}
|
|
|
|
IRomDB::IRomDB()
|
|
: mDirty( false )
|
|
{
|
|
mRomDBFileName[ 0 ] = '\0';
|
|
}
|
|
|
|
IRomDB::~IRomDB()
|
|
{
|
|
Commit();
|
|
}
|
|
|
|
void IRomDB::Reset()
|
|
{
|
|
mRomFiles.clear();
|
|
mRomDetails.clear();
|
|
mDirty = true;
|
|
}
|
|
|
|
bool IRomDB::OpenDB( const std::filesystem::path filename )
|
|
{
|
|
u32 num_read;
|
|
|
|
//
|
|
// Remember the filename
|
|
//
|
|
IO::Path::Assign( mRomDBFileName, filename.c_str() );
|
|
|
|
FILE * fh = fopen( filename.c_str(), "rb" );
|
|
if ( !fh )
|
|
{
|
|
DBGConsole_Msg( 0, "Failed to open RomDB from %s\n", mRomDBFileName );
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Check the magic number
|
|
//
|
|
u64 magic;
|
|
num_read = fread( &magic, sizeof( magic ), 1, fh );
|
|
if ( num_read != 1 || magic != ROMDB_MAGIC_NO )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB has wrong magic number." );
|
|
goto fail;
|
|
}
|
|
|
|
//
|
|
// Check the version number
|
|
//
|
|
u32 version;
|
|
num_read = fread( &version, sizeof( version ), 1, fh );
|
|
if ( num_read != 1 || version != ROMDB_CURRENT_VERSION )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB has wrong version for this build of Daedalus." );
|
|
goto fail;
|
|
}
|
|
|
|
u32 num_files;
|
|
num_read = fread( &num_files, sizeof( num_files ), 1, fh );
|
|
if ( num_read != 1 )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB EOF reading number of files." );
|
|
goto fail;
|
|
}
|
|
else if ( num_files > MAX_SENSIBLE_FILES )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB has unexpectedly large number of files (%d).", num_files );
|
|
goto fail;
|
|
}
|
|
|
|
mRomFiles.resize( num_files );
|
|
if( fread( &mRomFiles[0], sizeof(RomFilesKeyValue), num_files, fh ) != num_files )
|
|
{
|
|
goto fail;
|
|
}
|
|
// Redundant?
|
|
std::sort( mRomFiles.begin(), mRomFiles.end(), SSortByFilename() );
|
|
|
|
u32 num_details;
|
|
num_read = fread( &num_details, sizeof( num_details ), 1, fh );
|
|
if ( num_read != 1 )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB EOF reading number of details." );
|
|
goto fail;
|
|
}
|
|
else if ( num_details > MAX_SENSIBLE_DETAILS )
|
|
{
|
|
DBGConsole_Msg( 0, "RomDB has unexpectedly large number of details (%d).", num_details );
|
|
goto fail;
|
|
}
|
|
|
|
mRomDetails.resize( num_details );
|
|
if( fread( &mRomDetails[0], sizeof(RomDetails), num_details, fh ) != num_details )
|
|
{
|
|
goto fail;
|
|
}
|
|
// Redundant?
|
|
std::sort( mRomDetails.begin(), mRomDetails.end(), SSortDetailsByID() );
|
|
DBGConsole_Msg( 0, "RomDB initialised with %d files and %d details.", mRomFiles.size(), mRomDetails.size() );
|
|
fclose( fh );
|
|
return true;
|
|
|
|
fail:
|
|
fclose( fh );
|
|
return false;
|
|
}
|
|
|
|
bool IRomDB::Commit()
|
|
{
|
|
if( !mDirty )
|
|
return true;
|
|
|
|
//
|
|
// Check if we have a valid filename
|
|
//
|
|
if ( strlen( mRomDBFileName ) <= 0 )
|
|
return false;
|
|
|
|
FILE * fh = fopen( mRomDBFileName, "wb" );
|
|
|
|
if ( !fh )
|
|
return false;
|
|
|
|
//
|
|
// Write the magic
|
|
//
|
|
fwrite( &ROMDB_MAGIC_NO, sizeof( ROMDB_MAGIC_NO ), 1, fh );
|
|
|
|
//
|
|
// Write the version
|
|
//
|
|
fwrite( &ROMDB_CURRENT_VERSION, sizeof( ROMDB_CURRENT_VERSION ), 1, fh );
|
|
|
|
{
|
|
u32 num_files( mRomFiles.size() );
|
|
fwrite( &num_files, sizeof( num_files ), 1, fh );
|
|
fwrite( &mRomFiles[0], sizeof(RomFilesKeyValue), num_files, fh );
|
|
}
|
|
|
|
{
|
|
u32 num_details( mRomDetails.size() );
|
|
fwrite( &num_details, sizeof( num_details ), 1, fh );
|
|
fwrite( &mRomDetails[0], sizeof(RomDetails), num_details, fh );
|
|
}
|
|
|
|
fclose( fh );
|
|
|
|
mDirty = true;
|
|
return true;
|
|
}
|
|
|
|
void IRomDB::AddRomEntry( const std::filesystem::path filename, const RomID & id, u32 rom_size, ECicType cic_type )
|
|
{
|
|
// Update filename/id map
|
|
FilenameVec::iterator fit( std::lower_bound( mRomFiles.begin(), mRomFiles.end(), filename.c_str(), SSortByFilename() ) );
|
|
if( fit != mRomFiles.end() && strcmp( fit->FileName, filename.c_str() ) == 0 )
|
|
{
|
|
fit->ID = id;
|
|
}
|
|
else
|
|
{
|
|
RomFilesKeyValue filename_id( filename, id );
|
|
mRomFiles.insert( fit, filename_id );
|
|
}
|
|
|
|
// Update id/details map
|
|
DetailsVec::iterator dit( std::lower_bound( mRomDetails.begin(), mRomDetails.end(), id, SSortDetailsByID() ) );
|
|
if( dit != mRomDetails.end() && dit->ID == id )
|
|
{
|
|
dit->RomSize = rom_size;
|
|
dit->CicType = cic_type;
|
|
}
|
|
else
|
|
{
|
|
RomDetails id_details( id, rom_size, cic_type );
|
|
mRomDetails.insert( dit, id_details );
|
|
}
|
|
|
|
mDirty = true;
|
|
}
|
|
|
|
void IRomDB::AddRomDirectory(const std::filesystem::path directory)
|
|
{
|
|
DBGConsole_Msg(0, "Adding roms directory [C%s]", directory.c_str());
|
|
|
|
std::string full_path;
|
|
|
|
IO::FindHandleT find_handle;
|
|
IO::FindDataT find_data;
|
|
if(IO::FindFileOpen( directory.c_str(), &find_handle, find_data ))
|
|
{
|
|
do
|
|
{
|
|
const std::filesystem::path rom_filename = find_data.Name;
|
|
if(IsRomfilename( rom_filename.c_str() ))
|
|
{
|
|
IO::Filename full_path;
|
|
IO::Path::Combine(full_path, directory.c_str(), rom_filename.c_str());
|
|
|
|
AddRomFile(full_path);
|
|
}
|
|
}
|
|
while(IO::FindFileNext( find_handle, find_data ));
|
|
|
|
IO::FindFileClose( find_handle );
|
|
}
|
|
}
|
|
|
|
void IRomDB::AddRomFile(const std::filesystem::path filename)
|
|
{
|
|
RomID id;
|
|
u32 rom_size;
|
|
ECicType boot_type;
|
|
|
|
QueryByFilename(filename, &id, &rom_size, &boot_type);
|
|
}
|
|
|
|
static bool GenerateRomDetails( const std::filesystem::path filename, RomID * id, u32 * rom_size, ECicType * cic_type )
|
|
{
|
|
//
|
|
// Haven't seen this rom before - try to add it to the database
|
|
//
|
|
auto rom_file = ROMFile::Create( filename );
|
|
if( rom_file == NULL )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CNullOutputStream messages;
|
|
|
|
if( !rom_file->Open( messages ) )
|
|
{
|
|
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// They weren't there - so we need to find this info out for ourselves
|
|
// Only read in the header + bootcode
|
|
//
|
|
u32 bytes_to_read( RAMROM_GAME_OFFSET );
|
|
u32 size_aligned( AlignPow2( bytes_to_read, 4 ) ); // Needed?
|
|
u8 * bytes( new u8[size_aligned] );
|
|
|
|
if( !rom_file->LoadData( bytes_to_read, bytes, messages ) )
|
|
{
|
|
// Lots of files don't have any info - don't worry about it
|
|
delete [] bytes;
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Get the address of the rom header
|
|
// Setup the rom id and size
|
|
//
|
|
*rom_size = rom_file->GetRomSize();
|
|
if (*rom_size >= RAMROM_GAME_OFFSET)
|
|
{
|
|
*cic_type = ROM_GenerateCICType( bytes );
|
|
}
|
|
else
|
|
{
|
|
*cic_type = CIC_UNKNOWN;
|
|
}
|
|
|
|
//
|
|
// Swap into native format
|
|
//
|
|
ROMFile::ByteSwap_3210( bytes, bytes_to_read );
|
|
|
|
const ROMHeader * prh( reinterpret_cast<const ROMHeader *>( bytes ) );
|
|
*id = RomID( prh->CRC1, prh->CRC2, prh->CountryID );
|
|
|
|
delete [] bytes;
|
|
return true;
|
|
}
|
|
|
|
bool IRomDB::QueryByFilename( const std::filesystem::path filename, RomID * id, u32 * rom_size, ECicType * cic_type )
|
|
{
|
|
//
|
|
// First of all, check if we have these details cached in the rom database
|
|
//
|
|
FilenameVec::const_iterator fit( std::lower_bound( mRomFiles.begin(), mRomFiles.end(), filename.c_str(), SSortByFilename() ) );
|
|
if( fit != mRomFiles.end() && strcmp( fit->FileName, filename.c_str() ) == 0 )
|
|
{
|
|
if( QueryByID( fit->ID, rom_size, cic_type ) )
|
|
{
|
|
*id = fit->ID;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if( GenerateRomDetails( filename, id, rom_size, cic_type ) )
|
|
{
|
|
//
|
|
// Store this information for future reference
|
|
//
|
|
AddRomEntry( filename, *id, *rom_size, *cic_type );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool IRomDB::QueryByID( const RomID & id, u32 * rom_size, ECicType * cic_type ) const
|
|
{
|
|
DetailsVec::const_iterator it( std::lower_bound( mRomDetails.begin(), mRomDetails.end(), id, SSortDetailsByID() ) );
|
|
if( it != mRomDetails.end() && it->ID == id )
|
|
{
|
|
*rom_size = it->RomSize;
|
|
*cic_type = it->CicType;
|
|
return true;
|
|
}
|
|
|
|
// Can't generate the details, as we have no filename to work with
|
|
return false;
|
|
}
|
|
|
|
const char * IRomDB::QueryFilenameFromID( const RomID & id ) const
|
|
{
|
|
for( u32 i = 0; i < mRomFiles.size(); ++i )
|
|
{
|
|
if( mRomFiles[ i ].ID == id )
|
|
{
|
|
return mRomFiles[ i ].FileName;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|