mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
1300f0ce0d
3
CHANGES
3
CHANGES
|
@ -51,6 +51,7 @@ Features:
|
|||
- Discord Rich Presence now supports time elapsed
|
||||
- Additional scaling shaders
|
||||
- Support for GameShark Advance SP (.gsv) save file importing
|
||||
- Support for multiple saves per game using .sa2, .sa3, etc.
|
||||
Emulation fixes:
|
||||
- ARM7: Fix unsigned multiply timing
|
||||
- GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032)
|
||||
|
@ -66,6 +67,7 @@ Emulation fixes:
|
|||
- GBA Video: Fix OpenGL rendering on M1 Macs
|
||||
- GBA Video: Ignore horizontally off-screen sprite timing (fixes mgba.io/i/2391)
|
||||
- GBA Video: Fix Hblank timing (fixes mgba.io/i/2131, mgba.io/i/2310)
|
||||
- GBA Video: Fix rare crash in modes 3-5
|
||||
Other fixes:
|
||||
- Core: Don't attempt to restore rewind diffs past start of rewind
|
||||
- FFmpeg: Fix crash when encoding audio with some containers
|
||||
|
@ -84,6 +86,7 @@ Misc:
|
|||
- Qt: Add optional frame counter to OSD (closes mgba.io/i/1728)
|
||||
- Qt: Add optional emulation-related information on reset (closes mgba.io/i/1780)
|
||||
- Qt: Add QOpenGLWidget cross-thread codepath for macOS (fixes mgba.io/i/1754)
|
||||
- Qt: Enable -b for Boot BIOS menu option (fixes mgba.io/i/2074)
|
||||
- Windows: Attach to console if present
|
||||
|
||||
0.9.3: (2021-12-17)
|
||||
|
|
|
@ -230,7 +230,13 @@ bool mCoreAutoloadSave(struct mCore* core) {
|
|||
if (!core->dirs.save) {
|
||||
return false;
|
||||
}
|
||||
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, ".sav", O_CREAT | O_RDWR));
|
||||
int savePlayerId = 0;
|
||||
char sav[16] = ".sav";
|
||||
mCoreConfigGetIntValue(&core->config, "savePlayerId", &savePlayerId);
|
||||
if (savePlayerId > 1) {
|
||||
snprintf(sav, sizeof(sav), ".sa%i", savePlayerId);
|
||||
}
|
||||
return core->loadSave(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.save, sav, O_CREAT | O_RDWR));
|
||||
}
|
||||
|
||||
bool mCoreAutoloadPatch(struct mCore* core) {
|
||||
|
@ -378,6 +384,7 @@ void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config
|
|||
|
||||
mCoreConfigCopyValue(&core->config, config, "cheatAutosave");
|
||||
mCoreConfigCopyValue(&core->config, config, "cheatAutoload");
|
||||
mCoreConfigCopyValue(&core->config, config, "savePlayerId");
|
||||
|
||||
core->loadConfig(core, config);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "ffmpeg-decoder.h"
|
||||
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
void FFmpegDecoderInit(struct FFmpegDecoder* decoder) {
|
||||
|
@ -38,7 +39,7 @@ bool FFmpegDecoderOpen(struct FFmpegDecoder* decoder, const char* infile) {
|
|||
#else
|
||||
enum AVMediaType type = decoder->context->streams[i]->codec->codec_type;
|
||||
#endif
|
||||
struct AVCodec* codec;
|
||||
const struct AVCodec* codec;
|
||||
struct AVCodecContext* context = NULL;
|
||||
if (type == AVMEDIA_TYPE_VIDEO && decoder->videoStream < 0) {
|
||||
decoder->video = avcodec_alloc_context3(NULL);
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
|
||||
#include <libavcodec/version.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#if LIBAVCODEC_VERSION_MAJOR >= 58
|
||||
#include <libavcodec/bsf.h>
|
||||
#endif
|
||||
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
|
@ -125,7 +128,7 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
|
|||
return true;
|
||||
}
|
||||
|
||||
AVCodec* codec = avcodec_find_encoder_by_name(acodec);
|
||||
const AVCodec* codec = avcodec_find_encoder_by_name(acodec);
|
||||
if (!codec) {
|
||||
return false;
|
||||
}
|
||||
|
@ -197,7 +200,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in
|
|||
return true;
|
||||
}
|
||||
|
||||
AVCodec* codec = avcodec_find_encoder_by_name(vcodec);
|
||||
const AVCodec* codec = avcodec_find_encoder_by_name(vcodec);
|
||||
if (!codec) {
|
||||
return false;
|
||||
}
|
||||
|
@ -217,7 +220,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in
|
|||
if (encoder->pixFormat == AV_PIX_FMT_NONE) {
|
||||
return false;
|
||||
}
|
||||
if (vbr < 0 && !av_opt_find(&codec->priv_class, "crf", NULL, 0, 0)) {
|
||||
if (vbr < 0 && !av_opt_find((void*) &codec->priv_class, "crf", NULL, 0, 0)) {
|
||||
return false;
|
||||
}
|
||||
encoder->videoCodec = vcodec;
|
||||
|
@ -227,7 +230,7 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in
|
|||
}
|
||||
|
||||
bool FFmpegEncoderSetContainer(struct FFmpegEncoder* encoder, const char* container) {
|
||||
AVOutputFormat* oformat = av_guess_format(container, 0, 0);
|
||||
const AVOutputFormat* oformat = av_guess_format(container, 0, 0);
|
||||
if (!oformat) {
|
||||
return false;
|
||||
}
|
||||
|
@ -245,9 +248,9 @@ void FFmpegEncoderSetLooping(struct FFmpegEncoder* encoder, bool loop) {
|
|||
}
|
||||
|
||||
bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
|
||||
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
|
||||
AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
|
||||
const AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
const AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
|
||||
const AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
|
||||
if ((encoder->audioCodec && !acodec) || (encoder->videoCodec && !vcodec) || !oformat || (!acodec && !vcodec)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -261,8 +264,8 @@ bool FFmpegEncoderVerifyContainer(struct FFmpegEncoder* encoder) {
|
|||
}
|
||||
|
||||
bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||
AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
|
||||
AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
|
||||
const AVCodec* acodec = avcodec_find_encoder_by_name(encoder->audioCodec);
|
||||
const AVCodec* vcodec = avcodec_find_encoder_by_name(encoder->videoCodec);
|
||||
if ((encoder->audioCodec && !acodec) || (encoder->videoCodec && !vcodec) || !FFmpegEncoderVerifyContainer(encoder)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -276,9 +279,9 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
|||
encoder->currentVideoFrame = 0;
|
||||
encoder->skipResidue = 0;
|
||||
|
||||
AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
const AVOutputFormat* oformat = av_guess_format(encoder->containerFormat, 0, 0);
|
||||
#ifndef USE_LIBAV
|
||||
avformat_alloc_output_context2(&encoder->context, oformat, 0, outfile);
|
||||
avformat_alloc_output_context2(&encoder->context, (AVOutputFormat*) oformat, 0, outfile);
|
||||
#else
|
||||
encoder->context = avformat_alloc_context();
|
||||
strncpy(encoder->context->filename, outfile, sizeof(encoder->context->filename) - 1);
|
||||
|
|
|
@ -112,7 +112,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode3(struct GBAVideoSoftwareRenderer
|
|||
BACKGROUND_BITMAP_INIT;
|
||||
|
||||
uint32_t color = renderer->normalPalette[0];
|
||||
if (mosaicWait && localX >= 0 && localY >= 0) {
|
||||
if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < renderer->masterEnd && (localY >> 8) < renderer->masterHeight) {
|
||||
LOAD_16(color, ((localX >> 8) + (localY >> 8) * renderer->masterEnd) << 1, renderer->d.vramBG[0]);
|
||||
color = mColorFrom555(color);
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode4(struct GBAVideoSoftwareRenderer
|
|||
if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
|
||||
offset = 0xA000;
|
||||
}
|
||||
if (mosaicWait && localX >= 0 && localY >= 0) {
|
||||
if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < renderer->masterEnd && (localY >> 8) < renderer->masterHeight) {
|
||||
color = ((uint8_t*)renderer->d.vramBG[0])[offset + (localX >> 8) + (localY >> 8) * renderer->masterEnd];
|
||||
}
|
||||
|
||||
|
@ -194,7 +194,7 @@ void GBAVideoSoftwareRendererDrawBackgroundMode5(struct GBAVideoSoftwareRenderer
|
|||
if (GBARegisterDISPCNTIsFrameSelect(renderer->dispcnt)) {
|
||||
offset = 0xA000;
|
||||
}
|
||||
if (mosaicWait && localX >= 0 && localY >= 0) {
|
||||
if (mosaicWait && localX >= 0 && localY >= 0 && (localX >> 8) < 160 && (localY >> 8) < 128) {
|
||||
LOAD_16(color, offset + (localX >> 8) * 2 + (localY >> 8) * 320, renderer->d.vramBG[0]);
|
||||
color = mColorFrom555(color);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
#include "MultiplayerController.h"
|
||||
#include "Override.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QDateTime>
|
||||
#include <QMessageBox>
|
||||
#include <QMutexLocker>
|
||||
|
||||
#include <mgba/core/serialize.h>
|
||||
|
@ -70,6 +72,7 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
|||
|
||||
if (controller->m_multiplayer) {
|
||||
controller->m_multiplayer->attachGame(controller);
|
||||
controller->updatePlayerSave();
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(controller, "started");
|
||||
|
@ -289,6 +292,13 @@ void CoreController::loadConfig(ConfigController* config) {
|
|||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
|
||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
|
||||
|
||||
int playerId = m_multiplayer->playerId(this) + 1;
|
||||
QVariant savePlayerId = config->getOption("savePlayerId");
|
||||
if (m_multiplayer->attached() < 2 && savePlayerId.canConvert<int>()) {
|
||||
playerId = savePlayerId.toInt();
|
||||
}
|
||||
mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId);
|
||||
|
||||
QSize sizeBefore = screenDimensions();
|
||||
m_activeBuffer.resize(256 * 224 * sizeof(color_t));
|
||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
|
||||
|
@ -542,6 +552,41 @@ void CoreController::forceFastForward(bool enable) {
|
|||
emit fastForwardChanged(enable || m_fastForward);
|
||||
}
|
||||
|
||||
void CoreController::changePlayer(int id) {
|
||||
Interrupter interrupter(this);
|
||||
int playerId = 0;
|
||||
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &playerId);
|
||||
if (id == playerId) {
|
||||
return;
|
||||
}
|
||||
interrupter.resume();
|
||||
|
||||
QMessageBox* resetPrompt = new QMessageBox(QMessageBox::Question, tr("Reset the game?"),
|
||||
tr("Most games will require a reset to load the new save. Do you want to reset now?"),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
||||
connect(resetPrompt, &QMessageBox::buttonClicked, this, [this, resetPrompt, id](QAbstractButton* button) {
|
||||
Interrupter interrupter(this);
|
||||
switch (resetPrompt->standardButton(button)) {
|
||||
default:
|
||||
return;
|
||||
case QMessageBox::Yes:
|
||||
mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", id);
|
||||
m_resetActions.append([this]() {
|
||||
updatePlayerSave();
|
||||
});
|
||||
interrupter.resume();
|
||||
reset();
|
||||
break;
|
||||
case QMessageBox::No:
|
||||
mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", id);
|
||||
updatePlayerSave();
|
||||
break;
|
||||
}
|
||||
});
|
||||
resetPrompt->setAttribute(Qt::WA_DeleteOnClose);
|
||||
resetPrompt->show();
|
||||
}
|
||||
|
||||
void CoreController::overrideMute(bool override) {
|
||||
m_mute = override;
|
||||
|
||||
|
@ -739,7 +784,22 @@ void CoreController::loadSave(const QString& path, bool temporary) {
|
|||
m_threadContext.core->loadSave(m_threadContext.core, vf);
|
||||
}
|
||||
});
|
||||
reset();
|
||||
if (hasStarted()) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::loadSave(VFile* vf, bool temporary) {
|
||||
m_resetActions.append([this, vf, temporary]() {
|
||||
if (temporary) {
|
||||
m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
|
||||
} else {
|
||||
m_threadContext.core->loadSave(m_threadContext.core, vf);
|
||||
}
|
||||
});
|
||||
if (hasStarted()) {
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::loadPatch(const QString& patchPath) {
|
||||
|
@ -1070,6 +1130,26 @@ void CoreController::finishFrame() {
|
|||
QMetaObject::invokeMethod(this, "frameAvailable");
|
||||
}
|
||||
|
||||
void CoreController::updatePlayerSave() {
|
||||
int savePlayerId = 0;
|
||||
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId);
|
||||
if (savePlayerId == 0 || m_multiplayer->attached() > 1) {
|
||||
savePlayerId = m_multiplayer->playerId(this) + 1;
|
||||
}
|
||||
|
||||
QString saveSuffix;
|
||||
if (savePlayerId < 2) {
|
||||
saveSuffix = QLatin1String(".sav");
|
||||
} else {
|
||||
saveSuffix = QString(".sa%1").arg(savePlayerId);
|
||||
}
|
||||
QByteArray saveSuffixBin(saveSuffix.toUtf8());
|
||||
VFile* save = mDirectorySetOpenSuffix(&m_threadContext.core->dirs, m_threadContext.core->dirs.save, saveSuffixBin.constData(), O_CREAT | O_RDWR);
|
||||
if (save) {
|
||||
m_threadContext.core->loadSave(m_threadContext.core, save);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::updateFastForward() {
|
||||
// If we have "Fast forward" checked in the menu (m_fastForwardForced)
|
||||
// or are holding the fast forward button (m_fastForward):
|
||||
|
|
|
@ -143,6 +143,8 @@ public slots:
|
|||
void setFastForward(bool);
|
||||
void forceFastForward(bool);
|
||||
|
||||
void changePlayer(int id);
|
||||
|
||||
void overrideMute(bool);
|
||||
|
||||
void loadState(int slot = 0);
|
||||
|
@ -155,6 +157,7 @@ public slots:
|
|||
void saveBackupState();
|
||||
|
||||
void loadSave(const QString&, bool temporary);
|
||||
void loadSave(VFile*, bool temporary);
|
||||
void loadPatch(const QString&);
|
||||
void scanCard(const QString&);
|
||||
void replaceGame(const QString&);
|
||||
|
@ -223,6 +226,8 @@ private:
|
|||
void updateKeys();
|
||||
void finishFrame();
|
||||
|
||||
void updatePlayerSave();
|
||||
|
||||
void updateFastForward();
|
||||
|
||||
void updateROMInfo();
|
||||
|
|
|
@ -397,7 +397,11 @@ void Window::selectROM() {
|
|||
}
|
||||
|
||||
void Window::bootBIOS() {
|
||||
setController(m_manager->loadBIOS(mPLATFORM_GBA, m_config->getOption("gba.bios")), QString());
|
||||
QString bios(m_config->getOption("gba.bios"));
|
||||
if (bios.isEmpty()) {
|
||||
bios = m_config->getOption("bios");
|
||||
}
|
||||
setController(m_manager->loadBIOS(mPLATFORM_GBA, bios), QString());
|
||||
}
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
|
@ -1284,6 +1288,23 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_platformActions.insert(mPLATFORM_GBA, exportShark);
|
||||
#endif
|
||||
|
||||
m_actions.addSeparator("saves");
|
||||
Action* savePlayerAction;
|
||||
ConfigOption* savePlayer = m_config->addOption("savePlayerId");
|
||||
savePlayerAction = savePlayer->addValue(tr("Automatically determine"), 0, &m_actions, "saves");
|
||||
m_nonMpActions.append(savePlayerAction);
|
||||
|
||||
for (int i = 1; i < 5; ++i) {
|
||||
savePlayerAction = savePlayer->addValue(tr("Use player %0 save game").arg(i), i, &m_actions, "saves");
|
||||
m_nonMpActions.append(savePlayerAction);
|
||||
}
|
||||
savePlayer->connect([this](const QVariant& value) {
|
||||
if (m_controller) {
|
||||
m_controller->changePlayer(value.toInt());
|
||||
}
|
||||
}, this);
|
||||
m_config->updateOption("savePlayerId");
|
||||
|
||||
m_actions.addAction(tr("Load &patch..."), "loadPatch", this, &Window::selectPatch, "file");
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
|
|
Loading…
Reference in New Issue