/* pp_patcher v1.3 ** ** PP_Patcher is a small utility which enables programs to get at ** PowerPacked datafiles, 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 datafiles appear as 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). Of course, 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 ** increase the size of the diskbuffers (using 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. ** ** Freeware 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 #include #include "ppbase.h" /* enclosed in this directory */ /* Comment out the following line if you don't like autodetaching programs! */ #define DETACHED /* Well, the compiler has the inline-code ability -- why not use it? */ #define strlen _BUILTIN_strlen #define strcpy _BUILTIN_strcpy /* Some defines which make the source code look pretty */ typedef struct PPBase PPBASE; /* 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 by the Open()'er. */ struct filenode { struct MinNode mn; BPTR filehandle; char *new_filename, *orig_filename; short dirty; }; /* One of these for each caller to NewOpen() */ struct caller { struct MinNode mn; struct Task *tc; }; /* For inter-process communication */ typedef struct { struct Message Msg; /* Additional parameters will live here, eventually */ } MYMSG; /* Global data */ struct MinList templist, callers; struct Window *win; struct IntuitionBase *IntuitionBase; struct GfxBase *GfxBase; struct PPBase *PPBase; int patched, wbmode; char temppath[MAXPATHLEN]; struct MsgPort *pp_port; char *pp_portname = "pp.port"; #ifdef DETACHED /* Exported to detach module */ int _stack = 4000; int _priority = 5; char *_procname = "Powerpacker Patcher"; long _BackGroundIO = 1; /* We want stdio */ extern BPTR _Backstdout; #else #define _Backstdout Output() #endif /* Misc */ char GBANNER[] = "Powerpacker Patcher V1.1, Copyright (C) 1991, Michael Berg\n"; char WBBANNER[] = "Just double-click on my icon, or do an extended selection \ on a disk or a\ndrawer icon (this tells me where to put temporary files)\n"; char CLIBANNER[] = "Syntax: PP [] (temppath defaults to RAM:)\n"; char DEFPATH[] = "RAM:"; void climsg(register char *what) { if (_Backstdout) Write(_Backstdout,what,strlen(what)); } void wbmsg(register char *what) { register BPTR fh; if (fh = Open("CON:10/88/620/80/PP Notices:",MODE_NEWFILE)) { char dummy; Write(fh,what,strlen(what)); Write(fh,"Press \n",15); Read(fh,&dummy,1); Close(fh); } } /* Small puts() function (doesn't do a newline, though). If running from ** CLI, print the message in the CLI window. If running from WB, show the ** message in a small console window. */ void Say(register char *what) { if (wbmode) wbmsg(what); else #ifndef DETACHED climsg(what); #else { register char *cr = "\r"; climsg(cr); climsg(what); } #endif } /* CreatePort() should exist in the standard library, but here it is, just ** in case your library lacks this function (it isn't in ROM) */ struct MsgPort *myCreatePort(register char *name, register pri) { register UBYTE sigbit; register struct MsgPort *gotten; if ( gotten = (struct MsgPort *)AllocMem ( sizeof(*gotten), MEMF_CLEAR | MEMF_PUBLIC ) ) { if ((sigbit = AllocSignal(-1)) != -1) { gotten->mp_Node.ln_Name = name; gotten->mp_Node.ln_Pri = pri; gotten->mp_Node.ln_Type = NT_MSGPORT; gotten->mp_Flags = PA_SIGNAL; gotten->mp_SigBit = sigbit; gotten->mp_SigTask = FindTask(0); AddPort(gotten); return(gotten); } else FreeMem((char *)gotten,sizeof(*gotten)); } return(NULL); } /* DeletePort is also supposed to be in your standard library. Here it is, ** just in case... */ void myDeletePort(register struct MsgPort *p) { RemPort(p); FreeSignal(p->mp_SigBit); FreeMem((char *)p,sizeof(*p)); } /* Universal termination code */ void die(char *errmsg) { /* Print the (optional) error message */ if (errmsg) Say(errmsg); /* Get rid of our port */ if (pp_port) myDeletePort(pp_port); /* Restore the original DOS functions. You will not find these ** functions in the sourcecode. They are generated by the assembler ** macro DOSLibPatch -- see asmsup.c */ if (patched) { Forbid(); RestOpen(); RestClose(); RestExamine(); RestWrite(); Permit(); } /* Close libraries */ if (PPBase) CloseLibrary(PPBase); /* Finally! */ exit(errmsg? 20 : 0); } /* Open up all required libraries */ void openlibs() { if (!(PPBase = (PPBASE *)OpenLibrary("powerpacker.library",0))) die("You need powerpacker.library V33+\n"); } /* 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); } void doport() { if (!(pp_port = myCreatePort(pp_portname,0))) die("Couldn't create a message port\n"); } /* passargs passes messages to the 'PP' already running somewhere */ void passargs(register ac, register char **av) { register MYMSG *m; if (m = (MYMSG *)AllocMem(sizeof(MYMSG),MEMF_CLEAR)) { register struct Process *myself; myself = (struct Process *)FindTask(0); m->Msg.mn_Node.ln_Type = NT_MESSAGE; m->Msg.mn_Length = sizeof(MYMSG); m->Msg.mn_ReplyPort = &myself->pr_MsgPort; /* Passing of future parameters go here */ /* Tell him the bad news */ PutMsg(pp_port,(struct Message *)m); /* Receiver will free the message (he MUST NOT reply to it!) */ } else Say("Not enough memory for interprocess communication\n"); } void finalizepath() { register char c; c = temppath[strlen(temppath)-1]; if (c != ':' && c != '/') strcat(temppath,"/"); } void badstartup() { die(wbmode? WBBANNER : CLIBANNER); } void checkokpath() { register BPTR lock; register char *c; register allok = 0; char testdir[MAXPATHLEN]; strcpy(testdir,temppath); c = testdir + strlen(testdir) - 1; if (*c == '/') *c = '\0'; if (lock = Lock(testdir,ACCESS_READ)) { register struct FileInfoBlock *fib; if (fib = AllocMem(sizeof(*fib),MEMF_CLEAR)) { if (Examine(lock,fib)) allok = (fib->fib_DirEntryType >= 0); FreeMem(fib,sizeof(*fib)); } UnLock(lock); } if (!allok) die("Could not validate your path selection\n"); } void buildfromlock(register BPTR lock, register char *name) { register struct FileInfoBlock *fib; register BPTR olddir,newlock; char *fullname(); olddir = CurrentDir(lock); if (!(newlock = Lock(name,ACCESS_READ))) { CurrentDir(olddir); die("Could not get a lock on your specified PATH\n"); } if (fib = AllocMem(sizeof(*fib),MEMF_CLEAR)) { register retval; if (retval = Examine(newlock,fib)) strcpy(temppath,fullname(newlock,fib)); UnLock(newlock); CurrentDir(olddir); FreeMem(fib,sizeof(*fib)); if (!retval) die("Cannot examine your PATH specification\n"); } else { UnLock(newlock); die("Running low on memory\n"); } } /* Executed when PP starts up the very first time */ void doinitargs(register ac, register char **av) { register char c; register struct WBArg *arg; char debugstr[40]; extern struct WBStartup *WBenchMsg; /* So far, the only thing you can tell PP is where to put all ** the temporary files. This defaults to RAM: when no argument ** is given. Workbench argument passing is fully supported. */ switch (ac) { case 0 : /* Workbench */ switch (WBenchMsg->sm_NumArgs) { case 1 : strcpy(temppath,DEFPATH); break; case 2 : arg = &WBenchMsg->sm_ArgList[1]; if (arg->wa_Lock) buildfromlock(arg->wa_Lock,arg->wa_Name); else /* Should never happen */ strcpy(temppath,arg->wa_Name); break; default: badstartup(); } break; case 1 : strcpy(temppath,DEFPATH); break; case 2 : strcpy(temppath,av[1]); break; default: badstartup(); } finalizepath(); checkokpath(); } /* Open up everything */ void openstuff(register ac, register char **av) { if (pp_port = FindPort(pp_portname)) { /* There's already a working copy of PP running somewhere. ** Pass the arguments along to it, and then exit. */ passargs(ac,av); /* Don't let die() remove the port */ pp_port = NULL; die(NULL); } /* This is the first time around. Install everything */ doinitargs(ac,av); openlibs(); initlist(); installpatch(); doport(); /* Tell the world the good news */ Say(GBANNER); } /* 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 'temppath' 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->new_filename = AllocMem(strlen(filename)+1,0)) && (memgot->orig_filename = AllocMem(strlen(orig)+1,0)) ) { memgot->filehandle = fn; strcpy(memgot->new_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 (patched? RealExamine(parentlock,fib) : Examine(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 = ':'; } else strcat(pathandfile,":"); /* 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 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 file. This is a good chance for us to get rid of it, ** so that the temporary directory 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. */ void 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[2048]; /* Should suffice */ register short readlen; Seek(fn->filehandle, 0, -1); do { readlen = Read(fn->filehandle, buffer, 2048); RealWrite(orighandle, buffer, readlen); } while (readlen == 2048); RealClose(orighandle); } } /* Look for a powerpacker matchtag at the beginning of a file. Returns TRUE ** if the file was a powerpacker datafile. */ isppfile(register BPTR fh) { int ppmatchtag; /* This function leaves the file position ptr. at 0 for non-PP files */ Seek(fh,0,-1); Read(fh,(char *)&ppmatchtag,sizeof(int)); if (ppmatchtag == PP20 || ppmatchtag == PX20) return(1); else { Seek(fh,0,-1); return(0); } } /* 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; register struct Task *thistask; register struct caller *thiscaller; tempfh = RealOpen(filename,mode); /* We only deal with a few of the incoming calls: ** ** 1) Files which CAN in fact be opened ** ** 2) We can't do anything about new files ** ** 3) Equally, we don't care about CON: or NIL: file open requests ** ** 4) We don't care about non-crunched files ** ** 5) 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 ... ** ** 6) 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 ( !tempfh || mode == MODE_NEWFILE || !reallyfile(filename) || !isppfile(tempfh) || findcaller(thistask = FindTask(0)) || !addcaller(thistask) ) return(tempfh); /* We won't be needing the original file handle anymore */ RealClose(tempfh); tempfh = (BPTR)0; /* Now, ask ppLoadData to bring in the file */ if (!ppLoadData(filename,DECR_NONE,0,&memgot,&filelen,(int (*)())0)) { char filnambuf[MAXPATHLEN]; register char *t, *m; /* Generate a name for the temporary file */ t = filename; if (m = (char *)index(t,':')) t = m+1; while (m = (char *)index(t,'/')) t = m+1; strcpy(filnambuf,temppath); strcat(filnambuf,t); strcat(filnambuf,".tmp"); /* We have to ensure that the name is unique on 'temppath' */ 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 temporary 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 ** 'temppath', 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->new_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 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); if (isppfile(tmpfh)) { /* 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); } maydie() { if (callers.mlh_Head->mln_Succ) return(0); else return(1); } /* Hangaround() waits for messages to arrive at our port, and then deals ** with them. This routine could be written in a much smaller version, but ** I've written it so that it will be easy to add more 'commands'. */ hangaround() { register MYMSG *m; FOREVER { WaitPort(pp_port); while (m = (MYMSG *)GetMsg(pp_port)) { /* Eventually, some kind of actual communication ** will take place here. In this version, we simply ** quit whenever we spot an incomming message. */ /* Note! MUST NOT reply! (Caller may already have ** terminated, since he doesn't WaitPort()) */ FreeMem(m,sizeof(*m)); if (maydie()) die("PP V1.1: Terminated.\n"); else Say("PP V1.1: Can't terminate just yet.\n"); } } } /* Entry point */ void main(int argc, char *argv[]) { wbmode = (argc == 0); openstuff(argc,argv); hangaround(); /* Never returns */ }