[ Download | News | Screenshots | Source | API | FAQ ]
[ Introduction | Download | Quick Start | Details | Remote API ]
The API provides programmatic control of the emulator at a very granular level. It supports direct access to CPU/PPU/APU Memory and registers, to controller input and to save states. And, it offers a multitude of listeners that enable programs to tap into emulation events.
Programs written in Java can be loaded directly into the emulator for execution. Programs written other languages can control the emulator through the Remote API, which uses an internal socket connection for interprocess communication.
The .zip contains ports of the API and the Quick Start examples in the following languages: C, C#, Java, Lua and Python. It also includes a copy of the API Javadoc. Since all the functions and their associated parameters were ported across the languages, it should be possible to refer to the Javadoc regardless of which API conversion is used.
This quick start guide demonstrates how Nintaco can be programmatically controlled. The obligatory "Hello, World!" example displays its message center-framed and on top of the currently running game. It also animates a ball that bounces around the screen. To see it in action:
When you're done, press Stop.
The Tetris Bot is an AI that plays tetris. Try it out by:
Want to see it play faster? Press the Stop button. In the Arguments field, enter one word: fast. Then, press Run. Press Start from the A-TYPE menu to hand control to the faster AI.
Want to give the AI a more difficult challenge? Press the Stop button mid-play. Let a bunch of pieces drop to grow the stack. Then, press Run again to see if the AI can clean up the mess.
Alternatively, from the GAME TYPE menu, select B-TYPE. From the B-TYPE menu, select LEVEL 9 and HEIGHT 5, the most difficult combination. Press Start to see if the AI is up to the task. The Tetris Bot actually provides a convenient way to view all the B-TYPE endings.
The following steps demonstrate how to programmatically control Nintaco from an externally running program:
java -jar HelloWorld.jar
If an internal socket connection was successfully established between the example program and Nintaco, then you should see "Hello, World!" and a bouncing ball displayed above the running game.
To disconnect, press Stop Server and then break the example program (Ctrl+C). If you break the external program without stopping the server, the emulator may freeze; however, the Stop Server button will restore it.
Programs developed to control the emulator are written as if they were standalone applications; the entrypoint of the program is the main() method. To load the program into the emulator, it must be compiled and bundled into a JAR. From the Run Program dialog, enter the path to the JAR file or press the Find JAR button to browse to it. The Load JAR button scans the JAR for classes containing main() and it lists them in a combo box. If the JAR manifest specifies the application's entrypoint, the associated class will appear first in the list. If the JAR is modified during development, press the Load JAR button again to import the changes. To run the JAR, select the main class, enter any arguments to pass to main(), and press the Run button. The standard output and error streams are redirected to a text area within the Run Program dialog. To terminate the program, press Stop.
As far as a program is concerned, the API is a singleton. Classes interested in using the API can declare an object constant via the following code to make the reference available to all methods.
private final API api = ApiSource.getAPI();
The API starts out as disabled and while disabled only the add/remove listener methods work. After adding listeners, a program calls run() to activate the API; it signals that everything is setup and the program is ready to receive events. Listeners are cached and they rarely need to be removed. In the event that the API is temporarily disabled, listeners do not need to be re-added. They are automatically removed on program shutdown. And, most of the API methods that modify internal states do not have the side effect of triggering listeners. For example, a program that receives events when a region of CPU memory is updated can modify the same region from the event listener without creating infinite recursion.
The easiest way for a program to do something once-per-frame is within a FrameListener, which is called back immediately after a full frame was rendered, but just before the frame is displayed to the user. ScanlineListener works in a similar way, but it is invoked after a specified scanline was rendered. ScanlineCycleListener takes that one step further and responds to a specified dot. A program can manipulate controller input from a ControllersListener, which is called back immediately after the controllers were probed for data, but just before the probed data is exposed to the machine. AccessPointListener is triggered by a specified CPU Memory read or write, or instruction execution point and SpriteZeroListener is triggered by sprite zero hits. Finally, ActivateListener, DeactivateListener, StatusListener and StopListener respond to API enabled events, API disabled events, status message events and Stop button events, respectively.
The Program API is single-threaded. After the main() thread calls API.run(), the only thread that can safely invoke API methods is the one that executes the listeners. While a listener is executing, the emulator is effectively frozen; listeners need to return in a timely manner to avoid slowing down emulation. Programs can spawn off additional threads to perform parallel computations; however, the results of those computations should be exposed to and used from the listeners to act on the API.
The Remote API is exposed from the Start Program Server dialog, which accepts a local hostname and port to listen on. The Remote API provides a way for programs written in other languages to control the emulator. It uses a socket connection for interprocess communication enabling external programs to run on the same box as the emulator or a completely different machine.
Since C programs cannot contain multiple entry points, hello_world.c and tetris_bot.c must be compiled independently.
gcc hello_world.c nintaco.c -o hello_world.exe ./hello_world.exe
Some C API functions dynamically allocate strings; a char * returned by an API function must be freed.
From the Visual Studio Developer Command Prompt, execute the following commands:
csc HelloWorld.cs NintacoAPI.cs HelloWorld
In the main() method, add a line similar to the following.
initRemoteAPI() has no effect when the program is loaded directly into the emulator. In other words, a program containing the line above will function both locally and remotely.
In the Remote API, run() never returns. Instead, it enters an infinite loop that maintains the network connection. Add a StatusListener to monitor the loop.
This port requires the LuaSocket extension library.
lua -l socket hello_world.lua nintaco.lua
Navigate to the directory containing nintaco.py.
Windows users should use:
set PYTHONPATH=. python hello_world/hello_world.py nintaco.py
Unix users should use:
export PYTHONPATH=. python hello_world/hello_world.py nintaco.py
Copyright © 2018 nintaco.com
Nintaco is free software; you can redistribute it and/or modify it under the terms of LGPLv2.1.