/* *****************************************************************************
 * pp20.dll v1.0.9.4 - decrunch PowerPacker encrypted data files with password
 * by Stuart Caie & Peace^Testaware - this software is in the Public Domain
 * *****************************************************************************
 *
 * Website: http://www.testaware.de.tp
 *
 * Version history
 * 0.4       18-Sep-2003 : by Stuart Caie
 *                       - first basic release
 *
 * 1.0.9.1   12-Nov-2008 : by Peace^Testaware
 *                       - source adapted to Dev-C++ 4.9.9.2
 *                       - support for AMOS(Pro) PPbk format
 *                       - added ppDecrunchFile()
 *                       - added ppDecrunchMemory()
 *                       - added ppGetDecrunchSize()
 *                       - added ppGetCrunchMode()
 *                       - removed ppLoadData()
 *                       - changed PPERR_<CODE> to negative values
 *                       - improved PPMODE_<CODE> constants
 *                       - improved macro SwapLong(ptr)
 *                       - included example source for PureBasic
 *
 * 1.0.9.2   13-Nov-2008 : by Peace^Testaware
 *                       - ppDecrunch, ppDecrypt now subfunctions
 *
 * 1.0.9.3   16-Nov-2008 : by Peace^Testaware
 *                       - support for PowerPacker v1.x format (very old)
 *
 * 1.0.9.4   21-Nov-2008 : by Peace^Testaware
 *                       - added ppEncodeMemory
 * ****************************************************************************/

#include "dll.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define PPERR_OK           (0)  /* no error                  */
#define PPERR_ARGS         (-1) /* bad arguments to function */
#define PPERR_OPEN         (-2) /* error opening file        */
#define PPERR_READ         (-3) /* error reading from file   */
#define PPERR_SEEK         (-4) /* error seeking in file     */
#define PPERR_NOMEMORY     (-5) /* out of memory             */
#define PPERR_DATAFORMAT   (-6) /* error in data format      */
#define PPERR_PASSWORD     (-7) /* bad or missing password   */
#define PPERR_DECRUNCH     (-8) /* error decrunching data    */

#define PPMODE_PP11        (0x50503131)
#define PPMODE_PP20        (0x50503230)
#define PPMODE_PPBK        (0x5050626B)  /* AMOS PPbk Bank   */
#define PPMODE_PPLS        (0x50504C53)
#define PPMODE_PX20        (0x50583230)

#define PP_READ_BITS(nbits, var) do {                            \
  bit_cnt = (nbits); (var) = 0;                                  \
  while (bits_left < bit_cnt) {                                  \
    if (buf < src) return 0; /* out of source bits */            \
    bit_buffer |= *--buf << bits_left;                           \
    bits_left += 8;                                              \
  }                                                              \
  bits_left -= bit_cnt;                                          \
  while (bit_cnt--) {                                            \
    (var) = ((var) << 1) | (bit_buffer & 1);                     \
    bit_buffer >>= 1;                                            \
  }                                                              \
} while (0)

#define PP_BYTE_OUT(byte) do {                                   \
  if (out <= dest) return 0; /* output overflow */               \
  *--out = (byte); written++;                                    \
} while (0)

#define SwapLong(ptr) ((ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3])


/* *****************************************************************************
 * SIZE = ppGetDecrunchSize(*src, src_len)
 * *****************************************************************************
 * calculates the decrunched size of crunched buffer
 * -----------------------------------------------------------------------------
 * APTR  *src    = buffer of PowerPacker crunched datas
 * DWORD src_len = size in bytes of *src
 * -----------------------------------------------------------------------------
 * SIZE = size of decrunched buffer, else #PPERR_<CODE>
 * ****************************************************************************/
DLLIMPORT int ppGetDecrunchSize(unsigned char *src, unsigned int src_len)
{
          int dest_len = PPERR_DATAFORMAT;
          unsigned int mode;

          if (!src || !src_len) return PPERR_ARGS;

          mode   = SwapLong(src);

          if ((mode == PPMODE_PP11) || (mode == PPMODE_PP20) || (mode == PPMODE_PPBK) ||
             (mode == PPMODE_PPLS) || (mode == PPMODE_PX20)) {
             dest_len = (src[src_len-4] << 16) | (src[src_len-3] << 8) | src[src_len-2]; }

          return dest_len;
}

/* *****************************************************************************
 * MODE = ppGetCrunchMode(*src)
 * *****************************************************************************
 * returns the crunchmode of crunched buffer
 * -----------------------------------------------------------------------------
 * APTR *src = buffer of PowerPacker crunched datas
 * -----------------------------------------------------------------------------
 * MODE = #PPMODE_<CODE> detected format
 * ****************************************************************************/
DLLIMPORT int ppGetCrunchMode(unsigned char *src)
{
          int mode;

          if (src == NULL) return PPERR_ARGS;

          /* detect format, decrypt if necessary */
          switch SwapLong(src) {
                 case PPMODE_PP11: mode = PPMODE_PP11; break; /* PP11 */
                 case PPMODE_PP20: mode = PPMODE_PP20; break; /* PP20 */
                 case PPMODE_PPBK: mode = PPMODE_PPBK; break; /* PPbk */
                 case PPMODE_PPLS: mode = PPMODE_PPLS; break; /* PPLS */
                 case PPMODE_PX20: mode = PPMODE_PX20; break; /* PX20 */
                 default:
                         mode = PPERR_DATAFORMAT;
                         }
          return mode;
}

/* *****************************************************************************
 * ERR = ppDecrunchMemory(*src, *dest, src_len, dest_len [,password])
 * *****************************************************************************
 * decrunch PowerPacker crunched buffer, also encrypt by given password
 * -----------------------------------------------------------------------------
 * APTR  *src     = buffer of PowerPacker crunched datas
 * APTR  *dest    = buffer to store encrypted/decrunched datas
 * DWORD src_len  = size in bytes of *src
 * DWORD dest_len = size in bytes of decrunched buffer (=ppGetDecrunchSize())
 * CHAR  password = Password (max. 16 chars only!) to encrypt datas in *src
 * -----------------------------------------------------------------------------
 * ERR = #PPERR_OK -> buffer decrunched
 * ****************************************************************************/
DLLIMPORT int ppDecrunchMemory(unsigned char *src, unsigned char *dest,
          unsigned int src_len, unsigned int dest_len, unsigned char *password)
{
          int err = PPERR_OK, eff;

          if (!src || !dest || !src_len || !dest_len) return PPERR_ARGS;

          /* detect format, decrypt if necessary */
          switch SwapLong(src) {
                 case PPMODE_PP11: eff =  4; break; /* PP11 */
                 case PPMODE_PP20: eff =  4; break; /* PP20 */
                 case PPMODE_PPBK: eff = 20; break; /* PPbk */
                 case PPMODE_PPLS: eff =  8; break; /* PPLS */
                 case PPMODE_PX20: eff =  6;        /* PX20 */
                      if (!password || (ppCalcChecksum(password) != ((src[4]<<8)|src[5])))
                         err = PPERR_PASSWORD;
                      else
                         ppDecrypt(&src[10], src_len-14, ppCalcPasskey(password));
                         break;
                 default:
                         err = PPERR_DATAFORMAT;
                         }

          if (err) { return err; }

          if (!ppDecrunch(&src[eff], dest, src_len-(eff+8), dest_len)) {
             err = PPERR_DECRUNCH; }

          return  err;
}

/* *****************************************************************************
 * ERR = ppEncodeMemory(*src, *dest, src_len, dest_len, passkey [,*restore])
 * *****************************************************************************
 * encrypt and decrunch crunched buffer by given passkey
 * -----------------------------------------------------------------------------
 * APTR  *src     = buffer of PowerPacker encrypted PX20 datas 
 * APTR  *dest    = buffer to copy encrypted/decrunched datas
 * DWORD src_len  = size in bytes of *src
 * DWORD dest_len = size in bytes of decrunched buffer (=ppGetDecrunchSize())
 * ULONG passkey  = 32 bit-key to encrypt datas in *src
 * APTR  *restore = optional: clone of *src to restore buffer if wrong passkey
 * -----------------------------------------------------------------------------
 * ERR = #PPERR_OK -> all done
 * ****************************************************************************/
DLLIMPORT int ppEncodeMemory(unsigned char *src, unsigned char *dest,
          unsigned int src_len, unsigned int dest_len,
          unsigned int passkey, unsigned char *restore)
{
          int err = PPERR_OK, eff;

          if (!src || !dest || !src_len || !dest_len) return PPERR_ARGS;

          /* detect format, only for encrypted data (PX20) */
          switch SwapLong(src) {
                 case PPMODE_PX20: eff =  6;        /* PX20 */
                      passkey &= 0xFFFFFFFF;
                      ppDecrypt(&src[10], src_len-14, passkey);
                      break;
                 default:
                         err = PPERR_DATAFORMAT;
                         }

          /* error: no encrypted datas in *src */
          if (err) { return err; }

          if (!ppDecrunch(&src[eff], dest, src_len-(eff+8), dest_len)) {
             /* if restore buffer is set, copy into *src (recommended!) */
             if ((restore) != NULL) {
                           memmove(src, restore, src_len);
                           }
             err = PPERR_PASSWORD;
             }

          return  err;
}

/* *****************************************************************************
 * ERR = ppDecrunchFile(*filename, *savename [,password])
 * *****************************************************************************
 * decrunch given file direct to a new file
 * -----------------------------------------------------------------------------
 * APTR *filename = name of file with PowerPacker packed datas 
 * APTR *savename = filename to store encrypted/decrunched datas
 * CHAR password  = optional: (max. 16 chars only!) to encrypt datas
 * -----------------------------------------------------------------------------
 * ERR = #PPERR_OK -> all done
 * ****************************************************************************/
DLLIMPORT int ppDecrunchFile(unsigned char *filename, unsigned char *savename,
          unsigned char *password)
{
          unsigned char *src = NULL, *dest, *amos;
          unsigned int srclen, destlen;
          int err = PPERR_OK, eff;
          FILE *fh;
          HANDLE hFile;

          if (!filename || !savename) return PPERR_ARGS;

          /* open file, find out srclen, allocate src and read file */
          if ((fh = fopen(filename, "rb"))) {
               if ((fseek(fh, 0, SEEK_END) == 0) &&
	              (srclen = (unsigned int) ftell(fh)) &&
	              (fseek(fh, 0, SEEK_SET) == 0))
                  {
                  if ((src = malloc(srclen))) {
	                 if (fread(src, 1, srclen, fh) != srclen) {
	                    free(src); err = PPERR_READ;
                        }
                     }
                     else err = PPERR_NOMEMORY;
                  }
                  else err = PPERR_SEEK;
                       fclose(fh);
               }
               else err = PPERR_OPEN;
                     
               if (err) return err;

               fclose(fh);

               /* detect format, decrypt if necessary */
               switch SwapLong(src) {
                      case PPMODE_PP11: eff =  4; break; /* PP11 */
                      case PPMODE_PP20: eff =  4; break; /* PP20 */
                      case PPMODE_PPBK: eff = 20; break; /* PPbk */
                      case PPMODE_PPLS: eff =  8; break; /* PPLS */
                      case PPMODE_PX20: eff =  6;        /* PX20 */
                           if (!password || (ppCalcChecksum(password) != ((src[4]<<8)|src[5])))
                              err = PPERR_PASSWORD;
                           else
                               ppDecrypt(&src[10], srclen-14, ppCalcPasskey(password));
                               break;
                      default:
                              err = PPERR_DATAFORMAT;
                              }

               if (err) {
                  free(src);
                  return err;
               }

               /* allocate memory for decrunch buffer, then decrunch */
               destlen = (src[srclen-4] << 16) | (src[srclen-3] << 8) | src[srclen-2];
               if ((dest = malloc(destlen))) {
                  if (!ppDecrunch(&src[eff], dest, srclen-(eff+8), destlen))
                     err = PPERR_DECRUNCH;
                  else
                     hFile = CreateFile(savename, GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
                     if (hFile != INVALID_HANDLE_VALUE) {
                        switch SwapLong(src) {
                               case   PPMODE_PPBK: eff = 8; break; /* AMOS PPbk Bank = 8 Bytes ID String */
                        default:
                                eff = 0;
                                }
                        DWORD dwWritten;
                        if (WriteFile(hFile, &dest[eff], destlen-eff, &dwWritten, NULL)) {
                           if ((destlen-eff) != (dwWritten)) { err = PPERR_DECRUNCH; }
                           }
                        CloseHandle(hFile);
                     }
                  }
  
               if (src) { free(src); }
               if (dest) { free(dest); }

          return err;
}

/* *****************************************************************************
 * CKSUM = ppCalcChecksum(password)
 * *****************************************************************************
 * calculate a 16 bit checksum of given password, needed to control PX20 cksum
 * -----------------------------------------------------------------------------
 * CHAR password = max. 16 chars
 * -----------------------------------------------------------------------------
 * CKSUM = 16 bit checksum of password
 * ****************************************************************************/
DLLIMPORT int ppCalcChecksum(unsigned char *password)
{
          unsigned int cksum = 0;
          unsigned char c, shift;

          /* for each byte in the password */
          while ((c = *password++)) {
                /* barrel-shift the 16 bit checksum right by [c] bits */
                shift = c & 0x0F;
                if (shift) cksum = (cksum >> shift) | (cksum << (16-shift));
                   /* add c to the cksum, with 16 bit wrap */
                   cksum = (cksum + c) & 0xFFFF;
          }

          return cksum;
}

/* *****************************************************************************
 * PWKEY = ppCalcPasskey(password)
 * *****************************************************************************
 * calculate a 32 bit checksum of given password, needed to crack crypted buffer
 * -----------------------------------------------------------------------------
 * CHAR password = max. 16 chars
 * -----------------------------------------------------------------------------
 * PWKEY = 32 bit passkey of password
 * ****************************************************************************/
DLLIMPORT int ppCalcPasskey(unsigned char *password)
{
          unsigned int key = 0;
          unsigned char c;

          /* for each byte in the password */
          while ((c = *password++)) {
                /* rotate 32 bit key left by one bit */
                key = (key << 1) | (key >> (32-1));
                key &= 0xFFFFFFFF;

                /* add c to the key, with 32 bit wrap */
                key = (key + c) & 0xFFFFFFFF;

                /* swap lower and upper 16 bits */
                key = (key << 16) | (key >> 16);
                key &= 0xFFFFFFFF;
          }
  
          return key;
}

/* *****************************************************************************
 * INT ppDecrunch(*src, *dest, src_len, dest_len)
 * *****************************************************************************
 * intern: 1 all decrunched, 0 = error occured
 * -----------------------------------------------------------------------------
 * APTR  *src     = buffer of PowerPacker encrypted PX20 datas 
 * APTR  *dest    = buffer to copy encrypted/decrunched datas
 * DWORD src_len  = size in bytes of *src
 * DWORD dest_len = size in bytes of decrunched buffer
 * ****************************************************************************/
int ppDecrunch(unsigned char *src, unsigned char *dest, unsigned int src_len,
               unsigned int dest_len)
{
          unsigned char *buf, *out, *dest_end, *off_lens, bits_left = 0, bit_cnt;
          unsigned int bit_buffer = 0, x, todo, offbits, offset, written = 0;

          if (src == NULL || dest == NULL) return 0;

          /* set up input and output pointers */
          off_lens = src; src = &src[4];
          buf = &src[src_len];

          out = dest_end = &dest[dest_len];

          /* skip the first few bits */
          PP_READ_BITS(src[src_len + 3], x);

          /* while there are input bits left */
          while (written < dest_len)
                {
                PP_READ_BITS(1, x);
                if (x == 0)
                   {
                   /* bit==0: literal, then match. bit==1: just match */
                   todo = 1; do { PP_READ_BITS(2, x); todo += x; } while (x == 3);
                   while (todo--) { PP_READ_BITS(8, x); PP_BYTE_OUT(x); }

                   /* should we end decoding on a literal, break out of the main loop */
                   if (written == dest_len) break;
                   }

                /* match: read 2 bits for initial offset bitlength / match length */
                PP_READ_BITS(2, x);
                offbits = off_lens[x];
                todo = x+2;
                if (x == 3) {
                   PP_READ_BITS(1, x);
                   if (x == 0) offbits = 7;
                      PP_READ_BITS(offbits, offset);
                      do { PP_READ_BITS(3, x); todo += x; } while (x == 7); }
                   else {
                      PP_READ_BITS(offbits, offset);
                   }
                   if (&out[offset] >= dest_end) return 0; /* match_overflow */
                   while (todo--) { x = out[offset]; PP_BYTE_OUT(x); }
                }

          /* all output bytes written without error */
          return 1;
}

/* *****************************************************************************
 * VOID ppDecrypt(*data, len, key)
 * *****************************************************************************
 * intern: decrypt buffer with given passkey
 * -----------------------------------------------------------------------------
 * APTR  *data = buffer of PowerPacker encrypted PX20 datas 
 * DWORD len   = size in bytes of *data
 * ULONG key   = 32 bit key (=ppCalcPasskey())
 * ****************************************************************************/
void ppDecrypt(unsigned char *data, unsigned int len, unsigned int key)
{
          unsigned char k0 = (key >> 24) & 0xFF;
          unsigned char k1 = (key >> 16) & 0xFF;
          unsigned char k2 = (key >>  8) & 0xFF;
          unsigned char k3 = (key      ) & 0xFF;

          len = ((len + 3) >> 2) - 1;

          /* to replicate unofficial powerpacker.library v37.3 bug, uncomment line */
          len &= 0xFFFF;

          /* XOR data with key */
          do {
             *data++ ^= k0;
             *data++ ^= k1;
             *data++ ^= k2;
             *data++ ^= k3;
          } while (len--);
}


BOOL APIENTRY DllMain (HINSTANCE hInst     /* Library instance handle. */ ,
                       DWORD reason        /* Reason this function is being called. */ ,
                       LPVOID reserved     /* Not used. */ )
{
    switch (reason)
    {
      case DLL_PROCESS_ATTACH:
        break;

      case DLL_PROCESS_DETACH:
        break;

      case DLL_THREAD_ATTACH:
        break;

      case DLL_THREAD_DETACH:
        break;
    }

    /* Returns TRUE on success, FALSE on failure */
    return TRUE;
}