News:

Printed Amstrad Addict magazine announced, check it out here!

Main Menu

Reverse engineering Saboteur 2

Started by Singaja, 21:00, 17 November 15

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Singaja

I've been playing around with Saboteur 2, an important game of my childhood. It's a speccy port and not a particularly decent one, the animation and drawing is pretty sluggish so I hope it can be improved. So far I'm mostly creatively breaking the game  ;)
I'm always referring to the disk version loaded into the memory and that is what I'm analysing.


I'm including my current notes and I'd like to present a small showcase of what I found(notes cover much! more) , starting from something very simple, but fun nonetheless:

Held projectile in memory - 1 byte
ROM:3495 default is 1 - shuriken,if replaced with 8 will be a fully animated bat projectile going in straight line!, this does not affect the image on the hud

You can play around with colors:


color mappings
;;;;;;;;;;;;
; 00 gray
; 01 gray
; 02 light green
; 03 light yellow
; 04 dark blue
; 05 magenta
; 06 teal
; 07 pink
; 08 magenta
; 09 light yellow
; 0A yellow
; 0B white
; 0C red
; 0D magenta
; 0E orange
; 0F pink
; 10 sky blue
; 11 light green
; 12 green
; 13 cyan
; 14 black
; 15 blue
; 16 dark green
; 17 light blue
; 18 violet
; 19 very light green
; 1A lime green
; 1B light cyan
; 1C dark red
; 1D light violet
; 1E yellowish green
; 1f very light violet


3ee0 data
3ee6 data upper sky color -
3ee7 data moon color
3ee8 data upper wall tile/ladder infrastracture on sky tile color
3ee9 data upper building/characters/wall tile background color
3eea data middle sky color
3eeb data middle red bricks/red elements
3eec data middle wall tile/ladder infrastracture on sky tile color
3eed data middle building/characters/wall tile background color
3eee data bottom sky color
3eef data Very bottom red brick?
3ef0 data bottom wall tile/ladder infrastracture on sky tile color
3ef1 data bottom building
3ef2 data menu Red
3ef4 data cash color/timer color
3ef5

But that's pretty basic stuff, so I wanted to move on to something more challenging. I analysed the drawing routine for moon,barrel,wardrobe that kind of stuff , it always involved specifying the origin in hl registers, c - width , b - height, and ix start of tileset. This nasty ix is slow, so I recreated an unwinded loop without using ix at all. Then I actually automated it with a very simple c program which accepts width,height and starting ix and generated a ready to copy-paste opcodes to WinApe.


#include <stdio.h>
int main()
{
   int width = 2;
   int height = 2;
   int start = 0x1FA0;
   int addToNextRow = 0x21-width;

//ld a,(#1C1D) ; 13 cycles ;3A EC 1B
//ld (hl),a    ; 7 cycles ;77
//inc l        ; 4 cycles  ;2C
//ld a,(#1C1E) ; 13 cycles ;3A ED 1B
//ld (hl),a ;  ; 7 cycles ;77
//add hl,bc   ; 11 cycles ;09
   //ld      ix, 1B9Eh       ;  dd 21 9e 1b
//ROM:1DFC                 ld      c, 7
//ROM:1DFE                 ld      b, 9

   printf("0E %02hhX ",addToNextRow);
   for (int j = 0 ; j < height - 1 ; j++)
   {
      for (int i = 0 ; i < width - 1 ; i++)
      {
         printf("3A %02hhX %02hhX 77 2C ",start,((start & 0xFF00) >>);
         start++;
      }
      printf("3A %02hhX %02hhX 77 09 ",start,((start & 0xFF00) >>);
      start++;
   }
   for (int i = 0 ; i < width - 1 ; i++)
   {
      printf("3A %02hhX %02hhX 77 2C ",start,((start & 0xFF00) >>);
      start++;
   }
   printf("3A %02hhX %02hhX 77 C3 9A 1F\n",start,((start & 0xFF00) >>);
   return 0;
}

I hijack the original and am able to replace it with mine and it actually works! What's more I can now replace an arbitrary drawing entry point say a moon, and draw something else in it's place, see the screenshot:

Replacing drawing all of these doesn't really improve performance, but it's a start. The character drawings are far more complicated, so no spectacular success on this yet.
Anyway I hope somebody finds my notes interesting, I'll gladly provide any explanation needed.


Singaja


This is the routine responsible for setting up screens with full screen sky

ROM:21F9                 pop     hl       ;
ROM:21FA                 ld      a, (hl)
ROM:21FB                 push    hl       ; E5
ROM:21FC                 ld      hl, 640h ; 21 40 06
ROM:21FF                 ld      (hl), a  ; 77
ROM:2200                 ld      de, 641h ; 11 41 06
ROM:2203                 ld      bc, 23Fh ; 01 3F 02
ROM:2206                 ldir             ; ED B0 ; 1150 bytes in ldi
ROM:2208                 jp      loc_1A3F ;

The ldir is called with BC=0x23F,  I made a ldi unwinded version in memory locations:
40-3ff 959 bytes
ac91-adb7 295 bytes
I actually needed 1150 bytes (+6 for jumps). I redirected the original entry point and verified it with a breakpoint @0x40.
AND IT WORKS!  :o
Time of loading next sky screen is noticeably improved.
Here's a memory snapshot I made in WinAPE with this change applied and my latest notes.

chinnyhill10

The code is way beyond my understanding but it would be great to see an improved CPC version. Keep up the good work!
--
ChinnyVision - Reviews Of Classic Games Using Original Hardware
chinnyhill10 - YouTube

Singaja

#3


This is the routine involved in refreshing the screen from previous characters, turning it off will result in artefacts of the main character being drawn in weird places. It's regularly called in the game loop.


ROM:4D6F ; =============== S U B R O U T I N E =======================================
ROM:4D6F
ROM:4D6F
ROM:4D6F sub_4D6F:                               ; CODE XREF: sub_5B1E-4134p
ROM:4D6F                                         ; sub_5B1E-300Ep
ROM:4D6F                 ld      hl, 0AC0h
;draw main character
ROM:4D72                 ld      de, 0AC1h ; 11 C1 0A
ROM:4D75                 ld      bc, 23Fh  ; 01 3F 02 => CD 40 00 CALL
ROM:4D78
ROM:4D78 loc_4D78:                               ; CODE XREF: sub_4D7D+9j
ROM:4D78                 ld      (hl), 0FFh ; 36 FF   
;;;;
ROM:4D7A                 ldir               ;ED B0       => C9 00
ROM:4D7C                 ret              ; C9         => 00
ROM:4D7C ; End of function sub_4D6F
ROM:4D7C

You may notice it's also using the value BC = 0x23F for ldir. I was able to reuse the unwinded code. Now it feels a little bit more smooth especially when only player character is on the screen.


EDIT: Sorry it actually generates a nasty graphical glitch after restarting the game, I'll try to fix it.

Docent

#4
Quote from: Singaja on 23:07, 08 January 16
This is the routine responsible for setting up screens with full screen sky

ROM:21F9                 pop     hl       ;
ROM:21FA                 ld      a, (hl)
ROM:21FB                 push    hl       ; E5
ROM:21FC                 ld      hl, 640h ; 21 40 06
ROM:21FF                 ld      (hl), a  ; 77
ROM:2200                 ld      de, 641h ; 11 41 06
ROM:2203                 ld      bc, 23Fh ; 01 3F 02
ROM:2206                 ldir             ; ED B0 ; 1150 bytes in ldi
ROM:2208                 jp      loc_1A3F ;

The ldir is called with BC=0x23F,  I made a ldi unwinded version in memory locations:
40-3ff 959 bytes
ac91-adb7 295 bytes
I actually needed 1150 bytes (+6 for jumps). I redirected the original entry point and verified it with a breakpoint @0x40.
AND IT WORKS!  :o
Time of loading next sky screen is noticeably improved.
Here's a memory snapshot I made in WinAPE with this change applied and my latest notes.
You can speed it up further using stack and push reg trick. The code could like as follows:
di
ld (savesp),sp
ex (hl),sp
ld e,(hl)
ld d,e
ld sp, 640+23f
push de (unroll this 23f/2 times)
ld sp,(savesp)
ei
jp loc_1a3f
savesp: ds 1

It will take four times less space to unroll and will be 2.5 times faster (1150 nops vs 2875)

Singaja

#5
Quote from: Docent on 03:21, 09 January 16
You can speed it up further using stack and push reg trick. The code could like as follows:
di
ld (savesp),sp
ex (hl),sp
ld e,(hl)
ld d,e
ld sp, 640+23f
push de (unroll this 23f/2 times)
ld sp,(savesp)
ei
jp loc_1a3f
savesp: ds 1

It will take four times less space to unroll and will be 2.5 times faster (1150 nops vs 2875)
That sounds really great, however this instruction is not valid:

ex (hl),sp

The closest one is

ex (sp),hl

EDIT : I was able to correct Docent's code :-)


org &40
.spvalue dw 0
.hlValue dw 0
di
ld (spvalue),sp
ld (hlValue),hl
ld sp,(hlValue)
ld e,(hl)
ld d,e
ld sp, #640+#23f
push de ;(unroll this 23f/2 times)
ld sp,(spvalue)
ei

Funny stuff going in corners and really nice performance. I included a memory snapshot.

The new version not touching interrupts seems stable.

VincentGR


Docent

Quote from: Singaja on 11:58, 09 January 16
That sounds really great, however this instruction is not valid:

ex (hl),sp

The closest one is

ex (sp),hl

I put this code off top of my head and of course I meant ex (sp),hl :)

Quote from: Singaja on 11:58, 09 January 16
EDIT : I was able to correct Docent's code :-)


org &40
.spvalue dw 0
.hlValue dw 0
di
ld (spvalue),sp
ld (hlValue),hl
ld sp,(hlValue)
ld e,(hl)
ld d,e
ld sp, #640+#23f
push de ;(unroll this 23f/2 times)
ld sp,(spvalue)
ei

Funny stuff going in corners and really nice performance. I included a memory snapshot.
You don't need to make these modifications, just use ex (sp),hl instead.
You can also replace function at 4d6f with the following code:

di
ld (savesp),sp
ld de,#ffff
ld sp, 640+23f
push de (unroll this 23f/2 times)
ld sp,(savesp)
ei
ret


To get another 250% speed improvement :)

Singaja

#8
I made a 0x40 universal routine accepting hl for start point and bc for length

org &40
.spvalue dw 0
ld (spvalue),sp
ld e,(hl)
ld d,e
add hl,bc
inc hl
ld sp,hl
ld a,c
cp #3f
jp z,shorter
push de ;(unroll this (47f-23f)/2 times)
.shorter
push de ;(unroll this 23f/2+1 times)
ld sp,(spvalue)
ret

The #3f comparison is to dinstinguish between bc=0x23f and bc=0x47f
Currently used in:
1a02 - load next location, refresh after toolbox
1a0f - load next location, refresh building?
21be - another ldir which I'm not sure about the purpose,but turned off will make the game very slow
21f9 - load next location, blue sky
4d6f - gameloop refresh (23f and 47f)
In this version you have:
improved loading of screens (but not those inside building)
improved main loop which makes way more smooth gameplay
Occasional minor artefacts on the sides of screen for very brief moment.


I decided to stick to original wip naming convention for this snapshot ;-)

Trebmint

Perhaps you should ask Clive for some ideas as he's around on the scene and has just re-released saboteur. He's actually thinking of doing Sab 2 too so perhaps it would be worth asking him.

Singaja

#10
Quote from: Trebmint on 01:16, 13 January 16
Perhaps you should ask Clive for some ideas as he's around on the scene and has just re-released saboteur. He's actually thinking of doing Sab 2 too so perhaps it would be worth asking him.
I tried reaching his 'Saboteur' facebook and asked if he's ok with me modifying this 1987 release and even better maybe he has some ideas for improvements ;-)

EDIT: I got Clive's very positive reply :-) And he did pay a visit to our forum hehe
Quote from: Clive
Anyone who make a throwable bat is ok in my book Emotikon smile


Trebmint

Quote from: Singaja on 01:40, 13 January 16
I tried reaching his 'Saboteur' facebook and asked if he's ok with me modifying this 1987 release and even better maybe he has some ideas for improvements ;-)

EDIT: I got Clive's very positive reply :-) And he did pay a visit to our forum hehe


Yeah Clives a great guy. He really should get these out updated as IOS and android games


Singaja

#12
I think the glitches were caused by not preserving the a register, now it's done.


org &40
.spvalue dw 0
ld (spvalue),sp
ld e,(hl)
ld d,e
add hl,bc
inc hl
ld sp,hl
ld b,a
ld a,c
cp #3f
jp z,shorter
push de ;(unroll this (47f-23f)/2 times)
.shorter
push de ;(unroll this 23f/2+1 times)
ld sp,(spvalue)
ld a,b
ret

I'll be really grateful for testing this snapshot.

Powered by SMFPacks Menu Editor Mod