Skip to content

Commit

Permalink
Issue #16 - Import romlists from other frontend formats
Browse files Browse the repository at this point in the history
- Added the "--import-romlist" command line option which allows for
the import from Mamewah/Wahcade! .lst files, HyperSpin .xml files, and
Attract-Mode .txt files.  Multiple lists can be imported and combined
into a single Attract-Mode romlist.
  • Loading branch information
mickelson committed Mar 2, 2014
1 parent 084ab06 commit a9e44df
Show file tree
Hide file tree
Showing 8 changed files with 488 additions and 89 deletions.
33 changes: 28 additions & 5 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,12 @@ be selected from the "Sound" menu when in config mode and mapped to an action
or event.

**ARTWORK:** Attract-Mode supports PNG, JPEG, GIF, BMP and TGA image formats.
When deciding what image file to use for a particular artwork type
(default artworks are "marquee" and "screen"), Attract-Mode will use the
artwork/movie selection order set out below.
When deciding what image file to use for a particular artwork type, Attract-
Mode will use the artwork/movie selection order set out below. The default
artworks in Attract-Mode are: "marquee" (for cabinet marquee images),
"snap" (for game screen shots), "flyer" (for game flyer/box art) and "wheel"
(for HyperSpin wheel art). You can add others as needed in the emulator
configuration menu.

**MOVIE:** Attract-Mode should support any movie format supported by FFmpeg.
The movie to play is decided in the order set out below.
Expand Down Expand Up @@ -111,11 +114,31 @@ file that can be edited by most common spreadsheet programs (be sure to
load it as "Text CSV"). The file has one game entry per line, with the very
first line of the file specifying what each column represents.

In addition to the Romlist generation function available in config mode,
In addition to the romlist generation function available in config mode,
Attract-Mode can generate a romlist for multiple emulators from the command
line using the following command:

attract --build-rom-list [emulator names...]
attract --build-romlist <emulator names...>

You can also import romlists from MameWah/Wahcade! (.lst), Attract-Mode
(.txt) and HyperSpin (.xml) using the following command:

attract --import-romlist <file> [emulator name]

The --build-romlist and --import-romlist options can be chained together in
all sorts of strange and wonderful ways to generate combined Attract-Mode
romlists. So:

`attract --import-romlist mame.lst --import-romlist nintendo.lst nestopia`

will combine the entries from the mame.lst and nintendo.lst files (located
in the current directory) into a single Attract-Mode romlist. The "mame"
emulator will be used for the mame.lst games, while the "nestopia" emulator
will be used for the nintendo.lst games.

If you wish to specify the name of the created romlist at the command
line, you can do so with the `--output <name>` option. Beware that this will
overwrite any existing Attract-Mode romlist with the specified name.

[Compile.md]: Compile.md
[Layouts.md]: Layouts.md
210 changes: 190 additions & 20 deletions src/fe_build.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <fstream>
#include <list>
#include <map>
#include <strings.h> // strcasecmp()

extern const char *FE_ROMLIST_SUBDIR;

Expand Down Expand Up @@ -130,7 +131,7 @@ void write_romlist( const std::string &filename,
// one line header showing what the columns represent
//
outfile << "#" << FeRomInfo::indexStrings[i++];
while ( FeRomInfo::indexStrings[i] != NULL )
while ( i < FeRomInfo::Favourite )
outfile << ";" << FeRomInfo::indexStrings[i++];
outfile << std::endl;

Expand All @@ -144,12 +145,20 @@ void write_romlist( const std::string &filename,
}
}

struct myclasscmp
{
bool operator() ( const std::string &lhs, const std::string &rhs )
{
return ( strcasecmp( lhs.c_str(), rhs.c_str() ) < 0 );
}
};

void ini_import( const std::string &filename,
std::list<FeRomInfo> &romlist,
FeRomInfo::Index index,
const std::string &init_tag )
{
std::map <std::string, std::string> my_map;
std::map <std::string, std::string, myclasscmp> my_map;

// create entries in the map for each name we want to find
for ( std::list<FeRomInfo>::iterator itr=romlist.begin();
Expand Down Expand Up @@ -192,7 +201,7 @@ void ini_import( const std::string &filename,
size_t pos=0;
token_helper( line, pos, name, "=" );

std::map<std::string, std::string>::iterator itr;
std::map<std::string, std::string, myclasscmp>::iterator itr;
itr = my_map.find( name );
if ( itr != my_map.end() )
{
Expand Down Expand Up @@ -220,8 +229,8 @@ void ini_import( const std::string &filename,
}
}

std::cout << "Found info for " << count << " entries. File: "
<< filename << std::endl;
std::cout << "[Import " << filename << "] - found info for " << count
<< " entries." << std::endl;
}

void apply_import_extras( const FeEmulatorInfo &emulator,
Expand Down Expand Up @@ -255,31 +264,184 @@ void apply_import_extras( const FeEmulatorInfo &emulator,
} while ( pos < iextras.size() );
}

bool import_mamewah( const std::string &input_filename,
const std::string &emulator_name,
std::list<FeRomInfo> &romlist )
{
// Map the lines of the Mamewah / Wahcade! romlist file to our RomInfo structure
//
const FeRomInfo::Index wah_map[] =
{
FeRomInfo::Romname, // Name
FeRomInfo::Title, // Description
FeRomInfo::Year,
FeRomInfo::Manufacturer,
FeRomInfo::Cloneof,
FeRomInfo::LAST_INDEX, // Romof
FeRomInfo::DisplayType,
FeRomInfo::Rotation,
FeRomInfo::Control,
FeRomInfo::Status,
FeRomInfo::LAST_INDEX, // Colour status
FeRomInfo::LAST_INDEX, // Sound status
FeRomInfo::Category
};

std::ifstream myfile( input_filename.c_str() );
if ( !myfile.is_open() )
{
std::cerr << "Error opening file: " << input_filename << std::endl;
return false;
}

int count=0;
FeRomInfo current_rom;

while ( myfile.good() )
{
std::string line;
getline( myfile, line );

int index = ( count % 13 );

if ( wah_map[ index ] != FeRomInfo::LAST_INDEX )
{
size_t l = line.find_last_not_of( FE_WHITESPACE );
if ( l != std::string::npos )
{
current_rom.set_info(
wah_map[ index ],
line.substr( 0, l+1 ) );
}
}

if ( index == 12 )
{
current_rom.set_info(
FeRomInfo::Emulator,
emulator_name );

romlist.push_back( current_rom );
current_rom.clear();
count = 0;
}
else
count++;
}

if ( count > 1 )
{
std::cout << "Warning: Unexpected end of file encountered: " << input_filename
<< ", this probably means the import failed." << std::endl;
}

myfile.close();
return true;
}

}; // end namespace

bool FeSettings::build_romlist( const std::vector <std::string> &emu_names )
bool FeSettings::build_romlist( const std::vector< FeImportTask > &task_list,
const std::string &output_name )
{
std::list<FeRomInfo> total_romlist;
std::string name, list_name, path;
std::string best_name, list_name, path;

for ( std::vector<std::string>::const_iterator itr=emu_names.begin();
itr < emu_names.end(); ++itr )
for ( std::vector<FeImportTask>::const_iterator itr=task_list.begin();
itr < task_list.end(); ++itr )
{
FeEmulatorInfo *emu = get_emulator( *itr );
if ( emu == NULL )
if ( (*itr).file_name.empty() )
{
std::cout << "Error: Invalid -build-rom-list target: " << (*itr)
<< std::endl;
// Build romlist task
FeEmulatorInfo *emu = get_emulator( (*itr).emulator_name );
if ( emu == NULL )
{
std::cout << "Error: Invalid -build-rom-list target: " << (*itr).emulator_name
<< std::endl;
}
else
{
std::list<FeRomInfo> romlist;

best_name = emu->get_info( FeEmulatorInfo::Name );

build_basic_romlist( *emu, romlist );

apply_listxml( *emu, romlist );
apply_import_extras( *emu, romlist );

total_romlist.splice( total_romlist.end(), romlist );
}
}
else
{
// import romlist from file task
std::list<FeRomInfo> romlist;
name = emu->get_info( FeEmulatorInfo::Name );
std::string emu_name;

build_basic_romlist( *emu, romlist );
if ( (*itr).emulator_name.empty() )
{
// deduce the emulator name from the filename provided
size_t my_start = (*itr).file_name.find_last_of( "\\/" );
if ( my_start == std::string::npos ) // if there is no / we start at the beginning
my_start = 0;
else
my_start += 1;

size_t my_end = (*itr).file_name.find_last_of( "." );
if ( my_end != std::string::npos )
emu_name = (*itr).file_name.substr( my_start, my_end - my_start );
}
else
emu_name = (*itr).emulator_name;

apply_listxml( *emu, romlist );
apply_import_extras( *emu, romlist );
best_name = emu_name;

if ( tail_compare( (*itr).file_name, ".txt" ) )
{
// Attract-Mode format list
//
FeRomList temp_list;
temp_list.load_from_file( (*itr).file_name, ";" );

for ( int i=0; i< temp_list.size(); i++ )
romlist.push_back( temp_list[i] );
}
else if ( tail_compare( (*itr).file_name, ".lst" ) )
{
// Mamewah/Wahcade! format list
//
import_mamewah( (*itr).file_name, emu_name, romlist );
}
else if ( tail_compare( (*itr).file_name, ".xml" ) )
{
// HyperSpin format list
//
FeHyperSpinXMLParser my_parser( romlist );
if ( my_parser.parse( (*itr).file_name ) )
{
// Update the emulator entries
for ( std::list<FeRomInfo>::iterator itr=romlist.begin(); itr != romlist.end(); ++itr )
(*itr).set_info( FeRomInfo::Emulator, emu_name );
}
}
else
{
std::cerr << "Error: Unsupported --import-rom-list file: "
<< (*itr).file_name << std::endl;
}

std::cout << "[Import " << (*itr).file_name << "] - Imported " << romlist.size() << " entries."
<< std::endl;

FeEmulatorInfo *emu = get_emulator( emu_name );
if ( emu == NULL )
{
std::cout << "Warning: The emulator specified with --import-rom-list was not found: "
<< emu_name << std::endl;
}
else
apply_import_extras( *emu, romlist );

total_romlist.splice( total_romlist.end(), romlist );
}
Expand All @@ -291,14 +453,22 @@ bool FeSettings::build_romlist( const std::vector <std::string> &emu_names )
total_romlist.sort( FeRomListCompare::cmp );
FeRomListCompare::close_rex();

if ( emu_names.size() > 1 )
name = "multi";
if ( task_list.size() > 1 )
best_name = "multi";

path = get_config_dir();
confirm_directory( path, FE_ROMLIST_SUBDIR );

path += FE_ROMLIST_SUBDIR;
get_available_filename( path, name, FE_ROMLIST_FILE_EXTENSION, list_name );

// if we weren't given a specific output name, then we come up with a name
// that doesn't exist already
//
if ( output_name.empty() )
get_available_filename( path, best_name, FE_ROMLIST_FILE_EXTENSION, list_name );
else
list_name = path + output_name + FE_ROMLIST_FILE_EXTENSION;

write_romlist( list_name, total_romlist );

return true;
Expand Down
6 changes: 6 additions & 0 deletions src/fe_info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,12 @@ void FeRomInfo::dump( void ) const
std::cout << std::endl;
}

void FeRomInfo::clear()
{
for ( int i=0; i < LAST_INDEX; i++ )
m_info[i].clear();
}

SQRex *FeRomListCompare::m_rex = NULL;

void FeRomListCompare::init_rex( const std::string &re_mask )
Expand Down
2 changes: 2 additions & 0 deletions src/fe_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class FeRomInfo : public FeBaseConfigurable
void dump( void ) const;
std::string as_output( void ) const;

void clear();

private:
FeRomInfo &operator=( const FeRomInfo & );
std::string get_info_escaped( int ) const;
Expand Down
21 changes: 18 additions & 3 deletions src/fe_settings.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ extern const char *FE_EMULATOR_FILE_EXTENSION;

extern const char *FE_LAYOUT_UI_KEY_FILE;

// A container for each task when importing/building romlists from the command line
class FeImportTask
{
public:
FeImportTask( const std::string &fn, const std::string &en )
: file_name( fn ), emulator_name( en )
{
};

std::string file_name; // if empty, then we build romlist for specified emulator
std::string emulator_name;
};

class FeSettings : public FeBaseConfigurable
{
public:
Expand Down Expand Up @@ -220,10 +233,12 @@ class FeSettings : public FeBaseConfigurable
bool confirm_favs() const { return m_confirm_favs; }

//
// This function implements the command-line romlist generation
// A non-existing filename is chosen for the resulting romlist
// This function implements command-line romlist generation/imports
// If output_name is empty, then a non-existing filename is chosen for
// the resulting romlist file
//
bool build_romlist( const std::vector< std::string > &emu_list );
bool build_romlist( const std::vector< FeImportTask > &task_list,
const std::string &output_name );

// This function implements the config-mode romlist generation
// A romlist named "<emu_name>.txt" is created in the romlist dir,
Expand Down
Loading

0 comments on commit a9e44df

Please sign in to comment.