Today we have a special technical article from one of our supporting members going into great detail about Death Force, a classic game for the Fujitsu FM77AV with a hidden mystery, including a partial disassembly, memory analysis and gameplay strategies.
Death Force was released for Fujitsu FM77AV series 8-bit computer in 1987. The FM77AV had advanced graphics hardware, able to display up to 4,096 colors (along with other modes: 8 colors times 4 planes and 8 colors 2 planes + 64 colors). Death Force was one of the first software titles that made use of the color capabilities of FM77AV.
When I visited Game Preservation Society, one of the members raised a question: is it possible to play Death Force through all the way to the ending? The rumor was that the copy protection backfired, making it impossible to finish. According to another rumor, one user complained to the developer, Riverhill Soft, that the game was impossible to complete and was sent a fixed version. I wanted to find out if the rumors were true.
I did not buy Death Force when FM77AV was my primary computer, mainly due to budget reasons. Luckily, I was able to purchase an original copy of Death Force recently. The disks crossed the Pacific ocean with no apparent damage or mold, and I was able to make disk images using my working FM77AV40. The FM-7 series emulator XM7 could emulate the copy protection, and I was able to boot the program with no problem. I began my investigation into the rumors.
Conclusion
I will jump right to the conclusion: we have confirmed that there are at least two versions of Death Force. One version can be completed while the other cannot.
In my version, it was impossible to finish. Not because it was too difficult, but because the entrance to the pathway leading to the last boss chamber is blocked by a wall. The coordinate of the entrance at ($19,$0D) is blocked.
This entrance is on the screen pictured below. Internally, it is identified as MAP $79. The Death Force “world” is made up of a 16×16 matrix of maps. Each map has two layers: above and below ground. The map in the top-left corner is $00, and the ID increments by $01 moving to the right. Moving down the map matrix, the ID increases by $10. So, MAP $79 is 7 maps down, 9 maps right from the top-left corner map.
The stairs are not hidden at all, as you can see, but you cannot enter.
Below is the table of the entrance (stairs) coordinates of the final stage, stage 9.
$7C00~
14
75 05 0C 01
56 1B 09 04
77 17 1C 01
78 07 1F 01
79 19 0D 01
85 08 10 04
77 1C 07 04
87 1E 0F 04
58 0E 07 04
69 1E 13 04
6B 1E 13 04
48 0F 0B 01
89 05 16 04
6A 0F 14 04
57 19 0F 04
88 13 0F 04
8A 07 1D 01
7A 07 1D 01
7B 05 01 04
8B 05 07 01
The first byte $14 is the number of stairs. There are $14 (20) stairs in the final stage. If you look at the fifth set of stairs, it has four numbers: $79, $19, $0D, and $01. $79 is the map ID. ($19,$0D) is the coordinate of the stairs in map $79. Each map consists of 40×37 smaller blocks. The coordinate ($19,$0D) means $19 (25) blocks to the right, $0D (13) blocks down from the top-left corner. The last $01 tells that you need to move downward to enter the stairs from the field down to the underground level.
The following is the table for collision check. If the bit 0 is 1, the block is solid. Each byte covers 1×2 blocks. The bytes highlighted red are blocking the entrance to the stairs leading to the last-boss chamber. The player can go down no farther than Y=$0A. Since the entrance to the stairs is checked before the wall, if the Y-coordinate of the entrance is $0B, you can enter the stairs. However, the Y-coordinate of the entrance was written as $0D, making it impossible to enter.
$4C00~ 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 01 01 01 01 01 01 03 03 03 00 00 00 00 00 00 00 00 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 01 01 01 01 01 01 01 01 01 01 01 01 01 03 03 03 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 00 01 00 00 00 01 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 03 03 03 03 03 00 00 00 00 00 00 00 00 00 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 01 01 01 01 00 00 00 00 00 00 00 00 00 00 01 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 01 00 00 00 00 00 00 00 00 00 00 00 00 01 01 01 01 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
There are two solutions. One is to alter the Y-coordinate of the entrance from $0D to $0B. The problem with this method is that the coordinate is written in the program disk (A disk) track 15, side 0, sector 12. That would require modifying the disk data, so we want to avoid this. (Of course, it’s not a big deal if you are working with disk images, but you shouldn’t modify the few remaining original disks of such a classic game! If something fails, you may permanently destroy your copy. The original disk is a historic artifact. Don’t modify it if you own one.)
There is a better solution. The above wall table (at memory address $4C00) is constructed from 20×10 table when the player moves across maps. The 20×10 table is in the data disk, and it is copied from B disk before you start a new game. The 20×10 table for map $79 is as follows:
$7800~ 01 01 01 06 03 5B 5B 5B 5B 5B 02 08 01 01 01 01 01 01 06 03 09 09 09 0E 0A 01 01 06 03 5B 0F 10 09 09 09 09 09 09 0E 0F 00 00 00 1B 09 09 09 0E 0F 5B 0F 18 00 00 00 00 00 00 16 0F 00 00 00 00 00 00 00 16 0A 01 0B 18 2D 2E 2F 00 00 00 16 0F 00 00 00 00 00 00 00 1B 09 09 09 19 46 47 5A 00 1D 02 1E 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0F 5B 5B 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 0A 06 03 1E 1E 1E 1E 1E 1E 1E 1E 1E 03 1C 00 00 00 00 00 1B 09 0E 0F 5B 5B 5B 5B 5B 5B 5B 5B 5B 0F 18 00 00 00 00 00 00 00 16 0F 5B 5B 5B 5B 5B 5B 5B 5B 5B 20 1E 1E 1E 1E 1E 1E 1E 1E 1E 1F
You can shift the stairs by two blocks by moving the highlighted 6 bytes by one row. The table is written in track 11, side 1, sector 9 of the data disk. By modifying the sector as follows, you can enter the stairs leading to the last-boss chamber.
Before modification Disk:0 Track:11 Side:1 Sector:9 0000 18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[....... 0010 09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[... 0020 09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................ 0030 5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[............... 0040 00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[ 0050 20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./.. 0060 0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ .. 0070 1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[ 0080 01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[...... 0090 01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[.. 00a0 09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................ 00b0 0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[.............. 00c0 00 00 00 16 0a 01 0b 18 2d 2e 2f 00 00 00 16 0f|........-./..... 00d0 00 00 00 00 00 00 00 1b 09 09 09 19 46 47 5a 00|............FGZ. 00e0 1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................ 00f0 00 00 00 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|......[[........
After modification Disk:0 Track:11 Side:1 Sector:9 0000 18 00 00 1d 04 05 1c 00 5b 02 08 01 0b 18 00 1b|........[....... 0010 09 09 09 09 19 00 00 16 0c 0d 18 00 5b 0f 10 09|............[... 0020 09 19 00 00 00 00 00 00 00 00 00 1b 09 09 19 00|................ 0030 5b 0f 18 00 00 00 00 00 00 1d 02 1e 03 1c 00 00|[............... 0040 00 00 00 00 5b 0f 18 00 00 00 00 1d 02 1e 1f 5b|....[..........[ 0050 20 1e 1e 1e 1e 1e 1e 1e 5b 0f 18 2d 2e 2f 00 16| .......[..-./.. 0060 0f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 20 1e 1e|.[[[[[[[[[[[[ .. 0070 1e 1e 1e 1e 1f 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b 5b|.....[[[[[[[[[[[ 0080 01 01 01 06 03 5b 5b 5b 5b 5b 02 08 01 01 01 01|.....[[[[[...... 0090 01 01 06 03 09 09 09 0e 0a 01 01 06 03 5b 0f 10|.............[.. 00a0 09 09 09 09 09 09 0e 0f 00 00 00 1b 09 09 09 0e|................ 00b0 0f 5b 0f 18 00 00 00 00 00 00 16 0f 00 00 00 00|.[.............. 00c0 00 00 00 16 0a 01 0b 18 00 00 00 00 00 00 16 0f|................ 00d0 00 00 00 00 00 00 00 1b 09 09 09 19 2d 2e 2f 00|............-./. 00e0 1d 02 1e 1f 00 00 00 00 00 00 00 00 00 00 00 00|................ 00f0 46 47 5a 00 16 0f 5b 5b 00 00 00 00 00 00 00 00|FGZ...[[........
This change moves down the stairs graphics by two blocks in map $79, as you can see below. This change will allow you to finish the game without altering Disk A.
Well, it’s not very kind to explain how to modify the sector without giving a way to do so. If you have a data disk image of Death Force, and are having a problem finding a way to the last-boss chamber, you can modify the data-disk image by the following Python script. I don’t like Python. I like C++ better, but maybe more people have Python interpreter rather than a C++ compiler.
import sys inFName=sys.argv[1] outFName=inFName+"_patched" print("Input="+inFName) print("Output="+outFName) ifp=open(inFName,"rb") bin=ifp.read() ifp.close() print("Python inferiority.") print("Why do I have to convert an array to a list, edit,") print("and then convert it back to an array?") print("Surprisingly poor programming language.") print("Ditch Python. Use C++.") lst=[] for b in bin: lst.append(b) print("Input Length="+str(len(lst))+" bytes") nFound=0 for i in range(len(lst)-60): if lst[i:i+5]==[0x0b,0x18,0x2d,0x2e,0x2f] and lst[i+20:i+25]==[0x09,0x19,0x46,0x47,0x5a]: print("Found at 0x"+hex(i)) lst[i+42]=lst[i+22] lst[i+43]=lst[i+23] lst[i+44]=lst[i+24] lst[i+22]=lst[i+2] lst[i+23]=lst[i+3] lst[i+24]=lst[i+4] lst[i+2]=0 lst[i+3]=0 lst[i+4]=0 nFound+=1 if 1!=nFound: print("This should occur only once.\n") print("Something is not right.\n") quit() ofp=open(outFName,"wb") ofp.write(bytearray(lst)) ofp.close() print("Wrote to "+outFName) print("Rename the extension before using.")
If the data disk image is DFORCEDATA.D77, and if you saved this script as dforcepatch.py, you can type as:
python dforcepatch.py DFORCEDATA.D77
It will create a file called DFORCEDATA.D77_patched. Please change the extension to D77 and use.
Also I made my data disks near the ending available. I couldn’t go through the violent offence from the enemies in the last stage. I altered the player data in the data disk. Also I used a different method to enter the stairs, so the map hasn’t been modified in these data disks.
[Immediately before the last-boss battle] [Immediately after the last-boss battle]
As discussed in http://fm-7.com/forum/viewtopic.php?f=3&t=71, by killing yourself by bombs after killing the last boss, you can see the ending credits.
Confirming the Presence of the Bug-Fix Version
Another rumor about Death Force is that one user complained to the developer, Riverhill Soft, that the game was unbeatable and was sent a fixed version that could be completed. Is it real or fake?
Based on the above observation, if such a bug-fix version exists, the change from the un-finish-able version most likely be in track 15 side 0 sector 12 of A disk, or track 11 side 1 sector 9 of B disk. If the user who received a bug-fix version disk was able to finish the game, the change should be in A disk because B disk is used only once when the user starts a new game.
This question was solved by a copy of Death Force owned by a member of the Game Preservation Society and also disks in the Game Preservation Society collection. The member’s disk had a serial number 75524X 2D on the back of the disk. My copy has 6C184F 2D. The number probably is the serial number for the batch of the disk, not the serial for Death Force. Nonetheless, since the highest digit of his serial number is 7, and mine is 6, his copy is likely to be a newer version.
Below is the dump of track 15 side 0 sector 12 of his disk.
0000 14 75 05 0C 01 56 1B 09-04 77 17 1C 01 78 07 1F |.u...V...w...x..
0010 01 79 19 0B 01 85 08 10-04 77 1C 07 04 87 1E 0F |.y.......w......
0020 04 58 0E 07 04 69 1E 13-04 6B 1E 13 04 48 0F 0B |.X...i...k...H..
0030 01 89 05 16 04 6A 0F 14-04 57 19 0F 04 88 13 0F |.....j...W......
0040 04 8A 07 1D 01 7A 07 1D-01 7B 05 01 04 8B 05 07 |.....z...{......
0050 01 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
0060 00 00 00 00 00 90 00 02-04 00 00 C0 03 90 02 1E |...........タ....
0070 04 93 00 02 04 00 00 C0-03 90 02 28 06 96 00 04 |.......タ...(....
0080 08 82 03 00 01 8C 0B 32-08 AE 00 04 08 82 03 00 |.......2.ョ......
0090 01 0C 0B 32 0A C6 00 04-08 82 03 00 01 0C 0B 46 |...2.ニ.........F
00A0 0F 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 |................
And below is the same sector of my disk.
0000 14 75 05 0c 01 56 1b 09 04 77 17 1c 01 78 07 1f|.u...V...w...x..
0010 01 79 19 0d 01 85 08 10 04 77 1c 07 04 87 1e 0f|.y.......w......
0020 04 58 0e 07 04 69 1e 13 04 6b 1e 13 04 48 0f 0b|.X...i...k...H..
0030 01 89 05 16 04 6a 0f 14 04 57 19 0f 04 88 13 0f|.....j...W......
0040 04 8a 07 1d 01 7a 07 1d 01 7b 05 01 04 8b 05 07|.....z...{......
0050 01 39 1a 10 cc 0f 2f 8d 41 cc 03 0e f7 fd 16 b7|.9..../.A.......
0060 fd 15 7f fd 15 90 00 02 04 00 00 c0 03 90 02 1e|...............
0070 04 93 00 02 04 00 00 c0 03 90 02 28 06 96 00 04|...........(....
0080 08 82 03 00 01 8c 0b 32 08 ae 00 04 08 82 03 00|.......2........
0090 01 0c 0b 32 0a c6 00 04 08 82 03 00 01 0c 0b 46|...2...........F
00a0 0f 15 7f fd 15 f7 fd 16 4a b7 fd 15 7f fd 15 39|.......J.....9
00b0 34 02 64 e4 24 14 bd 2a cb cc 1d 00 b7 fc 80 f7|4.d.$..*........
00c0 fc a0 96 36 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...6.....d.$..*
00d0 cb cc 1d 01 b7 fc 80 f7 fc a0 96 35 b7 fc a1 7f|...........5...
00e0 fd 05 64 e4 24 14 bd 2a cb cc 1d 02 b7 fc 80 f7|..d.$..*........
00f0 fc a0 96 37 b7 fc a1 7f fd 05 64 e4 24 14 bd 2a|...7.....d.$..*
Four bytes from offset $0011 are 79 19 0B 01 in his version and 79 19 0D 01 in my version. This change matches exactly with one of the possible corrections I predicted.
The Game Preservation Society kindly checked three Death Force disks in their collection. Two of the three disks had $0D (bugged version) at offset $0013 of the sector, and one had $0B (fixed). Overall, we have checked six A disks, four of them were bugged and two were fixed. Although we cannot completely exclude the probability that a user somehow figured how to bug-fix the disk and made the change, such a probability is very slim. Most likely Riverhill Soft realized the bug after shipping the first version then published a corrected version.
It was somewhat lucky that I received a bugged version disk from Yahoo auction. If I picked up a corrected version, I would have finished with no problem, and I could have concluded that the rumor was false.
Was it due to the backfire of the copy protection?
Regarding one of the questions, whether it was due to the copy protection gone wrong, I can not conclude for sure. However, I have a feeling that this was not the case. The reason for this is because there was no disk access for any sort of copy-protection check when the player enters the last stage (stage 9) until reaching map $79.
The copy protection of Death Force checks the presence of $F6 and $F7 sectors, then checks the content of the sectors, and also checks that the sectors give for a CRC error. It is a very common copy protection in Fujitsu FM-7 series computers. I have modified the source code of FM-7 emulator called XM7 so that it breaks in the debugger when $F5, $F6, or $F7 is written to the sector register $FD1A. It does break while booting the game, indicating that the emulator is detecting the copy protection check. However, it did not break in the last stage.
From this observation I strongly believe it was not due to a copy protection mistake.
Was it just a plain old bug?
I believe it was a regular old bug:
1. In the fixed version, the player character can enter the stairs leading to the last-boss chamber without doing anything special.
2. The last stage, stage 9, has numerous errors with the stairs coordinates, not just the one leading to the last-boss chamber. Several are inaccessible as they are blocked by a wall. For the same reason, there are many chambers that you can go in but never get out.
3. The game does not have many special tricks or features. As I played through the game, I saw only two special features: one, when all 15 seals are collected the player appearance and the BGM changes, and two, the BGM changes when you kill the last boss. Other than that, the game progresses with the same rules and the same code. I cannot believe the developer wrote special code just for this entrance.
4. If the developer intended to make a special trick for this particular entrance, it would more likely be an invisible stairs at the beginning, which becomes visible when certain conditions are met. It sounds very strange if the developer keep it visible all the time and then move the stairs on a certain condition.
If it was not a bug, there must be a way to enter the stairs without altering the data disk or the program disk. We cannot fully exclude the possibility that the first version had an additional secret that the user needs to discover to enter the last-boss chamber, but it was too difficult and removed in the later versions. Possibilities are:
1. the Y-coordinate of the entrance in map $79 changes from $0D to $0B,
2. the wall in map $79 blocking the entrance moves down by 2 blocks, or
3. the properties of map $79 changes so that the wall moves down by 2 blocks by a KEY, if certain conditions are satisfied.
Possibility 1 is very unlikely because the coordinate of the entrance is written in disk A, which is write protected. Therefore it wouldn’t be saved to the data disk.
Possibility 2 is unlikely, too. If there is a code that alters map $79 somewhere, the alteration must be saved to the data disk once the blockage is removed. I have experimented if the change to the map can be saved. In XM7 debugger, I modified the map data of map $79 and made sure the map appearance has changed. Then I saved the data and examined the disk image to test if the modification was saved. Also I restarted the game from where I saved. In this experiment, I did not observe the changes in the map in the data disk, and the map appeared the same as pre-modification after re-booting and re-loading the data. Changes to the map were not saved, therefore Possibility 2 is unlikely.
There still is a possibility that this particular change remains in one session, and the player needs to re-satisfy the condition if the program is re-booted. However, all other status are saved to the data disk. It is very strange if this only one state is not savable.
I believe that the possibility 3 is most likely among all possibilities. In map-changing code, there is a very interesting piece:
2812 30 88 13 LEAX $13,X Map-properties structure (MAP $79: $A6B0)+$13 2815 86 06 LDA #$06 6 counts 2817 97 13 STA <$13 Loop counter 2819 E6 80 LDB ,X+ 281B C1 FF CMPB #$FF 281D 27 1A BEQ $2839 281F CE 78 00 LDU #$7800 2822 4F CLRA 2823 33 CB LEAU D,U 2825 A6 80 LDA ,X+ 2827 D6 20 LDB <$20 2829 C4 01 ANDB #$01 282B 27 02 BEQ $282F 282D 9A 14 ORA <$14 282F A7 C4 STA ,U 2831 0A 13 DEC <$13 2833 26 E4 BNE $2819
If the properties of a map is configured so that the map consumes a KEY, two temporary changes are made to the map when the player first enters. One is a change when the highest 2 bits match a certain pattern. If they match, the 2 bits will be replaced with a different pattern. However, since the binary code for the stairs has a different highest 2 bits, this change does not help. The second change is applied to overwrite arbitrary six bytes with arbitrary values. Six bytes are just enough to move the stairs.
The properties of map $79 is written in 32 bytes table at physical address $0B6B0. If bit 7 of the byte at offset $1F is 1, the map consumes a KEY. The bytes in offset $14 to $1E defines the six-byte modification to the map.
0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C ................ 0B6C0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 :02 ................ 0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00 ................
By modifying offset $14 to $1F as follows, the entrance to the stairs will be unblocked when the player enters map $79 with at least one key.
0B6B0: 04 04 03 08 04 08 0C 04 0B 16 04 12 16 00 00 00 :7C ................
0B6C0: 00 00 00 5c 2d 5d 2e 5e 2f 70 46 71 47 72 5a 82 :02 ................
0B6D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 :00 ................
Importantly, this change will be saved to the data disk with other status.
With this modification, map $79 will become as following image when you enter with a KEY.
As you can see, the stairs graphics will be stretched unnaturally. Although this modification to the map properties opens the path to the last-boss chamber, and the status is savable, I doubt if the developer could have added such a feature that breaks the appearance of the graphics. Although it is most likely among the above possibilities, I think it is ultimately not the intention.
Again, being unable to find a code that opens the path to the last-boss chamber does not mean it doesn’t exist. I cannot say that there is absolutely no way of entering the chamber without modifying the program or data disk. And there may yet be another method for the player to enter the last-boss chamber that I haven’t considered.
Nonetheless, after considering the above possibilities, my conclusion is that it was just a plain bug that was blocking the pathway to the last-boss chamber.
Invincibility Commands
It is known that if you type “SILVER” (case sensitive) and space while the opening music is playing, the player character becomes invincible until you restart the game. You can play in invincible mode, save, and restart into normal mode. For example, you can force the way through difficult maps in the invincibility mode, save, and then continue in the normal mode. (Reference: http://fm-7.com/museum/database/urawaza/810101300.html)
The code disassembly revealed that two more keywords are checked when the user presses the space key during the opening demo. The keyword is not checked exactly. Instead, it checks the total and XOR of the six characters typed before the space key. Therefore, the order of the letters does not affect the consequence. Also there are multiple set of characters that works for the feature.
The keywords are as follows.
“SILVER” Invincible (Checksum 0x17D5)
“1VKJIC” Zero Life (Checksum 0x6CA8) <- This feature is nullified anyway because Life is then read from the data disk.
“ARKoxo” Invincible + Infinite keys + Continuous shots of the Bomb + ? (Checksum 0x2034)
There might be a meaningful keyword that enables the second and the third features. However, there are too many combinations, and I could not find the one that has a meaning. The following is a C++ source code I used for finding the keywords for the second and the third features. If you are patient, you may be able to find meaningful words among the candidates.
// Copyright 2019 CaptainYS (soji@andrew.cmu.edu) // Can redistribue free of charge without permission. #include <stdio.h> #include <string.h> unsigned int Sum(const unsigned char buf[6]) { unsigned int sum=0; unsigned int A=0,B=0; for(int i=0; i<6; ++i) { A=(A^buf[i])&0xff; // AND is not really necessary. B=(B+buf[i])&0xff; } // 236C A8 84 EORA ,X // 236E EB 84 ADDB ,X // 2370 6F 80 CLR ,X+ // 2372 31 3F LEAY -1,Y // 2374 26 F6 BNE $236C return A*256+B; } inline unsigned char Increment(unsigned char c) { ++c; if('9'+1==c) { c='A'; } else if('Z'+1==c) { c='a'; } else if('z'<c) { c='0'; } return c; } int main(void) { printf("%02x\n",Sum((const unsigned char *)"SILVER")); // 17D5 "SILVER" Invincible // 6CA8 LIF=0 ?? // 2034 Invincible, Infinite KEY, Simultaneous bomb shots, +??? char buf[7]="000000"; while(0!=strcmp(buf,"zzzzzz")) { buf[5]=Increment(buf[5]); for(int i=5; 0<i; --i) { if('0'==buf[i]) { buf[i-1]=Increment(buf[i-1]); } else { break; } } for(int i=0; i<4; ++i) { // Doesn't take two same key strokes. if(buf[i]==buf[i+1]) { goto NEXT; } } auto s=Sum((const unsigned char *)buf); if(0x17D5==s || 0x6CA8==s || 0x2034==s) { printf("%04x: %s\n",s,buf); } NEXT: ; } printf("%s\n",buf); return 0; }
Ending (spoiler alert!)
Slow-Growth Problem of DUR Max
In this game, the player has four parameters: LIF, DUR, WEP, and MOV. The Maximum value of LIF, WEP, and MOV grows by collecting power-up items. There is no description of the condition that grows DUR Max in the manual, though, and the growth is extremely slow. DUR Max is written in $3C ($003C). I looked for the condition in disassembly that grows DUR Max. It turned out that DUR Max grows once every 128 times the player gets a damage.
The following disassembly is part of the damage routine.
1BFC 0C 48 INC <$48 1BFE 96 48 LDA <$48 1C00 84 7F ANDA #$7F 1C02 26 0D BNE $1C11 <- $48 is incremented for every damage. This BNE passes once every 128 times. 1C04 96 51 LDA <$51 <- If the damage is from self-inflicting (by bomb) <$51=1.Otherwise 0. 1C06 26 09 BNE $1C11 1C08 96 3C LDA <$3C <- Reading DUR Max. 1C0A 81 FF CMPA #$FF 1C0C 27 03 BEQ $1C11 <- Preventing overflow. 1C0E 4C INCA <- Increment DUR Max. 1C0F 97 3C STA <$3C <- Writing DUR Max.
The game balance should have been adjusted so that DUR Max is reasonably high when the user is close to the last-boss battle. But, if you play this game with no prior knowledge, your DUR Max won’t grow much until the last stage. What is worse is that if you play well and avoid damage, you won’t be rewarded. Good game play should be rewarded. Rather, this game penalizes you if you play well. Back in 1987, we did not have such methodology for adjusting the game-balance.
The present value of DUR (not max) recovers automatically. But the recovery is painfully slow. After killing a few enemies, you will need to wait for 20 to 30 seconds until DUR recovers. I was hoping the recovery may become faster with the growth of the player character, but it was not the case. DUR recovers by 1 every 64 iterations of the main loop. It is hard coded and never changes. Waiting 20 to 30 seconds every few battles is obviously unacceptable with the current standards of gameplay balance. Death Force is a game from 1987 after all.
The following is disassembly of where DUR is incremented by 1 every 64 iterations.
05E5 96 24 LDA <$24 <- Counter: incremented every iteration of the main loop. 05E7 84 3F ANDA #$3F 05E9 26 0E BNE $05F9 <- Pass once every 64 iterations. 05EB 96 38 LDA <$38 <- Reading DUR current value. 05ED 91 3C CMPA <$3C 05EF 24 01 BCC $05F2 <- Passes if there is a room for recovery. 05F1 4C INCA <- Recover by one. 05F2 97 38 STA <$38 <- Writing DUR current value.
Death Force World Map
The following maps are created by trimming and stitching screenshots together using 2D Retro Map Tool. I had to add support for transparency to stitch underground map where only passable blocks are visible at one time. I will upload the upgraded version shortly.
Playing Guide
Beginning
The basic gameplay involves (1) collecting all weapons and seals (I think the developer meant SEAL, but misspelled as SEEL in the game.), (2) killing the last boss, and (3) killing yourself. Aside from some hidden stairs, there are few tricks or secrets you need to find. Locations of the seals, weapons, and stairs are marked in the above maps.
This game is called an RPG, but unlike traditional role-playing games like Wizardry or Ultima, the player character does not grow just by killing enemies. You need to pick up items for increasing LIF, MOV, and WEP attributes, and take damages to increase DUR maximum. Boss characters drop items for increasing LIF, MOV, or WEP. The type of the item is random.
You also need to collect KEYs. You cannot get around without KEYs. One of the biggest nightmares in this game is that you can only carry up to 5 keys at one time. You cannot collect 100 keys before going into long travel. Therefore, you need to spend time on the way to re-supply keys when possible. Since the kind of item an enemy drops is random, you will spend a great deal of time on re-supplying keys. Even taking into account that the game is from 1987, it’s still very painful and takes tremendous patience.
If you play it on XM7 emulator, I suggest keeping a memory dump of the main system from $0000 to $007F always open. There the program keeps main parameters. Some of the parameters that I identified are listed in the Selected Technical Information section. You can carry only up to five keys at one time, but you cannot see how many keys you have. The game will torture you by not telling how many keys in your pocket! But the number of keys is in $003D. You’ll know when you finally have enough keys. Also you need to take damage 128 times to increase the internal value of DUR max by one. Extremely painful, but at least if you know how much more damage for increasing DUR max, it gets slightly more bearable. The damage count is $0048.
When you start a new game, the first thing to do is to collect keys. You need to work on this painfully boring task many times during the game play. What you can do is move to the map to the right from the starting map, and then go down. In there one L-PAC, which recovers your LIF, is hidden behind the wall. It is close to the top-right corner. The item re-appears when you get out and come back in to this map. Therefore you won’t need to worry about exhausting your LIF to death.
In some maps, enemies, items, and bosses never appear again once you kill them and pick them all. In other maps, they do re-appear indefinitely once you walk out and in to the map again. For example, you can quickly recover your LIF in such maps with indefinite L-PACs. One thing to be careful of is if the map consumes a key, you may exhaust your keys by walking in and out of the map many times. Some maps consumes a key but also you can get a key or two indefinitely. In those maps you won’t have to worry about exhausting keys. In a map that a weak boss re-appears indefinitely, you can kill a boss many times to train your L-MAX, W-MAX, and M-MAX.
Once you collect enough keys, go to MAP $21 and get 8-way shot. 8-way shot is very useful. If you kill all small enemies and get the 8-way shot, you will need to fight a boss. If you want to avoid a boss-battle, leave at least one small enemy.
After getting the 8-way shot, go to MAP $03 via underground. There you will see small enemies that drop an item. In this map, enemies and bosses will re-appear indefinitely. By killing enemies and collecting items, you can quickly recover your LIF, WEP, and MOV. If you also kill the bosses, you can train your L-MAX, W-MAX, and M-MAX. The bosses in this map are relatively weak. You can go close to the boss and blaze you 8-way shots. If you shoot from immediately above or below the boss, near the center, 3 of the 8 shots will hit the boss. It’s a good idea to exercise your patience, and spend several hours to train your L-MAX, W-MAX, and M_MAX in this map. You’ll need to do it before going to the last-boss battle. If you do it earlier, the rest of the game play will become easier.
Getting Guided Missile, Bomb, and Fire Thrower
After training in map $03 for several hours, you are ready to go and get three weapons, Guided Missile, Bomb, and Fire Thrower. Go to Stage 8 via underground and come out to the field in map $62. There are two bosses in map $62, but they are easy to defeat.
Then move to the map above (map $52). You will need to fight a relatively strong boss, which I call the “Water Bottle.” Try to blaze 8-way shots and attack physically at the same time. You may take substantial damage, but you should be able to win. Then go underground and get the Guided Missile. Once you have this weapon, it is easy to kill the Water Bottle. All you need to do is hide behind a wall and continue shooting the Guided Missiles. Actually if you have a wall, this strategy works for all the bosses, including the last boss.
Then go back to map $62 and go to the underground level. Walk to the south while re-supplying as needed and go to Stage 7. Go above ground at map $A0. Go down to $B0 and get the Flame Thrower. The boss at $B0 is strong. Therefore, leave one small enemy to avoid the boss battle.
Make sure to walk from map $C1 to $D1 from the right-hand side of the map. You need to kill two very strong bosses that I call “Sunny-Side Up” and “Pierrot” (“clown” in Japanese). Use hover to quickly take cover behind the wall and shoot guided missiles from there. These bosses won’t re-appear once you kill them, so they won’t be an issue in the return trip.
Then go to map $C0, and go underground. One very important thing in the underground level of map $C0 is to make sure you have at least one key when you go in, and then as soon as you go in, get two keys hidden behind the door. Since you can get two keys, you can go between maps $C0 and $B0 to recover your keys to max (five keys).
Once you have at least two keys, go to map $D0 and get the Bomb. This weapon is needed to see the ending roll because you will need to kill yourself after defeating the last boss.
Collecting Seals
Then all you need to do is collect the 15 seals. Locations of the seals are on the maps. You need to collect them in a specific order. I put numbers beside each in the maps so that you can know in what order you need to collect them.
I don’t know if you need to collect all the weapons as well. I have a feeling that you don’t, but I’ve not confirmed it. Maybe it is safe to also collect the Boomerang as well.
Training DUR Max
After collecting all seals, one last painful thing to do before fighting the last boss is to train DUR Max. In this game, DUR Max increases by one for every 128 times damage is taken. (By the way, the number you see on the screen is twice the internal value. It means that you need to take 256 times of damages to increase the number on screen by one.)
In my opinion, the most efficient location to train DUR Max is map $B5. Four small enemies with an item (I call them Shrimps) re-appears indefinitely. Also you can pick up two L-PACs. If you need more L-PAC, there is another one on map $B6. You can go to the secret underground level from a few maps below to recover WEP as well.
To kill these four small enemies, align them nearly horizontally and shoot 8-way shots. If you are lucky enough, four shots will hit them. Once you attack, they shoot you back. So you can get damages and train DUR Max.
You will need to continue this work for several hours. Remember: it’s a game from 1987 with dreadful gameplay balance.
Last Boss Battle
When your DUR grows like 50 on the screen, then you can go for the last-boss battle. I have drawn green lines to tell you how to get to the last-boss.
But, enemies in the last stage will fiercely attack you. You will get substantial damage before getting to the last-boss. The last place to recover is map $7B. There one of the weakest bosses, which I call Green, re-appears indefinitely. If you go down to $8B, you will need to fight a Pierrot, which will give you a good amount of damage. So, instead of going down and come back up, you can save and load to resurrect a Green. Since L-MAX, W-MAX, and M-MAX also recovers LIF, WEP, and MOV, you can easily top-off by repeating saving and loading in map $7B.
Then finally the last-boss battle. In the last-boss chamber, there are a bunch of small enemies which I call Blackberries. Blackberries are fast and blaze missiles at you. They move too quickly for guided missiles, so use your 8-way shots, bombs, and direct attacks to quickly kill them before getting too much damage.
Once you kill all Blackberries, you need to fight a normal Pierrot first, and then the last boss, which looks like a Pierrot, but with two eyes open. You can win by hiding behind a wall and shooting guided missiles.
After killing the last-boss, use Bomb to kill yourself to see the ending roll.
Selected Technical Information
Main Loop
$230~$24E Initialization
$251~$277 Main Loop
$279~$28B PAUSE loop (runs until ESC key stroke)
0251 8D 26 BSR $0279 <- If the user presses the ESC, this loop blocks until another ESC key stroke. 0253 BD 22 0B JSR $220B 0256 25 D8 BCS $0230 0258 8E 00 00 LDX #$0000 025B 9F 0E STX <$0E 025D BD 0A 5F JSR $0A5F 0260 BD 02 8E JSR $028E 0263 BD 02 B6 JSR $02B6 <- Moving: item-acquisition is in this routine. 0266 BD 15 E4 JSR $15E4 0269 BD 14 D4 JSR $14D4 026C BD 0A 28 JSR $0A28 026F BD 21 29 JSR $2129 0272 BD 2A 4C JSR $2A4C 0275 0C 24 INC <$24 0277 20 D8 BRA $0251
Map ID and State ID matching table
Addr +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +A +B +C +D +E +F Sum 7D00 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F ................ 7D10 : 00 00 00 00 00 00 01 01 01 01 01 02 02 02 02 02 :0F ................ 7D20 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E ................ 7D30 : 00 00 00 00 00 00 01 01 01 01 01 01 02 02 02 02 :0E ................ 7D40 : 00 00 00 00 00 00 09 09 09 01 01 01 03 03 03 03 :2A ................ 7D50 : 08 08 08 08 08 08 09 09 09 01 01 01 03 03 03 03 :5A ................ 7D60 : 08 08 08 08 08 08 09 09 09 09 09 09 03 03 03 03 :72 ................ 7D70 : 08 08 08 08 08 09 09 09 09 09 09 09 03 03 03 03 :73 ................ 7D80 : 07 07 07 08 08 09 09 09 09 09 09 09 03 03 03 03 :70 ................ 7D90 : 07 07 07 08 08 09 09 09 09 04 04 04 03 03 03 03 :61 ................ 7DA0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 03 03 :52 ................ 7DB0 : 07 07 07 07 06 06 06 06 04 04 04 04 04 04 04 04 :54 ................ 7DC0 : 07 07 07 07 06 06 06 06 05 05 04 04 04 04 04 04 :56 ................ 7DD0 : 07 07 07 06 06 06 06 06 05 05 04 04 04 04 04 04 :55 ................ 7DE0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A ................ 7DF0 : 07 07 07 06 06 06 06 05 05 05 05 05 05 05 05 05 :5A ................ ---------------------------------------------------------- ---------------- Sum : 50 50 50 4F 4C 4F 5E 5C 56 41 3F 41 34 34 33 33 :79
It tells rough placements of the stages.
Stage 0 Stage 1 Stage 2 Stage 8 Stage 9 Stage 3 Stage 7 Stage 6 Stage 4 Stage 5
Stage 5 is unreachable.
Basic Variables
Main CPU address space $0000 to $00FF (Physical address $30000 to $300FF)
DP=0x00 +00 Key stroke interpreted as joystick code. +01 Player character direction (0 is up. 8 directions clockwise.) +02 Block coordinate X $25 at the right edge. +03 Block coordinate Y $22 at the bottom edge. +04 Copy of block coordinate X. +05 Copy of block coordinate Y. +06 Copy of the joystick code. +07 Alternate between 0 and <$06 copy. +08 +09 Sword state. 0 is retracted. 3 is most extended. +0A +0B Move counter. The player character moves one step when it is zero. In normal mode it is reset to 3. In hover mode 2. +0C Alternate 0 and <$0D copy while button is held down. Probably for continuous shooting. +0D Last button state (for thrust event) +0E Higher byte of the timer tick count +0F Lower byte of the timer tick count +10 +11 +12 +13 General purpose +14 +15 Acquired item code +16 +17 $0A92~$0BE1 loop counter. Probably for moving enemies. +18 +19 +1A +1B +1C +1D Stage ID +1E [$7E00+MapID] +1F +20 Bit 0 indicates field or underground. Bit 1 is set to 1 when the player moved between the field and underground to flag that the map needs to be updated. Bit 1 will be reset to 0 as soon as the map is updated. +21 +22 Map ID +23 +24 Incremented every iteration of the main loop +25 When moving to a next map, [$7800+20*(Y/4)+(X/2)]&0xC0 +26 Pointer to the map properties set in the map-update routine. +27 +28 +29 +2A +2B +2C +2D Number of remaining enemies in the current map. +2E Cannot enter the stairs unless it is 0. If bit 7 is set, the player cannot get out of the map until the boss is killed. +2F Updated when the player moves across a stage border. The meaning is unknown yet. +30 Updated when the player moves across a stage border. The meaning is unknown yet. +31 +32 +33 +34 Counter since the last thrust or shooting. +35 LIF +36 MOV +37 WEP +38 DUR +39 LIF Max +3A MOV Max +3B WEP Max +3C DUR Max +3D Remaining Keys +3E Available weapon flags +3F +40 Number of acquired seals. (I believe SEEL in the game is misspelling) +41 +42 Last-acquired item type for showing a message. +43 GET **** I believe it is a counter for showing a message. +44 +45 +46 +47 +48 Damage counter. DUR Max is incremented when its bit 7 flips. +49 Updated when the player moves across a stage border. The meaning is unknown yet. +4A Updated when the player moves across a stage border. The meaning is unknown yet. +4B Updated when the player moves across a stage border. The meaning is unknown yet. +4C Updated when the player moves across a stage border. The meaning is unknown yet. +4D Updated when the player moves across a stage border. The meaning is unknown yet. +4E Updated when the player moves across a stage border. The meaning is unknown yet. +4F Updated when the player moves across a stage border. The meaning is unknown yet.
Identified Sub-Routines
$0694 Collision check with enemies and walls. $0A14 Display message. <$42 is the message code. <$43 is probably the duration. A register is probably the number of remaining items for the message. $0E87 Change stage. Called from $245B (Change map) $2414 D=20*(Y/4)+(X/2) $241F D=$4500+B*#$28+A $2429 Change map $27A0 Update passable blocks after changing the map.
Tables
Entrance-coordinate table from $7C00 was particularly useful.
$2109~ Moving vector table?
$2199~ Moving vector table? $2199+(joystick code & 0x0F) $FF means unavailable direction.
$3200~ Probably parameter table of enemies.
$328A~ Seal acquisition flags. 1 byte per seal. 0 or 1.
$3299~ I think it is the enemy list. Not confident.
$3330~ Looks to be maps, but too short.
$38C0~ BGM work area? Some bytes are in sync with the BGM.
$4500~$4B3F Map for detecting collision between enemies and items.
'0' is the player character. $FF is an empty block.
$00~$2F are enemies. $30~$7F are items.
$4C00~$4F1F Map for detecting collision between the wall.
$7800~ Map.
$7C00~ The first byte is the number of stairs for the current stage.
Subsequent 4-byte entries mean MapID, X, Y, direction of entry.
$7D00~$7DFF Map ID to Stage ID table.
Stairs Table
Stage 06
A4 05 0B 01 A7 1C 07 04 A7 17 1B 01 C7 05 17 01 D6 08 07 01 D7 05 17 01 E6 08 07 01 F6 08 04 01 Isn't this Y-coordinate a bug?
Stage 07
5F 07 0F 01 Obvious bug. Map ID 5F is outside of Stage 7. 82 07 0B 04 90 0D 15 01 90 1B 15 01 92 05 13 04 A0 05 03 04 A0 05 16 03 A3 05 0F 04 A3 1F 1B 04 B2 03 0D 01 C0 05 0F 04 C1 21 13 04 C2 17 09 01 C3 07 08 01 F0 0F 13 01
Stage 08
70 07 0F 04 71 13 13 04 71 23 0F 04 51 0B 13 04 52 23 17 04 62 07 13 04 72 1B 0B 01 63 0B 13 04 53 0B 13 04 64 17 13 04 54 19 0B 04 74 1F 0F 04 84 03 1B 04 94 17 13 04 65 21 0B 01
Stage 09
48 0F 0B 01 56 1B 09 04 57 19 0F 04 The entrance overlaps with the wall. Cannot enter. 58 0E 07 04 69 1E 13 04 Once enter, exit overlaps with the wall, therefore no exit. Is it intentional? 6A 0F 14 04 6B 1E 13 04 75 05 0C 01 77 17 1C 01 77 1C 07 04 78 07 1F 01 79 19 0D 01 Cannot enter. In the finish-able version, it is 79 19 0B 01. 7A 07 1D 01 7B 05 01 04 85 08 10 04 Bug? Intentional? It is off from the graphics. 87 1E 0F 04 88 13 0F 04 89 05 16 04 Once enter, exit overlaps with the wall, therefore no exit. Is it intentional? 8A 07 1D 01 8B 05 07 01
Item Acquisition
Item acquisition is processed in the routine from $0694. The same routine is used for collision check against enemies. The player does not get damage from the collision with an enemy, but cannot walk through an enemy.
JSR $15A8 will return the code of the item acquired. If it is greater than $0B (11), it is a seal, otherwise a regular item. Seals can only acquired in a specific order. The player can know that a seal is missed in an earlier stage if the player cannot pick up a new seal. The game is torturing the player with DUR, but this point was somewhat kind to the player.
If the player acquired a regular item, the program jumps to the address in the jump table $076B~.
06A9 BD 15 A8 JSR $15A8 <- Identify what item is acquired. 06AC 81 0B CMPA #$0B 06AE 22 22 BHI $06D2 <- Is a seal if the code is greater than $0B 06B0 C6 01 LDB #$01 06B2 96 15 LDA <$15 06B4 BD 15 A8 JSR $15A8 <- I think this sub-routine erases the item graphics. 06B7 97 15 STA <$15 06B9 8E 07 6B LDX #$076B 06BC 48 LSLA 06BD AD 96 JSR [A,X]
Below is the jump table.
0 0783 KEY 1 0790 WEAPON 0 8-Shot 2 0790 WEAPON 1 Guided 3 0790 WEAPON 2 Framethrower 4 0790 WEAPON 3 Boomerang 5 0790 WEAPON 4 BOMB 6 079D L-PAC 7 079D M-PAC 8 079D W-PAC 9 07CD L-MAX 10 07CD M-MAX 11 07CD W-MAX
The following disassembly is for acquiring a seal.
06D2 8E 32 8A LDX #$328A <- Seals already acquire. 06D5 80 0C SUBA #$0C <- Item code 12 is seal 0. Therefore, subtract 12 from the item code. 06D7 27 08 BEQ $06E1 <- If it is seal 0, always pass. 06D9 4A DECA <- To test if the previous seal is already acquired, A=A-1 06DA E6 86 LDB A,X 06DC 10 27 00 28 LBEQ $0708 <- Is seal[A] already acquired? 06E0 4C INCA <- Bring the value of A back to previous. 06E1 6C 86 INC A,X <- Raise the seal-acquired flag. 06E3 0C 40 INC <$40 <- Increment the seal count.
Changing the Stage
By monitoring $7C00 to $7CFF, you can see that the player moved across a stage border. The same routine is called after a boss battle.
The stairs table from $7C00 is extremely important. If the copy-protection backfire theory is correct, most likely this routine checks the copy protection, and will correct the location of the stairs in map $79 if the check passes.
However, the stairs table is read at the bottom of this routine, and the routine returns by PULS B,PC. Therefore, the stairs table is unaltered in this routine.
Of course, there is a possibility that the table is corrected somewhere totally different. For example, the developer expected someone may look into the code, the developer could have separated the routine for reading the stairs table, and the routine for correcting the table.
But, I think that is very unlikely.
0E87 34 04 PSHS B <- B register is stage ID.
0E89 86 02 LDA #$02
0E8B B7 FD 90 STA $FD90
0E8E 8E 0F 31 LDX #$0F31
0E91 E6 85 LDB B,X
0E93 8E 30 C3 LDX #$30C3
0E96 58 LSLB
0E97 EC 85 LDD B,X
0E99 10 8E 00 60 LDY #$0060
0E9D 8E 90 00 LDX #$9000
0EA0 BD 29 91 JSR $2991
0EA3 E6 E4 LDB ,S
0EA5 58 LSLB
0EA6 58 LSLB
0EA7 58 LSLB
0EA8 EB E4 ADDB ,S <- B=stage ID * 9
0EAA 8E 0E D7 LDX #$0ED7 <- [0ED7~] looks to be a table 9 bytes per row.
0EAD 3A ABX
0EAE EC 84 LDD ,X
0EB0 DD 49 STD <$49
0EB2 EC 02 LDD 2,X
0EB4 DD 4B STD <$4B
0EB6 EC 04 LDD 4,X
0EB8 DD 4D STD <$4D
0EBA EC 06 LDD 6,X
0EBC 97 4F STA <$4F
0EBE D7 2F STB <$2F
0EC0 A6 08 LDA 8,X
0EC2 97 30 STA <$30
0EC4 8E 31 06 LDX #$3106
0EC7 EC 84 LDD ,X
0EC9 EB E4 ADDB ,S <- D register has track and sector numbers.
0ECB 10 8E 00 01 LDY #$0001
0ECF 8E 7C 00 LDX #$7C00 <- Location of the stairs table buffer.
0ED2 BD 29 91 JSR $2991 <- Read sector.
0ED5 35 84 PULS B,PC
[0ED7~]
02 02 03 03 03 04 05 00 00
02 02 03 03 03 04 05 01 00
03 02 03 03 03 05 05 01 01
03 02 03 03 04 05 06 01 01
03 03 03 04 04 05 06 02 01
04 03 04 04 04 05 06 02 02
04 03 04 04 05 06 07 03 02
05 04 04 05 05 06 07 03 03
06 05 05 05 06 07 09 04 03
07 06 06 06 06 07 0A 06 05
Boss Battle
Boss-battle mode shares the main loop with the normal mode. Once the player kills all small enemies in the map in which a boss or two exist, the player cannot get out of the map until all the bosses are killed. The conditions for a boss to appear are for all small enemies to be killed and no more items to exist in the map.
The boss number (2 bosses per map maximum) is written in <$2E. If bit 7 of <$2E is 1, the player cannot get out of the map until all the bosses are killed. If small enemies are still alive, but a boss exists in the map, <$2E is 1. The player can get out of the map, but cannot enter stairs.
Following addresses are controlling <$2E.
0F61 LDA #$80 STA <$2E Take all items->cannot escape boss fight. 0FDF LDA #$81 STA <$2E When boss appears, $80->$81 106B LDA #$01 STA <$32 STA <$2E Can escape the boss but cannot use the stairs. 11D5 LDA #$82 STA <$2E Looks like the second boss. 11FE CLRA STA <$2E STA <$32 <$32 and <$2E works together? Couldn't find the meaning of<$32 yet.
Boss-generation routine is from $0F69.
0F69 96 2E LDA <$2E 0F6B 10 2A 01 19 LBPL $1088 <- Skip if no boss exists. 0F6F 81 80 CMPA #$80 0F71 10 22 00 6E LBHI $0FE3 <- Already in the boss-battle mode? 0F75 96 23 LDA <$23 0F77 9B 2B ADDA <$2B 0F79 9B 52 ADDA <$52 >0F7B 10 26 FC 66 LBNE $0BE5 <- The boss won't materialize if the sum of <$23, <$2B, <$52 is non zero Meaning not identified yet 0F7F F6 32 99 LDB $3299 <- Boss ID 0: Mint 1: Green brain 2: Silver brain 3: Sunny side up 4: Canteen 5: Robot 6: Octopus 7: Pierrot (or Clown) 8: Lady bug 9: Pierrot (or Clown) A: Pierrot (as Last Boss) 0F82 D7 13 STB <$13
These names are my own; I don’t know the official names. What is interesting is that you can control which boss appears by stopping XM7 at $0F82 and modifying the value of B register.
The graphics corrupted when I set B=$0B. Also, the length of the boss table is 11. I believe there are no more than 11 kinds of bosses in total. There are two Pierrot types, aside from the last boss. One may be unused. Or perhaps both are used and I simply could not identify which was which.
Boss properties looks to be described in two tables below. I haven’t yet figured out the meanings of the tables.
$30CF 0 1F 01 16 1 20 07 16 2 21 0D 16 3 43 02 24 4 45 06 16 5 46 0C 24 6 48 10 24 7 4B 04 16 8 4D 0A 24 9 4B 04 16 A 14 03 16 $30F0 0 3C 0B 1 46 1B 2 64 1B 3 78 05 4 96 0F 5 AA 05 6 BE 05 7 C8 0F 8 BE 05 9 D2 1B A E6 1F
*Game images belong to the original copyright holder.
NPO Game Preservation Society
Author: Sōji YAMAKAWA(Supporter Member)
Home Page: http://www.ysflight.com