Questions about saving unprotected versions of 48K games

The place for codemasters or beginners to talk about programming any language for the Spectrum.
Post Reply
User avatar
zxbruno
Manic Miner
Posts: 214
Joined: Sun Mar 04, 2018 6:13 am

Questions about saving unprotected versions of 48K games

Post by zxbruno »

I'm one of the few people in the world who still finds machine code complicated, so forgive me if the questions are dumb...

-Some older games load with Load""code and the code block occupies 49152 bytes or more. How does the rom know which address to jump to once the block is loaded? What is the first thing the rom Load routine does after loading such a long code block? Does it jump to whatever Randomize usr command was present after the Save command when the code block was saved? Something like save "bazhinga" code 16384,49152 : randomize usr 25200?

-If I want to do the above but start with an emulator and export a .bin or .raw file of 49152 bytes, could I manually add the JP address to it afterwards? The goal would be to have a simple, fool-proof way of saving a clean, unprotected .TAP file of any 48K game without having to know machine code, debugging, decrypting, moving blocks of code, etc.

-Some games load multiple blocks of code before jumping to the start address. Is there a way to create a breakpoint to make an emulator pause once all tape loading is done? The goal is to find the game start address without having to understand machine code. It's easy to recognize if the loader uses LDs and a JP at the end, but I get lost if it involves Calls and Rets...
User avatar
ketmar
Manic Miner
Posts: 697
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Questions about saving unprotected versions of 48K games

Post by ketmar »

i guess the absence of answers here is because nobody wants to be the first telling you that you abolutely have to learn CALL and RET to do what you want. ;-)

the trick with loading the whole RAM is exactly that: it replaces return address on a stack with another one, so when loader code finally performs RET, the saved address is already replaced with a game starting address. it has nothing to do with BASIC code, it is "direct machine stack manipulation". you still can save such blocks as normal, tho, using a trick with "CLEAR". "CLEAR" to some safe address (that moves the stack), and poke values where the original stack was. then save. now, loader doesn't do that additional "CLEAR", so stack pointer is where you poked your numbers. actually, the description is way more complicated than the trick itself. ;-)

as for saving the snapshot right after the loading is done... you can set a breakpoint in debugger, so the emulator will stop right before the ROM loader is going to RET (you may need to allow it to continue several times until all the data is loaded), and then save a snapshot. but this will work only when the game is using ROM routines, and if it is encrypted, then you'll get a working, but still encrypted snapshot.

but learning Z80 assembler is not that hard. 8-bit CPUs are really simple devices, with relatively small set of commands. understanding the basics is quite easy. you may not be able to code your own routines yet, but you will be able to read other's code. it is like learning some natural language -- you can get basic understanding of simple sentences quite fast. it won't allow you to write a poetry, but it may be enough for understanding things you want to understand.
User avatar
1024MAK
Bugaboo
Posts: 3114
Joined: Wed Nov 15, 2017 2:52 pm
Location: Sunny Somerset in the U.K. in Europe

Re: Questions about saving unprotected versions of 48K games

Post by 1024MAK »

CALL is a bit like GOSUB in BASIC.
RET is a bit like RETURN in BASIC.

In both cases, when the system jumps to the subroutine, the address (for CALL) / line number (for GOSUB) has to stored somewhere in RAM.

The Z80 CPU has a special register called the stack pointer that points to a memory location. This register is normally set up to point to an area of RAM out of the way of other code or data in RAM. Often it’s set to somewhere in high memory.

Now if the data loaded from tape overwrites the area of RAM where the stack pointer register points points to, the return address will be different, hence the CPU will “return” to a different area of code, which it will then execute as normal (normal as it sees it).

The other way of doing it, is to save the system variables, a short BASIC program, the variables of the BASIC program and the machine code all as one long block of ‘code’. When this loads, it literally overwrites the existing system variables. Within the system variables is a location that contains the next line number and statement that BASIC should process. The value loaded from tape will now point to the BASIC program that was in memory when it was saved. This will then RUN. Most then do the usual RANDOMISE USR xxxx to then run the machine code at xxxx.

Mark
:!: Standby alert :!:
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb :dance
Looking forward to summer later in the year.
User avatar
Bedazzle
Manic Miner
Posts: 305
Joined: Sun Mar 24, 2019 9:03 am

Re: Questions about saving unprotected versions of 48K games

Post by Bedazzle »

ketmar wrote: Fri Jun 19, 2020 1:43 am you can set a breakpoint in debugger, so the emulator will stop right before the ROM loader is going to RET
...or set breakpoint to "when TAPE starts/stops". If I remember correctly, Spin can do that.
User avatar
PQR
Manic Miner
Posts: 241
Joined: Sat May 12, 2018 11:35 am
Contact:

Re: Questions about saving unprotected versions of 48K games

Post by PQR »

zxbruno wrote: Thu Jun 18, 2020 12:17 am -If I want to do the above but start with an emulator and export a .bin or .raw file of 49152 bytes, could I manually add the JP address to it afterwards? The goal would be to have a simple, fool-proof way of saving a clean, unprotected .TAP file of any 48K game without having to know machine code, debugging, decrypting, moving blocks of code, etc.
This is essentially what the Multiface was designed to do. If you want to take a snapshot from any given 48K game while it is running then you need to preserve more than just the contents of memory. You need to preserve the state of the Z80 as well, including all values contained in the CPU's registers. The solution the Multiface employed was to use screen memory for the additional data, hence the garbled screen after loading such a snapshot.
User avatar
zxbruno
Manic Miner
Posts: 214
Joined: Sun Mar 04, 2018 6:13 am

Re: Questions about saving unprotected versions of 48K games

Post by zxbruno »

Thank you. Baby steps...

Here's an example of my limited knowledge and how far I went when trying to understand the machine code in Flying Shark's .TAP. This is from the .TAP collection for DivMMC that was shared a few days ago.

-Basic loader has randomize usr that runs code hidden in REM line
-Code copies loader to another location, sets black paper and ink, and proceeds to load screen$ and one code block using rom Load routine. So far this is easy to understand, even for someone who doesn't know machine code.

Code: Select all

23808 ld ix,16384
ld de 6912
call 31933
ld ix,32768
ld de,32768
call 31933
jp 40506
31933 has ld a,255 / sfc / call 1366. This is also easy to understand

40506 is not the game start address. A few more things happen:

Code: Select all

di
ld hl,22528
ld de,22529
ld bc,767
ld (hl),07
ldir
Not sure what the above does. Is it a simple attribute clear routine?

And then it continues...

Code: Select all

ld hl,40506
ld de,19000
ld bc,76
ldir
jp 19028
No idea what the above does... 19028 doesn't appear to be the game start address. What comes next is greek to me:

Code: Select all

ld sp,19074
ld hl,(19015)
ld de,76
add hl,de
ld de,23296
ld b,(hl)
inc hl
ld b,(hl)
inc hl
ld a,c
or b
jr z,19059
...
Is it doing some sort of decryption? This is where I feel lost. I understand it changes the stack to the loading screen area and runs something for about half a second before jumping to the menu. That's the extent of my "knowledge" :/
User avatar
ketmar
Manic Miner
Posts: 697
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Questions about saving unprotected versions of 48K games

Post by ketmar »

zxbruno wrote: Sat Jun 20, 2020 12:24 am

Code: Select all

di
ld hl,22528
ld de,22529
ld bc,767
ld (hl),07
ldir
Not sure what the above does. Is it a simple attribute clear routine?
yes, exactly. it sets all attributes to "7" (aka "white ink, black paper").
zxbruno wrote: Sat Jun 20, 2020 12:24 am And then it continues...

Code: Select all

ld hl,40506
ld de,19000
ld bc,76
ldir
jp 19028
No idea what the above does... 19028 doesn't appear to be the game start address.
this moves 76 bytes from address 40506 to address 19000 (which is a screen area). it is quite common to use screen area as a temporary buffer in loaders. so it moves some 76-byte routine (prolly with some working variables) to screen area, and then jumps into it.
zxbruno wrote: Sat Jun 20, 2020 12:24 am What comes next is greek to me:

Code: Select all

ld sp,19074
ld hl,(19015)
ld de,76
add hl,de
ld de,23296
ld b,(hl)
inc hl
ld b,(hl)
inc hl
ld a,c
or b
jr z,19059
...
Is it doing some sort of decryption? This is where I feel lost. I understand it changes the stack to the loading screen area and runs something for about half a second before jumping to the menu. That's the extent of my "knowledge" :/
it is hard to say without the rest of the code (and you prolly have a typo there, one of "ld b,(hl)" should be "ld c,(hl)", i guess). this may be a part of interface-2 restoration code (if this is IF-2 snapshot), or some decryption routine indeed.

p.s.: please, post the rest of the code, and we will decipher it together! you'll see that it is not that hard (i hope ;-).
User avatar
zxbruno
Manic Miner
Posts: 214
Joined: Sun Mar 04, 2018 6:13 am

Re: Questions about saving unprotected versions of 48K games

Post by zxbruno »

Thank you. You were right about the typo.

So, we know it jumps to 19028, but the previous routine copied 76 bytes to 19e3 therefore I'll start there:

Code: Select all

@19000:
di
ld hl,22528
ld de,22529
ld bc,767
ld (hl),7
ldir
ld hl,40506
ld de,19000
ld bc,76
ldir
jp 19028
This is what was at 40506 before the 76 bytes were copied to the screen memory. Why did they copy the routine that copied the routine? :?

And here's what I think is the rest of the routine before it jumps to the menu (49 bytes):

Code: Select all

@19028:

ld sp,19074
ld hl,(19015)
ld de,76
add hl,de
ld de,23296
ld b,(hl)
inc hl
ld c,(hl)
inc hl
ld a,c
or b
jr z,19059
push hl
ld h,d
ld l,e
inc de
ld (hl),0
ldir
ex de,hl
pop hl
ld c,(hl)
inc hl
ld b,(hl)
inc hl
ld a,c
or b
jr z,19071
ldir
jr 19041
ret
What's the point (no pun intended) in setting the stack pointer to 19074? I'm curious because the main code block is only 32K. Isn't there plenty of room to have the stack set somewhere outside of the screen area?

The routine ends with Ret, but as far as I can see it didn't start with a Call (remember, I'm thinking in Basic). Just trying to understand what happens above, and what the real decimal start address is. Where is it going after that Ret command? I added a breakpoint at 19071, where "Ret" is, but don't know what to do next. I look at registers in Spectaculator and see nothing that looks like a valid address to jump to. Still have a long way to go but I find this interesting.
User avatar
ketmar
Manic Miner
Posts: 697
Joined: Tue Jun 16, 2020 5:25 pm
Location: Ukraine

Re: Questions about saving unprotected versions of 48K games

Post by ketmar »

"RET" basically does this:
LET addr = peek(SP)+256*peek(SP+1) : LET SP = SP+2 : GOTO addr
i.e. it takes the two byte address at (SP), and increments SP by two bytes.

so, "ld sp,19074" sets SP to 19074. and "RET" reads address from 19074, and jumps to that read address.

and if you're interested, "CALL" does the opposite: decrements SP by two bytes (first), and then stores address of the next instruction (i.e. that one that follows "CALL") at (SP) and (SP+1). as you can see, it just prepared everything for "RET" to return to the instruction right after "CALL". this is basically all what CALL/RET does -- using part of the memory (whose address is hold in SP) to store temporary values.

so to know where "RET" will go, you have to look at the memory at 19074. the first byte (at 19074) will be the "low" part of the address, and the second byte (at 19075) will be the "high" part.


the assembler code does this (i translated it to more-or-less high level pseudocode):

Code: Select all

  hl = peekw(19015)+76
  de = 23296

again:
  count = peekw(hl)
  hl = hl+2

  if (count != 0) {
    zero count+1 bytes at de
    de = de+count
  }

  count = peekw(hl)
  hl = hl+2
  if (tmp == 0) goto exit;

  copy count bytes from hl to de
  hl = hl+count
  de = de+count
  goto again

exit:
  ret
as we can see, it simply copies around blocks of memory (with special case for "block of all zeroes"). it looks like the all game data was tightly put to avoid saving unnecessary bytes to tape, and now we're going to return code blocks to their original places.

this "returning" routine was copied to screen area because usually there is no executable code in screen$, so the code can freely operate on the whole Speccy memory from there, and be sure that it won't copy something over itself. this is the reason to set stack into SCREEN$ too, to avoid accidental stack corruption by some copy/zero operations.

if you wonder how we came to "zero bytes" from that "ldir" part, it is easy:
"LDIR" is a command to copy BC bytes from address stored in HL to address stored in DE. it does so step by step, like this:

Code: Select all

  tmpbyte = peekb(HL)
  pokeb (DE),tmpbyte
  HL = HL+1
  DE = DE+1
now, if we set DE to HL+1, and write some byte to the address from HL, then we will effectively copy that byte over the whole memory block (because it will be read, written to the next address, then read from that next address, writen forward again, and so on).


so, as you can see, nothing really tricky is going on here, we just copying memory blocks to where they should be for the game to run properly (they were prolly "condensed" by another routine prior saving).


so as you arrived at that final "RET", everything should be in place (if there are no other decryption routines in the game code itself), and we are ready to jump into that game code. your debugger should have "single step" command, which simply executes one instruction, so you can use that command to execute "RET" to avoid manual address calculations. if there are no other decryptors, you will prolly see commands to load new value to SP there (the "real" SP that the game will use), prolly init some other registers, and jump to the game title screen code.
Post Reply