mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
615 lines
15 KiB
C++
615 lines
15 KiB
C++
/*
|
|
NEStopia / Linux
|
|
Port by R. Belmont
|
|
|
|
fileio.cpp - handles movie and state I/O
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <iostream>
|
|
#include <fstream>
|
|
#include <vector>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
#include "core/api/NstApiEmulator.hpp"
|
|
#include "core/api/NstApiVideo.hpp"
|
|
#include "core/api/NstApiSound.hpp"
|
|
#include "core/api/NstApiInput.hpp"
|
|
#include "core/api/NstApiMachine.hpp"
|
|
#include "core/api/NstApiUser.hpp"
|
|
#include "core/api/NstApiNsf.hpp"
|
|
#include "core/api/NstApiMovie.hpp"
|
|
#include "core/api/NstApiFds.hpp"
|
|
#include "core/api/NstApiCartridge.hpp"
|
|
#include "audio.h"
|
|
#include "settings.h"
|
|
#include "unzip.h"
|
|
#include "main.h"
|
|
|
|
extern "C" {
|
|
#include <archive.h>
|
|
#include <archive_entry.h>
|
|
}
|
|
|
|
#define MAX_ITEMS (512)
|
|
|
|
extern Nes::Api::Emulator emulator;
|
|
extern GtkWidget *mainwindow;
|
|
extern char rootname[512];
|
|
extern char msgbuf[512];
|
|
|
|
static std::ifstream *moviePlayFile, *fdsBiosFile, *nstDBFile;
|
|
static std::fstream *movieRecFile;
|
|
|
|
struct archive *a;
|
|
struct archive_entry *entry;
|
|
int r;
|
|
|
|
static bool run_picker, cancelled;
|
|
static GtkTreeStore *treestore;
|
|
static GtkTreeIter treeiters[MAX_ITEMS];
|
|
static GtkCellRenderer *renderer;
|
|
static GtkTreeViewColumn *column;
|
|
static GtkTreeSelection *selection;
|
|
|
|
static int find_current_selection(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < MAX_ITEMS; i++)
|
|
{
|
|
if (gtk_tree_selection_iter_is_selected(selection, &treeiters[i]))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void on_archok_clicked(GtkButton *button, gpointer user_data)
|
|
{
|
|
run_picker = false;
|
|
}
|
|
|
|
void on_archcancel_clicked(GtkButton *button, gpointer user_data)
|
|
{
|
|
run_picker = false;
|
|
cancelled = true;
|
|
}
|
|
|
|
void on_archselect_destroyed(GtkButton *button, gpointer user_data)
|
|
{
|
|
run_picker = false;
|
|
}
|
|
|
|
static gint check_list_double(GtkWidget *widget, GdkEventButton *event, gpointer func_data)
|
|
{
|
|
if (event->type==GDK_2BUTTON_PRESS)
|
|
{
|
|
run_picker = false;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
void fileio_init(void)
|
|
{
|
|
moviePlayFile = NULL;
|
|
movieRecFile = NULL;
|
|
fdsBiosFile = NULL;
|
|
nstDBFile = NULL;
|
|
}
|
|
|
|
void fileio_do_state_save(void)
|
|
{
|
|
Nes::Api::Machine machine( emulator );
|
|
GtkWidget *dialog;
|
|
char defname[512];
|
|
|
|
defname[0] = '\0';
|
|
strcpy(defname, rootname);
|
|
strcat(defname, ".nst");
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("Save state (.nst)",
|
|
GTK_WINDOW(mainwindow),
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defname);
|
|
|
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
|
|
|
std::ofstream stateFile( filename, std::ifstream::out|std::ifstream::binary );
|
|
|
|
if (stateFile.is_open())
|
|
{
|
|
machine.SaveState(stateFile);
|
|
}
|
|
|
|
g_free (filename);
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
void fileio_do_state_load(void)
|
|
{
|
|
Nes::Api::Machine machine( emulator );
|
|
GtkWidget *dialog;
|
|
GtkFileFilter *filter;
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("Load state (.nst)",
|
|
GTK_WINDOW(mainwindow),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
filter = gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, "Nestopia save states");
|
|
gtk_file_filter_add_pattern(filter, "*.nst");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
|
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
|
|
|
std::ifstream stateFile( filename, std::ifstream::in|std::ifstream::binary );
|
|
|
|
if (stateFile.is_open())
|
|
{
|
|
machine.LoadState(stateFile);
|
|
}
|
|
|
|
g_free (filename);
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
void fileio_do_movie_save(void)
|
|
{
|
|
Nes::Api::Machine machine( emulator );
|
|
Nes::Api::Movie movie( emulator );
|
|
GtkWidget *dialog;
|
|
char defname[512];
|
|
|
|
defname[0] = '\0';
|
|
strcpy(defname, rootname);
|
|
strcat(defname, ".nsv");
|
|
|
|
if (movieRecFile)
|
|
{
|
|
delete movieRecFile;
|
|
movieRecFile = NULL;
|
|
}
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("Save a movie (.nsv)",
|
|
GTK_WINDOW(mainwindow),
|
|
GTK_FILE_CHOOSER_ACTION_SAVE,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), defname);
|
|
|
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
|
|
|
movieRecFile = new std::fstream(filename, std::ifstream::out|std::ifstream::binary);
|
|
|
|
if (movieRecFile->is_open())
|
|
{
|
|
movie.Record((std::iostream&)*movieRecFile, Nes::Api::Movie::CLEAN);
|
|
}
|
|
else
|
|
{
|
|
delete movieRecFile;
|
|
movieRecFile = NULL;
|
|
}
|
|
|
|
g_free (filename);
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
void fileio_do_movie_load(void)
|
|
{
|
|
Nes::Api::Machine machine( emulator );
|
|
Nes::Api::Movie movie( emulator );
|
|
GtkWidget *dialog;
|
|
GtkFileFilter *filter;
|
|
|
|
if (moviePlayFile)
|
|
{
|
|
delete moviePlayFile;
|
|
moviePlayFile = NULL;
|
|
}
|
|
|
|
dialog = gtk_file_chooser_dialog_new ("Load a movie (.nsv)",
|
|
GTK_WINDOW(mainwindow),
|
|
GTK_FILE_CHOOSER_ACTION_OPEN,
|
|
GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
|
|
GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
|
|
NULL);
|
|
|
|
filter = gtk_file_filter_new();
|
|
gtk_file_filter_set_name(filter, "Nestopia movies");
|
|
gtk_file_filter_add_pattern(filter, "*.nsv");
|
|
gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
|
|
|
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
|
{
|
|
char *filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
|
|
|
moviePlayFile = new std::ifstream( filename, std::ifstream::in|std::ifstream::binary );
|
|
|
|
if (moviePlayFile->is_open())
|
|
{
|
|
movie.Play(*moviePlayFile);
|
|
}
|
|
else
|
|
{
|
|
delete moviePlayFile;
|
|
moviePlayFile = NULL;
|
|
}
|
|
|
|
g_free (filename);
|
|
}
|
|
|
|
gtk_widget_destroy(dialog);
|
|
}
|
|
|
|
void fileio_do_movie_stop(void)
|
|
{
|
|
Nes::Api::Movie movie( emulator );
|
|
|
|
if (movieRecFile || moviePlayFile)
|
|
{
|
|
movie.Stop();
|
|
movie.Eject();
|
|
|
|
if (movieRecFile)
|
|
{
|
|
delete movieRecFile;
|
|
movieRecFile = NULL;
|
|
}
|
|
|
|
if (moviePlayFile)
|
|
{
|
|
delete moviePlayFile;
|
|
moviePlayFile = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void fileio_set_fds_bios(void)
|
|
{
|
|
Nes::Api::Fds fds( emulator );
|
|
char dirname[1024], *home;
|
|
|
|
if (fdsBiosFile)
|
|
{
|
|
return;
|
|
}
|
|
|
|
home = getenv("HOME");
|
|
snprintf(dirname, sizeof(dirname), "%s/.nestopia/disksys.rom", home);
|
|
|
|
fdsBiosFile = new std::ifstream(dirname, std::ifstream::in|std::ifstream::binary);
|
|
|
|
if (fdsBiosFile->is_open())
|
|
{
|
|
fds.SetBIOS(fdsBiosFile);
|
|
}
|
|
else
|
|
{
|
|
snprintf(msgbuf, sizeof(msgbuf), "~/.nestopia/disksys.rom not found, Disk System games will not work.");
|
|
print_message(msgbuf);
|
|
delete fdsBiosFile;
|
|
fdsBiosFile = NULL;
|
|
}
|
|
}
|
|
|
|
void fileio_shutdown(void)
|
|
{
|
|
if (nstDBFile)
|
|
{
|
|
delete nstDBFile;
|
|
nstDBFile = NULL;
|
|
}
|
|
|
|
if (fdsBiosFile)
|
|
{
|
|
delete fdsBiosFile;
|
|
fdsBiosFile = NULL;
|
|
}
|
|
}
|
|
|
|
static int checkExtension(const char *filename)
|
|
{
|
|
int nlen;
|
|
|
|
nlen = strlen(filename);
|
|
|
|
if ((!strcasecmp(&filename[nlen-4], ".nes")) ||
|
|
(!strcasecmp(&filename[nlen-4], ".fds")) ||
|
|
(!strcasecmp(&filename[nlen-4], ".nsf")) ||
|
|
(!strcasecmp(&filename[nlen-4], ".unf")) ||
|
|
(!strcasecmp(&filename[nlen-5], ".unif"))||
|
|
(!strcasecmp(&filename[nlen-4], ".xml")))
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int fileio_load_archive(const char *filename, unsigned char **dataout, int *datasize, int *dataoffset, const char *filetoload, char *outname)
|
|
{
|
|
FILE *f;
|
|
unsigned char idbuf[4];
|
|
int filesFound = 0;
|
|
std::vector<char *> filelist; // list of files we can load in this archive
|
|
|
|
// default case: outname is filename
|
|
if (outname)
|
|
{
|
|
strcpy(outname, filename);
|
|
}
|
|
|
|
f = fopen(filename, "rb");
|
|
|
|
if (!f)
|
|
{
|
|
return 0; // no good
|
|
}
|
|
|
|
fread(idbuf, 4, 1, f);
|
|
fclose(f);
|
|
|
|
// printf("ID bytes %c %c %x %x\n", idbuf[0], idbuf[1], idbuf[2], idbuf[3]);
|
|
|
|
// Handle all archives with common libarchive code
|
|
if ((idbuf[0] == 'P') && (idbuf[1] == 'K') && (idbuf[2] == 0x03) && (idbuf[3] == 0x04) || // zip
|
|
((idbuf[0] == '7') && (idbuf[1] == 'z') && (idbuf[2] == 0xbc) && (idbuf[3] == 0xaf)) || // 7zip
|
|
((idbuf[0] == 0xfd) && (idbuf[1] == 0x37) && (idbuf[2] == 0x7a) && (idbuf[3] == 0x58)) || // txz
|
|
((idbuf[0] == 0x1f) && (idbuf[1] == 0x8b) && (idbuf[2] == 0x08) && (idbuf[3] == 0x00)) || // tgz
|
|
((idbuf[0] == 0x42) && (idbuf[1] == 0x5a) && (idbuf[2] == 0x68) && (idbuf[3] == 0x39)) // tbz
|
|
) {
|
|
a = archive_read_new();
|
|
archive_read_support_filter_all(a);
|
|
archive_read_support_format_all(a);
|
|
r = archive_read_open_filename(a, filename, 10240);
|
|
|
|
int64_t entry_size;
|
|
|
|
if (r != ARCHIVE_OK) {
|
|
print_message("Archive failed to open.");
|
|
}
|
|
|
|
while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
|
|
const char *currentFile = archive_entry_pathname(entry);
|
|
unsigned char *fileContents;
|
|
entry_size = archive_entry_size(entry);
|
|
fileContents = (unsigned char *)malloc(entry_size);
|
|
|
|
if (filetoload != NULL) {
|
|
if (!strcmp(currentFile, filetoload)) {
|
|
archive_read_data(a, fileContents, entry_size);
|
|
archive_read_data_skip(a);
|
|
r = archive_read_free(a);
|
|
|
|
*datasize = entry_size;
|
|
*dataout = fileContents;
|
|
*dataoffset = 0;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
else {
|
|
if (checkExtension(currentFile))
|
|
{
|
|
char *tmpstr;
|
|
|
|
tmpstr = (char *)malloc(strlen(currentFile)+1);
|
|
strcpy(tmpstr, currentFile);
|
|
|
|
// add to the file list
|
|
filelist.push_back(tmpstr);
|
|
filesFound++;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
else if ((idbuf[0] == 'R') && (idbuf[1] == 'a') && (idbuf[2] == 'r') && (idbuf[3] == '!'))
|
|
{ // it's rar
|
|
print_message("Rar files are not supported.");
|
|
}
|
|
|
|
// if we found any files and weren't forced to load them, handle accordingly
|
|
if (filesFound)
|
|
{
|
|
// only 1 file found, just run it
|
|
if (filesFound == 1)
|
|
{
|
|
char fname[512];
|
|
|
|
strcpy(fname, filelist[0]);
|
|
|
|
free(filelist[0]);
|
|
filelist.clear();
|
|
|
|
strcpy(outname, fname);
|
|
|
|
return fileio_load_archive(filename, dataout, datasize, dataoffset, fname, NULL);
|
|
}
|
|
else // multiple files we can handle found, give the user a choice
|
|
{
|
|
int sel;
|
|
char fname[512];
|
|
|
|
GtkWidget *archselect = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_window_set_title(GTK_WINDOW (archselect), "Pick game in archive");
|
|
gtk_window_set_modal(GTK_WINDOW (archselect), TRUE);
|
|
|
|
GtkWidget *archbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
|
|
gtk_container_add(GTK_CONTAINER(archselect), archbox);
|
|
gtk_widget_show(archbox);
|
|
|
|
GtkWidget *scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
|
|
gtk_box_pack_start(GTK_BOX(archbox), scrolledwindow, TRUE, TRUE, 0);
|
|
gtk_widget_set_size_request(scrolledwindow, 340, 340);
|
|
gtk_widget_show(scrolledwindow);
|
|
|
|
GtkWidget *buttonbox = gtk_widget_new(GTK_TYPE_BOX, "halign", GTK_ALIGN_END, NULL);
|
|
gtk_box_pack_start(GTK_BOX(archbox), buttonbox, FALSE, TRUE, 0);
|
|
gtk_widget_show(buttonbox);
|
|
|
|
GtkWidget *archtree = gtk_tree_view_new();
|
|
gtk_container_add(GTK_CONTAINER (scrolledwindow), archtree);
|
|
gtk_tree_view_set_fixed_height_mode(GTK_TREE_VIEW (archtree), FALSE);
|
|
g_signal_connect(G_OBJECT(archtree), "button_press_event", G_CALLBACK(check_list_double), NULL);
|
|
gtk_widget_show(archtree);
|
|
|
|
// set up our tree store
|
|
treestore = gtk_tree_store_new(1, G_TYPE_STRING);
|
|
|
|
// attach the store to the tree
|
|
gtk_tree_view_set_model(GTK_TREE_VIEW(archtree), GTK_TREE_MODEL(treestore));
|
|
|
|
for (int fn = 0; fn < filelist.size(); fn++)
|
|
{
|
|
gtk_tree_store_insert(treestore, &treeiters[fn], NULL, 999999);
|
|
gtk_tree_store_set(treestore, &treeiters[fn], 0, filelist[fn], -1);
|
|
}
|
|
|
|
// create a cell renderer using the stock text one
|
|
renderer = gtk_cell_renderer_text_new();
|
|
|
|
// create a display column using the renderer
|
|
column = gtk_tree_view_column_new_with_attributes ("NES file",
|
|
renderer,
|
|
"text", 0,
|
|
NULL);
|
|
|
|
// add the display column and renderer to the tree view
|
|
gtk_tree_view_append_column(GTK_TREE_VIEW (archtree), column);
|
|
|
|
GtkWidget *archcancel = gtk_widget_new(GTK_TYPE_BUTTON, "label", GTK_STOCK_CANCEL, "halign", GTK_ALIGN_END, "margin-top", 8, "margin-bottom", 8, "margin-right", 8, NULL);
|
|
gtk_button_set_use_stock(GTK_BUTTON(archcancel), TRUE);
|
|
gtk_box_pack_start(GTK_BOX(buttonbox), archcancel, FALSE, FALSE, 0);
|
|
gtk_widget_show(archcancel);
|
|
|
|
GtkWidget *archok = gtk_widget_new(GTK_TYPE_BUTTON, "label", GTK_STOCK_OK, "halign", GTK_ALIGN_END, "margin-top", 8, "margin-bottom", 8, "margin-right", 8, NULL);
|
|
gtk_button_set_use_stock(GTK_BUTTON(archok), TRUE);
|
|
gtk_box_pack_start(GTK_BOX(buttonbox), archok, FALSE, FALSE, 0);
|
|
gtk_widget_show(archok);
|
|
|
|
// get the selection object too
|
|
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(archtree));
|
|
|
|
g_signal_connect(G_OBJECT(archcancel), "clicked",
|
|
G_CALLBACK(on_archcancel_clicked), NULL);
|
|
|
|
g_signal_connect(G_OBJECT(archok), "clicked",
|
|
G_CALLBACK(on_archok_clicked), NULL);
|
|
|
|
g_signal_connect(G_OBJECT(archselect), "destroy",
|
|
G_CALLBACK(on_archselect_destroyed), NULL);
|
|
|
|
gtk_widget_show(archselect);
|
|
|
|
run_picker = true;
|
|
cancelled = false;
|
|
|
|
while (run_picker)
|
|
{
|
|
gtk_main_iteration_do(TRUE);
|
|
}
|
|
|
|
sel = find_current_selection();
|
|
|
|
gtk_widget_destroy(archselect);
|
|
|
|
// was something picked?
|
|
if ((sel != -1) && (!cancelled))
|
|
{
|
|
strcpy(fname, filelist[sel]);
|
|
}
|
|
|
|
// free all the temp filenames
|
|
for (int fn = 0; fn < filelist.size(); fn++)
|
|
{
|
|
free(filelist[fn]);
|
|
}
|
|
|
|
// and wipe the vector
|
|
filelist.clear();
|
|
|
|
if ((sel != -1) && (!cancelled))
|
|
{
|
|
if (outname)
|
|
{
|
|
strcpy(outname, fname);
|
|
}
|
|
|
|
return fileio_load_archive(filename, dataout, datasize, dataoffset, fname, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void fileio_load_db(void)
|
|
{
|
|
Nes::Api::Cartridge::Database database( emulator );
|
|
char dirname[1024], datadirname[1024], *pwd;
|
|
|
|
if (nstDBFile)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pwd = getenv("PWD");
|
|
snprintf(dirname, sizeof(dirname), "%s/NstDatabase.xml", pwd);
|
|
snprintf(datadirname, sizeof(datadirname), "%s/NstDatabase.xml", DATADIR);
|
|
|
|
nstDBFile = new std::ifstream(datadirname, std::ifstream::in|std::ifstream::binary);
|
|
|
|
if (nstDBFile->is_open())
|
|
{
|
|
database.Load(*nstDBFile);
|
|
database.Enable(true);
|
|
}
|
|
else
|
|
{
|
|
print_message("NstDatabase.xml not found!");
|
|
nstDBFile = new std::ifstream(dirname, std::ifstream::in|std::ifstream::binary);
|
|
|
|
if (nstDBFile->is_open())
|
|
{
|
|
database.Load(*nstDBFile);
|
|
database.Enable(true);
|
|
}
|
|
else
|
|
{
|
|
print_message("NstDatabase.xml not found!");
|
|
delete nstDBFile;
|
|
nstDBFile = NULL;
|
|
}
|
|
}
|
|
}
|
|
|