friidump/libfriidump/unscrambler.c
2013-11-01 16:06:06 -04:00

370 lines
11 KiB
C

/***************************************************************************
* Copyright (C) 2007 by Arep *
* Support is provided through the forums at *
* http://wii.console-tribe.com *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
/*! \file
* \brief Unscrambler for Nintendo GameCube/Wii discs.
*
* As Nintendo GameCube/Wii discs use the standars DVD-ROM scrambling algorithm, but with different, unknown, seeds, the actual seeds have to be brute-forced.
* The functions in this file take care of the brute-forcing and of the actual unscrambling of the read sectors.
*
* The code in this file has been derived from unscrambler 0.4, Copyright (C) 2006 Victor Muñoz (xt5@ingenieria-inversa.cl), GPL v2+,
* http://www.ingenieria-inversa.cl/?lp_lang_pref=en .
*/
#include "misc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "constants.h"
#include "byteorder.h"
#include "ecma-267.h"
#include "unscrambler.h"
// #define unscramblerdebug(...) debug (__VA_ARGS__);
#define unscramblerdebug(...)
/*! \brief Size of the seeds cache (Do not touch) */
#define MAX_SEEDS 4
/*! \brief Number of bytes of a sector on which the EDC is calculated */
#define EDC_LENGTH (RAW_SECTOR_SIZE - 4) /* The EDC value is contained in the bottom 4 bytes of a frame */
/*! \brief A structure that represents a seed
*/
typedef struct t_seed {
int seed; //!< The seed, in numeric format.
unsigned char streamcipher[SECTOR_SIZE]; //!< The stream cipher generated from the seed through the LFSR.
} t_seed;
/*! \brief A structure that represents an unscrambler
*/
struct unscrambler_s {
t_seed seeds[(MAX_SEEDS + 1) * 16]; //!< The seeds cache.
bool bruteforce_seeds; //!< If true, whenever a seed for a sector is not cached, it will be found via a bruteforce attack, otherwise an error will be returned.
};
void unscrambler_set_disctype (u_int8_t disc_type){
disctype = disc_type;
// fprintf (stdout,"%d",disctype);
}
/**
* Adds a seed to the cache, calculating its streamcipher.
* @param seeds The seed cache.
* @param seed The seed to add.
* @return A structure representing the added seed, or NULL if it could not be added.
*/
static t_seed *add_seed (t_seed *seeds, unsigned short seed) {
int i;
t_seed *out;
unscramblerdebug ("Caching seed %04x\n", seed);
if (seeds -> seed == -2) {
out = NULL;
} else {
seeds -> seed = seed;
LFSR_init (seed);
for (i = 0; i < SECTOR_SIZE; i++)
seeds -> streamcipher[i] = LFSR_byte ();
out = seeds;
}
return (out);
}
/**
* Tests if the specified seed is the one used for the specified sector block: the check is done comparing the generated EDC with the one at the bottom of each
* sector. Sectors are processed in blocks, as the same seed is used for 16 consecutive sectors.
* @param buf The sector.
* @param j The seed.
* @return true if the seed is correct, false otherwise.
*/
static bool test_seed (u_int8_t *buf, int j) {
int i;
u_int8_t tmp[RAW_SECTOR_SIZE];
u_int32_t edc_calculated, edc_correct;
bool out;
memcpy (tmp, buf, RAW_SECTOR_SIZE);
LFSR_init (j);
for (i = 12; i < EDC_LENGTH; i++)
tmp[i] ^= LFSR_byte ();
edc_calculated = edc_calc (0x00000000, tmp, EDC_LENGTH);
edc_correct = my_ntohl (*((u_int32_t *) (&tmp[EDC_LENGTH])));
if (edc_calculated == edc_correct)
out = true;
else
out = false;
return (out);
}
/**
* Unscramble a complete block, using an already-cached seed.
* @param seed The seed to use for the unscrambling.
* @param _bin The 16-sector block to unscramble (RAW_BLOCK_SIZE).
* @param _bout The unscrambled 16-sector block (BLOCK_SIZE).
* @return True if the unscrambling was successful, false otherwise.
*/
static bool unscramble_frame (t_seed *seed, u_int8_t *_bin, u_int8_t *_bout) {
int i, j;
u_int8_t tmp[RAW_SECTOR_SIZE], *bin, *bout;
u_int32_t *_4bin, *_4cipher, edc_calculated, edc_correct;
bool out;
out = true;
for(j = 0; j < 16; j++) {
bin = &_bin[RAW_SECTOR_SIZE * j];
bout = &_bout[SECTOR_SIZE * j];
memcpy (tmp, bin, RAW_SECTOR_SIZE);
_4bin = (u_int32_t *) &tmp[12]; /* Scrambled data begin at byte 12 */
_4cipher = (u_int32_t *) seed -> streamcipher;
for (i = 0; i < 512; i++) /* Well, the scrambling algorithm is just a bitwise XOR... */
_4bin[i] ^= _4cipher[i];
//memcpy (bout, tmp + 6, SECTOR_SIZE); // copy CPR_MAI bytes
if (disctype==3) { //Regular
memcpy (bout, tmp + 12, SECTOR_SIZE); // DVD: copy 2048 bytes (starting from CPR_MAI)
}
else { //Nintendo
memcpy (bout, tmp + 6, SECTOR_SIZE); // Nintendo: copy 2048 bytes (up to CPR_MAI)
memcpy (&_bin[(RAW_SECTOR_SIZE * j)+2054], &tmp[2054], 6);
}
edc_calculated = edc_calc (0x00000000, tmp, EDC_LENGTH);
edc_correct = my_ntohl (*((u_int32_t *) (&tmp[EDC_LENGTH])));
if (edc_calculated != edc_correct) {
debug ("Bad EDC (%08x), must be %08x (sector = %d)", edc_calculated, edc_correct, j);
out = false;
}
}
return (out);
}
/**
* Initializes the seed cache.
* @param u The unscrambler structure.
*/
static void unscrambler_init_seeds (unscrambler *u) {
int i, j;
for (i = 0; i < 16; i++) {
for (j = 0; j < MAX_SEEDS; j++)
u -> seeds[i * MAX_SEEDS + j].seed = -1;
u -> seeds[i * MAX_SEEDS + j].seed = -2; // TODO Check what this does
}
return;
}
/**
* Creates a new structure representing an unscrambler.
* @return The newly-created structure, to be used with the other commands.
*/
unscrambler *unscrambler_new (void) {
unscrambler *u;
u = (unscrambler *) malloc (sizeof (unscrambler));
unscrambler_init_seeds (u);
u -> bruteforce_seeds = true;
return (u);
}
/**
* Frees resources used by an unscrambler structure and destroys it.
* @param u The unscrambler structure.
* @return NULL.
*/
void *unscrambler_destroy (unscrambler *u) {
my_free (u);
return (NULL);
}
void unscrambler_set_bruteforce (unscrambler *u, bool b) {
u -> bruteforce_seeds = b;
debug ("Seed bruteforcing %s", b ? "enabled" : "disabled");
return;
}
/**
* Unscrambles a 16-sector block.
* @param u The unscrambler structure.
* @param sector_no The number of the first sector in the block.
* @param inbuf The 16-sector block to unscramble. Each block must be RAW_SECTOR_SIZE bytes long, so that the total size is RAW_BLOCK_SIZE.
* @param outbuf The unscrambled 16-sector block. Each block will be SECTOR_SIZE bytes long, so that the total size is BLOCK_SIZE.
* @return True if the unscrambling was successful, false otherwise.
*/
bool unscrambler_unscramble_16sectors (unscrambler *u, u_int32_t sector_no, u_int8_t *inbuf, u_int8_t *outbuf) {
t_seed *seeds;
t_seed *current_seed;
int j;
bool out;
out = true;
seeds = &(u -> seeds[((sector_no / 16) & 0x0F) * MAX_SEEDS]);
/* Try to find the seed used for this sector */
current_seed = NULL;
while (!current_seed && (seeds -> seed) >= 0) {
if (test_seed (inbuf, seeds -> seed))
current_seed = seeds;
else
seeds++;
}
if (!current_seed && u -> bruteforce_seeds) {
/* The seed is not cached, yet. Try to find it with brute force... */
unscramblerdebug ("Brute-forcing seed for sector %d...", sector_no);
for (j = 0; !current_seed && j < 0x7FFF; j++) {
if (test_seed (inbuf, j)) {
if (!(current_seed = add_seed (seeds, j))) {
error ("No enough cache space for caching seed");
out = false;
}
}
}
if (current_seed)
unscramblerdebug ("Seed found: %04x", --j);
}
if (current_seed) {
/* OK, somehow seed was found: unscramble frame, write it and go on */
if (!unscramble_frame (current_seed, inbuf, outbuf)) {
error ("Error unscrambling frame %u\n", sector_no);
out = false;
} else {
out = true;
}
} else {
/* Well, we only get here if there are read errors */
error ("Cannot find seed for frame %u", sector_no);
out = false;
}
return (out);
}
/**
* Unscrambles a complete file.
* @param u The unscrambler structure.
* @param infile The input file name.
* @param outfile The output file name.
* @param progress A function to be called repeatedly during the operation, useful to report progress data/statistics.
* @param progress_data Data to be passed as-is to the progress function.
* @return True if the unscrambling was successful, false otherwise.
*/
bool unscrambler_unscramble_file (unscrambler *u, char *infile, char *outfile, unscrambler_progress_func progress, void *progress_data, u_int32_t *current_sector) {
FILE *in, *outfp;
bool out;
u_int8_t b_in[RAW_BLOCK_SIZE], b_out[BLOCK_SIZE];
size_t r;
my_off_t filesize;
int s;
u_int32_t total_sectors;
out = false;
if(!(in = fopen (infile ? infile : "", "rb"))) {
error ("Cannot open input file \"%s\"", infile);
} else if (!(outfp = fopen (outfile ? outfile : "", "wb"))) {
error ("Cannot open output file \"%s\"", outfile);
fclose (in);
} else {
/* Find out how many sectors we need to process */
my_fseek (in, 0, SEEK_END);
filesize = my_ftell (in);
total_sectors = (u_int32_t) (filesize / RAW_SECTOR_SIZE);
rewind (in);
/* First call to progress function */
if (progress)
progress (true, 0, total_sectors, progress_data);
s = 0, out = true;
while ((r = fread (b_in, 1, RAW_BLOCK_SIZE, in)) > 0 && out) {
if (r < RAW_BLOCK_SIZE) {
warning ("Short block read (%u bytes), padding with zeroes!", r);
memset (b_in + r, 0, sizeof (b_in) - r);
}
if (unscrambler_unscramble_16sectors (u, s, b_in, b_out)) {
clearerr (outfp);
if (!b_out) {
error ("NULL buffer");
out = false;
*(current_sector) = s;
}
else fwrite (b_out, SECTOR_SIZE, SECTORS_PER_BLOCK, outfp);
if (ferror (outfp)) {
error ("fwrite() to ISO output file failed");
out = false;
*(current_sector) = s;
}
} else {
debug ("unscrambler_unscramble_16sectors() failed");
out = false;
*(current_sector) = s;
}
s += 16;
if ((s % 320 == 0) || (s == total_sectors)) { //speedhack
if (progress)
progress (false, s, total_sectors, progress_data);
}
}
if (out) {
debug ("Image successfully unscrambled");
}
fclose (in);
fclose (outfp);
}
return (out);
}