updated the readme, moved 2mg, fixed bugs

This commit is contained in:
Sean 2020-02-23 00:54:26 -07:00
parent d8e52ee98d
commit 56774401be
35 changed files with 674 additions and 4558 deletions

344
2mg.c
View File

@ -1,344 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* Extracts 2mg and other prodos disk images into a directory structure
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <ctype.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <argp.h>
#include <stdbool.h>
#include <locale.h>
#include "handle.h"
#include "prodos_types.h"
static void doDirectory(uint16_t key, uint8_t *disk, uint32_t disklen,
int depth);
static void doEntry(uint8_t *entry, uint8_t *disk, uint32_t disklen,
int depth);
static void doFile(uint16_t key, uint32_t len, char *name, uint8_t *disk,
uint32_t disklen, int type);
static void doGSOS(uint16_t key, char *name, uint8_t filetype, uint8_t *disk,
uint32_t disklen, int depth);
const char *argp_program_version = "2mg 0.2";
const char *argp_program_bug_address = "sean@seancode.com";
static char doc[] = "Extract ProDOS disk images";
static char args_doc[] = "DISKIMAGE";
static struct argp_option options[] = {
{"list", 'l', 0, 0, "List files"},
{ 0 }
};
struct arguments {
char *diskimage;
bool list;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'l':
arguments->list = true;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
}
arguments->diskimage = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
int main(int argc, char **argv) {
struct arguments arguments;
arguments.list = false;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
setlocale(LC_NUMERIC, "");
FILE *f = fopen(arguments.diskimage, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", arguments.diskimage);
return -1;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
if (len < 64) {
fprintf(stderr, "%s is not a valid disk image\n", argv[1]);
fclose(f);
return -1;
}
uint8_t *header = malloc(64);
fread(header, 64, 1, f);
uint32_t disklen = len;
uint32_t diskofs = 0;
if (r4(header) == fourcc("2IMG")) {
if (r32(header + 0xc) != 1) {
fprintf(stderr, "Not a ProDOS disk image\n");
fclose(f);
return -1;
}
disklen = r32(header + 0x14) * 512;
diskofs = r32(header + 0x18);
}
free(header);
fseek(f, diskofs, SEEK_SET);
uint8_t *disk = malloc(disklen);
fread(disk, disklen, 1, f);
fclose(f);
doDirectory(2, disk, disklen, arguments.list ? 0 : -1);
free(disk);
return 0;
}
static void readFilename(uint8_t *filename, uint8_t length, char *outname) {
for (int i = 0; i < length; i++) {
char ch = filename[i];
if (isalnum(ch) || ch == '_' || ch == '.' || ch == ' ') {
*outname++ = ch;
} else {
*outname++ = 'x';
char hi = ch >> 4;
char lo = ch & 0xf;
if (hi > 9) {
*outname++ = 'a' + (hi - 10);
} else {
*outname++ = '0' + hi;
}
if (lo > 9) {
*outname++ = '0' + (lo - 10);
} else {
*outname++ = '0' + lo;
}
}
}
*outname = 0;
}
static void indent(int depth) {
for (int i = 0; i < depth; i++) {
printf(" ");
}
}
static void doDirectory(uint16_t key, uint8_t *disk, uint32_t disklen,
int depth) {
uint8_t *block = disk + key * 512;
if ((block[4] & 0xf0) != 0xf0 && (block[4] & 0xf0) != 0xe0) {
fprintf(stderr, "Invalid ProDOS disk\n");
return;
}
char dirname[50];
readFilename(block + 5, block[4] & 0xf, dirname);
if (depth < 0) {
mkdir(dirname, 0777);
chdir(dirname);
} else {
indent(depth);
printf("%s <dir>\n", dirname);
}
uint8_t entryLength = block[0x23];
uint8_t entriesPerBlock = block[0x24];
uint16_t fileCount = r16(block + 0x25);
uint8_t *entry = block + entryLength + 4;
uint8_t curEntry = 1;
uint16_t curFile = 0;
while (curFile < fileCount) {
if (entry[0] != 0) {
doEntry(entry, disk, disklen, depth < 0 ? -1 : depth + 1);
curFile++;
}
curEntry++;
entry += entryLength;
if (curEntry == entriesPerBlock) {
curEntry = 0;
block = disk + r16(block + 2) * 512;
entry = block + 4;
}
}
if (depth < 0) {
chdir("..");
}
}
static void printDateTime(uint16_t date, uint16_t time) {
printf("%02d-%02d-%02d %02d:%02d",
(date >> 9) & 0x7f, (date >> 5) & 0xf, date & 0x1f,
(time >> 8) & 0x1f, time & 0x3f);
}
static void doEntry(uint8_t *entry, uint8_t *disk, uint32_t disklen,
int depth) {
uint8_t filetype = entry[0x10];
uint16_t key = r16(entry + 0x11);
uint32_t eof = r24(entry + 0x15);
char filename[50];
readFilename(entry + 1, entry[0] & 0xf, filename);
switch (entry[0] & 0xf0) {
case 0x10: // seedling
case 0x20: // sapling
case 0x30: // tree
if (depth < 0) {
doFile(key, eof, filename, disk, disklen, entry[0] >> 4);
} else {
uint16_t createDate = r16(entry + 0x18);
uint16_t createTime = r16(entry + 0x1a);
uint16_t aux = r16(entry + 0x1f);
uint16_t modDate = r16(entry + 0x21);
uint16_t modTime = r16(entry + 0x23);
indent(depth);
printf("%s\n", filename);
indent(depth);
printf(" Size: %'d", eof);
printf(" Created: ");
printDateTime(createDate, createTime);
printf(" Modified: ");
printDateTime(modDate, modTime);
printf("\n");
indent(depth);
printf(" Type: $%02x Aux: $%04x ", filetype, aux);
uint32_t typeaux = filetype | (aux << 8);
for (int i = 0; i < numTypes; i++) {
if ((typeaux & fileTypes[i].mask) == fileTypes[i].id) {
printf("%s/%s\n", fileTypes[i].ext, fileTypes[i].desc);
break;
}
}
}
break;
case 0x50:
doGSOS(key, filename, filetype, disk, disklen, depth);
break;
case 0xd0:
doDirectory(key, disk, disklen, depth < 0 ? -1 : depth + 1);
break;
default:
fprintf(stderr, "Unknown file type: %x\n", entry[0] & 0xf0);
return;
}
}
static void dumpSeedling(uint8_t *block, uint32_t len, FILE *f) {
if (block == NULL) {
fseek(f, len, SEEK_CUR);
} else {
fwrite(block, len, 1, f);
}
}
static void dumpSapling(uint8_t *index, uint32_t len, FILE *f, uint8_t *disk,
uint32_t disklen) {
if (index == NULL) {
fseek(f, len, SEEK_CUR);
return;
}
while (len > 0) {
uint16_t blockid = index[0] | (index[256] << 8);
uint8_t *block = NULL;
if (blockid && (blockid + 1) * 512 <= disklen) {
block = disk + blockid * 512;
}
uint32_t blen = len > 512 ? 512 : len;
dumpSeedling(block, blen, f);
len -= blen;
index++;
}
}
static void dumpTree(uint8_t *index, uint32_t len, FILE *f, uint8_t *disk,
uint32_t disklen) {
if (index == NULL) {
fseek(f, len, SEEK_CUR);
return;
}
while (len > 0) {
uint16_t blockid = index[0] | (index[256] << 8);
uint8_t *block = NULL;
if (blockid && (blockid + 1) * 512 <= disklen) {
block = disk + blockid * 512;
}
uint32_t blen = len > 256 * 512 ? 256 * 512 : len;
dumpSapling(block, blen, f, disk, disklen);
len -= blen;
index++;
}
}
static void doGSOS(uint16_t key, char *name, uint8_t filetype, uint8_t *disk,
uint32_t disklen, int depth) {
uint8_t *block = disk + key * 512;
int type = *block;
uint16_t subkey = r16(block + 1);
uint32_t eof = r24(block + 5);
if (depth < 0) {
doFile(subkey, eof, name, disk, disklen, type);
}
char resname[50];
strncpy(resname, name, 50);
strncat(resname, ".res", 50);
block = disk + key * 512 + 0x100;
type = *block;
subkey = r16(block + 1);
uint32_t reof = r24(block + 5);
if (depth < 0) {
doFile(subkey, reof, resname, disk, disklen, type);
} else {
indent(depth);
printf("%s %d bytes resource: %d bytes\n", name, eof, reof);
}
}
static void doFile(uint16_t key, uint32_t len, char *name, uint8_t *disk,
uint32_t disklen, int type) {
uint8_t *block = disk + key * 512;
FILE *f = fopen(name, "wb");
if (!f) {
fprintf(stderr, "Failed to create '%s'\n", name);
return;
}
switch (type) {
case 1:
dumpSeedling(block, len, f);
break;
case 2:
dumpSapling(block, len, f, disk, disklen);
break;
case 3:
dumpTree(block, len, f, disk, disklen);
break;
}
fclose(f);
}

374
65816.h
View File

@ -1,374 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Defines the 65816 opcodes, their addressing modes, and sizes
*/
/**
Addressing modes:
imp implied
imm #$const (l)
immm #$const (l | h) if m16
immx #$const (l | h) if x16
imms #$const (l | h)
abs $addr (addrl | addrh)
abl $addr (addrl | addrh | bank)
abx $addr,x (addrl | addrh)
aby $addr,y (addrl | addrh)
ablx $addr,x (addrl | addrh | bank)
aix ($addr,x) (addrl | addrh)
zp $zp (offset)
zpx $zp,x (offset)
zpy $zp,y (offset)
zps $zp,s (offset)
ind ($addr) (addrl | addrh)
inz ($zp) (offset)
inl [$zp] (offset)
inx ($zp,x) (offset)
iny ($zp),y (offset)
inly [$zp],y (offset)
ins ($zp,s),y (offset)
rel $r (signed byte + pc)
rell $r (offsetl | offseth) + pc
bank $sb,$db (dstbnk | srcbnk)
db $op
dw $op
dd $op
*/
typedef enum {
IMP = 0,
IMM,
IMMM,
IMMX,
IMMS,
ABS,
ABL,
ABX,
ABY,
ABLX,
AIX,
ZP,
ZPX,
ZPY,
ZPS,
IND,
INZ,
INL,
INX,
INY,
INLY,
INS,
REL,
RELL,
BANK,
DB,
DW,
DD
} Address;
typedef enum {
BREAK = 0,
NORMAL = 1,
BRANCH = 2,
CALL = 3,
RETURN = 4,
JUMP = 5
} OpType;
typedef struct {
const char *inst;
Address address;
OpType type;
} Opcode;
static Opcode opcodes[] = {
{"brk", IMP, BREAK}, // 00
{"ora", INX, NORMAL}, // 01
{"cop", IMP, NORMAL}, // 02
{"ora", ZPS, NORMAL}, // 03
{"tsb", ZP, NORMAL}, // 04
{"ora", ZP, NORMAL}, // 05
{"asl", ZP, NORMAL}, // 06
{"ora", INL, NORMAL}, // 07
{"php", IMP, NORMAL}, // 08
{"ora", IMMM, NORMAL}, // 09
{"asl", IMP, NORMAL}, // 0a
{"phd", IMP, NORMAL}, // 0b
{"tsb", ABS, NORMAL}, // 0c
{"ora", ABS, NORMAL}, // 0d
{"asl", ABS, NORMAL}, // 0e
{"ora", ABL, NORMAL}, // 0f
{"bpl", REL, BRANCH}, // 10
{"ora", INY, NORMAL}, // 11
{"ora", INZ, NORMAL}, // 12
{"ora", INS, NORMAL}, // 13
{"trb", ZP, NORMAL}, // 14
{"ora", ZPX, NORMAL}, // 15
{"asl", ZPX, NORMAL}, // 16
{"ora", INLY, NORMAL}, // 17
{"clc", IMP, NORMAL}, // 18
{"ora", ABY, NORMAL}, // 19
{"inc", IMP, NORMAL}, // 1a
{"tcs", IMP, NORMAL}, // 1b
{"trb", ABS, NORMAL}, // 1c
{"ora", ABX, NORMAL}, // 1d
{"asl", ABX, NORMAL}, // 1e
{"ora", ABLX, NORMAL}, // 1f
{"jsr", ABS, CALL}, // 20
{"and", INX, NORMAL}, // 21
{"jsl", ABL, CALL}, // 22
{"and", ZPS, NORMAL}, // 23
{"bit", ZP, NORMAL}, // 24
{"and", ZP, NORMAL}, // 25
{"rol", ZP, NORMAL}, // 26
{"and", INL, NORMAL}, // 27
{"plp", IMP, NORMAL}, // 28
{"and", IMMM, NORMAL}, // 29
{"rol", IMP, NORMAL}, // 2a
{"pld", IMP, NORMAL}, // 2b
{"bit", ABS, NORMAL}, // 2c
{"and", ABS, NORMAL}, // 2d
{"rol", ABS, NORMAL}, // 2e
{"and", ABL, NORMAL}, // 2f
{"bmi", REL, BRANCH}, // 30
{"and", INY, NORMAL}, // 31
{"and", INZ, NORMAL}, // 32
{"and", INS, NORMAL}, // 33
{"bit", ZPX, NORMAL}, // 34
{"and", ZPX, NORMAL}, // 35
{"rol", ZPX, NORMAL}, // 36
{"and", INLY, NORMAL}, // 37
{"sec", IMP, NORMAL}, // 38
{"and", ABY, NORMAL}, // 39
{"dec", IMP, NORMAL}, // 3a
{"tsc", IMP, NORMAL}, // 3b
{"bit", ABX, NORMAL}, // 3c
{"and", ABX, NORMAL}, // 3d
{"rol", ABX, NORMAL}, // 3e
{"and", ABLX, NORMAL}, // 3f
{"rti", IMP, RETURN}, // 40
{"eor", INX, NORMAL}, // 41
{"db", DB, BREAK}, // 42
{"eor", ZPS, NORMAL}, // 43
{"mvp", BANK, NORMAL}, // 44
{"eor", ZP, NORMAL}, // 45
{"lsr", ZP, NORMAL}, // 46
{"eor", INL, NORMAL}, // 47
{"pha", IMP, NORMAL}, // 48
{"eor", IMMM, NORMAL}, // 49
{"lsr", IMP, NORMAL}, // 4a
{"phk", IMP, NORMAL}, // 4b
{"jmp", ABS, JUMP}, // 4c
{"eor", ABS, NORMAL}, // 4d
{"lsr", ABS, NORMAL}, // 4e
{"eor", ABL, NORMAL}, // 4f
{"bvc", REL, BRANCH}, // 50
{"eor", INY, NORMAL}, // 51
{"eor", INZ, NORMAL}, // 52
{"eor", INS, NORMAL}, // 53
{"mvn", BANK, NORMAL}, // 54
{"eor", ZPX, NORMAL}, // 55
{"lsr", ZPX, NORMAL}, // 56
{"eor", INLY, NORMAL}, // 57
{"cli", IMP, NORMAL}, // 58
{"eor", ABY, NORMAL}, // 59
{"phy", IMP, NORMAL}, // 5a
{"tcd", IMP, NORMAL}, // 5b
{"jmp", ABL, JUMP}, // 5c
{"eor", ABX, NORMAL}, // 5d
{"lsr", ABX, NORMAL}, // 5e
{"eor", ABLX, NORMAL}, // 5f
{"rts", IMP, RETURN}, // 60
{"adc", INX, NORMAL}, // 61
{"per", REL, NORMAL}, // 62
{"adc", ZPS, NORMAL}, // 63
{"stz", ZP, NORMAL}, // 64
{"adc", ZP, NORMAL}, // 65
{"ror", ZP, NORMAL}, // 66
{"adc", INL, NORMAL}, // 67
{"pla", IMP, NORMAL}, // 68
{"adc", IMMM, NORMAL}, // 69
{"ror", IMP, NORMAL}, // 6a
{"rtl", IMP, RETURN}, // 6b
{"jmp", IND, JUMP}, // 6c
{"adc", ABS, NORMAL}, // 6d
{"ror", ABS, NORMAL}, // 6e
{"adc", ABL, NORMAL}, // 6f
{"bvs", REL, BRANCH}, // 70
{"adc", INY, NORMAL}, // 71
{"adc", INZ, NORMAL}, // 72
{"adc", INS, NORMAL}, // 73
{"stz", ZPX, NORMAL}, // 74
{"adc", ZPX, NORMAL}, // 75
{"ror", ZPX, NORMAL}, // 76
{"adc", INLY, NORMAL}, // 77
{"sei", IMP, NORMAL}, // 78
{"adc", ABY, NORMAL}, // 79
{"ply", IMP, NORMAL}, // 7a
{"tdc", IMP, NORMAL}, // 7b
{"jmp", AIX, JUMP}, // 7c
{"adc", ABX, NORMAL}, // 7d
{"ror", ABX, NORMAL}, // 7e
{"adc", ABLX, NORMAL}, // 7f
{"bra", REL, JUMP}, // 80
{"sta", INX, NORMAL}, // 81
{"brl", RELL, JUMP}, // 82
{"sta", ZPS, NORMAL}, // 83
{"sty", ZP, NORMAL}, // 84
{"sta", ZP, NORMAL}, // 85
{"stx", ZP, NORMAL}, // 86
{"sta", INL, NORMAL}, // 87
{"dey", IMP, NORMAL}, // 88
{"bit", IMMM, NORMAL}, // 89
{"txa", IMP, NORMAL}, // 8a
{"phb", IMP, NORMAL}, // 8b
{"sty", ABS, NORMAL}, // 8c
{"sta", ABS, NORMAL}, // 8d
{"stx", ABS, NORMAL}, // 8e
{"sta", ABL, NORMAL}, // 8f
{"bcc", REL, BRANCH}, // 90
{"sta", INY, NORMAL}, // 91
{"sta", INZ, NORMAL}, // 92
{"sta", INS, NORMAL}, // 93
{"sty", ZPX, NORMAL}, // 94
{"sta", ZPX, NORMAL}, // 95
{"stx", ZPY, NORMAL}, // 96
{"sta", INLY, NORMAL}, // 97
{"tya", IMP, NORMAL}, // 98
{"sta", ABY, NORMAL}, // 99
{"txs", IMP, NORMAL}, // 9a
{"txy", IMP, NORMAL}, // 9b
{"stz", ABS, NORMAL}, // 9c
{"sta", ABX, NORMAL}, // 9d
{"stz", ABX, NORMAL}, // 9e
{"sta", ABLX, NORMAL}, // 9f
{"ldy", IMMX, NORMAL}, // a0
{"lda", INX, NORMAL}, // a1
{"ldx", IMMX, NORMAL}, // a2
{"lda", ZPS, NORMAL}, // a3
{"ldy", ZP, NORMAL}, // a4
{"lda", ZP, NORMAL}, // a5
{"ldx", ZP, NORMAL}, // a6
{"lda", INL, NORMAL}, // a7
{"tay", IMP, NORMAL}, // a8
{"lda", IMMM, NORMAL}, // a9
{"tax", IMP, NORMAL}, // aa
{"plb", IMP, NORMAL}, // ab
{"ldy", ABS, NORMAL}, // ac
{"lda", ABS, NORMAL}, // ad
{"ldx", ABS, NORMAL}, // ae
{"lda", ABL, NORMAL}, // af
{"bcs", REL, BRANCH}, // b0
{"lda", INY, NORMAL}, // b1
{"lda", INZ, NORMAL}, // b2
{"lda", INS, NORMAL}, // b3
{"ldy", ZPX, NORMAL}, // b4
{"lda", ZPX, NORMAL}, // b5
{"ldx", ZPY, NORMAL}, // b6
{"lda", INLY, NORMAL}, // b7
{"clv", IMP, NORMAL}, // b8
{"lda", ABY, NORMAL}, // b9
{"tsx", IMP, NORMAL}, // ba
{"tyx", IMP, NORMAL}, // bb
{"ldy", ABX, NORMAL}, // bc
{"lda", ABX, NORMAL}, // bd
{"ldx", ABY, NORMAL}, // be
{"lda", ABLX, NORMAL}, // bf
{"cpy", IMMX, NORMAL}, // c0
{"cmp", INX, NORMAL}, // c1
{"rep", IMM, NORMAL}, // c2
{"cmp", ZPS, NORMAL}, // c3
{"cpy", ZP, NORMAL}, // c4
{"cmp", ZP, NORMAL}, // c5
{"dec", ZP, NORMAL}, // c6
{"cmp", INL, NORMAL}, // c7
{"iny", IMP, NORMAL}, // c8
{"cmp", IMMM, NORMAL}, // c9
{"dex", IMP, NORMAL}, // ca
{"wai", IMP, NORMAL}, // cb
{"cpy", ABS, NORMAL}, // cc
{"cmp", ABS, NORMAL}, // cd
{"dec", ABS, NORMAL}, // ce
{"cmp", ABL, NORMAL}, // cf
{"bne", REL, BRANCH}, // d0
{"cmp", INY, NORMAL}, // d1
{"cmp", INZ, NORMAL}, // d2
{"cmp", INS, NORMAL}, // d3
{"pei", ZP, NORMAL}, // d4
{"cmp", ZPX, NORMAL}, // d5
{"dec", ZPX, NORMAL}, // d6
{"cmp", INLY, NORMAL}, // d7
{"cld", IMP, NORMAL}, // d8
{"cmp", ABY, NORMAL}, // d9
{"phx", IMP, NORMAL}, // da
{"stp", IMP, BREAK}, // db
{"jmp", IND, JUMP}, // dc
{"cmp", ABX, NORMAL}, // dd
{"dec", ABX, NORMAL}, // de
{"cmp", ABLX, NORMAL}, // df
{"cpx", IMMX, NORMAL}, // e0
{"sbc", INX, NORMAL}, // e1
{"sep", IMM, NORMAL}, // e2
{"sbc", ZPS, NORMAL}, // e3
{"cpx", ZP, NORMAL}, // e4
{"sbc", ZP, NORMAL}, // e5
{"inc", ZP, NORMAL}, // e6
{"sbc", INL, NORMAL}, // e7
{"inx", IMP, NORMAL}, // e8
{"sbc", IMMM, NORMAL}, // e9
{"nop", IMP, NORMAL}, // ea
{"xba", IMP, NORMAL}, // eb
{"cpx", ABS, NORMAL}, // ec
{"sbc", ABS, NORMAL}, // ed
{"inc", ABS, NORMAL}, // ee
{"sbc", ABL, NORMAL}, // ef
{"beq", REL, BRANCH}, // f0
{"sbc", INY, NORMAL}, // f1
{"sbc", INZ, NORMAL}, // f2
{"sbc", INS, NORMAL}, // f3
{"pea", IMMS, NORMAL}, // f4
{"sbc", ZPX, NORMAL}, // f5
{"inc", ZPX, NORMAL}, // f6
{"sbc", INLY, NORMAL}, // f7
{"sed", IMP, NORMAL}, // f8
{"sbc", ABY, NORMAL}, // f9
{"plx", IMP, NORMAL}, // fa
{"xce", IMP, NORMAL}, // fb
{"jsr", AIX, CALL}, // fc
{"sbc", ABX, NORMAL}, // fd
{"inc", ABX, NORMAL}, // fe
{"sbc", ABLX, NORMAL} // ff
};
static uint8_t addressSizes[] = {
1, // IMP
2, // IMM
3, // IMMM
3, // IMMX
3, // IMMS
3, // ABS
4, // ABL
3, // ABX
3, // ABY
4, // ABLX
3, // AIX
2, // ZP
2, // ZPX
2, // ZPY
2, // ZPS
3, // IND
2, // INZ
2, // INL
2, // INX
2, // INY
2, // INLY
2, // INS
2, // REL
3, // RELL
3, // BANK
1, // DB
2, // DW
4 // DD
};

View File

@ -3,11 +3,11 @@ CFLAGS=-Wall
all: 2mg regs
2mg: 2mg.o
$(CC) $(CFLAGS) -o $@ -largp $^
2mg: FORCE
$(MAKE) -C src ../2mg
regs: src/iigs.h
$(MAKE) -C src
regs: FORCE src/iigs.h
$(MAKE) -C src ../regs
src/iigs.h: iigs.dat
xxd -i $< $@
@ -18,10 +18,9 @@ iigs.dat: docmaker/docmaker iigs
docmaker/docmaker:
$(MAKE) -C docmaker
%.o: %.c
$(CC) -c $(CFLAGS) -o $@ $<
FORCE:
clean:
rm -f *.o 2mg regs
rm -f *.o
$(MAKE) -C docmaker clean
$(MAKE) -C src clean

246
README.md
View File

@ -1,214 +1,214 @@
# What is this?
This is a set of command-line tools designed specifically to reverse engineer Apple IIgs software. It is comprised of 3 separate tools; `2mg`, `omf`, and `regs`.
This is a set of command-line tools designed specifically to reverse engineer Apple IIgs software. It is comprised of 2 separate tools; `2mg` and `regs`. The first is a simple tool to extract disk images, the second is a more complicated tool that disassembles executables and allows you to query API information.
# 2mg
`2mg` extracts .2mg and .po prodos disk images. When you give it a disk image filename, it will create a folder with the name of the disk, and extract all the files into that folder with the proper hierarchy.
## 2mg
You can also use it to list the contents of the disk image with the `-l` or `--list` command line argument. Listing out the files will also give you the metadata associated with each file, such as creation date and file type.
`2mg` extracts .2mg and .po prodos disk images. You can also just list the contents of the disk image with the `-l` or `--list` command line argument. Otherwise, it will create a folder with the name of the disk and extract all the files into that folder.
# regs
Listing out the files will also give you the metadata associated with each
file. In particular, it will tell you the type and auxiliary type for
the files.
`regs` is a combination of a couple of disassembly tools. It can disassemble raw binary files, it can disassemble OMF files (like sys16 or tool files), and it can be used to inspect the Apple IIgs API.
Since it is a tracing disassembler, it will start disassembly at a given entrypoint and follow all possible paths. Everything not disassembled will be assumed to be data and shown as a hex dump.
## omf
The first time `regs` is used to disassemble a specific file, it will create a `.regs` file with auto-detected information.
`omf` is a rather complicated tool which is designed to extract relocatable segments from OMF files. Apple IIgs executables (.s16 files) and system tools (ex. SYSTEM/TOOLS/TOOL025) are in OMF format.
## regs commandline arguments
You first run this tool and pass it an OMF file and it will generate a .map file. This map file is a simple text file that you may edit. Each line is in the format:
`regs` has a few flags that you can use to customize its disassembly. If you are disassembling an OMF file, none of these flags do anything; the OMF file overrides this information.
`segment:memory location`
These flags are really only useful for the initial disassembly. Once a `.regs` file is created, you should use that to customize all future disassembly of that particular file.
The `segment`is the segment number from the OMF file, and `memory location` is where in memory to relocate that segment.
For regular binary executables, you can specify where in memory they should be loaded with the `-o` or `--org` flag. The passed address can be in decimal, or in hexadecimal if preceded with `$` or `0x`. Be sure to wrap the argument in single-quotes if you use `$`, otherwise your shell will interpret it as a variable. This address will also be used as the entrypoint of the executable.
`omf` does its best to automatically pack all the relocatable segments into the smallest memory possible, starting at `$2/0000`. You can change the starting memory address with the `-o` or `--org` argument. If you wish to manually specify where each segment should go in memory, feel free to edit the .map file however you wish.
You can force the disassembler to start in emulation mode with the `-e` flag. By default, the disassembler starts in native mode. You can force the disassembler to use an 8-bit accumulator with '-m' and 8-bit indices with '-x'. However, emulation mode assumes both of those flags already, just like on the actual hardware.
The next step is to run `omf` again, this time specifying the map file with `-m` or `--map`. This will apply the .map to the OMF and output each segment along with a corresponding .map file. `omf` will throw an error if the segments cannot be mapped as the .map file dictates (for example, you modified the map file so that the segments accidentally overlap).
You'll notice there is also a `-l` flag, this is used for API queries, as described below.
`omf` will modify each segment, applying the proper relocations before outputting the segment. If you wish to change where a segment is in memory, you should modify the original .map file and re-run `omf`. The resulting output files will be hardcoded for those specific memory locations.
## .regs files
The segment outputs will be named `segX` and `segX.map`, where `X` is the segment number, in hex. If you wish to change the prefix from `seg` use the `-p` or `--prefix` argument.
As stated earlier, the first time you execute the disassembler, it creates a `.regs` file for that given executable. The format of this file is fairly starightforward and lets you customize disassembly further.
At this point, you can use the resulting segment files and map files as input to the `regs` tool.
Each line in this file starts with an address. This address is of the format `$bank/offset` where both the dollar sign `$` and bank divider `/` are optional.
If the address is followed by an exclamation point `!`, it means this address is used as the point in memory the executable is loaded at. (OMF files will ignore this). If more than one address is followed by an exclamation point, only the first address encountered will be used.
If the address is followed by a colon `:`, it means this address is considered an entrypoint. Disassembly will start at this address. There can be as many entrypoints as you wish. In fact, as you disassemble a file, you may notice that disassembly is halted by indirect jumps. You can add entrypoints to the `.regs` file to continue disassembly at the destination of those jumps.
## regs
After the colon, you can optionally specify "e" "m", "x" or any combination of those characters to force the disassembler state when disassembly starts at that entry point.
`regs`is my 65816 tracing disassembler. It can be used in conjunction with the .map files created by `omf` or on its own. Since it is a tracing disassembler, it will start disassembly at a given location, and keep disassembling, following all possible paths. Everything not disassembled will be assumed to be data and shown in a hex view.
Finally, if the address is followed by a angle-bracket `<`, then this address has a symbolic name. The symbolic name should be terminated with a closing angle-bracket `>`. If an address has a symbolic name, this name will be used in place of the address whenever it appears in disassembly.
#### regs by itself
You can call `regs` and simply pass it a binary file and it will attempt to disassemble it. You can specify where in memory it should load the file before disassembly with the `-o` or `--org` argument. You can also control whether or not the disassembler is in emulation mode or 16-bit mode with various command-line arguments. Emulation mode is also useful for disassembling 8-bit Apple II software. Use `-e` to start in emulation mode (default native mode), `-m` to start with an 8-bit accumulator (default 16-bit), and `-x` to start with 8-bit index registers (default 16-bit).
#### regs with .map files
`regs` with .map files is where the disassembler really shines. You can call `regs` and pass it a .map file generated by `omf` and have real control over the disassembly. The .map file is designed to be edited by hand. The format is as follows:
An address can be followed by any and all of the preceding markers. For example, say you have an executable that is loaded at `$300`, which is also the entry point, and you want to start in emulation mode, and give the entrypoint a meaningful name. This is a very common scenario and would result in a `.regs` file with a single line:
```
gMAP "seg1"
sORG $30000
$30000:
$30053:mx
$31066:e
$35440:d <myTable>
$300!:e<start>
```
The first line specifies the segment file that this map applies to. The filename in the quotes should be relative to the current directory.
Notice I left out the bank divider since it wasn't needed (the bank would naturally be 0), and also the 'm' and 'x' flags are always on in emulation mode, so I didn't need to include them.
The next line specifies where the segment belongs in memory. **Do not edit this** if the segment was created by `omf`, since it has also been hardcoded in the binary.
The `.regs` file is actually rewritten every time you run the disassembler, so the format will be standardized and addresses will be sorted automatically for you.
The next lines are a list of entry points to begin disassembly at. If, when analyzing the disassembly you find a switch case encoded as an indirect jump, you can take that list of jumps and add them to the map file and re-run `regs` to disassemble the previously un-disassembled data. As you work through a disassembly, you may end up with a map file with hundreds of entry points, that's normal.
## Workflow
The flags after the colon are optional, and specify whether emulation mode should be enabled, or 16-bit or 8-bit accumulator and index registers should be used. It defaults to native mode with 16-bit registers.
Since it may not be obvious if you're used to other disassemblers, the workflow for this disassembler is of constant iteration. You run the disassembler on an executable, then tweak the `.regs` file to add entrypoints as necessary, and add symbols to memory addresses as you identify their purpose. Then re-run the disassembler each time. The end result is a disassembly that gets cleaner and clearer and easier to follow.
After the flags, you may optionally give the address a symbol name. Whenever
this memory location is referenced in the code, the symbol name will appear as a
comment.
## Disassembly notation
You can also use bank-separators if you wish. `$3/1066` is the same as `$31066`.
There are few things to note about the disassembly style of `regs`. The first is that since we traced the code flow, branch destinations are preceded by a line that shows up to 7 source addresses and whether they are above or below the current line. This will help you figure out code flow.
The `d` flag is unique in that it identifies the address as a data location and
not an entry point. This is used to give variables symbol names.
Next is that tool calls like `NewHandle` are shown as instructions. This helps dramatically clean up the disassembly. Instead of showing the code that loads X with the tool number and then jumping to the tool dispatch address, we replace it all with just the name of the tool called. This is controlled by the fingerprints in the iigs folder, described below.
Finally, and most unusual, I include "B:" and "D:" flags when the addressing uses DBR and Direct modes. This is because the IIgs can change the direct page and DBR register and thus those addresses cannot be depended to be accurate.
If an address starts with "B:", then the current value of the DBR register should be added to the address. If an address starts with "D:", then the current value of the Direct register should be added to the address.
## Pascal folder
Since they might be misleading, I also do not do symbol swapping on those addresses. Instead any matching symbols are included as a comment on the same line. That way if the IIgs is in a standard setup, then you know what those addresses represent, and if not, you can ignore the comment.
You'll notice a pascal folder in this repository. These are the original GS/OS pascal header files. This is to make it easier for you to look up the arguments and structures of various tool calls you'll come across when disassembling.
# Docmaker, the API, and the iigs folder
You'll notice a iigs folder that contains a bunch of text files that contain structures and function definitions related to the Apple IIgs API. These files are parsed and compiled into the `regs` program for two purposes.
One, the functions have fingerprints attached to them that the disassembler can use to identify when the program is calling those functions and replace the system calls with actual function names.
Two, you can query `regs` for any given structure or function and even have it calculate field offsets for you. This will help with disassembly.
Docmaker is the program that will read in the entire directory and generate a data file, which will get included when you compile `regs`.
## Querying the API
Querying the API is fairly simple, call `regs` with the `-l` flag, followed by a keyword. No need to provide a file to disassemble when the `-l` flag is included. Regs will then search the API for a data type or function that contains that keyword and output information about it.
If you're querying a structure, you can also use the `-o` or `--org` flag to specify where in ram that structure should start, and it will include the calculated offsets for each field.
For example, say the program you're disassembling calls `FrameRgn` and passes the value `$2/43a9` to the function. You can do the following:
```
$ regs -l framergn
FrameRgn: (
aRgnHandle: RgnHandle,
)
```
This lets you know that it takes a single argument which is of type `RgnHandle`. You can query on `RgnHandle` and find out it's a double pointer to `Region`. Next you can do:
```
$ regs -l region -o '$2/43a9'
Region: struct { // $a bytes
rgnSize: int16 // $02/43a9
rgnBBox: Rect // $02/43ab
data: int16[] // $02/43b3
}
```
Now you know exactly what the variables at those memory addresses are and that might help with disassembly further down the line. You might even want to add the fields back as symbols in the `.regs` file for future disassembly.
# Examples
The flexibility of these tools makes their use a little complicated. So here are some examples of how to go about disassembling various things.
### Disassembling an S16
## Disassembling an S16
I'll be using the S16 from Dream Zone as an example.
I'll use the S16 from Dream Zone as an example. Generate an initial map and first disassembly:
Generate a basic map of your S16:
`$ regs dream.sys16`
`$ omf dream.s16`
This will create a dream.sys16.regs file with an (unused) org address of `$00/0000`, and an entry point of `$01/0000`. It will also create 5 files from `seg1` to `seg5`, this is the disassembly for each segment in the S16.
This will create a file called `dream.s16.map`, which we could edit if we choose. We'll leave it as it is. Extract the segments of the OMF with:
Looking at the seg1 disassembly, we notice that after calling a tool like `MMStartUp` or `NewHandle` the accumulator is stored at `$01/e4d0`. I know from experience that this is the toolErr variable. So let's add a symbol for it so we know what's happening if the code inspects it later. Edit the dream.sys16.regs and add a line:
`$ omf --map=dream.s16.map dream.s16 --prefix=dream`
```
$01/e4d0<toolErr>
```
This will create files `dream1`to `dream5` as well as `dream1.map` to `dream5.map`.
The program's entry point is always the beginning of the first segment, so we'll start there.
`$ regs dream1.map > dream1.s`
This will disassemble the entry point. We can then modify the map to further refine the disassembly if we wish.
Rerun `regs` and now the disassembly properly references toolErr!
In a real project, you'll be adding hundreds or thousands of symbols. The tools are designed for that.
### Disassembling a Tool
This works the same as disassembling an S16, but with an important difference.
We'll start the same, generating a map and extracting it.
We want to start without any entry points. You can accomplish this by creating
an empty `.regs` file. Then run regs on the tool file.
```
$ omf TOOL025
$ omf --map=TOOL025.map TOOL025
$ touch TOOL025.regs
$ regs TOOL025
```
Now, we'll remove all disassembly instructions from the map. You'll see why in a second. So we edit the map file to look like the following:
Without any entrypoints, the entire file is just a hex dump. However, it's a hex dump of the segment content, so it is missing all of the overhead and format information found in an OMF. Thats why you can't just hex dump the original tool file for this.
All tools start with a tool table. The first dword specifies the number of tools in this file. The next dwords contain return addresses of the various tool entry points.
Let's say I want to disassemble `NoteOn`. Running `regs -l noteon` shows me that it's tool $0b inside the $19 toolset. Convert that to decimal to discover that's inside the TOOL025 file (the same file we already started using, how convenient). We can calculate the offset to tool $0b.
`$0b * 4 + $1/0000 = $1/002c`
We added `$1/0000` since that's where the toolset is disassembled, which we know from inspecting `seg1`. Back to `seg1` at that offset, we see "99 02 01 00" which is little endian for the address `$1/299`. Since these are return addresses, we need to increment that to get the actual entry point of `NoteOn`.
Add that entrypoint to the `.regs` file.
```
gMAP "seg1"
sORG $20000
$00/0000!
$01/029a:
```
That's it.. no disassembly instructions. Now we run the disassembler:
`$ regs seg1.map > seg1.s`
This will just give us a hex dump of the segment. That's actually what we want. All tools start with a tool table. The first dword specifies the number of tools in this toolset. The next dwords all contain addresses (minus 1) of the various tool entry points.
Let's say I want to disassemble NoteOn. We check the pascal folder and discover that it's tool $0b inside the $19 toolset. Which is the TOOL025 file we're working on (the tool numbers in the filenames are in decimal). So we calculate the offset to that entry point.
`$0b * 4 + $20000 = $2:002c`
If we look at the hex dump at that location we'll discover the entry point of NoteOn: `$2:02dd`. Well that's minus one, so we add the real entry point to the .map file:
```
gMAP "seg1"
sORG $20000
$2/02de:
```
And rerun the disassembler.
`$ regs seg1.map > seg1.s`
We have just disassembled the NoteOn function.
and rerun regs. Re-check seg1, and down at `$01/29a` we have the disassembly for the `NoteOn` function, awesome!
### Disassembling a Specific Tool Call in ROM
Let's say I want to disassemble WriteRamBlock. We discover it's in the sound toolset $08. If you search, you'll discover that there isn't a TOOL008 anywhere, so we'll have to pull it from ROM. I'll be using an older 128k ROM just because it's convenient.
Let's say I want to disassemble WriteRamBlock. `regs -l writeramblock` shows us that it's tool 9 in toolset 8. If you search, you'll discover that there isn't a TOOL008 anywhere because it's a toolset that's never been patched. Instead we'll need to disassemble it directly from ROM. I'll be using the 128k ROM01 just because I'm more familiar with it. You can use ROM00 or ROM03 instead, it's just the locations of things will be different.
First thing I do, is actually hand make a `rom.map` file for the ROM.
First thing I do, is actually handmake a `.regs` file for the ROM.
```
gMAP "APPLE2GS.ROM"
sORG $fe0000
$fe/0000:
$fe/0000!:
```
and disassemble it.
Because that's where a 128k ROM should be loaded into memory, and I happen to know that the tool bootstrap is also located at that address. For ROM03, you'll want to load the ROM into `$fc/0000`. The tool bootstrap initializes the dispatches in bank `$e1`. We see that code copies 16 bytes from `$fe/0051` to `$e1/0000`, which is the main tool dispatch. Let's add that entrypoint and disassemble again:
`$ regs rom.map > rom.s`
```
$fe/0051:
```
This is actually the bootstrap that initializes the `$e1/0000` tool call entrypoint. I notice it copies over a block of memory from `$fe/0051` into `$e1/0000`. So we add `$fe/0051` to the disassembly list of the map file, and disassemble it again.
Stepping through this dispatch code, we see it first looks up your toolset from a table at `$fe/012f`. Since we're after toolset $08, we calculate the offset: `$08 * 4 + $fe/012f = $fe/014f`. We see at that location "00 3e ff 00" which is the little-endian representation of the address `$ff/3e00`.
Following along with the disassembly, we discover that there's a toolset list starting at `$fe/012f`. It starts with a dword with the number of toolsets in the ROM, followed by a list of offsets to the various toolsets. We want toolset 8 for the sound toolset.
The dispatch code then uses another table at that address to determine the return address of the entry point of the tool you want. Since WriteRamBlock is tool 9, we calculate the offset into the new table: `$08 * 4 + $ff/3e00 = $ff/3e24`.
`$8 * 4 + $fe012f = $fe014f`
At that location is the value "a4 41 ff 00" which becomes `$ff/41a4` but since it's a return address, we should increment it before disassembling.
Look up the dword in that location and I find that the toolset is located at `$ff/3e00`. If you then jump to that location, you'll find this is in the exact same format as a tool on disk. It starts with a tool table. WriteRamBlock is tool 9.
```
$ff/41a5:
```
`$9 * 4 + $ff3e00 = $ff3e24`
At that location, we discover the offset to the tool entry point is `$ff/41a4` so we'll add `$ff/41a5`to the map file and rerun the disassembly.
Boom, we have just disassembled a specific tool call from ram.
Boom, we have just disassembled a specific tool call found in rom.
### Disassembling a simple ProDOS executable
ProDOS binaries aren't relocatable and don't have anything inside them that
specifies where in RAM they should be loaded. However, the filesystem
itself does have that information.
ProDOS binaries aren't relocatable and don't have anything inside them that specifies where in RAM they should be loaded. However, the filesystem itself does have that information.
Using `2mg` with the `-l` or `--list` argument will give a list of the
files along with metadata associated with the files. Let's use `BASIC.SYSTEM`
as an example.
Using `2mg` with the `-l` or `--list` argument will give a list of the files along with metadata associated with the files. Let's use `BASIC.SYSTEM` as an example.
You'll see that `BASIC.SYSTEM` has a type of `$ff` and auxtype of
`$2000`, and `2mg` identifies it as a "sys/ProDOS System File". This is
indeed a simple executable.
You'll see that `BASIC.SYSTEM` has a type of `$ff` and auxtype of `$2000`, and `2mg` identifies it as a "sys/ProDOS System File". This is indeed a simple executable.
The aux type specifies where in RAM to load this executable, in this
case, it's `$2000`.
The aux type specifies where in RAM to load this executable. In this case, it's `$2000`.
It is also important to note that these executables should start with 8-bit
registers.
It is also important to note that these executables should start disassembly in emulation mode, since they're actually 8-bit executables.
So we can use all of that information to disassemble this file.
We can use all of that information to disassemble this file.
`$ regs --org=0x2000 -m -x BASIC.SYSTEM > basic.s`
`$ regs --org=0x2000 -e BASIC.SYSTEM`
This tells regs to start with 8-bit accumulator and indices, and load the
file starting at `$2000` before disassembling it.
This tells regs to start in emulation mode with 8-bit accumulator and indices, and load the file starting at `$2000` before disassembling it.
Since it's not an OMF file, there will be only 1 output file, `seg1`. Note, however, that we still have a `BASIC.SYSTEM.regs` file so we can add symbols and alternative entrypoints if we wish (remember to include the 'e' flag on any alternative entrypoints, since regs defaults to native mode unless told otherwise).
The next time we want to disassemble this file, we don't have to pass any arguments since the flags and org are set properly in the `.regs` file.

View File

@ -1,418 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Defines important IIgs memory addresses.
*/
typedef struct {
uint32_t address;
const char *comment;
} MemAddress;
static MemAddress addresses[] = {
{0x03d0, "Enter BASIC"},
{0x03d2, "Reconnect DOS"},
{0x03d9, "Cow Sound"},
{0x03ea, "Reconnect IO"},
{0x03f2, "Control-Reset Vector"},
{0x03f5, "Ampersand Vector"},
{0x03f8, "Control-Y Vector"},
{0x0400, "Text Screen"},
{0x0800, "Text Screen 2"},
{0x0803, "Enter assembler"},
{0x2000, "Hires screen"},
{0x4000, "Hires screen 2"},
{0x9dbf, "Reconnect DOS 3.3"},
{0xa56e, "CATALOG"},
{0xc000, "KBD / 80STOREOFF"},
{0xc001, "80STOREON"},
{0xc002, "RDMAINRAM"},
{0xc003, "RDCARDRAM"},
{0xc004, "WRMAINRAM"},
{0xc005, "WRCARDRAM"},
{0xc006, "SETSLOTCXROM"},
{0xc007, "SETINTCXROM"},
{0xc008, "SETSTDZP"},
{0xc009, "SETALTZP"},
{0xc00a, "SETINTC3ROM"},
{0xc00b, "SETSLOTC3ROM"},
{0xc00c, "CLR80VID"},
{0xc00d, "SET80VID"},
{0xc00e, "CLRALTCHAR"},
{0xc00f, "SETALTCHAR"},
{0xc010, "KBDSTRB"},
{0xc011, "RDLCBNK2"},
{0xc012, "RDLCRAM"},
{0xc013, "RDRAMRD"},
{0xc014, "RDRAMWRT"},
{0xc015, "RDCXROM"},
{0xc016, "RDALTZP"},
{0xc017, "RDC3ROM"},
{0xc018, "RD80STORE"},
{0xc019, "RDVBL"},
{0xc01a, "RDTEXT"},
{0xc01b, "RDMIXED"},
{0xc01c, "RDPAGE2"},
{0xc01d, "RDHIRES"},
{0xc01e, "RDALTCHAR"},
{0xc01f, "RD80VID"},
{0xc020, "TAPEOUT"},
{0xc021, "MONOCOLOR"},
{0xc022, "TBCOLOR"},
{0xc023, "VGCINT"},
{0xc024, "MOUSEDATA"},
{0xc025, "KEYMODREG"},
{0xc026, "DATAREG"},
{0xc027, "KMSTATUS"},
{0xc028, "ROMBANK"},
{0xc029, "NEWVIDEO"},
{0xc02b, "LANGSEL"},
{0xc02c, "CHARROM"},
{0xc02d, "SLTROMSEL"},
{0xc02e, "VERTCNT"},
{0xc02f, "HORIZCNT"},
{0xc030, "SPKR"},
{0xc031, "DISKREG"},
{0xc032, "SCANINT"},
{0xc033, "CLOCKDATA"},
{0xc034, "CLOCKCTL"},
{0xc035, "SHADOW"},
{0xc036, "CYAREG"},
{0xc037, "DMAREG"},
{0xc038, "SCCBREG"},
{0xc039, "SCCAREG"},
{0xc03a, "SCCBDATA"},
{0xc03b, "SCCADATA"},
{0xc03c, "SOUNDCTL"},
{0xc03d, "SOUNDDATA"},
{0xc03e, "SOUNDADRL"},
{0xc03f, "SOUNDADRH"},
{0xc040, "STROBE"},
{0xc041, "INTEN"},
{0xc044, "MMDELTAX"},
{0xc045, "MMDELTAY"},
{0xc046, "DIAGTYPE"},
{0xc047, "CLRVBLINT"},
{0xc048, "CLRXYINT"},
{0xc050, "TXTCLR"},
{0xc051, "TXTSET"},
{0xc052, "MIXCLR"},
{0xc053, "MIXSET"},
{0xc054, "TXTPAGE1"},
{0xc055, "TXTPAGE2"},
{0xc056, "LORES"},
{0xc057, "HIRES"},
{0xc058, "CLRAN0"},
{0xc059, "SETAN0"},
{0xc05a, "CLRAN1"},
{0xc05b, "SETAN1"},
{0xc05c, "CLRAN2"},
{0xc05d, "SETAN2"},
{0xc05e, "DHIRESON"},
{0xc05f, "DHIRESOFF"},
{0xc060, "TAPEIN"},
{0xc061, "RDBTN0"},
{0xc062, "RDBTN1"},
{0xc063, "RDBTN2"},
{0xc064, "PADDL0"},
{0xc065, "PADDL1"},
{0xc066, "PADDL2"},
{0xc067, "PADDL3"},
{0xc068, "STATEREG"},
{0xc06d, "TESTREG"},
{0xc06e, "CLTRM"},
{0xc06f, "ENTM"},
{0xc070, "PTRIG"},
{0xc073, "BANKSEL"},
{0xc07e, "IOUDISON"},
{0xc07f, "IOUDISOFF"},
{0xc081, "ROMIN"},
{0xc083, "LCBANK2"},
{0xc08b, "LCBANK1"},
{0xc0e0, "PH0 off"},
{0xc0e1, "PH0 on"},
{0xc0e2, "PH1 off"},
{0xc0e3, "PH1 on"},
{0xc0e4, "PH2 off"},
{0xc0e5, "PH2 on"},
{0xc0e6, "PH3 off"},
{0xc0e7, "PH3 on"},
{0xc0e8, "motor off"},
{0xc0e9, "motor on"},
{0xc0ea, "drive 1"},
{0xc0eb, "drive 2"},
{0xc0ec, "q6 off"},
{0xc0ed, "q6 on"},
{0xc0ee, "q7 off"},
{0xc0ef, "q7 on"},
{0xc311, "AUXMOVE"},
{0xc314, "XFER"},
{0xc50d, "Smartport"},
{0xc70d, "Smartport"},
{0xcfff," CLRROM"},
{0xd1fc, "Hires Find"},
{0xd2c9, "Hires bg"},
{0xd331, "Hires graphics bg"},
{0xd33a, "Hires DRAW1"},
{0xd3b9, "Hires SHLOAD"},
{0xd683, "Clear FOR"},
{0xdafb, "Carriage Return"},
{0xe000, "Reset Int Basic"},
{0xe04b, "IntBASIC LIST"},
{0xe5ad, "NEW"},
{0xe5b7, "PLOT"},
{0xe836, "IntBASIC CHAIN"},
{0xefec, "IntBASIC RUN"},
{0xf07c, "IntBASIC LOAD"},
{0xf0e0, "Leave monitor"},
{0xf123, "DRAW shape"},
{0xf14f, "Plot point"},
{0xf171, "IntBASIC TRACE ON"},
{0xf176, "IntBASIC TRACE OFF"},
{0xf30a, "IntBASIC CON"},
{0xf317, "RESUME"},
{0xf328, "Clear error"},
{0xf3de, "HGR"},
{0xf3e4, "Show hires"},
{0xf3f2, "Clear hires"},
{0xf3f6, "Clear hires color"},
{0xf666, "Enter assembler"},
{0xf800, "PLOT"},
{0xf80e, "PLOT1"},
{0xf819, "HLINE"},
{0xf828, "VLINE"},
{0xf832, "CLRSCR"},
{0xf836, "CLRTOP"},
{0xf838, "Clear lores y"},
{0xf83c, "Clear rect"},
{0xf847, "GBASCALC"},
{0xf85e, "Add 3 COLOR"},
{0xf85f, "NXTCOL"},
{0xf864, "SETCOL"},
{0xf871, "SCRN"},
{0xf88c, "INSDS1.2"},
{0xf88e, "INSDS2"},
{0xf890, "GET816LEN"},
{0xf8d0, "INSTDSP"},
{0xf940, "PRNTYX"},
{0xf941, "PRNTAX"},
{0xf944, "PRNTX"},
{0xf948, "PRBLNK"},
{0xf94a, "PRBL2"},
{0xf94c, "Print X blank"},
{0xf953, "PCADJ"},
{0xf962, "TEXT2COPY"},
{0xfa40, "OLDIRQ"},
{0xfa4c, "BREAK"},
{0xfa59, "OLDBRK"},
{0xfa62, "RESET"},
{0xfaa6, "PWRUP"},
{0xfaba, "SLOOP"},
{0xfad7, "REGDSP"},
{0xfb19, "RTBL"},
{0xfb1e, "PREAD"},
{0xfb21, "PREAD4"},
{0xfb2f, "INIT"},
{0xfb39, "SETTXT"},
{0xfb40, "SETGR"},
{0xfb4b, "SETWND"},
{0xfb51, "SETWND2"},
{0xfb5b, "TABV"},
{0xfb60, "APPLEII"},
{0xfb6f, "SETPWRC"},
{0xfb78, "VIDWAIT"},
{0xfb88, "KBDWAIT"},
{0xfbb3, "VERSION"},
{0xfbbf, "ZIDBYTE2"},
{0xfbc0, "ZIDBYTE"},
{0xfbc1, "BASCALC"},
{0xfbdd, "BELL1"},
{0xfbe2, "BELL1.2"},
{0xfbe4, "BELL2"},
{0xfbf0, "STORADV"},
{0xfbf4, "ADVANCE"},
{0xfbfd, "VIDOUT"},
{0xfc10, "BS"},
{0xfc1a, "UP"},
{0xfc22, "VTAB"},
{0xfc24, "VTABZ"},
{0xfc2c, "ESC"},
{0xfc42, "CLREOP"},
{0xfc58, "HOME"},
{0xfc62, "CR"},
{0xfc66, "LF"},
{0xfc70, "SCROLL"},
{0xfc9c, "CLREOL"},
{0xfc9e, "CLREOLZ"},
{0xfca8, "WAIT"},
{0xfcb4, "NXTA4"},
{0xfcba, "NXTA1"},
{0xfcc9, "HEADR"},
{0xfd0c, "RDKEY"},
{0xfd10, "FD10"},
{0xfd18, "RDKEY1"},
{0xfd1b, "KEYIN"},
{0xfd35, "RDCHAR"},
{0xfd5a, "Wait return"},
{0xfd5c, "Ring bell wait"},
{0xfd67, "GETLNZ"},
{0xfd6a, "GETLN"},
{0xfd6c, "GETLN0"},
{0xfd6f, "GETLN1"},
{0xfd75, "Wait line"},
{0xfd8b, "CROUT1"},
{0xfd8e, "CROUT"},
{0xfd92, "PRA1"},
{0xfda3, "Print memory"},
{0xfdda, "PRBYTE"},
{0xfde3, "PRHEX"},
{0xfded, "COUT"},
{0xfdf0, "COUT1"},
{0xfdf6, "COUTZ"},
{0xfe1f, "IDROUTINE"},
{0xfe2c, "MOVE"},
{0xfe5e, "LIST"},
{0xfe61, "Disassembler"},
{0xfe80, "INVERSE"},
{0xfe84, "NORMAL"},
{0xfe86, "Set I"},
{0xfe89, "SETKBD"},
{0xfe8b, "INPORT"},
{0xfe93, "SETVID"},
{0xfe95, "OUTPORT"},
{0xfeb0, "Jump BASIC"},
{0xfeb6, "GO"},
{0xfebf, "Display regs"},
{0xfec2, "Perform trace"},
{0xfecd, "WRITE"},
{0xfefd, "READ"},
{0xff2d, "PRERR"},
{0xff3a, "BELL"},
{0xff3f, "RESTORE"},
{0xff44, "RSTR1"},
{0xff4a, "SAVE"},
{0xff4c, "SAV1"},
{0xff58, "IORTS"},
{0xff59, "OLDRST"},
{0xff65, "MON"},
{0xff69, "MONZ"},
{0xff6c, "MONZ2"},
{0xff70, "MONZ4"},
{0xff8a, "DIG"},
{0xffa7, "GETNUM"},
{0xffad, "NXTCHR"},
{0xffbe, "TOSUB"},
{0xffc7, "ZMODE"},
{0xe01e04, "StdText"},
{0xe01e08, "StdLine"},
{0xe01e0c, "StdRect"},
{0xe01e10, "StdRRect"},
{0xe01e14, "StdOval"},
{0xe01e18, "StdArc"},
{0xe01e1c, "StdPoly"},
{0xe01e20, "StdRgn"},
{0xe01e24, "StdPixels"},
{0xe01e28, "StdComment"},
{0xe01e2c, "StdTxMeas"},
{0xe01e30, "StdTxBnds"},
{0xe01e34, "StdGetPic"},
{0xe01e38, "StdPutPic"},
{0xe01e98, "ShieldCursor"},
{0xe01e9c, "UnshieldCursor"},
{0xe10000, "System Tool dispatch"},
{0xe10004, "System Tool dispatch"},
{0xe10008, "User Tool dispatch"},
{0xe1000c, "User Tool dispatch"},
{0xe10010, "Interrupt manager"},
{0xe10014, "COP manager"},
{0xe10018, "Abort manager"},
{0xe1001c, "System death manager"},
{0xe10020, "AppleTalk interrupt"},
{0xe10024, "Serial interrupt"},
{0xe10028, "Scanline interrupt"},
{0xe1002c, "Sound interrupt"},
{0xe10030, "VBlank interrupt"},
{0xe10034, "Mouse interrupt"},
{0xe10038, "250ms interrupt"},
{0xe1003c, "Keyboard interrupt"},
{0xe10040, "ADB Response"},
{0xe10044, "ADB SRQ"},
{0xe10048, "DA manager"},
{0xe1004c, "Flush Buffer"},
{0xe10050, "KbdMicro interrupt"},
{0xe10054, "1s interrupt"},
{0xe10058, "External VGC interrupt"},
{0xe1005c, "Ohter interrupt"},
{0xe10060, "Cursor update"},
{0xe10064, "IncBusy"},
{0xe10068, "DecBusy"},
{0xe1006c, "Bell vector"},
{0xe10070, "Break vector"},
{0xe10074, "Trace vector"},
{0xe10078, "Step vector"},
{0xe1007c, "ROM disk"},
{0xe10080, "ToWriteBram"},
{0xe10084, "ToReadBram"},
{0xe10088, "ToWriteTime"},
{0xe1008c, "ToReadTime"},
{0xe10090, "ToCtrlPanel"},
{0xe10094, "ToBramSetup"},
{0xe10098, "ToPrintMsg8"},
{0xe1009c, "ToPrintMsg16"},
{0xe100a0, "Native Ctl-Y"},
{0xe100a4, "ToAltDispCDA"},
{0xe100a8, "Prodos 16"},
{0xe100ac, "OS vector"},
{0xe100b0, "GS/OS"},
{0xe100b4, "P8 Switch"},
{0xe100b8, "Public Flags"},
{0xe100bc, "OS Kind"},
{0xe100bd, "OS Boot"},
{0xe100be, "OS Busy"},
{0xe100c0, "MsgPtr"},
{0xe100ca, "System Volume"},
{0xe10180, "ToBusyStrip"},
{0xe10184, "ToStrip"},
{0xe101b2, "MidiInputPoll"},
{0xe10200, "Memory manager"},
{0xe10204, "Set System Speed"},
{0xe10208, "Slot Arbiter"},
{0xe10220, "Hypercard callback"},
{0xe10224, "WordForRTL"},
{0xe11004, "ATLK Basic"},
{0xe11008, "ATLK Pascal"},
{0xe1100c, "ATLK RamGoComp"},
{0xe11010, "ATLK SoftReset"},
{0xe11014, "ATLK RamDispatch"},
{0xe11018, "ATLK RamForbid"},
{0xe1101c, "ATLK RamPermit"},
{0xe11020, "ATLK ProEntry"},
{0xe11022, "ATLK ProDOS"},
{0xe11026, "ATLK SerStatus"},
{0xe1102a, "ATLK SerWrite"},
{0xe1102e, "ATLK SerRead"},
{0xe1103e, "ATLK PFI"},
{0xe1d600, "ATLK CmdTable"},
{0xe1da00, "ATLK TickCount"}
};
#define numAddresses (sizeof(addresses) / sizeof(addresses[0]))
static const char *addressLookup(uint32_t addr, Map *map) {
for (int i = 0; i < numAddresses; i++) {
if (addresses[i].address >= addr) {
if (addresses[i].address == addr)
return addresses[i].comment;
break;
}
}
for (Rule *rule = map->rules; rule != NULL; rule = rule->next) {
if (rule->address == addr && rule->symbol != NULL) {
return rule->symbol;
}
}
if (addr & ~0xffff)
return addressLookup(addr & 0xffff, map); // try pageless
return NULL;
}

467
disasm.c
View File

@ -1,467 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* The main disassembler.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "disasm.h"
#include "65816.h"
#include "handle.h"
#include "addresses.h"
#include "prodos8.h"
#include "prodos16.h"
#include "smartport.h"
#include "tools.h"
static void dumphex(uint8_t *ptr, uint32_t addr, uint32_t len) {
uint8_t *eof = ptr + len;
for (int line = 0; ptr < eof; line++) {
printf("%02x/%04x: ", addr >> 16, addr & 0xffff);
uint8_t *p = ptr;
int skip = addr & 0xf;
int i = 0;
for (; i < skip; i++) {
if (i == 8) {
printf(" ");
}
printf(" ");
}
for (; i < 16 && p < eof; i++) {
if (i == 8) {
printf(" ");
}
printf("%02x ", *p++);
}
for (; i < 16; i++) {
if (i == 8) {
printf(" ");
}
printf(" ");
}
printf("| ");
i = 0;
for (; i < skip; i++) {
if (i == 8) {
printf(" ");
}
printf(" ");
}
for (; i < 16 && ptr < eof; i++) {
if (i == 8) {
printf(" ");
}
uint8_t c = *ptr++;
addr++;
printf("%c", (c >= ' ' && c <= '~') ? c : '.');
}
printf("\n");
}
}
void disassemble(uint8_t *data, size_t len, Map *map) {
uint8_t *ptr = data;
uint8_t *end = data + len;
uint16_t x = 0;
uint32_t val;
int8_t delta;
int16_t delta16;
uint32_t d6;
bool smart = false, dos8 = false, dos16 = false;
uint32_t addr = map->minMemory;
while (ptr < end) {
MapFlags flags = map->mem[addr - map->minMemory];
if ((flags & IsOpcode) || smart || dos8 || dos16) {
printf("%02x/%04x:", addr >> 16, addr & 0xffff);
uint8_t *start = ptr;
uint8_t opcode = *ptr++;
const char *inst = opcodes[opcode].inst;
Address mode = opcodes[opcode].address;
OpType type = opcodes[opcode].type;
if (smart || dos8) {
mode = DB;
inst = "db";
}
if (dos16) {
mode = DW;
inst = "dw";
}
uint16_t width = addressSizes[mode];
if (mode == IMMM && (flags & (IsEmu | IsM8))) {
width--;
}
if (mode == IMMX && (flags & (IsEmu | IsX8))) {
width--;
}
addr += width;
flags &= IsFlags; // clear changed flags
MapFlags oldFlags = flags;
for (int i = 0; i < width; i++) {
printf(" %02x", start[i]);
}
for (int i = 0; i < 4 - width; i++) {
printf(" ");
}
printf(" %s", inst);
for (int i = strlen(inst); i < 8; i++) {
printf(" ");
}
const char *comments = NULL;
uint8_t oprlen = 0;
uint8_t oper = 0;
switch (mode) {
case IMP:
break;
case IMM:
oper = *ptr++;
printf("#$%02x", oper);
oprlen = 4;
if (opcode == 0xe2) {
flags |= oper & IsFlags;
} else if (opcode == 0xc2) {
flags &= ~oper;
}
if ((flags ^ oldFlags) & IsX8) {
flags |= IsX8Changed;
}
if ((flags ^ oldFlags) & IsM8) {
flags |= IsM8Changed;
}
break;
case IMMM:
if (flags & (IsEmu | IsM8)) {
printf("#$%02x", *ptr++);
oprlen = 4;
} else {
val = r16(ptr); ptr += 2;
printf("#$%04x", val);
oprlen = 6;
comments = addressLookup(val, map);
}
break;
case IMMX:
if (flags & (IsEmu | IsX8)) {
x = *ptr++;
printf("#$%02x", x);
oprlen = 4;
} else {
x = r16(ptr); ptr += 2;
printf("#$%04x", x);
oprlen = 6;
comments = addressLookup(x, map);
}
break;
case IMMS:
val = r16(ptr); ptr += 2;
printf("#$%04x", val);
oprlen = 6;
comments = addressLookup(x, map);
break;
case ABS:
val = r16(ptr); ptr += 2;
if (type == JUMP || type == CALL) {
val |= addr & 0xff0000; // K
} else {
val |= flags & 0xff0000; // B
}
printf("$%02x/%04x", val >> 16, val & 0xffff);
oprlen = 8;
comments = addressLookup(val, map);
break;
case ABL:
val = r24(ptr); ptr += 3;
printf("$%02x/%04x", val >> 16, val & 0xffff);
oprlen = 8;
comments = addressLookup(val, map);
break;
case ABX:
val = r16(ptr); ptr += 2;
if (type == JUMP || type == CALL) {
val |= addr & 0xff0000; // K
} else {
val |= flags & 0xff0000; // B
}
printf("$%02x/%04x, x", val >> 16, val & 0xffff);
oprlen = 11;
comments = addressLookup(val, map);
break;
case ABY:
val = r16(ptr); ptr += 2;
if (type == JUMP || type == CALL) {
val |= addr & 0xff0000; // K
} else {
val |= flags & 0xff0000; // B
}
printf("$%02x/%04x, y", val >> 16, val & 0xffff);
oprlen = 11;
comments = addressLookup(val, map);
break;
case ABLX:
val = r24(ptr); ptr += 3;
printf("$%02x/%04x, x", val >> 16, val & 0xffff);
oprlen = 11;
comments = addressLookup(val, map);
break;
case AIX:
val = r16(ptr); ptr += 2;
if (type == JUMP || type == CALL) {
val |= addr & 0xff0000; // K
} else {
val |= flags & 0xff0000; // B
}
printf("($%02x/%04x, x)", val >> 16, val & 0xffff);
oprlen = 13;
comments = addressLookup(val, map);
break;
case ZP:
printf("$%02x", *ptr++);
oprlen = 3;
break;
case ZPX:
printf("$%02x, x", *ptr++);
oprlen = 6;
break;
case ZPY:
printf("$%02x, y", *ptr++);
oprlen = 6;
break;
case ZPS:
printf("$%02x, s", *ptr++);
oprlen = 6;
break;
case IND:
val = r16(ptr); ptr += 2;
if (type == JUMP || type == CALL) {
val |= addr & 0xff0000; // K
} else {
val |= flags & 0xff0000; // B
}
printf("($%02x/%04x)", val >> 16, val & 0xffff);
oprlen = 10;
comments = addressLookup(val, map);
break;
case INZ:
printf("($%02x)", *ptr++);
oprlen = 5;
break;
case INL:
printf("[$%02x]", *ptr++);
oprlen = 5;
break;
case INX:
printf("($%02x, x)", *ptr++);
oprlen = 8;
break;
case INY:
printf("($%02x), y", *ptr++);
oprlen = 8;
break;
case INLY:
printf("[$%02x], y", *ptr++);
oprlen = 8;
break;
case INS:
printf("($%02x, s), y", *ptr++);
oprlen = 11;
break;
case REL:
delta = *ptr++;
d6 = delta + addr;
printf("$%02x/%04x", d6 >> 16, d6 & 0xffff);
oprlen = 8;
comments = addressLookup(d6, map);
break;
case RELL:
delta16 = r16(ptr); ptr += 2;
d6 = delta16 + addr;
printf("$%02x/%04x", d6 >> 16, d6 & 0xffff);
oprlen = 8;
comments = addressLookup(d6, map);
break;
case BANK:
val = *ptr++;
printf("$%02x, $%02x", *ptr++, val);
oprlen = 8;
break;
case DB:
printf("$%02x", opcode);
oprlen = 3;
break;
case DW:
val = opcode | (*ptr++ << 8);
printf("$%04x", val);
oprlen = 5;
break;
case DD:
printf("$%08x", opcode | r24(ptr) << 8); ptr += 3;
oprlen = 9;
break;
}
if (smart) {
comments = smartportLookup(opcode);
smart = false;
}
if (dos8) {
comments = prodos8Lookup(opcode);
dos8 = false;
}
if (dos16) {
comments = prodos16Lookup(val);
dos16 = false;
}
// track plb
if (opcode == 0xab) { // plb, was it previously a9 xx 48?
bool bset = false;
ptr -= 4;
uint8_t lda = *ptr++;
uint8_t b = *ptr++;
uint8_t pha = *ptr++;
ptr++;
if (lda == 0xa9 && pha == 0x48) {
flags &= 0xffff;
flags |= b << 16; // set B
bset = true;
}
if (!bset) {
ptr -= 2;
uint8_t phk = *ptr++;
ptr++;
if (phk == 0x4b) {
flags &= 0xffff;
flags |= addr & 0xff0000; // B = K
bset = true;
}
}
if (!bset) {
ptr -= 7;
uint8_t lda = *ptr++;
uint8_t b = r16(ptr); ptr += 2;
ptr += 2; // sep #20
uint8_t pha = *ptr++;
ptr++;
if (lda == 0xa9 && pha == 0x48) {
flags &= 0xffff;
flags |= b << 16; // set B
bset = true;
}
}
}
if (opcode == 0x18) {
if (*ptr == 0xfb) { // clc xce = 16 bit mode
flags &= 0xffffff ^ IsEmu;
}
}
if (opcode == 0x38) {
if (*ptr == 0xfb) { // sec xce = 8 bit mode
flags |= IsEmu;
}
}
if ((flags ^ oldFlags) & IsEmu) {
flags |= IsEmuChanged;
}
if (opcode == 0xa2) { // ldx
if (*ptr == 0x22) { // jsl
if (r24(ptr + 1) == 0xe10000) { // jsl el/0000
comments = toolLookup(x);
}
}
}
if (opcode == 0x20) { // jsr
if (val == 0xc50d || val == 0xc70d) {
smart = true;
}
if (val == 0xbf00) {
dos8 = true;
}
}
if (opcode == 0x22) { // jsl
if (val == 0xe100a8) {
dos16 = true;
}
}
if (flags & (IsEmuChanged | IsM8Changed | IsX8Changed)) {
for (int i = oprlen; i < 16; i++) {
printf(" ");
}
printf("; ");
}
if (flags & IsEmuChanged) {
if (flags & IsEmu) {
printf(" 8-bit mode");
} else {
printf(" 16-bit mode");
}
}
if (flags & IsM8Changed) {
if (flags & IsM8) {
printf(" a.b");
} else {
printf(" a.w");
}
}
if (flags & IsX8Changed) {
if (flags & IsX8) {
printf(" x.b");
} else {
printf(" x.w");
}
}
if (comments != NULL) {
for (int i = oprlen; i < 16; i++) {
printf(" ");
}
printf("; %s", comments);
}
printf("\n");
} else {
uint32_t dlen = 0;
uint8_t *p = ptr;
while (p < end && !(map->mem[addr + dlen - map->minMemory] & IsOpcode)) {
p++;
dlen++;
}
uint32_t val;
switch (dlen) {
case 4:
printf("%02x/%04x:", addr >> 16, addr & 0xffff);
val = r32(ptr);
printf(" %02x %02x %02x %02x dd $%08x\n", val & 0xff,
(val >> 8) & 0xff, (val >> 16) & 0xff, val >> 24, val);
break;
case 2:
printf("%02x/%04x:", addr >> 16, addr & 0xffff);
val = r16(ptr);
printf(" %02x %02x dw $%04x\n", val & 0xff, val >> 8, val);
break;
case 1:
printf("%02x/%04x:", addr >> 16, addr & 0xffff);
printf(" %02x db $%04x\n", *ptr, *ptr);
break;
default:
dumphex(ptr, addr, dlen);
break;
}
ptr += dlen;
addr += dlen;
}
}
}

View File

@ -1,10 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* The main disassembler.
*/
#include "map.h"
extern void disassemble(uint8_t *data, size_t len, Map *map);

View File

@ -1,41 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Routines for reading multi-byte numbers from a stream
*/
#include <stdint.h>
static inline uint32_t fourcc(const char *p) {
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
static inline uint16_t r16(uint8_t *p) {
uint16_t r = *p++;
r |= *p << 8;
return r;
}
static inline uint32_t r24(uint8_t *p) {
uint32_t r = *p++;
r |= *p++ << 8;
r |= *p << 16;
return r;
}
static inline uint32_t r32(uint8_t *p) {
uint32_t r = *p++;
r |= *p++ << 8;
r |= *p++ << 16;
r |= *p << 24;
return r;
}
static inline uint32_t r4(uint8_t *p) {
uint32_t r = *p++ << 24;
r |= *p++ << 16;
r |= *p++ << 8;
r |= *p;
return r;
}

162
map.c
View File

@ -1,162 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* Handles the memory map, for the tracing disassembler.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "map.h"
#include "parser.h"
#include "handle.h"
static Rule *parseRule(ConfigFile *f) {
Rule *rule = malloc(sizeof(Rule));
rule->next = NULL;
if (!token(f, '$')) {
fail(f, "Address must be a hex value above 0 \n"
"starting with '$', i.e. $c20");
}
rule->address = hex(f);
if (token(f, '/')) {
rule->address <<= 16;
rule->address |= hex(f);
}
if (rule->address == 0) {
fail(f, "Address must be a hex value above 0 \n"
"starting with '$', i.e. $c20");
}
if (!token(f, ':')) {
fail(f, "Expected ':'");
}
rule->flags = IsOpcode;
bool foundFlag = false;
do {
foundFlag = false;
if (token(f, 'e')) {
rule->flags |= IsEmu;
foundFlag = true;
}
if (token(f, 'm')) {
rule->flags |= IsM8;
foundFlag = true;
}
if (token(f, 'x')) {
rule->flags |= IsX8;
foundFlag = true;
}
if (token(f, 'd')) {
rule->flags &= ~IsOpcode;
rule->flags |= IsData;
foundFlag = true;
}
} while (foundFlag);
rule->symbol = NULL;
if (token(f, '<')) {
uint8_t *fnp = f->p;
while (fnp < f->end && *fnp != '>') {
fnp++;
}
if (fnp == f->end) {
fail(f, "Symbol has no closing '>'.");
}
int flen = fnp - f->p;
rule->symbol = malloc(flen);
memcpy(rule->symbol, f->p, flen);
rule->symbol[flen] = 0;
f->p = fnp + 1;
}
return rule;
}
Map *loadMap(const char *filename, uint32_t org, MapFlags flags) {
Map *map = malloc(sizeof(Map));
map->rules = NULL;
map->minMemory = org;
map->maxMemory = 0;
map->mem = NULL;
FILE *f = fopen(filename, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", filename);
exit(-1);
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(len);
fread(data, len, 1, f);
fclose(f);
if (r4(data) != fourcc("gMAP")) {
// not a map file, use org as the only entry point
map->rules = malloc(sizeof(Rule));
map->rules->address = org;
map->rules->flags = flags;
map->rules->next = NULL;
map->filename = filename;
free(data);
return map;
}
ConfigFile c;
c.start = data;
c.p = c.start;
c.end = c.start + len;
c.filename = filename;
// set filename
c.p += 4;
eatSpaces(&c);
if (!token(&c, '"')) {
fail(&c, "Expected '\"' around the filename.");
}
uint8_t *fnp = c.p;
while (fnp < c.end && *fnp != '"') {
fnp++;
}
if (fnp == c.end) {
fail(&c, "Filename has no closing '\"'.");
}
int flen = fnp - c.p;
char *fname = malloc(flen);
memcpy(fname, c.p, flen);
fname[flen] = 0;
map->filename = fname;
c.p = fnp + 1;
eatSpaces(&c);
if (r4(c.p) != fourcc("sORG")) {
fail(&c, "Expected 'sORG' tag");
}
c.p += 4;
eatSpaces(&c);
if (!token(&c, '$')) {
fail(&c, "ORG must be a hex value above 0\n"
"starting with '$', i.e. $c20");
}
map->minMemory = hex(&c);
if (map->minMemory == 0) {
fail(&c, "ORG must be a hex value above 0\n"
"starting with '$', i.e. $c20");
}
eatSpaces(&c);
// load rules
Rule *lastRule = NULL;
while (c.p < c.end) {
Rule *rule = parseRule(&c);
if (lastRule == NULL) {
map->rules = rule;
} else {
lastRule->next = rule;
}
lastRule = rule;
eatSpaces(&c);
}
free(c.start);
return map;
}

38
map.h
View File

@ -1,38 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* The memory map for the tracing disassembler
*/
#include <stdint.h>
typedef enum {
IsData = 0x01,
IsOpcode = 0x02,
IsOperand = 0x04,
IsX8 = 0x10,
IsM8 = 0x20,
IsEmu = 0x100,
IsFlags = 0xff013f,
IsX8Changed = 0x200,
IsM8Changed = 0x400,
IsEmuChanged = 0x2000,
} MapFlags;
typedef struct Rule {
uint32_t address;
uint16_t flags;
char *symbol;
struct Rule *next;
} Rule;
typedef struct {
Rule *rules;
uint32_t minMemory;
uint32_t maxMemory;
MapFlags *mem;
const char *filename;
} Map;
extern Map *loadMap(const char *filename, uint32_t org, MapFlags flags);

584
omf.c
View File

@ -1,584 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* Handles parsing and relocating an OMF (s16, tool, etc)
*/
#include "handle.h"
#include "parser.h"
#include <stdlib.h>
#include <string.h>
#include <argp.h>
const char *argp_program_version = "omf 0.5";
const char *argp_program_bug_address = "sean@seancode.com";
static char doc[] = "Relocate and extract OMF segments"
"\vThis should be run twice. The first time to generate the map."
"The second time, to use that map to relocate and extract the segments";
static char args_doc[] = "FILE";
static struct argp_option options[] = {
{"org", 'o', "ADDRESS", OPTION_ARG_OPTIONAL,
"Start mapping the segments at this address, default $20000"},
{"map", 'm', "FILE", OPTION_ARG_OPTIONAL,
"Use this map to extract the segments"},
{"prefix", 'p', "PREFIX", OPTION_ARG_OPTIONAL,
"Prefix segment files with this. Default \"seg\""},
{ 0 }
};
struct arguments {
char *filename;
char *map;
char *prefix;
uint32_t org;
};
static inline uint32_t parseNum(const char *s) {
uint32_t res = 0;
while (isspace(*s)) {
s++;
}
bool ishex = false;
if (s[0] == '0' && s[1] == 'x') {
s += 2;
ishex = true;
} else if (s[0] == '$') {
s++;
ishex = true;
}
if (ishex) {
while (isxdigit(*s)) {
res <<= 4;
if (*s >= '0' && *s <= '9') {
res |= *s - '0';
} else if (*s >= 'a' && *s <= 'f') {
res |= *s - 'a' + 10;
} else if (*s >= 'A' && *s <= 'F') {
res |= *s - 'A' + 10;
}
s++;
}
} else {
while (isdigit(*s)) {
res *= 10;
res += *s - '0';
s++;
}
}
return res;
}
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'm':
arguments->map = arg;
break;
case 'p':
arguments->prefix = arg;
break;
case 'o':
if (arg) {
arguments->org = parseNum(arg);
}
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
}
arguments->filename = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
typedef struct Segment {
uint32_t bytecnt;
uint32_t resspc;
uint32_t length;
uint8_t lablen;
uint8_t numlen;
uint32_t banksize;
uint16_t kind;
uint32_t org;
uint32_t align;
uint16_t segnum;
uint32_t entry;
char name[256];
uint8_t *offset;
uint8_t *data;
uint32_t mapped;
struct Segment *next;
} Segment;
static Segment *loadOMF(uint8_t *data, size_t len);
static void mapSegments(Segment *segments, uint32_t min);
static void relocSegments(Segment *segments, char *prefix);
int main(int argc, char **argv) {
struct arguments arguments;
arguments.filename = "";
arguments.map = NULL;
arguments.prefix = "seg";
arguments.org = 0x20000;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
// open omf
FILE *f = fopen(arguments.filename, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", arguments.filename);
return -1;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(len);
fread(data, len, 1, f);
fclose(f);
Segment *segments = loadOMF(data, len);
if (arguments.map != NULL) {
// load the map!
f = fopen(arguments.map, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", arguments.map);
return -1;
}
ConfigFile c;
fseek(f, 0, SEEK_END);
len = ftell(f);
fseek(f, 0, SEEK_SET);
c.start = malloc(len);
fread(c.start, len, 1, f);
fclose(f);
c.p = c.start;
c.end = c.start + len;
while (c.p < c.end) {
uint32_t segnum = hex(&c);
if (!token(&c, ':')) {
fprintf(stderr, "Error: expected ':' in map.\n");
exit(-1);
}
uint32_t mapped = hex(&c);
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
if (seg->segnum == segnum) {
seg->mapped = mapped;
}
}
eatSpaces(&c);
}
free(c.start);
}
// map any unmapped segments
mapSegments(segments, arguments.org);
if (arguments.map != NULL) {
relocSegments(segments, arguments.prefix);
} else {
int mlen = strlen(arguments.filename);
char *mapname = malloc(mlen + 5);
memcpy(mapname, arguments.filename, mlen);
memcpy(mapname + mlen, ".map\0", 5);
f = fopen(mapname, "wb");
if (!f) {
fprintf(stderr, "Failed to create '%s'\n", mapname);
return -1;
}
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
fprintf(f, "%x:%x\n", seg->segnum, seg->mapped);
}
fclose(f);
fprintf(stdout, "Created '%s'. Edit it, and extract with: \n"
" %s --map=%s %s\n", mapname, argv[0], mapname, arguments.filename);
}
free(data);
}
static Segment *loadOMF(uint8_t *data, size_t len) {
Segment *last = NULL;
Segment *segments = NULL;
size_t ofs = 0;
while (ofs < len) {
Segment *seg = malloc(sizeof(Segment));
seg->next = NULL;
uint8_t *p = data + ofs;
seg->bytecnt = r32(p); p += 4;
seg->resspc = r32(p); p += 4;
seg->length = r32(p); p += 4;
uint8_t kind = *p++;
seg->lablen = *p++;
seg->numlen = *p++;
uint8_t version = *p++;
seg->banksize = r32(p); p += 4;
seg->kind = r16(p); p += 2;
p += 2; // undefined
seg->org = r32(p); p += 4;
seg->align = r32(p); p += 4;
p += 2; // byte order
seg->segnum = r16(p); p += 2;
seg->entry = r32(p); p += 4;
uint16_t dispname = r16(p); p += 2;
uint16_t dispdata = r16(p); p += 2;
p = data + ofs + dispname + 0xa;
uint8_t strlen = seg->lablen;
if (strlen == 0) {
strlen = *p++;
}
memcpy(seg->name, p, strlen);
seg->name[strlen] = 0;
seg->offset = data + ofs + dispdata;
if (version == 1) { // convert to v2
seg->bytecnt *= 512;
seg->kind = (kind & 0x1f) | ((kind & 0xe0) << 8);
}
seg->mapped = 0;
seg->data = NULL;
ofs += seg->bytecnt;
if (last == NULL) {
segments = seg;
} else {
last->next = seg;
}
last = seg;
}
return segments;
}
static int cmpmemory(const void *p1, const void *p2) {
const uint32_t *a = p1, *b = p2;
return *a - *b;
}
static void mapSegments(Segment *segments, uint32_t min) {
// we use a memory map that denotes runes of available memory.
// Each segment has a start and end, so we need an array of twice the
// number of segments (+2 for the absolute start and end of memory)
// count segments
uint32_t numSegs = 0;
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
numSegs++;
}
uint32_t *memory = malloc(sizeof(uint32_t) * (numSegs * 2 + 2));
int numNodes = 0;
memory[numNodes++] = min; // minimum
memory[numNodes++] = 0x1000000; // maximum possible memory for the IIgs.
// step one, map hardcoded or overridden targets
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
if (seg->org != 0 || seg->mapped != 0) {
if (seg->mapped == 0) {
if ((seg->kind & 0x1f) == 0x11) { // absolute bank
seg->mapped = seg->org << 16;
} else {
seg->mapped = seg->org;
}
}
bool collision = false;
// verify we aren't overlapping.. that would be tremendously bad.
for (int node = 0; node < numNodes; node += 2) {
if (seg->mapped < memory[node] &&
seg->mapped + seg->length > memory[node]) { // crosses!
collision = true;
}
if (seg->mapped < memory[node + 1] &&
seg->mapped + seg->length > memory[node + 1]) { // crosses!
collision = true;
}
}
if (collision) {
fprintf(stderr, "Segment #$%x collides with another segment!\n",
seg->segnum);
exit(-1);
}
memory[numNodes++] = seg->mapped;
memory[numNodes++] = seg->mapped + seg->length;
if (seg->mapped < memory[0]) { // below the minimum!
memory[0] = seg->mapped; // recalibrate the minimum
}
// resort memory map
qsort(memory, numNodes, sizeof(uint32_t), cmpmemory);
}
}
// finally, map everything else by first fit.
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
if (seg->mapped == 0) {
for (int node = 0; node < numNodes; node += 2) {
uint32_t base = memory[node];
if (seg->align && (base & (seg->align - 1))) { // snap to alignment
base += seg->align;
base &= ~(seg->align - 1);
}
if (seg->banksize &&(((base & (seg->banksize - 1)) + seg->length) &
~(seg->banksize - 1))) { // crosses bank
base += seg->banksize;
base &= ~(seg->banksize - 1);
}
// does it fit?
if (base < memory[node + 1] && memory[node + 1] - base >= seg->length) {
seg->mapped = base;
memory[numNodes++] = seg->mapped;
memory[numNodes++] = seg->mapped + seg->length;
qsort(memory, numNodes, sizeof(uint32_t), cmpmemory);
break;
}
}
if (seg->mapped == 0) {
fprintf(stderr, "Failed to map Segment #$%x, not enough free memory\n",
seg->segnum);
exit(-1);
}
}
}
free(memory);
}
typedef enum {
DONE = 0x00,
RELOC = 0xe2,
INTERSEG = 0xe3,
DS = 0xf1,
LCONST = 0xf2,
cRELOC = 0xf5,
cINTERSEG = 0xf6,
SUPER = 0xf7
} SegOp;
static void patch(uint8_t *data, uint8_t numBytes, uint32_t value) {
for (int i = 0; i < numBytes; i++, value >>= 8) {
data[i] = value & 0xff;
}
}
static void hexout(char *p, uint32_t val, int len) {
p += len;
while (len-- > 0) {
char ch = val & 0xf;
val >>= 4;
if (ch < 10) {
*--p = '0' + ch;
} else {
*--p = 'a' + (ch - 10);
}
}
}
static void relocSegments(Segment *segments, char *prefix) {
int prefixLen = strlen(prefix);
char *filename = malloc(prefixLen + 4);
char *mapname = malloc(prefixLen + 9);
for (Segment *seg = segments; seg != NULL; seg = seg->next) {
bool done = false;
uint32_t pc = seg->mapped;
uint8_t *p = seg->offset;
seg->data = calloc(seg->length, 1);
while (!done) {
uint8_t opcode = *p++;
switch (opcode) {
case DONE:
done = true;
break;
case RELOC:
{
uint8_t numBytes = *p++;
int8_t bitShift = *p++;
uint32_t offset = r32(p); p += 4;
uint32_t subOffset = r32(p) + seg->mapped; p += 4;
if (bitShift < 0) {
subOffset >>= -bitShift;
} else {
subOffset <<= bitShift;
}
patch(seg->data + offset, numBytes, subOffset);
}
break;
case INTERSEG:
{
uint8_t numBytes = *p++;
int8_t bitShift = *p++;
uint32_t offset = r32(p); p += 4;
p += 2; // filenum
uint16_t segnum = r16(p); p += 2;
uint32_t subOffset = r32(p); p += 4;
for (Segment *sub = segments; sub != NULL; sub = sub->next) {
if (sub->segnum == segnum) {
subOffset += sub->mapped;
break;
}
}
if (bitShift < 0) {
subOffset >>= -bitShift;
} else {
subOffset <<= bitShift;
}
patch(seg->data + offset, numBytes, subOffset);
}
break;
case DS:
pc += r32(p); p += 4; // filled with zeros
break;
case LCONST:
{
uint32_t count = r32(p); p += 4;
memcpy(seg->data + pc - seg->mapped, p, count); p += count;
pc += count;
}
break;
case cRELOC:
{
uint8_t numBytes = *p++;
int8_t bitShift = *p++;
uint16_t offset = r16(p); p += 2;
uint32_t subOffset = r16(p) + seg->mapped; p += 2;
if (bitShift < 0) {
subOffset >>= -bitShift;
} else {
subOffset <<= bitShift;
}
patch(seg->data + offset, numBytes, subOffset);
}
break;
case cINTERSEG:
{
uint8_t numBytes = *p++;
int8_t bitShift = *p++;
uint16_t offset = r16(p); p += 2;
uint8_t segnum = *p++;
uint32_t subOffset = r16(p); p += 2;
for (Segment *sub = segments; sub != NULL; sub = sub->next) {
if (sub->segnum == segnum) {
subOffset += sub->mapped;
break;
}
}
if (bitShift < 0) {
subOffset >>= -bitShift;
} else {
subOffset <<= bitShift;
}
patch(seg->data + offset, numBytes, subOffset);
}
break;
case SUPER:
{
uint32_t superLen = r32(p); p += 4;
uint8_t *superEnd = p + superLen;
uint8_t superType = *p++;
uint32_t superPage = 0;
while (p < superEnd) {
uint8_t numOfs = *p++;
if (numOfs & 0x80) {
superPage += 256 * (numOfs & 0x7f);
continue;
}
for (int o = 0; o <= numOfs; o++) {
uint32_t offset = superPage | *p++;
uint8_t numBytes = 0;
uint32_t subOffset = r16(seg->data + offset);
if (superType == 0 || superType == 1) { // RELOC2 | RELOC3
subOffset += seg->mapped;
numBytes = 2 + superType;
} else if (superType < 14) { // INTERSEG1--12
uint8_t segnum = seg->data[offset + 2];
for (Segment *sub = segments; sub != NULL; sub = sub->next) {
if (sub->segnum == segnum) {
subOffset += sub->mapped;
break;
}
}
numBytes = 3;
} else if (superType < 26) { // INTERSEG13--24
uint8_t segnum = superType - 13;
for (Segment *sub = segments; sub != NULL; sub = sub->next) {
if (sub->segnum == segnum) {
subOffset += sub->mapped;
break;
}
}
numBytes = 2;
} else { // INTERSEG25--36
uint8_t segnum = superType - 25;
for (Segment *sub = segments; sub != NULL; sub = sub->next) {
if (sub->segnum == segnum) {
subOffset += sub->mapped;
break;
}
}
subOffset >>= 16;
numBytes = 2;
}
patch(seg->data + offset, numBytes, subOffset);
}
superPage += 256;
}
}
break;
default:
if (opcode < 0xe0) {
memcpy(seg->data + pc - seg->mapped, p, opcode); p += opcode;
pc += opcode;
} else {
fprintf(stderr, "Unknown segment code: %x\n", opcode);
exit(-1);
}
break;
}
}
memcpy(filename, prefix, prefixLen);
int numLen = 3;
if (seg->segnum < 0x10) {
numLen = 1;
} else if (seg->segnum < 0x100) {
numLen = 2;
}
hexout(filename + prefixLen, seg->segnum, numLen);
filename[prefixLen + numLen] = 0;
memcpy(mapname, filename, prefixLen + numLen);
memcpy(mapname + prefixLen + numLen, ".map\0", 5);
FILE *f = fopen(filename, "wb");
if (!f) {
fprintf(stderr, "Failed to create '%s'\n", filename);
exit(-1);
}
fwrite(seg->data, 1, seg->length, f);
fclose(f);
f = fopen(mapname, "wb");
if (!f) {
fprintf(stderr, "Failed to create '%s'\n", mapname);
exit(-1);
}
fprintf(f, "gMAP \"%s\"\n", filename);
fprintf(f, "sORG $%x\n", seg->mapped);
if ((seg->kind & 0x1f) == 0) { // code
fprintf(f, "$%x:\n", seg->mapped + seg->entry);
}
fclose(f);
fprintf(stdout, "Extracted Segment #$%x %s into %s\n", seg->segnum,
seg->name, filename);
fprintf(stdout, "Created map %s\n", mapname);
}
free(filename);
}

View File

@ -1,66 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* Routines for parsing a simple config file
*/
#include "parser.h"
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
void fail(ConfigFile *f, char *format, ...) {
// calculate line and column
int line = 1;
int col = 1;
bool done = false;
while (f->p > f->start) {
if (*f->p == '\n') {
done = true;
line++;
}
if (!done) {
col++;
}
f->p--;
}
va_list args;
va_start(args, format);
fprintf(stderr, "%s[%d,%d] Error: ", f->filename, line, col);
vfprintf(stderr, format, args);
fprintf(stderr, "\n");
va_end(args);
exit(-1);
}
void eatSpaces(ConfigFile *f) {
while (f->p < f->end && isspace(*f->p)) {
f->p++;
}
}
bool token(ConfigFile *f, char ch) {
eatSpaces(f);
if (f->p < f->end && *f->p == ch) { // found it, consume
f->p++;
return true;
}
// not found, don't consume anything
return false;
}
uint32_t hex(ConfigFile *f) {
eatSpaces(f);
uint32_t res = 0;
while (f->p < f->end && isxdigit(*f->p)) {
res <<= 4;
if (*f->p >= '0' && *f->p <= '9') {
res |= *f->p - '0';
} else if (*f->p >= 'a' && *f->p <= 'f') {
res |= *f->p - 'a' + 10;
} else if (*f->p >= 'A' && *f->p <= 'F') {
res |= *f->p - 'A' + 10;
}
f->p++;
}
return res;
}

View File

@ -1,22 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Routines for parsing simple config files
*/
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
typedef struct {
uint8_t *start;
uint8_t *p;
uint8_t *end;
const char *filename;
} ConfigFile;
extern void fail(ConfigFile *f, char *format, ...);
extern void eatSpaces(ConfigFile *f);
extern bool token(ConfigFile *f, char ch);
extern uint32_t hex(ConfigFile *f);

View File

@ -1,144 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* The ProDOS 16 tool calls
*/
typedef struct {
uint16_t call;
const char *name;
} Prodos16;
static Prodos16 prodos16[] = {
{0x0001, "CREATE"},
{0x0002, "DESTROY"},
{0x0004, "CHANGE_PATH"},
{0x0005, "SET_FILE_INFO"},
{0x0006, "GET_FILE_INFO"},
{0x0008, "VOLUME"},
{0x0009, "SET_PREFIX"},
{0x000a, "GET_PREFIX"},
{0x000b, "CLEAR_BACKUP_BIT"},
{0x0010, "OPEN"},
{0x0011, "NEWLINE"},
{0x0012, "READ"},
{0x0013, "WRITE"},
{0x0014, "CLOSE"},
{0x0015, "FLUSH"},
{0x0016, "SET_MARK"},
{0x0017, "GET_MARK"},
{0x0018, "SET_EOF"},
{0x0019, "GET_EOF"},
{0x001a, "SET_LEVEL"},
{0x001b, "GET_LEVEL"},
{0x001c, "GET_DIR_ENTRY"},
{0x0020, "GET_DEV_NUM"},
{0x0021, "GET_LAST_DEV"},
{0x0022, "READ_BLOCK"},
{0x0023, "WRITE_BLOCK"},
{0x0024, "FORMAT"},
{0x0025, "ERASE_DISK"},
{0x0027, "GET_NAME"},
{0x0028, "GET_BOOT_VOL"},
{0x0029, "QUIT"},
{0x002a, "GET_VERSION"},
{0x002c, "D_INFO"},
{0x0031, "ALLOC_INTERRUPT"},
{0x0032, "DEALLOCATE_INTERRUPT"},
{0x0101, "Get_LInfo"},
{0x0102, "Set_LInfo"},
{0x0103, "Get_Lang"},
{0x0104, "Set_Lang"},
{0x0105, "Error"},
{0x0106, "Set_Variable"},
{0x0107, "Version"},
{0x0108, "Read_Indexed"},
{0x0109, "Init_Wildcard"},
{0x010a, "Next_Wildcard"},
{0x010b, "Read_Variable"},
{0x010c, "ChangeVector"},
{0x010d, "Execute"},
{0x010e, "FastFile"},
{0x010f, "Direction"},
{0x0110, "Redirect"},
{0x0113, "Stop"},
{0x0114, "ExpandDevices"},
{0x0115, "UnsetVariable"},
{0x0116, "Export"},
{0x0117, "PopVariables"},
{0x0118, "PushVariables"},
{0x0119, "SetStopFlag"},
{0x011a, "ConsoleOut"},
{0x011b, "SetIODevices"},
{0x011c, "GetIODevices"},
{0x011d, "GetCommand"},
{0x2001, "Create"},
{0x2002, "Destroy"},
{0x2003, "OSShutdown"},
{0x2004, "ChangePath"},
{0x2005, "SetFileInfo"},
{0x2006, "GetFileInfo"},
{0x2007, "JudgeName"},
{0x2008, "Volume"},
{0x2009, "SetPrefix"},
{0x200a, "GetPrefix"},
{0x200b, "ClearBackup"},
{0x200c, "SetSysPrefs"},
{0x200d, "Null"},
{0x200e, "ExpandPath"},
{0x200f, "GetSysPrefs"},
{0x2010, "Open"},
{0x2011, "NewLine"},
{0x2012, "Read"},
{0x2013, "Write"},
{0x2014, "Close"},
{0x2015, "Flush"},
{0x2016, "SetMark"},
{0x2017, "GetMark"},
{0x2018, "SetEOF"},
{0x2019, "GetEOF"},
{0x201a, "SetLevel"},
{0x201b, "GetLevel"},
{0x201c, "GetDirEntry"},
{0x201d, "BeginSession"},
{0x201e, "EndSession"},
{0x201f, "SessionStatus"},
{0x2020, "GetDevNumber"},
{0x2024, "Format"},
{0x2025, "EraseDisk"},
{0x2026, "ResetCache"},
{0x2027, "GetName"},
{0x2028, "GetBoolVol"},
{0x2029, "Quit"},
{0x202a, "GetVersion"},
{0x202b, "GetFSTInfo"},
{0x202c, "DInfo"},
{0x202d, "DStatus"},
{0x202e, "DControl"},
{0x202f, "DRead"},
{0x2030, "DWrite"},
{0x2031, "BindInt"},
{0x2032, "UnbindInt"},
{0x2033, "FSTSpecific"},
{0x2034, "AddNotifyProc"},
{0x2035, "DelNotifyProc"},
{0x2036, "DRename"},
{0x2037, "GetStdRefNum"},
{0x2038, "GetRefNum"},
{0x2039, "GetRefInfo"},
{0x203a, "SetStdRefNum"}
};
#define numProdos16 (sizeof(prodos16) / sizeof(prodos16[0]))
static const char *prodos16Lookup(uint16_t call) {
for (int i = 0; i < numProdos16; i++) {
if (prodos16[i].call >= call) {
if (prodos16[i].call == call)
return prodos16[i].name;
break;
}
}
return NULL;
}

View File

@ -1,56 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Defines the ProDOS 8 tool calls
*/
typedef struct {
uint16_t call;
const char *name;
} Prodos8;
static Prodos8 prodos8[] = {
{0x0040, "ALLOC_INTERRUPT"},
{0x0041, "DEALLOC_INTERRUPT"},
{0x0042, "AppleTalk"},
{0x0043, "SpecialOpenFork"},
{0x0044, "ByteRangeLock"},
{0x0065, "QUIT"},
{0x0080, "READ_BLOCK"},
{0x0081, "WRITE_BLOCK"},
{0x0082, "GET_TIME"},
{0x00c0, "CREATE"},
{0x00c1, "DESTROY"},
{0x00c2, "RENAME"},
{0x00c3, "SetFileInfo"},
{0x00c4, "GetFileInfo"},
{0x00c5, "ONLINE"},
{0x00c6, "SET_PREFIX"},
{0x00c7, "GET_PREFIX"},
{0x00c8, "OPEN"},
{0x00c9, "NEWLINE"},
{0x00ca, "READ"},
{0x00cb, "WRITE"},
{0x00cc, "CLOSE"},
{0x00cd, "FLUSH"},
{0x00ce, "SET_MARK"},
{0x00cf, "GET_MARK"},
{0x00d0, "SET_EOF"},
{0x00d1, "GET_EOF"},
{0x00d2, "SET_BUF"},
{0x00d3, "GET_BUF"}
};
#define numProdos8 (sizeof(prodos8) / sizeof(prodos8[0]))
static const char *prodos8Lookup(uint16_t call) {
for (int i = 0; i < numProdos8; i++) {
if (prodos8[i].call >= call) {
if (prodos8[i].call == call)
return prodos8[i].name;
break;
}
}
return NULL;
}

137
regs.c
View File

@ -1,137 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* The main disassembler
*/
#include <argp.h>
#include <stdbool.h>
#include "handle.h"
#include "scan.h"
#include "disasm.h"
const char *argp_program_version = "regs 0.1";
const char *argp_program_bug_address = "sean@seancode.com";
static char doc[] = "Disassemble Apple IIgs software";
static char args_doc[] = "FILE or MAP";
static struct argp_option options[] = {
{"org", 'o', "ADDRESS", 0,
"Starting address of the binary file"},
{"m", 'm', 0, OPTION_ARG_OPTIONAL,
"Start with 8-bit accumulator"},
{"x", 'x', 0, OPTION_ARG_OPTIONAL,
"Start with 8-bit indices"},
{"e", 'e', 0, OPTION_ARG_OPTIONAL,
"Start in emulation mode"},
{ 0 }
};
struct arguments {
char *filename;
uint32_t org;
MapFlags flags;
};
static inline uint32_t parseNum(const char *s) {
uint32_t res = 0;
while (isspace(*s)) {
s++;
}
bool ishex = false;
if (s[0] == '0' && s[1] == 'x') {
s += 2;
ishex = true;
} else if (s[0] == '$') {
s++;
ishex = true;
}
if (ishex) {
while (isxdigit(*s)) {
res <<= 4;
if (*s >= '0' && *s <= '9') {
res |= *s - '0';
} else if (*s >= 'a' && *s <= 'f') {
res |= *s - 'a' + 10;
} else if (*s >= 'A' && *s <= 'F') {
res |= *s - 'A' + 10;
}
s++;
}
} else {
while (isdigit(*s)) {
res *= 10;
res += *s - '0';
s++;
}
}
return res;
}
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = state->input;
switch (key) {
case 'o':
if (arg) {
arguments->org = parseNum(arg);
}
break;
case 'm':
if (arg) {
arguments->flags |= IsM8;
}
break;
case 'x':
if (arg) {
arguments->flags |= IsX8;
}
break;
case 'e':
if (arg) {
arguments->flags |= IsEmu;
}
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
}
arguments->filename = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
int main(int argc, char **argv) {
struct arguments arguments;
arguments.filename = "";
arguments.org = 0x300;
arguments.flags = IsOpcode;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
// load the map, if it exists
Map *map = loadMap(arguments.filename, arguments.org, arguments.flags);
// load the file
FILE *f = fopen(map->filename, "rb");
if (!f) {
fprintf(stderr, "Failed to open '%s'\n", map->filename);
return -1;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
uint8_t *data = malloc(len);
fread(data, len, 1, f);
fclose(f);
scan(data, len, map);
disassemble(data, len, map);
free(data);
}

187
scan.c
View File

@ -1,187 +0,0 @@
/**
* @copyright 2018 Sean Kasun
* The tracing scanner.
*/
#include "scan.h"
#include "65816.h"
#include "handle.h"
typedef struct {
uint32_t address;
MapFlags flags;
} Scan;
typedef struct {
uint32_t max;
uint32_t num;
Scan *values;
} List;
static void initList(List *list) {
list->max = 1024;
list->num = 0;
list->values = malloc(sizeof(Scan) * list->max);
}
static void freeList(List *list) {
free(list->values);
}
static void appendList(List *list, uint32_t address, MapFlags flags) {
list->values[list->num].address = address;
list->values[list->num].flags = flags;
list->num++;
if (list->num == list->max) {
list->max *= 1.6;
list->values = realloc(list->values, sizeof(Scan) * list->max);
}
}
static uint32_t fillMem(uint32_t address, int len, MapFlags flags, Map *map) {
for (int i = 0; i < len; i++) {
map->mem[address++ - map->minMemory] = flags;
}
return address;
}
void scan(uint8_t *data, size_t len, Map *map) {
List toScan;
initList(&toScan);
map->maxMemory = map->minMemory + len;
map->mem = calloc(len, sizeof(MapFlags));
for (Rule *rule = map->rules; rule != NULL; rule = rule->next) {
if (rule->flags & IsOpcode) {
appendList(&toScan, rule->address, rule->flags);
}
}
while (toScan.num > 0) {
toScan.num--;
MapFlags flags = toScan.values[toScan.num].flags;
uint32_t address = toScan.values[toScan.num].address;
while (address < map->maxMemory) {
uint8_t *ptr = data + address - map->minMemory;
uint8_t opcode = *ptr++;
if (map->mem[address - map->minMemory] & IsOperand) {
// we jumped into the middle of an opcode.. go backward to clear it
uint32_t offset = (address - map->minMemory) - 1;
while (offset > 0 && (map->mem[offset] & IsOperand)) {
map->mem[offset--] = IsData;
}
map->mem[offset] = IsData; // overwrite the opcode too.
}
Address mode = opcodes[opcode].address;
OpType type = opcodes[opcode].type;
if (address + addressSizes[mode] > map->maxMemory) {
mode = DB;
}
uint16_t width = addressSizes[mode];
if (mode == IMMM && (flags & (IsEmu | IsM8))) {
width--;
}
if (mode == IMMX && (flags & (IsEmu | IsX8))) {
width--;
}
if (mode == DB) {
map->mem[address++ - map->minMemory] = IsData;
} else {
map->mem[address++ - map->minMemory] = IsOpcode |
(flags & (IsEmu | IsX8 | IsM8));
address = fillMem(address, width - 1, IsOperand, map);
}
uint32_t val = 0;
int8_t delta;
int16_t delta16;
switch (mode) {
case IMM:
val = *ptr++;
if (opcode == 0xe2) {
flags |= val;
} else if (opcode == 0xc2) {
flags &= ~val;
}
break;
case IMMM:
if (flags & (IsEmu | IsM8)) {
val = *ptr++;
} else {
val = r16(ptr); ptr += 2;
}
break;
case IMMX:
if (flags & (IsEmu | IsX8)) {
val = *ptr++;
} else {
val = r16(ptr); ptr += 2;
}
break;
case IMMS:
val = r16(ptr); ptr += 2;
break;
case ABS:
val = r16(ptr); ptr += 2;
val |= address & 0xff0000;
break;
case ABL:
val = r24(ptr); ptr += 3;
break;
case REL:
delta = *ptr++;
val = delta + address;
break;
case RELL:
delta16 = r16(ptr); ptr += 2;
val = delta16 + address;
break;
default:
break;
}
if (opcode == 0x18 && *ptr == 0xfb) { // clc xce
flags &= ~IsEmu; // 16-bit
}
if (opcode == 0x38 && *ptr == 0xfb) { // sec xce
flags |= IsEmu; // 8-bit
}
if (opcode == 0x20) { // jsr
if ((val & 0xffff) == 0xc50d || (val & 0xffff) == 0xc70d) { // disk
address = fillMem(address, 3, IsData, map); // db, dw
if (*ptr++ >= 0x40) {
address = fillMem(address, 2, IsData, map); // dw
}
}
if ((val & 0xffff) == 0xbf00) { // prodos8
address = fillMem(address, 3, IsData, map); // db, dw
}
}
if (opcode == 0x22) { // jsl
if (val == 0xe100a8) {
address = fillMem(address, 6, IsData, map); // dw, dd
}
}
if (type == BRANCH || (type == CALL && mode != AIX) ||
(type == JUMP && mode != IND && mode != AIX)) {
if (val >= map->minMemory && val < map->maxMemory &&
!(map->mem[val - map->minMemory] & IsOpcode)) {
appendList(&toScan, val, flags);
}
}
if (type == BREAK || type == RETURN || type == JUMP) {
break;
}
}
}
freeList(&toScan);
}

11
scan.h
View File

@ -1,11 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* The tracing scanner.
*/
#include "map.h"
#include <stdlib.h>
extern void scan(uint8_t *data, size_t len, Map *map);

View File

@ -1,47 +0,0 @@
#pragma once
/**
* @copyright 2018 Sean Kasun
* Defines the SmartPort tool calls
*/
typedef struct {
uint8_t call;
const char *name;
} SmartPort;
static SmartPort smartport[] = {
{0x00, "Status"},
{0x01, "Read"},
{0x02, "Write"},
{0x03, "Format"},
{0x04, "Control"},
{0x05, "Init"},
{0x06, "Open"},
{0x07, "Close"},
{0x08, "Read"},
{0x09, "Write"},
{0x40, "Status"},
{0x41, "Read"},
{0x42, "Write"},
{0x43, "Format"},
{0x44, "Control"},
{0x45, "Init"},
{0x46, "Open"},
{0x47, "Close"},
{0x48, "Read"},
{0x49, "Write"}
};
#define numSmartPort (sizeof(smartport) / sizeof(smartport[0]))
static const char *smartportLookup(uint8_t call) {
for (int i = 0; i < numSmartPort; i++) {
if (smartport[i].call >= call) {
if (smartport[i].call == call)
return smartport[i].name;
break;
}
}
return NULL;
}

317
src/2mg.cc Normal file
View File

@ -0,0 +1,317 @@
/** @copyright 2020 Sean Kasun */
#include <argp.h>
#include <iostream>
#include <iomanip>
#include <locale>
#include <fstream>
#include <sys/stat.h>
#include <unistd.h>
#include "handle.h"
#include "prodos_types.h"
const char *argp_program_version = "2mg 0.2";
const char *argp_program_bug_address = "sean@seancode.com";
static char doc[] = "Extract ProDOS disk images";
static char args_doc[] = "DISKIMAGE";
static struct argp_option options[] = {
{"list", 'l', 0, 0, "List files"},
{ 0 }
};
struct arguments {
const char *diskimage;
bool list;
};
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
struct arguments *arguments = static_cast<struct arguments *>(state->input);
switch (key) {
case 'l':
arguments->list = true;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
}
arguments->diskimage = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
argp_usage(state);
}
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp = { options, parse_opt, args_doc, doc };
static std::string readFilename(Handle h, int len) {
static const char *digits = "0123456789abcdef";
std::string r;
for (int i = 0; i < len; i++) {
uint8_t ch = h->r8();
if (isalnum(ch) || ch == '_' || ch == '.' || ch == ' ') {
r += static_cast<char>(ch);
} else {
r += 'x';
uint8_t hi = ch >> 4;
uint8_t lo = ch & 0xf;
r += digits[hi];
r += digits[lo];
}
}
return r;
}
static void indent(int depth) {
for (int i = 0; i < depth; i++) {
std::cout << " ";
}
}
static void doEntry(uint32_t entry, Handle h, int depth);
static void doDirectory(uint16_t key, Handle h, int depth) {
uint32_t block = key * 512 + 4;
h->seek(block);
auto type = h->r8();
if ((type & 0xf0) != 0xf0 && (type & 0xf0) != 0xe0) {
std::cerr << "Invalid ProDOS disk" << std::endl;
return;
}
std::string dirname = readFilename(h, type & 0xf);
if (depth < 0) {
mkdir(dirname.c_str(), 0777);
chdir(dirname.c_str());
} else {
indent(depth);
std::cout << dirname << " <dir>" << std::endl;
}
h->seek(block + 0x1f);
auto entryLength = h->r8();
auto entriesPerBlock = h->r8();
auto fileCount = h->r16();
uint8_t curEntry = 1;
uint16_t curFile = 0;
while (curFile < fileCount) {
auto entry = block + curEntry * entryLength;
h->seek(entry);
auto type = h->r8();
if (type) {
doEntry(entry, h, depth < 0 ? -1 : depth + 1);
curFile++;
}
curEntry++;
if (curEntry == entriesPerBlock) {
curEntry = 0;
h->seek(block - 2);
block = h->r16() * 512 + 4;
}
}
if (depth < 0) {
chdir("..");
}
}
static void printDateTime(uint16_t date, uint16_t time) {
std::cout << std::setfill('0')
<< std::setw(2) << ((date >> 9) & 0x7f) << "-"
<< std::setw(2) << ((date >> 5) & 0xf) << "-"
<< std::setw(2) << (date & 0x1f) << " "
<< std::setw(2) << ((time >> 8) & 0x1f) << ":"
<< std::setw(2) << (time & 0x3f);
}
static void dumpSeedling(Handle h, uint32_t len, std::ostream &f) {
h->dump(len, f);
}
static void dumpSapling(Handle h, uint32_t len, std::ostream &f) {
auto index = h->tell();
while (len > 0) {
h->seek(index);
uint16_t blockid = h->r8();
h->skip(255);
blockid |= h->r8() << 8;
uint32_t blen = std::min<uint32_t>(512, len);
if (blockid && (blockid + 1) * 512 <= h->length) {
h->seek(blockid * 512);
dumpSeedling(h, blen, f);
} else {
f.seekp(blen, std::ios::cur);
}
len -= blen;
index++;
}
}
static void dumpTree(Handle h, uint32_t len, std::ostream &f) {
auto index = h->tell();
while (len > 0) {
h->seek(index);
uint16_t blockid = h->r8();
h->skip(255);
blockid |= h->r8() << 8;
uint32_t blen = std::min<uint32_t>(256 * 512, len);
if (blockid && (blockid + 1) * 512 <= h->length) {
h->seek(blockid * 512);
dumpSapling(h, blen, f);
} else {
f.seekp(blen, std::ios::cur);
}
len -= blen;
index++;
}
}
static void doFile(uint16_t key, uint32_t len, std::string name,
Handle h, int type) {
h->seek(key * 512);
std::ofstream f(name, std::ios::out | std::ios::binary | std::ios::trunc);
if (!f.is_open()) {
std::cerr << "Failed to create " << name << std::endl;
return;
}
switch (type) {
case 1:
dumpSeedling(h, len, f);
break;
case 2:
dumpSapling(h, len, f);
break;
case 3:
dumpTree(h, len, f);
break;
}
f.close();
}
static void doGSOS(uint16_t key, std::string filename, uint8_t type,
Handle h, int depth) {
h->seek(key * 512);
auto filetype = h->r8();
auto subkey = h->r16();
h->skip(2);
auto eof = h->r24();
if (depth < 0) {
doFile(subkey, eof, filename, h, filetype);
}
std::string resname = filename;
resname += ".res";
h->seek(key * 512 + 0x100);
filetype = h->r8();
subkey = h->r16();
h->skip(2);
auto reof = h->r24();
if (depth < 0) {
doFile(subkey, reof, resname, h, filetype);
} else {
indent(depth);
std::cout << filename << " " << eof << " bytes resource: "
<< reof << " bytes" << std::endl;
}
}
static void doEntry(uint32_t entry, Handle h, int depth) {
h->seek(entry);
auto type = h->r8();
std::string filename = readFilename(h, type & 0xf);
h->seek(entry + 0x10);
auto filetype = h->r8();
auto key = h->r16();
h->skip(2);
auto eof = h->r24();
switch (type & 0xf0) {
case 0x10: // seedling
case 0x20: // sapling
case 0x30: // tree
if (depth < 0) {
doFile(key, eof, filename, h, type >> 4);
} else {
auto createDate = h->r16();
auto createTime = h->r16();
h->skip(3);
auto aux = h->r16();
auto modDate = h->r16();
auto modTime = h->r16();
indent(depth);
std::cout << filename << std::endl;
indent(depth);
std::cout << " Size: " << eof;
std::cout << " Created: ";
printDateTime(createDate, createTime);
std::cout << " Modified: ";
printDateTime(modDate, modTime);
std::cout << std::endl;
indent(depth);
std::cout << " Type: $" << std::hex << std::setw(2)
<< std::setfill('0') << static_cast<int>(filetype)
<< " Aux: $" << std::setw(4) << aux << std::dec << " ";
uint32_t typeaux = filetype | (aux << 8);
for (int i = 0; i < numTypes; i++) {
if ((typeaux & fileTypes[i].mask) == fileTypes[i].id) {
std::cout << fileTypes[i].ext << "/" << fileTypes[i].desc;
break;
}
}
std::cout << std::endl;
}
break;
case 0x50:
doGSOS(key, filename, filetype, h, depth);
break;
case 0xd0:
doDirectory(key, h, depth < 0 ? -1 : depth + 1);
break;
default:
std::cerr << "Unknown file storage: " << static_cast<int>(type)
<< std::endl;
return;
}
}
class MyPunct : public std::numpunct<char> {
protected:
virtual char do_thousands_sep() const { return ','; }
virtual std::string do_grouping() const { return "\03"; }
};
int main(int argc, char **argv) {
struct arguments arguments;
arguments.list = false;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
std::cout.imbue(std::locale(std::locale::classic(), new MyPunct));
auto h = TheHandle::createFromFile(arguments.diskimage);
if (!h->isOpen()) {
std::cerr << "Failed to open " << arguments.diskimage << std::endl;
return -1;
}
if (h->length < 64) {
std::cerr << arguments.diskimage << " is not a valid prodos disk image"
<< std::endl;
return -1;
}
if (h->read(4) == "2IMG") {
h->seek(0xc);
if (h->r32() != 1) {
std::cerr << "Not a ProDOS disk image" << std::endl;
return -1;
}
h->seek(0x14);
auto disklen = h->r32() * 512;
auto diskofs = h->r32();
h->seek(diskofs);
h = TheHandle::createFromArray(h->readBytes(disklen));
}
doDirectory(2, h, arguments.list ? 0 : -1);
}

View File

@ -1,4 +1,5 @@
OBJS = main.o omf.o handle.o map.o disasm.o api.o scanner.o
OBJS2mg = 2mg.o handle.o
CXX = clang++
CXXFLAGS = -g -Wall -std=c++11
@ -7,7 +8,10 @@ ifeq ($(UNAME), Darwin)
LDFLAGS = -largp
endif
all: ../regs
all: ../regs ../2mg
../2mg: $(OBJS2mg)
$(CXX) $(CXXFLAGS) $(LIBS) -o $@ $(LDFLAGS) $^
../regs: $(OBJS)
$(CXX) $(CXXFLAGS) $(LIBS) -o $@ $(LDFLAGS) $^
@ -16,4 +20,4 @@ all: ../regs
$(CXX) -c $(CXXFLAGS) -o $@ $<
clean:
rm -f ../regs $(OBJS)
rm -f ../regs ../2mg $(OBJS) $(OBJS2mg)

View File

@ -1,6 +1,7 @@
/** @copyright 2020 Sean Kasun */
#include <iostream>
#include <algorithm>
#include "api.h"
Fingerprints::Fingerprints() {
@ -199,3 +200,151 @@ void API::setFunction(Handle h, std::shared_ptr<symbol::Symbol> s) {
f->signature.push_back(static_cast<int32_t>(h->r32()));
}
}
void API::search(std::string keyword, uint32_t org) {
for (auto &s : symbols) {
auto it = std::search(s.first.begin(), s.first.end(),
keyword.begin(), keyword.end(),
[](char a, char b) {
return std::toupper(a) == std::toupper(b);
});
if (it != s.first.end()) {
dumpSymbol(s.second, org);
}
}
}
void API::dumpRef(std::shared_ptr<symbol::Ref> ref) {
for (int i = 0; i < ref->pointer; i++) {
printf("^");
}
printf("%s", ref->symbol->name.c_str());
if (ref->array > 0) {
printf("[%d]", ref->array);
} else if (ref->array == 0) {
printf("[]");
}
}
void API::dumpSymbol(std::shared_ptr<symbol::Symbol> sym, uint32_t org) {
printf("%s: ", sym->name.c_str());
switch (sym->kind) {
case symbol::Kind::isIntrinsic:
switch (std::static_pointer_cast<symbol::Intrinsic>(sym)->type) {
case symbol::Intrinsic::U8:
printf("uint8");
break;
case symbol::Intrinsic::U16:
printf("uint16");
break;
case symbol::Intrinsic::U32:
printf("uint32");
break;
case symbol::Intrinsic::S8:
printf("int8");
break;
case symbol::Intrinsic::S16:
printf("int16");
break;
case symbol::Intrinsic::S32:
printf("int32");
break;
}
printf("\n");
break;
case symbol::Kind::isEnum:
{
auto e = std::static_pointer_cast<symbol::Enum>(sym);
printf("enum : %s {\n", e->type->name.c_str());
for (auto entry : e->entries) {
printf(" %s = $%x,\n", entry.first.c_str(), entry.second);
}
printf("}\n");
}
break;
case symbol::Kind::isAlias:
case symbol::Kind::isRef:
dumpRef(std::static_pointer_cast<symbol::Ref>(sym));
printf("\n");
break;
case symbol::Kind::isStruct:
{
auto s = std::static_pointer_cast<symbol::Struct>(sym);
printf("struct { // $%x bytes\n", sym->size);
for (auto &f : s->fields) {
printf(" %s: ", f.key.c_str());
switch (f.value->kind) {
case symbol::Kind::isRef:
dumpRef(std::static_pointer_cast<symbol::Ref>(f.value));
break;
default:
printf("%s", f.value->name.c_str());
break;
}
printf(" // $");
if (org > 0xffff) {
printf("%02x/%04x\n", org >> 16, org & 0xffff);
} else {
printf("%04x\n", org);
}
org += f.value->size;
}
printf("}\n");
}
break;
case symbol::Kind::isUnion:
{
auto s = std::static_pointer_cast<symbol::Struct>(sym);
printf("union { // $%x bytes\n", sym->size);
for (auto &f : s->fields) {
printf(" %s: ", f.key.c_str());
switch (f.value->kind) {
case symbol::Kind::isRef:
dumpRef(std::static_pointer_cast<symbol::Ref>(f.value));
break;
default:
printf("%s", f.value->name.c_str());
break;
}
printf(" // $");
if (org > 0xffff) {
printf("%02x/%04x\n", org >> 16, org & 0xffff);
} else {
printf("%04x\n", org);
}
}
printf("}\n");
}
break;
case symbol::Kind::isFunction:
{
auto f = std::static_pointer_cast<symbol::Function>(sym);
printf("(\n");
for (auto &a : f->arguments) {
printf(" %s: ", a.key.c_str());
dumpRef(a.ref);
printf(",\n");
}
if (f->returnType->symbol != nullptr) {
printf("): ");
dumpRef(f->returnType);
} else {
printf(")");
}
if (f->signature[0] >= 0) { // tool
printf(" = TOOL $%02x:%02x\n", f->signature[0],
f->signature[1]);
} else if (f->signature[0] == -1) { // p16/gsos
printf(" = GSOS $%04x\n", f->signature[2]);
} else if (f->signature[0] == -2) { // p8
printf(" = P8 $%02x\n", f->signature[2]);
} else if (f->signature[0] == -3) { // smartport
printf(" = Smartport $%02x\n", f->signature[2]);
} else if (f->signature[0] == -4) { // symbol
printf(" = $%02x/%04x\n", f->signature[1] >> 16,
f->signature[1] & 0xffff);
}
}
break;
}
}

View File

@ -73,6 +73,9 @@ class API {
public:
API(unsigned char *dat, unsigned int len);
std::map<std::string, std::shared_ptr<symbol::Symbol>> symbols;
void search(std::string keyword, uint32_t org);
void dumpSymbol(std::shared_ptr<symbol::Symbol> symbol, uint32_t org);
void dumpRef(std::shared_ptr<symbol::Ref> ref);
private:
std::shared_ptr<symbol::Symbol> lookup(uint32_t id);

View File

@ -64,10 +64,10 @@ std::shared_ptr<Inst> Disassembler::decodeInst(Handle f, Entry *entry) {
inst->type = opcodes[opcode].type;
auto mode = opcodes[opcode].addressing;
inst->length = sizes[mode];
if (mode == IMMM && (entry->flags & (IsEmu | IsM8))) {
if (mode == IMMM && (entry->flags & IsM8)) {
inst->length--;
}
if (mode == IMMX && (entry->flags & (IsEmu | IsX8))) {
if (mode == IMMX && (entry->flags & IsX8)) {
inst->length--;
}
entry->org += inst->length;
@ -86,15 +86,9 @@ std::shared_ptr<Inst> Disassembler::decodeInst(Handle f, Entry *entry) {
} else if (opcode == 0xc2) {
entry->flags &= ~inst->oper;
}
if ((entry->flags ^ oldFlags) & IsX8) {
entry->flags |= IsX8Changed;
}
if ((entry->flags ^ oldFlags) & IsM8) {
entry->flags |= IsM8Changed;
}
break;
case IMMM:
if (entry->flags & (IsEmu | IsM8)) {
if (entry->flags & IsM8) {
inst->oper = f->r8();
inst->operType = Opr::Imm8;
} else {
@ -103,7 +97,7 @@ std::shared_ptr<Inst> Disassembler::decodeInst(Handle f, Entry *entry) {
}
break;
case IMMX:
if (entry->flags & (IsEmu | IsX8)) {
if (entry->flags & IsX8) {
inst->oper = f->r8();
inst->operType = Opr::Imm8;
} else {
@ -194,7 +188,7 @@ std::shared_ptr<Inst> Disassembler::decodeInst(Handle f, Entry *entry) {
break;
case INX:
inst->oper = f->r8();
inst->operType = Opr::IndX;
inst->operType = Opr::IndXD;
break;
case INY:
inst->oper = f->r8();
@ -235,19 +229,25 @@ std::shared_ptr<Inst> Disassembler::decodeInst(Handle f, Entry *entry) {
}
if (opcode == 0x18) {
if (f->r8() == 0xfb) { // clc xce
entry->flags &= 0xffffff ^ IsEmu;
entry->flags &= ~IsEmu;
}
f->skip(-1);
}
if (opcode == 0x38) {
if (f->r8() == 0xfb) { // sec xce
entry->flags |= IsEmu;
entry->flags |= IsEmu | IsM8 | IsX8;
}
f->skip(-1);
}
if ((entry->flags ^ oldFlags) & IsEmu) {
entry->flags |= IsEmuChanged;
}
if ((entry->flags ^ oldFlags) & IsX8) {
entry->flags |= IsX8Changed;
}
if ((entry->flags ^ oldFlags) & IsM8) {
entry->flags |= IsM8Changed;
}
inst->flags = entry->flags;
return inst;
}
@ -318,14 +318,17 @@ std::string Disassembler::printInst(std::shared_ptr<Inst> inst) {
args = "(B:" + hex(inst->oper, 4) + ", x)";
comment += lookup(inst->oper);
break;
case Opr::IndXD:
args = "(D:" + hex(inst->oper, 2) + ")";
break;
case Opr::IndY:
args = "(" + hex(inst->oper, 6) + "), y";
args = "(D:" + hex(inst->oper, 2) + "), y";
break;
case Opr::IndL:
args = "[" + hex(inst->oper, 6) + "]";
args = "[D:" + hex(inst->oper, 2) + "]";
break;
case Opr::IndLY:
args = "[" + hex(inst->oper, 6) + "], y";
args = "[D:" + hex(inst->oper, 2) + "], y";
break;
case Opr::IndS:
args = "(" + hex(inst->oper, 2) + ", s), y";

View File

@ -28,7 +28,7 @@ enum InsType : uint16_t {
enum class Opr {
None = 0, Imm8, Imm16, Abs, AbsB, AbsD, AbsX, AbsXB, AbsXD,
AbsY, AbsYB, AbsYD, AbsS, Ind, IndB, IndD, IndX, IndXB,
AbsY, AbsYB, AbsYD, AbsS, Ind, IndB, IndD, IndX, IndXB, IndXD,
IndY, IndL, IndLY, IndS, Bank,
};

View File

@ -105,3 +105,7 @@ void TheHandle::seek(int64_t pos) {
void TheHandle::skip(int64_t ofs) {
pos += ofs;
}
void TheHandle::dump(int64_t length, std::ostream &f) {
f.write(reinterpret_cast<char*>(pos), length);
}

View File

@ -24,6 +24,7 @@ class TheHandle {
void skip(int64_t length);
std::string read(int32_t length);
std::vector<uint8_t> readBytes(int32_t length);
void dump(int64_t length, std::ostream &f);
int64_t length;

View File

@ -16,6 +16,7 @@ static struct argp_option options[] = {
{"m", 'm', 0, OPTION_ARG_OPTIONAL, "Start with 8-bit accumulator"},
{"x", 'x', 0, OPTION_ARG_OPTIONAL, "Start with 8-bit indices"},
{"e", 'e', 0, OPTION_ARG_OPTIONAL, "Start in emulation mode"},
{"l", 'l', "KEYWORD", 0, "Search API for KEYWORD"},
{ 0 },
};
@ -23,9 +24,11 @@ struct arguments {
const char *filename;
uint32_t org;
uint32_t flags;
const char *keyword;
};
static inline uint32_t parseNum(const char *s) {
uint32_t bank = 0;
uint32_t res = 0;
while (isspace(*s)) {
s++;
@ -39,7 +42,13 @@ static inline uint32_t parseNum(const char *s) {
ishex = true;
}
if (ishex) {
while (isxdigit(*s)) {
while (isxdigit(*s) || *s == '/') {
if (*s == '/') {
s++;
bank = res;
res = 0;
continue;
}
res <<= 4;
if (isdigit(*s)) {
res |= *s - '0';
@ -57,7 +66,7 @@ static inline uint32_t parseNum(const char *s) {
s++;
}
}
return res;
return (bank << 16) | res;
}
static error_t parse_opt(int key, char *arg, struct argp_state *state) {
@ -83,6 +92,9 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) {
arguments->flags |= IsEmu;
}
break;
case 'l':
arguments->keyword = arg;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 1) {
argp_usage(state);
@ -90,7 +102,7 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) {
arguments->filename = arg;
break;
case ARGP_KEY_END:
if (state->arg_num < 1) {
if (state->arg_num < 1 && !arguments->keyword) {
argp_usage(state);
}
break;
@ -103,12 +115,20 @@ static error_t parse_opt(int key, char *arg, struct argp_state *state) {
static struct argp argp = { options, parse_opt, args_doc, doc };
int main(int argc, char **argv) {
API api(iigs_dat, iigs_dat_len);
struct arguments arguments;
arguments.filename = "";
arguments.keyword = nullptr;
arguments.org = 0;
arguments.flags = 0;
argp_parse(&argp, argc, argv, 0, 0, &arguments);
if (arguments.keyword) {
api.search(arguments.keyword, arguments.org);
return 0;
}
// load map if it exists
Map map(arguments.filename, arguments.org);
OMF omf;
@ -130,7 +150,6 @@ int main(int argc, char **argv) {
// from the api
map.save();
API api(iigs_dat, iigs_dat_len);
auto prints = std::make_shared<Fingerprints>();
for (auto s : api.symbols) {

View File

@ -2,6 +2,7 @@
#include "map.h"
#include <string>
#include <fstream>
#include <algorithm>
struct Field {
uint32_t org;
@ -14,11 +15,13 @@ struct Field {
Map::Map(const char *filename, uint32_t org) : org(org) {
mapname = filename;
mapname += ".regs";
usedMap = false;
File file(mapname);
if (!file.is_open()) {
return;
}
usedMap = true;
while (!file.eof()) {
uint32_t ofs = file.hex();
if (file.check('!')) {
@ -35,6 +38,8 @@ Map::Map(const char *filename, uint32_t org) : org(org) {
switch (ch) {
case 'e':
entry.flags |= IsEmu;
entry.flags |= IsM8;
entry.flags |= IsX8;
break;
case 'm':
entry.flags |= IsM8;
@ -64,12 +69,13 @@ void Map::save() {
fields[org].isEntry = true;
if (entryPoint.flags & IsEmu) {
fields[org].flags += 'e';
}
if (entryPoint.flags & IsM8) {
fields[org].flags += 'm';
}
if (entryPoint.flags & IsX8) {
fields[org].flags += 'x';
} else { // only if not emu, otherwise its redundant
if (entryPoint.flags & IsM8) {
fields[org].flags += 'm';
}
if (entryPoint.flags & IsX8) {
fields[org].flags += 'x';
}
}
}
for (auto sym : symbols) {
@ -102,7 +108,7 @@ void Map::save() {
}
bool Map::needsEntry() {
return entryPoints.size() == 0;
return !usedMap;
}
std::vector<Entry> Map::getEntries() {

View File

@ -42,6 +42,7 @@ class Map {
private:
std::string mapname;
bool usedMap;
std::vector<Entry> entryPoints;
std::map<uint32_t, std::string> symbols;
std::string toAddress(uint32_t val);

View File

@ -22,6 +22,9 @@ static bool compareSegments(const Segment &a, const Segment &b) {
bool OMF::load(const char *filename, uint32_t org) {
handle = TheHandle::createFromFile(filename);
if (!handle->isOpen()) {
return false;
}
if (!isOMF()) {
Segment seg;
@ -31,6 +34,7 @@ bool OMF::load(const char *filename, uint32_t org) {
seg.mapped = org;
seg.data = handle;
seg.length = seg.bytecnt;
seg.segnum = 1;
segments.push_back(seg);
} else {
if (!loadSegments()) {

View File

@ -94,6 +94,9 @@ static bool compareBlocks(std::shared_ptr<Block> a, std::shared_ptr<Block> b) {
}
bool Scanner::basicBlocks() {
if (labels.empty()) { // no blocks
return true;
}
// always start at a label
auto address = labels.lower_bound(0)->first;
auto block = getBlock(address);

View File

@ -2,6 +2,7 @@
#pragma once
#include "map.h"
#include <functional>
struct Block {
explicit Block(uint32_t address) : address(address) {}

1290
tools.h

File diff suppressed because it is too large Load Diff