From ea51a3af261254e5455f63a0ef41e55ef1dfc471 Mon Sep 17 00:00:00 2001 From: wwylele Date: Tue, 22 Aug 2017 09:49:26 +0300 Subject: [PATCH 01/14] SwRasterizer: implement custom clip plane --- src/video_core/regs_rasterizer.h | 14 ++++++++++++-- src/video_core/swrasterizer/clipper.cpp | 15 +++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) 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/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index cdbc71502..cc76ba555 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp @@ -127,8 +127,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu // 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]); From addbcd5784c8195f49cecc20834537c80d1c8c72 Mon Sep 17 00:00:00 2001 From: wwylele Date: Tue, 22 Aug 2017 09:49:53 +0300 Subject: [PATCH 02/14] gl_rasterizer: implement custom clip plane --- .../renderer_opengl/gl_rasterizer.cpp | 28 +++++++ .../renderer_opengl/gl_rasterizer.h | 9 ++- .../renderer_opengl/gl_shader_gen.cpp | 80 +++++++++++-------- 3 files changed, 83 insertions(+), 34 deletions(-) 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 015e69da9..aa60b2e7f 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -24,6 +24,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; @@ -1008,8 +1044,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]; @@ -1021,36 +1055,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; @@ -1059,7 +1063,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); @@ -1190,6 +1198,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; @@ -1200,7 +1214,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); } )"; From 417cb45e3fc20a7529ce5d548ba0fbc36ea0a621 Mon Sep 17 00:00:00 2001 From: wwylele Date: Tue, 22 Aug 2017 09:47:15 +0300 Subject: [PATCH 03/14] SwRasterizer/Clipper: flip the sign convention to match PICA and OpenGL --- src/video_core/swrasterizer/clipper.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index cc76ba555..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,13 +116,13 @@ 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. @@ -157,7 +157,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu } if (g_state.regs.rasterizer.clip_enable) { - ClippingEdge custom_edge{-g_state.regs.rasterizer.GetClipCoef()}; + ClippingEdge custom_edge{g_state.regs.rasterizer.GetClipCoef()}; Clip(custom_edge); if (output_list->size() < 3) From 6d2734a074f44a24129db850339677d8d7b436aa Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 21 Jul 2017 21:17:57 -0500 Subject: [PATCH 04/14] Kernel/Memory: Give each Process its own page table. The loader is in charge of setting the newly created process's page table as the main one during the loading process. --- src/core/core.cpp | 1 - src/core/hle/kernel/vm_manager.cpp | 13 +++-- src/core/hle/kernel/vm_manager.h | 6 ++- src/core/loader/3dsx.cpp | 1 + src/core/loader/elf.cpp | 1 + src/core/loader/ncch.cpp | 1 + src/core/memory.cpp | 87 +++++------------------------- src/core/memory.h | 60 ++++++++++++++++++++- src/core/memory_setup.h | 10 ++-- 9 files changed, 93 insertions(+), 87 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 5332318cf..59b8768e7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -137,7 +137,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/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/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 65649d9d7..ea46b6ead 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -11,75 +11,18 @@ #include "core/hle/kernel/process.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, -}; - -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); @@ -90,9 +33,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) @@ -100,30 +43,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); } /** diff --git a/src/core/memory.h b/src/core/memory.h index c8c56babd..859a14202 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); @@ -209,4 +267,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); } From c34ec5e77cd9e83fcf5c929f3951557d5269b7a6 Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 21 Jul 2017 21:28:03 -0500 Subject: [PATCH 05/14] Kernel/Memory: Switch the current page table when a new process is scheduled. --- src/core/hle/kernel/thread.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index f5f2eb2f7..b7f094f46 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -178,8 +178,18 @@ static void SwitchContext(Thread* new_thread) { Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); + + if (!previous_thread || previous_thread->owner_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. } } From 214150f00c77474927cbdfb1598dbdb2cb4fcf32 Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 21 Jul 2017 22:22:59 -0500 Subject: [PATCH 06/14] Kernel/Memory: Changed GetPhysicalPointer so that it doesn't go through the current process' page table to obtain a pointer. --- src/core/hle/kernel/memory.cpp | 30 +++------------- src/core/hle/kernel/memory.h | 2 ++ src/core/memory.cpp | 65 ++++++++++++++++++++++++++++++++-- src/core/memory.h | 2 -- 4 files changed, 69 insertions(+), 30 deletions(-) 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/memory.cpp b/src/core/memory.cpp index ea46b6ead..4dcbf2274 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -4,10 +4,12 @@ #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/memory.h" #include "core/memory_setup.h" @@ -16,6 +18,9 @@ namespace Memory { +static std::array vram; +static std::array n3ds_extra_ram; + PageTable* current_page_table = nullptr; std::array* GetCurrentPageTablePointers() { @@ -236,9 +241,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 859a14202..b228a48c2 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -227,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); From b178089251200bd0309afcbcb06b43e7c82dc3bc Mon Sep 17 00:00:00 2001 From: Subv Date: Sat, 22 Jul 2017 19:37:26 -0500 Subject: [PATCH 07/14] Kernel/Threads: Don't clear the CPU instruction cache when performing a context switch from an idle thread into a thread in the same process. We were unnecessarily clearing the cache when going from Process A -> Idle -> Process A, this caused extreme performance regressions. --- src/core/hle/kernel/thread.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index b7f094f46..f77c39d18 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); @@ -179,7 +181,7 @@ static void SwitchContext(Thread* new_thread) { Core::CPU().LoadContext(new_thread->context); Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); - if (!previous_thread || previous_thread->owner_process != current_thread->owner_process) { + 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 From f18a176b601c8dc15b372607a4e9f289bdc25ee4 Mon Sep 17 00:00:00 2001 From: Subv Date: Mon, 7 Aug 2017 13:37:16 -0500 Subject: [PATCH 08/14] Kernel/Memory: Make IsValidPhysicalAddress not go through the current process' virtual memory mapping. --- src/core/memory.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 4dcbf2274..4d16736f5 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -208,8 +208,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) { From 3bde97ea059f7bff4e7ebbc59077a1211ae068f7 Mon Sep 17 00:00:00 2001 From: Subv Date: Mon, 7 Aug 2017 14:30:01 -0500 Subject: [PATCH 09/14] Tests/VFP: Use a standalone pagetable for the TestEnvironment memory operations. This fixes building the tests --- src/tests/core/arm/arm_test_common.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) 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) { From 7a3ab7c63ddcc79e9dfa46ae0347065f66052105 Mon Sep 17 00:00:00 2001 From: Subv Date: Sat, 12 Aug 2017 10:16:35 -0500 Subject: [PATCH 10/14] CPU/Dynarmic: Disable the fast page-table access in dynarmic until it supports switching page tables at runtime. --- src/core/arm/dynarmic/arm_dynarmic.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 7d2790b08..f2bd0d283 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; } From 3d86e3afc4b03037fb1ac8c0b637312a5d0e17f8 Mon Sep 17 00:00:00 2001 From: Subv Date: Mon, 28 Aug 2017 20:26:07 -0500 Subject: [PATCH 11/14] Services/NS: Port ns:s to the new service framework. --- src/core/CMakeLists.txt | 6 +++-- src/core/hle/service/ns/ns.cpp | 16 +++++++++++++ src/core/hle/service/ns/ns.h | 16 +++++++++++++ src/core/hle/service/ns/ns_s.cpp | 34 ++++++++++++++++++++++++++++ src/core/hle/service/{ => ns}/ns_s.h | 9 ++++---- src/core/hle/service/ns_s.cpp | 33 --------------------------- src/core/hle/service/service.cpp | 5 ++-- 7 files changed, 77 insertions(+), 42 deletions(-) create mode 100644 src/core/hle/service/ns/ns.cpp create mode 100644 src/core/hle/service/ns/ns.h create mode 100644 src/core/hle/service/ns/ns_s.cpp rename src/core/hle/service/{ => ns}/ns_s.h (68%) delete mode 100644 src/core/hle/service/ns_s.cpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 662030782..89578024f 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 @@ -334,7 +335,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/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); From a234e4c2009b08039d0698cbbcc8595a1f04a615 Mon Sep 17 00:00:00 2001 From: Huw Pascoe Date: Sun, 17 Sep 2017 15:42:45 +0100 Subject: [PATCH 12/14] Improved performance of FromAttributeBuffer Ternary operator is optimized by the compiler whereas std::min() is meant to return a value. I've noticed a 5%-10% emulation speed increase. --- src/video_core/shader/shader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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), " From 28c726f20545744a3052a3e8a0a3bf5ff95a5042 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Tue, 19 Sep 2017 03:18:26 +0200 Subject: [PATCH 13/14] WebService: Verify username and token (#2930) * WebService: Verify username and token; Log errors in PostJson * Fixup: added docstrings to the functions * Webservice: Added Icons to the verification, imrpved error detection in cpr, fixup nits * fixup: fmt warning --- dist/icons/checked.png | Bin 0 -> 451 bytes dist/icons/failed.png | Bin 0 -> 428 bytes dist/icons/icons.qrc | 6 ++ src/citra/config.cpp | 2 + src/citra/default_ini.h | 2 + src/citra_qt/CMakeLists.txt | 5 +- src/citra_qt/configuration/config.cpp | 6 ++ src/citra_qt/configuration/configure_web.cpp | 58 ++++++++++- src/citra_qt/configuration/configure_web.h | 12 ++- src/citra_qt/configuration/configure_web.ui | 75 ++++++++++---- src/core/settings.h | 1 + src/core/telemetry_session.cpp | 12 +++ src/core/telemetry_session.h | 10 ++ src/web_service/CMakeLists.txt | 2 + src/web_service/verify_login.cpp | 28 +++++ src/web_service/verify_login.h | 24 +++++ src/web_service/web_backend.cpp | 101 ++++++++++++++++--- src/web_service/web_backend.h | 16 +++ 18 files changed, 322 insertions(+), 38 deletions(-) create mode 100644 dist/icons/checked.png create mode 100644 dist/icons/failed.png create mode 100644 dist/icons/icons.qrc create mode 100644 src/web_service/verify_login.cpp create mode 100644 src/web_service/verify_login.h diff --git a/dist/icons/checked.png b/dist/icons/checked.png new file mode 100644 index 0000000000000000000000000000000000000000..c277e6b4043b2b9a7c7388e076d2c493a23272c7 GIT binary patch literal 451 zcmV;!0X+VRP)vS{GWXg z=jFEi{#(+mPYdl{bJ+sm-!h2X0+2YXqDA3r`Z z{0G?zlwk(Ok%)vW!-P4vv1&#KAea1K`JDUz+#3vF3}U;W`I!(fb_Hr!{gNAuT}jjo tH^dVdGoGE~X@(m@tY$_^vok<|0RUEP!L?xlWyb&j002ovPDHLkV1gj@#Tx(s literal 0 HcmV?d00001 diff --git a/dist/icons/failed.png b/dist/icons/failed.png new file mode 100644 index 0000000000000000000000000000000000000000..ac10f174a6016ce67b9584762866248331104a15 GIT binary patch literal 428 zcmV;d0aN~oP)iBJ(9{^)nYy>P1iS9e6X z!PkBz6qq{~ED9X_H1H;RLik$z*jdv8-<+vR4(t%bzxCJw@6ECkn6Hn4HXv8u123st z4YRt$xL&m1m#x)EKlwbxosNdtMuRAdG>*GnO7@h!9rznWTT6*Lf%)fhz?&5v>}cF0000u( literal 0 HcmV?d00001 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 a48ef08c7..45c28ad09 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp @@ -162,6 +162,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 4b13a2e1b..59faf773f 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h @@ -185,6 +185,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 ef114aad3..5261f4c4c 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -146,6 +146,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(); @@ -293,6 +297,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/settings.h b/src/core/settings.h index 024f14666..8d78cb424 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -133,6 +133,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 104a16cc9..ca517ff44 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/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 From 1aa66ed5eda649ade3abb1781e74592cd10b6d98 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Tue, 19 Sep 2017 10:28:57 +0200 Subject: [PATCH 14/14] WebService: Set USE_SYSTEM_CURL for travis linux builds --- .travis-build-docker.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis-build-docker.sh b/.travis-build-docker.sh index ca6fae42b..01249ace0 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 +cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_BUILD_TYPE=Release make -j4 ctest -VV -C Release