nestopia/source/win32/NstManagerPaths.cpp

724 lines
18 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 "NstIoFile.hpp"
#include "NstIoArchive.hpp"
#include "NstIoScreen.hpp"
#include "NstWindowUser.hpp"
#include "NstResourceString.hpp"
#include "NstDialogBrowse.hpp"
#include "NstDialogPaths.hpp"
#include "NstManagerPaths.hpp"
#include "NstManagerPathsFilter.hpp"
namespace Nestopia
{
namespace Managers
{
Paths::File::File()
: type(NONE)
{
}
Paths::File::~File()
{
}
Paths::Paths(Emulator& e,const Configuration& cfg,Window::Menu& m)
:
Manager ( e, m, this, &Paths::OnEmuEvent, IDM_OPTIONS_PATHS, &Paths::OnMenu ),
dialog ( new Window::Paths(cfg) )
{
recentImageDir = cfg["paths"]["images"]["recent-directory"].Str();
UpdateSettings();
}
Paths::~Paths()
{
}
void Paths::Save(Configuration& cfg) const
{
dialog->Save( cfg );
if (recentImageDir.DirectoryExists())
cfg["paths"]["images"]["recent-directory"].Str() = recentImageDir;
}
void Paths::UpdateSettings()
{
emulator.WriteProtectCartridge
(
dialog->GetSetting(Window::Paths::READONLY_CARTRIDGE)
);
}
void Paths::OnMenu(uint)
{
dialog->Open();
UpdateSettings();
}
void Paths::OnEmuEvent(const Emulator::Event event,const Emulator::Data data)
{
switch (event)
{
case Emulator::EVENT_NETPLAY_MODE:
menu[IDM_OPTIONS_PATHS].Enable( !data );
break;
}
}
bool Paths::SaveSlotExportingEnabled() const
{
return dialog->GetSetting(Window::Paths::AUTO_EXPORT_STATE_SLOTS);
}
bool Paths::SaveSlotImportingEnabled() const
{
return dialog->GetSetting(Window::Paths::AUTO_IMPORT_STATE_SLOTS);
}
bool Paths::AutoLoadCheatsEnabled() const
{
return dialog->GetSetting(Window::Paths::CHEATS_AUTO_LOAD);
}
bool Paths::AutoSaveCheatsEnabled() const
{
return dialog->GetSetting(Window::Paths::CHEATS_AUTO_SAVE);
}
bool Paths::UseStateCompression() const
{
return dialog->GetSetting(Window::Paths::COMPRESS_STATES);
}
bool Paths::BypassPatchValidation() const
{
return dialog->GetSetting(Window::Paths::PATCH_BYPASS_VALIDATION);
}
bool Paths::LocateFile(Path& path,const File::Types types) const
{
NST_ASSERT( path.File().Length() );
static const struct { uint type; wchar_t extension[4]; } lut[19] =
{
{ File::INES, L"nes" },
{ File::UNIF, L"unf" },
{ File::FDS, L"fds" },
{ File::NSF, L"nsf" },
{ File::BATTERY, L"sav" },
{ File::TAPE, L"tp" },
{ File::STATE|File::SLOTS, L"nst" },
{ File::IPS, L"ips" },
{ File::UPS, L"ups" },
{ File::MOVIE, L"nsv" },
{ File::ROM, L"rom" },
{ File::XML, L"xml" },
{ File::PALETTE, L"pal" },
{ File::WAVE, L"wav" },
{ File::AVI, L"avi" },
{ File::ARCHIVE, L"zip" },
{ File::ARCHIVE, L"rar" },
{ File::ARCHIVE, L"7z" }
};
for (uint i=0; i < 18; ++i)
{
if (types( lut[i].type ))
{
path.Directory() = GetDefaultDirectory( lut[i].type );
path.Extension() = lut[i].extension;
if (path.FileExists())
return true;
}
}
return false;
}
bool Paths::FindFile(Path& path) const
{
NST_ASSERT( path.File().Length() );
path.Directory() = Application::Instance::GetExePath().Directory();
if (path.FileExists())
return true;
for (uint i=0; i < Window::Paths::NUM_DIRS; ++i)
{
path.Directory() = dialog->GetDirectory( static_cast<Window::Paths::Type>(i) );
if (path.FileExists())
return true;
}
path.Directory() = recentImageDir;
if (path.FileExists())
return true;
path.Directory().Clear();
return false;
}
void Paths::UpdateRecentImageDirectory(const Path& path,const File::Types types) const
{
NST_ASSERT( path.Length() );
if (types( File::IMAGE ))
recentImageDir = path.Directory();
}
Path Paths::GetScreenShotPath() const
{
Path path;
path.Directory() = dialog->GetDirectory( Window::Paths::DIR_SCREENSHOT );
path.File() = emulator.GetImagePath().Target().File();
path.Extension().Clear();
const uint offset = path.Length() + 1;
path << L"_xxx." << dialog->GetScreenShotExtension();
for (uint i=1; i < 1000; ++i)
{
path[offset+0] = '0' + (i / 100);
path[offset+1] = '0' + (i % 100 / 10);
path[offset+2] = '0' + (i % 10);
if (!path.FileExists())
break;
}
return path;
}
wcstring Paths::GetDefaultExtension(const File::Types types)
{
return
(
types( File::INES ) ? L"nes" :
types( File::UNIF ) ? L"unf" :
types( File::FDS ) ? L"fds" :
types( File::NSF ) ? L"nsf" :
types( File::XML ) ? L"xml" :
types( File::BATTERY ) ? L"sav" :
types( File::TAPE ) ? L"tp" :
types( File::STATE|File::SLOTS ) ? L"nst" :
types( File::IPS ) ? L"ips" :
types( File::UPS ) ? L"ups" :
types( File::MOVIE ) ? L"nsv" :
types( File::ROM ) ? L"rom" :
types( File::ROM ) ? L"xml" :
types( File::PALETTE ) ? L"pal" :
types( File::WAVE ) ? L"wav" :
types( File::AVI ) ? L"avi" :
types( File::ARCHIVE ) ? L"zip" :
L""
);
}
const Path Paths::GetDefaultDirectory(const File::Types types) const
{
Window::Paths::Type type;
if (types( File::IMAGE|File::ROM ))
{
if (dialog->GetSetting(Window::Paths::USE_LAST_IMAGE_DIR) && recentImageDir.DirectoryExists())
return recentImageDir;
type = Window::Paths::DIR_IMAGE;
}
else if (types( File::STATE|File::SLOTS|File::MOVIE ))
{
type = Window::Paths::DIR_STATE;
}
else if (types( File::BATTERY|File::TAPE ))
{
type = Window::Paths::DIR_SAVE;
}
else if (types( File::PATCH ))
{
type = Window::Paths::DIR_PATCHES;
}
else
{
return Application::Instance::GetExePath().Directory();
}
return dialog->GetDirectory( type );
}
bool Paths::CheckFile(Path& path,const File::Types types,const Alert alert,const uint title) const
{
NST_ASSERT( types.Word() );
try
{
if (LoadFromFile( path, NULL, types ))
return true;
}
catch (int ids)
{
if (alert == NOISY)
{
Window::User::Fail( ids, title );
}
else if (alert == STICKY)
{
Io::Screen() << Resource::String(title) << ' ' << Resource::String(ids);
}
}
path.Clear();
return false;
}
Path Paths::BrowseLoad(const File::Types types,const GenericString dir,const Checking checking) const
{
NST_ASSERT( types.Word() );
Path path
(
Window::Browser::OpenFile
(
Filter( types ).Ptr(),
dir.Length() ? Path(dir) : GetDefaultDirectory( types ),
GetDefaultExtension( types )
)
);
if (path.Length())
{
UpdateRecentImageDirectory( path, types );
if (checking == CHECK_FILE)
CheckFile( path, types, NOISY );
}
return path;
}
Path Paths::BrowseSave(const File::Types types,const Method method,const GenericString initPath) const
{
NST_ASSERT( types.Word() );
Path path( initPath );
if (path.Directory().Empty())
path.Directory() = GetDefaultDirectory( types );
if (method == SUGGEST && path.File().Empty())
path.File() = emulator.GetImagePath().Target().File();
path.Extension() = GetDefaultExtension( types );
path = Window::Browser::SaveFile( Filter(types).Ptr(), path );
if (path.Length())
UpdateRecentImageDirectory( path, types );
return path;
}
void Paths::FixFile(const File::Type type,Path& path) const
{
if (path.File().Length())
{
if (path.Directory().Empty())
path.Directory() = GetDefaultDirectory( type );
if (path.Extension().Empty())
path.Extension() = GetDefaultExtension( type );
}
else
{
path.Clear();
}
}
Path Paths::GetSavePath(const Path& image,const File::Type type) const
{
NST_ASSERT( image.Length() );
Path save;
if (type & File::GAME)
{
save.Set
(
dialog->GetDirectory( (type & File::CARTRIDGE) ? Window::Paths::DIR_SAVE : Window::Paths::DIR_PATCHES ),
image.Target().File(),
(type & File::CARTRIDGE) ? L"sav" : L"ups"
);
}
return save;
}
Path Paths::GetCheatPath() const
{
return dialog->GetDirectory( Window::Paths::DIR_CHEATS );
}
Path Paths::GetCheatPath(const Path& image) const
{
return Path( dialog->GetDirectory( Window::Paths::DIR_CHEATS ), image.Target().File(), L"xml" );
}
Path Paths::GetPatchPath(const Path& image,const File::Type type) const
{
Path patch;
if ((type & File::CARTRIDGE) && dialog->GetSetting( Window::Paths::PATCH_AUTO_APPLY ))
{
patch.Set( dialog->GetDirectory( Window::Paths::DIR_PATCHES ), image.Target().File(), L"ups" );
if (!patch.FileExists())
patch.Extension() = L"ips";
}
return patch;
}
Path Paths::GetSamplesPath() const
{
return dialog->GetDirectory( Window::Paths::DIR_SAMPLES );
}
Paths::File::Type Paths::Load
(
File& file,
const File::Types types,
const GenericString path,
const Alert alert
) const
{
if (path.Length())
{
file.name = path;
if (file.name.Directory().Empty())
file.name.Directory() = GetDefaultDirectory( types );
}
else
{
file.name = BrowseLoad( types );
}
if (file.name.Empty() || (alert == QUIETLY && !file.name.FileExists()))
return File::NONE;
try
{
Application::Instance::Waiter wait;
file.type = LoadFromFile( file.name, &file.data, types );
}
catch (int ids)
{
if (alert == NOISY)
{
Window::User::Fail( ids, IDS_TITLE_ERROR );
}
else if (alert == STICKY)
{
Io::Screen() << Resource::String(IDS_TITLE_ERROR) << ' ' << Resource::String(ids);
}
file.type = File::NONE;
}
if (file.type == File::NONE)
{
file.name.Clear();
file.data.Clear();
}
return file.type;
}
bool Paths::Save
(
const void* const data,
const uint size,
const File::Type type,
Path path,
const Alert alert
) const
{
if (path.Directory().Empty())
path.Directory() = GetDefaultDirectory( type );
try
{
Io::File( path, Io::File::DUMP|Io::File::WRITE_THROUGH ).Write( data, size );
}
catch (Io::File::Exception ids)
{
if (alert == NOISY)
{
Window::User::Fail( ids, IDS_TITLE_ERROR );
}
else if (alert == STICKY)
{
Io::Screen() << Resource::String(IDS_TITLE_ERROR) << ' ' << Resource::String(ids);
}
return false;
}
return true;
}
Paths::File::Type Paths::LoadFromFile(Path& path,File::Data* const data,const File::Types types)
{
NST_ASSERT( path.Length() );
const GenericString fileInArchive( path.FileInArchive() );
GenericString filePath( path );
if (fileInArchive.Length())
filePath = path.Archive();
try
{
Io::File file( filePath, Io::File::READ|Io::File::EXISTING );
const File::Type type = CheckFile( types, file.Peek32(), path.Extension().Id() );
if (type == File::NONE)
throw IDS_FILE_ERR_INVALID;
if (type == File::ARCHIVE)
return LoadFromArchive( Io::Archive(file), path, data, fileInArchive, types );
if (fileInArchive.Length())
throw IDS_FILE_ERR_INVALID;
if (data)
file.Stream() >> *data;
return type;
}
catch (Io::File::Exception id)
{
throw int(id);
}
}
Paths::File::Type Paths::LoadFromArchive
(
const Io::Archive& archive,
Path& path,
File::Data* const data,
const GenericString& fileInArchive,
const File::Types types
)
{
uint index;
if (fileInArchive.Length())
{
index = archive.Find( fileInArchive );
}
else
{
uint count = 0;
GenericString filter[32];
if (types( File::INES )) filter[count++] = L"nes";
if (types( File::FDS )) filter[count++] = L"fds";
if (types( File::NSF )) filter[count++] = L"nsf";
if (types( File::BATTERY )) filter[count++] = L"sav";
if (types( File::TAPE )) filter[count++] = L"tp";
if (types( File::STATE )) filter[count++] = L"nst";
if (types( File::MOVIE )) filter[count++] = L"nsv";
if (types( File::IPS )) filter[count++] = L"ips";
if (types( File::UPS )) filter[count++] = L"ups";
if (types( File::ROM )) filter[count++] = L"rom";
if (types( File::XML )) filter[count++] = L"xml";
if (types( File::PALETTE )) filter[count++] = L"pal";
if (types( File::WAVE )) filter[count++] = L"wav";
if (types( File::AVI )) filter[count++] = L"avi";
if (types( File::UNIF ))
{
filter[count++] = L"unf";
filter[count++] = L"unif";
}
if (types( File::SLOTS ))
{
filter[count++] = L"ns1";
filter[count++] = L"ns2";
filter[count++] = L"ns3";
filter[count++] = L"ns4";
filter[count++] = L"ns5";
filter[count++] = L"ns6";
filter[count++] = L"ns7";
filter[count++] = L"ns8";
filter[count++] = L"ns9";
}
// If more than one file in the archive let the user choose which to load,
// non-valid files will be filtered out by named extension detection
index = archive.UserSelect( filter, count );
}
if (!index)
{
return File::NONE;
}
else if (index == Io::Archive::NO_FILES)
{
throw IDS_FILE_ERR_NOTHING_IN_ARCHIVE;
}
--index;
File::Type type = File::ARCHIVE;
if (data)
{
data->Resize( archive[index].Size() );
if (data->Size() < 4 || !archive[index].Uncompress( data->Ptr() ))
throw IDS_FILE_ERR_INVALID;
type = CheckFile
(
types,
FourCC<>::T( data->Ptr() ),
archive[index].GetName().Extension().Id()
);
if (type == File::NONE || type == File::ARCHIVE)
throw IDS_FILE_ERR_INVALID;
}
if (fileInArchive.Empty())
path << " <" << archive[index].GetName() << '>';
return type;
}
Paths::File::Type Paths::CheckFile(const File::Types types,const uint fileId,const uint extensionId)
{
File::Type type = File::NONE;
switch (fileId)
{
case File::ID_INES: if (types( File::INES )) type = File::INES; break;
case File::ID_UNIF: if (types( File::UNIF )) type = File::UNIF; break;
case File::ID_FDS:
case File::ID_FDS_RAW: if (types( File::FDS )) type = File::FDS; break;
case File::ID_NSF: if (types( File::NSF )) type = File::NSF; break;
case File::ID_IPS: if (types( File::IPS )) type = File::IPS; break;
case File::ID_UPS: if (types( File::UPS )) type = File::UPS; break;
case File::ID_NSV: if (types( File::MOVIE )) type = File::MOVIE; break;
case File::ID_NST: if (types( File::STATE|File::SLOTS )) type = File::STATE; break;
case File::ID_ZIP:
case File::ID_7Z:
case File::ID_RAR: if (types( File::ARCHIVE )) type = File::ARCHIVE; break;
default:
switch (extensionId)
{
// raw or text file, must check the file extension
case FourCC<'s','a','v'>::V: if (types( File::BATTERY )) type = File::BATTERY; break;
case FourCC<'t','p'>::V: if (types( File::TAPE )) type = File::TAPE; break;
case FourCC<'r','o','m'>::V: if (types( File::ROM )) type = File::ROM; break;
case FourCC<'x','m','l'>::V: if (types( File::XML )) type = File::XML; break;
case FourCC<'p','a','l'>::V: if (types( File::PALETTE )) type = File::PALETTE; break;
case FourCC<'w','a','v'>::V: if (types( File::WAVE )) type = File::WAVE; break;
case FourCC<'a','v','i'>::V: if (types( File::AVI )) type = File::AVI; break;
case FourCC<'n','e','s'>::V:
case FourCC<'u','n','f'>::V:
case FourCC<'u','n','i'>::V:
case FourCC<'f','d','s'>::V:
case FourCC<'n','s','f'>::V:
case FourCC<'i','p','s'>::V:
case FourCC<'u','p','s'>::V:
case FourCC<'n','s','v'>::V:
case FourCC<'n','s','t'>::V:
case FourCC<'n','s','0'>::V:
case FourCC<'n','s','1'>::V:
case FourCC<'n','s','2'>::V:
case FourCC<'n','s','3'>::V:
case FourCC<'n','s','4'>::V:
case FourCC<'n','s','5'>::V:
case FourCC<'n','s','6'>::V:
case FourCC<'n','s','7'>::V:
case FourCC<'n','s','8'>::V:
case FourCC<'n','s','9'>::V:
case FourCC<'z','i','p'>::V:
case FourCC<'r','a','r'>::V:
case FourCC<'7','z'>::V:
// either corrupt data or wrong extension, bail..
break;
case FourCC<'b','i','n'>::V:
// If it's a .bin, just assume it's a VC ROM and fail elsewhere if not
type = File::ROM;
break;
default:
// extension is unknown, but the file may still be valid,
// allow it to pass if only one file type was selected
if (types(~uint(File::ARCHIVE|File::IMAGE)) == File::ROM)
{
type = File::ROM;
}
else
{
switch (types(~uint(File::ARCHIVE)))
{
case File::XML: type = File::XML; break;
case File::BATTERY: type = File::BATTERY; break;
case File::TAPE: type = File::TAPE; break;
case File::PALETTE: type = File::PALETTE; break;
}
}
}
}
return type;
}
}
}