1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474
|
ATARI800 AS A LIBRARY
=====================
Libatari800 is a build target of the atari800 emulator that produces a static
library suitable for embedding in other programs. See the INSTALL file for
complete build instructions, but in most cases it can be compiled from the top
level source directory using:
./configure --target=libatari800
make
This target requires no external libraries and is platform independent. It is
also not useful by itself; instead it is designed for developers to embed the
emulator into another program.
Two sample programs are also compiled (but not installed) that demonstrate the
usage of the library: guess_settings and libatari800_test.
Using libatari800 to guess emulator settings
--------------------------------------------
The demo program guess_settings (source in util/guess_settings.c) attempts to
guess the major emulator settings (parameters like machine type, operating
system type, and PAL/NTSC) by running the emulator as fast as possible. If a
failure is detected (like hitting a BRK instruction, crashing the computer, or
entering the memo pad/self-test mode) the failure is noted and the next
permutation is checked.
If the source image is a cartridge and the cartridge type is unknown, all
cartridge types that match the source size are checked in turn. This can
greatly increase the number of permutations.
A success is determined by the absence of failure conditions in a specified
number of frames (default of 1000).
The program is built automatically (but not installed) when the compile target
is libatari800. It is built in the src directory and can be run from there
with:
src/guess_settings [options] [name_of_image]
For example:
$ src/guess_settings test.xex
test.xex: 400/800 NTSC OS/B status: OK through 1000 frames
test.xex: 400/800 PAL OS/B status: OK through 1000 frames
test.xex: 400/800 NTSC Altirra status: FAIL (CPU crash)
test.xex: 400/800 PAL Altirra status: FAIL (CPU crash)
test.xex: 64k XL NTSC XL ROM status: FAIL (CPU crash)
test.xex: 64k XL PAL XL ROM status: FAIL (CPU crash)
test.xex: 64k XL NTSC Altirra status: FAIL (CPU crash)
test.xex: 64k XL PAL Altirra status: FAIL (CPU crash)
test.xex: 128k XE NTSC XL ROM status: FAIL (CPU crash)
test.xex: 128k XE PAL XL ROM status: FAIL (CPU crash)
test.xex: 128k XE NTSC Altirra status: FAIL (CPU crash)
test.xex: 128k XE PAL Altirra status: FAIL (CPU crash)
test.xex: 5200 NTSC Atari status: FAIL (CPU crash)
test.xex: 5200 NTSC Altirra status: FAIL (CPU crash)
Here's an example with an 8k cartridge image, using additional command line
arguments to limit the testing to only NTSC XL machines:
$ src/guess_settings -xl -ntsc test.rom
test.rom: 64k XL NTSC XL ROM status: OK through 1000 frames (cart=1 'Standard 8 KB')
test.rom: 64k XL NTSC XL ROM status: FAIL (self test) (cart=21 'Right slot 8 KB')
test.rom: 64k XL NTSC XL ROM status: OK through 1000 frames (cart=39 'Phoenix 8 KB')
test.rom: 64k XL NTSC XL ROM status: FAIL (self test) (cart=44 'OSS 8 KB')
test.rom: 64k XL NTSC XL ROM status: FAIL (BRK instruction) (cart=53 'Low bank 8 KB')
test.rom: 64k XL NTSC XL ROM status: FAIL (unidentified cartridge)
test.rom: 64k XL NTSC Altirra status: OK through 1000 frames (cart=1 'Standard 8 KB')
test.rom: 64k XL NTSC Altirra status: FAIL (self test) (cart=21 'Right slot 8 KB')
test.rom: 64k XL NTSC Altirra status: OK through 1000 frames (cart=39 'Phoenix 8 KB')
test.rom: 64k XL NTSC Altirra status: FAIL (self test) (cart=44 'OSS 8 KB')
test.rom: 64k XL NTSC Altirra status: FAIL (self test) (cart=53 'Low bank 8 KB')
test.rom: 64k XL NTSC Altirra status: FAIL (unidentified cartridge)
Using libatari800 to generate video frames
------------------------------------------
The test program libatari800_test (source in util/libatari800_test.c) is also
built but not installed with the libatari800 compile target. It can be run
using:
src/libatari800_test
It is a very simple example. It boots the emulator into Memo Pad, and after 100
frames of emulation, simulates the 'A' key being pressed and held down to print
a bunch of "A" characters on the screen. A simple representation of the screen
is displayed as text output to the terminal.
LIBRARY OVERVIEW
================
The basic operation of the library is to generate one video frame of emulation,
which is equivalent to about 30,000 machine cycles on a 60 Hz NTSC system. The
library provides access to the raw screen buffer and other internals like the
user memory, but produces no output itself. Any video display, sound output, or
for that matter any user interface is left up to the calling program.
The library will calculate each video frame as fast as it can, with no delay
and no synchronization to any 50/60 Hz signal at all. It is up to the user
program to delay the processing of the next frame if real-time operation is
desired.
This also means that each frame can be calculated much much faster than the
real hardware, so for example: simulation tasks may be performed very quickly
and the results can be displayed only after all the processing has been
completed.
Usage
-----
The call to libatari800_next_frame is the basic interface to the emulator. It
accepts a pointer to a structure input_template_t, which describes user input
like the keyboard and joystick state. It performs one full frame's worth of
emulation, and returns control to the calling function.
The screen array and audio buffer are available at the end of every frame for
the calling function to present to the user.
The example program libatari800_test shows very simple usage of the library.
The basic usage to generate emulator frames is shown in this code snippet::
input_template_t input;
char *test_args[] = {
"-atari",
NULL,
};
libatari800_init(-1, test_args);
libatari800_clear_input_array(&input);
while (libatari800_get_frame_number() < 200) {
libatari800_next_frame(&input);
if (libatari800_get_frame_number() > 100) {
input.keychar = 'A';
}
}
libatari800_exit();
Note the usage of the test_args array that mimics command line arguments. In the
future, libatari800 may provide more direct specification of configuration
parameters, but this is not yet implemented.
So, currently, libatari800 relies on a valid configuration file to specify the
location of the ROM files. By default, libatari800 will scan in the same
directories as the normal atari800 program. To provide an alternate location,
add the "-config" argument and a path to the file, as in the following::
char *test_args[] = {
"-atari",
"-config",
"/path/to/.atari800.cfg",
NULL,
};
Advanced usage
--------------
In addition to the screen and audio, the entire internal state of the emulator
can be examined and even changed between frames. The state save format is used
to facilitate this, so it is even possible restore the state of the emulator
from save files generated from another atari800 instance.
State save files are difficult to parse as the size of the file (and indeed
offsets to elements within the file) can change from frame to frame depending
on the current state of the emulator or features currently being used. For
example, changing the number of disk drives in use or even changing the disk
image to point to a different file on the host file system will alter the size
of the state save file.
To simplify access and prevent the need for parsing the state save file, some
offsets into the state save file are created and saved into the statesav_tags_t
structure. Each entry in the structure points to the first byte in that section
of the state save buffer. For instance, access to the current program counter
can be found by:
unsigned char state[STATESAV_MAX_SIZE];
statesav_tags_t tags;
pc_state_t *pc;
libatari800_get_current_state(state, &tags);
pc = (pc_state_t *)&state.state[state.tags.pc];
printf("CPU PC=%04x\n", pc->PC);
Overview of source code changes
-------------------------------
The port-specific code lives in the src/libatari800 directory, but some changes
were needed in the main source code directory.
The libatari800 port differences include:
* the PNG and LIBZ libraries are deliberately excluded for maximal portability;
only the system math library is needed
* the system monitor is excluded, so any ability stop the 6502 other than at
the end of a frame is not available.
* the text-based UI to configure the emulator, select cartridges, debug with
the monitor, etc. is not included. Everything is left to the calling program.
* the crash menu is excluded
* some instrumentation of the state save file has been added
LIBRARY FUNCTIONS
=================
int libatari800_init (int argc, char ** argv)
Initialize emulator configuration
Sets emulator configuration using the supplied argument list. The arguments correspond to
command line arguments for the atari800 program, see its manual page for more information
on arguments and their functions.
Parameters
argc number of arguments in argv, or -1 if argv contains a NULL terminated list.
argv list of arguments.
Return values
FALSE if error in argument list
TRUE if successful
const char* libatari800_error_message ()
Get text description of latest error message
If the libatari800_next_frame return value indicates an error condition, this function
will return an error message suitable for display to the user.
Returns
text description of error
void libatari800_clear_input_array (input_template_t * input)
Clears input array structure
Clears any user input (keystrokes, joysticks, paddles) in the input array structure to
indicate that no user input is taking place.
This should be called to initialize the input array before the first call to
libatari800_next_frame.
Parameters
input pointer to input array structure
int libatari800_next_frame (input_template_t * input)
Perform one video frame's worth of emulation
This is the main driver for libatari800. This function runs the emulator for enough CPU
cycles to produce one video frame's worth of emulation. Results of the frame can be
retrieved using the libatari800_get_* functions.
Parameters
input input template structure defining the user input for the frame
Return values
0 successfully emulated frame
1 unidentified cartridge type
2 CPU crash
3 BRK instruction encountered
4 display list error
5 entered self-test mode
6 entered Memo Pad
7 encountered invalid escape opcode
int libatari800_mount_disk_image (int diskno, const char * filename, int readonly)
Use disk image in a disk drive
Insert a virtual floppy image into one of the emulated disk drives. Currently supported
formats include ATR, ATX, DCM, PRO, XFD.
Parameters
diskno disk drive number (1 - 8)
filename path to disk image
readonly if TRUE will be mounted as read-only
Return values
FALSE if error
TRUE if successful
int libatari800_reboot_with_file (const char * filename)
Restart emulation using file
Perform a cold start with a disk image, executable file, cartridge, cassette image, BASIC
file, or atari800 state save file.
This is currently the only way to load and have the emulator run an executable file or
BASIC program without a boot disk image. The atari800 emulator includes a built-in
bootloader for these files but is only available at machine start.
Parameters
path to file
Returns
file type, or 0 for error
UBYTE* libatari800_get_main_memory_ptr ()
Return pointer to main memory
This is actual array containing the emulator's main bank of 64k of RAM. Changes made here
will be reflected in the state of the emulator, potentially causing the emulated CPU to
halt if it encounters an illegal instruction.
Accessing memory through this pointer will not return hardware register information, this
provides access to the RAM only.
Returns
pointer to the beginning of the 64k block of main memory
UBYTE* libatari800_get_screen_ptr ()
Return pointer to screen data
The emulated screen is an array of 384x240 bytes in scan line order with the top of the
screen at the beginning of the array. Each byte represents the color index of a pixel in
the usual Atari palette (high 4 bits are the hue and low 4 bits are the luminance).
The large size of the screen includes the overscan areas not usually displayed. Typical
margins will be 24 on each the right and left, and 16 each on the top and bottom, leaving
an 8 pixel margin on all sides for the normal 320x192 addressable pixels.
Note that the screen is output only, and changes to this array will have no effect on the
emulation.
Returns
pointer to the beginning of the 92160 bytes of data holding the emulated screen.
UBYTE* libatari800_get_sound_buffer ()
Return pointer to sound data
If sound is used, each emulated frame will fill the sound buffer with samples at the
configured audio sample rate.
Because the emulation runs at a non-integer frame rate (approximately 59.923 frames per
second in NTSC, 49.861 fps in PAL), the number of samples is not a constant for all
frames -- it can vary by one sample per frame. For example, in NTSC with a sample rate of
44.1KHz, most frames will contain 736 samples, but one out of about every 19 frames will
contain 735 samples.
Use the function libatari800_get_sound_buffer_len to determine usable size of the sound
buffer.
Returns
pointer to the beginning of the sound sample buffer
int libatari800_get_sound_buffer_len ()
Return the usable size of the sound buffer.
Returns
number of bytes of valid data in the sound buffer
int libatari800_get_sound_buffer_allocated_size ()
Return the maximum size of the sound buffer.
Returns
number of bytes allocated in sound buffer
int libatari800_get_sound_frequency ()
Return the audio sample rate in samples per second
Returns
the audio sample rate, typically 44100 or 48000
int libatari800_get_num_sound_channels ()
Return the number of audio channels
Return values
1 mono
2 stereo
int libatari800_get_sound_sample_size ()
Return the sample size in bytes of each audio sample
Return values
1 8-bit audio
2 16-bit audio
float libatari800_get_fps ()
Return the video frame rate
It is important to note that libatari800 can run as fast as the host computer will allow,
but simulates operation as if it were running at NTSC or PAL frame rates. It is up to the
calling program to display frames at the correct rate.
The NTSC frame rate is 59.9227434 frames per second, and PAL is 49.8607597 fps.
Returns
floating point number representing the frame rate.
int libatari800_get_frame_number ()
Return the number of frames of emulation in the current state of the emulator
This is also equivalent to the number of times libatari800_next_frame has been called
since the initialization of libatarii800.
Calls to libatari800_init, cold starts, and warm starts do not reset this number; it will
continue to grow after these events. This value can get reset, however, if the emulator
state is restored from a previously saved state. In this case, the number of frames will
be restored to the value from the saved state.
Returns
number of frames that have been generated, or zero if no libatari800_next_frame has
not been called yet.
void libatari800_get_current_state (emulator_state_t * state)
Save the state of the emulator
Save the state of the emulator into a data structure that can later be used to restore
the emulator to this state using a call to libatari800_restore_state.
The emulator state structure can be examined by using the structure member tags as an
offset to locate the subsystem of interest, and casting the resulting offset into its own
struct. E.g. to find the value of the CPU registers and the current program counter, this
code:
emulator_state_t state;
cpu_state_t *cpu;
pc_state_t *pc;
libatari800_get_current_state(&state);
cpu = (cpu_state_t *)&state.state[state.tags.cpu];
pc = (pc_state_t *)&state.state[state.tags.pc];
printf("CPU A=%02x X=%02x Y=%02x PC=%04x0, cpu->A, cpu->X, cpu->Y, pc->PC);
gets the current state of the emulator, locates the cpu_state_t and the
pc_state_t structures within it, and prints the values of interest.
Parameters
state pointer to an already allocated emulator_state_t structure
void libatari800_restore_state (emulator_state_t * state)
Restore the state of the emulator
Return the emulator to a previous state as defined by a previous call to
libatari800_get_current_state.
Minimal error checking is performed on the data in state, so if the data in state has
been altered it is possible that the emulator will be returned to an invalid state and
further emulation will fail.
Parameters
state pointer to an already allocated emulator_state_t structure
void libatari800_exit ()
Free resources used by the emulator.
Release any memory or other resources used by the emulator. Further calls to
libatari800_* functions are not permitted after a call to this function, and attempting
to do so will have undefined behavior and likely crash the program.
|