/* pp_patcher v1.0 ** ** pp_patcher is a small utility which enables programs to get at ** PowerPacked files, as if these were normal files. This is acomplished ** by patching some vital DOS functions, thus redirecting calls to these to ** my own internal routines. The overall effect is that PowerPacked data- ** files seem to be normal files. You can TYPE them in a CLI window, or ** bring them directly into your favourite editor. Another good way to ** use this proggy is if you crunch your workbench icons. Workbench will ** never know the difference, but it reduces the diskspace occupied by ** icons by some 65-70% (usually). There is a nominal performance reduction ** due to the fact that we have to decrunch the icons before passing them ** on to Workbench, but this doesn't seem too annoying. Especially not if ** you use optimized (B.A.D.) disks, or the CLI command Addbuffers or ** equivalent. ** ** For further info, read the DOC file. ** ** Compiles under Aztec V5.0 using large code/large data/32 bit integers ** Should do fine under Lattice, too, if you fix the #pragma's. ** ** Shareware 1991, copyright (C) 1991 by Michael Berg ** Sct. Peders Gade 24A, 2th ** 8900 Randers ** DENMARK */ /* That's right -- ALL of these are necessary! */ #include #include #include #include #include #include #include #include "ppbase.h" /* enclosed in this directory */ #asm DOSLibPatch MACRO public _Real\1 public _New\1 public _Make\1 public _Rest\1 public _LVO\1 public \2 cseg _Make\1 ;make a doslib patch move.l a6,-(sp) ;save regs move.l \2,a6 ;get at the dosbase lea _LVO\1(a6),a6 ;jump vector address moveq #0,d0 move.w (a6),Orig\1 ;fetch the 'moveq #?,d0' opcode move.w 4(a6),d0 ;fetch the 'bra' offset add.l a6,d0 ;add to find branch target addq.l #4,d0 ;pc relative offset compensation move.l d0,Orig\1+4 ;we need this so we can jump directly move.w #$4ef9,(a6) ;initiate new sequence: jmp $abs_addrs move.l #_New\1,2(a6) ;jump to our new function, of corz! move.l (sp)+,a6 ;restore regs rts dseg ds 0 _Real\1 Orig\1 dc.w 0 ;this will be replaced by a moveq dc.w $4ef9,0,0 ;the JMP address will be fixed cseg _Rest\1 ;restore a doslib patch tst.l Orig\1+4 ;did we make it at all? beq.s 1$ ;nope, don't do anything move.l a6,-(sp) ;save regs move.l \2,a6 ;get at the dosbase lea _LVO\1(a6),a6 ;find jump vector address move.l Orig\1+4,d0 ;calculate a bra offset to the orig. addrs subq.l #4,d0 ;pc relative offset compensation sub.l a6,d0 ;subtract base address move.w Orig\1,(a6) ;restore the moveq opcode move.w #$6000,2(a6) ;opcode for 'bra.l' move.w d0,4(a6) ;save the branch offset move.l (sp)+,a6 ;restore registers 1$ rts ENDM ;DOSLibPatch is a macro which defines three functions. For Open(), they are: ; ;MakeOpen - Patch the DOS library Open() vector ;RestOpen - Restore the DOS library to it's original state ;RealOpen - This calls the original DOS function ; ;Furthermore, you yourself have to create a function called (in this case) ;NewOpen, with a parameter specification like Open(). Future calls to Open ;will be redirected to NewOpen. NewOpen has access to the original DOS code ;through the function RealOpen. RealOpen should also be defined like Open. ;More details follow. For now, you need to know that DOSLibPatch works only ;with the DOS library, which differes in format from other libraries. DOSLibPatch Open,_DOSBase DOSLibPatch Close,_DOSBase DOSLibPatch Examine,_DOSBase DOSLibPatch Write,_DOSBase #endasm /* Well, the compiler has the inline-code ability -- why not use them? */ #define strlen _BUILTIN_strlen #define strcpy _BUILTIN_strcpy /* Some defines which make the source code look pretty */ typedef struct IntuitionBase INTUIBASE; typedef struct GfxBase GFXBASE; typedef struct PPBase PPBASE; /* A tiny window enables the user to control the program */ struct NewWindow nw = { 0,0,320,10, -1,-1, CLOSEWINDOW, WINDOWDRAG | WINDOWCLOSE | WINDOWDEPTH | ACTIVATE, NULL,NULL, (UBYTE *)"Powerpacker Patcher V1.0", NULL,NULL, 0,0,0,0, WBENCHSCREEN }; /* powerpacker.library related functions */ extern int ppLoadData(char *, int, int, UBYTE **, int *, int (*)()); #pragma amicall(PPBase, 0x1e, ppLoadData(a0,d0,d1,a1,a2,a3)) /* Open()-patch related functions */ extern void MakeOpen(void), RestOpen(void); extern BPTR RealOpen(char *, int); extern BPTR NewOpen(char *, int); #pragma regcall(RealOpen(d1,d2)) /* All of these PRAGMAs are EXTREMELY */ #pragma regcall(NewOpen(d1,d2)) /* important !!!!!!!!!! */ /* Close()-patch related functions */ extern void MakeClose(void), RestClose(void); extern void RealClose(BPTR); extern void NewClose(BPTR); #pragma regcall(RealClose(d1)) #pragma regcall(NewClose(d1)) /* Examine()-patch related functions */ extern void MakeExamine(void), RestExamine(void); extern int RealExamine(BPTR, struct FileInfoBlock *); extern int NewExamine(BPTR, struct FileInfoBlock *); #pragma regcall(RealExamine(d1,d2)) #pragma regcall(NewExamine(d1,d2)) /* Write()-patch related functions */ extern void MakeWrite(void), RestWrite(void); extern int RealWrite(BPTR, char *, int); extern int NewWrite(BPTR, char *, int); #pragma regcall(RealWrite(d1,d2,d3)) #pragma regcall(NewWrite(d1,d2,d3)) /* Define the maximum length of a filename and its path ** 256 is, as far as I can tell, the AmigaDOS limit (due to BSTR's). */ #define MAXPATHLEN 256 /* Match tags for PowerPacked files */ #define PP20 (('P' << 24) + ('P' << 16) + ('2' << 8) + '0') #define PX20 (('P' << 24) + ('X' << 16) + ('2' << 8) + '0') /* One of these for each opened, powerpacked file, which has not yet been ** closed */ struct filenode { struct MinNode mn; BPTR filehandle; char *ram_filename, *orig_filename; short dirty; }; /* One of these for each caller to NewOpen() */ struct caller { struct MinNode mn; struct Task *tc; }; /* Global data */ struct MinList templist, callers; struct Window *win; struct IntuitionBase *IntuitionBase; struct GfxBase *GfxBase; struct PPBase *PPBase; int patched; /* Exported to detach module */ int _stack = 4000; int _priority = 5; char *_procname = "Powerpacker Patcher"; BPTR _BackGroundIO; /* Universal termination code */ void die(int errcode) { /* Restore the original DOS functions. You will not find these ** functions in the sourcecode. They are generated by the assembler ** macro DOSLibPatch -- see above */ if (patched) { Forbid(); RestOpen(); RestClose(); RestExamine(); RestWrite(); Permit(); } /* Close the window */ if (win) CloseWindow(win); /* Close libraries */ if (IntuitionBase) CloseLibrary(IntuitionBase); if (GfxBase) CloseLibrary(GfxBase); if (PPBase) CloseLibrary(PPBase); /* Finally! */ exit(errcode); } /* Open up all required libraries */ void openlibs() { if ( !(GfxBase = (GFXBASE *)OpenLibrary("graphics.library", 0)) || !(IntuitionBase = (INTUIBASE *)OpenLibrary("intuition.library", 0)) || !(PPBase = (PPBASE *)OpenLibrary("powerpacker.library",0)) ) /* Graphics & Intuition should NEVER fail to open */ die(10); } /* Attempt to open the window */ void openwindow() { if (!(win = OpenWindow(&nw))) die(20); } /* Install the DOS patches */ void installpatch() { Forbid(); MakeOpen(); MakeClose(); MakeExamine(); MakeWrite(); Permit(); patched = 1; } /* Some Lists need to be initialized */ void initlist() { NewList((struct List *)&templist); NewList((struct List *)&callers); } /* Open up everything */ void openstuff() { openlibs(); openwindow(); initlist(); installpatch(); } /* Add a new caller to NewOpen() to the list of callers */ addcaller(register struct Task *tc) { register struct caller *memgot; if (memgot = AllocMem(sizeof(*memgot),0)) { memgot->tc = tc; AddTail((struct List *)&callers, (struct Node *)memgot); return(1); } else return(0); } /* Add a file node to the list of files which we have created. These ** exist temporarily in RAM: and we need to get rid of these along the way, ** as they are Close()'d. */ addfilenode(register BPTR fn, register char *filename, register char *orig) { register struct filenode *memgot; if ( (memgot = AllocMem(sizeof(*memgot),MEMF_CLEAR)) && (memgot->ram_filename = AllocMem(strlen(filename)+1,0)) && (memgot->orig_filename = AllocMem(strlen(orig)+1,0)) ) { memgot->filehandle = fn; strcpy(memgot->ram_filename,filename); strcpy(memgot->orig_filename,orig); AddTail((struct List *)&templist, (struct Node *)memgot); return(1); } else return(0); } /* Find a filenode (keyed by its filehandle) */ struct filenode *findfilenode(register BPTR fn) { register struct filenode *search; /* Linear search is employed */ for ( search = (struct filenode *)(templist.mlh_Head); search->mn.mln_Succ; search = (struct filenode *)(search->mn.mln_Succ) ) if (search->filehandle == fn) return(search); return(NULL); } /* Find a caller on the callers list */ struct caller *findcaller(register struct Task *tc) { register struct caller *search; for ( search = (struct caller *)(callers.mlh_Head); search->mn.mln_Succ; search = (struct caller *)(search->mn.mln_Succ) ) if (search->tc == tc) return(search); return(NULL); } /* This baby builds a complete filename (including a path) from a BCPL ** pointer to a filehandle. Optimizations are most welcome. All those ** ParentDir() calls take a LONG time. */ char *fullname(register BPTR lock, register struct FileInfoBlock *fib) { static char pathandfile[MAXPATHLEN]; char tmp[MAXPATHLEN]; register BPTR parentlock, unlocklock; register char *co; strcpy(pathandfile,fib->fib_FileName); parentlock = lock; unlocklock = (BPTR)0; while (parentlock=ParentDir(parentlock)) { if (unlocklock) UnLock(unlocklock); if (RealExamine(parentlock,fib)) { strcpy(tmp,fib->fib_FileName); strcat(tmp,"/"); strcat(tmp,pathandfile); strcpy(pathandfile,tmp); } else { UnLock(parentlock); return(NULL); } unlocklock = parentlock; } if (unlocklock) UnLock(unlocklock); if (co = (char *)index(pathandfile,'/')) *co = ':'; /* This fixes a bug in the old RAM disk */ if (!strcmp(pathandfile,":")) strcpy(pathandfile,"RAM:"); return(pathandfile); } /* Is a filename really a file? We need to know this, because it is ** rediculous to try to read in the PowerPacker matchtag from something ** like CON:0/0/544/23/ConWindow. */ reallyfile(register char *filename) { /* Don't like to hard-wire it like this, but it seems to ** be the only realistic approach. Don't worry, you can't ** do an ASSIGN to any of these. If you could, we would have ** to check all volumenodes on the DeviceList stored in ** the RootNode of the DosLibrary. (Got that?!) */ static char *duds[] = { "NIL:", "CON:", "RAW:", "PRT:", "PAR:", "SER:" }; register short i; /* A simple, linear search is employed */ for (i = 0; i < sizeof(duds)/sizeof(char *); i++) { register short foundit; register char *cmp, *cmp2; register short j; cmp = duds[i]; cmp2 = filename; for (foundit = 1, j = 0; j < 4; j++) { if (toupper(*cmp++) != toupper(*cmp2++)) { foundit = 0; break; } } if (foundit) return(0); } /* One last check, just to be sure */ if (!strcmp(filename,"*")) return(0); return(1); } /* When somebody opens a PP file, we decrunch it into a temporary RAM file ** and return a filehandle to that file. When the caller closes the file ** (which it thinks is the original disk file), it will really be closing ** the temporary RAM file. This is a good chance for us to get rid of it, ** so that RAM: won't get crowded in time. HOWEVER! If the caller has ** written new data into the file, we have to rewrite the temporary file ** over the original (disk) file. flushout() does exactly that. */ flushout(register struct filenode *fn) { register BPTR orighandle; /* First of all, we have to open the original file. We're in ** trouble if this is not possible... */ if (orighandle = RealOpen(fn->orig_filename, MODE_NEWFILE)) { char buffer[4096]; /* should suffice when copying */ register short readlen; /* from a fast device like RAM: */ Seek(fn->filehandle, 0, -1); do { readlen = Read(fn->filehandle, buffer, 4096); RealWrite(orighandle, buffer, readlen); } while (readlen == 4096); RealClose(orighandle); } } /* This is the new Open() functions. All future calls to the DOS Open() ** function will be rerouted through here. */ BPTR NewOpen(register char *filename, register mode) { UBYTE *memgot; int filelen; register BPTR tempfh = (BPTR)0; register struct Task *thistask; register struct caller *thiscaller; /* We only deal with a few of the incoming calls: ** ** 1) We can't do anything about new files ** 2) Equally, we don't care about CON: or NIL: file open requests ** 3) If we have seen the calling task before, the one who is making ** the request must be ppLoadData. It is absolutely vital that ** we forward this request to the original DOS code. Otherwise ** we would end up in an infinite (recursive) loop, with ppLoadData ** calling NewOpen calling ppLoadData ... ** 4) If we cannot add a caller to the list of callers (see 3), we ** ignore the call. If we miss a few in a low memory situation, ** so be it. */ if ( mode == MODE_NEWFILE || !reallyfile(filename) || findcaller(thistask = FindTask(0)) || !addcaller(thistask) ) return(RealOpen(filename,mode)); /* Now, ask ppLoadData to bring in the file */ if (!ppLoadData(filename,DECR_NONE,0,&memgot,&filelen,(int (*)())0)) { char filnambuf[40]; register char *t, *m; /* Generate a name for the temporary file in RAM */ t = filename; if (m = (char *)index(t,':')) t = m+1; while (m = (char *)index(t,'/')) t = m+1; strcpy(filnambuf,"RAM:"); strcat(filnambuf,t); strcat(filnambuf,".tmp"); /* We have to ensure that the name is unique on RAM: */ while (tempfh = RealOpen(filnambuf,MODE_OLDFILE)) { char *xtra = "?"; /* Pad the name with random characters. This ** should do the trick */ RealClose(tempfh); *xtra = 'A' + (rand() % 26); strcat(filnambuf,xtra); } /* Now, open the RAM file and flush data we loaded into ** this file. */ if (tempfh = RealOpen(filnambuf,MODE_NEWFILE)) { /* Remember that WE created that file */ if (!addfilenode(tempfh,filnambuf,filename)) { /* Couln't do it. Simulate a "Can't open ** file" from the real Open(). */ RealClose(tempfh); DeleteFile(filnambuf); tempfh = (BPTR)0; } else { /* Flush out the file. Probably should do ** a check on RealWrite... Oh well. */ RealWrite(tempfh,(char *)memgot,filelen); Seek(tempfh,0,-1); } } /* Housekeeping */ FreeMem(memgot,filelen); } /* We no longer have to worry about this caller */ thiscaller = findcaller(thistask); Remove((struct Node *)thiscaller); FreeMem(thiscaller, sizeof(*thiscaller)); /* Return a filehandle to the file */ return(tempfh); } /* Yep! A new Write() function. We have to know if a process has updated ** the (substitute) file we created for it. If true, mark the file as ** being "dirty", so that we can later save it over the original PP file. */ int NewWrite(register BPTR filehandle, register char *buffer, int length) { register struct filenode *fn; register wrtret; wrtret = RealWrite(filehandle, buffer, length); if (fn = findfilenode(filehandle)) fn->dirty = 1; return(wrtret); } /* A new Close() function. It removes non-dirty, temporary files from ** RAM, and it keeps track of which files have to be updated back onto ** disk. */ void NewClose(register BPTR filehandle) { register struct filenode *fn; if (fn = findfilenode(filehandle)) { if (fn->dirty) flushout(fn); RealClose(filehandle); DeleteFile(fn->ram_filename); Remove((struct Node *)fn); FreeMem(fn,sizeof(*fn)); } else RealClose(filehandle); } /* A new Examine() function. Often, programs examine a file before opening ** it. This way, they can allocate just enough memory to hold the entire ** file. However, we have to correct Examine() calls to PowerPacked files, ** so that the correct amount of memory will be allocated by the caller. */ int NewExamine(BPTR lock, struct FileInfoBlock *fib) { int ppmatchtag; int decrunchinfo; register examinereturn; register BPTR tmpfh; struct FileInfoBlock fib_backup; /* Start off by examining the lock */ if (!(examinereturn = RealExamine(lock,fib))) return(0); /* If it's a directory, or if it's pp_LoadData, never mind */ if (fib->fib_DirEntryType >= 0 || findcaller(FindTask(0))) return(examinereturn); /* The lock target was a simple file. Check to see if it's a ** PP file */ fib_backup = *fib; tmpfh = RealOpen(fullname(lock,fib),MODE_OLDFILE); *fib = fib_backup; if (!tmpfh) return(examinereturn); Seek(tmpfh,0,-1); Read(tmpfh,(char *)&ppmatchtag,sizeof(int)); if (ppmatchtag == PP20 || ppmatchtag == PX20) { /* It was. Examine decrunchinfo to get at the original ** filesize, so that programs trying to allocate enough ** memory to hold a certain file will get the correct ** filesize (which is, of corz, size of the decrunched ** file!) */ Seek(tmpfh,-4,1); Read(tmpfh,(char *)&decrunchinfo,sizeof(int)); fib->fib_Size = decrunchinfo >> 8; } RealClose(tmpfh); return(examinereturn); } /* Hangaround() simply waits forever for a message from Intuition. When we ** get one, it can only be a CLOSEWINDOW request. */ hangaround() { WaitPort(win->UserPort); GetMsg(win->UserPort); } /* Entry point */ void main() { openstuff(); hangaround(); die(0); }