Editing Painter source code for the jaguar to include the Jagfest special edition levels
You may or may not know that the jaguar game painter can be downloaded as source code.
not long back i downloaded this and started to look at it, when doing so i made a excellent discovery. low and behold amongst the source code files there is a file called 'JAGFEST.LVL' this got me thinking.
after a few tests, renaming the level files in the source, recompiling and testing on the jaguar, it appears that this file is indeed the jagfest levels from the limited edition.
The game as it is does not have these levels included unless you have the special edition, however i also noticed that there are several passwords to load other level files, the original levels and the demo levels for example. this got me thinking again, 'Can i add some code that will allow me to get to the jagfest levels from the password screen.
well of course the answer is yes, but i know next to nothing about assembly, so it was all a bit hit and miss. fortunately i have some programming knowledge so i knew that the code could be copy and pasted (or most of it) and make it work
after messing about with the program while viewing the source code there are several passwords to access different levels, there is a sound effect confirming that you have entered a correct password for each of these also
| demo | demo levels selected | load the demo version levels |
| original | original levels selected | load the original levels for the ST |
| standard | standard levels selected | load the standard jaguar levels |
| jagfest | jagfest levels selected | load the jagfest limited edition levels |
the top three are what already exists, the bottom one is what we want to happen. the top three have sound samples, the bottom one as yet does not because the program was not written to do this. so that the flow of things is kept the same I decided to make a sound for this, the format of this is as follows:
8hz, 16 bit, mono
to save time I used a program called 'Alive Text to Speech' this program converts typed text into a wave file.
to get a reasonable representation of what I wanted I had to type in the following: 'JAgFESSTT LevvElS SelectED.' then converted it as an 8hz, 16 bit, mono wave file, then loaded it into 'Sony Sound Forge 8' this program allows you to edit the sound sample as it is a bit quiet in its initial form, I then used SOX to convert the wave file into the correct format .raw file that the jaguar uses in this game. Thanks to Seb of The Removers for the help on the wave conversion, much appreciated.
alternatively, download the file I made here.
now you have that sorted we will move onto the programming.
open main.s in your favourite text editor
Stage 1, Editing code so the program recognises the password for the extra levels
search the file for the first instance of '.dmo', you should have something similar to the following:
| cmp.l #'ORIG',d4 bne.s .dmo cmp.l #'INAL',d5 bne.s .dmo move.w #2,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp12_end-samp12,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp12,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra .ex |
|
| .dmo:
|
cmp.l #'DEMO',d4 bne.s .std move.w #1,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp11_end-samp11,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp11,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| .std:
|
cmp.l #'STAN',d4 bne.s .skp cmp.l #'DARD',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| .skp: | bsr find_password |
you can see that there is a pattern here
bne is 'break if not equal' I believe, so if the password doesn't match jump to the next function and check the next password
we have to decide how many letters we want the password, we will use 'JAG FEST' for this tutorial.
basically all we have to do is copy the subroutine .std and paste it underneath, changing the pasted .std to .jag and changing the bne.s commands so that the code follows the same pattern like above. then change the cmp.l commands to reflect the password, you should now have something that looks like this:
| .std:
|
cmp.l #'STAN',d4 bne.s .jag cmp.l #'DARD',d5 bne.s .jag move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| .jag:
|
cmp.l #'JAG
',d4 bne.s .skp cmp.l #'FEST',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| .skp: | bsr find_password |
We will be coming back to here a little later to change the sound sample that is played, but all in good time.
Stage 2, Editing code so it knows there is a new level set
search for 'demo.lvl' and you should find the following:
| level_space: | |
| level_file_data: |
.dcb.b 64000,0
.phrase |
| level_file_data_1:
|
.incbin "newlvl.lvl" .dcb.b 256,0 .phrase |
| level_file_data_2:
|
.incbin "demo.lvl" .dcb.b 256,0 .phrase |
|
level_file_data_3:
|
.incbin "levels.lvl" .dcb.b 256,0 .phrase |
|
level_file: |
.dc.w 0 .phrase |
copy the subroutine 'level_file_data_3:' and paste it in between 'level_file_data_3:' and 'level_file:', then the the subroutine just pasted change the command '.incbin "levels.lvl"' to reflect the new level set (.incbin "jagfest.lvl") and edit the subroutine label to be incremented by one, you should have something similar to this:
| level_space: | |
| level_file_data: |
.dcb.b 64000,0
.phrase |
| level_file_data_1:
|
.incbin "newlvl.lvl" .dcb.b 256,0 .phrase |
| level_file_data_2:
|
.incbin "demo.lvl" .dcb.b 256,0 .phrase |
| level_file_data_3:
|
.incbin "levels.lvl" .dcb.b 256,0 .phrase |
| level_file_data_4:
|
.incbin "jagfest.lvl" .dcb.b 256,0 .phrase |
| level_file: |
.dc.w 0 .phrase |
next search upwards for 'level_file_data_3:' and you should find this:
| game_init: | |
| .1:
|
cmp.w #0,level_file bne .2 lea level_file_data_1,a0 lea level_file_data,a1 bra.s .copy |
| .2:
|
cmp.w #1,level_file bne .3 lea level_file_data_2,a0 lea level_file_data,a1 bra.s .copy |
| .3:
|
cmp.w #2,level_file bne .copy lea level_file_data_3,a0 lea level_file_data,a1 |
| .copy: | move.w #1999,d7 |
this is, as far as I can tell to make the levels accessible as 1, 2, 3 etc when actually they are loaded as 0, 1, 2
again we have a bne command that jumps to the next label in the code. we also have incrementing 'cmp.w' commands (note these values are always one behind as they start at 0 and not 1)
i think bra is some kind of goto. 'break' and goto the specified label ?
copy the subroutine 3 and paste it in between .3: and .copy:, on the routine just pasted, change the .3: to .4: change the 'cmp.w #2.level_file' to ' cmp.w #3,level_file' and change 'lea level_file_data_3,a0' to 'lea level_file_data_4,a0' on .3 edit 'bne .copy' to 'bne .4', at the bottom add 'bra.s .copy'
you should have something that looks like this:
| game_init: | |
| .1:
|
cmp.w #0,level_file bne .2 lea level_file_data_1,a0 lea level_file_data,a1 bra.s .copy |
| .2:
|
cmp.w #1,level_file bne .3 lea level_file_data_2,a0 lea level_file_data,a1 bra.s .copy |
| .3:
|
cmp.w #2,level_file bne .4 lea level_file_data_3,a0 lea level_file_data,a1 bra.s .copy |
|
.4:
|
cmp.w #3,level_file bne .copy lea level_file_data_4,a0 lea level_file_data,a1 |
| .copy: | move.w #1999,d7 |
as you can see we are still keeping the general layout of the code as it was before, we are just adding more
search up for '.jag:' to go back to your previously entered password function. we have to edit this so that it knows what levels it needs to load
| .jag:
|
cmp.l #'JAG
',d4 bne.s .skp cmp.l #'FEST',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
we need to edit the highlighted line above to represent the levels we want to load, see below for a comparison table:
| level_file_data_1: | .incbin "newlvl.lvl" | .1: | cmp.w #0,level_file bne .2 lea level_file_data_1,a0 lea level_file_data,a1 bra.s .copy |
.std: | cmp.l #'STAN',d4 bne.s .jag cmp.l #'DARD',d5 bne.s .jag move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| level_file_data_2: | .incbin "demo.lvl" | .2: | cmp.w #1,level_file bne .3 lea level_file_data_2,a0 lea level_file_data,a1 bra.s .copy |
.dmo: | cmp.l #'DEMO',d4 bne.s .std move.w #1,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp11_end-samp11,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp11,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
| level_file_data_3: | .incbin "levels.lvl" | .3: | cmp.w #2,level_file bne .4 lea level_file_data_3,a0 lea level_file_data,a1 bra.s .copy |
.clr: | clr.b (a5)+ dbf d0,.clr move.b (a6)+,d4 lsl.l #8,d4 move.b (a6)+,d4 lsl.l #8,d4 move.b (a6)+,d4 lsl.l #8,d4 move.b (a6)+,d4 move.b (a6)+,d5 lsl.l #8,d5 move.b (a6)+,d5 lsl.l #8,d5 move.b (a6)+,d5 lsl.l #8,d5 move.b (a6)+,d5 cmp.l #'ORIG',d4 bne.s .dmo cmp.l #'INAL',d5 bne.s .dmo move.w #2,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp12_end-samp12,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp12,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra .ex |
| level_file_data_4: | .incbin "jagfest.lvl" | .4: | cmp.w #3,level_file bne .copy lea level_file_data_4,a0 lea level_file_data,a1 |
.jag: | cmp.l #'JAGF',d4 bne.s .skp cmp.l #'EST',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp20_end-samp20,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp20,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
as you can see from the comparison table above everything from the different snippets of code matches except our new function we have created, as it is the standard levels are loaded when our password is entered.
change the line 'move.w #0,level_file' to read 'move.w #3,level_file'
Stage 3, Editing code to add the new sound sample for 'jagfest levels selected'
we now need to tell the program to load the sound sample into memory, search down for 'demo.raw'
you should be taken to the following:
| samp1: | .incbin "egb.pcm" |
| samp1_end: | .phrase |
| samp2: | .incbin "secs20.raw" |
| samp2_end: | .phrase |
| samp3: | .incbin "secs10.raw" |
| samp3_end: | .phrase |
| samp4: | .incbin "secs5.raw" |
| samp4_end: | .phrase |
| samp5: | .incbin "secs0.raw" |
| samp5_end: | .phrase |
| samp6: | .incbin "aborted.raw" |
| samp6_end: | .phrase |
| samp7: | .incbin "cut.raw" |
| samp7_end: | .phrase |
| samp8: | .incbin "ding.raw" |
| samp8_end: | .phrase |
| samp9: | .incbin "paused.raw" |
| samp9_end: | .phrase |
| samp10: | .incbin "stan.raw" |
| samp10_end: | .phrase |
| samp11: | .incbin "demo.raw" |
| samp11_end: | .phrase |
| samp12: | .incbin "orig.raw" |
| samp12_end: | .phrase |
| samp13: | .incbin "beep.raw" |
| samp13_end: | .phrase |
| samp14: | .incbin "munch.raw" |
| samp14_end: | .phrase |
| samp15: | .incbin "cheer.raw" |
| samp15_end: | .phrase |
| samp16: | .incbin "move1.raw" |
| samp16_end: | .phrase |
| samp17: | .incbin "move2.raw" |
| samp17_end: | .phrase |
| samp18: | .incbin "move3.raw" |
| samp18_end: | .phrase |
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp20: | .dc.l 0 |
| samp20_end: | .phrase |
incbin includes the named file in binary form at that specific point in the source code.
what we need to do is at the end of this list rename the two samp20 lines to samp21:
from this:
| samp20: | .dc.l 0 |
| samp20_end: | .phrase |
to this:
| samp21: | .dc.l 0 |
| samp21_end: | .phrase |
next we need to copy samp19 and paste it in between samp19 and samp21 and rename the newly pasted samp19 to samp20
from this:
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp21: | .dc.l 0 |
| samp21_end: | .phrase |
to this:
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp21: | .dc.l 0 |
| samp21_end: | .phrase |
to this
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp20: | .incbin "move4.raw" |
| samp20_end: | .phrase |
| samp21: | .dc.l 0 |
| samp21_end: | .phrase |
next we need to replace the filename in samp20 with our very own sound sample we have produced
simply change samp20: to read
| samp20: | .incbin "jagfest.raw" |
you should now have this:
| samp19: | .incbin "move4.raw" |
| samp19_end: | .phrase |
| samp20: | .incbin "jagfest.raw" |
| samp20_end: | .phrase |
| samp21: | .dc.l 0 |
| samp21_end: | .phrase |
we now need to work out the position and size of the new sound, scroll down about twenty lines until you come to the following
| .dc.l | samp18_end-samp18, samp18 | |
| .dc.l | samp19_end-samp19, samp19 | |
| mod_table: | .dc.l modi | |
after the '.dc.l samp19_end-samp19, samp19' command add another line that reads '.dc.l samp20_end-samp20, samp20'
so you have this:
| .dc.l | samp18_end-samp18, samp18 | |
| .dc.l | samp19_end-samp19, samp19 | |
| .dc.l | samp20_end-samp20, samp20 | |
| mod_table: | .dc.l modi | |
we are nearly done, thank god.
search up for '.jag:'
this will take you to our routine that we specified the password in earlier
| .jag:
|
cmp.l #'JAG ',d4 bne.s .skp cmp.l #'FEST',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp10_end-samp10,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp10,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
the yellow and pink highlighted sections are the sections that we need to change, we need to change these values to match the lines we entered previously
basically we need to change the samp10 wording to read samp20, so we have something like this.
| .jag:
|
cmp.l #'JAG ',d4 bne.s .skp cmp.l #'FEST',d5 bne.s .skp move.w #0,level_file clr.w START_LEVEL movem.l d0-d4/a0-a1,-(a7) move.l #samp20_end-samp20,d0 ;sample length move.l #0,d1 ;looping length moveq #20,d2 ;priority moveq #0,d3 ;default volume moveq #0,d4 ;default frequency lea samp20,a0 ;start address move.l #0,a1 ;loop start address jsr player+$c movem.l (a7)+,d0-d4/a0-a1 bra.s .ex |
i think we are about done, save and close main.s
now all we have to compile the source code into a binary
before we can do this i want a binary file with no header, to get this we need to check the makefile
open the makefile and change the aln parameters from
ALNFLAGS = -v -v -e -g -rd -a $(STADDR) x B000
to
ALNFLAGS = -n -v -v -e -g -rd -a $(STADDR) x B000
the added -n is for no header.
save and close the makefile
open a dos window and go to the painter source code directory
type nmake at the command prompt and press enter
well it looks like we have a compile error, no matter we will deal with these as they come up.
Warnings on the most part can be ignored, it is the Errors we have to deal with, lets look at what happens when we compile the code for the first time:
C:\Jaguar\PAINTSRC>nmake
Microsoft (R) Program Maintenance Utility Version 1.61.6038
Copyright (C) Microsoft Corp 1988-1996. All rights reserved.
mac -fb -g
main
main.s(1352):
Error:
expression out of range
main.s(3402):
Warning: Null offset in indexed addressing mode.
main.s(4102):
Warning: Null offset in indexed addressing mode.
main.s(4560):
Warning: Null offset in indexed addressing mode.
main.s(4613):
Warning: Null offset in indexed addressing mode.
main.s(4627):
Warning: Null offset in indexed addressing mode.
main.s(4754):
Warning: Null offset in indexed addressing mode.
main.s(4784):
Warning: Null offset in indexed addressing mode.
main.s(4793):
Warning: Null offset in indexed addressing mode.
NMAKE : fatal
error U1077: 'mac' : return code '0x1'
Stop.
ok, mac (the jaguar compiler) does give some useful info if not much, highlighted in pink above is the line number where the problem is. if you are using ultraedit it displays the line numbers for you so its easy, if you are using notepad or other program that does not display line numbers then the line in question is:
| movem.l (a7)+,d0-d4/a0-a1 | |
| bra.s .ex | |
| .std: | cmp.l #'STAN',d4 |
i have highlighted the line that is the problem.
change this line from bra.s .ex to bra.w .ex
what we have just done is
change the bra command from 'short' to 'word' variable type
at the dos prompt again type nmake and see what happens
if you have followed this guide the source code should now compile perfectly
alternatively, if you cant be bothered to make this modification yourself,
you can download the main.s file
here
if you are lazy and get theis just copy it over the file already in the source code folder and compile it
Dont forget to get the jagfest.raw sound sample and copy it over too.
you will now need to get it on the jaguar some way, i am using a flash card, you can use whatever.
you have now successfully edited the painter source code to include the special edition jagfest levels
this was the result of staring at the source code for many hours working out what was what