Hello! I would like to take the time to talk about how I have been able to get Crowd Control working on an actual hardware NES console! Something which still feels like magic every time I sit in front of a console and see effects coming through.
The story starts with the advent of the N8 Pro flash cart which introduced a USB port onto the hardware for the first time. At one GDQ event, megmactv was showing off how she had been leveraging this new feature to create cooperative gameplay in Zelda 1 by sending messages through the USB port. She shared with me her code for this which got us started in making this possible. And although the patch I ended up with has been reworked and extended immensely I feel it very important to credit her with getting this off of the ground!
Developing a Communication Format
One of the first things we needed to do is try to figure out a universal system for trying to communicate information in and out of an NES game to the Crowd Control system and what we came up with was a simple variable byte command system which can perform a handful of simple tasks like reading or writing from specific addresses in the game’s memory.
A command is sent over the USB with an increasing numerical ID for each message to ensure that commands are executed in order and verified to complete, an action ID indicating which type of action to carry out, and then a variable number of options and values depending on the specific command.
For example, the single address read command that I came up with is formatted like this:
NN A1A1 A2A2...
NN - Number of addresses to read
AXAX- repeated for each address, address to read
Returns: V1 V2 ...
All values read in order
So for example if you wanted to request the values in memory at addresses
$066D, and you happened to be up to message ID 23 you would send to the game this message:
MSG ACT NUM ADDR1 ADDR2 ADDR3
23 00 03 12 00 6F 06 6D 06
i.e. the data 23000312006F066D06
The full format I created for the current system is available here if you are interested: Comm Format
Getting Data in and out of an NES game via Assembly
The second part of the process is working out how to get the games to process and respond to these messages.
Although it makes the process a bit more difficult the best way we had to do this was to manually patch the game’s code to insert routines to communicate with Crowd Control and ferry data in and out of the game as it runs.
Step one is getting a routine that runs between every frame during blanking periods to handle the hardware communication over the USB port. Depending on the game this is either done by taking over the NMI vector or by inserting a jump into the original NMI routine.
;;; rewrite nmi pointer to be the comm function.
#org $fffa, $2000a, 2
This function’s sole purpose is to check the USB port for incoming messages, dump a message into RAM for later processing, and then mark a trigger indicating that a message is ready to be handled.
The actual processing of the messages is handled via another hijack inserted into the game’s main routine so as not to overflow the blanking period. This routine just runs every frame and checks the flag for a message being ready and then jumps to another command processing routine inserted into free space in the game.
#org $DA00, $1DA10
Finally, the communication function checks the action ID of the incoming message and uses a jump table to send that off to a handful of routines that take the requested reads or writes, modifies the values in memory, and then sends a message with the requested values for read actions.
;jump table for actions
#byte Reads, ArrayReads
#byte Writes, ArrayWrites
#byte Freeze, CommReturn
Handling Freezing and Continuous Actions
The last major piece of the puzzle was figuring out how todo things like freezing a memory address to a specific value.
This starts with creating a table (usually in the game’s SRAM) for storing a list of addresses which need to be continuously checked or altered.
;;; SRAM Table for Freezes
;;; Format - FF AA AA VV MM SS WW CA CA CV CS CT XX XX XX XX
; 0 FF - Used flag/fingerprint - AB indicates valid in-use slot
; 1-2 AA - Address
; 3 VV - Value
; 4 MM - Mask
; 5 SS - Size
; 6 WW - Write Type
; 7-8 CA - Compare Address
; 9 CV - Compare Value
; A CS - Compare Size
; B CT - Compare Type
; C-F XX - Unused/Expansion
Then, in our routine that is already running once per frame inside of the main game logic, we add another call to a function that scans the freeze table for active actions and handles checking for writes that need to be carried out using different kinds of comparison options. The system supports a variety of ways of adding conditions to freezing like masking to only manipulate certain bits or only modifying the value if it’s greater than another value.
;public enum CompareType : byte
; Never = 0x00,
; Equal = 0x01,
; NotEqual = 0x02,//6,A,C,E
; Always = 0x03,//7,B,D,F
; GreaterThan = 0x04,
; GreaterThanOrEqual = 0x05,
; LessThan = 0x08,
; LessThanOrEqual = 0x09,
; NotLessThan = 0x05,
; NotGreaterThan = 0x09,
; MaskSet = 0x11,
; MaskUnset = 0x12,
Putting it all Together
With all of these pieces put together it’s mostly just a matter of taking the overall patch I have developed and working out for each individual game how to integrate it. Finding unused RAM to store the message buffer and flags and the table of freeze commands, finding free space to stick all of the code needed for the patch, locating hijack locations to call each of our functions, etc.
If you’d like to see all of the code used to make this happen, I have included the entire patch for NES hardware support for Punch Out here! Comm Patch
Thank you so much for taking the time to read over this, if you have any questions, suggestions, or just want to talk you can reach me via Discord: dtothefourth#4444
Crowd Control is the app that lets your viewers interact with the games you play on stream. Crowd Control supports +100 games and has been installed by over 70,000 live creators.
Use Crowd Control on your next stream by visiting crowdcontrol.live
Have any questions, need help with Crowd Control or just want to hang? Join our Discord