Imgread.c

From OpenStreetMap Wiki
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 = &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 = &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;
}