+static bool dumper_cancel_requested (dumper *dmp) {
+ if (!dmp || !dmp -> cancel)
+ return false;
+ if (!dmp -> cancel (dmp -> cancel_data))
+ return false;
+ dmp -> cancelled = true;
+ return true;
+}
+
+static int dumper_xbox_ref_cancel_requested (void *cancel_data) {
+ return dumper_cancel_requested ((dumper *) cancel_data) ? 1 : 0;
+}
+
+static double xbox_portable_now_seconds (void) {
+ struct timeval now;
+ gettimeofday (&now, NULL);
+ return (double) now.tv_sec + ((double) now.tv_usec / 1000000.0);
+}
+
+static u_int64_t xbox_portable_file_size (const char *path) {
+ FILE *file;
+ my_off_t position;
+
+ if (!path || path[0] == '\0')
+ return 0;
+ file = fopen (path, "rb");
+ if (!file)
+ return 0;
+ if (my_fseek (file, 0, SEEK_END) != 0) {
+ fclose (file);
+ return 0;
+ }
+ position = my_ftell (file);
+ fclose (file);
+ if (position < 0)
+ return 0;
+ return (u_int64_t) position;
+}
+
+static void xbox_portable_copy_string (char *destination, size_t destination_size, const char *source) {
+ if (!destination || destination_size == 0)
+ return;
+ if (!source)
+ source = "";
+ snprintf (destination, destination_size, "%s", source);
+}
+
+static void xbox_portable_result_begin (dumper *dmp, const char *path, char mode) {
+ if (!dmp)
+ return;
+ xbox_ref_dump_result_init (&(dmp -> xbox_ref_result));
+ dmp -> xbox_ref_result.attempted = 1;
+ dmp -> xbox_ref_result.mode = mode;
+ xbox_portable_copy_string (dmp -> xbox_ref_result.output_path,
+ sizeof (dmp -> xbox_ref_result.output_path), path);
+}
+
+static void xbox_portable_sanitize_filename_component (char *value) {
+ size_t read_index;
+ size_t write_index;
+ bool previous_space;
+
+ if (!value)
+ return;
+
+ write_index = 0;
+ previous_space = true;
+ for (read_index = 0; value[read_index] != '\0'; ++read_index) {
+ unsigned char c = (unsigned char) value[read_index];
+ bool forbidden = c < 32 || c == '"' || c == '*' || c == '/' ||
+ c == ':' || c == '<' || c == '>' || c == '?' || c == '\\' || c == '|';
+
+ if (forbidden)
+ continue;
+ if (c == ' ') {
+ if (previous_space)
+ continue;
+ value[write_index++] = ' ';
+ previous_space = true;
+ } else {
+ value[write_index++] = (char) c;
+ previous_space = false;
+ }
+ }
+
+ while (write_index > 0 && (value[write_index - 1] == ' ' ||
+ value[write_index - 1] == '.'))
+ --write_index;
+ value[write_index] = '\0';
+}
+
+static bool xbox_portable_resolve_output_path (dumper *dmp, bool xiso_mode) {
+ xbox_portable_metadata metadata;
+ char title[XBOX_PORTABLE_TITLE_MAX];
+ char path[XBOX_REF_RESULT_PATH_MAX];
+ char **target;
+ const char *extension;
+
+ if (!dmp)
+ return false;
+
+ target = xiso_mode ? &(dmp -> outfile_xiso) : &(dmp -> outfile_iso);
+ if (!*target || (*target)[0] != '\0')
+ return true;
+
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX-PORTABLE] Resolving the default output filename from the unlocked XBE/DMI view.\n");
+ disc_xbox_wait_ready (dmp -> dsk, 30000);
+ if (disc_xbox_unlock (dmp -> dsk) < 0)
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX-PORTABLE][WARN] Filename preflight unlock returned failure; attempting metadata reads anyway.\n");
+ disc_xbox_refresh_volume (dmp -> dsk);
+ disc_xbox_wait_ready (dmp -> dsk, 30000);
+
+ xbox_portable_metadata_init (&metadata);
+ if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
+ error ("Could not derive an Xbox output filename because XBE/DMI metadata was unavailable; provide an explicit output path");
+ return false;
+ }
+
+ xbox_portable_copy_string (title, sizeof (title),
+ metadata.title[0] ? metadata.title : "Xbox DVD");
+ xbox_portable_sanitize_filename_component (title);
+ if (!title[0])
+ xbox_portable_copy_string (title, sizeof (title), "Xbox DVD");
+ extension = xiso_mode ? ".xiso" : ".iso";
+
+ if (metadata.media_id[0])
+ snprintf (path, sizeof (path), "%s[%s]%s", title, metadata.media_id, extension);
+ else
+ snprintf (path, sizeof (path), "%s%s", title, extension);
+
+ my_free (*target);
+ my_strdup (*target, path);
+ xbox_ref_log_retarget (*target);
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX-PORTABLE] Derived output filename: %s\n", *target);
+ return true;
+}
+
+static void xbox_portable_result_capture_metadata (dumper *dmp) {
+ xbox_portable_metadata metadata;
+
+ if (!dmp || (dmp -> xbox_ref_result.title[0] &&
+ dmp -> xbox_ref_result.have_game_region))
+ return;
+
+ xbox_portable_metadata_init (&metadata);
+ if (!xbox_portable_read_metadata_from_disc (dmp -> dsk, &metadata)) {
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX-PORTABLE][WARN] XBE title/region metadata was not available from the unlocked view.\n");
+ return;
+ }
+
+ xbox_portable_copy_string (dmp -> xbox_ref_result.title,
+ sizeof (dmp -> xbox_ref_result.title), metadata.title);
+ xbox_portable_copy_string (dmp -> xbox_ref_result.media_id,
+ sizeof (dmp -> xbox_ref_result.media_id), metadata.media_id);
+ xbox_portable_copy_string (dmp -> xbox_ref_result.region,
+ sizeof (dmp -> xbox_ref_result.region), metadata.region);
+ dmp -> xbox_ref_result.have_game_region = metadata.have_game_region;
+ dmp -> xbox_ref_result.game_region = metadata.game_region;
+
+ xbox_ref_log_fprintf (stderr,
+ "[XBOX-PORTABLE] XBE metadata: title='%s', media_id='%s', region='%s', GameRegion=0x%08x.\n",
+ dmp -> xbox_ref_result.title[0] ? dmp -> xbox_ref_result.title : "(unknown)",
+ dmp -> xbox_ref_result.media_id[0] ? dmp -> xbox_ref_result.media_id : "(unknown)",
+ dmp -> xbox_ref_result.region[0] ? dmp -> xbox_ref_result.region : "(unknown)",
+ dmp -> xbox_ref_result.game_region);
+}
+
+static void xbox_portable_result_finish (dumper *dmp,
+ bool success,
+ u_int32_t expected_output_sectors,
+ double started_seconds,
+ bool xiso_mode) {
+ xbox_ref_dump_result *result;
+ const char *crc32;
+ const char *md5;
+ const char *sha1;
+ const char *sha256;
+
+ if (!dmp || !(dmp -> xbox_ref_result).attempted)
+ return;
+
+ result = &(dmp -> xbox_ref_result);
+ result -> dump_success = success ? 1 : 0;
+ result -> cancelled = dmp -> cancelled ? 1 : 0;
+ result -> output_sectors = expected_output_sectors;
+ result -> output_size = xbox_portable_file_size (result -> output_path);
+ result -> completed_sectors = (u_int32_t) (result -> output_size / SECTOR_SIZE);
+ result -> elapsed_seconds = xbox_portable_now_seconds () - started_seconds;
+
+ if (!dmp -> hashing || result -> output_size == 0)
+ return;
+
+ if (xiso_mode) {
+ crc32 = dumper_get_xiso_crc32 (dmp);
+ md5 = dumper_get_xiso_md5 (dmp);
+ sha1 = dumper_get_xiso_sha1 (dmp);
+ sha256 = dumper_get_xiso_sha2 (dmp);
+ } else {
+ crc32 = dumper_get_iso_crc32 (dmp);
+ md5 = dumper_get_iso_md5 (dmp);
+ sha1 = dumper_get_iso_sha1 (dmp);
+ sha256 = dumper_get_iso_sha2 (dmp);
+ }
+
+ if (!crc32 || !md5 || !sha1 || !sha256 ||
+ !crc32[0] || !md5[0] || !sha1[0] || !sha256[0])
+ return;
+
+ xbox_portable_copy_string (result -> crc32, sizeof (result -> crc32), crc32);
+ xbox_portable_copy_string (result -> md5, sizeof (result -> md5), md5);
+ xbox_portable_copy_string (result -> sha1, sizeof (result -> sha1), sha1);
+ xbox_portable_copy_string (result -> sha256, sizeof (result -> sha256), sha256);
+ result -> hashes_complete = 1;
+}
+