Multi-Loaders, how did they work and how do you write one?

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
Nomad
Manic Miner
Posts: 600
Joined: Thu Dec 28, 2017 12:38 pm

Multi-Loaders, how did they work and how do you write one?

Post by Nomad »

So I understand the custom loader is the following.

1) Copy ROM loader to RAM.
2) Fix absolute jumps.
3) Change timing constants.
4) Change border colour code.

So for doing stuff like changing the loading colours, way the screen$ load .. fair enough.
That is not so bad, its just a few adjustments to the stock rom code. But what I don't understand is..

The games that were multi-part, how was that coded? I am assuming the memory was just over-written with a portion retained for persistent data in the game. Or is that too simplistic?

Are there any source examples on how you do this?
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: Multi-Loaders, how did they work and how do you write one?

Post by Ast A. Moore »

Nomad wrote: Wed Jan 03, 2018 6:04 pm So I understand the custom loader is the following.

1) Copy ROM loader to RAM.
2) Fix absolute jumps.
3) Change timing constants.
4) Change border colour code.
Or, write your own from scratch.
Nomad wrote: Wed Jan 03, 2018 6:04 pmBut what I don't understand is..

The games that were multi-part, how was that coded? I am assuming the memory was just over-written with a portion retained for persistent data in the game.
Pretty much. 128K games required multiple parts (as in more than a one data block), because different data was loaded into different RAM banks, and before you could start loading data, you needed to switch banks.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Alessandro
Dynamite Dan
Posts: 1908
Joined: Wed Nov 15, 2017 11:10 am
Location: Messina, Italy
Contact:

Re: Multi-Loaders, how did they work and how do you write one?

Post by Alessandro »

Have a look at my SetoLOAD turbo loader. It was built as a modification of the ROM load routine, plus some custom border effects for the Multi version. It comes in several flavors and can be freely used in your projects - even commercial ones. All I ask you in return is to mention me as its author somewhere, for instance: "'SetoLOAD' turbo loading scheme by Alessandro Grussu".
Hikaru
Microbot
Posts: 100
Joined: Mon Nov 13, 2017 1:42 pm
Location: Russia
Contact:

Re: Multi-Loaders, how did they work and how do you write one?

Post by Hikaru »

I think you might be confusing loaders with ... ... loaders? - I guess it is in fact the same word. >.> Well, let's call them 'loaders' and 'decoders' for the sake of this example.

Decoders: as in the exact routines that decode the tape signals into bits and bytes of data and store these in the memory. This determines the overall encoding scheme used for the data on the tape, as well as the loading speed, reliability, border colors, some special functions such as loading SCREEN$ in fancy ways, and such like.

Loaders: as in 'BASIC loaders' or 'program loaders'. For instance, a game loader (written in BASIC) that loads and runs a game (written in machine code), using a number of LOAD ""CODE statements and then a RANDOMIZE USR in the end.

Put simply, the relationship between the two is that Loaders rely upon Decoders in order to work. But, although you can write your own Decoder, it's not necessary if what you really want is a Loader, because the Spectrum ROM already includes a default Decoder for you. This is used by the LOAD command among other things, and similarly it can also be used from assembly, e.g. like this:

Code: Select all

	ld ix,Address
	ld de,Length
	scf
	sbc a
	exa
	call #0562
	jr nc,LoadError
This will load a 'headerless block' of code.
So a Loader in machine code can be as simple as chaining several such pieces of code together, all using the default Decoder provided with the ROM, with a JP Game in the end, not unlike LOAD ""CODE ... RANDOMIZE USR statement sequence in BASIC.

Multi-part or multi-load games are not any different in this regard. It is again just about the way these loading-statements are combined, whether it's BASIC or machine code - determining the order in which things are loaded, looping if necessary (bang - ur dead, pls load level 1) and so on.
Inactive account
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: Multi-Loaders, how did they work and how do you write one?

Post by Bizzley »

If your question was referring to how the actual LOAD CODE routines were customised and interpreted then I think the above answers give you pretty much all you need to know. If on the other hand it was more about the logistics of a multi-load game i.e. don't care too much what the LOAD routines do code-wise internally but rather want to know more about how you actually go about structuring a game to be multi-load (especially on a non-bankable 48K model) then that's a different thing. I can tell you how I did but it wouldn't be THE way it's done, just one of many different ways it can be.
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
Nomad
Manic Miner
Posts: 600
Joined: Thu Dec 28, 2017 12:38 pm

Re: Multi-Loaders, how did they work and how do you write one?

Post by Nomad »

Alessandro wrote: Thu Jan 04, 2018 11:33 am Have a look at my SetoLOAD turbo loader. It was built as a modification of the ROM load routine, plus some custom border effects for the Multi version. It comes in several flavors and can be freely used in your projects - even commercial ones. All I ask you in return is to mention me as its author somewhere, for instance: "'SetoLOAD' turbo loading scheme by Alessandro Grussu".
Thank you for the tip, I will take a look. About the credit, of course I would give you credit for what you wrote :) I am surprised people wouldn't do that.
Hikaru wrote: Thu Jan 04, 2018 12:27 pm I think you might be confusing loaders with ... ... loaders? - I guess it is in fact the same word. >.> Well, let's call them 'loaders' and 'decoders' for the sake of this example.
Yes I was lumping them together when they really are two separate operations I guess.
Hikaru wrote: Thu Jan 04, 2018 12:27 pm Decoders: as in the exact routines that decode the tape signals into bits and bytes of data and store these in the memory. This determines the overall encoding scheme used for the data on the tape, as well as the loading speed, reliability, border colors, some special functions such as loading SCREEN$ in fancy ways, and such like.

Loaders: as in 'BASIC loaders' or 'program loaders'. For instance, a game loader (written in BASIC) that loads and runs a game (written in machine code), using a number of LOAD ""CODE statements and then a RANDOMIZE USR in the end.

Put simply, the relationship between the two is that Loaders rely upon Decoders in order to work. But, although you can write your own Decoder, it's not necessary if what you really want is a Loader, because the Spectrum ROM already includes a default Decoder for you. This is used by the LOAD command among other things, and similarly it can also be used from assembly, e.g. like this:

Code: Select all

	ld ix,Address
	ld de,Length
	scf
	sbc a
	exa
	call #0562
	jr nc,LoadError
This will load a 'headerless block' of code.
So a Loader in machine code can be as simple as chaining several such pieces of code together, all using the default Decoder provided with the ROM, with a JP Game in the end, not unlike LOAD ""CODE ... RANDOMIZE USR statement sequence in BASIC.

Multi-part or multi-load games are not any different in this regard. It is again just about the way these loading-statements are combined, whether it's BASIC or machine code - determining the order in which things are loaded, looping if necessary (bang - ur dead, pls load level 1) and so on.
Ah ok so the headerless code just overwrites the previous code for the multi-loader games with a jp back to the start of the game loop once the new code is loaded into memory.
Ast A. Moore wrote: Wed Jan 03, 2018 7:08 pm Pretty much. 128K games required multiple parts (as in more than a one data block), because different data was loaded into different RAM banks, and before you could start loading data, you needed to switch banks.
So this is why people say its more difficult to write for the +2/+3. Interesting. I always wondered why there was not as much written for these systems. Its a shame as a I had a +3 new for Christmas, but there were never as many games that took advantage of its extra power.
Bizzley wrote: Thu Jan 04, 2018 3:47 pm If your question was referring to how the actual LOAD CODE routines were customised and interpreted then I think the above answers give you pretty much all you need to know. If on the other hand it was more about the logistics of a multi-load game i.e. don't care too much what the LOAD routines do code-wise internally but rather want to know more about how you actually go about structuring a game to be multi-load (especially on a non-bankable 48K model) then that's a different thing. I can tell you how I did but it wouldn't be THE way it's done, just one of many different ways it can be.
I was curious about how the multi-load games were designed, what design constraints that placed on your game. Any tips would be great. If you have any examples of how the code worked that would be nice to look at.
User avatar
Ast A. Moore
Rick Dangerous
Posts: 2640
Joined: Mon Nov 13, 2017 3:16 pm

Re: Multi-Loaders, how did they work and how do you write one?

Post by Ast A. Moore »

Nomad wrote: Thu Jan 04, 2018 4:19 pm
Ast A. Moore wrote: Wed Jan 03, 2018 7:08 pm Pretty much. 128K games required multiple parts (as in more than a one data block), because different data was loaded into different RAM banks, and before you could start loading data, you needed to switch banks.
So this is why people say its more difficult to write for the +2/+3. Interesting. I always wondered why there was not as much written for these systems. Its a shame as a I had a +3 new for Christmas, but there were never as many games that took advantage of its extra power.
Not quite. The +2/+2A/+3 offered little to no advantages over the original 128K machine. Aside from the disk drive of the +3, in many respects they could all be considered identical (a few minor technicalities aside). And people did take advantage of the extra memory, the AY sound chip, and a few other technical details that distinguished these machines from the 48K Speccy. The reason for the smaller number of titles released specifically for these machines has more to do with the fact that they came out a little too late. A shift toward 16-bit computers had already begun, and unlike us—enthusiasts overcome with nostalgic feelings—publishers were in this business to make money. The money was in the 16-bit market, so that was where the resources went.
Every man should plant a tree, build a house, and write a ZX Spectrum game.

Author of A Yankee in Iraq, a 50 fps shoot-’em-up—the first game to utilize the floating bus on the +2A/+3,
and zasm Z80 Assembler syntax highlighter.
User avatar
Joefish
Rick Dangerous
Posts: 2041
Joined: Tue Nov 14, 2017 10:26 am

Re: Multi-Loaders, how did they work and how do you write one?

Post by Joefish »

The easiest way to understand a multi-load game is if all the levels and graphics run in exactly the same game engine. The obvious example is Gauntlet. The 48K can only hold so many level maps in memory. Once you get past those, a bit of machine code then loads some more data off the tape, over-writing the level data that's in memory with some new levels, then carries on playing the game. The players' scores, along with all the graphics and game code are unaffected.

Now come to something like R-Type. Again, the 48K Spectrum can only hold one level in memory, so it would load them one at a time as you come to them. But on a 128K you can page out the top 16K of memory, switch in another page of RAM and load a level or two into that, and the game's just the right size that it can all be held in memory at once. Now a game could put the data in the same place in each page, so the game code just uses whichever page is switched in. Or it could use the pages like a RAM disk, copying whatever it needs into its working RAM then switching back to a default page to run the game. This is a better strategy if the data is compressed and needs unpacking before it can be used. In this case you might use lower RAM to hold the live copy of the data, and put the game code in one specific page at the top of memory - just remember to page it back in before jumping to it!

It gets a bit more complicated if you're actually loading in game code as part of the multi-load, so that different levels of the game have different behaviour. Then you need to carefully plan where it's going to end up in memory and when it's going to be called. It's much easier to just load data.

As for writing one, I strongly recommend just calling the headerless load function in ROM as described above. Don't bother with custom fast laoders and the like. The reason is that a lot of people will be playing on an emulator, and the emulator can detect the call to the ROM function and substitute instant data loading from a .TAP file instead. This is also a good strategy for loading a 128K game, switching memory pages then loading another headerless 16K file to fill each one.

It's easier to load a headerless file from machine code than one with a header. If you want to use regular files with header and body, you can load the header as a 17-byte headerless load and just throw it away, then load the bulk of the file as a second headerless load.
User avatar
Seven.FFF
Manic Miner
Posts: 735
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: Multi-Loaders, how did they work and how do you write one?

Post by Seven.FFF »

Joefish wrote: Thu Jan 04, 2018 5:18 pm The reason is that a lot of people will be playing on an emulator, and the emulator can detect the call to the ROM function and substitute instant data loading from a .TAP file instead.
divMMCs too. People with these devices often complain about custom loaders not working, and end up using .Z80 snapshots - something which is fine for single load games, but inconvenient for multiload games.

You'd usually only bother with a custom fastloader if you were doing a boutique cassette release, and you wanted to cut down on loading time.
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
Bizzley
Microbot
Posts: 124
Joined: Thu Nov 16, 2017 10:47 am

Re: Multi-Loaders, how did they work and how do you write one?

Post by Bizzley »

Unfortunately unlike the code that actually loads data from disk or tape there is no similar "block of code" you can add to a program to make it multi-load. It's all about how you lay out your data and then how you access it. You were right in that you set aside a section of RAM specifically for the level data and then over-write it when you load in a new level and yes, it really is that simple. A level in a game will usually comprise of things like map data, tile data, character data, sprite data, sprite patterns, bitmaps and game code (to name but a few) that is specific to the level you are playing. So storing off something that you're only going to use in one particular level in what you'd call the main block of game code that wasn't overwritten would be wasteful if memory is tight, though may be feasible if you have lots of memory to play with.

Perhaps an easier way to wrap your head around multi-load levels is to think of them as individual games where just a few important details stay the same when you finish one level and get transferred to the next. Things like the score, lives, credits, weapons, abilities, level number, objects carried etc. Rather than waste time loading in the same game code over and over it makes more sense to just load in a pared down version of the game (i.e. the data specific to that level as mentioned above) and instead of starting the game fresh continue playing with those saved off values. It's all a trick really, you're playing the same game over and over and just replacing the game map, sprites and sometimes game logic with something different each time.

The old way of preparing levels - when assemblers and development hardware was less sophisticated and powerful - was to code each level as if it was an individual game, make sure it worked properly, and then wait until the game was finished. When you were ready you'd then assemble up each level and save off the block of code that you've set aside to hold the level. It was a bit of a juggling act since you had to make sure that any addressing carried out by code in that block of code, such as a Call to a subroutine that was in a different part of memory, was the same and not invalid because of something you'd changed. Modern cross-assemblers now let you assemble different blocks of code to the same address and write out the data after each run so quickly that it's much easier to control what you have and ensure it's correct.

Unless you have a very strict and rigid control of where you put the data that made up a level, e.g. such as always putting the sprite data at the same memory address in each level, then the easiest way to make sure you know where everything is is to use a pointer to that data and then access it via that pointer. This sounds complicated but it's as simple as setting aside a few bytes at the start of each level and then changing those bytes to point towards where the actual data is in that level. You don't need that many pointers to be honest, these are the ones I had for one of my old games and they were all I needed :

Code: Select all

		org 50350 
START		db 00
SYNCHRO		dw 0000
START1		db 06
START2		db 37
START3		db 75
SYNCHRO1	dw SYNC1
SYNCHRO2	dw SYNC2
SYNCHRO3	dw SYNC3
TSPRADD		dw TSPRTABLE
TADD		dw TILETABLE
SEQTABLE	dw SQTABLE
SPEVENT1	dw SPEV1
SPEVENT2	dw SPEV2
Then it doesn't matter where the actual data is being held (so for example TSPRTABLE is where the Sprite Table Data starts) because when you assemble everything the address TSPRADD will point to the address of TSPRTABLE and as long as you access it via the TSPRADD address it will work for every level regardless of where you put the Sprite Table.

There are a few other things to consider, like a hierarchical approach to the way you use code and store variables in the game so that starting a game, starting a new level, restarting a level, losing a life, losing all lives etc. can be handled easily without losing track of what goes where. Another thing to consider is trying to arrange the level code so that if a player does lose all their lives then they really shouldn't have to load that level in again just because you've overwritten or changed something in that level.
"He made eloquent speeches to an audience consisting of a few depressed daffodil roots, and sometimes the cat from next door."
User avatar
Alessandro
Dynamite Dan
Posts: 1908
Joined: Wed Nov 15, 2017 11:10 am
Location: Messina, Italy
Contact:

Re: Multi-Loaders, how did they work and how do you write one?

Post by Alessandro »

Seven.FFF wrote: Thu Jan 04, 2018 6:49 pmYou'd usually only bother with a custom fastloader if you were doing a boutique cassette release, and you wanted to cut down on loading time.
I disagree. There are quite a few people around (including myself) who load data into their Spectrums by "reading" the files on a PC through Tapir or a similar program and redirecting the sound output to real hardware. Custom loaders are really useful in such a situation. For this reason I always release my software as TZX turbo tape image files as well. I even developed my own custom turbo loader for that!
User avatar
Seven.FFF
Manic Miner
Posts: 735
Joined: Sat Nov 25, 2017 10:50 pm
Location: USA

Re: Multi-Loaders, how did they work and how do you write one?

Post by Seven.FFF »

Yes, true :)
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
NXtel NXTP ESP Update ESP Reset CSpect Plugins
Post Reply