Imgread.c
Jump to navigation
Jump to search
Copy this source code to a file called "imgread.c", compile with the C-compiler of you choice and run it! Caveat emptor.
# include <stdio.h>
# include <string.h>
# include <malloc.h>
# include <stdint.h>
/*
* Note: The acronym "FAT" as used in this program just means "File Allocation Table".
* The format of the .IMG file has nothing to do with Micro$oft's various "FAT" filesystems.
*
* This program was written as part of the 'mkgmap' project by Steve Hosgood.
* (c) Steve Hosgood, September 2009.
* This source code is released under the terms of GPLv3.
* ( See http://www.gnu.org/copyleft/gpl.html for details.... )
*
*/
typedef struct imgfile {
char *filename; /* strdup-ed */
FILE *fp;
unsigned long bytespercluster;
unsigned char xor;
} IMGFILE;
struct fat {
/* a struct to maintain FAT details of a given file */
unsigned char *name; /* strdup-ed */
unsigned short nclusters;
unsigned long size;
unsigned short *clusterlist; /* malloc-ed */
IMGFILE *in;
};
/* the FAT for the filestore itself */
static struct fat fs = { NULL, 0, 0, NULL, NULL };
static int
yr( int y )
{
if (y >= 0x63)
return y+1900;
return y+2000;
}
static void
memxor( unsigned char *bp, int len, unsigned char xor )
{
while (len > 0) {
*bp++ ^= xor;
len--;
}
return;
}
/*
* Read 'bytesperblock' bytes from a subfile of an .IMG file whose FAT info is provided via 'fp'.
* The block to read is number 'blockno', where the size of blocks was the afore-mentioned 'bytesperblock'.
* 'buffer' must point to a buffer at least 'bytesperblock' long.
*
* You can't easily read a subfile in varying-length blocks because 'blockno' counts in units of 'bytesperblock'.
* You can't read a block whose size spans two clusters in the FAT.
*/
static unsigned char *
blockread( unsigned char *buffer, int bytesperblock, int blockno, struct fat *fp )
{
int blockspercluster = fp->in->bytespercluster / bytesperblock;
int index = blockno / blockspercluster;
int offset = (blockno*bytesperblock) % fp->in->bytespercluster;
if (index >= fp->nclusters) {
fprintf(stderr, "Attempt to read block that's not in the clusterlist\n");
return NULL;
}
if (bytesperblock + offset > fp->in->bytespercluster) {
/* block spans clusters - can't cope (yet) */
fprintf(stderr, "Attempt to read block %d spanning clusters\n", blockno);
return NULL;
}
fseek(fp->in->fp, fp->clusterlist[index] * fp->in->bytespercluster + offset, SEEK_SET);
fread(buffer, sizeof( char ), bytesperblock, fp->in->fp);
if (fp->in->xor != 0x00)
memxor(buffer, bytesperblock, fp->in->xor);
return buffer;
}
/*
* Read an entire cluster from a subfile of an .IMG file whose FAT info is provided via 'fp'.
* This is the best way to transcribe a complete subfile as it reads nice big chunks of data.
*/
static unsigned char *
clusterread( unsigned char *buffer, int index, struct fat *fp )
{
if (index >= fp->nclusters) {
fprintf(stderr, "Attempt to read cluster that's not in the clusterlist\n");
return NULL;
}
fseek(fp->in->fp, fp->clusterlist[index] * fp->in->bytespercluster, SEEK_SET);
fread(buffer, sizeof( char ), fp->in->bytespercluster, fp->in->fp);
if (fp->in->xor != 0x00)
memxor(buffer, fp->in->bytespercluster, fp->in->xor);
return buffer;
}
static void
write_subfile( struct fat *subfile )
{
int n, nbytes;
long len;
unsigned char *cbuffer = malloc(subfile->in->bytespercluster);
FILE *out;
len = subfile->size;
out = fopen(subfile->name, "wb");
if (out == NULL) {
fprintf(stderr, "Can't open %s\n", subfile->name);
return;
}
/* write file, cluster by cluster */
for (n = 0; len > 0 && n < subfile->nclusters; n++) {
if (clusterread(cbuffer, n, subfile) == NULL)
break;
nbytes = (len > subfile->in->bytespercluster)? subfile->in->bytespercluster: len;
fwrite(cbuffer, 1, nbytes, out);
len -= nbytes;
}
free(cbuffer);
fclose(out);
return;
}
/*
* This program unpacks an .IMG file into a subdirectory of the working-directory.
* The subdirectory name can be specified with a '-o subdir' argument on the command line. If not given, the subdirectory
* is created with a unique name starting with the characters "imgread-" followed by some random stuff.
*
* The files created within the subdirectory have filenames as specified from within the .IMG file's structure.
*
*/
int
main( int argc, char *argv[] )
{
unsigned char buffer[0x400], mapname[64], filename[16];
unsigned short C, H, S, nblocks;
unsigned long fsize, nclusters, nc, fsizetot = 0;
int i, n;
IMGFILE in;
struct fat subfile;
FILE *out;
if (argc > 1) {
/* process the args */
char **ap = &argv[1], *outdir = NULL;
while (--argc > 0) {
if ((*ap)[0] == '-') {
switch ((*ap)[1]) {
case 'o':
outdir = *(++ap);
argc--;
if (mkdir(outdir, 0755) != 0) {
fprintf(stderr, "Could not create output directory %s\n", outdir);
return 3;
}
break;
default:
fprintf(stderr, "Option %s unknown\n", (*ap));
return 2;
}
ap++;
}
}
if (argc == 0) {
in.filename = strdup((*ap));
if ((in.fp = fopen(in.filename, "rb")) == NULL) {
fprintf(stderr, "Cannot read %s\n", in.filename);
return 1;
}
}
else {
fprintf(stderr, "No '.img' filename given\n");
return 1;
}
if (outdir == NULL) {
strncpy(filename, "imgread-XXXXXX", 15);
mkdtemp(filename); /* creates directory with mode 0700 */
chmod(filename, 0755); /* this is a better mode IMHO */
outdir = filename;
}
if (chdir(outdir) != 0) {
fprintf(stderr, "Could not 'chdir' to directory %s\n", outdir);
return 3;
}
}
else {
fprintf(stderr, "Usage: %s [-o output-dir] mapfile.img\n", argv[0]);
return 0;
}
/* get the IMG header block */
fread(buffer, sizeof( char ), 0x200, in.fp);
if ((out = fopen("IMG.hdr", "wb")) != NULL) {
fwrite(buffer, sizeof( char ), 0x200, out);
fclose(out);
}
printf("IMG Header:\n");
printf("Byte @ 0x00: 0x%02X XOR byte\n", in.xor = buffer[0x00]);
if (in.xor != 0x00)
memxor(buffer, 0x200, in.xor);
if ((out = fopen("IMG.hdr", "wb")) != NULL) {
/* if we ever create an IMG file, we'll be doing it with a zero XOR byte, thanks... */
fwrite(buffer, sizeof( char ), 0x200, out);
fclose(out);
}
printf("Byte @ 0x0A: %d Expiry month\n", buffer[0x0A]+1);
printf("Byte @ 0x0B: %d Expiry year\n", yr(buffer[0x0A]));
printf("Byte @ 0x0F: 0x%02X checksum\n", buffer[0x0F]);
printf("String @ 0x10: %s\n", &buffer[0x10]);
printf("Byte @ 0x17: 0x%02X ??\n", buffer[0x17]);
printf("Word @ 0x18: 0x%04X sectors??\n", S = *(( unsigned short * )&buffer[0x18]));
printf("Word @ 0x1A: 0x%04X heads??\n", H = *(( unsigned short * )&buffer[0x1A]));
printf("Byte @ 0x1C: 0x%04X cylinders??\n", C = buffer[0x1C]);
if (C == 0x00) C = 0x100;
printf("Byte @ 0x1D: 0x%02X ??\n", buffer[0x1D]);
printf("Word @ 0x1E: 0x%04X ??\n", *(( unsigned short * )&buffer[0x1E]));
printf("Word @ 0x39: %d Creation year\n", *(( unsigned short * )&buffer[0x39]));
printf("Byte @ 0x3B: %d Creation month\n", buffer[0x3B]+1);
printf("Byte @ 0x3C: %d Creation day\n", buffer[0x3C]+1);
printf("Byte @ 0x3D: %d Creation hour\n", buffer[0x3D]);
printf("Byte @ 0x3E: %d Creation minute\n", buffer[0x3E]);
printf("Byte @ 0x3F: %d Creation sec\n", buffer[0x3F]);
printf("Byte @ 0x40: 0x%02X ??\n", buffer[0x40]);
printf("String @ 0x41: %s\n", &buffer[0x41]);
printf("Byte @ 0x48: 0x%02X ??\n", buffer[0x48]);
# if 0
{
unsigned char t = buffer[0x5D];
buffer[0x5D] = '\0'; /* the map name (first part) isn't null-terminated */
printf("String @ 0x49: <%s>\n", &buffer[0x49]);
buffer[0x5D] = t;
}
# endif
strncpy(mapname, &buffer[0x49], 0x14);
printf("Word @ 0x5D: 0x%04X heads??\n", *(( unsigned short * )&buffer[0x5D]));
printf("Word @ 0x5F: 0x%04X sectors??\n", *(( unsigned short * )&buffer[0x5F]));
printf("Byte @ 0x61: 0x%02X E1\n", buffer[0x61]);
printf("Byte @ 0x62: 0x%02X E2: cluster-size = %ld\n", buffer[0x62], in.bytespercluster = (1L << (buffer[0x61]+buffer[0x62])));
printf("Word @ 0x63: %d nClusters?? (calculated: %d)\n", nblocks = *(( unsigned short * )&buffer[0x5F]), (S * H * C) / in.bytespercluster);
//printf("String @ 0x65: <%s>\n", &buffer[0x65]);
strcpy(&mapname[0x14], &buffer[0x65]);
printf("String @ 0x49/0x65: <%s>\n", mapname);
printf("Partition Table:\n");
for (i = 0; i < 4; i++) {
int base = 0x1BE + i*16;
printf("\tByte @ 0x%X: 0x%02X Boot flag??\n", base, buffer[base]);
printf("\tByte @ 0x%X: 0x%02X Start head??\n", base+1, buffer[base+1]);
printf("\tByte @ 0x%X: 0x%02X Start sector??\n", base+2, buffer[base+2]);
printf("\tByte @ 0x%X: 0x%02X Start cylinder??\n", base+3, buffer[base+3]);
printf("\tByte @ 0x%X: 0x%02X Filesystem type??\n", base+4, buffer[base+4]);
printf("\tByte @ 0x%X: 0x%02X End head??\n", base+5, buffer[base+5]);
printf("\tByte @ 0x%X: 0x%02X End sector??\n", base+6, buffer[base+6]);
printf("\tByte @ 0x%X: 0x%02X End cylinder??\n", base+7, buffer[base+7]);
printf("\tDWord @ 0x%X: %ld Rel-sectors??\n", base+8, *(( uint32_t * )&buffer[base+8]));
printf("\tDWord @ 0x%X: %ld Nsectors??\n\n", base+12, *(( uint32_t * )&buffer[base+12]));
}
printf("Word @ 0x1FE: 0x%02X partition-table end\n\n", *(( unsigned short * )&buffer[0x1FE]));
/* second block of file is all-zeroes */
fread(buffer, sizeof( char ), 0x200, in.fp);
if (in.xor != 0x00)
memxor(buffer, 0x200, in.xor);
/* third block of img file is where the FAT for the filestore begins */
/* first entry (fake file called " . ") reserves clusters for the IMG header and the FAT area itself */
printf("Filestore:\n");
fread(buffer, sizeof( char ), 0x200, in.fp);
if (in.xor != 0x00)
memxor(buffer, 0x200, in.xor);
printf("Byte @ 0x00: 0x%02X File flag\n", buffer[0x00]);
strncpy(filename, &buffer[0x01], 8);
filename[8] = '.';
strncpy(&filename[9], &buffer[0x09], 3);
filename[12] = '\0';
printf("String @ 0x01: <%s> Filename\n", filename);
printf("DWord @ 0x0C: %ld file size\n", fs.size = *(( uint32_t * )&buffer[0x0C]));
printf("Word @ 0x10: 0x%02X sequence\n", *(( unsigned short * )&buffer[0x10]));
/* allocate a FAT struct to keep track of the blocks of the master-directory */
fs.nclusters = (fs.size / in.bytespercluster) + 1;
fs.name = NULL;
fs.in = ∈
fs.clusterlist = calloc(sizeof( unsigned short ), fs.nclusters);
for (nc = i = 0; i < 240; i++) {
int base = 0x20 + i*2;
unsigned short cluster = *(( unsigned short * )&buffer[base]);
if (cluster == 0xFFFF)
break;
//printf("\tWord @ 0x%X: %d cluster\n", base, cluster);
fs.clusterlist[nc] = cluster;
nc++;
if (nc >= fs.nclusters) {
fprintf(stderr, "Aagh - fs.clusterlist too long\n");
return 10;
}
}
if (nc != fs.nclusters-1)
/* not sure if it's possible for the master-directory's FAT to use more than one disk-block */
printf("Hmm - fs.clusterlist too short\n");
/* so, basically, the first three 0x200 byte blocks of an .img file must appear in sequence */
/* from here on though, anything goes */
printf("FS: space taken %ld\n", fs.nclusters * in.bytespercluster);
subfile.name = NULL;
subfile.in = ∈ /* we can just set this entry once, and re-use it for all subfiles */
/* fourth block of f/s is the entry for the first proper file */
/* (assuming the master-directory's FAT was only one block long itself) */
for (n = 3; n < fs.size/0x200; n++) {
unsigned short seq;
printf("File:\n");
if (blockread(buffer, 0x200, n, &fs) == NULL)
break;
if (buffer[0x00] == 0x01) {
/* it's only a real entry if File Flag == 0x01 */
printf("Byte @ 0x00: 0x%02X File flag\n", buffer[0x00]);
strncpy(filename, &buffer[0x01], 8);
filename[8] = '.';
strncpy(&filename[9], &buffer[0x09], 3);
filename[12] = '\0';
printf("String @ 0x01: <%s> Filename\n", filename);
printf("DWord @ 0x0C: %ld file size\n", fsize = *(( uint32_t * )&buffer[0x0C]));
fsizetot += fsize;
printf("Word @ 0x10: 0x%02X sequence\n", seq = *(( unsigned short * )&buffer[0x10]));
if (fsize != 0 && seq == 0) {
/* a new file starts here */
if (subfile.name != NULL) {
/* flush the existing file that is pending */
if (nc != subfile.nclusters)
printf("Hmm - fs.clusterlist too short (%d vs. %d)\n", nc, subfile.nclusters);
write_subfile(&subfile);
/* free dynamically-allocated FAT stuff */
free(subfile.name);
free(subfile.clusterlist);
}
/* reload the FAT struct ready for a new file */
subfile.name = strdup(filename);
subfile.size = fsize;
subfile.nclusters = (fsize / in.bytespercluster) + 1;
subfile.clusterlist = calloc(sizeof( unsigned short ), subfile.nclusters);
nc = 0;
}
for (i = 0; i < 240; i++) {
int base = 0x20 + i*2;
unsigned short cluster = *(( unsigned short * )&buffer[base]);
if (cluster == 0xFFFF)
break;
//printf("\tWord @ 0x%X: %d cluster %d\n", base, cluster, nc);
subfile.clusterlist[nc] = cluster;
nc++;
if (nc > subfile.nclusters) {
fprintf(stderr, "Aagh - subfile.clusterlist too long\n");
return 10;
}
}
}
}
/* no more files in FAT */
if (subfile.name != NULL) {
/* flush the existing (final) file that is pending */
if (nc != subfile.nclusters)
printf("Hmm - fs.clusterlist too short (%d vs. %d)\n", nc, subfile.nclusters);
write_subfile(&subfile);
/* free dynamically-allocated FAT stuff */
free(subfile.name);
free(subfile.clusterlist);
}
printf("Total size of subfiles: %ld\n", fsizetot);
fclose(in.fp);
free(in.filename);
return 0;
}