/* Wrapster file archiver client for XAD.
 * Copyright (C) 2000-2002 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.
 */

/* Wrapster is a way of using the Napster MP3 sharing system to not only
 * share MP3s, but any files you like.
 * See http://www.unwrapper.com/ for more information, or do a Google
 * search on "Wrapster".
 *
 * The CRC32 feature of Wrapster is ignored completely, for two reasons:
 * - the Wrapster authors haven't told anybody what the CRC calculation is
 * - because of this, other wrapper authors have used different CRCs!!!!
 * Therefore, there's no real point checking.
 */

/* From the Wrapster 2.0 .HLP file:
 * Wrapster File Format for v1.0 and v2.0
 *
 * Ok... here is the official specification for the layout of a Wrapster
 * file. This section is intended for programmers and anyone who might be
 * curious about how this whole thing works. The reasons for including this
 * specification is to encourage other programmers to write their own
 * Wrapster clones for other Operating Systems, or to extend the features
 * of the Windows version of Wrapster itself. If you extend the features of
 * Wrapster, use some of the unused bytes so that your client can easily
 * identify those files which it creates.
 * 
 * A Wrapster file will have the following format on disk:
 * 
 * Header
 *   FrameHeader           4 bytes         0xFF, 0xFB, 0x18, 0x0C
 *   Reserved (Unused)     6 bytes
 *   Signature             10 bytes        "wrapster"#0#0
 *   Version               8 bytes         "2.0"#0#0#0#0#0
 *   FileCount             4 bytes
 * 
 * Wrapster File Entry
 * 
 * Immediately following the header of the file you will find FileCount
 * entries each containing the following data
 * 
 *   OriginalFileName      256 bytes
 *   Reserved (Unused)     32 bytes
 *   CRC32                 4 bytes
 *   Size                  4 bytes
 *   FileData              Size bytes
 * 
 * Padding
 * 
 * After all file entries in the Wrapster file you will encounter padding
 * that helps to assure that the files contained within the archive remain
 * intact. This was done as a precaution since many downloads across
 * Napster tend to get the last few bytes cut off.
 * 
 * ID3 Tag
 * The last 128 bytes of the padding contain an MP3 identification tag that
 * will show up in the play-list of programs like Winamp as an ID3 tag. You
 * may put any values in here that you like. See the MP3 specification for
 * more information.
 */

/* Wrapster v3.0 format - reverse engineered
 *
 * Format is entirely encapsulated in MP3 frames:
 * 0xFF,0xFB,0x90,0x04,{413 bytes of actual data}
 *
 * Header
 *   Reserved (Unused)     6 bytes
 *   Signature             10 bytes        "wwapster"#0#0
 *   Version               8 bytes         "3.0"#0#0#0#0#0
 *   FileCount             4 bytes
 *
 * Then follows [FileCount] file headers:
 *
 * File Header
 *   OriginalFileName      256 bytes
 *   Reserved (Unused)     32 bytes
 *   CRC32                 4 bytes
 *   Size                  4 bytes
 *
 * Then follows the file data for each file, in the same order as the
 * file headers.
 */

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

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

#ifndef XADMASTERFILE
#define Wrap_Client		FirstClient
#define NEXTCLIENT		0
const UBYTE version[] = "$VER: Wrapster 1.3 (17.08.2002)";
#endif
#define WRAP_VERSION		1
#define WRAP_REVISION		3

#define XADBASE  REG(a6, struct xadMasterBase *xadMasterBase)

#define head_magic      (0x00)
#define head_reserved   (0x04)
#define head_signature  (0x0a)
#define head_version    (0x14)
#define head_filecount  (0x1c)
#define head_SIZEOF     (0x20)

#define file_name       (0x000)
#define file_reserved   (0x100)
#define file_crc32      (0x120)
#define file_size       (0x124)
#define file_SIZEOF     (0x128)

ASM(BOOL) Wrap_RecogData(REG(d0, ULONG size), REG(a0, STRPTR d), XADBASE) {
  return (BOOL) (d[0]==0xFF && d[1]==0xFB && d[2]==0x18 && d[3]==0x0C
                 && (memcmp(&d[10], "wrapster\0", 10) == 0)) ? 1 : 0;
}

ASM(BOOL) Wrap3_RecogData(REG(d0, ULONG size), REG(a0, STRPTR d), XADBASE) {
  return (BOOL) (d[0]==0xFF && d[1]==0xFB && d[2]==0x90 && d[3]==0x04
                 && (memcmp(&d[10], "wwapster\0", 10) == 0)) ? 1 : 0;
}

ASM(LONG) Wrap_GetInfo(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  UBYTE buf[file_SIZEOF], is_v3;
  struct xadSkipInfo *slink = NULL,  *si;
  struct xadFileInfo *fi;
  ULONG numfiles, offset, skipto;
  LONG err = XADERR_OK;

  struct TagItem nametags[] = {
    { XAD_CHARACTERSET, CHARSET_WINDOWS },
    { XAD_STRINGSIZE, 256 },
    { XAD_CSTRING, 0 },
    { TAG_DONE, 0 }
  };

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

  struct TagItem tags[] = {
    { XAD_USESKIPINFO, 1 },
    { TAG_DONE, 0 }
  };

  /* read the file header */
  if ((err = xadHookAccess(XADAC_READ, head_SIZEOF, buf, ai))) return err;

  /* is this a wrapster v3 file? */
  is_v3 = (buf[2] == 0x90);

  /* skip all the mp3 frame headers */
  if (is_v3) {
    for (numfiles = ai->xai_InSize / 417; numfiles--; offset += 417) {
      if (!(si = (struct xadSkipInfo *) xadAllocObjectA(XADOBJ_SKIPINFO,0)))
        return XADERR_NOMEMORY;
      si->xsi_Position = offset;
      si->xsi_SkipSize = 4;
      if (slink) slink->xsi_Next = si; else ai->xai_SkipInfo = si;
      slink = si;
    }
  }

  /* check the version - is it "1.0", "2.0" or "3.0" ? */
  if (buf[head_version] < '1' || buf[head_version] > '3') {
    return XADERR_DATAFORMAT;
  }

  /* get the number of files in this archive */
  numfiles = EndGetI32(&buf[head_filecount]);

  if (is_v3) {
    /* offset of the file data, NOT counting the mp3 headers */
    offset = (head_SIZEOF-4) + (file_SIZEOF * numfiles);
  }

  while (numfiles-- && ((ai->xai_InPos+file_SIZEOF) < ai->xai_InSize)) {
    /* read the file header */
    if ((err = xadHookTagAccessA(XADAC_READ, file_SIZEOF, buf, ai, tags))) {
      break;
    }

    /* allocate a file entry */
    if (!(fi = (struct xadFileInfo *) xadAllocObjectA(XADOBJ_FILEINFO,NULL))) {
      err = XADERR_NOMEMORY;
      break;
    }

    fi->xfi_Size  = fi->xfi_CrunchSize = EndGetI32(&buf[file_size]);
    fi->xfi_Flags = XADFIF_SEEKDATAPOS | XADFIF_EXTRACTONBUILD;
    if (is_v3) {
      fi->xfi_DataPos = offset + (((offset/413)+1)*4);
      offset += fi->xfi_Size;
    }
    else {
      fi->xfi_DataPos = ai->xai_InPos;
    }

    /* copy filename (and convert if necessary) */
    nametags[2].ti_Data = (ULONG) &buf[file_name];
    if (!(fi->xfi_FileName = xadConvertNameA(CHARSET_HOST, nametags))) {
      err = XADERR_NOMEMORY;
      break;
    }

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

    /* add file entry. The xadAddFileEntry() automatic seeking tag
     * XAD_SETINPOS cannot be used, because it does not support the
     * XAD_USESKIPINFO tag (current as of XAD V12)
     */
    skipto = ai->xai_InPos + ((is_v3) ? 0 : fi->xfi_Size);
    if ((err = xadAddFileEntryA(fi, ai, NULL))) break;
    if (ai->xai_InPos != skipto) {
      if ((err = xadHookTagAccessA(XADAC_INPUTSEEK, skipto - ai->xai_InPos,
                                   NULL, ai, tags))) break;
    }
  }

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

ASM(LONG) Wrap_UnArchive(REG(a0, struct xadArchiveInfo *ai), XADBASE) {
  struct TagItem tags[] = {
    { XAD_USESKIPINFO, 1 },
    { TAG_DONE, 0 }
  };
  return xadHookTagAccessA(XADAC_COPY,ai->xai_CurFile->xfi_Size,NULL,ai,tags);
}

const struct xadClient Wrap3_Client = {
  NEXTCLIENT, XADCLIENT_VERSION, 12,
  WRAP_VERSION, WRAP_REVISION,
  32, XADCF_FILEARCHIVER | XADCF_FREEFILEINFO |
      XADCF_FREEXADSTRINGS | XADCF_FREESKIPINFO,
  0, "Wrapster 3",

  /* client functions */
  (BOOL (*)()) Wrap3_RecogData,
  (LONG (*)()) Wrap_GetInfo,
  (LONG (*)()) Wrap_UnArchive,
  (void (*)()) NULL
};

const struct xadClient Wrap_Client = {
  (struct xadClient *) &Wrap3_Client, XADCLIENT_VERSION, 12,
  WRAP_VERSION, WRAP_REVISION,
  32, XADCF_FILEARCHIVER | XADCF_FREEFILEINFO | XADCF_FREEXADSTRINGS,
  0, "Wrapster",

  /* client functions */
  (BOOL (*)()) Wrap_RecogData,
  (LONG (*)()) Wrap_GetInfo,
  (LONG (*)()) Wrap_UnArchive,
  NULL
};
