Imagine v1
Found on:
"Ah Diddums"
"Cosmic Cruiser"
Loader files:
"Cosmic Cruiser"
ROM Header
Contains no filename, loader code uses that space.
ROM Data
Area loads to $0300-0334 and forces an autostart to $0341
Encoding:
Contains no filename, loader code uses that space.
ROM Data
Area loads to $0300-0334 and forces an autostart to $0341
Endianess:LSbF
Threshold:~$161-$179
Lead-in:1 bit / 2 pulses
Sync:None
Structure:Threshold:~$161-$179
Lead-in:1 bit / 2 pulses
Sync:None
Header:None
Checksum:None
Checksum:None
Header:
No header means that start and end addresses are hardcoded into the loader.
$036a lda #$ff Start low byte. Offset 0x2f
$036c sta $ac
$036e lda #$0f Start high byte. Offset 0x33
$036a sta $ad
$03f3/$03f0 cmp #$d0 End high address. Offset 0xb8 or 0xb7
End low address will always be zero. The end address is exclusive. Start address above is actual program address -1, the first address is just to store the pilot bit as part of loader initialization.
Encoding:
Loader uses two pulses per bit.
The total duration of first plus the second pulse is always the the same.
Timer B is used to measure the duration of both pulses and Timer A measures duration of second pulse. A bit is decoded based on the duration of the second pulse.
Pulse 1 (Even):
Timer B's current value is stored in memory, $0400 is added to the value and is then divided by 2.
Timer A and B is then initialized to $400 and started.
Pulse 2 (Odd):
Timer A's value is fetched and the stored Timer B value subtracted from it.
If A<B a 1 bit is ROR:ed into the current byte value.
So the threshold is based on the actual duration of both pulses and could potentially be changed any time during load and differ between releases.
Statistics:
No header means that start and end addresses are hardcoded into the loader.
$036a lda #$ff Start low byte. Offset 0x2f
$036c sta $ac
$036e lda #$0f Start high byte. Offset 0x33
$036a sta $ad
$03f3/$03f0 cmp #$d0 End high address. Offset 0xb8 or 0xb7
End low address will always be zero. The end address is exclusive. Start address above is actual program address -1, the first address is just to store the pilot bit as part of loader initialization.
Encoding:
Loader uses two pulses per bit.
The total duration of first plus the second pulse is always the the same.
Timer B is used to measure the duration of both pulses and Timer A measures duration of second pulse. A bit is decoded based on the duration of the second pulse.
Pulse 1 (Even):
Timer B's current value is stored in memory, $0400 is added to the value and is then divided by 2.
Timer A and B is then initialized to $400 and started.
Pulse 2 (Odd):
Timer A's value is fetched and the stored Timer B value subtracted from it.
If A<B a 1 bit is ROR:ed into the current byte value.
So the threshold is based on the actual duration of both pulses and could potentially be changed any time during load and differ between releases.
Statistics:
| Ah Diddums | ||
| Even | Odd | Bit |
| 473 µs ($3a) | 298 µs ($24) | 0 |
| 268 µs ($21 | 498 µs ($3d) | 1 |
| Even+Odd average=769 µs | ||
| Cosmic Cruiser | ||
| Even | Odd | Bit |
| 494 µs ($3d) | 305 µs ($26) | 0 |
| 271 µs ($21) | 524 µs ($41) | 1 |
| Even+Odd average=798 µs | ||
.label loadingDone = $93
.label bitCount = $9c
.label SCROLY = $d011
.label EXTCOL = $d020
.label TIMALO = $dc04
.label TIMAHI = $dc05
.label TIMBLO = $dc06
.label TIMBHI = $dc07
.label CIAICR = $dc0d
.label CIACRA = $dc0e
.label CIACRB = $dc0f
.label CI2ICR = $dd0d
.pc=$033c
.byte $03,$00,$03,$33,$03
// Autostart code jumps here
START:lda$01
and#$1f
sta$01
// Delay Loop
ldx#$ff
d2:ldy#$ff
d1:dey
bned1
dex
bned2
sei
// Clear all timer interrupts and service any pending
lda#$7f
staCI2ICR
staCIAICR
ldaCIAICR
ldaCI2ICR
// New IRQ vector $039d
lda#$9d
sta$0314
lda#$03
sta$0315
// Set load address $0fff
lda#$ff
sta$ac
lda#$0f
sta$ad
// Crude screen blank
ldaSCROLY
and#$08
staSCROLY
lda#$90
staCIAICR// Timer A: Enable FLAG interrupt
ldx#$01
stxbitCount
sty$fe// Y is zero from delay loop
styTIMALO// Timer A Low
styTIMBLO// Timer B Low
styloadingDone// Reset loading done flag
lda#$04
staTIMAHI// Timer A High
staTIMBHI// Timer B High
cli// Enable IRQ
// Wait for loading to finish
waitLoad:ldaloadingDone
beqwaitLoad
jmp$8800// Game start address
#####
# IRQ
#####
IRQ:ldaCIAICR// Service CIAICR, ignoring result
ldy#0
//Invert $fe, will cause different behaviour for even and odd pulses
lda$fe
eor#$ff
sta$fe
bploddPulse
// Even pulses
styCIACRB// Stops Timer B, sets it to count CPU cycles
ldaTIMBLO// Fetch Timer B Low
sta$fc
ldaTIMBHI// Fetch Timer B High
sta$fb
lda#$19
staCIACRA// Timer A: Load latched value,one-shot mode, start
staCIACRB// Timer B: Load latched value,one-shot mode, start
clc
// Add $400 to fetched Timer B value and divide it by 2
lda#$04
adc$fb
lsr
sta$fb
ror$fc
incEXTCOL// Border color flash
jmp$febc// Pulls Y,X,A from stacks and RTI
// Odd pulses
oddPulse:styCIACRA// Timer A: Load latched value,one-shot mode, start
sec
// Fetch Timer A value and Subtract the stored Timer B / 2 value
ldaTIMALO
sbc$fc
ldaTIMAHI
sbc$fb
// Bit = 1 if Timer B/2 > Timer A
ror$fd
decbitCount
bnecontLoad// Check if byte finished
ldx#$08
stxbitCount// Reset bitcount
lda$fd
sta($ac),y
// Increase load address
inc$ac
bneC_03f1
inc$ad
C_03f1:lda$ad
cmp#$d0// Check for end address $d000 (exclusive)
bcccontLoad
incloadingDone// Mark loading as finished
contLoad:jmp$febc// Pulls Y,X,A from stacks and RTI
.label bitCount = $9c
.label SCROLY = $d011
.label EXTCOL = $d020
.label TIMALO = $dc04
.label TIMAHI = $dc05
.label TIMBLO = $dc06
.label TIMBHI = $dc07
.label CIAICR = $dc0d
.label CIACRA = $dc0e
.label CIACRB = $dc0f
.label CI2ICR = $dd0d
.pc=$033c
.byte $03,$00,$03,$33,$03
// Autostart code jumps here
START:lda$01
and#$1f
sta$01
// Delay Loop
ldx#$ff
d2:ldy#$ff
d1:dey
bned1
dex
bned2
sei
// Clear all timer interrupts and service any pending
lda#$7f
staCI2ICR
staCIAICR
ldaCIAICR
ldaCI2ICR
// New IRQ vector $039d
lda#$9d
sta$0314
lda#$03
sta$0315
// Set load address $0fff
lda#$ff
sta$ac
lda#$0f
sta$ad
// Crude screen blank
ldaSCROLY
and#$08
staSCROLY
lda#$90
staCIAICR// Timer A: Enable FLAG interrupt
ldx#$01
stxbitCount
sty$fe// Y is zero from delay loop
styTIMALO// Timer A Low
styTIMBLO// Timer B Low
styloadingDone// Reset loading done flag
lda#$04
staTIMAHI// Timer A High
staTIMBHI// Timer B High
cli// Enable IRQ
// Wait for loading to finish
waitLoad:ldaloadingDone
beqwaitLoad
jmp$8800// Game start address
#####
# IRQ
#####
IRQ:ldaCIAICR// Service CIAICR, ignoring result
ldy#0
//Invert $fe, will cause different behaviour for even and odd pulses
lda$fe
eor#$ff
sta$fe
bploddPulse
// Even pulses
styCIACRB// Stops Timer B, sets it to count CPU cycles
ldaTIMBLO// Fetch Timer B Low
sta$fc
ldaTIMBHI// Fetch Timer B High
sta$fb
lda#$19
staCIACRA// Timer A: Load latched value,one-shot mode, start
staCIACRB// Timer B: Load latched value,one-shot mode, start
clc
// Add $400 to fetched Timer B value and divide it by 2
lda#$04
adc$fb
lsr
sta$fb
ror$fc
incEXTCOL// Border color flash
jmp$febc// Pulls Y,X,A from stacks and RTI
// Odd pulses
oddPulse:styCIACRA// Timer A: Load latched value,one-shot mode, start
sec
// Fetch Timer A value and Subtract the stored Timer B / 2 value
ldaTIMALO
sbc$fc
ldaTIMAHI
sbc$fb
// Bit = 1 if Timer B/2 > Timer A
ror$fd
decbitCount
bnecontLoad// Check if byte finished
ldx#$08
stxbitCount// Reset bitcount
lda$fd
sta($ac),y
// Increase load address
inc$ac
bneC_03f1
inc$ad
C_03f1:lda$ad
cmp#$d0// Check for end address $d000 (exclusive)
bcccontLoad
incloadingDone// Mark loading as finished
contLoad:jmp$febc// Pulls Y,X,A from stacks and RTI