2 Likes
Unusual custom turbo loaders - with sources
I have always been fascinated by turbo loaders; my games all feature a TZX version with one of such schemes implemented, and I have been experimenting with several of them.
I reverse engineered the loading routines of four 'exotic' loaders and recorded data with ZX Spin as WAV files, later converted as TZX images with WAV2TZX, in order to discover the timings. Normal loading speed chunks of data can be altered in Tapir with those timings, in order to 're-save' them as turbo speed data.
UPDATE: A fifth loader, found in Erbe Software's re-release of Cobra, has been added (see below).
UPDATE 2: A sixth loader from Alternative has been added.
The results are here for anyone interested to enjoy.
1) Biturbo (both I and II variants)
First programmed by Giovanni "G.B. Max" Zanetti in 1985, the Biturbo loading scheme is fast and very reliable, since it was used for recording software on the Spectrum cassette side of the infamous Italian SIPE tape magazines featuring hacked games with changed names and badly translated into Italian, and such tapes weren't exactly of high quality to begin with.
The first variant was used until about April 1987 to be replaced with the second one, which has exactly the same timings except for the pilot tone, but a 'scrambled' appearance instead of the orderly one associated with Biturbo I. The pilot tone colors of Biturbo I also seem to vary randomly between these combinations: black-green; red-yellow; magenta-white.
Biturbo II was the object of my first attempt at reverse engineering of a custom loader but at the time my knowledge of Spectrum machine code was too scarce to 'isolate' the loading routine. Only recently I was able to extract it from an actual TZX file and reuse it effectively.
Biturbo II is being used as the custom loader for the TZX files of my forthcoming game Cousin Horace.
Example files
I reverse engineered the loading routines of four 'exotic' loaders and recorded data with ZX Spin as WAV files, later converted as TZX images with WAV2TZX, in order to discover the timings. Normal loading speed chunks of data can be altered in Tapir with those timings, in order to 're-save' them as turbo speed data.
UPDATE: A fifth loader, found in Erbe Software's re-release of Cobra, has been added (see below).
UPDATE 2: A sixth loader from Alternative has been added.
The results are here for anyone interested to enjoy.
1) Biturbo (both I and II variants)
First programmed by Giovanni "G.B. Max" Zanetti in 1985, the Biturbo loading scheme is fast and very reliable, since it was used for recording software on the Spectrum cassette side of the infamous Italian SIPE tape magazines featuring hacked games with changed names and badly translated into Italian, and such tapes weren't exactly of high quality to begin with.
The first variant was used until about April 1987 to be replaced with the second one, which has exactly the same timings except for the pilot tone, but a 'scrambled' appearance instead of the orderly one associated with Biturbo I. The pilot tone colors of Biturbo I also seem to vary randomly between these combinations: black-green; red-yellow; magenta-white.
Biturbo II was the object of my first attempt at reverse engineering of a custom loader but at the time my knowledge of Spectrum machine code was too scarce to 'isolate' the loading routine. Only recently I was able to extract it from an actual TZX file and reuse it effectively.
Biturbo II is being used as the custom loader for the TZX files of my forthcoming game Cousin Horace.
; Biturbo 1 loading routine ; by Giovanni "G.B. Max" Zanetti ; Used 1985-1987 ca. ; Readapted by Alessandro Grussu, April 2014 ; ------------------ ; Standard timings: ; ------------------ ; Pilot pulse : 2165 ; Pilot length: 8350 ; Sync 1 pulse: 714 ; Sync 2 pulse: 714 ; Zero pulse : 486 ; One pulse : 833 ORG 65355 LD A,255 SCF INC D EX AF,AF' DEC D DI IN A,(254) RRA AND 032 OR 002 LD C,A CP A RET NZ CALL 65485 JR NC,65371 LD HL,01045 DJNZ 65380 DEC HL LD A,H OR L JR NZ,65380 CALL 65481 JR NC,65371 LD B,156 CALL 65481 JR NC,65371 LD A,198 CP B JR NC,65372 INC H JR NZ,65392 LD B,201 CALL 65485 JR NC,65371 LD A,B CP 212 JR NC,65407 CALL 65485 RET NC LD A,C XOR 003 LD C,A LD H,000 LD B,208 JR 65457 EX AF,AF' JR NZ,65441 LD (IX+000),L JR 65451 RL C XOR L RET NZ LD A,C RRA LD C,A INC DE JR 65453 INC IX DEC DE EX AF,AF' LD B,210 LD L,001 CALL 65481 RET NC LD A,220 CP B RL L LD B,208 JP NC,65459 LD A,H XOR L LD H,A LD A,D OR E JR NZ,65433 RET CALL 65485 RET NC LD A,013 DEC A JR NZ,65487 AND A INC B RET Z LD A,127 IN A,(254) RRA XOR C AND 032 JR Z,65491 LD A,C CPL LD C,A LD A,R AND 007 OR 008 OUT (254),A SCF RET
; Biturbo 2 loading routine ; by Giovanni "G.B. Max" Zanetti ; Used 1987-1990 ca. ; Readapted by Alessandro Grussu, September 2013 ; ------------------ ; Standard timings: ; ------------------ ; Pilot pulse : 2165 ; Pilot length: 3190 ; Sync 1 pulse: 714 ; Sync 2 pulse: 714 ; Zero pulse : 486 ; One pulse : 833 ORG 65355 LD A, 255 SCF INC D EX AF, AF' DEC D DI IN A, (254) RRA AND 32 OR 2 LD C, A CP A RET NZ CALL 65485 JR NC, 65371 LD HL, 1045 DJNZ 65380 DEC HL LD A, H OR L JR NZ, 65380 CALL 65481 JR NC, 65371 LD B, 156 CALL 65481 JR NC, 65371 LD A, 198 CP B JR NC, 65372 INC H JR NZ, 65392 LD B, 201 CALL 65485 JR NC, 65371 LD A, B CP 212 JR NC, 65407 CALL 65485 RET NC LD A, C XOR 3 LD C, A LD H, 0 LD B, 208 JR 65457 EX AF, AF' JR NZ, 65441 LD (IX+0), L JR 65451 RL C XOR L RET NZ LD A, C RRA LD C, A INC DE JR 65453 INC IX DEC DE EX AF, AF' LD B, 210 LD L, 1 CALL 65481 RET NC LD A, 220 CP B RL L LD B, 208 JP NC, 65459 LD A, H XOR L LD H, A LD A, D OR E JR NZ, 65433 RET CALL 65485 RET NC LD A, 13 DEC A JR NZ, 65487 AND A INC B RET Z LD A, 127 IN A, (254) RRA XOR C AND 32 JR Z, 65491 LD A, C CPL LD C, A LD A, B NOP AND 7 OR 8 OUT (254), A SCF RET
Example files
Post edited by Alessandro Grussu on
Comments
First published in Your Sinclair (February 1989 issue) as a type-in, this loading routine was used within a program for 'fancy' screen loading. It comes in four speeds: Normal (same as ROM loading), Medium (not very fast), Fast (very fast) and Aaargh! (too fast, unpractical for real use). Colour schemes are Normal, Multicolor and Masked (no stripes, just a black border).
The code was originally located at 64000. After some trial and error I managed to relocate it as high as 65200; at higher addresses the code does not work properly, although there is still room left until the top of RAM.
Here follow the sources for Multicolor Medium and Fast settings.
Example file
This loading scheme was used between 1987 and 1989 in the Special Games tape magazines published by Edigamma, the second force - after SIPE - of the Italian market of hacked games with false names sold with magazines in newsagents.
It is a strange loader, with a peculiar frequency, unusual color scheme and a long pilot tone. It is not very fast - loads a headerless screen data file in about 28 seconds against the 21 of Biturbo II. The author is unknown.
Example file
A flexible (six speeds available) and reliable loader that I have used for Cronopios y Famas, Kobrahsoft's SL3 comes with no less than 13 different combination of colors. Although I employed speed 4 at first (CYF's TZX files are 'recorded' at it), I think speed 3 would be a safer bet although I did not experience any problem whatsoever with the faster option when tested on real hardware. Hence, as an example, follow the sources for the multicolor and magenta-green/black-white loading routines both at speed 3, which is still very fast.
Example file
Another loading scheme I just isolated, taken from the Erbe re-release of Cobra. I don't know if it was used with other titles, but presumably it was.
Speed seems to be pretty much the same as Biturbo I/II.
Example file
Thank you leespoons; actually, there is more to come, because I just found this.
6) Alternative Software
Used in this release from the software house; I don't remember it being present in other Alternative titles right now.
Border pattern looks very similar to Biturbo I, although as a loader it is noticeably slower - will load a headerless screen data file in about 25 seconds. Still faster than SG, though.
Example file
not sure if that would even count as a turbo loader?
anyway, please delete this post if its not relevant, and i'll ask elsewhere
There is one, but for technical reasons it only works in 128k mode. Can't remember what it's called offhand though but I think it came out of the demo scene - I'll post a link if I remember.
edit:
Song in Lines 5 and MDA Demo both by Busysoft.
Also Lyra II Megademo which was on a SU cover tape in 1991.
And finally Removal Deluxe which performs the neat trick of loading the loader itself as a short BASIC program then switching straight to full screen mode with no leader in between (I've always wondered if this was possible) - can't get the game to work though :(
Nice work - its definitely going into my ZX library of useful assembly files!.. :)
Thanks! As for the Tapir bit, it's dead easy. Just open in it the file with the data you need to convert, then select the desired block and from the drop-down menu in the lower part of the Tapir window select Turbo speed. The timings will now be editable. Just change the six values in accordance with the loading scheme you are using and click on the 'Commit' button (lower right). Et voila - you now have your turbo block ready.
Examine the example files I provided here in Tapir to see how values change from one scheme to another.
Games List 2016 - Games List 2015 - Games List 2014
Here is another practical demonstration of some of these loaders put to work. The Loader-examples.zip archive contains the following:
- Pieces Of Eight (Vadim's repacked version with initial screen restored), reassembled with Special Games loader;
- Slowglass (128K), transferred from the DSK file, packed and reassembled with Multi-Load at Fast speed;
- Thanatos (128K fixed version with full compatibility), packed and reassembled with Kobrahsoft SL3 - Multicolor, Speed 3;
- Xecutor (bug-fixed by Einar Saukas), packed and reassembled with Erbe loader.
[URL="http://http://www.alessandrogrussu.it/zx/Loader-examples.zip"]Download[/URL]Azzurro 8-bit Jam (Multi-Load fast setting), 2'01" [previously 4'12"]
Box Reloaded (Kobrahsoft SL3 speed 3), 1'29" [4'44"]
El Stompo (Multi-Load fast setting), 1'07" [4'26"]
Isotopia (Kobrahsoft SL3 speed 3), 4'44" [9'33"]
Knightmare ZX (Kobrahsoft SL3 speed 3), 3'40" [7'05"]
Sunbucket (Multi-Load fast setting), 1'04" [4'16"]
Dingo (Multi-Load fast setting), 2'09" [previously 4'31"]
The Speccies (Multi-Load fast setting), 2'06" [4'30"]
Speccy Bros. (Multi-Load fast setting), 1'39" [3'32"]
I have 855T pulses to store a copy of the 'current' tape bit value, 1710T to invert the current bit value and store, and the huge 2327T (only on the first byte pulse) which indicates that the byte is a copy of the previously loaded byte.
It's not brilliant. Except after CLS!
Save_Bytes proc ld hl,#053f push hl ld hl,#0c98 l04d0: ex af,af' ; flag byte to A' ld a,(ix+0) inc a ld (LDSV_lastbyte),a ; ensure mismatch in last byte check in first saved byte inc de dec ix di ld a,#02 ld b,a l04d8: djnz l04d8 out (#fe),a xor #0f ld b,#a4 dec l jr nz,l04d8 dec b dec h jp p,l04d8 ld b,#2f l04ea: djnz l04ea out (#fe),a ld a,#0d ld b,#37 l04f2: djnz l04f2 out (#fe),a ld b,#3b xor a ; set initial last bit saved flag ex af,af' ; and move to A' ld l,a ; flag byte to L jp l0507 ; branch to save the flag byte (which will not be a repeating byte) and initialise parity ; pulses at 855t for normal byte save ; 1710t for repeating bit save ; 2327t for repeating last byte saved save_byte: ld a,d or e jp z,save_parity ; 18 ld l,(ix+#00) ld a,(LDSV_lastbyte) cp l jp nz,sv_new_last_byte ; +43 = 61 ; 2327t tpulse1 defl (2327-61-37)/13 tpulse2 defl 2327/13 ld bc,+(tpulse1 shl 8) or tpulse2 call save_edge_bc jp sv_next_byte ; Note: we skip updating parity for repeating bytes sv_new_last_byte: ld a,l ld (LDSV_lastbyte),a ; +14 = 75 ; 855t tpulse1 defl (855-75-37)/13 tpulse2 defl 855/13 sv_normal: ld bc,+(tpulse1 shl 8) or tpulse2 call save_edge_bc jp save_byte_entry ; 855t tpulse1 defl (855-18-31)/13 tpulse2 defl 855/13 save_parity: ld l,h ld bc,+(tpulse1 shl 8) or tpulse2 call save_edge_bc save_byte_entry: ld b,#31-2 ld a,h xor l l0507: ld h,a ld a,#01 scf jp l0525 l0511: ld a,#0e bit 7,b l0514: djnz l0514 ex af,af' jr nc,l051c ld b,#42 l051a: djnz l051a l051c: ex af,af' out (#fe),a ld b,#3e jr nz,l0511 ld a,l or a rl a jr z,sv_next_byte ld b,#3b xor a inc a ; A = 1 for border/mic, clear Carry to rotate into L l0525: ex af,af' xor l ; look for bit transition in bit 7 rla ; Carry set for bit transitions ld a,l ; take a copy of L before rotating ex af,af' ld b,#39 rl l jp nz,l0514 sv_next_byte: dec de inc ix ld b,#31 ld a,#7f in a,(#fe) rra ret nc ld a,d inc a jp nz,save_byte ld b,#3b l053c: djnz l053c ret save_edge_bc: djnz save_edge_bc ld a,1 out (#fe),a ld b,c .sv_pulse_len2: djnz .sv_pulse_len2 ld a,#0e out (#fe),a ret endp Load_Bytes proc inc d ex af,af' dec d di ld a,#0f out (#fe),a ld hl,#053f push hl in a,(#fe) rra and #20 or #02 ld c,a cp a l056b: ret nz l056c: call l05e7 jr nc,l056b ld hl,#0415 l0574: djnz l0574 dec hl ld a,h or l jr nz,l0574 call l05e3 jr nc,l056b l0580: ld b,#9c call l05e3 jr nc,l056b ld a,#c6 cp b jr nc,l056c inc h jr nz,l0580 l058f: ld b,#c9 call l05e7 jr nc,l056b ld a,b cp #d4 jr nc,l058f call l05e7 ret nc ld a,c xor #03 ld c,a ld h,#00 ld b,#b0 ld l,#01 res 7,c ; initialise last bit loaded state jr .wait_pulse ; first (flag) byte will not be a repeating byte l05a9: ex af,af' jr nz,l05b3 jr nc,l05bd ld (ix+#00),l ld a,l ld (LDSV_lastbyte),a jr l05c2 l05b3: push bc rl c xor l ld a,c pop bc ret nz rra ex af,af' ld b,#b1 jr l05c8 l05bd: ld a,(ix+#00) xor l ret nz l05c2: inc ix l05c4: dec de l05c5: ex af,af' ld b,#b2 l05c8: ld l,#01 ; bd = 189 ; da = 218 ; f3 = 243 ; cp #cb = 203 ; cp #e6 = 230 ; pulses at 855t for store current bit ; 1710t for toggle and store current bit ; 2327t for repeating last byte loaded (first byte pulse only) call l05e3 ret nc ld a,b ; testing first pulse for long pulse length cp #e6 jp nc,.ld_repeat_byte ld b,#b2 .wait_pulse: call l05e3 ret nc ld a,#cb cp b jr nc,.set_load_bit ld a,c xor 128 ld c,a .set_load_bit: ld a,c rla rl l ld b,#b0-1 jp nc,.wait_pulse .loaded_byte: ld a,h xor l ld h,a .rep_byte_load: ld a,d or e jr nz,l05a9 ld a,h cp #01 ret l05e3: call l05e7 ret nc l05e7: ld a,#16 l05e9: dec a jr nz,l05e9 and a l05ed: inc b ret z ld a,#7f in a,(#fe) rra ret nc xor c and #20 jr z,l05ed ld a,c xor %00100111 ld c,a and #07 or #08 out (#fe),a scf ret .ld_repeat_byte: ld a,(LDSV_lastbyte) ld l,a jp .rep_byte_load ; we won't update parity with repeating bytes endpYes, but my idea is for something a bit quicker than the ROM loader but using timings as reliable for loading data after compression!
500T for store current bit, 1000T for invert current bit and store, 1500T to repeat last loaded byte.