User Tools

Site Tools


commodore:tapes:loaders:mega-save

Mega-Save

  • Mastering tool: Mega-Save
  • Loader A.K.A.: CHR Loader
  • Available mastering program versions: 1.3
  • Threshold variants used/configurable: 3/3
  • Additional threshold variants found: 1 (used in “Action Pack 2”)
  • Mastering tool discovered by: Paul Jones (Ziggy72)
  • Loader disassembled from: Cauldron (Saved using the “Mega-Speed” setting)

Information/structure

After one CBM boot file, there is a variable number of turbo blocks.

Turbo blocks

  • Mega-Speed x9 (fastest)
    • Threshold: 0x0107 (263) clock cycles (TAP byte : 0x20).
    • Bit 0 pulse: 0x19
    • Bit 1 pulse: 0x28
  • Ultra-Speed x7 (medium)
    • Threshold: 0x016E (366) clock cycles (TAP byte : 0x2D).
    • Bit 0 pulse: 0x26
    • Bit 1 pulse: 0x36
  • Hyper-Speed x5 (slowest)
    • Threshold: 0x01FA (506) clock cycles (TAP byte : 0x3E).
    • Bit 0 pulse: 0x36
    • Bit 1 pulse: 0x47
  • Endianess : MSbF
  • Pre-pilot byte: 0x20 (256 bytes)
  • Pilot byte: 0x63 (159 bytes)
  • Sync train: 0x64 up to 0xFF (156 bytes)
  • Payload
    • Header
      • 1 byte: Non-zero value expected by the loader (0x01 was saved during tests)
      • 2 bytes: Start address (LSBF)
      • 2 bytes: End address + 1 (LSBF)
      • 2 bytes: Execution vector
      • 1 byte: Flag for restarting the loader after file is complete (1=restart, 0=execute code)
      • 1 byte: Flag for code execution via vector or BASIC RUN command (1=vector, 0=RUN)
      • 2 bytes: Unused
    • Data
    • 1 byte : Checksum (0 XOR all data bytes)
  • Post-data : None
  • Trailer : None

Disassembled loader

loader.asm
; From: Cauldron 
; Note: Assemble with 64tass
 
; **************
; * CBM Header *
; **************
 
*=$0351
 
J0351   SEI           
 
        LDA #$07      ; Set the read bit timer/counter threshold to 0x0107
        STA $DD06     
 
        LDX #$01      
 
; Byte-align with the incoming stream by shifting bits in until the first pilot byte is read
 
_align  JSR rd_bit    ; Read a bit
 
        ROL $F7       ; Shift each of them into the byte receive register, MSbF
        LDA $F7       
        CMP #$63      ; Until the first occurrence of 0x63 (pilot byte) is received
        BNE _align    
 
        LDY #$64      ; Pre-load the first expected sync byte in Y
 
; Read the whole pilot sequence
 
_pilot  JSR rd_byte   ; Keep reading bytes until the pilot train is over
        CMP #$63      
        BEQ _pilot    
 
; Check that the sync sequence is as expected (Note: $F7 = byte read by means of a call to rd_byte)
 
_sync   CPY $F7       ; Is the currently expected sync byte following?
        BNE _align    ; Start over if not
 
        JSR rd_byte   ; Read byte
 
        INY           ; Bump the expected sync byte value
        BNE _sync     ; Read the whole sync sequence (0x64-0xff inclusive)
 
        CMP #$00      ; In absence of issues A is set to 0x01 here
        BEQ J0351     
 
; Read and store file header
 
_header JSR rd_byte   ; Read 10 header bytes
        STA $002B,Y   ; Overwrite BASIC program pointers
        STA $00F9,Y   ; Also store them in RAM where they will be changed
        INY           
        CPY #$0A      
        BNE _header   
 
        LDY #$00      
        STY $90       ; Zero the status flags (not set by this code)
        STY $02       ; Zero the checkbyte register
 
; Read and store data from tape into RAM
 
_data   JSR rd_byte   ; Read a byte
        STA ($F9),Y   ; Store in RAM
 
        EOR $02       ; Update the checkbyte value
        STA $02       
 
        INC $F9       ; Bump the destination pointer
        BNE *+4       
        INC $FA       
 
        LDA $F9       ; And check if the file is complete
        CMP $2D       ; by comparing the destination pointer
        LDA $FA       ; to its one-past-end value
        SBC $2E       
        BCC _data     
 
        JSR rd_byte   ; Read a byte
 
        INY           
        STY $C0       ; Control motor via software
 
        CLI           
 
        CLC           
 
        LDA #$00      ; Make sure the call to $FC93 does not restore the standard IRQ
        STA $02A0     
        JSR $FC93     ; Disable interrupts, un-blank the screen, and stop cassette motor
 
        JSR $E453     ; Copy BASIC vectors to RAM
 
        LDA $F7       ; Compare saved and computed checkbytes
        EOR $02       
        ORA $90       ; And check that the status flags do not indicate an error
        BEQ *+5       
 
        JMP $FCE2     ; Soft-reset if any of the above checks fails
 
; Code execution after a file is completely loaded in
 
        LDA $31       ; Check flag #1
        BEQ *+5       ; If not set move on
        JMP J02B9     ; Otherwise re-execute the loader
 
        LDA $32       ; Check flag #2
        BEQ *+5       ; If not set move on in order to issue a BASIC RUN command
        JMP ($002F)   ; Otherwise execute a custom routine pointed by the vector $2f/$30
 
        JSR $A533     ; Relink lines of tokenized program text
 
        LDX #$03      ; Set the number of characters in keyboard buffer to 3
        STX $C6       
 
        LDA T02F4-1,X ; And copy R, <shift> + U, <return> into the buffer
        STA $0276,X   
        DEX           
        BNE *-7       
 
        JMP J02E9     
 
; --------------------------------
 
; Read byte: read 8 bits from tape, grouping them MSbF
;
; Returns: the read byte in A
 
rd_byte LDA #$07      ; Prepare for 8 bits
        STA $F8       ; Using $f8 as a counter
 
B03EB   JSR rd_bit    ; Read a bit
 
        ROL $F7       ; Shift each of them into the byte receive register, MSbF
 
        INC $D020     
 
        DEC $F8       ; And loop until 8 bits are received
        BPL B03EB     
 
        LDA $F7       ; Return read byte in A
 
        RTS           
 
        .cerror * > $03FC, "The CBM Header code is too long to fit in the tape buffer!"
 
; --------------------------------
 
        .align $03FC, $00 ; Padding
 
 
; ************
; * CBM Data *
; ************
 
*=$02A7
 
NMIH    LDA #$80      
        ORA $91       
        JMP $F6EF     
 
J02AE   LDA #<NMIH    
 
        SEI           
 
        STA $0328     ; Disable <Run Stop> + <Restore>
        LDA #>NMIH    
        STA $0329     
 
J02B9   CLI           
 
        LDY #$00      
        STY $C6       ; No char in keyboard buffer
        STY $C0       ; Enable tape motor
        STY $02       ; Zero the checkbyte register (also done later on)
 
        LDA $D011     ; Blank the screen
        AND #$EF      
        STA $D011     
 
        DEX           ; Wait a bit
        BNE *-1       
        DEY           
        BNE *-4       
 
        SEI           
 
        JMP J0351     ; Execute the main loader code
 
; --------------------------------
 
; Read bit: loops until a falling edge is received on the read line and uses
;           the read bit timer/counter to discriminate the current bit value
;
; Returns: the read bit in the Carry flag
 
rd_bit  LDA $DC0D     ; Loop until a falling edge is detected
        AND #$10      ; on the read line of the tape port
        BEQ *-5       
 
        LDA $DD0D     
        STX $DD07     
        LSR           
        LSR           ; Move read bit into the Carry flag
 
        LDA #$19      ; Restart the bit read threshold timer/counter
        STA $DD0F     
 
        RTS           
 
; --------------------------------
 
J02E9   JSR $A68E     ; Reset pointer to current text character to the beginning of program text
 
        LDA #$00      
        TAY           
        STA ($7A),Y   
 
        JMP $A474     ; Print READY
 
; --------------------------------
 
; Characters to be injected in the keyboard buffer, if required
 
T02F4   .byte $52,$D5,$0D ; R, <shift> + U, <return>
 
        .cerror * > $0300, "The CBM Data code is too long to fit in front of the vector table!"
 
; --------------------------------
 
; Overwrite BASIC vectors in RAM
 
        .align $0300, $00 ; Padding
 
T0300   .word $E38B ; Leave IERROR unchanged
        .word J02AE ; Autostart the turbo loader, once loaded, by overwriting IMAIN

Re-assembling the loader

The above source code can be reassembled into two binary files to be used e.g. in a PC mastering tool a la prg2tap. One way to go about that is to create 2 intermediate assembly files as per below:

part1.asm
*=$0351

.BINARY "loader.prg", $0351-$02a7+2
part2.asm
*=$02a7

.BINARY "loader.prg", 0+2, $0304-$02a7

A batch file could be used as per below in order to assemble the source and split it:

build.bat
@echo off
:: $Id: build.bat,v 1.1 2017/09/05 18:37:33 luigidifraia Exp $
::
:: (C) 2017 Luigi Di Fraia
::
:: Assemble the loader
::
64tass loader.asm -o loader.prg
IF %ERRORLEVEL% GEQ 1 EXIT /B 1
::
:: Now split it into CBM Header and Data files
::
64tass part1.asm -o cbm_header.prg
IF %ERRORLEVEL% GEQ 1 EXIT /B 1
64tass part2.asm -o cbm_data.prg
IF %ERRORLEVEL% GEQ 1 EXIT /B 1
::
:: Cleanup
::
DEL loader.prg
commodore/tapes/loaders/mega-save.txt · Last modified: 2018/10/03 19:59 by Luigi Di Fraia