Author Topic: Installing a patch locally in one program only  (Read 2910 times)

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Installing a patch locally in one program only
« on: October 02, 2020, 09:07:36 AM »
I search a source code example where an INIT installs a patch in a single program instead of globally, system-wide. I have 2 new extensions that aim to make REALbasic and CodeWarrior more readable, especially on a CRT screen. They slow your computer down because the patch has to check in which program it's running and this has to be done everytime bold text is being drawn. When it draws text you notice that it's slower. The impact on other programs could be eliminated by installing the patch only in REALbasic or CodeWarrior.

Offline IIO

  • Platinum Member
  • *****
  • Posts: 4443
  • just a number
Re: Installing a patch locally in one program only
« Reply #1 on: October 02, 2020, 12:03:15 PM »
I search a source code example where an INIT installs a patch in a single program instead of globally, system-wide

awesome idea.

though i am afraid that many, many programs use apple APIs for stuff like navigation, windows, fonts.... but it could work by switching between two modes i think, i.e. replacing something with X and also with Y where Y is identical with the system suitcase or whatever ... you know like running "platinum" using kaleidoscope.

there are several INITs which allow to exclude specific apps ... like default folder if i am not wrong. not sure if that helps?
insert arbitrary signature here

Offline Daniel

  • Gold Member
  • *****
  • Posts: 300
  • Programmer, Hacker, Thinker
Re: Installing a patch locally in one program only
« Reply #2 on: October 03, 2020, 06:32:58 PM »
I search a source code example where an INIT installs a patch in a single program instead of globally, system-wide. I have 2 new extensions that aim to make REALbasic and CodeWarrior more readable, especially on a CRT screen. They slow your computer down because the patch has to check in which program it's running and this has to be done everytime bold text is being drawn. When it draws text you notice that it's slower. The impact on other programs could be eliminated by installing the patch only in REALbasic or CodeWarrior.

Your INIT should patch a routine that is called once and only once at the start of every program, like InitMenus.

The global InitMenus patch will do the program name checks and conditionally install the patch you actually care about. You will be inside a Process at that point, and any patches made will be local to that Process.

You will still waste time in every program, but only when the programs are started.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Installing a patch locally in one program only
« Reply #3 on: October 06, 2020, 11:17:32 AM »
The InitMenus patch will waste very little time.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Installing a patch locally in one program only
« Reply #4 on: October 07, 2020, 01:35:40 PM »
It works, but I patched InitDialogs instead of InitMenus because it comes after the other initializations.

As a simple exercise, I draw an L over every oval in AppleWorks.

First I make a 'C68K' code resource 128 which will install the FrameOval patch.

Code: [Select]
//========================================
// File: FrameOvalCR.h
//========================================

#pragma once

void main();

pascal void New_FrameOval(const Rect* const r);

Code: [Select]
//========================================
// File: FrameOvalCR.cp
//========================================

#include "FrameOvalCR.h"

// Rem: The next include may not be precompiled
// because it contains inline assembler.
#include <SetupA4.h>

FrameOvalProcPtr old_FrameOval;

void main()
{
    ProcPtr old_address;
    ProcPtr new_address;

    old_address=::GetToolTrapAddress(_FrameOval);
    old_FrameOval=reinterpret_cast<FrameOvalProcPtr>(old_address);
    new_address=reinterpret_cast<ProcPtr>(New_FrameOval);
    ::SetToolTrapAddress(new_address,
                         _FrameOval);
}

pascal void New_FrameOval(const Rect* const r)
{
    EnterCallback();

    old_FrameOval(r);

    const SInt16 top=r->top;
    const SInt16 left=r->left;
    const SInt16 bottom=QD(r->bottom-1);
    const SInt16 right=QD(r->right-1);

    ::MoveTo(left,
             top);
    ::LineTo(left,
             bottom);
    ::LineTo(right,
             bottom);

    ExitCallback();
}

I link this into my extension.
Its patch verifies whether we are in AppleWorks.
Then it copies 'C68K' resource 128 into AppleWorks's zone.
Then it executes 'C68K' resource 128's main.

Code: [Select]
//========================================
// File: FrameOvalBOBO68KINIT.h
//========================================

#pragma once

int main();

Bool Run();

void InstallPatch();

pascal void New_InitDialogs(void *ignored);

Bool TryToInstallPatch();

bool IsTargetApName();

Bool SearchExtension(FSSpec &spec);

Bool LoadCodeResource(const FSSpec &spec,
                      Handle &h);

Bool LoadCodeResource(OSType type,
                      SInt16 id,
                      Handle &h);

Code: [Select]
//========================================
// File: FrameOvalBOBO68KINIT.cp
//========================================

#include "FrameOvalBOBO68KINIT.h"

// Rem: The next includes may not be precompiled
// because they contain inline assembler.
#include <A4Stuff.h>
#include <SetupA4.h>

InitDialogsProcPtr old_InitDialogs;

int main()
{
    EnterCodeResource();
    PrepareCallback();

    NoGuiError::Init_class();

    const SInt16 iconID=Run() ? INIT::goodIconID : INIT::badIconID;
    if (!INIT::DrawINITIcon(iconID))
        {
        Error::Show("\pDrawINITIcon fails");
        }

    ExitCodeResource();
    return 0;
}

Bool Run()
{
    // 1. Me.
    Handle me;
    if (!INIT::GetINITHandle(me))
        {
        Return_false("\pGetINITHandle fails");
        }

    // 2. Patch.
    InstallPatch();

    return True;
}

void InstallPatch()
{
    ProcPtr old_address;
    ProcPtr new_address;

    old_address=::GetToolTrapAddress(_InitDialogs);
    old_InitDialogs=reinterpret_cast<InitDialogsProcPtr>(old_address);
    new_address=reinterpret_cast<ProcPtr>(New_InitDialogs);
    ::SetToolTrapAddress(new_address,
                         _InitDialogs);
}

pascal void New_InitDialogs(void* const ignored)
{
    EnterCallback();

    old_InitDialogs(ignored);

    if (!TryToInstallPatch())
        {
        Error::Show("\pTryToInstallPatch fails");
        }

    ExitCallback();
}

Bool TryToInstallPatch()
{
    if (!IsTargetApName())
        {
        return True;
        }

    FSSpec spec;
    if (!SearchExtension(spec))
        {
        Return_false("\pSearchExtension fails");
        }

    Handle h;
    if (!LoadCodeResource(spec,
                          h))
        {
        Return_false("\pLoadCodeResource fails");
        }

    typedef pascal void (*MainProcPtr)();
    MainProcPtr const proc=reinterpret_cast<MainProcPtr>(*h);
    proc();

    return True;
}

bool IsTargetApName()
{
    static ConstStringPtr const targetApName="\pAppleWorks";
    ConstStringPtr x=targetApName;
    ConstStringPtr const last=x+*x;
    ConstStringPtr y=LMGetCurApName();
    while (x<=last)
        {
        if (*x!=*y)
            {
            return false;
            }
        x++;
        y++;
        }
    return true;
}

Bool SearchExtension(FSSpec &spec)
{
    SInt16 vRefNum;
    SInt32 dirID;
    OSErr err=::FindFolder(kOnSystemDisk,
                           kExtensionFolderType,
                           false,
                           &vRefNum,
                           &dirID);
    if (err==fnfErr)
        {
        Return_false("\pExtension folder doesn't exist");
        }
    if (err)
        {
        Return_false_err("\pFindFolder fails",err);
        }

    bool exists;
    if (!MakeSpec(vRefNum,
                  dirID,
                  "\pFrame oval BOBO 68K",
                  spec,
                  exists))
        {
        Return_false("\pMakeSpec fails");
        }
    if (!exists)
        {
        // Rem: The extension was moved, renamed or deleted.
        Return_false("\pExtension doesn't exist");
        }

    return True;
}

Bool LoadCodeResource(const FSSpec &spec,
                      Handle &h)
{
    const SInt16 refNum=::FSpOpenResFile(&spec,
                                         fsRdPerm);
    OSErr err=::ResError();
    if (err)
        {
        Return_false_err("\pFSpOpenResFile fails",err);
        }

    const Bool ok=LoadCodeResource('C68K',
                                   128,
                                   h);

    ::CloseResFile(refNum);
    err=::ResError();
    if (err)
        {
        Return_false_err("\pCloseResFile fails",err);
        }

    if (!ok)
        {
        Return_false("\pLoadCodeResource fails");
        }

    return True;
}

Bool LoadCodeResource(const OSType type,
                      const SInt16 id,
                      Handle &h)
{
    if (!Resource::Get1(type,
                        id,
                        h))
        {
        Return_false("\pGet1 fails");
        }

    if (!Resource::Detach(h))
        {
        Return_false("\pDetach fails");
        }

    // Rem: Because we are now in a program,
    // we can move the handle to the end,
    // which cannot be done in a normal extension.
    ::HLockHi(h);
    const OSErr err=::MemError();
    if (err)
        {
        Return_false_err("\pHLockHi fails",err);
        }

    return True;
}

Instead of IsTargetApName I tried also the following, but it crashed when Finder started:

Code: [Select]
    bool isBOBO;
    if (!CheckProcess(isBOBO))
        {
        Return_false("\pCheckProcess fails");
        }
    if (!isBOBO)
        {
        return True;
        }

Bool CheckProcess(bool &isBOBO)
{
    ProcessSerialNumber psn;
    OSErr err=::GetCurrentProcess(&psn);
    if (err)
        {
        Return_false_err("\pGetCurrentProcess fails",err);
        }

    ProcessInfoRec info;
    err=::GetProcessInformation(&psn,
                                &info);
    if (err)
        {
        Return_false_err("\pGetProcessInformation fails",err);
        }

    isBOBO=(info.processSignature=='BOBO');

    return True;
}

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Installing a patch locally in one program only
« Reply #5 on: October 11, 2020, 06:21:44 AM »
The PPC version works too. I'm updating all my extensions. Some of them get a 'pref' resource where you can choose the programs in which you want to install the patch.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Installing a patch locally in one program only
« Reply #6 on: October 17, 2020, 03:02:29 PM »
I wrote
Code: [Select]
typedef pascal void (*MainProcPtr)(); but "pascal" should be omitted here. It doesn't make a difference here because main has parameters nor return value.

See the solution in the attachment.

Offline OS923

  • Platinum Member
  • *****
  • Posts: 888
Re: Installing a patch locally in one program only
« Reply #7 on: October 19, 2020, 06:54:16 AM »
I updated my Macros extension. You can now use different macros for the programs of your choice.