Site copyright © 2006-2023 Jennifer Taylor.
Blog
A place where I can ramble about my projects. To get a permalink to any blog entry, click on the date of publish at the top of the post.
The Road To Stream Deck Control
November 5th 2022, 10:03:44 am
Awhile back a friend linked me to a video tour of somebody's home arcade that was posted on YouTube. At one point they showed off that they had a wall-mounted phone with pictures of the games that when tapped would switch the game on. Wow! The idea grabbed me. What a cool addition to a home setup! The way I've been turning my games on and off for over a decade is by flipping a physical switch on a bunch of power strips that each game is plugged into. Its slow, hard on the knees and doesn't look even remotely cool. So I started researching how to set up a slick control scheme similar to the one in the video.Above all else, the most important goal for my own setup was "no internet of shit!". Basically, no external servers, no required phone apps, no accounts, no monthly subscription fees, no chance of the company going bust in several years like every company in the home automation space seems prone to. I don't want to lose control over my own things next time there's an AWS outage. I don't want to have to re-learn a new ecosystem in three years when the product line I invested in is deemed unprofitable and discontinued. I don't want my usage patterns monitored and data sold so that some company can subsidize what they sell to me. If I couldn't manage this goal, the project would be a non-starter.I also wanted fast control. In my research I saw a lot of different people talk about home automation setups where it takes 5-10 seconds to turn on a light when tapping a digital switch. That's unacceptably slow. I wanted my stuff to behave much like it did before: instant power-on when a switch was flicked. Finally, if at all possible I wanted everything to be open-source. The space seems so immature still that I didn't want to be locked into weird quirks and caveats of a particular implementation. I wanted something that would be fully customizable to how I imagined it and I didn't want to have to work around firmware bugs or unimplemented features.After a lot of research I landed on Home Assistant for the brains of the system. It's got a thriving ecosystem with tons of integrations and a super cool way to build and integrate your own hardware sensors with ESPHome. There's a huge variety of video tutorials and "Top 10" lists on YouTube so I don't have to discover features purely by reading Home Assistant's frankly cluttered and somewhat poor documentation. Home Assistant is a bit rough around the edges sometimes and has a somewhat ugly UI but for the most part it fit the bill. Most importantly it was fully open-source and entirely self-hosted! It even has an open-source Android app that talks directly to my installation with no third-party intermediary! I already had a server running in my network closet downstairs so I went with Home Assistant Core installed in a Python venv and served by nginx. I slapped a subdomain on it, used Let's Encrypt for SSL and called it a day.Finding IP-enabled AC switches turned out to be a much harder task than figuring out the software. I didn't really want to go with WiFi switches if possible due to the sheer amount of devices I would need to put on my network (44 games and counting!). WiFi also has weird latency issues depending on the distance something is from the nearest hotspot and can be unreliable at times. There really isn't much of an established Z-Wave ecosystem in the USA either. That seems to be popular mostly in Europe so most of the AC switches I found were for UK voltages and plugs. Zigbee switches in the USA seemed mostly centered around wall-mounted physical switches with remote control. Most of the products people used for Home Assistant plug-in control in America were cheap Chinese crap off of Amazon. I didn't want to risk a fire in my house, nor did I want to give Amazon a cent of my money if at all possible. There was an interesting product on adafruit that lets you build your own AC switches. Paired with an ESP8266 and ESPHome this looked like an excellent solution. However, the logistics of building 44 individual switches manually was daunting and I didn't see a good way to house the control electronics. There was also the Shelly 1 switch that looked promising. However, it was really meant to be installed in the outlet directly and it was also WiFi, so that was a no-go.It turns out the solution was staring me directly in the face the whole time. My roommate had an AP-7900 rack PDU he got from his dad that he was using as a simple power strip. I've also used AP-7900s in embedded labs back at my first job and even coded some simple python control programs for them way back in the day. Their upsides met my needs perfectly: switched PDUs tend to use TCP/IP to query and control outlet state, they're enterprise grade and last for decades in datacenters and they're proven safe! The main downside, access to ethernet, was not a problem for me given I've previously run ethernet drops to every corner of the house. So I traded my roommate for a spare surge protected power strip I had in a closet and messed around with integrating the AP-7900 into Home Assistant. After finding a post on the Home Assistant forums where somebody else integrated one, I slapped together a template yaml file for myself and tested it out with my candy cabs in the basement.A quick glance at eBay prices told me that outfitting all my games with AP-7900s was just not financially viable. They are rock solid and hold their value even decades after purchase. Also, I have my cabinets balanced on different circuits to ensure that no one breaker is ever loaded past 50% of its capacity. That meant that I had several clusters of games where there were only 3-4 total games to control. So, if I wanted to grab AP-7900s for the whole house, I would need around 20 of them. At about $125 a pop, that would run me at least $2500 just in the power switches. So I started digging into other PDUs that existed. I ended up finding out about the NP-02B 2-outlet PDU. I checked eBay and somebody was selling them for $40 a pop with fixed shipping. Bingo! Even assuming 20+ units, that's still around $800. While shockingly pricey compared to the cheap Amazon plugs it was still a massive savings and fit within my price range. So I bought one and tested out integrating it into Home Assistant.After the initial success I decided to go all-in on NP-02B PDUs. I ordered as many as I could purchase from the guy on eBay selling them for cheap and got to work integrating them. Once I was happy with my settings I threw a repo up on GitHub with my yaml template files. I also wrote a simple python script that could fetch the outlet state or set the outlet state of any NP-02B regardless of firmware. This became necessary as only the oldest firmwares supported SNMP out of the box and its implementation was buggy. So, I needed to hit endpoints exposed by the HTTP interface to control the units. Even worse, the documented HTTP API only worked properly in half the units so I needed to reverse-engineer the other units. To make it easier to integrate with Home Assistant I encapsulated all of the quirks into the simple python scripts so that I could use the same template regardless of the PDU firmware.A few of the NP-02B units that I ordered came non-working. The seller was super nice and sent me replacement units for free and said I could keep the old ones for parts. I opened them up and noticed they all had the same chip blown on the power supply board. Curious, I thought. But the back-and-forth with the seller revealed that he was also planning to list 30 more and I told him I would absolutely be buying again.It was at this point that I had about 25 games integrated into my Home Assistant setup. This is when I started noticing problems. When I had only a few games integrated everything felt snappy like I expected it to. But, with 25 games, sometimes it took upwards of 10 seconds for a tap to result in the game turning on. In the meantime, you got no feedback that the system was attempting to power on the game and it felt really unresponsive. I started digging into the Home Assistant logs and at this point I realized that it was polling only one switch at a time! Furthermore, Home Assistant would only send state change requests when it wasn't actively polling. I checked and polling each outlet took about 250ms to 500ms depending on the outlet. Oof. Reading the Home Assistant documentation again, I verified that this was undocumented behavior. Going to the forums I found somebody complaining of similar issues and the response was basically "command-line switches suffer this problem, we won't be fixing this." Yikes! The command-line switch integration is advertised on their site as the preferred way of integrating with otherwise-unsupported devices! This wasn't something I could have known when I tested the first few games but it was definitely a dealbreaker. I'd already spent a few hundred on the project at this point, however, so it was time to get creative.A major part of the polling slowdown was the network delay as well as the delay before the PDUs would send back their results. So, what if I created a daemon process that did the polling in the background and responded to Home Assistant instantly? In theory, it could work. So I went about taking the simple scripts that I'd made for the NP-02B and turning them into more of a full-fledged python project. I abstracted both the AP-7900 and NP-02B control and made a command-line interface that handled daemonizing as well as background caching. I swapped my yaml files over to use this utility and put up another github repo with the daemon program. Measuring the difference before and after, now Home Assistant could sweep the entire set of outlets in less than 2 seconds. I'd attained fast control again! Maybe not instant, but a worst-case delay of 2 seconds was way better than a worst-case delay of 10 seconds. Even better, my solution was available to anyone else that ran into this problem!I ordered another chunk of NP-02B PDUs from eBay and got to work integrating them with the rest of my games. This went smoothly except for there were a few islands of games that didn't have access to an ethernet jack. For these, I figured the bandwidth requirements were low enough that I could throw some WiFi bridges under the games and make due that way. I grabbed a few bridge units that I had lying around from my previous house and wired it all up. The best one lasted 3 days before it disconnected from the WiFi. Even worse, when the outlets weren't reachable Home Assistant would pause for a long time waiting for outlets. The sluggish button problem was back in a bad way. I don't think its "done" to have to reach behind games and unplug/replug a WiFi bridge twice a week in order to turn games on. So, I made the tough decision to pull several more ethernet drops through the house. All in all I pulled five more drops inside the finished space and covered every remaining wall that didn't have ethernet on it. I also added two cables for the garage games that ran to a secondary switch in the furnace utility room. Some of the locations were not ethernet-drop friendly and required a great deal of creativity and drywall patching to finish.With the connectivity problem out of the way I ordered more PDUs until I was able to hook up every game to my Home Assistant installation. All in all, I ordered 18 NP-02Bs and 2 AP-7900s. Including the third AP-7900 that I traded my roommate for I had 21 different PDUs controlling 44 games in Home Assistant. That turned out to be too much for Home Assistant's command-line integration to handle even with my daemon command-line utilities. So, it was back to the drawing board. From some research I figured out that the RESTful switch integration made use of parallel polling. I already had all the code in place to abstract controlling AP-7900s, NP-02Bs and even a NP-02 that got shipped to me by mistake. So, I added a second interface to it so that the core could be controlled via command-line or via REST and pushed the update to github. As an added benefit, I used the same core python library in my Naomi net dimm setup so that I could power-cycle games when sending a new one. Swapping all the config yamls for 44 games was tedious but well worth it! After the cutover, Home Assistant was able to poll all 44 games in less than a second and could respond to a power on or power off tap in a fraction of a second! Wow, way better than it could ever do even with the daemon that I wrote!Remember the NP-02B units that came DOA? Where I thought that the broken units were just a fluke? Well, the problem ended up coming back in a bad way. A few weeks after I finished setting up all the games I realized that my Crazy Taxi wasn't responding to power-on taps. The power on button had also disappeared from my net boot web page, a sign that the PDU wasn't responding. I checked and sure enough it was completely dead. I popped it open on my bench and sure enough, the exact same chip had blown on the power supply. Curious, I started googling and that's when I found several reports of people talking about the same defect in their units. Well, shit. I'd just bought 18 of these things and apparently there's a known flaw in the power supply that causes them to kill themselves after several years. I checked and the manufacturer even acknowledged it, stating the current revision doesn't suffer from the flaw. Ugh, it was time to figure out how to fix them myself. I found what I thought was a compatible power supply board on Mouser, ordered a few, and then bodged one into the dead unit. Success! It was right back to life and after reassembly was controlling the Crazy Taxi like it never died.At this point I had the fundamentals down. I had lightning fast control of my cabinets from any computer with an internet connection as well as through the Home Assistant app on my phone. The control was all local so I never needed to deal with AWS having a bad day or an ISP outage blocking me from turning on my games. All of the code was either open-sourced by me or an existing open-source project with a vibrant community. All of the switches themselves were safe, battle-tested and user serviceable if they failed. But I was missing one thing: guest control. And with that, I was missing the "wow factor." Having to be a turbo-nerd and go to some self-hosted website to turn on your games for your guests does not scream "cool" in the least.I went back to the drawing board researching tablets and mounts. I figured surely there's a large ecosystem of people using Home Assistant, so there's gotta be a figured out solution to having a wall-mounted control surface. Turns out, nope! The people that did this cobbled their setups together with cheap tablets they found on Amazon or eBay and printed up atrociously ugly wall mounts. There was a lot of discussion about what tablets were even powerful enough to run the Home Assistant web frontend without lagging. There was the question of finding tablets that could be rooted so they could run in kiosk mode. The more I thought about it, the more I felt like tablets were a hack and not a real solution. I sat on the problem for about a month and considered designing my own hardware for the problem. And then I saw a tweet with a picture of an elgato Stream Deck.This thing seemed perfect. It had actual buttons with haptic feedback. The screen seemed high resolution enough to have detailed photos and text on each button. It was self contained. It could probably be easily mounted on a wall. All it needed to drive it was a single USB C connection. Wow! I dug in and researched open-source alternatives to the official program and quickly found a python library that worked with all major models. I jumped on eBay, ordered one and got to work prototyping a Home Assistant front-end for it. What I ended up with is a python program that can talk to a Stream Deck over USB on any OS, takes as its configuration a list of entities and uses the Home Assistant local REST API to poll for state as well as toggle power for any configured game.The only question that remained now was how do I install these things on the wall? They needed some sort of controlling computer and I didn't want to have to run a full desktop for each installation. The natural choice would be a Raspberry Pi. Problem is, they're pretty much a terrible fit for this sort of thing. They cost about 10x what they're worth right now, they're massively overpowered for what I need to do, they're bulky, power hungry and they have a tendency to corrupt microSD cards. So I started looking for alternatives and landed on the Rock Pi S. These things are basically perfect for small internet appliances! They don't even have video/audio out, but they do have built-in ethernet, WiFi and USB and they are dead simple to bootstrap. They're dirt cheap and readily available from the manufacturer's preferred distributor. They're also incredibly tiny and they run Debian out of the box!Now, the only thing left was to drill holes in the Stream Deck base and mount it on the wall with toggle bolts. It turns out that if you mount the Stream Deck upside-down by rotating the mount, it sits at the perfect angle for operating a wall-mounted control panel. Even better, reusing thet magnetic base means that the Stream Deck snaps into place and can be easily removed for servicing. All of the Rock Pi S bits fit snugly inside the cable chase inside the base and I didn't have to design and print a holder! I installed the first Stream Deck upstairs to control the games that are on the main floor and used some D-line micro raceway to neatly tuck the ethernet and USB power cords away instead of leaving them dangling below the installation. Perfect!The second one I wanted to install on a wall in the laundry room that didn't have an outlet. Now, I'm no stranger to snaking power to new outlet locations so I was prepared to pop the ceiling tiles down and run romex all the way back to the breaker panel. But then I realized that I had some light switches on the same wall, in the same stud bay cavity, just on the other side of the wall! Perfect. A quick cut, an old work box, some fish tape and a little bit of romex later I had an outlet in the perfect spot to install the second Stream Deck. I imaged the microSD card from the first installation, changed the games in the config, set it up for WiFi and then installed everything onto the wall.After a six month journey I now have what I consider a "cool" installation. Guests can tap a button to turn on any game in the house, the installation looks slick, it's obvious how to use and I even get to monitor things on my phone. The outlets respond instantly to a power-on or power-off tap on my phone, computer or any of the Stream Decks mounted on the walls. Turning a game on using any interface instantly shows up on the other interfaces. Its locally-hosted and open-source across the board. The outlet hardware is documented and user serviceable, enterprise grade and battle tested by thousands of data centers. The candy cabinet power state seamlessly integrates with my net boot web interface. And even better, anyone else who wants can follow in my footsteps to set up a similarly slick setup! What more could I ask for?
Doom for SEGA Naomi!
This was originally published as a twitter thread before I deleted my account. Some contents were edited to better fit a blog post.I ported Doom to SEGA Naomi. If you netboot you can play it on your cabinet right now! I also wrote a WAD injector you can use with WAD files that you own to grab a ready-to-play ROM. You can get these out of your original copies or by purchasing Doom Ultimate on GoG. Or follow this link and grab the shareware version. More complete information is available on this page. What's stopping you from hearing E1M1 on your New Net City?Recovering a Grid Profile
Midway's The Grid is an incredibly fun game, partially because it allows you to create a profile that tracks your stats and lets you work towards character unlocks. Its fun to have your actual name announced when you make a great shot or when you're on a killing spree and its neat to be able to look back and see your best kill streak as well as how many kills and deaths you've had since you picked up the game. The main board of The Grid stores all player profiles on a battery-backed SRAM chip and looks the profile up on the cabinet it was created on when you pull up the profile on another cabinet. The problem with this setup is that the profile can't be transferred to a separate collection of cabinets, and most of these chips are very close to running out of juice at which point they will lose your profile.The obvious solution is to make backups of your profile using my Grid SRAM project. Several people have built Arduino shields and used the code I wrote to back up, edit and restore their profiles, myself included. But, what if you didn't get to your chip in time and your profile is lost? Well, hopefully you've taken a picture of the profile screen at some point, because the linked tools will allow you to recreate a profile and save it to a new SRAM chip so that you can load it in-game and continue where you left off! Let's assume that I wanted to recreate the profile shown in the below photo.It's not the best photo, but we can see everything we need! I'm going to create a new SRAM file called "restored.sram" which I can later write to a new SRAM chip using the Grid SRAM Ardiuno shield and utilities linked above. However, if you already have an SRAM file that you've saved from a cabinet you can start with that file in order to edit an existing profile or create a new one there. I'll create that file by running "python3 editor.py restored.sram" in the "GridUtilities" folder of the Grid SRAM project that I've got downloaded and running on my computer. When I do that, I'm greeted with an empty profile screen and a bunch of options along the bottom. After hitting the "a" key to add a profile, I'm prompted with the following screen.Most of the fields are self-explanatory. The PIN is the number you type in on the keypad to get to your profile, so naturally it won't be shown anywhere on the screenshot! But most of the other fields are pretty easy to just copy from the screenshot and put into the various text boxes. You won't need to enter the win percentage as the game calculates that by your total plays and total wins. You also won't need to enter the characters unlocked as they are calculated based on the number of kills you have as well as how much money you've earned. However, when it comes to the tower progress and number of clears you can't just type in a number. You have to figure out where you were based on the color and number of levels highlighted in the screenshot you took. Here's where I'm at on the editor screen.First, take a look at the color of each of the level screens on the right side of your screenshot. The following table specifies what color you might see and what tower number to put into the "Tower Progress" section on the editor:- Green - 1
- Red - 2
- Light Blue - 3
- Pink - 4
- Yellow - 5
- Orange - 6
- Silver - 7
- Purple - 8
- Dark Blue - 9
- Black - 10
- Cleo - $200,000 earned. Type CLE (253) to select once unlocked.
- Kristy - 750 total kills. Type KRI (574) to select once unlocked.
- Darla - 3,000 total kills. Type DRL (375) to select once unlocked.
- The Host - 1,500 total kills. Type HOS (467) to select once unlocked.
- Trom - $1,500,000 earned. Type TRO (876) to select once unlocked.
- Cameraman - 10,000 total kills. Type CAM (226) to select once unlocked.
- Scorpion - $500,000 earned. Type SCO (726) to select once unlocked.
- Sub Zero - 4,500 total kills. Type SUB (782) to select once unlocked.
- Grid Man - $1,000,000 earned. Type GRI (474) to select once unlocked.
- The Lawyer - 7,500 total kills. Type LAW (529) to select once unlocked.
- Noob Saibot - 1 tower clear in single player. Type PNS (767) to select once unlocked.
- Red Dog - 3 tower clears in single player. Type RED (733) to select once unlocked.
Creating a Settings file For Monkey Ball
February 12th 2022, 8:26:30 am
The Naomi Settings Patcher I wrote about recently is pretty cool, but it is ultimately useless without settings definition files. I designed the format to be human-readable and easy to create but somebody still has to go about creating the files themselves! When I created the settings definition file for Monkey Ball I took notes so that I could write up a blog post on my methods for figuring out various settings in an EEPROM image. Note that you can see the finished settings definition file for Monkey Ball here: https://github.com/DragonMinded/netboot/blob/trunk/naomi/settings/definitions/BDF0.settings. The tools I refer to across this blog post are available here: https://github.com/DragonMinded/netboot/. With all that that out of the way, lets get started figuring out the settings for Monkey Ball!First, I made sure my BIOS, region and controls were all correct in demul. I also deleted thedummy.sram
and dummy.eeprom
files in the nvram folder just in case. This meant that I would be starting off completely fresh. Next, I loaded up the net boot ROM for Monkey Ball using the "Naomi/Naomi2 Boot" option in demul and waited for the game to finish loading completely. Once it showed the attract screen I completely closed demul (it doesn't seem to write the eeprom files until its closed) and then I took the "dummy.eeprom" file out of the nvram folder and ran it through eeprominfo
to get this output:$ ./eeprominfo dummy.eepromOkay, so the game serial is BDF0 and the length of the game settings is 32 bytes. That's enough to start a settings file. I created a new file using
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 00 00 00 00 00 ff 00 00 00 80 00 00 00 00 00 00 00 ff 00 00 00 80 00 00
eeprominfo
named "BDF0.settings" with the following command:$ ./eeprominfo --generate-default-settings-file dummy.eepromThen I opened it up to look at what was generated:
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 00 00 00 00 00 ff 00 00 00 80 00 00 00 00 00 00 00 ff 00 00 00 80 00 00
Settings file BDF0.settings created with defaults from EEPROM!
$ vim naomi/settings/definitions/BDF0.settings
The eeprominfo
utility generated a read-only setting for each byte in the EEPROM that it found:Setting00: byte, read-only, default is 00If you compare the bytes, you can see that all it did was make a single-byte setting for each byte in the eeprom and write down the defaults. It also set them all to read-only since I didn't know what any of them did yet. To test that I got the defaults right, I ran the following:
Setting01: byte, read-only, default is 03
Setting02: byte, read-only, default is 00
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
Setting08: byte, read-only, default is 00
Setting09: byte, read-only, default is 00
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
Setting12: byte, read-only, default is 00
Setting13: byte, read-only, default is ff
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
Setting16: byte, read-only, default is 00
Setting17: byte, read-only, default is 80
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
Setting20: byte, read-only, default is 00
Setting21: byte, read-only, default is 00
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
Setting24: byte, read-only, default is 00
Setting25: byte, read-only, default is ff
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
Setting28: byte, read-only, default is 00
Setting29: byte, read-only, default is 80
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
./edit_settings mbdefault.eeprom --serial BDF0
Its important to note that the "mbdefault.eeprom" file did not exist when I ran this command, which is why I gave it the "--serial BDF0" argument. I made no changes and exited out, saying yes to write the EEPROM image and then I compared the newly written "mbdefault.eeprom" against "dummy.eeprom". They were exactly the same so I knew that edit_settings
created the file properly.Now it was time to start changing settings and seeing what happened to the EEPROM file. I could try to reverse-engineer the test code for Monkey Ball, but that would take a long time and its not something that very many people can do. I booted up demul again, immediately going into the test menu right when I saw the Naomi splash screen and headed to game test mode. There's three sections that looked like they were persistent: game assignments which is probably stored in the eeprom, joystick calibration which is also probably stored in the eeprom, and bookkeeping which might be stored in the eeprom or sram. I didn't really care about how the bookkeeping was stored, so I made a note to myself to just ignore it. I headed to game assignments first and looked at what was available:Then I started messing with one of the settings. Just looking at the eeprom data, I was already betting that the second byte "03" was the number of monkeys since it matched the number on the screen. So I changed that one first. Cycling through it, I saw that it could be set to the values 2 through 5. So I set the number of monkeys to 5, exited back to system test so that it would write the EEPROM, exited demul and ran eeprominfo again:$ ./eeprominfo dummy.eepromAs expected, the second byte changed to 05 and the rest were the same as before. So that's for sure the "Number of Monkeys" setting. I updated "Setting1" to this instead:
Serial: BDF0
Game Settings Hex: 00 05 00 00 00 00 00 00 00 00 00 00 00 ff 00 00 00 80 00 00 00 00 00 00 00 ff 00 00 00 80 00 00
Number of Monkeys: byte, default is 03, values are 2 to 5
Then I tested that it was right by doing the following:$ ./eeprominfo dummy.eeprom --display-parsed-settingsIt looked good to me, so I tackled the other two settings by doing the exact same thing, narrowing down the byte that each setting used and updating the definitions file accordingly when I figured each one out. Here's what I came up with:
Serial: BDF0
Game Settings Hex: 00 05 00 00 00 00 00 00 00 00 00 00 00 ff 00 00 00 80 00 00 00 00 00 00 00 ff 00 00 00 80 00 00
Parsed Game Settings:
Number of Monkeys: 5
Game Difficulty: byte, default is 00, 0 - Normal, 1 - HardNow it was on to the calibration screen. I did the same thing on that screen as I did for game assignments: took a screenshot of the defaults then saved them and exited demul. This is what I got when I ran the eeprominfo utility:
Number of Monkeys: byte, default is 03, values are 2 to 5
Ball Velocity Boost: byte, default is 00, 0 - Off, 1 - On
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
Setting08: byte, read-only, default is 00
Setting09: byte, read-only, default is 00
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
Setting12: byte, read-only, default is 00
Setting13: byte, read-only, default is ff
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
Setting16: byte, read-only, default is 00
Setting17: byte, read-only, default is 80
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
Setting20: byte, read-only, default is 00
Setting21: byte, read-only, default is 00
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
Setting24: byte, read-only, default is 00
Setting25: byte, read-only, default is ff
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
Setting28: byte, read-only, default is 00
Setting29: byte, read-only, default is 80
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
$ ./eeprominfo dummy.eepromAnd here's what the settings looked like before I saved:I could see the
Serial: BDF0
Game Settings Hex: 01 05 01 00 00 00 00 00 00 60 00 00 00 a0 00 00 80 7f 00 00 00 60 00 00 00 a0 00 00 80 7f 00 00
60
and a0
values that were displayed on the screen in a few places of the eeprom so I was willing to bet those were the calibration options. At this point I decided to try something a little bit different from the first section to see if I could narrow each setting down faster. I added the settings to the settings definition file "BDF0.settings", set them to four different unique values using edit_settings
and then copied "dummy.eeprom" back into the demul nvram folder. What I was hoping would happen is that the values I set would show up on the screen, allowing me to correlate the settings.Here's how I set the settings file to do this:Game Difficulty: byte, default is 00, 0 - Normal, 1 - HardThen I ran:
Number of Monkeys: byte, default is 03, values are 2 to 5
Ball Velocity Boost: byte, default is 00, 0 - Off, 1 - On
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
Setting08: byte, read-only, default is 00
Setting09: byte, default is 60, values are 00 to ff in hex
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
Setting12: byte, read-only, default is 00
Setting13: byte, default is a0, values are 00 to ff in hex
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
Setting16: byte, read-only, default is 00
Setting17: byte, read-only, default is 80
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
Setting20: byte, read-only, default is 00
Setting21: byte, default is 60, values are 00 to ff in hex
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
Setting24: byte, read-only, default is 00
Setting25: byte, default is a0, values are 00 to ff in hex
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
Setting28: byte, read-only, default is 00
Setting29: byte, read-only, default is 80
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
./edit_settings dummy.eeprom
And after setting the values I copied the eeprom back to demul and re-started it. When I loaded game settings, nothing was changed though... Hmmmm. Maybe I was missing something? But wait, when I went into joystick calibration, changed nothing and chose the "exit with save" option, it didn't just change the 4 bytes that I messed with. It also messed with several other bytes as well. And when the game wrote defaults way back at the top, it didn't write 60
and a0
for the spots I thought were calibration, it wrote 00
and ff
. I figured those other bytes that changed were related as well. So I changed the definition file to this:Game Difficulty: byte, default is 00, 0 - Normal, 1 - HardStill no dice. The calibration screen only showed the defaults. That's a bummer. At this point I decided to just configure some values on the configuration screen in demul and then print out what the eeprom looked like after calibrating:
Number of Monkeys: byte, default is 03, values are 2 to 5
Ball Velocity Boost: byte, default is 00, 0 - Off, 1 - On
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
Setting08: byte, read-only, default is 00
Setting09: byte, default is 60, values are 00 to ff in hex
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
Setting12: byte, read-only, default is 00
Setting13: byte, default is a0, values are 00 to ff in hex
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
Setting16: byte, read-only, default is 80
Setting17: byte, read-only, default is 7f
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
Setting20: byte, read-only, default is 00
Setting21: byte, default is 60, values are 00 to ff in hex
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
Setting24: byte, read-only, default is 00
Setting25: byte, default is a0, values are 00 to ff in hex
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
Setting28: byte, read-only, default is 80
Setting29: byte, read-only, default is 7f
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
$ ./eeprominfo dummy.eeprom --displaySo It looked like my guesses were sorta right. I went back in with a fresh EEPROM, ran the calibration one more time and set some unique values for each setting.After saving that exact calibration, I printed out the eeprom to see what values were there. It looked like I was actually right about the calibration values, but there was more going on:
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 39 39 00 00 ff fe 00 00 80 7f 00 00 00 60 00 00 ff fe 00 00 80 7f 00 00
Parsed Game Settings:
Game Difficulty: Normal
Number of Monkeys: 3
Ball Velocity Boost: Off
Setting09: 39
Setting13: fe
Setting21: 60
Setting25: fe
$ ./eeprominfo dummy.eeprom --displayIt was then that I realized the 7f values were pretty suspicious. They were the same as our Now H/Now V values on the calibration screen. I bet those were the joystick center values. For some reason, the game was storing the low values twice, the center values twice (with the first value equal to the second value plus 1), and the high values twice (with the first value equal to the second value plus 1). I can work with that.I started with the following settings modifications:
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 26 26 00 00 e6 e5 00 00 80 7f 00 00 59 59 00 00 b9 b8 00 00 80 7f 00 00
Parsed Game Settings:
Game Difficulty: Normal
Number of Monkeys: 3
Ball Velocity Boost: Off
Setting09: 26
Setting13: e5
Setting21: 59
Setting25: b8
Game Difficulty: byte, default is 00, 0 - Normal, 1 - HardPrinting out the eeprom I had gave me this:
Number of Monkeys: byte, default is 03, values are 2 to 5
Ball Velocity Boost: byte, default is 00, 0 - Off, 1 - On
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
Setting08: byte, read-only, default is 00
Left: byte, default is 60, values are 00 to ff in hex
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
Setting12: byte, read-only, default is 00
Right: byte, default is a0, values are 00 to ff in hex
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
Setting16: byte, read-only, default is 80
Horizontal Center (Now H): byte, default is 7f, values are 00 to ff in hex
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
Setting20: byte, read-only, default is 00
Push: byte, default is 60, values are 00 to ff in hex
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
Setting24: byte, read-only, default is 00
Pull: byte, default is a0, values are 00 to ff in hex
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
Setting28: byte, read-only, default is 80
Vertical Center (Now V): byte, default is 7f, values are 00 to ff in hex
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
$ ./eeprominfo dummy.eeprom --displayOkay, so I knew that these were the right settings but I still had to handle the other bytes. In situations like this, you can do a fancy trick in "BDF0.settings" where you state that a setting default is dependent on another setting's value. Here's what my settings definition looked like after making that change:
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 26 26 00 00 e6 e5 00 00 80 7f 00 00 59 59 00 00 b9 b8 00 00 80 7f 00 00
Parsed Game Settings:
Game Difficulty: Normal
Number of Monkeys: 3
Ball Velocity Boost: Off
Left: 26
Right: e5
Horizontal Center (Now H): 7f
Push: 59
Pull: b8
Vertical Center (Now V): 7f
Game Difficulty: byte, default is 00, 0 - Normal, 1 - HardIn the process of figuring all this out I also realized that the game does not display the current calibration on the screen. It always resets back to
Number of Monkeys: byte, default is 03, values are 2 to 5
Ball Velocity Boost: byte, default is 00, 0 - Off, 1 - On
Setting03: byte, read-only, default is 00
Setting04: byte, read-only, default is 00
Setting05: byte, read-only, default is 00
Setting06: byte, read-only, default is 00
Setting07: byte, read-only, default is 00
LeftUnknown: byte, read-only, default is value of Left
Left: byte, default is 60, values are 00 to ff in hex
Setting10: byte, read-only, default is 00
Setting11: byte, read-only, default is 00
RightUnknown: byte, read-only, default is value of Right + 1
Right: byte, default is a0, values are 00 to ff in hex
Setting14: byte, read-only, default is 00
Setting15: byte, read-only, default is 00
HCenterUnknown: byte, read-only, default is value of Horizontal Center (Now H) + 1
Horizontal Center (Now H): byte, default is 7f, values are 00 to ff in hex
Setting18: byte, read-only, default is 00
Setting19: byte, read-only, default is 00
PushUnknown: byte, read-only, default is value of Push
Push: byte, default is 60, values are 00 to ff in hex
Setting22: byte, read-only, default is 00
Setting23: byte, read-only, default is 00
PullUnknown: byte, read-only, default is value of Pull + 1
Pull: byte, default is a0, values are 00 to ff in hex
Setting26: byte, read-only, default is 00
Setting27: byte, read-only, default is 00
VCenterUnknown: byte, read-only, default is value of Vertical Center (Now V) + 1
Vertical Center (Now V): byte, default is 7f, values are 00 to ff in hex
Setting30: byte, read-only, default is 00
Setting31: byte, read-only, default is 00
60
/a0
for both left/right and push/pull if you exit the calibration and re-enter it. So my strategy of setting values and seeing what happened would never have worked for Monkey Ball. I figured that out by running the calibration, saving and exiting and immediately re-entering the screen at which point the calibration values were back to their defaults again. However, it lead me down the path of figuring out that there were other settings bytes that mattered. Now that I had this all figured out, I tested it by going into demul, setting the values for the calibration to some random values, then making a copy of the eeprom. I edited the copy and changed some of the calibrations, and then looked at the output of eeprominfo:$ ./eeprominfo dummy.eeprom --displayCool, its adjusting those other bytes the way it should! Finally, I set the values back to what they were before using
Serial: BDF0
Game Settings Hex: 00 03 00 00 00 00 00 00 39 39 00 00 ff fe 00 00 80 7f 00 00 00 00 00 00 ff fe 00 00 80 7f 00 00
Parsed Game Settings:
Game Difficulty: Normal
Number of Monkeys: 3
Ball Velocity Boost: Off
Left: 39
Right: fe
Horizontal Center (Now H): 7f
Push: 00
Pull: fe
Vertical Center (Now V): 7f
edit_settings
and then compared it to the copy of the EEPROM file that I made above. Everything matched byte-for-byte which meant the settings definition was adjusting the correct bytes in the correct manner!And that's it! After going through all that, I had a settings editor for Monkey Ball:Keep in mind that Monkey Ball ended up being a bit difficult to figure out due to the way the calibration screen worked. When I did Marvel Vs. Capcom 2, Crazy Taxi and Jambo Safari it was much more straightforward as they used a much more simple layout for their EEPROM settings. It should be pretty easy to figure out settings for other games if you are motivated to change settings for your favorite game. If you come up with any new EEPROM settings files, feel free to send them to me to be included in the github repo!