// 8086 disassembler
// reads binary from stdin, writes assembly to stdout

// known issues:
// 1. all immediates are printed as a signed short in hex
// 2. some invalid 8086 may be disassembled without errors
// 3. incorrectly disassembles data sections (no way around this really)

// command line flags:
// flag 1: decimal number of bytes to skip from stdin
// flag 2: if present, only disassembles one instruction

#include <stdio.h>

void printhex(unsigned char src) {
    char map[] = "0123456789abcdef";
    putchar(map[src >> 4]);
    putchar(map[src & 0x0f]);
}

void printhexword(unsigned short src) {
    printhex(src >> 8);
    printhex(src & 0xff);
}

void strcpy2(char *dest, char *src) {
    while (*src) {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = 0;
}

void print(char *src) {
    while (*src) {
        putchar(*src);
        src++;
    }
}

short currbyte = 0;

int getchar2() {
    currbyte++;
    return getchar();
}

char reg8[24] = "al\0cl\0dl\0bl\0ah\0ch\0dh\0bh";
char reg16[24] = "ax\0cx\0dx\0bx\0sp\0bp\0si\0di";

void printreg(char src, int bitnum, char *map) {
    src >>= bitnum;
    src &= 7;
    print(map + src * 3);
}

void printbase(char src) {
    src &= 7;
    if (src == 0) {
        print("bx+si");
    } else if (src == 1) {
        print("bx+di");
    } else if (src == 2) {
        print("bp+si");
    } else if (src == 3) {
        print("bp+di");
    } else if (src == 4) {
        print("si");
    } else if (src == 5) {
        print("di");
    } else if (src == 6) {
        print("bp");
    } else if (src == 7) {
        print("bx");
    }
}

char segment[4] = {0};
char shouldnotprintptr = 0;

void printrm(unsigned char modrm, short disp, char isword) {
    char *map = reg8;
    if (isword) {
        map = reg16;
    }

    if ((modrm >> 6) == 3) {
        printreg(modrm, 0, map);
    } else {
        if (!shouldnotprintptr) {
            if (isword) {
                print("word ptr ");
            } else {
                print("byte ptr ");
            }
        }

        print("[");
        print(segment);
        
        if ((modrm & 0b11000111) == 0b00000110) {
            if (disp < 0) {
                print("-");
                disp = -disp;
            }
            print("0x");
            printhexword(disp);
        } else {
            printbase(modrm);
            if (disp < 0) {
                print("-");
                disp = -disp;
            } else {
                print("+");
            }
            print("0x");
            printhexword(disp);
        }

        print("]");
    }
}

short getdisp(unsigned char modrm) {
    if ((modrm & 0b11000111) == 0b00000110) {
        return getchar2() | (getchar2() << 8);
    }
    if ((modrm >> 6) == 0) {
        return 0;
    }
    if ((modrm >> 6) == 1) {
        return (char)getchar2();
    }
    if ((modrm >> 6) == 2) {
        return getchar2() | (getchar2() << 8);
    }
    return 0;
}

short getimm8() {
    return (char)getchar2();
}

short getimm16() {
    return getchar2() | (getchar2() << 8);
}

void getmodrm(unsigned char *modrm, short *disp) {
    *modrm = getchar2();
    *disp = getdisp(*modrm);
}

short lastimm;

void printimm(short imm) {
    lastimm = imm;
    if (imm < 0) {
        print("-");
        imm = -imm;
    }
    print("0x");
    printhexword(imm);
}

#define MOD(name) print(name); getmodrm(&modrm, &disp);
#define COM print(", ");
#define RM8 printrm(modrm, disp, 0);
#define R8 printreg(modrm, 3, reg8);
#define RM16 printrm(modrm, disp, 1);
#define R16 printreg(modrm, 3, reg16);
#define IMM8 printimm(getimm8());
#define IMM16 printimm(getimm16());

void printsegreg(unsigned char modrm) {
    if (((modrm >> 3) & 7) == 0) {
        print("es");
    } else if (((modrm >> 3) & 7) == 1) {
        print("cs");
    } else if (((modrm >> 3) & 7) == 2) {
        print("ss");
    } else if (((modrm >> 3) & 7) == 3) {
        print("ds");
    }
}

unsigned char modrm;
short disp;

int printblock(int op, char *name, int blockstart, char regforimm) {
    if (op >= 0x80 && op <= 0x83) {
        if (regforimm == 0) {
            getmodrm(&modrm, &disp);
        }
        if (((modrm >> 3) & 7) == regforimm) {
            print(name);
            if (op == 0x80 || op == 0x82) {
                RM8 COM IMM8
            } else if (op == 0x81) {
                RM16 COM IMM16
            } else if (op == 0x83) {
                RM16 COM IMM8
            }

            return 1;
        }
        return 0;
    }

    if (op < blockstart || op > blockstart + 5) {
        return 0;
    }

    if (op == 0x00 + blockstart) {
        MOD(name) RM8 COM R8
    } else if (op == 0x01 + blockstart) {
        MOD(name) RM16 COM R16
    } else if (op == 0x02 + blockstart) {
        MOD(name) R8 COM RM8
    } else if (op == 0x03 + blockstart) {
        MOD(name) R16 COM RM16
    } else if (op == 0x04 + blockstart) {
        print(name); print("al"); COM IMM8
    } else if (op == 0x05 + blockstart) {
        print(name); print("ax"); COM IMM16
    }
    
    return 1;
}

void printjmp8(char *name) {
    print(name); IMM8
    // print instr addr of the destination
    print(" ; ");
    printimm(currbyte + lastimm);
}

void printjmp16(char *name) {
    print(name); IMM16
    // print instr addr of the destination
    print(" ; ");
    printimm(currbyte + lastimm);
}

int main(int argc, char *argv[]) {
    int breakafterfirst = 0;

    if (argc > 1) {
        char *str = argv[1];
        int result = 0;

        while (*str >= '0' && *str <= '9') {
            result = result * 10 + (*str - '0');
            str++;
        }

        for (int i = 0; i < result; i++) {
            getchar();
        }
    }

    if (argc > 2) {
        breakafterfirst = 1;
    }

    short curr = 0;

    while (1) {
        int op = getchar2();
        if (op == EOF) {
            return 0;
        } else if (op == 0x2e) {
            strcpy2(segment, "cs:");
        } else if (op == 0x36) {
            strcpy2(segment, "ss:");
        } else if (op == 0x3e) {
            strcpy2(segment, "ds:");
        } else if (op == 0x26) {
            strcpy2(segment, "es:");
        } else if (op == 0xf3) {
            print("rep ");
        } else if (op == 0xf2) {
            print("repne ");
        } else if (op == 0xf0) {
            print("lock ");
        } else {
            // add must ALWAYS come first, as printblock does weird thing that reuses modr/m from add
            if (printblock(op, "add ", 0x00, 0x00)) {

            } else if (op == 0x06) {
                print("push es");
            } else if (op == 0x07) {
                print("pop es");
            } else if (printblock(op, "or ", 0x08, 0x01)) {

            } else if (op == 0x0e) {
                print("push cs");
            } else if (printblock(op, "adc ", 0x10, 0x02)) {
                
            } else if (op == 0x16) {
                print("push ss");
            } else if (op == 0x17) {
                print("pop ss");
            } else if (printblock(op, "sbb ", 0x18, 0x03)) {

            } else if (op == 0x1e) {
                print("push ds");
            } else if (op == 0x1f) {
                print("pop ds");
            } else if (printblock(op, "and ", 0x20, 0x04)) {

            } else if (op == 0x27) {
                print("daa");
            } else if (printblock(op, "sub ", 0x28, 0x05)) {

            } else if (op == 0x2f) {
                print("das");
            } else if (printblock(op, "xor ", 0x30, 0x06)) {

            } else if (op == 0x37) {
                print("aaa");
            } else if (printblock(op, "cmp ", 0x38, 0x07)) {

            } else if (op == 0x3f) {
                print("aas");
            } else if (op >= 0x40 && op <= 0x47) {
                print("inc ");
                printreg(op, 0, reg16);
            } else if (op >= 0x48 && op <= 0x4f) {
                print("dec ");
                printreg(op, 0, reg16);
            } else if (op >= 0x50 && op <= 0x57) {
                print("push ");
                printreg(op, 0, reg16);
            } else if (op >= 0x58 && op <= 0x5f) {
                print("pop ");
                printreg(op, 0, reg16);
            } else if (op == 0x70) {
                printjmp8("jo ");
            } else if (op == 0x71) {
                printjmp8("jno ");
            } else if (op == 0x72) {
                printjmp8("jb ");
            } else if (op == 0x73) {
                printjmp8("jae ");
            } else if (op == 0x74) {
                printjmp8("je ");
            } else if (op == 0x75) {
                printjmp8("jne ");
            } else if (op == 0x76) {
                printjmp8("jbe ");
            } else if (op == 0x77) {
                printjmp8("ja ");
            } else if (op == 0x78) {
                printjmp8("js ");
            } else if (op == 0x79) {
                printjmp8("jns ");
            } else if (op == 0x7a) {
                printjmp8("jp ");
            } else if (op == 0x7b) {
                printjmp8("jnp ");
            } else if (op == 0x7c) {
                printjmp8("jl ");
            } else if (op == 0x7d) {
                printjmp8("jge ");
            } else if (op == 0x7e) {
                printjmp8("jle ");
            } else if (op == 0x7f) {
                printjmp8("jg ");
            } else if (op == 0x84) {
                MOD("test ") RM8 COM R8
            } else if (op == 0x85) {
                MOD("test ") RM16 COM R16
            } else if (op == 0x86) {
                MOD("xchg ") RM8 COM R8
            } else if (op == 0x87) {
                MOD("xchg ") RM16 COM R16
            } else if (op == 0x88) {
                MOD("mov ") RM8 COM R8
            } else if (op == 0x89) {
                MOD("mov ") RM16 COM R16
            } else if (op == 0x8a) {
                MOD("mov ") R8 COM RM8
            } else if (op == 0x8b) {
                MOD("mov ") R16 COM RM16
            } else if (op == 0x8c) {
                MOD("mov ") RM16 COM printsegreg(modrm);
            } else if (op == 0x8d) {
                MOD("lea ") R16 COM RM16;
            } else if (op == 0x8e) {
                MOD("mov ") printsegreg(modrm); COM RM16
            } else if (op == 0x8f) {
                MOD("pop ") RM16
            } else if (op == 0x90) {
                print("nop");
            } else if (op >= 0x91 && op <= 0x97) {
                print("xchg ax"); COM printreg(op - 0x90, 0, reg16);
            } else if (op == 0x98) {
                print("cbw");
            } else if (op == 0x99) {
                print("cwd");
            } else if (op == 0x9a) {
                short offset = getimm16();
                short seg = getimm16();

                print("call ");
                printimm(seg);
                print(":");
                printimm(offset);
            } else if (op == 0x9b) {
                print("wait");
            } else if (op == 0x9c) {
                print("pushf");
            } else if (op == 0x9d) {
                print("popf");
            } else if (op == 0x9e) {
                print("sahf");
            } else if (op == 0x9f) {
                print("lahf");
            } else if (op == 0xa0) {
                print("mov al"); COM print("byte ptr ["); print(segment); IMM16 print("]");
            } else if (op == 0xa1) {
                print("mov ax"); COM print("word ptr ["); print(segment); IMM16 print("]");
            } else if (op == 0xa2) {
                print("mov "); print("byte ptr ["); print(segment); IMM16 print("]"); COM print("al");
            } else if (op == 0xa3) {
                print("mov "); print("word ptr ["); print(segment); IMM16 print("]"); COM print("ax");
            } else if (op == 0xa4) {
                print("movsb");
            } else if (op == 0xa5) {
                print("movsw");
            } else if (op == 0xa6) {
                print("cmpsb");
            } else if (op == 0xa7) {
                print("cmpsw");
            } else if (op == 0xa8) {
                print("test al"); COM IMM8
            } else if (op == 0xa9) {
                print("test ax"); COM IMM16
            } else if (op == 0xaa) {
                print("stosb");
            } else if (op == 0xab) {
                print("stosw");
            } else if (op == 0xac) {
                print("lodsb");
            } else if (op == 0xad) {
                print("lodsw");
            } else if (op == 0xae) {
                print("scasb");
            } else if (op == 0xaf) {
                print("scasw");
            } else if (op >= 0xb0 && op <= 0xb7) {
                print("mov "); printreg(op - 0xb0, 0, reg8); COM IMM8
            } else if (op >= 0xb8 && op <= 0xbf) {
                print("mov "); printreg(op - 0xb0, 0, reg16); COM IMM16
            } else if (op == 0xc2) {
                print("ret "); IMM16
            } else if (op == 0xc3) {
                print("ret");
            } else if (op == 0xc4) {
                shouldnotprintptr = 1;
                MOD("les ") R16 COM RM16
                shouldnotprintptr = 0;
            } else if (op == 0xc5) {
                shouldnotprintptr = 1;
                MOD("lds ") R16 COM RM16
                shouldnotprintptr = 0;
            } else if (op == 0xc6) {
                MOD("mov ") RM8 COM IMM8
            } else if (op == 0xc7) {
                MOD("mov ") RM16 COM IMM16
            } else if (op == 0xca) {
                print("retf "); IMM16
            } else if (op == 0xcb) {
                print("retf");
            } else if (op == 0xcc) {
                print("int 0x03");
            } else if (op == 0xcd) {
                print("int "); IMM8
            } else if (op == 0xce) {
                print("into");
            } else if (op == 0xcf) {
                print("iret");
            } else if (op == 0xd0 || op == 0xd1 || op == 0xd2 || op == 0xd3) {
                getmodrm(&modrm, &disp);
                if (((modrm >> 3) & 7) == 0) {
                    print("rol ");
                } else if (((modrm >> 3) & 7) == 1) {
                    print("ror ");
                } else if (((modrm >> 3) & 7) == 2) {
                    print("rcl ");
                } else if (((modrm >> 3) & 7) == 3) {
                    print("rcr ");
                } else if (((modrm >> 3) & 7) == 4) {
                    print("shl ");
                } else if (((modrm >> 3) & 7) == 5) {
                    print("shr ");
                } else if (((modrm >> 3) & 7) == 6) {
                    print("sal ");
                } else if (((modrm >> 3) & 7) == 7) {
                    print("sar ");
                }
                if (op == 0xd0) {
                    RM8 COM print("0x01");
                } else if (op == 0xd1) {
                    RM16 COM print("0x01");
                } else if (op == 0xd2) {
                    RM8 COM print("cl");
                } else if (op == 0xd3) {
                    RM16 COM print("cl");
                }
                
            } else if (op == 0xd4) {
                print("aam "); IMM8
            } else if (op == 0xd5) {
                print("aad "); IMM8
            } else if (op == 0xd6) {
                print("salc");
            } else if (op == 0xd7) {
                print("xlat");
            } else if (op >= 0xd8 && op <= 0xdf) {
                MOD("esc")
            } else if (op == 0xe0) {
                printjmp8("loopnz ");
            } else if (op == 0xe1) {
                printjmp8("loopz ");
            } else if (op == 0xe2) {
                printjmp8("loop ");
            } else if (op == 0xe3) {
                printjmp8("jcxz ");
            } else if (op == 0xe4) {
                print("in al"); COM IMM8
            } else if (op == 0xe5) {
                print("in ax"); COM IMM8
            } else if (op == 0xe6) {
                print("out "); IMM8 COM print("al");
            } else if (op == 0xe7) {
                print("out "); IMM8 COM print("ax");
            } else if (op == 0xe8) {
                printjmp16("call ");
            } else if (op == 0xe9) {
                printjmp16("jmp ");
            } else if (op == 0xea) {
                short offset = getimm16();
                short seg = getimm16();

                print("jmp ");
                printimm(seg);
                print(":");
                printimm(offset);
            } else if (op == 0xeb) {
                printjmp8("jmp ");
            } else if (op == 0xec) {
                print("in al, dx");
            } else if (op == 0xed) {
                print("in ax, dx");
            } else if (op == 0xee) {
                print("out dx, al");
            } else if (op == 0xef) {
                print("out dx, ax");
            } else if (op == 0xf4) {
                print("hlt");
            } else if (op == 0xf5) {
                print("cmc");
            } else if (op == 0xf6 || op == 0xf7) {
                getmodrm(&modrm, &disp);
                if (((modrm >> 3) & 7) == 0) {
                    print("test ");
                } else if (((modrm >> 3) & 7) == 2) {
                    print("not ");
                } else if (((modrm >> 3) & 7) == 3) {
                    print("neg ");
                } else if (((modrm >> 3) & 7) == 4) {
                    print("mul ");
                } else if (((modrm >> 3) & 7) == 5) {
                    print("imul ");
                } else if (((modrm >> 3) & 7) == 6) {
                    print("div ");
                } else if (((modrm >> 3) & 7) == 7) {
                    print("idiv ");
                }

                if (op == 0xf6) {
                    RM8
                    if (((modrm >> 3) & 7) == 0) {
                        COM IMM8
                    }
                } else {
                    RM16
                    if (((modrm >> 3) & 7) == 0) {
                        COM IMM16
                    }
                }
            } else if (op == 0xf8) {
                print("clc");
            } else if (op == 0xf9) {
                print("stc");
            } else if (op == 0xfa) {
                print("cli");
            } else if (op == 0xfb) {
                print("sti");
            } else if (op == 0xfc) {
                print("cld");
            } else if (op == 0xfd) {
                print("std");
            } else if (op == 0xfe) {
                getmodrm(&modrm, &disp);
                if (((modrm >> 3) & 7) == 0) {
                    print("inc ");
                } else if (((modrm >> 3) & 7) == 1) {
                    print("dec ");
                }
                RM8
            } else if (op == 0xff) {
                getmodrm(&modrm, &disp);
                if (((modrm >> 3) & 7) == 0) {
                    print("inc "); RM16
                } else if (((modrm >> 3) & 7) == 1) {
                    print("dec "); RM16
                } else if (((modrm >> 3) & 7) == 2) {
                    print("call "); RM16
                } else if (((modrm >> 3) & 7) == 3) {
                    shouldnotprintptr = 1;
                    print("call far ptr "); RM16
                    shouldnotprintptr = 0;
                } else if (((modrm >> 3) & 7) == 4) {
                    print("jmp "); RM16
                } else if (((modrm >> 3) & 7) == 5) {
                    shouldnotprintptr = 1;
                    print("jmp far ptr "); RM16
                    shouldnotprintptr = 0;
                } else if (((modrm >> 3) & 7) == 6) {
                    print("push "); RM16
                }
            } else {
                print("; !");
            }

            // print instr addr
            print(" ; ");
            printhexword(curr);
            print("\n");
            *segment = 0;
            curr = currbyte;
        }

        if (breakafterfirst) {
            return 0;
        }
    }

    return 0;
}
