What Can Romulus Do?

As “Romulus,” my NES Level Editor Framework, nears a usable state, I’ve decided it would be a good idea to give a summary of what it actually does. Romulus will be available as a .NET DLL, and will be usable from any .NET language. There is a thread for discussing Romulus on RHDN.

Romulus actually fills two roles: it acts as a general purpose library for NES level editors, and it also defines the plug-in architecture used by its sister project, SuiteNES. Here, I’m addressing Romulus in terms of a level editor library.

What It Does

Romulus addresses a few different aspects of making a level editor. One central aspect is accessing ROM data. Romulus helps with pointers and pointer tables, and loads “patterns” (i.e. tiles), palettes, and even (simple) custom data structures.

Another feature is that Romulus can draw images using graphic data from a game. The drawing classes are intended to mimic NES graphic hardware. This makes it simpler to render data and graphics that were designed for NES hardware.

Extending on the graphic rendering capabilities, Romulus includes WinForms controls that can be used for the actual level editor UI. There’s a ScreenControl that shows a single screen at a time, and a ScreenViewport control that can show and scroll around arbitrarily large game areas (give Editroid a try to see it in action).

Romulus also aids in the creation of an undo system. The Action and ActionQueue classes act as a foundation, defining a simple object model for actions that can be undone and redone. There is also a class to “dispatch” actions to let different parts of the UI know when they need to update themselves in response to said actions. Romulus can even populate the drop-down menus for undo/redo buttons in a ToolStrip.

Dealing With Data

The Rom class is the core of data access. It loads ROM images from disk or memory and provides functions to access that data. A big issue Romulus addresses is pointers. While pointers may be sparse in some games, others define all their data in a large maze of pointers.

Romulus knows two kinds of pointers. ROM offsets refer to a location within the ROM image. These are represented by the pHRom type (pointer to headered ROM).  CPU pointers refer to a memory address and are represented by the pCpu type. These CPU pointers often refer to one or more ROM offsets, depending on what ROM data might be banked in. Resolving these pointers can range from inconvenient to miserable. To simplify things, Romulus has classes that simulate NES mappers. By using pointers in the same manner as NES hardware, these Mapper objects make conversion between pointers and ROM offsets painless, and in some cases can even eliminate the need to do this conversion at all, providing banked access to ROM data. And if an NES mapper is not supported out of the box, you can write a new Mapper class that will work with Romulus. (Currently MMC1, MMC3, and CxROM are supported, though other mappers may be similar enough that these can be used in their place).

pCpu dataAddress = (pCpu)0xC024;
// Get byte at $C024. The actual ROM offset depends
// on what bank is loaded.
byte value = mapper[dataAddress];

For example, a level editor might create a separate Mapper object for each level. That Mapper references the ROM banks that contain the level’s data. This way the same code can be used to traverse pointer trees in different levels without the need to consider what bank the level’s data is in. The code simply obtains the level’s Mapper object.

Pointers are also dealt with in a type-safe manner. Instead of simply storing every pointer as an integer, there are separate types to represent different kinds of pointers. All can be cast back and forth from integers. The type safety means, for instance, that you will never accidentally use a CPU address where you should use a ROM offset.

Romulus can load other common, basic data structures. The PatternTable class loads graphic tiles. The CompositePalette and SimplePalette classes load 16- and 4-color palettes respectively. You can even load custom data structures (note that reference types and arrays are not supported):

// Sample 3-byte structure that describes an enemy within a level
struct EnemyDat {
    ByteYX ScreenPosition; // This structure is defined by Romulus
    byte enemyParams;
    byte enemyType;
}

// Sample usage
var dataOffset = (pHRom)0x1234; // ROM file offset
// Load data for an enemy
EnemyDat pieceOfData = MyRom.GetObject<EnemyDat>(dataOffset);

Drawing/UI

Romulus can use game data to draw to a bitmap, but Romulus’ graphic capabilities are best used with the included WinForms controls. Romulus includes a ScreenControl class, which shows a single game screen. Tell it what tile set to use, what palette to use, and which tiles go where, and out comes an image of the game screen. As with the actual NES, the background is made of a grid of tiles, and sprites are drawn on top. Since Romulus already provides a mechanism to load graphics and palettes, there’s usually not much work involved in getting something on the screen.

The ScreenViewport is a more advanced control that allows scrolling around large game areas. It works by splitting the game area up into screens, each represented by a ScreenView. The ScreenView class is very much like the ScreenControl, and beyond that little is needed to get a ScreenViewport up and running, so it usually isn’t much more complicated than the ScreenControl.

Romulus also includes a PatternTableControl that shows a pattern table and allows the user to select a pattern. This can be used for UI to edit a game’s building blocks’ composition, title screens, and more. There’s a PaletteDialog that can be used to select a color from the NES palette. There will eventually be a hex editor (not yet complete), and more controls are likely to come.

The Undo System

This is the most complicated part of Romulus to use, but an undo system is a very useful tool. The undo system is composed of the Action class, which represents edits made to a ROM, and the ActionQueue class, which manages the actions, including the list of what can be undone/redone.

The undo system is implemented by creating a derived ActionQueue class and a derived Action class (or, more typically, a type hierarchy of Action classes representing different kinds of edits). The Action class is responsible for performing any actual edits.

Generally, the UI will update itself in response to Actions being performed. For instance, if the user changes a block in a level, without an undo system the typical approach would be to directly modify the ROM data and immediately redraw part of the level. With the undo system, the approach is to create an Action object and pass it to the action queue, along the lines of:

void UpdateBlock(int x, int y, byte newValue) {
    var action = new UpdateBlockAction(x, y, newValue);
    EditorActionQueue.Do(action);
    // Note that we don't update the UI here.
}

The hypothetical UpdateBlockAction class implements the behavior for modifying a level block. (It should additionally look up the original value so that it can restore it if the user undoes the action.) The code doesn’t update the UI. Instead, the UI must listen for actions and update itself accordingly. When an UpdateBlockAction is performed, the UI can see which part of the level is affected by looking at the properties of the UpdateBlockAction object, and redraw itself as needed.

To make it simpler to listen for actions there is the ActionDispatcher class. Each element of the UI can create an ActionDispatcher that filters out those actions that are not relevant. The ActionDispatcher is attached to the ActionQueue, and an event is raised each time a relevant action is done or undone.

There is also an UndoMenuManager class that can automatically populate undo/redo menus in any ToolStripDropDown (namely a MenuStrip menu, or a ToolStripSplitButton or ToolStripDropDownButton).

Ta-Da

That’s about it. There’s nothing magical about Romulus. It probably won’t do anything for you that you couldn’t do yourself. It just aims to be a polished, straightforward, simple-yet-useful library, there to relieve you of menial work and to help structure the different facets of an editor. There will probably be more articles to come on specific areas of Romulus. As of yet there hasn’t been a public release, but if you’re interested in trying it out, leave a comment or pop in on RHDN.

Leave a Reply

Your email address will not be published.


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">