/* MakeSFX: join UnZipSFX and a .zip archive into a single self-extracting   */
/* Amiga program.  On most systems simple concatenation does the job but for */
/* the Amiga a special tool is needed.  By Paul Kienitz, no rights reserved. */
/* This program is written portably, so if anyone really wants to they can   */
/* produce Amiga self-extracting programs on a non-Amiga.  We are careful    */
/* not to mix Motorola-format longwords read from files with native long     */
/* integers.  Not necessarily limited to use with only the Zip format --     */
/* just combine any archive with any self-extractor program that is capable  */
/* of reading a HUNK_DEBUG section at the end as an archive.                 */

#include <stat.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dos/dos.h>
#include <clib/dos_protos.h>
#if (defined(DEBUG) && defined(__SASC))
#  include <sprof.h>
#endif

typedef unsigned long ulg;
typedef unsigned char uch;
typedef unsigned short bool;
#define false 0
#define true  1

/* the following are extracted from Commodore include file dos/doshunks.h: */
#define HUNK_NAME       1000L
#define HUNK_CODE       1001L
#define HUNK_DATA       1002L
#define HUNK_BSS        1003L
#define HUNK_RELOC32    1004L
#define HUNK_SYMBOL     1008L
#define HUNK_DEBUG      1009L
#define HUNK_END        1010L
#define HUNK_HEADER     1011L
#define HUNK_OVERLAY    1013L
#define HUNK_BREAK      1014L

/* Convert a big-endian (Motorola) sequence of four bytes to a longword: */
#define CHARS2LONG(b)   (((ulg)(b)[0] << 24) | ((ulg)(b)[1] << 16) | \
                         ((ulg)(b)[2] << 8) | ((ulg)(b)[3]))
/* b must be (uch *) in each of these.  Now the reverse: */
#define LONG2CHARS(b,l) ((b)[0] = (uch)((l) >> 24), (b)[1] = (uch)((l) >> 16),\
                         (b)[2] = (uch)((l) >> 8), (b)[3] = (uch)(l))

#define COPYBUFFER      16384

ulg totalwritten = 0;


bool CopyData(FILE *out, FILE *inn, ulg archivesize,
              char *outname, char *inname)
{
    static uch buf[COPYBUFFER];
    ulg written;
    size_t chunk;

    if (archivesize) {
        LONG2CHARS(buf, HUNK_DEBUG);
        written = (archivesize + 3) / 4;
        LONG2CHARS(buf + 4, written);
        if (fwrite(buf, 1, 8, out) < 8) {
            printf("Error writing in-between data to %s\n", outname);
            return false;
        }
        totalwritten += 8;
    }
    written = 0;
    do {
        chunk = fread(buf, 1, COPYBUFFER, inn);
        if (ferror(inn)) {
            printf("Error reading data from %s\n", inname);
            return false;
        }
        if (!archivesize && !written) {   /* true only for first block read */
            if (CHARS2LONG(buf) != HUNK_HEADER) {
                printf("%s is not an Amiga executable.\n", inname);
                return false;
            }
        }
        if (fwrite(buf, 1, chunk, out) < chunk) {
            printf("Error writing %s to %s\n", archivesize ? "archive data" :
                                               "self-extractor code", outname);
            return false;
        }
        written += chunk;
        totalwritten += chunk;
    } while (!feof(inn));
    if (archivesize) {
        if (written != archivesize) {
            printf("Wrong number of bytes copied from archive %s\n", outname);
            return false;
        }
        LONG2CHARS(buf, 0);
        chunk = 3 - (written + 3) % 4;
        LONG2CHARS(buf + chunk, HUNK_END);
        chunk += 4;
        if (fwrite(buf, 1, chunk, out) < chunk) {
            printf("Error writing end-marker data to %s\n", outname);
            return false;
        }
        totalwritten += chunk;
    }
    return true;
}


void main(int argc, char **argv)
{
    FILE *out, *arch, *tool;
    char *toolname = argv[3];
    struct stat ss;
    int ret;
    ulg archivesize;

    if (argc < 3 || argc > 4) {
        printf("Usage: %s <result-file> <zip-archive> [<self-extractor-"
               "program>]\nThe third arg defaults to \"UnZipSFX\" in the"
               " current dir or C:.\n", argv[0]);
        exit(20);
    }
    if (!(arch = fopen(argv[2], "rb"))) {
        printf("Could not find archive file %s\n", argv[2]);
        exit(10);
    }
    if (stat(argv[2], &ss) || !(archivesize = ss.st_size)) {
        fclose(arch);
        printf("Could not check size of archive %s, or file is empty.\n",
               argv[2]);
        exit(10);
    }
    if (argc < 4)
        toolname = "UnZipSFX";
    if (!(tool = fopen(toolname, "rb"))) {
        BPTR lk = Lock("C:", ACCESS_READ);
        BPTR ocd = lk ? CurrentDir(lk) : 0;
        if (!(tool = fopen(toolname, "rb"))) {
            fclose(arch);
            printf("Could not find self-extractor program %s\n", toolname);
            if (lk)
                UnLock(CurrentDir(ocd));
            exit(10);
        }
        if (lk)
            UnLock(CurrentDir(ocd));
    }
    if (!(out = fopen(argv[1], "wb"))) {
        fclose(arch);
        fclose(tool);
        printf("Could not create output file %s\n", argv[1]);
        exit(10);
    }
    ret = CopyData(out, tool, 0, argv[1], toolname)
          && CopyData(out, arch, archivesize, argv[1], argv[2]) ? 0 : 10;
    fclose(out);
    fclose(arch);
    fclose(tool);
    if (ret) {
        printf("Deleting %s\n", argv[1]);
        remove(argv[1]);
    } else
        printf("%s successfully written, size %lu bytes.\n",
                argv[1], totalwritten);
    exit(ret);
}


#if (defined(AZTEC_C) && defined(MCH_AMIGA))
void _wb_parse(void) { }        /* avoid unneeded infrastructure */
#endif
