[gnome-games/wip/aplazas/playstation-disc-image] Parse PlayStation disc images properly
- From: Adrien Plazas <aplazas src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-games/wip/aplazas/playstation-disc-image] Parse PlayStation disc images properly
- Date: Fri, 16 Jun 2017 05:41:33 +0000 (UTC)
commit bbcce3aea8ef1a29ed7f42664e88a20c4b5008ea
Author: Adrien Plazas <kekun plazas laposte net>
Date: Fri Dec 23 06:10:22 2016 +0100
Parse PlayStation disc images properly
plugins/playstation/src/Makefile.am | 1 +
plugins/playstation/src/playstation-disc-image.c | 510 ++++++++++++++++++++++
plugins/playstation/src/playstation-header.vala | 103 +----
3 files changed, 536 insertions(+), 78 deletions(-)
---
diff --git a/plugins/playstation/src/Makefile.am b/plugins/playstation/src/Makefile.am
index 1b0067e..ca69157 100644
--- a/plugins/playstation/src/Makefile.am
+++ b/plugins/playstation/src/Makefile.am
@@ -36,6 +36,7 @@ libgames_playstation_plugin_la_DEPENDENCIES = \
libgames_playstation_plugin_la_SOURCES = \
$(BUILT_SOURCES) \
+ playstation-disc-image.c \
playstation-error.vala \
playstation-game-factory.vala \
playstation-header.vala \
diff --git a/plugins/playstation/src/playstation-disc-image.c
b/plugins/playstation/src/playstation-disc-image.c
new file mode 100644
index 0000000..be2cd1d
--- /dev/null
+++ b/plugins/playstation/src/playstation-disc-image.c
@@ -0,0 +1,510 @@
+// This file is part of GNOME Games. License: GPL-3.0+.
+
+#include <errno.h>
+#include <glib.h>
+#include <string.h>
+#include <stdio.h>
+
+////////////////////////////////////////////////////////////////////////
+// GamesDiscImageTime
+////////////////////////////////////////////////////////////////////////
+
+#define GAMES_DISC_IMAGE_FRAMES_PER_SECOND 75
+
+typedef struct {
+ guint8 minute;
+ guint8 second;
+ guint8 frame;
+} GamesDiscImageTime;
+
+typedef enum {
+ GAMES_DISC_IMAGE_ERROR_CANT_OPEN_FILE,
+} GamesPlayStationError;
+
+#define GAMES_DISC_IMAGE_ERROR games_disc_image_error_quark ()
+
+GQuark games_disc_image_error_quark (void);
+
+// FIXME What is BCD?
+static guint8
+bcd_to_integer (guint8 bcd)
+{
+ return bcd / 16 * 10 + bcd % 16;
+}
+
+// FIXME What is BCD?
+static guint8
+integer_to_bcd (guint8 integer)
+{
+ return integer / 10 * 16 + integer % 10;
+}
+
+static void
+games_disc_image_time_set_from_minute_second_frame (GamesDiscImageTime *time,
+ guint8 minute,
+ guint8 second,
+ guint8 frame)
+{
+ time->minute = minute;
+ time->second = second;
+ time->frame = frame;
+}
+
+static void
+games_disc_image_time_set_from_time_reference (GamesDiscImageTime *time,
+ guint8 *time_reference)
+{
+ gint32 block; // The value of the clock containing the target time
+ int minute, second, frame;
+
+ // Get the time reference
+ // FIXME Make these ifdefs cleaner.
+#if defined(__arm__)
+ guchar *u = (guchar *) time_reference;
+ block = (u[3] << 24) | (u[2] << 16) | (u[1] << 8) | u[0];
+#elif defined(__BIGENDIAN__)
+ block = (time_reference[0] & 0xff) | ((time_reference[1] & 0xff) << 8) | ((time_reference[2] & 0xff) <<
16) | (time_reference[3] << 24);
+#else
+ block = *((gint32 *) time_reference);
+#endif
+
+ block += 2 * GAMES_DISC_IMAGE_FRAMES_PER_SECOND;
+ minute = block / (60 * GAMES_DISC_IMAGE_FRAMES_PER_SECOND);
+ block = block - minute * (60 * GAMES_DISC_IMAGE_FRAMES_PER_SECOND);
+ second = block / GAMES_DISC_IMAGE_FRAMES_PER_SECOND;
+ frame = block - second * GAMES_DISC_IMAGE_FRAMES_PER_SECOND;
+
+ minute = ((minute / 10) << 4) | minute % 10;
+ second = ((second / 10) << 4) | second % 10;
+ frame = ((frame / 10) << 4) | frame % 10;
+
+ time->minute = minute;
+ time->second = second;
+ time->frame = frame;
+}
+
+static gint
+games_disc_image_time_get_sector (const GamesDiscImageTime *time)
+{
+ return (bcd_to_integer (time->minute) * 60 + bcd_to_integer (time->second) - 2) *
GAMES_DISC_IMAGE_FRAMES_PER_SECOND + bcd_to_integer (time->frame);
+}
+
+static void
+games_disc_image_time_increment (GamesDiscImageTime *time)
+{
+ guint8 i_minute, i_second, i_frame;
+
+ i_minute = bcd_to_integer (time->minute);
+ i_second = bcd_to_integer (time->second);
+ i_frame = bcd_to_integer (time->frame);
+
+ i_frame++;
+ if (i_frame == GAMES_DISC_IMAGE_FRAMES_PER_SECOND) {
+ i_frame = 0;
+ i_second++;
+ if (i_second == 60) {
+ i_second = 0;
+ i_minute++;
+ }
+ }
+
+ time->minute = integer_to_bcd (i_minute);
+ time->second = integer_to_bcd (i_second);
+ time->frame = integer_to_bcd (i_frame);
+}
+
+////////////////////////////////////////////////////////////////////////
+// GamesDiscFrame
+////////////////////////////////////////////////////////////////////////
+
+#define GAMES_DISC_IMAGE_FRAME_SIZE 2352
+#define GAMES_DISC_IMAGE_FRAME_HEADER_SIZE 12
+
+typedef struct _GamesDiscFrameMode1 GamesDiscFrameMode1;
+struct _GamesDiscFrameMode1 {
+ guint8 synchronization[12];
+ guint8 header[12];
+ guint8 content[2048];
+ guint8 error_correction_code[280];
+};
+
+typedef struct _GamesDiscFrameMode2 GamesDiscFrameMode2;
+struct _GamesDiscFrameMode2 {
+ guint8 synchronization[12];
+ guint8 content[2340];
+};
+
+typedef union _GamesDiscFrame GamesDiscFrame;
+union _GamesDiscFrame {
+ GamesDiscFrameMode1 mode1;
+ GamesDiscFrameMode2 mode2;
+};
+
+////////////////////////////////////////////////////////////////////////
+// GamesDiscFileInfo
+////////////////////////////////////////////////////////////////////////
+
+typedef struct _GamesDiscFileInfo GamesDiscFileInfo;
+struct _GamesDiscFileInfo {
+ guint8 length;
+ guint8 ext_attr_length;
+ guint8 extent[8];
+ guint8 size[8];
+ guint8 date[7];
+ guint8 flags;
+ guint8 file_unit_size;
+ guint8 interleave;
+ guint8 volume_sequence_number[4];
+ guint8 name_length;
+};
+
+typedef gboolean (*GamesDiscFileInfoForeachCallback) (const GamesDiscFileInfo *file_info, gpointer
user_data);
+
+static gboolean
+games_disc_file_info_is_directory (GamesDiscFileInfo *file_info)
+{
+ g_return_val_if_fail (file_info != NULL, FALSE);
+
+ return file_info->flags & 0x2;
+}
+
+static gboolean
+games_disc_file_info_is_valid (const GamesDiscFileInfo *file_info)
+{
+ const gsize magic_size = 47; // FIXME Magic number, I have no ida what it it but it works.
+
+ g_return_val_if_fail (file_info != NULL, FALSE);
+
+ return file_info->length >= magic_size + file_info->name_length;
+}
+
+static gchar *
+games_disc_file_info_access_name (GamesDiscFileInfo *file_info)
+{
+ g_return_val_if_fail (file_info != NULL, NULL);
+
+ return (gchar *) file_info + sizeof (GamesDiscFileInfo);
+}
+
+static gchar *
+games_disc_file_info_get_name (GamesDiscFileInfo *file_info)
+{
+ g_return_val_if_fail (file_info != NULL, NULL);
+
+ return g_strndup (games_disc_file_info_access_name (file_info), file_info->name_length);
+}
+
+static GamesDiscFileInfo *
+games_disc_file_info_get_next (const GamesDiscFileInfo *file_info)
+{
+ g_return_val_if_fail (file_info != NULL, NULL);
+
+ if (!games_disc_file_info_is_valid (file_info))
+ return NULL;
+
+ return (GamesDiscFileInfo *) ((gpointer) file_info + file_info->length);
+}
+
+static void
+games_disc_file_info_foreach_file (const GamesDiscFileInfo *file_info,
+ gsize size,
+ GamesDiscFileInfoForeachCallback callback,
+ gpointer user_data)
+{
+ const GamesDiscFileInfo *current;
+ GamesDiscFileInfo *next;
+
+ g_return_if_fail (file_info != NULL);
+
+ for (current = file_info; current != NULL && games_disc_file_info_is_valid (current); current =
games_disc_file_info_get_next (current)) {
+ // The file info should never go beyond the end of the buffer.
+ if ((gpointer) current - (gpointer) file_info + sizeof (GamesDiscFileInfo) >= size ||
+ (gpointer) current - (gpointer) file_info + current->length >= size)
+ break;
+ // FIXME Why is that commented?
+/* for (current = file_info; games_disc_file_info_is_valid (current); current = next) {*/
+/* // If the next starts after the expected buffer size, then the current is somehow invalid.*/
+/* // Try to avoid segfault if the format is unexpected.*/
+/* next = games_disc_file_info_get_next (current);*/
+/* if (next != NULL && next >= file_info + 2048) // FIXME Magic value.*/
+/* break;*/
+
+ if (!callback (current, user_data))
+ break;
+ }
+ // FIXME Why is that commented?
+/* )*/
+
+/* while (file_info->length != 0) {*/
+/* if (!callback (file_info, user_data))*/
+/* break;*/
+
+/* i += file_info->length;*/
+/* if (i )*/
+/* file_info += file_info->length;*/
+/* }*/
+}
+
+////////////////////////////////////////////////////////////////////////
+// GamesDisc
+////////////////////////////////////////////////////////////////////////
+
+struct _GamesDiscImage {
+ FILE *file_handle;
+};
+
+typedef struct _GamesDiscImage GamesDiscImage;
+
+void
+games_disc_image_open (GamesDiscImage *disc,
+ const char *filename,
+ GError **error)
+{
+ disc->file_handle = fopen (filename, "rb");
+ if (disc->file_handle == 0)
+ g_set_error (error,
+ GAMES_DISC_IMAGE_ERROR,
+ GAMES_DISC_IMAGE_ERROR_CANT_OPEN_FILE,
+ "Couldn’t open disc image file: %s",
+ g_strerror (errno));
+}
+
+gboolean
+games_disc_image_read_frame (GamesDiscImage *disc,
+ const GamesDiscImageTime *time,
+ GamesDiscFrame *frame)
+{
+ gsize read;
+ gint sector;
+
+ g_return_val_if_fail (disc != NULL, FALSE);
+ g_return_val_if_fail (time != NULL, FALSE);
+ g_return_val_if_fail (frame != NULL, FALSE);
+
+ sector = games_disc_image_time_get_sector (time);
+ // TODO Check multiplication won't overflow by dividing the type's max by sizeof (GamesDiscFrame) and
compare it to sector.
+ fseek (disc->file_handle, sector * sizeof (GamesDiscFrame), SEEK_SET);
+ // TODO Check errors.
+ read = fread (frame, 1, sizeof (GamesDiscFrame), disc->file_handle);
+ // TODO Check errors.
+
+ return read == sizeof (GamesDiscFrame);
+}
+
+gboolean
+games_disc_image_read_directory (GamesDiscImage *disc,
+ GamesDiscImageTime *time,
+ guint8 *dst)
+{
+ gsize read;
+ gint sector;
+
+ sector = games_disc_image_time_get_sector(time);
+ fseek(disc->file_handle, sector * GAMES_DISC_IMAGE_FRAME_SIZE + GAMES_DISC_IMAGE_FRAME_HEADER_SIZE + 12,
SEEK_SET);
+ read = fread (dst, 1, 2048, disc->file_handle);
+ if (read == -1)
+ return FALSE;
+
+ games_disc_image_time_increment (time); // FIXME We should either always incerment it or never increment
it.
+
+ sector = games_disc_image_time_get_sector(time);
+ fseek(disc->file_handle, sector * GAMES_DISC_IMAGE_FRAME_SIZE + GAMES_DISC_IMAGE_FRAME_HEADER_SIZE + 12,
SEEK_SET);
+ read = fread (dst + 2048, 1, 2048, disc->file_handle);
+ if (read == -1)
+ return FALSE;
+
+ return TRUE;
+}
+
+typedef struct {
+ const gchar *filename;
+ GamesDiscImageTime *time;
+ gboolean is_dir;
+ gboolean found;
+} GetFileData;
+
+static gboolean
+get_file_co (GamesDiscFileInfo *file_info,
+ gpointer user_data)
+{
+ GetFileData *data = (GetFileData *) user_data;
+
+ if (games_disc_file_info_is_directory (file_info)) {
+ if (g_ascii_strncasecmp (games_disc_file_info_access_name (file_info), data->filename,
file_info->name_length) == 0) {
+ if (data->filename[file_info->name_length] != '\\')
+ return TRUE;
+
+ data->filename += file_info->name_length + 1;
+
+ games_disc_image_time_set_from_time_reference (data->time, file_info->extent);
+ data->is_dir = TRUE;
+ data->found = TRUE;
+
+ return FALSE;
+ }
+ }
+ else {
+ if (g_ascii_strncasecmp (games_disc_file_info_access_name (file_info), data->filename, strlen
(data->filename)) == 0) {
+ games_disc_image_time_set_from_time_reference (data->time, file_info->extent);
+ data->is_dir = FALSE;
+ data->found = TRUE;
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean
+games_disc_image_get_file (const GamesDiscImage *disc,
+ const GamesDiscFileInfo *file_info,
+ const gchar *filename,
+ GamesDiscImageTime *time)
+{
+ guint8 ddir[4096];
+ GetFileData data = { 0 };
+ data.filename = filename;
+ data.time = time;
+ data.is_dir = TRUE;
+ data.found = FALSE;
+
+ g_return_val_if_fail (filename != NULL, FALSE);
+
+ while (data.is_dir) {
+ data.filename = filename;
+ data.time = time;
+ data.is_dir = FALSE;
+ data.found = FALSE;
+
+ games_disc_file_info_foreach_file (file_info, 4096, get_file_co, &data);
+
+ if (data.found && data.is_dir) {
+ if (!games_disc_image_read_directory (disc, time, ddir))
+ return FALSE;
+
+ file_info = (GamesDiscFileInfo *) ddir;
+
+ break; // Parse the sub directory.
+ }
+ }
+
+ return data.found;
+}
+
+////////////////////////////////////////////////////////////////////////
+// PlayStation
+////////////////////////////////////////////////////////////////////////
+
+typedef struct _PlayStationGamesDiscInfo {
+ gchar *label;
+ gchar *exe;
+} PlayStationGamesDiscInfo;
+
+gboolean
+games_disc_image_get_playstation_info (GamesDiscImage *disc,
+ PlayStationGamesDiscInfo *games_disc_image_info)
+{
+ gchar label_buffer[33] = "";
+
+ GamesDiscFileInfo *dir;
+ GamesDiscImageTime time;
+ guchar mdir[4096];
+ gchar exe_buffer[256];
+ gint i, len, c;
+
+ GamesDiscFrame frame;
+
+ games_disc_image_time_set_from_minute_second_frame (&time, integer_to_bcd (0), integer_to_bcd (2),
integer_to_bcd (0x10));
+ if (!games_disc_image_read_frame (disc, &time, &frame))
+ return FALSE;
+
+ memset (label_buffer, 0, sizeof (label_buffer));
+ memset (exe_buffer, 0, sizeof (exe_buffer));
+
+ strncpy (label_buffer, (const char *) frame.mode2.content + 52, 32);
+
+ // Skip head and sub, and go to the root directory record
+ dir = (GamesDiscFileInfo *) (frame.mode1.content + 156);
+
+ games_disc_image_time_set_from_time_reference (&time, dir->extent);
+
+ if (!games_disc_image_read_directory (disc, &time, mdir))
+ return FALSE;
+
+ if (games_disc_image_get_file (disc, (GamesDiscFileInfo *) mdir, "SYSTEM.CNF;1", &time)) {
+ if (!games_disc_image_read_frame (disc, &time, &frame))
+ return FALSE;
+
+ sscanf ((char *) frame.mode1.content, "BOOT = cdrom:\\%255s", exe_buffer);
+ if (!games_disc_image_get_file (disc, (GamesDiscFileInfo *) mdir, exe_buffer, &time)) {
+ sscanf ((char *) frame.mode1.content, "BOOT = cdrom:%255s", exe_buffer);
+ if (!games_disc_image_get_file (disc, (GamesDiscFileInfo *) mdir, exe_buffer, &time)) {
+ char *ptr = strstr((char *) frame.mode1.content, "cdrom:"); // Possibly the executable is in some
subdir.
+ if (ptr == NULL)
+ return FALSE;
+
+ // Skip "cdrom:".
+ ptr += 6;
+
+ // Skip slashes.
+ while (*ptr == '\\' || *ptr == '/')
+ ptr++;
+
+ strncpy (exe_buffer, ptr, 255);
+ exe_buffer[255] = '\0';
+ ptr = exe_buffer;
+
+ // Keep only the first line.
+ while (*ptr != '\0' && *ptr != '\r' && *ptr != '\n')
+ ptr++;
+ *ptr = '\0';
+
+ if (!games_disc_image_get_file (disc, (GamesDiscFileInfo *) mdir, exe_buffer, &time))
+ return FALSE; // Executable not found.
+ }
+ }
+ }
+ else if (games_disc_image_get_file (disc, (GamesDiscFileInfo *) mdir, "PSX.EXE;1", &time))
+ strcpy (exe_buffer, "PSX.EXE;1");
+ else
+ return FALSE; // SYSTEM.CNF and PSX.EXE not found
+
+ if (games_disc_image_info != NULL) {
+ games_disc_image_info->label = strndup (label_buffer, sizeof (label_buffer));
+ games_disc_image_info->exe = strndup (exe_buffer, sizeof (exe_buffer));
+ }
+
+ return TRUE;
+}
+
+gboolean
+get_playstation_info (const gchar *image_filename,
+ gchar **label,
+ gchar **exe)
+{
+ GError *error = NULL;
+ GamesDiscImage disc = { 0 };
+
+ games_disc_image_open (&disc, image_filename, &error);
+ if (error != NULL) {
+ g_debug ("%s", error->message);
+ g_clear_error (&error);
+
+ return FALSE;
+ }
+
+ PlayStationGamesDiscInfo games_disc_image_info = { 0 };
+ if (!games_disc_image_get_playstation_info (&disc, &games_disc_image_info))
+ return FALSE;
+
+ *label = games_disc_image_info.label;
+ *exe = games_disc_image_info.exe;
+
+ return TRUE;
+}
+
+GQuark games_disc_image_error_quark (void)
+{
+ return g_quark_from_static_string ("games-disc-image-error-quark");
+}
diff --git a/plugins/playstation/src/playstation-header.vala b/plugins/playstation/src/playstation-header.vala
index 19d1275..ff3eb12 100644
--- a/plugins/playstation/src/playstation-header.vala
+++ b/plugins/playstation/src/playstation-header.vala
@@ -1,24 +1,7 @@
// This file is part of GNOME Games. License: GPL-3.0+.
private class Games.PlayStationHeader : Object {
- private const size_t[] HEADER_OFFSETS = {
- 0x85D2, // .bin.ecm
- 0x9320, // .bin
- 0x9360, // .iso
- };
- private const size_t HEADER_TITLE_OFFSET = 0x20;
- private const string HEADER_MAGIC_VALUE = "PLAYSTATION";
-
- private const size_t[] BOOT_OFFSETS = {
- 0xBE64, // .bin.ecm
- 0xD368, // .bin
- 0xD3A8, // .iso
- 0xA9F98, // .bin
- };
- private const string BOOT_MAGIC_VALUE = "BOOT";
-
// The ID prefixes must always be in uppercase.
- private const string[] IDS = { "SLUS", "SCUS", "SLES", "SCES", "SLPS", "SLPM", "SCPS" };
private const size_t DISC_ID_SIZE = 10;
private static Regex disc_id_regex;
@@ -38,83 +21,44 @@ private class Games.PlayStationHeader : Object {
if (_disc_id != null)
return;
- _disc_id = get_id_from_boot ();
+ string label;
+ string exe;
+ get_playstation_info (file.get_path (), out label, out exe);
+
+ _disc_id = parse_id_from_exe (exe);
if (_disc_id != null)
return;
- _disc_id = search_id_in_header ();
+ _disc_id = parse_id_from_label (label);
if (_disc_id != null)
return;
throw new PlayStationError.INVALID_HEADER (_("Invalid PlayStation header: disc ID not found
in “%s”."), file.get_uri ());
}
- private string? search_id_in_header () throws Error {
- var offset = get_header_offset ();
- if (offset == null)
- return null;
-
- var stream = new StringInputStream (file);
- var header = stream.read_string_for_size (offset + HEADER_TITLE_OFFSET, DISC_ID_SIZE);
-
- var raw_id = header.up ();
- raw_id = raw_id.replace ("_", "-");
-
- foreach (var id in IDS) {
- if (!(id in header))
- continue;
-
- if (is_a_disc_id (raw_id))
- return raw_id;
- }
-
- return null;
- }
-
- private size_t? get_header_offset () throws Error {
- var stream = new StringInputStream (file);
+ private string? parse_id_from_exe (string exe) throws Error {
+ var disc_id = exe.strip ();
+ disc_id = disc_id.split (";")[0];
+ disc_id = disc_id.replace ("_", "-");
+ disc_id = disc_id.replace (".", "");
+ disc_id = disc_id.up ();
- foreach (var offset in HEADER_OFFSETS)
- if (stream.has_string (offset, HEADER_MAGIC_VALUE))
- return offset;
-
- return null;
- }
-
- private string? get_id_from_boot () throws Error {
- var offset = get_boot_offset ();
- if (offset == null)
+ if (!is_a_disc_id (disc_id))
return null;
- var stream = new StringInputStream (file);
- var header = stream.read_string (offset);
- header = header.up ();
-
- foreach (var id in IDS) {
- if (!(id in header))
- continue;
-
- var raw_id = header.split (id)[1];
- raw_id = raw_id.split (";")[0];
- raw_id = raw_id.replace ("_", "-");
- raw_id = raw_id.replace (".", "");
- raw_id = (id + raw_id).up ();
-
- if (is_a_disc_id (raw_id))
- return raw_id;
- }
-
- return null;
+ return disc_id;
}
- private size_t? get_boot_offset () throws Error {
- var stream = new StringInputStream (file);
+ private string? parse_id_from_label (string label) throws Error {
+ var disc_id = label.strip ();
+ disc_id = disc_id.replace ("_", "-");
+ disc_id = disc_id.strip ();
+ disc_id = disc_id.up ();
- foreach (var offset in BOOT_OFFSETS)
- if (stream.has_string (offset, BOOT_MAGIC_VALUE))
- return offset;
+ if (!is_a_disc_id (disc_id))
+ return null;
- return null;
+ return disc_id;
}
private static bool is_a_disc_id (string disc_id) {
@@ -123,4 +67,7 @@ private class Games.PlayStationHeader : Object {
return disc_id_regex.match (disc_id);
}
+
+ [CCode (cname = "get_playstation_info")]
+ private static extern bool get_playstation_info (string filename, out string label, out string exe);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]