mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1318 lines
31 KiB
C++
1318 lines
31 KiB
C++
////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Nestopia - NES/Famicom emulator written in C++
|
|
//
|
|
// Copyright (C) 2003-2008 Martin Freij
|
|
//
|
|
// This file is part of Nestopia.
|
|
//
|
|
// Nestopia 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.
|
|
//
|
|
// Nestopia 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 Nestopia; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include "NstWindowUser.hpp"
|
|
#include "NstIoFile.hpp"
|
|
#include "NstIoArchive.hpp"
|
|
#include "NstIoStream.hpp"
|
|
#include "NstSystemThread.hpp"
|
|
#include "NstIoLog.hpp"
|
|
#include "NstManagerPaths.hpp"
|
|
#include "NstDialogLauncher.hpp"
|
|
#include "../core/NstCrc32.hpp"
|
|
#include "../core/NstChecksum.hpp"
|
|
#include "../core/NstXml.hpp"
|
|
|
|
|
|
namespace Nestopia
|
|
{
|
|
namespace Window
|
|
{
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
Launcher::List::Files::Entry::Entry(uint t)
|
|
:
|
|
file (0),
|
|
path (0),
|
|
name (0),
|
|
pRom (0),
|
|
cRom (0),
|
|
wRam (0),
|
|
vRam (0),
|
|
mapper (0),
|
|
type (t),
|
|
attributes (0)
|
|
{}
|
|
|
|
class Launcher::List::Files::Inserter
|
|
{
|
|
public:
|
|
|
|
typedef Paths::Settings Settings;
|
|
typedef Settings::Include Include;
|
|
|
|
Inserter
|
|
(
|
|
Strings&,
|
|
Entries&,
|
|
const Include,
|
|
const Nes::Cartridge::Database&
|
|
);
|
|
|
|
bool Add(const GenericString);
|
|
|
|
protected:
|
|
|
|
void ReadFile(wcstring const,const Dialog&);
|
|
|
|
enum Stop
|
|
{
|
|
STOP_SEARCH
|
|
};
|
|
|
|
enum
|
|
{
|
|
PATH_NOT_ADDED
|
|
};
|
|
|
|
const Include include;
|
|
uint compressed;
|
|
|
|
struct
|
|
{
|
|
Path string;
|
|
bool incSubDir;
|
|
uint reference;
|
|
} path;
|
|
|
|
Strings strings;
|
|
Entries entries;
|
|
Strings& saveStrings;
|
|
Entries& saveEntries;
|
|
|
|
private:
|
|
|
|
enum Type
|
|
{
|
|
TYPE_INVALID,
|
|
TYPE_PROCESSED
|
|
};
|
|
|
|
typedef std::set<uint> FileChecksums;
|
|
typedef Collection::Buffer Buffer;
|
|
typedef Type (Inserter::*Parser)();
|
|
|
|
Parser GetParser() const;
|
|
|
|
inline uint Crc(const uint,const uint) const;
|
|
|
|
bool UniqueFile();
|
|
bool PrepareFile(const uint=1,const uint=0);
|
|
Type ParseAny();
|
|
Type ParseXml();
|
|
Type ParseNes();
|
|
Type ParseUnf();
|
|
Type ParseFds();
|
|
Type ParseNsf();
|
|
Type ParsePatch();
|
|
Type ParseArchive();
|
|
void AddEntry(uint,const Nes::Cartridge::Profile&);
|
|
void AddEntry(const Entry&);
|
|
|
|
Buffer buffer;
|
|
FileChecksums parsedFiles;
|
|
const Nes::Cartridge::Database& imageDatabase;
|
|
};
|
|
|
|
Launcher::List::Files::Inserter::Inserter
|
|
(
|
|
Strings& v,
|
|
Entries& e,
|
|
const Include i,
|
|
const Nes::Cartridge::Database& r
|
|
)
|
|
:
|
|
include ( i ),
|
|
saveStrings ( v ),
|
|
saveEntries ( e ),
|
|
imageDatabase ( r )
|
|
{}
|
|
|
|
Launcher::List::Files::Inserter::Parser Launcher::List::Files::Inserter::GetParser() const
|
|
{
|
|
if (const uint extension = path.string.Extension().Id())
|
|
{
|
|
if (include[Include::ANY])
|
|
{
|
|
if (extension == FourCC<'x','m','l'>::V)
|
|
{
|
|
return include[Include::XML] ? &Inserter::ParseXml : NULL;
|
|
}
|
|
else
|
|
{
|
|
return &Inserter::ParseAny;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (extension)
|
|
{
|
|
case FourCC<'n','e','s'>::V: return include[Include::NES] ? &Inserter::ParseNes : NULL;
|
|
case FourCC<'u','n','f'>::V:
|
|
case FourCC<'u','n','i','f'>::V: return include[Include::UNF] ? &Inserter::ParseUnf : NULL;
|
|
case FourCC<'x','m','l'>::V: return include[Include::XML] ? &Inserter::ParseXml : NULL;
|
|
case FourCC<'f','d','s'>::V: return include[Include::FDS] ? &Inserter::ParseFds : NULL;
|
|
case FourCC<'n','s','f'>::V: return include[Include::NSF] ? &Inserter::ParseNsf : NULL;
|
|
case FourCC<'i','p','s'>::V:
|
|
case FourCC<'u','p','s'>::V: return include[Include::PATCH] ? &Inserter::ParsePatch : NULL;
|
|
case FourCC<'z','i','p'>::V:
|
|
case FourCC<'r','a','r'>::V:
|
|
case FourCC<'7','z'>::V: return include[Include::ARCHIVE] ? &Inserter::ParseArchive : NULL;
|
|
}
|
|
}
|
|
}
|
|
else if (include[Include::ANY])
|
|
{
|
|
return &Inserter::ParseAny;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void Launcher::List::Files::Inserter::AddEntry(const Entry& entry)
|
|
{
|
|
entries.PushBack( entry );
|
|
|
|
Entry& back = entries.Back();
|
|
|
|
back.file = (strings << path.string.File());
|
|
|
|
if (path.reference == PATH_NOT_ADDED)
|
|
path.reference = (strings << path.string.Directory());
|
|
|
|
NST_VERIFY( path.string.Directory() == strings[path.reference] );
|
|
|
|
back.path = path.reference;
|
|
|
|
if (entries.Size() == MAX_ENTRIES)
|
|
throw STOP_SEARCH;
|
|
}
|
|
|
|
bool Launcher::List::Files::Inserter::Add(const GenericString fileName)
|
|
{
|
|
NST_ASSERT( fileName.Length() && fileName.Length() <= MAX_PATH );
|
|
|
|
if (entries.Size() == MAX_ENTRIES)
|
|
return false;
|
|
|
|
compressed = 0;
|
|
path.string = fileName;
|
|
strings = saveStrings;
|
|
|
|
{
|
|
const int index = strings.Find( path.string.Directory() );
|
|
|
|
if (index != Strings::NONE)
|
|
path.reference = index;
|
|
else
|
|
path.reference = PATH_NOT_ADDED;
|
|
}
|
|
|
|
if (Parser const parser = GetParser())
|
|
{
|
|
try
|
|
{
|
|
(*this.*parser)();
|
|
}
|
|
catch (Io::File::Exception)
|
|
{
|
|
return false;
|
|
}
|
|
catch (Stop)
|
|
{
|
|
// reached file count limit
|
|
}
|
|
|
|
if (entries.Size())
|
|
{
|
|
saveEntries.PushBack( entries );
|
|
saveStrings = strings;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Launcher::List::Files::Inserter::ReadFile(wcstring const fileName,const Dialog& dialog)
|
|
{
|
|
path.string.File() = fileName;
|
|
|
|
if (Parser const parser = GetParser())
|
|
{
|
|
if (dialog)
|
|
dialog.Control( IDC_LAUNCHER_FILESEARCH_FILE ).Text() << path.string.Ptr();
|
|
|
|
compressed = 0;
|
|
buffer.Clear();
|
|
|
|
try
|
|
{
|
|
(*this.*parser)();
|
|
}
|
|
catch (Io::File::Exception)
|
|
{
|
|
// file I/O failure, just skip it
|
|
}
|
|
}
|
|
|
|
path.string.File().Clear();
|
|
}
|
|
|
|
inline uint Launcher::List::Files::Inserter::Crc(const uint start,const uint length) const
|
|
{
|
|
NST_ASSERT( buffer.Size() );
|
|
return Nes::Core::Crc32::Compute( reinterpret_cast<const uchar*>(&buffer[start]), length );
|
|
}
|
|
|
|
bool Launcher::List::Files::Inserter::UniqueFile()
|
|
{
|
|
NST_ASSERT( buffer.Size() );
|
|
return !include[Include::UNIQUE] || parsedFiles.insert(Crc(0,buffer.Size())).second;
|
|
}
|
|
|
|
bool Launcher::List::Files::Inserter::PrepareFile(const uint minSize,const uint fileId)
|
|
{
|
|
NST_ASSERT( path.string.Length() && minSize && minSize >= bool(fileId) * 4U );
|
|
|
|
if (buffer.Empty())
|
|
{
|
|
const Io::File file( path.string, Io::File::COLLECT );
|
|
const uint size = file.Size();
|
|
|
|
if (size >= minSize && (!fileId || fileId == file.Peek32()))
|
|
{
|
|
buffer.Resize( size );
|
|
file.Read( buffer.Ptr(), size );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
return
|
|
(
|
|
buffer.Size() >= minSize &&
|
|
(!fileId || fileId == FourCC<>::T( buffer.Ptr() ))
|
|
);
|
|
}
|
|
|
|
void Launcher::List::Files::Inserter::AddEntry(const uint flags,const Nes::Cartridge::Profile& profile)
|
|
{
|
|
Entry entry( flags );
|
|
|
|
if (!profile.game.title.empty())
|
|
entry.name = strings << profile.game.title.c_str();
|
|
|
|
entry.pRom = profile.board.GetPrg() / Nes::Core::SIZE_1K;
|
|
entry.cRom = profile.board.GetChr() / Nes::Core::SIZE_1K;
|
|
entry.wRam = profile.board.GetWram() / Nes::Core::SIZE_1K;
|
|
entry.vRam = profile.board.GetVram() / Nes::Core::SIZE_1K;
|
|
|
|
if (profile.board.mapper != Nes::Cartridge::Profile::Board::NO_MAPPER)
|
|
entry.mapper = profile.board.mapper;
|
|
|
|
if (profile.board.HasBattery())
|
|
entry.attributes |= Entry::ATR_BATTERY;
|
|
|
|
if (profile.multiRegion)
|
|
{
|
|
entry.attributes |= Entry::ATR_NTSC_PAL;
|
|
}
|
|
else switch (profile.system.type)
|
|
{
|
|
case Nes::Cartridge::Profile::System::NES_PAL:
|
|
case Nes::Cartridge::Profile::System::NES_PAL_A:
|
|
case Nes::Cartridge::Profile::System::NES_PAL_B:
|
|
case Nes::Cartridge::Profile::System::DENDY:
|
|
|
|
entry.attributes |= Entry::ATR_PAL;
|
|
break;
|
|
|
|
default:
|
|
|
|
entry.attributes |= Entry::ATR_NTSC;
|
|
break;
|
|
}
|
|
|
|
entry.hash = profile.hash;
|
|
|
|
AddEntry( entry );
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseNes()
|
|
{
|
|
if (PrepareFile( 16, Managers::Paths::File::ID_INES ))
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
Io::Stream::In stream( buffer );
|
|
Nes::Cartridge::Profile profile;
|
|
Nes::Cartridge::ReadInes( stream, Nes::Machine::FAVORED_NES_NTSC, profile );
|
|
|
|
if (!imageDatabase.FindEntry( profile.hash, Nes::Machine::FAVORED_NES_NTSC ))
|
|
{
|
|
uint length = buffer.Size();
|
|
|
|
if (length >= 16+Nes::Core::SIZE_8K)
|
|
{
|
|
length -= 16;
|
|
|
|
if (length > Nes::Cartridge::NesHeader::MAX_PRG_ROM + Nes::Cartridge::NesHeader::MAX_CHR_ROM)
|
|
length = Nes::Cartridge::NesHeader::MAX_PRG_ROM + Nes::Cartridge::NesHeader::MAX_CHR_ROM;
|
|
else
|
|
length -= length % Nes::Core::SIZE_8K;
|
|
|
|
if (length != profile.board.GetPrg() + profile.board.GetChr())
|
|
{
|
|
Nes::Cartridge::Profile::Hash hash;
|
|
hash.Compute( buffer.At(16), length );
|
|
|
|
if (imageDatabase.FindEntry( hash, Nes::Machine::FAVORED_NES_NTSC ))
|
|
profile.hash = hash;
|
|
}
|
|
}
|
|
}
|
|
|
|
AddEntry( Entry::NES | compressed, profile );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseUnf()
|
|
{
|
|
if (PrepareFile( 32, Managers::Paths::File::ID_UNIF ))
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
Io::Stream::In stream( buffer );
|
|
Nes::Cartridge::Profile profile;
|
|
Nes::Cartridge::ReadUnif( stream, Nes::Machine::FAVORED_NES_NTSC, profile );
|
|
|
|
AddEntry( Entry::UNF | compressed, profile );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseXml()
|
|
{
|
|
if (PrepareFile())
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
Io::Stream::In stream( buffer );
|
|
Nes::Cartridge::Profile profile;
|
|
|
|
if (NES_SUCCEEDED(Nes::Cartridge::ReadRomset( stream, Nes::Machine::FAVORED_NES_NTSC, Nes::Machine::DONT_ASK_PROFILE, profile )))
|
|
AddEntry( Entry::XML | compressed, profile );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseFds()
|
|
{
|
|
const bool hasHeader = PrepareFile( 16, Managers::Paths::File::ID_FDS );
|
|
|
|
if (hasHeader || PrepareFile( 4, Managers::Paths::File::ID_FDS_RAW ))
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
Entry entry( Entry::FDS | compressed );
|
|
|
|
const uint size = buffer.Size() - (hasHeader ? 16 : 0);
|
|
entry.pRom = (size / Nes::Core::SIZE_1K) + (size % Nes::Core::SIZE_1K != 0);
|
|
entry.wRam = 32;
|
|
entry.vRam = 8;
|
|
entry.attributes |= Entry::ATR_DEFAULT_FDS;
|
|
|
|
AddEntry( entry );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseNsf()
|
|
{
|
|
if (PrepareFile( 128, Managers::Paths::File::ID_NSF ))
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
enum
|
|
{
|
|
NSF_CHIP_FDS = 0x4,
|
|
NSF_CHIP_MMC5 = 0x8
|
|
};
|
|
|
|
#pragma pack(push,1)
|
|
|
|
struct Header
|
|
{
|
|
uchar pad1[14];
|
|
char name[32];
|
|
uchar pad2[32];
|
|
char maker[32];
|
|
uchar pad3[12];
|
|
uchar mode;
|
|
uchar chip;
|
|
uchar pad4[4];
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
NST_COMPILE_ASSERT( sizeof(Header) == 128 );
|
|
|
|
Header& header = reinterpret_cast<Header&>( buffer.Front() );
|
|
|
|
Entry entry( Entry::NSF | compressed );
|
|
|
|
const uint size = buffer.Size() - sizeof(Header);
|
|
entry.pRom = (size / Nes::Core::SIZE_1K) + (size % Nes::Core::SIZE_1K != 0);
|
|
|
|
switch (header.mode & 0x3U)
|
|
{
|
|
case 0x0: entry.attributes |= Entry::ATR_NTSC; break;
|
|
case 0x1: entry.attributes |= Entry::ATR_PAL; break;
|
|
default: entry.attributes |= Entry::ATR_NTSC_PAL; break;
|
|
}
|
|
|
|
switch (header.chip)
|
|
{
|
|
case NSF_CHIP_MMC5: entry.wRam = 8+1; break;
|
|
case NSF_CHIP_FDS: entry.wRam = 8+32; break;
|
|
default: entry.wRam = 8; break;
|
|
}
|
|
|
|
header.pad2[0] = '\0'; // in case string is not terminated
|
|
entry.name = strings.Import( header.name );
|
|
|
|
AddEntry( entry );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParsePatch()
|
|
{
|
|
if (PrepareFile( 5, Managers::Paths::File::ID_IPS ) || PrepareFile( 4, Managers::Paths::File::ID_UPS ))
|
|
{
|
|
if (UniqueFile())
|
|
{
|
|
Entry entry( Entry::PATCH | compressed );
|
|
AddEntry( entry );
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
}
|
|
|
|
return TYPE_INVALID;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseArchive()
|
|
{
|
|
compressed = Entry::ARCHIVE;
|
|
|
|
Io::File file;
|
|
|
|
try
|
|
{
|
|
file.Open( path.string, Io::File::READ|Io::File::EXISTING );
|
|
}
|
|
catch (Io::File::Exception)
|
|
{
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
const Io::Archive archive( file );
|
|
const uint length = path.string.Length();
|
|
|
|
for (uint i=0; i < archive.NumFiles(); ++i)
|
|
{
|
|
NST_VERIFY( archive[i].GetName().Length() );
|
|
|
|
// will look like: "archive.zip <image.nes>"
|
|
path.string << " <" << archive[i].GetName();
|
|
|
|
Parser const parser = GetParser();
|
|
|
|
if (parser && parser != &Inserter::ParseArchive)
|
|
{
|
|
path.string << '>';
|
|
buffer.Resize( archive[i].Size() );
|
|
|
|
if (archive[i].Uncompress( buffer.Ptr() ))
|
|
(*this.*parser)();
|
|
}
|
|
|
|
path.string.ShrinkTo( length );
|
|
}
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
Launcher::List::Files::Inserter::Type Launcher::List::Files::Inserter::ParseAny()
|
|
{
|
|
const bool notCompressed = buffer.Empty();
|
|
|
|
if
|
|
(
|
|
(!include[Include::NES] || ParseNes() == TYPE_INVALID) &&
|
|
(!include[Include::UNF] || ParseUnf() == TYPE_INVALID) &&
|
|
(!include[Include::FDS] || ParseFds() == TYPE_INVALID) &&
|
|
(!include[Include::NSF] || ParseNsf() == TYPE_INVALID) &&
|
|
(!include[Include::PATCH] || ParsePatch() == TYPE_INVALID) &&
|
|
( include[Include::ARCHIVE] && notCompressed)
|
|
)
|
|
ParseArchive();
|
|
|
|
return TYPE_PROCESSED;
|
|
}
|
|
|
|
class Launcher::List::Files::Searcher : Inserter
|
|
{
|
|
public:
|
|
|
|
Searcher
|
|
(
|
|
Strings&,
|
|
Entries&,
|
|
const Settings&,
|
|
const Nes::Cartridge::Database&
|
|
);
|
|
|
|
void Search();
|
|
|
|
private:
|
|
|
|
enum Abort
|
|
{
|
|
ABORT_SEARCH
|
|
};
|
|
|
|
typedef std::set<HeapString> SearchedPaths;
|
|
|
|
ibool OnInitDialog (Param&);
|
|
|
|
bool UniquePath();
|
|
void Start(System::Thread::Terminator);
|
|
void Search(System::Thread::Terminator);
|
|
void ReadPath(wcstring const,System::Thread::Terminator);
|
|
|
|
const Settings::Folders& folders;
|
|
SearchedPaths searchedPaths;
|
|
Dialog dialog;
|
|
System::Thread thread;
|
|
};
|
|
|
|
Launcher::List::Files::Searcher::Searcher
|
|
(
|
|
Strings& v,
|
|
Entries& e,
|
|
const Settings& s,
|
|
const Nes::Cartridge::Database& r
|
|
)
|
|
:
|
|
Inserter (v,e,s.include,r),
|
|
folders (s.folders),
|
|
dialog (IDD_LAUNCHER_SEARCH,WM_INITDIALOG,this,&Searcher::OnInitDialog)
|
|
{}
|
|
|
|
void Launcher::List::Files::Searcher::Start(System::Thread::Terminator terminator)
|
|
{
|
|
try
|
|
{
|
|
for (Settings::Folders::const_iterator it(folders.begin()), end(folders.end()); it != end; ++it)
|
|
{
|
|
if (it->path.Length())
|
|
{
|
|
path.string = it->path;
|
|
|
|
if (UniquePath())
|
|
{
|
|
path.incSubDir = it->incSubDir;
|
|
path.reference = PATH_NOT_ADDED;
|
|
Search( terminator );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (Stop)
|
|
{
|
|
}
|
|
catch (...)
|
|
{
|
|
static_cast<Generic&>(dialog).Close();
|
|
return;
|
|
}
|
|
|
|
static_cast<Generic&>(dialog).Close();
|
|
|
|
saveStrings = strings;
|
|
saveEntries = entries;
|
|
}
|
|
|
|
ibool Launcher::List::Files::Searcher::OnInitDialog(Param&)
|
|
{
|
|
thread.Start( System::Thread::Callback(this,&Searcher::Start) );
|
|
return true;
|
|
}
|
|
|
|
void Launcher::List::Files::Searcher::Search()
|
|
{
|
|
if (folders.size() && (include.Word() & Include::FILES))
|
|
{
|
|
bool enabled = !Application::Instance::GetMainWindow().Enable( false );
|
|
dialog.Open();
|
|
Application::Instance::GetMainWindow().Enable( enabled );
|
|
}
|
|
}
|
|
|
|
void Launcher::List::Files::Searcher::Search(System::Thread::Terminator terminate)
|
|
{
|
|
NST_ASSERT( path.string.Length() );
|
|
|
|
struct FileFinder
|
|
{
|
|
WIN32_FIND_DATA data;
|
|
HANDLE const handle;
|
|
|
|
FileFinder(wcstring path)
|
|
: handle(::FindFirstFile( path, &data )) {}
|
|
|
|
~FileFinder()
|
|
{
|
|
if (handle != INVALID_HANDLE_VALUE)
|
|
::FindClose( handle );
|
|
}
|
|
};
|
|
|
|
path.string.File() = "*.*";
|
|
|
|
FileFinder findFile( path.string.Ptr() );
|
|
|
|
path.string.File().Clear();
|
|
|
|
if (findFile.handle != INVALID_HANDLE_VALUE)
|
|
{
|
|
do
|
|
{
|
|
if (terminate)
|
|
throw ABORT_SEARCH;
|
|
|
|
if (!(findFile.data.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM)))
|
|
{
|
|
if (findFile.data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
ReadPath( findFile.data.cFileName, terminate );
|
|
else
|
|
ReadFile( findFile.data.cFileName, dialog );
|
|
}
|
|
}
|
|
while (::FindNextFile( findFile.handle, &findFile.data ));
|
|
}
|
|
}
|
|
|
|
bool Launcher::List::Files::Searcher::UniquePath()
|
|
{
|
|
NST_ASSERT( path.string.Length() );
|
|
|
|
SearchedPaths::iterator it( searchedPaths.lower_bound( path.string ) );
|
|
|
|
if (it == searchedPaths.end() || *it != path.string)
|
|
{
|
|
searchedPaths.insert( it, path.string );
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Launcher::List::Files::Searcher::ReadPath(wcstring const subDir,System::Thread::Terminator terminator)
|
|
{
|
|
NST_ASSERT( subDir );
|
|
|
|
if (path.incSubDir && *subDir && *subDir != '.')
|
|
{
|
|
path.string.Directory() += subDir;
|
|
|
|
if (UniquePath())
|
|
{
|
|
const uint reference = path.reference;
|
|
path.reference = PATH_NOT_ADDED;
|
|
Search( terminator );
|
|
path.reference = reference;
|
|
}
|
|
|
|
path.string.Directory() -= 1;
|
|
}
|
|
}
|
|
|
|
Launcher::List::Files::Files()
|
|
:
|
|
dirty (false),
|
|
//loaded (!Application::Instance::GetExePath(L"launcher.xml").FileExists())
|
|
loaded (!Application::Instance::GetConfigPath(L"launcher.xml").FileExists())//bg
|
|
{
|
|
if (loaded)
|
|
Io::Log() << "Launcher: database file \"launcher.xml\" not present\r\n";
|
|
}
|
|
|
|
void Launcher::List::Files::Load()
|
|
{
|
|
if (loaded)
|
|
return;
|
|
|
|
loaded = true;
|
|
|
|
try
|
|
{
|
|
typedef Nes::Core::Xml Xml;
|
|
Xml xml;
|
|
|
|
{
|
|
//Io::Stream::In stream( Application::Instance::GetExePath(L"launcher.xml") );
|
|
Io::Stream::In stream( Application::Instance::GetConfigPath(L"launcher.xml") );//bg
|
|
xml.Read( stream );
|
|
}
|
|
|
|
if (!xml.GetRoot().IsType( L"launcher" ) || !xml.GetRoot().GetAttribute( L"version" ).IsValue(L"1.1"))
|
|
throw 1;
|
|
|
|
for (Xml::Node node(xml.GetRoot().GetFirstChild()); node; node=node.GetNextSibling())
|
|
{
|
|
Entry entry;
|
|
|
|
if (node.IsType( L"romset" ))
|
|
{
|
|
entry.type = Entry::XML;
|
|
}
|
|
else if (node.IsType( L"ines" ))
|
|
{
|
|
entry.type = Entry::NES;
|
|
}
|
|
else if (node.IsType( L"unif" ))
|
|
{
|
|
entry.type = Entry::UNF;
|
|
}
|
|
else if (node.IsType( L"fds" ))
|
|
{
|
|
entry.type = Entry::FDS;
|
|
}
|
|
else if (node.IsType( L"nsf" ))
|
|
{
|
|
entry.type = Entry::NSF;
|
|
}
|
|
else if (node.IsType( L"patch" ))
|
|
{
|
|
entry.type = Entry::PATCH;
|
|
}
|
|
else
|
|
{
|
|
throw 1;
|
|
}
|
|
|
|
wcstring string;
|
|
|
|
if (!*(string=node.GetChild( L"file" ).GetValue()))
|
|
throw 1;
|
|
|
|
entry.file = strings << string;
|
|
|
|
if (!*(string=node.GetChild( L"dir" ).GetValue()))
|
|
throw 1;
|
|
|
|
entry.path = strings << string;
|
|
|
|
if (node.GetChild( L"archive" ).IsValue( L"yes" ))
|
|
entry.type |= Entry::ARCHIVE;
|
|
|
|
switch (entry.type & Entry::ALL)
|
|
{
|
|
case Entry::XML:
|
|
case Entry::UNF:
|
|
case Entry::NES:
|
|
{
|
|
if (*(string=node.GetChild( L"name" ).GetValue()))
|
|
entry.name = strings << string;
|
|
|
|
const Xml::Node system( node.GetChild( L"system" ) );
|
|
|
|
if (system.IsValue( L"vs" ))
|
|
{
|
|
entry.attributes = Entry::ATR_VS;
|
|
}
|
|
else if (system.IsValue( L"pc10" ))
|
|
{
|
|
entry.attributes = Entry::ATR_PC10;
|
|
}
|
|
else if (system.IsValue( L"pal" ))
|
|
{
|
|
entry.attributes = Entry::ATR_PAL;
|
|
}
|
|
else if (system.IsValue( L"ntsc/pal" ))
|
|
{
|
|
entry.attributes = Entry::ATR_NTSC_PAL;
|
|
}
|
|
else
|
|
{
|
|
entry.attributes = Entry::ATR_NTSC;
|
|
}
|
|
|
|
entry.pRom = node.GetChild( L"prg" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.cRom = node.GetChild( L"chr" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.wRam = node.GetChild( L"wram" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.vRam = node.GetChild( L"vram" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.mapper = node.GetChild( L"mapper" ).GetUnsignedValue() & 0xFFFF;
|
|
|
|
if (node.GetChild( L"battery" ).IsValue( L"yes" ))
|
|
entry.attributes |= Entry::ATR_BATTERY;
|
|
|
|
entry.hash.Assign( node.GetChild( L"sha1" ).GetValue(), node.GetChild( L"crc" ).GetValue() );
|
|
break;
|
|
}
|
|
|
|
case Entry::FDS:
|
|
|
|
entry.pRom = node.GetChild( L"prg" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.wRam = 32;
|
|
entry.attributes = Entry::ATR_DEFAULT_FDS;
|
|
break;
|
|
|
|
case Entry::NSF:
|
|
{
|
|
if (*string)
|
|
entry.name = strings << string;
|
|
|
|
entry.pRom = node.GetChild( L"prg" ).GetUnsignedValue() & 0xFFFF;
|
|
entry.wRam = node.GetChild( L"wram" ).GetUnsignedValue() & 0xFFFF;
|
|
|
|
const Xml::Node system( node.GetChild( L"system" ) );
|
|
|
|
if (system.IsValue( L"pal" ))
|
|
{
|
|
entry.attributes = Entry::ATR_PAL;
|
|
}
|
|
else if (system.IsValue( L"ntsc/pal" ))
|
|
{
|
|
entry.attributes = Entry::ATR_NTSC_PAL;
|
|
}
|
|
else
|
|
{
|
|
entry.attributes = Entry::ATR_NTSC;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
entries.PushBack( entry );
|
|
}
|
|
}
|
|
catch (...)
|
|
{
|
|
dirty = true;
|
|
|
|
Clear();
|
|
User::Warn( IDS_LAUNCHER_ERR_LOAD_DB );
|
|
}
|
|
|
|
Defrag();
|
|
}
|
|
|
|
void Launcher::List::Files::Save()
|
|
{
|
|
if (dirty)
|
|
{
|
|
//const Path fileName( Application::Instance::GetExePath(L"launcher.xml") );
|
|
const Path fileName( Application::Instance::GetConfigPath(L"launcher.xml") );//bg
|
|
|
|
if (entries.Size())
|
|
{
|
|
try
|
|
{
|
|
typedef Nes::Core::Xml Xml;
|
|
|
|
Xml xml;
|
|
Xml::Node root( xml.Create(L"launcher") );
|
|
root.AddAttribute( L"version", L"1.1" );
|
|
|
|
for (Entries::ConstIterator it(entries.Begin()), end(entries.End()); it != end; ++it)
|
|
{
|
|
wcstring type;
|
|
|
|
switch (it->type & Entry::ALL)
|
|
{
|
|
case Entry::NES: type = L"ines"; break;
|
|
case Entry::UNF: type = L"unif"; break;
|
|
case Entry::XML: type = L"romset"; break;
|
|
case Entry::FDS: type = L"fds"; break;
|
|
case Entry::NSF: type = L"nsf"; break;
|
|
case Entry::PATCH: type = L"patch"; break;
|
|
default: continue;
|
|
}
|
|
|
|
Xml::Node node( root.AddChild( type ) );
|
|
|
|
node.AddChild( L"file", strings[it->file] );
|
|
node.AddChild( L"dir", strings[it->path] );
|
|
|
|
if (it->type & Entry::ARCHIVE)
|
|
node.AddChild( L"archive", L"yes" );
|
|
|
|
wchar_t buffer[32];
|
|
|
|
switch (it->type & Entry::ALL)
|
|
{
|
|
case Entry::NSF:
|
|
{
|
|
NST_ASSERT( !(it->attributes & ~uint(Entry::ATR_NTSC_PAL)) );
|
|
|
|
if (it->name)
|
|
node.AddChild( L"name", strings[it->name] );
|
|
|
|
wcstring system = L"ntsc";
|
|
|
|
if (it->attributes & Entry::ATR_PAL)
|
|
{
|
|
if (it->attributes & Entry::ATR_NTSC)
|
|
system = L"ntsc/pal";
|
|
else
|
|
system = L"pal";
|
|
}
|
|
|
|
node.AddChild( L"system", system );
|
|
|
|
if (it->pRom)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->pRom) );
|
|
node.AddChild( L"prg", buffer );
|
|
}
|
|
|
|
if (it->wRam)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->wRam) );
|
|
node.AddChild( L"wram", buffer );
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case Entry::FDS:
|
|
|
|
if (it->pRom)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->pRom) );
|
|
node.AddChild( L"prg", buffer );
|
|
}
|
|
break;
|
|
|
|
case Entry::XML:
|
|
case Entry::UNF:
|
|
case Entry::NES:
|
|
{
|
|
if (it->name)
|
|
node.AddChild( L"name", strings[it->name] );
|
|
|
|
wcstring system = L"ntsc";
|
|
|
|
if (it->attributes & Entry::ATR_VS)
|
|
{
|
|
system = L"vs";
|
|
}
|
|
else if (it->attributes & Entry::ATR_PC10)
|
|
{
|
|
system = L"pc10";
|
|
}
|
|
else if (it->attributes & Entry::ATR_PAL)
|
|
{
|
|
if (it->attributes & Entry::ATR_NTSC)
|
|
system = L"ntsc/pal";
|
|
else
|
|
system = L"pal";
|
|
}
|
|
|
|
node.AddChild( L"system", system );
|
|
|
|
if (it->pRom)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->pRom) );
|
|
node.AddChild( L"prg", buffer );
|
|
}
|
|
|
|
if (it->cRom)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->cRom) );
|
|
node.AddChild( L"chr", buffer );
|
|
}
|
|
|
|
if (it->wRam)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->wRam) );
|
|
node.AddChild( L"wram", buffer );
|
|
}
|
|
|
|
if (it->vRam)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->vRam) );
|
|
node.AddChild( L"vram", buffer );
|
|
}
|
|
|
|
if (it->attributes & Entry::ATR_BATTERY)
|
|
node.AddChild( L"battery", L"yes" );
|
|
|
|
if (it->mapper)
|
|
{
|
|
std::swprintf( buffer, L"%u", uint(it->mapper) );
|
|
node.AddChild( L"mapper", buffer );
|
|
}
|
|
|
|
if (it->hash)
|
|
{
|
|
std::swprintf( buffer, L"%08X", uint(it->hash.GetCrc32()) );
|
|
node.AddChild( L"crc", buffer );
|
|
|
|
wchar_t sha1[Entry::Hash::SHA1_WORD_LENGTH*8+1];
|
|
sha1[Entry::Hash::SHA1_WORD_LENGTH*8] = '\0';
|
|
|
|
for (uint i=0; i < Entry::Hash::SHA1_WORD_LENGTH; ++i)
|
|
std::swprintf( sha1+i*8, L"%08X", uint(it->hash.GetSha1()[i]) );
|
|
|
|
node.AddChild( L"sha1", sha1 );
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Collection::Buffer buffer;
|
|
|
|
{
|
|
Io::Stream::Out stream( buffer );
|
|
xml.Write( root, stream );
|
|
}
|
|
|
|
xml.Destroy();
|
|
|
|
Io::File( fileName, Io::File::DUMP ).Write( buffer.Ptr(), buffer.Size() );
|
|
|
|
Io::Log() << "Launcher: database saved to \"launcher.xml\"\r\n";
|
|
}
|
|
catch (...)
|
|
{
|
|
User::Warn( IDS_LAUNCHER_ERR_SAVE_DB );
|
|
}
|
|
}
|
|
else if (fileName.FileExists())
|
|
{
|
|
if (Io::File::Delete( fileName.Ptr() ))
|
|
Io::Log() << "Launcher: empty database, deleted \"launcher.xml\"\r\n";
|
|
else
|
|
Io::Log() << "Launcher: warning, couldn't delete \"launcher.xml\"!\r\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Launcher::List::Files::Defrag()
|
|
{
|
|
typedef std::map<GenericString,uint> References;
|
|
|
|
References references;
|
|
|
|
if (entries.Size())
|
|
{
|
|
Entries tmp;
|
|
tmp.Reserve( entries.Size() );
|
|
|
|
for (Entries::ConstIterator it(entries.Begin()), end(entries.End()); it != end; ++it)
|
|
{
|
|
if (it->type)
|
|
{
|
|
if ( it->file ) references[strings[it->file]] = 0;
|
|
if ( it->path ) references[strings[it->path]] = 0;
|
|
if ( it->name ) references[strings[it->name]] = 0;
|
|
|
|
tmp.PushBack( *it );
|
|
}
|
|
}
|
|
|
|
if (entries.Size() != tmp.Size())
|
|
entries = tmp;
|
|
}
|
|
|
|
if (entries.Size())
|
|
{
|
|
Strings tmp( strings.Size() );
|
|
|
|
for (References::iterator it(references.begin()), end(references.end()); it != end; ++it)
|
|
it->second = (tmp << it->first);
|
|
|
|
for (Entries::Iterator it(entries.Begin()), end(entries.End()); it != end; ++it)
|
|
{
|
|
if ( it->file ) it->file = references.find( strings[ it->file ] )->second;
|
|
if ( it->path ) it->path = references.find( strings[ it->path ] )->second;
|
|
if ( it->name ) it->name = references.find( strings[ it->name ] )->second;
|
|
}
|
|
|
|
strings = tmp;
|
|
}
|
|
else
|
|
{
|
|
Clear();
|
|
}
|
|
}
|
|
|
|
bool Launcher::List::Files::Insert(const Nes::Cartridge::Database& imageDatabase,const GenericString fileName)
|
|
{
|
|
return dirty |=
|
|
(
|
|
fileName.Length() &&
|
|
Inserter( strings, entries, Paths::Settings::Include(true), imageDatabase ).Add( fileName )
|
|
);
|
|
}
|
|
|
|
bool Launcher::List::Files::ShouldDefrag() const
|
|
{
|
|
uint garbage = 0;
|
|
|
|
for (Entries::ConstIterator it(entries.Begin()), end(entries.End()); it != end; ++it)
|
|
{
|
|
garbage += (it->type == 0);
|
|
|
|
if (garbage > GARBAGE_THRESHOLD)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Launcher::List::Files::Clear()
|
|
{
|
|
if (entries.Size())
|
|
dirty = true;
|
|
|
|
entries.Destroy();
|
|
strings.Clear();
|
|
}
|
|
|
|
void Launcher::List::Files::Refresh
|
|
(
|
|
const Paths::Settings& settings,
|
|
const Nes::Cartridge::Database& imageDatabase
|
|
)
|
|
{
|
|
dirty = true;
|
|
Searcher( strings, entries, settings, imageDatabase ).Search();
|
|
}
|
|
|
|
Nes::Cartridge::Database::Entry Launcher::List::Files::Entry::SearchDb(const Nes::Cartridge::Database* db) const
|
|
{
|
|
Nes::Cartridge::Database::Entry entry;
|
|
|
|
if (db && (type & (NES|UNF|XML)))
|
|
entry = db->FindEntry( hash, Nes::Machine::FAVORED_NES_NTSC );
|
|
|
|
return entry;
|
|
}
|
|
|
|
uint Launcher::List::Files::Entry::GetSystem(const Nes::Cartridge::Database* db) const
|
|
{
|
|
if (const Nes::Cartridge::Database::Entry entry = SearchDb( db ))
|
|
{
|
|
if (entry.IsMultiRegion())
|
|
{
|
|
return SYSTEM_NTSC_PAL;
|
|
}
|
|
else switch (entry.GetSystem())
|
|
{
|
|
case Nes::Cartridge::Profile::System::NES_NTSC:
|
|
case Nes::Cartridge::Profile::System::FAMICOM:
|
|
return SYSTEM_NTSC;
|
|
|
|
case Nes::Cartridge::Profile::System::NES_PAL:
|
|
case Nes::Cartridge::Profile::System::NES_PAL_A:
|
|
case Nes::Cartridge::Profile::System::NES_PAL_B:
|
|
case Nes::Cartridge::Profile::System::DENDY:
|
|
return SYSTEM_PAL;
|
|
|
|
case Nes::Cartridge::Profile::System::VS_UNISYSTEM:
|
|
case Nes::Cartridge::Profile::System::VS_DUALSYSTEM:
|
|
return SYSTEM_VS;
|
|
|
|
case Nes::Cartridge::Profile::System::PLAYCHOICE_10:
|
|
return SYSTEM_PC10;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (attributes & ATR_VS)
|
|
{
|
|
return SYSTEM_VS;
|
|
}
|
|
else if (attributes & ATR_PC10)
|
|
{
|
|
return SYSTEM_PC10;
|
|
}
|
|
else if ((attributes & ATR_NTSC_PAL) == ATR_NTSC_PAL)
|
|
{
|
|
return SYSTEM_NTSC_PAL;
|
|
}
|
|
else if (attributes & ATR_NTSC)
|
|
{
|
|
return SYSTEM_NTSC;
|
|
}
|
|
else if (attributes & ATR_PAL)
|
|
{
|
|
return SYSTEM_PAL;
|
|
}
|
|
}
|
|
|
|
return SYSTEM_UNKNOWN;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
}
|
|
}
|
|
|