master
Jeffrey Paul 5 years ago
commit 1f23cecf7f
  1. 492
      Bonus Junk/MegaBoot.c
  2. 1
      Open Firmware/install.f
  3. 1
      Open Firmware/ramboot
  4. 1
      Open Firmware/ramboot-src
  5. 0
      Patch Partition/BuildpDES/BuildpDES
  6. BIN
      Patch Partition/BuildpDES/BuildpDES Data/BuildpDES/TargetDataMacOS.tdt
  7. BIN
      Patch Partition/BuildpDES/BuildpDES Data/CWSettingsMacOS.stg
  8. BIN
      Patch Partition/BuildpDES/BuildpDES.SYM
  9. 49
      Patch Partition/BuildpDES/BuildpDES.c
  10. BIN
      Patch Partition/BuildpDES/BuildpDES.mcp
  11. BIN
      Patch Partition/BuildpDES/Ephemerboot Installer
  12. 0
      Patch Partition/BuildpDES/TemplatepDES.rsrc
  13. BIN
      Patch Partition/Ephemerboot Data/CWSettingsMacOS.stg
  14. BIN
      Patch Partition/Ephemerboot Data/MegaBoot/TargetDataMacOS.tdt
  15. 698
      Patch Partition/Ephemerboot.c
  16. BIN
      Patch Partition/Ephemerboot.mcp
  17. 0
      Patch Partition/EphemerbootPatch.rsrc
  18. 1
      Read Me Second.txt
  19. 1
      machack speech.txt

@ -0,0 +1,492 @@
#include <InterruptSafeDebug.h>
#include <TradDriverLoaderLib.h>
#include <A4Stuff.h>
#pragma parameter __D0 VMMap( __A0, __A1 )
OSErr VMMap( UInt32 log, UInt32 phys ) TWOWORDINLINE( 0x7007, 0xfe0a );
#pragma parameter __D0 VMUnmap( __A0, __A1 )
OSErr VMUnmap( UInt32 log, UInt32 key ) TWOWORDINLINE( 0x7008, 0xfe0a );
UInt8 gWindowStor[ 0x1fff ];
UInt32 gWindowPage;
struct {
UInt8 flags[4];
DrvQEl dqe;
} gMyDQE;
UInt32 gActualSize = 0x08000000 >> 9;
typedef struct PhysRun {
UInt32 startPage;
UInt32 numBytes;
} PhysRun;
PhysRun gRuns[] = { { 0x0c000000 >> 12, 0x08000000 } };
int gNumRuns = 1;
extern OSErr __Startup__();
extern void DriverHeader();
extern void main(void);
extern void CallBootResource( Handle );
extern OSErr DriverOpen();
extern OSErr DriverClose();
extern OSErr DriverPrime( IOParamPtr:__a0, DCtlPtr:__a1 );
extern OSErr DriverStatus( CntrlParamPtr:__a0, DCtlPtr:__a1 );
extern OSErr DriverControl( CntrlParamPtr:__a0, DCtlPtr:__a1 );
extern void NumToHex( UInt32, UInt8 * );
void PrintTrap( UInt16 trap );
void UltimatePatch();
asm OSErr __Startup__() {
subq #4, sp // gimme that return ptr back!
pea (a3)
_DetachResource // no return value
move.l a4, -(sp)
jsr SetCurrentA4 // boot block calling conventions are pretty freestyle
jsr main
move.l (sp)+, a4
move.l (sp), a0
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
move.w (a0)+, -(sp)
jsr PrintTrap
addq #2, sp
// move.w #dsSystemFileErr, d0;
move.w #resNotFound, d0;
rts
}
ExitCodeResource();
}
asm void DriverHeader() {
dc.w dNeedLockMask | dStatEnableMask | dCtlEnableMask | dReadEnableMask | dWritEnableMask
dc.w 0 // delay
dc.w 0 // evt mask
dc.w 0 // menu
dc.w 0x1a
dc.w 0x1e
dc.w 0x28
dc.w 0x32
dc.w 0x3c
dc.b "\p.RAMB0"
@open:
jmp DriverOpen
@prime:
movem.l a0/a1, -(sp)
jsr DriverPrime
bra.s @finish
@control:
movem.l a0/a1, -(sp)
jsr DriverControl
bra.s @finish
@status:
movem.l a0/a1, -(sp)
jsr DriverStatus
bra.s @finish
@close:
jmp DriverClose
@finish:
movem.l (sp)+, a0/a1
move.w 6 (a0), d1 // PB.ioTrap
btst # noQueueBit, d1
bne.s @rtn
move.l 0x8fc, -(sp) // jIODone
@rtn:
move.w d0, 0x10 (a0) // PB.ioResult
rts
}
extern OSErr DriverOpen() {
return noErr;
}
void PrintTrap( UInt16 trap ) {
UInt8 trapstr[8];
EnterCodeResource();
NumToHex( trap, trapstr );
ISDebugText( trapstr+4, 4 );
ExitCodeResource();
}
asm void UltimatePatch() {
movem.l a0/a1/d0/d1, -(sp)
move.l 0x12 (sp), a0
move.w (a0), -(sp)
jsr PrintTrap
addq #2, sp
movem.l (sp)+, a0/a1/d0/d1
jmp 0x80000000
}
asm long SetCurrentA4() {
lea __Startup__,a0
adda.l #305419896,a0
move.l a0,d0
exg d0,a4
rts
}
extern OSErr DriverPrime( IOParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
OSErr err = noErr;
UInt32 ioPage, ioPen;
int bcnt;
EnterCodeResource();
ISDebugStr( ( pb->ioTrap & 0xff ) == aRdCmd? "\pI" : "\pO" );
pb->ioActCount = 0;
VMUnmap( gWindowPage, 1 );
while ( pb->ioActCount < pb->ioReqCount ) {
UInt32 maxTrans, actTrans;
Ptr disk, buff;
for ( bcnt = 0, ioPen = dce->dCtlPosition + pb->ioActCount; bcnt < gNumRuns && gRuns[bcnt].numBytes <= ioPen; bcnt++ ) {
ioPen -= gRuns[bcnt].numBytes;
}
if ( bcnt == gNumRuns ) {
err = ioErr;
break;
}
VMMap( gWindowPage, gRuns[bcnt].startPage + ( ioPen >> 12 ) );
maxTrans = 0x1000 - ( ioPen & 0xfff );
actTrans = pb->ioReqCount - pb->ioActCount;
actTrans = (maxTrans<actTrans)? maxTrans : actTrans;
disk = (Ptr) ( gWindowPage << 12 ) + ( ioPen & 0xfff );
buff = pb->ioBuffer + pb->ioActCount;
if ( pb->ioTrap & 0xff == aRdCmd ) {
BlockMoveData( disk, buff, actTrans );
} else {
BlockMoveData( buff, disk, actTrans );
}
VMUnmap( gWindowPage, 1 );
pb->ioActCount += actTrans;
}
ISDebugStr( "\pDone" );
ExitCodeResource();
return err;
}
extern OSErr DriverControl( CntrlParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
EnterCodeResource();
ISDebugStr( "\pC" );
ExitCodeResource();
return controlErr;
}
extern OSErr DriverStatus( CntrlParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
EnterCodeResource();
ISDebugStr( "\pS" );
ExitCodeResource();
return statusErr;
}
extern OSErr DriverClose
void main( void ) {
OSErr err = noErr;
VCBPtr bootV;
SInt16 bootD;
CntrlParam pb;
SInt16 myRN;
UInt32 dlong;
EnterCodeResource();
InitInterruptSafeDebug();
* ( (UInt32 *) ( (UInt32) UltimatePatch + 0x16 ) ) = * (UInt32 *) 0x28;
//* ( (UInt32 *) 0x28 ) = (UInt32) &UltimatePatch;
ISDebugStr( "\pRAMB03" );
Delay( 200, &dlong );
gWindowPage = ( (UInt32) &gWindowStor + 0xfff ) >> 12;
HoldMemory( (Ptr) ( gWindowPage << 12 ), 0x1000 );
VMUnmap( gWindowPage, 1 );
ISDebugStr( "\pphysical fenestration" );
{ // close the system file & knock the resource manager silly
//CloseResFile( LMGetSysMap() );
/*LMSetTopMapHndl( NULL );
LMSetCurMap( 1 );*/
LMSetSysMapHndl( NULL );
LMSetSysMap( 1 );
}
{ // unmount the boot vol
ISDebugStr( "\pSys closed" );
pb.ioCompletion = NULL;
bootV = (VCBPtr) GetVCBQHdr()->qHead;
bootD = bootV->vcbDrvNum;
pb.ioVRefNum = bootV->vcbVRefNum;
pb.ioNamePtr = NULL;
//err = PBUnmountVol( (ParmBlkPtr) &pb );
}
if ( err == noErr ) { // install our drvr
ISDebugStr( "\pVol unmounted" );
err = TradInstallDriverFromPtr( (DRVRHeaderPtr) &DriverHeader, 48, 127, &myRN );
}
if ( err == noErr ) {
err = OpenDriver( "\p.RAMB0", &myRN );
}
if ( err == noErr ) { // find the boot drive & link in after it
DrvQElPtr dqe;
SInt16 highDrive = 0;
for ( dqe = (DrvQElPtr) GetDrvQHdr()->qHead; dqe->dQDrive != bootD; dqe = (DrvQElPtr) dqe->qLink ) {
if ( dqe->dQDrive > highDrive ) highDrive = dqe->dQDrive;
}
gMyDQE.dqe.qLink = dqe->qLink;
dqe->qLink = (QElemPtr) &gMyDQE.dqe;
for ( dqe = (DrvQElPtr) gMyDQE.dqe.qLink; dqe; dqe = (DrvQElPtr) dqe->qLink ) {
if ( dqe->dQDrive > highDrive ) highDrive = dqe->dQDrive;
}
gMyDQE.flags[0] = 0; // unlocked
gMyDQE.flags[1] = 8; // nonejectable
gMyDQE.flags[2] = 0; // reserved
gMyDQE.flags[3] = 0; // n/a
gMyDQE.dqe.dQDrive = highDrive + 1;
gMyDQE.dqe.dQRefNum = myRN;
gMyDQE.dqe.dQFSID = 0;
gMyDQE.dqe.dQDrvSz = (UInt16) gActualSize;
gMyDQE.dqe.dQDrvSz2 = (UInt32) gActualSize >> 16;
PrintTrap( myRN );
}
if ( err == noErr ) {
HVolumeParam vpb;
DrvQElPtr dqe;
vpb.ioVRefNum = gMyDQE.dqe.dQDrive;
vpb.ioVolIndex = 0;
vpb.ioNamePtr = NULL;
vpb.ioCompletion = 0;
err = PBMountVol( (ParmBlkPtr) &vpb );
if ( err == noErr ) {
ISDebugStr( "\pmounted RAM disk" );
err = PBHGetVInfoSync( (HParmBlkPtr) &vpb );
}
for ( dqe = (DrvQElPtr) GetDrvQHdr()->qHead; dqe && ( err || vpb.ioVFndrInfo[0] == 0 ); dqe = (DrvQElPtr) dqe->qLink ) {
err = PBUnmountVol( (ParmBlkPtr) &vpb );
vpb.ioVRefNum = dqe->dQDrive;
err = PBMountVol( (ParmBlkPtr) &vpb );
if ( vpb.ioVRefNum == bootD ) err = dsBadStartupDisk; // no recursion!
vpb.ioVolIndex = 0;
if ( err == noErr ) {
ISDebugStr( "\pmounted something else" );
err = PBHGetVInfoSync( (HParmBlkPtr) &vpb );
}
}
if ( dqe == NULL ) err = dsBadStartupDisk;
/* pb.ioVRefNum = 15; //gMyDQE.dqe.dQDrive;
err = PBMountVol( (ParmBlkPtr) &pb );
}
if ( err == noErr ) {
HVolumeParam vpb;
vpb.ioVRefNum = pb.ioVRefNum;
vpb.ioVolIndex = 0;
vpb.ioNamePtr = 0;
vpb.ioCompletion = 0;
err = PBHGetVInfoSync( (HParmBlkPtr) &vpb );*/
if ( err == noErr ) {
LMSetBootDrive( vpb.ioVDrvInfo );
( (WDPBPtr) &vpb )->ioWDDirID = vpb.ioVFndrInfo[0];
( (WDPBPtr) &vpb )->ioWDProcID = 'ERIK'; // Erik... that's me ;v)
err = PBOpenWDSync( (WDPBPtr) &vpb );
}
if ( err == noErr ) {
ISDebugStr( "\pFound a System Folder" );
err = PBSetVolSync( (ParmBlkPtr) &vpb );
}
}
if ( err == noErr ) {
ISDebugStr( "\pSet the sys folder" );
Delay( 60, &dlong );
/*if ( InitResources() < 0 ) {
err = dsSystemFileErr;
ISDebugStr( "\pNo InitResources() for U" );
}*/
LMSetSysMap( OpenResFile( LMGetSysResName() ) );
Delay( 60, &dlong );
if ( LMGetSysMap() == 0 ) err = fnfErr;
}
if ( err == noErr ) {
Handle boot2;
OSType type;
SInt16 id, rx = 1;
UInt8 check[8];
LMSetSysMapHndl( LMGetTopMapHndl() );
do {
boot2 = GetIndResource( 'boot', rx );
err = ResError();
GetResInfo( boot2, &id, &type, check );
PrintTrap( id );
PrintTrap( rx );
NumToHex( * (UInt32 *) *boot2, check );
ISDebugText( check, 8 );
rx++;
} while( err == noErr && * (UInt32 *) *boot2 != 0x204ba025 );
Delay( 300, &dlong );
ISDebugStr( "\pCalling boot" );
if ( err == noErr ) {
* ( (UInt32 *) 0x28 ) = (UInt32) &UltimatePatch;
CallBootResource( boot2 );
}
}
{
UInt8 errstr[8];
NumToHex( err, errstr );
ISDebugText( errstr, 8 );
ISDebugStr("\preturning");
Delay( 600, &dlong );
}
ExitCodeResource();
}
asm void CallBootResource( Handle boot2 ) {
movea.l 4 (sp), a3
movea.l (a3), a0
jmp (a0)
}
void NumToHex( UInt32 num, UInt8 *hex ) {
int ncnt;
for ( ncnt = 0; ncnt < 8; ncnt++ ) {
int nibble = ( num >> (28-ncnt*4) ) & 0x0f;
nibble += (nibble>9)?('a'-10):'0';
hex[ncnt] = nibble;
}
}
/*if ( err == noErr ) {
pb.ioVRefNum = gMyDQE.dqe.dQDrive;
err = PBMountVol( (ParmBlkPtr) &pb );
}
if ( err == noErr ) {
ISDebugStr( "\pMounted" );
SetVol( NULL, pb.ioVRefNum );*/
/*void main(void) {
Ptr windowStor, window;
UInt32 lcnt;
SetCurrentA4();
InitInterruptSafeDebug();
ISDebugStr( "\pHello" );
Delay(60, &lcnt);
window = (Ptr) ( ( (UInt32) &gWindowStor + 0xfff ) & ~0xfff );
HoldMemory( window, 0x1000 );
VMUnmap( ( (UInt32) window ) >> 12, 1 );
VMMap( ( (UInt32) window ) >> 12, 0 );
for ( lcnt = 0; lcnt < 4; lcnt++ ) {
UInt8 hstr[9];
*hstr = 8;
NumToHex( ( (UInt32 *) window )[lcnt], hstr + 1 );
ISDebugStr( hstr );
}
VMUnmap( ( (UInt32) window ) >> 12, 1 );
UnholdMemory( window, 0x1000 );
//Debugger();
Delay(600, &lcnt);
}*/() {
return noErr;
}

@ -0,0 +1 @@
\ Open Firmware script to install Ephemerboot hex 70.0.74.77.a unselect-dev " true" " use-nvramrc?" $setenv " : ram-boot "" hd:,"(5c):tbxi"" $boot ;" nvramrc $setenv " ram-boot" " boot-command" $setenv " 04000000" " ramboot-disk-size" $setenv ." Set nv vars" cr

@ -0,0 +1 @@
<CHRP-BOOT> <COMPATIBLE> MacRISC </COMPATIBLE> <DESCRIPTION> Pre-bootloader clips visible RAM to allocate a RAM disk. </DESCRIPTION> <BOOT-SCRIPT> hex 70.0.74.77.a unselect-dev " /options" find-package drop constant _options " /memory" find-package drop constant _memory 2.00000 constant reboot-reserved-space : boot-tbxj " &device;:&partition;,"(5c):tbxj" 2dup open-dev if $boot then ." RAMBoot: Deferring to selected physical disk. 2drop mac-boot ; struct 4 field &gt;next-run 4 field &gt;start-addr 4 field &gt;byte-count constant /phys-run variable first-phys-run first-phys-run " reg" _memory get-package-property drop begin ( run addr len ) dup while decode-int &gt;r ( run addr len ) rot ( addr len run ) /phys-run alloc-mem dup &gt;r ( addr len run newrun ) swap ! ( addr len ) r&gt; ( addr len newrun ) r&gt; over &gt;start-addr ! ( addr len newrun ) -rot decode-int &gt;r ( newrun addr len ) rot ( addr len newrun ) r&gt; over &gt;byte-count ! dup &gt;start-addr @ ." Encoded range " . dup &gt;byte-count @ ." , " . cr &gt;next-run -rot repeat 2drop off first-phys-run @ begin dup &gt;next-run @ ?dup while nip repeat &gt;r r@ &gt;byte-count @ reboot-reserved-space - dup r@ &gt;byte-count ! r&gt; &gt;start-addr @ + constant reserved-run-start 0 first-phys-run @ begin tuck &gt;byte-count @ + swap &gt;next-run @ ?dup 0= until constant total-ram " ramboot-disk-size" _options get-package-property if ." RAMBoot: could not get prefs from NVRAM" boot-tbxj then \ decode-int decode-string $number drop constant disk-ram 2drop : encode-int+ encode-int encode+ ; : encode-start &gt;start-addr @ encode-int+ ; : encode-count &gt;byte-count @ encode-int+ ; 0 0 encode-bytes total-ram disk-ram - dup 0&lt; if 3drop ." RAMBoot: specified size larger than installed memory." boot-tbxj then first-phys-run @ begin ( addr len rem run ) tuck &gt;byte-count @ ( addr len run rem count ) over &lt; while ( addr len run rem ) &gt;r &gt;r ( addr len ) r@ encode-start r@ encode-count r&gt; r&gt; ( addr len run rem ) over &gt;byte-count @ - swap ( addr len rem run ) &gt;next-run @ repeat swap &gt;r -rot ( rem addr len ) r@ encode-start 2 pick encode-int+ reserved-run-start encode-int+ reboot-reserved-space encode-int+ " /memory" find-device " reg" 2dup delete-property property unselect-dev ( rem ) ." done with /memory" cr dup r@ &gt;start-addr @ + encode-int rot ( addr len rem ) r@ &gt;byte-count @ swap - ?dup 0&lt;&gt; if encode-int+ else 2drop 0 0 encode-bytes then r&gt; &gt;next-run @ ( addr len run ) begin ?dup while &gt;r r@ encode-start r@ encode-count r&gt; &gt;next-run @ repeat " /chosen" find-device " ramboot-phys-ranges" property unselect-dev " /options" find-device " ramboot-checksum" _options get-package-property 0= if " ramboot-checksum" 2dup delete-property property then unselect-dev boot-tbxj </BOOT-SCRIPT> </CHRP-BOOT> 

@ -0,0 +1 @@
hex 70.0.74.77.a unselect-dev " /options" find-package drop constant _options " /memory" find-package drop constant _memory 2.00000 constant reboot-reserved-space : boot-tbxj " &device;:&partition;,"(5c):tbxj" 2dup open-dev if $boot then ." RAMBoot: Deferring to selected physical disk. 2drop mac-boot ; struct 4 field >next-run 4 field >start-addr 4 field >byte-count constant /phys-run variable first-phys-run first-phys-run " reg" _memory get-package-property drop begin ( run addr len ) dup while decode-int >r ( run addr len ) rot ( addr len run ) /phys-run alloc-mem dup >r ( addr len run newrun ) swap ! ( addr len ) r> ( addr len newrun ) r> over >start-addr ! ( addr len newrun ) -rot decode-int >r ( newrun addr len ) rot ( addr len newrun ) r> over >byte-count ! dup >start-addr @ ." Encoded range " . dup >byte-count @ ." , " . cr >next-run -rot repeat 2drop off first-phys-run @ begin dup >next-run @ ?dup while nip repeat >r r@ >byte-count @ reboot-reserved-space - dup r@ >byte-count ! r> >start-addr @ + constant reserved-run-start 0 first-phys-run @ begin tuck >byte-count @ + swap >next-run @ ?dup 0= until constant total-ram " ramboot-disk-size" _options get-package-property if ." RAMBoot: could not get prefs from NVRAM" boot-tbxj then \ decode-int decode-string $number drop constant disk-ram 2drop : encode-int+ encode-int encode+ ; : encode-start >start-addr @ encode-int+ ; : encode-count >byte-count @ encode-int+ ; 0 0 encode-bytes total-ram disk-ram - dup 0< if 3drop ." RAMBoot: specified size larger than installed memory." boot-tbxj then first-phys-run @ begin ( addr len rem run ) tuck >byte-count @ ( addr len run rem count ) over < while ( addr len run rem ) >r >r ( addr len ) r@ encode-start r@ encode-count r> r> ( addr len run rem ) over >byte-count @ - swap ( addr len rem run ) >next-run @ repeat swap >r -rot ( rem addr len ) r@ encode-start 2 pick encode-int+ reserved-run-start encode-int+ reboot-reserved-space encode-int+ " /memory" find-device " reg" 2dup delete-property property unselect-dev ( rem ) ." done with /memory" cr dup r@ >start-addr @ + encode-int rot ( addr len rem ) r@ >byte-count @ swap - ?dup 0<> if encode-int+ else 2drop 0 0 encode-bytes then r> >next-run @ ( addr len run ) begin ?dup while >r r@ encode-start r@ encode-count r> >next-run @ repeat " /chosen" find-device " ramboot-phys-ranges" property unselect-dev " /options" find-device " ramboot-checksum" _options get-package-property 0= if " ramboot-checksum" 2dup delete-property property then unselect-dev boot-tbxj

@ -0,0 +1,49 @@
//BuildpDES.c
//©5/18/02 David "Potatoswatter" Krauss
#include <SCSI.h>
extern UInt16 DriverChecksum( Handle dH ) {
UInt32 bcnt;
UInt32 sum;
sum = 0;
for ( bcnt = 0; bcnt < GetHandleSize( dH ); bcnt++ ) {
sum += (UInt8) *( *dH + bcnt );
sum <<= 1;
if ( sum & 0x10000 ) sum |= 1;
}
if ( (UInt16) sum == 0 ) {
sum = -1;
PtrToXHand( &sum, dH, 1 );
return DriverChecksum( dH );
}
return sum;
}
void main( void ) {
Handle patch;
PatchDescriptor **pd;
patch = Get1Resource( 'ptch', 128 );
DetachResource( patch );
pd = (PatchDescriptor **) Get1Resource( 'pDES', 128 );
DetachResource( (Handle) pd );
(**pd).patchDescriptorLen = ( GetHandleSize((Handle)pd) + 1 ) & ~1;
(**pd).patchCRC = DriverChecksum( patch );
(**pd).patchSize = GetHandleSize( patch );
OpenResFile( "\pEphemerboot Installer" );
RemoveResource( Get1Resource( 'pDES', 128 ) );
AddResource( (Handle) pd, 'pDES', 128, "\p" );
RemoveResource( Get1Resource( 'ptch', 128 ) );
AddResource( patch, 'ptch', 128, "\p" );
}

@ -0,0 +1,698 @@
//RAMBoot.c - boot 9 from RAM.
//©5/18/02 - 5/27/02 David "Potatoswatter" Krauss - v1.0.0c1
#include <InterruptSafeDebug.h>
#include <TradDriverLoaderLib.h>
#include <MoreDisks.h>
#include <A4Stuff.h>
#pragma parameter __D0 VMMap( __A0, __A1 )
OSErr VMMap( UInt32 log, UInt32 phys ) TWOWORDINLINE( 0x7007, 0xfe0a );
#pragma parameter __D0 VMUnmap( __A0, __A1 )
OSErr VMUnmap( UInt32 log, UInt32 valid ) TWOWORDINLINE( 0x7008, 0xfe0a );
Ptr gWindowStor;
UInt32 gWindowPage;
Ptr gDiskWindowPtr;
Ptr gSumWindowPtr, gSum2WindowPtr;
struct {
UInt8 flags[4];
DrvQEl dqe;
} gMyDQE;
UInt32 gActualSize = 0;
typedef struct PhysRun {
UInt32 startPage;
UInt32 numBytes;
} PhysRun;
PhysRun *gRuns;
UInt32 gNumRuns;
enum {
kPageSize = 0x1000,
kPageBits = 0xfffff000,
kSumBoxVolume = kPageSize, // one page per sum box
kSumBoxVolumeLog2 = 12,
kSumBoxSide = 8, // box is square array of lattices
kSumBoxSideLog2 = 3,
kSumBoxDimensions = 4, // plus one more for depth
kSumBoxDepth = 1, // bytes, tho the true depth is in bits
kSumBytesPerPage = kSumBoxSide * kSumBoxDimensions * kSumBoxDepth,
kSumRatio = kSumBoxVolume / kSumBytesPerPage,
kMaxDimensions = kSumBoxDimensions
};
Ptr gCurSumPtr;
UInt32 gCurSumPhys;
UInt32 gSumBase;
Ptr gCurSum2Ptr;
UInt32 gCurSum2Phys;
UInt32 gSum2Base;
Ptr gCurSum3Ptr;
Ptr gSum3Ptr;
UInt32 gSum3Size;
Ptr gSum3Str;
UInt32 gSum3StrSize;
const char gTreeChosen[] = "Devices:device-tree:chosen";
const char gPhysRunsProp[] = "ramboot-phys-ranges";
const char gTreeOptions[] = "Devices:device-tree:options";
const char gSumPropertyName[] = "ramboot-checksum";
RegEntryID gOptionsEntry;
extern pascal OSErr main(void);
extern void DriverHeader();
extern OSErr DriverOpen();
extern OSErr DriverClose();
extern OSErr DriverPrime( IOParamPtr:__a0, DCtlPtr:__a1 );
extern OSErr DriverStatus( CntrlParamPtr:__a0, DCtlPtr:__a1 );
extern OSErr DriverControl( CntrlParamPtr:__a0, DCtlPtr:__a1 );
extern pascal OSErr GestaltProc( UInt32, SInt32 * );
extern void MapDisk( UInt32, Boolean );
extern void UnmapDisk( void );
extern void WriteDiskChecksum( void );
extern void MakeDiskChecksum( Ptr in, Ptr out );
extern void MakeChecksum( Ptr in, Ptr out, UInt32 side, int dimensions );
extern void CorrectDisk( Ptr, Ptr, Ptr, UInt32, int );
extern void PrintHex( UInt32, int );
void PrintHex( UInt32 num, int size ) {
char out[8];
int ccnt = size;
while ( ccnt-- > 0 ) {
int nibble;
nibble = ( num >> ( ccnt * 4 ) ) & 0xf;
out[7-ccnt] = (nibble<10)? nibble+'0' : nibble-10+'a';
}
ISDebugText( (UInt8 *) out + 8 - size, size );
}
extern pascal OSErr main() {
OSErr err = noErr;
SInt16 myRN;
Boolean diskInited;
SInt32 dl;
EnterCodeResource();
InitInterruptSafeDebug();
ISDebugStr( "\pRAMB05" );
if ( GetToolTrapAddress( 0xabe9 ) == GetToolTrapAddress( _Unimplemented ) ) err = paramErr;
if ( err == noErr ) {
OSErr gerr;
gerr = Gestalt( '¨mb0', &dl );
if ( gerr == noErr ) {
ISDebugStr( "\pRAMBoot: deferred by Gestalt" );
err = paramErr;
} else err = NewGestalt( '¨mb0', &GestaltProc );
}
if ( err == noErr ) {
RegEntryID chosenRE;
RegistryEntryIDInit( &chosenRE );
{
err = RegistryCStrEntryLookup( NULL, gTreeChosen, &chosenRE );
}
if ( err == noErr ) {
err = RegistryPropertyGetSize( &chosenRE, gPhysRunsProp, &gNumRuns );
if ( err ) ISDebugStr( "\pRAMBoot: RAM disk space not reserved. Please reinstall." );
}
if ( err == noErr ) {
gRuns = (PhysRun *) NewPtrSys( gNumRuns );
err = MemError();
}
if ( err == noErr ) {
int rcnt;
err = RegistryPropertyGet( &chosenRE, gPhysRunsProp, gRuns, &gNumRuns );
gNumRuns /= sizeof( PhysRun );
for ( rcnt = 0; rcnt < gNumRuns; rcnt++ ) {
gRuns[rcnt].startPage /= 0x1000;
}
for ( rcnt = 0; rcnt < gNumRuns; rcnt++ ) {
gActualSize += gRuns[rcnt].numBytes;
}
}
RegistryEntryIDDispose( &chosenRE );
}
if ( err == noErr ) {
gWindowStor = NewPtrSys( kPageSize * 4 - 1 ); // 3 pages + padding
err = MemError();
if ( err == noErr ) {
gDiskWindowPtr = (Ptr) ( ( (UInt32) gWindowStor + kPageSize-1 ) & kPageBits );
LockMemory( gDiskWindowPtr, kPageSize * 3 );
gWindowPage = (UInt32) gDiskWindowPtr / kPageSize;
PrintHex( gWindowPage, 8 );
gSumWindowPtr = gDiskWindowPtr + kPageSize;
gSum2WindowPtr = gSumWindowPtr + kPageSize;
}
if ( err == noErr ) {
PhysRun *lastRun = &gRuns[gNumRuns-1];
UInt32 sumSize, sum2Size;
#define PaddedSumSize( x ) ( ( ( ( ( x + kPageSize-1 ) / kPageSize ) * kSumBytesPerPage ) + kPageSize-1 ) & kPageBits )
sumSize = PaddedSumSize( gActualSize - ( gActualSize / kSumRatio ) );
sum2Size = PaddedSumSize( sumSize );
gActualSize -= sumSize + sum2Size;
lastRun->numBytes -= sumSize + sum2Size;
gSumBase = ( lastRun->startPage * kPageSize ) + lastRun->numBytes;
gSum2Base = gSumBase + sumSize;
gSum3Size = ( sum2Size / kPageSize ) * kSumBytesPerPage;// PaddedSumSize( sum2Size );
gSum3StrSize = gSum3Size * 2;
PrintHex( gSum3StrSize, 4 );
#undef PaddedSumSize
}
if ( err == noErr ) {
RegistryEntryIDInit( &gOptionsEntry );
if ( err == noErr ) {
err = RegistryCStrEntryLookup( NULL, gTreeOptions, &gOptionsEntry );
}
if ( err == noErr ) {
gSum3Ptr = NewPtrSysClear( gSum3Size );
err = MemError();
}
if ( err == noErr ) {
gSum3Str = NewPtrSysClear( gSum3StrSize );
err = MemError();
}
if ( err == noErr ) {
UInt32 actSize = gSum3StrSize;
err = RegistryPropertyGet( &gOptionsEntry, gSumPropertyName, gSum3Str, &actSize );
if ( err==noErr && actSize != gSum3StrSize ) {
ISDebugStr( "\pSum3 size changed?!" );
PrintHex( actSize, 4 );
}
}
if ( err == noErr ) {
int bcnt;
Ptr pen = gSum3Str;
for ( bcnt = 0; bcnt < gSum3Size; bcnt++ ) {
int nibble = *pen++;
nibble -= nibble>='a'? 'a'-10 : '0';
gSum3Ptr[bcnt] = nibble << 4;
nibble = *pen++;
nibble -= nibble>='a'? 'a'-10 : '0';
gSum3Ptr[bcnt] |= nibble;
}
}
if ( err == nrNotFoundErr ) {
err = noErr;
diskInited = false;
ISDebugStr( "\pRAMBoot: Master checksum not found. RAM disk data may be damaged." );
} else diskInited = true;
}
}
if ( err == noErr /*&& diskInited*/ ) {
UInt32 bcnt;
char badSum[ kSumBytesPerPage ];
UnmapDisk();
if ( diskInited ) {
ISDebugStr( "\pCorrecting checksum l2" );
for ( bcnt = 0; bcnt < gActualSize; bcnt += kPageSize * kSumRatio * kSumRatio ) {
MapDisk( bcnt, true );
MakeDiskChecksum( gSum2WindowPtr, badSum );
PrintHex( *(UInt32*) gCurSum3Ptr, 8 );
PrintHex( *(UInt32*) badSum, 8 );
CorrectDisk( gSum2WindowPtr, gCurSum3Ptr, badSum, kSumBoxSide, kSumBoxDimensions );
UnmapDisk();
}
}
ISDebugStr( "\pCorrecting checksum l1" );
for ( bcnt = 0; bcnt < gActualSize; bcnt += kPageSize * kSumRatio ) {
MapDisk( bcnt, true );
MakeDiskChecksum( gSumWindowPtr, badSum );
CorrectDisk( gSumWindowPtr, gCurSum2Ptr, badSum, kSumBoxSide, kSumBoxDimensions );
UnmapDisk();
}
ISDebugStr( "\pCorrecting disk" );
for ( bcnt = 0; bcnt < gActualSize; bcnt += kPageSize ) {
MapDisk( bcnt, true );
MakeDiskChecksum( gDiskWindowPtr, badSum );
PrintHex( *(UInt32*) badSum, 8 );
CorrectDisk( gDiskWindowPtr, gCurSumPtr, badSum, kSumBoxSide, kSumBoxDimensions );
UnmapDisk();
}
}
if ( err == noErr ) {
gActualSize /= 512;
err = TradInstallDriverFromPtr( (DRVRHeaderPtr) &DriverHeader, 48, 127, &myRN );
}
if ( err == noErr ) {
ISDebugStr( "\pinstalled drvr" );
err = OpenDriver( "\p.RAMB0", &myRN );
}
if ( err == noErr ) { // find the boot drive & link in after it
gMyDQE.flags[0] = 0; // unlocked
gMyDQE.flags[1] = 8; // nonejectable
gMyDQE.flags[2] = 0; // reserved
gMyDQE.flags[3] = 0; // n/a
gMyDQE.dqe.dQDrive = MoreFindFreeDriveNumber( 8 );
gMyDQE.dqe.dQRefNum = myRN;
gMyDQE.dqe.dQFSID = 0;
gMyDQE.dqe.qType = 1;
gMyDQE.dqe.dQDrvSz = (UInt16) gActualSize;
gMyDQE.dqe.dQDrvSz2 = (UInt32) gActualSize >> 16;
Enqueue( (QElemPtr) &gMyDQE.dqe, GetDrvQHdr() );
if ( /*!LMGetBootDrive() &&diskInited */true ) {
ISDebugStr( "\pSet boot" );
LMSetBootDrive( gMyDQE.dqe.dQDrive );
}
ISDebugStr( "\pLoaded drvr" );
}
ExitCodeResource();
return noErr;
}
asm void DriverHeader() {
dc.w dNeedLockMask | dStatEnableMask | dCtlEnableMask | dReadEnableMask | dWritEnableMask | dNeedTimeMask
dc.w 0 // delay
dc.w 0 // evt mask
dc.w 0 // menu
dc.w 0x1a
dc.w 0x1e
dc.w 0x28
dc.w 0x32
dc.w 0x3c
dc.b "\p.RAMB0"
@open:
jmp DriverOpen
@prime:
movem.l a0/a1, -(sp)
jsr DriverPrime
bra.s @finish
@control:
movem.l a0/a1, -(sp)
jsr DriverControl
bra.s @finish
@status:
movem.l a0/a1, -(sp)
jsr DriverStatus
bra.s @finish
@close:
jmp DriverClose
@finish:
movem.l (sp)+, a0/a1
move.w 6 (a0), d1 // PB.ioTrap
btst # noQueueBit, d1
bne.s @rtn
move.l 0x8fc, -(sp) // jIODone
@rtn:
move.w d0, 0x10 (a0) // PB.ioResult
rts
}
extern OSErr DriverOpen() {
return noErr;
}
extern OSErr DriverPrime( IOParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
OSErr err = noErr;
UInt32 ioPen;
int bcnt;
EnterCodeResource();
//ISDebugStr( ( pb->ioTrap & 0xff ) == aRdCmd? "\pR" : (/*Debugger(),*/ "\pW") );
pb->ioActCount = 0;
while ( pb->ioActCount < pb->ioReqCount ) {
UInt32 maxTrans, actTrans;
Ptr disk, buff;
Boolean read;
read = ( pb->ioTrap & 0xff ) == aRdCmd;
ioPen = dce->dCtlPosition + pb->ioActCount;
MapDisk( ioPen, !read );
maxTrans = kPageSize - ( ioPen & (kPageSize-1) );
actTrans = pb->ioReqCount - pb->ioActCount;
actTrans = (maxTrans<actTrans)? maxTrans : actTrans;
disk = gDiskWindowPtr + ( ioPen & 0xfff );
buff = pb->ioBuffer + pb->ioActCount;
//PrintHex( actTrans, 4 );
//PrintHex( ioPen, 8 );
//PrintHex( gRuns[bcnt].startPage + ( ioPen >> 12 ), 5 );
//ISDebugText( (UInt8*) disk, 200 );
//ISDebugText( (UInt8*) buff, 4 );
if ( read ) {
BlockMoveData( disk, buff, actTrans );
} else {
BlockMoveData( buff, disk, actTrans );
WriteDiskChecksum();
}
UnmapDisk();
pb->ioActCount += actTrans;
}
//ISDebugStr( "\pDone" );
if ( err ) ISDebugStr( "\p(err)" );
ExitCodeResource();
return err;
}
extern OSErr DriverControl( CntrlParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
OSErr err;
EnterCodeResource();
//ISDebugStr( "\pC" );
switch( pb->csCode ) {
case kFormat: {
{
gSum3Ptr = NewPtrSysClear( gSum3Size );
err = MemError();
if ( err == noErr ) {
RegistryPropertyDelete( &gOptionsEntry, gSumPropertyName );
err = RegistryPropertyCreate( &gOptionsEntry, gSumPropertyName, gSum3Ptr, gSum3Size );
}
}
if ( err == noErr ) {
UInt32 pcnt, lcnt;
for ( pcnt = 0; pcnt < gActualSize * 512; pcnt += kPageSize ) {
MapDisk( pcnt, true );
for ( lcnt = 0; lcnt < kPageSize; lcnt += sizeof(long) ) * (UInt32 *) &gDiskWindowPtr[lcnt] = 0;
UnmapDisk();
}
for ( pcnt = 0; pcnt < gActualSize * 512; pcnt += kPageSize * kSumRatio ) {
MapDisk( pcnt, true );
for ( lcnt = 0; lcnt < kPageSize; lcnt += sizeof(long) ) * (UInt32 *) &gSumWindowPtr[lcnt] = 0;
UnmapDisk();
}
for ( pcnt = 0; pcnt < gActualSize * 512; pcnt += kPageSize * kSumRatio * kSumRatio ) {
MapDisk( pcnt, true );
for ( lcnt = 0; lcnt < kPageSize; lcnt += sizeof(long) ) * (UInt32 *) &gSum2WindowPtr[lcnt] = 0;
UnmapDisk();
}
}
break;
}
case kVerify:
err = noErr;
break;
case accRun:
err = PostEvent( diskEvt, gMyDQE.dqe.dQDrive );
dce->dCtlFlags &= ~dNeedTimeMask;
break;
default:
PrintHex( pb->csCode, 4 );
err = controlErr; break;
}
ExitCodeResource();
return err;
}
extern OSErr DriverStatus( CntrlParamPtr pb:__a0, DCtlPtr dce:__a1 ) {
OSErr err;
EnterCodeResource();
//ISDebugStr( "\pS" );
switch( pb->csCode ) {
case kReturnFormatList:
pb->csParam[0] = 1;
(* (UInt32 **) &pb->csParam[1])[0] = gActualSize;
(* (UInt32 **) &pb->csParam[1])[1] = 0;
err = noErr; break;
default:
PrintHex( pb->csCode, 4 );
err = statusErr; break;
}
ExitCodeResource();
return err;
}
extern OSErr DriverClose() {
return noErr;
}
extern pascal OSErr GestaltProc( UInt32, SInt32 *resp ) {
*resp = 0;
return noErr;
}
extern void MapDisk( UInt32 pos, Boolean sumToo ) {
{
int bcnt;
UInt32 pen;
for ( bcnt = 0, pen = pos; bcnt < gNumRuns && gRuns[bcnt].numBytes <= pen; bcnt++ ) {
pen -= gRuns[bcnt].numBytes;
}
if ( bcnt == gNumRuns ) {
return;
}
VMMap( gWindowPage, gRuns[bcnt].startPage + ( pen / kPageSize ) );
}
if ( sumToo ) {
UInt32 sumPos, sum2Pos;
{
sumPos = ( pos / kPageSize ) * kSumBytesPerPage;
gCurSumPhys = gSumBase + sumPos;
VMMap( gWindowPage + 1, gCurSumPhys / kPageSize );
gCurSumPtr = gSumWindowPtr + ( gCurSumPhys & (kPageSize-1) );
}
{
sum2Pos = ( sumPos / kPageSize ) * kSumBytesPerPage;
gCurSum2Phys = gSum2Base + sum2Pos;
VMMap( gWindowPage + 2, gCurSum2Phys / kPageSize );
gCurSum2Ptr = gSum2WindowPtr + ( gCurSum2Phys & (kPageSize-1) );
}
{
gCurSum3Ptr = gSum3Ptr + ( sum2Pos / kPageSize ) * kSumBytesPerPage;
}
}
}
extern void UnmapDisk( void ) {
VMUnmap( gWindowPage, 0 );
VMUnmap( gWindowPage + 1, 0 );
VMUnmap( gWindowPage + 2, 0 );
}
extern void WriteDiskChecksum( void ) {
OSErr err;
MakeDiskChecksum( gDiskWindowPtr, gCurSumPtr );
MakeDiskChecksum( gSumWindowPtr, gCurSum2Ptr );
MakeDiskChecksum( gSum2WindowPtr, gCurSum3Ptr );
{
int bcnt;
for ( bcnt = 0; bcnt < gSum3Size; bcnt++ ) {
int nibble;
nibble = ( gSum3Ptr[bcnt] >> 4 ) & 0xf;
gSum3Str[bcnt * 2] = nibble>9? 'a'-10 : '0';
gSum3Str[bcnt * 2] += nibble;
nibble = gSum3Ptr[bcnt] & 0xf;
gSum3Str[bcnt * 2 + 1] = nibble>9? 'a'-10 : '0';
gSum3Str[bcnt * 2 + 1] += nibble;
}
err = RegistryPropertySet( &gOptionsEntry, gSumPropertyName, gSum3Str, gSum3StrSize );
}
if ( err == noErr ) {
err = RegistryPropertySetMod( &gOptionsEntry, gSumPropertyName, kRegPropertyValueIsSavedToNVRAM );
}
if ( err ) {
ISDebugStr( "\pcouldn't write nvsum" );
PrintHex( err, 4 );
}
}
extern void MakeDiskChecksum( Ptr in, Ptr out ) {
MakeChecksum( in, out, kSumBoxSide, kSumBoxDimensions );
}
extern void MakeChecksum( Ptr data, Ptr sumPtr, UInt32 side, int dimensions ) {
int underBits, overBits, overStart, dcnt, sum;
UInt32 underCount, overCount, ucnt, ocnt, lcnt, loff, off;
underBits = 0;
overBits = kSumBoxVolumeLog2 - kSumBoxSideLog2;
for ( dcnt = 0; dcnt < dimensions; dcnt++ ) {
underCount = 1 << underBits;
overCount = 1 << overBits;
overStart = underBits + kSumBoxSideLog2;
for ( lcnt = 0; lcnt < kSumBoxSide; lcnt++ ) {
sum = 0;
loff = lcnt << underBits;
for ( ocnt = 0; ocnt < overCount; ocnt++ ) {
off = loff + ( ocnt << overStart );
for ( ucnt = 0; ucnt < underCount; ucnt++ ) {
sum ^= data[ off + ucnt ];
}
}
*sumPtr++ = sum;
}
underBits += kSumBoxSideLog2;
overBits -= kSumBoxSideLog2;
}
}
extern void CorrectDisk( Ptr data, Ptr good, Ptr check, UInt32 side, int dimensions ) {
int dcnt;
UInt32 bcnt;
UInt8 bad = 0;
for ( bcnt = 0; bcnt < side; bcnt++ ) {
if ( good[bcnt] != check[bcnt] ) {
// if ( bad & (good[bcnt]^check[bcnt]) ) ISDebugStr( "\pToo many errors" );
bad |= good[bcnt] ^ check[bcnt];
}
//PrintHex( good[bcnt], 2 );
//PrintHex( check[bcnt], 2 );
}
if(bad)PrintHex( bad, 2 );
while ( bad ) {
UInt32 coords[ kMaxDimensions ];
int cubex = bad & -bad; // what's bad & -bad? Bitwise arithmetic! Oh god it's 3:50 am.
bad ^= cubex;
for ( dcnt = 0; dcnt < dimensions; dcnt++ ) {
Ptr gcr = &good[dcnt*side], ccr = &check[dcnt*side]; // good/check coordinate row
coords[dcnt] = -1;
for ( bcnt = 0; bcnt < side; bcnt++ ) {
if ( ( gcr[bcnt] ^ ccr[bcnt] ) & cubex ) {
if ( coords[dcnt] != -1 ) {
ISDebugStr( "\px" );
coords[dcnt] = -1;
break;
}
coords[dcnt] = bcnt;
}
}
if ( coords[dcnt] == -1 ) {
//ISDebugStr( "\p?" );
break;
}
}
if ( dcnt != dimensions ) continue; // from above break;
{
UInt32 off = 0;
UInt32 dcoeff = 1;
for ( dcnt = 0; dcnt < dimensions; dcnt++ ) {
off += dcoeff * coords[dcnt];
dcoeff *= side;
}
PrintHex( ( data[off] & cubex ) != 0, 1 );
data[off] ^= cubex;
}
}
}

Binary file not shown.

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
Unfortunately, do to problems with installing on the demo machines at MacHack, and my physical location in New York (I had to do some high-school exams on 6/21), Ephemerboot was not presented at the Hack Show. It didn't get voted on & wasn't eligible for awards - here on the CD is its initial introduction. As part of a last-ditch effort to be allowed to present, I wrote this speech script (during the hack show, but in New York). This covers the more interesting bits of the project faster than the Read Me, and also covers some stuff the other file doesn't, so I suggest reading it first. Special thanks to Miro Jurisic (author of the 1998 winning Hack) for his hurculean efforts trying to install Ephemerboot on about a million different Macs, and to get a presentation slot. ----- Hello, I'm Dave Krauss, and I'm gonna tell you about the hack Ephemerboot. Ephemerboot is a bootable RAM disk driver for NewWorld Macs. To boot a newworld mac from ram, a few difficulties must be overcome. Data must be retrieved from powered-off RAM chips; Virtual Memory must be mercilessly hacked; and unspeakable evils must be perpetrated in the Forth level of Open Firmware hell. Machines since the iMac stop maintaining their dimms for a fraction of a second during reboot. During the missed refresh cycles, data fades away, a process called "bit rot". But not everything is erased. Just as human memory can be ephemeral, so is the ram of a newworld mac at reboot. hence ephemerboot. To eliminate bit rot, I implemented a data checksumming and recovery scheme. When the Mac powers up, it reads the checksums and uses them to correct the rotten bits, which luckily occur one at a time. The checksum is also stored in ram, tho. So it's a good idea to checksum the checksum. Then I checksum that checksum again, and that one's small enough to be flashed to nonvolatile ram where it's safe. So, the driver writes 3 checksums with every write. Next is the problem of allocating memory in a way that it's safe even through a reboot. That has to be done in Open firmware. A bootloader script erases the Mac's automatically-generated description of its ram chips, and replaces it with a new one. Data is safe in chips the Mac OS doesn't know it has. This also gets around basic limitations in the Mac OS, like the 1.5 gig limit. A 500 meg Ephboot disk should let a 2 gig machine use all of its memory. But if the operating system doesn't know about the memory, how will the driver access it? Normally, the OS controls the gate between the pointers we know and the chips. There is no published Apple API that allows us to use physical ram. So I reverse-engineered an obscure Apple utility that lets you look at physical memory. There are just two calls to the mini-api, which let you access any memory in the machine, including PowerPC interrupt vectors. This is powerful stuff - see the source code. Finally, there's the problem of how to load the driver early enough to boot from it. Obviously an extension won't do. Ephemerboot installs code into your disk's patch partition, where it's executed really early. I don't have time to talk about patch partitions, but they're not very well documented either. What I can tell you is that the source on the CD will include a little utility for you to write your own patch partitions. Maybe some of you can use it to keep the tradition of hacking 9 alive.
Loading…
Cancel
Save