home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Frozen Fish 1: Amiga
/
FrozenFish-Apr94.iso
/
bbs
/
alib
/
d5xx
/
d542
/
pp.lha
/
PP
/
PP.C
< prev
next >
Wrap
C/C++ Source or Header
|
1991-09-19
|
23KB
|
967 lines
/* 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 <functions.h>
#include <exec/execbase.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <exec/memory.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <intuition/intuition.h>
#include <workbench/startup.h>
#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>] (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 <RETURN>\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 */
}