Source

gb_emulator / gb_emulator / src / gb_input.cpp

/*  Copyright © 2011 Chris Spencer <spencercw@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

#include <gb_emulator/gb_input.h>

#include <Windows.h>
#include <CommDlg.h>
#include <XInput.h>

#include <gb_emulator/gb.h>
#include <gb_emulator/gb_memory.h>
#include <gb_emulator/gb_sound.h>
#include <gb_emulator/gb_video.h>

static const double PI = 3.14159265358979;

GbInput::GbInput(Gb &gb):
gb_(gb),
keys_(0),
joy_(0),
joyAxis_(0),
xInputPacket_(0),
xInputPoll_(0),
recording_(false)
{
}

GbInput::~GbInput()
{
}

void GbInput::poll()
{
	MSG message;
	while (PeekMessage(&message, NULL, 0, 0, PM_REMOVE))
	{
		// "Steal" keyboard input messages, or otherwise pass them on
		switch (message.message)
		{
		case WM_KEYDOWN:
		case WM_KEYUP:
			{
				// Translate the key
				uint8_t key = KEY_NONE;
				switch (message.wParam)
				{
				// A
				case 'A':
				case VK_RETURN:
				case VK_SPACE:
					key = KEY_A;
					break;

				// B
				case 'B':
				case VK_ESCAPE:
				case VK_BACK:
					key = KEY_B;
					break;

				// Start
				case VK_INSERT:
				case VK_HOME:
				case VK_PRIOR:
					key = KEY_START;
					break;
				
				// Select
				case VK_DELETE:
				case VK_END:
				case VK_NEXT:
					key = KEY_SELECT;
					break;

				// Directions
				case VK_UP:
					key = KEY_UP;
					break;
				case VK_DOWN:
					key = KEY_DOWN;
					break;
				case VK_LEFT:
					key = KEY_LEFT;
					break;
				case VK_RIGHT:
					key = KEY_RIGHT;
					break;

				default:
					// Handle command key presses
					if (message.message == WM_KEYUP)
					{
						switch (message.wParam)
						{
						case VK_F5:
							// Save
							gb_.save();
							break;

						case VK_F8:
							// Load
							gb_.load();
							break;

						case 'O':
							// Open ROM
							openRomDialog();
							break;

						case 'Q':
							// Quit
							gb_.saveRam();
							exit(0);
							break;

						// Debugging
						#ifdef DEBUG
						case 'P':  // P
							gb_.cpu_->pause = !gb_.cpu_->pause;
							break;
						case 'R':
							// Toggle audio recording
							if (!recording_)
							{
								gb_.sound_->record("audio.wav");
							}
							else
							{
								gb_.sound_->stopRecording();
							}
							recording_ = !recording_;
							break;
						case 'C':  // C
							gb_.video_->drawBackground = !gb_.video_->drawBackground;
							break;
						case 'W':  // W
							gb_.video_->drawWindow = !gb_.video_->drawWindow;
							break;
						case 'S':  // S
							gb_.video_->drawSprites = !gb_.video_->drawSprites;
							break;
						case 'D':  // D
							gb_.video_->dumpGraphics();
							break;
						#endif
						}
					}
				}

				// Handle the key
				if (key != KEY_NONE)
				{
					if (message.message == WM_KEYUP)
					{
						keys_ &= ~key;
					}
					else
					{
						keys_ |=  key;
					}
				}
			}
			break;

		case WM_QUIT:
			// Save the battery-backed RAM and exit
			gb_.saveRam();
			exit(0);
			break;

		default:
			DispatchMessage(&message);
		}
	}

	// Check for any controller input. Don't check every time because XInputGetState() is rather
	// slow
	XINPUT_STATE state;
	memset(&state, 0, sizeof(state));
	if (!(++xInputPoll_ %= 100) && XInputGetState(0, &state) == ERROR_SUCCESS)
	{
		if (state.dwPacketNumber != xInputPacket_)
		{
			// Buttons
			joy_ = KEY_NONE;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP)    joy_ |= KEY_UP;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN)  joy_ |= KEY_DOWN;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)  joy_ |= KEY_LEFT;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) joy_ |= KEY_RIGHT;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START)      joy_ |= KEY_START;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK)       joy_ |= KEY_SELECT;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A)          joy_ |= KEY_A;
			if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B)          joy_ |= KEY_B;

			// Thumb stick
			double lx = state.Gamepad.sThumbLX;
			double ly = state.Gamepad.sThumbLY;
			double magnitude = sqrt(lx * lx + ly * ly);

			if (magnitude > XINPUT_GAMEPAD_LEFT_THUMB_DEADZONE)
			{
				// Determine the angle
				double angle = atan2(ly, lx);
				if (angle < 0.25 * PI && angle >= -0.25 * PI)
				{
					joyAxis_ = KEY_RIGHT;
				}
				else if (angle < -0.25 * PI && angle >= -0.75 * PI)
				{
					joyAxis_ = KEY_DOWN;
				}
				else if (angle < 0.75 * PI && angle >= 0.25 * PI)
				{
					joyAxis_ = KEY_UP;
				}
				else
				{
					joyAxis_ = KEY_LEFT;
				}
			}
			else
			{
				joyAxis_ = KEY_NONE;
			}

			xInputPacket_ = state.dwPacketNumber;
		}
	}

	// Populate the joypad register
	uint8_t prev = gb_.mem_->ioPorts()[P1];
	uint8_t p1 = translate();

	// Bring the CPU out of stop mode and request an interrupt if any of the pins have gone from
	// high to low
	if (((prev & JOYP_RIGHT_OR_A)     && !(p1 & JOYP_RIGHT_OR_A))    ||
	    ((prev & JOYP_LEFT_OR_B)      && !(p1 & JOYP_LEFT_OR_B))     ||
	    ((prev & JOYP_DOWN_OR_START)  && !(p1 & JOYP_DOWN_OR_START)) ||
	    ((prev & JOYP_UP_OR_SELECT)   && !(p1 & JOYP_UP_OR_SELECT)))
	{
		gb_.cpu_->stop = false;
		gb_.mem_->ioPorts()[IF] |= JOYPAD_INTR;
	}
}

uint8_t GbInput::translate()
{
	uint8_t p1 = gb_.mem_->ioPorts()[P1];
	p1 &= 0x30;
	p1 |= 0x0f;

	if (!(p1 & JOYP_BUTTONS))
	{
		if ((keys_ & KEY_A)      || (joy_ & KEY_A))      p1 &= ~JOYP_RIGHT_OR_A;
		if ((keys_ & KEY_B)      || (joy_ & KEY_B))      p1 &= ~JOYP_LEFT_OR_B;
		if ((keys_ & KEY_START)  || (joy_ & KEY_START))  p1 &= ~JOYP_DOWN_OR_START;
		if ((keys_ & KEY_SELECT) || (joy_ & KEY_SELECT)) p1 &= ~JOYP_UP_OR_SELECT;
	}
	if (!(p1 & JOYP_DIRECTION))
	{
		if ((keys_ & KEY_UP)    || (joy_ & KEY_UP)    || (joyAxis_ & KEY_UP))    p1 &= ~JOYP_UP_OR_SELECT;
		if ((keys_ & KEY_DOWN)  || (joy_ & KEY_DOWN)  || (joyAxis_ & KEY_DOWN))  p1 &= ~JOYP_DOWN_OR_START;
		if ((keys_ & KEY_LEFT)  || (joy_ & KEY_LEFT)  || (joyAxis_ & KEY_LEFT))  p1 &= ~JOYP_LEFT_OR_B;
		if ((keys_ & KEY_RIGHT) || (joy_ & KEY_RIGHT) || (joyAxis_ & KEY_RIGHT)) p1 &= ~JOYP_RIGHT_OR_A;
	}

	gb_.mem_->ioPorts()[P1] = p1;
	return p1;
}

void GbInput::openRomDialog()
{
#ifdef _WIN32
	wchar_t fileName[32768];
	fileName[0] = L'\0';

	OPENFILENAMEW ofn;
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = gb_.video_->window();
	ofn.lpstrFilter = L"Game Boy ROMs (*.gb, *.gbc)\0*.gb;*.gbc\0";
	ofn.lpstrFile = fileName;
	ofn.nMaxFile = sizeof(fileName)/sizeof(fileName[0]);
	ofn.lpstrTitle = L"Open ROM";
	ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_PATHMUSTEXIST;

	if (GetOpenFileNameW(&ofn))
	{
		gb_.reset(ofn.lpstrFile);
	}
#else
#error Not implemented.
#endif
}