;-------------------------------------------------------------------- ; Atari Infocom Story Extractor ;-------------------------------------------------------------------- ; This program is Copyright © 1995 Mike Fenton ; Feel free though to slice and dice the program however you want. ; Just don't charge your buddy anything for it or we'll tell him he ; could've gotten it for free from us! ; There's a couple of cool little examples of programming included. I ; suppose you'll come up with even cooler ones that I'll be forced to ; try to out-cool... ;-) ; Note that no animals were harmed in the creation of this program, and ; only pure guess-work went into its making (the hallmark of a true ; programmer, if I may say so myself). I have absolutely no idea how an ; actual Infocom database is made. ; (So use this at your own risk!) include exec/exec.i FUNCCNT SET LIB_USERDEF FUNCDEF MACRO _LVO\1 EQU FUNCCNT FUNCCNT SET FUNCCNT-6 ENDM include exec/exec_lib.i include dos/dos.i include dos/dos_lib.i include dos/dosextens.i include intuition/intuition.i include intuition/intuition_lib.i include libraries/asl.i include libraries/asl_lib.i include workbench/startup.i CALLEXEC MACRO movea.l 4.w,a6 jsr _LVO\1(a6) ENDM CALLSYS MACRO ; call function w/a6 already set jsr _LVO\1(a6) ENDM CALLASL MACRO ; call ASL movea.l _AslBase,a6 jsr _LVO\1(a6) ENDM ;-------------------------------------------------------------------- ; You can probably skip this part, as it just sets stuff up move.l d0,CliLen ; a0 -> CLI_DATA, d0 = CLI_DATA_LEN move.l a0,CliDat moveq #20,d5 ; return code = 20 ; Starting? How... suba.l a1,a1 CALLEXEC FindTask movea.l d0,a4 ; task pointer clr.l returnMsg tst.l pr_CLI(a4) ; BPTR (was it called from CLI?) bne.s go_program ; if so, skip this bit... ; ok, Workbench, how did I get here? lea pr_MsgPort(a4),a0 CALLSYS WaitPort lea pr_MsgPort(a4),a0 CALLSYS GetMsg move.l d0,returnMsg ; look at the system, libraries go_program lea IntName(pc),a1 ; Intuition moveq #0,d0 CALLSYS OpenLibrary move.l d0,_IntuitionBase ; any kind of intuition? beq QuitStart ; nope, I'm gone! movea.l d0,a0 cmpi.w #37,LIB_VERSION(a0) ; 2.04+ ? bge.s .1 move.l #IntName,ReqStr bsr MyRequest bra CloseInt .1 lea DosName(pc),a1 ; Dos move.l #37,d0 CALLSYS OpenLibrary move.l d0,_DOSBase bne.s .3 move.l #DosName,ReqStr bsr MyRequest bra CloseInt .3 moveq.l #37,d0 lea ASLname(pc),a1 ; ASL CALLSYS OpenLibrary move.l d0,_AslBase bne.s .4 move.l #ASLname,ReqStr bsr MyRequest bra CloseDos ; ok, system checks out... prepare for task .4 tst.l returnMsg beq.s .7 movea.l returnMsg(pc),a2 ; get the argument list move.l sm_ArgList(a2),d0 beq.s .7 ; nothing really ; get directory WB tells me to use, save current directory move.l d0,a3 move.l wa_Lock(a3),d1 CALLDOS CurrentDir ; CD to same dir as argument move.l d0,prevDir ; a project icon? well, I'll just look at the first one... cmpi.l #2,sm_NumArgs(a2) blt.s .7 addq.l #8,a3 ; next arg move.l wa_Lock(a3),UserLock ; save argument directory move.l wa_Name(a3),UserName ; save argument filename ; restore pointer and length (CLI) .7 move.l CliLen(pc),d0 movea.l CliDat(pc),a0 bsr _main ; go ; ok, I'm done... move.l d0,d5 ; save condition ; close up system stuff... movea.l _AslBase(pc),a1 CALLEXEC CloseLibrary CloseDos tst.l returnMsg beq.s .1 move.l prevDir(pc),d1 CALLDOS CurrentDir ; restore current directory .1 movea.l _DOSBase(pc),a1 CALLEXEC CloseLibrary CloseInt movea.l _IntuitionBase(pc),a1 CALLSYS CloseLibrary ; put everything back... QuitStart tst.l returnMsg ; Is there a message? beq.s exitToDOS ; if not, skip... CALLSYS Forbid move.l returnMsg(pc),a1 CALLSYS ReplyMsg exitToDOS move.l d5,d0 ; exit code rts ;-------------------------------------------------------------------- MyRequest suba.l a0,a0 ; default screen lea ReqIText1(pc),a1 suba.l a2,a2 ; no postext lea OkdIText(pc),a3 ; negtext moveq #0,d0 moveq #0,d1 move.w #185,d2 ; width move.w #60,d3 ; height CALLINT AutoRequest movea.l 4.w,a6 ; set vector to exec rts ReqIText1 DC.B 2,1 ; black on white DC.B 1 ; jam2 DC.B 0 DC.W 10,5 DC.L 0 ; default font DC.L Bad1 ; string ptr DC.L ReqIText2 ReqIText2 DC.B 2,1 DC.B 1 ; jam2 DC.B 0 DC.W 10,15 DC.L 0 ; default font ReqStr DC.L 0 ; string ptr DC.L ReqIText3 ReqIText3 DC.B 2,1 DC.B 1 ; jam2 DC.B 0 DC.W 10,25 DC.L 0 ; default font DC.L Version ; string ptr DC.L 0 OkdIText DC.B 2,1 DC.B 1 ; jam2 DC.B 0 DC.W 5,3 DC.L 0 ; default font DC.L Okd DC.L 0 Okd dc.b 'Ok',0 Bad1 dc.b 'I can''t find',0 Version dc.b 'version 37',0 ;-------------------------------------------------------------------- DosName dc.b 'dos.library',0 IntName dc.b 'intuition.library',0 ASLname dc.b 'asl.library',0 cnop 0,2 ;-------------------------------------------------------------------- _DOSBase dc.l 0 _IntuitionBase dc.l 0 _AslBase dc.l 0 CliLen dc.l 0 CliDat dc.l 0 returnMsg dc.l 0 UserLock dc.l 0 UserName dc.l 0 prevDir dc.l 0 ;-------------------------------------------------------------------- ; ok, ready to really run _main clr.b WorkFile ; buffer lea WorkFile(pc),a3 move.l #GFTitle,GFTags+4 ; setup tags to read move.l #MyLOK,GFTags+12 move.b #$10,GFTags+23 lea GFTags(pc),a4 ; a4 = tags bsr DoMyReq ; get file name tst.l d0 beq.s .ok1 moveq #10,d0 ; oops, no file! rts ; try to open file .ok1 move.l #WorkFile,d1 move.l #MODE_OLDFILE,d2 CALLDOS Open ; open read file move.l d0,File1 ; File1 = read file bne.s .ok2 bsr IoErrReport moveq #20,d0 ; yowza! rts ; get length of file and verify validity .ok2 move.l d0,d1 ; d1 = file handle bsr GetFLen ; get file length cmpi.l #-1,d0 bne.s .ok3 bsr CloseFiles ; this won't actually return .ok3 clr.b Flags ; clear flags cmpi.l #92160,d0 ; standard? beq.s .ok4 cmpi.l #92176,d0 ; 16-byte header? beq.s .ok5 moveq #1,d0 ; bad length bsr Condition ; report warning bra.s .ok4 .ok5 ori.b #1,Flags ; set header flag moveq #16,d2 bsr MySeek ; get configuration ID (part of boot code) .ok4 bsr GetBlock move.l 6(a0),d0 ; get my ID cmpi.l #$20B91EA2,d0 ; old type (Zork) beq .cfg1 cmpi.l #$A9008DE7,d0 ; improved loader (Infidel) beq .cfg2 cmpi.l #$A94F850A,d0 ; new loader (Wishbringer) beq .cfg3 moveq #4,d0 ; unknown configuration .fail bsr Condition ; report problem .fail2 move.l File1(pc),d1 ; close open files beq.s .ok6 CALLDOS Close .ok6 move.l File2(pc),d1 beq.s .ok7 CALLDOS Close .ok7 moveq #20,d0 ; no um ugh rts ; find the starting sector and verify value .cfg1 moveq #0,d0 move.b $25(a0),d0 ; get starting sector # cmpi.b #$39,d0 ; $825 bra .cont .cfg2 moveq #0,d2 btst #0,Flags beq.s .sc2 moveq #16,d2 .sc2 addi.w #$1400,d2 ; get $1B00 ... bsr MySeek bsr GetBlock moveq #0,d0 move.b $22(a0),d0 ; get starting sector # cmpi.b #$39,d0 bra.s .cont .cfg3 moveq #0,d2 btst #0,Flags beq.s .sc3 moveq #16,d2 .sc3 addi.w #$1900,d2 ; get $2500 ... bsr MySeek bsr GetBlock moveq #0,d0 move.b $31(a0),d0 ; get starting sector # cmpi.b #$49,d0 .cont beq.s .ok8 move.l d0,-(a7) ; save d0 moveq #3,d0 ; bad loader? bsr Condition ; report warning move.l (a7)+,d0 ; restore d0 ; get first block and check type .ok8 subq.l #1,d0 ; - 1 lsl.l #7,d0 ; * 128 (bps) moveq #0,d2 btst #0,Flags beq.s .sc4 moveq #16,d2 .sc4 add.l d0,d2 ; start position bsr MySeek bsr GetBlock ; get 1st block of data cmpi.b #3,(a0) ; type 3? beq.s .ok9 move.l a0,-(a7) ; save a0 moveq #2,d0 ; bad type? bsr Condition ; report warning movea.l (a7)+,a0 ; restore a0 ; verify S# is actually numbers .ok9 moveq #$12,d1 ; offset moveq #5,d0 ; length .snloop cmpi.b #'0',(a0,d1.w) bcs.s .badsn ; use carry (unsigned cmp) cmpi.b #'9',(a0,d1.w) bhi.s .badsn addq.b #1,d1 dbra d0,.snloop bra.s .goodsn .badsn moveq #7,d0 ; bad serial number bra .fail ; get save file and prepare for output .goodsn lea DefFName,a0 lea WorkFile2,a1 bsr MyCopy256 ; copy default name move.l #GF3Title,GFTags+4 ; setup tags to save move.l #MySOK,GFTags+12 move.b #$30,GFTags+23 lea WorkFile2(pc),a3 lea GFTags(pc),a4 ; a4 = tags bsr DoMyReq ; get file name tst.l d0 bne .fail2 move.l #WorkFile2,d1 move.l #MODE_NEWFILE,d2 CALLDOS Open move.l d0,File2 ; File2 = write file bne.s .ok10 bsr CloseFiles ; won't actually return ; get length of initial block, begin output .ok10 lea WorkArea(pc),a0 moveq #0,d4 move.b 4(a0),d4 ; get # of pages addq.w #1,d4 ; + 1 lsr.w #1,d4 ; div 2 (# of blocks) .loop bsr PutBlock ; write data subq.w #1,d4 beq.s .sp1 ; done, skip to next part bsr GetBlock ; get next block bra.s .loop ; switch disks, if necessary .sp1 bsr GetBlock moveq #127,d0 moveq #0,d1 .sp2 or.l (a0)+,d1 ; add bits to d1 dbra d0,.sp2 tst.l d1 beq .next ; block = 0, continue cmpi.l #$1A1A1A1A,d1 ; (may not need this) beq .next ; ok, more data here, check what user thinks... lea HelpMe(pc),a1 bsr Request1 cmpi.l #1,d0 blt.s .next ; get next disk beq .fail2 ; abort ; ok, extract the data on this disk .extract bsr PutBlock ; put data bsr GetBlock ; get next block moveq #127,d0 moveq #0,d1 .sp3 or.l (a0)+,d1 ; add bits to d1 dbra d0,.sp3 tst.l d1 beq.s .ok11 ; block = 0, continue cmpi.l #$1A1A1A1A,d1 ; (may not need this) bne.s .extract ; ok, continue .ok11 bra IMDone ; ask for next disk file .next lea WorkFile(pc),a3 move.l #GF2Title,GFTags+4 ; setup tags to read next move.l #MyLOK,GFTags+12 move.b #$10,GFTags+23 lea GFTags(pc),a4 ; a4 = tags bsr DoMyReq ; get file name tst.l d0 bne .fail2 move.l File1(pc),d1 ; close old file beq.s .sp5 CALLDOS Close .sp5 move.l #WorkFile,d1 move.l #MODE_OLDFILE,d2 CALLDOS Open ; open read file move.l d0,File1 ; File1 = read file bne.s .ok12 bsr CloseFiles ; won't actually return ; skip header if found .ok12 move.l d0,d1 ; d1 = file handle bsr GetFLen ; get file length cmpi.l #-1,d0 bne.s .ok13 bsr CloseFiles ; this won't actually return .ok13 cmpi.l #92160,d0 ; standard? beq .ok15 cmpi.l #92176,d0 ; 16-byte header? beq.s .ok14 moveq #1,d0 ; bad length bsr Condition ; report warning bra .ok15 .ok14 moveq #16,d2 bsr MySeek .ok15 bsr GetBlock bra .extract ;-------------------------------------------------------------------- ; Modules (these should probably go before _main, oh well) GetBlock move.l File1(pc),d1 ; fh move.l #WorkArea,d2 ; buffer move.l #512,d3 CALLDOS Read cmpi.l #-1,d0 ; error? beq.s CloseFiles tst.l d0 ; eof? bne.s .1 addq.l #4,a7 ; don't go to main bra IMDone ; file is done, I'm done .1 lea WorkArea(pc),a0 rts PutBlock move.l File2(pc),d1 ; fh move.l #WorkArea,d2 ; buffer move.l #512,d3 CALLDOS Write cmpi.l #-1,d0 ; error? beq.s CloseFiles rts CloseFiles bsr IoErrReport ; report problem move.l File1(pc),d1 ; close open files beq.s .ok1 CALLDOS Close .ok1 move.l File2(pc),d1 beq.s .ok2 CALLDOS Close .ok2 moveq #20,d0 ; no um ugh addq.l #4,a7 ; don't return to main rts MySeek move.l File1(pc),d1 ; fh of read file ; (slower than offset_current, but I'm only doing a few of these) moveq #OFFSET_BEGINNING,d3 CALLDOS Seek CALLSYS IoErr ; (v37 work-around) tst.l d0 bne.s CloseFiles ; yowza! rts IMDone moveq #8,d0 bsr Condition ; report move.l File1(pc),d1 ; close open files beq.s .1 CALLDOS Close .1 move.l File2(pc),d1 beq.s .2 CALLDOS Close .2 moveq #0,d0 ; ugh, uh uh arg (technical) rts ;------------------------------------------------------------------ ; Misc File Stuff ;------------------------------------------------------------------ ; Get length of file by seeking ; (this is a natural for files that are already open) ; d1 = file handle : d0 = length or -1 if error GetFLen move.l d1,-(a7) ; save d1 moveq #0,d2 moveq #OFFSET_END,d3 CALLDOS Seek ; move file cursor to end CALLSYS IoErr ; v37 work-around tst.l d0 bne.s .error ; not 0 = problem move.l (a7),d1 ; restore d1 (no pop) moveq #0,d2 moveq #OFFSET_BEGINNING,d3 ; move file cursor to start CALLSYS Seek ; d0 = end position (length) move.l d0,d3 ; save d0 CALLSYS IoErr ; v37 work-around tst.l d0 beq.s .done ; 0 = success .error moveq #-1,d3 ; return -1 .done move.l d3,d0 ; restore d0 addq.l #4,a7 ; pop d1 rts ;-------------------------------------------------------------------- ; a0 = buffer AttachFNames lea MyDir(pc),a1 lea MyFile(pc),a2 moveq #0,d0 moveq #0,d1 .0 move.b (a1)+,d0 beq.s .1 move.b d0,(a0)+ ; copy normal chars move.b d0,d1 bne.s .0 .1 tst.b d1 beq.s .2 ; no dir string cmpi.b #':',d1 beq.s .2 ; check for : at end cmpi.b #'/',d1 beq.s .2 ; check for / at end move.b #'/',(a0)+ ; nope, then gimme a slash here .2 move.b (a2)+,(a0)+ ; copy file name directly bne.s .2 rts ;------------------------------------------------------------------ DetachFNames lea MyDir(pc),a1 lea MyFile(pc),a2 moveq #0,d0 moveq #0,d1 clr.b (a2) .1 addq.b #1,d1 ; d1 = length + 1 move.b (a0)+,(a1)+ ; copystr bne.s .1 .2 move.b -(a0),d0 ; check prev char cmpi.b #'/',d0 ; reached end of dir? beq.s .3 cmpi.b #':',d0 beq.s .3 clr.b -(a1) ; clear file name char subq.b #1,d1 ; d1 = current length bne.s .2 ; continue... bra.s .4 ; oops, no dir, just file .3 tst.b d1 ; no file name? beq.s .5 addq.l #1,a0 ; ok, adding file name .4 move.b (a0)+,(a2)+ ; copy file name bne.s .4 .5 rts ;------------------------------------------------------------------ ; a0 = source, a1 = destination (copy until null or a max of 256 bytes) MyCopy256 move.w #255,d0 .loop move.b (a0)+,(a1)+ ; copy (max 256 bytes) dbeq d0,.loop ; loop until null or done rts ;-------------------------------------------------------------------- ; My requester ;-------------------------------------------------------------------- ; (a3 = Buffer, a4 = Req Tags): ; d0 = return code (0 = ok), (Buffer) = new file name DoMyReq movem.l d2/a2/a5/a6,-(a7) ; save regs movea.l a3,a0 ; file name buffer bsr DetachFNames moveq #-1,d2 ; failed alloc code moveq #0,d0 ; type (file requester) movea.l a4,a0 ; a0 = * Tags CALLASL AllocAslRequest ; allocate request struct move.l d0,MyReq beq.s .quit ; serious problem moveq #-2,d2 ; failed request code suba.l a1,a1 ; no tags movea.l d0,a0 CALLSYS AslRequest ; gimme file! tst.l d0 beq.s .cancel ; cancel movea.l MyReq(pc),a0 movea.l rf_Dir(a0),a0 ; get ptr to directory string lea MyDir(pc),a1 bsr MyCopy256 ; MyDir=Dir movea.l MyReq(pc),a0 movea.l rf_File(a0),a0 ; get ptr to filename string lea MyFile(pc),a1 bsr MyCopy256 ; MyFile=File tst.b MyFile ; name filled? beq.s .cancel movea.l a3,a0 ; buffer bsr AttachFNames tst.b (a3) ; anything? beq.s .cancel moveq #0,d2 ; success .cancel movea.l MyReq(pc),a0 ; free requester CALLSYS FreeAslRequest .quit move.l d2,d0 ; d0 = return code movem.l (a7)+,d2/a2/a5/a6 ; restore regs rts ;------------------------------------------------------------------ ; Error Report ;-------------------------------------------------------------------- IoErrReport CALLDOS IoErr ; get error num move.l d0,Status ; store it moveq #6,d0 ; get condition string bsr FindCond lea Status(pc),a1 ; get * error num lea StuffChar(pc),a2 lea MyString(pc),a3 CALLEXEC RawDoFmt ; format number lea MyString+16(pc),a2 move.l Status(pc),d1 ; status moveq #0,d2 ; header move.l a2,d3 ; buffer moveq #127,d4 ; len = 127 CALLDOS Fault ; get error message bra.s ConReport Condition bsr FindCond lea MyString(pc),a1 .3 move.b (a0)+,(a1)+ bne.s .3 ConReport lea CondText(pc),a1 Request1 suba.l a0,a0 ; default screen suba.l a2,a2 ; no IDCMP stuff CALLINT EasyRequestArgs rts StuffChar move.b d0,(a3)+ rts ; d0 = # : a0 = * result FindCond lea Conditions(pc),a0 subq.w #1,d0 bmi.s .1 .2 tst.b (a0)+ bne.s .2 dbra d0,.2 .1 rts ;------------------------------------------------------------------ ; Data ;------------------------------------------------------------------ ; long words... cnop 0,4 ; align.l CondText dc.l HelpMe-CondText,0 dc.l ReqTitle dc.l MyString dc.l Conditions HelpMe dc.l GFTags-CondText,0 dc.l ReqTitle dc.l HelpMeTxt dc.l HelpMeGads GFTags dc.l ASL_Hail,GFTitle dc.l ASL_OKText,MyLOK dc.l ASL_FuncFlags,$10 ; ($10 load/$30 save) dc.l ASL_File,MyFile dc.l ASL_Dir,MyDir dc.l ASL_Pattern,PFPattern dc.l TAG_DONE MyReq dc.l 0 Status dc.l 0 File1 dc.l 0 File2 dc.l 0 ; bytes... ReqTitle dc.b 'AInfocom Converter',0 GFTitle dc.b 'Select disk file',0 GF2Title dc.b 'Select next disk file',0 GF3Title dc.b 'Select name of story data file',0 DefFName dc.b 'ram:Story.data',0 PFPattern dc.b '~(#?.info)',0 MyLOK dc.b 'Load',0 MySOK dc.b 'Save',0 Conditions dc.b 'OK',0 ; 0 dc.b 'Warning: Bad Disk File Length!',0 ; 1 dc.b 'Warning: Type may not be supported!',0 ; 2 dc.b 'Warning: Loader may be corrupted!',0 ; 3 dc.b 'Unknown configuration!',0 ; 4 dc.b 'Unexpected data encountered!',0 ; 5 dc.b 'DOS Error %ld - ',0 ; 6 dc.b 'Bad serial number!',0 ; 7 dc.b 'I''m done.',0 ; 8 HelpMeTxt dc.b 'Extra data found...',10 dc.b '(A 2-disk game should',10 dc.b 'not have extra data.)',10 dc.b 'Should I extract it or',10 dc.b 'continue with next disk?',0 HelpMeGads dc.b 'Abort|Extract|Next Disk',0 ; bit 0 = 16 byte header (plenty of room for more flags!) Flags dc.b 0 ; buffers... cnop 0,4 ; align.l MyString ds.b 144 WorkFile ds.b 256 WorkFile2 ds.b 256 MyFile ds.b 256 MyDir ds.b 256 WorkArea ds.b 512