snes9x/qt/src/Snes9xController.cpp

853 lines
20 KiB
C++
Raw Normal View History

2023-03-23 21:53:43 +00:00
#include "Snes9xController.hpp"
#include "EmuConfig.hpp"
2023-03-23 21:53:43 +00:00
#include "SoftwareScalers.hpp"
#include <filesystem>
namespace fs = std::filesystem;
#include "snes9x.h"
#include "memmap.h"
#include "apu/apu.h"
#include "gfx.h"
#include "snapshot.h"
#include "controls.h"
#include "cheats.h"
#include "movie.h"
#include "display.h"
#include "conffile.h"
#include "statemanager.h"
Snes9xController *g_snes9xcontroller = nullptr;
StateManager g_state_manager;
Snes9xController::Snes9xController()
{
init();
}
Snes9xController::~Snes9xController()
{
deinit();
}
Snes9xController *Snes9xController::get()
{
if (!g_snes9xcontroller)
{
g_snes9xcontroller = new Snes9xController();
}
return g_snes9xcontroller;
}
void Snes9xController::init()
{
Settings.MouseMaster = true;
Settings.SuperScopeMaster = true;
Settings.JustifierMaster = true;
Settings.MultiPlayer5Master = true;
Settings.Transparency = true;
Settings.Stereo = true;
Settings.ReverseStereo = false;
Settings.SixteenBitSound = true;
Settings.StopEmulation = true;
Settings.HDMATimingHack = 100;
Settings.SkipFrames = 0;
Settings.TurboSkipFrames = 9;
Settings.NetPlay = false;
Settings.UpAndDown = false;
Settings.InterpolationMethod = DSP_INTERPOLATION_GAUSSIAN;
Settings.FrameTime = 16639;
Settings.FrameTimeNTSC = 16639;
Settings.FrameTimePAL = 20000;
Settings.DisplayFrameRate = false;
Settings.DisplayTime = false;
Settings.DisplayFrameRate = false;
Settings.DisplayPressedKeys = false;
Settings.DisplayIndicators = true;
Settings.SoundPlaybackRate = 48000;
Settings.SoundInputRate = 32040;
Settings.BlockInvalidVRAMAccess = true;
Settings.SoundSync = false;
Settings.Mute = false;
Settings.DynamicRateControl = false;
Settings.DynamicRateLimit = 5;
Settings.SuperFXClockMultiplier = 100;
Settings.MaxSpriteTilesPerLine = 34;
Settings.OneClockCycle = 6;
Settings.OneSlowClockCycle = 8;
Settings.TwoClockCycles = 12;
Settings.ShowOverscan = false;
Settings.InitialInfoStringTimeout = 120;
CPU.Flags = 0;
rewind_buffer_size = 0;
rewind_frame_interval = 5;
Memory.Init();
S9xInitAPU();
S9xInitSound(0);
S9xSetSamplesAvailableCallback([](void *data) {
((Snes9xController *)data)->SamplesAvailable();
}, this);
S9xGraphicsInit();
S9xInitInputDevices();
S9xUnmapAllControls();
S9xCheatsEnable();
active = false;
}
void Snes9xController::deinit()
{
if (active)
S9xAutoSaveSRAM();
S9xGraphicsDeinit();
S9xDeinitAPU();
}
void Snes9xController::updateSettings(const EmuConfig * const config)
{
Settings.UpAndDown = config->allow_opposing_dpad_directions;
Settings.InterpolationMethod = config->sound_filter;
if (config->fixed_frame_rate == 0.0)
{
Settings.FrameTimeNTSC = 16639;
Settings.FrameTimePAL = 20000;
Settings.FrameTime = Settings.FrameTimeNTSC;
}
else
{
Settings.FrameTimeNTSC = Settings.FrameTimePAL = Settings.FrameTime =
1000000 / config->fixed_frame_rate;
}
Settings.TurboSkipFrames = config->fast_forward_skip_frames;
Settings.DisplayTime = config->show_time;
if (config->display_messages == EmuConfig::eInscreen)
Settings.AutoDisplayMessages = true;
else
Settings.AutoDisplayMessages = false;
Settings.DisplayFrameRate = config->show_frame_rate;
Settings.DisplayPressedKeys = config->show_pressed_keys;
Settings.DisplayIndicators = config->show_indicators;
if (Settings.SoundPlaybackRate != config->playback_rate || Settings.SoundInputRate != config->input_rate)
{
Settings.SoundInputRate = config->input_rate;
Settings.SoundPlaybackRate = config->playback_rate;
S9xUpdateDynamicRate();
}
Settings.BlockInvalidVRAMAccess = !config->allow_invalid_vram_access;
Settings.SoundSync = config->speed_sync_method == EmuConfig::eSoundSync;
Settings.Mute = config->mute_audio;
Settings.DynamicRateControl = config->dynamic_rate_control;
Settings.DynamicRateLimit = config->dynamic_rate_limit * 1000;
Settings.SuperFXClockMultiplier = config->superfx_clock_multiplier;
if (rewind_buffer_size != config->rewind_buffer_size && active)
{
g_state_manager.init(config->rewind_buffer_size * 1048576);
}
rewind_buffer_size = config->rewind_buffer_size;
rewind_frame_interval = config->rewind_frame_interval;
if (config->remove_sprite_limit)
Settings.MaxSpriteTilesPerLine = 128;
else
Settings.MaxSpriteTilesPerLine = 34;
2023-09-02 21:07:16 +00:00
const int overclock_cycles[4][2] = { { 6, 8 }, { 6, 6 }, { 3, 4 }, { 1, 1 } };
Settings.OneClockCycle = overclock_cycles[config->overclock][0];
Settings.OneSlowClockCycle = overclock_cycles[config->overclock][1];
Settings.TwoClockCycles = overclock_cycles[config->overclock][0] * 2;
2023-03-23 21:53:43 +00:00
Settings.ShowOverscan = config->show_overscan;
high_resolution_effect = config->high_resolution_effect;
config_folder = config->findConfigDir();
auto doFolder = [&](int location, std::string &dest, const std::string &src, const char *subfolder_name)
{
if (location == EmuConfig::eROMDirectory)
dest = "";
else if (location == EmuConfig::eConfigDirectory)
dest = config_folder + "/" + subfolder_name;
else
dest = src;
};
doFolder(config->sram_location, sram_folder, config->sram_folder, "sram");
doFolder(config->state_location, state_folder, config->state_folder, "state");
doFolder(config->cheat_location, cheat_folder, config->cheat_folder, "cheat");
doFolder(config->patch_location, patch_folder, config->patch_folder, "patch");
doFolder(config->export_location, export_folder, config->export_folder, "export");
}
bool Snes9xController::openFile(std::string filename)
{
if (active)
S9xAutoSaveSRAM();
active = false;
auto result = Memory.LoadROM(filename.c_str());
if (result)
{
active = true;
Memory.LoadSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str());
}
return active;
}
void Snes9xController::mainLoop()
{
if (!active)
return;
if (rewind_buffer_size > 0)
{
if (rewinding)
{
uint16 joypads[8];
for (int i = 0; i < 8; i++)
joypads[i] = MovieGetJoypad(i);
rewinding = g_state_manager.pop();
for (int i = 0; i < 8; i++)
MovieSetJoypad(i, joypads[i]);
}
else if (IPPU.TotalEmulatedFrames % rewind_frame_interval == 0)
g_state_manager.push();
if (rewinding)
Settings.Mute |= 0x80;
else
Settings.Mute &= ~0x80;
}
S9xMainLoop();
}
void Snes9xController::setPaused(bool paused)
{
Settings.Paused = paused;
}
void Snes9xController::updateSoundBufferLevel(int empty, int total)
{
S9xUpdateDynamicRate(empty, total);
}
bool8 S9xDeinitUpdate(int width, int height)
{
static int last_width = 0;
static int last_height = 0;
int yoffset = 0;
auto &display = Snes9xController::get()->screen_output_function;
if (display == nullptr)
return true;
if (width < 256 || height < 224)
return false;
if (last_height > height)
memset(GFX.Screen + GFX.RealPPL * height, 0, GFX.Pitch * (last_height - height));
last_width = width;
last_height = height;
if (Settings.ShowOverscan)
{
if (height == SNES_HEIGHT)
{
yoffset = -8;
height = SNES_HEIGHT_EXTENDED;
}
if (height == SNES_HEIGHT * 2)
{
yoffset = -16;
height = SNES_HEIGHT_EXTENDED * 2;
}
}
else
{
if (height == SNES_HEIGHT_EXTENDED)
{
yoffset = 7;
height = SNES_HEIGHT;
}
if (height == SNES_HEIGHT_EXTENDED * 2)
{
yoffset = 14;
height = SNES_HEIGHT * 2;
}
}
uint16_t *screen_view = GFX.Screen + (yoffset * (int)GFX.RealPPL);
auto hires_effect = Snes9xController::get()->high_resolution_effect;
if (!Settings.Paused)
{
if (hires_effect == EmuConfig::eScaleUp)
{
S9xForceHires(screen_view, GFX.Pitch, width, height);
last_width = width;
}
else if (hires_effect == EmuConfig::eScaleDown)
{
S9xMergeHires(screen_view, GFX.Pitch, width, height);
last_width = width;
}
}
display(screen_view, width, height, GFX.Pitch, Settings.PAL ? 50.0 : 60.098813);
return true;
}
bool8 S9xContinueUpdate(int width, int height)
{
return S9xDeinitUpdate(width, height);
}
void S9xSyncSpeed()
{
if (Settings.TurboMode)
{
IPPU.FrameSkip++;
if ((IPPU.FrameSkip > Settings.TurboSkipFrames) && !Settings.HighSpeedSeek)
{
IPPU.FrameSkip = 0;
IPPU.SkippedFrames = 0;
IPPU.RenderThisFrame = true;
}
else
{
IPPU.SkippedFrames++;
IPPU.RenderThisFrame = false;
}
return;
}
IPPU.RenderThisFrame = true;
}
void S9xParsePortConfig(ConfigFile&, int)
{
}
std::string S9xGetDirectory(s9x_getdirtype dirtype)
{
std::string dirname;
auto c = Snes9xController::get();
switch (dirtype)
{
case HOME_DIR:
dirname = c->config_folder;
break;
case SNAPSHOT_DIR:
dirname = c->state_folder;
break;
case PATCH_DIR:
dirname = c->patch_folder;
break;
case CHEAT_DIR:
dirname = c->cheat_folder;
break;
case SRAM_DIR:
dirname = c->sram_folder;
break;
case SCREENSHOT_DIR:
case SPC_DIR:
dirname = c->export_folder;
break;
default:
dirname = "";
}
/* Check if directory exists, make it and/or set correct permissions */
if (dirtype != HOME_DIR && dirname != "")
{
fs::path path(dirname);
if (!fs::exists(path))
{
fs::create_directories(path);
2023-03-23 21:53:43 +00:00
}
else if ((fs::status(path).permissions() & fs::perms::owner_write) == fs::perms::none)
{
fs::permissions(path, fs::perms::owner_write, fs::perm_options::add);
}
}
/* Anything else, use ROM filename path */
if (dirname == "" && !Memory.ROMFilename.empty())
{
fs::path path(Memory.ROMFilename);
path.remove_filename();
if (!fs::is_directory(path))
dirname = fs::current_path().u8string();
else
dirname = path.u8string();
}
return dirname;
}
void S9xInitInputDevices()
{
}
void S9xHandlePortCommand(s9xcommand_t, short, short)
{
}
bool S9xPollButton(unsigned int, bool *)
{
return false;
}
void S9xToggleSoundChannel(int c)
{
static int sound_switch = 255;
if (c == 8)
sound_switch = 255;
else
sound_switch ^= 1 << c;
S9xSetSoundControl(sound_switch);
}
std::string S9xGetFilenameInc(std::string e, enum s9x_getdirtype dirtype)
{
fs::path rom_filename(Memory.ROMFilename);
fs::path filename_base(S9xGetDirectory(dirtype));
filename_base /= rom_filename.filename();
fs::path new_filename;
if (e[0] != '.')
e = "." + e;
int i = 0;
do
{
std::string new_extension = std::to_string(i);
while (new_extension.length() < 3)
new_extension = "0" + new_extension;
new_extension += e;
new_filename = filename_base;
new_filename.replace_extension(new_extension);
i++;
} while (fs::exists(new_filename));
return new_filename.u8string();
}
bool8 S9xInitUpdate()
{
return true;
}
void S9xExtraUsage()
{
}
bool8 S9xOpenSoundDevice()
{
return true;
}
bool S9xPollAxis(unsigned int axis, short *value)
{
return true;
}
void S9xParseArg(char *argv[], int &index, int argc)
{
}
void S9xExit()
{
}
bool S9xPollPointer(unsigned int, short *, short *)
{
return false;
}
void Snes9xController::SamplesAvailable()
{
static std::vector<int16_t> data;
if (sound_output_function)
{
int samples = S9xGetSampleCount();
if (data.size() < samples)
data.resize(samples);
S9xMixSamples((uint8_t *)data.data(), samples);
sound_output_function(data.data(), samples);
}
else
{
S9xClearSamples();
}
}
2023-06-28 22:58:00 +00:00
void Snes9xController::clearSoundBuffer()
{
S9xClearSamples();
}
2023-03-23 21:53:43 +00:00
void S9xMessage(int message_class, int type, const char *message)
{
S9xSetInfoString(message);
}
const char *S9xStringInput(const char *prompt)
{
return "";
}
bool8 S9xOpenSnapshotFile(const char *filename, bool8 read_only, STREAM *file)
{
if (read_only)
{
if ((*file = OPEN_STREAM(filename, "rb")))
return (true);
else
fprintf(stderr, "Failed to open file stream for reading.\n");
}
else
{
if ((*file = OPEN_STREAM(filename, "wb")))
{
return (true);
}
else
{
fprintf(stderr, "Couldn't open stream with zlib.\n");
}
}
fprintf(stderr, "Couldn't open snapshot file:\n%s\n", filename);
return false;
}
void S9xCloseSnapshotFile(STREAM file)
{
CLOSE_STREAM(file);
}
void S9xAutoSaveSRAM()
{
2023-07-03 22:19:54 +00:00
printf("%s\n", S9xGetFilename(".srm", SRAM_DIR).c_str());
2023-03-23 21:53:43 +00:00
Memory.SaveSRAM(S9xGetFilename(".srm", SRAM_DIR).c_str());
S9xSaveCheatFile(S9xGetFilename(".cht", CHEAT_DIR).c_str());
}
bool Snes9xController::acceptsCommand(const char *command)
{
auto cmd = S9xGetCommandT(command);
return !(cmd.type == S9xNoMapping || cmd.type == S9xBadMapping);
}
void Snes9xController::updateBindings(const EmuConfig *const config)
{
const char *snes9x_names[] = {
"Up",
"Down",
"Left",
"Right",
"A",
"B",
"X",
"Y",
"L",
"R",
"Start",
"Select",
"Turbo A",
"Turbo B",
"Turbo X",
"Turbo Y",
"Turbo L",
"Turbo R",
};
S9xUnmapAllControls();
switch (config->port_configuration)
{
case EmuConfig::eTwoControllers:
S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);
S9xSetController(1, CTL_JOYPAD, 1, 1, 1, 1);
break;
case EmuConfig::eMousePlusController:
S9xSetController(0, CTL_MOUSE, 0, 0, 0, 0);
S9xSetController(1, CTL_JOYPAD, 0, 0, 0, 0);
break;
case EmuConfig::eSuperScopePlusController:
S9xSetController(0, CTL_SUPERSCOPE, 0, 0, 0, 0);
S9xSetController(1, CTL_JOYPAD, 0, 0, 0, 0);
break;
case EmuConfig::eControllerPlusMultitap:
S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);
S9xSetController(1, CTL_MP5, 1, 2, 3, 4);
break;
default:
S9xSetController(0, CTL_JOYPAD, 0, 0, 0, 0);
S9xSetController(1, CTL_NONE, 0, 0, 0, 0);
}
2023-03-23 21:53:43 +00:00
for (int controller_number = 0; controller_number < 5; controller_number++)
{
auto &controller = config->binding.controller[controller_number];
for (int i = 0; i < config->num_controller_bindings; i++)
{
for (int b = 0; b < config->allowed_bindings; b++)
{
auto binding = controller.buttons[i * config->allowed_bindings + b];
if (binding.hash() == 0)
continue;
std::string name = "Joypad" +
std::to_string(controller_number + 1) + " " +
snes9x_names[i];
auto cmd = S9xGetCommandT(name.c_str());
S9xMapButton(binding.hash(), cmd, false);
}
}
}
for (int i = 0; i < config->num_shortcuts; i++)
{
auto command = S9xGetCommandT(EmuConfig::getShortcutNames()[i]);
if (command.type == S9xNoMapping)
continue;
for (int b = 0; b < 4; b++)
{
auto binding = config->binding.shortcuts[i * 4 + b];
if (binding.type != 0)
S9xMapButton(binding.hash(), command, false);
}
}
2024-04-30 21:28:45 +00:00
auto cmd = S9xGetCommandT("Pointer Mouse1+Superscope+Justifier1");
S9xMapPointer(EmuBinding::MOUSE_POINTER, cmd, false);
mouse_x = mouse_y = 0;
S9xReportPointer(EmuBinding::MOUSE_POINTER, mouse_x, mouse_y);
cmd = S9xGetCommandT("{Mouse1 L,Superscope Fire,Justifier1 Trigger}");
S9xMapButton(EmuBinding::MOUSE_BUTTON1, cmd, false);
cmd = S9xGetCommandT("{Justifier1 AimOffscreen Trigger,Superscope AimOffscreen}");
S9xMapButton(EmuBinding::MOUSE_BUTTON3, cmd, false);
cmd = S9xGetCommandT("{Mouse1 R,Superscope Cursor,Justifier1 Start}");
S9xMapButton(EmuBinding::MOUSE_BUTTON2, cmd, false);
2023-03-23 21:53:43 +00:00
}
void Snes9xController::reportBinding(EmuBinding b, bool active)
{
S9xReportButton(b.hash(), active);
}
2024-04-30 21:28:45 +00:00
void Snes9xController::reportMouseButton(int button, bool pressed)
{
S9xReportButton(EmuBinding::MOUSE_POINTER + button, pressed);
}
void Snes9xController::reportPointer(int x, int y)
{
mouse_x += x;
mouse_y += y;
S9xReportPointer(EmuBinding::MOUSE_POINTER, mouse_x, mouse_y);
}
2023-03-23 21:53:43 +00:00
static fs::path save_slot_path(int slot)
{
std::string extension = std::to_string(slot);
while (extension.length() < 3)
extension = "0" + extension;
fs::path path(S9xGetDirectory(SNAPSHOT_DIR));
path /= fs::path(Memory.ROMFilename).filename();
path.replace_extension(extension);
return path;
}
void Snes9xController::loadUndoState()
{
S9xUnfreezeGame(S9xGetFilename(".undo", SNAPSHOT_DIR).c_str());
}
std::string Snes9xController::getStateFolder()
{
return S9xGetDirectory(SNAPSHOT_DIR);
}
bool Snes9xController::loadState(int slot)
{
return loadState(save_slot_path(slot).u8string());
}
bool Snes9xController::loadState(std::string filename)
{
if (!active)
return false;
S9xFreezeGame(S9xGetFilename(".undo", SNAPSHOT_DIR).c_str());
if (S9xUnfreezeGame(filename.c_str()))
{
auto info_string = filename + " loaded";
S9xSetInfoString(info_string.c_str());
return true;
}
else
{
fprintf(stderr, "Failed to load state file: %s\n", filename.c_str());
return false;
}
}
bool Snes9xController::saveState(std::string filename)
{
if (!active)
return false;
if (S9xFreezeGame(filename.c_str()))
{
auto info_string = filename + " saved";
S9xSetInfoString(info_string.c_str());
return true;
}
else
{
fprintf(stderr, "Couldn't save state file: %s\n", filename.c_str());
return false;
}
}
void Snes9xController::mute(bool muted)
{
Settings.Mute = muted;
}
bool Snes9xController::isAbnormalSpeed()
{
return (Settings.TurboMode || rewinding);
}
void Snes9xController::reset()
{
S9xReset();
}
void Snes9xController::softReset()
{
S9xSoftReset();
}
bool Snes9xController::saveState(int slot)
{
return saveState(save_slot_path(slot).u8string());
}
void Snes9xController::setMessage(std::string message)
{
S9xSetInfoString(message.c_str());
2023-12-16 19:12:09 +00:00
}
std::vector<std::tuple<bool, std::string, std::string>> Snes9xController::getCheatList()
{
std::vector<std::tuple<bool, std::string, std::string>> cheat_list;
cheat_list.reserve(Cheat.group.size());
for (auto &c : Cheat.group)
cheat_list.push_back({ c.enabled, c.name, S9xCheatGroupToText(c) });
return std::move(cheat_list);
}
void Snes9xController::disableAllCheats()
{
for (size_t i = 0; i < Cheat.group.size(); i++)
{
S9xDisableCheatGroup(i);
}
}
void Snes9xController::enableCheat(int index)
{
S9xEnableCheatGroup(index);
}
void Snes9xController::disableCheat(int index)
{
S9xDisableCheatGroup(index);
}
bool Snes9xController::addCheat(std::string description, std::string code)
{
return S9xAddCheatGroup(description, code) >= 0;
}
void Snes9xController::deleteCheat(int index)
{
S9xDeleteCheatGroup(index);
}
void Snes9xController::deleteAllCheats()
{
S9xDeleteCheats();
}
int Snes9xController::tryImportCheats(std::string filename)
{
return S9xImportCheatsFromDatabase(filename);
}
std::string Snes9xController::validateCheat(std::string code)
{
return S9xCheatValidate(code);
}
int Snes9xController::modifyCheat(int index, std::string name, std::string code)
{
return S9xModifyCheatGroup(index, name, code);
}