/* AWARD BIOS の内容をダンプするプログラム Copyright (C) 2000 J. Kunugiza */
/* 2000/10/29 : Verison 1.00 : 取り敢えず作ってみた。一応動くらしい。     */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUF_SIZE 1024
#define TRUE 1
#define FALSE 0

typedef unsigned char  BYTE;
typedef unsigned short WORD;
typedef unsigned long  DWORD;


/* AWARD BIOS の LHA ヘッダ */

struct lha_header_t {
  BYTE size;
  BYTE checksum;
  char method[5];
  DWORD packed_size;
  DWORD orig_size;
  DWORD filetype;
  BYTE attrib;
  BYTE level;
  BYTE fn_len;
  char filename[20];
  WORD crc;
  BYTE os_type;
};


/* ファイル種別と、説明のリスト（表示用） */

const struct {
	DWORD type;
	char desc[80];
} filetypelist[] = {
	{ 0x40010000, "CPU microcode" },
	{ 0x60000000, "???" },
	{ 0x40020000, "EPA bitmap image" },
	{ 0x40030000, "APCI table" },
	{ 0x40070000, "???" },
	{ 0x08000000, "NCR SCSI BIOS" },
	{ 0x40130000, "Additional BIOS" },
	{ 0x50000000, "Main system BIOS" },
	{ 0x50000000, "ASUS logo bitmap image?" },
	{ 0x00000000, "(unknown file type)" }
};


/* ファイル種別の数値 -> 説明文に変換 */

char *get_filedesc(DWORD type)
{
	int i;
	
	i = 0;
	while (filetypelist[i].type != 0) {
		if (filetypelist[i].type == type) {
			break;
		}
		i++;
	}
	return (char *)filetypelist[i].desc;
}


/* "-lh5-" を探し、lha ヘッダを頭出しする */

int find_lha_header(FILE *f)
{
	unsigned char buf[BUF_SIZE];
	int i, j;
	
	for (;;) {
		if (fread(buf, sizeof(buf), 1, f) != 1) {
			if (feof(f)) {
				return 0;
			} else {
				perror("fread");
				exit(1);
			}
		}
		for (i = 0; i < sizeof(buf); i++) {
			if (buf[i] == '-') {
				j = sizeof(buf) - i;
				if (j >= 5) {
					if (strncmp(&buf[i], "-lh5-", 5) == 0) {
						fseek(f, (j * -1) - 2, SEEK_CUR);
						return 1;
					}
				} else {
					fseek(f, (j * -1), SEEK_CUR);
					break;
				}
			}
		}
	}
}


/* LHA ヘッダを lha_header 構造体に読み込む */

int read_lha_header(FILE *f, struct lha_header_t *lha_header)
{
	int size;
	unsigned char header[256], *buf;

	size = fgetc(f);		/* ヘッダサイズを取得 */
	if (size <= 0) {
		if (ferror(f)) {
			perror("fgetc");
			exit(1);
		} else {
			return 0;
		}
	}
	lha_header->size = size;
	if (fread(header, size + 1, 1, f) != 1) {
		if (ferror(f)) {
			perror("fread");
			exit(1);
		} else {
			return 0;
		}
	}

#define TO_BYTE(a)  (BYTE)(*a++)
#define TO_WORD(a)  (WORD)(*a | *(a+1) << 8); a += 2
#define TO_DWORD(a) (DWORD)(*a | *(a+1) << 8 | *(a+2) << 16 | *(a+3) << 24); a += 4

	buf = header;
	lha_header->checksum = TO_BYTE(buf);
	memcpy(lha_header->method, buf, 5);
	lha_header->method[5] = '\0';
	buf += 5;
	lha_header->packed_size = TO_DWORD(buf);
	lha_header->orig_size = TO_DWORD(buf);
	lha_header->filetype = TO_DWORD(buf);
	lha_header->attrib = TO_BYTE(buf);
	lha_header->level = TO_BYTE(buf);
	lha_header->fn_len = TO_BYTE(buf);
	memcpy(lha_header->filename, buf, lha_header->fn_len);
	lha_header->filename[lha_header->fn_len] = '\0';
	buf += lha_header->fn_len;
	lha_header->crc = TO_WORD(buf);
	lha_header->os_type = TO_BYTE(buf);
	
	return 1;
}


/* LHA ヘッダをダンプする */

int print_bios_header(long offset, struct lha_header_t *lha_header)
{
	char *filedesc;

	filedesc = get_filedesc(lha_header->filetype);

	printf("OFFSET = 0x%05lX   TYPE      = 0x%08lX (%s)\n", offset, lha_header->filetype, filedesc);
	printf("                   FILENAME  = %s\n", lha_header->filename);
	printf("                   COMP SIZE = 0x%08lX (%ld) bytes\n", lha_header->packed_size, lha_header->packed_size);
	printf("                   ORIG SIZE = 0x%08lX (%ld) bytes\n", lha_header->orig_size, lha_header->orig_size);

	return 1;
}


/* アーカイブを LZH ファイルとして抽出し、保存する */

int save_archive(FILE *f, struct lha_header_t *lha_header, long offset)
{
	FILE *lzh;
	char filename[15], buf[BUF_SIZE];
	long size, read;

	/* ファイル名は、OFFSETアドレス.lzh となる */
	sprintf(filename, "%08lX.lzh", offset);

	if(!(lzh = fopen(filename, "wb"))) {
		perror("fopen");
		exit(1);
	}
	size = lha_header->size + lha_header->packed_size + 3;
	fseek(f, offset, SEEK_SET);
	for (;;) {
		read = (size < sizeof(buf) ? size : sizeof(buf));
		if(!fread(buf, read, 1, f)) {
			perror("fread");
			exit(1);
		}
		if(!fwrite(buf, read, 1, lzh)) {
			perror("fwrite");
			exit(1);
		}
		size -= read;
		if (size == 0) {
			break;
		}
	}
	fclose(lzh);
	return 1;
}


/* BIOS の中身をダンプ（オプションによりセーブ）する */

int dump(FILE *bios, int save)
{
	struct lha_header_t lha_header;
	long offset;

	while (find_lha_header(bios) > 0) {
		offset = ftell(bios);
		read_lha_header(bios, &lha_header);
		if (save) {
			save_archive(bios, &lha_header, offset);
		}
		print_bios_header(offset, &lha_header);
	}
}


void usage(char *command)
{
	fprintf(stderr, "usage: %s [-s] filename\n", command);
	exit(1);
}


int main(int argc, char *argv[])
{
	FILE *bios;
	char *filename;
	int save;

	if (argc == 2) {
		filename = argv[1];
		save = FALSE;
	} else if (argc == 3) {
		if (strncmp(argv[1], "-s", 2) != 0) {
			fprintf(stderr, "Invalid option: %s\n", argv[1]);
			usage(argv[0]);
		}
		filename = argv[2];
		save = TRUE;
	} else {
		usage(argv[0]);
	}
	if (!(bios = fopen(filename, "rb"))) {
		perror("fopen");
		usage(argv[0]);
	}
	dump(bios, save);
	fclose(bios);
	return 0;
}
