On February 19th, 2018, I figured out two small ORIC Manic Miner hacks. They enable infinite lives and free transporting to play any room. I documented the hacks on page Manic Miner on ORIC. Today (February 21st, 2018) I wanted to try out the BBC Micro version of Manic Miner. More specifically, it was my intention to find similar hacks for the BBC Micro version, too, but I soon discovered that the B-em BBC Micro emulator does not have a machine language monitor! How would I be able to modify RAM at run-time? And how would I get RAM dumps to examine in the first place? The situation looked pretty bad.
I was working on Fedora Linux 27, so I checked out file linux-gui.c
and learned how the Allegro 4 GUI menu system worked. But I could not find a way to create text boxes that would accept keyboard input from the user. I tried googling and everything, but I guess this Allegro menu/dialog system is pretty primitive and not comparable to the "real" GUI frameworks that allow all kinds of windows to be created. I know next to nothing about GUIs, I am a command-line guy, basically. Why did I study the Allegro menu system? Because I wanted to add very primitive machine language monitor features to B-em, that was the reason. I was not sure whether I could do it, but I only needed four operations to work:
Because I could not use the Allegro menu GUI, I went for a pretty ugly hack: I made B-em a TCP-server listening at localhost (IP address 127.0.0.1) port 9999. That is the interface for the four operations described above. When B-em 2.2.3kk is running, you can activate its TCP-server hack by pressing F1. It will suspend the BBC Micro emulation just like F11 does when entering the Allegro GUI menus. You can use any TCP-client to connect to B-em as long as you know the very simple text-based command protocol B-em 2.2.3kk supports on top of TCP/IP.
If you are not interested in a pretty detailed explanation of my Manic Miner hacking, please go to the bottom of this page's Conclusion to find out how to make use of B-em 2.2.3kk and the BBC Micro Manic Miner hacks.
I first created a small C program to find out where the Miner Willy lives variable is located. Then I took three RAM dumps of a running Manic Miner: first
with three lives, second
with two lives and finally third
with one life left. The C program to analyze the dumps is as follows:
#include <stdio.h> #include <stdlib.h> #define MEM_DUMP_SIZE 65536 int main(void) { FILE *f1, *f2, *f3; unsigned char membuf1[MEM_DUMP_SIZE]; unsigned char membuf2[MEM_DUMP_SIZE]; unsigned char membuf3[MEM_DUMP_SIZE]; int i; f1 = fopen("first", "r"); fread(membuf1, MEM_DUMP_SIZE, 1, f1); fclose(f1); f2 = fopen("second", "r"); fread(membuf2, MEM_DUMP_SIZE, 1, f2); fclose(f2); f3 = fopen("third", "r"); fread(membuf3, MEM_DUMP_SIZE, 1, f3); fclose(f3); for (i = 0; i < MEM_DUMP_SIZE; i++) { if (membuf1[i] == 0x02 && membuf2[i] == 0x01 && membuf3[i] == 0x00) { printf("address %d could control lives number\n", i); } } return 0; }
I got the following output:
[kalevi@localhost memdumps]$ ./a.out address 26558 could control lives number
I wrote number 4 to the suggested RAM location 26558, killed Miner Willy and yes, the location was right. Then I created a second C program to examine first
dump:
#include <stdio.h> #include <stdlib.h> #define MEM_DUMP_SIZE 65536 int main(void) { FILE *f; unsigned char membuf[MEM_DUMP_SIZE]; int i; f = fopen("first", "r"); fread(membuf, MEM_DUMP_SIZE, 1, f); fclose(f); /* Look for LDA $67be */ for (i = 0; i < MEM_DUMP_SIZE - 3; i++) { if (membuf[i] == 0xad && membuf[i+1] == 0xbe && membuf[i+2] == 0x67) { printf("address %d %x matches\n", i, i); } } return 0; }
That program gave me two clues:
[kalevi@localhost memdumps]$ ./a.out address 26505 6789 matched address 26597 67e5 matched
Then I had a problem: I needed to disassemble the machine code, but my primitive machine language monitor only supported RAM writes and raw RAM dumps to a file. I did not want to code a disassembler, because it would have been very time-consuming. I wanted to use an existing 6502 disassembler. I found DCC6502 from github.com and surely enough it was simple and neat, containing only one C source file. But the damn program would always disassemble files from the beginning and I needed to disassemble from a specific offset!
So I had to digress and add a command line option -s
to specify the starting offset. The code was extremely simple to understand, so it took only 10-15 minutes. I will probably do a git pull request to the author, because my patch is nice and useful.
I could then disassemble the machine code, but the first clue did not look promising, so I moved on to the second:
[kalevi@localhost memdumps]$ dcc6502 -o 26597 -s 26597 -m 50 first ; Source generated by DCC6502 version v2.0 ; For more info about DCC6502, see https://github.com/tcarmelveilleux/dcc6502 ; FILENAME: first, File Size: 50, ORG: $67E5 ;--------------------------------------------------------------------------- $67E5 LDA $67BE ; $67E8 CMP #$00 ; $67EA BEQ $67FA ; $67EC DEC $67BE ; $67EF LDA $80 ; $67F1 JSR $62C3 ; $67F4 JSR $62F4 ; $67F7 JMP $6208 ; $67FA LDA #$32 ; $67FC STA $71 ; $67FE LDA #$80 ; $6800 STA $70 ; $6802 LDX #$29 ; $6804 LDY #$00 ; $6806 TYA ; $6807 STA ($70),Y ; $6809 INY ; $680A BNE $6807 ; $680C INC $71 ; $680E DEX ; $680F BNE $6807 ; $6811 JSR $2EF0 ; $6814 LDX #$00 ; $6816 LDA $0000,X ;
With 6502 CPU, DEC stands for "DECrease memory" and I was pretty sure that this was indeed the right code to get rid of! Manic Miner was making lives variable smaller, so I prevented it by inserting three NOP opcodes (0xEA in hex, 234 in decimal) to replace the DEC and its 16-bit operand (split in two 8-bit values):
[kalevi@localhost memdumps]$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. s 26604 234 OKAY s 26605 234 OKAY s 26606 234 OKAY q QUITTING Connection closed by foreign host.
Now that the infinite lives case was done, it was time to investigate room selection. I played Manic Miner and took two RAM dumps: second
after I reached the second room and third
after reaching the third room. I already had the first
RAM dump from the lives investigation, so I just copied it to the new memdumps2
directory. Then I created my third small C program:
#include <stdio.h> #include <stdlib.h> #define MEM_DUMP_SIZE 65536 int main(void) { FILE *f1, *f2, *f3; unsigned char membuf1[MEM_DUMP_SIZE]; unsigned char membuf2[MEM_DUMP_SIZE]; unsigned char membuf3[MEM_DUMP_SIZE]; int i; f1 = fopen("first", "r"); fread(membuf1, MEM_DUMP_SIZE, 1, f1); fclose(f1); f2 = fopen("second", "r"); fread(membuf2, MEM_DUMP_SIZE, 1, f2); fclose(f2); f3 = fopen("third", "r"); fread(membuf3, MEM_DUMP_SIZE, 1, f3); fclose(f3); for (i = 0; i < MEM_DUMP_SIZE; i++) { if (membuf1[i] == 0x00 && membuf2[i] == 0x01 && membuf3[i] == 0x02) { printf("address %d could control room number\n", i); } } return 0; }
So this time I was looking for RAM locations that were increasing by one. I got the following result:
[kalevi@localhost memdumps2]$ ./a.out address 25437 could control room number
I tried to modify the RAM location:
kalevi@localhost memdumps2]$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. s 25437 5 OKAY q QUITTING Connection closed by foreign host.
But it was a total failure - no observable effect. I killed Miner Willy, but he was not transported to a new room. So I created a fourth C program:
#include <stdio.h> #include <stdlib.h> #define MEM_DUMP_SIZE 65536 int main(void) { FILE *f; unsigned char membuf[MEM_DUMP_SIZE]; int i; f = fopen("first", "r"); fread(membuf, MEM_DUMP_SIZE, 1, f); fclose(f); /* Look for STA $635d */ for (i = 0; i < MEM_DUMP_SIZE - 3; i++) { if (membuf[i] == 0x8d && membuf[i+1] == 0x5d && membuf[i+2] == 0x63) { printf("address %d %x matches\n", i, i); } } return 0; }
In fact I created several program variants looking for LDA, LDX and LDY commands, but none of them found anything. STA absolute finally matched:
[kalevi@localhost memdumps2]$ ./a.out address 25368 6318 matches address 25397 6335 matches
I disassembled the first location, but it did not look good, how about the second? I backtracked from the suggested RAM location a little bit, trying earlier addresses before STA $635D:
[kalevi@localhost memdumps2]$ dcc6502 -o 25387 -s 25387 -m 50 first ; Source generated by DCC6502 version v2.0 ; For more info about DCC6502, see https://github.com/tcarmelveilleux/dcc6502 ; FILENAME: first, File Size: 50, ORG: $632B ;--------------------------------------------------------------------------- $632B ADC #$0A ; $632D STA $635C ; $6330 JMP $6310 ; $6333 STA $80 ; $6335 STA $635D ; $6338 DEC $635D ; $633B LDA $6B7F ; $633E CMP #$00 ; $6340 BEQ $6351 ; $6342 LDA $80 ; $6344 CMP #$13 ; $6346 BCC $6351 ; $6348 LDA #$01 ; $634A STA $80 ; $634C LDA #$00 ; $634E NOP ; $634F NOP ; $6350 NOP ; $6351 RTS ; $6352 .byte $DF ; INVALID OPCODE !!! $6353 STX $8C8D ; $6356 .byte $EB ; INVALID OPCODE !!! $6357 .byte $8B ; INVALID OPCODE !!! $6358 TXA ; $6359 SBC #$89 ; $635B DEY ; $635C NOP ;
I looked at STA $80 right before STA $635D and remembered that ORIC Manic Miner had its room variable stored in the zero page so $80 (128 decimal) could be what I was looking for! So I tried to write to that RAM location:
[kalevi@localhost memdumps2]$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. s 128 4 OKAY q QUITTING Connection closed by foreign host.
And to my great delight, it finally did the trick! After Miner Willy died once, he was transported to a new room! Note that this BBC Micro version room variable starts from number one, not from zero like on Commodore 16, Commodore 64 and ORIC versions, for example.
Here are the room numbers and their names:
room number | name |
---|---|
1 | Central Cavern |
2 | The Cold Room |
3 | The Menagerie |
4 | Abandoned Uranium Workings |
5 | Eugene's Lair |
6 | Processing Plant |
7 | The Vat |
8 | Miner Willy Meets The Kong Beast |
9 | Wacky Amoebatrons |
10 | The Endorian Forest |
11 | Attack Of The Mutant Telephones |
12 | Return Of The Alien Kong Beast |
13 | Ore Refinery |
14 | Skylab Landing Bay |
15 | The Bank |
16 | The Sixteenth Cavern |
17 | The Warehouse |
18 | Amoebatrons' Revenge |
19 | The Meteor Storm |
20 | The Final Barrier |
I hope this article provided you with a little bit of interesting information about some simple 6502 based machine language/assembly hacking. I am by no means an expert, so most things are new to me. To summarize, if you want to try these Manic Miner hacks, you need my special B-em 2.2.3kk release. It contains support for joystick-to-keyboard mapping (i.e. you can use USB controller with games that support only keyboard on BBC Micro) and it has a pretty ugly TCP-server hack to enable two basic machine language monitor features (i.e. ability to dump BBC Micro RAM to files and ability to write to arbitrary BBC Micro RAM locations).
The required steps are:
[kalevi@localhost]$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. s 26604 234 OKAY s 26605 234 OKAY s 26606 234 OKAY q QUITTING Connection closed by foreign host.
[kalevi@localhost]$ telnet 127.0.0.1 9999 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. s 128 4 OKAY q QUITTING Connection closed by foreign host.
Have fun! I know I will, very soon. Now I am too tired to play Manic Miner.