Add PS3 disc image conversion command with key resolution and metadata extraction

This commit is contained in:
2026-03-16 00:40:46 +00:00
parent eec6c33195
commit 45994c885d
7 changed files with 990 additions and 0 deletions

View File

@@ -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
)

View File

@@ -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_

View File

@@ -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]);

View File

@@ -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
View 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, &regions, &region_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;
}

View File

@@ -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");
}

View File

@@ -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