e705e71a97
This is integration of the frontend with the core. Disable the button mappings that use it. The frontend should implement on its own.
370 lines
29 KiB
HTML
370 lines
29 KiB
HTML
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
|
<html lang="en-US">
|
|
<head>
|
|
<meta http-equiv="content-type" content="text/html;charset=iso-8859-1">
|
|
<meta http-equiv="Content-Style-Type" content="text/css">
|
|
<meta name="description" content="How to Port Snes9x to a New Platform">
|
|
<style type="text/css">
|
|
<!-- ul { list-style-type:none } h2 { margin-top:3em } h3 { margin-top:2em } -->
|
|
</style>
|
|
<title>Porting Snes9x</title>
|
|
</head>
|
|
<body>
|
|
<h1 style="text-align:center">How to Port Snes9x to a New Platform</h1>
|
|
<div style="text-align:right">
|
|
Version: 1.60<br>
|
|
</div>
|
|
<h2>Introduction</h2>
|
|
<p>
|
|
This is brief description of the steps to port Snes9x to the new platform. It describes what code you have to write and what functions exist that you can make use of. It also gives some insights as to how Snes9x actually works, although that will be subject of another document yet to be written.
|
|
</p>
|
|
<h2>System Requirements</h2>
|
|
<p>
|
|
A C++ compiler. For the most part Snes9x really isn't written in C++, it just uses the C++ compiler as a “better C” compiler to get inline functions and so on. GCC is good for compiling Snes9x (<a href="http://gcc.gnu.org/">http://gcc.gnu.org/</a>).
|
|
</p>
|
|
<p>
|
|
A fast CPU. SNES emulation is very compute intensive; two, or sometimes three CPUs to emulate, an 8-channel 16-bit stereo sound digital signal processor with real-time sample decompression, filter and echo effects, two custom graphics processor chips that can produce transparency, scaling, rotation and window effects in 32768 colors, and finally hardware DMA all take their toll on the host CPU.
|
|
</p>
|
|
<p>
|
|
Enough RAM. Snes9x uses 8MB to load SNES ROM images and several MB for emulating sound, graphics, custom chips, and so on.
|
|
</p>
|
|
<p>
|
|
A 16-bit color (two bytes per pixel) or deeper display, at least 512*478 pixels in resolution. Pixel format conversion may be required before you place the rendered SNES screen on to the display.
|
|
</p>
|
|
<p>
|
|
Snes9x outputs 16-bit stereo digital sound data. Ports may convert it from there to 8-bit or mono. Some ports can use interrupts or callbacks from the sound system to know when more sound data is required, most other ports have to periodically poll the host sound system to see if more data is required; if it is then the sound mixing code is called to fill the sound buffer with SNES sound data, which then can be passed on to the host sound system. Sound data is generated as an array of bytes (<code>uint8</code>) for 8-bit sound or shorts (<code>int16</code>) for 16-bit data. Stereo sound data generates twice as many samples, with each channel's samples interleaved, first left's then right's.
|
|
</p>
|
|
<p>
|
|
For the user to be able to control and play SNES games, some form of input device is required, a joypad or keyboard, for example. The real SNES can have 2 eight-button digital joypads connected to it or 5 joypads when an optional multi-player adaptor is connected, although most games only require a single joypad. Access to all eight buttons and the direction pad, of course, are usually required by most games. Snes9x does emulate the multi-player adaptor hardware, if you were wondering, but its still up to you to provide the emulation of the individual joypads.
|
|
</p>
|
|
<p>
|
|
The real SNES also has a SNES mouse, Super Scope and Justifier (light-gun) available as optional extras. Snes9x can emulate all of these using some form of pointing device, usually the host system's mouse.
|
|
</p>
|
|
<p>
|
|
Some SNES game cartridges contains a small amount of extra RAM and a battery, so ROMs could save a player's progress through a game for games that takes many hours to play from start to finish. Snes9x simulates this S-RAM by saving the contents of the area of memory occupied by the S-RAM into a file then automatically restoring it again the next time the user plays the same game. If the hardware you're porting to doesn't have a storage media available then you could be in trouble.
|
|
</p>
|
|
<p>
|
|
Snes9x also implements freeze-game files which can record the state of the SNES hardware and RAM at a particular point in time and can restore it to that exact state at a later date - the result is that users can save a game at any point, not just at save-game or password points provided by the original game coders. Each freeze file is over 400k in size. To help save disk space, Snes9x can be compiled with zlib (<a href="http://www.zlib.net/">http://www.zlib.net/</a>), which is used to GZIP compress the freeze files, reducing the size to typically below 100k. zlib is also used to load GZIP or ZIP compressed ROM images. Additionally, Snes9x supports JMA archives compressed with NSRT (<a href="http://nsrt.edgeemu.com/">http://nsrt.edgeemu.com/</a>).
|
|
</p>
|
|
<h2>Compile-Time Options</h2>
|
|
<h3><code>DEBUGGER</code></h3>
|
|
<p>
|
|
Enables extra code to assist you in debugging SNES ROMs. The debugger has only ever been a quick-hack and user-interface to debugger facilities is virtually non-existent. Most of the debugger information is output via stdout and enabling the debugger slows the whole emulator down slightly. However, the debugger options available are very powerful; you could use it to help get your port working. You probably still want to ship the finished version with the debugger disabled, it will only confuse non-technical users.
|
|
</p>
|
|
<h3><code>RIGHTSHIFT_IS_SAR</code></h3>
|
|
<p>
|
|
Define this if your compiler uses shift right arithmetic for signed values. For example, GCC and Visual C++ use shift right arithmetic.
|
|
</p>
|
|
<h3><code>ZLIB / UNZIP_SUPPORT / JMA_SUPPORT</code></h3>
|
|
<p>
|
|
Define these if you want to support GZIP/ZIP/JMA compressed ROM images and GZIP compressed freeze-game files.
|
|
</p>
|
|
<h3><code>USE_OPENGL</code></h3>
|
|
<p>
|
|
Define this and set <code>Settings.OpenGLEnable</code> to <code>true</code>, then you'll get the rendered SNES image as one OpenGL texture.
|
|
</p>
|
|
<h3>Typical Options Common for Most Platforms</h3>
|
|
<p><code>
|
|
ZLIB<br>
|
|
UNZIP_SUPPORT<br>
|
|
JMA_SUPPORT<br>
|
|
RIGHTSHIFT_IS_SAR<br>
|
|
</code></p>
|
|
<h2>Editing port.h</h2>
|
|
<p>
|
|
You may need to edit <code>port.h</code> to fit Snes9x to your system.
|
|
</p>
|
|
<p>
|
|
If the byte ordering of your system is least significant byte first, make sure <code>LSB_FIRST</code> is defined, otherwise make sure it's not defined.
|
|
</p>
|
|
<p>
|
|
You'll need to make sure what pixel format your system uses for 16-bit colors (<code>RGB565</code>, <code>RGB555</code>, <code>BGR565</code> or <code>BGR555</code>), and if it's not <code>RGB565</code>, define <code>PIXEL_FORMAT</code> to it so that Snes9x will use it to render the SNES screen. For example, Windows uses <code>RGB565</code>, Mac OS X uses <code>RGB555</code>. If your system supports more than one pixel format, you can define <code>GFX_MULTI_FORMAT</code> and change Snes9x's pixel format dynamically by calling <code>S9xSetRenderPixelFormat</code> function. If your system is 24 or 32-bit only, then don't define anything; instead write a conversion routine that will take a complete rendered 16-bit SNES screen in <code>RGB565</code> format and convert to the format required to be displayed on your system.
|
|
</p>
|
|
<p>
|
|
<code>port.h</code> also typedefs some types; <code>uint8</code> for an unsigned 8-bit quantity, <code>uint16</code> for an unsigned 16-bit quantity, <code>uint32</code> for a 32-bit unsigned quantity and <code>bool8</code> for a <code>true</code>/<code>false</code> type. Signed versions are also typedef'ed.
|
|
</p>
|
|
<h2>Controllers Management</h2>
|
|
<p>
|
|
Read <code>controls.h</code>, <code>crosshair.h</code>, <code>controls.txt</code> and <code>control-inputs.txt</code> for details. This section is the minimal explanation to get the SNES controls workable.
|
|
</p>
|
|
<p>
|
|
The real SNES allows several different types of devices to be plugged into the game controller ports. The devices Snes9x emulates are a joypad, multi-player adaptor known as the Multi Player 5 or Multi Tap (allowing a further 4 joypads to be plugged in), a 2-button mouse, a light gun known as the Super Scope, and a light gun known as the Justifier.
|
|
</p>
|
|
<p>
|
|
In your initialization code, call <code>S9xUnmapAllControl</code> function.
|
|
</p>
|
|
<p>
|
|
Map any IDs to each SNES controller's buttons and pointers. (ID 249-255 are reserved).
|
|
</p>
|
|
<p>
|
|
Typically, use <code>S9xMapPointer</code> function for the pointer of the SNES mouse, Super Scope and Justifier, <code>S9xMapButton</code> function for other buttons. Set <code>poll</code> to <code>false</code> for the joypad buttons, <code>true</code> for the other buttons and pointers.
|
|
</p>
|
|
<p>
|
|
<code>S9xMapButton(k1P_A_Button, s9xcommand_t cmd = S9xGetCommandT("Joypad1 A"), false);</code>
|
|
</p>
|
|
<p>
|
|
In your main emulation loop, before <code>S9xMainLoop</code> function is called, check your system's keyboard/joypad, and call <code>S9xReportButton</code> function to report the states of the SNES joypad buttons to Snes9x. Checking and reporting input immediately before <code>S9xMainLoop</code> is preferred to minimize input latency.
|
|
</p>
|
|
<p>
|
|
<code>void MyMainLoop (void)<br>
|
|
{<br>
|
|
MyReportButttons();<br>
|
|
S9xMainLoop();<br>
|
|
}</code>
|
|
</p>
|
|
<p>
|
|
<code>void MyReportButtons (void)<br>
|
|
{<br>
|
|
S9xReportButton(k1P_A_Button, (key_is_pressed ? true : false));<br>
|
|
}</code>
|
|
</p>
|
|
<p>
|
|
Prepare your <code>S9xPollButton</code> and <code>S9xPollPointer</code> function to reply Snes9x's request for other buttons/cursors states.
|
|
</p>
|
|
<p>
|
|
Call <code>S9xSetController</code> function. It connects each input device to each SNES input port.<br>
|
|
Here's typical controller settings that is used by the real SNES games:
|
|
</p>
|
|
<p>Joypad<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_JOYPAD, 1, 0, 0, 0);</code>
|
|
</p>
|
|
<p>Mouse (port 1)<br>
|
|
<code>S9xSetController(0, CTL_MOUSE, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_JOYPAD, 1, 0, 0, 0);</code>
|
|
</p>
|
|
<p>Mouse (port 2)<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_MOUSE, 1, 0, 0, 0);</code>
|
|
</p>
|
|
<p>Super Scope<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_SUPERSCOPE, 0, 0, 0, 0);</code>
|
|
</p>
|
|
<p>Multi Player 5<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_MP5, 1, 2, 3, 4);</code>
|
|
</p>
|
|
<p>Justifier<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_JUSTIFIER, 0, 0, 0, 0);</code>
|
|
</p>
|
|
<p>Justifier (2 players)<br>
|
|
<code>S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);<br>
|
|
S9xSetController(1, CTL_JUSTIFIER, 1, 0, 0, 0);</code>
|
|
</p>
|
|
<h2>Existing Interface Functions</h2>
|
|
<h3><code>bool8 Memory.Init (void)</code></h3>
|
|
<p>
|
|
Allocates and initializes several major lumps of memory, for example the SNES ROM and RAM arrays, tile cache arrays, etc. Returns <code>false</code> if memory allocation fails.
|
|
</p>
|
|
<h3><code>void Memory.Deinit (void)</code></h3>
|
|
<p>
|
|
Deallocates the memory allocations made by <code>Memory.Init</code> function.
|
|
</p>
|
|
<h3><code>bool8 S9xGraphicsInit (void)</code></h3>
|
|
<p>
|
|
Allocates and initializes several lookup tables used to speed up SNES graphics rendering. Call after you have initialized the <code>GFX.Screen</code> and <code>GFX.Pitch</code> values. Returns <code>false</code> if memory allocation fails.
|
|
</p>
|
|
<h3><code>void S9xGraphicsDeinit (void)</code></h3>
|
|
<p>
|
|
Deallocates the memory allocations made by <code>S9xGraphicsInit</code> function.
|
|
</p>
|
|
<h3><code>bool8 S9xInitAPU (void)</code></h3>
|
|
<p>
|
|
Allocates and initializes several arrays used by the sound CPU and sound generation code. Returns <code>false</code> if memory allocation fails.
|
|
</p>
|
|
<h3><code>void S9xDeinitAPU (void)</code></h3>
|
|
<p>
|
|
Deallocates the allocations made by <code>S9xInitAPU</code> function.
|
|
</p>
|
|
<h3><code>bool8 S9xInitSound (int buffer_ms)</code></h3>
|
|
<p>
|
|
Allocates memory for mixing and queueing SNES sound data, does more sound code initialization and opens the host system's sound device by calling <code>S9xOpenSoundDevice</code>, a function you must provide. Before calling this function you must set up <code>Settings.SoundSync</code>, <code>Settings.SoundPlaybackRate</code>, and <code>Settings.SoundInputRate</code>. (see section below)<br>
|
|
<code>buffer_ms</code>, given in milliseconds, is the memory buffer size for queueing sound data. Leave this at 0 to use the suggested default.<br>
|
|
</p>
|
|
<h3><code>void S9xReset (void)</code></h3>
|
|
<p>
|
|
Resets the SNES emulated hardware back to the state it was in at “switch-on” except the S-RAM area is preserved (“hardware reset”). The effect is it resets the current game back to the start. This function is automatically called by <code>Memory.LoadROM</code> function.
|
|
</p>
|
|
<h3><code>void S9xSoftReset (void)</code></h3>
|
|
<p>
|
|
Similar to <code>S9xReset</code> function, but “software reset” as you press the SNES reset button.
|
|
</p>
|
|
<h3><code>bool8 Memory.LoadROM (const char *filepath)</code></h3>
|
|
<p>
|
|
Attempts to load the specified ROM image filename into the emulated ROM area. There are many different SNES ROM image formats and the code attempts to auto-detect as many different types as it can and in a vast majority of the cases gets it right.<br>
|
|
There are several ROM image options in the <code>Settings</code>structure; allow the user to set them before calling <code>Memory.LoadROM</code> function, or make sure they are all reset to default values before each call to <code>Memory.LoadROM</code> function. See <code>Settings.ForceXXX</code> in <code>snes9x.h</code>.
|
|
</p>
|
|
<h3><code>bool8 Memory.LoadMultiCart (const char *cartApath, const char *cartBpath)</code></h3>
|
|
<p>
|
|
Attempts to load multiple ROM images into the emulated ROM area, for the multiple cartridge systems such as Sufami Turbo, Same Game, etc.
|
|
</p>
|
|
<h3><code>bool8 Memory.LoadSRAM (const char *filepath)</code></h3>
|
|
<p>
|
|
Call this function to load the associated S-RAM save file (if any). The filename should be based on the ROM image name to allow easy linkage. The current ports change the directory and the filename extension of the ROM filename to derive the S-RAM filename.
|
|
</p>
|
|
<h3><code>bool8 Memory.SaveSRAM (const char *filepath)</code></h3>
|
|
<p>
|
|
Call this function to save the emulated S-RAM area into a file so it can be restored again the next time the user wants to play the game. Remember to call this when just before the emulator exits or when the user has been playing a game and is about to load another one.
|
|
</p>
|
|
<h3><code>void S9xMainLoop (void)</code></h3>
|
|
<p>
|
|
The emulator main loop. Call this from your own main loop that calls this function (if a ROM image is loaded and the game is not paused), processes any pending host system events, then goes back around the loop again until the emulator exits. <code>S9xMainLoop</code> function normally returns control to your main loop once every emulated frame, when it reaches the start of scan-line zero. However it may take a few frames when a huge memory transfer is being emulated. The function can return more often if the <code>DEBUGGER</code> compile-time flag is defined and the CPU has hit a break point, or the <code>DEBUG_MODE_FLAG</code> bit is set in <code>CPU.Flags</code> or instruction single-stepping is enabled.
|
|
</p>
|
|
<h3><code>void S9xMixSamples (uint8 *buffer, int sample_count)</code></h3>
|
|
<p>
|
|
Call this function from your host sound system handling code to fill <code>buffer</code> with ready-mixed SNES sound data. The buffer will be filled with an array of <code>sample_count</code> <code>int16</code>. Samples are in pairs, first a left channel sample followed by the right channel sample.<br>
|
|
If there are fewer queued samples than you request by <code>sample_count</code>, the function fills <code>buffer</code> with silent sound data and returns <code>false</code>.
|
|
</p>
|
|
<h3><code>int S9xGetSampleCount (void)</code></h3>
|
|
<p>
|
|
Returns the number of sound samples available in the buffer for your configured playback settings.
|
|
</p>
|
|
<h3><code>void S9xSetSamplesAvailableCallback (void (*) samples_available (void *), void *data)</code></h3>
|
|
<p>
|
|
Call this function to set up a callback that is run when sound samples are made available. <code>samples_available</code> is a function you provide that returns <code>void</code> and takes a pointer as an argument. <code>data</code> is a pointer that you may wish to pass to your callback or can be <code>NULL</code>. Inside this function is the only time that it is safe to access Snes9x's sound data. Ideally, all samples reported available will be removed from Snes9x's buffer when this function is called and moved to either the sound driver output or a separate buffer that can be locked in a thread-safe fashion. If you are using a callback-oriented sound API, it is recommended to set up a separate buffer that can accumulate at least enough samples to fulfill more than one host sound driver callback request.<br>
|
|
If you wish to disable a callback you have set up or need to temporarily shut down your sound system, you may pass <code>NULL</code> for both arguments.
|
|
</p>
|
|
<h3><code>bool8 S9xSyncSound (void)</code></h3>
|
|
<p>
|
|
Call this function to synchronize the sound buffers to the game state. This will call the <code>samples_available</code> function to try and move sound data out of Snes9x. If Snes9x is unable to make enough space to accumulate the sound data for a whole video frame, this function will return <code>false</code>. In this case, you may wish to wait until your host sound system uses the available samples, and <code>S9xSyncSound</code> returns <code>true</code> before continuing to execute <code>S9xMainLoop</code>.
|
|
</p>
|
|
<h3><code>bool8 S9xSetSoundMute (bool8 mute)</code></h3>
|
|
<p>
|
|
Call with a <code>true</code> parameter to cause the <code>S9xMixSamples</code> to fill the output buffer with silence, instead of filling the output buffer with sound data. Useful if your sound system is interrupt or callback driven and the game has been paused either directly or indirectly because the user is interacting with the emulator's user interface in some way.
|
|
</p>
|
|
<h3><code>bool8 S9xFreezeGame (const char *filepath)</code></h3>
|
|
<p>
|
|
Call this function to record the current SNES hardware state into a file, the file can be loaded back using <code>S9xUnfreezeGame</code> function at a later date effectively restoring the current game to exact same spot. Call this function while you're processing any pending system events when <code>S9xMainLoop</code> function has returned control to you in your main loop.
|
|
</p>
|
|
<h3><code>bool8 S9xUnfreezeGame (const char *filepath)</code></h3>
|
|
<p>
|
|
Restore the SNES hardware back to the exactly the state it was in when <code>S9xFreezeGame</code> function was used to generate the file specified. You have to arrange the correct ROM is already loaded using <code>Memory.LoadROM</code> function, an easy way to arrange this is to base freeze-game filenames on the ROM image name. The UNIX/Linux ports load freeze-game files when the user presses a function key, with the names romfilename.000 for F1, romfilename.001 for F2, etc. Games are frozen in the first place when the user presses Shift-function key. You could choose some other scheme.
|
|
</p>
|
|
<h3><code>void S9xDumpSPCSnapshot (void)</code></h3>
|
|
<p>
|
|
Call this funtion to make a so-called SPC file, a snapshot of SNES sound state. Actual dump occurs at the first key-on event after this function is called.
|
|
</p>
|
|
<h3><code>void S9xSetInfoString (const char *string)</code></h3>
|
|
<p>
|
|
Call this function if you want to show a message onto the SNES screen.
|
|
</p>
|
|
<h3>Other Available Functions</h3>
|
|
<p>
|
|
See <code>movie.h</code> and <code>movie.cpp</code> to support the Snes9x movie feature.<br>
|
|
See <code>cheats.h</code>, <code>cheats.cpp</code> and <code>cheats2.cpp</code> to support the cheat feature.
|
|
</p>
|
|
<h2>Interface Functions You Need to Implement</h2>
|
|
<h3><code>bool8 S9xOpenSnapshotFile (const char *filepath, bool8 read_only, STREAM *file)</code></h3>
|
|
<p>
|
|
This function opens a freeze-game file. <code>STREAM</code> is defined as a <code>gzFile</code> if <code>ZLIB</code> is defined else it's defined as <code>FILE *</code>. The <code>read_only</code> parameter is set to <code>true</code> when reading a freeze-game file and <code>false</code> when writing a freeze-game file. Open the file <code>filepath</code> and return its pointer <code>file</code>.
|
|
</p>
|
|
<h3><code>void S9xCloseSnapshotFile (STREAM file)</code></h3>
|
|
<p>
|
|
This function closes the freeze-game file opened by <code>S9xOpenSnapshotFile</code> function.
|
|
</p>
|
|
<h3><code>void S9xExit (void)</code></h3>
|
|
<p>
|
|
Called when some fatal error situation arises or when the “q” debugger command is used.
|
|
</p>
|
|
<h3><code>bool8 S9xInitUpdate (void)</code></h3>
|
|
<p>
|
|
Called just before Snes9x begins to render an SNES screen. Use this function if you should prepare before drawing, otherwise let it empty.
|
|
</p>
|
|
<h3><code>bool8 S9xDeinitUpdate (int width, int height)</code></h3>
|
|
<p>
|
|
Called once a complete SNES screen has been rendered into the <code>GFX.Screen</code> memory buffer, now is your chance to copy the SNES rendered screen to the host computer's screen memory. The problem is that you have to cope with different sized SNES rendered screens: 256*224, 256*239, 512*224, 512*239, 512*448 and 512*478.
|
|
</p>
|
|
<h3><code>void S9xMessage (int type, int number, const char *message)</code></h3>
|
|
<p>
|
|
When Snes9x wants to display an error, information or warning message, it calls this function. Check in <code>messages.h</code> for the types and individual message numbers that Snes9x currently passes as parameters.<br>
|
|
The idea is display the message string so the user can see it, but you choose not to display anything at all, or change the message based on the message number or message type.<br>
|
|
Eventually all debug output will also go via this function, trace information already does.
|
|
</p>
|
|
<h3><code>bool8 S9xOpenSoundDevice (void)</code></h3>
|
|
<p>
|
|
<code>S9xInitSound</code> function calls this function to actually open the host sound device.
|
|
</p>
|
|
<h3><code>const char *S9xGetFilename (const char *extension, enum s9x_getdirtype dirtype)</code></h3>
|
|
<p>
|
|
When Snes9x needs to know the name of the cheat/IPS file and so on, this function is called. Check <code>extension</code> and <code>dirtype</code>, and return the appropriate filename. The current ports return the ROM file path with the given extension.
|
|
</p>
|
|
<h3><code>const char *S9xGetFilenameInc (const char *extension, enum s9x_getdirtype dirtype)</code></h3>
|
|
<p>
|
|
Almost the same as <code>S9xGetFilename</code> function, but used for saving SPC files etc. So you have to take care not to delete the previously saved file, by increasing the number of the filename; romname.000.spc, romname.001.spc, ...
|
|
</p>
|
|
<h3><code>const char *S9xGetDirectory (enum s9x_getdirtype dirtype)</code></h3>
|
|
<p>
|
|
Called when Snes9x wants to know the directory <code>dirtype</code>.
|
|
</p>
|
|
<h3><code>const char *S9xBasename (const char *path)</code></h3>
|
|
<p>
|
|
Called when Snes9x wants to know the name of the ROM image. Typically, extract the filename from <code>path</code> and return it.
|
|
</p>
|
|
<h3><code>void S9xAutoSaveSRAM (void)</code></h3>
|
|
<p>
|
|
If <code>Settings.AutoSaveDelay</code> is not zero, Snes9x calls this function when the contents of the S-RAM has been changed. Typically, call <code>Memory.SaveSRAM</code> function from this function.
|
|
</p>
|
|
<h3><code>void S9xToggleSoundChannel (int c)</code></h3>
|
|
<p>
|
|
If your port can match Snes9x's built-in <code>SoundChannelXXX</code> command (see <code>controls.cpp</code>), you may choose to use this function. Otherwise return <code>NULL</code>. Basically, turn on/off the sound channel <code>c</code> (0-7), and turn on all channels if <code>c</code> is 8.
|
|
</p>
|
|
<h3><code>void S9xSyncSpeed (void)</code></h3>
|
|
<p>
|
|
Called at the end of <code>S9xMainLoop</code> function, when emulating one frame has been done. You should adjust the frame rate in this function.
|
|
</p>
|
|
<h2>Global Variables</h2>
|
|
<h3><code>uint16 *GFX.Screen</code></h3>
|
|
<p>
|
|
A <code>uint16</code> array pointer to (at least) 2*512*478 bytes buffer where Snes9x puts the rendered SNES screen. However, if your port will not support hires mode (<code>Settings.SupportHiRes = false</code>), then a 2*256*239 bytes buffer is allowed. You should allocate the space by yourself. As well you can change the <code>GFX.Screen</code> value after <code>S9xDeinitUpdate</code> function is called so that double-buffering will be easy.
|
|
</p>
|
|
<h3><code>uint32 GFX.Pitch</code></h3>
|
|
<p>
|
|
Bytes per line (not pixels per line) of the <code>GFX.Screen</code> buffer. Typically set it to 1024. When the SNES screen is 256 pixels width and <code>Settings.OpenGLEnable</code> is <code>false</code>, last half 512 bytes per line are unused. When <code>Settings.OpenGLEnable</code> is <code>true</code>, <code>GFX.Pitch</code> is ignored.
|
|
</p>
|
|
<h3>Settings structure</h3>
|
|
<p>
|
|
There are various switches in the <code>Settings</code> structure. See <code>snes9x.h</code> for details. At least the settings below are required for good emulation.
|
|
</p>
|
|
<p><code>
|
|
memset(&Settings, 0, sizeof(Settings));<br>
|
|
Settings.MouseMaster = true;<br>
|
|
Settings.SuperScopeMaster = true;<br>
|
|
Settings.JustifierMaster = true;<br>
|
|
Settings.MultiPlayer5Master = true;<br>
|
|
Settings.FrameTimePAL = 20000;<br>
|
|
Settings.FrameTimeNTSC = 16667;<br>
|
|
Settings.SoundPlaybackRate = 32040;<br>
|
|
Settings.SoundInputRate = 31947;<br>
|
|
Settings.SupportHiRes = true;<br>
|
|
Settings.Transparency = true;<br>
|
|
Settings.AutoDisplayMessages = true;<br>
|
|
Settings.InitialInfoStringTimeout = 120;<br>
|
|
Settings.HDMATimingHack = 100;<br>
|
|
Settings.BlockInvalidVRAMAccessMaster = true;
|
|
</code></p>
|
|
<h3><code>Settings.SoundInputRate</code></h3>
|
|
<p>
|
|
Adjusts the sound rate through resampling. For every <code>Settings.SoundInputRate</code> samples generated by the SNES, <code>Settings.SoundPlaybackRate</code> samples will be produced.
|
|
</p>
|
|
<p>
|
|
On a NTSC SNES, the video refresh rate is usually 60.09881Hz, while the audio output is 32040Hz. This means for every video frame displayed, 532.4565 sound frames are produced. A modern computer display or television will usually provide one of two video rates, 60.00Hz exactly or 59.94Hz to support broadcast television. The sound output, however, is usually fixed at exact values like 32000Hz, 44100Hz, or 48000Hz. This means that the closest video-to-sound ratio we can achieve, with 60.00Hz and 32000Hz, is 1.0/533.3333. This discrepancy means if we wait for vertical sync and update once per refresh, we will experience a sound deficit of 0.87 samples every frame, eventually causing a sound underrun and producing audio problems. <br>
|
|
</p>
|
|
<p>
|
|
Settings.SoundInputRate can be set to a value such that the ratio with the video rate matches the SNES output. A 60.00Hz display, for example, should be set to 60.00 / 60.09881 * 32040, which is about 31987. The Snes9x resampler will then stretch the sound samples to fit this ratio. It may be beneficial to provide an option for users to configure <code>Settings.SoundInputRate</code> to suit their own systems. Setting <code>Settings.SoundInputRate</code> to a value that matches the actual output rate (i.e. 31915hz for 59.94Hz) or lower will allow the users to eliminate crackling. A range of 31000hz to 33000hz should be inclusive enough for all displays. Use of this setting paired with the <code>S9xSyncSound</code> function can eliminate sound discontinuity. However, this concept is difficult for most users to understand. If possible, read the display's refresh rate and automatically calculate the appropriate input rate by default. In practice, few displays exceed a target of 60.00Hz, so a good default would be no greater than 31950Hz, but probably lower.
|
|
</p>
|
|
<h3><code>Settings.DynamicRateControl</code> and <code>S9xUpdateDynamicRate(int numerator, int denominator)</code></h3>
|
|
<p>
|
|
To eliminate buffer overflows and underflows, dynamic rate control can be implemented and used. While this can accommodate for Settings.SoundInputRate not being set correctly, it works better the closer that value is to ideal.
|
|
</p>
|
|
<p>
|
|
To implement, set <code>Settings.DynamicRateControl</code> to <code>true</code>. At the beginning of your <code>samples_available</code> callback, check the hardware output buffer's fill level. Report the amount of free space in the buffer as a fraction of total buffer size to <code>S9xUpdateDynamicRate</code>, and Snes9x will try and keep the buffer close to 50% full. To tune this, <code>Settings.DynamicRateLimit</code> can be changed. A larger value will increase the range of frequencies it can use, but will also cause more noticeable pitch changes.
|
|
</p>
|
|
<div style="text-align:right; margin-top:3em">
|
|
Original document (c) Copyright 1998 Gary Henderson;
|
|
Updated most recently by: 2019/2/26 BearOso
|
|
</div>
|
|
</body>
|
|
</html>
|