summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.travis-deps.sh4
-rwxr-xr-x.travis-upload.sh2
-rw-r--r--.travis.yml1
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md4
-rw-r--r--dist/citra.xml24
-rw-r--r--src/audio_core/sdl2_sink.cpp4
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp5
-rw-r--r--src/core/hle/applets/erreula.cpp6
-rw-r--r--src/core/hle/applets/mii_selector.cpp6
-rw-r--r--src/core/hle/applets/swkbd.cpp6
-rw-r--r--src/core/hle/kernel/event.cpp5
-rw-r--r--src/core/hle/kernel/timer.cpp5
-rw-r--r--src/core/hle/service/apt/apt.cpp11
-rw-r--r--src/core/hle/service/apt/apt.h34
-rw-r--r--src/core/hle/service/apt/apt_a.cpp2
-rw-r--r--src/core/hle/service/apt/apt_u.cpp2
-rw-r--r--src/core/hle/service/cfg/cfg.cpp10
-rw-r--r--src/core/hle/service/err_f.cpp316
-rw-r--r--src/core/memory.cpp14
-rw-r--r--src/core/memory.h9
-rw-r--r--src/video_core/command_processor.cpp24
-rw-r--r--src/video_core/pica.h2
23 files changed, 317 insertions, 181 deletions
diff --git a/.travis-deps.sh b/.travis-deps.sh
index ab3a32382..9fd21cc57 100755
--- a/.travis-deps.sh
+++ b/.travis-deps.sh
@@ -19,8 +19,8 @@ if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
if [ ! -e $HOME/.local/lib/libSDL2.la ]; then
echo "SDL2 not found in cache, get and build it..."
- wget http://libsdl.org/release/SDL2-2.0.4.tar.gz -O - | tar xz
- cd SDL2-2.0.4
+ wget http://libsdl.org/release/SDL2-2.0.5.tar.gz -O - | tar xz
+ cd SDL2-2.0.5
./configure --prefix=$HOME/.local
make -j4 && make install
else
diff --git a/.travis-upload.sh b/.travis-upload.sh
index 1bec74b3d..2eeda4c50 100755
--- a/.travis-upload.sh
+++ b/.travis-upload.sh
@@ -14,7 +14,6 @@ if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then
UPLOAD_DIR="/citra/nightly/osx-amd64"
mkdir "$REV_NAME"
- brew install lftp
cp build/src/citra/Release/citra "$REV_NAME"
cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME"
@@ -122,5 +121,4 @@ EOL
ARCHIVE_NAME="${REV_NAME}.tar.xz"
tar -cJvf "$ARCHIVE_NAME" "$REV_NAME"
- lftp -c "open -u citra-builds,$BUILD_PASSWORD sftp://builds.citra-emu.org; set sftp:auto-confirm yes; put -O '$UPLOAD_DIR' '$ARCHIVE_NAME'"
fi
diff --git a/.travis.yml b/.travis.yml
index ea99be2c4..a9e7aadd2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -25,7 +25,6 @@ addons:
- libqt5opengl5-dev
- xorg-dev
- lib32stdc++6 # For CMake
- - lftp # To upload builds
- clang-format-4.0
cache:
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 56503f1ad..5c9b7f86a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -156,7 +156,7 @@ if (ENABLE_SDL2)
if (CITRA_USE_BUNDLED_SDL2)
# Detect toolchain and platform
if (MSVC14 AND ARCHITECTURE_x86_64)
- set(SDL2_VER "SDL2-2.0.4")
+ set(SDL2_VER "SDL2-2.0.5")
else()
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable CITRA_USE_BUNDLED_SDL2 and provide your own.")
endif()
diff --git a/README.md b/README.md
index 92e2d04a5..7d1e1de0d 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Citra Emulator
[![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg?branch=master)](https://travis-ci.org/citra-emu/citra)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra)
-Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and OS X. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
+Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project.
@@ -23,7 +23,7 @@ If you want to contribute please take a look at the [Contributor's Guide](CONTRI
* __Windows__: [Windows Build](https://github.com/citra-emu/citra/wiki/Building-For-Windows)
* __Linux__: [Linux Build](https://github.com/citra-emu/citra/wiki/Building-For-Linux)
-* __OSX__: [OS X Build](https://github.com/citra-emu/citra/wiki/Building-For-OS-X)
+* __macOS__: [macOS Build](https://github.com/citra-emu/citra/wiki/Building-for-macOS)
### Support
diff --git a/dist/citra.xml b/dist/citra.xml
index bcb9acd87..6d47c8760 100644
--- a/dist/citra.xml
+++ b/dist/citra.xml
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-ctr-3dsx">
- <comment>3DS homebrew executable</comment>
- <comment xml:lang="fr">Exécutable 3DS homebrew</comment>
+ <comment>Nintendo 3DS homebrew executable</comment>
+ <comment xml:lang="fr">Exécutable non-officiel pour Nintendo 3DS </comment>
<acronym>3DSX</acronym>
<icon name="citra"/>
<glob pattern="*.3dsx"/>
@@ -10,8 +10,8 @@
</mime-type>
<mime-type type="application/x-ctr-cci">
- <comment>3DS cartridge image</comment>
- <comment xml:lang="fr">Image de cartouche 3DS</comment>
+ <comment>Nintendo 3DS cartridge image</comment>
+ <comment xml:lang="fr">Image de cartouche Nintendo 3DS</comment>
<acronym>CCI</acronym>
<expanded-acronym>CTR Cart Image</expanded-acronym>
<icon name="citra"/>
@@ -21,8 +21,8 @@
</mime-type>
<mime-type type="application/x-ctr-cxi">
- <comment>3DS executable</comment>
- <comment xml:lang="fr">Exécutable 3DS</comment>
+ <comment>Nintendo 3DS executable</comment>
+ <comment xml:lang="fr">Exécutable Nintendo 3DS</comment>
<acronym>CXI</acronym>
<expanded-acronym>CTR eXecutable Image</expanded-acronym>
<icon name="citra"/>
@@ -31,8 +31,8 @@
</mime-type>
<mime-type type="application/x-ctr-cia">
- <comment>3DS importable archive</comment>
- <comment xml:lang="fr">Archive importable 3DS</comment>
+ <comment>Nintendo 3DS importable archive</comment>
+ <comment xml:lang="fr">Archive installable Nintendo 3DS</comment>
<acronym>CIA</acronym>
<expanded-acronym>CTR Importable Archive</expanded-acronym>
<icon name="citra"/>
@@ -40,8 +40,8 @@
</mime-type>
<mime-type type="application/x-ctr-smdh">
- <comment>3DS icon</comment>
- <comment xml:lang="fr">Icône 3DS</comment>
+ <comment>Nintendo 3DS icon and metadata</comment>
+ <comment xml:lang="fr">Icône et métadonnées Nintendo 3DS</comment>
<acronym>SMDH</acronym>
<expanded-acronym>System Menu Data Header</expanded-acronym>
<glob pattern="*.smdh"/>
@@ -49,8 +49,8 @@
</mime-type>
<mime-type type="application/x-ctr-cbmd">
- <comment>3DS banner</comment>
- <comment xml:lang="fr">Bannière 3DS</comment>
+ <comment>Nintendo 3DS banner</comment>
+ <comment xml:lang="fr">Bannière Nintendo 3DS</comment>
<acronym>CBMD</acronym>
<expanded-acronym>CTR Banner Model Data</expanded-acronym>
<glob pattern="*.cbmd"/>
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
index 75cc0d6dd..4b66cd826 100644
--- a/src/audio_core/sdl2_sink.cpp
+++ b/src/audio_core/sdl2_sink.cpp
@@ -25,7 +25,7 @@ struct SDL2Sink::Impl {
SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
if (SDL_Init(SDL_INIT_AUDIO) < 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed");
+ LOG_CRITICAL(Audio_Sink, "SDL_Init(SDL_INIT_AUDIO) failed with: %s", SDL_GetError());
impl->audio_device_id = 0;
return;
}
@@ -45,7 +45,7 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
impl->audio_device_id =
SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
if (impl->audio_device_id <= 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed");
+ LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with: %s", SDL_GetError());
return;
}
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index b4444c869..ca8a94ee9 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -52,6 +52,7 @@ static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) {
user_callbacks.MemoryWrite16 = &Memory::Write16;
user_callbacks.MemoryWrite32 = &Memory::Write32;
user_callbacks.MemoryWrite64 = &Memory::Write64;
+ user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
return user_callbacks;
}
@@ -130,9 +131,9 @@ MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64));
void ARM_Dynarmic::ExecuteInstructions(int num_instructions) {
MICROPROFILE_SCOPE(ARM_Jit);
- jit->Run(static_cast<unsigned>(num_instructions));
+ unsigned ticks_executed = jit->Run(static_cast<unsigned>(num_instructions));
- AddTicks(num_instructions);
+ AddTicks(ticks_executed);
}
void ARM_Dynarmic::SaveContext(Core::ThreadContext& ctx) {
diff --git a/src/core/hle/applets/erreula.cpp b/src/core/hle/applets/erreula.cpp
index 14964427b..e1379ac4d 100644
--- a/src/core/hle/applets/erreula.cpp
+++ b/src/core/hle/applets/erreula.cpp
@@ -10,7 +10,7 @@ namespace HLE {
namespace Applets {
ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -36,7 +36,7 @@ ResultCode ErrEula::ReceiveParameter(const Service::APT::MessageParameter& param
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -57,7 +57,7 @@ ResultCode ErrEula::StartImpl(const Service::APT::AppletStartupParameter& parame
Service::APT::MessageParameter message;
message.buffer.resize(parameter.buffer.size());
std::fill(message.buffer.begin(), message.buffer.end(), 0);
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp
index 53a8683a4..3455b9201 100644
--- a/src/core/hle/applets/mii_selector.cpp
+++ b/src/core/hle/applets/mii_selector.cpp
@@ -19,7 +19,7 @@ namespace HLE {
namespace Applets {
ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -44,7 +44,7 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -73,7 +73,7 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
Service::APT::MessageParameter message;
message.buffer.resize(sizeof(MiiResult));
std::memcpy(message.buffer.data(), &result, message.buffer.size());
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/applets/swkbd.cpp b/src/core/hle/applets/swkbd.cpp
index 06ddf538b..1e21337f5 100644
--- a/src/core/hle/applets/swkbd.cpp
+++ b/src/core/hle/applets/swkbd.cpp
@@ -22,7 +22,7 @@ namespace HLE {
namespace Applets {
ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter const& parameter) {
- if (parameter.signal != static_cast<u32>(Service::APT::SignalType::LibAppJustStarted)) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
UNIMPLEMENTED();
// TODO(Subv): Find the right error code
@@ -47,7 +47,7 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
- result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
@@ -108,7 +108,7 @@ void SoftwareKeyboard::Finalize() {
Service::APT::MessageParameter message;
message.buffer.resize(sizeof(SoftwareKeyboardConfig));
std::memcpy(message.buffer.data(), &config, message.buffer.size());
- message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);
Service::APT::SendParameter(message);
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index 1489c7002..3e116e3df 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -22,6 +22,11 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) {
evt->reset_type = reset_type;
evt->name = std::move(name);
+ if (reset_type == ResetType::Pulse) {
+ LOG_ERROR(Kernel, "Unimplemented event reset type Pulse");
+ UNIMPLEMENTED();
+ }
+
return evt;
}
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index a9f98223c..eac181f4e 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -31,6 +31,11 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->interval_delay = 0;
timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
+ if (reset_type == ResetType::Pulse) {
+ LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse");
+ UNIMPLEMENTED();
+ }
+
return timer;
}
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index c4bd65986..31e5e07b2 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -396,6 +396,15 @@ void StartLibraryApplet(Service::Interface* self) {
cmd_buff[1] = applet->Start(parameter).raw;
}
+void CancelLibraryApplet(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 exiting = cmd_buff[1] & 0xFF;
+
+ cmd_buff[1] = 1; // TODO: Find the return code meaning
+
+ LOG_WARNING(Service_APT, "(STUBBED) called exiting=%u", exiting);
+}
+
void SetScreenCapPostPermission(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -523,7 +532,7 @@ void Init() {
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");
parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start");
- next_parameter.signal = static_cast<u32>(SignalType::AppJustStarted);
+ next_parameter.signal = static_cast<u32>(SignalType::Wakeup);
next_parameter.destination_id = 0x300;
}
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index a118cda1f..44dbd8757 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -46,12 +46,23 @@ static_assert(sizeof(CaptureBufferInfo) == 0x20, "CaptureBufferInfo struct has i
/// Signals used by APT functions
enum class SignalType : u32 {
None = 0x0,
- AppJustStarted = 0x1,
- LibAppJustStarted = 0x2,
- LibAppFinished = 0x3,
- LibAppClosed = 0xA,
- ReturningToApp = 0xB,
- ExitingApp = 0xC,
+ Wakeup = 0x1,
+ Request = 0x2,
+ Response = 0x3,
+ Exit = 0x4,
+ Message = 0x5,
+ HomeButtonSingle = 0x6,
+ HomeButtonDouble = 0x7,
+ DspSleep = 0x8,
+ DspWakeup = 0x9,
+ WakeupByExit = 0xA,
+ WakeupByPause = 0xB,
+ WakeupByCancel = 0xC,
+ WakeupByCancelAll = 0xD,
+ WakeupByPowerButtonClick = 0xE,
+ WakeupToJumpHome = 0xF,
+ RequestForSysApplet = 0x10,
+ WakeupToLaunchApplication = 0x11,
};
/// App Id's used by APT functions
@@ -381,6 +392,17 @@ void PreloadLibraryApplet(Service::Interface* self);
void StartLibraryApplet(Service::Interface* self);
/**
+ * APT::CancelLibraryApplet service function
+ * Inputs:
+ * 0 : Command header [0x003B0040]
+ * 1 : u8, Application exiting (0 = not exiting, 1 = exiting)
+ * Outputs:
+ * 0 : Header code
+ * 1 : Result code
+ */
+void CancelLibraryApplet(Service::Interface* self);
+
+/**
* APT::GetStartupArgument service function
* Inputs:
* 1 : Parameter Size (capped to 0x300)
diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp
index f27ad91b7..a7a0c8a41 100644
--- a/src/core/hle/service/apt/apt_a.cpp
+++ b/src/core/hle/service/apt/apt_a.cpp
@@ -25,7 +25,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x001E0084, StartLibraryApplet, "StartLibraryApplet"},
- {0x003B0040, nullptr, "CancelLibraryApplet?"},
+ {0x003B0040, CancelLibraryApplet, "CancelLibraryApplet"},
{0x003E0080, nullptr, "ReplySleepQuery"},
{0x00430040, NotifyToWait, "NotifyToWait?"},
{0x00440000, GetSharedFont, "GetSharedFont?"},
diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp
index d6ad42e21..a731c39f6 100644
--- a/src/core/hle/service/apt/apt_u.cpp
+++ b/src/core/hle/service/apt/apt_u.cpp
@@ -67,7 +67,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00380040, nullptr, "PreloadResidentApplet"},
{0x00390040, nullptr, "PrepareToStartResidentApplet"},
{0x003A0044, nullptr, "StartResidentApplet"},
- {0x003B0040, nullptr, "CancelLibraryApplet"},
+ {0x003B0040, CancelLibraryApplet, "CancelLibraryApplet"},
{0x003C0042, nullptr, "SendDspSleep"},
{0x003D0042, nullptr, "SendDspWakeUp"},
{0x003E0080, nullptr, "ReplySleepQuery"},
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 24eee6903..d3d0f3b55 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -45,7 +45,8 @@ static_assert(sizeof(SaveFileConfig) == 0x455C,
enum ConfigBlockID {
StereoCameraSettingsBlockID = 0x00050005,
SoundOutputModeBlockID = 0x00070001,
- ConsoleUniqueIDBlockID = 0x00090001,
+ ConsoleUniqueID1BlockID = 0x00090000,
+ ConsoleUniqueID2BlockID = 0x00090001,
UsernameBlockID = 0x000A0000,
BirthdayBlockID = 0x000A0001,
LanguageBlockID = 0x000A0002,
@@ -409,7 +410,12 @@ ResultCode FormatConfig() {
if (!res.IsSuccess())
return res;
- res = CreateConfigInfoBlk(ConsoleUniqueIDBlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
+ res = CreateConfigInfoBlk(ConsoleUniqueID1BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
+ &CONSOLE_UNIQUE_ID);
+ if (!res.IsSuccess())
+ return res;
+
+ res = CreateConfigInfoBlk(ConsoleUniqueID2BlockID, sizeof(CONSOLE_UNIQUE_ID), 0xE,
&CONSOLE_UNIQUE_ID);
if (!res.IsSuccess())
return res;
diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp
index 3ca4f98de..9905757c7 100644
--- a/src/core/hle/service/err_f.cpp
+++ b/src/core/hle/service/err_f.cpp
@@ -2,9 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include <chrono>
+#include <iomanip>
+#include <sstream>
+
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "core/hle/result.h"
#include "core/hle/service/err_f.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -12,12 +18,45 @@
namespace ERR_F {
-enum {
- ErrSpecifier0 = 0,
- ErrSpecifier1 = 1,
- ErrSpecifier3 = 3,
- ErrSpecifier4 = 4,
+enum class FatalErrType : u32 {
+ Generic = 0,
+ Corrupted = 1,
+ CardRemoved = 2,
+ Exception = 3,
+ ResultFailure = 4,
+ Logged = 5,
+};
+
+enum class ExceptionType : u32 {
+ PrefetchAbort = 0,
+ DataAbort = 1,
+ Undefined = 2,
+ VectorFP = 3,
+};
+
+struct ExceptionInfo {
+ u8 exception_type;
+ INSERT_PADDING_BYTES(3);
+ u32 sr;
+ u32 ar;
+ u32 fpexc;
+ u32 fpinst;
+ u32 fpinst2;
+};
+static_assert(sizeof(ExceptionInfo) == 0x18, "ExceptionInfo struct has incorrect size");
+
+struct ExceptionContext final {
+ std::array<u32, 16> arm_regs;
+ u32 cpsr;
};
+static_assert(sizeof(ExceptionContext) == 0x44, "ExceptionContext struct has incorrect size");
+
+struct ExceptionData {
+ ExceptionInfo exception_info;
+ ExceptionContext exception_context;
+ INSERT_PADDING_WORDS(1);
+};
+static_assert(sizeof(ExceptionData) == 0x60, "ExceptionData struct has incorrect size");
// This is used instead of ResultCode from result.h
// because we can't have non-trivial data members in unions.
@@ -30,150 +69,191 @@ union RSL {
BitField<27, 5, u32> level;
};
-union ErrInfo {
- u8 specifier;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- RSL result_code; // 0x4
- u32 address; // 0x8
- INSERT_PADDING_BYTES(4); // 0xC
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- } errtype1;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- INSERT_PADDING_BYTES(0xC); // 0x4
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- u8 error_type; // 0x20
- INSERT_PADDING_BYTES(3); // 0x21
- u32 fault_status_reg; // 0x24
- u32 fault_addr; // 0x28
- u32 fpexc; // 0x2C
- u32 finst; // 0x30
- u32 finst2; // 0x34
- INSERT_PADDING_BYTES(0x34); // 0x38
- u32 sp; // 0x6C
- u32 pc; // 0x70
- u32 lr; // 0x74
- u32 cpsr; // 0x78
- } errtype3;
-
- struct {
- u8 specifier; // 0x0
- u8 rev_high; // 0x1
- u16 rev_low; // 0x2
- RSL result_code; // 0x4
- INSERT_PADDING_BYTES(8); // 0x8
- u32 pid_low; // 0x10
- u32 pid_high; // 0x14
- u32 aid_low; // 0x18
- u32 aid_high; // 0x1C
- char debug_string1[0x2E]; // 0x20
- char debug_string2[0x2E]; // 0x4E
- } errtype4;
+struct ErrInfo {
+ struct ErrInfoCommon {
+ u8 specifier; // 0x0
+ u8 rev_high; // 0x1
+ u16 rev_low; // 0x2
+ RSL result_code; // 0x4
+ u32 pc_address; // 0x8
+ u32 pid; // 0xC
+ u32 title_id_low; // 0x10
+ u32 title_id_high; // 0x14
+ u32 app_title_id_low; // 0x18
+ u32 app_title_id_high; // 0x1C
+ } errinfo_common;
+ static_assert(sizeof(ErrInfoCommon) == 0x20, "ErrInfoCommon struct has incorrect size");
+
+ union {
+ struct {
+ char data[0x60]; // 0x20
+ } generic;
+
+ struct {
+ ExceptionData exception_data; // 0x20
+ } exception;
+
+ struct {
+ char message[0x60]; // 0x20
+ } result_failure;
+ };
};
-enum { PrefetchAbort = 0, DataAbort = 1, UndefInstr = 2, VectorFP = 3 };
+static std::string GetErrType(u8 type_code) {
+ switch (static_cast<FatalErrType>(type_code)) {
+ case FatalErrType::Generic:
+ return "Generic";
+ case FatalErrType::Corrupted:
+ return "Corrupted";
+ case FatalErrType::CardRemoved:
+ return "CardRemoved";
+ case FatalErrType::Exception:
+ return "Exception";
+ case FatalErrType::ResultFailure:
+ return "ResultFailure";
+ case FatalErrType::Logged:
+ return "Logged";
+ default:
+ return "Unknown Error Type";
+ }
+}
-static std::string GetErrInfo3Type(u8 type_code) {
- switch (type_code) {
- case PrefetchAbort:
+static std::string GetExceptionType(u8 type_code) {
+ switch (static_cast<ExceptionType>(type_code)) {
+ case ExceptionType::PrefetchAbort:
return "Prefetch Abort";
- case DataAbort:
+ case ExceptionType::DataAbort:
return "Data Abort";
- case UndefInstr:
- return "Undefined Instruction";
- case VectorFP:
- return "Vector Floating Point";
+ case ExceptionType::Undefined:
+ return "Undefined Exception";
+ case ExceptionType::VectorFP:
+ return "Vector Floating Point Exception";
default:
- return "unknown";
+ return "Unknown Exception Type";
}
}
+static std::string GetCurrentSystemTime() {
+ auto now = std::chrono::system_clock::now();
+ auto time = std::chrono::system_clock::to_time_t(now);
+
+ std::stringstream time_stream;
+ time_stream << std::put_time(std::localtime(&time), "%Y/%m/%d %H:%M:%S");
+ return time_stream.str();
+}
+
+static void LogGenericInfo(const ErrInfo::ErrInfoCommon& errinfo_common) {
+ LOG_CRITICAL(Service_ERR, "PID: 0x%08X", errinfo_common.pid);
+ LOG_CRITICAL(Service_ERR, "REV: 0x%08X_0x%08X", errinfo_common.rev_high,
+ errinfo_common.rev_low);
+ LOG_CRITICAL(Service_ERR, "TID: 0x%08X_0x%08X", errinfo_common.title_id_high,
+ errinfo_common.title_id_low);
+ LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errinfo_common.app_title_id_high,
+ errinfo_common.app_title_id_low);
+ LOG_CRITICAL(Service_ERR, "ADR: 0x%08X", errinfo_common.pc_address);
+
+ LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errinfo_common.result_code.raw);
+ LOG_CRITICAL(Service_ERR, " Level: %u", errinfo_common.result_code.level.Value());
+ LOG_CRITICAL(Service_ERR, " Summary: %u", errinfo_common.result_code.summary.Value());
+ LOG_CRITICAL(Service_ERR, " Module: %u", errinfo_common.result_code.module.Value());
+ LOG_CRITICAL(Service_ERR, " Desc: %u", errinfo_common.result_code.description.Value());
+}
+
+/* ThrowFatalError function
+ * Inputs:
+ * 0 : Header code [0x00010800]
+ * 1-32 : FatalErrInfo
+ * Outputs:
+ * 0 : Header code
+ * 1 : Result code
+ */
static void ThrowFatalError(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- LOG_CRITICAL(Service_ERR, "Fatal error!");
+ LOG_CRITICAL(Service_ERR, "Fatal error");
const ErrInfo* errinfo = reinterpret_cast<ErrInfo*>(&cmd_buff[1]);
+ LOG_CRITICAL(Service_ERR, "Fatal error type: %s",
+ GetErrType(errinfo->errinfo_common.specifier).c_str());
- switch (errinfo->specifier) {
- case ErrSpecifier0:
- case ErrSpecifier1: {
- const auto& errtype = errinfo->errtype1;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
- LOG_CRITICAL(Service_ERR, "ADR: 0x%08X", errtype.address);
-
- LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw);
- LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value());
- LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value());
- LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value());
- LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value());
+ // Generic Info
+ LogGenericInfo(errinfo->errinfo_common);
+
+ switch (static_cast<FatalErrType>(errinfo->errinfo_common.specifier)) {
+ case FatalErrType::Generic:
+ case FatalErrType::Corrupted:
+ case FatalErrType::CardRemoved:
+ case FatalErrType::Logged: {
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
+ case FatalErrType::Exception: {
+ const auto& errtype = errinfo->exception;
+
+ // Register Info
+ LOG_CRITICAL(Service_ERR, "ARM Registers:");
+ for (u32 index = 0; index < errtype.exception_data.exception_context.arm_regs.size();
+ ++index) {
+ if (index < 13) {
+ LOG_DEBUG(Service_ERR, "r%u=0x%08X", index,
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 13) {
+ LOG_CRITICAL(Service_ERR, "SP=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 14) {
+ LOG_CRITICAL(Service_ERR, "LR=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ } else if (index == 15) {
+ LOG_CRITICAL(Service_ERR, "PC=0x%08X",
+ errtype.exception_data.exception_context.arm_regs.at(index));
+ }
+ }
+ LOG_CRITICAL(Service_ERR, "CPSR=0x%08X", errtype.exception_data.exception_context.cpsr);
- case ErrSpecifier3: {
- const auto& errtype = errinfo->errtype3;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
- LOG_CRITICAL(Service_ERR, "TYPE: %s", GetErrInfo3Type(errtype.error_type).c_str());
-
- LOG_CRITICAL(Service_ERR, "PC: 0x%08X", errtype.pc);
- LOG_CRITICAL(Service_ERR, "LR: 0x%08X", errtype.lr);
- LOG_CRITICAL(Service_ERR, "SP: 0x%08X", errtype.sp);
- LOG_CRITICAL(Service_ERR, "CPSR: 0x%08X", errtype.cpsr);
-
- switch (errtype.error_type) {
- case PrefetchAbort:
- case DataAbort:
- LOG_CRITICAL(Service_ERR, "Fault Address: 0x%08X", errtype.fault_addr);
- LOG_CRITICAL(Service_ERR, "Fault Status Register: 0x%08X", errtype.fault_status_reg);
+ // Exception Info
+ LOG_CRITICAL(
+ Service_ERR, "EXCEPTION TYPE: %s",
+ GetExceptionType(errtype.exception_data.exception_info.exception_type).c_str());
+ switch (static_cast<ExceptionType>(errtype.exception_data.exception_info.exception_type)) {
+ case ExceptionType::PrefetchAbort:
+ LOG_CRITICAL(Service_ERR, "IFSR: 0x%08X", errtype.exception_data.exception_info.sr);
+ LOG_CRITICAL(Service_ERR, "r15: 0x%08X", errtype.exception_data.exception_info.ar);
+ case ExceptionType::DataAbort:
+ LOG_CRITICAL(Service_ERR, "DFSR: 0x%08X", errtype.exception_data.exception_info.sr);
+ LOG_CRITICAL(Service_ERR, "DFAR: 0x%08X", errtype.exception_data.exception_info.ar);
break;
- case VectorFP:
- LOG_CRITICAL(Service_ERR, "FPEXC: 0x%08X", errtype.fpexc);
- LOG_CRITICAL(Service_ERR, "FINST: 0x%08X", errtype.finst);
- LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X", errtype.finst2);
+ case ExceptionType::VectorFP:
+ LOG_CRITICAL(Service_ERR, "FPEXC: 0x%08X",
+ errtype.exception_data.exception_info.fpinst);
+ LOG_CRITICAL(Service_ERR, "FINST: 0x%08X",
+ errtype.exception_data.exception_info.fpinst);
+ LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X",
+ errtype.exception_data.exception_info.fpinst2);
break;
}
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
- case ErrSpecifier4: {
- const auto& errtype = errinfo->errtype4;
- LOG_CRITICAL(Service_ERR, "PID: 0x%08X_0x%08X", errtype.pid_low, errtype.pid_high);
- LOG_CRITICAL(Service_ERR, "REV: %d", errtype.rev_low | (errtype.rev_high << 16));
- LOG_CRITICAL(Service_ERR, "AID: 0x%08X_0x%08X", errtype.aid_low, errtype.aid_high);
+ case FatalErrType::ResultFailure: {
+ const auto& errtype = errinfo->result_failure;
- LOG_CRITICAL(Service_ERR, "RSL: 0x%08X", errtype.result_code.raw);
- LOG_CRITICAL(Service_ERR, " Level: %u", errtype.result_code.level.Value());
- LOG_CRITICAL(Service_ERR, " Summary: %u", errtype.result_code.summary.Value());
- LOG_CRITICAL(Service_ERR, " Module: %u", errtype.result_code.module.Value());
- LOG_CRITICAL(Service_ERR, " Desc: %u", errtype.result_code.description.Value());
-
- LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string1);
- LOG_CRITICAL(Service_ERR, "%s", errtype.debug_string2);
+ // Failure Message
+ LOG_CRITICAL(Service_ERR, "Failure Message: %s", errtype.message);
+ LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
}
- }
- cmd_buff[1] = 0; // No error
+ } // switch FatalErrType
+
+ cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
}
const Interface::FunctionInfo FunctionTable[] = {
+ // clang-format off
{0x00010800, ThrowFatalError, "ThrowFatalError"},
+ {0x00020042, nullptr, "SetUserString"},
+ // clang-format on
};
////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 64c388374..65e4bba85 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -45,13 +45,11 @@ struct SpecialRegion {
* requires an indexed fetch and a check for NULL.
*/
struct PageTable {
- static const size_t NUM_ENTRIES = 1 << (32 - PAGE_BITS);
-
/**
* Array of memory pointers backing each page. An entry can only be non-null if the
* corresponding entry in the `attributes` array is of type `Memory`.
*/
- std::array<u8*, NUM_ENTRIES> pointers;
+ std::array<u8*, PAGE_TABLE_NUM_ENTRIES> pointers;
/**
* Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
@@ -63,13 +61,13 @@ struct PageTable {
* Array of fine grained page attributes. If it is set to any value other than `Memory`, then
* the corresponding entry in `pointers` MUST be set to null.
*/
- std::array<PageType, NUM_ENTRIES> attributes;
+ std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes;
/**
* Indicates the number of externally cached resources touching a page that should be
* flushed before the memory is accessed
*/
- std::array<u8, NUM_ENTRIES> cached_res_count;
+ std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count;
};
/// Singular page table used for the singleton process
@@ -77,6 +75,10 @@ static PageTable main_page_table;
/// Currently active page table
static PageTable* current_page_table = &main_page_table;
+std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() {
+ return &current_page_table->pointers;
+}
+
static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
@@ -84,7 +86,7 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
u32 end = base + size;
while (base != end) {
- ASSERT_MSG(base < PageTable::NUM_ENTRIES, "out of range mapping at %08X", base);
+ ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
// Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
// null here
diff --git a/src/core/memory.h b/src/core/memory.h
index 8fd3080ff..903b58a22 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <cstddef>
#include <string>
#include "common/common_types.h"
@@ -17,6 +18,7 @@ namespace Memory {
const u32 PAGE_SIZE = 0x1000;
const u32 PAGE_MASK = PAGE_SIZE - 1;
const int PAGE_BITS = 12;
+const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS);
/// Physical memory regions as seen from the ARM11
enum : PAddr {
@@ -166,4 +168,11 @@ void RasterizerFlushRegion(PAddr start, u32 size);
* Flushes and invalidates any externally cached rasterizer resources touching the given region.
*/
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
+
+/**
+ * Dynarmic has an optimization to memory accesses when the pointer to the page exists that
+ * can be used by setting up the current page table as a callback. This function is used to
+ * retrieve the current page table for that purpose.
+ */
+std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers();
}
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index fda91e29c..b7c32035e 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -215,18 +215,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler;
- if (g_debug_context) {
+ if (g_debug_context && g_debug_context->recorder) {
for (int i = 0; i < 3; ++i) {
const auto texture = regs.GetTextures()[i];
if (!texture.enabled)
continue;
u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress());
- if (g_debug_context && Pica::g_debug_context->recorder)
- g_debug_context->recorder->MemoryAccessed(
- texture_data, Pica::Regs::NibblesPerPixel(texture.format) *
- texture.config.width / 2 * texture.config.height,
- texture.config.GetPhysicalAddress());
+ g_debug_context->recorder->MemoryAccessed(
+ texture_data, Pica::Regs::NibblesPerPixel(texture.format) *
+ texture.config.width / 2 * texture.config.height,
+ texture.config.GetPhysicalAddress());
}
}
@@ -236,7 +235,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
const size_t VERTEX_CACHE_SIZE = 32;
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
- std::array<Shader::OutputRegisters, VERTEX_CACHE_SIZE> vertex_cache;
+ std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
+ Shader::OutputVertex output_vertex;
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
@@ -266,7 +266,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
if (vertex == vertex_cache_ids[i]) {
- output_registers = vertex_cache[i];
+ output_vertex = vertex_cache[i];
vertex_cache_hit = true;
break;
}
@@ -285,16 +285,16 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
output_registers = shader_unit.output_registers;
+ // Retrieve vertex from register data
+ output_vertex = output_registers.ToVertex(regs.vs);
+
if (is_indexed) {
- vertex_cache[vertex_cache_pos] = output_registers;
+ vertex_cache[vertex_cache_pos] = output_vertex;
vertex_cache_ids[vertex_cache_pos] = vertex;
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
}
}
- // Retrieve vertex from register data
- Shader::OutputVertex output_vertex = output_registers.ToVertex(regs.vs);
-
// Send to renderer
using Pica::Shader::OutputVertex;
auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index b2db609ec..99bd59a69 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -40,7 +40,7 @@ namespace Pica {
// field offset. Otherwise, the compiler will fail to compile this code.
#define PICA_REG_INDEX_WORKAROUND(field_name, backup_workaround_index) \
((typename std::enable_if<backup_workaround_index == PICA_REG_INDEX(field_name), \
- size_t>::type)PICA_REG_INDEX(field_name))
+ size_t>::type) PICA_REG_INDEX(field_name))
#endif // _MSC_VER
struct Regs {