diff --git a/ChainBoot.py b/ChainBoot.py new file mode 100755 index 0000000..f850dce --- /dev/null +++ b/ChainBoot.py @@ -0,0 +1,301 @@ +#!/usr/bin/env python3 + +# Multicast client +# Adapted from: http://chaos.weblogs.us/archives/164 + +import socket +import os +from os import path +import struct +import sys + +import time + +from snefru_hash import append_snefru + + + + +my_unique_ltoudp_id = b'El' + (os.getpid() & 0xFFFF).to_bytes(2, 'big') + + +image = open('ChainLoader.bin', 'rb').read() +image2 = sys.argv[1:] +image2dict = {} +for img2name in image2: + img2 = open(img2name, 'rb').read() + img2name = path.basename(img2name) + while len(img2) % 512: img2 += b'\0' + image2dict[img2name] = bytearray(img2) + + img2name = img2name.encode('mac_roman') + image += struct.pack('>LB', len(img2)//512, len(img2name)) + img2name + while len(image) % 2: image += b'\0' + +image += bytes(5) +image += b'Elliot'*10000 # padding, too much! +image = append_snefru(image) + +open('/tmp/imgdebug', 'wb').write(image) + +# typedef short (*j_code)( short command, # SP+4 (sign-extend to long) +# DGlobals *g, # SP+8 +# int **var1, # SP+12 +# int **var2); # SP+16 + +# We return our OSErr in d0, and leave the 16 bytes of arguments on the stack for the caller to clean + + +ANY = "0.0.0.0" +MCAST_ADDR = "239.192.76.84" +MCAST_PORT = 1954 + +# Create a UDP socket +sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) # IPPROTO_UDP could just be 0 + +# Allow multiple sockets to use the same PORT number +sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) +sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEPORT,1) + +# Bind to the port that we know will receive multicast data +sock.bind((ANY,MCAST_PORT)) + +# Tell the kernel that we want to add ourselves to a multicast group +# The address for the multicast group is the third param +status = sock.setsockopt(socket.IPPROTO_IP, +socket.IP_ADD_MEMBERSHIP, +socket.inet_aton(MCAST_ADDR) + socket.inet_aton(ANY)) + + + +# Wrap up in all sorts of crap... +def mk_ddp(dest_node, dest_socket, src_node, src_socket, proto_type, data): + # Wrap into DDP datagram + data = struct.pack('>HBBB', len(data) + 5, dest_socket, src_socket, proto_type) + data + + # Wrap into LLAP packet + data = struct.pack('>BBB', dest_node, src_node, 1) + data + + # Wrap the novel LToUDP header + data = my_unique_ltoudp_id + data + + return data + + + +def pstring(x): + try: + x = x.encode('mac_roman') + except AttributeError: + pass + + return bytes([len(x)]) + x + + + +while 1: + data, addr = sock.recvfrom(1024) + + # Okay... LToUDP parsing here. Let's start with the LLAP packet. + # Man, chaining protocols is hard. This will inevitably require a rewrite. + # Be careful to keep this as one cascading thing... + + if len(data) < 4: continue + if data.startswith(my_unique_ltoudp_id): continue + data = data[4:] # Trim down to LLAP packet (not "frame") + + if len(data) < 3: continue + llap_dest_node = data[0] + llap_src_node = data[1] + llap_proto_type = data[2] + data = data[3:] # Trim down to LLAP payload + + # Try to extract a DDP header, which is all we want! + if llap_proto_type == 1: + # ddp, short + if len(data) < 5: continue + ddp_len, ddp_dest_socket, ddp_src_socket, ddp_proto_type = struct.unpack_from('>HBBB', data) + data = data[5:ddp_len] + elif llap_proto_type == 2: + # ddp, long (what should we do with this extra information?) + if len(data) < 13: continue + ddp_len, ddp_cksum, ddp_dest_net, ddp_src_net, ddp_dest_node, ddp_src_node, ddp_dest_socket, ddp_src_socket = struct.unpack_from('>4H5B', data) + ddp_hop_count = (ddp_len >> 10) & 0xF + ddp_len &= 0x3FF + data = data[13:ddp_len] + else: + # llap control packet -- can probably ignore! + continue + + print(f'datagram {llap_src_node}:{ddp_src_socket}->{llap_dest_node}:{ddp_dest_socket}', end=' ') + + if ddp_proto_type == 2: + # Name Binding Protocol + + if len(data) < 2: continue + nbp_func = data[0] >> 4 + nbp_tuple_cnt = data[0] & 0xF + nbp_id = data[1] + data = data[2:] + + nbp_tuples = [] + while data and len(nbp_tuples) < nbp_tuple_cnt: + if len(data) < 5: break + this_tuple = list(struct.unpack_from('>HBBB', data)) + data = data[5:] + for i in range(3): + # This should be coded more defensively, perhaps using exceptions + this_tuple.append(data[1:1+data[0]].decode('mac_roman')) + data = data[1+data[0]:] + nbp_tuples.append(tuple(this_tuple)) + + + print('nbp func', nbp_func, 'id', nbp_id) + for t in nbp_tuples: + print(f' net:node:sock={t[0]}:{t[1]}:{t[2]} enum={t[3]} object:type@zone={t[4]}:{t[5]}@{t[6]}') + + if nbp_func == 2: + # NBP LkUp + + if len(nbp_tuples) == 1 and nbp_tuples[0][5] == 'BootServer': # Looking for us + sock.sendto(mk_ddp( + dest_node=llap_src_node, dest_socket=ddp_src_socket, + src_node=99, src_socket=99, + proto_type=2, + data=bytes([0x31, nbp_id]) + # 3=LkUp-Reply, 1=number-of-tuples + struct.pack('>HBBB', # Pack the first tuple + 0, # (My) Network number + 99, # (My) Node ID + 99, # (My) Socket number + 0, # (My) Enumerator (i.e. which of many possible names for 99/99 is this?) + ) + + pstring(nbp_tuples[0][4]) + pstring('BootServer') + pstring('*') + ), + (MCAST_ADDR, MCAST_PORT)) + + elif ddp_proto_type == 10: + # Mysterious ATBOOT protocol + + if len(data) < 2: continue + boot_type, boot_vers = struct.unpack_from('>BB', data) + data = data[2:] + + # rbNullCommand EQU 0 ; ignore this one + # rbMapUser EQU 1 ; user record request + # rbUserReply EQU 2 ; user record reply + # rbImageRequest EQU 3 ; image request & bitmap + # rbImageData EQU 4 ; image data + # rbImageDone EQU 5 ; server done with current image + # rbUserRecordUpdate EQU 6 ; new user info to server + # rbUserRecordAck EQU 7 ; info received from server + + if boot_type == 1: + # It might be neater to trim off the "type" and "version" fields a bit earlier + boot_machine_id, boot_timestamp, boot_username = struct.unpack_from('>HL 34p', data) + + print(f'atboot type={boot_type}, vers={boot_vers}, machineID={boot_machine_id}, userName={boot_username}') + + # // This defines a user record. + # // Some of these fields can be determined on the fly by the boot server, + # // while others are stored on disk by the boot server + # typedef struct + # { + # char serverName[serverNameLength]; // server name to boot off of + # char serverZone[zoneNameLength]; // and the zone it lives in + # char serverVol[volNameLength]; // the volume name + # short serverAuthMeth; // authentication method to use (none, clear txt, rand) + # unsigned long sharedSysDirID; // dir id of shared system folder + # unsigned long userDirID; // dir id of the user's private system folder + # unsigned long finderInfo[8]; // blessed folder dir id, startup folder dir id etc... + # unsigned char bootBlocks[138]; // see Inside Mac V-135 + # unsigned short bootFlag; // server based flags + # unsigned char pad[306-18]; // pad to ddpMaxData + # }userRecord; + + # // This defines the user record reply sent to workstations. + # typedef struct + # { + # unsigned char Command; /* record or image request command word */ + # unsigned char pversion; /* version of boot protocol spoken by requestor */ + # unsigned short osID; /* type and version of os */ + # unsigned int userData; /* time stamp goes here */ + # unsigned short blockSize; /* size of blocks we will send */ + # unsigned short imageID; /* image ID */ + # short result; /* error codes */ + # unsigned int imageSize; /* size of image in blocks */ + # userRecord userRec; /* tell user where to go */ + # }BootPktRply; + + # Ignore the silly user record. Just make the fucker work! + sock.sendto(mk_ddp( + dest_node=llap_src_node, dest_socket=ddp_src_socket, + src_node=99, src_socket=99, + proto_type=10, + data=struct.pack('>BBHLHHhL', 2, 1, boot_machine_id, boot_timestamp, 512, 0, 0, len(image) // 512).ljust(586, b'\0') + ), + (MCAST_ADDR, MCAST_PORT)) + + elif boot_type == 3: + # It seems to want part of the boot image! + # print('it wants data', data) + + # typedef struct + # { + # unsigned short imageID; /* */ + # unsigned char section; /* "section" of the image the bitmap refers to */ + # unsigned char flags; /* ??? */ + # unsigned short replyDelay; + # unsigned char bitmap[512]; /* bitmap of the section of the image requested */ + # }bir; // Boot Image Req + + print('Boot Image Req') + + if len(data) < 6: continue + boot_image_id, boot_section, boot_flags, boot_reply_delay = struct.unpack_from('>HBBH', data) + data = data[6:] + + # Okay, pretty much just send the bits that were requested! + print('Sending blocks') + for blocknum in range(len(image) // 512): + if len(data) > blocknum // 8 and (data[blocknum // 8] >> (blocknum % 8)) & 1: + # typedef struct { + # unsigned char packetType; /* The command number */ + # unsigned char packetVersion; /* Protocol version number */ + # unsigned short packetImage; /* The image this block belongs to */ + # unsigned short packetBlockNo; /* The block of the image (starts with 1) */ + # unsigned char packetData[512];/* the data portion of the packet */ + # } BootBlock,*BootBlockPtr; + + # print(f' {blocknum}', end='', flush=True) + sock.sendto(mk_ddp( + dest_node=llap_src_node, dest_socket=ddp_src_socket, + src_node=99, src_socket=99, + proto_type=10, + data=struct.pack('>BBHH', 4, 1, boot_image_id, blocknum) + image[blocknum*512:blocknum*512+512] + ), + (MCAST_ADDR, MCAST_PORT)) + + # time.sleep(0.5) + # break # wait for another request, you mofo! + + elif boot_type == 128: + boot_seq, boot_blkoffset, boot_bytelen, boot_imgname = struct.unpack_from('>HLL32p', data) + boot_imgname = boot_imgname.decode('mac_roman') + for ofs in range(0, boot_bytelen, 512): + thisblk = image2dict[boot_imgname][boot_blkoffset*512+ofs:boot_blkoffset*512+ofs+512] + sock.sendto(mk_ddp( + dest_node=llap_src_node, dest_socket=ddp_src_socket, + src_node=99, src_socket=99, + proto_type=10, + data=struct.pack('>BBHL', 129, 1, boot_seq, ofs) + thisblk + ), + (MCAST_ADDR, MCAST_PORT)) + + else: + print(f'ATBOOT type={boot_type} vers={boot_vers} contents={repr(data)}') + + + + + else: + print('unknown ddp payload', ddp_proto_type) diff --git a/ChainLoader.a b/ChainLoader.a new file mode 100644 index 0000000..586994f --- /dev/null +++ b/ChainLoader.a @@ -0,0 +1,991 @@ +myUnitNum equ 52 +myDRefNum equ ~myUnitNum + + +Code + cmp.l #1,4(SP) + beq getBootBlocks + cmp.l #2,4(SP) + beq getSysVol + cmp.l #3,4(SP) + beq mountSysVol + + move.l #-1,d0 + rts + + +getBootBlocks +; Now is the time to install our DRVR because we are guaranteed an opportunity to boot if we don't fuck up. + + link A6,#-$32 + movem.l A2-A4/D3-D7,-(SP) + + ; Copy the DRVR -> A2 + move.l #DrvrEnd-DrvrBase,D0 + lea DrvrBase,A0 + bsr InstallUnderBufPtr + move.l A0,A2 + + ; Patch the server address into the DRVR code + move.l 12(A6),A0 ; global pointer + move.l 24(A0),D0 ; AddrBlock + + lea gSaveAddr-DrvrBase(A2),A0 + + move.b #10,15(A0) ; hardcode DDP protocol ID + move.b D0,13(A0) ; socket + lsr.w #4,D0 + lsr.w #4,D0 + move.b D0,11(A0) ; node + swap D0 + move.w D0,7(A0) ; network + + ; Patch the disk image name and size into the DRVR code + bsr BootPicker ; A0 = pstring ptr + lea gNumBlks-DrvrBase(A2),A1 + move.l -4(A0),(A1) ; the size is underneath the pstring ptr + move.l -4(A0),D6 ; D6 = block count, needed for DQE + lea gQFilename-DrvrBase(A2),A1 ; A1 = dest + moveq.l #1,D0 + add.b (A0),D0 + dc.w $A22E ; _BlockMoveData + + ; Install the driver in the unit table + move.l #myDRefNum,D0 + dc.w $A43D ; _DrvrInstall ReserveMem + + ; That call created a DCE. Find and lock. + move.l $11C,A0 ; UTableBase + move.l myUnitNum*4(A0),A0 + dc.w $A029 ; _HLock + move.l (A0),A0 + + ; Populate the empty DCE that DrvrInstall left us + move.l A2,0(A0) ; dCtlDriver is pointer (not hdl) + + move.w 0(A2),D0 ; get DRVR.drvrFlags + bclr #6,D0 ; Clear dRAMBased bit (to treat dCtlDriver as a pointer) + move.w D0,4(A0) ; set DCE.dCtlFlags + + move.w 2(A2),$22(A0) ; drvrDelay to dCtlDelay + move.w 4(A2),$24(A0) ; drvrEMask to dCtlEMask + move.w 6(A2),$26(A0) ; drvrMenu to dCtlMenu + + ; Open the driver + lea -$32(SP),SP + move.l SP,A0 + bsr clearblock + lea DrvrNameString,A1 + move.l A1,$12(A0) ; IOFileName + dc.w $A000 ; _Open + lea $32(SP),SP + + ; Create a DQE + move.l #$16,D0 + dc.w $A71E ; _NewPtr ,Sys,Clear + add.l #4,A0 ; has some cheeky flags at negative offset + move.l A0,A3 + lea gDQEAddr,A0 + move.l A3,(A0) + + ; Populate the DQE + move.l D6,D0 ; needs fixing for Ruby Slipper + lsl.l #5,D0 ; convert to bytes + lsl.l #4,D0 + swap D0 + move.l D0,$C(A3) ; dQDrvSz/dQDrvSz2 + move.l #$80080000,-4(A3) ; secret flags, see http://mirror.informatimago.com/next/developer.apple.com/documentation/mac/Files/Files-112.html + move.w #1,4(A3) ; qType + move.w #0,$A(A3) ; dQFSID should be for a native fs + + ; Into the drive queue (which will further populate the DQE) + bsr findFreeDriveNum ; get drvnum in D0 + lea gDriveNum,A0 + move.w D0,(A0) + swap D0 + move.w #myDRefNum,D0 ; D0.H = drvnum, D0.L = drefnum + move.l D0,D3 ; we need this in a sec + move.l A3,A0 + dc.w $A04E ; _AddDrive + + ; Open our socket + lea -$32(SP),SP + move.l SP,A0 + bsr clearblock + move.w #-10,$18(A0) ; ioRefNum = .MPP + move.w #248,$1A(A0) ; csCode = openSkt + move.b #10,$1C(A0) ; socket = 10, same as ATBOOT uses + lea DrvrSockListener-DrvrBase(A2),A2 + move.l A2,$1E(A0) ; listener + dc.w $A004 ; _Control + lea $32(SP),SP + + ; Get the real 1024k of boot blocks to a temp location + ; (it will eventually get trashed, but we have time) + lea 4096(A5),A4 ; above BootGlobals + + lea -$32(SP),SP + move.l SP,A0 + bsr clearblock + move.l D3,22(A0) ; IOVRefNum=drvnum, IORefNum=drefnum + move.l A4,32(A0) ; IOBuffer + move.l #1024,36(A0) ; IOReqCount = 2 blocks + move.l #0,46(A0) ; IOPosOffset = 0 + move.w #1,44(A0) ; IOPosMode = from start + dc.w $A002 ; _Read + lea $32(SP),SP + + ; Put the boot blocks in netBOOT's global data structure as requested + move.l A4,A0 + move.l 12(A6),A1 + lea $BA(A1),A1 + move.l #$138,D0 + dc.w $A22E ; _BlockMoveData + + move.l A1,A0 ; A0 = truncated BBs + move.l A4,A1 ; A1 = full-length BBs + bsr HealInjuredBootBlocks + + ; Sundry hacks to make the boot process kinda-work + bsr DisableDiskCache + bsr TrackMostRecentResource + bsr FakeLapMgr + bsr InstallMPPWrapper +; bsr fixDriveNumBug ; not sure if I even need to do this any more? + + ; Clean up our stack frame + movem.l (SP)+,A2-A4/D3-D7 + unlk A6 + + move.l #0,D0 + rts + +getSysVol ; pointless call :( + move.l #0,D0 + rts + +mountSysVol + link A6,#-$32 + movem.l A2-A4/D3,-(SP) + + lea gDriveNum,A0 + move.w (A0),D3 + + ; System 7 needs MountVol to return the right vRefNum + move.l $366,A0 ; Steal existing PB from FSQHead + + ; Set aside the FS queue to stop MountVol deadlocking + move.w $360,-(SP) ; FSBusy + move.l $362,-(SP) ; FSQHead + move.l $366,-(SP) ; FSQTail + clr.w $360 + clr.l $362 + clr.l $366 + + ; MountVol + bsr clearblock + move.w D3,$16(A0) ; ioVRefNum = ioDrvNum = the drive number + dc.w $A00F ; _MountVol + bne error + + ; Restore the FS queue + move.l (SP)+,$366 + move.l (SP)+,$362 + move.w (SP)+,$360 + + ; Tattle about the DQE and VCB + move.l 4+12(A6),A1 + move.l $356+2,A0 ; VCBQHdr.QHead (maybe I should be clever-er) + move.l A0,(A1) + + move.l 4+16(A6),A1 + lea gDQEAddr,A0 + move.l (A0),(A1) + + movem.l (SP)+,A2-A4/D3 + unlk A6 + + move.l #0,d0 + rts + +error + move.w #$7777,D0 + dc.w $A9C9 + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +; The .netBOOT driver installs a ToExtFS hook that triggers on _MountVol +; and calls our mountSysVol. The hook routine checks that the drive number +; is 4. On machines with >2 existing drives, this check fails, and we +; never get called (i.e. Mini vMac). + +; The solution is to head patch the .netBOOT ToExtFS hook. We use a +; one-shot patch on _MountVol to gain control after the hook is installed +; but before it is called, and install our new hook. + +bystanderTrap equ $A00F ; _MountVol +gTheirDriveNum dc.w 0 +gOrigBystanderTrap dc.l 0 +gOrigExtFS dc.l 0 +NetBootName dc.b 8, ".netBOOT", 0 + +fixDriveNumBug + ; Get the .netBOOT driver refnum (only to search for the right drive) + lea -$32(A6),A0 ; Use our caller's stack frame + bsr clearblock + lea NetBootName,A1 + move.l A1,$12(A0) ; IOFileName + dc.w $A000 ; _Open + bne error + move.w $18(A0),D0 ; Result in IORefNum + + ; Search for the drive with that number in dQRefNum + lea 2(A1),A0 ; Treat qHead like qLink. +nbFindLoop move.l (A0),A0 ; follow qLink + cmp.w 8(A0),D0 ; is the dQRefNum the .netBOOT driver? + beq.s nbFound ; then we found the .netBOOT drive + cmp.l 6(A1),A0 ; have we reached qTail? + beq error ; then we didn't find the .netBOOT drive + bra.s nbFindLoop +nbFound move.w 6(A0),D0 ; Get dqDrive (drive number) + + ; Save drivenum in a global for our patch to use + lea gTheirDriveNum,A0 + move.w D0,(A0) + + ; Only install the patch if we need to + cmp.w #4,D0 ; A drivenum of 4 will work anyway + bne.s installOneshotPatch + rts + + +; Install a self-disabling patch on _MountVol +installOneshotPatch + move.w #bystanderTrap,D0 ; Save original in a global + dc.w $A346 ; _GetOSTrapAddress + lea gOrigBystanderTrap,A1 + move.l A0,(A1) + + move.w #bystanderTrap,D0 ; Install + lea oneshotPatch,A0 + dc.w $A247 ; _SetOSTrapAddress + + rts + + +; Our _MountVol patch +oneshotPatch + clr.l -(SP) + movem.l D0/D1/A0/A1,-(SP) ; Save "OS trap" registers + + move.l $3F2,A0 ; Save the ToExtFS hook in a global to call later + lea gOrigExtFS,A1 + move.l A0,(A1) + lea toExtFSPatch,A0 ; Install the ToExtFS head patch + move.l A0,$3F2 + + lea gOrigBystanderTrap,A0 ; Remove this patch from _MountVol + move.l (A0),A0 + move.l A0,16(SP) + move.w #bystanderTrap,D0 + dc.w $A047 ; _SetTrapAddress + + movem.l (SP)+,D0/D1/A0/A1 + rts + + +; Our head patch on the ToExtFS hook +toExtFSPatch + movem.l A0-A4/D1-D2,-(SP) ; Save the same registers at the real ToExtFS hook + + cmp.b #$F,$6+1(A0) ; Check for a _MountVol call (IOTrap+1) + bne.s hookReturn + + lea gTheirDriveNum,A1 ; Check for the CORRECT drive number, + move.w (A1),D0 ; instead of erroneously checking for 4. + cmp.w $16(A0),D0 ; IODrvNum + bne.s hookReturn + + lea gOrigExtFS,A1 ; Rejoin the ToExtFS hook AFTER the buggy code + move.l (A1),A1 +hookScan add.l #2,A1 ; Scan for "lea DrvQHdr,A2" (or similar) + cmp.w #$308,2(A1) + bne.s hookScan + jmp (A1) ; and enter at that point + +hookReturn movem.l (SP)+,A0-A4/D1-D2 + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +clearblock + move.w #$32/2-1,D0 +.loop clr.w (A0)+ + dbra D0,.loop + lea -$32(A0),A0 + rts + +findFreeDriveNum ; and return it in D0. Free to trash the usual registers + ; Find a free drive number (nicked this code from BootUtils.a:AddMyDrive) + LEA $308,A0 ; [DrvQHdr] + MOVEQ #4,D0 ; start with drive number 4 +.CheckDrvNum + MOVE.L 2(A0),A1 ; [qHead] start with first drive +.CheckDrv + CMP.W 6(A1),D0 ; [dqDrive] does this drive already have our number? + BEQ.S .NextDrvNum ; yep, bump the number and try again. + CMP.L 6(A0),A1 ; [qTail] no, are we at the end of the queue? + BEQ.S .GotDrvNum ; if yes, our number's unique! Go use it. + MOVE.L 0(A1),A1 ; [qLink] point to next queue element + BRA.S .CheckDrv ; go check it. + +.NextDrvNum + ; this drive number is taken, pick another + ADDQ.W #1,D0 ; bump to next possible drive number + BRA.S .CheckDrvNum ; try the new number +.GotDrvNum + rts + + + +DrvrNameString + dc.b 8, ".LANDisk", 0 + + +gDriveNum dc.w 0 +gDQEAddr dc.l 0 + + +; code on this side is for start only, and stays in the netBOOT driver globals until released + +BufPtrCopy + +; code on this side gets copied beneath BufPtr (is that the best place??) + + +; Shall we start with a driver? +DrvrBase + dc.w $4F00 ; dReadEnable dWritEnable dCtlEnable dStatEnable dNeedLock + dc.w 0 ; delay + dc.w 0 ; evt mask + dc.w 0 ; menu + + dc.w DrvrOpen-DrvrBase + dc.w DrvrPrime-DrvrBase + dc.w DrvrControl-DrvrBase + dc.w DrvrStatus-DrvrBase + dc.w DrvrClose-DrvrBase + dc.b 8, ".LANDisk", 0 + +g +gNumBlks dc.l 0 ; the source of all truth +gMyDCE dc.l 0 +gTheirPB dc.l 0 ; pointer to the pending ParamBlk +gQuery +gQTypeVer dc.w 0 ; part of gQuery +gQSeqNum dc.w 0 ; part of gQuery +gQOffset dc.l 0 ; part of gQuery +gQLen dc.l 0 ; part of gQuery +gQFilename dcb.b 32 ; part of gQuery ; set by the init code +gQueryEnd +gWDS dcb.b 2+4+2+4+2 +gMyPB dcb.b $32+2 ; allow us to clear it with move.l's + odd +gSaveAddr dcb.b 16 +gAddr dcb.b 16 + even + +; a0=iopb, a1=dce on entry to all of these... + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrOpen + move.l A0,-(SP) + lea gMyDCE,A0 ; dodgy, need this for IODone + move.l A1,(A0) + move.l (SP)+,A0 + + move.w #0,$10(A0) ; ioResult + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrPrime + cmp.b #2,$7(A0) ; ioTrap == aRdCmd + bne .notRead + + move.w #1,$10(A0) ; ioResult = pending. We will return without an answer. + + move.l A1,D0 ; save and restore A1 + lea gTheirPB,A1 ; keep a copy of the pending PB in our code (a bit messy) + move.l A0,(A1) + move.l D0,A1 + + ; For short requests, DevMgr puts offset in DCE + ; We, oddly, stuff a BLOCK offset into ioPosOffset + btst.b #0,$2C(A0) ; test ioPosMode & kUseWidePositioning + bne.s .wide +.notwide move.l $10(A1),D0 + bra.s .gotD0 +.wide move.l $2E(A0),D0 ; the block offset can only be up to 32 bits + or.l $32(A0),D0 +.gotD0 ror.l #4,D0 ; now D0 = (LS 23 bits) followed by (MS 9 bits) + ror.l #5,D0 + move.l D0,$2E(A0) ; ioPosOffset = D0 = byte_offset/512 + + movem.l A0-A4,-(SP) ; sleazy function that will trash stuff + bsr.s DrvrTransmitRequest ; takes A0 as PB argument + movem.l (SP)+,A0-A4 + + bge.s .noerr + dc.w $A9FF ; Elliot, this is where we get those "unimp traps" + move.w #-36,$10(A0) ; ioResult = ioErr +.noerr + + rts + +.notRead + move.w #$DDDD,D0 + dc.w $A9C9 + + move.w #-20,$10(A0) + rts + +DrvrTransmitRequest ; On entry, A0=pb. Trashes A0-A4/D0 so be careful. Uses our globals extensively. + lea g,A2 + + move.b gSaveAddr-g(A2),gAddr-g(A2) ; reasonable way to copy the address struct, I guess + move.l gSaveAddr-g+1(A2),gAddr-g+1(A2) + move.l gSaveAddr-g+5(A2),gAddr-g+5(A2) + move.l gSaveAddr-g+9(A2),gAddr-g+9(A2) + move.w gSaveAddr-g+13(A2),gAddr-g+13(A2) + move.b gSaveAddr-g+15(A2),gAddr-g+15(A2) + + move.w #$8001,gQTypeVer-g(A2) ; Means a polite request, v1 + addq.w #1,gQSeqNum-g(A2) ; Increment the sequence counter + + move.l $28(A0),D0 ; [ioActCount (in bytes) + lsr.l #4,D0 + lsr.l #5,D0 ; / 512] + add.l $2E(A0),D0 ; + ioPosOffset (in blocks) + move.l D0,gQOffset-g(A2) ; -> "offset" field of request + + move.l $24(A0),D0 ; ioReqCount (in bytes) + sub.l $28(A0),D0 ; - ioActCount (in bytes) + move.l D0,gQLen-g(A2) ; -> "length" field of request + + lea gAddr,A3 + clr.w gWDS-g(A2) ; WDS+0 reserved field + move.l A3,gWDS-g+2(A2) ; WDS+2 pointer to address struct + lea gQuery,A3 + move.w #gQueryEnd-gQuery,gWDS-g+6(A2) ; WDS+6 length of second entry + move.l A3,gWDS-g+8(A2) ; WDS+8 pointer to second entry + clr.w gWDS-g+12(A2) ; WDS+10 zero to end the list + + lea gMyPB,A0 +.retry + bsr DrvrClearBlock + move.w #-10,$18(A0) ; ioRefNum = .MPP + move.w #246,$1A(A0) ; csCode = writeDDP + move.b #10,$1C(A0) ; socket = 10 (hardcoded) + move.b #1,$1D(A0) ; set checksumFlag + pea gWDS + move.l (SP)+,$1E(A0) ; wdsPointer to our WriteDataStructure + dc.w $A404 ; _Control ,async + + cmp.w #-91,$10(A0) ; This happens when the RAM MPP replaces the ROM one + bne.s .noNeedToRetry ; so we need to reopen socket #10, then retry... + + bsr DrvrClearBlock + move.w #-10,$18(A0) ; ioRefNum = .MPP + move.w #248,$1A(A0) ; csCode = openSkt + move.b #10,$1C(A0) ; socket = 10 (hardcoded) + pea DrvrSockListener + move.l (SP)+,$1E(A0) ; listener + dc.w $A004 ; _Control (synchronous, better hope it doesn't deadlock!) + + bra.s .retry + +.noNeedToRetry + move.w $10(A0),D0 ; for our caller to figure out what went wrong + + rts + +DrvrClearBlock + move.w #$32/2-1,D0 +.loop clr.w (A0)+ + dbra D0,.loop + lea -$32(A0),A0 + rts + +DrvrSockListener ; works closely with the Prime routine +; A0 Reserved for internal use by the .MPP driver. You must preserve this register until after the ReadRest routine has completed execution. +; A1 Reserved for internal use by the .MPP driver. You must preserve this register until after the ReadRest routine has completed execution. +; A2 Pointer to the .MPP driver's local variables. Elliot says: the frame and packet header ("RHA") are at offset 1 from A2 (keeps the 2-byte fields aligned) +; A3 Pointer to the first byte in the RHA past the DDP header bytes (the first byte after the DDP protocol type field). +; A4 Pointer to the ReadPacket routine. The ReadRest routine starts 2 bytes after the start of the ReadPacket routine. +; A5 Free for your use before and until your socket listener calls the ReadRest routine. +; D0 Lower byte is the destination socket number of the packet. +; D1 Word indicating the number of bytes in the DDP packet left to be read (that is, the number of bytes following the DDP header). +; D2 Free for your use. +; D3 Free for your use. + + cmp.b #10,-1(A3) ; DDP protocol type better be ATBOOT + bne.s .trashpacket + + moveq.l #8,D3 + jsr (A4) ; Read 8 bytes + + cmp.w #$8101,-8(A3) ; Check protocol and version + bne.s .trashpacket + + lea gQSeqNum,A5 ; Check packet sequence number + move.w (A5),D2 + cmp.w -6(A3),D2 + bne.s .trashpacket + + lea gTheirPB,A5 + move.l (A5),A5 + move.l $28(A5),D2 ; keep this in D2 because we will use it in a sec + cmp.l -4(A3),D2 ; ioActCount = this offset? + bne.s .outoforder + + move.l $20(A5),A3 ; ioBuffer + add.l D2,A3 ; A3 = ioBuffer + ioReqDone + move.l #512,D3 ; D3 = bytes to read = 512 + add.l D3,D2 ; D2 = new ioReqDone = old ioReqDone + 512 + jsr 2(A4) ; ReadRest + ; From this point on, use A1 instead of A5 + + lea gTheirPB,A1 + move.l (A1),A1 + move.l D2,$28(A1) ; update ioActCount + cmp.l $24(A1),D2 ; does it equal ioReqCount? + bne.s .rts + + lea gMyDCE,A1 + move.l (A1),A1 + + moveq.l #0,D0 + move.l $8FC,A0 ; jIODone (D0 = result, A1 = DCE) + jmp (A0) + + +.outoforder ; apparent dropped packet, so please resend + moveq.l #0,D3 + jsr 2(A4) ; ReadRest nothing + + movem.l A0-A4,-(SP) + lea gTheirPB,A0 + move.l (A0),A0 + bsr DrvrTransmitRequest + movem.l (SP)+,A0-A4 + + rts + + move.w #$1111,D0 ; won't happen with old-school LocalTalk + dc.w $A9C9 + +.trashpacket + move.w #$BBBB,D0 ; take this out when I'm confident the case doesn't matter + dc.w $A9C9 + + moveq.l #0,D3 + jmp 2(A4) ; ReadRest nothing + +.rts + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrControl + move.w #-17,$10(A0) ; ioResult = controlErr + bra DrvrFinish + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrStatus + cmp.w #6,$1A(A0) + beq.s status_fmtLstCode + cmp.w #8,$1A(A0) + beq.s status_drvStsCode + bra.s status_unknown + +status_fmtLstCode ; tell them about our size + movem.l A2/A3,-(SP) + + lea gNumBlks,A3 + move.l (A3),D0 + lsl.l #5,D0 ; convert from blocks to bytes + lsl.l #4,D0 + + move.w #1,$1C(A0) + move.l $1C+2(A0),A2 + move.l D0,0(A2) + move.l #$40000000,4(A2) + + movem.l (SP)+,A2/A3 + + move.w #0,$10(A0) ; ioResult = noErr + bra DrvrFinish + +status_drvStsCode ; tell them about some of our flags + move.w #0,$1C(A0) ; csParam[0..1] = track no (0) + move.l #$80080000,$1C+2(A0) ; csParam[2..5] = same flags as dqe + + move.w #0,$10(A0) ; ioResult = noErr + bra DrvrFinish + +status_unknown + move.w $1A(A0),D0 ; dodgy, for debugging + add.w #$3000,D0 + dc.w $A9C9 + + move.w #-18,$10(A0) ; ioResult + bra DrvrFinish + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrClose + move.w #$4444,D0 + dc.w $A9C9 + + move.w #0,$10(A0) ; ioResult + rts + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DrvrFinish + move.w 6(A0),D1 ; iopb.ioTrap + btst #9,D1 ; noQueueBit + bne.s DrvrNoIoDone + move.l $8FC,-(SP) ; jIODone +DrvrNoIoDone + rts +DrvrEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +HealInjuredBootBlocks ; patch the BBs to fix themselves if executed + +; Args: A0 = truncated boot blocks (138b), A1 = correct boot blocks (1024b) + +; If these boot blocks are executable (from offset 2), then the 138 bytes of +; declarative data copied by the netBOOT driver are not enough. We edit the boot +; blocks with a stub that copies the entire 1k into place. System 7 needs this. + +; This unfortunately clobbers the declarative part of the boot blocks, so we need to +; check for declarative boot blocks and leave them unchanged + + move.l A0,-(SP) ; Save A1 to a global, but need to keep A0 + lea .gFullLoc,A0 + move.l A1,(A0) + move.l (SP)+,A0 + + move.b 6(A0),D0 ; BBVersion + cmp.b #$44,D0 + beq.s .executable + and.b #$C0,D0 + cmp.b #$C0,D0 + beq.s .executable + rts + +; Relevant structure of the boot blocks: +; 0-1 bbID always 'LK' +; 2-5 bbEntry BRA.W base+128 +; 6-7 bbVersion interpreted as above +; 8-137 data +; 138-1023 code (MISSING from netBOOT's short version) + +; Our self-repairing boot blocks: +; 0-1 bbID always 'LK' +; 2-5 bbEntry BRA.W base+8 +; 6-7 bbVersion interpreted as above +; 8-13 JSR .inPlaceFixRoutine +; 14-137 junk + + +.executable ; Need to leave bytes 6,7 intact + move.l #$60000004,2(A0) ; BB+2: BRA.W BB+8 + move.w #$4EB9,8(A0) ; BB+8: JSR .inPlaceFixRoutine (abs) + lea .inPlaceFixRoutine,A1 + move.l A1,10(A0) + + move.l A0,A1 ; Clear the icache with a BlockMove + move.l #138,D0 + dc.w $A02E ; _BlockMove + + rts + +.inPlaceFixRoutine ; The BB stub JSRs to here + move.l (SP)+,A1 ; so the return address will be at BB+14 + lea -14(A1),A1 ; "Rewind" to the start of the BB + move.l .gFullLoc,A0 ; And here are our full boot blocks + move.l #1024,D0 + dc.w $A02E ; _BlockMove (needs to clear cache) + + jmp 2(A1) ; Jump to the fixed-up BB + +.gFullLoc dc.l 0 ; Stuff addr of full-length BB in here + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +DisableDiskCache + move.w #$1A0,D0 ; _GetResource + dc.w $A746 ; _GetToolTrapAddress + lea .jmpOld+2,A1 + move.l A0,(A1) + + lea .GetResourcePatch,A0 + move.l #.GetResourcePatchEnd-.GetResourcePatch,D0 + bsr InstallInSysHeap + + move.w #$1A0,D0 + dc.w $A647 ; _SetToolTrapAddress + + rts + +.GetResourcePatch + cmp.l #'ptch',6(SP) + bne.s .jmpOld + cmp.w #41,4(SP) + bne.s .jmpOld + + bra.s .memoryhole +.jmpOld + jmp $12345678 +.memoryhole move.l (SP)+,A0 + addq #6,SP + clr.l (SP) + jmp (A0) +.GetResourcePatchEnd + + + +TrackMostRecentResource + move.w #$1A0,D0 ; _GetResource + dc.w $A746 ; _GetToolTrapAddress + lea GRjmp+2,A1 + move.l A0,(A1) + + lea GetResourcePatch,A0 + move.l #GetResourcePatchEnd-GetResourcePatch,D0 + bsr InstallInSysHeap ; takes and returns A0 + + move.w #$1A0,D0 + dc.w $A647 ; _SetToolTrapAddress + + rts + +GetResourcePatch + move.l A0,D0 + lea GRrecord,A0 + move.l 6(SP),(A0)+ + move.w 4(SP),(A0)+ + move.l 4(A6),(A0)+ + move.l D0,A0 ; maybe the register needed saving?? +GRjmp jmp $12345678 + + dc.l 'Elmo' +GRrecord dcb.b 20 +GetResourcePatchEnd + + + +FakeLapMgr + tst.l $B18 + bgt.s .nofix + + moveq.l #MyLapMgrEnd-MyLapMgr,D0 + dc.w $A440 ; _ResrvMemSys + moveq.l #MyLapMgrEnd-MyLapMgr,D0 + dc.w $A522 ; _NewHandleSys + dc.w $A029 ; _HLock + move.l (A0),A1 + lea MyLapMgr,A0 + moveq.l #MyLapMgrEnd-MyLapMgr,D0 + dc.w $A02E ; _BlockMove + move.l A1,$B18 ; -> LAPMgrPtr + +.nofix rts + +MyLapMgr + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + nop + rts +.exit +MyLapMgrEnd + + + + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +InstallMPPWrapper + movem.l A2-A4,-(SP) + + ; Copy the fake .MPP DRVR, with its address in A2 + move.l #MPPWrapperEnd-MPPWrapper,D0 + lea MPPWrapper,A0 + bsr InstallInSysHeap + move.l A0,A2 + + ; Get DCE ptr in A0 + move.l $11C,A0 ; UTableBase + move.l 9*4(A0),A0 + move.l (A0),A0 ; got ptr to MPP DCE + + ; I have commented out the "driver is in a handle" lines + ; Get driver code ptr in A1 + move.l (A0),A1 ; should be a ptr, never a handle +; btst.b #6,4+1(A0) +; beq.s .isptr +; move.l (A1),A1 +.isptr + + ; Copy some information over... + move.l (A1),(A2) ; all the flags + move.l 4(A1),4(A2) + + moveq.l #5-1,D0 ; do it five times + lea 8(A1),A3 ; A3 = original relative word-size ptr + lea MPPOrigOpen-MPPWrapper(A2),A4 ; A4 = new absolute long-size ptr +.routineloop + clr.l D1 + move.w (A3)+,D1 + add.l A1,D1 + move.l D1,(A4)+ + dbra D0,.routineloop + + ; And "take over" the execution of the driver code + move.l A2,(A0) ; dCtlDriver = our new driver code +; bclr.b #6,4+1(A0) ; bit of a race condition, but who cares + + movem.l (SP)+,A2-A4 + + rts + + +MPPWrapper + dc.w 0 ; flags, to be filled in + dc.w 0 ; delay + dc.w 0 ; evt mask + dc.w 0 ; menu + dc.w MPPOpen-MPPWrapper + dc.w MPPPrime-MPPWrapper + dc.w MPPControl-MPPWrapper + dc.w MPPStatus-MPPWrapper + dc.w MPPClose-MPPWrapper + dc.b 4,".MPP " + dc.w $3004 ; a version code + +MPPOrigOpen dc.l 0 +MPPOrigPrime dc.l 0 +MPPOrigControl dc.l 0 +MPPOrigStatus dc.l 0 +MPPOrigClose dc.l 0 + +MPPOpen + move.l MPPOrigOpen,-(SP) + rts + +MPPPrime + move.l MPPOrigPrime,-(SP) + rts + +MPPControl + move.l MPPOrigControl,-(SP) + rts + +MPPStatus + move.l MPPOrigStatus,-(SP) + rts + +MPPClose + movem.l A0-A4/D0-D7,-(SP) ; ultra-cautious + + ; Preload some resources before networking goes away + + clr.l -(SP) + move.l #'lmgr',-(SP) + move.w #0,-(SP) + dc.w $A9A0 ; _GetResource + addq.l #4,SP + + clr.l -(SP) + move.l #'AINI',-(SP) + move.w #30000,-(SP) + dc.w $A9A0 ; _GetResource + addq.l #4,SP + + clr.l -(SP) + move.l #'STR ',-(SP) + move.w #$BFE3,-(SP) + dc.w $A9A0 ; _GetResource + addq.l #4,SP + + clr.l -(SP) + move.l #'STR#',-(SP) + move.w #$BF89,-(SP) + dc.w $A9A0 ; _GetResource + addq.l #4,SP + + movem.l (SP)+,A0-A4/D0-D7 + + move.l MPPOrigClose,-(SP) + rts + +ResourcesToPreload + +MPPWrapperEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + + +InstallInSysHeap +; takes A0/D0 as args, returns ptr in A0 + movem.l A0/D0,-(SP) + dc.w $A51E ; NewPtr ,Sys + movem.l (SP)+,A1/D0 ; now A1=old, A0=new + exg A0,A1 ; now A1=new, A0=old + move.l D0,-(SP) + dc.w $A02E ; _BlockMove + move.l (SP)+,D0 + exg A1,A0 ; now A1=old, A0=new + + subq.l #1,D0 +.wipeloop move.b $99,(A1)+ + dbra D0,.wipeloop + + rts + + +InstallUnderBufPtr +; takes A0/D0 as args, returns ptr in A0 + sub.l D0,$10C ; BufPtr + move.l $10C,A1 + move.l D0,-(SP) + dc.w $A02E ; _BlockMove + move.l (SP)+,D0 + exg A0,A1 ; now A1=old, A0=new + + subq.l #1,D0 +.wipeloop move.b $99,(A1)+ + dbra D0,.wipeloop + + rts + + +BootPicker include "BootPicker.a" diff --git a/LocalTalkMonitor.py b/LocalTalkMonitor.py index ac74f12..5c76820 100755 --- a/LocalTalkMonitor.py +++ b/LocalTalkMonitor.py @@ -100,8 +100,15 @@ while 1: print(' ATBOOT image request', data.hex()) elif boot_type == 4: print(' ATBOOT image reply', data.hex()) + elif boot_type == 128: + a, b, c, d = struct.unpack_from('>HLL32p', data) + d = d.decode('mac_roman') + print(f' ATBOOT Elliot block seq={hex(a)} blkIdx={hex(b)} byteLen={hex(c)} img={repr(d)}') + elif boot_type == 129: + a, b = struct.unpack_from('>HL', data) + print(f' ATBOOT Elliot reply seq={hex(a)} relByteIdx={hex(b)}') else: - print(' ' + data.hex()) + print(' ATBOOT', boot_type, boot_vers, data.hex()) else: print('Totally unknown DDP type', ddp_proto_type) diff --git a/Makefile b/Makefile index 61ce390..c6bdaa0 100644 --- a/Makefile +++ b/Makefile @@ -74,6 +74,12 @@ BootPicker.bin: BootPicker.a +ChainLoader.bin: ChainLoader.a BootPicker.a + vasm-1/vasmm68k_mot -quiet -Fbin -pic -o $@ $< + +testchain: ChainLoader.bin FORCE + zsh -c 'trap "kill %1" SIGINT SIGTERM EXIT; ./ChainBoot.py **/*.dsk /Users/elliotnunn/Downloads/SSW-71-Universal/Hd-100.iso & make testclient' + FORCE: