2016-03-18 01:17:39 -07:00
using System ;
using System.Collections.Generic ;
using System.Data.SQLite ;
using System.IO ;
2016-03-24 14:35:48 -07:00
using System.Linq ;
2016-03-25 11:20:58 -07:00
using System.Text ;
2016-03-18 01:17:39 -07:00
using System.Text.RegularExpressions ;
using System.Web ;
2016-03-29 13:48:10 -07:00
using SabreTools.Helper ;
2016-03-28 17:54:24 -07:00
2016-03-29 13:48:10 -07:00
namespace SabreTools
2016-03-18 01:17:39 -07:00
{
2016-03-29 14:49:03 -07:00
/// <summary>
/// Generate a DAT from the data in the database
/// </summary>
2016-03-18 01:17:39 -07:00
class Generate
{
// Private instance variables
2016-03-24 17:38:08 -07:00
private string _systems ;
private string _sources ;
2016-03-31 16:24:58 -07:00
private string _outdir ;
2016-03-18 01:17:39 -07:00
private string _connectionString ;
2016-03-24 13:23:13 -07:00
private bool _norename ;
2016-03-18 01:17:39 -07:00
private bool _old ;
// Private required variables
private Dictionary < int , string > _headers ;
2016-03-28 18:40:35 -07:00
private Logger _logger ;
2016-03-18 01:17:39 -07:00
2016-03-29 14:49:03 -07:00
/// <summary>
/// Initialize a Generate object with the given information
/// </summary>
/// <param name="systems">Comma-separated list of systems to be included in the DAT (blank means all)</param>
/// <param name="sources">Comma-separated list of sources to be included in the DAT (blank means all)</param>
2016-03-31 16:24:58 -07:00
/// <param name="outdir">The output folder where the generated DAT will be put; blank means the current directory</param>
2016-03-29 14:49:03 -07:00
/// <param name="connectionString">Connection string for SQLite</param>
/// <param name="logger">Logger object for file or console output</param>
/// <param name="norename">True if files should not be renamed with system and/or source in merged mode (default false)</param>
/// <param name="old">True if the output file should be in RomVault format (default false)</param>
2016-03-31 16:24:58 -07:00
public Generate ( string systems , string sources , string outdir , string connectionString , Logger logger , bool norename = false , bool old = false )
2016-03-18 01:17:39 -07:00
{
2016-03-24 14:07:32 -07:00
_systems = systems ;
_sources = sources ;
2016-03-18 01:17:39 -07:00
_connectionString = connectionString ;
2016-03-24 13:23:13 -07:00
_norename = norename ;
2016-03-18 01:17:39 -07:00
_old = old ;
2016-03-28 17:54:24 -07:00
_logger = logger ;
2016-03-18 01:17:39 -07:00
2016-03-31 16:24:58 -07:00
// Take care of special outfolder cases
_outdir = ( outdir = = "" ? outdir :
( outdir . Contains ( "/" ) & & ! outdir . EndsWith ( "/" ) ? outdir + "/" :
2016-03-31 16:40:28 -07:00
( outdir . Contains ( "\\" ) & & ! outdir . EndsWith ( "\\" ) ? outdir + "\\" :
( ! outdir . Contains ( "/" ) & & ! outdir . Contains ( "\\" ) ? outdir + "\\" : outdir )
)
2016-03-31 16:24:58 -07:00
)
) ;
2016-03-31 18:21:00 -07:00
if ( _outdir ! = "" & & ! Directory . Exists ( _outdir ) )
2016-03-31 16:40:28 -07:00
{
Directory . CreateDirectory ( _outdir ) ;
}
2016-03-31 16:24:58 -07:00
2016-03-18 01:17:39 -07:00
_headers = new Dictionary < int , string > ( ) ;
_headers . Add ( 25 , "a7800.xml" ) ;
_headers . Add ( 228 , "fds.xml" ) ;
_headers . Add ( 31 , "lynx.xml" ) ;
_headers . Add ( 0 , "mega.xml" ) ; // Merged version of all other headers
_headers . Add ( 234 , "n64.xml" ) ;
_headers . Add ( 238 , "nes.xml" ) ;
_headers . Add ( 241 , "snes.xml" ) ; // Self-created to deal with various headers
}
2016-03-29 14:49:03 -07:00
/// <summary>
/// Generate a DAT file that is represented by the data in the Generate object.
/// </summary>
/// <returns>True if the file could be created, false otherwise</returns>
2016-03-18 01:17:39 -07:00
public bool Export ( )
{
2016-03-24 17:46:47 -07:00
// Check to see if the source is an import-only. If so, tell the user and exit
int id = 0 ;
if ( _sources ! = "" & & Int32 . TryParse ( _sources , out id ) & & id < = 14 )
2016-04-04 23:11:29 -07:00
{
2016-03-31 16:40:28 -07:00
_logger . Warning ( "This source (" + id + ") is import-only so a DAT cannot be created. We apologize for the inconvenience." ) ;
2016-03-24 17:46:47 -07:00
return false ;
}
2016-03-18 01:17:39 -07:00
// Get the system name, if applicable
2016-03-24 17:38:08 -07:00
string systemname = "" ;
if ( _systems ! = "" )
2016-03-18 01:17:39 -07:00
{
2016-03-24 17:38:08 -07:00
string query = "SELECT manufacturer, system FROM systems WHERE id in (" + _systems + ")" ;
2016-03-18 01:17:39 -07:00
using ( SQLiteConnection dbc = new SQLiteConnection ( _connectionString ) )
{
dbc . Open ( ) ;
using ( SQLiteCommand slc = new SQLiteCommand ( query , dbc ) )
{
using ( SQLiteDataReader sldr = slc . ExecuteReader ( ) )
{
// If there are no games for this combination, return nothing
if ( ! sldr . HasRows )
{
2016-03-30 13:36:52 -07:00
_logger . Error ( "No system could be found with id in \"" + _systems + "\". Please check and try again." ) ;
2016-03-18 01:17:39 -07:00
return false ;
}
2016-03-24 14:23:12 -07:00
// Retrieve and build the system name from all retrieved
int tempsize = 0 ;
while ( sldr . Read ( ) & & tempsize < 3 )
{
systemname + = ( tempsize = = 0 ?
sldr . GetString ( 0 ) + " - " + sldr . GetString ( 1 ) :
2016-03-24 17:38:08 -07:00
"; " + sldr . GetString ( 0 ) + " - " + sldr . GetString ( 1 ) ) ;
2016-03-24 14:23:12 -07:00
tempsize + + ;
}
// If there are more than 3 systems, just put "etc." on the end
if ( sldr . Read ( ) )
{
2016-03-24 17:38:08 -07:00
systemname + = "; etc." ;
2016-03-24 14:23:12 -07:00
}
2016-03-18 01:17:39 -07:00
}
}
}
}
2016-03-24 17:38:08 -07:00
else
{
systemname = "ALL" ;
}
2016-03-18 01:17:39 -07:00
2016-03-24 17:38:08 -07:00
string sourcename = "" ;
if ( _sources ! = "" )
2016-03-18 01:17:39 -07:00
{
2016-03-24 17:38:08 -07:00
string query = "SELECT name FROM sources WHERE id in (" + _sources + ")" ;
2016-03-29 21:46:27 -07:00
2016-03-18 01:17:39 -07:00
using ( SQLiteConnection dbc = new SQLiteConnection ( _connectionString ) )
{
dbc . Open ( ) ;
using ( SQLiteCommand slc = new SQLiteCommand ( query , dbc ) )
{
using ( SQLiteDataReader sldr = slc . ExecuteReader ( ) )
{
// If there are no games for this combination, return nothing
if ( ! sldr . HasRows )
{
2016-03-30 13:36:52 -07:00
_logger . Error ( "No source could be found with id in \"" + _sources + "\". Please check and try again." ) ;
2016-03-18 01:17:39 -07:00
return false ;
}
2016-03-24 14:23:12 -07:00
// Retrieve and build the source name from all retrieved
int tempsize = 0 ;
while ( sldr . Read ( ) & & tempsize < 3 )
{
2016-03-24 17:38:08 -07:00
sourcename + = ( tempsize = = 0 ? sldr . GetString ( 0 ) : "; " + sldr . GetString ( 0 ) ) ;
2016-03-24 14:23:12 -07:00
tempsize + + ;
}
// If there are more than 3 systems, just put "etc." on the end
if ( sldr . Read ( ) )
{
2016-03-24 17:38:08 -07:00
sourcename + = "; etc." ;
2016-03-24 14:23:12 -07:00
}
2016-03-18 01:17:39 -07:00
}
}
}
}
2016-03-24 17:38:08 -07:00
else
{
sourcename = "Merged" ;
}
2016-03-18 01:17:39 -07:00
// Retrieve the list of processed roms
List < RomData > roms = ProcessRoms ( ) ;
2016-03-18 15:01:00 -07:00
// If the output is null, nothing was found so return false
if ( roms = = null )
{
return false ;
}
2016-03-18 01:17:39 -07:00
// Create a name for the file based on the retrieved information
string version = DateTime . Now . ToString ( "yyyyMMddHHmmss" ) ;
2016-04-07 11:17:47 -07:00
string intname = systemname + " (" + sourcename + ")" ;
2016-03-18 01:17:39 -07:00
string datname = systemname + " (" + sourcename + " " + version + ")" ;
// Create and open an output file for writing (currently uses current time, change to "last updated time"
2016-03-31 16:24:58 -07:00
_logger . Log ( "Opening file for writing: " + _outdir + datname + ( _old ? ".dat" : ".xml" ) ) ;
2016-03-18 01:17:39 -07:00
try
{
2016-03-31 16:24:58 -07:00
FileStream fs = File . Create ( _outdir + datname + ( _old ? ".dat" : ".xml" ) ) ;
2016-03-25 11:20:58 -07:00
StreamWriter sw = new StreamWriter ( fs , Encoding . UTF8 ) ;
2016-03-18 01:17:39 -07:00
// Temporarilly set _system if we're in MEGAMERGED mode to get the right header skip XML
2016-03-24 17:38:08 -07:00
if ( _systems = = "" & & _sources = = "" )
2016-03-18 01:17:39 -07:00
{
2016-03-24 17:38:08 -07:00
_systems = "0" ;
2016-03-18 01:17:39 -07:00
}
string header_old = "clrmamepro (\n" +
2016-04-07 11:17:47 -07:00
"\tname \"" + HttpUtility . HtmlEncode ( intname ) + "\"\n" +
2016-03-18 01:17:39 -07:00
"\tdescription \"" + HttpUtility . HtmlEncode ( datname ) + "\"\n" +
"\tversion \"" + version + "\"\n" +
2016-03-24 17:38:08 -07:00
( _systems ! = "" & & _systems . Split ( ',' ) . Length = = 1 & & _headers . ContainsKey ( Int32 . Parse ( _systems ) ) ? " header \"" + _headers [ Int32 . Parse ( _systems ) ] + "\"\n" : "" ) +
2016-03-18 01:17:39 -07:00
"\tcomment \"\"\n" +
"\tauthor \"The Wizard of DATz\"\n" +
")\n" ;
string header = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE datafile PUBLIC \"-//Logiqx//DTD ROM Management Datafile//EN\" \"http://www.logiqx.com/Dats/datafile.dtd\">\n\n" +
"\t<datafile>\n" +
"\t\t<header>\n" +
2016-04-07 11:17:47 -07:00
"\t\t\t<name>" + HttpUtility . HtmlEncode ( intname ) + "</name>\n" +
2016-03-18 01:17:39 -07:00
"\t\t\t<description>" + HttpUtility . HtmlEncode ( datname ) + "</description>\n" +
"\t\t\t<category>The Wizard of DATz</category>\n" +
"\t\t\t<version>" + version + "</version>\n" +
"\t\t\t<date>" + version + "</date>\n" +
"\t\t\t<author>The Wizard of DATz</author>\n" +
2016-03-24 17:38:08 -07:00
"\t\t\t<clrmamepro" +
( _systems ! = "" & & _systems . Split ( ',' ) . Length = = 1 & & _headers . ContainsKey ( Int32 . Parse ( _systems ) ) ? " header=\"" + _headers [ Int32 . Parse ( _systems ) ] + "\"\n" : "" ) + "/>\n" +
2016-03-18 01:17:39 -07:00
"\t\t</header>\n" ;
// Unset _system again if we're in MEGAMERGED mode
2016-03-24 17:38:08 -07:00
if ( _systems = = "0" & & _sources = = "" )
2016-03-18 01:17:39 -07:00
{
2016-03-24 17:38:08 -07:00
_systems = "" ;
2016-03-18 01:17:39 -07:00
}
// Write the header out
sw . Write ( ( _old ? header_old : header ) ) ;
// Write out each of the machines and roms
string lastgame = "" ;
foreach ( RomData rom in roms )
{
string state = "" ;
if ( lastgame ! = "" & & lastgame ! = rom . Game )
{
2016-03-25 09:23:32 -07:00
state + = ( _old ? ")\n" : "\t</machine>\n" ) ;
2016-03-18 01:17:39 -07:00
}
if ( lastgame ! = rom . Game )
{
2016-03-25 11:01:23 -07:00
state + = ( _old ? "game (\n\tname \"" + rom . Game + "\"\n" +
"\tdescription \"" + rom . Game + "\"\n" :
2016-03-18 01:17:39 -07:00
"\t<machine name=\"" + HttpUtility . HtmlEncode ( rom . Game ) + "\">\n" +
"\t\t<description>" + HttpUtility . HtmlEncode ( rom . Game ) + "</description>\n" ) ;
}
if ( _old )
{
state + = "\t" + rom . Type + " ( name \"" + rom . Name + "\"" +
( rom . Size ! = 0 ? " size " + rom . Size : "" ) +
2016-03-22 13:37:31 -07:00
( rom . CRC ! = "" ? " crc " + rom . CRC . ToLowerInvariant ( ) : "" ) +
( rom . MD5 ! = "" ? " md5 " + rom . MD5 . ToLowerInvariant ( ) : "" ) +
( rom . SHA1 ! = "" ? " sha1 " + rom . SHA1 . ToLowerInvariant ( ) : "" ) +
2016-03-18 01:17:39 -07:00
" )\n" ;
}
else
{
state + = "\t\t<" + rom . Type + " name=\"" + HttpUtility . HtmlEncode ( rom . Name ) + "\"" +
( rom . Size ! = - 1 ? " size=\"" + rom . Size + "\"" : "" ) +
2016-03-22 13:37:31 -07:00
( rom . CRC ! = "" ? " crc=\"" + rom . CRC . ToLowerInvariant ( ) + "\"" : "" ) +
( rom . MD5 ! = "" ? " md5=\"" + rom . MD5 . ToLowerInvariant ( ) + "\"" : "" ) +
( rom . SHA1 ! = "" ? " sha1=\"" + rom . SHA1 . ToLowerInvariant ( ) + "\"" : "" ) +
2016-03-18 01:17:39 -07:00
" />\n" ;
}
2016-03-22 15:39:26 -07:00
lastgame = rom . Game ;
2016-03-18 01:17:39 -07:00
sw . Write ( state ) ;
}
sw . Write ( ( _old ? ")" : "\t</machine>\n</datafile>" ) ) ;
2016-03-28 17:54:24 -07:00
_logger . Log ( "File written!" ) ;
2016-04-04 23:11:29 -07:00
sw . Close ( ) ;
2016-03-18 01:17:39 -07:00
fs . Close ( ) ;
}
catch ( Exception ex )
{
2016-03-30 13:36:52 -07:00
_logger . Error ( ex . ToString ( ) ) ;
2016-03-18 01:17:39 -07:00
return false ;
}
return true ;
}
2016-03-29 14:49:03 -07:00
/// <summary>
/// Preprocess the rom data that is to be included in the outputted DAT
/// </summary>
2016-03-31 12:26:01 -07:00
/// <remarks>To make this even more accurate, files with a more recent "LastUpdated" should be considered the parent if all else is the same.</remarks>
2016-03-29 14:49:03 -07:00
/// <returns>A List of RomData objects containing all information about the files</returns>
2016-04-04 23:11:29 -07:00
public List < RomData > ProcessRoms ( )
2016-03-18 01:17:39 -07:00
{
List < RomData > roms = new List < RomData > ( ) ;
2016-03-24 17:38:08 -07:00
// Check if we have listed sources or systems
2016-03-24 20:56:04 -07:00
bool sysmerged = ( _systems = = "" | | _systems . Split ( ',' ) . Length > 1 ) ;
bool srcmerged = ( _sources = = "" | | _sources . Split ( ',' ) . Length > 1 ) ;
2016-03-18 01:17:39 -07:00
bool merged = sysmerged | | srcmerged ;
string query = @ "
2016-03-22 00:12:36 -07:00
SELECT DISTINCT systems . manufacturer AS manufacturer , systems . system AS system , systems . id AS systemid ,
2016-03-18 01:17:39 -07:00
sources . name AS source , sources . url AS url , sources . id AS sourceid ,
games . name AS game , files . name AS name , files . type AS type , checksums . size AS size , checksums . crc AS crc ,
checksums . md5 AS md5 , checksums . sha1 AS sha1
FROM systems
JOIN games
ON systems . id = games . system
JOIN sources
ON games . source = sources . id
JOIN files
ON games . id = files . setid
JOIN checksums
ON files . id = checksums . file " +
2016-03-24 17:38:08 -07:00
( _systems ! = "" | | _sources ! = "" ? "\nWHERE" : "" ) +
( _sources ! = "" ? " sources.id in (" + _sources + ")" : "" ) +
( _systems ! = "" & & _sources ! = "" ? " AND" : "" ) +
( _systems ! = "" ? " systems.id in (" + _systems + ")" : "" ) +
2016-03-22 00:12:36 -07:00
"\nORDER BY " +
2016-03-28 14:28:51 -07:00
( merged ? "checksums.size, checksums.crc, systems.id, sources.id, checksums.md5, checksums.sha1"
2016-03-22 00:12:36 -07:00
: "systems.id, sources.id, games.name, files.name" ) ;
2016-03-19 15:44:35 -07:00
2016-03-18 01:17:39 -07:00
using ( SQLiteConnection dbc = new SQLiteConnection ( _connectionString ) )
{
dbc . Open ( ) ;
using ( SQLiteCommand slc = new SQLiteCommand ( query , dbc ) )
{
using ( SQLiteDataReader sldr = slc . ExecuteReader ( ) )
{
// If there are no games for this combination, return nothing
if ( ! sldr . HasRows )
{
2016-03-30 13:36:52 -07:00
_logger . Error ( "No games could be found with those inputs. Please check and try again." ) ;
2016-03-18 01:17:39 -07:00
return null ;
}
2016-03-22 00:12:36 -07:00
// Retrieve and process the roms for merging
2016-03-18 01:17:39 -07:00
while ( sldr . Read ( ) )
{
2016-03-21 22:06:33 -07:00
RomData temp = new RomData
{
Manufacturer = sldr . GetString ( 0 ) ,
System = sldr . GetString ( 1 ) ,
SystemID = sldr . GetInt32 ( 2 ) ,
Source = sldr . GetString ( 3 ) ,
URL = sldr . GetString ( 4 ) ,
SourceID = sldr . GetInt32 ( 5 ) ,
Game = sldr . GetString ( 6 ) ,
Name = sldr . GetString ( 7 ) ,
Type = sldr . GetString ( 8 ) ,
Size = sldr . GetInt32 ( 9 ) ,
CRC = sldr . GetString ( 10 ) ,
MD5 = sldr . GetString ( 11 ) ,
SHA1 = sldr . GetString ( 12 ) ,
} ;
2016-03-18 01:17:39 -07:00
if ( merged )
{
2016-03-28 14:28:51 -07:00
// If it's the first rom in the list, don't touch it
if ( roms . Count ! = 0 )
2016-03-22 00:12:36 -07:00
{
2016-03-28 14:28:51 -07:00
// Check if the rom is a duplicate
RomData last = roms [ roms . Count - 1 ] ;
bool shouldcont = false ;
if ( temp . Type = = "rom" & & last . Type = = "rom" )
{
shouldcont = ( ( temp . Size ! = - 1 & & temp . Size = = last . Size ) & & (
( temp . CRC ! = "" & & last . CRC ! = "" & & temp . CRC = = last . CRC ) | |
( temp . MD5 ! = "" & & last . MD5 ! = "" & & temp . MD5 = = last . MD5 ) | |
( temp . SHA1 ! = "" & & last . SHA1 ! = "" & & temp . SHA1 = = last . SHA1 )
)
) ;
}
else if ( temp . Type = = "disk" & & last . Type = = "disk" )
{
shouldcont = ( ( temp . MD5 ! = "" & & last . MD5 ! = "" & & temp . MD5 = = last . MD5 ) | |
( temp . SHA1 ! = "" & & last . SHA1 ! = "" & & temp . SHA1 = = last . SHA1 )
) ;
}
// If it's a duplicate, skip adding it to the output but add any missing information
if ( shouldcont )
{
last . CRC = ( last . CRC = = "" & & temp . CRC ! = "" ? temp . CRC : last . CRC ) ;
last . MD5 = ( last . MD5 = = "" & & temp . MD5 ! = "" ? temp . MD5 : last . MD5 ) ;
last . SHA1 = ( last . SHA1 = = "" & & temp . SHA1 ! = "" ? temp . SHA1 : last . SHA1 ) ;
roms . RemoveAt ( roms . Count - 1 ) ;
roms . Insert ( roms . Count , last ) ;
continue ;
}
2016-03-22 00:12:36 -07:00
}
2016-03-24 13:23:13 -07:00
// Rename the game associated if it's still valid and we allow renames
if ( ! _norename )
{
temp . Game = temp . Game +
( sysmerged ? " [" + temp . Manufacturer + " - " + temp . System + "]" : "" ) +
( srcmerged ? " [" + temp . Source + "]" : "" ) ;
}
2016-03-18 01:17:39 -07:00
}
roms . Add ( temp ) ;
}
}
}
}
2016-03-22 00:12:36 -07:00
// If we're in a merged mode, resort by the correct parameters
2016-03-28 14:28:51 -07:00
if ( merged )
2016-03-22 00:12:36 -07:00
{
2016-03-28 14:28:51 -07:00
roms . Sort ( delegate ( RomData x , RomData y )
2016-03-22 00:12:36 -07:00
{
2016-03-28 14:28:51 -07:00
if ( x . SystemID = = y . SystemID )
2016-03-22 00:12:36 -07:00
{
2016-03-28 14:28:51 -07:00
if ( x . SourceID = = y . SourceID )
2016-03-22 00:12:36 -07:00
{
2016-03-28 14:28:51 -07:00
if ( x . Game = = y . Game )
{
return String . Compare ( x . Name , y . Name ) ;
}
return String . Compare ( x . Game , y . Game ) ;
2016-03-22 00:12:36 -07:00
}
2016-03-28 14:28:51 -07:00
return ( _norename ? String . Compare ( x . Game , y . Game ) : x . SourceID - y . SourceID ) ;
2016-03-22 00:12:36 -07:00
}
2016-03-28 14:28:51 -07:00
return ( _norename ? String . Compare ( x . Game , y . Game ) : x . SystemID - y . SystemID ) ;
} ) ;
}
2016-03-22 00:12:36 -07:00
// Now check rename within games
string lastname = "" , lastgame = "" ;
for ( int i = 0 ; i < roms . Count ; i + + )
{
RomData rom = roms [ i ] ;
// Now relable any roms that have the same name inside of the same game
bool samename = false , samegame = false ;
if ( rom . Name ! = "" )
{
samename = ( lastname = = rom . Name ) ;
}
if ( rom . Game ! = "" )
{
samegame = ( lastgame = = rom . Game ) ;
}
lastname = rom . Name ;
lastgame = rom . Game ;
// If the name and set are the same, rename it with whatever is different
if ( samename & & samegame )
{
rom . Name = Regex . Replace ( rom . Name , @"^(.*)(\..*)" , "$1(" +
( rom . CRC ! = "" ? rom . CRC :
( rom . MD5 ! = "" ? rom . MD5 :
( rom . SHA1 ! = "" ? rom . SHA1 : "Alt" ) ) ) +
")$2" ) ;
}
// Assign back just in case
roms [ i ] = rom ;
}
2016-03-18 01:17:39 -07:00
return roms ;
}
}
2016-03-29 14:49:03 -07:00
/// <summary>
/// Intermediate struct for holding and processing rom data
/// </summary>
2016-03-18 01:17:39 -07:00
public struct RomData
{
public string Manufacturer ;
public string System ;
public int SystemID ;
public string Source ;
public string URL ;
public int SourceID ;
public string Game ;
public string Name ;
public string Type ;
public int Size ;
public string CRC ;
public string MD5 ;
public string SHA1 ;
}
}