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.
User avatar
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 » 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.

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?
0 x

User avatar
Ast A. Moore
Manic Miner
Posts: 662
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 » Wed Jan 03, 2018 7:08 pm

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 pm
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.
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.
1 x
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
Microbot
Posts: 199
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 » 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".
1 x
Author of Lost In My Spectrum, Apulija-13, Funky Fungus, Cousin Horace, The Trunk, Seto Taisho Vs Yokai, Seto Taisho To Kazan, Sophia, Doom Pit and the Spectrumpedia.

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 » 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.

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.
1 x
Inactive account

Bizzley
Berk
Posts: 30
Joined: Thu Nov 16, 2017 10:47 am

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

Post by Bizzley » 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.
1 x

User avatar
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 » Thu Jan 04, 2018 4:19 pm

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.
0 x

User avatar
Ast A. Moore
Manic Miner
Posts: 662
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 » Thu Jan 04, 2018 4:49 pm

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.
0 x
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.

Joefish
Manic Miner
Posts: 285
Joined: Tue Nov 14, 2017 10:26 am

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

Post by Joefish » Thu Jan 04, 2018 5:18 pm

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.
1 x

User avatar
Seven.FFF
Manic Miner
Posts: 283
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 » Thu Jan 04, 2018 6:49 pm

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.
0 x
Robin Verhagen-Guest
SevenFFF / Threetwosevensixseven / colonel32
seven-fff.com/blog

Bizzley
Berk
Posts: 30
Joined: Thu Nov 16, 2017 10:47 am

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

Post by Bizzley » Thu Jan 04, 2018 6:54 pm

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.
2 x

Post Reply