mirror of
https://github.com/aaru-dps/libaaruformat.git
synced 2026-07-08 18:06:18 +00:00
Add PS3 disc image conversion command with key resolution and metadata extraction
This commit is contained in:
@@ -34,6 +34,10 @@ add_executable(aaruformattool
|
||||
sfo.h
|
||||
iso9660_mini.c
|
||||
iso9660_mini.h
|
||||
convert_ps3.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/aes128.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_crypto.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../src/ps3/ps3_encryption_map.c
|
||||
termbox2.h
|
||||
)
|
||||
|
||||
|
||||
@@ -43,5 +43,7 @@ int cli_compare(const char *path1, const char *path2, bool use_long);
|
||||
int convert(const char *input_path, const char *output_path, bool use_long);
|
||||
int upgrade_ddt_to_alpha21(const char *path);
|
||||
int inject_media_tag(const char *tag_type, const char *media_tag_file, const char *image_file);
|
||||
int convert_ps3(const char *input_path, const char *output_path, const char *disc_key_hex, const char *data1_key_hex,
|
||||
const char *ird_path);
|
||||
|
||||
#endif // LIBAARUFORMAT_TOOL_AARUFORMATTOOL_H_
|
||||
|
||||
@@ -215,6 +215,34 @@ int cmd_inject_media_tag(int argc, char *argv[])
|
||||
return result;
|
||||
}
|
||||
|
||||
int cmd_convert_ps3(int argc, char *argv[])
|
||||
{
|
||||
struct arg_str *input_filename = arg_str1(NULL, NULL, "<input>", "Input ISO or AaruFormat image");
|
||||
struct arg_str *output_filename = arg_str1(NULL, NULL, "<output>", "Output AaruFormat image");
|
||||
struct arg_str *disc_key_arg = arg_str0(NULL, "disc-key", "<hex>", "32-char hex disc key");
|
||||
struct arg_str *data1_key_arg = arg_str0(NULL, "data1-key", "<hex>", "32-char hex data1 key");
|
||||
struct arg_str *ird_arg = arg_str0(NULL, "ird", "<path>", "Path to IRD file");
|
||||
struct arg_end *end = arg_end(10);
|
||||
void *argtable[] = {input_filename, output_filename, disc_key_arg, data1_key_arg, ird_arg, end};
|
||||
|
||||
if(arg_parse(argc, argv, argtable) > 0)
|
||||
{
|
||||
arg_print_errors(stderr, end, "convert-ps3");
|
||||
usage_convert_ps3();
|
||||
arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *disc_key_hex = disc_key_arg->count > 0 ? disc_key_arg->sval[0] : NULL;
|
||||
const char *data1_key_hex = data1_key_arg->count > 0 ? data1_key_arg->sval[0] : NULL;
|
||||
const char *ird_path = ird_arg->count > 0 ? ird_arg->sval[0] : NULL;
|
||||
|
||||
const int result =
|
||||
convert_ps3(input_filename->sval[0], output_filename->sval[0], disc_key_hex, data1_key_hex, ird_path);
|
||||
arg_freetable(argtable, sizeof(argtable) / sizeof(argtable[0]));
|
||||
return result;
|
||||
}
|
||||
|
||||
Command commands[] = {
|
||||
{ "identify", cmd_identify},
|
||||
{ "info", cmd_info},
|
||||
@@ -227,6 +255,7 @@ Command commands[] = {
|
||||
{ "convert", cmd_convert},
|
||||
{"upgrade-ddt-to-alpha21", cmd_upgrade_ddt_to_alpha21},
|
||||
{ "inject-media-tag", cmd_inject_media_tag},
|
||||
{ "convert-ps3", cmd_convert_ps3},
|
||||
};
|
||||
|
||||
const size_t num_commands = sizeof(commands) / sizeof(commands[0]);
|
||||
|
||||
@@ -43,5 +43,6 @@ int cmd_cli_compare(int argc, char *argv[]);
|
||||
int cmd_convert(int argc, char *argv[]);
|
||||
int cmd_upgrade_ddt_to_alpha21(int argc, char *argv[]);
|
||||
int cmd_inject_media_tag(int argc, char *argv[]);
|
||||
int cmd_convert_ps3(int argc, char *argv[]);
|
||||
|
||||
#endif // LIBAARUFORMAT_COMMANDS_H
|
||||
|
||||
932
tool/convert_ps3.c
Normal file
932
tool/convert_ps3.c
Normal file
@@ -0,0 +1,932 @@
|
||||
/*
|
||||
* This file is part of the Aaru Data Preservation Suite.
|
||||
* Copyright (c) 2019-2026 Natalia Portillo.
|
||||
*
|
||||
* This program 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.
|
||||
*
|
||||
* This program 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, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*
|
||||
* convert-ps3 command: converts ISO or AaruFormat PS3 disc images to AaruFormat
|
||||
* with decrypted sector storage and proper media tags/metadata.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include <aaruformat.h>
|
||||
|
||||
#include "aaruformattool.h"
|
||||
#include "ird.h"
|
||||
#include "iso9660_mini.h"
|
||||
#include "sfo.h"
|
||||
|
||||
#include "../src/ps3/ps3_crypto.h"
|
||||
#include "../src/ps3/ps3_encryption_map.h"
|
||||
|
||||
/* ANSI color codes */
|
||||
#define ANSI_RESET "\033[0m"
|
||||
#define ANSI_BOLD "\033[1m"
|
||||
#define ANSI_RED "\033[31m"
|
||||
#define ANSI_GREEN "\033[32m"
|
||||
#define ANSI_YELLOW "\033[33m"
|
||||
#define ANSI_CYAN "\033[36m"
|
||||
#define ANSI_WHITE "\033[37m"
|
||||
#define ANSI_BLUE "\033[34m"
|
||||
|
||||
#define PROGRESS_BAR_WIDTH 40
|
||||
#define PS3_SECTOR_SIZE 2048
|
||||
|
||||
static void print_error_ps3(const char *msg) { fprintf(stderr, ANSI_RED " ✗ %s" ANSI_RESET "\n", msg); }
|
||||
|
||||
static void print_success_ps3(const char *msg) { printf(ANSI_GREEN " ✓ %s" ANSI_RESET "\n", msg); }
|
||||
|
||||
static void print_info_ps3(const char *label, const char *value)
|
||||
{ printf(" " ANSI_YELLOW "%-20s" ANSI_RESET " %s\n", label, value); }
|
||||
|
||||
static void format_bytes_ps3(uint64_t bytes, char *buffer, size_t buffer_size)
|
||||
{
|
||||
const char *units[] = {"B", "KiB", "MiB", "GiB", "TiB"};
|
||||
int unit_idx = 0;
|
||||
double size = (double)bytes;
|
||||
|
||||
while(size >= 1024.0 && unit_idx < 4)
|
||||
{
|
||||
size /= 1024.0;
|
||||
unit_idx++;
|
||||
}
|
||||
|
||||
if(unit_idx == 0)
|
||||
snprintf(buffer, buffer_size, "%llu %s", (unsigned long long)bytes, units[unit_idx]);
|
||||
else
|
||||
snprintf(buffer, buffer_size, "%.2f %s", size, units[unit_idx]);
|
||||
}
|
||||
|
||||
static int parse_hex_key(const char *hex, uint8_t key[16])
|
||||
{
|
||||
if(strlen(hex) != 32) return -1;
|
||||
|
||||
for(int i = 0; i < 16; i++)
|
||||
{
|
||||
unsigned int byte;
|
||||
|
||||
if(sscanf(hex + i * 2, "%2x", &byte) != 1) return -1;
|
||||
|
||||
key[i] = (uint8_t)byte;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int try_sidecar_file(const char *input_path, const char *suffix, uint8_t *out, size_t expected_len)
|
||||
{
|
||||
size_t path_len = strlen(input_path) + strlen(suffix) + 1;
|
||||
char *path = malloc(path_len);
|
||||
|
||||
if(path == NULL) return -1;
|
||||
|
||||
/* First try: append suffix (e.g., "input.iso.disc_key") */
|
||||
snprintf(path, path_len, "%s%s", input_path, suffix);
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
|
||||
/* Second try: replace extension (e.g., "input.disc_key") */
|
||||
if(f == NULL)
|
||||
{
|
||||
const char *dot = strrchr(input_path, '.');
|
||||
const char *sep = strrchr(input_path, '/');
|
||||
|
||||
/* Only replace if dot is after last path separator (i.e., it's a file extension) */
|
||||
if(dot != NULL && (sep == NULL || dot > sep))
|
||||
{
|
||||
size_t base_len = (size_t)(dot - input_path);
|
||||
size_t new_len = base_len + strlen(suffix) + 1;
|
||||
char *alt_path = malloc(new_len);
|
||||
|
||||
if(alt_path != NULL)
|
||||
{
|
||||
memcpy(alt_path, input_path, base_len);
|
||||
memcpy(alt_path + base_len, suffix, strlen(suffix) + 1);
|
||||
f = fopen(alt_path, "rb");
|
||||
free(alt_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
free(path);
|
||||
|
||||
if(f == NULL) return -1;
|
||||
|
||||
size_t read = fread(out, 1, expected_len, f);
|
||||
fclose(f);
|
||||
|
||||
return read == expected_len ? 0 : -1;
|
||||
}
|
||||
|
||||
/* ISO sector read callback for iso9660_read_file */
|
||||
typedef struct
|
||||
{
|
||||
FILE *fp;
|
||||
uint64_t total_sectors;
|
||||
} IsoReadCtx;
|
||||
|
||||
static int32_t iso_read_sector_cb(void *user_data, uint64_t sector, uint8_t *buffer)
|
||||
{
|
||||
IsoReadCtx *ctx = (IsoReadCtx *)user_data;
|
||||
|
||||
if(sector >= ctx->total_sectors) return -1;
|
||||
|
||||
if(fseek(ctx->fp, (long)(sector * PS3_SECTOR_SIZE), SEEK_SET) != 0) return -1;
|
||||
|
||||
if(fread(buffer, 1, PS3_SECTOR_SIZE, ctx->fp) != PS3_SECTOR_SIZE) return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* AaruFormat sector read callback for iso9660_read_file */
|
||||
typedef struct
|
||||
{
|
||||
aaruformat_context *ctx;
|
||||
} AarufReadCtx;
|
||||
|
||||
static int32_t aaruf_read_sector_cb(void *user_data, uint64_t sector, uint8_t *buffer)
|
||||
{
|
||||
AarufReadCtx *ctx = (AarufReadCtx *)user_data;
|
||||
uint32_t length = PS3_SECTOR_SIZE;
|
||||
uint8_t status = 0;
|
||||
|
||||
int32_t ret = aaruf_read_sector(ctx->ctx, sector, false, buffer, &length, &status);
|
||||
|
||||
return ret == AARUF_STATUS_OK ? 0 : -1;
|
||||
}
|
||||
|
||||
int convert_ps3(const char *input_path, const char *output_path, const char *disc_key_hex, const char *data1_key_hex,
|
||||
const char *ird_path)
|
||||
{
|
||||
int result = 0;
|
||||
char buffer[256];
|
||||
uint8_t disc_key[16];
|
||||
bool have_disc_key = false;
|
||||
uint8_t data1_key[16];
|
||||
bool have_data1 = false;
|
||||
IrdFile ird;
|
||||
bool have_ird = false;
|
||||
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN "════════════════════════════════════════════════════════════════════════════════\n"
|
||||
" PS3 DISC IMAGE CONVERTER\n"
|
||||
"════════════════════════════════════════════════════════════════════════════════" ANSI_RESET "\n");
|
||||
|
||||
memset(&ird, 0, sizeof(ird));
|
||||
|
||||
/* ── Step 1: Resolve key material ────────────────────────────── */
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Key Resolution" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
/* 1a: --disc-key */
|
||||
if(disc_key_hex != NULL)
|
||||
{
|
||||
if(parse_hex_key(disc_key_hex, disc_key) == 0)
|
||||
{
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key provided via --disc-key");
|
||||
}
|
||||
else
|
||||
print_error_ps3("Invalid --disc-key format (expected 32 hex chars)");
|
||||
}
|
||||
|
||||
/* 1b: --data1-key */
|
||||
if(!have_disc_key && data1_key_hex != NULL)
|
||||
{
|
||||
if(parse_hex_key(data1_key_hex, data1_key) == 0)
|
||||
{
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from --data1-key");
|
||||
}
|
||||
else
|
||||
print_error_ps3("Invalid --data1-key format (expected 32 hex chars)");
|
||||
}
|
||||
|
||||
/* 1c: --ird */
|
||||
if(ird_path != NULL)
|
||||
{
|
||||
if(ps3_parse_ird(ird_path, &ird) == 0 && ird.valid)
|
||||
{
|
||||
have_ird = true;
|
||||
snprintf(buffer, sizeof(buffer), "IRD loaded: %s — %s (v%d)", ird.game_id, ird.game_name, ird.version);
|
||||
print_success_ps3(buffer);
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
memcpy(data1_key, ird.d1, 16);
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from IRD data1");
|
||||
}
|
||||
}
|
||||
else
|
||||
print_error_ps3("Cannot parse IRD file");
|
||||
}
|
||||
|
||||
/* 1d: Sidecar files */
|
||||
if(!have_disc_key)
|
||||
{
|
||||
if(try_sidecar_file(input_path, ".disc_key", disc_key, 16) == 0)
|
||||
{
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key loaded from sidecar .disc_key file");
|
||||
}
|
||||
}
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
if(try_sidecar_file(input_path, ".data1", data1_key, 16) == 0)
|
||||
{
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from sidecar .data1 file");
|
||||
}
|
||||
}
|
||||
|
||||
if(!have_disc_key || !have_ird)
|
||||
{
|
||||
/* Try sidecar .ird — both "input.iso.ird" and "input.ird" */
|
||||
size_t ird_sidecar_len = strlen(input_path) + 5;
|
||||
char *ird_sidecar = malloc(ird_sidecar_len);
|
||||
|
||||
if(ird_sidecar != NULL)
|
||||
{
|
||||
/* First: append .ird to full path */
|
||||
snprintf(ird_sidecar, ird_sidecar_len, "%s.ird", input_path);
|
||||
|
||||
if(!have_ird && ps3_parse_ird(ird_sidecar, &ird) == 0 && ird.valid)
|
||||
{
|
||||
have_ird = true;
|
||||
snprintf(buffer, sizeof(buffer), "Sidecar IRD loaded: %s — %s", ird.game_id, ird.game_name);
|
||||
print_success_ps3(buffer);
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
memcpy(data1_key, ird.d1, 16);
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from sidecar IRD data1");
|
||||
}
|
||||
}
|
||||
|
||||
free(ird_sidecar);
|
||||
}
|
||||
|
||||
/* Second: replace extension with .ird (e.g., "input.iso" → "input.ird") */
|
||||
if(!have_ird)
|
||||
{
|
||||
const char *dot = strrchr(input_path, '.');
|
||||
const char *sep = strrchr(input_path, '/');
|
||||
|
||||
if(dot != NULL && (sep == NULL || dot > sep))
|
||||
{
|
||||
size_t base_len = (size_t)(dot - input_path);
|
||||
size_t alt_ird_len = base_len + 5;
|
||||
char *alt_ird = malloc(alt_ird_len);
|
||||
|
||||
if(alt_ird != NULL)
|
||||
{
|
||||
memcpy(alt_ird, input_path, base_len);
|
||||
memcpy(alt_ird + base_len, ".ird", 5);
|
||||
|
||||
if(ps3_parse_ird(alt_ird, &ird) == 0 && ird.valid)
|
||||
{
|
||||
have_ird = true;
|
||||
snprintf(buffer, sizeof(buffer), "Sidecar IRD loaded: %s — %s", ird.game_id, ird.game_name);
|
||||
print_success_ps3(buffer);
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
memcpy(data1_key, ird.d1, 16);
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from sidecar IRD data1");
|
||||
}
|
||||
}
|
||||
|
||||
free(alt_ird);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Step 2: Open source image ───────────────────────────────── */
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Opening Source Image" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
bool is_iso = false;
|
||||
aaruformat_context *input_ctx = NULL;
|
||||
FILE *iso_fp = NULL;
|
||||
uint64_t total_sectors;
|
||||
uint32_t media_type = PS3BD;
|
||||
|
||||
/* Try to open as AaruFormat first */
|
||||
input_ctx = aaruf_open(input_path, false, NULL);
|
||||
|
||||
if(input_ctx != NULL)
|
||||
{
|
||||
media_type = input_ctx->image_info.MediaType;
|
||||
|
||||
if(media_type != PS3DVD && media_type != PS3BD)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Source image media type is %s, expected PS3DVD or PS3BD",
|
||||
media_type_to_string(media_type));
|
||||
print_error_ps3(buffer);
|
||||
aaruf_close(input_ctx);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
total_sectors = input_ctx->image_info.Sectors;
|
||||
snprintf(buffer, sizeof(buffer), "AaruFormat image: %s, %llu sectors", media_type_to_string(media_type),
|
||||
(unsigned long long)total_sectors);
|
||||
print_success_ps3(buffer);
|
||||
|
||||
/* 1e: Try media tags for keys if not yet resolved */
|
||||
if(!have_disc_key)
|
||||
{
|
||||
uint32_t tag_len = 16;
|
||||
if(aaruf_read_media_tag(input_ctx, disc_key, kMediaTagPs3DiscKey, &tag_len) == AARUF_STATUS_OK)
|
||||
{
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key loaded from source image media tags");
|
||||
}
|
||||
}
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
uint32_t tag_len = 16;
|
||||
if(aaruf_read_media_tag(input_ctx, data1_key, kMediaTagPs3Data1, &tag_len) == AARUF_STATUS_OK)
|
||||
{
|
||||
have_data1 = true;
|
||||
ps3_derive_disc_key(data1_key, disc_key);
|
||||
have_disc_key = true;
|
||||
print_success_ps3("Disc key derived from source image data1 media tag");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Try as raw ISO */
|
||||
iso_fp = fopen(input_path, "rb");
|
||||
|
||||
if(iso_fp == NULL)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Cannot open input file: %s", strerror(errno));
|
||||
print_error_ps3(buffer);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(iso_fp, 0, SEEK_END);
|
||||
long file_size = ftell(iso_fp);
|
||||
rewind(iso_fp);
|
||||
|
||||
if(file_size <= 0 || file_size % PS3_SECTOR_SIZE != 0)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Invalid ISO file size (%ld bytes, not a multiple of %d)", file_size,
|
||||
PS3_SECTOR_SIZE);
|
||||
print_error_ps3(buffer);
|
||||
fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
is_iso = true;
|
||||
total_sectors = (uint64_t)file_size / PS3_SECTOR_SIZE;
|
||||
media_type = PS3BD;
|
||||
|
||||
/* Check sector 1 for PlayStation 3 identification */
|
||||
if(total_sectors >= 2)
|
||||
{
|
||||
uint8_t sector1[PS3_SECTOR_SIZE];
|
||||
|
||||
if(fseek(iso_fp, PS3_SECTOR_SIZE, SEEK_SET) == 0 &&
|
||||
fread(sector1, 1, PS3_SECTOR_SIZE, iso_fp) == PS3_SECTOR_SIZE)
|
||||
{
|
||||
if(memcmp(sector1, "PlayStation3", 12) != 0)
|
||||
{
|
||||
print_error_ps3("Sector 1 does not contain PlayStation 3 identification.");
|
||||
fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success_ps3("PlayStation 3 identification found in sector 1");
|
||||
}
|
||||
|
||||
rewind(iso_fp);
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Raw ISO image: %llu sectors (%.2f GiB)", (unsigned long long)total_sectors,
|
||||
(double)file_size / (1024.0 * 1024.0 * 1024.0));
|
||||
print_success_ps3(buffer);
|
||||
print_info_ps3("Media type:", "PS3BD (default for ISO)");
|
||||
}
|
||||
|
||||
if(!have_disc_key)
|
||||
{
|
||||
print_error_ps3("No disc key found. Provide --disc-key, --data1-key, --ird, or place sidecar files.");
|
||||
if(input_ctx != NULL) aaruf_close(input_ctx);
|
||||
if(iso_fp != NULL) fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ── Step 3: Read sector 0 and parse encryption map ──────────── */
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Parsing Encryption Map" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
uint8_t sector0[PS3_SECTOR_SIZE];
|
||||
|
||||
if(is_iso)
|
||||
{
|
||||
rewind(iso_fp);
|
||||
|
||||
if(fread(sector0, 1, PS3_SECTOR_SIZE, iso_fp) != PS3_SECTOR_SIZE)
|
||||
{
|
||||
print_error_ps3("Cannot read sector 0 from ISO");
|
||||
fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t s0_len = PS3_SECTOR_SIZE;
|
||||
uint8_t s0_status;
|
||||
int32_t s0_ret = aaruf_read_sector(input_ctx, 0, false, sector0, &s0_len, &s0_status);
|
||||
|
||||
if(s0_ret != AARUF_STATUS_OK)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Cannot read sector 0 from AaruFormat image (error %d)", s0_ret);
|
||||
print_error_ps3(buffer);
|
||||
aaruf_close(input_ctx);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
Ps3PlaintextRegion *regions = NULL;
|
||||
uint32_t region_count = 0;
|
||||
|
||||
int32_t map_ret = ps3_parse_encryption_map(sector0, PS3_SECTOR_SIZE, ®ions, ®ion_count);
|
||||
|
||||
if(map_ret != 0)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Cannot parse encryption map from sector 0 (error %d)", map_ret);
|
||||
print_error_ps3(buffer);
|
||||
if(input_ctx != NULL) aaruf_close(input_ctx);
|
||||
if(iso_fp != NULL) fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), "Encryption map: %u plaintext region(s)", region_count);
|
||||
print_success_ps3(buffer);
|
||||
|
||||
for(uint32_t i = 0; i < region_count; i++)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), " Region %u: sectors %u–%u", i, regions[i].start_sector,
|
||||
regions[i].end_sector);
|
||||
printf(" %s\n", buffer);
|
||||
}
|
||||
|
||||
/* Serialize encryption map for media tag */
|
||||
uint8_t *enc_map_data = NULL;
|
||||
uint32_t enc_map_length = 0;
|
||||
ps3_serialize_encryption_map(regions, region_count, &enc_map_data, &enc_map_length);
|
||||
|
||||
/* ── Step 4: Create destination AaruFormat image ─────────────── */
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Creating Destination Image" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
const char *app_name = "aaruformattool";
|
||||
size_t app_name_len = strlen(app_name);
|
||||
|
||||
aaruformat_context *output_ctx = aaruf_create(output_path, media_type, PS3_SECTOR_SIZE, total_sectors, 0, 0, NULL,
|
||||
(const uint8_t *)app_name, (uint8_t)app_name_len, 1, 0, false);
|
||||
|
||||
if(output_ctx == NULL)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Cannot create output image: %s", strerror(errno));
|
||||
print_error_ps3(buffer);
|
||||
free(enc_map_data);
|
||||
free(regions);
|
||||
if(input_ctx != NULL) aaruf_close(input_ctx);
|
||||
if(iso_fp != NULL) fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
return -1;
|
||||
}
|
||||
|
||||
print_success_ps3("Destination image created");
|
||||
|
||||
/* Write media tags (before sectors so lazy init can find them) */
|
||||
aaruf_write_media_tag(output_ctx, disc_key, kMediaTagPs3DiscKey, 16);
|
||||
print_success_ps3("Written media tag: PS3 Disc Key");
|
||||
|
||||
if(have_data1)
|
||||
{
|
||||
aaruf_write_media_tag(output_ctx, data1_key, kMediaTagPs3Data1, 16);
|
||||
print_success_ps3("Written media tag: PS3 Data1");
|
||||
}
|
||||
|
||||
if(have_ird)
|
||||
{
|
||||
aaruf_write_media_tag(output_ctx, ird.d2, kMediaTagPs3Data2, 16);
|
||||
print_success_ps3("Written media tag: PS3 Data2");
|
||||
|
||||
if(ird.has_pic)
|
||||
{
|
||||
aaruf_write_media_tag(output_ctx, ird.pic, kMediaTagPs3Pic, 115);
|
||||
print_success_ps3("Written media tag: PS3 PIC");
|
||||
}
|
||||
}
|
||||
|
||||
if(enc_map_data != NULL)
|
||||
{
|
||||
aaruf_write_media_tag(output_ctx, enc_map_data, kMediaTagPs3EncryptionMap, enc_map_length);
|
||||
print_success_ps3("Written media tag: PS3 Encryption Map");
|
||||
}
|
||||
|
||||
/* ── Step 5: Copy sectors ────────────────────────────────────── */
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Converting Sectors" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n\n");
|
||||
|
||||
uint8_t *sector_data = malloc(PS3_SECTOR_SIZE);
|
||||
|
||||
if(sector_data == NULL)
|
||||
{
|
||||
print_error_ps3("Cannot allocate sector buffer");
|
||||
result = -1;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
clock_t start_time = clock();
|
||||
uint64_t bytes_processed = 0;
|
||||
|
||||
for(uint64_t sector = 0; sector < total_sectors; sector++)
|
||||
{
|
||||
/* Progress */
|
||||
if(sector % 1000 == 0 || sector == total_sectors - 1)
|
||||
{
|
||||
double percentage = (double)(sector + 1) / (double)total_sectors * 100.0;
|
||||
double elapsed = (double)(clock() - start_time) / CLOCKS_PER_SEC;
|
||||
double speed = elapsed > 0 ? (double)bytes_processed / elapsed : 0;
|
||||
|
||||
char speed_str[32];
|
||||
format_bytes_ps3((uint64_t)speed, speed_str, sizeof(speed_str));
|
||||
|
||||
int filled = (int)(percentage / 100.0 * PROGRESS_BAR_WIDTH);
|
||||
if(filled > PROGRESS_BAR_WIDTH) filled = PROGRESS_BAR_WIDTH;
|
||||
|
||||
printf("\r " ANSI_CYAN "[");
|
||||
|
||||
for(int b = 0; b < PROGRESS_BAR_WIDTH; b++)
|
||||
{
|
||||
if(b < filled)
|
||||
printf(ANSI_GREEN "█");
|
||||
else if(b == filled)
|
||||
printf(ANSI_YELLOW "▓");
|
||||
else
|
||||
printf(ANSI_WHITE "░");
|
||||
}
|
||||
|
||||
printf(ANSI_CYAN "] " ANSI_WHITE "%5.1f%%" ANSI_RESET " │ %s/s ", percentage, speed_str);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
/* Read sector from source */
|
||||
if(is_iso)
|
||||
{
|
||||
if(fseek(iso_fp, (long)(sector * PS3_SECTOR_SIZE), SEEK_SET) != 0 ||
|
||||
fread(sector_data, 1, PS3_SECTOR_SIZE, iso_fp) != PS3_SECTOR_SIZE)
|
||||
{
|
||||
printf("\n");
|
||||
snprintf(buffer, sizeof(buffer), "Error reading ISO sector %llu", (unsigned long long)sector);
|
||||
print_error_ps3(buffer);
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t read_len = PS3_SECTOR_SIZE;
|
||||
uint8_t status;
|
||||
int32_t ret = aaruf_read_sector(input_ctx, sector, false, sector_data, &read_len, &status);
|
||||
|
||||
if(ret != AARUF_STATUS_OK)
|
||||
{
|
||||
printf("\n");
|
||||
snprintf(buffer, sizeof(buffer), "Error reading sector %llu (error %d)", (unsigned long long)sector,
|
||||
ret);
|
||||
print_error_ps3(buffer);
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Determine sector status based on encryption map */
|
||||
uint8_t write_status;
|
||||
|
||||
if(ps3_is_sector_encrypted(regions, region_count, sector))
|
||||
write_status = SectorStatusUnencrypted; /* Encrypted on disc → store decrypted */
|
||||
else
|
||||
write_status = SectorStatusDumped; /* Plaintext region → store as-is */
|
||||
|
||||
/* Write sector to output */
|
||||
int32_t wret = aaruf_write_sector(output_ctx, sector, false, sector_data, write_status, PS3_SECTOR_SIZE);
|
||||
|
||||
if(wret != AARUF_STATUS_OK)
|
||||
{
|
||||
printf("\n");
|
||||
snprintf(buffer, sizeof(buffer), "Error writing sector %llu (error %d)", (unsigned long long)sector, wret);
|
||||
print_error_ps3(buffer);
|
||||
result = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
bytes_processed += PS3_SECTOR_SIZE;
|
||||
}
|
||||
|
||||
printf("\n\n");
|
||||
|
||||
if(result == 0)
|
||||
{
|
||||
double elapsed = (double)(clock() - start_time) / CLOCKS_PER_SEC;
|
||||
char size_str[32];
|
||||
format_bytes_ps3(bytes_processed, size_str, sizeof(size_str));
|
||||
snprintf(buffer, sizeof(buffer), "Converted %llu sectors (%s) in %.1f seconds",
|
||||
(unsigned long long)total_sectors, size_str, elapsed);
|
||||
print_success_ps3(buffer);
|
||||
}
|
||||
|
||||
/* ── Step 6: Extract and write metadata ──────────────────────── */
|
||||
if(result == 0)
|
||||
{
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Extracting Metadata" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
/* Try to read PARAM.SFO from the ISO filesystem */
|
||||
uint8_t *sfo_data = NULL;
|
||||
uint32_t sfo_length = 0;
|
||||
|
||||
if(is_iso)
|
||||
{
|
||||
IsoReadCtx iso_ctx = {.fp = iso_fp, .total_sectors = total_sectors};
|
||||
iso9660_read_file(iso_read_sector_cb, &iso_ctx, "/PS3_GAME/PARAM.SFO", &sfo_data, &sfo_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
AarufReadCtx aaruf_ctx = {.ctx = input_ctx};
|
||||
iso9660_read_file(aaruf_read_sector_cb, &aaruf_ctx, "/PS3_GAME/PARAM.SFO", &sfo_data, &sfo_length);
|
||||
}
|
||||
|
||||
if(sfo_data != NULL && sfo_length > 0)
|
||||
{
|
||||
SfoFile sfo;
|
||||
|
||||
if(ps3_parse_sfo(sfo_data, sfo_length, &sfo) == 0)
|
||||
{
|
||||
const char *title = ps3_sfo_get_string(&sfo, "TITLE");
|
||||
const char *title_id = ps3_sfo_get_string(&sfo, "TITLE_ID");
|
||||
|
||||
if(title != NULL)
|
||||
{
|
||||
print_info_ps3("Title:", title);
|
||||
aaruf_set_media_title(output_ctx, (const uint8_t *)title, (int32_t)strlen(title));
|
||||
}
|
||||
|
||||
if(title_id != NULL)
|
||||
{
|
||||
print_info_ps3("Title ID:", title_id);
|
||||
aaruf_set_media_part_number(output_ctx, (const uint8_t *)title_id, (int32_t)strlen(title_id));
|
||||
}
|
||||
|
||||
if(title != NULL || title_id != NULL)
|
||||
print_success_ps3("Metadata extracted from PARAM.SFO");
|
||||
else
|
||||
printf(" " ANSI_YELLOW "⚠ PARAM.SFO found but no mappable fields" ANSI_RESET "\n");
|
||||
|
||||
ps3_free_sfo(&sfo);
|
||||
}
|
||||
else
|
||||
printf(" " ANSI_YELLOW "⚠ Cannot parse PARAM.SFO" ANSI_RESET "\n");
|
||||
|
||||
free(sfo_data);
|
||||
}
|
||||
else
|
||||
printf(" " ANSI_YELLOW "⚠ PARAM.SFO not found in filesystem" ANSI_RESET "\n");
|
||||
|
||||
/* If IRD provides game name, use it as title if not already set */
|
||||
if(have_ird && ird.game_name[0] != '\0')
|
||||
{
|
||||
print_info_ps3("IRD Game Name:", ird.game_name);
|
||||
print_info_ps3("IRD Game ID:", ird.game_id);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Step 7: Copy metadata from source AaruFormat image ──────── */
|
||||
if(result == 0 && !is_iso && input_ctx != NULL)
|
||||
{
|
||||
printf("\n" ANSI_BOLD ANSI_CYAN " Copying Source Metadata" ANSI_RESET "\n");
|
||||
printf(ANSI_BLUE "────────────────────────────────────────────────────────────────────────────────" ANSI_RESET
|
||||
"\n");
|
||||
|
||||
/* Copy media tags (skip ones we already wrote) */
|
||||
size_t tags_size = 0;
|
||||
uint8_t *tags_map = NULL;
|
||||
int32_t ret = aaruf_get_readable_media_tags(input_ctx, NULL, &tags_size);
|
||||
|
||||
if(ret == AARUF_ERROR_BUFFER_TOO_SMALL && tags_size > 0)
|
||||
{
|
||||
tags_map = malloc(tags_size);
|
||||
|
||||
if(tags_map != NULL)
|
||||
{
|
||||
ret = aaruf_get_readable_media_tags(input_ctx, tags_map, &tags_size);
|
||||
|
||||
if(ret == AARUF_STATUS_OK)
|
||||
{
|
||||
int copied = 0;
|
||||
|
||||
for(size_t i = 0; i < tags_size; i++)
|
||||
{
|
||||
if(!tags_map[i]) continue;
|
||||
|
||||
/* Skip PS3-specific tags we already wrote */
|
||||
if((int32_t)i == kMediaTagPs3DiscKey || (int32_t)i == kMediaTagPs3Data1 ||
|
||||
(int32_t)i == kMediaTagPs3Data2 || (int32_t)i == kMediaTagPs3Pic ||
|
||||
(int32_t)i == kMediaTagPs3EncryptionMap)
|
||||
continue;
|
||||
|
||||
uint32_t tag_len = 0;
|
||||
ret = aaruf_read_media_tag(input_ctx, NULL, (int32_t)i, &tag_len);
|
||||
|
||||
if(ret != AARUF_ERROR_BUFFER_TOO_SMALL || tag_len == 0) continue;
|
||||
|
||||
uint8_t *tag_data = malloc(tag_len);
|
||||
|
||||
if(tag_data == NULL) continue;
|
||||
|
||||
ret = aaruf_read_media_tag(input_ctx, tag_data, (int32_t)i, &tag_len);
|
||||
|
||||
if(ret == AARUF_STATUS_OK)
|
||||
{
|
||||
if(aaruf_write_media_tag(output_ctx, tag_data, (int32_t)i, tag_len) == AARUF_STATUS_OK)
|
||||
copied++;
|
||||
}
|
||||
|
||||
free(tag_data);
|
||||
}
|
||||
|
||||
if(copied > 0)
|
||||
{
|
||||
snprintf(buffer, sizeof(buffer), "Copied %d additional media tag(s)", copied);
|
||||
print_success_ps3(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
free(tags_map);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy dump hardware */
|
||||
size_t dumphw_size = 0;
|
||||
ret = aaruf_get_dumphw(input_ctx, NULL, &dumphw_size);
|
||||
|
||||
if(ret == AARUF_ERROR_BUFFER_TOO_SMALL && dumphw_size > 0)
|
||||
{
|
||||
uint8_t *dumphw = malloc(dumphw_size);
|
||||
|
||||
if(dumphw != NULL)
|
||||
{
|
||||
ret = aaruf_get_dumphw(input_ctx, dumphw, &dumphw_size);
|
||||
|
||||
if(ret == AARUF_STATUS_OK)
|
||||
{
|
||||
if(aaruf_set_dumphw(output_ctx, dumphw, dumphw_size) == AARUF_STATUS_OK)
|
||||
print_success_ps3("Copied dump hardware information");
|
||||
}
|
||||
|
||||
free(dumphw);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy JSON metadata */
|
||||
size_t json_size = 0;
|
||||
ret = aaruf_get_aaru_json_metadata(input_ctx, NULL, &json_size);
|
||||
|
||||
if(ret == AARUF_ERROR_BUFFER_TOO_SMALL && json_size > 0)
|
||||
{
|
||||
uint8_t *json = malloc(json_size);
|
||||
|
||||
if(json != NULL)
|
||||
{
|
||||
ret = aaruf_get_aaru_json_metadata(input_ctx, json, &json_size);
|
||||
|
||||
if(ret == AARUF_STATUS_OK)
|
||||
{
|
||||
if(aaruf_set_aaru_json_metadata(output_ctx, json, json_size) == AARUF_STATUS_OK)
|
||||
print_success_ps3("Copied Aaru JSON metadata");
|
||||
}
|
||||
|
||||
free(json);
|
||||
}
|
||||
}
|
||||
|
||||
/* Copy basic metadata fields */
|
||||
int32_t meta_len = 0;
|
||||
uint8_t *meta_buf = NULL;
|
||||
|
||||
/* Creator */
|
||||
meta_len = 0;
|
||||
|
||||
if(aaruf_get_creator(input_ctx, NULL, &meta_len) == AARUF_ERROR_BUFFER_TOO_SMALL && meta_len > 0)
|
||||
{
|
||||
meta_buf = malloc(meta_len);
|
||||
|
||||
if(meta_buf != NULL)
|
||||
{
|
||||
int32_t ml = meta_len;
|
||||
|
||||
if(aaruf_get_creator(input_ctx, meta_buf, &ml) == AARUF_STATUS_OK)
|
||||
aaruf_set_creator(output_ctx, meta_buf, ml);
|
||||
|
||||
free(meta_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Comments */
|
||||
meta_len = 0;
|
||||
|
||||
if(aaruf_get_comments(input_ctx, NULL, &meta_len) == AARUF_ERROR_BUFFER_TOO_SMALL && meta_len > 0)
|
||||
{
|
||||
meta_buf = malloc(meta_len);
|
||||
|
||||
if(meta_buf != NULL)
|
||||
{
|
||||
int32_t ml = meta_len;
|
||||
|
||||
if(aaruf_get_comments(input_ctx, meta_buf, &ml) == AARUF_STATUS_OK)
|
||||
aaruf_set_comments(output_ctx, meta_buf, ml);
|
||||
|
||||
free(meta_buf);
|
||||
}
|
||||
}
|
||||
|
||||
/* Media sequence */
|
||||
int32_t seq = 0, last_seq = 0;
|
||||
|
||||
if(aaruf_get_media_sequence(input_ctx, &seq, &last_seq) == AARUF_STATUS_OK && seq > 0)
|
||||
aaruf_set_media_sequence(output_ctx, seq, last_seq);
|
||||
}
|
||||
|
||||
cleanup:
|
||||
free(sector_data);
|
||||
free(enc_map_data);
|
||||
free(regions);
|
||||
|
||||
/* Close images */
|
||||
if(output_ctx != NULL) aaruf_close(output_ctx);
|
||||
if(input_ctx != NULL) aaruf_close(input_ctx);
|
||||
if(iso_fp != NULL) fclose(iso_fp);
|
||||
ps3_free_ird(&ird);
|
||||
|
||||
if(result == 0) { printf("\n" ANSI_GREEN " ✓ Conversion completed successfully!" ANSI_RESET "\n\n"); }
|
||||
else
|
||||
{
|
||||
printf("\n" ANSI_RED " ✗ Conversion failed." ANSI_RESET "\n\n");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
21
tool/usage.c
21
tool/usage.c
@@ -159,3 +159,24 @@ void usage_inject_media_tag()
|
||||
printf(" <media-tag-file> Path to the file containing the media tag data.\n");
|
||||
printf(" <image-file> Path to the AaruFormat image file.\n");
|
||||
}
|
||||
|
||||
void usage_convert_ps3()
|
||||
{
|
||||
printf("\nUsage:\n");
|
||||
printf(" aaruformattool convert-ps3 <input> <output> [options]\n\n");
|
||||
printf("Converts a PS3 disc ISO or AaruFormat image to an AaruFormat image with\n");
|
||||
printf("decrypted sector storage and PS3 encryption metadata.\n\n");
|
||||
printf("Arguments:\n");
|
||||
printf(" <input> Path to input ISO or AaruFormat image.\n");
|
||||
printf(" <output> Path to output AaruFormat image.\n\n");
|
||||
printf("Options:\n");
|
||||
printf(" --disc-key=<hex> 32-char hex disc key (16 bytes).\n");
|
||||
printf(" --data1-key=<hex> 32-char hex data1 key (disc key will be derived).\n");
|
||||
printf(" --ird=<path> Path to IRD file (extracts keys, PIC, metadata).\n\n");
|
||||
printf("Key resolution order:\n");
|
||||
printf(" 1. --disc-key argument\n");
|
||||
printf(" 2. --data1-key argument (derives disc key)\n");
|
||||
printf(" 3. --ird file (extracts data1 key, derives disc key)\n");
|
||||
printf(" 4. Sidecar files: <input>.disc_key, <input>.data1, <input>.ird\n");
|
||||
printf(" 5. Source AaruFormat image media tags\n");
|
||||
}
|
||||
|
||||
@@ -33,5 +33,6 @@ void usage_cli_compare();
|
||||
void usage_convert();
|
||||
void usage_upgrade_ddt_to_alpha21();
|
||||
void usage_inject_media_tag();
|
||||
void usage_convert_ps3();
|
||||
|
||||
#endif // LIBAARUFORMAT_USAGE_H
|
||||
|
||||
Reference in New Issue
Block a user