Initial reworking of main.c's code into librvth/rvth.c.

This provides a C interface for opening an RVT-H disk image and
retrieving bank information. rvth.c provides a higher-level abstraction
than the raw disk tables (now referred to as NHCD), which makes it
easier to e.g. print disc names and timestamps.

rvth_open() parses the RVT-H bank table and disc headers in order to get
all of the necessary information. It also checks for deleted disc images
and marks them as such.

Deleted images are identified and marked as such. Note that deleted
dual-layer Wii images are currently identified as single-layer, since
we aren't parsing the FST to determine the highest disc address.
This commit is contained in:
David Korth 2018-01-15 22:38:21 -05:00
parent 2f61ac5a06
commit 20fb383790
4 changed files with 502 additions and 107 deletions

View File

@ -39,6 +39,8 @@ extern "C" {
#pragma pack(1)
#define NHCD_BANK_COUNT 8
/**
* RVT-H bank table header.
* All fields are in big-endian.
@ -83,10 +85,12 @@ typedef enum {
*/
typedef struct _NHCD_BankTable {
NHCD_BankTable_Header header;
NHCD_BankEntry entries[8];
NHCD_BankEntry entries[NHCD_BANK_COUNT];
} NHCD_BankTable;
//ASSERT_STRUCT(NHCD_BankTable, 512*9);
/** Byte values. **/
// Bank table address.
#define NHCD_BANKTABLE_ADDRESS 0x60000000ULL
// Bank 1 starting address.
@ -94,6 +98,15 @@ typedef struct _NHCD_BankTable {
// Maximum bank size.
#define NHCD_BANK_SIZE 0x118940000ULL
/** LBA values. **/
// Bank table address.
#define NHCD_BANKTABLE_ADDRESS_LBA 0x300000U
// Bank 1 starting address.
#define NHCD_BANK_1_START_LBA 0x300009U
// Maximum bank size.
#define NHCD_BANK_SIZE_LBA 0x8C4A00U
// Block size.
#define NHCD_BLOCK_SIZE 512

View File

@ -18,4 +18,342 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
// TODO: RVT-H handling code.
#include "rvth.h"
#include "byteswap.h"
#include "nhcd_structs.h"
#include "gcn_structs.h"
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// RVT-H main struct.
struct _RvtH {
FILE *f_img;
RvtH_BankEntry entries[NHCD_BANK_COUNT];
};
/**
* Trim a game title.
* @param title Game title.
* @param size Size of game title.
*/
static void trim_title(char *title, int size)
{
size--;
for (; size >= 0; size--) {
if (title[size] != ' ')
break;
title[size] = 0;
}
}
/**
* Parse an RVT-H timestamp.
* @param nhcd_entry NHCD bank entry.
* @return Timestamp, or -1 if invalid.
*/
static time_t rvth_parse_timestamp(const NHCD_BankEntry *nhcd_entry)
{
// Date format: "YYYYMMDD"
// Time format: "HHMMSS"
unsigned int ymd = 0;
unsigned int hms = 0;
unsigned int i;
// Convert the date to an unsigned integer.
for (i = 0; i < 8; i++) {
if (unlikely(!isdigit(nhcd_entry->mdate[i]))) {
// Invalid digit.
return -1;
}
ymd *= 10;
ymd += (nhcd_entry->mdate[i] & 0xF);
}
// Sanity checks:
// - Must be higher than 19000101.
// - Must be lower than 99991231.
if (unlikely(ymd < 19000101 || ymd > 99991231)) {
// Invalid date.
return -1;
}
// Convert the time to an unsigned integer.
for (i = 0; i < 6; i++) {
if (unlikely(!isdigit(nhcd_entry->mtime[i]))) {
// Invalid digit.
return -1;
}
hms *= 10;
hms += (nhcd_entry->mtime[i] & 0xF);
}
// Sanity checks:
// - Must be lower than 235959.
if (unlikely(hms > 235959)) {
// Invalid time.
return -1;
}
// Convert to Unix time.
// NOTE: struct tm has some oddities:
// - tm_year: year - 1900
// - tm_mon: 0 == January
struct tm ymdtime;
ymdtime.tm_year = (ymd / 10000) - 1900;
ymdtime.tm_mon = ((ymd / 100) % 100) - 1;
ymdtime.tm_mday = ymd % 100;
ymdtime.tm_hour = (hms / 10000);
ymdtime.tm_min = (hms / 100) % 100;
ymdtime.tm_sec = hms % 100;
// tm_wday and tm_yday are output variables.
ymdtime.tm_wday = 0;
ymdtime.tm_yday = 0;
ymdtime.tm_isdst = 0;
// If conversion fails, this will return -1.
return timegm(&ymdtime);
}
/**
* Open an RVT-H disk image.
* TODO: R/W mode.
* @param filename Filename.
* @return RvtH struct pointer if the file is a valid RvtH disk image; NULL on error. (check errno)
*/
RvtH *rvth_open(const char *filename)
{
NHCD_BankTable_Header header;
FILE *f_img;
RvtH *rvth;
RvtH_BankEntry *rvth_entry;
int ret;
unsigned int i;
uint32_t addr;
size_t size;
// Open the disk image.
f_img = fopen(filename, "rb");
if (!f_img) {
// Could not open the file.
return NULL;
}
// Check the bank table header.
errno = 0;
ret = fseeko(f_img, NHCD_BANKTABLE_ADDRESS, SEEK_SET);
if (ret != 0) {
// Seek error.
return NULL;
}
errno = 0;
size = fread(&header, 1, sizeof(header), f_img);
if (size != sizeof(header)) {
// Short read.
int err = errno;
if (err == 0) {
err = EIO;
}
fclose(f_img);
errno = err;
return NULL;
}
// Allocate memory for the RvtH object
rvth = calloc(1, sizeof(RvtH));
if (!rvth) {
// Error allocating memory.
int err = errno;
fclose(f_img);
errno = err;
return NULL;
}
rvth->f_img = f_img;
rvth_entry = rvth->entries;
addr = (uint32_t)(NHCD_BANKTABLE_ADDRESS + 512);
for (i = 0; i < ARRAY_SIZE(rvth->entries); i++, rvth_entry++, addr += 512) {
NHCD_BankEntry nhcd_entry;
GCN_DiscHeader discHeader;
errno = 0;
ret = fseeko(f_img, addr, SEEK_SET);
if (ret != 0) {
// Seek error.
int err = errno;
if (err == 0) {
err = EIO;
}
free(rvth);
fclose(f_img);
errno = err;
return NULL;
}
errno = 0;
size = fread(&nhcd_entry, 1, sizeof(nhcd_entry), f_img);
if (size != sizeof(nhcd_entry)) {
// Short read.
int err = errno;
if (err == 0) {
err = EIO;
}
free(rvth);
fclose(f_img);
errno = err;
return NULL;
}
// Check the type.
switch (be32_to_cpu(nhcd_entry.type)) {
default:
// Unknown bank type...
rvth_entry->type = RVTH_BankType_Unknown;
break;
case 0:
// "Empty" bank. May have a deleted image.
rvth_entry->type = RVTH_BankType_Empty;
break;
case NHCD_BankType_GCN:
// GameCube
rvth_entry->type = RVTH_BankType_GCN;
break;
case NHCD_BankType_Wii_SL:
// Wii (single-layer)
rvth_entry->type = RVTH_BankType_Wii_SL;
break;
case NHCD_BankType_Wii_DL:
// Wii (dual-layer)
rvth_entry->type = RVTH_BankType_Wii_DL;
break;
}
// For valid types, use the listed LBAs if they're non-zero.
if (rvth_entry->type >= RVTH_BankType_GCN) {
rvth_entry->lba_start = be32_to_cpu(nhcd_entry.lba_start);
rvth_entry->lba_len = be32_to_cpu(nhcd_entry.lba_len);
}
if (rvth_entry->lba_start == 0 || rvth_entry->lba_len == 0) {
// Invalid LBAs. Use the default values.
rvth_entry->lba_start = NHCD_BANK_1_START_LBA + (NHCD_BANK_SIZE_LBA * i);
rvth_entry->lba_len = NHCD_BANK_SIZE_LBA;
}
if (rvth_entry->type == RVTH_BankType_Unknown) {
// Unknown entry type.
// Don't bother reading anything else.
continue;
}
// Read the GCN disc header.
// TODO: For non-deleted banks, verify the magic number?
errno = 0;
ret = fseeko(f_img, (int64_t)rvth_entry->lba_start * 512, SEEK_SET);
if (ret != 0) {
// Seek error.
int err = errno;
if (err == 0) {
err = EIO;
}
free(rvth);
fclose(f_img);
errno = err;
return NULL;
}
size = fread(&discHeader, 1, sizeof(discHeader), f_img);
if (size != sizeof(discHeader)) {
// Short read.
int err = errno;
if (err == 0) {
err = EIO;
}
free(rvth);
fclose(f_img);
errno = err;
return NULL;
}
// If the entry type is Empty, check for GCN/Wii magic.
if (rvth_entry->type == RVTH_BankType_Empty) {
if (discHeader.magic_wii == cpu_to_be32(WII_MAGIC)) {
// Wii disc image.
// TODO: Detect SL vs. DL.
// TODO: Actual disc image size?
rvth_entry->type = RVTH_BankType_Wii_SL;
rvth_entry->is_deleted = true;
} else if (discHeader.magic_gcn == cpu_to_be32(GCN_MAGIC)) {
// GameCube disc image.
// TODO: Actual disc image size?
rvth_entry->type = RVTH_BankType_GCN;
rvth_entry->is_deleted = true;
} else {
// Probably actually empty...
rvth_entry->timestamp = -1;
continue;
}
}
// Copy the game ID.
memcpy(rvth_entry->id6, discHeader.id6, 6);
// Read the disc title.
memcpy(rvth_entry->game_title, discHeader.game_title, 64);
// Remove excess spaces.
trim_title(rvth_entry->game_title, 64);
// Parse the timestamp.
rvth_entry->timestamp = rvth_parse_timestamp(&nhcd_entry);
// TODO: Check encryption status.
}
// RVT-H image loaded.
return rvth;
}
/**
* Close an opened RVT-H disk image.
* @param rvth RVT-H disk image.
*/
void rvth_close(RvtH *img)
{
if (!img)
return;
if (img->f_img) {
fclose(img->f_img);
}
free(img);
}
/**
* Get a bank table entry.
* @param rvth RVT-H disk image.
* @param bank Bank number. (0-7)
* @return Bank table entry, or NULL if out of range or empty.
*/
const RvtH_BankEntry *rvth_get_BankEntry(const RvtH *rvth, unsigned int bank)
{
if (!rvth) {
errno = EINVAL;
return NULL;
}
if (bank >= ARRAY_SIZE(rvth->entries)) {
errno = ERANGE;
return NULL;
}
// Check if the bank is empty.
if (rvth->entries[bank].type == RVTH_BankType_Empty) {
return NULL;
}
return &rvth->entries[bank];
}

99
src/librvth/rvth.h Normal file
View File

@ -0,0 +1,99 @@
/***************************************************************************
* RVT-H Tool (librvth) *
* rvth.c: RVT-H image handler. *
* *
* Copyright (c) 2018 by David Korth. *
* *
* 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, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
#ifndef __RVTHTOOL_LIBRVTH_RVTH_H__
#define __RVTHTOOL_LIBRVTH_RVTH_H__
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
#define RVTH_BANK_COUNT 8
#define RVTH_BLOCK_SIZE 512
// RVT-H struct.
struct _RvtH;
typedef struct _RvtH RvtH;
// RVT-H bank entry.
typedef struct RvtH_BankEntry {
uint32_t lba_start; // Starting LBA. (512-byte sectors)
uint32_t lba_len; // Length, in 512-byte sectors.
time_t timestamp; // Timestamp. (no timezone information)
uint8_t type; // Bank type. (See RvtH_BankType_e.)
char id6[6]; // Game ID. (NOT NULL-terminated!)
char game_title[65]; // Game title. (from GCN header)
uint8_t crypto_type; // Encryption type. (See RvtH_CryptoType_e.)
bool is_deleted; // If true, this entry was deleted.
} RvtH_BankEntry;
// RVT-H bank types.
typedef enum {
RVTH_BankType_Empty = 0, // Magic is 0.
RVTH_BankType_Unknown = 1, // Unknown magic.
RVTH_BankType_GCN = 2,
RVTH_BankType_Wii_SL = 3,
RVTH_BankType_Wii_DL = 4,
RVTH_BankType_MAX
} RvtH_BankType_e;
// Encryption types.
typedef enum {
RVTH_CryptoType_Unknown = 0, // Unknown encryption.
RVTH_CryptoType_None = 1, // No encryption.
RVTH_CryptoType_Debug = 2, // RVT-R encryption.
RVTH_CryptoType_Retail = 3, // Retail encryption.
RVTH_CryptoType_Korean = 4, // Korean retail encryption.
} RvtH_CryptoType_e;
/** Functions. **/
/**
* Open an RVT-H disk image.
* TODO: R/W mode.
* @param filename Filename.
* @return RvtH struct pointer if the file is a valid RvtH disk image; NULL on error. (check errno)
*/
RvtH *rvth_open(const char *filename);
/**
* Close an opened RVT-H disk image.
* @param rvth RVT-H disk image.
*/
void rvth_close(RvtH *rvth);
/**
* Get a bank table entry.
* @param rvth RVT-H disk image.
* @param bank Bank number. (0-7)
* @return Bank table entry, or NULL if out of range or empty.
*/
const RvtH_BankEntry *rvth_get_BankEntry(const RvtH *rvth, unsigned int bank);
#ifdef __cplusplus
}
#endif
#endif /* __RVTHTOOL_LIBRVTH_RVTH_H__ */

View File

@ -26,124 +26,82 @@
#include <stdlib.h>
#include <string.h>
#include "byteswap.h"
#include "nhcd_structs.h"
#include "gcn_structs.h"
#include "librvth/byteswap.h"
#include "librvth/rvth.h"
/**
* Print an RVT-H Bank Table.
* @param tbl [in] RVT-H bank table.
* @param f_img [in,opt] If not NULL, disk image file for loading the disc headers.
* @param rvth [in] RVT-H disk image.
* @return 0 on success; non-zero on error.
*/
static int print_bank_table(const NHCD_BankTable *tbl, FILE *f_img)
static int print_bank_table(const RvtH *rvth)
{
// Verify the bank table header.
if (tbl->header.magic != cpu_to_be32(NHCD_BANKTABLE_MAGIC)) {
fprintf(stderr, "*** ERROR: Bank table is invalid.\n");
return 1;
}
// TODO: Figure out the other entries in the bank table.
// TODO: Calculate expected LBAs and compare them to the bank table.
struct tm timestamp;
// Print the entries.
const NHCD_BankEntry *entry = tbl->entries;
for (unsigned int i = 0; i < ARRAY_SIZE(tbl->entries); i++, entry++) {
const uint32_t type = be32_to_cpu(entry->type);
for (unsigned int i = 0; i < RVTH_BANK_COUNT; i++) {
const RvtH_BankEntry *const entry = rvth_get_BankEntry(rvth, i);
if (!entry) {
printf("Bank %u: Empty\n\n", i+1);
continue;
}
const char *s_type;
switch (type) {
case 0:
// TODO: Print "Deleted" if a game is still present.
switch (entry->type) {
case RVTH_BankType_Empty:
// NOTE: Should not happen...
s_type = "Empty";
break;
case NHCD_BankType_GCN:
case RVTH_BankType_Unknown:
// TODO: Print the bank type magic?
s_type = "Unknown";
break;
case RVTH_BankType_GCN:
s_type = "GameCube";
break;
case NHCD_BankType_Wii_SL:
case RVTH_BankType_Wii_SL:
s_type = "Wii (Single-Layer)";
break;
case NHCD_BankType_Wii_DL:
case RVTH_BankType_Wii_DL:
// TODO: Invalid for Bank 8; need to skip the next bank.
s_type = "Wii (Dual-Layer)";
break;
default:
s_type = NULL;
s_type = "Unknown";
break;
}
if (s_type) {
printf("Bank %u: %s\n", i+1, s_type);
} else {
printf("Bank %u: Unknown (0x%08X)\n", i+1, type);
printf("Bank %u: %s%s\n", i+1, s_type, (entry->is_deleted ? " [DELETED]" : ""));
if (entry->type <= RVTH_BankType_Unknown ||
entry->type >= RVTH_BankType_MAX)
{
// Cannot display any information for this type.
continue;
}
if (type != 0 && s_type != NULL) {
// Print timestamp.
printf("- Timestamp: %.4s/%.2s/%.2s %.2s:%.2s:%.2s\n",
&entry->mdate[0], &entry->mdate[4], &entry->mdate[6],
&entry->mtime[0], &entry->mtime[2], &entry->mtime[4]);
// Print the timestamp.
// TODO: Reentrant gmtime() if available.
if (entry->timestamp != -1) {
timestamp = *gmtime(&entry->timestamp);
printf("- Timestamp: %04d/%02d/%02d %02d:%02d:%02d\n",
timestamp.tm_year + 1900, timestamp.tm_mon + 1, timestamp.tm_mday,
timestamp.tm_hour, timestamp.tm_min, timestamp.tm_sec);
} else {
printf("- Timestamp: Unknown\n");
}
// LBAs.
uint32_t lba_start = be32_to_cpu(entry->lba_start);
uint32_t lba_len = be32_to_cpu(entry->lba_len);
if (lba_start == 0) {
// No LBA start address.
// Use the default LBA start and length.
lba_start = (uint32_t)((NHCD_BANK_1_START + (NHCD_BANK_SIZE * i)) / NHCD_BLOCK_SIZE);
lba_len = NHCD_BANK_SIZE / NHCD_BLOCK_SIZE;
}
printf("- LBA start: 0x%08X\n", entry->lba_start);
printf("- LBA length: 0x%08X\n", entry->lba_len);
printf("- LBA start: 0x%08X\n", lba_start);
printf("- LBA length: 0x%08X\n", lba_len);
// Game ID.
printf("- Game ID: %.6s\n", entry->id6);
if (f_img) {
int ret; size_t size; int i;
bool is_wii = false;
// Game title.
// rvth_open() has already trimmed the title.
printf("- Title: %.64s\n", entry->game_title);
// Read the disc header.
int64_t addr = (int64_t)lba_start * NHCD_BLOCK_SIZE;
GCN_DiscHeader discHeader;
ret = fseeko(f_img, addr, SEEK_SET);
if (ret != 0) {
printf("- *** ERROR: Cannot seek to LBA start position: %s\n", strerror(errno));
goto skip_disc_info;
}
size = fread(&discHeader, 1, sizeof(discHeader), f_img);
if (size != sizeof(discHeader)) {
printf("- *** ERROR: Unable to read the GCN/Wii disc header.\n");
goto skip_disc_info;
}
// Check for magic numbers.
if (discHeader.magic_wii == cpu_to_be32(WII_MAGIC)) {
printf("- Disc type: Wii\n");
is_wii = true;
} else if (discHeader.magic_gcn == cpu_to_be32(GCN_MAGIC)) {
printf("- Disc type: GameCube\n");
is_wii = false;
} else {
printf("- Disc type: Unknown\n");
goto skip_disc_info;
}
// Print the game ID.
printf("- Game ID: %.6s\n", discHeader.id6);
// Print the disc title.
// First, trim excess spaces.
for (i = ARRAY_SIZE(discHeader.game_title)-1; i >= 0; i--) {
if (discHeader.game_title[i] != ' ')
break;
discHeader.game_title[i] = 0;
}
printf("- Title: %.64s\n", discHeader.game_title);
// TODO: Print encryption status for Wii.
}
skip_disc_info:
// TODO: Print encryption status for Wii.
printf("\n");
}
@ -164,29 +122,16 @@ int main(int argc, char *argv[])
}
// Open the disk image.
FILE *f_img = fopen(argv[1], "rb");
if (!f_img) {
RvtH *rvth = rvth_open(argv[1]);
if (!rvth) {
fprintf(stderr, "*** ERROR opening '%s': %s\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}
// Read the bank table.
NHCD_BankTable tbl;
int ret = fseeko(f_img, NHCD_BANKTABLE_ADDRESS, SEEK_SET);
if (ret != 0) {
fprintf(stderr, "*** ERROR seeking to bank table: %s\n", strerror(errno));
return EXIT_FAILURE;
}
size_t size = fread(&tbl, 1, sizeof(NHCD_BankTable), f_img);
if (size != sizeof(tbl)) {
fprintf(stderr, "*** ERROR reading bank table.\n");
return EXIT_FAILURE;
}
// Print the bank table.
printf("RVT-H Bank Table:\n\n");
print_bank_table(&tbl, f_img);
fclose(f_img);
print_bank_table(rvth);
rvth_close(rvth);
return EXIT_SUCCESS;
}