/*
   patchffs.c -- 62 char filenames for ffs by Harry "Piru" Sintonen

This patch allows you to use 62 characters long filenames,
and 38 characters long disk labels.

Here is a example of 62 char filename:

01_smack_my_very_long_filename_and_bitch_up_this_is_cool_eh.mp3

There is no separate disk label length check so please be careful
not to relabel disk label to over 38 characters. V43 ffs seems to
have sanity check for disk labels.

  version history

  1.0.1 - mid-july 1998 - initial release. i gave this version for
          few frieds on irc.

  1.0.2 - 8th august - added patches 8 and 9 fixing some fixed size
          filename copy-loops in v43 ffs. maybe also in other versions.

  1.0.3 - 8th august - added patch 10 fixing ACTION_EXAMINE packet
          filename limit. this was a bit tricky coz i had to recode the
          asm routine. but wooah! it was worth it, now about 10x more
          programs can access files :)

  1.0.4 - 19th august - really compiled in STRICT mode. now it sould
          be quite warning free.

   - Harry "Piru" Sintonen
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long int filesize(const char *filename) {
  FILE *fh;
  long int ret=(-1);
  if (fh=fopen(filename,"r")) {
    if (fseek(fh,0,SEEK_END) != -1) ret=ftell(fh);
    fclose(fh);
  }
  return ret;
}

unsigned short int w(unsigned char *in) {
  return (unsigned short int) ((in[0]<<8) | in[1]);
}

unsigned long lw(unsigned char *in) {
  return (unsigned long) ((in[0]<<24) | (in[1]<<16) | (in[2]<<8) | in[3]);
}

void pw(unsigned char *pos,unsigned short int val) {
  pos[0]=val>>8; pos[1]=val&0xff;
}

void plw(unsigned char *pos,unsigned long val) {
  pos[0]=(val>>24)&0xff; pos[1]=(val>>16)&0xff;
  pos[2]=(val>>8) &0xff; pos[3]= val     &0xff;
}

unsigned char *scanfor(unsigned char *start,unsigned char *end,
                       unsigned long match) {
  end=end-4;
  for (; start<end; start=start+2) {
    if (lw(start)==match) {
      return start;
    }
  }
  return 0;
}

unsigned int patch(unsigned char *start,unsigned char *end,
                   unsigned long match, unsigned long replacement) {
  unsigned int cnt=0;

  end=end-4;
  for (; start<end; start=start+2) {
    if (lw(start)==match) {
      plw(start,replacement); cnt++;
    }
  }
  return cnt;
}

void trypatchexe(char *outfile,unsigned char *inbuffer, size_t flen) {
  unsigned long r,mod=0;
  unsigned char *pos;
  FILE *out;

  if (flen & 3) return;

  /* try removing beta requester */
  if (lw(inbuffer+8)==2) {
    for (r=0; r<(flen-2); r=r+2) {
      if (w(inbuffer+r)==0x6100) {
        pos=inbuffer+r+2+w(inbuffer+r+2);
        if ( (pos>=inbuffer) && (pos<inbuffer+flen) ) {
          if ( (w(pos)==0x4ef9) && (lw(pos+2)==0) ) {
            /* yep this is the betareqcall,
               so kill it with two nops */
            plw(inbuffer+r,0x4e714e71);
            mod++;
            printf("  removed beta test from filesystem.\n\n");
          }
        }
      }
    }
  }

  /* fix it to allow 62 char filenames */

  if (flen>=15000) {

    /* patch stack space allocations: */

    /* lea (-32*2,sp),sp  =>  lea (-64*2,sp),sp     THIS ONE... */
    if (patch(inbuffer+11800,inbuffer+flen,0x4fefffc0,0x4fefff80)==0) {
      printf("patch 1 failed.\n"); return;
    }
    /* lea (-32,sp),sp  =>  lea (-64,sp),sp      ...BEFORE THIS :) */
    if (patch(inbuffer+11800,inbuffer+flen,0x4fefffe0,0x4fefffc0)==0) {
      printf("patch 2 failed.\n"); return;
    }
    /* lea (32,sp),sp  =>  lea (64,sp),sp */
    if (patch(inbuffer+11800,inbuffer+flen,0x4fef0020,0x4fef0040)==0) {
      printf("patch 3 failed.\n"); return;
    }
    /* lea (32,sp),a0  =>  lea (64,sp),a0 */
    if (patch(inbuffer+11800,inbuffer+flen,0x41ef0020,0x41ef0040)==0) {
      printf("patch 4 failed.\n"); return;
    }
    /* lea (32,sp),a1  =>  lea (64,sp),a1 */
    if (patch(inbuffer+11800,inbuffer+flen,0x43ef0020,0x43ef0040)==0) {
      printf("patch 5 failed.\n"); return;
    }

    /* patch filename len checks: */

    /* cmp.w #32-2,d3  => cmp.w #64-2,d3 */
    if (patch(inbuffer+8000,inbuffer+9500,0x0c43001e,0x0c43003e)==0) {
      printf("patch 6 failed.\n"); return;
    }
    /* cmp.w #32-2,d1  => cmp.w #64-2,d1 */
    if (patch(inbuffer+13500,inbuffer+15000,0x0c41001e,0x0c41003e)==0) {
      printf("patch 7 failed.\n"); return;
    }


    /* patch fixed size filename copy-loops
       (at least in some V43 ffs versions) */

    /* moveq #32-4,d0 ; move.l xxx  => moveq #64-4,d0 ; move.l xxx */
    if (patch(inbuffer+11800,inbuffer+flen,0x701c2fb1,0x703c2fb1)==0) {
      printf("patch 8 failed. some stuff might fuck up.\n");
    }

    /* moveq #32-4,d0 ; move.l yyy  => moveq #64-4,d0 ; move.l yyy */
    if (patch(inbuffer+11800,inbuffer+flen,0x701c21b1,0x703c21b1)==0) {
      printf("patch 9 failed. some stuff might fuck up.\n");
    }

/* some fixed size stuff in ACTION_EXAMINE:

        movem.l (-$50,a0),d0-d7            ;6 4CE8 00FF FFB0
        movem.l d0-d7,(fib_FileName,a4)    ;6 48EC 00FF 0008
        clr.w   (fib_Comment,a4)           ;4 426C 0090
        tst.b   (-$B8,a0)                  ;4 4A28 FF48
        beq.b   .skip                      ;2 6724
        movem.l (-$B8,a0),d0-d7            ;6 4CE8 00FF FF48
        movem.l d0-d7,(fib_Comment,a4)     ;6 48EC 00FF 0090
        movem.l (-$98,a0),d0-d7            ;6 4CE8 00FF FF68
        movem.l d0-d7,(fib_Comment+$20,a4) ;6 48EC 00FF 00B0
        movem.l (-$78,a0),d0-d3            ;6 4CE8 000F FF88
        movem.l d0-d3,(fib_Comment+$40,a4) ;6 48EC 000F 00D0
.skip   ; can trash d0-d7/a0/a1            =58


this is the fixed version:

        lea     (-$50,a0),a1               ;4 43E8 FFB0
        moveq   #fib_FileName,d0           ;2 7008
.copy1  move.l  (a1)+,(0,a4,d0.l)          ;4 2999 0800
        move.l  (a1)+,(4,a4,d0.l)          ;4 2999 0804
        addq.l  #8,d0                      ;2 5080
        cmp.w   #fib_FileName+64,d0        ;4 0C40 0048
        bne.b   .copy1                     ;2 66F0
        clr.w   (fib_Comment,a4)           ;4 426C 0090
        tst.b   (-$B8,a0)                  ;4 4A28 FF48
        beq.b   .skip                      ;2 671A
        lea     (-$B8,a0),a1               ;4 43E8 FF48
        move.l  #fib_Comment,d0            ;6 203C 0000 0090
.copy2  move.l  (a1)+,(0,a4,d0.l)          ;4 2999 0800
        move.l  (a1)+,(4,a4,d0.l)          ;4 2999 0804
        addq.l  #8,d0                      ;2 5080
        cmp.w   #fib_Comment+80,d0         ;4 0C40 00E0
        bne.b   .copy2                     ;2 66F0
.skip
*/
    /* try to find movem (-$50,a0),d0-d7 /
                   movem.l d0-d7,(fib_FileName,a4) etc. */

    if (pos=scanfor(inbuffer+11800,inbuffer+flen,0x4ce800ff)) {
      if ( (lw(pos+4)==0xffb048ec) && (lw(pos+8)==0x00ff0008) &&
           (lw(pos+12)==0x426c0090) && (lw(pos+16)==0x4a28ff48) &&
           (lw(pos+20)==0x67244ce8) && (lw(pos+24)==0x00ffff48) &&
           (lw(pos+28)==0x48ec00ff) && (lw(pos+32)==0x00904ce8) ) {

        plw(pos,   0x43e8ffb0); plw(pos+4, 0x70082999);
        plw(pos+8, 0x08002999); plw(pos+12,0x08045080);
        plw(pos+16,0x0c400048); plw(pos+20,0x66f0426c);
        plw(pos+24,0x00904a28); plw(pos+28,0xff48671a);
        plw(pos+32,0x43e8ff48); plw(pos+36,0x203c0000);
        plw(pos+40,0x00902999); plw(pos+44,0x08002999);
        plw(pos+48,0x08045080); plw(pos+52,0x0c4000e0);
        pw(pos+56,0x66f0);

      } else pos=0;
    }
    if (pos==0) {
      printf("patch 10 failed. some stuff might fuck up.\n");
    }

    printf("\n  filesystem patched for max 62 character filenames.\n\n"
           "  WARNING! Consider this patch extremely *DANGEROUS*!\n"
           "  This *WILL* definetely trash, kill, burn and fuck up your "
           "disk.\n\n  #include <disclaimer.h>\n\n"
           "  NOTE: Due the nature of filename hashing disk containing long\n"
           "  filenames might appear corrupt to non-patched filesystem. Also,"
           "\n"
           "  I have no idea what will happen in validation process (horrors\n"
           "  probably). So please, be careful with this little toy.\n\n");

    mod++;

  } else printf("filesystem is too short to be patched.\n");

  if (mod) {
    printf("  saving file %s\n",outfile);

    remove(outfile);
    if (out=fopen(outfile,"w")) {
      if (fwrite(inbuffer,flen,1,out) != 1) {
        fclose(out); out=NULL; remove(outfile);
        printf("error writing file, destination file removed.\n");
      }
      if (out) fclose(out);
    } else printf("error opening file for writing.\n");

    printf("  done.\n\n");

  } else printf("file wasn't modified in any way so saving skipped.\n");

}

int main(int argc,char **argv) {
  unsigned char *inbuffer;
  char *infile,outfile[513]={0};
  FILE *in;
  size_t flen;

  printf("patchffs 1.0.4 Copyright © 1998 Harry \"Piru\" Sintonen\n\n");

  if ( (argc<2) || (argc>3) ) {
    printf("usage: %s infile [outfile]\n",argv[0]);
    return 0;
  }

  infile=argv[1];
  if (argc>2) {
    strncpy(outfile,argv[2],512);
  } else {
    sprintf(outfile,"%s.patched",infile);
  }

  flen=filesize(infile);

  if (in=fopen(infile,"r")) {
    if (inbuffer=(unsigned char *)malloc(flen)) {
      if (fread(inbuffer,flen,1,in) == 1) {
        fclose(in); in=NULL;
        if ( (lw(inbuffer)==0x000003f3) && 
             (lw(inbuffer+4)==0) ) {
          trypatchexe(outfile,inbuffer,flen);
        } else {
          printf("bad executable file\n");
        }
      } else {
        printf("error reading file\n");
      }
      free(inbuffer);
    } else {
      printf("malloc for %ld bytes failed\n",flen);
    }
    if (in) fclose(in);
  } else {
    printf("could not open file %s\n",infile);
  }

  return 0;
}
