/* MakeSFX file archiver client for XAD.
 * Copyright (C) 2000 Stuart Caie <kyzer@4u.net>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* MakeSFX is an AmigaDOS shell script that generates the assembler
 * source code of a program that would save blocks of data to files.
 * It then assembles this program to create a self-extracting archive.
 */

#include <libraries/xadmaster.h>
#include <proto/xadmaster.h>
#include <string.h>

#include "SDI_compiler.h"
#include "ConvertE.c"

#ifndef XADMASTERFILE
#define MSFX_Client		FirstClient
#define NEXTCLIENT		0
const UBYTE version[] = "$VER: MakeSFX 1.0 (09.12.2000)";
#endif
#define MSFX_VERSION		1
#define MSFX_REVISION		0

#define XADBASE  REG(a6, struct xadMasterBase *xadMasterBase)
#define SKIP(offset) if ((err = xadHookAccess(XADAC_INPUTSEEK, \
  (ULONG)(offset), NULL, ai))) goto exit_handler
#define SEEK(offset) SKIP((offset) - ai->xai_InPos)
#define READ(buffer,length) if ((err = xadHookAccess(XADAC_READ, \
  (ULONG)(length), (APTR)(buffer), ai))) goto exit_handler
#define ALLOCOBJ(t,v,kind,tags) \
  if (!((v) = (t) xadAllocObjectA((kind),(tags)))) ERROR(NOMEMORY)
#define ERROR(error) do { err = XADERR_##error; goto exit_handler; } while (0)


ASM(BOOL) MSFX10_RecogData(REG(d0,ULONG s),REG(a0,STRPTR d),XADBASE) {
  return (memcmp(d,    "\x00\x00\x03\xF3", 4) == 0
      &&  memcmp(d+32, "\x2C\x78\x00\x04\x43\xFA\x00\x10"
                       "\x4E\xAE\xFE\x68\x2C\x40\x4A\x80"
                       "\x66\x10\x70\x14\x4E\x75\x64\x6F"
                       "\x73\x2E\x6C\x69\x62\x72\x61\x72"
                       "\x79\x00", 34) == 0) ? 1 : 0;
}

ASM(BOOL) MSFX12_RecogData(REG(d0,ULONG s),REG(a0,STRPTR d),XADBASE) {
  return (memcmp(d,    "\x00\x00\x03\xF3", 4) == 0
      &&  memcmp(d+40, "\x70\x21\x4E\xAE\xFD\xD8\x4A\x80"
                       "\x66\x04\x70\x14\x4E\x75\x2C\x40"
                       "\x2A\x4F\x49\xFA", 20) == 0) ? 1 : 0;
}

ASM(LONG) MSFX10_GetInfo(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  ULONG filenum = 1, offset = 66;
  ULONG nameoff, dataoff, filelen, jsr, jmp;
  struct xadFileInfo *link = NULL,  *fi;
  LONG err = XADERR_OK;
  UBYTE buffer[26];

  struct TagItem filetags[]  = {
    { XAD_OBJNAMESIZE, 0 },
    { TAG_DONE, 0 }
  };

  struct TagItem datetags[] = {
    { XAD_DATECURRENTTIME, 1 },
    { XAD_GETDATEXADDATE,  0 },
    { TAG_DONE, 0 }
  };

  while (1) {
    /* read the next file header */
    SEEK(offset);
    READ(buffer, 26);

    if (EndGetM16(buffer) != 0x203c || EndGetM16(&buffer[6]) != 0x41fa ||
    EndGetM16(&buffer[10]) != 0x43fa) break; /* LOOP EXIT CONDITION */

    /* move.l #file_length,d0    ;0000: 20 3C XX XX XX XX
     * lea    file_name(pc),a0   ;0006: 41 FA XX XX
     * lea    file_data(pc),a1   ;000A: 43 FA XX XX
     *then 1 of...
     * jsr    write_file         ;000E: 4E B9 XX XX XX XX
     * bsr.b  write_file         ;000E: 61 XX
     * bsr.w  write_file         ;000E: 61 00 XX XX
     *then 1 of...
     * jmp    next_file_header   ;????: 4E F9 XX XX XX XX
     * bra.b  next_file_header   ;????: 60 XX
     * bra.w  next_file_header   ;????: 60 00 XX XX
     */

    filelen = EndGetM32(&buffer[2]);
    nameoff = EndGetM16(&buffer[8])  + 8 + offset;
    dataoff = EndGetM16(&buffer[12]) + 12 + offset;

    /* skip past the first bsr/jsr instruction */
    jsr = EndGetM16(&buffer[14]);
         if (jsr == 0x4EB9) jsr = 20;
    else if (jsr == 0x6100) jsr = 18;
    else if (jsr >= 0x6102 && jsr <= 0x61FE && !(jsr&1)) jsr = 16;
    else ERROR(DATAFORMAT);

    /* work out the next file's offset from the bra/jmp instruction */
    jmp = EndGetM16(&buffer[jsr]);
         if (jmp == 0x4EF9) offset  = EndGetM32(&buffer[jsr+2]) + 32;
    else if (jmp == 0x6000) offset += EndGetM16(&buffer[jsr+2]) + jsr+2;
    else if (jmp >= 0x6002 && jmp <= 0x60FE && !(jmp&1))
      offset += (jmp & 0xFE) + jsr+2;
    else ERROR(DATAFORMAT);


    filetags[0].ti_Data = dataoff - nameoff;
    ALLOCOBJ(struct xadFileInfo *, fi, XADOBJ_FILEINFO, filetags);

    fi->xfi_EntryNumber = filenum++;
    fi->xfi_Size        = filelen;
    fi->xfi_Flags       = XADFIF_SEEKDATAPOS;
    fi->xfi_DataPos     = dataoff;

    /* read in filename */
    SEEK(nameoff);
    READ(fi->xfi_FileName, dataoff-nameoff);
    fi->xfi_CrunchSize = fi->xfi_Size;

    /* fill in today's date */
    datetags[1].ti_Data = (ULONG) &fi->xfi_Date;
    xadConvertDatesA(datetags);

    if (link) link->xfi_Next = fi; else ai->xai_FileInfo = fi;
    link = fi;
  }

exit_handler:
  if (err) {
    if (!ai->xai_FileInfo) return err;
    ai->xai_Flags |= XADAIF_FILECORRUPT;
    ai->xai_LastError = err;
  }
  return XADERR_OK;
}

ASM(LONG) MSFX12_GetInfo(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  ULONG filenum = 1, offset = 66;
  ULONG nameoff, dataoff, filelen, jsr, jmp;
  struct xadFileInfo *link = NULL,  *fi;
  LONG err = XADERR_OK;
  UBYTE buffer[26];

  struct TagItem filetags[]  = {
    { XAD_OBJNAMESIZE, 0 },
    { TAG_DONE, 0 }
  };

  struct TagItem datetags[] = {
    { XAD_DATECURRENTTIME, 1 },
    { XAD_GETDATEXADDATE,  0 },
    { TAG_DONE, 0 }
  };

  /* find offset of main file-writing loop - get BSR instruction offset */
  SEEK(64);
  READ(buffer, 8);
  jsr = EndGetM16(buffer);
  if (jsr == 0x6100) {
    offset = 66 + EndGetM16(&buffer[2]); jsr = 4;
  }
  else if (jsr >= 0x6102 && jsr <= 0x61FE && !(jsr&1)) {
    offset = 66 + (jsr & 0xFF); jsr = 2;
  }
  else {
    ERROR(DATAFORMAT);
  }

  /* check instruction after BSR - if it's LEA xxxx(PC),A0
   * then there's an autoexecute script we can collect
   */
  jmp = EndGetM16(&buffer[jsr]);
  if (jmp == 0x41FA) {
    jmp = EndGetM16(&buffer[jsr+2])+jsr+66;
    filetags[0].ti_Data = 15;
    ALLOCOBJ(struct xadFileInfo *, fi, XADOBJ_FILEINFO, filetags);
    fi->xfi_EntryNumber = filenum++;
    fi->xfi_Size        = offset - jmp;
    fi->xfi_Flags       = XADFIF_SEEKDATAPOS;
    fi->xfi_DataPos     = jmp;
    xadCopyMem("AutoRun.script", fi->xfi_FileName, 15);
    fi->xfi_CrunchSize = fi->xfi_Size;
    datetags[1].ti_Data = (ULONG) &fi->xfi_Date;
    xadConvertDatesA(datetags);
    ai->xai_FileInfo = fi; link = fi;
  }

  while (1) {
    /* read the next file header */
    SEEK(offset);
    READ(buffer, 24);

    /* LOOP EXIT CONDITION */
    if (EndGetM16(buffer)!=0x47FA || EndGetM16(&buffer[4])!=0x4E94) break;

    /* lea    file_info(pc),a3   ;0000: 47 FA XX XX
     * jsr    (a4)               ;0004: 4E 94
     *then one of
     * jmp    next_file_header   ;0006: 4E F9 XX XX XX XX
     * bra.b  next_file_header   ;0006: 60 XX
     * bra.w  next_file_header   ;0006: 60 00 XX XX
     *then file_info:
     * dc.l   file_name          ;????: XX XX XX XX
     * dc.l   file_data          ;????: XX XX XX XX
     * dc.l   file_length        ;????: XX XX XX XX
     */

    jmp = EndGetM16(&buffer[2]) + 2;
    filelen = EndGetM32(&buffer[jmp+8]);
    nameoff = EndGetM32(&buffer[jmp])   + 32;
    dataoff = EndGetM32(&buffer[jmp+4]) + 32;

    /* work out the next file's offset from the bra/jmp instruction */
    jmp = EndGetM16(&buffer[6]);
         if (jmp == 0x4EF9) offset  = EndGetM32(&buffer[8]) + 32;
    else if (jmp == 0x6000) offset += EndGetM16(&buffer[8]) + 8;
    else if (jmp >= 0x6002 && jmp <= 0x60FE && !(jmp&1))
      offset += (jmp & 0xFE) + 8;
    else ERROR(DATAFORMAT);

    filetags[0].ti_Data = dataoff - nameoff;
    ALLOCOBJ(struct xadFileInfo *, fi, XADOBJ_FILEINFO, filetags);

    fi->xfi_EntryNumber = filenum++;
    fi->xfi_Size        = filelen;
    fi->xfi_Flags       = XADFIF_SEEKDATAPOS;
    fi->xfi_DataPos     = dataoff;

    /* read in filename */
    SEEK(nameoff);
    READ(fi->xfi_FileName, dataoff-nameoff);
    fi->xfi_CrunchSize = fi->xfi_Size;

    /* fill in today's date */
    datetags[1].ti_Data = (ULONG) &fi->xfi_Date;
    xadConvertDatesA(datetags);

    if (link) link->xfi_Next = fi; else ai->xai_FileInfo = fi;
    link = fi;
  }

exit_handler:
  if (err) {
    if (!ai->xai_FileInfo) return err;
    ai->xai_Flags |= XADAIF_FILECORRUPT;
    ai->xai_LastError = err;
  }
  return XADERR_OK;
}

ASM(LONG) MSFX_UnArchive(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  return xadHookAccess(XADAC_COPY, ai->xai_CurFile->xfi_Size, NULL, ai);
}

const struct xadClient MSFX12_Client = {
  NEXTCLIENT, XADCLIENT_VERSION, 6, MSFX_VERSION, MSFX_REVISION,
  190, XADCF_FILEARCHIVER | XADCF_FREEFILEINFO,
  0, "MakeSFX 1.2",

  /* client functions */
  (BOOL (*)()) MSFX12_RecogData,
  (LONG (*)()) MSFX12_GetInfo,
  (LONG (*)()) MSFX_UnArchive,
  NULL
};

const struct xadClient MSFX_Client = {
  (struct xadClient *) &MSFX12_Client,
  XADCLIENT_VERSION, 6, MSFX_VERSION, MSFX_REVISION,
  66, XADCF_FILEARCHIVER | XADCF_FREEFILEINFO,
  0, "MakeSFX 1.0",

  /* client functions */
  (BOOL (*)()) MSFX10_RecogData,
  (LONG (*)()) MSFX10_GetInfo,
  (LONG (*)()) MSFX_UnArchive,
  NULL
};
