[gnome-video-arcade] Make game play back work again with recent MAME releases.



commit c658f0f6fc7415b5a14f623e6a066c9ca0e36931
Author: Matthew Barnes <mbarnes redhat com>
Date:   Mon Apr 13 11:13:05 2009 -0400

    Make game play back work again with recent MAME releases.
    
    2009-04-13  Matthew Barnes  <mbarnes redhat com>
    
    	* configure.ac:
    	Post-release version bump.
    
    	* src/Makefile.am:
    	Add gva-input-file.[ch].
    
    	* src/gva-error.h:
    	Add GVA_ERROR_FORMAT for file format errors.
    
    	* src/gva-input-file.c:
    	* src/gva-input-file.h:
    	New GvaInputFile class for parsing input file headers.
    	Knows about all header formats dating back to MAME 0.112.
    
    	* src/gva-mame-common.c (gva_mame_get_input_files):
    	Return a list of GvaInputFiles instead of a hash table of
    	filenames and ROM names.
    
    	* src/gva-play-back.c (play_back_add_input_file):
    	Take a GvaInputFile instead of a filename and ROM name.
    
    	* src/gva-play-back.c (gva_play_back_show):
    	Adapt to new gva_mame_get_input_files() signature.
    
    	* src/gva-util.c (gva_get_debug_flags):
    	* src/gva-util.h (GvaDebugFlags):
    	Add GVA_DEBUG_INP flag, which prints input file details.
---
 .gitignore            |    1 +
 ChangeLog             |   30 +++
 NEWS                  |   13 +
 README                |    4 +-
 configure.ac          |    2 +-
 po/.gitignore         |    2 +
 src/Makefile.am       |    2 +
 src/gva-error.h       |    3 +
 src/gva-input-file.c  |  592 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/gva-input-file.h  |   89 ++++++++
 src/gva-mame-common.c |   45 +---
 src/gva-mame.h        |    2 +-
 src/gva-play-back.c   |   53 +++--
 src/gva-ui.c          |    4 +-
 src/gva-util.c        |    3 +-
 src/gva-util.h        |    5 +-
 16 files changed, 786 insertions(+), 64 deletions(-)

diff --git a/.gitignore b/.gitignore
index af7af70..82a293b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@ depcomp
 gnome-doc-utils.make
 gtk-doc.make
 install-sh
+intltool-*.in
 libtool
 ltmain.sh
 missing
diff --git a/ChangeLog b/ChangeLog
index c4f7b49..bf571ab 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,33 @@
+2009-04-13  Matthew Barnes  <mbarnes redhat com>
+
+	* configure.ac:
+	Post-release version bump.
+
+	* src/Makefile.am:
+	Add gva-input-file.[ch].
+
+	* src/gva-error.h:
+	Add GVA_ERROR_FORMAT for file format errors.
+
+	* src/gva-input-file.c:
+	* src/gva-input-file.h:
+	New GvaInputFile class for parsing input file headers.
+	Knows about all header formats dating back to MAME 0.112.
+
+	* src/gva-mame-common.c (gva_mame_get_input_files):
+	Return a list of GvaInputFiles instead of a hash table of
+	filenames and ROM names.
+
+	* src/gva-play-back.c (play_back_add_input_file):
+	Take a GvaInputFile instead of a filename and ROM name.
+
+	* src/gva-play-back.c (gva_play_back_show):
+	Adapt to new gva_mame_get_input_files() signature.
+
+	* src/gva-util.c (gva_get_debug_flags):
+	* src/gva-util.h (GvaDebugFlags):
+	Add GVA_DEBUG_INP flag, which prints input file details.
+
 2009-04-11  Matthew Barnes  <mbarnes redhat com>
 
 	* src/gva-process.c (process_stdout_ready):
diff --git a/NEWS b/NEWS
index e1b1187..bbfcc88 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,16 @@
+GNOME Video Arcade 0.6.7
+========================
+
+        Released April ??, 2009
+
+        What's New
+        ----------
+        * Make game play back work again with recent MAME releases.
+        * GNOME Video Arcade now works on OpenBSD.  Huge thanks to
+          Pierre Riteau and Antoine Jacoutot for their assistance.
+        * Set the GVA_DEBUG environment variable to "inp" to show
+          input file header information.
+
 GNOME Video Arcade 0.6.6
 ========================
 
diff --git a/README b/README
index 2abd42a..419fe38 100644
--- a/README
+++ b/README
@@ -127,11 +127,11 @@ including GNOME Video Arcade.  This section supplements the INSTALL
 file with information specific to GNOME Video Arcade.
 
 These instructions are written specifically for GNOME Video Arcade
-version 0.6.5 and may change in forthcoming releases.
+version 0.6.7 and may change in forthcoming releases.
 
 The standard installation procedure looks like this:
 
-   $ cd /path/to/gnome-video-arcade-0.6.5
+   $ cd /path/to/gnome-video-arcade-0.6.7
    $ ./configure
    $ make
    $ su -c "make install"
diff --git a/configure.ac b/configure.ac
index 3606686..7d89b17 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
 
 AC_PREREQ([2.54])
 
-m4_define([gva_version], 0.6.6)
+m4_define([gva_version], 0.6.7)
 
 AC_INIT([GNOME Video Arcade], [gva_version],
         [mbarnes redhat com], gnome-video-arcade)
diff --git a/po/.gitignore b/po/.gitignore
index 68c54d2..98ea2e1 100644
--- a/po/.gitignore
+++ b/po/.gitignore
@@ -1,2 +1,4 @@
 POTFILES
 *.gmo
+.intltool-merge-cache
+Makefile.in.in
diff --git a/src/Makefile.am b/src/Makefile.am
index 139381a..2126b7a 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -47,6 +47,8 @@ gnome_video_arcade_SOURCES = \
 	gva-game-store.h		\
 	gva-history.c			\
 	gva-history.h			\
+	gva-input-file.c		\
+	gva-input-file.h		\
 	gva-link-button.c		\
 	gva-link-button.h		\
 	gva-main.c			\
diff --git a/src/gva-error.h b/src/gva-error.h
index bf09444..2207630 100644
--- a/src/gva-error.h
+++ b/src/gva-error.h
@@ -55,6 +55,8 @@ G_BEGIN_DECLS
  * GvaError:
  * @GVA_ERROR_CONFIG:
  *      Configuration error.
+ * @GVA_ERROR_FORMAT:
+ *      File format error.
  * @GVA_ERROR_MAME:
  *      Error from a MAME process.
  * @GVA_ERROR_QUERY:
@@ -68,6 +70,7 @@ G_BEGIN_DECLS
 typedef enum
 {
         GVA_ERROR_CONFIG,
+        GVA_ERROR_FORMAT,
         GVA_ERROR_MAME,
         GVA_ERROR_QUERY,
         GVA_ERROR_SYSTEM
diff --git a/src/gva-input-file.c b/src/gva-input-file.c
new file mode 100644
index 0000000..4b521e7
--- /dev/null
+++ b/src/gva-input-file.c
@@ -0,0 +1,592 @@
+/* Copyright 2007-2009 Matthew Barnes
+ *
+ * This file is part of GNOME Video Arcade.
+ *
+ * GNOME Video Arcade 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 3 of
+ * the License, or (at your option) any later version.
+ *
+ * GNOME Video Arcade 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* A brief history of MAME's INP format.
+ *
+ * ??? - 0.112
+ * - Unknown.  No INP headers?
+ *
+ * 0.113 - 0.115
+ * - Simple headers introduced.
+ *
+ * 0.116 - 0.124
+ * - Added support for playback of "extended" INP files that are commonly
+ *   found on compete sites. [David Haywood]
+ *
+ * 0.125 - 0.128
+ * - Versioned headers introduced (INP version 2.0).
+ * - Dropped support for simple and extended formats.
+ * - Rewrote INP recording from scratch, since all old INPs are broken
+ *   anyways. Header now includes timestamp, which overrides the default
+ *   time base for MAME's system time. ... [Aaron Giles]
+ *
+ * 0.129 - 0.130 (present)
+ * - INP version 3.0.
+ * - To fix 02688 (DIP switch settings are not being stored in INP files),
+ *   old INP files had to be broken. Since they were already broken, the
+ *   corefile module was enhanced to support streaming compression, and the
+ *   new INP files are output and processed compressed. [Aaron Giles]
+ */
+
+#include "gva-input-file.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+
+#include "gva-db.h"
+#include "gva-error.h"
+#include "gva-mame.h"
+#include "gva-util.h"
+
+#define GVA_INPUT_FILE_GET_PRIVATE(obj) \
+        (G_TYPE_INSTANCE_GET_PRIVATE \
+        ((obj), GVA_TYPE_INPUT_FILE, GvaInputFilePrivate))
+
+typedef struct _GvaInpHeaderSimple GvaInpHeaderSimple;
+typedef struct _GvaInpHeaderExtended GvaInpHeaderExtended;
+typedef struct _GvaInpHeaderVersioned GvaInpHeaderVersioned;
+
+enum {
+        PROP_0,
+        PROP_FILENAME,
+        PROP_FORMAT,
+        PROP_GAME,
+        PROP_ORIGIN,
+        PROP_TIMESTAMP
+};
+
+struct _GvaInputFilePrivate
+{
+        gchar *filename;
+        gchar *format;
+        gchar *game;
+        gchar *origin;
+        time_t timestamp;
+};
+
+/* Simple Header */
+struct _GvaInpHeaderSimple
+{
+        gchar rom_name[9];      /* ROM name */
+        gint8 major_version;    /* MAME major version */
+        gint8 minor_version;    /* MAME minor version */
+        gint8 beta_version;     /* MAME beta version */
+        gchar reserved[20];     /* unused padding */
+};
+
+/* Extended Header */
+struct _GvaInpHeaderExtended
+{
+        gchar header[7];        /* "XINP\0\0\0" */
+        gchar rom_name[9];      /* ROM name */
+        gchar origin[32];       /* MAME version string */
+        guint32 timestamp;      /* approx start time */
+        gchar reserved[32];     /* unused padding */
+};
+
+/* Versioned Header */
+struct _GvaInpHeaderVersioned
+{
+        gchar header[8];        /* "MAMEINP\0" */
+        gint64 timestamp;       /* approx start time */
+        gint16 major_version;   /* INP format major version */
+        gint16 minor_version;   /* INP format minor version */
+        gchar rom_name[12];     /* ROM name */
+        gchar origin[32];       /* MAME version string */
+};
+
+static gpointer parent_class = NULL;
+
+static void
+input_file_dump_header (GvaInputFile *input_file)
+{
+        const gchar *filename;
+        const gchar *format;
+        const gchar *game;
+        const gchar *origin;
+        gchar *inpname;
+        time_t timestamp;
+
+        filename = gva_input_file_get_filename (input_file);
+        format = gva_input_file_get_format (input_file);
+        game = gva_input_file_get_game (input_file);
+        origin = gva_input_file_get_origin (input_file);
+        timestamp = gva_input_file_get_timestamp (input_file);
+
+        inpname = g_strdelimit (g_path_get_basename (filename), ".", '\0');
+
+        g_debug ("Input file: %s", inpname);
+        g_debug ("Game: %s", game);
+        g_debug ("%s", format);
+        g_debug ("Created %s", ctime (&timestamp)); 
+        g_debug ("Recorded using %s", origin);
+        g_debug ("--");
+
+        g_free (inpname);
+}
+
+static gboolean
+input_file_read_simple (GvaInputFile *input_file,
+                        GMappedFile *mapped_file)
+{
+        GvaInpHeaderSimple *header;
+        const gchar *contents;
+        const gchar *filename;
+        struct stat st;
+        GString *buffer;
+        gsize length;
+
+        filename = gva_input_file_get_filename (input_file);
+
+        contents = g_mapped_file_get_contents (mapped_file);
+        length = g_mapped_file_get_length (mapped_file);
+
+        if (length < sizeof (GvaInpHeaderSimple))
+                return FALSE;
+
+        header = (GvaInpHeaderSimple *) contents;
+
+        input_file->priv->game = g_strndup (header->rom_name, 9);
+
+        buffer = g_string_sized_new (64);
+        g_string_append_printf (
+                buffer, "MAME %d.%d",
+                header->major_version,
+                header->minor_version);
+        if (header->beta_version > 0)
+                g_string_append_printf (
+                        buffer, "b%d", header->beta_version);
+        input_file->priv->origin = g_string_free (buffer, FALSE);
+
+        if (g_stat (filename, &st) == 0)
+                input_file->priv->timestamp = st.st_mtime;
+
+        input_file->priv->format = g_strdup ("INP simple format");
+
+        if (gva_get_debug_flags () & GVA_DEBUG_INP)
+                input_file_dump_header (input_file);
+
+        return TRUE;
+}
+
+static gboolean
+input_file_read_extended (GvaInputFile *input_file,
+                          GMappedFile *mapped_file)
+{
+        GvaInpHeaderExtended *header;
+        const gchar *contents;
+        gsize length;
+
+        contents = g_mapped_file_get_contents (mapped_file);
+        length = g_mapped_file_get_length (mapped_file);
+
+        if (length < sizeof (GvaInpHeaderExtended))
+                return FALSE;
+
+        if (memcmp (contents, "XINP\0\0\0", 7) != 0)
+                return FALSE;
+
+        header = (GvaInpHeaderExtended *) contents;
+
+        input_file->priv->game = g_strndup (header->rom_name, 9);
+        input_file->priv->origin = g_strndup (header->origin, 32);
+        input_file->priv->timestamp = GINT32_FROM_LE (header->timestamp);
+
+        input_file->priv->format = g_strdup ("INP extended format");
+
+        if (gva_get_debug_flags () & GVA_DEBUG_INP)
+                input_file_dump_header (input_file);
+
+        return TRUE;
+}
+
+static gboolean
+input_file_read_versioned (GvaInputFile *input_file,
+                           GMappedFile *mapped_file)
+{
+        GvaInpHeaderVersioned *header;
+        const gchar *contents;
+        gsize length;
+
+        contents = g_mapped_file_get_contents (mapped_file);
+        length = g_mapped_file_get_length (mapped_file);
+
+        if (length < sizeof (GvaInpHeaderVersioned))
+                return FALSE;
+
+        if (memcmp (contents, "MAMEINP\0", 8) != 0)
+                return FALSE;
+
+        header = (GvaInpHeaderVersioned *) contents;
+
+        input_file->priv->game = g_strndup (header->rom_name, 12);
+        input_file->priv->origin = g_strndup (header->origin, 32);
+        input_file->priv->timestamp = GINT64_FROM_LE (header->timestamp);
+
+        input_file->priv->format = g_strdup_printf (
+                "INP version %d.%d",
+                GINT16_FROM_LE (header->major_version),
+                GINT16_FROM_LE (header->minor_version));
+
+        if (gva_get_debug_flags () & GVA_DEBUG_INP)
+                input_file_dump_header (input_file);
+
+        return TRUE;
+}
+
+static void
+input_file_set_filename (GvaInputFile *input_file,
+                         const gchar *filename)
+{
+        g_return_if_fail (input_file->priv->filename == NULL);
+
+        input_file->priv->filename = g_strdup (filename);
+}
+
+static void
+input_file_set_property (GObject *object,
+                         guint property_id,
+                         const GValue *value,
+                         GParamSpec *pspec)
+{
+        switch (property_id)
+        {
+                case PROP_FILENAME:
+                        input_file_set_filename (
+                                GVA_INPUT_FILE (object),
+                                g_value_get_string (value));
+                        return;
+        }
+
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+input_file_get_property (GObject *object,
+                         guint property_id,
+                         GValue *value,
+                         GParamSpec *pspec)
+{
+        switch (property_id)
+        {
+                case PROP_FILENAME:
+                        g_value_set_string (
+                                value, gva_input_file_get_filename (
+                                GVA_INPUT_FILE (object)));
+                        return;
+
+                case PROP_FORMAT:
+                        g_value_set_string (
+                                value, gva_input_file_get_format (
+                                GVA_INPUT_FILE (object)));
+                        return;
+
+                case PROP_GAME:
+                        g_value_set_string (
+                                value, gva_input_file_get_game (
+                                GVA_INPUT_FILE (object)));
+                        return;
+
+                case PROP_ORIGIN:
+                        g_value_set_string (
+                                value, gva_input_file_get_origin (
+                                GVA_INPUT_FILE (object)));
+                        return;
+
+                case PROP_TIMESTAMP:
+                        g_value_set_int64 (
+                                value, gva_input_file_get_timestamp (
+                                GVA_INPUT_FILE (object)));
+                        return;
+        }
+
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+}
+
+static void
+input_file_finalize (GObject *object)
+{
+        GvaInputFilePrivate *priv;
+
+        priv = GVA_INPUT_FILE_GET_PRIVATE (object);
+
+        g_free (priv->filename);
+        g_free (priv->format);
+        g_free (priv->game);
+        g_free (priv->origin);
+
+        /* Chain up to parent's finalize() method. */
+        G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+input_file_class_init (GvaInputFileClass *class)
+{
+        GObjectClass *object_class;
+
+        parent_class = g_type_class_peek_parent (class);
+        g_type_class_add_private (class, sizeof (GvaInputFilePrivate));
+
+        object_class = G_OBJECT_CLASS (class);
+        object_class->set_property = input_file_set_property;
+        object_class->get_property = input_file_get_property;
+        object_class->finalize = input_file_finalize;
+
+        /**
+         * GvaInputFile:filename:
+         *
+         * The name of the input file.
+         **/
+        g_object_class_install_property (
+                object_class,
+                PROP_FILENAME,
+                g_param_spec_string (
+                        "filename",
+                        NULL,
+                        NULL,
+                        NULL,
+                        G_PARAM_READWRITE |
+                        G_PARAM_CONSTRUCT_ONLY));
+
+        /**
+         * GvaInputFile:format:
+         *
+         * The format of the input file.
+         **/
+        g_object_class_install_property (
+                object_class,
+                PROP_FORMAT,
+                g_param_spec_string (
+                        "format",
+                        NULL,
+                        NULL,
+                        NULL,
+                        G_PARAM_READABLE));
+
+        /**
+         * GvaInputFile:game:
+         *
+         * The corresponding ROM name for the input file.
+         **/
+        g_object_class_install_property (
+                object_class,
+                PROP_GAME,
+                g_param_spec_string (
+                        "game",
+                        NULL,
+                        NULL,
+                        NULL,
+                        G_PARAM_READABLE));
+
+        /**
+         * GvaInputFile:origin:
+         *
+         * The version of MAME that recorded the input file.
+         **/
+        g_object_class_install_property (
+                object_class,
+                PROP_ORIGIN,
+                g_param_spec_string (
+                        "origin",
+                        NULL,
+                        NULL,
+                        NULL,
+                        G_PARAM_READABLE));
+
+        /**
+         * GvaInputFile:timestamp:
+         *
+         * The creation timestamp for the input file.
+         **/
+        g_object_class_install_property (
+                object_class,
+                PROP_TIMESTAMP,
+                g_param_spec_int64 (
+                        "timestamp",
+                        NULL,
+                        NULL,
+                        G_MININT64,
+                        G_MAXINT64,
+                        0,
+                        G_PARAM_READABLE));
+}
+
+static void
+input_file_init (GvaInputFile *input_file)
+{
+        input_file->priv = GVA_INPUT_FILE_GET_PRIVATE (input_file);
+}
+
+GType
+gva_input_file_get_type (void)
+{
+        static GType type = 0;
+
+        if (G_UNLIKELY (type == 0))
+        {
+                static const GTypeInfo type_info =
+                {
+                        sizeof (GvaInputFileClass),
+                        (GBaseInitFunc) NULL,
+                        (GBaseFinalizeFunc) NULL,
+                        (GClassInitFunc) input_file_class_init,
+                        (GClassFinalizeFunc) NULL,
+                        NULL,  /* class_data */
+                        sizeof (GvaInputFile),
+                        0,     /* n_preallocs */
+                        (GInstanceInitFunc) input_file_init,
+                        NULL   /* value_table */
+                };
+
+                type = g_type_register_static (
+                        G_TYPE_OBJECT, "GvaInputFile", &type_info, 0);
+        }
+
+        return type;
+}
+
+GvaInputFile *
+gva_input_file_new (const gchar *filename)
+{
+        g_return_val_if_fail (filename != NULL, NULL);
+
+        return g_object_new (GVA_TYPE_INPUT_FILE, "filename", filename, NULL);
+}
+
+gboolean
+gva_input_file_read (GvaInputFile *input_file,
+                     GError **error)
+{
+        GMappedFile *mapped_file = NULL;
+        const gchar *filename;
+        gboolean success = TRUE;
+
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), FALSE);
+
+        if (input_file->priv->format != NULL)
+                goto exit;
+
+        filename = gva_input_file_get_filename (input_file);
+
+        mapped_file = g_mapped_file_new (filename, FALSE, error);
+        if (mapped_file == NULL)
+                return FALSE;
+
+        input_file->priv->filename = g_strdup (filename);
+
+        /* Check for a versioned INP header. */
+        if (input_file_read_versioned (input_file, mapped_file))
+                goto exit;
+
+        /* Check for an extended INP header. */
+        if (input_file_read_extended (input_file, mapped_file))
+                goto exit;
+
+        /* Check for a simple INP header. */
+        if (input_file_read_simple (input_file, mapped_file))
+                goto exit;
+
+        g_set_error (
+                error, GVA_ERROR, GVA_ERROR_FORMAT,
+                _("Invalid or unsupported INP file format"));
+
+        success = FALSE;
+
+exit:
+        if (success)
+        {
+                g_object_freeze_notify (G_OBJECT (input_file));
+                g_object_notify (G_OBJECT (input_file), "format");
+                g_object_notify (G_OBJECT (input_file), "game");
+                g_object_notify (G_OBJECT (input_file), "origin");
+                g_object_notify (G_OBJECT (input_file), "timestamp");
+                g_object_thaw_notify (G_OBJECT (input_file));
+        }
+
+        if (mapped_file != NULL)
+                g_mapped_file_free (mapped_file);
+
+        return success;
+}
+
+GvaProcess *
+gva_input_file_play_back (GvaInputFile *input_file,
+                          GError **error)
+{
+        GvaProcess *process;
+        const gchar *filename;
+        const gchar *game;
+        gchar *inpname;
+
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), NULL);
+
+        if (!gva_input_file_read (input_file, error))
+                return NULL;
+
+        filename = gva_input_file_get_filename (input_file);
+        game = gva_input_file_get_game (input_file);
+
+        inpname = g_strdelimit (g_path_get_basename (filename), ".", '\0');
+        process = gva_mame_playback_game (game, inpname, error);
+        g_free (inpname);
+
+        return process;
+}
+
+const gchar *
+gva_input_file_get_filename (GvaInputFile *input_file)
+{
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), NULL);
+
+        return input_file->priv->filename;
+}
+
+const gchar *
+gva_input_file_get_format (GvaInputFile *input_file)
+{
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), NULL);
+
+        if (input_file->priv->format == NULL)
+                return NULL;
+
+        return input_file->priv->format;
+}
+
+const gchar *
+gva_input_file_get_game (GvaInputFile *input_file)
+{
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), NULL);
+
+        return input_file->priv->game;
+}
+
+const gchar *
+gva_input_file_get_origin (GvaInputFile *input_file)
+{
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), NULL);
+
+        return input_file->priv->origin;
+}
+
+time_t
+gva_input_file_get_timestamp (GvaInputFile *input_file)
+{
+        g_return_val_if_fail (GVA_IS_INPUT_FILE (input_file), 0);
+
+        return input_file->priv->timestamp;
+}
diff --git a/src/gva-input-file.h b/src/gva-input-file.h
new file mode 100644
index 0000000..fe7e640
--- /dev/null
+++ b/src/gva-input-file.h
@@ -0,0 +1,89 @@
+/* Copyright 2007-2009 Matthew Barnes
+ *
+ * This file is part of GNOME Video Arcade.
+ *
+ * GNOME Video Arcade 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 3 of
+ * the License, or (at your option) any later version.
+ *
+ * GNOME Video Arcade 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION: gva-input-file
+ * @short_description: A stable interface for MAME INP files
+ *
+ * A #GvaInputFile provides metadata about a MAME INP file.  It handles
+ * both current and historical INP formats.
+ **/
+
+#ifndef GVA_INPUT_FILE_H
+#define GVA_INPUT_FILE_H
+
+#include "gva-common.h"
+#include "gva-process.h"
+
+/* Standard GObject macros */
+#define GVA_TYPE_INPUT_FILE \
+        (gva_input_file_get_type ())
+#define GVA_INPUT_FILE(obj) \
+        (G_TYPE_CHECK_INSTANCE_CAST \
+        ((obj), GVA_TYPE_INPUT_FILE, GvaInputFile))
+#define GVA_INPUT_FILE_CLASS(cls) \
+        (G_TYPE_CHECK_CLASS_CAST \
+        ((cls), GVA_TYPE_INPUT_FILE, GvaInputFileClass))
+#define GVA_IS_INPUT_FILE(obj) \
+        (G_TYPE_CHECK_INSTANCE_TYPE \
+        ((obj), GVA_TYPE_INPUT_FILE))
+#define GVA_IS_INPUT_FILE_CLASS(cls) \
+        (G_TYPE_CHECK_CLASS_TYPE \
+        ((cls), GVA_TYPE_INPUT_FILE))
+#define GVA_INPUT_FILE_GET_CLASS(obj) \
+        (G_TYPE_INSTANCE_GET_CLASS \
+        ((obj), GVA_TYPE_INPUT_FILE, GvaInputFileClass))
+
+G_BEGIN_DECLS
+
+typedef struct _GvaInputFile GvaInputFile;
+typedef struct _GvaInputFileClass GvaInputFileClass;
+typedef struct _GvaInputFilePrivate GvaInputFilePrivate;
+
+/**
+ * GvaInputFile:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ **/
+struct _GvaInputFile
+{
+        GObject parent;
+        GvaInputFilePrivate *priv;
+};
+
+struct _GvaInputFileClass
+{
+        GObjectClass parent_class;
+};
+
+GType           gva_input_file_get_type         (void);
+GvaInputFile *  gva_input_file_new              (const gchar *filename);
+gboolean        gva_input_file_read             (GvaInputFile *input_file,
+                                                 GError **error);
+GvaProcess *    gva_input_file_play_back        (GvaInputFile *input_file,
+                                                 GError **error);
+const gchar *   gva_input_file_get_filename     (GvaInputFile *input_file);
+const gchar *   gva_input_file_get_format       (GvaInputFile *input_file);
+const gchar *   gva_input_file_get_game         (GvaInputFile *input_file);
+const gchar *   gva_input_file_get_origin       (GvaInputFile *input_file);
+time_t          gva_input_file_get_timestamp    (GvaInputFile *input_file);
+
+G_END_DECLS
+
+#endif /* GVA_INPUT_FILE_H */
diff --git a/src/gva-mame-common.c b/src/gva-mame-common.c
index 7feaa39..649cdd7 100644
--- a/src/gva-mame-common.c
+++ b/src/gva-mame-common.c
@@ -27,6 +27,7 @@
 #endif
 
 #include "gva-error.h"
+#include "gva-input-file.h"
 #include "gva-mame-process.h"
 #include "gva-mute-button.h"
 #include "gva-preferences.h"
@@ -291,18 +292,18 @@ gva_mame_get_search_paths (const gchar *config_key,
  * gva_mame_get_input_files:
  * @error: return location for a @GError, or %NULL
  *
- * Returns a #GHashTable of input files for playback.  The keys of the
- * #GHashTable are absolute filenames and the values are the corresponding
- * game name.  If an error occurs, it returns %NULL and sets @error.
+ * Returns a list of #GvaInputFile instances corresponding to files in the
+ * input directory.  If an error occurs, it returns %NULL and sets @error.
  *
- * Returns: a #GHashTable of input files, or %NULL
+ * Returns: a list of #GvaInputFile instances, or %NULL
  **/
-GHashTable *
+GList *
 gva_mame_get_input_files (GError **error)
 {
         GHashTable *hash_table;
         const gchar *basename;
         const gchar *directory;
+        GList *list = NULL;
         GDir *dir;
 
         directory = gva_mame_get_input_directory (error);
@@ -319,41 +320,17 @@ gva_mame_get_input_files (GError **error)
         while ((basename = g_dir_read_name (dir)) != NULL)
         {
                 gchar *filename;
-                GIOChannel *channel;
-                GError *local_error = NULL;
+                GvaInputFile *input_file;
 
                 filename = g_build_filename (directory, basename, NULL);
-
-                channel = g_io_channel_new_file (filename, "r", &local_error);
-
-                if (channel != NULL)
-                {
-                        gchar name[16];
-                        GIOStatus status;
-
-                        status = g_io_channel_read_chars (
-                                channel, name, sizeof (name),
-                                NULL, &local_error);
-                        if (status == G_IO_STATUS_NORMAL)
-                                g_hash_table_insert (
-                                        hash_table, filename,
-                                        g_strdup (name));
-                        g_io_channel_unref (channel);
-                }
-
-                if (local_error != NULL)
-                {
-                        g_free (filename);
-                        g_hash_table_destroy (hash_table);
-                        g_propagate_error (error, local_error);
-                        hash_table = NULL;
-                        break;
-                }
+                input_file = gva_input_file_new (filename);
+                list = g_list_prepend (list, input_file);
+                g_free (filename);
         }
 
         g_dir_close (dir);
 
-        return hash_table;
+        return g_list_reverse (list);
 }
 
 /**
diff --git a/src/gva-mame.h b/src/gva-mame.h
index 8dbf9b2..6c523a2 100644
--- a/src/gva-mame.h
+++ b/src/gva-mame.h
@@ -55,7 +55,7 @@ gchar *         gva_mame_get_config_value       (const gchar *config_key,
 gboolean        gva_mame_has_config_value       (const gchar *config_key);
 gchar **        gva_mame_get_search_paths       (const gchar *config_key,
                                                  GError **error);
-GHashTable *    gva_mame_get_input_files        (GError **error);
+GList *         gva_mame_get_input_files        (GError **error);
 GvaProcess *    gva_mame_list_xml               (GError **error);
 gchar *         gva_mame_verify_roms            (const gchar *name,
                                                  GError **error);
diff --git a/src/gva-play-back.c b/src/gva-play-back.c
index ada1057..6ed3b03 100644
--- a/src/gva-play-back.c
+++ b/src/gva-play-back.c
@@ -28,6 +28,7 @@
 #include "gva-db.h"
 #include "gva-error.h"
 #include "gva-game-store.h"
+#include "gva-input-file.h"
 #include "gva-mame.h"
 #include "gva-time.h"
 #include "gva-tree-view.h"
@@ -113,33 +114,42 @@ play_back_selection_changed_cb (GtkTreeSelection *tree_selection)
 }
 
 static void
-play_back_add_input_file (const gchar *inpfile,
-                          const gchar *name,
+play_back_add_input_file (GvaInputFile *input_file,
                           GvaGameStore *game_store)
 {
         GtkTreeIter iter;
+        const gchar *filename;
+        const gchar *game;
         gchar *comment = NULL;
         gchar **result = NULL;
         gchar *inpname;
         gchar *sql;
         gint rows;
         struct stat st;
-        time_t *time;
+        time_t timestamp;
         GError *error = NULL;
 
-        if (g_stat (inpfile, &st) != 0)
+        if (!gva_input_file_read (input_file, &error))
         {
-                g_warning ("%s: %s", inpfile, g_strerror (errno));
+                gva_error_handle (&error);
                 return;
         }
 
-        time = &st.st_ctime;
+        game = gva_input_file_get_game (input_file);
+        filename = gva_input_file_get_filename (input_file);
+        timestamp = gva_input_file_get_timestamp (input_file);
+
+        if (g_stat (filename, &st) != 0)
+        {
+                g_warning ("%s: %s", filename, g_strerror (errno));
+                return;
+        }
 
         /* Try to fetch the comment from the database. */
         sql = g_strdup_printf (
                 "SELECT comment FROM playback WHERE "
                 "name == '%s' AND inode == %" G_GINT64_FORMAT,
-                name, (gint64) st.st_ino);
+                game, (gint64) st.st_ino);
         if (gva_db_get_table (sql, &result, &rows, NULL, &error))
         {
                 if (rows > 0)
@@ -154,7 +164,7 @@ play_back_add_input_file (const gchar *inpfile,
         {
                 sql = g_strdup_printf (
                         "SELECT description FROM available "
-                        "WHERE name == '%s'", name);
+                        "WHERE name == '%s'", game);
                 if (gva_db_get_table (sql, &result, &rows, NULL, &error))
                 {
                         if (rows > 0)
@@ -168,7 +178,7 @@ play_back_add_input_file (const gchar *inpfile,
         /* The game may not be available anymore. */
         if (comment == NULL)
         {
-                g_warning ("%s: Game '%s' not found", inpfile, name);
+                g_warning ("%s: Game '%s' not found", filename, game);
                 return;
         }
 
@@ -176,14 +186,14 @@ play_back_add_input_file (const gchar *inpfile,
 
         gtk_tree_store_set (
                 GTK_TREE_STORE (game_store), &iter,
-                GVA_GAME_STORE_COLUMN_NAME, name,
+                GVA_GAME_STORE_COLUMN_NAME, game,
                 GVA_GAME_STORE_COLUMN_COMMENT, comment,
                 GVA_GAME_STORE_COLUMN_INODE, (gint64) st.st_ino,
-                GVA_GAME_STORE_COLUMN_INPFILE, inpfile,
-                GVA_GAME_STORE_COLUMN_TIME, time,
+                GVA_GAME_STORE_COLUMN_INPFILE, filename,
+                GVA_GAME_STORE_COLUMN_TIME, &timestamp,
                 -1);
 
-        inpname = g_strdelimit (g_path_get_basename (inpfile), ".", '\0');
+        inpname = g_strdelimit (g_path_get_basename (filename), ".", '\0');
         gva_game_store_index_insert (game_store, inpname, &iter);
         g_free (inpname);
 
@@ -239,7 +249,7 @@ gva_play_back_init (void)
 void
 gva_play_back_show (const gchar *inpname)
 {
-        GHashTable *hash_table;
+        GList *list;
         GvaGameStore *game_store;
         GtkTreeView *view;
         GError *error = NULL;
@@ -247,17 +257,14 @@ gva_play_back_show (const gchar *inpname)
         view = GTK_TREE_VIEW (GVA_WIDGET_PLAY_BACK_TREE_VIEW);
         game_store = GVA_GAME_STORE (gtk_tree_view_get_model (view));
 
-        hash_table = gva_mame_get_input_files (&error);
+        gva_game_store_clear (game_store);
+
+        list = gva_mame_get_input_files (&error);
         gva_error_handle (&error);
 
-        if (hash_table != NULL)
-        {
-                gva_game_store_clear (game_store);
-                g_hash_table_foreach (
-                        hash_table, (GHFunc) play_back_add_input_file,
-                        game_store);
-                g_hash_table_destroy (hash_table);
-        }
+        g_list_foreach (list, (GFunc) play_back_add_input_file, game_store);
+        g_list_foreach (list, (GFunc) g_object_unref, NULL);
+        g_list_free (list);
 
         if (inpname != NULL)
         {
diff --git a/src/gva-ui.c b/src/gva-ui.c
index 6f06b99..d877d36 100644
--- a/src/gva-ui.c
+++ b/src/gva-ui.c
@@ -64,7 +64,9 @@ static const gchar *license =
 "along with this program.  If not, see <http://www.gnu.org/licenses/>.";
 
 static void
-record_game_exited (GvaProcess *process, gint status, gchar *inpname)
+record_game_exited (GvaProcess *process,
+                    gint status,
+                    gchar *inpname)
 {
         if (process->error == NULL)
                 gva_play_back_show (inpname);
diff --git a/src/gva-util.c b/src/gva-util.c
index 23add53..f5bfa1d 100644
--- a/src/gva-util.c
+++ b/src/gva-util.c
@@ -165,7 +165,8 @@ gva_get_debug_flags (void)
                 {
                         { "mame",  GVA_DEBUG_MAME },
                         { "sql",   GVA_DEBUG_SQL },
-                        { "io",    GVA_DEBUG_IO }
+                        { "io",    GVA_DEBUG_IO },
+                        { "inp",   GVA_DEBUG_INP }
                 };
 
                 const gchar *env = g_getenv ("GVA_DEBUG");
diff --git a/src/gva-util.h b/src/gva-util.h
index 4df3c5a..e1004d0 100644
--- a/src/gva-util.h
+++ b/src/gva-util.h
@@ -41,6 +41,8 @@ G_BEGIN_DECLS
  *      Print SQL commands to the game database.
  * @GVA_DEBUG_IO:
  *      Print all communication between GVA and MAME.
+ * @GVA_DEBUG_INP:
+ *      Print information about input files.
  *
  * These flags indicate which types of debugging messages will be triggered
  * at runtime. Debugging messages can be triggered by setting the GVA_DEBUG
@@ -51,7 +53,8 @@ typedef enum
         GVA_DEBUG_NONE = 0,
         GVA_DEBUG_MAME = 1 << 0,
         GVA_DEBUG_SQL  = 1 << 1,
-        GVA_DEBUG_IO   = 1 << 2
+        GVA_DEBUG_IO   = 1 << 2,
+        GVA_DEBUG_INP  = 1 << 3
 } GvaDebugFlags;
 
 gchar *         gva_choose_inpname              (const gchar *game);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]