This repository has been archived on 2026-05-18. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gcmbrowser/source/.svn/text-base/gcminfo.cpp.svn-base

675 lines
19 KiB
Plaintext
Raw Permalink Normal View History

2026-05-18 12:36:54 -04:00
#include "gcminfo.h"
#include <unistd.h>
#include <vector>
#include <string>
int needByteSwap = 0;
// these are functions that the user really doesn't need to see,
// so they're declared here away from the external API. They're
// used mainly for populating the internal FST data structure.
// All the user-necessary functions are listed out in gcminfo.h.
int gcmPopulateFST(GCM *data, FILE *file);
void *gcmWhichData(int whichdata, GCM *data);
int gcmGetData(int whichdata, GCM *data, FILE *file);
int gcmGetFile(GCM *data, GCM_FSTEntry *entry, int fnum, FILE *file);
int gcmCheckMagic(FILE *file);
// checks the magic number of the DVD file, and sets needByteSwap
// returns the final value of needByteSwap. Returns -1 if the magic
// word doesn't match either way and needByteSwap couldn't be set.
int gcmCheckMagic(FILE *file)
{
int magic = 0;
fseek(file, GCMPieces[GCM_DHEAD_DVDMAGIC].offset, 0);
fread(&magic, GCMPieces[GCM_DHEAD_DVDMAGIC].size, 1, file);
if ( magic == GCNDVD_MAGICNUM ) {
needByteSwap = 0;
} else {
magic = (((magic&0x000000FF)<<24)+((magic&0x0000FF00)<<8)+
((magic&0x00FF0000)>>8)+((magic&0xFF000000)>>24));
if ( magic == GCNDVD_MAGICNUM ) {
needByteSwap = 1;
}
else needByteSwap = -1;
}
return needByteSwap;
}
// swap from big to little endian (only needed on intel machines)
// only works if needByteSwap is > -1
int byteSwap (int nLongNumber)
{
if ( needByteSwap ) return (((nLongNumber&0x000000FF)<<24)+((nLongNumber&0x0000FF00)<<8)+
((nLongNumber&0x00FF0000)>>8)+((nLongNumber&0xFF000000)>>24));
else if ( needByteSwap == -1 ) return 0; // refuse to work
}
// return the type (0=file 1=directory) for the given FST entry
int gcmGetFSTEntryType(GCM_FSTEntry *entry)
{
return entry->fname_offset >> 24;
}
// uses one of the GCM_* definitions to return a pointer to
// the appropriate point of the GCM struct to read data into.
void *gcmWhichData(int whichdata, GCM *data)
{
void *toPrint = NULL;
switch (whichdata) {
case GCM_DHEAD_GAMECODE:
toPrint = &data->head.gamecode;
break;
case GCM_DHEAD_GAMEMAKER:
toPrint = &data->head.gamemaker;
break;
case GCM_DHEAD_DVDMAGIC:
toPrint = &data->head.magicword;
break;
case GCM_DHEAD_GAMENAME:
toPrint = &data->head.gamename;
break;
case GCM_DHEAD_DEBUGMON:
toPrint = &data->head.debugmon;
break;
case GCM_DHEAD_DEBUGMONADDR:
toPrint = &data->head.debugmonaddr;
break;
case GCM_DHEAD_BOOTFILE:
toPrint = &data->head.bootfile;
break;
case GCM_DHEAD_FST:
toPrint = &data->fst.offset;
break;
case GCM_DHEAD_FSTSIZE:
toPrint = &data->fst.size;
break;
case GCM_DHEAD_FSTMAXSIZE:
toPrint = &data->fst.maxsize;
break;
case GCM_DHEAD_USERPOS:
toPrint = &data->head.userpos;
break;
case GCM_DHEAD_USERLEN:
toPrint = &data->head.userlen;
break;
case GCM_DHEADINF_DEBUGMONSIZE:
toPrint = &data->headInf.debugmonsize;
break;
case GCM_DHEADINF_SIMMEMSIZE:
toPrint = &data->headInf.simmemsize;
break;
case GCM_DHEADINF_ARGOFFSET:
toPrint = &data->headInf.simmemsize;
break;
case GCM_DHEADINF_DEBUGFLAG:
toPrint = &data->headInf.debugflag;
break;
case GCM_DHEADINF_TRACKLOCATION:
toPrint = &data->headInf.tracklocation;
break;
case GCM_DHEADINF_TRACKSIZE:
toPrint = &data->headInf.tracksize;
break;
case GCM_DHEADINF_COUNTRYCODE:
toPrint = &data->headInf.countrycode;
break;
case GCM_APPLOADER_VERSION:
toPrint = &data->apploader.datever;
break;
case GCM_APPLOADER_ENTRY:
toPrint = &data->apploader.entry;
break;
case GCM_APPLOADER_SIZE:
toPrint = &data->apploader.size;
break;
case GCM_APPLOADER_TRAILERSIZE:
toPrint = &data->apploader.trailersize;
break;
case GCM_APPLOADER_BINARY:
toPrint = &data->apploader.apploader;
break;
case GCM_FST_ROOT:
toPrint = (void *)"Not implemented yet";
break;
case GCM_FST_STRINGTABLEOFF:
toPrint = &data->fst.stringtable_offset;
break;
default:
return NULL;
}
return toPrint;
}
// print the info in data as indicated by the GCM_*
// passed to whichdata
int gcmPrintData(int whichdata, GCM *data)
{
void *ptr;
if ( GCMPieces[whichdata].type == 1) {
char *theData = (char *)gcmWhichData(whichdata,data);
printf("%-38s:\t %s\n", GCMPieces[whichdata].name, theData);
}
else {
int *theData = (int *)gcmWhichData(whichdata,data);
if ( theData ) {
printf("%-38s:\t %x\n", GCMPieces[whichdata].name, *theData);
}
else {
printf("%-38s:\t refusing to print NULL pointer...\n",
"(untested function/invalid data)"); // this means you're using a feature that doesn't work (yet)
}
}
}
// print the information for a given file in data (by entry num)
int gcmPrintFile(GCM *data, int fnum)
{
GCM_FSTEntry f;
char *fname = NULL;
// printf("DEBUG : retrieving file data for number %d\n", fnum);
f = data->fst.entries.at(fnum);
// see gcmPrint for how this is laid out...
if ( fnum >= data->fst.entries.size() )
fname = "(out_of_bounds string)" ;
else
fname = (char *)data->fst.stringtable.at(fnum)->c_str();
printf("%-20s%-6d%-6x%-12x%-20d\n",
fname,
fnum,
gcmGetFSTEntryType(&f),
f.file_offset,
f.file_length);
}
// reads data into dest and returns the number of bytes read
// (only used internally by the other gcm functions, this should
// be transparent outside the API guts)
int gcmGetData(int whichdata, GCM *data, FILE *file)
{
int offset = 0;
void *dest = gcmWhichData(whichdata, data);
if ( whichdata > GCM_MAX_PIECES || dest == NULL || file == NULL) {
return 0;
}
switch ( GCMPieces[whichdata].area ) {
case 0:
offset = GCMPieces[whichdata].offset;
break;
case 1:
offset = GCMPieces[GCM_DHEAD].offset + GCMPieces[whichdata].offset;
break;
case 2:
offset = GCMPieces[GCM_DHEADINF].offset + GCMPieces[whichdata].offset;
break;
case 3:
offset = GCMPieces[GCM_APPLOADER].offset + GCMPieces[whichdata].offset;
break;
case 4:
offset = data->fst.offset + GCMPieces[whichdata].offset;
default:
return 0;
}
fseek(file, offset, 0);
if ( GCMPieces[whichdata].type == 1 ) {
fgets((char *)dest, GCMPieces[whichdata].size, file) == NULL;
}
else {
fread(dest, 1, GCMPieces[whichdata].size, file);
int *tmp = (int *)dest;
*tmp = byteSwap(*tmp);
}
rewind(file);
return GCMPieces[whichdata].size;
}
// get the file at fnum from file for the GCM data and store it
// in entry
int gcmGetFile(GCM *data, GCM_FSTEntry *entry, int fnum, FILE *file)
{
if ( fnum > -1 && file != NULL ) {
rewind(file);
fseek(file, data->fst.offset + (12 * fnum), 0);
if ( !feof(file) ) {
fread(&entry->fname_offset, 4, 1, file);
entry->fname_offset = byteSwap(entry->fname_offset);
fread(&entry->file_offset, 4, 1, file);
entry->file_offset = byteSwap(entry->file_offset);
fread(&entry->file_length, 4, 1, file);
entry->file_length = byteSwap(entry->file_length);
data->fst.entries.push_back(*entry);
}
}
else return -1;
}
// populate the FST of the given GCM from file
int gcmPopulateFST(GCM *data, FILE *file)
{
GCM_FSTEntry *newEntry;
GCM_FSTEntry root;
int i = 0;
int j = 0;
int oldpos = 0;
int maxentries = 0;
char *tmpbuff = new char[GCNDVD_FNAME_LENGTH];
std::string * strdest = new std::string("");
char tmp = 0xFF;
int f = 0;
int *appSize = NULL;
if ( !tmpbuff ) {
return -1;
}
fseek(file, data->fst.offset, 0);
// now just loop until there are no more entries...
gcmGetFile(data, &root, 0,file);
root = data->fst.entries.front();
// this is where we inject the filesystem entries for bootloader.bin, apploader.bin,
// and game.dol ... these aren't technically part of the filesystem, but we
// do this so they can be viewed/extracted like other files.
/*
appSize = (int *)gcmWhichData(GCM_APPLOADER_SIZE, data);
newEntry = new GCM_FSTEntry;
newEntry->fname_offset = 0;
newEntry->file_offset = GCM_APPLOADER_CODE_LOC;
newEntry->file_length = *appSize;
newEntry->stringtable_off = 1;
data->fst.entries.push_back(*newEntry);
delete newEntry;*/
for ( int i = 1; i < root.file_length ; i++) {
newEntry = new GCM_FSTEntry;
gcmGetFile(data, newEntry, i, file);
delete newEntry;
}
// we're past the FST proper; now the string table...
data->fst.stringtable_offset = data->fst.offset + (root.file_length * 12);
fseek(file, data->fst.stringtable_offset, 0);
//printf("FST string table offset calculated to 0x\%x...\n", data->fst.stringtable_offset);
data->fst.stringtable.clear();
data->fst.stringtable.push_back(&std::string("/\0"));
//data->fst.stringtable.push_back(&std::string("apploader.bin\0"));
memset(tmpbuff, GCNDVD_FNAME_LENGTH, 0x00);
while ( ftell(file) < (data->fst.offset + data->fst.size) && !feof(file)) {
i = 0;
while ( 1 ) {
tmp = fgetc(file);
if ( tmp == 0x00 ) {
break;
}
tmpbuff[i] = tmp;
i++;
}
tmpbuff[i] = '\0';
strdest->clear();
strdest->reserve(i);
strdest->assign(tmpbuff);
data->fst.stringtable.push_back(strdest);
// printf("DEBUG : FILENAME WAS %s ... added %s\n", strdest,
// data->fst.stringtable.at(f+1)->c_str());
memset(tmpbuff, GCNDVD_FNAME_LENGTH, 0x00);
strdest = new std::string("");
f++;
}
delete tmpbuff;
return 0;
}
// called inside of gcmInit to make sure everything inside is sane
// (only needed internally)
int gcmZeroOut(GCM *data)
{
data->apploader.entry = 0;
data->apploader.size = 0;
data->apploader.trailersize = 0;
data->fst.maxsize = 0;
data->fst.offset = 0;
data->fst.size = 0;
data->head.bootfile = 0;
data->head.debugmon = 0;
data->head.debugmonaddr = 0;
data->head.gamecode = 0;
data->head.gamemaker = 0;
data->head.magicword = 0;
data->head.userlen = 0;
data->head.userpos = 0;
data->headInf.argoff = 0;
data->headInf.countrycode = 0;
data->headInf.debugflag = 0;
data->headInf.debugmonsize = 0;
data->headInf.simmemsize = 0;
data->headInf.tracklocation = 0;
data->headInf.tracksize = 0;
}
// reads all info from the GCM in gcmname and puts it in data
int gcmInit(char *gcmname, GCM *data)
{
FILE *gcm = fopen(gcmname, "r+b");
int i = 0;
if (!gcm) {
return -1;
}
gcmZeroOut(data);
switch ( gcmCheckMagic(gcm) ) {
case 0:
needByteSwap = 1;
break;
case 1:
break;
case -1:
//printf("DEBUG: DVD Magic number on GCM image was incorrect or couldn't be understood!\n");
//printf("DEBUG: Aborting in GCMInit! (filename %s)\n", gcmname);
fclose(gcm);
return -1;
}
for ( i = 3; i < GCM_MAX_PIECES ; i++) {
gcmGetData(i,data, gcm);
}
gcmPopulateFST(data, gcm);
fclose(gcm);
data->fname = new char[strlen(gcmname)+1];
if ( !data->fname ) {
printf("couldn't store GCM filename for later retrieval. Aborting.\n");
gcmFreeStringTable(data);
exit(-1);
}
else strcpy(data->fname, gcmname);
return 0;
}
// print all information about the GCM in a columnar format,
// including the FST.
int gcmPrint(GCM *data)
{
int ml = 0;
int i = 0;
for (i = 3; i < GCM_MAX_PIECES; i++ ) {
gcmPrintData(i,data);
}
GCM_FSTEntry root = data->fst.entries.front();
printf("%-38s:\t%d\n", "Total files in the FST", root.file_length);
printf("%-38s:\t%d\n", "Total entries in the FST string table", data->fst.stringtable.size());
return 0;
}
int gcmPrintFST(GCM *data)
{
int ml = 0;
int i = 0;
char *tbuff = new char[GCNDVD_FNAME_LENGTH];
if (!tbuff) {
printf("Couldn't get temporary buffer for full path printing. Aborting.\n");
return -1;
} else {
memset(tbuff, 0x00, GCNDVD_FNAME_LENGTH);
}
GCM_FSTEntry root = data->fst.entries.front();
printf("%-38s:\t%d\n", "Total files in the FST", root.file_length);
printf("%-38s:\t%d\n", "Total entries in the FST string table", data->fst.stringtable.size());
printf("\n\n\n%30s\n", "(FST CONTENTS)");
printf("%-16s %-6s %-6s %s %-12s\n",
"(name)", "(fnum)", "(type)", "(offset)", "(length)");
for (i = 0; i < data->fst.entries.size() - 1 ; i++) {
//printf("DEBUG: retrieving file data for number %d\n", i);
gcmPrintFile(data, i);
}
printf("\n\t\t\t(PRINTING FILES WITH FULL PATH)\n");
for (i = 0; i < data->fst.entries.size() - 1; i++ ) {
printf("File %d:", i);
gcmGetFullFileName(data, i, tbuff);
printf(" %s\n", tbuff);
memset(tbuff, 0x00, GCNDVD_FNAME_LENGTH);
}
delete tbuff;
return 0;
}
// frees memory used by the string table. MUST be called before zeroing
// the gcm for new reading (gcmInit) or quitting the application.
// This should be accompanied by clearing the entries vector, though the
// destructor in that member should take care of cleanup for you.
// Thou shalt not leak memory!
int gcmFreeStringTable(GCM *data)
{
std::string *string = NULL;
int i = 0;
//printf("freeing memory used by FST string table... ");
for (i = 0; i < (data->fst.stringtable.size()) ; i++ ) {
string = (std::string *)data->fst.stringtable.at(i);
// we create the "/" entry for 0 manually, and it's not alloc'ed
// so don't try freeing it!
if ( string != NULL && i > 0 ) {
delete string;
}
string = NULL;
}
data->fst.stringtable.clear();
delete data->fname;
//printf(" ...done.\n");
}
GCM_FSTEntry gcmGetFileByFnum(GCM *data, int fnum)
{
return data->fst.entries.at(fnum);
}
int gcmGetFileByOffset(GCM *data, int offset)
{
GCM_FSTEntry curr;
bool done = false;
for (int i = 0; i < data->fst.entries.size() -1; i++) {
curr = data->fst.entries.at(i);
if (curr.file_offset == offset) {
return i;
}
}
}
// takes in a full pathname and retrieves the fnum
// for the appropriate file in the FST
unsigned int gcmGetFileByName(GCM *data, char *name)
{
GCM_FSTEntry curr;
bool done = false;
int nextStart = 0;
int i = 0;
char *tmp2 = (char *) new char[GCNDVD_FNAME_LENGTH];
if ( !tmp2 ) {
fprintf(stderr, "Failed to allocate temporary string storage\n");
return -1;
}
for ( i = 0; i < data->fst.stringtable.size() ; i++ ) {
gcmGetFullFileName(data, i, tmp2);
//printf("DEBUG : comparing filename %s to original %s...\n", name, tmp2);
if ( !strcmp(name, tmp2) ) {
delete tmp2;
//printf("DEBUG : found filename %s at number %d", name, i);
return i;
}
}
delete tmp2;
return -1;
}
// extracts a file into outfile from the GCM given the fnum.
// FIXME : allow the user to specify a different output filename?
unsigned int gcmExtractFile(GCM *data, int fnum, char *outfname)
{
FILE *out = NULL;
FILE *gcm = NULL;
int pos = 0;
int endpos = 0;
int b = 0;
GCM_FSTEntry curr;
std::string execstring = "";
char *outfile = NULL;
std::string tmpStr = "";
// check that the fnum is in range
if ( fnum < data->fst.entries.size() - 1) {
curr = data->fst.entries.at(fnum);
}
else {
return -1;
}
// if it's a file, open the outfile, open the GCM, and perform the copy - clean up and exit
if ( gcmGetFSTEntryType(&curr) == 0 ) {
if ( outfname ) {
outfile = outfname;
tmpStr = "";
} else {
outfile = new char[GCNDVD_FNAME_LENGTH];
if ( !outfile ) return -1;
else gcmGetFullFileName(data, fnum, outfile);
tmpStr = ".";
}
tmpStr.append(outfile);
gcm = fopen(data->fname, "rb");
out = fopen(tmpStr.c_str(), "wb");
if ( !gcm || !out ) {
gcm ? fclose(gcm) : printf("Couldn't read from gcm file\n");
out ? fclose(out) : printf("Couldn't write to output file\n");
return -1;
}
fseek(gcm, curr.file_offset, 0);
endpos = curr.file_offset + curr.file_length;
if ( ftell(gcm) != curr.file_offset || !out || !gcm) {
delete outfile;
fclose(gcm);
fclose(out);
return -1;
}
while ( !feof(gcm) && ftell(gcm) <= endpos) {
fread(&b, 1, 1, gcm);
if ( needByteSwap == 1 ) b = byteSwap(b); // swap the bytes for the host
fwrite(&b, 1, 1, out);
}
if ( outfile && !outfname )delete outfile;
fclose(gcm);
fclose(out);
printf("exploded %s\n", tmpStr.c_str());
return 0;
}
// if it's a dir, construct the mkdir command, do it, clean up and exit.
else {
if ( outfname )
outfile = outfname;
else {
outfile = new char[GCNDVD_FNAME_LENGTH];
if (!outfile ) {
return -1;
}
}
gcmGetFullFileName(data, fnum, outfile);
if ( !outfname )
execstring.append("mkdir -p .");
else
execstring.append("mkdir -p ");
execstring.append(outfile);
int retcode = system(execstring.c_str());
if ( outfile && !outfname ) delete outfile;
return 0;
}
}
// explodes the FST onto the host filesystem
int gcmExplode(GCM *data)
{
for ( int i = 0; i <= (data->fst.entries.size() - 1) ; i++ ) {
if ( gcmExtractFile(data, i, NULL) < 0 ) return -1;
}
}
// retrieves the full filename for fnum with full path
int gcmGetFullFileName(GCM *data, int fnum, char *dest)
{
bool finished = false;
GCM_FSTEntry curr;
std::vector<int> fnames;
int fn = fnum;
int depth = 0;
std::string work = "";
curr = data->fst.entries.at(fn);
while (!finished) {
depth++;
if ( depth > data->fst.entries.size() - 1) {
printf("went too far. aborting.\n");
exit(-1);
}
if ( fn == 0 ) {
fnames.push_back(0);
finished = true;
break;
}
if ( gcmGetFSTEntryType(&curr) == 0 ) {
// file
fnames.push_back(fn);
while (gcmGetFSTEntryType(&curr) == 0) {
// search up for the parent directory....
curr = data->fst.entries.at(fn - 1);
fn -= 1;
}
}
else if ( gcmGetFSTEntryType(&curr) == 1) {
// directory
fnames.push_back(fn);
fn = curr.file_offset;
curr = data->fst.entries.at(fn);
}
}
if ( fnum != 0 ) {
for (int i = fnames.size() -1 ; i >= 0; i--) {
if ( i < fnames.size()-1 ) {
work.append("/");
}
if ( fnames.at(i) != 0 ) work.append(data->fst.stringtable.at(fnames.at(i))->c_str());
}
} else work = "/";
strcpy(dest, work.c_str());
return 0;
}