mirror of
https://github.com/GerbilSoft/rvthtool.git
synced 2025-06-19 12:05:37 -04:00
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:
parent
2f61ac5a06
commit
20fb383790
@ -39,6 +39,8 @@ extern "C" {
|
|||||||
|
|
||||||
#pragma pack(1)
|
#pragma pack(1)
|
||||||
|
|
||||||
|
#define NHCD_BANK_COUNT 8
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RVT-H bank table header.
|
* RVT-H bank table header.
|
||||||
* All fields are in big-endian.
|
* All fields are in big-endian.
|
||||||
@ -83,10 +85,12 @@ typedef enum {
|
|||||||
*/
|
*/
|
||||||
typedef struct _NHCD_BankTable {
|
typedef struct _NHCD_BankTable {
|
||||||
NHCD_BankTable_Header header;
|
NHCD_BankTable_Header header;
|
||||||
NHCD_BankEntry entries[8];
|
NHCD_BankEntry entries[NHCD_BANK_COUNT];
|
||||||
} NHCD_BankTable;
|
} NHCD_BankTable;
|
||||||
//ASSERT_STRUCT(NHCD_BankTable, 512*9);
|
//ASSERT_STRUCT(NHCD_BankTable, 512*9);
|
||||||
|
|
||||||
|
/** Byte values. **/
|
||||||
|
|
||||||
// Bank table address.
|
// Bank table address.
|
||||||
#define NHCD_BANKTABLE_ADDRESS 0x60000000ULL
|
#define NHCD_BANKTABLE_ADDRESS 0x60000000ULL
|
||||||
// Bank 1 starting address.
|
// Bank 1 starting address.
|
||||||
@ -94,6 +98,15 @@ typedef struct _NHCD_BankTable {
|
|||||||
// Maximum bank size.
|
// Maximum bank size.
|
||||||
#define NHCD_BANK_SIZE 0x118940000ULL
|
#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.
|
// Block size.
|
||||||
#define NHCD_BLOCK_SIZE 512
|
#define NHCD_BLOCK_SIZE 512
|
||||||
|
|
||||||
|
@ -18,4 +18,342 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
|
* 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
99
src/librvth/rvth.h
Normal 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__ */
|
@ -26,124 +26,82 @@
|
|||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include "byteswap.h"
|
#include "librvth/byteswap.h"
|
||||||
#include "nhcd_structs.h"
|
#include "librvth/rvth.h"
|
||||||
#include "gcn_structs.h"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print an RVT-H Bank Table.
|
* Print an RVT-H Bank Table.
|
||||||
* @param tbl [in] RVT-H bank table.
|
* @param rvth [in] RVT-H disk image.
|
||||||
* @param f_img [in,opt] If not NULL, disk image file for loading the disc headers.
|
|
||||||
* @return 0 on success; non-zero on error.
|
* @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.
|
struct tm timestamp;
|
||||||
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.
|
|
||||||
|
|
||||||
// Print the entries.
|
// Print the entries.
|
||||||
const NHCD_BankEntry *entry = tbl->entries;
|
for (unsigned int i = 0; i < RVTH_BANK_COUNT; i++) {
|
||||||
for (unsigned int i = 0; i < ARRAY_SIZE(tbl->entries); i++, entry++) {
|
const RvtH_BankEntry *const entry = rvth_get_BankEntry(rvth, i);
|
||||||
const uint32_t type = be32_to_cpu(entry->type);
|
if (!entry) {
|
||||||
|
printf("Bank %u: Empty\n\n", i+1);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const char *s_type;
|
const char *s_type;
|
||||||
switch (type) {
|
switch (entry->type) {
|
||||||
case 0:
|
case RVTH_BankType_Empty:
|
||||||
// TODO: Print "Deleted" if a game is still present.
|
// NOTE: Should not happen...
|
||||||
s_type = "Empty";
|
s_type = "Empty";
|
||||||
break;
|
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";
|
s_type = "GameCube";
|
||||||
break;
|
break;
|
||||||
case NHCD_BankType_Wii_SL:
|
case RVTH_BankType_Wii_SL:
|
||||||
s_type = "Wii (Single-Layer)";
|
s_type = "Wii (Single-Layer)";
|
||||||
break;
|
break;
|
||||||
case NHCD_BankType_Wii_DL:
|
case RVTH_BankType_Wii_DL:
|
||||||
// TODO: Invalid for Bank 8; need to skip the next bank.
|
// TODO: Invalid for Bank 8; need to skip the next bank.
|
||||||
s_type = "Wii (Dual-Layer)";
|
s_type = "Wii (Dual-Layer)";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
s_type = NULL;
|
s_type = "Unknown";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_type) {
|
printf("Bank %u: %s%s\n", i+1, s_type, (entry->is_deleted ? " [DELETED]" : ""));
|
||||||
printf("Bank %u: %s\n", i+1, s_type);
|
if (entry->type <= RVTH_BankType_Unknown ||
|
||||||
} else {
|
entry->type >= RVTH_BankType_MAX)
|
||||||
printf("Bank %u: Unknown (0x%08X)\n", i+1, type);
|
{
|
||||||
|
// Cannot display any information for this type.
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type != 0 && s_type != NULL) {
|
// Print the timestamp.
|
||||||
// Print timestamp.
|
// TODO: Reentrant gmtime() if available.
|
||||||
printf("- Timestamp: %.4s/%.2s/%.2s %.2s:%.2s:%.2s\n",
|
if (entry->timestamp != -1) {
|
||||||
&entry->mdate[0], &entry->mdate[4], &entry->mdate[6],
|
timestamp = *gmtime(&entry->timestamp);
|
||||||
&entry->mtime[0], &entry->mtime[2], &entry->mtime[4]);
|
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.
|
// LBAs.
|
||||||
uint32_t lba_start = be32_to_cpu(entry->lba_start);
|
printf("- LBA start: 0x%08X\n", entry->lba_start);
|
||||||
uint32_t lba_len = be32_to_cpu(entry->lba_len);
|
printf("- LBA length: 0x%08X\n", 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", lba_start);
|
// Game ID.
|
||||||
printf("- LBA length: 0x%08X\n", lba_len);
|
printf("- Game ID: %.6s\n", entry->id6);
|
||||||
|
|
||||||
if (f_img) {
|
// Game title.
|
||||||
int ret; size_t size; int i;
|
// rvth_open() has already trimmed the title.
|
||||||
bool is_wii = false;
|
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.
|
// TODO: Print encryption status for Wii.
|
||||||
}
|
|
||||||
|
|
||||||
skip_disc_info:
|
|
||||||
printf("\n");
|
printf("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,29 +122,16 @@ int main(int argc, char *argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open the disk image.
|
// Open the disk image.
|
||||||
FILE *f_img = fopen(argv[1], "rb");
|
RvtH *rvth = rvth_open(argv[1]);
|
||||||
if (!f_img) {
|
if (!rvth) {
|
||||||
fprintf(stderr, "*** ERROR opening '%s': %s\n", argv[1], strerror(errno));
|
fprintf(stderr, "*** ERROR opening '%s': %s\n", argv[1], strerror(errno));
|
||||||
return EXIT_FAILURE;
|
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.
|
// Print the bank table.
|
||||||
printf("RVT-H Bank Table:\n\n");
|
printf("RVT-H Bank Table:\n\n");
|
||||||
print_bank_table(&tbl, f_img);
|
print_bank_table(rvth);
|
||||||
fclose(f_img);
|
rvth_close(rvth);
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user