Reinstallable.c

/*
 *  Sample reinstallable init
 *  v 1.0
 *
 *  August 1993  Greg Robbins
 *
 *  This sample INIT patches a trap globally yet is reinstallable:
 *  it can be recompiled and run without rebooting.
 *
 *  Usually, when an INIT patches a trap, changing the INIT requires
 *  reinstalling the INIT in the Extensions folder and rebooting.
 *  This INIT demonstrates a technique which allows new INIT code to
 *  replace most of the old code without the developer having to reboot.
 *
 *  This INIT just patches Standard File (_Pack3) and beeps when
 *  a standard file dialog is raised.
 *
 *  Demonstration:
 *
 *  ¥ put the Reinstallable Init in the Extensions folder and reboot
 *  ¥ bring up a standard file dialog; notice the beep
 *  ¥ drag the Reinstallable twoBeep INIT file onto the
 *    LaunchInits program
 *  ¥ bring up a standard file dialog again and notice both beeps 
 *
 *  The trick is to keep a global handle in the system heap containing 
 *  the addresses of the routines we want executed for each
 *  patched item.  The handle to the globals is available via Gestalt.
 *
 *  A permanently installed dispatcher routine (part of the init code)
 *  is used to get the address of our patch routine and jump to it.
 *
 *  The second time the init code runs, it will use the existing global
 *  handle, and just update the addresses of our patch routines so the
 *  dispatcher code jumps to the correct places.
 *
 *  A couple of critical items:
 *    - The init loaded at boot time contains the patch dispatchers, so
 *      it must never be disposed of.  We will simply strand it in the
 *      heap when the first reinstallation occurs.
 *    - If we change anything about which routines are patched, this invalidates
 *      the global handle struct format, so we must reboot
 *    - Similarly, if the dispatching code is changed, we must reboot
 *      since only the dispatching code from the first time the init is
 *      installed can be used.
 *    - The dispatch and patch routines destroy registers, so they
 *      may not be suitable for OS patches
 *
 *  Globals:
 *
 *  Since this code only uses a single true global (a handle to our
 *  globals in the system heap) I used a simpler method than Think C's
 *  A4 globals.  The global handle is stored smack in the middle of the
 *  GetGlobalsHandleFromStorage routine. Calling GGHFS with a handle
 *  stores the handle there; calling GGHFS with no parameter returns
 *  the stored handle.  (The handle can also be retrieved from
 *  Gestalt, but that is too slow a method for use in a trap patch.)
 *  
 */
 
#define SystemSevenOrLater 1    // leave out unnecessary glue code
 
#include <Memory.h>
#include <GestaltEqu.h>
#include <StandardFile.h>
#include <Resources.h>
#include <Events.h>
#include <SysEqu.h>
#include <Traps.h>
 
#define kOptionKeyCode 0x3A
 
#define kReinstInitSelector 'RŽi–'  // something suitably obscure
 
// global types
 
typedef struct Globals {
    Size    globalsSize;    // size of this struct
    Handle  initHandle;     // handle for this INIT if reinstalled
    
    // traps and hooks
    ProcPtr pack3TrueAddr;  // old trap address
    ProcPtr pack3PatchAddr; // my patch routine address
    
    ProcPtr reinstInitGestaltFunctionAddr;
                            // address of my Gestalt function
    
    // other globals can go here
    
} Globals, *GlobalsPtr, **GlobalsHandle;
 
// prototypes
 
Boolean KeyIsDown(short);
GlobalsHandle GetGlobalsHandleFromStorage(GlobalsHandle);
void SetGlobalsHandleToStorage(GlobalsHandle *);
void ReinstInitGestaltDispatch(void);
void Pack3PatchDispatch(void);
pascal OSErr ReinstInitGestaltFunction (OSType, long *);
void Pack3Patch(void);
 
 
 
// installation routine
 
main()
{
 
    Handle          initHandle, previousInitHandle;
    GlobalsHandle   gHandle;
    Boolean         installedFlag, firstInstallFlag;
    OSErr           retCode;
    long            gestaltResponse;
    
    // Think C points A0 at the start of the block;
    // use it to get the handle to the INIT resource
    asm {
        RecoverHandle
        MOVE.L  A0, initHandle
    }
 
    // never hurts to lock the block of code we are in
    HLock(initHandle);
    
    // since this INIT uses the "lazy" installation method
    // (it's just a detached resource) it's critical that
    // the INIT resource had its system heap bit 
    // set.  I won't check for that here.
    
    // flag that this has not yet successfully installed
    installedFlag = false;
 
    // don't install if the Option key is depressed
    if (KeyIsDown(kOptionKeyCode))
        goto Bail;
    
    
    // check that System 7 is available
    retCode = Gestalt(gestaltSystemVersion, &gestaltResponse);
    if (retCode != noErr || gestaltResponse < 0x0700)
        goto Bail;
 
    // find if my global struct already exists
    retCode = Gestalt(kReinstInitSelector, &gestaltResponse);
    
    if (retCode != noErr || gestaltResponse == 0) {
        
        // this is the first installation
        
        // install Gestalt selector to return handle of our globals 
        retCode = NewGestalt(kReinstInitSelector, 
            ReinstInitGestaltDispatch);
        if (retCode != noErr) goto Bail;
    
        // no existing globals, so allocate them
 
        gHandle = (GlobalsHandle) NewHandleSys(sizeof(Globals));
        if (gHandle == nil) goto Bail;
 
        // record the size of the globals
        (**gHandle).globalsSize = sizeof(Globals);
        
        // since this is the first install, this INIT's handle
        // must never be disposed to protect our dispatchers
 
        (**gHandle).initHandle = nil;
 
        // we are not reinstalling the INIT
        firstInstallFlag = true;
    }
    else {
        
        // this is a reinstallation
        
        // get the handle for the existing globals
        gHandle = (GlobalsHandle) gestaltResponse;
 
        // ensure that the globals block is the current size
    
        if ((**gHandle).globalsSize != sizeof(Globals)) {
        
            SetHandleSize((Handle) gHandle, sizeof(Globals));
            retCode = MemError();
            if (retCode != noErr) goto Bail;
        }
        
        // update or reset any globals here
        
        // get rid of the last incarnation of the INIT code
        // (unless it was the first)
        
        previousInitHandle = (**gHandle).initHandle;
        if (previousInitHandle != nil)
            DisposeHandle(previousInitHandle);
        
        // save the handle to this version of the INIT
        
        (**gHandle).initHandle = initHandle;
        
        // we are reinstalling the INIT
        firstInstallFlag = false;
    }
 
    // okay, we're committed to installation    
    installedFlag = true;
    
    // make the INIT float free
    DetachResource(initHandle);
    HLock(initHandle);
 
    // save the globals handle into the GetGlobalsHandleFromStorage code
    (void) GetGlobalsHandleFromStorage(gHandle);
    
    // patch _Pack3 trap (Standard File Package)
    
    if (firstInstallFlag) {
    
        // save the old trap address, then point the trap at my dispatch
        // function
        
        (**gHandle).pack3TrueAddr = (ProcPtr) GetToolTrapAddress(_Pack3);
        SetToolTrapAddress((long) Pack3PatchDispatch, _Pack3);
    }
    
    // store the address of my patch in the globals
    // so the dispatcher knows where to go
    
    (**gHandle).pack3PatchAddr = Pack3Patch;
    
    // store the address of my gestalt function in the globals
    (**gHandle).reinstInitGestaltFunctionAddr = ReinstInitGestaltFunction;
 
    
 
Bail:
    
    // could do a ShowINIT here based on the installedFlag
    if (!installedFlag) DebugStr("\p Reinstallable INIT installation failed");
}
 
 
// Dispatch routines
//
// These routines are the targets of any trap and hook addresses we
// installed at INIT time (the Gestalt function as well)
//
// This code cannot change or move until reboot.  We will strand the
// first version of the INIT code that is installed to ensure
// that these routines remain present.  These routines are not
// used in the reinstalled code (i.e. the routines from the first 
// install will continue to be called)
//
// If a lot of trap patches are in place, a more general dispatcher
// might be appropriate
 
void ReinstInitGestaltDispatch()
{
    // dispatch to my Gestalt routine
    
    // destroys D0, A0
    asm {
        MOVE.L  #0, -(SP)                   ; parameter to GetGlobalsHandle...
        JSR     GetGlobalsHandleFromStorage
        ADDQ.L  #4, SP                      ; dispose of parameter
        MOVE.L  D0, A0                      ; deref the globals handle
        MOVE.L  (A0), A0                    ;   to get the routine address
        MOVE.L  Globals.reinstInitGestaltFunctionAddr(A0), A0
        JMP     (A0)                        ; jump to the routine
    }
}
 
void Pack3PatchDispatch()
{
    // dispatch to my Pack3 trap patch
    // destroys A0, D0
    asm {
        MOVE.L  #0, -(SP)
        JSR     GetGlobalsHandleFromStorage
        ADDQ.L  #4, SP
        MOVE.L  D0, A0
        MOVE.L  (A0), A0
        MOVE.L  Globals.pack3PatchAddr(A0), A0;
        JMP     (A0)
    }
}
 
//
// Patch routines and other meat code
//
 
void Pack3Patch()
{
    // patch to Standard File
    // just beeps before the dialog is raised
    
    SysBeep(10);
    
    asm {
        // get the address of the real standard file trap
        // and jump to it
        
        MOVE.L  #0, -(SP)                   ; parameter to GetGlobalsHandle...
        JSR     GetGlobalsHandleFromStorage
        ADDQ.L  #4, SP                      ; dispose of parameter
        MOVE.L  D0, A0                      ; deref the globals handle
        MOVE.L  (A0), A0                    ; to get the real trap address
        MOVE.L  Globals.pack3TrueAddr(A0), A0
        JMP     (A0)                        ; jump to it
    }
}
 
// this Gestalt function returns the address of the globals block
pascal OSErr ReinstInitGestaltFunction (OSType selector, long * response)
{
    *response = (long) GetGlobalsHandleFromStorage(nil);
 
    return noErr;
}
 
 
// Keith Rollin's utility for using GetKeys from C
// returns true if the Key with the given KeyCode is down
 
Boolean KeyIsDown(short keyCode)
{
    union {
        KeyMap asMap;
        unsigned char asBytes[16];
    } myMap;
 
    GetKeys(myMap.asMap);
    return ((myMap.asBytes[keyCode >> 3] >> 
        (keyCode & 0x07)) & 1) != 0;
}
 
// GetGlobalsHandleFromStorage lets us magically store a handle
// in the code's block of memory
//
// passing in a value for gHandle saves the value; calling this
// routine with nil later retrieves the value
 
GlobalsHandle GetGlobalsHandleFromStorage(GlobalsHandle gHandle)
{
    GlobalsHandle *gHandlePtr;
    
    // get the address of our handle storage space
    
    asm {
        BSR     @1                  ; push the address of the saved space
        
        DC.L    0                   ; the saved handle will go here
        
    @1
        MOVE.L  (SP)+, gHandlePtr   ; pop the address off of the stack
    }
    
    // use the pointer to store the handle if we were passed a handle
    if (gHandle) *gHandlePtr = gHandle;
    
    // return the handle that is there now
    return *gHandlePtr;
}