/* Dump and interpret an object file. */

/*
Copyright (c) 2001, Richard Krehbiel
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

o Redistributions of source code must retain the above copyright
  notice, this list of conditions and the following disclaimer.

o Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer in the
  documentation and/or other materials provided with the distribution.

o Neither the name of the copyright holder nor the names of its
  contributors may be used to endorse or promote products derived from
  this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.

*/

#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

#include "rad50.h"

#include "util.h"

#define WORD(cp) ((*(cp) & 0xff) + ((*((cp)+1) & 0xff) << 8))

#define NPSECTS 256

#ifndef DEFAULT_OBJECTFORMAT_RT11
#define DEFAULT_OBJECTFORMAT_RT11       0
#endif

int             psectid = 0;
char           *psects[NPSECTS];
FILE           *bin = NULL;
int             badbin = 0;
int             xferad = 1;

/* memcheck - crash out if a pointer (returned from malloc) is NULL. */

void           *memcheck(
    void *ptr)
{
    if (ptr == NULL) {
        fprintf(stderr, "Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    return ptr;
}

char           *readrec(
    FILE *fp,
    int *len,
    int rt11)
{
    int             c,
                    i;
    int             chksum;
    char           *buf;

    chksum = 0;

    if (rt11) {
        while (c = fgetc(fp), c != EOF && c == 0) ;

        if (c == EOF)
            return NULL;

        if (c != 1) {
            fprintf(stderr, "Improperly formatted OBJ file (1)\n");
            return NULL;               /* Not a properly formatted file. */
        }

        chksum -= c;

        c = fgetc(fp);
        if (c != 0) {
            fprintf(stderr, "Improperly formatted OBJ file (2)\n");
            return NULL;               /* Not properly formatted */
        }

        chksum -= c;                   /* even though for 0 the checksum isn't changed... */
    }

    c = fgetc(fp);
    if (c == EOF) {
        if (rt11) {
            fprintf(stderr, "Improperly formatted OBJ file (3)\n");
        }
        return NULL;
    }
    *len = c;

    chksum -= c;

    c = fgetc(fp);
    if (c == EOF) {
        fprintf(stderr, "Improperly formatted OBJ file (4)\n");
        return NULL;
    }

    *len += (c << 8);

    chksum -= c;

    if (rt11) {
        *len -= 4;                     /* Subtract header and length bytes from length */
    }

    if (*len < 0) {
        fprintf(stderr, "Improperly formatted OBJ file (5)\n");
        return NULL;
    }

    buf = malloc(*len);
    if (buf == NULL) {
        fprintf(stderr, "Out of memory allocating %d bytes\n", *len);
        return NULL;                   /* Bad alloc */
    }

    i = fread(buf, 1, *len, fp);
    if (i < *len) {
        free(buf);
        fprintf(stderr, "Improperly formatted OBJ file (6)\n");
        return NULL;
    }

    for (i = 0; i < *len; i++)
        chksum -= (buf[i] & 0xff);

    if (rt11) {
        c = fgetc(fp);
        c &= 0xff;
        chksum &= 0xff;

        if (c != chksum) {
            free(buf);
            fprintf(stderr, "Bad record checksum, " "calculated=$%04x, recorded=$%04x\n", chksum, c);
            return NULL;
        }
    } else {
        if (*len & 1) {
            /* skip 1 byte of padding */
            c = fgetc(fp);
            if (c == EOF) {
                free(buf);
                fprintf(stderr, "EOF where padding byte should be\n");
                return NULL;
            }

        }
    }

    return buf;
}

void dump_bytes(
    char *buf,
    int len)
{
    int             i,
                    j;

    for (i = 0; i < len; i += 8) {
        printf("\t%3.3o: ", i);
        for (j = i; j < len && j < i + 8; j++)
            printf("%3.3o ", buf[j] & 0xff);

        printf("%*s", (i + 8 - j) * 4, "");

        for (j = i; j < len && j < i + 8; j++) {
            int             c = buf[j] & 0xff;

            if (!isprint(c))
                c = '.';
            putchar(c);
        }

        putchar('\n');
    }
}

void dump_words(
    unsigned addr,
    char *buf,
    int len)
{
    int             i,
                    j;

    for (i = 0; i < len; i += 8) {
        printf("\t%6.6o: ", addr);

        for (j = i; j < len && j < i + 8; j += 2)
            if (len - j >= 2) {
                unsigned        word = WORD(buf + j);

                printf("%6.6o ", word);
            } else
                printf("%3.3o    ", buf[j] & 0xff);

        printf("%*s", (i + 8 - j) * 7 / 2, "");

        for (j = i; j < len && j < i + 8; j++) {
            int             c = buf[j] & 0xff;

            if (!isprint(c))
                c = '.';
            putchar(c);
        }

        putchar('\n');
        addr += 8;
    }
}

void dump_bin(
    unsigned addr,
    char *buf,
    int len)
{
    int             chksum;     /* Checksum is negative sum of all
                                   bytes including header and length */
    int             FBR_LEAD1 = 1,
            FBR_LEAD2 = 0;
    int             i;
    unsigned        hdrlen = len + 6;

    for (i = 0; i < 8; i++)
        fputc(0, bin);
    chksum = 0;
    if (fputc(FBR_LEAD1, bin) == EOF)
        return;                        /* All recs begin with 1,0 */
    chksum -= FBR_LEAD1;
    if (fputc(FBR_LEAD2, bin) == EOF)
        return;
    chksum -= FBR_LEAD2;

    i = hdrlen & 0xff;                 /* length, lsb */
    chksum -= i;
    if (fputc(i, bin) == EOF)
        return;

    i = (hdrlen >> 8) & 0xff;          /* length, msb */
    chksum -= i;
    if (fputc(i, bin) == EOF)
        return;

    i = addr & 0xff;                   /* origin, msb */
    chksum -= i;
    if (fputc(i, bin) == EOF)
        return;

    i = (addr >> 8) & 0xff;            /* origin, lsb */
    chksum -= i;
    if (fputc(i, bin) == EOF)
        return;

    if ((len == 0) || (buf == NULL))
        return;                        /* end of tape block */

    i = fwrite(buf, 1, len, bin);
    if (i < len)
        return;

    while (len > 0) {                  /* All the data bytes */
        chksum -= *buf++ & 0xff;
        len--;
    }

    chksum &= 0xff;

    fputc(chksum, bin);                /* Followed by the checksum byte */

    return;                            /* Worked okay. */
}

void trim(
    char *buf)
{
    char           *cp;

    for (cp = buf + strlen(buf); cp > buf; cp--)
        if (cp[-1] != ' ')
            break;
    *cp = 0;
}

char          **all_gsds = NULL;
int             nr_gsds = 0;
int             gsdsize = 0;

void add_gsdline(
    char *line)
{
    if (nr_gsds >= gsdsize || all_gsds == NULL) {
        gsdsize += 128;
        all_gsds = memcheck(realloc(all_gsds, gsdsize * sizeof(char *)));
    }

    all_gsds[nr_gsds++] = line;
}

void got_gsd(
    char *cp,
    int len)
{
    int             i;
    char           *gsdline;

    for (i = 2; i < len; i += 8) {
        char            name[8];
        unsigned        value;
        unsigned        flags;

        gsdline = memcheck(malloc(256));

        unrad50(WORD(cp + i), name);
        unrad50(WORD(cp + i + 2), name + 3);
        name[6] = 0;

        value = WORD(cp + i + 6);
        flags = cp[i + 4] & 0xff;

        switch (cp[i + 5] & 0xff) {
        case 0:
            sprintf(gsdline, "\tMODNAME %s=%o flags=%o\n", name, value, flags);
            break;
        case 1:
            sprintf(gsdline, "\tCSECT %s=%o flags=%o\n", name, value, flags);
            break;
        case 2:
            sprintf(gsdline, "\tISD %s=%o flags=%o\n", name, value, flags);
            break;
        case 3:
            sprintf(gsdline, "\tXFER %s=%o flags=%o\n", name, value, flags);
            xferad = value;
            break;
        case 4:
            sprintf(gsdline, "\tGLOBAL %s=%o %s%s%s %s flags=%o\n", name, value,
                    flags &   01 ? "WEAK " : "",
                    flags &   04 ? "LIB "  : "",
                    flags &  010 ? "DEF"   : "REF",
                    flags &  040 ? "REL"   : "ABS",
                    flags);
            break;
        case 5:
            sprintf(gsdline, "\tPSECT %s=%o %s%s %s %s %s %s %s flags=%o\n", name, value,
                    flags &   01 ? "SAV " : "",
                    flags &   02 ? "LIB " : "",
                    flags &   04 ? "OVR"  : "CON",
                    flags &  020 ? "RO"   : "RW",
                    flags &  040 ? "REL"  : "ABS",
                    flags & 0100 ? "GBL"  : "LCL",
                    flags & 0200 ? "D"    : "I",
                    flags);
            psects[psectid] = memcheck(strdup(name));
            trim(psects[psectid++]);
            psectid %= NPSECTS;
            break;
        case 6:
            sprintf(gsdline, "\tIDENT %s=%o flags=%o\n", name, value, flags);
            break;
        case 7:
            sprintf(gsdline, "\tVSECT %s=%o flags=%o\n", name, value, flags);
            break;
        case 010:
            sprintf(gsdline, "\tCompletion Routine Name %s=%o flags=%o\n", name, value, flags);
            break;
        default:
            sprintf(gsdline, "\t***Unknown GSD entry type %d flags=%o\n", cp[i + 5] & 0xff, flags);
            break;
        }

        gsdline = memcheck(realloc(gsdline, strlen(gsdline) + 1));
        add_gsdline(gsdline);
    }
}

int compare_gsdlines(
    const void *p1,
    const void *p2)
{
    const char     *const *l1 = p1,
                   *const *l2 = p2;

    return strcmp(*l1, *l2);
}

void got_endgsd(
    char *cp,
    int len)
{
    int             i;

    (void)cp;
    (void)len;

    if (nr_gsds == 0) {
        return;
    }

    qsort(all_gsds, nr_gsds, sizeof(char *), compare_gsdlines);

    printf("GSD:\n");

    for (i = 0; i < nr_gsds; i++) {
        fputs(all_gsds[i], stdout);
        free(all_gsds[i]);
    }

    printf("ENDGSD\n");

    free(all_gsds);
    all_gsds = NULL;
    nr_gsds = 0;
    gsdsize = 0;
}

unsigned        last_text_addr = 0;

void got_text(
    char *cp,
    int len)
{
    unsigned        addr = WORD(cp + 2);

    last_text_addr = addr;

    printf("TEXT ADDR=%o LEN=%o\n", last_text_addr, len - 4);

    dump_words(last_text_addr, cp + 4, len - 4);

    if (bin)
        dump_bin(last_text_addr, cp + 4, len - 4);
}

void rad50name(
    char *cp,
    char *name)
{
    unrad50(WORD(cp), name);
    unrad50(WORD(cp + 2), name + 3);
    name[6] = 0;
    trim(name);
}

void got_rld(
    char *cp,
    int len)
{
    int             i;

    printf("RLD\n");

    for (i = 2; i < len;) {
        unsigned        addr;
        unsigned        word;
        unsigned        disp = cp[i + 1] & 0xff;
        char            name[8];
        char           *byte;

        addr = last_text_addr + disp - 4;

        byte = "";
        if (cp[i] & 0200)
            byte = " byte";

        switch (cp[i] & 0x7f) {
        case 01:
            printf("\tInternal%s %o=%o\n", byte, addr, WORD(cp + i + 2));
            i += 4;
            break;
        case 02:
            rad50name(cp + i + 2, name);
            printf("\tGlobal%s %o=%s\n", byte, addr, name);
            i += 6;
            break;
        case 03:
            printf("\tInternal displaced%s %o=%o\n", byte, addr, WORD(cp + i + 2));
            i += 4;
            badbin = 1;
            break;
        case 04:
            rad50name(cp + i + 2, name);
            printf("\tGlobal displaced%s %o=%s\n", byte, addr, name);
            i += 6;
            badbin = 1;
            break;
        case 05:
            rad50name(cp + i + 2, name);
            word = WORD(cp + i + 6);
            printf("\tGlobal plus offset%s %o=%s+%o\n", byte, addr, name, word);
            i += 8;
            badbin = 1;
            break;
        case 06:
            rad50name(cp + i + 2, name);
            word = WORD(cp + i + 6);
            printf("\tGlobal plus offset displaced%s %o=%s+%o\n", byte, addr, name, word);
            i += 8;
            badbin = 1;
            break;
        case 07:
            rad50name(cp + i + 2, name);
            word = WORD(cp + i + 6);
            printf("\tLocation counter definition %s+%o\n", name, word);
            i += 8;

            last_text_addr = word;
            break;
        case 010:
            word = WORD(cp + i + 2);
            printf("\tLocation counter modification %o\n", word);
            i += 4;

            last_text_addr = word;
            break;
        case 011:
            printf("\t.LIMIT %o\n", addr);
            i += 2;
            break;

        case 012:
            rad50name(cp + i + 2, name);
            printf("\tPSECT%s %o=%s\n", byte, addr, name);
            i += 6;
            badbin = 1;
            break;
        case 014:
            rad50name(cp + i + 2, name);

            printf("\tPSECT displaced%s %o=%s\n", byte, addr, name);
            i += 6;
            badbin = 1;
            break;
        case 015:
            rad50name(cp + i + 2, name);
            word = WORD(cp + i + 6);
            printf("\tPSECT plus offset%s %o=%s+%o\n", byte, addr, name, word);
            i += 8;
            badbin = 1;
            break;
        case 016:
            rad50name(cp + i + 2, name);
            word = WORD(cp + i + 6);
            printf("\tPSECT plus offset displaced%s %o=%s+%o\n", byte, addr, name, word);
            i += 8;
            badbin = 1;
            break;

        case 017:
            badbin = 1;
            printf("\tComplex%s %o=", byte, addr);
            i += 2; {
                char           *xp = cp + i;
                int             size;

                for (;;) {
                    size = 1;
                    switch (*xp) {
                    case 000:
                        fputs("nop ", stdout);
                        break;
                    case 001:
                        fputs("+ ", stdout);
                        break;
                    case 002:
                        fputs("- ", stdout);
                        break;
                    case 003:
                        fputs("* ", stdout);
                        break;
                    case 004:
                        fputs("/ ", stdout);
                        break;
                    case 005:
                        fputs("& ", stdout);
                        break;
                    case 006:
                        fputs("! ", stdout);
                        break;
                    case 010:
                        fputs("neg ", stdout);
                        break;
                    case 011:
                        fputs("^C ", stdout);
                        break;
                    case 012:
                        fputs("store ", stdout);
                        break;
                    case 013:
                        fputs("store{disp} ", stdout);
                        break;

                    case 016:
                        rad50name(xp + 1, name);
                        printf("%s ", name);
                        size = 5;
                        break;

                    case 017:
                        assert((xp[1] & 0377) < psectid);
                        printf("%s:%o ", psects[xp[1] & 0377], WORD(xp + 2));
                        size = 4;
                        break;

                    case 020:
                        printf("%o ", WORD(xp + 1));
                        size = 3;
                        break;
                    default:
                        printf("**UNKNOWN COMPLEX CODE** %o\n", *xp & 0377);
                        return;
                    }
                    i += size;
                    if (*xp == 012 || *xp == 013)
                        break;
                    xp += size;
                }
                fputc('\n', stdout);
                break;
            }

        default:
            printf("\t***Unknown RLD code %o\n", cp[i] & 0xff);
            return;
        }
    }
}

void got_isd(
    char *cp,
    int len)
{
    (void)cp;
    printf("ISD len=%o\n", len);
}

void got_endmod(
    char *cp,
    int len)
{
    (void)cp;
    (void)len;
    printf("ENDMOD\n");
}

void got_libhdr(
    char *cp,
    int len)
{
    (void)cp;
    (void)len;
    printf("LIBHDR\n");
}

void got_libend(
    char *cp,
    int len)
{
    (void)cp;
    (void)len;
    printf("LIBEND\n");
}

int main(
    int argc,
    char *argv[])
{
    int             len;
    FILE           *fp;
    int             arg;
    int             rt11 = DEFAULT_OBJECTFORMAT_RT11;
    char            *infile = 0;
    char            *outfile = 0;

    for (arg = 1; arg < argc; arg++) {
        if (*argv[arg] == '-') {
            char           *cp;

            cp = argv[arg] + 1;
            if (!strcasecmp(cp, "rt11")) {
                rt11 = 1;
            } else if (!strcasecmp(cp, "rsx")) {
                rt11 = 0;
            } else {
                fprintf(stderr, "Unknown option %s\n", argv[arg]);
                exit(EXIT_FAILURE);
            }
        }
        else if (infile == 0) {
            infile = argv[arg];
        }
        else if (outfile == 0) {
            outfile = argv[arg];
        }
        else {
            fprintf(stderr, "Extra parameter %s\n", argv[arg]);
            exit(EXIT_FAILURE);
        }
    }

    if (infile == 0) {
        fprintf(stderr, "Usage: dumpobj [ -rt11 ] [ -rsx ] input.obj [ output.obj ]\n");
        exit(1);
    }

    fp = fopen(infile, "rb");
    if (fp == NULL) {
        fprintf(stderr, "Unable to open %s\n", infile);
        return EXIT_FAILURE;
    }
    if (outfile != 0) {
        bin = fopen(outfile, "wb");
        if (bin == NULL) {
            fprintf(stderr, "Unable to open %s\n", outfile);
            return EXIT_FAILURE;
        }
    }

    char           *cp;

    while ((cp = readrec(fp, &len, rt11)) != NULL) {
        switch (cp[0] & 0xff) {
        case 1:
            got_gsd(cp, len);
            break;
        case 2:
            got_endgsd(cp, len);
            break;
        case 3:
            got_text(cp, len);
            break;
        case 4:
            got_rld(cp, len);
            break;
        case 5:
            got_isd(cp, len);
            break;
        case 6:
            got_endmod(cp, len);
            break;
        case 7:
            got_libhdr(cp, len);
            break;
        case 8:
            got_libend(cp, len);
            break;
        default:
            printf("Unknown record type %o\n", cp[0] & 0xff);
            break;
        }

        free(cp);
    }

    if (bin) {
        dump_bin(xferad, NULL, 0);
        fclose(bin);
        if (badbin)
            fprintf(stderr, "Probable errors in binary file\n");
    }

    fclose(fp);
    return EXIT_SUCCESS;
}
