Commit code, circa 2006

This commit is contained in:
2026-05-18 12:36:54 -04:00
commit 6358fb3415
38 changed files with 6889 additions and 0 deletions

View File

@@ -0,0 +1,94 @@
# Visual SlickEdit generated file. Do not edit this file except in designated areas.
# -----Begin user-editable area-----
# -----End user-editable area-----
# Make command to use for dependencies
MAKECMD=gmake
# If no configuration is specified, "Debug" will be used
ifndef "CFG"
CFG=Debug
endif
#
# Configuration: Debug
#
ifeq "$(CFG)" "Debug"
OUTDIR=Debug
OUTFILE=$(OUTDIR)/gcmbrowser
CFG_INC=
CFG_LIB=
CFG_OBJ=
COMMON_OBJ=$(OUTDIR)/gcmbrowser.o
OBJ=$(COMMON_OBJ) $(CFG_OBJ)
COMPILE=g++ -c -g -o "$(OUTDIR)/$(*F).o" $(CFG_INC) "$<"
LINK=g++ -g -o "$(OUTFILE)" $(OBJ) $(CFG_LIB)
# Pattern rules
$(OUTDIR)/%.o : source/%.cpp
$(COMPILE)
# Build rules
all: $(OUTFILE)
$(OUTFILE): $(OUTDIR) $(OBJ)
$(LINK)
$(OUTDIR):
mkdir -p "$(OUTDIR)"
# Rebuild this project
rebuild: cleanall all
# Clean this project
clean:
rm -f source/*o
rm -f $(CFG)/*o
rm -f $(OUTFILE)
# Clean this project and all dependencies
cleanall: clean
endif
#
# Configuration: Release
#
ifeq "$(CFG)" "Release"
OUTDIR=Release
OUTFILE=$(OUTDIR)/gcmbrowser
CFG_INC=
CFG_LIB=
CFG_OBJ=
COMMON_OBJ=$(OUTDIR)/gcmbrowser.o
OBJ=$(COMMON_OBJ) $(CFG_OBJ)
COMPILE=g++ -c -o "$(OUTDIR)/$(*F).o" $(CFG_INC) "$<"
LINK=g++ -o "$(OUTFILE)" $(OBJ) $(CFG_LIB)
# Pattern rules
$(OUTDIR)/%.o : source/%.cpp
$(COMPILE)
# Build rules
all: $(OUTFILE)
$(OUTFILE): $(OUTDIR) $(OBJ)
$(LINK)
$(OUTDIR):
mkdir -p "$(OUTDIR)"
# Rebuild this project
rebuild: cleanall all
# Clean this project
clean:
rm -f source/*o
rm -f $(CFG)/*o
rm -f $(OUTFILE)
# Clean this project and all dependencies
cleanall: clean
endif

View File

@@ -0,0 +1,26 @@
This is libgcm V0.1a, an early alpha of my library for
manipulating gamecube master files. It currently has
little functionality, but you can list and extract the contents
of GCMs - the only thing I haven't got extracting correctly
yet is the apploader stuff, because I haven't even really
begun working on that yet, and don't know how to really test
the results of that extraction.
Before this is over, I intend to have the library
ready to insert, append, and create entire new images for
burning to a GCM. My final test of this ability will be to
completely unpack a disc image onto the drive (which it can
already do, with the exception of the apploader) and re-pack
it and see if it plays.
Maybe someone will be helped by this code. GPL applies.
This code is free as in freedom. If you have any additions you
would like to see in the next release, I'd be happy to include them,
with credit. Just email them to me or find me in #gcdev on EFNet.
Enjoy!
Andrew K
andrew@aklabs.net
andrewk in #gcdev on EFNet
120817314 ICQ

View File

@@ -0,0 +1,163 @@
#include "gcminfo.h"
#include <string.h>
#include <unistd.h>
// All the interesting stuff is in gcminfo.c|h ; gcmbrowser.cpp is just a proof
// and simple util.
const char *usage =
"gcmbrowser V0.01 (C) 2005 Andrew Kesterson (andrew@aklabs.net) \n\
\n\
Usage: gcmbrowser <.gcm file> <operation>\n\
\n\
operations:\n\
-h : print this help\n\
-H : print the disk header (maker, title, etc)\n\
-E : explode the filesystem into the current directory\n\
-e <file> : extract \"file\" from the gcm. With -o you\n\
may specify an optional output filename; by default,\n\
the file will be named the same (and reside in the\n\
same directory tree) as in the image. You may specify\n\
the source file by file number or by filename (full path\n\
is necessary when specifying by filename.)\n\
-o <filename> : specify the destination for a -e operation.\n\
-l : list the names and file numbers of all files in the GCM.\n\
All files are printed with full path.\n\
-d : when extracting a file, create all needed directories for\n\
the output file. This can, but really shouldn't, be used with\n\
the -o option.\n\
\n";
const char *shortopts = "hHEe:o:ld";
#define OP_PRINT_HEADER 0
#define OP_EXPLODE 2
#define OP_EXTRACT 4
#define OP_LIST 8
#define OP_CREATE_DIRS 1024
int main(int argc, char **argv)
{
std::string gcmFname;
std::string outFname;
std::string extractFname;
unsigned int extractFnumber= -1;
int outIsDigit = 1;
int ctr = 0;
int op = 0;
GCM theGCM;
unsigned int opt = 0;
unsigned int ret = 0;
if ( argc < 2 ) {
fprintf(stderr, usage);
return 1;
}
opt = getopt(argc, argv, shortopts);
while ( opt != -1) {
switch ( opt ){
case 'h':
fprintf(stderr, usage);
return 1;
case 'H':
op = OP_PRINT_HEADER;
break;
case 'E':
op = OP_EXPLODE;
break;
case 'e':
op = OP_EXTRACT;
for ( ctr = 0; ctr < strlen(optarg) ; ctr++ ) {
if ( isprint(optarg[ctr]) && !isdigit(optarg[ctr])) {
outIsDigit = 0;
} else {
//printf("DEBUG : extract filename character %c is a digit\n", optarg[ctr]);
continue;
}
}
if ( outIsDigit == 0 ) {
//printf("DEBUG : extract filename option is NON-DIGIT %s\n", optarg);
extractFname.reserve(strlen(optarg));
extractFname.assign(optarg);
break;
}
else {
//printf("DEBUG : extract filename option is file number %s\n", optarg);
extractFnumber = atoi(optarg);
break;
}
case 'o':
outFname.reserve(strlen(optarg));
outFname.assign(optarg);
break;
case 'l':
op = OP_LIST;
break;
case 'd':
op |= OP_CREATE_DIRS;
break;
default:
fprintf(stderr, "I don't understand option -%c\n", opt);
return 1;
}
opt = getopt(argc, argv, shortopts);
}
gcmFname.reserve(strlen(argv[optind]));
gcmFname.assign(argv[optind]);
if ( gcmInit((char *)gcmFname.c_str(), &theGCM) ) {
printf("Couldn't init GCM file %s\n", argv[1]);
return 1;
}
int top = 0;
if ( op & OP_CREATE_DIRS ) top = op ^ OP_CREATE_DIRS;
else top = op;
switch ( top ) {
case OP_EXPLODE:
ret = gcmExplode(&theGCM);
if ( ! ret )
fprintf(stderr, "Failed to explode filesystem.\n");
break;
case OP_EXTRACT:
if ( extractFnumber == -1 ) {
//printf("DEBUG : getting file number for %s\n", extractFname.c_str());
extractFnumber = gcmGetFileByName(&theGCM, (char *)extractFname.c_str());
//printf("DEBUG: got %d\n", extractFnumber);
}
if ( extractFnumber > theGCM.fst.stringtable.size() || extractFnumber == -1) {
fprintf(stderr, "File number %d requested was beyond the length of the filesystem\n", extractFnumber);
break;
}
if ( outFname.length() == 0 && ( op & OP_CREATE_DIRS ) ) {
char *outfile = (char *) new char[GCNDVD_FNAME_LENGTH];
gcmGetFullFileName(&theGCM, extractFnumber, outfile);
std::string execstring = "";
// this is a quick hack to ensure that the output directory exists
execstring.append("mkdir -p .");
execstring.append("`dirname ");
execstring.append(outfile);
execstring.append("`");
system(execstring.c_str());
delete outfile;
}
if ( outFname.length() > 0 )
ret = gcmExtractFile(&theGCM, extractFnumber, (char *)outFname.c_str());
else
ret = gcmExtractFile(&theGCM, extractFnumber, NULL);
if ( ret != 0 )
fprintf(stderr, "Failed to extract file number %d to output file %s\n", extractFnumber, outFname.length() > 0 ? (char *)outFname.c_str() : "" );
break;
case OP_PRINT_HEADER:
ret = gcmPrint(&theGCM);
break;
case OP_LIST:
ret = gcmPrintFST(&theGCM);
break;
}
gcmFreeStringTable(&theGCM);
return ret;
}

View File

@@ -0,0 +1,674 @@
#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;
}

View File

@@ -0,0 +1,217 @@
// thanks to groepaz and everyone else who wrote YAGCD, most of my info came from it
// also thanks to WntrMute for loaning me some source to clear things up about the FST
// (C) 2005 Andrew K andrew@aklabs.net
// License: Take all you want, but credit for all you take.
#include <vector>
#include <string>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LIBGCMVERSION "libGCM Version 0.1a (build Sep 4 2005)"
// defines for internal program control (not necessarily GCM-related)
#define GCNDVD_MAGICNUM 0xc2339f3d // if this isn't correct in the GCM,
// the program will abort.
#define GCNDVD_SECTORS 712880 // not really used, but left for future updates that might use it
#define GCNDVD_SECTORSIZE 2048 // " "
#define GCNDVD_FNAME_LENGTH 1024
#define GCM_MAX_PIECES 29 // change this if (for whatever reason) you add a new entry in GCMPieces
// these all define indices in the GCMPieces global array where
// information about (where to look in the GCM for) a given piece
// of data about the GCM can be found. The rationale should
// be easy enough to follow.
#define GCM_DHEAD 0
#define GCM_DHEADINF 1
#define GCM_APPLOADER 2
#define GCM_DHEAD_GAMECODE 3
#define GCM_DHEAD_GAMEMAKER 4
#define GCM_DHEAD_DVDMAGIC 5
#define GCM_DHEAD_GAMENAME 6
#define GCM_DHEAD_DEBUGMON 7
#define GCM_DHEAD_DEBUGMONADDR 8
#define GCM_DHEAD_BOOTFILE 9
#define GCM_DHEAD_FST 10
#define GCM_DHEAD_FSTSIZE 11
#define GCM_DHEAD_FSTMAXSIZE 12
#define GCM_DHEAD_USERPOS 13
#define GCM_DHEAD_USERLEN 14
#define GCM_DHEADINF_DEBUGMONSIZE 15
#define GCM_DHEADINF_SIMMEMSIZE 16
#define GCM_DHEADINF_ARGOFFSET 17
#define GCM_DHEADINF_DEBUGFLAG 18
#define GCM_DHEADINF_TRACKLOCATION 19
#define GCM_DHEADINF_TRACKSIZE 20
#define GCM_DHEADINF_COUNTRYCODE 21
#define GCM_APPLOADER_VERSION 22
#define GCM_APPLOADER_ENTRY 23
#define GCM_APPLOADER_SIZE 24
#define GCM_APPLOADER_TRAILERSIZE 25
#define GCM_APPLOADER_BINARY 26
#define GCM_APPLOADER_CODE_LOC 0x2450
#define GCM_FST_ROOT 28
#define GCM_FST_STRINGTABLEOFF 29
typedef struct {
int area; // 0 = basic structure, 1 = header, 2= header info, 3 = apploader, 4 = FST
char *name;
long offset;
long size;
int type; // 1 for string, 0 for numeric value (strings aren't byteswapped)
} GCMPiece;
// pieces with area 1 add the base of the disk header to their offset
// pieces with area 2 add the base of the disk header info to their offset
// pieces with area 3 add the base of the apploader to their offset
// pieces with area 4 add the base of the FST to their offset (garnered from the disk header)
static GCMPiece GCMPieces [] ={
// root disc pieces
{0, "Disk Header (boot.bin)", 0x00000000, 0x0440, 0},
{0, "Disk Header Information (bi2.bin)", 0x00000440, 0x2000, 0},
{0, "Apploader (appldr.bin)", 0x00002440, 0x2000, 0}, // 0x2000 size is an estimate I think
// disk header
{1, "Game code", 0x0000, 0x0004, 0},
{1, "Game maker code", 0x0004, 0x0002, 0},
{1, "DVD Magic Word", 0x001c, 0x0004, 0},
{1, "Game name", 0x0020, 0x03e0, 1},
{1, "Debug monitor offset", 0x0400, 0x0004, 0},
{1, "Debug monitor load address", 0x0404, 0x0004, 0},
{1, "Bootfile offset", 0x0420, 0x0004, 0},
{1, "FST offset", 0x0424, 0x0004, 0},
{1, "FST size", 0x0428, 0x0004, 0},
{1, "FST Max Size", 0x042c, 0x0004, 0},
{1, "User position", 0x0430, 0x0004, 0},
{1, "User length", 0x0434, 0x0004, 0},
// the disk header info is loaded to 0x800000f4 when the disc is loaded by the IPL (tho we're reading it straight
// out of the .gcm image, so YMMV)
{2, "Debug-monitor size", 0x0000, 0x0004, 0},
{2, "Simulated memory size", 0x0004, 0x0004, 0},
{2, "Argument offset", 0x0008, 0x0004, 0},
{2, "Debug flag", 0x000c, 0x0004, 0},
{2, "Track location", 0x0010, 0x0004, 0},
{2, "Track size", 0x0014, 0x0004, 0},
{2, "Countrycode", 0x0018, 0x0004, 0},
// apploader header info
{3, "Version/Date", 0x0000, 0x000F, 1}, // don't include the padding in the date
{3, "Apploader entry point", 0x0010, 0x0004, 0},
{3, "Apploader size", 0x0014, 0x0004, 0},
{3, "Trailer size", 0x0018, 0x0004, 0},
{3, "Apploader binary", 0x0020, 0, 1}, // no length given - taken from the "apploader size"
// FST info
{4, "Root directory entry", 0x00, 0x0c, 0},
{4, "FST String table offset", 0x0008, 0x0004, 0}
};
// primary GCM disk header
typedef struct GCM_DiskHeader{
int gamecode;
int gamemaker;
int magicword;
char gamename[0x03e0];
int debugmon;
int debugmonaddr;
int bootfile;
int userpos;
int userlen;
};
// the GCM disk header additional info
typedef struct GCM_DiskHeaderInfo {
int debugmonsize;
int simmemsize;
int argoff;
int debugflag;
int tracklocation;
int tracksize;
int countrycode;
};
// information about the apploader on the GCM
typedef struct GCM_ApploaderInfo{
char datever[10];
int entry;
int size;
int trailersize;
char apploader[0x200]; // actual apploader data
};
// information about a particular file in the FST
typedef struct GCM_FSTEntry {
int fname_offset; // offset into the FST string table for the filename
// the offset is only actually 3 bytes, but
// we're only reading, not writing so it doesn't matter
// if we're grande sized ....
// the first byte of fname_offset is actually the flags of the file; 0: file 1: dir
int file_offset; // if this is a directory, this is actually the offset of the parent
int file_length; // actually the number of entries (root) or next_offset (dir)
int stringtable_off; // offset in the string table
};
// information about the names and locations of files in the GCM
typedef struct GCM_FST {
int size;
int offset;
int stringtable_offset;
int maxsize;
std::vector <GCM_FSTEntry>entries; // list of entries
std::vector <std::string *>stringtable; // string table
};
// GCM image data structure
typedef struct GCM {
GCM_DiskHeader head;
GCM_DiskHeaderInfo headInf;
GCM_ApploaderInfo apploader;
GCM_FST fst;
char *fname;
};
// these are just the functions that the user would need beyond
// the functionality provided by gcmInit's loader; more internal
// functions can be found inside gcminfo.c
// returns 0 for a regular file, 1 for a directory
extern int gcmGetFSTEntryType(GCM_FSTEntry *entry);
// initialises the GCM data structures with FST and such;
extern int gcmInit(char *gcmname, GCM *data);
// prints a given portion of the GCM data
// whichdata should be an indice into the GCMPieces array
extern int gcmPrintData(int whichdata, GCM *data);
// prints all the data in the GCM in a (fairly) columnar format
extern int gcmPrint(GCM *data);
// prints a comprehensive listing of all files in the GCM
extern int gcmPrintFST(GCM *data);
// retrieves a given file and puts it in the FST entries,
// given the fnum
extern int gcmGetFile(GCM *data, GCM_FSTEntry *entry, int fnum, FILE *file);
// prints information for a given fname
extern int gcmPrintFile(GCM *data, int fnum);
// frees all data in the FST stringtable
extern int gcmFreeStringTable(GCM *data);
// extracts a file, given the fnum, into the current dir, unless outfname is specified
extern unsigned int gcmExtractFile(GCM *data, int fnum, char *outfname);
// puts the full filename (with path) into dest, given fnum
extern int gcmGetFullFileName(GCM *data, int fnum, char *dest);
// returns the fnum of a given file by its offset
extern int gcmGetFileByOffset(GCM *data, int offset);
// returns the fnum of a given file by its full pathname
extern unsigned int gcmGetFileByName(GCM *data, char *name);
// returns the actual entry of a file given its fnum
extern GCM_FSTEntry gcmGetFileByFnum(GCM *data, int fnum);
// explodes the FST onto the host filesystem in the current directory
extern int gcmExplode(GCM *data);

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
/* What you are seeing is a work in progress. Source and comments
it contains could be dangerous, profane, or just plain wrong.
While I generally don't publish source code unless I feel it's in
a semi-production state (or at least to the point where someone
somewhere could benefit from it), I can't guarantee anything.
If you use this code and the genie of your computer (or your cube!) escapes in
a puff of smoke, I will not be held liable. You have been warned.
This code may be ugly, incomplete, or just plain wrong.
No F'in warranty implied!
Andrew K 2005 andrew@aklabs.net */