/*
 * Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
 *
 * (c) Copyright 1996, 1997, 1998 Gary Henderson (gary@daniver.demon.co.uk) and
 *                                Jerremy Koot (jkoot@euronet.nl)
 *
 * Super FX C emulator code 
 * (c) Copyright 1997, 1998 Lestat (lstat@hotmail.com) and
 *                          Gary Henderson.
 * Super FX assembler emulator code (c) Copyright 1998 zsKnight and _Demo_.
 *
 * DSP1 emulator code (c) Copyright 1998 Lestat and Gary Henderson.
 * DOS port code contains the works of other authors. See headers in
 * individual files.
 *
 * Permission to use, copy, modify and distribute Snes9x in both binary and
 * source form, for non-commercial purposes, is hereby granted without fee,
 * providing that this license information and copyright notice appear with
 * all copies and any derived work.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event shall the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Snes9x is freeware for PERSONAL USE only. Commercial users should
 * seek permission of the copyright holders first. Commercial use includes
 * charging money for Snes9x or software derived from Snes9x.
 *
 * The copyright holders request that bug fixes and improvements to the code
 * should be forwarded to them so everyone can benefit from the modifications
 * in future versions.
 *
 * Super NES and Super Nintendo Entertainment System are trademarks of
 * Nintendo Co., Limited and its subsidiary companies.
 */

#include <signal.h>
#include <errno.h>

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <ctype.h>

#if defined(__linux) || defined(__DJGPP) || defined(__sun)
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <signal.h>
#endif

#ifdef __DJGPP
#include <crt0.h>
#endif

#ifdef __linux
#include <sys/soundcard.h>
#endif

#ifdef __sun
#ifdef __SVR4
#include <sys/audioio.h>
#else
#include <sun/audioio.h>
#endif
#endif

#if defined(__sun) && defined(__GNUC__)
typedef void (*SIG_PF)();
#endif

#include "snes9x.h"
#include "memmap.h"
#include "debug.h"
#include "cpuexec.h"
#include "ppu.h"
#include "snapshot.h"
#include "apu.h"
#include "display.h"
#include "gfx.h"
#include "soundux.h"

uint32 joypads [5] = {0};
int NumControllers = 2;

#ifdef JOYSTICK_SUPPORT
#ifdef __linux
#include <linux/joystick.h>

#define JOYSTICK1 "/dev/js0"
#define JOYSTICK2 "/dev/js1"

int joystick1 = -1;
int joystick2 = -1;
struct JS_DATA_TYPE joy1_initial_val = {0};
struct JS_DATA_TYPE joy2_initial_val = {0};

#endif

void InitJoysticks ();
void ReadJoysticks ();
#endif

bool8 joystick_has_four_buttons = FALSE;
bool8 joystick_has_six_buttons = FALSE;
bool8 joystick_has_eight_buttons = FALSE;

#ifdef SIDEWINDER_SUPPORT
#ifdef __linux
#include <linux/sidewinder.h>
#define SIDEWINDER_DEV "/dev/sidewinder"
int sidewinder = -1;
#endif

void InitSidewinders ();
int ReadSidewinders ();
bool8 alternate_sidewinder_mapping = FALSE;
#endif

void InitTimer ();

char *rom_filename = NULL;
char *snapshot_filename = NULL;

#if defined(__linux) || defined(__sun)
static void sigbrkhandler(int)
{
#ifdef DEBUGGER
    CPU.Flags |= DEBUG_MODE_FLAG;
    signal(SIGINT, (SIG_PF) sigbrkhandler);
#endif
}
#endif

void OutOfMemory ()
{
    fprintf (stderr, "\
Snes9X: Memory allocation failure - not enough RAM/virtual memory available.\n\
        S9xExiting...\n");
    Memory.Deinit ();
    S9xDeinitAPU ();
    
    exit (1);
}

void S9xParseArg (char **argv, int &i, int argc)
{
    if (strcmp (argv [i], "-4") == 0 ||
	     strcasecmp (argv [i], "-four") == 0)
    {
	joystick_has_four_buttons = TRUE;
	Settings.JoystickEnabled = 1;
    }
    else if (strcmp (argv [i], "-6") == 0 ||
	     strcasecmp (argv [i], "-six") == 0)
    {
	joystick_has_six_buttons = TRUE;
	Settings.JoystickEnabled = 1;
    }
    else if (strcmp (argv [i], "-8") == 0 ||
	     strcasecmp (argv [i], "-eight") == 0)
    {
	joystick_has_eight_buttons = TRUE;
	Settings.JoystickEnabled = 1;
    }
    else if (strcmp (argv [i], "-j") == 0 ||
	     strcasecmp (argv [i], "-nojoy") == 0)
	Settings.JoystickEnabled = 0;
    else if (strcasecmp (argv [i], "-b") == 0 ||
	     strcasecmp (argv [i], "-bs") == 0 ||
	     strcasecmp (argv [i], "-buffersize") == 0)
    {
	if (i + 1 < argc)
	    Settings.SoundBufferSize = atoi (argv [++i]);
	else
	    S9xUsage ();
    }
    else if (strcmp (argv [i], "-l") == 0 ||
	     strcasecmp (argv [i], "-loadsnapshot") == 0)
    {
	if (i + 1 < argc)
	    snapshot_filename = argv [++i];
	else
	    S9xUsage ();
    }
#ifdef SIDEWINDER_SUPPORT
    else if (strcmp (argv [i], "-a") == 0)
    {
	alternate_sidewinder_mapping = TRUE;
    }
#endif
    else
	S9xParseDisplayArg (argv, i, argc);
}

int main (int argc, char **argv)
{
#ifdef __DJGPP
    _crt0_startup_flags = _CRT0_FLAG_FILL_SBRK_MEMORY;
#endif    

    if (argc < S9xMinCommandLineArgs ())
	S9xUsage ();
	
    ZeroMemory (&Settings, sizeof (Settings));

#ifdef JOYSTICK_SUPPORT
    Settings.JoystickEnabled = TRUE;
#else
    Settings.JoystickEnabled = FALSE;
#endif

    Settings.SoundPlaybackRate = 4;
    Settings.Stereo = TRUE;
    Settings.SoundBufferSize = 0;
    Settings.SPCTo65c816Ratio = 2;
    Settings.DisableSoundEcho = FALSE;
    Settings.APUEnabled = TRUE;
    Settings.H_Max = (100 * SNES_CYCLES_PER_SCANLINE) / 100;
    Settings.SkipFrames = AUTO_FRAMERATE;
    Settings.Shutdown = TRUE;
    Settings.FrameTimePAL = 20000;
    Settings.FrameTimeNTSC = 16667;
    Settings.FrameTime = Settings.FrameTimeNTSC;
    Settings.DisableSampleCaching = FALSE;
    Settings.DisableMasterVolume = FALSE;
    Settings.Mouse = TRUE;
    Settings.SuperScope = TRUE;
    Settings.MultiPlayer5 = TRUE;
    Settings.ControllerOption = SNES_MULTIPLAYER5;
    Settings.Transparency = FALSE;
    Settings.SixteenBit = FALSE;
    Settings.SupportHiRes = FALSE;

    rom_filename = S9xParseArgs (argv, argc);

    Settings.Transparency = Settings.ForceTransparency;
    if (Settings.ForceNoTransparency)
	Settings.Transparency = FALSE;

    if (Settings.Transparency)
	Settings.SixteenBit = TRUE;

    Settings.HBlankStart = (256 * Settings.H_Max) / SNES_HCOUNTER_MAX;

    if (!Memory.Init () || !S9xInitAPU())
	OutOfMemory ();

    uint32 saved_flags = CPU.Flags;
    if (!snapshot_filename)
    {
	if (rom_filename)
	{
	    if (!Memory.LoadROM (rom_filename))
	    {
		char dir [_MAX_DIR];
		char drive [_MAX_DRIVE];
		char name [_MAX_FNAME];
		char ext [_MAX_EXT];
		char fname [_MAX_PATH];

		_splitpath (rom_filename, drive, dir, name, ext);
		_makepath (fname, drive, dir, name, ext);

		strcpy (fname, S9xGetROMDirectory ());
		strcat (fname, SLASH_STR);
		strcat (fname, name);
		if (ext [0])
		{
		    strcat (fname, ".");
		    strcat (fname, ext);
		}
		_splitpath (fname, drive, dir, name, ext);
		_makepath (fname, drive, dir, name, ext);
		if (!Memory.LoadROM (fname))
		{
		    printf ("Error opening: %s\n", rom_filename);
		    exit (1);
		}
	    }
	    Memory.LoadSRAM (S9xGetSRAMFilename ());
	}
	else
	{
	    S9xReset ();
	    Settings.Paused |= 2;
	}
    }
    CPU.Flags = saved_flags;

#if !defined(__MSDOS) && defined(DEBUGGER)
#if defined(__unix) && !defined(__NeXT__)
    struct sigaction sa;
#if defined(__linux)
    sa.sa_handler = sigbrkhandler;
#else
    sa.sa_handler = (SIG_PF) sigbrkhandler;
#endif

#ifdef SA_RESTART
    sa.sa_flags = SA_RESTART;
#else
    sa.sa_flags = 0;
#endif

    sigemptyset(&sa.sa_mask);
    sigaction(SIGINT, &sa, NULL);
#else
    signal(SIGINT, (SIG_PF) sigbrkhandler);
#endif
#endif

    S9xInitInputDevices ();

    S9xInitDisplay (argc, argv);
    if (!S9xGraphicsInit ())
	OutOfMemory ();
	
    S9xTextMode ();
    (void) S9xInitSound (Settings.SoundPlaybackRate, Settings.Stereo,
		      Settings.SoundBufferSize);
    if (!Settings.APUEnabled)
	S9xSetSoundMute (TRUE);

    if (snapshot_filename)
    {
	int Flags = CPU.Flags & (DEBUG_MODE_FLAG | TRACE_FLAG);
	if (!S9xLoadSnapshot (snapshot_filename))
	    exit (1);
	CPU.Flags |= Flags;
    }

    S9xGraphicsMode ();

    sprintf (String, "\"%s\" %s: %s", Memory.ROMName, TITLE, VERSION);
    S9xSetTitle (String);
    
#ifdef JOYSTICK_SUPPORT
    uint32 JoypadSkip = 0;
#endif

    InitTimer ();
    if (!Settings.APUEnabled)
	S9xSetSoundMute (FALSE);

#if 0
    {
	FILE *fs = fopen ("test.bin", "r");
	if (fs)
	{
	    int bytes = fread (IAPU.RAM, 1, 1024 * 64, fs);
	    fclose (fs);
	    int len = 0;
	    char buffer [200];
	    for (int i = 0; i < bytes; i += len)
	    {
		len = S9xAPUOPrint (buffer, i);
		printf ("%s\n", buffer);
	    }
	}
    }
#endif

#if 0
    if (Settings.APUEnabled)
    {
	// Needed for Goal
	for (int z = 0; z < 0x2000; z++)
	{
	    APU_EXECUTE1();
	}
    }
#endif

    while (1)
    {
	if (!Settings.Paused
#ifdef DEBUGGER
	    || (CPU.Flags & (DEBUG_MODE_FLAG | SINGLE_STEP_FLAG))
#endif
           )
	    S9xMainLoop ();

	if (Settings.Paused
#ifdef DEBUGGER
	    || (CPU.Flags & DEBUG_MODE_FLAG)
#endif
           )
	{
	    S9xSetSoundMute (TRUE);
	}

#ifdef DEBUGGER
	if (CPU.Flags & DEBUG_MODE_FLAG)
	{
	    S9xDoDebug ();
	}
	else
#endif
	if (Settings.Paused)
	    S9xProcessEvents (TRUE);

#ifdef JOYSTICK_SUPPORT
	if (Settings.JoystickEnabled && (JoypadSkip++ & 1) == 0)
	    ReadJoysticks ();
#endif
#if defined (__linux) && defined (SIDEWINDER_SUPPORT)
	ReadSidewinders ();
#endif
	S9xProcessEvents (FALSE);
	
	if (!Settings.Paused
#ifdef DEBUGGER
	    && !(CPU.Flags & DEBUG_MODE_FLAG)
#endif	    
           )
	{
	    S9xSetSoundMute (FALSE);
	}
    }
    return (0);
}

void S9xExit ()
{
    S9xDeinitDisplay ();
    Memory.SaveSRAM (S9xGetSRAMFilename ());
    Memory.Deinit ();
    S9xDeinitAPU ();
    
    exit (0);
}

#if defined(__linux) || defined(__sun)
void S9xInitInputDevices ()
{
#ifdef SIDEWINDER_SUPPORT
    InitSidewinders ();
#endif
#ifdef JOYSTICK_SUPPORT
    InitJoysticks ();
#endif
}

#if defined (JOYSTICK_SUPPORT)
void InitJoysticks ()
{
    if ((joystick1 = open (JOYSTICK1, O_RDONLY)) == -1 ||
	read (joystick1, &joy1_initial_val, JS_RETURN) != JS_RETURN)
    {
	(void) close (joystick1);
	joystick1 = joystick2 = -1;
	printf ("Joystick initialisation failed.\n");
    }
    else
    {
	joystick2 = open (JOYSTICK2, O_RDONLY);
	if (joystick2 >= 0 &&
	    read (joystick2, &joy2_initial_val, JS_RETURN) != JS_RETURN)
	{
	    (void) close (joystick2);
	    joystick2 = -1;
	}
    }
}

void ReadJoysticks ()
{
    struct JS_DATA_TYPE joy1, joy2;
    static struct JS_DATA_TYPE prev_joy1, prev_joy2;

    if (joystick1 >= 0 &&
	read (joystick1, &joy1, JS_RETURN) == JS_RETURN)
    {
	if ((joy1.buttons ^ prev_joy1.buttons) & 1)
	{
	    if (!joystick_has_four_buttons && !joystick_has_six_buttons &&
		!joystick_has_eight_buttons)
	    {
		if (joy1.buttons & 1)
		    joypads [0] |= SNES_A_MASK;
		else
		    joypads [0] &= ~SNES_A_MASK;
	    }
	    else
	    {
		if (joy1.buttons & 1)
		    joypads [0] |= SNES_Y_MASK;
		else
		    joypads [0] &= ~SNES_Y_MASK;
	    }
	}
	if ((joy1.buttons ^ prev_joy1.buttons) & 2)
	{
	    if (!joystick_has_four_buttons && !joystick_has_six_buttons &&
		!joystick_has_eight_buttons)
	    {
		if (joy1.buttons & 2)
		    joypads [0] |= SNES_B_MASK;
		else
		    joypads [0] &= ~SNES_B_MASK;
	    }
	    else
	    {
		if (joy1.buttons & 2)
		    joypads [0] |= SNES_X_MASK;
		else
		    joypads [0] &= ~SNES_X_MASK;
	    }
	}
	    
	if (joy1.x < (joy1_initial_val.x >> 1))
	    joypads [0] |= SNES_LEFT_MASK;
	else
	if (prev_joy1.x < (joy1_initial_val.x >> 1))
	    joypads [0] &= ~SNES_LEFT_MASK;

	if (joy1.x > joy1_initial_val.x * 3 / 2)
	    joypads [0] |= SNES_RIGHT_MASK;
	else
	if (prev_joy1.x > joy1_initial_val.x * 3 / 2)
	    joypads [0] &= ~SNES_RIGHT_MASK;

	if (joy1.y < (joy1_initial_val.y >> 1))
	    joypads [0] |= SNES_UP_MASK;
	else
	if (prev_joy1.y < (joy1_initial_val.y >> 1))
	    joypads [0] &= ~SNES_UP_MASK;

	if (joy1.y > joy1_initial_val.y * 3 / 2)
	    joypads [0] |= SNES_DOWN_MASK;
	else
	if (prev_joy1.y > joy1_initial_val.y * 3 / 2)
	    joypads [0] &= ~SNES_DOWN_MASK;

	prev_joy1 = joy1;
    }

    if (joystick2 >= 0 &&
	read (joystick2, &joy2, JS_RETURN) == JS_RETURN)
    {
	if (joystick_has_four_buttons || joystick_has_six_buttons ||
	    joystick_has_eight_buttons)
	{
	    if ((joy2.buttons ^ prev_joy2.buttons) & 1)
	    {
		if (joy2.buttons & 1)
		    joypads [0] |= SNES_B_MASK;
		else
		    joypads [0] &= ~SNES_B_MASK;
	    }
	    if ((joy2.buttons ^ prev_joy2.buttons) & 2)
	    {
		if (joy2.buttons & 2)
		    joypads [0] |= SNES_A_MASK;
		else
		    joypads [0] &= ~SNES_A_MASK;
	    }
	
	    if (joystick_has_six_buttons || joystick_has_eight_buttons)
	    {
		if (joy2.x < (joy2_initial_val.x >> 1))
		    joypads [0] |= SNES_TL_MASK;
		else
		if (prev_joy2.x < (joy2_initial_val.x >> 1))
		    joypads [0] &= ~SNES_TL_MASK;

		if (joy2.y < (joy2_initial_val.y >> 1))
		    joypads [0] |= SNES_TR_MASK;
		else
		if (prev_joy2.y < (joy2_initial_val.y >> 1))
		    joypads [0] &= ~SNES_TR_MASK;
	    }
	    if (joystick_has_eight_buttons)
	    {
		if (joy2.x > joy2_initial_val.x * 3 / 2)
		    joypads [0] |= SNES_SELECT_MASK;
		else
		if (prev_joy2.x > joy2_initial_val.x * 3 / 2)
		    joypads [0] &= ~SNES_SELECT_MASK;

		if (joy2.y > joy2_initial_val.y * 3 / 2)
		    joypads [0] |= SNES_START_MASK;
		else
		if (prev_joy2.y > joy2_initial_val.y * 3 / 2)
		    joypads [0] &= ~SNES_START_MASK;
	    }
	}
	else
	{
	    if ((joy2.buttons ^ prev_joy2.buttons) & 1)
	    {
		if (joy2.buttons & 1)
		    joypads [1] |= SNES_A_MASK;
		else
		    joypads [1] &= ~SNES_A_MASK;
	    }
	    if ((joy2.buttons ^ prev_joy2.buttons) & 2)
	    {
		if (joy2.buttons & 2)
		    joypads [1] |= SNES_B_MASK;
		else
		    joypads [1] &= ~SNES_B_MASK;
	    }
	    
	    if (joy2.x < (joy2_initial_val.x >> 1))
		joypads [1] |= SNES_LEFT_MASK;
	    else
	    if (prev_joy2.x < (joy2_initial_val.x >> 1))
		joypads [1] &= ~SNES_LEFT_MASK;

	    if (joy2.x > joy2_initial_val.x * 3 / 2)
		joypads [1] |= SNES_RIGHT_MASK;
	    else
	    if (prev_joy2.x > joy2_initial_val.x * 3 / 2)
		joypads [1] &= ~SNES_RIGHT_MASK;

	    if (joy2.y < (joy2_initial_val.y >> 1))
		joypads [1] |= SNES_UP_MASK;
	    else
	    if (prev_joy2.y < (joy2_initial_val.y >> 1))
		joypads [1] &= SNES_UP_MASK;

	    if (joy2.y > joy2_initial_val.y * 3 / 2)
		joypads [1] |= SNES_DOWN_MASK;
	    else
	    if (prev_joy2.y > joy2_initial_val.y * 3 / 2)
		joypads [1] &= ~SNES_DOWN_MASK;
	}
	prev_joy2 = joy2;
    }
}
#endif // defined (JOYSTICK_SUPPORT)
#endif // defined (__linux)

#ifdef SIDEWINDER_SUPPORT
int SidewinderToSNES [] =
{
    SNES_UP_MASK, SNES_DOWN_MASK, SNES_RIGHT_MASK, SNES_LEFT_MASK,
    SNES_B_MASK, SNES_A_MASK, SNES_A_MASK, 
    SNES_Y_MASK, SNES_X_MASK, SNES_B_MASK, 
    SNES_TL_MASK, SNES_TR_MASK,
    SNES_START_MASK, SNES_SELECT_MASK
};

int SidewinderToSNES2 [] =
{
    SNES_UP_MASK, SNES_DOWN_MASK, SNES_RIGHT_MASK, SNES_LEFT_MASK,
    SNES_A_MASK, SNES_B_MASK, SNES_A_MASK, 
    SNES_X_MASK, SNES_Y_MASK, SNES_B_MASK, 
    SNES_TL_MASK, SNES_TR_MASK,
    SNES_START_MASK, SNES_SELECT_MASK
};

#ifdef __linux
void InitSidewinders ()
{
    struct SW_READ_DATA data;
    if ((sidewinder = open (SIDEWINDER_DEV, O_RDONLY)) >= 0 &&
	read (sidewinder, (char *) &data, SW_RETURN) == SW_RETURN)
    {
	printf ("%d Sidewinder%s detected.\n", data.count, 
		data.count != 1 ? "s" : "");
	if (data.count > NumControllers)
	    NumControllers = data.count;
    }
}

int ReadSidewinders ()
{
    static SW_READ_DATA prev_data = {0};
    SW_READ_DATA data;

    if (sidewinder >= 0 &&
	read (sidewinder, (char *) &data, SW_RETURN) == SW_RETURN)
    {
	if (data.count)
	{
	    if (data.count < 2)
		NumControllers = 2;
	    else
	    {
		if (data.count > NumControllers)
		{
		    NumControllers = data.count;
		    if (NumControllers > 5)
			NumControllers = 5;
		}
	    }

	    for (int s = 0; s < data.count && s < 5; s++)
	    {
		if (data.valid [s])
		{
		    for (int i = 0; i < SW_NUM_SIDEWINDER_BUTTONS; i++)
		    {
			if (/*i < 4 || */
			    ((prev_data.buttons [s] ^ data.buttons [s]) & (1 << i)))
			{
			    if (alternate_sidewinder_mapping)
			    {
				if (data.buttons [s] & (1 << i))
				    joypads [s] |= SidewinderToSNES2 [i];
				else
				    joypads [s] &= ~SidewinderToSNES2 [i];
			    }
			    else
			    {
				if (data.buttons [s] & (1 << i))
				    joypads [s] |= SidewinderToSNES [i];
				else
				    joypads [s] &= ~SidewinderToSNES [i];
			    }
			}
		    }
		    prev_data.buttons [s] = data.buttons [s];
		}
	    }
	}
    }
    return (sidewinder >= 0 ? data.count : 0);
}
#endif
#endif

const char *GetHomeDirectory ()
{
#ifdef __MSDOS
    return (".");
#else
    return (getenv ("HOME"));
#endif
}

const char *S9xGetSnapshotDirectory ()
{
    static char filename [PATH_MAX];
    const char *snapshot;
    
    if (!(snapshot = getenv ("SNES96_SNAPSHOT_DIR")))
    {
	const char *home = GetHomeDirectory ();
	strcpy (filename, home);
	strcat (filename, SLASH_STR);
#ifdef __MSDOS
	strcat (filename, "snesnaps");
#else
	strcat (filename, ".snes96_snapshots");
#endif	
	mkdir (filename, 0777);
    }
    else
	return (snapshot);

    return (filename);
}

const char *S9xGetSRAMFilename ()
{
    static char filename [PATH_MAX];
    char drive [_MAX_DRIVE];
    char dir [_MAX_DIR];
    char fname [_MAX_FNAME];
    char ext [_MAX_EXT];

    _splitpath (Memory.ROMFilename, drive, dir, fname, ext);
    strcpy (filename, S9xGetSnapshotDirectory ());
    strcat (filename, SLASH_STR);
    strcat (filename, fname);
    strcat (filename, ".srm");

    return (filename);
}

const char *S9xGetROMDirectory ()
{
    const char *roms;
    
    if (!(roms = getenv ("SNES96_ROM_DIR")))
	return ("." SLASH_STR "roms");
    else
	return (roms);
}

const char *basename (const char *f)
{
    const char *p;
    if ((p = strrchr (f, '/')) != NULL || (p = strrchr (f, '\\')) != NULL)
	return (p + 1);

#ifdef __DJGPP
    if (p = strrchr (f, SLASH_CHAR))
	return (p + 1);
#endif

    return (f);
}

const char *S9xChooseFilename (bool8 read_only)
{
    char def [PATH_MAX];
    char title [PATH_MAX];
    char drive [_MAX_DRIVE];
    char dir [_MAX_DIR];
    char ext [_MAX_EXT];

    _splitpath (Memory.ROMFilename, drive, dir, def, ext);
    strcat (def, ".s96");
    sprintf (title, "%s snapshot filename",
	    read_only ? "Select load" : "Choose save");
    const char *filename;

    S9xSetSoundMute (TRUE);
    filename = S9xSelectFilename (def, S9xGetSnapshotDirectory (), "s96", title);
    S9xSetSoundMute (FALSE);
    return (filename);
}

bool8 S9xOpenSnapshotFile (const char *fname, bool8 read_only, STREAM *file)
{
    char filename [PATH_MAX];
    char drive [_MAX_DRIVE];
    char dir [_MAX_DIR];
    char ext [_MAX_EXT];

    _splitpath (fname, drive, dir, filename, ext);

    if (*drive || *dir == '/' ||
#ifdef __DJGPP
	*dir == '\\' ||
#endif
	(*dir == '.' && (*(dir + 1) == '/'
#ifdef __DJGPP
			 || *(dir + 1) == '\\'
#endif
        )))
    {
	strcpy (filename, fname);
	if (!*ext)
	    strcat (filename, ".s96");
    }
    else
    {
	strcpy (filename, S9xGetSnapshotDirectory ());
	strcat (filename, SLASH_STR);
	strcat (filename, fname);
	if (!*ext)
	    strcat (filename, ".s96");
    }
    
#ifdef ZLIB
    if (read_only)
    {
	if ((*file = OPEN_STREAM (filename, "rb")))
	    return (TRUE);
    }
    else
    {
	if ((*file = OPEN_STREAM (filename, "wb")))
	    return (TRUE);
    }
#else
    char command [PATH_MAX];
    
    if (read_only)
    {
	sprintf (command, "gzip -d <\"%s\"", filename);
	if (*file = popen (command, "r"))
	    return (TRUE);
    }
    else
    {
	sprintf (command, "gzip --best >\"%s\"", filename);
	if (*file = popen (command, "wb"))
	    return (TRUE);
    }
#endif
    return (FALSE);
}

void S9xCloseSnapshotFile (STREAM file)
{
#ifdef ZLIB
    CLOSE_STREAM (file);
#else
    pclose (file);
#endif
}

bool8 S9xInitUpdate ()
{
    return (TRUE);
}

bool8 S9xDeinitUpdate (int Width, int Height, bool8 sixteen_bit)
{
    S9xPutImage (Width, Height);
#if 0
    extern int plot2_count, plot4_count, plot8_count, plotobj_count;

    printf ("\r%5d, %5d, %5d, %5d", plot2_count, plot4_count, plot8_count, plotobj_count);
    plot2_count = 0;
    plot4_count = 0;
    plot8_count = 0;
    plotobj_count = 0;
    fflush (stdout);
#endif
    return (TRUE);
}

static unsigned long now ()
{
    static unsigned long seconds_base = 0;
    struct timeval tp;
    gettimeofday (&tp, NULL);
    if (!seconds_base)
	seconds_base = tp.tv_sec;

    return ((tp.tv_sec - seconds_base) * 1000 + tp.tv_usec / 1000);
}

void OutputFrameRate ()
{
    static int FrameCount = 0;
    static unsigned long then = now ();

    if (++FrameCount % 60 == 0)
    {
	unsigned long here = now ();
//	printf ("\rFrame rate: %.2lfms", (double) (here - then) / 60);
//	fflush (stdout);
	then = here;
    }
}

#ifndef __DJGPP
void _makepath (char *path, const char *, const char *dir,
		const char *fname, const char *ext)
{
    if (dir && *dir)
    {
	strcpy (path, dir);
	strcat (path, "/");
    }
    else
	*path = 0;
    strcat (path, fname);
    if (ext && *ext)
    {
        strcat (path, ".");
        strcat (path, ext);
    }
}

void _splitpath (const char *path, char *drive, char *dir, char *fname,
		 char *ext)
{
    *drive = 0;

    char *slash = strrchr (path, '/');
    if (!slash)
	slash = strrchr (path, '\\');

    char *dot = strrchr (path, '.');

    if (dot && slash && dot < slash)
	dot = NULL;

    if (!slash)
    {
	strcpy (dir, "");
	strcpy (fname, path);
        if (dot)
        {
	    *(fname + (dot - path)) = 0;
	    strcpy (ext, dot + 1);
        }
	else
	    strcpy (ext, "");
    }
    else
    {
	strcpy (dir, path);
	*(dir + (slash - path)) = 0;
	strcpy (fname, slash + 1);
        if (dot)
	{
	    *(fname + (dot - slash) - 1) = 0;
    	    strcpy (ext, dot + 1);
	}
	else
	    strcpy (ext, "");
    }
}
#endif

void S9xToggleSoundChannel (int c)
{
    static int channel_enable = 255;
    if (c == 8)
	channel_enable = 255;
    else
	channel_enable ^= 1 << c;
    S9xSetSoundControl (channel_enable);
}

#if defined(__sun) || defined(__linux)
static void SoundTrigger ()
{
    if (Settings.APUEnabled)
	CPU.Flags |= PROCESS_SOUND_FLAG;
}

void StopTimer ()
{
    struct itimerval timeout;

    timeout.it_interval.tv_sec = 0;
    timeout.it_interval.tv_usec = 0;
    timeout.it_value.tv_sec = 0;
    timeout.it_value.tv_usec = 0;
    if (setitimer (ITIMER_REAL, &timeout, NULL) < 0)
	perror ("setitimer");
}

void InitTimer ()
{
    struct itimerval timeout;
    struct sigaction sa;
    
#if defined(__linux)
    sa.sa_handler = (SIG_PF) SoundTrigger;
#else
    sa.sa_handler = (SIG_PF) SoundTrigger;
#endif

#if defined (SA_RESTART)
    sa.sa_flags = SA_RESTART;
#else
    sa.sa_flags = 0;
#endif

    sigemptyset (&sa.sa_mask);
    sigaction (SIGALRM, &sa, NULL);
    
    timeout.it_interval.tv_sec = 0;
    timeout.it_interval.tv_usec = 10000;
    timeout.it_value.tv_sec = 0;
    timeout.it_value.tv_usec = 10000;
    if (setitimer (ITIMER_REAL, &timeout, NULL) < 0)
	perror ("setitimer");
}
#endif

#if defined(__linux) || defined(__sun)
void S9xSyncSpeed ()
{
    if (Settings.SkipFrames == AUTO_FRAMERATE)
    {
	static struct timeval next1 = {0};
	struct timeval now;

	while (gettimeofday (&now, NULL) < 0) ;
	if (next1.tv_sec == 0)
	{
	    next1 = now;
	    next1.tv_usec++;
	}

	if (timercmp(&next1, &now, >))
	{
	    do
	    {
		CHECK_SOUND ();
		S9xProcessEvents (FALSE);
		while (gettimeofday (&now, NULL) < 0) ;
	    } while (timercmp(&next1, &now, >));
	    IPPU.RenderThisFrame = TRUE;
	    IPPU.SkippedFrames = 0;
	}
	else
	{
	    if (IPPU.SkippedFrames < 10)
	    {
		IPPU.SkippedFrames++;
		IPPU.RenderThisFrame = FALSE;
	    }
	    else
	    {
		IPPU.RenderThisFrame = TRUE;
		IPPU.SkippedFrames = 0;
		next1 = now;
	    }
	}
	next1.tv_usec += Settings.FrameTime;
	if (next1.tv_usec >= 1000000)
	{
	    next1.tv_sec += next1.tv_usec / 1000000;
	    next1.tv_usec %= 1000000;
	}
    }
    else
    {
	if (IPPU.FrameSkip++ >= (int) Settings.SkipFrames)
	{
	    IPPU.FrameSkip = 0;
	    IPPU.SkippedFrames = 0;
	    IPPU.RenderThisFrame = TRUE;
	}
	else
	{
	    IPPU.SkippedFrames++;
	    IPPU.RenderThisFrame = FALSE;
	}
    }
}
#endif

static long log2 (long num)
{
    long n = 0;

    while (num >>= 1)
	n++;

    return (n);
}

static long power (int num, int pow)
{
    long val = num;
    int i;
    
    if (pow == 0)
	return (1);

    for (i = 1; i < pow; i++)
	val *= num;

    return (val);
}

#ifdef __sun
static int Rates[8] =
{
    0, 8000, 11025, 16000, 22050, 32000, 37800, 44100
};

static int BufferSizes [8] =
{
    0, 256, 256, 256, 512, 512, 1024, 1024
};

bool8 S9xOpenSoundDevice (int mode, bool8 stereo, int buffer_size)
{
    if ((so.sound_fd = open ("/dev/audio", O_WRONLY)) < 0)
	return (FALSE);

    audio_info_t audio;

    AUDIO_INITINFO (&audio);
    audio.play.sample_rate = Rates [mode & 7];
    audio.play.channels = stereo ? 2 : 1;
    audio.play.precision = 16;
    audio.play.encoding = AUDIO_ENCODING_LINEAR;

    ioctl (so.sound_fd, AUDIO_SETINFO, &audio);
    if (ioctl (so.sound_fd, AUDIO_GETINFO, &audio) != 0)
	return (FALSE);

    so.stereo = audio.play.channels - 1;
    so.playback_rate = audio.play.sample_rate;
    so.encoded = audio.play.encoding != AUDIO_ENCODING_LINEAR;
    so.sixteen_bit = audio.play.precision == 16;

    for (int i = 1; i < 7; i++)
	if (audio.play.sample_rate <= Rates [i])
	    break;
    so.buffer_size = BufferSizes [i];
    if (buffer_size > 0)
    if (so.stereo)
	so.buffer_size *= 2;
    if (so.sixteen_bit)
	so.buffer_size *= 2;
    if (so.buffer_size > MAX_BUFFER_SIZE)
	so.buffer_size = MAX_BUFFER_SIZE;
    so.last_eof = -1;

    printf ("Rate: %d, Buffer size: %d, 16-bit: %s, Stereo: %s, Encoded: %s\n",
	    so.playback_rate, so.buffer_size, so.sixteen_bit ? "yes" : "no",
	    so.stereo ? "yes" : "no", so.encoded ? "yes" : "no");

    return (TRUE);
}
#endif

#if defined(__linux)
static int Rates[8] =
{
    0, 8192, 11025, 16500, 22050, 29300, 36600, 44000
};

static int BufferSizes [8] =
{
    0, 256, 256, 256, 512, 512, 1024, 1024
};

bool8 S9xOpenSoundDevice (int mode, bool8 stereo, int buffer_size)
{
    int I, J, K;

    if ((so.sound_fd = open ("/dev/dsp", O_WRONLY)) < 0)
	return (FALSE);

    J = AFMT_S16_LE;
    if (ioctl (so.sound_fd, SNDCTL_DSP_SETFMT, &J) < 0)
	return (FALSE);

    if (J != AFMT_S16_LE)
    {
	so.sixteen_bit = FALSE;
	J = AFMT_U8;
	if (ioctl (so.sound_fd, SNDCTL_DSP_SETFMT, &J) < 0)
	    return (FALSE);
    }
    else
	so.sixteen_bit = TRUE;

    so.stereo = stereo;
    if (ioctl (so.sound_fd, SNDCTL_DSP_STEREO, &so.stereo) < 0)
	return (FALSE);
    
    so.playback_rate = Rates[mode & 0x07];
    if (ioctl (so.sound_fd, SNDCTL_DSP_SPEED, &so.playback_rate) < 0)
	return (FALSE);

    if (buffer_size == 0)
	buffer_size = BufferSizes [mode & 7];

    if (buffer_size > MAX_BUFFER_SIZE / 4)
	buffer_size = MAX_BUFFER_SIZE / 4;
    if (so.sixteen_bit)
	buffer_size *= 2;
    if (so.stereo)
	buffer_size *= 2;

    int power2 = log2 (buffer_size);
    J = K = power2 | (SOUND_BUFS << 16);
    if (ioctl (so.sound_fd, SNDCTL_DSP_SETFRAGMENT, &J) < 0)
	return (FALSE);
    ioctl (so.sound_fd, SNDCTL_DSP_GETBLKSIZE, &so.buffer_size);
    
    printf ("Rate: %d, Buffer size: %d, 16-bit: %s, Stereo: %s, Encoded: %s\n",
	    so.playback_rate, so.buffer_size, so.sixteen_bit ? "yes" : "no",
	    so.stereo ? "yes" : "no", so.encoded ? "yes" : "no");

    return (TRUE);
}
#endif

#if defined (__linux) || defined (__sun)
void S9xUnixProcessSound (void)
{
    uint8 Buf[MAX_BUFFER_SIZE];

    CPU.Flags &= ~PROCESS_SOUND_FLAG;
    int sample_count = so.buffer_size;
    if (so.sixteen_bit)
	sample_count >>= 1;

#ifdef __linux
    audio_buf_info info;
    if (ioctl (so.sound_fd, SNDCTL_DSP_GETOSPACE, &info) == -1 ||
	info.bytes < so.buffer_size)
	return;
#endif
#ifdef __sun
    audio_info_t audio;
    if (ioctl (so.sound_fd, AUDIO_GETINFO, &audio) < 0)
	return;

    if (audio.play.eof == 0)
	so.last_eof = -2;
    else
    if (audio.play.eof == so.last_eof)
	return;

    so.last_eof++;
#endif

    S9xMixSamples (Buf, sample_count);

    if (!so.mute_sound)
    {
	int J = 0;
	int I;
	do
	{
	    I = write (so.sound_fd, (char *) Buf + J, so.buffer_size - J);
	    if (I > 0)
		J += I;
	} while ((I < 0 && errno == EINTR) || J < so.buffer_size);
    }
#ifdef __sun
    write (so.sound_fd, NULL, 0);
#endif
}
#endif

uint32 S9xReadJoypad (int which1)
{
    if (which1 < NumControllers)
	return (0x80000000 | joypads [which1]);
    return (0);
}

#ifdef __sun
uint8 int2ulaw(int ch)
{
    int mask;

    if (ch < 0) {
      ch = -ch;
      mask = 0x7f;
    }
    else {
      mask = 0xff;
    }

    if (ch < 32) {
	ch = 0xF0 | ( 15 - (ch/2) );
    } else if (ch < 96) {
        ch = 0xE0 | ( 15 - (ch-32)/4 );
    } else if (ch < 224) {
	ch = 0xD0 | ( 15 - (ch-96)/8 );
    } else if (ch < 480) {
	ch = 0xC0 | ( 15 - (ch-224)/16 );
    } else if (ch < 992 ) {
	ch = 0xB0 | ( 15 - (ch-480)/32 );
    } else if (ch < 2016) {
	ch = 0xA0 | ( 15 - (ch-992)/64 );
    } else if (ch < 4064) {
	ch = 0x90 | ( 15 - (ch-2016)/128 );
    } else if (ch < 8160) {
	ch = 0x80 | ( 15 - (ch-4064)/256 );
    } else {
	ch = 0x80;
    }
    return (uint8)(mask & ch);
}
#endif
