#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "include/memmem.h"

FILE *imageHandle;
size_t imageSize;
unsigned char *imageData, *directory, *directoryEntry, *room, *picture, *pictureEnd, *sound;

FILE *dictHandle;
size_t dictSize;
unsigned char *dictData;

char outName[16];
FILE *outHandle;
size_t outSize;

#include <assert.h>
#include <limits.h>
unsigned char rotl8 (unsigned char n, unsigned int c)
{
  const unsigned int mask = (CHAR_BIT*sizeof(n)-1);

  assert ( (c<=mask) &&"rotate by type width or more");
  c &= mask;  // avoid undef behaviour with NDEBUG.  0 overhead for most types / compilers
  return (n<<c) | (n>>( (-c)&mask ));
}

int main (int argc, char **argv) {
	int roomNumber, soundNumber, viewNumber, i;
	int startSector, numSectors, offsetInStartSector;
	unsigned char soundHeader[2], *w;
	bool encryptedWords;
	
	if (argc < 2) {
		fprintf(stderr, "usage: GALextract imagefile\n");
		return 1;
	}
	/* Load disk image given on the command line. */
	imageHandle = fopen(argv[1], "rb");
	if (imageHandle == NULL) {
		perror(argv[1]);
		exit(1);
	}
	fseek(imageHandle, 0, SEEK_END);
	imageSize = ftell(imageHandle);
	imageData = malloc(imageSize);
	fseek(imageHandle, 0, SEEK_SET);
	fread(imageData, 1, imageSize, imageHandle);
	fclose(imageHandle);
	
	/* Find dictionary data */
	if (dictData = memmem(imageData, imageSize, "\x90\x00\x80\x81\x01", 5)) encryptedWords = true; else
	if (dictData = memmem(imageData, imageSize, "\x21\x00\x01\x03\x02", 5)) encryptedWords = false; else {
		fprintf(stderr, "Cannot find dictionary data.\n");
		free(imageData);
		exit(1);
	}
	/* Decrypt (if necessary) and save dictionary */
	w = dictData;
	while (w < (imageData+imageSize)) {
		if (encryptedWords) *w = rotl8(*w, 1);
		if (w[0] == '{' && w[1] == 0) break;
		w++;
	}	
	dictSize = w - dictData;
	dictHandle = fopen("WORDS.BIN", "wb");
	if (dictHandle == NULL) {
		perror("WORDS.BIN");
		free(imageData);
		exit(1);
	}
	fwrite(dictData, 1, dictSize, dictHandle);
	fclose(dictHandle);
	
	
	/* Determine directory position. It's at offset 0x0500 on the IBM PCjr version, and at 0x1400 on all other versions. */
	directory = imageData + 0x1400;
	if ( (directory[0xFE]==0x2A && directory[0xFF]==0x2A) || (directory[0xFE]==0xF6 && directory[0xFF]==0xF6) )directory = imageData + 0x0500;
	
	for (roomNumber=0; roomNumber<256; roomNumber++) {
		directoryEntry = directory + (roomNumber<<2);
		if (memcmp(directoryEntry, "\x2A\x2A\x2A\x2A", 4)==0 || memcmp(directoryEntry, "\0\0\0\0", 4)==0) continue; // skip empty entries
		startSector = (directoryEntry[3] | (directoryEntry[2] << 8)) & 0x3FF;
		offsetInStartSector = (directoryEntry[0] + ((directoryEntry[1]&0x80) << 1)) & 0x1FF;
		numSectors = ((directoryEntry[2] | (directoryEntry[1] << 8)) >> 4) & 0x3FF;
		room = imageData + (startSector << 9) + offsetInStartSector;
		if (room > (imageData + imageSize)) continue;
		if (roomNumber < 90) { // regular room; extract logic and picture
			// determine size of logic resource by adding the sizes of the four blocks together
			outSize = (room[0] | (room[1]<<8)) +
			          (room[2] | (room[3]<<8)) +
				  (room[4] | (room[5]<<8)) +
				  (room[6] | (room[7]<<8)) +8;
			// write logic resource
			sprintf(outName, "RM.%u", roomNumber);
			outHandle = fopen(outName, "wb");
			if (outHandle == NULL) {
				perror(outName);
				free(imageData);
				exit(1);
			}
			fwrite(room, 1, outSize, outHandle);
			fclose(outHandle);
			// The picture resource starts immediately after the logic resource. Determine its end by searching for FF.
			picture = room + outSize;
			pictureEnd = memchr(picture, 0xFF, (numSectors<<9) -offsetInStartSector -outSize);
			if (pictureEnd != NULL) {
				outSize = pictureEnd +1 - picture;
				// write picture resource
				sprintf(outName, "PIC.%u", roomNumber);
				outHandle = fopen(outName, "wb");
				if (outHandle == NULL) {
					perror(outName);
					free(imageData);
					exit(1);
				}
				fwrite(picture, 1, outSize, outHandle);
				fclose(outHandle);
			}
		} else
		if (roomNumber== 127) continue; else // unknown, garbage data 
		if (roomNumber < 128) { // sound effect
			soundNumber = ((roomNumber-90)<<2) +1;
			sound = room +8;
			for (i=0; i<4; i++) {
				outSize = (room[0 +i*2] | (room[1 +i*2]<<8));
				sprintf(outName, "SND.%u", soundNumber);
				outHandle = fopen(outName, "wb");
				if (outHandle == NULL) {
					perror(outName);
					free(imageData);
					exit(1);
				}
				soundHeader[0] = outSize & 0xFF;
				soundHeader[1] = outSize >> 8;
				fwrite(soundHeader, 1, 2, outHandle);
				fwrite(sound, 1, outSize, outHandle);
				fclose(outHandle);
				sound += outSize;
				soundNumber++;
			}
		} else { // view
			outSize = room[1]*256+room[0];
			viewNumber = roomNumber -128;
			if (outSize == 1 && room[2] == 0xff)
			{
				printf("view.%d: not used\n", viewNumber);
				continue;
			}
			// write view resource			
			sprintf(outName, "VIEW.%u", viewNumber);
			outHandle = fopen(outName, "wb");
			if (outHandle == NULL) {
				perror(outName);
				free(imageData);
				exit(1);
			}
			fwrite(room+2, 1, outSize, outHandle);
			fclose(outHandle);
		}
	}
	free(imageData);
}
