diff --git a/stella/src/common/FrameBufferGL.cxx b/stella/src/common/FrameBufferGL.cxx new file mode 100644 index 000000000..b6770efaf --- /dev/null +++ b/stella/src/common/FrameBufferGL.cxx @@ -0,0 +1,537 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferGL.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include "Console.hxx" +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "FrameBufferGL.hxx" +#include "MediaSrc.hxx" +#include "Settings.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferGL::FrameBufferGL() + : myTexture(0), + myFilterParam(GL_NEAREST) +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferGL::~FrameBufferGL() +{ + if(myTexture) + SDL_FreeSurface(myTexture); + + glDeleteTextures(1, &myTextureID); + glDeleteTextures(256, myFontTextureID); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferGL::createScreen() +{ + SDL_GL_SetAttribute( SDL_GL_RED_SIZE, myRGB[0] ); + SDL_GL_SetAttribute( SDL_GL_GREEN_SIZE, myRGB[1] ); + SDL_GL_SetAttribute( SDL_GL_BLUE_SIZE, myRGB[2] ); + SDL_GL_SetAttribute( SDL_GL_ALPHA_SIZE, myRGB[3] ); + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ); + + GLint viewportX = 0, viewportY = 0; + uInt32 screenWidth = 0; + uInt32 screenHeight = 0; + + uInt32 imageWidth = (uInt32) (myWidth * theZoomLevel * theAspectRatio); + uInt32 imageHeight = myHeight * theZoomLevel; + + // Determine if we're in fullscreen or windowed mode + // In fullscreen mode, we clip the SDL screen to known resolutions + // In windowed mode, we use the actual image resolution for the SDL screen + if(mySDLFlags & SDL_FULLSCREEN) + { + SDL_Rect rect = viewport(imageWidth, imageHeight); + + viewportX = rect.x; + viewportY = rect.y; + screenWidth = rect.w - 1; + screenHeight = rect.h - 1; + } + else + { + screenWidth = imageWidth; + screenHeight = imageHeight; + viewportX = viewportY = 0; + } + + myScreen = SDL_SetVideoMode(screenWidth, screenHeight, 0, mySDLFlags); + if(myScreen == NULL) + { + cerr << "ERROR: Unable to open SDL window: " << SDL_GetError() << endl; + return false; + } + + glPushAttrib(GL_ENABLE_BIT); + + // Center the screen horizontally and vertically + glViewport(viewportX, viewportY, imageWidth, imageHeight); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + + glOrtho(0.0, (GLdouble) imageWidth/(theZoomLevel * theAspectRatio), + (GLdouble) imageHeight/theZoomLevel, 0.0, 0.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + +#ifdef TEXTURES_ARE_LOST + createTextures(); +#endif + + // Make sure any old parts of the screen are erased + glClear(GL_COLOR_BUFFER_BIT); + + theRedrawEntireFrameIndicator = true; + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferGL::init() +{ + // Get the desired width and height of the display + myWidth = myMediaSource->width() << 1; + myHeight = myMediaSource->height(); + + // Get the aspect ratio for the display + // Since the display is already doubled horizontally, we half the + // ratio that is provided + theAspectRatio = myConsole->settings().getFloat("gl_aspect") / 2; + if(theAspectRatio <= 0.0) + theAspectRatio = 1.0; + + // Now create the OpenGL SDL screen + Uint32 initflags = SDL_INIT_VIDEO | SDL_INIT_TIMER; + if(SDL_Init(initflags) < 0) + return false; + + // Check which system we are running under + x11Available = false; +#if UNIX && (!__APPLE__) + SDL_VERSION(&myWMInfo.version); + if(SDL_GetWMInfo(&myWMInfo) > 0) + if(myWMInfo.subsystem == SDL_SYSWM_X11) + x11Available = true; +#endif + + // Get the maximum size of a window for THIS screen + theMaxZoomLevel = maxWindowSizeForScreen(); + + // Check to see if window size will fit in the screen + if((uInt32)myConsole->settings().getInt("zoom") > theMaxZoomLevel) + theZoomLevel = theMaxZoomLevel; + else + theZoomLevel = myConsole->settings().getInt("zoom"); + + mySDLFlags = SDL_OPENGL; + mySDLFlags |= myConsole->settings().getBool("fullscreen") ? SDL_FULLSCREEN : 0; + + // Set the window title and icon + setWindowAttributes(); + + // Set up the OpenGL attributes + myDepth = SDL_GetVideoInfo()->vfmt->BitsPerPixel; + switch(myDepth) + { + case 8: + myRGB[0] = 3; + myRGB[1] = 3; + myRGB[2] = 2; + myRGB[3] = 0; + break; + + case 15: + myRGB[0] = 5; + myRGB[1] = 5; + myRGB[2] = 5; + myRGB[3] = 0; + break; + + case 16: + myRGB[0] = 5; + myRGB[1] = 6; + myRGB[2] = 5; + myRGB[3] = 0; + break; + + case 24: + myRGB[0] = 8; + myRGB[1] = 8; + myRGB[2] = 8; + myRGB[3] = 0; + break; + + case 32: + myRGB[0] = 8; + myRGB[1] = 8; + myRGB[2] = 8; + myRGB[3] = 8; + break; + + default: // This should never happen + break; + } + + // Create the screen + if(!createScreen()) + return false; + + // Now check to see what color components were actually created + SDL_GL_GetAttribute( SDL_GL_RED_SIZE, (int*)&myRGB[0] ); + SDL_GL_GetAttribute( SDL_GL_GREEN_SIZE, (int*)&myRGB[1] ); + SDL_GL_GetAttribute( SDL_GL_BLUE_SIZE, (int*)&myRGB[2] ); + SDL_GL_GetAttribute( SDL_GL_ALPHA_SIZE, (int*)&myRGB[3] ); + +#ifndef TEXTURES_ARE_LOST + // Create the texture surface and texture fonts + createTextures(); +#endif + + // Set up the palette *after* we know the color components + // and the textures + setupPalette(); + + // Show some OpenGL info + if(myConsole->settings().getBool("showinfo")) + { + ostringstream colormode; + colormode << "Color : " << myDepth << " bit, " << myRGB[0] << "-" + << myRGB[1] << "-" << myRGB[2] << "-" << myRGB[3]; + + cout << endl + << "Vendor : " << glGetString(GL_VENDOR) << endl + << "Renderer: " << glGetString(GL_RENDERER) << endl + << "Version : " << glGetString(GL_VERSION) << endl + << colormode.str() << endl << endl; + } + + // Make sure that theUseFullScreenFlag sets up fullscreen mode correctly + if(myConsole->settings().getBool("fullscreen")) + { + grabMouse(true); + showCursor(false); + } + else + { + // Keep mouse in game window if grabmouse is selected + grabMouse(myConsole->settings().getBool("grabmouse")); + + // Show or hide the cursor depending on the 'hidecursor' argument + showCursor(!myConsole->settings().getBool("hidecursor")); + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::drawMediaSource() +{ + // Copy the mediasource framebuffer to the RGB texture + uInt8* currentFrame = myMediaSource->currentFrameBuffer(); + uInt8* previousFrame = myMediaSource->previousFrameBuffer(); + uInt32 width = myMediaSource->width(); + uInt32 height = myMediaSource->height(); + uInt16* buffer = (uInt16*) myTexture->pixels; + + register uInt32 y; + for(y = 0; y < height; ++y ) + { + const uInt32 bufofsY = y * width; + const uInt32 screenofsY = y * myTexture->w; + + register uInt32 x; + for(x = 0; x < width; ++x ) + { + const uInt32 bufofs = bufofsY + x; + uInt8 v = currentFrame[bufofs]; + if(v == previousFrame[bufofs] && !theRedrawEntireFrameIndicator) + continue; + + // x << 1 is times 2 ( doubling width ) + const uInt32 pos = screenofsY + (x << 1); + buffer[pos] = buffer[pos+1] = (uInt16) myPalette[v]; + } + } + + // Texturemap complete texture to surface so we have free scaling + // and antialiasing + glBindTexture(GL_TEXTURE_2D, myTextureID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, myTexture->w, myTexture->h, + GL_RGB, GL_UNSIGNED_SHORT_5_6_5, myTexture->pixels); + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); + glColor3f(0.0, 0.0, 0.0); + + glBegin(GL_QUADS); + glTexCoord2f(myTexCoord[0], myTexCoord[1]); glVertex2i(0, 0); + glTexCoord2f(myTexCoord[2], myTexCoord[1]); glVertex2i(myWidth, 0); + glTexCoord2f(myTexCoord[2], myTexCoord[3]); glVertex2i(myWidth, myHeight); + glTexCoord2f(myTexCoord[0], myTexCoord[3]); glVertex2i(0, myHeight); + glEnd(); + + // The frame doesn't need to be completely redrawn anymore + theRedrawEntireFrameIndicator = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::preFrameUpdate() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::postFrameUpdate() +{ + // Now show all changes made to the textures + SDL_GL_SwapBuffers(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::drawBoundedBox(uInt32 x, uInt32 y, uInt32 w, uInt32 h) +{ + // First draw the box in the background, alpha-blended + glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND); + glColor4f(0.0, 0.0, 0.0, 0.7); + glRecti(x, y, x+w, y+h); + + // Now draw the outer edges + glLineWidth(theZoomLevel/2); + glColor4f(0.8, 0.8, 0.8, 1.0); + glBegin(GL_LINE_LOOP); + glVertex2i(x, y ); // Top Left + glVertex2i(x+w, y ); // Top Right + glVertex2i(x+w, y+h); // Bottom Right + glVertex2i(x, y+h); // Bottom Left + glEnd(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::drawText(uInt32 x, uInt32 y, const string& message) +{ + for(uInt32 i = 0; i < message.length(); i++) + drawChar(x + i*8, y, (uInt32) message[i]); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::drawChar(uInt32 x, uInt32 y, uInt32 c) +{ + if(c >= 256 ) + return; + + glBindTexture(GL_TEXTURE_2D, myFontTextureID[c]); + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2i(x, y ); + glTexCoord2f(1, 0); glVertex2i(x+8, y ); + glTexCoord2f(1, 1); glVertex2i(x+8, y+8); + glTexCoord2f(0, 1); glVertex2i(x, y+8); + glEnd(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferGL::createTextures() +{ + if(myTexture) + SDL_FreeSurface(myTexture); + + glDeleteTextures(1, &myTextureID); + glDeleteTextures(256, myFontTextureID); + + uInt32 w = power_of_two(myWidth); + uInt32 h = power_of_two(myHeight); + + myTexCoord[0] = 0.0f; + myTexCoord[1] = 0.0f; + myTexCoord[2] = (GLfloat) myWidth / w; + myTexCoord[3] = (GLfloat) myHeight / h; + + myTexture = SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, 16, + 0x0000F800, 0x000007E0, 0x0000001F, 0x00000000); + + if(myTexture == NULL) + return false; + + // Create an OpenGL texture from the SDL texture + bool showinfo = myConsole->settings().getBool("showinfo"); + string filter = myConsole->settings().getString("gl_filter"); + if(filter == "linear") + { + myFilterParam = GL_LINEAR; + if(showinfo) + cout << "Using GL_LINEAR filtering.\n"; + } + else if(filter == "nearest") + { + myFilterParam = GL_NEAREST; + if(showinfo) + cout << "Using GL_NEAREST filtering.\n"; + } + + glGenTextures(1, &myTextureID); + glBindTexture(GL_TEXTURE_2D, myTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myFilterParam); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myFilterParam); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, + myTexture->pixels); + + // Now create the font textures. There are 256 fonts of 8x8 pixels. + // These will be stored in 256 textures of size 8x8. + SDL_Surface* fontTexture = SDL_CreateRGBSurface(SDL_SWSURFACE, 8, 8, 32, + #if SDL_BYTEORDER == SDL_LIL_ENDIAN + 0x000000FF, 0x0000FF00, 0x00FF0000, 0xFF000000); + #else + 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF); + #endif + + if(fontTexture == NULL) + return false; + + // Create a texture for each character + glGenTextures(256, myFontTextureID); + + for(uInt32 c = 0; c < 256; c++) + { + // First clear the texture + SDL_Rect tmp; + tmp.x = 0; tmp.y = 0; tmp.w = 8; tmp.h = 8; + SDL_FillRect(fontTexture, &tmp, + SDL_MapRGBA(fontTexture->format, 0xff, 0xff, 0xff, 0x0)); + + // Now fill the texture with font data + for(uInt32 y = 0; y < 8; y++) + { + for(uInt32 x = 0; x < 8; x++) + { + if((ourFontData[(c << 3) + y] >> x) & 1) + { + tmp.x = x; + tmp.y = y; + tmp.w = tmp.h = 1; + SDL_FillRect(fontTexture, &tmp, + SDL_MapRGBA(fontTexture->format, 0x10, 0x10, 0x10, 0xff)); + } + } + } + + glBindTexture(GL_TEXTURE_2D, myFontTextureID[c]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myFilterParam); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myFilterParam); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, + fontTexture->pixels); + } + + SDL_FreeSurface(fontTexture); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferGL::toggleFilter() +{ + if(myFilterParam == GL_NEAREST) + { + myFilterParam = GL_LINEAR; + myConsole->settings().setString("gl_filter", "linear"); + showMessage("GL_LINEAR filtering"); + } + else + { + myFilterParam = GL_NEAREST; + myConsole->settings().setString("gl_filter", "nearest"); + showMessage("GL_NEAREST filtering"); + } + + glBindTexture(GL_TEXTURE_2D, myTextureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myFilterParam); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myFilterParam); + + for(uInt32 i = 0; i < 256; i++) + { + glBindTexture(GL_TEXTURE_2D, myFontTextureID[i]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, myFilterParam); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, myFilterParam); + } + + // The filtering has changed, so redraw the entire screen + theRedrawEntireFrameIndicator = true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SDL_Rect FrameBufferGL::viewport(uInt32 width, uInt32 height) +{ + SDL_Rect rect; + rect.x = rect.y = 0; + rect.w = width; rect.h = height; + + struct Screenmode + { + uInt32 w; + uInt32 h; + }; + + // List of valid fullscreen OpenGL modes + Screenmode myScreenmode[6] = { + {320, 240 }, + {640, 480 }, + {800, 600 }, + {1024, 768 }, + {1280, 1024}, + {1600, 1200} + }; + + for(uInt32 i = 0; i < 6; i++) + { + if(width <= myScreenmode[i].w && height <= myScreenmode[i].h) + { + rect.x = (myScreenmode[i].w - width) / 2; + rect.y = (myScreenmode[i].h - height) / 2; + rect.w = myScreenmode[i].w; + rect.h = myScreenmode[i].h; + + return rect; + } + } + + // If we get here, it probably indicates an error + // But we have to return something ... + return rect; +} diff --git a/stella/src/common/FrameBufferGL.hxx b/stella/src/common/FrameBufferGL.hxx new file mode 100644 index 000000000..95575c16b --- /dev/null +++ b/stella/src/common/FrameBufferGL.hxx @@ -0,0 +1,169 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferGL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#ifndef FRAMEBUFFER_GL_HXX +#define FRAMEBUFFER_GL_HXX + +#include +#include +#include + +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "bspf.hxx" + +class Console; +class MediaSource; + +/** + This class implements an SDL OpenGL framebuffer. + + @author Stephen Anthony + @version $Id: FrameBufferGL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +*/ +class FrameBufferGL : public FrameBufferSDL +{ + public: + /** + Creates a new SDL OpenGL framebuffer + */ + FrameBufferGL(); + + /** + Destructor + */ + virtual ~FrameBufferGL(); + + /** + Switches between the two filtering options in OpenGL. + Currently, these are GL_NEAREST and GL_LINEAR. + */ + void toggleFilter(); + + ////////////////////////////////////////////////////////////////////// + // The following methods are derived from FrameBufferSDL.hxx + ////////////////////////////////////////////////////////////////////// + /** + This routine is called whenever the screen needs to be recreated. + It updates the global screen variable. + */ + virtual bool createScreen(); + + /** + This routine is called to map a given r,g,b triple to the screen palette. + + @param r The red component of the color. + @param g The green component of the color. + @param b The blue component of the color. + */ + virtual Uint32 mapRGB(Uint8 r, Uint8 g, Uint8 b) + { return SDL_MapRGB(myTexture->format, r, g, b); } + + ////////////////////////////////////////////////////////////////////// + // The following methods are derived from FrameBuffer.hxx + ////////////////////////////////////////////////////////////////////// + /** + This routine should be called once the console is created to setup + the video system for us to use. Return false if any operation fails, + otherwise return true. + */ + virtual bool init(); + + /** + This routine should be called anytime the MediaSource needs to be redrawn + to the screen. + */ + virtual void drawMediaSource(); + + /** + This routine should be called to draw a rectangular box with sides + at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param w The width of the box + @param h The height of the box + */ + virtual void drawBoundedBox(uInt32 x, uInt32 y, uInt32 w, uInt32 h); + + /** + This routine should be called to draw text at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param message The message text + */ + virtual void drawText(uInt32 x, uInt32 y, const string& message); + + /** + This routine should be called to draw character 'c' at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param c The character to draw + */ + virtual void drawChar(uInt32 x, uInt32 y, uInt32 c); + + /** + This routine is called before any drawing is done (per-frame). + */ + virtual void preFrameUpdate(); + + /** + This routine is called after any drawing is done (per-frame). + */ + virtual void postFrameUpdate(); + + private: + + bool createTextures(); + + SDL_Rect viewport(uInt32 width, uInt32 height); + + uInt32 power_of_two(uInt32 input) + { + uInt32 value = 1; + while( value < input ) + value <<= 1; + return value; + } + + private: + // The main texture buffer + SDL_Surface* myTexture; + + // The depth of the texture buffer + uInt32 myDepth; + + // The size of color components for OpenGL + uInt32 myRGB[4]; + + // The OpenGL main texture handle + GLuint myTextureID; + + // OpenGL texture coordinates for the main surface + GLfloat myTexCoord[4]; + + // The OpenGL font texture handles (one for each character) + GLuint myFontTextureID[256]; + + // The texture filtering to use + GLint myFilterParam; +}; + +#endif diff --git a/stella/src/common/FrameBufferSDL.cxx b/stella/src/common/FrameBufferSDL.cxx new file mode 100644 index 000000000..63fd0863e --- /dev/null +++ b/stella/src/common/FrameBufferSDL.cxx @@ -0,0 +1,268 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferSDL.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include "Console.hxx" +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "MediaSrc.hxx" +#include "Settings.hxx" + +#include "stella.xpm" // The Stella icon + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferSDL::FrameBufferSDL() + : x11Available(false), + theZoomLevel(1), + theMaxZoomLevel(1), + theAspectRatio(1.0), + myPauseStatus(false) +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferSDL::~FrameBufferSDL() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::pauseEvent(bool status) +{ + myPauseStatus = status; + setupPalette(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::setupPalette() +{ + // Shade the palette to 75% normal value in pause mode + float shade = 1.0; + if(myPauseStatus) + shade = 0.75; + + const uInt32* gamePalette = myMediaSource->palette(); + for(uInt32 i = 0; i < 256; ++i) + { + Uint8 r, g, b; + + r = (Uint8) (((gamePalette[i] & 0x00ff0000) >> 16) * shade); + g = (Uint8) (((gamePalette[i] & 0x0000ff00) >> 8) * shade); + b = (Uint8) ((gamePalette[i] & 0x000000ff) * shade); + + myPalette[i] = mapRGB(r, g, b); + } + + theRedrawEntireFrameIndicator = true; +} + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::toggleFullscreen() +{ + bool isFullscreen = !myConsole->settings().getBool("fullscreen"); + + // Update the settings + myConsole->settings().setBool("fullscreen", isFullscreen); + + if(isFullscreen) + mySDLFlags |= SDL_FULLSCREEN; + else + mySDLFlags &= ~SDL_FULLSCREEN; + + if(!createScreen()) + return; + + if(isFullscreen) // now in fullscreen mode + { + grabMouse(true); + showCursor(false); + } + else // now in windowed mode + { + grabMouse(myConsole->settings().getBool("grabmouse")); + showCursor(!myConsole->settings().getBool("hidecursor")); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::resize(int mode) +{ + // reset size to that given in properties + // this is a special case of allowing a resize while in fullscreen mode + if(mode == 0) + { + myWidth = myMediaSource->width() << 1; + myHeight = myMediaSource->height(); + } + else if(mode == 1) // increase size + { + if(myConsole->settings().getBool("fullscreen")) + return; + + if(theZoomLevel == theMaxZoomLevel) + theZoomLevel = 1; + else + theZoomLevel++; + } + else if(mode == -1) // decrease size + { + if(myConsole->settings().getBool("fullscreen")) + return; + + if(theZoomLevel == 1) + theZoomLevel = theMaxZoomLevel; + else + theZoomLevel--; + } + + if(!createScreen()) + return; + + // Update the settings + myConsole->settings().setInt("zoom", theZoomLevel); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::showCursor(bool show) +{ + if(show) + SDL_ShowCursor(SDL_ENABLE); + else + SDL_ShowCursor(SDL_DISABLE); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::grabMouse(bool grab) +{ + if(grab) + SDL_WM_GrabInput(SDL_GRAB_ON); + else + SDL_WM_GrabInput(SDL_GRAB_OFF); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferSDL::fullScreen() +{ + return myConsole->settings().getBool("fullscreen"); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 FrameBufferSDL::maxWindowSizeForScreen() +{ + if(!x11Available) + return 4; + +#ifdef UNIX + // Otherwise, lock the screen and get the width and height + myWMInfo.info.x11.lock_func(); + Display* theX11Display = myWMInfo.info.x11.display; + myWMInfo.info.x11.unlock_func(); + + int screenWidth = DisplayWidth(theX11Display, DefaultScreen(theX11Display)); + int screenHeight = DisplayHeight(theX11Display, DefaultScreen(theX11Display)); + + uInt32 multiplier = screenWidth / myWidth; + bool found = false; + + while(!found && (multiplier > 0)) + { + // Figure out the desired size of the window + int width = (int) (myWidth * multiplier * theAspectRatio); + int height = myHeight * multiplier; + + if((width < screenWidth) && (height < screenHeight)) + found = true; + else + multiplier--; + } + + if(found) + return multiplier; + else + return 1; +#endif + + return 4; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSDL::setWindowAttributes() +{ + // Set the window title + ostringstream name; + name << "Stella: \"" << myConsole->properties().get("Cartridge.Name") << "\""; + SDL_WM_SetCaption(name.str().c_str(), "stella"); + +#ifndef MAC_OSX + // Set the window icon + uInt32 w, h, ncols, nbytes; + uInt32 rgba[256], icon[32 * 32]; + uInt8 mask[32][4]; + + sscanf(stella_icon[0], "%d %d %d %d", &w, &h, &ncols, &nbytes); + if((w != 32) || (h != 32) || (ncols > 255) || (nbytes > 1)) + { + cerr << "ERROR: Couldn't load the icon.\n"; + return; + } + + for(uInt32 i = 0; i < ncols; i++) + { + unsigned char code; + char color[32]; + uInt32 col; + + sscanf(stella_icon[1 + i], "%c c %s", &code, color); + if(!strcmp(color, "None")) + col = 0x00000000; + else if(!strcmp(color, "black")) + col = 0xFF000000; + else if (color[0] == '#') + { + sscanf(color + 1, "%06x", &col); + col |= 0xFF000000; + } + else + { + cerr << "ERROR: Couldn't load the icon.\n"; + return; + } + rgba[code] = col; + } + + memset(mask, 0, sizeof(mask)); + for(h = 0; h < 32; h++) + { + const char* line = stella_icon[1 + ncols + h]; + for(w = 0; w < 32; w++) + { + icon[w + 32 * h] = rgba[(int)line[w]]; + if(rgba[(int)line[w]] & 0xFF000000) + mask[h][w >> 3] |= 1 << (7 - (w & 0x07)); + } + } + + SDL_Surface *surface = SDL_CreateRGBSurfaceFrom(icon, 32, 32, 32, + 32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); + SDL_WM_SetIcon(surface, (unsigned char *) mask); + SDL_FreeSurface(surface); +#endif +} diff --git a/stella/src/common/FrameBufferSDL.hxx b/stella/src/common/FrameBufferSDL.hxx new file mode 100644 index 000000000..520755bfc --- /dev/null +++ b/stella/src/common/FrameBufferSDL.hxx @@ -0,0 +1,163 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#ifndef FRAMEBUFFER_SDL_HXX +#define FRAMEBUFFER_SDL_HXX + +#include +#include + +#include "FrameBuffer.hxx" +#include "Settings.hxx" +#include "bspf.hxx" + +/** + This class is a base class for the SDL framebuffer and is derived from + the core FrameBuffer class. + + It defines all common code shared between the software + and OpenGL video modes, as well as required methods defined in + the core FrameBuffer. + + @author Stephen Anthony + @version $Id: FrameBufferSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +*/ +class FrameBufferSDL : public FrameBuffer +{ + public: + /** + Creates a new SDL framebuffer + */ + FrameBufferSDL(); + + /** + Destructor + */ + virtual ~FrameBufferSDL(); + + + /** + Toggles between fullscreen and window mode. Grabmouse and hidecursor + activated when in fullscreen mode. + */ + void toggleFullscreen(); + + /** + This routine is called when the user wants to resize the window. + A '1' argument indicates that the window should increase in size, while '-1' + indicates that the windows should decrease in size. A '0' indicates that + the window should be sized according to the current properties. + Can't resize in fullscreen mode. Will only resize up to the maximum size + of the screen. + */ + void resize(int mode); + + /** + Shows or hides the cursor based on the given boolean value. + */ + void showCursor(bool show); + + /** + Grabs or ungrabs the mouse based on the given boolean value. + */ + void grabMouse(bool grab); + + /** + Answers if the display is currently in fullscreen mode. + */ + bool fullScreen(); + + /** + Answers the current zoom level of the SDL + */ + uInt32 zoomLevel() { return theZoomLevel; } + + /** + Calculate the maximum window size that the current screen can hold. + Only works in X11 for now. If not running under X11, always return 4. + */ + uInt32 maxWindowSizeForScreen(); + + /** + Set the title and icon for the main SDL window. + */ + void setWindowAttributes(); + + /** + Set up the palette for a screen of any depth > 8. + */ + void setupPalette(); + + ////////////////////////////////////////////////////////////////////// + // The following methods are derived from FrameBuffer.hxx + ////////////////////////////////////////////////////////////////////// + /** + This routine is called when the emulation has been paused. + + @param status Toggle pause based on status + */ + void pauseEvent(bool status); + + ////////////////////////////////////////////////////////////////////// + // The following methods must be defined in child classes + ////////////////////////////////////////////////////////////////////// + /** + This routine is called whenever the screen needs to be recreated. + It updates the global screen variable. + */ + virtual bool createScreen() = 0; + + /** + This routine is called to map a given r,g,b triple to the screen palette. + + @param r The red component of the color. + @param g The green component of the color. + @param b The blue component of the color. + */ + virtual Uint32 mapRGB(Uint8 r, Uint8 g, Uint8 b) = 0; + + protected: + // The SDL video buffer + SDL_Surface* myScreen; + + // SDL initialization flags + uInt32 mySDLFlags; + + // SDL palette + Uint32 myPalette[256]; + + // Used to get window-manager specifics + SDL_SysWMinfo myWMInfo; + + // Indicates if we are running under X11 + bool x11Available; + + // Indicates the current zoom level of the SDL screen + uInt32 theZoomLevel; + + // Indicates the maximum zoom of the SDL screen + uInt32 theMaxZoomLevel; + + // The aspect ratio of the window + float theAspectRatio; + + // Indicates whether the emulation has paused + bool myPauseStatus; +}; + +#endif diff --git a/stella/src/common/FrameBufferSoft.cxx b/stella/src/common/FrameBufferSoft.cxx new file mode 100644 index 000000000..2c0c5b812 --- /dev/null +++ b/stella/src/common/FrameBufferSoft.cxx @@ -0,0 +1,440 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferSoft.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include "Console.hxx" +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "FrameBufferSoft.hxx" +#include "MediaSrc.hxx" +#include "Settings.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferSoft::FrameBufferSoft() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +FrameBufferSoft::~FrameBufferSoft() +{ + if(myRectList) + delete myRectList; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferSoft::createScreen() +{ + int w = myWidth * theZoomLevel; + int h = myHeight * theZoomLevel; + + myScreen = SDL_SetVideoMode(w, h, 0, mySDLFlags); + if(myScreen == NULL) + { + cerr << "ERROR: Unable to open SDL window: " << SDL_GetError() << endl; + return false; + } + + theRedrawEntireFrameIndicator = true; + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool FrameBufferSoft::init() +{ + // Get the desired width and height of the display + myWidth = myMediaSource->width() << 1; + myHeight = myMediaSource->height(); + + // Now create the software SDL screen + Uint32 initflags = SDL_INIT_VIDEO | SDL_INIT_TIMER; + if(SDL_Init(initflags) < 0) + return false; + + // Check which system we are running under + x11Available = false; +#if UNIX && (!__APPLE__) + SDL_VERSION(&myWMInfo.version); + if(SDL_GetWMInfo(&myWMInfo) > 0) + if(myWMInfo.subsystem == SDL_SYSWM_X11) + x11Available = true; +#endif + + // Get the maximum size of a window for THIS screen + theMaxZoomLevel = maxWindowSizeForScreen(); + + // Check to see if window size will fit in the screen + if((uInt32)myConsole->settings().getInt("zoom") > theMaxZoomLevel) + theZoomLevel = theMaxZoomLevel; + else + theZoomLevel = myConsole->settings().getInt("zoom"); + + mySDLFlags = SDL_SWSURFACE; + mySDLFlags |= myConsole->settings().getBool("fullscreen") ? SDL_FULLSCREEN : 0; + + // Set up the rectangle list to be used in the dirty update + myRectList = new RectList(); + if(!myRectList) + { + cerr << "ERROR: Unable to get memory for SDL rects" << endl; + return false; + } + + // Set the window title and icon + setWindowAttributes(); + + // Create the screen + if(!createScreen()) + return false; + setupPalette(); + + // Make sure that theUseFullScreenFlag sets up fullscreen mode correctly + if(myConsole->settings().getBool("fullscreen")) + { + grabMouse(true); + showCursor(false); + } + else + { + // Keep mouse in game window if grabmouse is selected + grabMouse(myConsole->settings().getBool("grabmouse")); + + // Show or hide the cursor depending on the 'hidecursor' argument + showCursor(!myConsole->settings().getBool("hidecursor")); + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::drawMediaSource() +{ + uInt8* currentFrame = myMediaSource->currentFrameBuffer(); + uInt8* previousFrame = myMediaSource->previousFrameBuffer(); + uInt16 screenMultiple = (uInt16) theZoomLevel; + + uInt32 width = myMediaSource->width(); + uInt32 height = myMediaSource->height(); + + struct Rectangle + { + uInt8 color; + uInt16 x, y, width, height; + } rectangles[2][160]; + + // This array represents the rectangles that need displaying + // on the current scanline we're processing + Rectangle* currentRectangles = rectangles[0]; + + // This array represents the rectangles that are still active + // from the previous scanlines we have processed + Rectangle* activeRectangles = rectangles[1]; + + // Indicates the number of active rectangles + uInt16 activeCount = 0; + + // This update procedure requires theWidth to be a multiple of four. + // This is validated when the properties are loaded. + for(uInt16 y = 0; y < height; ++y) + { + // Indicates the number of current rectangles + uInt16 currentCount = 0; + + // Look at four pixels at a time to see if anything has changed + uInt32* current = (uInt32*)(currentFrame); + uInt32* previous = (uInt32*)(previousFrame); + + for(uInt16 x = 0; x < width; x += 4, ++current, ++previous) + { + // Has something changed in this set of four pixels? + if((*current != *previous) || theRedrawEntireFrameIndicator) + { + uInt8* c = (uInt8*)current; + uInt8* p = (uInt8*)previous; + + // Look at each of the bytes that make up the uInt32 + for(uInt16 i = 0; i < 4; ++i, ++c, ++p) + { + // See if this pixel has changed + if((*c != *p) || theRedrawEntireFrameIndicator) + { + // Can we extend a rectangle or do we have to create a new one? + if((currentCount != 0) && + (currentRectangles[currentCount - 1].color == *c) && + ((currentRectangles[currentCount - 1].x + + currentRectangles[currentCount - 1].width) == (x + i))) + { + currentRectangles[currentCount - 1].width += 1; + } + else + { + currentRectangles[currentCount].x = x + i; + currentRectangles[currentCount].y = y; + currentRectangles[currentCount].width = 1; + currentRectangles[currentCount].height = 1; + currentRectangles[currentCount].color = *c; + currentCount++; + } + } + } + } + } + + // Merge the active and current rectangles flushing any that are of no use + uInt16 activeIndex = 0; + + for(uInt16 t = 0; (t < currentCount) && (activeIndex < activeCount); ++t) + { + Rectangle& current = currentRectangles[t]; + Rectangle& active = activeRectangles[activeIndex]; + + // Can we merge the current rectangle with an active one? + if((current.x == active.x) && (current.width == active.width) && + (current.color == active.color)) + { + current.y = active.y; + current.height = active.height + 1; + + ++activeIndex; + } + // Is it impossible for this active rectangle to be merged? + else if(current.x >= active.x) + { + // Flush the active rectangle + SDL_Rect temp; + + temp.x = active.x * screenMultiple << 1; + temp.y = active.y * screenMultiple; + temp.w = active.width * screenMultiple << 1; + temp.h = active.height * screenMultiple; + + myRectList->add(&temp); + SDL_FillRect(myScreen, &temp, myPalette[active.color]); + + ++activeIndex; + } + } + + // Flush any remaining active rectangles + for(uInt16 s = activeIndex; s < activeCount; ++s) + { + Rectangle& active = activeRectangles[s]; + + SDL_Rect temp; + temp.x = active.x * screenMultiple << 1; + temp.y = active.y * screenMultiple; + temp.w = active.width * screenMultiple << 1; + temp.h = active.height * screenMultiple; + + myRectList->add(&temp); + SDL_FillRect(myScreen, &temp, myPalette[active.color]); + } + + // We can now make the current rectangles into the active rectangles + Rectangle* tmp = currentRectangles; + currentRectangles = activeRectangles; + activeRectangles = tmp; + activeCount = currentCount; + + currentFrame += width; + previousFrame += width; + } + + // Flush any rectangles that are still active + for(uInt16 t = 0; t < activeCount; ++t) + { + Rectangle& active = activeRectangles[t]; + + SDL_Rect temp; + temp.x = active.x * screenMultiple << 1; + temp.y = active.y * screenMultiple; + temp.w = active.width * screenMultiple << 1; + temp.h = active.height * screenMultiple; + + myRectList->add(&temp); + SDL_FillRect(myScreen, &temp, myPalette[active.color]); + } + + // The frame doesn't need to be completely redrawn anymore + theRedrawEntireFrameIndicator = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::preFrameUpdate() +{ + // Start a new rectlist on each display update + myRectList->start(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::postFrameUpdate() +{ + // Now update all the rectangles at once + SDL_UpdateRects(myScreen, myRectList->numRects(), myRectList->rects()); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::drawBoundedBox(uInt32 x, uInt32 y, uInt32 w, uInt32 h) +{ + SDL_Rect tmp; + + // Scale all values to the current window size + x *= theZoomLevel; + y *= theZoomLevel; + w *= theZoomLevel; + h *= theZoomLevel; + + // First draw the underlying box + tmp.x = x; + tmp.y = y; + tmp.w = w; + tmp.h = h; + myRectList->add(&tmp); + SDL_FillRect(myScreen, &tmp, myPalette[myBGColor]); + + // Now draw the bounding sides + tmp.x = x; + tmp.y = y; + tmp.w = w; + tmp.h = theZoomLevel; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); // top + + tmp.x = x; + tmp.y = y + h - theZoomLevel; + tmp.w = w; + tmp.h = theZoomLevel; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); // bottom + + tmp.x = x; + tmp.y = y; + tmp.w = theZoomLevel; + tmp.h = h; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); // left + + tmp.x = x + w - theZoomLevel; + tmp.y = y; + tmp.w = theZoomLevel; + tmp.h = h; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); // right +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::drawText(uInt32 xorig, uInt32 yorig, const string& message) +{ + SDL_Rect tmp; + + uInt8 length = message.length(); + for(uInt32 x = 0; x < length; x++) + { + for(uInt32 y = 0; y < 8; y++) + { + for(uInt32 z = 0; z < 8; z++) + { + char letter = message[x]; + if((ourFontData[(letter << 3) + y] >> z) & 1) + { + tmp.x = ((x<<3) + z + xorig) * theZoomLevel; + tmp.y = (y + yorig) * theZoomLevel; + tmp.w = tmp.h = theZoomLevel; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); + } + } + } + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void FrameBufferSoft::drawChar(uInt32 xorig, uInt32 yorig, uInt32 c) +{ + if(c >= 256 ) + return; + + SDL_Rect tmp; + + for(uInt32 y = 0; y < 8; y++) + { + for(uInt32 z = 0; z < 8; z++) + { + if((ourFontData[(c << 3) + y] >> z) & 1) + { + tmp.x = (z + xorig) * theZoomLevel; + tmp.y = (y + yorig) * theZoomLevel; + tmp.w = tmp.h = theZoomLevel; + SDL_FillRect(myScreen, &tmp, myPalette[myFGColor]); + } + } + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +RectList::RectList(Uint32 size) +{ + currentSize = size; + currentRect = 0; + + rectArray = new SDL_Rect[currentSize]; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +RectList::~RectList() +{ + delete[] rectArray; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RectList::add(SDL_Rect* newRect) +{ + if(currentRect >= currentSize) + { + currentSize = currentSize * 2; + SDL_Rect *temp = new SDL_Rect[currentSize]; + + for(Uint32 i = 0; i < currentRect; ++i) + temp[i] = rectArray[i]; + + delete[] rectArray; + rectArray = temp; + } + + rectArray[currentRect].x = newRect->x; + rectArray[currentRect].y = newRect->y; + rectArray[currentRect].w = newRect->w; + rectArray[currentRect].h = newRect->h; + + ++currentRect; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SDL_Rect* RectList::rects() +{ + return rectArray; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Uint32 RectList::numRects() +{ + return currentRect; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void RectList::start() +{ + currentRect = 0; +} diff --git a/stella/src/common/FrameBufferSoft.hxx b/stella/src/common/FrameBufferSoft.hxx new file mode 100644 index 000000000..7a145e378 --- /dev/null +++ b/stella/src/common/FrameBufferSoft.hxx @@ -0,0 +1,153 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: FrameBufferSoft.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#ifndef FRAMEBUFFER_SOFT_HXX +#define FRAMEBUFFER_SOFT_HXX + +#include +#include + +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "bspf.hxx" + +class Console; +class MediaSource; +class RectList; + + +/** + This class implements an SDL software framebuffer. + + @author Stephen Anthony + @version $Id: FrameBufferSoft.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +*/ +class FrameBufferSoft : public FrameBufferSDL +{ + public: + /** + Creates a new SDL software framebuffer + */ + FrameBufferSoft(); + + /** + Destructor + */ + virtual ~FrameBufferSoft(); + + ////////////////////////////////////////////////////////////////////// + // The following methods are derived from FrameBufferSDL.hxx + ////////////////////////////////////////////////////////////////////// + /** + This routine is called whenever the screen needs to be recreated. + It updates the global screen variable. + */ + virtual bool createScreen(); + + /** + This routine is called to map a given r,g,b triple to the screen palette. + + @param r The red component of the color. + @param g The green component of the color. + @param b The blue component of the color. + */ + virtual Uint32 mapRGB(Uint8 r, Uint8 g, Uint8 b) + { return SDL_MapRGB(myScreen->format, r, g, b); } + + ////////////////////////////////////////////////////////////////////// + // The following methods are derived from FrameBuffer.hxx + ////////////////////////////////////////////////////////////////////// + /** + This routine should be called once the console is created to setup + the video system for us to use. Return false if any operation fails, + otherwise return true. + */ + virtual bool init(); + + /** + This routine should be called anytime the MediaSource needs to be redrawn + to the screen. + */ + virtual void drawMediaSource(); + + /** + This routine should be called to draw a rectangular box with sides + at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param w The width of the box + @param h The height of the box + */ + virtual void drawBoundedBox(uInt32 x, uInt32 y, uInt32 w, uInt32 h); + + /** + This routine should be called to draw text at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param message The message text + */ + virtual void drawText(uInt32 x, uInt32 y, const string& message); + + /** + This routine should be called to draw character 'c' at the specified coordinates. + + @param x The x coordinate + @param y The y coordinate + @param c The character to draw + */ + virtual void drawChar(uInt32 x, uInt32 y, uInt32 c); + + /** + This routine is called before any drawing is done (per-frame). + */ + virtual void preFrameUpdate(); + + /** + This routine is called after any drawing is done (per-frame). + */ + virtual void postFrameUpdate(); + + private: + // Used in the dirty update of the SDL surface + RectList* myRectList; +}; + +/** + + */ +class RectList +{ + public: + RectList(Uint32 size = 512); + ~RectList(); + + void add(SDL_Rect* rect); + + SDL_Rect* rects(); + Uint32 numRects(); + void start(); + + private: + Uint32 currentSize, currentRect; + + SDL_Rect* rectArray; +}; + +#endif diff --git a/stella/src/common/Snapshot.cxx b/stella/src/common/Snapshot.cxx new file mode 100644 index 000000000..74e6fd1fd --- /dev/null +++ b/stella/src/common/Snapshot.cxx @@ -0,0 +1,188 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: Snapshot.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include "bspf.hxx" +#include "FrameBuffer.hxx" +#include "Console.hxx" +#include "MediaSrc.hxx" +#include "Snapshot.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Snapshot::Snapshot(Console* console, MediaSource* mediasrc) + : myConsole(console), + myMediaSource(mediasrc), + palette((png_colorp) NULL) +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Snapshot::~Snapshot() +{ + if(palette) + delete[] palette; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Snapshot::png_write_data(png_structp ctx, png_bytep area, png_size_t size) +{ + ofstream* out = (ofstream *) png_get_io_ptr(ctx); + out->write((const char *)area, size); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Snapshot::png_io_flush(png_structp ctx) +{ + ofstream* out = (ofstream *) png_get_io_ptr(ctx); + out->flush(); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Snapshot::png_user_warn(png_structp ctx, png_const_charp str) +{ + cerr << "Snapshot: libpng warning: " << str << endl; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void Snapshot::png_user_error(png_structp ctx, png_const_charp str) +{ + cerr << "Snapshot: libpng error: " << str << endl; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 Snapshot::savePNG(string filename, uInt32 multiplier) +{ + // FIXME - we shouldn't use the mediasource, but should instead use + // the framebuffer. + // Right now, the snapshot doesn't take into account any special effects + // that the framebuffer may be performing (aspect correction, OpenGL, etc) + // This will require framebuffer support for exporting its actual pixels, + // so it will have to wait ... + + png_structp png_ptr = 0; + png_infop info_ptr = 0; + + uInt8* pixels = myMediaSource->currentFrameBuffer(); + + // PNG and window dimensions will be different because of scaling + uInt32 picWidth = myMediaSource->width() * multiplier << 1; + uInt32 picHeight = myMediaSource->height() * multiplier; + uInt32 width = myMediaSource->width(); + uInt32 height = myMediaSource->height(); + + ofstream* out = new ofstream(filename.c_str(), ios_base::binary); + if(!out) + return 0; + + // Create the palette if it hasn't previously been set + if(!palette) + { + palette = (png_colorp) new png_color[256]; + if(!palette) + { + cerr << "Snapshot: Couldn't allocate memory for PNG palette\n"; + out->close(); + + return 0; + } + + const uInt32* gamePalette = myMediaSource->palette(); + for(uInt32 i = 0; i < 256; ++i) + { + palette[i].red = (uInt8) ((gamePalette[i] & 0x00ff0000) >> 16); + palette[i].green = (uInt8) ((gamePalette[i] & 0x0000ff00) >> 8); + palette[i].blue = (uInt8) (gamePalette[i] & 0x000000ff); + } + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_user_error, png_user_warn); + if(png_ptr == NULL) + { + cerr << "Snapshot: Couldn't allocate memory for PNG file\n"; + return 0; + } + + // Allocate/initialize the image information data. REQUIRED. + info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) + { + cerr << "Snapshot: Couldn't create image information for PNG file\n"; + + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + out->close(); + + return 0; + } + + png_set_write_fn(png_ptr, out, png_write_data, png_io_flush); + + png_set_IHDR(png_ptr, info_ptr, picWidth, picHeight, 8, + PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // Set the 8-bit palette + png_set_PLTE(png_ptr, info_ptr, palette, 256); + + // Write the file header information. REQUIRED + png_write_info(png_ptr, info_ptr); + + // Pack pixels into bytes + png_set_packing(png_ptr); + + // The width has to be scaled by 2 * multiplier. Each pixel must be + // present scaleX times. Each scanline must be present scaleY times. + uInt32 scaleX = multiplier << 1; + uInt32 scaleY = multiplier; + + // Create a buffer to hold the new scanline. + uInt8* newScanline = new uInt8[width * scaleX]; + uInt8* oldScanline; + + // Look at each original scanline + for(uInt32 y = 0; y < height; y++) + { + // First construct a new scanline that is scaled + oldScanline = (uInt8*) pixels + y*width; + + uInt32 px = 0; + for(uInt32 x = 0; x < width; x++) + for(uInt32 offx = 0; offx < scaleX; offx++) + newScanline[px++] = oldScanline[x]; + + // Now output the new scanline 'scaleY' times + for(uInt32 offy = 0; offy < scaleY; offy++) + { + png_bytep row_pointer = (uInt8*) newScanline; + png_write_row(png_ptr, row_pointer); + } + } + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, (png_infopp)NULL); + + if(newScanline) + delete[] newScanline; + + out->close(); + delete out; + + return 1; +} diff --git a/stella/src/common/Snapshot.hxx b/stella/src/common/Snapshot.hxx new file mode 100644 index 000000000..c6d0ff297 --- /dev/null +++ b/stella/src/common/Snapshot.hxx @@ -0,0 +1,75 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: Snapshot.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#ifndef SNAPSHOT_HXX +#define SNAPSHOT_HXX + +class Console; +class MediaSource; + +#include +#include "bspf.hxx" + +class Snapshot +{ + public: + /** + Create a new shapshot class for taking snapshots in PNG format. + + @param console The console + @param mediasrc The mediasource + */ + Snapshot(Console* console, MediaSource* mediasrc); + + /** + The destructor. + */ + ~Snapshot(); + + /** + This routine saves the current frame buffer to a PNG file, + appropriately scaled by the amount specified in 'multiplier'. + + @param filename The filename of the PNG file + @param multiplier The amount that multiplication (zoom level) + + @return The resulting error code + */ + uInt32 savePNG(string filename, uInt32 multiplier = 1); + + private: + static void png_write_data(png_structp ctx, png_bytep area, png_size_t size); + + static void png_io_flush(png_structp ctx); + + static void png_user_warn(png_structp ctx, png_const_charp str); + + static void png_user_error(png_structp ctx, png_const_charp str); + + private: + // The Console for the system + Console* myConsole; + + // The Mediasource for the system + MediaSource* myMediaSource; + + // The PNG palette + png_colorp palette; +}; + +#endif diff --git a/stella/src/common/SoundSDL.cxx b/stella/src/common/SoundSDL.cxx new file mode 100644 index 000000000..7623b5537 --- /dev/null +++ b/stella/src/common/SoundSDL.cxx @@ -0,0 +1,334 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2004 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SoundSDL.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include + +#include "TIASound.h" +#include "Serializer.hxx" +#include "Deserializer.hxx" +#include "System.hxx" + +#include "SoundSDL.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundSDL::SoundSDL(uInt32 fragsize, uInt32 queuesize) + : myCurrentVolume(SDL_MIX_MAXVOLUME), + myFragmentSize(fragsize), + myIsInitializedFlag(false), + myIsMuted(false), + mySampleRate(31400), + mySampleQueue(queuesize) +{ + if(SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) + { + cerr << "WARNING: Couldn't initialize SDL audio system! " << endl; + cerr << " " << SDL_GetError() << endl; + return; + } + else + { + SDL_AudioSpec desired; + desired.freq = mySampleRate; + desired.format = AUDIO_U8; + desired.channels = 1; + desired.samples = myFragmentSize; + desired.callback = callback; + desired.userdata = (void*)this; + + if(SDL_OpenAudio(&desired, &myHardwareSpec) < 0) + { + cerr << "WARNING: Couldn't open SDL audio system! " << endl; + cerr << " " << SDL_GetError() << endl; + return; + } + + // Make sure the sample buffer isn't to big (if it is the sound code + // will not work so we'll need to disable the audio support) + if(((float)myHardwareSpec.samples / (float)myHardwareSpec.freq) >= 0.25) + { + cerr << "WARNING: Audio device doesn't support real time audio! Make "; + cerr << "sure a sound" << endl; + cerr << " server isn't running. Audio is disabled..." << endl; + + SDL_CloseAudio(); + return; + } + + myIsInitializedFlag = true; + myIsMuted = false; + mySampleRate = myHardwareSpec.freq; + myFragmentSize = myHardwareSpec.samples; + + cerr << "Freq: " << (int)myHardwareSpec.freq << endl; + cerr << "Format: " << (int)myHardwareSpec.format << endl; + cerr << "Channels: " << (int)myHardwareSpec.channels << endl; + cerr << "Silence: " << (int)myHardwareSpec.silence << endl; + cerr << "Samples: " << (int)myHardwareSpec.samples << endl; + cerr << "Size: " << (int)myHardwareSpec.size << endl; + + // Now initialize the TIASound object which will actually generate sound + Tia_sound_init(31400, mySampleRate); + + // And start the SDL sound subsystem ... + SDL_PauseAudio(0); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundSDL::~SoundSDL() +{ + if(myIsInitializedFlag) + { + SDL_CloseAudio(); + } + + myIsInitializedFlag = false; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL::isSuccessfullyInitialized() const +{ + return myIsInitializedFlag; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::mute(bool state) +{ + if(myIsInitializedFlag) + { + // Ignore multiple calls to do the same thing + if(myIsMuted == state) + { + return; + } + + myIsMuted = state; + + SDL_PauseAudio(myIsMuted ? 1 : 0); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::setVolume(Int32 percent) +{ + // TODO: Verify that this works... + if(myIsInitializedFlag) + { + if((percent >= 0) && (percent <= 100)) + { + SDL_LockAudio(); + myCurrentVolume = (uInt32)(((float)percent / 100.0) * SDL_MIX_MAXVOLUME); + SDL_UnlockAudio(); + } + else if(percent == -1) // If -1 has been specified, play sound at default volume + { + myCurrentVolume = SDL_MIX_MAXVOLUME; + } + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::update() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::set(uInt16 addr, uInt8 value) +{ + Update_tia_sound(addr, value); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL::save(Serializer& out) +{ + string device = "TIASound"; + + try + { + out.putString(device); + + uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; + + // Only get the TIA sound registers if sound is enabled + if(myIsInitializedFlag) + Tia_get_registers(®1, ®2, ®3, ®4, ®5, ®6); + + out.putLong(reg1); + out.putLong(reg2); + out.putLong(reg3); + out.putLong(reg4); + out.putLong(reg5); + out.putLong(reg6); + + out.putLong(myLastSoundUpdateCycle); + } + catch(char *msg) + { + cerr << msg << endl; + return false; + } + catch(...) + { + cerr << "Unknown error in save state for " << device << endl; + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +bool SoundSDL::load(Deserializer& in) +{ + string device = "TIASound"; + + try + { + if(in.getString() != device) + return false; + + uInt8 reg1 = 0, reg2 = 0, reg3 = 0, reg4 = 0, reg5 = 0, reg6 = 0; + reg1 = (uInt8) in.getLong(); + reg2 = (uInt8) in.getLong(); + reg3 = (uInt8) in.getLong(); + reg4 = (uInt8) in.getLong(); + reg5 = (uInt8) in.getLong(); + reg6 = (uInt8) in.getLong(); + + myLastSoundUpdateCycle = (Int32) in.getLong(); + + // Only update the TIA sound registers if sound is enabled + if(myIsInitializedFlag) + Tia_set_registers(reg1, reg2, reg3, reg4, reg5, reg6); + } + catch(char *msg) + { + cerr << msg << endl; + return false; + } + catch(...) + { + cerr << "Unknown error in load state for " << device << endl; + return false; + } + + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::callback(void* udata, uInt8* stream, int len) +{ + SoundSDL* sound = (SoundSDL*)udata; + + if(sound->isSuccessfullyInitialized()) + { + Tia_process(stream, len); + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundSDL::SampleQueue::SampleQueue(uInt32 capacity) + : myCapacity(capacity), + myBuffer(0), + mySize(0), + myHead(0), + myTail(0) +{ + myBuffer = new uInt8[myCapacity]; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SoundSDL::SampleQueue::~SampleQueue() +{ + delete[] myBuffer; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::SampleQueue::clear() +{ + myHead = myTail = mySize = 0; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundSDL::SampleQueue::dequeue(uInt8* buffer, uInt32 size) +{ + // We can only dequeue up to the number of items in the queue + if(size > mySize) + { + size = mySize; + } + + if((myHead + size) < myCapacity) + { + memcpy((void*)buffer, (const void*)(myBuffer + myHead), size); + myHead += size; + } + else + { + uInt32 s1 = myCapacity - myHead; + uInt32 s2 = size - s1; + memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1); + memcpy((void*)(buffer + s1), (const void*)myBuffer, s2); + myHead = (myHead + size) % myCapacity; + } + + mySize -= size; + + return size; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SoundSDL::SampleQueue::enqueue(uInt8* buffer, uInt32 size) +{ + // If an attempt is made to enqueue more than the queue can hold then + // we'll only enqueue the last myCapacity elements. + if(size > myCapacity) + { + buffer += (size - myCapacity); + size = myCapacity; + } + + if((myTail + size) < myCapacity) + { + memcpy((void*)(myBuffer + myTail), (const void*)buffer, size); + myTail += size; + } + else + { + uInt32 s1 = myCapacity - myTail; + uInt32 s2 = size - s1; + memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1); + memcpy((void*)myBuffer, (const void*)(buffer + s1), s2); + myTail = (myTail + size) % myCapacity; + } + + if((mySize + size) > myCapacity) + { + myHead = (myHead + ((mySize + size) - myCapacity)) % myCapacity; + mySize = myCapacity; + } + else + { + mySize += size; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +uInt32 SoundSDL::SampleQueue::size() const +{ + return mySize; +} diff --git a/stella/src/ui/dos/SndDOS.hxx b/stella/src/common/SoundSDL.hxx similarity index 61% rename from stella/src/ui/dos/SndDOS.hxx rename to stella/src/common/SoundSDL.hxx index f23145584..0d4b3770c 100644 --- a/stella/src/ui/dos/SndDOS.hxx +++ b/stella/src/common/SoundSDL.hxx @@ -13,40 +13,42 @@ // See the file "license" for information on usage and redistribution of // this file, and for a DISCLAIMER OF ALL WARRANTIES. // -// $Id: SndDOS.hxx,v 1.2 2002-11-13 03:47:55 bwmott Exp $ +// $Id: SoundSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ //============================================================================ -#ifndef SOUNDDOS_HXX -#define SOUNDDOS_HXX +#ifndef SOUNDSDL_HXX +#define SOUNDSDL_HXX +#include + +#include "Sound.hxx" #include "bspf.hxx" #include "MediaSrc.hxx" /** - This class implements aa sound class for the DOS front-end. It supports - SoundBlaster compatible sound cards. + This class implements the sound API for SDL. - @author Bradford W. Mott - @version $Id: SndDOS.hxx,v 1.2 2002-11-13 03:47:55 bwmott Exp $ + @author Stephen Anthony and Bradford W. Mott + @version $Id: SoundSDL.hxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ */ -class SoundDOS +class SoundSDL : public Sound { public: /** Create a new sound object */ - SoundDOS(bool activate = true); + SoundSDL(uInt32 fragsize, uInt32 queuesize); /** Destructor */ - virtual ~SoundDOS(); + virtual ~SoundSDL(); public: /** Closes the sound device */ - void close(); + void closeDevice(); /** Return the playback sample rate for the sound device. @@ -71,19 +73,41 @@ class SoundDOS /** Sets the volume of the sound device to the specified level. The - volume is given as a precentage from 0 to 100. + volume is given as a percentage from 0 to 100. A -1 indicates + that the volume shouldn't be changed at all. - @param volume The new volume for the sound device + @param percent The new volume percentage level for the sound device */ - void setSoundVolume(uInt32 volume); + void setVolume(Int32 percent); /** - Update the sound device using the audio sample from the specified - media source. - - @param mediaSource The media source to get audio samples from. + Generates audio samples to fill the sample queue. */ - void updateSound(MediaSource& mediaSource); + void update(); + + /** + Sets the sound register to a given value. + + @param addr The register address + @param value The value to save into the register + */ + void set(uInt16 addr, uInt8 value); + + /** + Saves the current state of this device to the given Serializer. + + @param out The serializer device to save to. + @return The result of the save. True on success, false on failure. + */ + bool save(Serializer& out); + + /** + Loads the current state of this device from the given Deserializer. + + @param in The deserializer device to load from. + @return The result of the load. True on success, false on failure. + */ + bool load(Deserializer& in); private: /** @@ -132,12 +156,19 @@ class SoundDOS */ uInt32 size() const; + /** + Answers the maximum number of samples the queue can hold. + + @return The maximum number of samples in the queue. + */ + uInt32 capacity() const { return myCapacity; } + private: const uInt32 myCapacity; uInt8* myBuffer; - volatile uInt32 mySize; - volatile uInt32 myHead; - volatile uInt32 myTail; + uInt32 mySize; + uInt32 myHead; + uInt32 myTail; }; private: @@ -147,25 +178,27 @@ class SoundDOS // SDL fragment size uInt32 myFragmentSize; + // Audio specification structure + SDL_AudioSpec myHardwareSpec; + // Indicates if the sound device was successfully initialized bool myIsInitializedFlag; - // Mutex - bool myUpdateLock; - bool myCallbackLock; - // Indicates if the sound is currently muted bool myIsMuted; // DSP sample rate uInt32 mySampleRate; + // The sample queue size (which is auto-adapting) + uInt32 mySampleQueueSize; + // Queue which holds samples from the media source before they are played SampleQueue mySampleQueue; private: - // Callback function invoked by the sound library when it needs data - static void callback(void* udata, void* stream, int len); + // Callback function invoked by the SDL Audio library when it needs data + static void callback(void* udata, uInt8* stream, int len); }; -#endif +#endif diff --git a/stella/src/common/mainSDL.cxx b/stella/src/common/mainSDL.cxx new file mode 100644 index 000000000..3cc9b5897 --- /dev/null +++ b/stella/src/common/mainSDL.cxx @@ -0,0 +1,992 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-2002 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: mainSDL.cxx,v 1.1 2004-05-24 17:18:22 stephena Exp $ +//============================================================================ + +#include +#include +#include +#include +#include + +#ifdef HAVE_GETTIMEOFDAY + #include + #include +#endif + +#include + +#include "bspf.hxx" +#include "Console.hxx" +#include "Event.hxx" +#include "StellaEvent.hxx" +#include "EventHandler.hxx" +#include "FrameBuffer.hxx" +#include "FrameBufferSDL.hxx" +#include "FrameBufferSoft.hxx" +#include "PropsSet.hxx" +#include "Sound.hxx" +#include "SoundSDL.hxx" +#include "Settings.hxx" + +#ifdef DISPLAY_OPENGL + #include "FrameBufferGL.hxx" + + // Indicates whether to use OpenGL mode + static bool theUseOpenGLFlag; +#endif + +#if defined(UNIX) + #include "SettingsUNIX.hxx" +#elif defined(WIN32) + #include "SettingsWin32.hxx" +#else + #error Unsupported platform! +#endif + +static void cleanup(); +static bool setupJoystick(); +static void handleEvents(); +static uInt32 getTicks(); +static bool setupProperties(PropertiesSet& set); + +#ifdef JOYSTICK_SUPPORT +// static uInt32 thePaddleNumber; + + // Lookup table for joystick numbers and events + StellaEvent::JoyStick joyList[StellaEvent::LastJSTICK] = { + StellaEvent::JSTICK_0, StellaEvent::JSTICK_1, StellaEvent::JSTICK_2, + StellaEvent::JSTICK_3, StellaEvent::JSTICK_5, StellaEvent::JSTICK_5 + }; + StellaEvent::JoyCode joyButtonList[StellaEvent::LastJCODE] = { + StellaEvent::JBUTTON_0, StellaEvent::JBUTTON_1, StellaEvent::JBUTTON_2, + StellaEvent::JBUTTON_3, StellaEvent::JBUTTON_4, StellaEvent::JBUTTON_5, + StellaEvent::JBUTTON_6, StellaEvent::JBUTTON_7, StellaEvent::JBUTTON_8, + StellaEvent::JBUTTON_9, StellaEvent::JBUTTON_10, StellaEvent::JBUTTON_11, + StellaEvent::JBUTTON_12, StellaEvent::JBUTTON_13, StellaEvent::JBUTTON_14, + StellaEvent::JBUTTON_15, StellaEvent::JBUTTON_16, StellaEvent::JBUTTON_17, + StellaEvent::JBUTTON_18, StellaEvent::JBUTTON_19 + }; + + enum JoyType { JT_NONE, JT_REGULAR, JT_STELLADAPTOR_1, JT_STELLADAPTOR_2 }; + + struct Stella_Joystick + { + SDL_Joystick* stick; + JoyType type; + }; + + static Stella_Joystick theJoysticks[StellaEvent::LastJSTICK]; + + // Static lookup tables for Stelladaptor axis support + static Event::Type SA_Axis[2][2][3] = { + Event::JoystickZeroLeft, Event::JoystickZeroRight, Event::PaddleZeroResistance, + Event::JoystickZeroUp, Event::JoystickZeroDown, Event::PaddleOneResistance, + Event::JoystickOneLeft, Event::JoystickOneRight, Event::PaddleTwoResistance, + Event::JoystickOneUp, Event::JoystickOneDown, Event::PaddleThreeResistance + }; +#endif + +// Pointer to the console object or the null pointer +static Console* theConsole = (Console*) NULL; + +// Pointer to the display object or the null pointer +static FrameBufferSDL* theDisplay = (FrameBufferSDL*) NULL; + +// Pointer to the sound object or the null pointer +static Sound* theSound = (Sound*) NULL; + +// Pointer to the settings object or the null pointer +static Settings* theSettings = (Settings*) NULL; + +// Indicates if the mouse should be grabbed +static bool theGrabMouseIndicator = false; + +// Indicates if the mouse cursor should be hidden +static bool theHideCursorIndicator = false; + +// Indicates the current paddle mode +static Int32 thePaddleMode; + +// Indicates relative mouse position horizontally +static Int32 mouseX = 0; + +// Indicates whether to show information during program execution +static bool theShowInfoFlag; + +struct Switches +{ + SDLKey scanCode; + StellaEvent::KeyCode keyCode; +}; + +// Place the most used keys first to speed up access +// Todo - initialize this array in the same order as the SDLK +// keys are defined, so it can be a constant-time LUT +static Switches keyList[] = { + { SDLK_F1, StellaEvent::KCODE_F1 }, + { SDLK_F2, StellaEvent::KCODE_F2 }, + { SDLK_F3, StellaEvent::KCODE_F3 }, + { SDLK_F4, StellaEvent::KCODE_F4 }, + { SDLK_F5, StellaEvent::KCODE_F5 }, + { SDLK_F6, StellaEvent::KCODE_F6 }, + { SDLK_F7, StellaEvent::KCODE_F7 }, + { SDLK_F8, StellaEvent::KCODE_F8 }, + { SDLK_F9, StellaEvent::KCODE_F9 }, + { SDLK_F10, StellaEvent::KCODE_F10 }, + { SDLK_F11, StellaEvent::KCODE_F11 }, + { SDLK_F12, StellaEvent::KCODE_F12 }, + { SDLK_F13, StellaEvent::KCODE_F13 }, + { SDLK_F14, StellaEvent::KCODE_F14 }, + { SDLK_F15, StellaEvent::KCODE_F15 }, + + { SDLK_UP, StellaEvent::KCODE_UP }, + { SDLK_DOWN, StellaEvent::KCODE_DOWN }, + { SDLK_LEFT, StellaEvent::KCODE_LEFT }, + { SDLK_RIGHT, StellaEvent::KCODE_RIGHT }, + { SDLK_SPACE, StellaEvent::KCODE_SPACE }, + { SDLK_LCTRL, StellaEvent::KCODE_LCTRL }, + { SDLK_RCTRL, StellaEvent::KCODE_RCTRL }, + { SDLK_LALT, StellaEvent::KCODE_LALT }, + { SDLK_RALT, StellaEvent::KCODE_RALT }, + { SDLK_LSUPER, StellaEvent::KCODE_LWIN }, + { SDLK_RSUPER, StellaEvent::KCODE_RWIN }, + { SDLK_MENU, StellaEvent::KCODE_MENU }, + + { SDLK_a, StellaEvent::KCODE_a }, + { SDLK_b, StellaEvent::KCODE_b }, + { SDLK_c, StellaEvent::KCODE_c }, + { SDLK_d, StellaEvent::KCODE_d }, + { SDLK_e, StellaEvent::KCODE_e }, + { SDLK_f, StellaEvent::KCODE_f }, + { SDLK_g, StellaEvent::KCODE_g }, + { SDLK_h, StellaEvent::KCODE_h }, + { SDLK_i, StellaEvent::KCODE_i }, + { SDLK_j, StellaEvent::KCODE_j }, + { SDLK_k, StellaEvent::KCODE_k }, + { SDLK_l, StellaEvent::KCODE_l }, + { SDLK_m, StellaEvent::KCODE_m }, + { SDLK_n, StellaEvent::KCODE_n }, + { SDLK_o, StellaEvent::KCODE_o }, + { SDLK_p, StellaEvent::KCODE_p }, + { SDLK_q, StellaEvent::KCODE_q }, + { SDLK_r, StellaEvent::KCODE_r }, + { SDLK_s, StellaEvent::KCODE_s }, + { SDLK_t, StellaEvent::KCODE_t }, + { SDLK_u, StellaEvent::KCODE_u }, + { SDLK_v, StellaEvent::KCODE_v }, + { SDLK_w, StellaEvent::KCODE_w }, + { SDLK_x, StellaEvent::KCODE_x }, + { SDLK_y, StellaEvent::KCODE_y }, + { SDLK_z, StellaEvent::KCODE_z }, + + { SDLK_0, StellaEvent::KCODE_0 }, + { SDLK_1, StellaEvent::KCODE_1 }, + { SDLK_2, StellaEvent::KCODE_2 }, + { SDLK_3, StellaEvent::KCODE_3 }, + { SDLK_4, StellaEvent::KCODE_4 }, + { SDLK_5, StellaEvent::KCODE_5 }, + { SDLK_6, StellaEvent::KCODE_6 }, + { SDLK_7, StellaEvent::KCODE_7 }, + { SDLK_8, StellaEvent::KCODE_8 }, + { SDLK_9, StellaEvent::KCODE_9 }, + + { SDLK_KP0, StellaEvent::KCODE_KP0 }, + { SDLK_KP1, StellaEvent::KCODE_KP1 }, + { SDLK_KP2, StellaEvent::KCODE_KP2 }, + { SDLK_KP3, StellaEvent::KCODE_KP3 }, + { SDLK_KP4, StellaEvent::KCODE_KP4 }, + { SDLK_KP5, StellaEvent::KCODE_KP5 }, + { SDLK_KP6, StellaEvent::KCODE_KP6 }, + { SDLK_KP7, StellaEvent::KCODE_KP7 }, + { SDLK_KP8, StellaEvent::KCODE_KP8 }, + { SDLK_KP9, StellaEvent::KCODE_KP9 }, + { SDLK_KP_PERIOD, StellaEvent::KCODE_KP_PERIOD }, + { SDLK_KP_DIVIDE, StellaEvent::KCODE_KP_DIVIDE }, + { SDLK_KP_MULTIPLY, StellaEvent::KCODE_KP_MULTIPLY}, + { SDLK_KP_MINUS, StellaEvent::KCODE_KP_MINUS }, + { SDLK_KP_PLUS, StellaEvent::KCODE_KP_PLUS }, + { SDLK_KP_ENTER, StellaEvent::KCODE_KP_ENTER }, + { SDLK_KP_EQUALS, StellaEvent::KCODE_KP_EQUALS }, + + { SDLK_BACKSPACE, StellaEvent::KCODE_BACKSPACE }, + { SDLK_TAB, StellaEvent::KCODE_TAB }, + { SDLK_CLEAR, StellaEvent::KCODE_CLEAR }, + { SDLK_RETURN, StellaEvent::KCODE_RETURN }, + { SDLK_ESCAPE, StellaEvent::KCODE_ESCAPE }, + { SDLK_COMMA, StellaEvent::KCODE_COMMA }, + { SDLK_MINUS, StellaEvent::KCODE_MINUS }, + { SDLK_PERIOD, StellaEvent::KCODE_PERIOD }, + { SDLK_SLASH, StellaEvent::KCODE_SLASH }, + { SDLK_BACKSLASH, StellaEvent::KCODE_BACKSLASH }, + { SDLK_SEMICOLON, StellaEvent::KCODE_SEMICOLON }, + { SDLK_EQUALS, StellaEvent::KCODE_EQUALS }, + { SDLK_QUOTE, StellaEvent::KCODE_QUOTE }, + { SDLK_BACKQUOTE, StellaEvent::KCODE_BACKQUOTE }, + { SDLK_LEFTBRACKET, StellaEvent::KCODE_LEFTBRACKET}, + { SDLK_RIGHTBRACKET,StellaEvent::KCODE_RIGHTBRACKET}, + + { SDLK_PRINT, StellaEvent::KCODE_PRTSCREEN }, + { SDLK_MODE, StellaEvent::KCODE_SCRLOCK }, + { SDLK_PAUSE, StellaEvent::KCODE_PAUSE }, + { SDLK_INSERT, StellaEvent::KCODE_INSERT }, + { SDLK_HOME, StellaEvent::KCODE_HOME }, + { SDLK_PAGEUP, StellaEvent::KCODE_PAGEUP }, + { SDLK_DELETE, StellaEvent::KCODE_DELETE }, + { SDLK_END, StellaEvent::KCODE_END }, + { SDLK_PAGEDOWN, StellaEvent::KCODE_PAGEDOWN } + }; + +/** + Returns number of ticks in microseconds +*/ +#ifdef HAVE_GETTIMEOFDAY +inline uInt32 getTicks() +{ + timeval now; + gettimeofday(&now, 0); + + return (uInt32) (now.tv_sec * 1000000 + now.tv_usec); +} +#else +inline uInt32 getTicks() +{ + return (uInt32) SDL_GetTicks() * 1000; +} +#endif + + +/** + This routine should be called once setupDisplay is called + to create the joystick stuff. +*/ +bool setupJoystick() +{ +#ifdef JOYSTICK_SUPPORT + // Keep track of how many Stelladaptors we've found + uInt8 saCount = 0; + + // First clear the joystick array + for(uInt32 i = 0; i < StellaEvent::LastJSTICK; i++) + { + theJoysticks[i].stick = (SDL_Joystick*) NULL; + theJoysticks[i].type = JT_NONE; + } + + // Initialize the joystick subsystem + if((SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1) || (SDL_NumJoysticks() <= 0)) + { + if(theShowInfoFlag) + cout << "No joysticks present, use the keyboard.\n"; + + return true; + } + + // Try to open 4 regular joysticks and 2 Stelladaptor devices + uInt32 limit = SDL_NumJoysticks() <= StellaEvent::LastJSTICK ? + SDL_NumJoysticks() : StellaEvent::LastJSTICK; + for(uInt32 i = 0; i < limit; i++) + { + string name = SDL_JoystickName(i); + theJoysticks[i].stick = SDL_JoystickOpen(i); + + // Skip if we couldn't open it for any reason + if(theJoysticks[i].stick == NULL) + continue; + + // Figure out what type of joystick this is + if(name.find("Stelladaptor", 0) != string::npos) + { + saCount++; + if(saCount > 2) // Ignore more than 2 Stelladaptors + { + theJoysticks[i].type = JT_NONE; + continue; + } + else if(saCount == 1) + { + name = "Left Stelladaptor (Left joystick, Paddles 0 and 1, Left driving controller)"; + theJoysticks[i].type = JT_STELLADAPTOR_1; + } + else if(saCount == 2) + { + name = "Right Stelladaptor (Right joystick, Paddles 2 and 3, Right driving controller)"; + theJoysticks[i].type = JT_STELLADAPTOR_2; + } + + if(theShowInfoFlag) + cout << "Joystick " << i << ": " << name << endl; + } + else + { + theJoysticks[i].type = JT_REGULAR; + if(theShowInfoFlag) + { + cout << "Joystick " << i << ": " << SDL_JoystickName(i) + << " with " << SDL_JoystickNumButtons(theJoysticks[i].stick) + << " buttons.\n"; + } + } + } +#endif + + return true; +} + + +/** + This routine should be called regularly to handle events +*/ +void handleEvents() +{ + SDL_Event event; + Uint8 type; + SDLKey key; + SDLMod mod; + + // Check for an event + while(SDL_PollEvent(&event)) + { + // keyboard events + if(event.type == SDL_KEYDOWN) + { + key = event.key.keysym.sym; + mod = event.key.keysym.mod; + type = event.type; + + // An attempt to speed up event processing + // All SDL-specific event actions are accessed by either + // Control or Alt keys. So we quickly check for those. + if(mod & KMOD_ALT) + { + if(key == SDLK_EQUALS) + theDisplay->resize(1); + else if(key == SDLK_MINUS) + theDisplay->resize(-1); + else if(key == SDLK_RETURN) + { + theDisplay->toggleFullscreen(); + } +#ifdef DISPLAY_OPENGL + else if(key == SDLK_f && theUseOpenGLFlag) + ((FrameBufferGL*)theDisplay)->toggleFilter(); +#endif +#ifdef DEVELOPER_SUPPORT + else if(key == SDLK_END) // Alt-End increases XStart + { + theConsole->changeXStart(1); + theDisplay->resize(0); + } + else if(key == SDLK_HOME) // Alt-Home decreases XStart + { + theConsole->changeXStart(0); + theDisplay->resize(0); + } + else if(key == SDLK_PAGEUP) // Alt-PageUp increases YStart + { + theConsole->changeYStart(1); + theDisplay->resize(0); + } + else if(key == SDLK_PAGEDOWN) // Alt-PageDown decreases YStart + { + theConsole->changeYStart(0); + theDisplay->resize(0); + } +#endif + } + else if(mod & KMOD_CTRL) + { + if(key == SDLK_g) + { + // don't change grabmouse in fullscreen mode + if(!theDisplay->fullScreen()) + { + theGrabMouseIndicator = !theGrabMouseIndicator; + theSettings->setBool("grabmouse", theGrabMouseIndicator); + theDisplay->grabMouse(theGrabMouseIndicator); + } + } + else if(key == SDLK_h) + { + // don't change hidecursor in fullscreen mode + if(!theDisplay->fullScreen()) + { + theHideCursorIndicator = !theHideCursorIndicator; + theSettings->setBool("hidecursor", theHideCursorIndicator); + theDisplay->showCursor(!theHideCursorIndicator); + } + } +#ifdef DEVELOPER_SUPPORT + else if(key == SDLK_f) // Ctrl-f toggles NTSC/PAL mode + { + theConsole->toggleFormat(); + theDisplay->setupPalette(); + } + else if(key == SDLK_p) // Ctrl-p toggles different palettes + { + theConsole->togglePalette(); + theDisplay->setupPalette(); + } + else if(key == SDLK_END) // Ctrl-End increases Width + { + theConsole->changeWidth(1); + theDisplay->resize(0); + } + else if(key == SDLK_HOME) // Ctrl-Home decreases Width + { + theConsole->changeWidth(0); + theDisplay->resize(0); + } + else if(key == SDLK_PAGEUP) // Ctrl-PageUp increases Height + { + theConsole->changeHeight(1); + theDisplay->resize(0); + } + else if(key == SDLK_PAGEDOWN) // Ctrl-PageDown decreases Height + { + theConsole->changeHeight(0); + theDisplay->resize(0); + } + else if(key == SDLK_s) // Ctrl-s saves properties to a file + { + if(theConsole->settings().getBool("mergeprops")) // Attempt to merge with propertiesSet + { + theConsole->saveProperties(theSettings->userPropertiesFilename(), true); + } + else // Save to file in home directory + { + string newPropertiesFile = theConsole->settings().baseDir() + "/" + \ + theConsole->properties().get("Cartridge.Name") + ".pro"; + replace(newPropertiesFile.begin(), newPropertiesFile.end(), ' ', '_'); + theConsole->saveProperties(newPropertiesFile); + } + } +#endif + } + else // check all the other keys + { + for(unsigned int i = 0; i < sizeof(keyList) / sizeof(Switches); ++i) + { + if(keyList[i].scanCode == key) + theConsole->eventHandler().sendKeyEvent(keyList[i].keyCode, 1); + } + } + } + else if(event.type == SDL_KEYUP) + { + key = event.key.keysym.sym; + type = event.type; + + for(unsigned int i = 0; i < sizeof(keyList) / sizeof(Switches); ++i) + { + if(keyList[i].scanCode == key) + theConsole->eventHandler().sendKeyEvent(keyList[i].keyCode, 0); + } + } + else if(event.type == SDL_MOUSEMOTION) + { + Int32 resistance; + uInt32 zoom = theDisplay->zoomLevel(); + Int32 width = theDisplay->width() * zoom; + Event::Type type = Event::NoType; + + // Grabmouse and hidecursor introduce some lag into the mouse movement, + // so we need to fudge the numbers a bit + if(theGrabMouseIndicator && theHideCursorIndicator) + { + mouseX = (int)((float)mouseX + (float)event.motion.xrel + * 1.5 * (float) zoom); + } + else + { + mouseX = mouseX + event.motion.xrel * zoom; + } + + // Check to make sure mouseX is within the game window + if(mouseX < 0) + mouseX = 0; + else if(mouseX > width) + mouseX = width; + + resistance = (Int32)(1000000.0 * (width - mouseX) / width); + + // Now, set the event of the correct paddle to the calculated resistance + if(thePaddleMode == 0) + type = Event::PaddleZeroResistance; + else if(thePaddleMode == 1) + type = Event::PaddleOneResistance; + else if(thePaddleMode == 2) + type = Event::PaddleTwoResistance; + else if(thePaddleMode == 3) + type = Event::PaddleThreeResistance; + + theConsole->eventHandler().sendEvent(type, resistance); + } + else if(event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) + { + Event::Type type = Event::LastType; + Int32 value; + + if(event.type == SDL_MOUSEBUTTONDOWN) + value = 1; + else + value = 0; + + if(thePaddleMode == 0) + type = Event::PaddleZeroFire; + else if(thePaddleMode == 1) + type = Event::PaddleOneFire; + else if(thePaddleMode == 2) + type = Event::PaddleTwoFire; + else if(thePaddleMode == 3) + type = Event::PaddleThreeFire; + + theConsole->eventHandler().sendEvent(type, value); + } + else if(event.type == SDL_ACTIVEEVENT) + { + if((event.active.state & SDL_APPACTIVE) && (event.active.gain == 0)) + { + if(!theConsole->eventHandler().doPause()) + { + theConsole->eventHandler().sendEvent(Event::Pause, 1); + } + } + } + else if(event.type == SDL_QUIT) + { + theConsole->eventHandler().sendEvent(Event::Quit, 1); + } + else if(event.type == SDL_VIDEOEXPOSE) + { + theDisplay->refresh(); + } + +#ifdef JOYSTICK_SUPPORT + // Read joystick events and modify event states + StellaEvent::JoyStick stick; + StellaEvent::JoyCode code; + Int32 state; + Uint8 axis; + Uint8 button; + Int32 resistance; + Sint16 value; + JoyType type; + + if(event.jbutton.which >= StellaEvent::LastJSTICK) + return; + + stick = joyList[event.jbutton.which]; + type = theJoysticks[event.jbutton.which].type; + + // Figure put what type of joystick we're dealing with + // Stelladaptors behave differently, and can't be remapped + switch(type) + { + case JT_NONE: + break; + + case JT_REGULAR: + + if((event.type == SDL_JOYBUTTONDOWN) || (event.type == SDL_JOYBUTTONUP)) + { + if(event.jbutton.button >= StellaEvent::LastJCODE) + return; + + code = joyButtonList[event.jbutton.button]; + state = event.jbutton.state == SDL_PRESSED ? 1 : 0; + + theConsole->eventHandler().sendJoyEvent(stick, code, state); + } + else if(event.type == SDL_JOYAXISMOTION) + { + axis = event.jaxis.axis; + value = event.jaxis.value; + + if(axis == 0) // x-axis + { + theConsole->eventHandler().sendJoyEvent(stick, StellaEvent::JAXIS_LEFT, + (value < -16384) ? 1 : 0); + theConsole->eventHandler().sendJoyEvent(stick, StellaEvent::JAXIS_RIGHT, + (value > 16384) ? 1 : 0); + } + else if(axis == 1) // y-axis + { + theConsole->eventHandler().sendJoyEvent(stick, StellaEvent::JAXIS_UP, + (value < -16384) ? 1 : 0); + theConsole->eventHandler().sendJoyEvent(stick, StellaEvent::JAXIS_DOWN, + (value > 16384) ? 1 : 0); + } + } + break; // Regular joystick + + case JT_STELLADAPTOR_1: + case JT_STELLADAPTOR_2: + + if((event.type == SDL_JOYBUTTONDOWN) || (event.type == SDL_JOYBUTTONUP)) + { + button = event.jbutton.button; + state = event.jbutton.state == SDL_PRESSED ? 1 : 0; + + // Send button events for the joysticks/paddles + if(button == 0) + { + if(type == JT_STELLADAPTOR_1) + { + theConsole->eventHandler().sendEvent(Event::JoystickZeroFire, state); + theConsole->eventHandler().sendEvent(Event::PaddleZeroFire, state); + } + else + { + theConsole->eventHandler().sendEvent(Event::JoystickOneFire, state); + theConsole->eventHandler().sendEvent(Event::PaddleTwoFire, state); + } + } + else if(button == 1) + { + if(type == JT_STELLADAPTOR_1) + theConsole->eventHandler().sendEvent(Event::PaddleOneFire, state); + else + theConsole->eventHandler().sendEvent(Event::PaddleThreeFire, state); + } + } + else if(event.type == SDL_JOYAXISMOTION) + { + axis = event.jaxis.axis; + value = event.jaxis.value; + + // Send axis events for the joysticks + theConsole->eventHandler().sendEvent(SA_Axis[type-2][axis][0], + (value < -16384) ? 1 : 0); + theConsole->eventHandler().sendEvent(SA_Axis[type-2][axis][1], + (value > 16384) ? 1 : 0); + + // Send axis events for the paddles + resistance = (Int32) (1000000.0 * (32767 - value) / 65534); + theConsole->eventHandler().sendEvent(SA_Axis[type-2][axis][2], resistance); + } + break; // Stelladaptor joystick + + default: + break; + } +#endif + } +} + + +/** + Setup the properties set by first checking for a user file + "$HOME/.stella/stella.pro", then a system-wide file "/etc/stella.pro". + Return false if neither file is found, else return true. + + @param set The properties set to setup +*/ +bool setupProperties(PropertiesSet& set) +{ + bool useMemList = false; + string theAlternateProFile = theSettings->getString("altpro"); + string theUserProFile = theSettings->userPropertiesFilename(); + string theSystemProFile = theSettings->systemPropertiesFilename(); + +#ifdef DEVELOPER_SUPPORT + // If the user wishes to merge any property modifications to the + // PropertiesSet file, then the PropertiesSet file MUST be loaded + // into memory. + useMemList = theSettings->getBool("mergeprops"); +#endif + + // Check to see if the user has specified an alternate .pro file. + + if(theAlternateProFile != "") + { + set.load(theAlternateProFile, &Console::defaultProperties(), useMemList); + return true; + } + + if(theUserProFile != "") + { + set.load(theUserProFile, &Console::defaultProperties(), useMemList); + return true; + } + else if(theSystemProFile != "") + { + set.load(theSystemProFile, &Console::defaultProperties(), useMemList); + return true; + } + else + { + set.load("", &Console::defaultProperties(), false); + return true; + } +} + + +/** + Does general cleanup in case any operation failed (or at end of program). +*/ +void cleanup() +{ +#ifdef JOYSTICK_SUPPORT + for(uInt32 i = 0; i < StellaEvent::LastJSTICK; i++) + { + if(SDL_JoystickOpened(i)) + SDL_JoystickClose(theJoysticks[i].stick); + } +#endif + + if(theSettings) + delete theSettings; + + if(theConsole) + delete theConsole; + + if(theSound) + delete theSound; + + if(theDisplay) + delete theDisplay; + + SDL_Quit(); +} + + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +int main(int argc, char* argv[]) +{ +#if defined(UNIX) + theSettings = new SettingsUNIX(); +#elif defined(WIN32) + theSettings = new SettingsWin32(); +#else + #error Unsupported platform! +#endif + if(!theSettings) + { + cleanup(); + return 0; + } + theSettings->loadConfig(); + + // Take care of commandline arguments + if(!theSettings->loadCommandLine(argc, argv)) + { + string message = "Stella version 1.4_cvs\n\nUsage: stella [options ...] romfile"; + theSettings->usage(message); + cleanup(); + return 0; + } + + // Cache some settings so they don't have to be repeatedly searched for + thePaddleMode = theSettings->getInt("paddle"); + theShowInfoFlag = theSettings->getBool("showinfo"); + theGrabMouseIndicator = theSettings->getBool("grabmouse"); + theHideCursorIndicator = theSettings->getBool("hidecursor"); + + // Request that the SDL window be centered, if possible + putenv("SDL_VIDEO_CENTERED=1"); + + // Get a pointer to the file which contains the cartridge ROM + const char* file = argv[argc - 1]; + + // Open the cartridge image and read it in + ifstream in(file, ios_base::binary); + if(!in) + { + cerr << "ERROR: Couldn't open " << file << "..." << endl; + cleanup(); + return 0; + } + + uInt8* image = new uInt8[512 * 1024]; + in.read((char*)image, 512 * 1024); + uInt32 size = in.gcount(); + in.close(); + + // Create a properties set for us to use and set it up + PropertiesSet propertiesSet; + if(!setupProperties(propertiesSet)) + { + delete[] image; + cleanup(); + return 0; + } + + // Create an SDL window + string videodriver = theSettings->getString("video"); + if(videodriver == "soft") + { + theDisplay = new FrameBufferSoft(); + if(theShowInfoFlag) + cout << "Using software mode for video.\n"; + } +#ifdef DISPLAY_OPENGL + else if(videodriver == "gl") + { + theDisplay = new FrameBufferGL(); + theUseOpenGLFlag = true; + if(theShowInfoFlag) + cout << "Using OpenGL mode for video.\n"; + } +#endif + else // a driver that doesn't exist was requested, so use software mode + { + theDisplay = new FrameBufferSoft(); + if(theShowInfoFlag) + cout << "Using software mode for video.\n"; + } + + if(!theDisplay) + { + cerr << "ERROR: Couldn't set up display.\n"; + delete[] image; + cleanup(); + return 0; + } + + // Create a sound object for playing audio + if(theSettings->getBool("sound")) + { + uInt32 fragsize = theSettings->getInt("fragsize"); + uInt32 bufsize = theSettings->getInt("bufsize"); + theSound = new SoundSDL(fragsize, bufsize); + if(theShowInfoFlag) + { + cout << "Sound enabled, using fragment size = " << fragsize; + cout << " and buffer size = " << bufsize << "." << endl; + } + } + else // even if sound has been disabled, we still need a sound object + { + theSound = new Sound(); + if(theShowInfoFlag) + cout << "Sound disabled.\n"; + } + theSound->setVolume(theSettings->getInt("volume")); + + // Get just the filename of the file containing the ROM image + const char* filename = (!strrchr(file, '/')) ? file : strrchr(file, '/') + 1; + + // Create the 2600 game console + theConsole = new Console(image, size, filename, *theSettings, propertiesSet, + *theDisplay, *theSound); + + // Free the image since we don't need it any longer + delete[] image; + + // Setup the SDL joysticks + // This must be done after the console is created + if(!setupJoystick()) + { + cerr << "ERROR: Couldn't set up joysticks.\n"; + cleanup(); + return 0; + } + + // These variables are common to both timing options + // and are needed to calculate the overall frames per second. + uInt32 frameTime = 0, numberOfFrames = 0; + + if(theSettings->getBool("accurate")) // normal, CPU-intensive timing + { + // Set up accurate timing stuff + uInt32 startTime, delta; + uInt32 timePerFrame = (uInt32)(1000000.0 / (double)theSettings->getInt("framerate")); + + // Set the base for the timers + frameTime = 0; + + // Main game loop + for(;;) + { + // Exit if the user wants to quit + if(theConsole->eventHandler().doQuit()) + { + break; + } + + startTime = getTicks(); + handleEvents(); + theConsole->update(); + + // Now, waste time if we need to so that we are at the desired frame rate + for(;;) + { + delta = getTicks() - startTime; + + if(delta >= timePerFrame) + break; + } + + frameTime += getTicks() - startTime; + ++numberOfFrames; + } + } + else // less accurate, less CPU-intensive timing + { + // Set up less accurate timing stuff + uInt32 startTime, virtualTime, currentTime; + uInt32 timePerFrame = (uInt32)(1000000.0 / (double)theSettings->getInt("framerate")); + + // Set the base for the timers + virtualTime = getTicks(); + frameTime = 0; + + // Main game loop + for(;;) + { + // Exit if the user wants to quit + if(theConsole->eventHandler().doQuit()) + { + break; + } + + startTime = getTicks(); + handleEvents(); + theConsole->update(); + + currentTime = getTicks(); + virtualTime += timePerFrame; + if(currentTime < virtualTime) + { + SDL_Delay((virtualTime - currentTime)/1000); + } + + currentTime = getTicks() - startTime; + frameTime += currentTime; + ++numberOfFrames; + } + } + + if(theShowInfoFlag) + { + double executionTime = (double) frameTime / 1000000.0; + double framesPerSecond = (double) numberOfFrames / executionTime; + + cout << endl; + cout << numberOfFrames << " total frames drawn\n"; + cout << framesPerSecond << " frames/second\n"; + cout << endl; + cout << "Cartridge Name: " << theConsole->properties().get("Cartridge.Name"); + cout << endl; + cout << "Cartridge MD5: " << theConsole->properties().get("Cartridge.MD5"); + cout << endl << endl; + } + + // Cleanup time ... + cleanup(); + return 0; +} diff --git a/stella/src/common/stella.xpm b/stella/src/common/stella.xpm new file mode 100644 index 000000000..5b4875975 --- /dev/null +++ b/stella/src/common/stella.xpm @@ -0,0 +1,38 @@ +/* XPM */ +static char * stella_icon[] = { +"32 32 3 1", +" c None", +". c #000000", +"+ c #FFFFFF", +" ", +" ", +" ", +" ", +" .............. ", +" .............. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ..++..++..++.. ", +" ....++..++..++.... ", +" ....++..++..++.... ", +" ....++++..++..++++.... ", +" ....++++..++..++++.... ", +" ..++++....++....++++.. ", +" ..++++....++....++++.. ", +" ......++......++......++...... ", +" ......++......++......++...... ", +" ..++++++.. ..++.. ..++++++.. ", +" ..++++++.. ..++.. ..++++++.. ", +" ..++++.... ..++.. ....++++.. ", +" ..++++.... ..++.. ....++++.. ", +" ........ ...... ........ ", +" ........ ...... ........ ", +" ", +" ", +" ", +" "}; diff --git a/stella/src/ui/dos/PCJoys.cxx b/stella/src/ui/dos/PCJoys.cxx deleted file mode 100644 index 844452bd2..000000000 --- a/stella/src/ui/dos/PCJoys.cxx +++ /dev/null @@ -1,208 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-1999 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: PCJoys.cxx,v 1.1.1.1 2001-12-27 19:54:32 bwmott Exp $ -//============================================================================ - -#include -#include -#include "PCJoys.hxx" - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PCJoysticks::PCJoysticks(bool useAxisMidpoint) - : myGamePort(0x0201), - myUseAxisMidpoint(useAxisMidpoint) -{ - for(uInt32 i = 0; i < 4; ++i) - { - myMinimum[i] = 0x0FFFFFFF; - myMaximum[i] = 0; - myMidpoint[i] = 0; - } - - // Determine which joysticks are present - myPresent = detect(); - - // If we're using midpoints then calibrate them now - if(myUseAxisMidpoint) - { - calibrate(); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -PCJoysticks::~PCJoysticks() -{ -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool PCJoysticks::present() const -{ - return myPresent; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PCJoysticks::read(bool button[4], Int16 axis[4]) -{ - // Get the state of each of the buttons - uInt8 b = (~inportb(myGamePort)) >> 4; - button[0] = b & 0x01 ? true : false; - button[1] = b & 0x02 ? true : false; - button[2] = b & 0x04 ? true : false; - button[3] = b & 0x08 ? true : false; - - // Count how long it takes each axis to change states - Int32 count[4] = {0, 0, 0, 0}; - disable(); - outportb(myGamePort, 0); - for(uInt32 counter = 0; counter < 0x000FFFFF; ++counter) - { - uInt8 i = inportb(myGamePort); - - if(i & 0x01) - ++count[0]; - if(i & 0x02) - ++count[1]; - if(i & 0x04) - ++count[2]; - if(i & 0x08) - ++count[3]; - if(!(i & myPresent)) - break; - } - enable(); - - // Determine the position of each of the axes - for(uInt32 t = 0; t < 4; ++t) - { - // Is this axis being used? - if(myPresent & (1 << t)) - { - // Update the maximum for this axis if needed - if(count[t] > myMaximum[t]) - myMaximum[t] = count[t]; - - // Update the minimum for this axis if needed - if(count[t] < myMinimum[t]) - myMinimum[t] = count[t]; - - // If the minimum and maximum aren't far enough apart then assume 0 - if(myMaximum[t] - myMinimum[t] < (myMaximum[t] >> 3)) - { - axis[t] = 0; - } - else - { - // Are we using midpoints? - if(myUseAxisMidpoint) - { - // Yes, so calculate axis value using midpoint - if(count[t] < myMidpoint[t]) - { - double n = myMidpoint[t] - count[t]; - double d = myMidpoint[t] - myMinimum[t]; - double f = ((d == 0.0) ? 0.0 : (n / d)); - if(d < (myMaximum[t] >> 3)) - axis[t] = 0; - else - axis[t] = (Int16)((Int32)(-32767L * f)); - } - else - { - double n = count[t] - myMidpoint[t]; - double d = myMaximum[t] - myMidpoint[t]; - double f = ((d == 0.0) ? 0.0 : (n / d)); - if(d < (myMaximum[t] >> 3)) - axis[t] = 0; - else - axis[t] = (Int16)((Int32)(32767L * f)); - } - } - else - { - // No, so calculate axis value without using the midpoint - double n = count[t] - myMinimum[t]; - double d = myMaximum[t] - myMinimum[t]; - double f = ((d == 0.0) ? 0.5 : (n / d)); - axis[t] = (Int16)((Int32)(65534L * f) - 32767); - } - } - } - else - { - axis[t] = 0; - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt8 PCJoysticks::detect() const -{ - uInt8 present = 0; - - // Reset the game port bits to 1's - outportb(myGamePort, 0); - - clock_t start = clock(); - - // Wait for low on all four joystick bits or for time to expire - do - { - present = inportb(myGamePort) & 0x0F; - } - while(((clock() - start) < (CLOCKS_PER_SEC / 2)) && present); - - // Setup joystick present mask that tells which joysticks are present - present = (present ^ 0x0F) & 0x0F; - present = present & (((present & 0x03) == 0x03) ? 0x0F : 0x0C); - present = present & (((present & 0x0C) == 0x0C) ? 0x0F : 0x03); - - return present; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void PCJoysticks::calibrate() -{ - // Make sure a joystick is present before we do a lot of work - if(present()) - { - // Count how long it takes each axis to change states - Int32 count[4] = {0, 0, 0, 0}; - disable(); - outportb(myGamePort, 0); - for(uInt32 counter = 0; counter < 0x000FFFFF; ++counter) - { - uInt8 i = inportb(myGamePort); - - if(i & 0x01) - ++count[0]; - if(i & 0x02) - ++count[1]; - if(i & 0x04) - ++count[2]; - if(i & 0x08) - ++count[3]; - if(!(i & myPresent)) - break; - } - enable(); - - for(uInt32 t = 0; t < 4; ++t) - { - myMidpoint[t] = count[t]; - } - } -} - diff --git a/stella/src/ui/dos/PCJoys.hxx b/stella/src/ui/dos/PCJoys.hxx deleted file mode 100644 index d9f37bd1a..000000000 --- a/stella/src/ui/dos/PCJoys.hxx +++ /dev/null @@ -1,71 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-1999 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: PCJoys.hxx,v 1.1.1.1 2001-12-27 19:54:32 bwmott Exp $ -//============================================================================ - -#ifndef PCJOYSTICKS_HXX -#define PCJOYSTICKS_HXX - -#include "bspf.hxx" - -class PCJoysticks -{ - public: - /** - Constructor - - @param useAxisMidpoint Indicates if a "midpoints" should be used - */ - PCJoysticks(bool useAxisMidpoint); - - /** - Destructor - */ - ~PCJoysticks(); - - public: - /** - Answers true iff a joystick is connected to the system - - @return true iff a joysticks is connected - */ - bool present() const; - - /** - Read the state of the joystick and update the button and axis arrays - - @param buttons Array which holds the button state upon exit - @param axes Array which holds the axis state upon exit - */ - void read(bool buttons[4], Int16 axes[4]); - - private: - // Answers mask that indicates which joysticks are present - uInt8 detect() const; - - // Calibrate axes midpoints - void calibrate(); - - private: - uInt8 myPresent; - const uInt16 myGamePort; - const bool myUseAxisMidpoint; - Int32 myMinimum[4]; - Int32 myMaximum[4]; - Int32 myMidpoint[4]; -}; -#endif - diff --git a/stella/src/ui/dos/SndDOS.cxx b/stella/src/ui/dos/SndDOS.cxx deleted file mode 100644 index f761bde77..000000000 --- a/stella/src/ui/dos/SndDOS.cxx +++ /dev/null @@ -1,264 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2003 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: SndDOS.cxx,v 1.3 2003-02-17 05:09:21 bwmott Exp $ -//============================================================================ - -#include "SndDOS.hxx" -#include "dos.h" -#include "dos_sb.h" - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundDOS::SoundDOS(bool activate) - : myCurrentVolume(100), - myFragmentSize(2048), - myIsInitializedFlag(false), - myUpdateLock(false), - myIsMuted(false), - mySampleRate(31400), - mySampleQueue(mySampleRate) -{ - if(activate) - { - int bps = 8; - int stereo = 0; - int buffersize = myFragmentSize; - int playback_freq = mySampleRate; - - if(sb_init(&playback_freq, &bps, &buffersize, &stereo) < 0) - { - cerr << "WARNING: Couldn't initialize audio system! " << endl; - myIsInitializedFlag = false; - mySampleRate = 0; - return; - } - - mySampleRate = playback_freq; - myFragmentSize = buffersize; - - myIsInitializedFlag = true; - myIsMuted = false; - - // Start playing audio - sb_startoutput((sbmix_t)callback, (void*)this); - } - else - { - myIsInitializedFlag = false; - myIsMuted = true; - mySampleRate = 0; - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundDOS::~SoundDOS() -{ - close(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 SoundDOS::getSampleRate() const -{ - return myIsInitializedFlag ? mySampleRate : 0; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool SoundDOS::isSuccessfullyInitialized() const -{ - return myIsInitializedFlag; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::mute(bool state) -{ - if(!myIsInitializedFlag) - { - return; - } - - // Ignore multiple calls to do the same thing - if(myIsMuted == state) - { - return; - } - - myIsMuted = state; - - if(myIsMuted) - { - sb_stopoutput(); - } - else - { - sb_startoutput((sbmix_t)callback, (void*)this); - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::close() -{ - if(myIsInitializedFlag) - { - sb_shutdown(); - } - - myIsInitializedFlag = false; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::setSoundVolume(uInt32 percent) -{ - if(myIsInitializedFlag) - { - if((percent >= 0) && (percent <= 100)) - { - // TODO: At some point we should support setting the volume... - myCurrentVolume = percent; - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::updateSound(MediaSource& mediaSource) -{ - if(myIsInitializedFlag) - { - // Move all of the generated samples into our private sample queue - uInt8 buffer[4096]; - while(mediaSource.numberOfAudioSamples() > 0) - { - uInt32 size = mediaSource.dequeueAudioSamples(buffer, 4096); - mySampleQueue.enqueue(buffer, size); - } - - // Block until the sound interrupt has consumed all but 142 milliseconds - // of the available audio samples - uInt32 leave = mySampleRate / 7; - while(mySampleQueue.size() > leave) - { - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::callback(void* udata, void* stream, int len) -{ - SoundDOS* sound = (SoundDOS*)udata; - - if(!sound->myIsInitializedFlag) - { - return; - } - - // Don't use samples unless there's at least 100 milliseconds worth of data - if(sound->mySampleQueue.size() < (sound->mySampleRate / 10)) - { - return; - } - - sound->mySampleQueue.dequeue((uInt8*)stream, len); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundDOS::SampleQueue::SampleQueue(uInt32 capacity) - : myCapacity(capacity), - myBuffer(0), - mySize(0), - myHead(0), - myTail(0) -{ - myBuffer = new uInt8[myCapacity]; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -SoundDOS::SampleQueue::~SampleQueue() -{ - delete[] myBuffer; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::SampleQueue::clear() -{ - myHead = myTail = mySize = 0; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 SoundDOS::SampleQueue::dequeue(uInt8* buffer, uInt32 size) -{ - // We can only dequeue up to the number of items in the queue - if(size > mySize) - { - size = mySize; - } - - if((myHead + size) < myCapacity) - { - memcpy((void*)buffer, (const void*)(myBuffer + myHead), size); - myHead += size; - } - else - { - uInt32 s1 = myCapacity - myHead; - uInt32 s2 = size - s1; - memcpy((void*)buffer, (const void*)(myBuffer + myHead), s1); - memcpy((void*)(buffer + s1), (const void*)myBuffer, s2); - myHead = (myHead + size) % myCapacity; - } - - mySize -= size; - - return size; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void SoundDOS::SampleQueue::enqueue(uInt8* buffer, uInt32 size) -{ - disable(); - if((mySize + size) > myCapacity) - { - size = myCapacity - mySize; - } - enable(); - - if(size == 0) - { - return; - } - - if((myTail + size) < myCapacity) - { - memcpy((void*)(myBuffer + myTail), (const void*)buffer, size); - myTail += size; - } - else - { - uInt32 s1 = myCapacity - myTail; - uInt32 s2 = size - s1; - memcpy((void*)(myBuffer + myTail), (const void*)buffer, s1); - memcpy((void*)myBuffer, (const void*)(buffer + s1), s2); - myTail = (myTail + size) % myCapacity; - } - - disable(); - mySize += size; - enable(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -uInt32 SoundDOS::SampleQueue::size() const -{ - return mySize; -} - diff --git a/stella/src/ui/dos/dos_sb.c b/stella/src/ui/dos/dos_sb.c deleted file mode 100644 index 42c71aaca..000000000 --- a/stella/src/ui/dos/dos_sb.c +++ /dev/null @@ -1,1126 +0,0 @@ -/* -** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of version 2 of the GNU Library General -** Public License as published by the Free Software Foundation. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -** Library General Public License for more details. To obtain a -** copy of the GNU Library General Public License, write to the Free -** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -** Any permitted reproduction of these routines, in whole or in part, -** must bear this legend. -** -** dos_sb.c -** -** DOS Sound Blaster routines -** -** NOTE: the information in this file has been gathered from many -** Internet documents, and from source code written by Ethan Brodsky. -** -** $Id: dos_sb.c,v 1.1 2002-11-13 03:47:55 bwmott Exp $ -*/ - -#include -#include -#include -#include -#include -#include -#include -#include "dos_sb.h" - -#ifdef DJGPP_USE_NEARPTR -#include -#endif - -/* customize this logging routine as needed */ -#define log_printf printf - -typedef char int8; -typedef short int16; -typedef int int32; - -typedef unsigned char uint8; -typedef unsigned short uint16; -typedef unsigned int uint32; - -typedef uint8 boolean; - -#undef TRUE -#define TRUE 1 -#undef FALSE -#define FALSE 0 - -/* General defines */ -#define LOW_BYTE(x) (uint8) ((x) & 0xFF) -#define HIGH_BYTE(x) (uint8) ((x) >> 8) - -#define INVALID 0xFFFFFFFF -#define DEFAULT_TIMEOUT 20000 - -#define DETECT_POLL_REPS 1000 - -#define DSP_VERSION_SB_15 0x0200 -#define DSP_VERSION_SB_20 0x0201 -#define DSP_VERSION_SB_PRO 0x0300 -#define DSP_VERSION_SB16 0x0400 - -/* Sample format bitfields */ -#define SB_FORMAT_8BIT 0x00 -#define SB_FORMAT_16BIT 0x01 -#define SB_FORMAT_MONO 0x00 -#define SB_FORMAT_STEREO 0x02 - -/* DSP register offsets */ -#define DSP_RESET 0x06 -#define DSP_READ 0x0A -#define DSP_READ_READY 0x0E -#define DSP_WRITE 0x0C -#define DSP_WRITE_BUSY 0x0C -#define DSP_DMA_ACK_8BIT 0x0E -#define DSP_DMA_ACK_16BIT 0x0F - -/* SB 1.0 commands */ -#define DSP_DMA_TIME_CONST 0x40 -#define DSP_DMA_DAC_8BIT 0x14 -#define DSP_DMA_PAUSE_8BIT 0xD0 -#define DSP_DMA_CONT_8BIT 0xD4 -#define DSP_SPEAKER_ON 0xD1 -#define DSP_SPEAKER_OFF 0xD3 -#define DSP_GET_VERSION 0xE1 - -/* SB 1.5 - Pro commands */ -#define DSP_DMA_BLOCK_SIZE 0x48 -#define DSP_DMA_DAC_AI_8BIT 0x1C /* low-speed autoinit */ -#define DSP_DMA_DAC_HS_8BIT 0x90 /* high-speed autoinit */ - -/* SB16 commands */ -#define DSP_DMA_DAC_RATE 0x41 -#define DSP_DMA_START_16BIT 0xB0 -#define DSP_DMA_START_8BIT 0xC0 -#define DSP_DMA_DAC_MODE 0x06 -#define DSP_DMA_PAUSE_16BIT 0xD5 -#define DSP_DMA_CONT_16BIT 0xD6 -#define DSP_DMA_STOP_8BIT 0xDA - -/* DMA flags */ -#define DSP_DMA_UNSIGNED 0x00 -#define DSP_DMA_SIGNED 0x10 -#define DSP_DMA_MONO 0x00 -#define DSP_DMA_STEREO 0x20 - -/* DMA address/port/command defines */ -#define DMA_MASKPORT_16BIT 0xD4 -#define DMA_MODEPORT_16BIT 0xD6 -#define DMA_CLRPTRPORT_16BIT 0xD8 -#define DMA_ADDRBASE_16BIT 0xC0 -#define DMA_COUNTBASE_16BIT 0XC2 -#define DMA_MASKPORT_8BIT 0x0A -#define DMA_MODEPORT_8BIT 0x0B -#define DMA_CLRPTRPORT_8BIT 0x0C -#define DMA_ADDRBASE_8BIT 0x00 -#define DMA_COUNTBASE_8BIT 0x01 -#define DMA_STOPMASK_BASE 0x04 -#define DMA_STARTMASK_BASE 0x00 -#define DMA_AUTOINIT_MODE 0x58 -#define DMA_ONESHOT_MODE 0x48 - -/* centerline */ -#define SILENCE_SIGNED 0x00 -#define SILENCE_UNSIGNED 0x80 - - -/* DOS low-memory buffer info */ -static struct -{ - _go32_dpmi_seginfo buffer; - uint32 bufaddr; /* linear address */ - uint32 offset; - uint32 page; -} dos; - -/* Interrupt stuff */ -static struct -{ - _go32_dpmi_seginfo old_interrupt; - _go32_dpmi_seginfo new_interrupt; - uint32 irq_vector; - uint8 pic_rotateport; - uint8 pic_maskport; - uint8 irq_stopmask; - uint8 irq_startmask; -} intr; - -/* DMA information */ -static struct -{ - boolean autoinit; - volatile int count; - uint16 addrport; - uint16 ackport; -} dma; - -/* 8 and 16 bit DMA ports */ -static const uint8 dma8_ports[4] = { 0x87, 0x83, 0x81, 0x82 }; -static const uint8 dma16_ports[4] = { 0xFF, 0x8B, 0x89, 0x8A }; - -/* Sound Blaster context */ -static struct -{ - boolean initialized; - - uint16 baseio; - uint16 dsp_version; - - uint32 sample_rate; - uint8 format; - - uint8 irq, dma, dma16; - - void *buffer; - uint32 buf_size; - uint32 buf_chunk; - - sbmix_t callback; - void *userdata; -} sb; - - -/* -** Basic DSP routines -*/ -static void dsp_write(uint8 value) -{ - int timeout = DEFAULT_TIMEOUT; - - /* wait until DSP is ready... */ - while (timeout-- && (inportb(sb.baseio + DSP_WRITE_BUSY) & 0x80)) - ; /* loop */ - - outportb(sb.baseio + DSP_WRITE, value); -} - -static uint8 dsp_read(void) -{ - int timeout = DEFAULT_TIMEOUT; - - while (timeout-- && (0 == (inportb(sb.baseio + DSP_READ_READY) & 0x80))) - ; /* loop */ - - return inportb(sb.baseio + DSP_READ); -} - -/* returns TRUE if DSP found and successfully reset, FALSE otherwise */ -static boolean dsp_reset(void) -{ - outportb(sb.baseio + DSP_RESET, 1); /* reset command */ - delay(5); /* 5 usec delay */ - outportb(sb.baseio + DSP_RESET, 0); /* clear */ - delay(5); /* 5 usec delay */ - - if (0xAA == dsp_read()) - return TRUE; - - /* BLEH, we failed */ - return FALSE; -} - -/* return DSP version in 8:8 major:minor format */ -static uint16 dsp_getversion(void) -{ - uint16 major, minor; - - dsp_write(DSP_GET_VERSION); - - major = dsp_read(); - minor = dsp_read(); - - return ((major << 8) + minor); -} - -/* -** BLASTER environment variable parsing -*/ -static boolean get_env_item(char *env, void *ptr, char find, - int base, int width) -{ - char *item; - int value; - - item = strrchr(env, find); - if (NULL == item) - return FALSE; - - item++; - value = strtol(item, NULL, base); - - switch (width) - { - case 32: - *(uint32 *) ptr = value; - break; - case 16: - *(uint16 *) ptr = value; - break; - case 8: - *(uint8 *) ptr = value; - break; - default: - break; - } - - return TRUE; -} - -/* parse the BLASTER environment variable */ -static boolean parse_blaster_env(void) -{ - char blaster[256], *penv; - - penv = getenv("BLASTER"); - - /* bail out if we can't find it... */ - if (NULL == penv) - return FALSE; - - /* copy it, normalize case */ - strcpy(blaster, penv); - strupr(blaster); - - if (FALSE == get_env_item(blaster, &sb.baseio, 'A', 16, 16)) - return FALSE; - if (FALSE == get_env_item(blaster, &sb.irq, 'I', 10, 8)) - return FALSE; - if (FALSE == get_env_item(blaster, &sb.dma, 'D', 10, 8)) - return FALSE; - get_env_item(blaster, &sb.dma16, 'H', 10, 8); - - return TRUE; -} - -/* -** Brute force autodetection code -*/ - -/* detect the base IO by attempting to -** reset the DSP at known addresses -*/ -static uint16 detect_baseio(void) -{ - int i; - static uint16 port_val[] = - { - 0x210, 0x220, 0x230, 0x240, - 0x250, 0x260, 0x280, (uint16) INVALID - }; - - for (i = 0; (uint16) INVALID != port_val[i]; i++) - { - sb.baseio = port_val[i]; - if (dsp_reset()) - break; - } - - /* will return INVALID if not found */ - return port_val[i]; -} - -/* stop all DSP activity */ -static void dsp_stop(void) -{ - /* pause 8/16 bit DMA mode digitized sound IO */ - dsp_reset(); - dsp_write(DSP_DMA_PAUSE_8BIT); - dsp_write(DSP_DMA_PAUSE_16BIT); -} - -/* return number of set bits in byte x */ -static int bitcount(uint8 x) -{ - int i, set_count = 0; - - for (i = 0; i < 8; i++) - if (x & (1 << i)) - set_count++; - - return set_count; -} - -/* returns position of lowest bit set in byte x (INVALID if none) */ -static int bitpos(uint8 x) -{ - int i; - - for (i = 0; i < 8; i++) - if (x & (1 << i)) - return i; - - return INVALID; -} - -static uint8 detect_dma(boolean high_dma) -{ - uint8 dma_maskout, dma_mask; - int i; - - /* stop DSP activity */ - dsp_stop(); - - dma_maskout = ~0x10; /* initially mask only DMA4 */ - - /* poll to find out which dma channels are in use */ - for (i = 0; i < DETECT_POLL_REPS; i++) - dma_maskout &= ~(inportb(0xD0) & 0xF0) | (inportb(0x08) >> 4); - - /* TODO: this causes a pretty nasty sound */ - /* program card, see whch channel becomes active */ - if (FALSE == high_dma) - { - /* 8 bit */ - dsp_write(DSP_DMA_DAC_8BIT); - dsp_write(0xF0); - dsp_write(0xFF); - } - else - { - dsp_write(DSP_DMA_START_16BIT); /* 16-bit, D/A, S/C, FIFO off */ - dsp_write(DSP_DMA_SIGNED | DSP_DMA_MONO); /* 16-bit mono signed PCM */ - dsp_write(0xF0); - dsp_write(0xFF); - } - - /* poll to find out which DMA channels are in use with sound */ - dma_mask = 0; /* dma channels active during audio, minus masked out */ - for (i = 0; i < DETECT_POLL_REPS; i++) - dma_mask |= (((inportb(0xD0) & 0xF0) | (inportb(0x08) >> 4)) & dma_maskout); - - /* stop all DSP activity */ - dsp_stop(); - - if (1 == bitcount(dma_mask)) - return (uint8) bitpos(dma_mask); - else - return (uint8) INVALID; -} - -static void dsp_transfer(uint8 dma) -{ - outportb(DMA_MASKPORT_8BIT, DMA_STOPMASK_BASE | dma); - - /* write DMA mode: single-cycle read transfer */ - outportb(DMA_MODEPORT_8BIT, DMA_ONESHOT_MODE | dma); - outportb(DMA_CLRPTRPORT_8BIT, 0x00); - - /* one transfer */ - outportb(DMA_COUNTBASE_8BIT + (2 * dma), 0x00); /* low */ - outportb(DMA_COUNTBASE_8BIT + (2 * dma), 0x00); /* high */ - - /* address */ - outportb(DMA_ADDRBASE_8BIT + (2 * dma), 0x00); - outportb(DMA_ADDRBASE_8BIT + (2 * dma), 0x00); - outportb(dma8_ports[dma], 0x00); - - /* unmask DMA channel */ - outportb(DMA_MASKPORT_8BIT, DMA_STARTMASK_BASE | dma); - - /* 8-bit single cycle DMA mode */ - dsp_write(DSP_DMA_DAC_8BIT); - dsp_write(0x00); - dsp_write(0x00); -} - -#define MAX_IRQS 11 -static _go32_dpmi_seginfo old_handler[MAX_IRQS]; -static _go32_dpmi_seginfo new_handler[MAX_IRQS]; -static volatile boolean irq_hit[MAX_IRQS]; - -static void irq2_handler(void) { irq_hit[2] = TRUE; } -END_OF_STATIC_FUNCTION(irq2_handler); -static void irq3_handler(void) { irq_hit[3] = TRUE; } -END_OF_STATIC_FUNCTION(irq3_handler); -static void irq5_handler(void) { irq_hit[5] = TRUE; } -END_OF_STATIC_FUNCTION(irq5_handler); -static void irq7_handler(void) { irq_hit[7] = TRUE; } -END_OF_STATIC_FUNCTION(irq7_handler); -static void irq10_handler(void) { irq_hit[10] = TRUE; } -END_OF_STATIC_FUNCTION(irq10_handler); - -static void set_handler(int handler, int irq, int vector) -{ - new_handler[irq].pm_offset = (int) handler; - new_handler[irq].pm_selector = _go32_my_cs(); - _go32_dpmi_get_protected_mode_interrupt_vector(vector, &old_handler[irq]); - _go32_dpmi_chain_protected_mode_interrupt_vector(vector, &new_handler[irq]); -} - -static void ack_interrupt(uint8 irq) -{ - /* acknowledge the interrupts! */ - inportb(sb.baseio + 0x0E); - if (irq > 7) - outportb(0xA0, 0x20); - outportb(0x20, 0x20); -} - -static uint8 detect_irq(void) -{ - int pic1_oldmask, pic2_oldmask; - boolean irq_mask[MAX_IRQS]; - uint8 irq = (uint8) INVALID; - int i; - - LOCK_FUNCTION(irq2_handler); - LOCK_FUNCTION(irq3_handler); - LOCK_FUNCTION(irq5_handler); - LOCK_FUNCTION(irq7_handler); - LOCK_FUNCTION(irq10_handler); - LOCK_VARIABLE(irq_hit); - - /* install temp handlers */ - set_handler((int) irq2_handler, 2, 0x0A); - set_handler((int) irq3_handler, 3, 0x0B); - set_handler((int) irq5_handler, 5, 0x0D); - set_handler((int) irq7_handler, 7, 0x0F); - set_handler((int) irq10_handler, 10, 0x72); - - for (i = 0; i < MAX_IRQS; i++) - irq_hit[i] = FALSE; - - /* save old IRQ mask and unmask IRQs */ - pic1_oldmask = inportb(0x21); - outportb(0x21, pic1_oldmask & 0x53); - pic2_oldmask = inportb(0xA1); - outportb(0xA1, pic1_oldmask & 0xFB); - - /* wait to see what interrupts are triggered without sound */ - delay(100); - - /* mask out any interrupts triggered without sound */ - for (i = 0; i < MAX_IRQS; i++) - { - irq_mask[i] = irq_hit[i]; - irq_hit[i] = FALSE; - } - - /* try to trigger an interrupt using DSP command F2 */ - dsp_write(0xF2); - delay(100); - - /* detect triggered interrupts */ - for (i = 0; i < MAX_IRQS; i++) - { - if (TRUE == irq_hit[i] && FALSE == irq_mask[i]) - { - irq = i; - ack_interrupt(irq); - } - } - - /* if F2 fails to trigger an int, run a short transfer */ - if ((uint8) INVALID == irq) - { - dsp_reset(); - dsp_transfer(sb.dma); - - delay(100); - - /* detect triggered interrupts */ - for (i = 0; i < MAX_IRQS; i++) - { - if (TRUE == irq_hit[i] && FALSE == irq_mask[i]) - { - irq = i; - ack_interrupt(irq); - } - } - } - - /* reset DSP just in case */ - dsp_reset(); - - /* remask IRQs */ - outportb(0x21, pic1_oldmask); - outportb(0xA1, pic2_oldmask); - - /* uninstall handlers */ - _go32_dpmi_set_protected_mode_interrupt_vector(0x0A, &old_handler[2]); - _go32_dpmi_set_protected_mode_interrupt_vector(0x0B, &old_handler[3]); - _go32_dpmi_set_protected_mode_interrupt_vector(0x0D, &old_handler[5]); - _go32_dpmi_set_protected_mode_interrupt_vector(0x0F, &old_handler[7]); - _go32_dpmi_set_protected_mode_interrupt_vector(0x72, &old_handler[10]); - - return irq; -} - -/* try and detect an SB without environment variables */ -static boolean sb_detect(void) -{ - sb.baseio = detect_baseio(); - if ((uint16) INVALID == sb.baseio) - return FALSE; - - sb.dma = detect_dma(FALSE); - if ((uint8) INVALID == sb.dma) - return FALSE; - - sb.dma16 = detect_dma(TRUE); - - sb.irq = detect_irq(); - if ((uint8) INVALID == sb.irq) - return FALSE; - - return TRUE; -} - -/* -** Probe for an SB -*/ -static boolean sb_probe(void) -{ - boolean success = FALSE; - int probe; - - for (probe = 0; probe < 2; probe++) - { - /* first time look for BLASTER=, - ** second time, probe the hardware - */ - if (0 == probe) - success = parse_blaster_env(); - else - success = sb_detect(); - - if (FALSE == success) - continue; - - if (FALSE == dsp_reset()) - { - log_printf("could not reset SB DSP: check BLASTER= variable\n"); - } - else - { - sb.dsp_version = dsp_getversion(); - return TRUE; - } - } - - return FALSE; -} - -/* copy data to DOS memory buffer */ -#ifdef DJGPP_USE_NEARPTR -#define DOS_PUTBUFFER(dest, src, len) -#else /* !DJGPP_USE_NEARPTR */ -#define DOS_PUTBUFFER(dest, src, len) \ -{ \ - dosmemput((src), (len), (dest)); \ -} -#endif /* !DJGPP_USE_NEARPTR */ - -/* -** Interrupt handler for 8/16-bit audio -*/ -static void sb_isr(void) -{ - uint32 address; - - /* maybe needed for slow machines? */ - /*DISABLE_INTS();*/ - - dma.count++; - - /* NOTE: this only works with 8-bit, as one-shot mode - ** does not seem to work with 16-bit transfers - */ - if (FALSE == dma.autoinit) - { - dsp_write(DSP_DMA_DAC_8BIT); - dsp_write(LOW_BYTE(sb.buf_size - 1)); - dsp_write(HIGH_BYTE(sb.buf_size - 1)); - } - - /* indicate we got the interrupt */ - inportb(dma.ackport); - - /* determine the current playback position */ - address = inportb(dma.addrport); - address |= (inportb(dma.addrport) << 8); - address -= dos.offset; - - if (address < sb.buf_size) - { - sb.callback(sb.userdata, sb.buffer + sb.buf_chunk, sb.buf_size); - DOS_PUTBUFFER(dos.bufaddr + sb.buf_chunk, sb.buffer + sb.buf_chunk, - sb.buf_chunk); - } - else - { - sb.callback(sb.userdata, sb.buffer, sb.buf_size); - DOS_PUTBUFFER(dos.bufaddr, sb.buffer, sb.buf_chunk); - } - - /* acknowledge interrupt was taken */ - if (sb.irq > 7) - outportb(0xA0, 0x20); - outportb(0x20, 0x20); - - /* maybe needed for slow machines? */ - /*ENABLE_INTS();*/ -} -END_OF_STATIC_FUNCTION(sb_isr); - - -/* install the SB ISR */ -static void sb_setisr(void) -{ - /* lock variables, routines */ - LOCK_VARIABLE(dma); - LOCK_VARIABLE(dos); - LOCK_VARIABLE(sb); - LOCK_FUNCTION(sb_isr); - - if (sb.format & SB_FORMAT_16BIT) - { - dma.ackport = sb.baseio + DSP_DMA_ACK_16BIT; - dma.addrport = DMA_ADDRBASE_16BIT + (4 * (sb.dma16 - 4)); - } - else - { - dma.ackport = sb.baseio + DSP_DMA_ACK_8BIT; - dma.addrport = DMA_ADDRBASE_8BIT + (2 * sb.dma); - } - - if (sb.irq < 8) - { - /* PIC 1 */ - intr.irq_vector = 0x08 + sb.irq; - intr.pic_rotateport = 0x20; - intr.pic_maskport = 0x21; - } - else - { - /* PIC 2 */ - intr.irq_vector = 0x70 + (sb.irq - 8); - intr.pic_rotateport = 0xA0; - intr.pic_maskport = 0xA1; - } - - intr.irq_stopmask = 1 << (sb.irq % 8); - intr.irq_startmask = ~intr.irq_stopmask; - - /* reset DMA count */ - dma.count = 0; - - DISABLE_INTS(); - - outportb(intr.pic_maskport, inportb(intr.pic_maskport) | intr.irq_stopmask); - - _go32_dpmi_get_protected_mode_interrupt_vector(intr.irq_vector, - &intr.old_interrupt); - intr.new_interrupt.pm_offset = (int) sb_isr; - intr.new_interrupt.pm_selector = _go32_my_cs(); - _go32_dpmi_allocate_iret_wrapper(&intr.new_interrupt); - _go32_dpmi_set_protected_mode_interrupt_vector(intr.irq_vector, - &intr.new_interrupt); - - /* unmask the PIC, get things ready to roll */ - outportb(intr.pic_maskport, inportb(intr.pic_maskport) & intr.irq_startmask); - - ENABLE_INTS(); -} - -/* remove SB ISR, restore old */ -static void sb_resetisr(void) -{ - DISABLE_INTS(); - - outportb(intr.pic_maskport, inportb(intr.pic_maskport) | intr.irq_stopmask); - - _go32_dpmi_set_protected_mode_interrupt_vector(intr.irq_vector, - &intr.old_interrupt); - _go32_dpmi_free_iret_wrapper(&intr.new_interrupt); - - ENABLE_INTS(); -} - -/* allocate sound buffers */ -static boolean sb_allocate_buffers(int buf_size) -{ - int double_bufsize; - - /* TODO: I don't like this. */ - if (sb.format & SB_FORMAT_16BIT) - { - sb.buf_size = 2 * buf_size; - sb.buf_chunk = sb.buf_size * sizeof(uint16); - } - else - { - sb.buf_size = buf_size; - sb.buf_chunk = sb.buf_size * sizeof(uint8); - } - - double_bufsize = 2 * sb.buf_chunk; - - dos.buffer.size = (double_bufsize + 15) >> 4; - if (_go32_dpmi_allocate_dos_memory(&dos.buffer)) - return FALSE; - - /* calc linear address */ - dos.bufaddr = dos.buffer.rm_segment << 4; - if (sb.format & SB_FORMAT_16BIT) - { - dos.page = (dos.bufaddr >> 16) & 0xFF; - /* bleh! thanks, creative! */ - dos.offset = (dos.bufaddr >> 1) & 0xFFFF; - } - else - { - dos.page = (dos.bufaddr >> 16) & 0xFF; - dos.offset = dos.bufaddr & 0xFFFF; - } - -#ifdef DJGPP_USE_NEARPTR - sb.buffer = (uint8 *) dos.bufaddr + __djgpp_conventional_base; -#else /* !DJGPP_USE_NEARPTR */ - sb.buffer = malloc(double_bufsize); - if (NULL == sb.buffer) - return FALSE; -#endif /* !DJGPP_USE_NEARPTR */ - - /* clear out the buffers */ - if (sb.format & SB_FORMAT_16BIT) - memset(sb.buffer, SILENCE_SIGNED, double_bufsize); - else - memset(sb.buffer, SILENCE_UNSIGNED, double_bufsize); - - DOS_PUTBUFFER(dos.bufaddr, sb.buffer, double_bufsize); - - return TRUE; -} - -/* free them buffers */ -static void sb_free_buffers(void) -{ - sb.callback = NULL; - - _go32_dpmi_free_dos_memory(&dos.buffer); - -#ifndef DJGPP_USE_NEARPTR - free(sb.buffer); -#endif /* !DJGPP_USE_NEARPTR */ - - sb.buffer = NULL; -} - -/* get rid of all things SB */ -void sb_shutdown(void) -{ - if (TRUE == sb.initialized) - { - sb.initialized = FALSE; - - dsp_reset(); - sb_resetisr(); - sb_free_buffers(); - } -} - -/* initialize sound bastard */ -int sb_init(int *sample_rate, int *bps, int *buf_size, int *stereo) -{ -#define CLAMP_RATE(in_rate, min_rate, max_rate) \ - (in_rate < min_rate ? min_rate : \ - (in_rate > max_rate ? max_rate : in_rate)) - - /* don't init twice! */ - if (TRUE == sb.initialized) - return 0; - - memset(&sb, 0, sizeof(sb)); - sb.dma = (uint8)INVALID; - sb.dma16 = (uint8)INVALID; - - if (FALSE == sb_probe()) - return -1; - - log_printf("\nSB DSP version: %d.%d baseio: %X IRQ: %d DMA: %d High: %d\n", - sb.dsp_version >> 8, sb.dsp_version & 0xFF, - sb.baseio, sb.irq, sb.dma, sb.dma16); - - /* try autoinit DMA first */ - dma.autoinit = TRUE; - sb.format = (16 == *bps) ? SB_FORMAT_16BIT : SB_FORMAT_8BIT; - sb.format |= (TRUE == *stereo) ? SB_FORMAT_STEREO : SB_FORMAT_MONO; - - /* determine which SB model we have, and act accordingly */ - if (sb.dsp_version < DSP_VERSION_SB_15) - { - /* SB 1.0 */ - sb.sample_rate = CLAMP_RATE(*sample_rate, 4000, 22050); - sb.format &= ~(SB_FORMAT_16BIT | SB_FORMAT_STEREO); - dma.autoinit = FALSE; - } - else if (sb.dsp_version < DSP_VERSION_SB_20) - { - /* SB 1.5 */ - sb.sample_rate = CLAMP_RATE(*sample_rate, 5000, 22050); - sb.format &= ~(SB_FORMAT_16BIT | SB_FORMAT_STEREO); - } - else if (sb.dsp_version < DSP_VERSION_SB_PRO) - { - /* SB 2.0 */ - sb.sample_rate = CLAMP_RATE(*sample_rate, 5000, 44100); - sb.format &= ~(SB_FORMAT_16BIT | SB_FORMAT_STEREO); - } - else if (sb.dsp_version < DSP_VERSION_SB16) - { - /* SB Pro */ - if (sb.format & SB_FORMAT_STEREO) - sb.sample_rate = CLAMP_RATE(*sample_rate, 5000, 22050); - else - sb.sample_rate = CLAMP_RATE(*sample_rate, 5000, 44100); - sb.format &= ~SB_FORMAT_16BIT; - } - else - { - /* SB 16 */ - sb.sample_rate = CLAMP_RATE(*sample_rate, 5000, 44100); - } - - /* sanity check for 16-bit */ - if ((sb.format & SB_FORMAT_16BIT) && ((uint8) INVALID == sb.dma16)) - { - sb.format &= ~SB_FORMAT_16BIT; - log_printf("16-bit DMA channel not available, dropping to 8-bit\n"); - } - - /* allocate buffer / DOS memory */ - if (FALSE == sb_allocate_buffers(*buf_size)) - return -1; - - /* set the new IRQ vector! */ - sb_setisr(); - - sb.initialized = TRUE; - - /* return the actual values */ - *sample_rate = sb.sample_rate; - *buf_size = sb.buf_size; - *bps = (sb.format & SB_FORMAT_16BIT) ? 16 : 8; - *stereo = (sb.format & SB_FORMAT_STEREO) ? TRUE : FALSE; - return 0; -} - -void sb_stopoutput(void) -{ - if (TRUE == sb.initialized) - { - if (sb.format & SB_FORMAT_16BIT) - { - dsp_write(DSP_DMA_PAUSE_16BIT); /* pause 16-bit DMA */ - dsp_write(DSP_DMA_STOP_8BIT); - dsp_write(DSP_DMA_PAUSE_16BIT); - } - else - { - dsp_write(DSP_DMA_PAUSE_8BIT); /* pause 8-bit DMA */ - dsp_write(DSP_SPEAKER_OFF); - } - } -} - -/* return time constant for older sound bastards */ -static uint8 get_time_constant(int rate) -{ - return ((65536 - (256000000L / rate)) >> 8); -} - -static void init_samplerate(int rate) -{ - if ((sb.format & SB_FORMAT_16BIT) || sb.dsp_version >= DSP_VERSION_SB16) - { - dsp_write(DSP_DMA_DAC_RATE); - dsp_write(HIGH_BYTE(rate)); - dsp_write(LOW_BYTE(rate)); - } - else - { - dsp_write(DSP_DMA_TIME_CONST); - dsp_write(get_time_constant(rate)); - } -} - -/* set the sample rate */ -void sb_setrate(int rate) -{ - if (sb.format & SB_FORMAT_16BIT) - { - dsp_write(DSP_DMA_PAUSE_16BIT); /* pause 16-bit DMA */ - init_samplerate(rate); - dsp_write(DSP_DMA_CONT_16BIT); /* continue 16-bit DMA */ - } - else - { - dsp_write(DSP_DMA_PAUSE_8BIT); /* pause 8-bit DMA */ - init_samplerate(rate); - dsp_write(DSP_DMA_CONT_8BIT); /* continue 8-bit DMA */ - } - - sb.sample_rate = rate; -} - -/* start SB DMA transfer */ -static void start_transfer(void) -{ - uint8 dma_mode, start_command, mode_command; - int dma_length; - - dma.count = 0; - - dma_length = sb.buf_size << 1; - - if (TRUE == dma.autoinit) - { - start_command = DSP_DMA_DAC_MODE; /* autoinit DMA */ - dma_mode = DMA_AUTOINIT_MODE; - } - else - { - start_command = 0; - dma_mode = DMA_ONESHOT_MODE; - } - - /* things get a little bit nasty here, look out */ - if (sb.format & SB_FORMAT_16BIT) - { - uint8 dma_base = sb.dma16 - 4; - - dma_mode |= dma_base; - start_command |= DSP_DMA_START_16BIT; - mode_command = DSP_DMA_SIGNED; - - outportb(DMA_MASKPORT_16BIT, DMA_STOPMASK_BASE | dma_base); - outportb(DMA_MODEPORT_16BIT, dma_mode); - outportb(DMA_CLRPTRPORT_16BIT, 0x00); - outportb(DMA_ADDRBASE_16BIT + (4 * dma_base), LOW_BYTE(dos.offset)); - outportb(DMA_ADDRBASE_16BIT + (4 * dma_base), HIGH_BYTE(dos.offset)); - outportb(DMA_COUNTBASE_16BIT + (4 * dma_base), LOW_BYTE(dma_length - 1)); - outportb(DMA_COUNTBASE_16BIT + (4 * dma_base), HIGH_BYTE(dma_length - 1)); - outportb(dma16_ports[dma_base], dos.page); - outportb(DMA_MASKPORT_16BIT, DMA_STARTMASK_BASE | dma_base); - } - else - { - dma_mode |= sb.dma; - start_command |= DSP_DMA_START_8BIT; - mode_command = DSP_DMA_UNSIGNED; - - outportb(DMA_MASKPORT_8BIT, DMA_STOPMASK_BASE + sb.dma); - outportb(DMA_MODEPORT_8BIT, dma_mode); - outportb(DMA_CLRPTRPORT_8BIT, 0x00); - outportb(DMA_ADDRBASE_8BIT + (2 * sb.dma), LOW_BYTE(dos.offset)); - outportb(DMA_ADDRBASE_8BIT + (2 * sb.dma), HIGH_BYTE(dos.offset)); - outportb(DMA_COUNTBASE_8BIT + (2 * sb.dma), LOW_BYTE(dma_length - 1)); - outportb(DMA_COUNTBASE_8BIT + (2 * sb.dma), HIGH_BYTE(dma_length - 1)); - outportb(dma8_ports[sb.dma], dos.page); - outportb(DMA_MASKPORT_8BIT, DMA_STARTMASK_BASE + sb.dma); - } - - /* check stereo */ - if (sb.format & SB_FORMAT_STEREO) - mode_command |= DSP_DMA_STEREO; - else - mode_command |= DSP_DMA_MONO; - - init_samplerate(sb.sample_rate); - - /* start things going */ - if ((sb.format & SB_FORMAT_16BIT) || sb.dsp_version >= DSP_VERSION_SB16) - { - dsp_write(start_command); - dsp_write(mode_command); - dsp_write(LOW_BYTE(sb.buf_size - 1)); - dsp_write(HIGH_BYTE(sb.buf_size - 1)); - } - else - { - /* turn on speaker */ - dsp_write(DSP_SPEAKER_ON); - - if (TRUE == dma.autoinit) - { - dsp_write(DSP_DMA_BLOCK_SIZE); /* set buffer size */ - dsp_write(LOW_BYTE(sb.buf_size - 1)); - dsp_write(HIGH_BYTE(sb.buf_size - 1)); - - if (sb.dsp_version < DSP_VERSION_SB_20) - dsp_write(DSP_DMA_DAC_AI_8BIT); /* low speed autoinit */ - else - dsp_write(DSP_DMA_DAC_HS_8BIT); - } - else - { - dsp_write(DSP_DMA_DAC_8BIT); - dsp_write(LOW_BYTE(sb.buf_size - 1)); - dsp_write(HIGH_BYTE(sb.buf_size - 1)); - } - } -} - -/* start playing the output buffer */ -int sb_startoutput(sbmix_t fillbuf, void *userdata) -{ - int count; - - /* make sure we really should be here... */ - if (FALSE == sb.initialized || NULL == fillbuf) - return -1; - - /* stop any current processing */ - sb_stopoutput(); - - /* set the callback routine */ - sb.callback = fillbuf; - - /* set the userdata pointer */ - sb.userdata = userdata; - - /* get the transfer going */ - start_transfer(); - - /* this ugly bit of code checks to see we really have - ** interrupts firing at this point... - */ - count = clock(); - while (((clock() - count) < CLOCKS_PER_SEC / 2) && (dma.count < 2)) - ; /* loop */ - - if (dma.count < 2) - { - if (TRUE == dma.autoinit) - { - log_printf("Autoinit DMA failed, trying one-shot mode.\n"); - dsp_reset(); - dma.autoinit = FALSE; - dma.count = 0; - return (sb_startoutput(fillbuf, userdata)); - } - else - { - log_printf("One-shot DMA mode failed, sound will not be heard.\n"); - log_printf("%d counts\n", dma.count); - return -1; - } - } - else - { - return 0; - } -} - diff --git a/stella/src/ui/dos/dos_sb.h b/stella/src/ui/dos/dos_sb.h deleted file mode 100644 index abc7e792a..000000000 --- a/stella/src/ui/dos/dos_sb.h +++ /dev/null @@ -1,54 +0,0 @@ -/* -** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) -** -** This program is free software; you can redistribute it and/or -** modify it under the terms of version 2 of the GNU Library General -** Public License as published by the Free Software Foundation. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -** Library General Public License for more details. To obtain a -** copy of the GNU Library General Public License, write to the Free -** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. -** -** Any permitted reproduction of these routines, in whole or in part, -** must bear this legend. -** -** dos_sb.h -** -** DOS Sound Blaster header file -** $Id: dos_sb.h,v 1.1 2002-11-13 03:47:55 bwmott Exp $ -*/ - -#ifndef DOS_SB_H -#define DOS_SB_H - -/* Thanks, Allegro! */ -#define BPS_TO_TIMER(x) (1193182L / (long)(x)) -#define END_OF_FUNCTION(x) void x##_end(void) {} -#define END_OF_STATIC_FUNCTION(x) static void x##_end(void) {} -#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void*)&x, sizeof(x)) -#define LOCK_FUNCTION(x) _go32_dpmi_lock_code(x, (long)x##_end - (long)x) - -#define DISABLE_INTS() __asm__ __volatile__ ("cli") -#define ENABLE_INTS() __asm__ __volatile__ ("sti") - -typedef void (*sbmix_t)(void *userdata, void *buffer, int size); - -#ifdef __cplusplus -extern "C" { -#endif - -extern int sb_init(int *sample_rate, int *bps, int *buf_size, int *stereo); -extern void sb_shutdown(void); -extern int sb_startoutput(sbmix_t fillbuf, void *userdata); -extern void sb_stopoutput(void); -extern void sb_setrate(int rate); - -#ifdef __cplusplus -} -#endif /* __cplusplus */ - -#endif /* DOS_SB_H */ - diff --git a/stella/src/ui/dos/mainDOS.cxx b/stella/src/ui/dos/mainDOS.cxx deleted file mode 100644 index 8c6d8aa67..000000000 --- a/stella/src/ui/dos/mainDOS.cxx +++ /dev/null @@ -1,1218 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2003 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: mainDOS.cxx,v 1.10 2003-02-17 05:17:41 bwmott Exp $ -//============================================================================ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "bspf.hxx" -#include "Console.hxx" -#include "Event.hxx" -#include "MediaSrc.hxx" -#include "PropsSet.hxx" -#include "SndDOS.hxx" -#include "System.hxx" -#include "PCJoys.hxx" -#include "scandef.h" -#include "vga.hxx" - -#define END_OF_FUNCTION(x) void x##_end(void) {} -#define END_OF_STATIC_FUNCTION(x) static void x##_end(void) {} -#define LOCK_VARIABLE(x) _go32_dpmi_lock_data((void*)&x, sizeof(x)) -#define LOCK_FUNCTION(x) _go32_dpmi_lock_code((void*)x, (long)x##_end - (long)x) - -// Pointer to the console object or the null pointer -Console* theConsole; - -// Event objects to use -Event theEvent; -Event theKeyboardEvent; - -// Array of flags for each keyboard key-code -volatile bool theKeyboardKeyState[128]; - -// Used to ignore some number of key codes -volatile uInt32 theNumOfKeyCodesToIgnore; - -// An alternate properties file to use -string theAlternateProFile = ""; - -// Indicates if the entire frame should be redrawn -bool theRedrawEntireFrameFlag = true; - -// Indicates if the user wants to quit -bool theQuitIndicator = false; - -// Indicates if the emulator should be paused -bool thePauseIndicator = false; - -// Indicates whether to show some game info on program exit -bool theShowInfoFlag = false; - -// Indicates what the desired frame rate is -uInt32 theDesiredFrameRate = 60; - -// Indicate which paddle mode we're using: -// 0 - Mouse emulates paddle 0 -// 1 - Mouse emulates paddle 1 -// 2 - Mouse emulates paddle 2 -// 3 - Mouse emulates paddle 3 -// 4 - Use real Atari 2600 paddles -uInt32 thePaddleMode = 0; - -// Indicates if emulation should synchronize with video instead of system timer -bool theSynchronizeVideoFlag = false; - -// Indicates if sound should be enabled or not -bool theSoundEnabledFlag = true; - -// Indicates if the Mode X graphics should be used or not -bool theUseModeXFlag = false; - -// Pointer to the joysticks object -PCJoysticks* theJoysticks; - -#define MESSAGE_INTERVAL 2 - -// Mouse IRQ -#define MOUSE_BIOS 0x33 - -// VGA Card definitions -#define VGA_BIOS 0x10 -#define VGA_PEL_ADDRESS 0x03c8 -#define VGA_PEL_DATA 0x03c9 - -// Remembers which video mode to restore when the program exits -static uInt16 theDefaultVideoMode; - -static uInt16 thePixelDataTable[256]; - -// Indicates the width and height of the screen -static uInt32 theWidth; -static uInt32 theHeight; - -// Keyboard Interrupt definitions -_go32_dpmi_seginfo theOldKeyboardHandler; -_go32_dpmi_seginfo theKeyboardHandler; -static void keyboardInterruptServiceRoutine(void); - -// Indicates the current state to use for state saving -static uInt32 theCurrentState = 0; - -// The locations for various required files -static string theHomeDir; -static string theStateDir; - -/** - Return true if the program is executing in a NT DOS virtual machine. -*/ -bool isWindowsNt() -{ - const char* p = getenv("OS"); - - if(((p) && (stricmp(p, "Windows_NT") == 0)) || - (_get_dos_version(1) == 0x0532)) - { - return true; - } - else - { - return false; - } -} - -/** - Changes the current state slot. -*/ -void changeState(uInt32 change) -{ - // Calculate new state slot - theCurrentState = (theCurrentState + ((change == 0) ? 9 : 1)) % 10; - - // Print appropriate message - ostringstream buf; - buf << "Changed to slot " << theCurrentState; - string message = buf.str(); - theConsole->mediaSource().showMessage(message, - MESSAGE_INTERVAL * theDesiredFrameRate); -} - -/** - Saves state of the current game in the current slot. -*/ -void saveState() -{ - ostringstream buf; - string md5 = theConsole->properties().get("Cartridge.MD5"); - - string sub1 = theStateDir + '\\' + md5.substr(0, 8); - string sub2 = sub1 + '\\' + md5.substr(8, 8); - string sub3 = sub2 + '\\' + md5.substr(16, 8); - buf << sub3 << '\\' << md5.substr(24, 8) << ".st" << theCurrentState; - string filename = buf.str(); - - // If needed create the sub-directory corresponding to the characters - // of the MD5 checksum - if(access(sub1.c_str(), F_OK) != 0) - { - if(mkdir(sub1.c_str(), S_IWUSR) != 0) - { - theConsole->mediaSource().showMessage(string() = "Error Saving State", - MESSAGE_INTERVAL * theDesiredFrameRate); - return; - } - } - - if(access(sub2.c_str(), F_OK) != 0) - { - if(mkdir(sub2.c_str(), S_IWUSR) != 0) - { - theConsole->mediaSource().showMessage(string() = "Error Saving State", - MESSAGE_INTERVAL * theDesiredFrameRate); - return; - } - } - - if(access(sub3.c_str(), F_OK) != 0) - { - if(mkdir(sub3.c_str(), S_IWUSR) != 0) - { - theConsole->mediaSource().showMessage(string() = "Error Saving State", - MESSAGE_INTERVAL * theDesiredFrameRate); - return; - } - } - - // Do a state save using the System - int result = theConsole->system().saveState(filename, md5); - - // Print appropriate message - buf.str(""); - if(result == 1) - buf << "State " << theCurrentState << " saved"; - else if(result == 2) - buf << "Error saving state " << theCurrentState; - else if(result == 3) - buf << "Invalid state " << theCurrentState << " file"; - - string message = buf.str(); - theConsole->mediaSource().showMessage(message, MESSAGE_INTERVAL * - theDesiredFrameRate); -} - -/** - Loads state from the current slot for the current game. -*/ -void loadState() -{ - ostringstream buf; - string md5 = theConsole->properties().get("Cartridge.MD5"); - - string sub1 = theStateDir + '\\' + md5.substr(0, 8); - string sub2 = sub1 + '\\' + md5.substr(8, 8); - string sub3 = sub2 + '\\' + md5.substr(16, 8); - buf << sub3 << '\\' << md5.substr(24, 8) << ".st" << theCurrentState; - string filename = buf.str(); - - // Do a state load using the System - int result = theConsole->system().loadState(filename, md5); - - // Print appropriate message - buf.str(""); - if(result == 1) - buf << "State " << theCurrentState << " loaded"; - else if(result == 2) - buf << "Error loading state " << theCurrentState; - else if(result == 3) - buf << "Invalid state " << theCurrentState << " file"; - - string message = buf.str(); - theConsole->mediaSource().showMessage(message, MESSAGE_INTERVAL * - theDesiredFrameRate); -} - -/** - This is the keyboard interrupt service routine. It's called - whenever a key is pressed or released on the keyboard. -*/ -static void keyboardInterruptServiceRoutine(void) -{ - // Get the scan code of the key - uInt8 code = inportb(0x60); - - // Are we ignoring some key codes? - if(theNumOfKeyCodesToIgnore > 0) - { - --theNumOfKeyCodesToIgnore; - } - // Handle the pause key - else if(code == 0xE1) - { - // Toggle the state of the pause key. The pause key only sends a "make" - // code it does not send a "break" code. Also the "make" code is the - // sequence 0xE1, 0x1D, 0x45, 0xE1, 0x9D, 0xC5 so we'll need to skip the - // remaining 5 values in the sequence. - theKeyboardKeyState[SCAN_PAUSE] = !theKeyboardKeyState[SCAN_PAUSE]; - theNumOfKeyCodesToIgnore = 5; - } - // Handle the "extended" and the "error" key codes - else if((code == 0xE0) || (code == 0x00)) - { - // Currently, we ignore the "extended" and "error" key codes. We should - // probably modify the "extended" key code support so that we can identify - // the extended keys... - } - else - { - // Update the state of the key - theKeyboardKeyState[code & 0x7F] = !(code & 0x80); - } - - // Ack the interrupt - outp(0x20, 0x20); -} -END_OF_STATIC_FUNCTION(keyboardInterruptServiceRoutine); - -/** - This routine should be called once the console is created to setup - the graphics mode -*/ -void startup() -{ - union REGS regs; - - // Get the desired width and height of the display - theWidth = theConsole->mediaSource().width(); - theHeight = theConsole->mediaSource().height(); - - // Initialize the pixel data table - for(uInt32 j = 0; j < 256; ++j) - { - thePixelDataTable[j] = j | (j << 8); - } - - // Lets save the current video mode - regs.h.ah = 0x0f; - int86(VGA_BIOS, ®s, ®s); - theDefaultVideoMode = regs.h.al; - - // Plop into 320x200x256 mode 13 - regs.w.ax = 0x0013; - regs.h.ah = 0x00; - int86(VGA_BIOS, ®s, ®s); - - // Setup VGA graphics mode - if(theUseModeXFlag) - { - VgaSetMode(VGA_320_240_60HZ); - - // Clear the screen - outp(0x3C4, 0x02); - outp(0x3C5, 0x0F); - for(uInt32 i = 0; i < 240 * 80; ++i) - { - _farpokeb(_dos_ds, 0xA0000 + i, 0); - } - } - else - { - VgaSetMode(VGA_320_200_60HZ); - - // Clear the screen - for(uInt32 i = 0; i < 320 * 200; ++i) - { - _farpokew(_dos_ds, 0xA0000 + i, 0); - } - } - - // Setup color palette for the video card - const uInt32* palette = theConsole->mediaSource().palette(); - outp(VGA_PEL_ADDRESS, 0); - for(int index = 0; index < 256; index++) - { - outp(VGA_PEL_DATA, (palette[index] & 0x00ff0000) >> 18); - outp(VGA_PEL_DATA, (palette[index] & 0x0000ff00) >> 10); - outp(VGA_PEL_DATA, (palette[index] & 0x000000ff) >> 2); - } - - // Install keyboard interrupt handler - LOCK_VARIABLE(theKeyboardKeyState); - LOCK_VARIABLE(theNumOfKeyCodesToIgnore); - LOCK_FUNCTION(keyboardInterruptServiceRoutine); - for(uInt32 k = 0; k < 128; ++k) - { - theKeyboardKeyState[k] = false; - } - theNumOfKeyCodesToIgnore = 0; - disable(); - _go32_dpmi_get_protected_mode_interrupt_vector(0x09, &theOldKeyboardHandler); - theKeyboardHandler.pm_selector = _go32_my_cs(); - theKeyboardHandler.pm_offset = (long)(&keyboardInterruptServiceRoutine); - _go32_dpmi_allocate_iret_wrapper(&theKeyboardHandler); - _go32_dpmi_set_protected_mode_interrupt_vector(0x09, &theKeyboardHandler); - enable(); - - // Initialize mouse handler via DOS interrupt - regs.w.ax = 0x0000; - int86(MOUSE_BIOS, ®s, ®s); - - if(regs.w.ax != 0x0000) - { - // Set mouse bounding box to 0,0 to 511,511 - regs.w.ax = 0x0007; - regs.w.cx = 0; - regs.w.dx = 511; - int86(MOUSE_BIOS, ®s, ®s); - - regs.w.ax = 0x0008; - regs.w.cx = 0; - regs.w.dx = 511; - int86(MOUSE_BIOS, ®s, ®s); - } - - // Set joystick pointer to null - theJoysticks = 0; - - // Register function to remove interrupts when program exits - void shutdownInterrupts(); - atexit(shutdownInterrupts); -} - -/** - This function should be registered with the atexit function so that it's - automatically called when the program terminates. It's responsible for - removing any interrupts we're using. -*/ -void shutdownInterrupts() -{ - // Restore the keyboard interrupt routine - disable(); - _go32_dpmi_set_protected_mode_interrupt_vector(0x09, &theOldKeyboardHandler); - _go32_dpmi_free_iret_wrapper(&theKeyboardHandler); - enable(); -} - -/** - This function should be called right before the program exists to - clean up things and reset the graphics mode. -*/ -void shutdown() -{ - union REGS regs; - - // Restore previous display mode - regs.h.ah = 0x00; - regs.h.al = theDefaultVideoMode; - int86(VGA_BIOS, ®s, ®s); - - // Delete the joystick object - delete theJoysticks; - theJoysticks = 0; -} - -/** -*/ -void updateDisplay(MediaSource& mediaSource) -{ - uInt32* current = (uInt32*)mediaSource.currentFrameBuffer(); - uInt32* previous = (uInt32*)mediaSource.previousFrameBuffer(); - - // Are we updating a Mode X display? - if(theUseModeXFlag) - { - uInt32 width = theWidth / 4; - uInt32 height = (theHeight > 240) ? 240 : theHeight; - int offset = ((240 - height) / 2) * 80; - - // See if we can enable near pointers for updating the screen - if(__djgpp_nearptr_enable()) - { - // We've got near pointers enabled so let's use them - uInt8* data = (uInt8*)(0xA0000 + __djgpp_conventional_base + offset) - + (((160 - theWidth) / 2) * 2) / 4; - - // TODO: Rearrange this loop so we don't have to do as many calls to - // outp(). This is rather slow when the entire screen changes. - for(uInt32 y = 0; y < height; ++y) - { - uInt8* screen = data; - - for(uInt32 x = 0; x < width; ++x) - { - if(*current != *previous) - { - uInt8* frame = (uInt8*)current; - - outp(0x3C4, 0x02); - outp(0x3C5, 0x03); - *screen = *frame; - *(screen + 1) = *(frame + 2); - - outp(0x3C4, 0x02); - outp(0x3C5, 0x0C); - *screen = *(frame + 1); - *(screen + 1) = *(frame + 3); - } - screen += 2; - current++; - previous++; - } - data += 80; - } - - // Disable the near pointers - __djgpp_nearptr_disable(); - } - else - { - // Counldn't enable near pointers so we'll use a slower methods :-( - uInt8* data = (uInt8*)(0xA0000 + offset) - + (((160 - theWidth) / 2) * 2) / 4; - - // TODO: Rearrange this loop so we don't have to do as many calls to - // outp(). This is rather slow when the entire screen changes. - for(uInt32 y = 0; y < height; ++y) - { - uInt8* screen = data; - - for(uInt32 x = 0; x < width; ++x) - { - if(*current != *previous) - { - uInt8* frame = (uInt8*)current; - - outp(0x3C4, 0x02); - outp(0x3C5, 0x03); - _farpokeb(_dos_ds, (uInt32)screen, *frame); - _farpokeb(_dos_ds, (uInt32)(screen + 1), *(frame + 2)); - - outp(0x3C4, 0x02); - outp(0x3C5, 0x0C); - _farpokeb(_dos_ds, (uInt32)screen, *(frame + 1)); - _farpokeb(_dos_ds, (uInt32)(screen + 1), *(frame + 3)); - } - screen += 2; - current++; - previous++; - } - data += 80; - } - } - } - else - { - uInt32 width = theWidth / 4; - uInt32 height = (theHeight > 200) ? 200 : theHeight; - int offset = ((200 - height) / 2) * 320; - - // See if we can enable near pointers for updating the screen - if(__djgpp_nearptr_enable()) - { - // We've got near pointers enabled so let's use them - uInt16* data = (uInt16*)(0xA0000 + __djgpp_conventional_base + offset) - + ((160 - theWidth) / 2); - - for(uInt32 y = 0; y < height; ++y) - { - uInt16* screen = data; - data += 160; - - for(uInt32 x = 0; x < width; ++x) - { - if(*current != *previous) - { - uInt8* frame = (uInt8*)current; - - *screen++ = thePixelDataTable[*frame++]; - *screen++ = thePixelDataTable[*frame++]; - *screen++ = thePixelDataTable[*frame++]; - *screen++ = thePixelDataTable[*frame]; - } - else - { - screen += 4; - } - current++; - previous++; - } - } - - // Disable the near pointers - __djgpp_nearptr_disable(); - } - else - { - // Counldn't enable near pointers so we'll use a slower methods :-( - uInt16* data = (uInt16*)(0xA0000 + offset) + ((160 - theWidth) / 2); - - for(uInt32 y = 0; y < height; ++y) - { - uInt16* screen = data; - data += 160; - - for(uInt32 x = 0; x < width; ++x) - { - if(*current != *previous) - { - uInt8* frame = (uInt8*)current; - - _farpokew(_dos_ds, (uInt32)screen++, thePixelDataTable[*frame++]); - _farpokew(_dos_ds, (uInt32)screen++, thePixelDataTable[*frame++]); - _farpokew(_dos_ds, (uInt32)screen++, thePixelDataTable[*frame++]); - _farpokew(_dos_ds, (uInt32)screen++, thePixelDataTable[*frame++]); - } - else - { - screen += 4; - } - current++; - previous++; - } - } - } - } -} - -/** - This routine is called by the updateEvents routine to handle updated - the events based on the current keyboard state. -*/ -void updateEventsUsingKeyboardState() -{ - struct Switches - { - uInt16 scanCode; - Event::Type eventCode; - string message; - }; - - static Switches list[] = { - { SCAN_1, Event::KeyboardZero1, "" }, - { SCAN_2, Event::KeyboardZero2, "" }, - { SCAN_3, Event::KeyboardZero3, "" }, - { SCAN_Q, Event::KeyboardZero4, "" }, - { SCAN_W, Event::KeyboardZero5, "" }, - { SCAN_E, Event::KeyboardZero6, "" }, - { SCAN_A, Event::KeyboardZero7, "" }, - { SCAN_S, Event::KeyboardZero8, "" }, - { SCAN_D, Event::KeyboardZero9, "" }, - { SCAN_Z, Event::KeyboardZeroStar, "" }, - { SCAN_X, Event::KeyboardZero0, "" }, - { SCAN_C, Event::KeyboardZeroPound, "" }, - - { SCAN_8, Event::KeyboardOne1, "" }, - { SCAN_9, Event::KeyboardOne2, "" }, - { SCAN_0, Event::KeyboardOne3, "" }, - { SCAN_I, Event::KeyboardOne4, "" }, - { SCAN_O, Event::KeyboardOne5, "" }, - { SCAN_P, Event::KeyboardOne6, "" }, - { SCAN_K, Event::KeyboardOne7, "" }, - { SCAN_L, Event::KeyboardOne8, "" }, - { SCAN_SCOLON, Event::KeyboardOne9, "" }, - { SCAN_COMMA, Event::KeyboardOneStar, "" }, - { SCAN_STOP, Event::KeyboardOne0, "" }, - { SCAN_FSLASH, Event::KeyboardOnePound, "" }, - - { SCAN_DOWN, Event::JoystickZeroDown, "" }, - { SCAN_UP, Event::JoystickZeroUp, "" }, - { SCAN_LEFT, Event::JoystickZeroLeft, "" }, - { SCAN_RIGHT, Event::JoystickZeroRight, "" }, - { SCAN_SPACE, Event::JoystickZeroFire, "" }, - { SCAN_Z, Event::BoosterGripZeroTrigger, "" }, - { SCAN_X, Event::BoosterGripZeroBooster, "" }, - - { SCAN_W, Event::JoystickZeroUp, "" }, - { SCAN_S, Event::JoystickZeroDown, "" }, - { SCAN_A, Event::JoystickZeroLeft, "" }, - { SCAN_D, Event::JoystickZeroRight, "" }, - { SCAN_TAB, Event::JoystickZeroFire, "" }, - { SCAN_1, Event::BoosterGripZeroTrigger, "" }, - { SCAN_2, Event::BoosterGripZeroBooster, "" }, - - { SCAN_L, Event::JoystickOneDown, "" }, - { SCAN_O, Event::JoystickOneUp, "" }, - { SCAN_K, Event::JoystickOneLeft, "" }, - { SCAN_SCOLON, Event::JoystickOneRight, "" }, - { SCAN_J, Event::JoystickOneFire, "" }, - { SCAN_N, Event::BoosterGripOneTrigger, "" }, - { SCAN_M, Event::BoosterGripOneBooster, "" }, - - { SCAN_F1, Event::ConsoleSelect, "" }, - { SCAN_F2, Event::ConsoleReset, "" }, - { SCAN_F3, Event::ConsoleColor, "Color Mode" }, - { SCAN_F4, Event::ConsoleBlackWhite, "BW Mode" }, - { SCAN_F5, Event::ConsoleLeftDifficultyA, "Left Difficulty A" }, - { SCAN_F6, Event::ConsoleLeftDifficultyB, "Left Difficulty B" }, - { SCAN_F7, Event::ConsoleRightDifficultyA, "Right Difficulty A" }, - { SCAN_F8, Event::ConsoleRightDifficultyB, "Right Difficulty B" } - }; - - // Handle pausing the emulator - if((!thePauseIndicator) && (theKeyboardKeyState[SCAN_PAUSE])) - { - thePauseIndicator = true; - theConsole->mediaSource().pause(true); - } - else if(thePauseIndicator && (!theKeyboardKeyState[SCAN_PAUSE])) - { - thePauseIndicator = false; - theConsole->mediaSource().pause(false); - } - - // Handle quiting the emulator - if(theKeyboardKeyState[SCAN_ESC]) - { - theQuitIndicator = true; - } - - // Handle switching save state slots - static bool changedState = false; - if((theKeyboardKeyState[SCAN_F10]) && !changedState) - { - if(theKeyboardKeyState[SCAN_LSHIFT] || theKeyboardKeyState[SCAN_RSHIFT]) - { - changeState(0); - } - else - { - changeState(1); - } - changedState = true; - } - else if(!theKeyboardKeyState[SCAN_F10]) - { - changedState = false; - } - - static bool savedState = false; - if(!savedState && theKeyboardKeyState[SCAN_F9]) - { - saveState(); - savedState = true; - } - else if(!theKeyboardKeyState[SCAN_F9]) - { - savedState = false; - } - - static bool loadedState = false; - if(!loadedState && theKeyboardKeyState[SCAN_F11]) - { - loadState(); - loadedState = true; - } - else if(!theKeyboardKeyState[SCAN_F11]) - { - loadedState = false; - } - - // First we clear all of the keyboard events - for(unsigned int k = 0; k < sizeof(list) / sizeof(Switches); ++k) - { - theKeyboardEvent.set(list[k].eventCode, 0); - } - - // Now, change the event state if needed for each event - for(unsigned int i = 0; i < sizeof(list) / sizeof(Switches); ++i) - { - if(theKeyboardKeyState[list[i].scanCode]) - { - if(theKeyboardEvent.get(list[i].eventCode) == 0) - { - theEvent.set(list[i].eventCode, 1); - theKeyboardEvent.set(list[i].eventCode, 1); - if(list[i].message != "") - { - theConsole->mediaSource().showMessage(list[i].message, - 2 * theDesiredFrameRate); - } - } - } - else - { - if(theKeyboardEvent.get(list[i].eventCode) == 0) - { - theEvent.set(list[i].eventCode, 0); - theKeyboardEvent.set(list[i].eventCode, 0); - } - } - } -} - -/** - This routine should be called regularly to handle events -*/ -void handleEvents() -{ - union REGS regs; - - // Update events based on keyboard state - updateEventsUsingKeyboardState(); - - // Update paddles if we're using the mouse to emulate one - if(thePaddleMode < 4) - { - // Update the paddle resistance and fire button based on the mouse settings - regs.w.ax = 0x0003; - int86(MOUSE_BIOS, ®s, ®s); - Int32 resistance = (uInt32)((1000000.0 * (512 - regs.w.cx)) / 512); - - if(thePaddleMode == 0) - { - theEvent.set(Event::PaddleZeroResistance, resistance); - theEvent.set(Event::PaddleZeroFire, (regs.w.bx & 0x07) ? 1 : 0); - } - else if(thePaddleMode == 1) - { - theEvent.set(Event::PaddleOneResistance, resistance); - theEvent.set(Event::PaddleOneFire, (regs.w.bx & 0x07) ? 1 : 0); - } - else if(thePaddleMode == 2) - { - theEvent.set(Event::PaddleTwoResistance, resistance); - theEvent.set(Event::PaddleTwoFire, (regs.w.bx & 0x07) ? 1 : 0); - } - else if(thePaddleMode == 3) - { - theEvent.set(Event::PaddleThreeResistance, resistance); - theEvent.set(Event::PaddleThreeFire, (regs.w.bx & 0x07) ? 1 : 0); - } - } - - // If no joystick object is available create one - if(theJoysticks == 0) - { - theJoysticks = new PCJoysticks(thePaddleMode != 4); - } - - if(theJoysticks->present()) - { - bool buttons[4]; - Int16 axes[4]; - - theJoysticks->read(buttons, axes); - - theEvent.set(Event::JoystickZeroFire, buttons[0] ? - 1 : theKeyboardEvent.get(Event::JoystickZeroFire)); - - theEvent.set(Event::BoosterGripZeroTrigger, buttons[1] ? - 1 : theKeyboardEvent.get(Event::BoosterGripZeroTrigger)); - - theEvent.set(Event::JoystickZeroLeft, (axes[0] < -16384) ? - 1 : theKeyboardEvent.get(Event::JoystickZeroLeft)); - - theEvent.set(Event::JoystickZeroRight, (axes[0] > 16384) ? - 1 : theKeyboardEvent.get(Event::JoystickZeroRight)); - - theEvent.set(Event::JoystickZeroUp, (axes[1] < -16384) ? - 1 : theKeyboardEvent.get(Event::JoystickZeroUp)); - - theEvent.set(Event::JoystickZeroDown, (axes[1] > 16384) ? - 1 : theKeyboardEvent.get(Event::JoystickZeroDown)); - - theEvent.set(Event::JoystickOneFire, buttons[2] ? - 1 : theKeyboardEvent.get(Event::JoystickOneFire)); - - theEvent.set(Event::BoosterGripOneTrigger, buttons[3] ? - 1 : theKeyboardEvent.get(Event::BoosterGripOneTrigger)); - - theEvent.set(Event::JoystickOneLeft, (axes[2] < -16384) ? - 1 : theKeyboardEvent.get(Event::JoystickOneLeft)); - - theEvent.set(Event::JoystickOneRight, (axes[2] > 16384) ? - 1 : theKeyboardEvent.get(Event::JoystickOneRight)); - - theEvent.set(Event::JoystickOneUp, (axes[3] < -16384) ? - 1 : theKeyboardEvent.get(Event::JoystickOneUp)); - - theEvent.set(Event::JoystickOneDown, (axes[3] > 16384) ? - 1 : theKeyboardEvent.get(Event::JoystickOneDown)); - - // If we're using real paddles then set paddle events as well - if(thePaddleMode == 4) - { - uInt32 r; - - theEvent.set(Event::PaddleZeroFire, buttons[0]); - r = (uInt32)((1.0E6L * (axes[0] + 32767L)) / 65536); - theEvent.set(Event::PaddleZeroResistance, r); - - theEvent.set(Event::PaddleOneFire, buttons[1]); - r = (uInt32)((1.0E6L * (axes[1] + 32767L)) / 65536); - theEvent.set(Event::PaddleOneResistance, r); - - theEvent.set(Event::PaddleTwoFire, buttons[2]); - r = (uInt32)((1.0E6L * (axes[2] + 32767L)) / 65536); - theEvent.set(Event::PaddleTwoResistance, r); - - theEvent.set(Event::PaddleThreeFire, buttons[3]); - r = (uInt32)((1.0E6L * (axes[3] + 32767L)) / 65536); - theEvent.set(Event::PaddleThreeResistance, r); - } - } -} - -/** - Ensure that the necessary directories are created for Stella under - STELLA_HOME or the current working directory if STELLA_HOME is not - defined. - - Required directories are $STELLA_HOME/state. - This must be called before any other function. -*/ -bool setupDirs() -{ - // Get the directory to use - theHomeDir = getenv("STELLA_HOME"); - if(theHomeDir == "") - { - theHomeDir = "."; - } - - // Remove any trailing backslashes - while((theHomeDir.length() >= 1) && - (theHomeDir[theHomeDir.length() - 1] == '\\')) - { - theHomeDir = theHomeDir.substr(0, theHomeDir.length() - 1); - } - - // Create state saving directory if needed - theStateDir = theHomeDir + "\\state"; - if(access(theStateDir.c_str(), F_OK) != 0) - { - if(mkdir(theStateDir.c_str(), S_IWUSR) != 0) - { - cerr << "ERROR: Creating state save directory " - << theStateDir << endl; - return false; - } - } - - if(access(theStateDir.c_str(), R_OK | W_OK | X_OK | D_OK) != 0) - { - cerr << "ERROR: Access not allowed to the state save directory " - << theStateDir << endl; - return false; - } - - return true; -} - -/** - Display a usage message and exit the program -*/ -void usage() -{ - static const char* message[] = { - "", - "Stella for DOS version 1.3", - "", - "Usage: stella [option ...] file", - "", - "Valid options are:", - "", - " -fps Display the given number of frames per second", - " -modex Use 320x240 video mode instead of 320x200", - " -nosound Disables audio output", - " -paddle <0|1|2|3|real> Indicates which paddle the mouse should emulate", - " or that real Atari 2600 paddles are being used", - " -pro Use given properties file instead of stella.pro", - " -showinfo Show some game info on exit", - " -vsync Synchronize with video instead of system timer", - 0 - }; - - for(uInt32 i = 0; message[i]; ++i) - { - cerr << message[i] << endl; - } - exit(1); -} - -/** - Setup the properties set by loading from the file stella.pro - - @param set The properties set to setup -*/ -bool setupProperties(PropertiesSet& set) -{ - // Try to load the properties file from either the current working - // directory or the $STELLA_HOME directory - string filename1 = (theAlternateProFile != "") ? theAlternateProFile : - "stella.pro"; - string filename2 = theHomeDir + '\\' + filename1; - - if(access(filename1.c_str(), R_OK | F_OK) == 0) - { - // File is accessible so load properties from it - set.load(filename1, &Console::defaultProperties(), false); - return true; - } - else if(access(filename2.c_str(), R_OK | F_OK) == 0) - { - // File is accessible so load properties from it - set.load(filename2, &Console::defaultProperties(), false); - return true; - } - else - { - set.load("", &Console::defaultProperties(), false); - return true; - } -} - -/** - Should be called to parse the command line arguments - - @param argc The count of command line arguments - @param argv The command line arguments -*/ -void handleCommandLineArguments(int argc, char* argv[]) -{ - // Make sure we have the correct number of command line arguments - if((argc < 2) || (argc > 7)) - { - usage(); - } - - for(Int32 i = 1; i < (argc - 1); ++i) - { - // See which command line switch they're using - if(string(argv[i]) == "-fps") - { - // They're setting the desired frame rate - Int32 rate = atoi(argv[++i]); - if((rate < 1) || (rate > 300)) - { - rate = 60; - } - - theDesiredFrameRate = rate; - } - else if(string(argv[i]) == "-paddle") - { - // They're setting the desired frame rate - if(string(argv[i + 1]) == "real") - { - thePaddleMode = 4; - } - else - { - thePaddleMode = atoi(argv[i + 1]); - if((thePaddleMode < 0) || (thePaddleMode > 3)) - { - usage(); - } - } - ++i; - } - else if(string(argv[i]) == "-nosound") - { - theSoundEnabledFlag = false; - } - else if(string(argv[i]) == "-modex") - { - theUseModeXFlag = true; - } - else if(string(argv[i]) == "-showinfo") - { - theShowInfoFlag = true; - } - else if(string(argv[i]) == "-pro") - { - theAlternateProFile = argv[++i]; - } - else if(string(argv[i]) == "-vsync") - { - theSynchronizeVideoFlag = true; - } - else - { - usage(); - } - } -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -int main(int argc, char* argv[]) -{ - // Find out if we're running in an NT DOS virtual machine - bool windowsNtFlag = isWindowsNt(); - - // First set up the directories where Stella will find RC and state files - if(!setupDirs()) - { - return 0; - } - - // Handle the command line arguments - handleCommandLineArguments(argc, argv); - - // Get a pointer to the file which contains the cartridge ROM - const char* file = argv[argc - 1]; - - // Open the cartridge image and read it in. The cartridge image is - // searched for in the current working directory, the $STELLA_HOME\ROMS - // directory, and finally the $STELLA_HOME directory. - string file1(file); - string file2(theHomeDir + "\\ROMS\\" + file1); - string file3(theHomeDir + '\\' + file1); - - ifstream in; - in.open(file1.c_str(), ios::in | ios::binary); - if(!in) - { - in.close(); - in.clear(); - in.open(file2.c_str(), ios::in | ios::binary); - if(!in) - { - in.close(); - in.clear(); - in.open(file3.c_str(), ios::in | ios::binary); - if(!in) - { - cerr << "ERROR: Couldn't locate " << file << "..." << endl; - exit(1); - } - } - } - - uInt8* image = new uInt8[512 * 1024]; - in.read((char*)image, 512 * 1024); - uInt32 size = in.gcount(); - in.close(); - - // Create a properties set for us to use and set it up - PropertiesSet propertiesSet; - if(!setupProperties(propertiesSet)) - { - delete[] image; - exit(1); - } - - // Get just the filename of the file containing the ROM image - const char* filename = (!strrchr(file, '\\')) ? - file : strrchr(file, '\\') + 1; - - // Create a sound object for use with the console - SoundDOS sound(theSoundEnabledFlag); -// sound.setSoundVolume(settings->theDesiredVolume); - - // Create the 2600 game console - theConsole = new Console(image, size, filename, - theEvent, propertiesSet, sound.getSampleRate()); - - // Free the image since we don't need it any longer - delete[] image; - - startup(); - - // Get the starting time in case we need to print statistics - clock_t startingTime = clock(); - - uInt32 numberOfFrames = 0; - for( ; !theQuitIndicator ; ++numberOfFrames) - { - // Remember the current time before we start drawing the frame - uclock_t startTimeStamp = uclock(); - - // Ask the media source to prepare the next frame - if(!thePauseIndicator) - { - theConsole->mediaSource().update(); - sound.mute(false); - sound.updateSound(theConsole->mediaSource()); - } - else - { - sound.mute(true); - } - - // If vsync is selected or we're running under NT then wait for VSYNC - if(windowsNtFlag || theSynchronizeVideoFlag) - { - // Wait until previous retrace has ended - while(inp(0x3DA) & 0x08); - - // Wait until next retrace has begun - while(!(inp(0x3DA) & 0x08)); - } - - // Update the display and handle events - updateDisplay(theConsole->mediaSource()); - handleEvents(); - - // Waste time if we need to so that we are at the desired frame rate - if(!(windowsNtFlag || theSynchronizeVideoFlag)) - { - for(;;) - { - uclock_t endTimeStamp = uclock(); - long long delta = endTimeStamp - startTimeStamp; - if(delta >= (UCLOCKS_PER_SEC / theDesiredFrameRate)) - { - break; - } - } - } - } - - // Get the ending time in case we need to print statistics - clock_t endingTime = clock(); - - // Close the sound device - sound.close(); - - uInt32 scanlines = theConsole->mediaSource().scanlines(); - string cartName = theConsole->properties().get("Cartridge.Name"); - string cartMD5 = theConsole->properties().get("Cartridge.MD5"); - delete theConsole; - shutdown(); - - if(theShowInfoFlag) - { - double executionTime = (endingTime - startingTime) / (double)CLOCKS_PER_SEC; - double framesPerSecond = numberOfFrames / executionTime; - - cout << endl; - cout << numberOfFrames << " total frames drawn\n"; - cout << framesPerSecond << " frames/second\n"; - cout << scanlines << " scanlines in last frame\n"; - cout << endl; - cout << "Cartridge Name: " << cartName << endl; - cout << "Cartridge MD5: " << cartMD5 << endl; - cout << endl; - cout << endl; - } -} - diff --git a/stella/src/ui/dos/scandef.h b/stella/src/ui/dos/scandef.h deleted file mode 100644 index 54709d3c5..000000000 --- a/stella/src/ui/dos/scandef.h +++ /dev/null @@ -1,132 +0,0 @@ -/*****************************************************************************/ -/* */ -/* LIBRARY Functions - Scan code definitions Copyright K.W 1994 */ -/* */ -/*****************************************************************************/ -/* */ -/* Title : Keyboard Code Definitions */ -/* */ -/* File Name : SCANDEF.H */ -/* */ -/* Author : Keith Wilkins */ -/* */ -/* Version : 0.01 */ -/* */ -/* Desciption : This header file defines all of the scan code definitions */ -/* */ -/* Functions : None */ -/* */ -/*****************************************************************************/ -/* */ -/* Revision History: */ -/* */ -/* Version Date Who Description of changes */ -/* ------- ---- --- ---------------------- */ -/* */ -/* 0.01 13/11/94 K.W Creation */ -/* */ -/* */ -/*****************************************************************************/ - -#ifndef SCANDEF_H - #define SCANDEF_H - -#define SCAN_BASE (0x00) -#define SCAN_PAUSE (0x00) // We'll use 0x00 for the pause code -#define SCAN_ESC (0x01) -#define SCAN_1 (0x02) -#define SCAN_2 (0x03) -#define SCAN_3 (0x04) -#define SCAN_4 (0x05) -#define SCAN_5 (0x06) -#define SCAN_6 (0x07) -#define SCAN_7 (0x08) -#define SCAN_8 (0x09) -#define SCAN_9 (0x0a) -#define SCAN_0 (0x0b) -#define SCAN_MINUS (0x0c) -#define SCAN_EQUALS (0x0d) -#define SCAN_BSPACE (0x0e) -#define SCAN_TAB (0x0f) - -#define SCAN_Q (0x10) -#define SCAN_W (0x11) -#define SCAN_E (0x12) -#define SCAN_R (0x13) -#define SCAN_T (0x14) -#define SCAN_Y (0x15) -#define SCAN_U (0x16) -#define SCAN_I (0x17) -#define SCAN_O (0x18) -#define SCAN_P (0x19) -#define SCAN_LSQRB (0x1a) -#define SCAN_RSQRB (0x1b) -#define SCAN_RETURN (0x1c) -#define SCAN_ENTER (0x1c) -#define SCAN_CTRL (0x1d) -#define SCAN_A (0x1e) -#define SCAN_S (0x1f) - -#define SCAN_D (0x20) -#define SCAN_F (0x21) -#define SCAN_G (0x22) -#define SCAN_H (0x23) -#define SCAN_J (0x24) -#define SCAN_K (0x25) -#define SCAN_L (0x26) -#define SCAN_SCOLON (0x27) -#define SCAN_APSTPY (0x28) // Apostrophy ''''' -#define SCAN_TILDE (0x29) -#define SCAN_LSHIFT (0x2a) -#define SCAN_HASH (0x2b) -#define SCAN_Z (0x2c) -#define SCAN_X (0x2d) -#define SCAN_C (0x2e) -#define SCAN_V (0x2f) - -#define SCAN_B (0x30) -#define SCAN_N (0x31) -#define SCAN_M (0x32) -#define SCAN_COMMA (0x33) -#define SCAN_STOP (0x34) -#define SCAN_FSLASH (0x35) -#define SCAN_RSHIFT (0x36) -#define SCAN_STAR (0x37) -#define SCAN_ALT (0x38) -#define SCAN_SPACE (0x39) -#define SCAN_CAPS (0x3a) -#define SCAN_F1 (0x3b) -#define SCAN_F2 (0x3c) -#define SCAN_F3 (0x3d) -#define SCAN_F4 (0x3e) -#define SCAN_F5 (0x3f) - -#define SCAN_F6 (0x40) -#define SCAN_F7 (0x41) -#define SCAN_F8 (0x42) -#define SCAN_F9 (0x43) -#define SCAN_F10 (0x44) -#define SCAN_NUMLCK (0x45) -#define SCAN_SCRLCK (0x46) -#define SCAN_HOME (0x47) -#define SCAN_UP (0x48) -#define SCAN_PGUP (0x49) -#define SCAN_DASH (0x4a) // Number pad minus -#define SCAN_LEFT (0x4b) -#define SCAN_CENTRE (0x4c) // Number pad centre -#define SCAN_RIGHT (0x4d) -#define SCAN_PLUS (0x4e) // Number pad plus -#define SCAN_END (0x4f) - -#define SCAN_DOWN (0x50) -#define SCAN_PGDN (0x51) -#define SCAN_INS (0x52) -#define SCAN_DEL (0x53) - -#define SCAN_BSLASH (0x56) -#define SCAN_F11 (0x57) -#define SCAN_F12 (0x58) - -#endif - -/* END OF SCANDEF.H */ diff --git a/stella/src/ui/dos/vga.cxx b/stella/src/ui/dos/vga.cxx deleted file mode 100644 index 7da41e7f7..000000000 --- a/stella/src/ui/dos/vga.cxx +++ /dev/null @@ -1,287 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2003 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// This code is based on the vga256fb frame buffer device driver for -// Linux by Salvatore Sanfilippo . The vga256fb -// code can be found at http://www.kyuzz.org/antirez/vga256fb.htm. It -// was released under the GNU General Public License. -// -// $Id: vga.cxx,v 1.1 2003-02-17 05:17:42 bwmott Exp $ -//============================================================================ - -#include -#include - -#include "vga.hxx" - -// Structure for holding VGA mode information and register settings -struct VgaModeInfo -{ - unsigned int xres; // X resolution - unsigned int yres; // Y resolution - bool chained; // Chained flag - unsigned char crt[0x19]; // 24 CRT sub-registers - unsigned char attrib[0x15]; // 21 attribute sub-registers - unsigned char graphic[0x09]; // 9 graphic sub-registers - unsigned char sequencer[0x05]; // 5 sequencer sub-registers - unsigned char misc; // misc register -}; - -// VGA mode information for 320x200x256 colors at 60Hz -static VgaModeInfo Vga320x200x60Hz = { - xres: 320, - yres: 200, - chained: true, - crt: { - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0A, 0x3E, - 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xC2, 0x84, 0x8F, 0x28, 0x40, 0x90, 0x08, 0xA3 }, - attrib: { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x41, 0x00, 0x0F, 0x00, 0x00, }, - graphic: { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF }, - sequencer: { - 0x03, 0x01, 0x0F, 0x00, 0x0E }, - misc: 0x63 -}; - -// VGA mode information for 320x200x256 colors at 70Hz (standard BIOS 13h mode) -static VgaModeInfo Vga320x200x70Hz = { - xres: 320, - yres: 200, - chained: true, - crt: { - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0xBF, 0x1F, - 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x9C, 0x8E, 0x8F, 0x28, 0x40, 0x96, 0xB9, 0xA3 }, - attrib: { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x41, 0x00, 0x0F, 0x00, 0x00 }, - graphic: { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF }, - sequencer: { - 0x03, 0x01, 0x0F, 0x00, 0x0E }, - misc: 0x63 -}; - -// VGA mode information for 320x240x256 colors at 60Hz (square pixels) -static VgaModeInfo Vga320x240x60Hz = { -xres: 320, -yres: 240, -chained: false, - crt: { - 0x5F, 0x4F, 0x50, 0x82, 0x54, 0x80, 0x0D, 0x3E, - 0x00, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0xEA, 0xAC, 0xDF, 0x28, 0x00, 0xE7, 0x06, 0xE3 }, - attrib: { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, - 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x41, 0x00, 0x0F, 0x00, 0x00, }, - graphic: { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0F, 0xFF }, - sequencer: { - 0x03, 0x01, 0x0F, 0x00, 0x06 }, - misc: 0xE3 -}; - -#define V_CRT_INDEX 0x3D4 // CRT address (index) register -#define V_CRT_RW 0x3D5 // CRT data register -#define V_ISTAT1_R 0x3DA // Input status register #1 -#define V_FEATURE_W 0x3DA // Feature control register, (write) -#define V_SEQ_INDEX 0x3C4 // Sequencer address (index) register -#define V_SEQ_RW 0x3C5 // Sequencer data register -#define V_GR_INDEX 0x3CE // VGA address (index) register -#define V_GR_RW 0x3CF // VGA data register -#define V_MISC_R 0x3CC // VGA misc register (read) -#define V_MISC_W 0x3C2 // VGA misc register (write) -#define V_ATTR_IW 0x3C0 // Attribute index and data register (write) -#define V_ATTR_R 0x3C1 // Attribute data register (read) - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline void put_sequence(int i, unsigned char b) -{ - outportb(V_SEQ_INDEX, i); - outportb(V_SEQ_RW, b); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline void put_graph(int i, unsigned char b) -{ - outportb(V_GR_INDEX, i); - outportb(V_GR_RW, b); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline void put_misc(unsigned char b) -{ - outportb(V_MISC_W, b); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline void put_attr(int i, unsigned char b) -{ - // Warning: as you can see the 0x20 bit will be set to zero - // so the video output will be disabled, if you want read or write - // (since some VGA cards allows you to write without setting the PAS - // bit) without put the video off OR the index with 0x20 */ - - // reset the flip/flop - inportb(V_ISTAT1_R); - - // set the index - outportb(V_ATTR_IW, i); - - // write data - outportb(V_ATTR_IW, b); - - // reset the flip/flop - inportb(V_ISTAT1_R); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline void put_crt(int i, unsigned char b) -{ - outportb(V_CRT_INDEX, i); - outportb(V_CRT_RW, b); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static inline unsigned char get_crt(int i) -{ - outportb(V_CRT_INDEX, i); - return inportb(V_CRT_RW); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void disable_video(void) -{ - // Get the current value - volatile unsigned char t = inportb(V_ATTR_IW); - - // Reset the flip/flop - inportb(V_ISTAT1_R); - - // Clear the PAS bit - t &= 0xDF; - - // Set the port - outportb(V_ATTR_IW, t); - - // Reset the flip/flop - inportb(V_ISTAT1_R); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void enable_video(void) -{ - // Get the current value - volatile unsigned char t = inportb(V_ATTR_IW); - - // Reset the flip/flop - inportb(V_ISTAT1_R); - - // Set the PAS bit - t |= 0x20; - - // set the port - outportb(V_ATTR_IW, t); - - // Reset the flip/flop - inportb(V_ISTAT1_R); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void unlock_crt_registers(void) -{ - volatile unsigned char aux = get_crt(0x11); - aux &= 0x7f; - put_crt(0x11, aux); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void load_regs(VgaModeInfo *regs) -{ - int j; - - disable_video(); - disable(); - - // Set misc register - put_misc(regs->misc); - - // Sequencer sync reset on - put_sequence(0x00, 0x01); - - // Sequencer registers - for(j = 0; j <= 0x04; j++) - { - put_sequence(j, regs->sequencer[j]); - } - - // Sequencer reset off - put_sequence(0x00, 0x03); - - unlock_crt_registers(); - // crt registers - for(j = 0; j <= 0x18; j++) - { - put_crt(j, regs->crt[j]); - } - - // Graphic registers - for(j = 0; j <= 0x08; j++) - { - put_graph(j, regs->graphic[j]); - } - - // Attrib registers - for(j = 0; j <= 0x14; j++) - { - put_attr(j, regs->attrib[j]); - } - - enable(); - enable_video(); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -bool VgaSetMode(int mode) -{ - VgaModeInfo* info = 0; - - if(mode == VGA_320_200_60HZ) - { - info = &Vga320x200x60Hz; - } - else if(mode == VGA_320_200_70HZ) - { - info = &Vga320x200x70Hz; - } - else if(mode == VGA_320_240_60HZ) - { - info = &Vga320x240x60Hz; - } - else - { - assert(false); - } - - load_regs(info); - return info->chained; -} - diff --git a/stella/src/ui/dos/vga.hxx b/stella/src/ui/dos/vga.hxx deleted file mode 100644 index fb8297ec4..000000000 --- a/stella/src/ui/dos/vga.hxx +++ /dev/null @@ -1,34 +0,0 @@ -//============================================================================ -// -// SSSS tt lll lll -// SS SS tt ll ll -// SS tttttt eeee ll ll aaaa -// SSSS tt ee ee ll ll aa -// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" -// SS SS tt ee ll ll aa aa -// SSSS ttt eeeee llll llll aaaaa -// -// Copyright (c) 1995-2003 by Bradford W. Mott -// -// See the file "license" for information on usage and redistribution of -// this file, and for a DISCLAIMER OF ALL WARRANTIES. -// -// $Id: vga.hxx,v 1.1 2003-02-17 05:17:42 bwmott Exp $ -//============================================================================ - -#ifndef VGA_HXX -#define VGA_HXX - -#include "bspf.hxx" - -#define VGA_320_200_60HZ 1 -#define VGA_320_200_70HZ 2 -#define VGA_320_240_60HZ 3 - -/** - Change the graphics mode to the specified mode. -*/ -extern bool VgaSetMode(int mode); - -#endif - diff --git a/stella/src/unix/SettingsUNIX.cxx b/stella/src/unix/SettingsUNIX.cxx new file mode 100644 index 000000000..e317e4648 --- /dev/null +++ b/stella/src/unix/SettingsUNIX.cxx @@ -0,0 +1,182 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SettingsUNIX.cxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include +#include +#include + +#include "bspf.hxx" +#include "Console.hxx" +#include "EventHandler.hxx" +#include "StellaEvent.hxx" + +#include "Settings.hxx" +#include "SettingsUNIX.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SettingsUNIX::SettingsUNIX() +{ + // First set variables that the parent class needs + myBaseDir = getenv("HOME"); + string stelladir = myBaseDir + "/.stella"; + + if(access(stelladir.c_str(), R_OK|W_OK|X_OK) != 0 ) + mkdir(stelladir.c_str(), 0777); + + myStateDir = stelladir + "/state/"; + if(access(myStateDir.c_str(), R_OK|W_OK|X_OK) != 0 ) + mkdir(myStateDir.c_str(), 0777); + + myUserPropertiesFile = stelladir + "/stella.pro"; + mySystemPropertiesFile = "/etc/stella.pro"; + myUserConfigFile = stelladir + "/stellarc"; + mySystemConfigFile = "/etc/stellarc"; + + // Set up the names of the input and output config files + mySettingsOutputFilename = myUserConfigFile; + if(access(myUserConfigFile.c_str(), R_OK) == 0) + mySettingsInputFilename = myUserConfigFile; + else + mySettingsInputFilename = mySystemConfigFile; + + mySnapshotFile = ""; + myStateFile = ""; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SettingsUNIX::~SettingsUNIX() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SettingsUNIX::usage(string& message) +{ + cout << endl + << message << endl + << endl + << "Valid options are:" << endl + << endl + << " -video Type is one of the following:\n" + << " soft SDL software mode\n" + #ifdef DISPLAY_OPENGL + << " gl SDL OpenGL mode\n" + << endl + << " -gl_filter Type is one of the following:\n" + << " nearest Normal scaling (GL_NEAREST)\n" + << " linear Blurred scaling (GL_LINEAR)\n" + << " -gl_aspect Scale the width by the given amount\n" + << endl + #endif + << " -sound <0|1> Enable sound generation\n" + << " -fragsize The size of sound fragments (should be a power of two)\n" + << " -bufsize The size of the sound buffer\n" + << " -framerate Display the given number of frames per second\n" + << " -zoom Makes window be 'size' times normal\n" + << " -fullscreen <0|1> Play the game in fullscreen mode\n" + << " -grabmouse <0|1> Keeps the mouse in the game window\n" + << " -hidecursor <0|1> Hides the mouse cursor in the game window\n" + << " -volume Set the volume (0 - 100)\n" + #ifdef JOYSTICK_SUPPORT + << " -paddle <0|1|2|3|real> Indicates which paddle the mouse should emulate\n" + << " or that real Atari 2600 paddles are being used\n" + #else + << " -paddle <0|1|2|3> Indicates which paddle the mouse should emulate\n" + #endif + << " -altpro Use the given properties file instead of stella.pro\n" + << " -showinfo <0|1> Shows some game info\n" + << " -accurate <0|1> Accurate game timing (uses more CPU)\n" + #ifdef SNAPSHOT_SUPPORT + << " -ssdir The directory to save snapshot files to\n" + << " -ssname How to name the snapshot (romname or md5sum)\n" + << " -sssingle <0|1> Generate single snapshot instead of many\n" + #endif + << endl + #ifdef DEVELOPER_SUPPORT + << " DEVELOPER options (see Stella manual for details)\n" + << " -Dformat Sets \"Display.Format\"\n" + << " -Dxstart Sets \"Display.XStart\"\n" + << " -Dwidth Sets \"Display.Width\"\n" + << " -Dystart Sets \"Display.YStart\"\n" + << " -Dheight Sets \"Display.Height\"\n" + << " -mergeprops <0|1> Merge changed properties into properties file,\n" + << " or save into a separate file\n" + #endif + << endl; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string SettingsUNIX::stateFilename(uInt32 state) +{ + if(!myConsole) + return ""; + + ostringstream buf; + buf << myStateDir << myConsole->properties().get("Cartridge.MD5") + << ".st" << state; + + myStateFile = buf.str(); + return myStateFile; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string SettingsUNIX::snapshotFilename() +{ + if(!myConsole) + return ""; + + string filename; + string path = getString("ssdir"); + string theSnapshotName = getString("ssname"); + + if(theSnapshotName == "romname") + path = path + "/" + myConsole->properties().get("Cartridge.Name"); + else if(theSnapshotName == "md5sum") + path = path + "/" + myConsole->properties().get("Cartridge.MD5"); + + // Replace all spaces in name with underscores + replace(path.begin(), path.end(), ' ', '_'); + + // Check whether we want multiple snapshots created + if(!getBool("sssingle")) + { + // Determine if the file already exists, checking each successive filename + // until one doesn't exist + filename = path + ".png"; + if(access(filename.c_str(), F_OK) == 0 ) + { + ostringstream buf; + for(uInt32 i = 1; ;++i) + { + buf.str(""); + buf << path << "_" << i << ".png"; + if(access(buf.str().c_str(), F_OK) == -1 ) + break; + } + filename = buf.str(); + } + } + else + filename = path + ".png"; + + mySnapshotFile = filename; + return mySnapshotFile; +} diff --git a/stella/src/unix/SettingsUNIX.hxx b/stella/src/unix/SettingsUNIX.hxx new file mode 100644 index 000000000..6def00ab9 --- /dev/null +++ b/stella/src/unix/SettingsUNIX.hxx @@ -0,0 +1,70 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SettingsUNIX.hxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +//============================================================================ + +#ifndef SETTINGS_UNIX_HXX +#define SETTINGS_UNIX_HXX + +#include "bspf.hxx" + +class Console; + + +/** + This class defines UNIX-like OS's (Linux) system specific settings. + + @author Stephen Anthony + @version $Id: SettingsUNIX.hxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +*/ +class SettingsUNIX : public Settings +{ + public: + /** + Create a new UNIX settings object + */ + SettingsUNIX(); + + /** + Destructor + */ + virtual ~SettingsUNIX(); + + public: + /** + This method should be called to get the filename of a state file + given the state number. + + @return String representing the full path of the state filename. + */ + virtual string stateFilename(uInt32 state); + + /** + This method should be called to get the filename of a snapshot. + + @return String representing the full path of the snapshot filename. + */ + virtual string snapshotFilename(); + + /** + Display the commandline settings for this UNIX version of Stella. + + @param message A short message about this version of Stella + */ + virtual void usage(string& message); +}; + +#endif diff --git a/stella/src/win32/SettingsWin32.cxx b/stella/src/win32/SettingsWin32.cxx new file mode 100644 index 000000000..5ef1f380e --- /dev/null +++ b/stella/src/win32/SettingsWin32.cxx @@ -0,0 +1,188 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SettingsWin32.cxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +//============================================================================ + +#include +#include +#include + +#include +#include +#include + +#include "bspf.hxx" +#include "Console.hxx" +#include "EventHandler.hxx" +#include "StellaEvent.hxx" + +#include "Settings.hxx" +#include "SettingsWin32.hxx" + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SettingsWin32::SettingsWin32() +{ + // First set variables that the parent class needs + myBaseDir = ".\\"; + string stelladir = myBaseDir; + +// if(access(stelladir.c_str(), R_OK|W_OK|X_OK) != 0 ) +// mkdir(stelladir.c_str(), 0777); + + myStateDir = stelladir + "state\\"; +// if(access(myStateDir.c_str(), R_OK|W_OK|X_OK) != 0 ) +// mkdir(myStateDir.c_str(), 0777); + + myUserPropertiesFile = stelladir + "stella.pro"; + mySystemPropertiesFile = stelladir + "stella.pro"; + myUserConfigFile = stelladir + "stellarc"; + mySystemConfigFile = stelladir + "stellarc"; + + // Set up the names of the input and output config files + mySettingsOutputFilename = myUserConfigFile; +// if(access(myUserConfigFile.c_str(), R_OK) == 0) + mySettingsInputFilename = myUserConfigFile; +// else +// mySettingsInputFilename = mySystemConfigFile; + + mySnapshotFile = ""; + myStateFile = ""; + + // Now create Win32 specific settings + set("accurate", "false"); // Don't change this, or the sound will skip +#ifdef SNAPSHOT_SUPPORT + set("ssdir", ".\\"); +#endif +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +SettingsWin32::~SettingsWin32() +{ +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void SettingsWin32::usage(string& message) +{ + cout << endl + << message << endl + << endl + << "Valid options are:" << endl + << endl + << " -video Type is one of the following:\n" + << " soft SDL software mode\n" + #ifdef DISPLAY_OPENGL + << " gl SDL OpenGL mode\n" + << endl + << " -gl_filter Type is one of the following:\n" + << " nearest Normal scaling (GL_NEAREST)\n" + << " linear Blurred scaling (GL_LINEAR)\n" + << " -gl_aspect Scale the width by the given amount\n" + << endl + #endif + << " -sound <0|1> Enable sound generation\n" + << " -fragsize The size of sound fragments (should be a power of two)\n" + << " -bufsize The size of the sound buffer\n" + << " -framerate Display the given number of frames per second\n" + << " -zoom Makes window be 'size' times normal\n" + << " -fullscreen <0|1> Play the game in fullscreen mode\n" + << " -grabmouse <0|1> Keeps the mouse in the game window\n" + << " -hidecursor <0|1> Hides the mouse cursor in the game window\n" + << " -volume Set the volume (0 - 100)\n" + #ifdef JOYSTICK_SUPPORT + << " -paddle <0|1|2|3|real> Indicates which paddle the mouse should emulate\n" + << " or that real Atari 2600 paddles are being used\n" + #else + << " -paddle <0|1|2|3> Indicates which paddle the mouse should emulate\n" + #endif + << " -altpro Use the given properties file instead of stella.pro\n" + << " -showinfo <0|1> Shows some game info\n" + << " -accurate <0|1> Accurate game timing (uses more CPU)\n" + #ifdef SNAPSHOT_SUPPORT + << " -ssdir The directory to save snapshot files to\n" + << " -ssname How to name the snapshot (romname or md5sum)\n" + << " -sssingle <0|1> Generate single snapshot instead of many\n" + #endif + << endl + #ifdef DEVELOPER_SUPPORT + << " DEVELOPER options (see Stella manual for details)\n" + << " -Dformat Sets \"Display.Format\"\n" + << " -Dxstart Sets \"Display.XStart\"\n" + << " -Dwidth Sets \"Display.Width\"\n" + << " -Dystart Sets \"Display.YStart\"\n" + << " -Dheight Sets \"Display.Height\"\n" + << " -mergeprops <0|1> Merge changed properties into properties file,\n" + << " or save into a separate file\n" + #endif + << endl; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string SettingsWin32::stateFilename(uInt32 state) +{ + if(!myConsole) + return ""; + + ostringstream buf; + buf << myStateDir << myConsole->properties().get("Cartridge.MD5") + << ".st" << state; + + myStateFile = buf.str(); + return myStateFile; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string SettingsWin32::snapshotFilename() +{ + if(!myConsole) + return ""; + + string filename; + string path = getString("ssdir"); + string theSnapshotName = getString("ssname"); + + if(theSnapshotName == "romname") + path = path + "\\" + myConsole->properties().get("Cartridge.Name"); + else if(theSnapshotName == "md5sum") + path = path + "\\" + myConsole->properties().get("Cartridge.MD5"); + + // Replace all spaces in name with underscores + replace(path.begin(), path.end(), ' ', '_'); + + // Check whether we want multiple snapshots created + if(!getBool("sssingle")) + { + // Determine if the file already exists, checking each successive filename + // until one doesn't exist + filename = path + ".png"; + if(access(filename.c_str(), F_OK) == 0 ) + { + ostringstream buf; + for(uInt32 i = 1; ;++i) + { + buf.str(""); + buf << path << "_" << i << ".png"; + if(access(buf.str().c_str(), F_OK) == -1 ) + break; + } + filename = buf.str(); + } + } + else + filename = path + ".png"; + + mySnapshotFile = filename; + return mySnapshotFile; +} diff --git a/stella/src/win32/SettingsWin32.hxx b/stella/src/win32/SettingsWin32.hxx new file mode 100644 index 000000000..5543a58fd --- /dev/null +++ b/stella/src/win32/SettingsWin32.hxx @@ -0,0 +1,70 @@ +//============================================================================ +// +// SSSS tt lll lll +// SS SS tt ll ll +// SS tttttt eeee ll ll aaaa +// SSSS tt ee ee ll ll aa +// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator" +// SS SS tt ee ll ll aa aa +// SSSS ttt eeeee llll llll aaaaa +// +// Copyright (c) 1995-1999 by Bradford W. Mott +// +// See the file "license" for information on usage and redistribution of +// this file, and for a DISCLAIMER OF ALL WARRANTIES. +// +// $Id: SettingsWin32.hxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +//============================================================================ + +#ifndef SETTINGS_WIN32_HXX +#define SETTINGS_WIN32_HXX + +#include "bspf.hxx" + +class Console; + + +/** + This class defines Windows system specific settings. + + @author Stephen Anthony + @version $Id: SettingsWin32.hxx,v 1.1 2004-05-24 17:18:23 stephena Exp $ +*/ +class SettingsWin32 : public Settings +{ + public: + /** + Create a new UNIX settings object + */ + SettingsWin32(); + + /** + Destructor + */ + virtual ~SettingsWin32(); + + public: + /** + This method should be called to get the filename of a state file + given the state number. + + @return String representing the full path of the state filename. + */ + virtual string stateFilename(uInt32 state); + + /** + This method should be called to get the filename of a snapshot. + + @return String representing the full path of the snapshot filename. + */ + virtual string snapshotFilename(); + + /** + Display the commandline settings for this UNIX version of Stella. + + @param message A short message about this version of Stella + */ + virtual void usage(string& message); +}; + +#endif