diff --git a/.travis-build-docker.sh b/.travis-build-docker.sh
index c656dd5b1..3cea2c2b9 100644
--- a/.travis-build-docker.sh
+++ b/.travis-build-docker.sh
@@ -14,7 +14,7 @@ echo y | sh cmake-3.9.0-Linux-x86_64.sh --prefix=cmake
export PATH=/citra/cmake/cmake-3.9.0-Linux-x86_64/bin:$PATH
mkdir build && cd build
-cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1
+cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1
make -j4
ctest -VV -C Release
diff --git a/dist/icons/checked.png b/dist/icons/checked.png
new file mode 100644
index 000000000..c277e6b40
Binary files /dev/null and b/dist/icons/checked.png differ
diff --git a/dist/icons/failed.png b/dist/icons/failed.png
new file mode 100644
index 000000000..ac10f174a
Binary files /dev/null and b/dist/icons/failed.png differ
diff --git a/dist/icons/icons.qrc b/dist/icons/icons.qrc
new file mode 100644
index 000000000..f0c44862f
--- /dev/null
+++ b/dist/icons/icons.qrc
@@ -0,0 +1,6 @@
+
+
+ checked.png
+ failed.png
+
+
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index da962d5b6..50bc39a26 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -164,6 +164,8 @@ void Config::ReadValues() {
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
Settings::values.telemetry_endpoint_url = sdl2_config->Get(
"WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
+ Settings::values.verify_endpoint_url = sdl2_config->Get(
+ "WebService", "verify_endpoint_url", "https://services.citra-emu.org/api/profile");
Settings::values.citra_username = sdl2_config->Get("WebService", "citra_username", "");
Settings::values.citra_token = sdl2_config->Get("WebService", "citra_token", "");
}
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 10f381534..fdf5c3def 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -188,6 +188,8 @@ gdbstub_port=24689
enable_telemetry =
# Endpoint URL for submitting telemetry data
telemetry_endpoint_url = https://services.citra-emu.org/api/telemetry
+# Endpoint URL to verify the username and token
+verify_endpoint_url = https://services.citra-emu.org/api/profile
# Username and token for Citra Web Service
# See https://services.citra-emu.org/ for more info
citra_username =
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index e0a19fd9e..add7566c2 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -79,6 +79,7 @@ set(UIS
main.ui
)
+file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
create_directory_groups(${SRCS} ${HEADERS} ${UIS})
@@ -92,10 +93,10 @@ endif()
if (APPLE)
set(MACOSX_ICON "../../dist/citra.icns")
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
- add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES} ${MACOSX_ICON})
+ add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES} ${MACOSX_ICON})
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
else()
- add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES})
+ add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${ICONS} ${THEMES})
endif()
target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 92bc0da50..933383de5 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -150,6 +150,10 @@ void Config::ReadValues() {
qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
.toString()
.toStdString();
+ Settings::values.verify_endpoint_url =
+ qt_config->value("verify_endpoint_url", "https://services.citra-emu.org/api/profile")
+ .toString()
+ .toStdString();
Settings::values.citra_username = qt_config->value("citra_username").toString().toStdString();
Settings::values.citra_token = qt_config->value("citra_token").toString().toStdString();
qt_config->endGroup();
@@ -299,6 +303,8 @@ void Config::SaveValues() {
qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
qt_config->setValue("telemetry_endpoint_url",
QString::fromStdString(Settings::values.telemetry_endpoint_url));
+ qt_config->setValue("verify_endpoint_url",
+ QString::fromStdString(Settings::values.verify_endpoint_url));
qt_config->setValue("citra_username", QString::fromStdString(Settings::values.citra_username));
qt_config->setValue("citra_token", QString::fromStdString(Settings::values.citra_token));
qt_config->endGroup();
diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp
index 8715fb018..38ce19c0f 100644
--- a/src/citra_qt/configuration/configure_web.cpp
+++ b/src/citra_qt/configuration/configure_web.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include
#include "citra_qt/configuration/configure_web.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
@@ -11,7 +12,9 @@ ConfigureWeb::ConfigureWeb(QWidget* parent)
: QWidget(parent), ui(std::make_unique()) {
ui->setupUi(this);
connect(ui->button_regenerate_telemetry_id, &QPushButton::clicked, this,
- &ConfigureWeb::refreshTelemetryID);
+ &ConfigureWeb::RefreshTelemetryID);
+ connect(ui->button_verify_login, &QPushButton::clicked, this, &ConfigureWeb::VerifyLogin);
+ connect(this, &ConfigureWeb::LoginVerified, this, &ConfigureWeb::OnLoginVerified);
this->setConfiguration();
}
@@ -34,19 +37,66 @@ void ConfigureWeb::setConfiguration() {
ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry);
ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username));
ui->edit_token->setText(QString::fromStdString(Settings::values.citra_token));
+ // Connect after setting the values, to avoid calling OnLoginChanged now
+ connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
+ connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged);
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
QString::number(Core::GetTelemetryId(), 16).toUpper());
+ user_verified = true;
}
void ConfigureWeb::applyConfiguration() {
Settings::values.enable_telemetry = ui->toggle_telemetry->isChecked();
- Settings::values.citra_username = ui->edit_username->text().toStdString();
- Settings::values.citra_token = ui->edit_token->text().toStdString();
+ if (user_verified) {
+ Settings::values.citra_username = ui->edit_username->text().toStdString();
+ Settings::values.citra_token = ui->edit_token->text().toStdString();
+ } else {
+ QMessageBox::warning(this, tr("Username and token not verfied"),
+ tr("Username and token were not verified. The changes to your "
+ "username and/or token have not been saved."));
+ }
Settings::Apply();
}
-void ConfigureWeb::refreshTelemetryID() {
+void ConfigureWeb::RefreshTelemetryID() {
const u64 new_telemetry_id{Core::RegenerateTelemetryId()};
ui->label_telemetry_id->setText("Telemetry ID: 0x" +
QString::number(new_telemetry_id, 16).toUpper());
}
+
+void ConfigureWeb::OnLoginChanged() {
+ if (ui->edit_username->text().isEmpty() && ui->edit_token->text().isEmpty()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
+ ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
+ } else {
+ user_verified = false;
+ ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
+ ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
+ }
+}
+
+void ConfigureWeb::VerifyLogin() {
+ verified =
+ Core::VerifyLogin(ui->edit_username->text().toStdString(),
+ ui->edit_token->text().toStdString(), [&]() { emit LoginVerified(); });
+ ui->button_verify_login->setDisabled(true);
+ ui->button_verify_login->setText(tr("Verifying"));
+}
+
+void ConfigureWeb::OnLoginVerified() {
+ ui->button_verify_login->setEnabled(true);
+ ui->button_verify_login->setText(tr("Verify"));
+ if (verified.get()) {
+ user_verified = true;
+ ui->label_username_verified->setPixmap(QPixmap(":/icons/checked.png"));
+ ui->label_token_verified->setPixmap(QPixmap(":/icons/checked.png"));
+ } else {
+ ui->label_username_verified->setPixmap(QPixmap(":/icons/failed.png"));
+ ui->label_token_verified->setPixmap(QPixmap(":/icons/failed.png"));
+ QMessageBox::critical(
+ this, tr("Verification failed"),
+ tr("Verification failed. Check that you have entered your username and token "
+ "correctly, and that your internet connection is working."));
+ }
+}
diff --git a/src/citra_qt/configuration/configure_web.h b/src/citra_qt/configuration/configure_web.h
index 20bc254b9..ad2d58f6e 100644
--- a/src/citra_qt/configuration/configure_web.h
+++ b/src/citra_qt/configuration/configure_web.h
@@ -4,6 +4,7 @@
#pragma once
+#include
#include
#include
@@ -21,10 +22,19 @@ public:
void applyConfiguration();
public slots:
- void refreshTelemetryID();
+ void RefreshTelemetryID();
+ void OnLoginChanged();
+ void VerifyLogin();
+ void OnLoginVerified();
+
+signals:
+ void LoginVerified();
private:
void setConfiguration();
+ bool user_verified = true;
+ std::future verified;
+
std::unique_ptr ui;
};
diff --git a/src/citra_qt/configuration/configure_web.ui b/src/citra_qt/configuration/configure_web.ui
index d8d283fad..dd996ab62 100644
--- a/src/citra_qt/configuration/configure_web.ui
+++ b/src/citra_qt/configuration/configure_web.ui
@@ -6,8 +6,8 @@
0
0
- 400
- 300
+ 926
+ 561
@@ -31,14 +31,30 @@
-
-
-
-
+
-
+
+
+
+ 0
+ 0
+
+
+
+ Qt::RightToLeft
+
- Username:
+ Verify
- -
+
-
+
+
+ Sign up
+
+
+
+ -
36
@@ -52,7 +68,22 @@
- -
+
-
+
+
+
+ -
+
+
+ Username:
+
+
+
+ -
+
+
+
+ -
36
@@ -62,13 +93,6 @@
- -
-
-
- Sign up
-
-
-
-
@@ -76,6 +100,19 @@
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
@@ -105,17 +142,17 @@
-
-
- Telemetry ID:
-
+
+ Telemetry ID:
+
-
- 0
- 0
+ 0
+ 0
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 12a280aa7..164c8dd0f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -135,7 +135,8 @@ set(SRCS
hle/service/nim/nim_aoc.cpp
hle/service/nim/nim_s.cpp
hle/service/nim/nim_u.cpp
- hle/service/ns_s.cpp
+ hle/service/ns/ns.cpp
+ hle/service/ns/ns_s.cpp
hle/service/nwm/nwm.cpp
hle/service/nwm/nwm_cec.cpp
hle/service/nwm/nwm_ext.cpp
@@ -335,7 +336,8 @@ set(HEADERS
hle/service/nim/nim_aoc.h
hle/service/nim/nim_s.h
hle/service/nim/nim_u.h
- hle/service/ns_s.h
+ hle/service/ns/ns.h
+ hle/service/ns/ns_s.h
hle/service/nwm/nwm.h
hle/service/nwm/nwm_cec.h
hle/service/nwm/nwm_ext.h
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 0a0b91590..34c5aa381 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -56,7 +56,9 @@ static Dynarmic::UserCallbacks GetUserCallbacks(
user_callbacks.memory.Write16 = &Memory::Write16;
user_callbacks.memory.Write32 = &Memory::Write32;
user_callbacks.memory.Write64 = &Memory::Write64;
- user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
+ // TODO(Subv): Re-add the page table pointers once dynarmic supports switching page tables at
+ // runtime.
+ user_callbacks.page_table = nullptr;
user_callbacks.coprocessors[15] = std::make_shared(interpeter_state);
return user_callbacks;
}
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 629e90607..453d89614 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -146,7 +146,6 @@ void System::Reschedule() {
}
System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
- Memory::InitMemoryMap();
LOG_DEBUG(HW_Memory, "initialized OK");
if (Settings::values.use_cpu_jit) {
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 496d07cb5..7f27e9655 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -8,7 +8,6 @@
#include
#include
#include
-#include "audio_core/audio_core.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -24,7 +23,7 @@
namespace Kernel {
-static MemoryRegionInfo memory_regions[3];
+MemoryRegionInfo memory_regions[3];
/// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system
/// memory configuration type.
@@ -96,9 +95,6 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) {
}
}
-std::array vram;
-std::array n3ds_extra_ram;
-
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) {
using namespace Memory;
@@ -143,30 +139,14 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin
return;
}
- // TODO(yuriks): Use GetPhysicalPointer when that becomes independent of the virtual
- // mappings.
- u8* target_pointer = nullptr;
- switch (area->paddr_base) {
- case VRAM_PADDR:
- target_pointer = vram.data();
- break;
- case DSP_RAM_PADDR:
- target_pointer = AudioCore::GetDspMemory().data();
- break;
- case N3DS_EXTRA_RAM_PADDR:
- target_pointer = n3ds_extra_ram.data();
- break;
- default:
- UNREACHABLE();
- }
+ u8* target_pointer = Memory::GetPhysicalPointer(area->paddr_base + offset_into_region);
// TODO(yuriks): This flag seems to have some other effect, but it's unknown what
MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO;
- auto vma = address_space
- .MapBackingMemory(mapping.address, target_pointer + offset_into_region,
- mapping.size, memory_state)
- .Unwrap();
+ auto vma =
+ address_space.MapBackingMemory(mapping.address, target_pointer, mapping.size, memory_state)
+ .Unwrap();
address_space.Reprotect(vma,
mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite);
}
diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h
index 08c1a9989..da6bb3563 100644
--- a/src/core/hle/kernel/memory.h
+++ b/src/core/hle/kernel/memory.h
@@ -26,4 +26,6 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region);
void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
void MapSharedPages(VMManager& address_space);
+
+extern MemoryRegionInfo memory_regions[3];
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index b957c45dd..324415a36 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -171,6 +171,8 @@ static void SwitchContext(Thread* new_thread) {
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle);
+ auto previous_process = Kernel::g_current_process;
+
current_thread = new_thread;
ready_queue.remove(new_thread->current_priority, new_thread);
@@ -178,8 +180,18 @@ static void SwitchContext(Thread* new_thread) {
Core::CPU().LoadContext(new_thread->context);
Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress());
+
+ if (previous_process != current_thread->owner_process) {
+ Kernel::g_current_process = current_thread->owner_process;
+ Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table;
+ // We have switched processes and thus, page tables, clear the instruction cache so we
+ // don't keep stale data from the previous process.
+ Core::CPU().ClearInstructionCache();
+ }
} else {
current_thread = nullptr;
+ // Note: We do not reset the current process and current page table when idling because
+ // technically we haven't changed processes, our threads are just paused.
}
}
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index cef1f7fa8..7a007c065 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -56,6 +56,10 @@ void VMManager::Reset() {
initial_vma.size = MAX_ADDRESS;
vma_map.emplace(initial_vma.base, initial_vma);
+ page_table.pointers.fill(nullptr);
+ page_table.attributes.fill(Memory::PageType::Unmapped);
+ page_table.cached_res_count.fill(0);
+
UpdatePageTableForVMA(initial_vma);
}
@@ -328,16 +332,17 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
switch (vma.type) {
case VMAType::Free:
- Memory::UnmapRegion(vma.base, vma.size);
+ Memory::UnmapRegion(page_table, vma.base, vma.size);
break;
case VMAType::AllocatedMemoryBlock:
- Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_block->data() + vma.offset);
+ Memory::MapMemoryRegion(page_table, vma.base, vma.size,
+ vma.backing_block->data() + vma.offset);
break;
case VMAType::BackingMemory:
- Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_memory);
+ Memory::MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory);
break;
case VMAType::MMIO:
- Memory::MapIoRegion(vma.base, vma.size, vma.mmio_handler);
+ Memory::MapIoRegion(page_table, vma.base, vma.size, vma.mmio_handler);
break;
}
}
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 38e0d74d0..1302527bb 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -9,6 +9,7 @@
#include
#include "common/common_types.h"
#include "core/hle/result.h"
+#include "core/memory.h"
#include "core/mmio.h"
namespace Kernel {
@@ -102,7 +103,6 @@ struct VirtualMemoryArea {
* - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/
*/
class VMManager final {
- // TODO(yuriks): Make page tables switchable to support multiple VMManagers
public:
/**
* The maximum amount of address space managed by the kernel. Addresses above this are never
@@ -184,6 +184,10 @@ public:
/// Dumps the address space layout to the log, for debugging
void LogLayout(Log::Level log_level) const;
+ /// Each VMManager has its own page table, which is set as the main one when the owning process
+ /// is scheduled.
+ Memory::PageTable page_table;
+
private:
using VMAIter = decltype(vma_map)::iterator;
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
new file mode 100644
index 000000000..9e19c38bf
--- /dev/null
+++ b/src/core/hle/service/ns/ns.cpp
@@ -0,0 +1,16 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/ns/ns_s.h"
+
+namespace Service {
+namespace NS {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared()->InstallAsService(service_manager);
+}
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
new file mode 100644
index 000000000..c3d67d98c
--- /dev/null
+++ b/src/core/hle/service/ns/ns.h
@@ -0,0 +1,16 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NS {
+
+/// Registers all NS services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns_s.cpp b/src/core/hle/service/ns/ns_s.cpp
new file mode 100644
index 000000000..d952888dc
--- /dev/null
+++ b/src/core/hle/service/ns/ns_s.cpp
@@ -0,0 +1,34 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/ns_s.h"
+
+namespace Service {
+namespace NS {
+
+NS_S::NS_S() : ServiceFramework("ns:s", 2) {
+ static const FunctionInfo functions[] = {
+ {0x000100C0, nullptr, "LaunchFIRM"},
+ {0x000200C0, nullptr, "LaunchTitle"},
+ {0x00030000, nullptr, "TerminateApplication"},
+ {0x00040040, nullptr, "TerminateProcess"},
+ {0x000500C0, nullptr, "LaunchApplicationFIRM"},
+ {0x00060042, nullptr, "SetFIRMParams4A0"},
+ {0x00070042, nullptr, "CardUpdateInitialize"},
+ {0x00080000, nullptr, "CardUpdateShutdown"},
+ {0x000D0140, nullptr, "SetTWLBannerHMAC"},
+ {0x000E0000, nullptr, "ShutdownAsync"},
+ {0x00100180, nullptr, "RebootSystem"},
+ {0x00110100, nullptr, "TerminateTitle"},
+ {0x001200C0, nullptr, "SetApplicationCpuTimeLimit"},
+ {0x00150140, nullptr, "LaunchApplication"},
+ {0x00160000, nullptr, "RebootSystemClean"},
+ };
+ RegisterHandlers(functions);
+}
+
+NS_S::~NS_S() = default;
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns_s.h b/src/core/hle/service/ns/ns_s.h
similarity index 68%
rename from src/core/hle/service/ns_s.h
rename to src/core/hle/service/ns/ns_s.h
index 90288a521..660ae453f 100644
--- a/src/core/hle/service/ns_s.h
+++ b/src/core/hle/service/ns/ns_s.h
@@ -4,18 +4,17 @@
#pragma once
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
namespace Service {
namespace NS {
-class NS_S final : public Interface {
+/// Interface to "ns:s" service
+class NS_S final : public ServiceFramework {
public:
NS_S();
-
- std::string GetPortName() const override {
- return "ns:s";
- }
+ ~NS_S();
};
} // namespace NS
diff --git a/src/core/hle/service/ns_s.cpp b/src/core/hle/service/ns_s.cpp
deleted file mode 100644
index 215c9aacc..000000000
--- a/src/core/hle/service/ns_s.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/hle/service/ns_s.h"
-
-namespace Service {
-namespace NS {
-
-const Interface::FunctionInfo FunctionTable[] = {
- {0x000100C0, nullptr, "LaunchFIRM"},
- {0x000200C0, nullptr, "LaunchTitle"},
- {0x00030000, nullptr, "TerminateApplication"},
- {0x00040040, nullptr, "TerminateProcess"},
- {0x000500C0, nullptr, "LaunchApplicationFIRM"},
- {0x00060042, nullptr, "SetFIRMParams4A0"},
- {0x00070042, nullptr, "CardUpdateInitialize"},
- {0x00080000, nullptr, "CardUpdateShutdown"},
- {0x000D0140, nullptr, "SetTWLBannerHMAC"},
- {0x000E0000, nullptr, "ShutdownAsync"},
- {0x00100180, nullptr, "RebootSystem"},
- {0x00110100, nullptr, "TerminateTitle"},
- {0x001200C0, nullptr, "SetApplicationCpuTimeLimit"},
- {0x00150140, nullptr, "LaunchApplication"},
- {0x00160000, nullptr, "RebootSystemClean"},
-};
-
-NS_S::NS_S() {
- Register(FunctionTable);
-}
-
-} // namespace NS
-} // namespace Service
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index aad950e50..f267aad74 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -38,7 +38,7 @@
#include "core/hle/service/news/news.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nim/nim.h"
-#include "core/hle/service/ns_s.h"
+#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nwm/nwm.h"
#include "core/hle/service/pm_app.h"
#include "core/hle/service/ptm/ptm.h"
@@ -215,6 +215,8 @@ void Init() {
SM::g_service_manager = std::make_shared();
SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
+ NS::InstallInterfaces(*SM::g_service_manager);
+
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
@@ -246,7 +248,6 @@ void Init() {
AddService(new HTTP::HTTP_C);
AddService(new LDR::LDR_RO);
AddService(new MIC::MIC_U);
- AddService(new NS::NS_S);
AddService(new PM::PM_APP);
AddService(new SOC::SOC_U);
AddService(new SSL::SSL_C);
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
index 74e336487..69cdc0867 100644
--- a/src/core/loader/3dsx.cpp
+++ b/src/core/loader/3dsx.cpp
@@ -270,6 +270,7 @@ ResultStatus AppLoader_THREEDSX::Load() {
Kernel::g_current_process = Kernel::Process::Create(std::move(codeset));
Kernel::g_current_process->svc_access_mask.set();
Kernel::g_current_process->address_mappings = default_address_mappings;
+ Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table;
// Attach the default resource limit (APPLICATION) to the process
Kernel::g_current_process->resource_limit =
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index cfcde9167..2f27606a1 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -397,6 +397,7 @@ ResultStatus AppLoader_ELF::Load() {
Kernel::g_current_process = Kernel::Process::Create(std::move(codeset));
Kernel::g_current_process->svc_access_mask.set();
Kernel::g_current_process->address_mappings = default_address_mappings;
+ Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table;
// Attach the default resource limit (APPLICATION) to the process
Kernel::g_current_process->resource_limit =
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 7aff7f29b..79ea50147 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -172,6 +172,7 @@ ResultStatus AppLoader_NCCH::LoadExec() {
codeset->memory = std::make_shared>(std::move(code));
Kernel::g_current_process = Kernel::Process::Create(std::move(codeset));
+ Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table;
// Attach a resource limit to the process based on the resource limit category
Kernel::g_current_process->resource_limit =
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 097bc5b47..68a6b1ac2 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -4,83 +4,31 @@
#include
#include
+#include "audio_core/audio_core.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/hle/kernel/memory.h"
#include "core/hle/kernel/process.h"
#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/memory_setup.h"
-#include "core/mmio.h"
#include "video_core/renderer_base.h"
#include "video_core/video_core.h"
namespace Memory {
-enum class PageType {
- /// Page is unmapped and should cause an access error.
- Unmapped,
- /// Page is mapped to regular memory. This is the only type you can get pointers to.
- Memory,
- /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
- /// invalidation
- RasterizerCachedMemory,
- /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
- Special,
- /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and
- /// invalidation
- RasterizerCachedSpecial,
-};
+static std::array vram;
+static std::array n3ds_extra_ram;
-struct SpecialRegion {
- VAddr base;
- u32 size;
- MMIORegionPointer handler;
-};
-
-/**
- * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
- * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and
- * fetching requirements when accessing. In the usual case of an access to regular memory, it only
- * requires an indexed fetch and a check for NULL.
- */
-struct PageTable {
- /**
- * 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 pointers;
-
- /**
- * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
- * type `Special`.
- */
- std::vector special_regions;
-
- /**
- * 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 attributes;
-
- /**
- * Indicates the number of externally cached resources touching a page that should be
- * flushed before the memory is accessed
- */
- std::array cached_res_count;
-};
-
-/// Singular page table used for the singleton process
-static PageTable main_page_table;
-/// Currently active page table
-static PageTable* current_page_table = &main_page_table;
+PageTable* current_page_table = nullptr;
std::array* GetCurrentPageTablePointers() {
return ¤t_page_table->pointers;
}
-static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
+static void MapPages(PageTable& page_table, 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);
@@ -91,9 +39,9 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
while (base != end) {
ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
- current_page_table->attributes[base] = type;
- current_page_table->pointers[base] = memory;
- current_page_table->cached_res_count[base] = 0;
+ page_table.attributes[base] = type;
+ page_table.pointers[base] = memory;
+ page_table.cached_res_count[base] = 0;
base += 1;
if (memory != nullptr)
@@ -101,30 +49,24 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
}
}
-void InitMemoryMap() {
- main_page_table.pointers.fill(nullptr);
- main_page_table.attributes.fill(PageType::Unmapped);
- main_page_table.cached_res_count.fill(0);
-}
-
-void MapMemoryRegion(VAddr base, u32 size, u8* target) {
+void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
- MapPages(base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
}
-void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler) {
+void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
- MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
- current_page_table->special_regions.emplace_back(SpecialRegion{base, size, mmio_handler});
+ page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler});
}
-void UnmapRegion(VAddr base, u32 size) {
+void UnmapRegion(PageTable& page_table, VAddr base, u32 size) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base);
- MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
}
/**
@@ -273,8 +215,7 @@ bool IsValidVirtualAddress(const VAddr vaddr) {
}
bool IsValidPhysicalAddress(const PAddr paddr) {
- boost::optional vaddr = PhysicalToVirtualAddress(paddr);
- return vaddr && IsValidVirtualAddress(*vaddr);
+ return GetPhysicalPointer(paddr) != nullptr;
}
u8* GetPointer(const VAddr vaddr) {
@@ -306,9 +247,63 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
}
u8* GetPhysicalPointer(PAddr address) {
- // TODO(Subv): This call should not go through the application's memory mapping.
- boost::optional vaddr = PhysicalToVirtualAddress(address);
- return vaddr ? GetPointer(*vaddr) : nullptr;
+ struct MemoryArea {
+ PAddr paddr_base;
+ u32 size;
+ };
+
+ static constexpr MemoryArea memory_areas[] = {
+ {VRAM_PADDR, VRAM_SIZE},
+ {IO_AREA_PADDR, IO_AREA_SIZE},
+ {DSP_RAM_PADDR, DSP_RAM_SIZE},
+ {FCRAM_PADDR, FCRAM_N3DS_SIZE},
+ {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE},
+ };
+
+ const auto area =
+ std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) {
+ return address >= area.paddr_base && address < area.paddr_base + area.size;
+ });
+
+ if (area == std::end(memory_areas)) {
+ LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%08X", address);
+ return nullptr;
+ }
+
+ if (area->paddr_base == IO_AREA_PADDR) {
+ LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%08X", address);
+ return nullptr;
+ }
+
+ u32 offset_into_region = address - area->paddr_base;
+
+ u8* target_pointer = nullptr;
+ switch (area->paddr_base) {
+ case VRAM_PADDR:
+ target_pointer = vram.data() + offset_into_region;
+ break;
+ case DSP_RAM_PADDR:
+ target_pointer = AudioCore::GetDspMemory().data() + offset_into_region;
+ break;
+ case FCRAM_PADDR:
+ for (const auto& region : Kernel::memory_regions) {
+ if (offset_into_region >= region.base &&
+ offset_into_region < region.base + region.size) {
+ target_pointer =
+ region.linear_heap_memory->data() + offset_into_region - region.base;
+ break;
+ }
+ }
+ ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address");
+ break;
+ case N3DS_EXTRA_RAM_PADDR:
+ target_pointer = n3ds_extra_ram.data() + offset_into_region;
+ break;
+ default:
+ UNREACHABLE();
+ }
+
+ return target_pointer;
}
void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
diff --git a/src/core/memory.h b/src/core/memory.h
index c8c56babd..b228a48c2 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -7,8 +7,10 @@
#include
#include
#include
+#include
#include
#include "common/common_types.h"
+#include "core/mmio.h"
namespace Memory {
@@ -21,6 +23,59 @@ const u32 PAGE_MASK = PAGE_SIZE - 1;
const int PAGE_BITS = 12;
const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS);
+enum class PageType {
+ /// Page is unmapped and should cause an access error.
+ Unmapped,
+ /// Page is mapped to regular memory. This is the only type you can get pointers to.
+ Memory,
+ /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
+ /// invalidation
+ RasterizerCachedMemory,
+ /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
+ Special,
+ /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and
+ /// invalidation
+ RasterizerCachedSpecial,
+};
+
+struct SpecialRegion {
+ VAddr base;
+ u32 size;
+ MMIORegionPointer handler;
+};
+
+/**
+ * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
+ * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and
+ * fetching requirements when accessing. In the usual case of an access to regular memory, it only
+ * requires an indexed fetch and a check for NULL.
+ */
+struct PageTable {
+ /**
+ * 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 pointers;
+
+ /**
+ * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of
+ * type `Special`.
+ */
+ std::vector special_regions;
+
+ /**
+ * 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 attributes;
+
+ /**
+ * Indicates the number of externally cached resources touching a page that should be
+ * flushed before the memory is accessed
+ */
+ std::array cached_res_count;
+};
+
/// Physical memory regions as seen from the ARM11
enum : PAddr {
/// IO register area
@@ -126,6 +181,9 @@ enum : VAddr {
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
};
+/// Currently active page table
+extern PageTable* current_page_table;
+
bool IsValidVirtualAddress(const VAddr addr);
bool IsValidPhysicalAddress(const PAddr addr);
@@ -169,8 +227,6 @@ boost::optional PhysicalToVirtualAddress(PAddr addr);
/**
* Gets a pointer to the memory region beginning at the specified physical address.
- *
- * @note This is currently implemented using PhysicalToVirtualAddress().
*/
u8* GetPhysicalPointer(PAddr address);
@@ -209,4 +265,4 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode);
* retrieve the current page table for that purpose.
*/
std::array* GetCurrentPageTablePointers();
-}
+} // namespace Memory
diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h
index 3fdf3a87d..c58baa50b 100644
--- a/src/core/memory_setup.h
+++ b/src/core/memory_setup.h
@@ -9,24 +9,24 @@
namespace Memory {
-void InitMemoryMap();
-
/**
* Maps an allocated buffer onto a region of the emulated process address space.
*
+ * @param page_table The page table of the emulated process.
* @param base The address to start mapping at. Must be page-aligned.
* @param size The amount of bytes to map. Must be page-aligned.
* @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
*/
-void MapMemoryRegion(VAddr base, u32 size, u8* target);
+void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target);
/**
* Maps a region of the emulated process address space as a IO region.
+ * @param page_table The page table of the emulated process.
* @param base The address to start mapping at. Must be page-aligned.
* @param size The amount of bytes to map. Must be page-aligned.
* @param mmio_handler The handler that backs the mapping.
*/
-void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler);
+void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler);
-void UnmapRegion(VAddr base, u32 size);
+void UnmapRegion(PageTable& page_table, VAddr base, u32 size);
}
diff --git a/src/core/settings.h b/src/core/settings.h
index 62680ecbf..89f6a66b6 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -134,6 +134,7 @@ struct Values {
// WebService
bool enable_telemetry;
std::string telemetry_endpoint_url;
+ std::string verify_endpoint_url;
std::string citra_username;
std::string citra_token;
} extern values;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 5f946c863..8488f20a3 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -15,6 +15,7 @@
#ifdef ENABLE_WEB_SERVICE
#include "web_service/telemetry_json.h"
+#include "web_service/verify_login.h"
#endif
namespace Core {
@@ -75,6 +76,17 @@ u64 RegenerateTelemetryId() {
return new_telemetry_id;
}
+std::future VerifyLogin(std::string username, std::string token, std::function func) {
+#ifdef ENABLE_WEB_SERVICE
+ return WebService::VerifyLogin(username, token, Settings::values.verify_endpoint_url, func);
+#else
+ return std::async(std::launch::async, [func{std::move(func)}]() {
+ func();
+ return false;
+ });
+#endif
+}
+
TelemetrySession::TelemetrySession() {
#ifdef ENABLE_WEB_SERVICE
if (Settings::values.enable_telemetry) {
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index 65613daae..550c6ea2d 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -4,6 +4,7 @@
#pragma once
+#include
#include
#include "common/telemetry.h"
@@ -47,4 +48,13 @@ u64 GetTelemetryId();
*/
u64 RegenerateTelemetryId();
+/**
+ * Verifies the username and token.
+ * @param username Citra username to use for authentication.
+ * @param token Citra token to use for authentication.
+ * @param func A function that gets exectued when the verification is finished
+ * @returns Future with bool indicating whether the verification succeeded
+ */
+std::future VerifyLogin(std::string username, std::string token, std::function func);
+
} // namespace Core
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 1df6c5677..8384ce744 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -3,20 +3,30 @@
// Refer to the license.txt file included.
#include "core/core.h"
+#include "core/memory.h"
#include "core/memory_setup.h"
#include "tests/core/arm/arm_test_common.h"
namespace ArmTests {
+static Memory::PageTable page_table;
+
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared(this)) {
- Memory::MapIoRegion(0x00000000, 0x80000000, test_memory);
- Memory::MapIoRegion(0x80000000, 0x80000000, test_memory);
+
+ page_table.pointers.fill(nullptr);
+ page_table.attributes.fill(Memory::PageType::Unmapped);
+ page_table.cached_res_count.fill(0);
+
+ Memory::MapIoRegion(page_table, 0x00000000, 0x80000000, test_memory);
+ Memory::MapIoRegion(page_table, 0x80000000, 0x80000000, test_memory);
+
+ Memory::current_page_table = &page_table;
}
TestEnvironment::~TestEnvironment() {
- Memory::UnmapRegion(0x80000000, 0x80000000);
- Memory::UnmapRegion(0x00000000, 0x80000000);
+ Memory::UnmapRegion(page_table, 0x80000000, 0x80000000);
+ Memory::UnmapRegion(page_table, 0x00000000, 0x80000000);
}
void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) {
diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h
index 2874fd127..4fef00d76 100644
--- a/src/video_core/regs_rasterizer.h
+++ b/src/video_core/regs_rasterizer.h
@@ -5,10 +5,10 @@
#pragma once
#include
-
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
+#include "video_core/pica_types.h"
namespace Pica {
@@ -31,7 +31,17 @@ struct RasterizerRegs {
BitField<0, 24, u32> viewport_size_y;
- INSERT_PADDING_WORDS(0x9);
+ INSERT_PADDING_WORDS(0x3);
+
+ BitField<0, 1, u32> clip_enable;
+ BitField<0, 24, u32> clip_coef[4]; // float24
+
+ Math::Vec4 GetClipCoef() const {
+ return {float24::FromRaw(clip_coef[0]), float24::FromRaw(clip_coef[1]),
+ float24::FromRaw(clip_coef[2]), float24::FromRaw(clip_coef[3])};
+ }
+
+ INSERT_PADDING_WORDS(0x1);
BitField<0, 24, u32> viewport_depth_range; // float24
BitField<0, 24, u32> viewport_depth_near_plane; // float24
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index aa95ef21d..7b0cd1b66 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -169,6 +169,8 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, proctex_diff_lut_buffer.handle);
// Sync fixed function OpenGL state
+ SyncClipEnabled();
+ SyncClipCoef();
SyncCullMode();
SyncBlendEnabled();
SyncBlendFuncs();
@@ -401,6 +403,18 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncCullMode();
break;
+ // Clipping plane
+ case PICA_REG_INDEX(rasterizer.clip_enable):
+ SyncClipEnabled();
+ break;
+
+ case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[0], 0x48):
+ case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[1], 0x49):
+ case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[2], 0x4a):
+ case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[3], 0x4b):
+ SyncClipCoef();
+ break;
+
// Depth modifiers
case PICA_REG_INDEX(rasterizer.viewport_depth_range):
SyncDepthScale();
@@ -1280,6 +1294,20 @@ void RasterizerOpenGL::SetShader() {
}
}
+void RasterizerOpenGL::SyncClipEnabled() {
+ state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0;
+}
+
+void RasterizerOpenGL::SyncClipCoef() {
+ const auto raw_clip_coef = Pica::g_state.regs.rasterizer.GetClipCoef();
+ const GLvec4 new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
+ raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};
+ if (new_clip_coef != uniform_block_data.data.clip_coef) {
+ uniform_block_data.data.clip_coef = new_clip_coef;
+ uniform_block_data.dirty = true;
+ }
+}
+
void RasterizerOpenGL::SyncCullMode() {
const auto& regs = Pica::g_state.regs;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 78e218efe..46c62961c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -151,14 +151,21 @@ private:
LightSrc light_src[8];
alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages
alignas(16) GLvec4 tev_combiner_buffer_color;
+ alignas(16) GLvec4 clip_coef;
};
static_assert(
- sizeof(UniformData) == 0x460,
+ sizeof(UniformData) == 0x470,
"The size of the UniformData structure has changed, update the structure in the shader");
static_assert(sizeof(UniformData) < 16384,
"UniformData structure must be less than 16kb as per the OpenGL spec");
+ /// Syncs the clip enabled status to match the PICA register
+ void SyncClipEnabled();
+
+ /// Syncs the clip coefficients to match the PICA register
+ void SyncClipCoef();
+
/// Sets the OpenGL shader in accordance with the current PICA register state
void SetShader();
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index c536e61e1..9fe183944 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -25,6 +25,42 @@ using TevStageConfig = TexturingRegs::TevStageConfig;
namespace GLShader {
+static const std::string UniformBlockDef = R"(
+#define NUM_TEV_STAGES 6
+#define NUM_LIGHTS 8
+
+struct LightSrc {
+ vec3 specular_0;
+ vec3 specular_1;
+ vec3 diffuse;
+ vec3 ambient;
+ vec3 position;
+ vec3 spot_direction;
+ float dist_atten_bias;
+ float dist_atten_scale;
+};
+
+layout (std140) uniform shader_data {
+ vec2 framebuffer_scale;
+ int alphatest_ref;
+ float depth_scale;
+ float depth_offset;
+ int scissor_x1;
+ int scissor_y1;
+ int scissor_x2;
+ int scissor_y2;
+ vec3 fog_color;
+ vec2 proctex_noise_f;
+ vec2 proctex_noise_a;
+ vec2 proctex_noise_p;
+ vec3 lighting_global_ambient;
+ LightSrc light_src[NUM_LIGHTS];
+ vec4 const_color[NUM_TEV_STAGES];
+ vec4 tev_combiner_buffer_color;
+ vec4 clip_coef;
+};
+)";
+
PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
PicaShaderConfig res;
@@ -1010,8 +1046,6 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
std::string out = R"(
#version 330 core
-#define NUM_TEV_STAGES 6
-#define NUM_LIGHTS 8
in vec4 primary_color;
in vec2 texcoord[3];
@@ -1023,36 +1057,6 @@ in vec4 gl_FragCoord;
out vec4 color;
-struct LightSrc {
- vec3 specular_0;
- vec3 specular_1;
- vec3 diffuse;
- vec3 ambient;
- vec3 position;
- vec3 spot_direction;
- float dist_atten_bias;
- float dist_atten_scale;
-};
-
-layout (std140) uniform shader_data {
- vec2 framebuffer_scale;
- int alphatest_ref;
- float depth_scale;
- float depth_offset;
- int scissor_x1;
- int scissor_y1;
- int scissor_x2;
- int scissor_y2;
- vec3 fog_color;
- vec2 proctex_noise_f;
- vec2 proctex_noise_a;
- vec2 proctex_noise_p;
- vec3 lighting_global_ambient;
- LightSrc light_src[NUM_LIGHTS];
- vec4 const_color[NUM_TEV_STAGES];
- vec4 tev_combiner_buffer_color;
-};
-
uniform sampler2D tex[3];
uniform samplerBuffer lighting_lut;
uniform samplerBuffer fog_lut;
@@ -1061,7 +1065,11 @@ uniform samplerBuffer proctex_color_map;
uniform samplerBuffer proctex_alpha_map;
uniform samplerBuffer proctex_lut;
uniform samplerBuffer proctex_diff_lut;
+)";
+ out += UniformBlockDef;
+
+ out += R"(
// Rotate the vector v by the quaternion q
vec3 quaternion_rotate(vec4 q, vec3 v) {
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
@@ -1197,6 +1205,12 @@ out float texcoord0_w;
out vec4 normquat;
out vec3 view;
+)";
+
+ out += UniformBlockDef;
+
+ out += R"(
+
void main() {
primary_color = vert_color;
texcoord[0] = vert_texcoord0;
@@ -1207,7 +1221,7 @@ void main() {
view = vert_view;
gl_Position = vert_position;
gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0
- // TODO (wwylele): calculate gl_ClipDistance[1] from user-defined clipping plane
+ gl_ClipDistance[1] = dot(clip_coef, vert_position);
}
)";
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp
index e9063e616..2857d2829 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -52,7 +52,8 @@ OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
// The hardware takes the absolute and saturates vertex colors like this, *before* doing
// interpolation
for (unsigned i = 0; i < 4; ++i) {
- ret.color[i] = float24::FromFloat32(std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f));
+ float c = std::fabs(ret.color[i].ToFloat32());
+ ret.color[i] = float24::FromFloat32(c < 1.0f ? c : 1.0f);
}
LOG_TRACE(HW_GPU, "Output vertex: pos(%.2f, %.2f, %.2f, %.2f), quat(%.2f, %.2f, %.2f, %.2f), "
diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp
index cdbc71502..a52129eb7 100644
--- a/src/video_core/swrasterizer/clipper.cpp
+++ b/src/video_core/swrasterizer/clipper.cpp
@@ -31,7 +31,7 @@ public:
: coeffs(coeffs), bias(bias) {}
bool IsInside(const Vertex& vertex) const {
- return Math::Dot(vertex.pos + bias, coeffs) <= float24::FromFloat32(0);
+ return Math::Dot(vertex.pos + bias, coeffs) >= float24::FromFloat32(0);
}
bool IsOutSide(const Vertex& vertex) const {
@@ -116,19 +116,18 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
static const float24 f0 = float24::FromFloat32(0.0);
static const float24 f1 = float24::FromFloat32(1.0);
static const std::array clipping_edges = {{
- {Math::MakeVec(f1, f0, f0, -f1)}, // x = +w
- {Math::MakeVec(-f1, f0, f0, -f1)}, // x = -w
- {Math::MakeVec(f0, f1, f0, -f1)}, // y = +w
- {Math::MakeVec(f0, -f1, f0, -f1)}, // y = -w
- {Math::MakeVec(f0, f0, f1, f0)}, // z = 0
- {Math::MakeVec(f0, f0, -f1, -f1)}, // z = -w
- {Math::MakeVec(f0, f0, f0, -f1), Math::Vec4(f0, f0, f0, EPSILON)}, // w = EPSILON
+ {Math::MakeVec(-f1, f0, f0, f1)}, // x = +w
+ {Math::MakeVec(f1, f0, f0, f1)}, // x = -w
+ {Math::MakeVec(f0, -f1, f0, f1)}, // y = +w
+ {Math::MakeVec(f0, f1, f0, f1)}, // y = -w
+ {Math::MakeVec(f0, f0, -f1, f0)}, // z = 0
+ {Math::MakeVec(f0, f0, f1, f1)}, // z = -w
+ {Math::MakeVec(f0, f0, f0, f1), Math::Vec4(f0, f0, f0, EPSILON)}, // w = EPSILON
}};
// Simple implementation of the Sutherland-Hodgman clipping algorithm.
// TODO: Make this less inefficient (currently lots of useless buffering overhead happens here)
- for (auto edge : clipping_edges) {
-
+ auto Clip = [&](const ClippingEdge& edge) {
std::swap(input_list, output_list);
output_list->clear();
@@ -147,12 +146,24 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
}
reference_vertex = &vertex;
}
+ };
+
+ for (auto edge : clipping_edges) {
+ Clip(edge);
// Need to have at least a full triangle to continue...
if (output_list->size() < 3)
return;
}
+ if (g_state.regs.rasterizer.clip_enable) {
+ ClippingEdge custom_edge{g_state.regs.rasterizer.GetClipCoef()};
+ Clip(custom_edge);
+
+ if (output_list->size() < 3)
+ return;
+ }
+
InitScreenCoordinates((*output_list)[0]);
InitScreenCoordinates((*output_list)[1]);
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 334d82a8a..c93811892 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -1,10 +1,12 @@
set(SRCS
telemetry_json.cpp
+ verify_login.cpp
web_backend.cpp
)
set(HEADERS
telemetry_json.h
+ verify_login.h
web_backend.h
)
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
new file mode 100644
index 000000000..1bc3b5afe
--- /dev/null
+++ b/src/web_service/verify_login.cpp
@@ -0,0 +1,28 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include
+#include "web_service/verify_login.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+std::future VerifyLogin(std::string& username, std::string& token,
+ const std::string& endpoint_url, std::function func) {
+ auto get_func = [func, username](const std::string& reply) -> bool {
+ func();
+ if (reply.empty())
+ return false;
+ nlohmann::json json = nlohmann::json::parse(reply);
+ std::string result;
+ try {
+ result = json["username"];
+ } catch (const nlohmann::detail::out_of_range&) {
+ }
+ return result == username;
+ };
+ return GetJson(get_func, endpoint_url, false, username, token);
+}
+
+} // namespace WebService
diff --git a/src/web_service/verify_login.h b/src/web_service/verify_login.h
new file mode 100644
index 000000000..303f5dbbc
--- /dev/null
+++ b/src/web_service/verify_login.h
@@ -0,0 +1,24 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace WebService {
+
+/**
+ * Checks if username and token is valid
+ * @param username Citra username to use for authentication.
+ * @param token Citra token to use for authentication.
+ * @param endpoint_url URL of the services.citra-emu.org endpoint.
+ * @param func A function that gets exectued when the verification is finished
+ * @returns Future with bool indicating whether the verification succeeded
+ */
+std::future VerifyLogin(std::string& username, std::string& token,
+ const std::string& endpoint_url, std::function func);
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index d28a3f757..b17d82f9c 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -18,6 +18,19 @@ static constexpr char API_VERSION[]{"1"};
static std::unique_ptr g_session;
+void Win32WSAStartup() {
+#ifdef _WIN32
+ // On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
+ // initialize Winsock globally, which fixes this problem. Without this, only the first CPR
+ // session will properly be created, and subsequent ones will fail.
+ WSADATA wsa_data;
+ const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
+ if (wsa_result) {
+ LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
+ }
+#endif
+}
+
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
const std::string& username, const std::string& token) {
if (url.empty()) {
@@ -31,16 +44,7 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym
return;
}
-#ifdef _WIN32
- // On Windows, CPR/libcurl does not properly initialize Winsock. The below code is used to
- // initialize Winsock globally, which fixes this problem. Without this, only the first CPR
- // session will properly be created, and subsequent ones will fail.
- WSADATA wsa_data;
- const int wsa_result{WSAStartup(MAKEWORD(2, 2), &wsa_data)};
- if (wsa_result) {
- LOG_CRITICAL(WebService, "WSAStartup failed: %d", wsa_result);
- }
-#endif
+ Win32WSAStartup();
// Built request header
cpr::Header header;
@@ -56,8 +60,81 @@ void PostJson(const std::string& url, const std::string& data, bool allow_anonym
}
// Post JSON asynchronously
- static cpr::AsyncResponse future;
- future = cpr::PostAsync(cpr::Url{url.c_str()}, cpr::Body{data.c_str()}, header);
+ static std::future future;
+ future = cpr::PostCallback(
+ [](cpr::Response r) {
+ if (r.error) {
+ LOG_ERROR(WebService, "POST returned cpr error: %u:%s",
+ static_cast(r.error.code), r.error.message.c_str());
+ return;
+ }
+ if (r.status_code >= 400) {
+ LOG_ERROR(WebService, "POST returned error status code: %u", r.status_code);
+ return;
+ }
+ if (r.header["content-type"].find("application/json") == std::string::npos) {
+ LOG_ERROR(WebService, "POST returned wrong content: %s",
+ r.header["content-type"].c_str());
+ return;
+ }
+ },
+ cpr::Url{url}, cpr::Body{data}, header);
}
+template
+std::future GetJson(std::function func, const std::string& url,
+ bool allow_anonymous, const std::string& username,
+ const std::string& token) {
+ if (url.empty()) {
+ LOG_ERROR(WebService, "URL is invalid");
+ return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); });
+ }
+
+ const bool are_credentials_provided{!token.empty() && !username.empty()};
+ if (!allow_anonymous && !are_credentials_provided) {
+ LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
+ return std::async(std::launch::async, [func{std::move(func)}]() { return func(""); });
+ }
+
+ Win32WSAStartup();
+
+ // Built request header
+ cpr::Header header;
+ if (are_credentials_provided) {
+ // Authenticated request if credentials are provided
+ header = {{"Content-Type", "application/json"},
+ {"x-username", username.c_str()},
+ {"x-token", token.c_str()},
+ {"api-version", API_VERSION}};
+ } else {
+ // Otherwise, anonymous request
+ header = cpr::Header{{"Content-Type", "application/json"}, {"api-version", API_VERSION}};
+ }
+
+ // Get JSON asynchronously
+ return cpr::GetCallback(
+ [func{std::move(func)}](cpr::Response r) {
+ if (r.error) {
+ LOG_ERROR(WebService, "GET returned cpr error: %u:%s",
+ static_cast(r.error.code), r.error.message.c_str());
+ return func("");
+ }
+ if (r.status_code >= 400) {
+ LOG_ERROR(WebService, "GET returned error code: %u", r.status_code);
+ return func("");
+ }
+ if (r.header["content-type"].find("application/json") == std::string::npos) {
+ LOG_ERROR(WebService, "GET returned wrong content: %s",
+ r.header["content-type"].c_str());
+ return func("");
+ }
+ return func(r.text);
+ },
+ cpr::Url{url}, header);
+}
+
+template std::future GetJson(std::function func,
+ const std::string& url, bool allow_anonymous,
+ const std::string& username, const std::string& token);
+
} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index d17100398..a63c75d13 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -4,6 +4,8 @@
#pragma once
+#include
+#include
#include
#include "common/common_types.h"
@@ -20,4 +22,18 @@ namespace WebService {
void PostJson(const std::string& url, const std::string& data, bool allow_anonymous,
const std::string& username = {}, const std::string& token = {});
+/**
+ * Gets JSON from services.citra-emu.org.
+ * @param func A function that gets exectued when the json as a string is received
+ * @param url URL of the services.citra-emu.org endpoint to post data to.
+ * @param allow_anonymous If true, allow anonymous unauthenticated requests.
+ * @param username Citra username to use for authentication.
+ * @param token Citra token to use for authentication.
+ * @return future that holds the return value T of the func
+ */
+template
+std::future GetJson(std::function func, const std::string& url,
+ bool allow_anonymous, const std::string& username = {},
+ const std::string& token = {});
+
} // namespace WebService