summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/audio_renderer.cpp89
-rw-r--r--src/audio_core/audio_renderer.h49
-rw-r--r--src/audio_core/cubeb_sink.cpp3
-rw-r--r--src/audio_core/time_stretch.cpp9
-rw-r--r--src/audio_core/time_stretch.h1
-rw-r--r--src/common/CMakeLists.txt4
-rw-r--r--src/common/alignment.h12
-rw-r--r--src/common/file_util.cpp13
-rw-r--r--src/common/hex_util.cpp19
-rw-r--r--src/common/hex_util.h5
-rw-r--r--src/common/logging/backend.cpp3
-rw-r--r--src/common/logging/log.h3
-rw-r--r--src/common/memory_util.cpp177
-rw-r--r--src/common/memory_util.h21
-rw-r--r--src/common/param_package.cpp18
-rw-r--r--src/common/param_package.h2
-rw-r--r--src/common/telemetry.h4
-rw-r--r--src/common/web_result.h1
-rw-r--r--src/core/CMakeLists.txt31
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp9
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h4
-rw-r--r--src/core/core.cpp83
-rw-r--r--src/core/core.h49
-rw-r--r--src/core/core_cpu.cpp14
-rw-r--r--src/core/core_cpu.h17
-rw-r--r--src/core/crypto/key_manager.cpp839
-rw-r--r--src/core/crypto/key_manager.h109
-rw-r--r--src/core/crypto/partition_data_manager.cpp594
-rw-r--r--src/core/crypto/partition_data_manager.h109
-rw-r--r--src/core/file_sys/bis_factory.cpp12
-rw-r--r--src/core/file_sys/bis_factory.h8
-rw-r--r--src/core/file_sys/card_image.cpp14
-rw-r--r--src/core/file_sys/card_image.h5
-rw-r--r--src/core/file_sys/content_archive.cpp561
-rw-r--r--src/core/file_sys/content_archive.h29
-rw-r--r--src/core/file_sys/control_metadata.cpp13
-rw-r--r--src/core/file_sys/control_metadata.h1
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp26
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.h7
-rw-r--r--src/core/file_sys/ips_layer.cpp276
-rw-r--r--src/core/file_sys/ips_layer.h29
-rw-r--r--src/core/file_sys/partition_filesystem.cpp16
-rw-r--r--src/core/file_sys/partition_filesystem.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp158
-rw-r--r--src/core/file_sys/patch_manager.h12
-rw-r--r--src/core/file_sys/registered_cache.cpp91
-rw-r--r--src/core/file_sys/registered_cache.h43
-rw-r--r--src/core/file_sys/romfs.cpp4
-rw-r--r--src/core/file_sys/romfs.h2
-rw-r--r--src/core/file_sys/romfs_factory.cpp7
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp13
-rw-r--r--src/core/file_sys/sdmc_factory.cpp8
-rw-r--r--src/core/file_sys/sdmc_factory.h4
-rw-r--r--src/core/file_sys/submission_package.cpp9
-rw-r--r--src/core/file_sys/submission_package.h3
-rw-r--r--src/core/file_sys/vfs.cpp12
-rw-r--r--src/core/file_sys/vfs.h46
-rw-r--r--src/core/file_sys/vfs_layered.cpp3
-rw-r--r--src/core/file_sys/vfs_layered.h3
-rw-r--r--src/core/file_sys/vfs_offset.cpp4
-rw-r--r--src/core/file_sys/vfs_offset.h2
-rw-r--r--src/core/file_sys/vfs_real.cpp3
-rw-r--r--src/core/file_sys/vfs_real.h3
-rw-r--r--src/core/file_sys/vfs_static.h4
-rw-r--r--src/core/file_sys/vfs_types.h21
-rw-r--r--src/core/file_sys/vfs_vector.cpp7
-rw-r--r--src/core/file_sys/vfs_vector.h3
-rw-r--r--src/core/file_sys/xts_archive.cpp3
-rw-r--r--src/core/file_sys/xts_archive.h3
-rw-r--r--src/core/gdbstub/gdbstub.cpp6
-rw-r--r--src/core/hle/ipc.h5
-rw-r--r--src/core/hle/ipc_helpers.h3
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp2
-rw-r--r--src/core/hle/kernel/errors.h23
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp25
-rw-r--r--src/core/hle/kernel/hle_ipc.h21
-rw-r--r--src/core/hle/kernel/kernel.cpp26
-rw-r--r--src/core/hle/kernel/kernel.h16
-rw-r--r--src/core/hle/kernel/mutex.cpp2
-rw-r--r--src/core/hle/kernel/object.cpp1
-rw-r--r--src/core/hle/kernel/object.h1
-rw-r--r--src/core/hle/kernel/process.cpp40
-rw-r--r--src/core/hle/kernel/process.h76
-rw-r--r--src/core/hle/kernel/scheduler.cpp32
-rw-r--r--src/core/hle/kernel/scheduler.h19
-rw-r--r--src/core/hle/kernel/server_port.cpp4
-rw-r--r--src/core/hle/kernel/server_session.cpp7
-rw-r--r--src/core/hle/kernel/shared_memory.cpp7
-rw-r--r--src/core/hle/kernel/svc.cpp448
-rw-r--r--src/core/hle/kernel/svc.h31
-rw-r--r--src/core/hle/kernel/svc_wrap.h64
-rw-r--r--src/core/hle/kernel/thread.cpp39
-rw-r--r--src/core/hle/kernel/thread.h19
-rw-r--r--src/core/hle/kernel/vm_manager.cpp75
-rw-r--r--src/core/hle/kernel/vm_manager.h23
-rw-r--r--src/core/hle/service/acc/acc.cpp76
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp204
-rw-r--r--src/core/hle/service/acc/profile_manager.h38
-rw-r--r--src/core/hle/service/am/am.cpp125
-rw-r--r--src/core/hle/service/am/am.h4
-rw-r--r--src/core/hle/service/am/applet_ae.cpp4
-rw-r--r--src/core/hle/service/am/applet_oe.cpp3
-rw-r--r--src/core/hle/service/am/idle.cpp6
-rw-r--r--src/core/hle/service/am/omm.cpp34
-rw-r--r--src/core/hle/service/am/tcap.cpp23
-rw-r--r--src/core/hle/service/am/tcap.h17
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp47
-rw-r--r--src/core/hle/service/aoc/aoc_u.h2
-rw-r--r--src/core/hle/service/audio/audren_u.cpp37
-rw-r--r--src/core/hle/service/audio/hwopus.cpp39
-rw-r--r--src/core/hle/service/es/es.cpp18
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp38
-rw-r--r--src/core/hle/service/filesystem/filesystem.h13
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp34
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.cpp30
-rw-r--r--src/core/hle/service/hid/controllers/controller_base.h45
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.cpp42
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h56
-rw-r--r--src/core/hle/service/hid/controllers/gesture.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/gesture.h63
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/keyboard.h50
-rw-r--r--src/core/hle/service/hid/controllers/mouse.cpp43
-rw-r--r--src/core/hle/service/hid/controllers/mouse.h50
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp620
-rw-r--r--src/core/hle/service/hid/controllers/npad.h289
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.cpp39
-rw-r--r--src/core/hle/service/hid/controllers/stubbed.h34
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.cpp65
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h63
-rw-r--r--src/core/hle/service/hid/controllers/xpad.cpp45
-rw-r--r--src/core/hle/service/hid/controllers/xpad.h60
-rw-r--r--src/core/hle/service/hid/hid.cpp518
-rw-r--r--src/core/hle/service/hid/hid.h402
-rw-r--r--src/core/hle/service/lbl/lbl.cpp56
-rw-r--r--src/core/hle/service/ldr/ldr.cpp52
-rw-r--r--src/core/hle/service/mm/mm_u.cpp50
-rw-r--r--src/core/hle/service/nfc/nfc.cpp53
-rw-r--r--src/core/hle/service/nfp/nfp.cpp270
-rw-r--r--src/core/hle/service/nfp/nfp.h23
-rw-r--r--src/core/hle/service/nifm/nifm.cpp1
-rw-r--r--src/core/hle/service/nim/nim.cpp17
-rw-r--r--src/core/hle/service/npns/npns.cpp88
-rw-r--r--src/core/hle/service/npns/npns.h15
-rw-r--r--src/core/hle/service/ns/ns.cpp55
-rw-r--r--src/core/hle/service/ns/pl_u.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp27
-rw-r--r--src/core/hle/service/nvdrv/devices/nvmap.cpp72
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.cpp8
-rw-r--r--src/core/hle/service/nvflinger/buffer_queue.h11
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp12
-rw-r--r--src/core/hle/service/prepo/prepo.cpp21
-rw-r--r--src/core/hle/service/ptm/psm.cpp71
-rw-r--r--src/core/hle/service/ptm/psm.h15
-rw-r--r--src/core/hle/service/service.cpp12
-rw-r--r--src/core/hle/service/service.h3
-rw-r--r--src/core/hle/service/set/set_cal.cpp3
-rw-r--r--src/core/hle/service/usb/usb.cpp49
-rw-r--r--src/core/hle/service/vi/vi.cpp59
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp22
-rw-r--r--src/core/loader/elf.cpp32
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/core/loader/loader.h20
-rw-r--r--src/core/loader/nax.cpp4
-rw-r--r--src/core/loader/nax.h1
-rw-r--r--src/core/loader/nro.cpp55
-rw-r--r--src/core/loader/nro.h5
-rw-r--r--src/core/loader/nso.cpp53
-rw-r--r--src/core/loader/nso.h15
-rw-r--r--src/core/loader/nsp.cpp71
-rw-r--r--src/core/loader/nsp.h6
-rw-r--r--src/core/loader/xci.cpp41
-rw-r--r--src/core/loader/xci.h6
-rw-r--r--src/core/memory.cpp2
-rw-r--r--src/core/memory_hook.h15
-rw-r--r--src/core/perf_stats.cpp4
-rw-r--r--src/core/settings.h6
-rw-r--r--src/core/telemetry_session.cpp25
-rw-r--r--src/core/telemetry_session.h8
-rw-r--r--src/tests/core/arm/arm_test_common.cpp13
-rw-r--r--src/tests/core/arm/arm_test_common.h10
-rw-r--r--src/video_core/CMakeLists.txt4
-rw-r--r--src/video_core/command_processor.cpp2
-rw-r--r--src/video_core/engines/fermi_2d.cpp55
-rw-r--r--src/video_core/engines/fermi_2d.h22
-rw-r--r--src/video_core/engines/kepler_memory.cpp11
-rw-r--r--src/video_core/engines/kepler_memory.h7
-rw-r--r--src/video_core/engines/maxwell_3d.cpp63
-rw-r--r--src/video_core/engines/maxwell_3d.h94
-rw-r--r--src/video_core/engines/maxwell_compute.cpp6
-rw-r--r--src/video_core/engines/maxwell_dma.cpp78
-rw-r--r--src/video_core/engines/maxwell_dma.h12
-rw-r--r--src/video_core/engines/shader_bytecode.h366
-rw-r--r--src/video_core/engines/shader_header.h5
-rw-r--r--src/video_core/gpu.cpp6
-rw-r--r--src/video_core/macro_interpreter.cpp25
-rw-r--r--src/video_core/macro_interpreter.h17
-rw-r--r--src/video_core/memory_manager.cpp18
-rw-r--r--src/video_core/memory_manager.h8
-rw-r--r--src/video_core/rasterizer_cache.h127
-rw-r--r--src/video_core/rasterizer_interface.h11
-rw-r--r--src/video_core/renderer_base.h6
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h9
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.cpp2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp187
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h32
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp848
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h800
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp35
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h57
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp1531
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.h8
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp117
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h22
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.cpp19
-rw-r--r--src/video_core/renderer_opengl/gl_shader_manager.h13
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp270
-rw-r--r--src/video_core/renderer_opengl/gl_state.h40
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h68
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp25
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h3
-rw-r--r--src/video_core/renderer_opengl/utils.cpp38
-rw-r--r--src/video_core/renderer_opengl/utils.h15
-rw-r--r--src/video_core/surface.cpp483
-rw-r--r--src/video_core/surface.h466
-rw-r--r--src/video_core/textures/astc.cpp32
-rw-r--r--src/video_core/textures/astc.h2
-rw-r--r--src/video_core/textures/decoders.cpp252
-rw-r--r--src/video_core/textures/decoders.h38
-rw-r--r--src/video_core/textures/texture.h36
-rw-r--r--src/video_core/utils.h22
-rw-r--r--src/web_service/CMakeLists.txt4
-rw-r--r--src/web_service/telemetry_json.cpp107
-rw-r--r--src/web_service/telemetry_json.h25
-rw-r--r--src/web_service/verify_login.cpp1
-rw-r--r--src/web_service/web_backend.cpp235
-rw-r--r--src/web_service/web_backend.h58
-rw-r--r--src/yuzu/CMakeLists.txt12
-rw-r--r--src/yuzu/bootmanager.cpp77
-rw-r--r--src/yuzu/bootmanager.h10
-rw-r--r--src/yuzu/compatdb.cpp27
-rw-r--r--src/yuzu/compatdb.h4
-rw-r--r--src/yuzu/configuration/config.cpp32
-rw-r--r--src/yuzu/configuration/config.h14
-rw-r--r--src/yuzu/configuration/configure_debug.cpp2
-rw-r--r--src/yuzu/configuration/configure_debug.ui23
-rw-r--r--src/yuzu/configuration/configure_general.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.ui29
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp4
-rw-r--r--src/yuzu/configuration/configure_graphics.ui4
-rw-r--r--src/yuzu/configuration/configure_input.cpp89
-rw-r--r--src/yuzu/configuration/configure_input.h9
-rw-r--r--src/yuzu/configuration/configure_input.ui28
-rw-r--r--src/yuzu/configuration/configure_system.cpp290
-rw-r--r--src/yuzu/configuration/configure_system.h48
-rw-r--r--src/yuzu/configuration/configure_system.ui252
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints.cpp33
-rw-r--r--src/yuzu/debugger/graphics/graphics_breakpoints_p.h2
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp7
-rw-r--r--src/yuzu/debugger/wait_tree.cpp13
-rw-r--r--src/yuzu/debugger/wait_tree.h1
-rw-r--r--src/yuzu/game_list.cpp17
-rw-r--r--src/yuzu/game_list_worker.cpp59
-rw-r--r--src/yuzu/game_list_worker.h2
-rw-r--r--src/yuzu/main.cpp315
-rw-r--r--src/yuzu/main.h13
-rw-r--r--src/yuzu/main.ui45
-rw-r--r--src/yuzu/util/limitable_input_dialog.cpp59
-rw-r--r--src/yuzu/util/limitable_input_dialog.h31
-rw-r--r--src/yuzu_cmd/config.cpp15
-rw-r--r--src/yuzu_cmd/default_ini.h8
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp50
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.h12
-rw-r--r--src/yuzu_cmd/yuzu.cpp21
276 files changed, 13526 insertions, 4583 deletions
diff --git a/src/audio_core/audio_renderer.cpp b/src/audio_core/audio_renderer.cpp
index 6f0ff953a..23e5d3f10 100644
--- a/src/audio_core/audio_renderer.cpp
+++ b/src/audio_core/audio_renderer.cpp
@@ -30,7 +30,7 @@ public:
return info;
}
- VoiceInfo& Info() {
+ VoiceInfo& GetInfo() {
return info;
}
@@ -51,9 +51,30 @@ private:
VoiceInfo info{};
};
+class AudioRenderer::EffectState {
+public:
+ const EffectOutStatus& GetOutStatus() const {
+ return out_status;
+ }
+
+ const EffectInStatus& GetInfo() const {
+ return info;
+ }
+
+ EffectInStatus& GetInfo() {
+ return info;
+ }
+
+ void UpdateState();
+
+private:
+ EffectOutStatus out_status{};
+ EffectInStatus info{};
+};
AudioRenderer::AudioRenderer(AudioRendererParameter params,
Kernel::SharedPtr<Kernel::Event> buffer_event)
- : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count) {
+ : worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
+ effects(params.effect_count) {
audio_out = std::make_unique<AudioCore::AudioOut>();
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
@@ -96,11 +117,29 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
memory_pool_count * sizeof(MemoryPoolInfo));
// Copy VoiceInfo structs
- std::size_t offset{sizeof(UpdateDataHeader) + config.behavior_size + config.memory_pools_size +
- config.voice_resource_size};
+ std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
+ config.memory_pools_size + config.voice_resource_size};
for (auto& voice : voices) {
- std::memcpy(&voice.Info(), input_params.data() + offset, sizeof(VoiceInfo));
- offset += sizeof(VoiceInfo);
+ std::memcpy(&voice.GetInfo(), input_params.data() + voice_offset, sizeof(VoiceInfo));
+ voice_offset += sizeof(VoiceInfo);
+ }
+
+ std::size_t effect_offset{sizeof(UpdateDataHeader) + config.behavior_size +
+ config.memory_pools_size + config.voice_resource_size +
+ config.voices_size};
+ for (auto& effect : effects) {
+ std::memcpy(&effect.GetInfo(), input_params.data() + effect_offset, sizeof(EffectInStatus));
+ effect_offset += sizeof(EffectInStatus);
+ }
+
+ // Update memory pool state
+ std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
+ for (std::size_t index = 0; index < memory_pool.size(); ++index) {
+ if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
+ memory_pool[index].state = MemoryPoolStates::Attached;
+ } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
+ memory_pool[index].state = MemoryPoolStates::Detached;
+ }
}
// Update voices
@@ -114,14 +153,8 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
}
}
- // Update memory pool state
- std::vector<MemoryPoolEntry> memory_pool(memory_pool_count);
- for (std::size_t index = 0; index < memory_pool.size(); ++index) {
- if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestAttach) {
- memory_pool[index].state = MemoryPoolStates::Attached;
- } else if (mem_pool_info[index].pool_state == MemoryPoolStates::RequestDetach) {
- memory_pool[index].state = MemoryPoolStates::Detached;
- }
+ for (auto& effect : effects) {
+ effect.UpdateState();
}
// Release previous buffers and queue next ones for playback
@@ -144,6 +177,14 @@ std::vector<u8> AudioRenderer::UpdateAudioRenderer(const std::vector<u8>& input_
voice_out_status_offset += sizeof(VoiceOutStatus);
}
+ std::size_t effect_out_status_offset{
+ sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
+ response_data.voice_resource_size};
+ for (const auto& effect : effects) {
+ std::memcpy(output_params.data() + effect_out_status_offset, &effect.GetOutStatus(),
+ sizeof(EffectOutStatus));
+ effect_out_status_offset += sizeof(EffectOutStatus);
+ }
return output_params;
}
@@ -244,11 +285,29 @@ void AudioRenderer::VoiceState::RefreshBuffer() {
break;
}
- samples = Interpolate(interp_state, std::move(samples), Info().sample_rate, STREAM_SAMPLE_RATE);
+ samples =
+ Interpolate(interp_state, std::move(samples), GetInfo().sample_rate, STREAM_SAMPLE_RATE);
is_refresh_pending = false;
}
+void AudioRenderer::EffectState::UpdateState() {
+ if (info.is_new) {
+ out_status.state = EffectStatus::New;
+ } else {
+ if (info.type == Effect::Aux) {
+ ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_info) == 0,
+ "Aux buffers tried to update");
+ ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_info) == 0,
+ "Aux buffers tried to update");
+ ASSERT_MSG(Memory::Read32(info.aux_info.return_buffer_base) == 0,
+ "Aux buffers tried to update");
+ ASSERT_MSG(Memory::Read32(info.aux_info.send_buffer_base) == 0,
+ "Aux buffers tried to update");
+ }
+ }
+}
+
static constexpr s16 ClampToS16(s32 value) {
return static_cast<s16>(std::clamp(value, -32768, 32767));
}
diff --git a/src/audio_core/audio_renderer.h b/src/audio_core/audio_renderer.h
index dfef89e1d..046417da3 100644
--- a/src/audio_core/audio_renderer.h
+++ b/src/audio_core/audio_renderer.h
@@ -28,6 +28,16 @@ enum class PlayState : u8 {
Paused = 2,
};
+enum class Effect : u8 {
+ None = 0,
+ Aux = 2,
+};
+
+enum class EffectStatus : u8 {
+ None = 0,
+ New = 1,
+};
+
struct AudioRendererParameter {
u32_le sample_rate;
u32_le sample_count;
@@ -128,6 +138,43 @@ struct VoiceOutStatus {
};
static_assert(sizeof(VoiceOutStatus) == 0x10, "VoiceOutStatus has wrong size");
+struct AuxInfo {
+ std::array<u8, 24> input_mix_buffers;
+ std::array<u8, 24> output_mix_buffers;
+ u32_le mix_buffer_count;
+ u32_le sample_rate; // Stored in the aux buffer currently
+ u32_le sampe_count;
+ u64_le send_buffer_info;
+ u64_le send_buffer_base;
+
+ u64_le return_buffer_info;
+ u64_le return_buffer_base;
+};
+static_assert(sizeof(AuxInfo) == 0x60, "AuxInfo is an invalid size");
+
+struct EffectInStatus {
+ Effect type;
+ u8 is_new;
+ u8 is_enabled;
+ INSERT_PADDING_BYTES(1);
+ u32_le mix_id;
+ u64_le buffer_base;
+ u64_le buffer_sz;
+ s32_le priority;
+ INSERT_PADDING_BYTES(4);
+ union {
+ std::array<u8, 0xa0> raw;
+ AuxInfo aux_info;
+ };
+};
+static_assert(sizeof(EffectInStatus) == 0xc0, "EffectInStatus is an invalid size");
+
+struct EffectOutStatus {
+ EffectStatus state;
+ INSERT_PADDING_BYTES(0xf);
+};
+static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
+
struct UpdateDataHeader {
UpdateDataHeader() {}
@@ -173,11 +220,13 @@ public:
Stream::State GetStreamState() const;
private:
+ class EffectState;
class VoiceState;
AudioRendererParameter worker_params;
Kernel::SharedPtr<Kernel::Event> buffer_event;
std::vector<VoiceState> voices;
+ std::vector<EffectState> effects;
std::unique_ptr<AudioOut> audio_out;
AudioCore::StreamPtr stream;
};
diff --git a/src/audio_core/cubeb_sink.cpp b/src/audio_core/cubeb_sink.cpp
index 392039688..d31a1c844 100644
--- a/src/audio_core/cubeb_sink.cpp
+++ b/src/audio_core/cubeb_sink.cpp
@@ -121,7 +121,8 @@ CubebSink::CubebSink(std::string target_device_name) {
const auto collection_end{collection.device + collection.count};
const auto device{
std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
- return target_device_name == info.friendly_name;
+ return info.friendly_name != nullptr &&
+ target_device_name == info.friendly_name;
})};
if (device != collection_end) {
output_device = device->devid;
diff --git a/src/audio_core/time_stretch.cpp b/src/audio_core/time_stretch.cpp
index d72d67994..2fe0b3aef 100644
--- a/src/audio_core/time_stretch.cpp
+++ b/src/audio_core/time_stretch.cpp
@@ -10,8 +10,7 @@
namespace AudioCore {
-TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count)
- : m_sample_rate(sample_rate), m_channel_count(channel_count) {
+TimeStretcher::TimeStretcher(u32 sample_rate, u32 channel_count) : m_sample_rate{sample_rate} {
m_sound_touch.setChannels(channel_count);
m_sound_touch.setSampleRate(sample_rate);
m_sound_touch.setPitch(1.0);
@@ -33,10 +32,10 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
// We were given actual_samples number of samples, and num_samples were requested from us.
double current_ratio = static_cast<double>(num_in) / static_cast<double>(num_out);
- const double max_latency = 1.0; // seconds
+ const double max_latency = 0.25; // seconds
const double max_backlog = m_sample_rate * max_latency;
const double backlog_fullness = m_sound_touch.numSamples() / max_backlog;
- if (backlog_fullness > 5.0) {
+ if (backlog_fullness > 4.0) {
// Too many samples in backlog: Don't push anymore on
num_in = 0;
}
@@ -50,7 +49,7 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
// This low-pass filter smoothes out variance in the calculated stretch ratio.
// The time-scale determines how responsive this filter is.
- constexpr double lpf_time_scale = 2.0; // seconds
+ constexpr double lpf_time_scale = 0.712; // seconds
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
diff --git a/src/audio_core/time_stretch.h b/src/audio_core/time_stretch.h
index decd760f1..bb2270b96 100644
--- a/src/audio_core/time_stretch.h
+++ b/src/audio_core/time_stretch.h
@@ -27,7 +27,6 @@ public:
private:
u32 m_sample_rate;
- u32 m_channel_count;
soundtouch::SoundTouch m_sound_touch;
double m_stretch_ratio = 1.0;
};
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 8985e4367..eccd8f64a 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -29,7 +29,7 @@ if ($ENV{CI})
if (BUILD_VERSION)
# This leaves a trailing space on the last word, but we actually want that
# because of how it's styled in the title bar.
- set(BUILD_FULLNAME "${REPO_NAME} #${BUILD_VERSION} ")
+ set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
else()
set(BUILD_FULLNAME "")
endif()
@@ -64,8 +64,6 @@ add_library(common STATIC
logging/text_formatter.cpp
logging/text_formatter.h
math_util.h
- memory_util.cpp
- memory_util.h
microprofile.cpp
microprofile.h
microprofileui.h
diff --git a/src/common/alignment.h b/src/common/alignment.h
index 225770fab..d94a2291f 100644
--- a/src/common/alignment.h
+++ b/src/common/alignment.h
@@ -19,4 +19,16 @@ constexpr T AlignDown(T value, std::size_t size) {
return static_cast<T>(value - value % size);
}
+template <typename T>
+constexpr bool Is4KBAligned(T value) {
+ static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
+ return (value & 0xFFF) == 0;
+}
+
+template <typename T>
+constexpr bool IsWordAligned(T value) {
+ static_assert(std::is_unsigned_v<T>, "T must be an unsigned value.");
+ return (value & 0b11) == 0;
+}
+
} // namespace Common
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 548463787..b52492da6 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -15,21 +15,24 @@
#ifdef _WIN32
#include <windows.h>
// windows.h needs to be included before other windows headers
-#include <commdlg.h> // for GetSaveFileName
-#include <direct.h> // getcwd
+#include <direct.h> // getcwd
#include <io.h>
#include <shellapi.h>
#include <shlobj.h> // for SHGetFolderPath
#include <tchar.h>
#include "common/string_util.h"
-// 64 bit offsets for windows
+#ifdef _MSC_VER
+// 64 bit offsets for MSVC
#define fseeko _fseeki64
#define ftello _ftelli64
-#define atoll _atoi64
+#define fileno _fileno
+#endif
+
+// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64
#define stat _stat64
#define fstat _fstat64
-#define fileno _fileno
+
#else
#ifdef __APPLE__
#include <sys/param.h>
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index 589ae5cbf..5b63f9e81 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) {
return 0;
}
+std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
+ std::vector<u8> out(str.size() / 2);
+ if (little_endian) {
+ for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
+ out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ } else {
+ for (std::size_t i = 0; i < str.size(); i += 2)
+ out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
+ }
+ return out;
+}
+
+std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
+ std::string out;
+ for (u8 c : vector)
+ out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
+ return out;
+}
+
std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
if (len != 32) {
LOG_ERROR(Common,
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 863a5ccd9..68f003cb6 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <vector>
#include <fmt/format.h>
#include "common/common_types.h"
@@ -14,6 +15,8 @@ namespace Common {
u8 ToHexNibble(char c1);
+std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
+
template <std::size_t Size, bool le = false>
std::array<u8, Size> HexStringToArray(std::string_view str) {
std::array<u8, Size> out{};
@@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
return out;
}
+std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
+
template <std::size_t Size>
std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
std::string out;
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 31ad72f38..5753b871a 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -203,6 +203,7 @@ void DebuggerBackend::Write(const Entry& entry) {
SUB(Service, NFP) \
SUB(Service, NIFM) \
SUB(Service, NIM) \
+ SUB(Service, NPNS) \
SUB(Service, NS) \
SUB(Service, NVDRV) \
SUB(Service, PCIE) \
@@ -211,10 +212,12 @@ void DebuggerBackend::Write(const Entry& entry) {
SUB(Service, PM) \
SUB(Service, PREPO) \
SUB(Service, PSC) \
+ SUB(Service, PSM) \
SUB(Service, SET) \
SUB(Service, SM) \
SUB(Service, SPL) \
SUB(Service, SSL) \
+ SUB(Service, TCAP) \
SUB(Service, Time) \
SUB(Service, USB) \
SUB(Service, VI) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index abbd056ee..d4ec31ec3 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -83,6 +83,7 @@ enum class Class : ClassType {
Service_NFP, ///< The NFP service
Service_NIFM, ///< The NIFM (Network interface) service
Service_NIM, ///< The NIM service
+ Service_NPNS, ///< The NPNS service
Service_NS, ///< The NS services
Service_NVDRV, ///< The NVDRV (Nvidia driver) service
Service_PCIE, ///< The PCIe service
@@ -91,10 +92,12 @@ enum class Class : ClassType {
Service_PM, ///< The PM service
Service_PREPO, ///< The PREPO (Play report) service
Service_PSC, ///< The PSC service
+ Service_PSM, ///< The PSM service
Service_SET, ///< The SET (Settings) service
Service_SM, ///< The SM (Service manager) service
Service_SPL, ///< The SPL service
Service_SSL, ///< The SSL service
+ Service_TCAP, ///< The TCAP service.
Service_Time, ///< The time service
Service_USB, ///< The USB (Universal Serial Bus) service
Service_VI, ///< The VI (Video interface) service
diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp
deleted file mode 100644
index 9736fb12a..000000000
--- a/src/common/memory_util.cpp
+++ /dev/null
@@ -1,177 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/logging/log.h"
-#include "common/memory_util.h"
-
-#ifdef _WIN32
-#include <windows.h>
-// Windows.h needs to be included before psapi.h
-#include <psapi.h>
-#include "common/common_funcs.h"
-#include "common/string_util.h"
-#else
-#include <cstdlib>
-#include <sys/mman.h>
-#endif
-
-#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
-#include <unistd.h>
-#define PAGE_MASK (getpagesize() - 1)
-#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK))
-#endif
-
-// This is purposely not a full wrapper for virtualalloc/mmap, but it
-// provides exactly the primitive operations that Dolphin needs.
-
-void* AllocateExecutableMemory(std::size_t size, bool low) {
-#if defined(_WIN32)
- void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
-#else
- static char* map_hint = nullptr;
-#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
- // This OS has no flag to enforce allocation below the 4 GB boundary,
- // but if we hint that we want a low address it is very likely we will
- // get one.
- // An older version of this code used MAP_FIXED, but that has the side
- // effect of discarding already mapped pages that happen to be in the
- // requested virtual memory range (such as the emulated RAM, sometimes).
- if (low && (!map_hint))
- map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */
-#endif
- void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC,
- MAP_ANON | MAP_PRIVATE
-#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT)
- | (low ? MAP_32BIT : 0)
-#endif
- ,
- -1, 0);
-#endif /* defined(_WIN32) */
-
-#ifdef _WIN32
- if (ptr == nullptr) {
-#else
- if (ptr == MAP_FAILED) {
- ptr = nullptr;
-#endif
- LOG_ERROR(Common_Memory, "Failed to allocate executable memory");
- }
-#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT)
- else {
- if (low) {
- map_hint += size;
- map_hint = (char*)round_page(map_hint); /* round up to the next page */
- }
- }
-#endif
-
-#if EMU_ARCH_BITS == 64
- if ((u64)ptr >= 0x80000000 && low == true)
- LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!");
-#endif
-
- return ptr;
-}
-
-void* AllocateMemoryPages(std::size_t size) {
-#ifdef _WIN32
- void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE);
-#else
- void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
-
- if (ptr == MAP_FAILED)
- ptr = nullptr;
-#endif
-
- if (ptr == nullptr)
- LOG_ERROR(Common_Memory, "Failed to allocate raw memory");
-
- return ptr;
-}
-
-void* AllocateAlignedMemory(std::size_t size, std::size_t alignment) {
-#ifdef _WIN32
- void* ptr = _aligned_malloc(size, alignment);
-#else
- void* ptr = nullptr;
-#ifdef ANDROID
- ptr = memalign(alignment, size);
-#else
- if (posix_memalign(&ptr, alignment, size) != 0)
- LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
-#endif
-#endif
-
- if (ptr == nullptr)
- LOG_ERROR(Common_Memory, "Failed to allocate aligned memory");
-
- return ptr;
-}
-
-void FreeMemoryPages(void* ptr, std::size_t size) {
- if (ptr) {
-#ifdef _WIN32
- if (!VirtualFree(ptr, 0, MEM_RELEASE))
- LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg());
-#else
- munmap(ptr, size);
-#endif
- }
-}
-
-void FreeAlignedMemory(void* ptr) {
- if (ptr) {
-#ifdef _WIN32
- _aligned_free(ptr);
-#else
- free(ptr);
-#endif
- }
-}
-
-void WriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) {
-#ifdef _WIN32
- DWORD oldValue;
- if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue))
- LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg());
-#else
- mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ);
-#endif
-}
-
-void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) {
-#ifdef _WIN32
- DWORD oldValue;
- if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
- &oldValue))
- LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg());
-#else
- mprotect(ptr, size,
- allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ);
-#endif
-}
-
-std::string MemUsage() {
-#ifdef _WIN32
-#pragma comment(lib, "psapi")
- DWORD processID = GetCurrentProcessId();
- HANDLE hProcess;
- PROCESS_MEMORY_COUNTERS pmc;
- std::string Ret;
-
- // Print information about the memory usage of the process.
-
- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
- if (nullptr == hProcess)
- return "MemUsage Error";
-
- if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc)))
- Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7));
-
- CloseHandle(hProcess);
- return Ret;
-#else
- return "";
-#endif
-}
diff --git a/src/common/memory_util.h b/src/common/memory_util.h
deleted file mode 100644
index aad071979..000000000
--- a/src/common/memory_util.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <cstddef>
-#include <string>
-
-void* AllocateExecutableMemory(std::size_t size, bool low = true);
-void* AllocateMemoryPages(std::size_t size);
-void FreeMemoryPages(void* ptr, std::size_t size);
-void* AllocateAlignedMemory(std::size_t size, std::size_t alignment);
-void FreeAlignedMemory(void* ptr);
-void WriteProtectMemory(void* ptr, std::size_t size, bool executable = false);
-void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute = false);
-std::string MemUsage();
-
-inline int GetPageSize() {
- return 4096;
-}
diff --git a/src/common/param_package.cpp b/src/common/param_package.cpp
index 9526ca0c6..b916b4866 100644
--- a/src/common/param_package.cpp
+++ b/src/common/param_package.cpp
@@ -20,7 +20,15 @@ constexpr char KEY_VALUE_SEPARATOR_ESCAPE[] = "$0";
constexpr char PARAM_SEPARATOR_ESCAPE[] = "$1";
constexpr char ESCAPE_CHARACTER_ESCAPE[] = "$2";
+/// A placeholder for empty param packages to avoid empty strings
+/// (they may be recognized as "not set" by some frontend libraries like qt)
+constexpr char EMPTY_PLACEHOLDER[] = "[empty]";
+
ParamPackage::ParamPackage(const std::string& serialized) {
+ if (serialized == EMPTY_PLACEHOLDER) {
+ return;
+ }
+
std::vector<std::string> pairs;
Common::SplitString(serialized, PARAM_SEPARATOR, pairs);
@@ -46,7 +54,7 @@ ParamPackage::ParamPackage(std::initializer_list<DataType::value_type> list) : d
std::string ParamPackage::Serialize() const {
if (data.empty())
- return "";
+ return EMPTY_PLACEHOLDER;
std::string result;
@@ -120,4 +128,12 @@ bool ParamPackage::Has(const std::string& key) const {
return data.find(key) != data.end();
}
+void ParamPackage::Erase(const std::string& key) {
+ data.erase(key);
+}
+
+void ParamPackage::Clear() {
+ data.clear();
+}
+
} // namespace Common
diff --git a/src/common/param_package.h b/src/common/param_package.h
index 7842cd4ef..6a0a9b656 100644
--- a/src/common/param_package.h
+++ b/src/common/param_package.h
@@ -32,6 +32,8 @@ public:
void Set(const std::string& key, int value);
void Set(const std::string& key, float value);
bool Has(const std::string& key) const;
+ void Erase(const std::string& key);
+ void Clear();
private:
DataType data;
diff --git a/src/common/telemetry.h b/src/common/telemetry.h
index 8d6ab986b..854a73fae 100644
--- a/src/common/telemetry.h
+++ b/src/common/telemetry.h
@@ -153,6 +153,7 @@ struct VisitorInterface : NonCopyable {
/// Completion method, called once all fields have been visited
virtual void Complete() = 0;
+ virtual bool SubmitTestcase() = 0;
};
/**
@@ -178,6 +179,9 @@ struct NullVisitor : public VisitorInterface {
void Visit(const Field<std::chrono::microseconds>& /*field*/) override {}
void Complete() override {}
+ bool SubmitTestcase() override {
+ return false;
+ }
};
/// Appends build-specific information to the given FieldCollection,
diff --git a/src/common/web_result.h b/src/common/web_result.h
index 969926674..8bfa2141d 100644
--- a/src/common/web_result.h
+++ b/src/common/web_result.h
@@ -5,6 +5,7 @@
#pragma once
#include <string>
+#include "common/common_types.h"
namespace Common {
struct WebResult {
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index e4a676e91..64fdf38cd 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -18,6 +18,8 @@ add_library(core STATIC
crypto/encryption_layer.h
crypto/key_manager.cpp
crypto/key_manager.h
+ crypto/partition_data_manager.cpp
+ crypto/partition_data_manager.h
crypto/ctr_encryption_layer.cpp
crypto/ctr_encryption_layer.h
crypto/xts_encryption_layer.cpp
@@ -70,6 +72,7 @@ add_library(core STATIC
file_sys/vfs_real.cpp
file_sys/vfs_real.h
file_sys/vfs_static.h
+ file_sys/vfs_types.h
file_sys/vfs_vector.cpp
file_sys/vfs_vector.h
file_sys/xts_archive.cpp
@@ -153,6 +156,8 @@ add_library(core STATIC
hle/service/am/omm.h
hle/service/am/spsm.cpp
hle/service/am/spsm.h
+ hle/service/am/tcap.cpp
+ hle/service/am/tcap.h
hle/service/aoc/aoc_u.cpp
hle/service/aoc/aoc_u.h
hle/service/apm/apm.cpp
@@ -233,6 +238,24 @@ add_library(core STATIC
hle/service/hid/irs.h
hle/service/hid/xcd.cpp
hle/service/hid/xcd.h
+ hle/service/hid/controllers/controller_base.cpp
+ hle/service/hid/controllers/controller_base.h
+ hle/service/hid/controllers/debug_pad.cpp
+ hle/service/hid/controllers/debug_pad.h
+ hle/service/hid/controllers/gesture.cpp
+ hle/service/hid/controllers/gesture.h
+ hle/service/hid/controllers/keyboard.cpp
+ hle/service/hid/controllers/keyboard.h
+ hle/service/hid/controllers/mouse.cpp
+ hle/service/hid/controllers/mouse.h
+ hle/service/hid/controllers/npad.cpp
+ hle/service/hid/controllers/npad.h
+ hle/service/hid/controllers/stubbed.cpp
+ hle/service/hid/controllers/stubbed.h
+ hle/service/hid/controllers/touchscreen.cpp
+ hle/service/hid/controllers/touchscreen.h
+ hle/service/hid/controllers/xpad.cpp
+ hle/service/hid/controllers/xpad.h
hle/service/lbl/lbl.cpp
hle/service/lbl/lbl.h
hle/service/ldn/ldn.cpp
@@ -259,6 +282,8 @@ add_library(core STATIC
hle/service/nifm/nifm.h
hle/service/nim/nim.cpp
hle/service/nim/nim.h
+ hle/service/npns/npns.cpp
+ hle/service/npns/npns.h
hle/service/ns/ns.cpp
hle/service/ns/ns.h
hle/service/ns/pl_u.cpp
@@ -306,6 +331,8 @@ add_library(core STATIC
hle/service/prepo/prepo.h
hle/service/psc/psc.cpp
hle/service/psc/psc.h
+ hle/service/ptm/psm.cpp
+ hle/service/ptm/psm.h
hle/service/service.cpp
hle/service/service.h
hle/service/set/set.cpp
@@ -397,8 +424,8 @@ create_target_directory_groups(core)
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt lz4_static mbedtls opus unicorn open_source_archives)
if (ENABLE_WEB_SERVICE)
- add_definitions(-DENABLE_WEB_SERVICE)
- target_link_libraries(core PUBLIC json-headers web_service)
+ target_compile_definitions(core PRIVATE -DENABLE_WEB_SERVICE)
+ target_link_libraries(core PRIVATE web_service)
endif()
if (ARCHITECTURE_x86_64)
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 7e978cf7a..4d2491870 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -129,7 +129,7 @@ public:
};
std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
- auto& current_process = Core::CurrentProcess();
+ auto* current_process = Core::CurrentProcess();
auto** const page_table = current_process->VMManager().page_table.pointers.data();
Dynarmic::A64::UserConfig config;
@@ -144,7 +144,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
// Multi-process state
config.processor_id = core_index;
- config.global_monitor = &exclusive_monitor->monitor;
+ config.global_monitor = &exclusive_monitor.monitor;
// System registers
config.tpidrro_el0 = &cb->tpidrro_el0;
@@ -171,10 +171,9 @@ void ARM_Dynarmic::Step() {
cb->InterpreterFallback(jit->GetPC(), 1);
}
-ARM_Dynarmic::ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
- std::size_t core_index)
+ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index)
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
- exclusive_monitor{std::dynamic_pointer_cast<DynarmicExclusiveMonitor>(exclusive_monitor)} {
+ exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
ThreadContext ctx{};
inner_unicorn.SaveContext(ctx);
PageTableChanged();
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 4ee92ee27..512bf8ce9 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -23,7 +23,7 @@ class DynarmicExclusiveMonitor;
class ARM_Dynarmic final : public ARM_Interface {
public:
- ARM_Dynarmic(std::shared_ptr<ExclusiveMonitor> exclusive_monitor, std::size_t core_index);
+ ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
~ARM_Dynarmic();
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
@@ -62,7 +62,7 @@ private:
ARM_Unicorn inner_unicorn;
std::size_t core_index;
- std::shared_ptr<DynarmicExclusiveMonitor> exclusive_monitor;
+ DynarmicExclusiveMonitor& exclusive_monitor;
Memory::PageTable* current_page_table = nullptr;
};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index b6acfb3e4..6d5b5a2d0 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -71,9 +71,9 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
}
/// Runs a CPU core while the system is powered on
-void RunCpuCore(std::shared_ptr<Cpu> cpu_state) {
+void RunCpuCore(Cpu& cpu_state) {
while (Core::System::GetInstance().IsPoweredOn()) {
- cpu_state->RunLoop(true);
+ cpu_state.RunLoop(true);
}
}
} // Anonymous namespace
@@ -95,7 +95,7 @@ struct System::Impl {
status = ResultStatus::Success;
// Update thread_to_cpu in case Core 0 is run from a different host thread
- thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
+ thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
@@ -136,18 +136,19 @@ struct System::Impl {
if (virtual_filesystem == nullptr)
virtual_filesystem = std::make_shared<FileSys::RealVfsFilesystem>();
- kernel.MakeCurrentProcess(Kernel::Process::Create(kernel, "main"));
+ auto main_process = Kernel::Process::Create(kernel, "main");
+ kernel.MakeCurrentProcess(main_process.get());
- cpu_barrier = std::make_shared<CpuBarrier>();
+ cpu_barrier = std::make_unique<CpuBarrier>();
cpu_exclusive_monitor = Cpu::MakeExclusiveMonitor(cpu_cores.size());
for (std::size_t index = 0; index < cpu_cores.size(); ++index) {
- cpu_cores[index] = std::make_shared<Cpu>(cpu_exclusive_monitor, cpu_barrier, index);
+ cpu_cores[index] = std::make_unique<Cpu>(*cpu_exclusive_monitor, *cpu_barrier, index);
}
telemetry_session = std::make_unique<Core::TelemetrySession>();
service_manager = std::make_shared<Service::SM::ServiceManager>();
- Service::Init(service_manager, virtual_filesystem);
+ Service::Init(service_manager, *virtual_filesystem);
GDBStub::Init();
renderer = VideoCore::CreateRenderer(emu_window);
@@ -159,12 +160,12 @@ struct System::Impl {
// Create threads for CPU cores 1-3, and build thread_to_cpu map
// CPU core 0 is run on the main thread
- thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0];
+ thread_to_cpu[std::this_thread::get_id()] = cpu_cores[0].get();
if (Settings::values.use_multi_core) {
for (std::size_t index = 0; index < cpu_core_threads.size(); ++index) {
cpu_core_threads[index] =
- std::make_unique<std::thread>(RunCpuCore, cpu_cores[index + 1]);
- thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1];
+ std::make_unique<std::thread>(RunCpuCore, std::ref(*cpu_cores[index + 1]));
+ thread_to_cpu[cpu_core_threads[index]->get_id()] = cpu_cores[index + 1].get();
}
}
@@ -184,7 +185,7 @@ struct System::Impl {
LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath);
return ResultStatus::ErrorGetLoader;
}
- std::pair<boost::optional<u32>, Loader::ResultStatus> system_mode =
+ std::pair<std::optional<u32>, Loader::ResultStatus> system_mode =
app_loader->LoadKernelSystemMode();
if (system_mode.second != Loader::ResultStatus::Success) {
@@ -244,6 +245,7 @@ struct System::Impl {
for (auto& cpu_core : cpu_cores) {
cpu_core.reset();
}
+ cpu_exclusive_monitor.reset();
cpu_barrier.reset();
// Shutdown kernel and core timing
@@ -281,9 +283,9 @@ struct System::Impl {
std::unique_ptr<VideoCore::RendererBase> renderer;
std::unique_ptr<Tegra::GPU> gpu_core;
std::shared_ptr<Tegra::DebugContext> debug_context;
- std::shared_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
- std::shared_ptr<CpuBarrier> cpu_barrier;
- std::array<std::shared_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
+ std::unique_ptr<ExclusiveMonitor> cpu_exclusive_monitor;
+ std::unique_ptr<CpuBarrier> cpu_barrier;
+ std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cpu_cores;
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> cpu_core_threads;
std::size_t active_core{}; ///< Active core, only used in single thread mode
@@ -297,7 +299,7 @@ struct System::Impl {
std::string status_details = "";
/// Map of guest threads to CPU cores
- std::map<std::thread::id, std::shared_ptr<Cpu>> thread_to_cpu;
+ std::map<std::thread::id, Cpu*> thread_to_cpu;
Core::PerfStats perf_stats;
Core::FrameLimiter frame_limiter;
@@ -310,6 +312,10 @@ Cpu& System::CurrentCpuCore() {
return impl->CurrentCpuCore();
}
+const Cpu& System::CurrentCpuCore() const {
+ return impl->CurrentCpuCore();
+}
+
System::ResultStatus System::RunLoop(bool tight_loop) {
return impl->RunLoop(tight_loop);
}
@@ -340,7 +346,11 @@ PerfStatsResults System::GetAndResetPerfStats() {
return impl->GetAndResetPerfStats();
}
-Core::TelemetrySession& System::TelemetrySession() const {
+TelemetrySession& System::TelemetrySession() {
+ return *impl->telemetry_session;
+}
+
+const TelemetrySession& System::TelemetrySession() const {
return *impl->telemetry_session;
}
@@ -348,30 +358,44 @@ ARM_Interface& System::CurrentArmInterface() {
return CurrentCpuCore().ArmInterface();
}
-std::size_t System::CurrentCoreIndex() {
+const ARM_Interface& System::CurrentArmInterface() const {
+ return CurrentCpuCore().ArmInterface();
+}
+
+std::size_t System::CurrentCoreIndex() const {
return CurrentCpuCore().CoreIndex();
}
Kernel::Scheduler& System::CurrentScheduler() {
- return *CurrentCpuCore().Scheduler();
+ return CurrentCpuCore().Scheduler();
}
-const std::shared_ptr<Kernel::Scheduler>& System::Scheduler(std::size_t core_index) {
- ASSERT(core_index < NUM_CPU_CORES);
- return impl->cpu_cores[core_index]->Scheduler();
+const Kernel::Scheduler& System::CurrentScheduler() const {
+ return CurrentCpuCore().Scheduler();
+}
+
+Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
+ return CpuCore(core_index).Scheduler();
}
-Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() {
+const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
+ return CpuCore(core_index).Scheduler();
+}
+
+Kernel::Process* System::CurrentProcess() {
return impl->kernel.CurrentProcess();
}
-const Kernel::SharedPtr<Kernel::Process>& System::CurrentProcess() const {
+const Kernel::Process* System::CurrentProcess() const {
return impl->kernel.CurrentProcess();
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
- ASSERT(core_index < NUM_CPU_CORES);
- return impl->cpu_cores[core_index]->ArmInterface();
+ return CpuCore(core_index).ArmInterface();
+}
+
+const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
+ return CpuCore(core_index).ArmInterface();
}
Cpu& System::CpuCore(std::size_t core_index) {
@@ -379,10 +403,19 @@ Cpu& System::CpuCore(std::size_t core_index) {
return *impl->cpu_cores[core_index];
}
+const Cpu& System::CpuCore(std::size_t core_index) const {
+ ASSERT(core_index < NUM_CPU_CORES);
+ return *impl->cpu_cores[core_index];
+}
+
ExclusiveMonitor& System::Monitor() {
return *impl->cpu_exclusive_monitor;
}
+const ExclusiveMonitor& System::Monitor() const {
+ return *impl->cpu_exclusive_monitor;
+}
+
Tegra::GPU& System::GPU() {
return *impl->gpu_core;
}
diff --git a/src/core/core.h b/src/core/core.h
index f9a3e97e3..cfacceb81 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -129,11 +129,11 @@ public:
*/
bool IsPoweredOn() const;
- /**
- * Returns a reference to the telemetry session for this emulation session.
- * @returns Reference to the telemetry session.
- */
- Core::TelemetrySession& TelemetrySession() const;
+ /// Gets a reference to the telemetry session for this emulation session.
+ Core::TelemetrySession& TelemetrySession();
+
+ /// Gets a reference to the telemetry session for this emulation session.
+ const Core::TelemetrySession& TelemetrySession() const;
/// Prepare the core emulation for a reschedule
void PrepareReschedule();
@@ -144,21 +144,36 @@ public:
/// Gets an ARM interface to the CPU core that is currently running
ARM_Interface& CurrentArmInterface();
+ /// Gets an ARM interface to the CPU core that is currently running
+ const ARM_Interface& CurrentArmInterface() const;
+
/// Gets the index of the currently running CPU core
- std::size_t CurrentCoreIndex();
+ std::size_t CurrentCoreIndex() const;
/// Gets the scheduler for the CPU core that is currently running
Kernel::Scheduler& CurrentScheduler();
- /// Gets an ARM interface to the CPU core with the specified index
+ /// Gets the scheduler for the CPU core that is currently running
+ const Kernel::Scheduler& CurrentScheduler() const;
+
+ /// Gets a reference to an ARM interface for the CPU core with the specified index
ARM_Interface& ArmInterface(std::size_t core_index);
+ /// Gets a const reference to an ARM interface from the CPU core with the specified index
+ const ARM_Interface& ArmInterface(std::size_t core_index) const;
+
/// Gets a CPU interface to the CPU core with the specified index
Cpu& CpuCore(std::size_t core_index);
- /// Gets the exclusive monitor
+ /// Gets a CPU interface to the CPU core with the specified index
+ const Cpu& CpuCore(std::size_t core_index) const;
+
+ /// Gets a reference to the exclusive monitor
ExclusiveMonitor& Monitor();
+ /// Gets a constant reference to the exclusive monitor
+ const ExclusiveMonitor& Monitor() const;
+
/// Gets a mutable reference to the GPU interface
Tegra::GPU& GPU();
@@ -172,13 +187,16 @@ public:
const VideoCore::RendererBase& Renderer() const;
/// Gets the scheduler for the CPU core with the specified index
- const std::shared_ptr<Kernel::Scheduler>& Scheduler(std::size_t core_index);
+ Kernel::Scheduler& Scheduler(std::size_t core_index);
- /// Provides a reference to the current process
- Kernel::SharedPtr<Kernel::Process>& CurrentProcess();
+ /// Gets the scheduler for the CPU core with the specified index
+ const Kernel::Scheduler& Scheduler(std::size_t core_index) const;
- /// Provides a constant reference to the current process.
- const Kernel::SharedPtr<Kernel::Process>& CurrentProcess() const;
+ /// Provides a pointer to the current process
+ Kernel::Process* CurrentProcess();
+
+ /// Provides a constant pointer to the current process.
+ const Kernel::Process* CurrentProcess() const;
/// Provides a reference to the kernel instance.
Kernel::KernelCore& Kernel();
@@ -224,6 +242,9 @@ private:
/// Returns the currently running CPU core
Cpu& CurrentCpuCore();
+ /// Returns the currently running CPU core
+ const Cpu& CurrentCpuCore() const;
+
/**
* Initialize the emulated system.
* @param emu_window Reference to the host-system window used for video output and keyboard
@@ -246,7 +267,7 @@ inline TelemetrySession& Telemetry() {
return System::GetInstance().TelemetrySession();
}
-inline Kernel::SharedPtr<Kernel::Process>& CurrentProcess() {
+inline Kernel::Process* CurrentProcess() {
return System::GetInstance().CurrentProcess();
}
diff --git a/src/core/core_cpu.cpp b/src/core/core_cpu.cpp
index 265f8ed9c..fffda8a99 100644
--- a/src/core/core_cpu.cpp
+++ b/src/core/core_cpu.cpp
@@ -49,10 +49,8 @@ bool CpuBarrier::Rendezvous() {
return false;
}
-Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
- std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index)
- : cpu_barrier{std::move(cpu_barrier)}, core_index{core_index} {
-
+Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index)
+ : cpu_barrier{cpu_barrier}, core_index{core_index} {
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
@@ -64,15 +62,15 @@ Cpu::Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
arm_interface = std::make_unique<ARM_Unicorn>();
}
- scheduler = std::make_shared<Kernel::Scheduler>(*arm_interface);
+ scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
}
Cpu::~Cpu() = default;
-std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
+std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_cores) {
if (Settings::values.use_cpu_jit) {
#ifdef ARCHITECTURE_x86_64
- return std::make_shared<DynarmicExclusiveMonitor>(num_cores);
+ return std::make_unique<DynarmicExclusiveMonitor>(num_cores);
#else
return nullptr; // TODO(merry): Passthrough exclusive monitor
#endif
@@ -83,7 +81,7 @@ std::shared_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(std::size_t num_core
void Cpu::RunLoop(bool tight_loop) {
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
- if (!cpu_barrier->Rendezvous()) {
+ if (!cpu_barrier.Rendezvous()) {
// If rendezvous failed, session has been killed
return;
}
diff --git a/src/core/core_cpu.h b/src/core/core_cpu.h
index ee7e04abc..1d2bdc6cd 100644
--- a/src/core/core_cpu.h
+++ b/src/core/core_cpu.h
@@ -41,8 +41,7 @@ private:
class Cpu {
public:
- Cpu(std::shared_ptr<ExclusiveMonitor> exclusive_monitor,
- std::shared_ptr<CpuBarrier> cpu_barrier, std::size_t core_index);
+ Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index);
~Cpu();
void RunLoop(bool tight_loop = true);
@@ -59,8 +58,12 @@ public:
return *arm_interface;
}
- const std::shared_ptr<Kernel::Scheduler>& Scheduler() const {
- return scheduler;
+ Kernel::Scheduler& Scheduler() {
+ return *scheduler;
+ }
+
+ const Kernel::Scheduler& Scheduler() const {
+ return *scheduler;
}
bool IsMainCore() const {
@@ -71,14 +74,14 @@ public:
return core_index;
}
- static std::shared_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
+ static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(std::size_t num_cores);
private:
void Reschedule();
std::unique_ptr<ARM_Interface> arm_interface;
- std::shared_ptr<CpuBarrier> cpu_barrier;
- std::shared_ptr<Kernel::Scheduler> scheduler;
+ CpuBarrier& cpu_barrier;
+ std::unique_ptr<Kernel::Scheduler> scheduler;
std::atomic<bool> reschedule_pending = false;
std::size_t core_index;
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index bf3a70944..904afa039 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -4,23 +4,56 @@
#include <algorithm>
#include <array>
+#include <bitset>
+#include <cctype>
#include <fstream>
#include <locale>
+#include <map>
#include <sstream>
#include <string_view>
#include <tuple>
#include <vector>
+#include <mbedtls/bignum.h>
+#include <mbedtls/cipher.h>
+#include <mbedtls/cmac.h>
+#include <mbedtls/sha256.h>
+#include "common/common_funcs.h"
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/key_manager.h"
+#include "core/crypto/partition_data_manager.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
#include "core/settings.h"
namespace Core::Crypto {
+constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
+
+using namespace Common;
+
+const std::array<SHA256Hash, 2> eticket_source_hashes{
+ "B71DB271DC338DF380AA2C4335EF8873B1AFD408E80B3582D8719FC81C5E511C"_array32, // eticket_rsa_kek_source
+ "E8965A187D30E57869F562D04383C996DE487BBA5761363D2D4D32391866A85C"_array32, // eticket_rsa_kekek_source
+};
+
+const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
+ {{S128KeyType::Master, 0}, "master_key_"},
+ {{S128KeyType::Package1, 0}, "package1_key_"},
+ {{S128KeyType::Package2, 0}, "package2_key_"},
+ {{S128KeyType::Titlekek, 0}, "titlekek_"},
+ {{S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob)}, "keyblob_key_source_"},
+ {{S128KeyType::Keyblob, 0}, "keyblob_key_"},
+ {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
+};
+
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
Key128 out{};
@@ -37,57 +70,136 @@ Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, K
return out;
}
-boost::optional<Key128> DeriveSDSeed() {
+Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source) {
+ AESCipher<Key128> sbk_cipher(sbk, Mode::ECB);
+ AESCipher<Key128> tsec_cipher(tsec, Mode::ECB);
+ tsec_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
+ sbk_cipher.Transcode(source.data(), source.size(), source.data(), Op::Decrypt);
+ return source;
+}
+
+Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source) {
+ Key128 master_root;
+ std::memcpy(master_root.data(), keyblob.data(), sizeof(Key128));
+
+ AESCipher<Key128> master_cipher(master_root, Mode::ECB);
+
+ Key128 master{};
+ master_cipher.Transcode(master_source.data(), master_source.size(), master.data(), Op::Decrypt);
+ return master;
+}
+
+std::array<u8, 144> DecryptKeyblob(const std::array<u8, 176>& encrypted_keyblob,
+ const Key128& key) {
+ std::array<u8, 0x90> keyblob;
+ AESCipher<Key128> cipher(key, Mode::CTR);
+ cipher.SetIV(std::vector<u8>(encrypted_keyblob.data() + 0x10, encrypted_keyblob.data() + 0x20));
+ cipher.Transcode(encrypted_keyblob.data() + 0x20, keyblob.size(), keyblob.data(), Op::Decrypt);
+ return keyblob;
+}
+
+void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
+ const auto kek_generation_source =
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
+ const auto key_generation_source =
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
+
+ if (HasKey(S128KeyType::Master, crypto_revision)) {
+ for (auto kak_type :
+ {KeyAreaKeyType::Application, KeyAreaKeyType::Ocean, KeyAreaKeyType::System}) {
+ if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(kak_type))) {
+ const auto source =
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(kak_type));
+ const auto kek =
+ GenerateKeyEncryptionKey(source, GetKey(S128KeyType::Master, crypto_revision),
+ kek_generation_source, key_generation_source);
+ SetKey(S128KeyType::KeyArea, kek, crypto_revision, static_cast<u64>(kak_type));
+ }
+ }
+
+ AESCipher<Key128> master_cipher(GetKey(S128KeyType::Master, crypto_revision), Mode::ECB);
+ for (auto key_type : {SourceKeyType::Titlekek, SourceKeyType::Package2}) {
+ if (HasKey(S128KeyType::Source, static_cast<u64>(key_type))) {
+ Key128 key{};
+ master_cipher.Transcode(
+ GetKey(S128KeyType::Source, static_cast<u64>(key_type)).data(), key.size(),
+ key.data(), Op::Decrypt);
+ SetKey(key_type == SourceKeyType::Titlekek ? S128KeyType::Titlekek
+ : S128KeyType::Package2,
+ key, crypto_revision);
+ }
+ }
+ }
+}
+
+Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
+ AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
+ Key128 mac_key{};
+ mac_cipher.Transcode(mac_source.data(), mac_key.size(), mac_key.data(), Op::Decrypt);
+ return mac_key;
+}
+
+std::optional<Key128> DeriveSDSeed() {
const FileUtil::IOFile save_43(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
"/system/save/8000000000000043",
"rb+");
if (!save_43.IsOpen())
- return boost::none;
+ return {};
+
const FileUtil::IOFile sd_private(
FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "/Nintendo/Contents/private", "rb+");
if (!sd_private.IsOpen())
- return boost::none;
+ return {};
- sd_private.Seek(0, SEEK_SET);
std::array<u8, 0x10> private_seed{};
- if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != 0x10)
- return boost::none;
+ if (sd_private.ReadBytes(private_seed.data(), private_seed.size()) != private_seed.size()) {
+ return {};
+ }
std::array<u8, 0x10> buffer{};
std::size_t offset = 0;
for (; offset + 0x10 < save_43.GetSize(); ++offset) {
- save_43.Seek(offset, SEEK_SET);
+ if (!save_43.Seek(offset, SEEK_SET)) {
+ return {};
+ }
+
save_43.ReadBytes(buffer.data(), buffer.size());
- if (buffer == private_seed)
+ if (buffer == private_seed) {
break;
+ }
}
- if (offset + 0x10 >= save_43.GetSize())
- return boost::none;
+ if (!save_43.Seek(offset + 0x10, SEEK_SET)) {
+ return {};
+ }
Key128 seed{};
- save_43.Seek(offset + 0x10, SEEK_SET);
- save_43.ReadBytes(seed.data(), seed.size());
+ if (save_43.ReadBytes(seed.data(), seed.size()) != seed.size()) {
+ return {};
+ }
return seed;
}
-Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys) {
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK)))
+Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys) {
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek)))
return Loader::ResultStatus::ErrorMissingSDKEKSource;
- if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration)))
+ if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)))
return Loader::ResultStatus::ErrorMissingAESKEKGenerationSource;
if (!keys.HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)))
return Loader::ResultStatus::ErrorMissingAESKeyGenerationSource;
const auto sd_kek_source =
- keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK));
+ keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek));
const auto aes_kek_gen =
- keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration));
+ keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration));
const auto aes_key_gen =
keys.GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration));
const auto master_00 = keys.GetKey(S128KeyType::Master);
const auto sd_kek =
GenerateKeyEncryptionKey(sd_kek_source, master_00, aes_kek_gen, aes_key_gen);
+ keys.SetKey(S128KeyType::SDKek, sd_kek);
if (!keys.HasKey(S128KeyType::SDSeed))
return Loader::ResultStatus::ErrorMissingSDSeed;
@@ -118,9 +230,147 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManag
return source; ///< Return unaltered source to satisfy output requirement.
});
+ keys.SetKey(S256KeyType::SDKey, sd_keys[0], static_cast<u64>(SDKeyType::Save));
+ keys.SetKey(S256KeyType::SDKey, sd_keys[1], static_cast<u64>(SDKeyType::NCA));
+
return Loader::ResultStatus::Success;
}
+std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
+ if (!ticket_save.IsOpen())
+ return {};
+
+ std::vector<u8> buffer(ticket_save.GetSize());
+ if (ticket_save.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
+ return {};
+ }
+
+ std::vector<TicketRaw> out;
+ u32 magic{};
+ for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
+ if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
+ buffer[offset + 3] == 0x0) {
+ out.emplace_back();
+ auto& next = out.back();
+ std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw));
+ offset += next.size();
+ }
+ }
+
+ return out;
+}
+
+template <size_t size>
+static std::array<u8, size> operator^(const std::array<u8, size>& lhs,
+ const std::array<u8, size>& rhs) {
+ std::array<u8, size> out{};
+ std::transform(lhs.begin(), lhs.end(), rhs.begin(), out.begin(), std::bit_xor<>());
+ return out;
+}
+
+template <size_t target_size, size_t in_size>
+static std::array<u8, target_size> MGF1(const std::array<u8, in_size>& seed) {
+ // Avoids truncation overflow within the loop below.
+ static_assert(target_size <= 0xFF);
+
+ std::array<u8, in_size + 4> seed_exp{};
+ std::memcpy(seed_exp.data(), seed.data(), in_size);
+
+ std::vector<u8> out;
+ size_t i = 0;
+ while (out.size() < target_size) {
+ out.resize(out.size() + 0x20);
+ seed_exp[in_size + 3] = static_cast<u8>(i);
+ mbedtls_sha256(seed_exp.data(), seed_exp.size(), out.data() + out.size() - 0x20, 0);
+ ++i;
+ }
+
+ std::array<u8, target_size> target;
+ std::memcpy(target.data(), out.data(), target_size);
+ return target;
+}
+
+template <size_t size>
+static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
+ u64 offset = 0;
+ for (size_t i = 0x20; i < data.size() - 0x10; ++i) {
+ if (data[i] == 0x1) {
+ offset = i + 1;
+ break;
+ } else if (data[i] != 0x0) {
+ return {};
+ }
+ }
+
+ return offset;
+}
+
+std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+ const RSAKeyPair<2048>& key) {
+ u32 cert_authority;
+ std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority));
+ if (cert_authority == 0)
+ return {};
+ if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) {
+ LOG_INFO(Crypto,
+ "Attempting to parse ticket with non-standard certificate authority {:08X}.",
+ cert_authority);
+ }
+
+ Key128 rights_id;
+ std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
+
+ if (rights_id == Key128{})
+ return {};
+
+ Key128 key_temp{};
+
+ if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) {
+ std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
+ return std::make_pair(rights_id, key_temp);
+ }
+
+ mbedtls_mpi D; // RSA Private Exponent
+ mbedtls_mpi N; // RSA Modulus
+ mbedtls_mpi S; // Input
+ mbedtls_mpi M; // Output
+
+ mbedtls_mpi_init(&D);
+ mbedtls_mpi_init(&N);
+ mbedtls_mpi_init(&S);
+ mbedtls_mpi_init(&M);
+
+ mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
+ mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
+ mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100);
+
+ mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
+
+ std::array<u8, 0x100> rsa_step;
+ mbedtls_mpi_write_binary(&M, rsa_step.data(), rsa_step.size());
+
+ u8 m_0 = rsa_step[0];
+ std::array<u8, 0x20> m_1;
+ std::memcpy(m_1.data(), rsa_step.data() + 0x01, m_1.size());
+ std::array<u8, 0xDF> m_2;
+ std::memcpy(m_2.data(), rsa_step.data() + 0x21, m_2.size());
+
+ if (m_0 != 0)
+ return {};
+
+ m_1 = m_1 ^ MGF1<0x20>(m_2);
+ m_2 = m_2 ^ MGF1<0xDF>(m_1);
+
+ const auto offset = FindTicketOffset(m_2);
+ if (!offset)
+ return {};
+ ASSERT(*offset > 0);
+
+ std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
+
+ return std::make_pair(rights_id, key_temp);
+}
+
KeyManager::KeyManager() {
// Initialize keys
const std::string hactool_keys_dir = FileUtil::GetHactoolConfigurationPath();
@@ -137,6 +387,15 @@ KeyManager::KeyManager() {
AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "title.keys", true);
AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "title.keys_autogenerated", true);
+ AttemptLoadKeyFile(yuzu_keys_dir, hactool_keys_dir, "console.keys", false);
+ AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, "console.keys_autogenerated", false);
+}
+
+static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_t length) {
+ if (base.size() < begin + length)
+ return false;
+ return std::all_of(base.begin() + begin, base.begin() + begin + length,
+ [](u8 c) { return std::isxdigit(c); });
}
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
@@ -158,6 +417,9 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
out[0].erase(std::remove(out[0].begin(), out[0].end(), ' '), out[0].end());
out[1].erase(std::remove(out[1].begin(), out[1].end(), ' '), out[1].end());
+ if (out[0].compare(0, 1, "#") == 0)
+ continue;
+
if (is_title_keys) {
auto rights_id_raw = Common::HexStringToArray<16>(out[0]);
u128 rights_id{};
@@ -174,6 +436,50 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
const auto index = s256_file_id.at(out[0]);
Key256 key = Common::HexStringToArray<32>(out[1]);
s256_keys[{index.type, index.field1, index.field2}] = key;
+ } else if (out[0].compare(0, 8, "keyblob_") == 0 &&
+ out[0].compare(0, 9, "keyblob_k") != 0) {
+ if (!ValidCryptoRevisionString(out[0], 8, 2))
+ continue;
+
+ const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
+ keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
+ } else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
+ if (!ValidCryptoRevisionString(out[0], 18, 2))
+ continue;
+
+ const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
+ encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
+ } else {
+ for (const auto& kv : KEYS_VARIABLE_LENGTH) {
+ if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
+ continue;
+ if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
+ const auto index =
+ std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
+ const auto sub = kv.first.second;
+ if (sub == 0) {
+ s128_keys[{kv.first.first, index, 0}] =
+ Common::HexStringToArray<16>(out[1]);
+ } else {
+ s128_keys[{kv.first.first, kv.first.second, index}] =
+ Common::HexStringToArray<16>(out[1]);
+ }
+
+ break;
+ }
+ }
+
+ static constexpr std::array<const char*, 3> kak_names = {
+ "key_area_key_application_", "key_area_key_ocean_", "key_area_key_system_"};
+ for (size_t j = 0; j < kak_names.size(); ++j) {
+ const auto& match = kak_names[j];
+ if (out[0].compare(0, std::strlen(match), match) == 0) {
+ const auto index =
+ std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
+ s128_keys[{S128KeyType::KeyArea, index, j}] =
+ Common::HexStringToArray<16>(out[1]);
+ }
+ }
}
}
}
@@ -187,6 +493,28 @@ void KeyManager::AttemptLoadKeyFile(const std::string& dir1, const std::string&
LoadFromFile(dir2 + DIR_SEP + filename, title);
}
+bool KeyManager::BaseDeriveNecessary() const {
+ const auto check_key_existence = [this](auto key_type, u64 index1 = 0, u64 index2 = 0) {
+ return !HasKey(key_type, index1, index2);
+ };
+
+ if (check_key_existence(S256KeyType::Header))
+ return true;
+
+ for (size_t i = 0; i < CURRENT_CRYPTO_REVISION; ++i) {
+ if (check_key_existence(S128KeyType::Master, i) ||
+ check_key_existence(S128KeyType::KeyArea, i,
+ static_cast<u64>(KeyAreaKeyType::Application)) ||
+ check_key_existence(S128KeyType::KeyArea, i, static_cast<u64>(KeyAreaKeyType::Ocean)) ||
+ check_key_existence(S128KeyType::KeyArea, i,
+ static_cast<u64>(KeyAreaKeyType::System)) ||
+ check_key_existence(S128KeyType::Titlekek, i))
+ return true;
+ }
+
+ return false;
+}
+
bool KeyManager::HasKey(S128KeyType id, u64 field1, u64 field2) const {
return s128_keys.find({id, field1, field2}) != s128_keys.end();
}
@@ -207,13 +535,30 @@ Key256 KeyManager::GetKey(S256KeyType id, u64 field1, u64 field2) const {
return s256_keys.at({id, field1, field2});
}
-template <std::size_t Size>
-void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
+Key256 KeyManager::GetBISKey(u8 partition_id) const {
+ Key256 out{};
+
+ for (const auto& bis_type : {BISKeyType::Crypto, BISKeyType::Tweak}) {
+ if (HasKey(S128KeyType::BIS, partition_id, static_cast<u64>(bis_type))) {
+ std::memcpy(
+ out.data() + sizeof(Key128) * static_cast<u64>(bis_type),
+ s128_keys.at({S128KeyType::BIS, partition_id, static_cast<u64>(bis_type)}).data(),
+ sizeof(Key128));
+ }
+ }
+
+ return out;
+}
+
+template <size_t Size>
+void KeyManager::WriteKeyToFile(KeyCategory category, std::string_view keyname,
const std::array<u8, Size>& key) {
const std::string yuzu_keys_dir = FileUtil::GetUserPath(FileUtil::UserPath::KeysDir);
std::string filename = "title.keys_autogenerated";
- if (!title_key)
+ if (category == KeyCategory::Standard)
filename = dev_mode ? "dev.keys_autogenerated" : "prod.keys_autogenerated";
+ else if (category == KeyCategory::Console)
+ filename = "console.keys_autogenerated";
const auto add_info_text = !FileUtil::Exists(yuzu_keys_dir + DIR_SEP + filename);
FileUtil::CreateFullPath(yuzu_keys_dir + DIR_SEP + filename);
std::ofstream file(yuzu_keys_dir + DIR_SEP + filename, std::ios::app);
@@ -227,7 +572,7 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
}
file << fmt::format("\n{} = {}", keyname, Common::HexArrayToString(key));
- AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, title_key);
+ AttemptLoadKeyFile(yuzu_keys_dir, yuzu_keys_dir, filename, category == KeyCategory::Title);
}
void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
@@ -237,8 +582,15 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
Key128 rights_id;
std::memcpy(rights_id.data(), &field2, sizeof(u64));
std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
- WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
+ WriteKeyToFile(KeyCategory::Title, Common::HexArrayToString(rights_id), key);
+ }
+
+ auto category = KeyCategory::Standard;
+ if (id == S128KeyType::Keyblob || id == S128KeyType::KeyblobMAC || id == S128KeyType::TSEC ||
+ id == S128KeyType::SecureBoot || id == S128KeyType::SDSeed || id == S128KeyType::BIS) {
+ category = KeyCategory::Console;
}
+
const auto iter2 = std::find_if(
s128_file_id.begin(), s128_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
@@ -246,7 +598,30 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter2 != s128_file_id.end())
- WriteKeyToFile(false, iter2->first, key);
+ WriteKeyToFile(category, iter2->first, key);
+
+ // Variable cases
+ if (id == S128KeyType::KeyArea) {
+ static constexpr std::array<const char*, 3> kak_names = {"key_area_key_application_{:02X}",
+ "key_area_key_ocean_{:02X}",
+ "key_area_key_system_{:02X}"};
+ WriteKeyToFile(category, fmt::format(kak_names.at(field2), field1), key);
+ } else if (id == S128KeyType::Master) {
+ WriteKeyToFile(category, fmt::format("master_key_{:02X}", field1), key);
+ } else if (id == S128KeyType::Package1) {
+ WriteKeyToFile(category, fmt::format("package1_key_{:02X}", field1), key);
+ } else if (id == S128KeyType::Package2) {
+ WriteKeyToFile(category, fmt::format("package2_key_{:02X}", field1), key);
+ } else if (id == S128KeyType::Titlekek) {
+ WriteKeyToFile(category, fmt::format("titlekek_{:02X}", field1), key);
+ } else if (id == S128KeyType::Keyblob) {
+ WriteKeyToFile(category, fmt::format("keyblob_key_{:02X}", field1), key);
+ } else if (id == S128KeyType::KeyblobMAC) {
+ WriteKeyToFile(category, fmt::format("keyblob_mac_key_{:02X}", field1), key);
+ } else if (id == S128KeyType::Source && field1 == static_cast<u64>(SourceKeyType::Keyblob)) {
+ WriteKeyToFile(category, fmt::format("keyblob_key_source_{:02X}", field2), key);
+ }
+
s128_keys[{id, field1, field2}] = key;
}
@@ -260,7 +635,7 @@ void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
std::tie(id, field1, field2);
});
if (iter != s256_file_id.end())
- WriteKeyToFile(false, iter->first, key);
+ WriteKeyToFile(KeyCategory::Standard, iter->first, key);
s256_keys[{id, field1, field2}] = key;
}
@@ -286,63 +661,391 @@ void KeyManager::DeriveSDSeedLazy() {
return;
const auto res = DeriveSDSeed();
- if (res != boost::none)
- SetKey(S128KeyType::SDSeed, res.get());
+ if (res)
+ SetKey(S128KeyType::SDSeed, *res);
+}
+
+static Key128 CalculateCMAC(const u8* source, size_t size, const Key128& key) {
+ Key128 out{};
+
+ mbedtls_cipher_cmac(mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB), key.data(),
+ key.size() * 8, source, size, out.data());
+ return out;
+}
+
+void KeyManager::DeriveBase() {
+ if (!BaseDeriveNecessary())
+ return;
+
+ if (!HasKey(S128KeyType::SecureBoot) || !HasKey(S128KeyType::TSEC))
+ return;
+
+ const auto has_bis = [this](u64 id) {
+ return HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Crypto)) &&
+ HasKey(S128KeyType::BIS, id, static_cast<u64>(BISKeyType::Tweak));
+ };
+
+ const auto copy_bis = [this](u64 id_from, u64 id_to) {
+ SetKey(S128KeyType::BIS,
+ GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Crypto)), id_to,
+ static_cast<u64>(BISKeyType::Crypto));
+
+ SetKey(S128KeyType::BIS,
+ GetKey(S128KeyType::BIS, id_from, static_cast<u64>(BISKeyType::Tweak)), id_to,
+ static_cast<u64>(BISKeyType::Tweak));
+ };
+
+ if (has_bis(2) && !has_bis(3))
+ copy_bis(2, 3);
+ else if (has_bis(3) && !has_bis(2))
+ copy_bis(3, 2);
+
+ std::bitset<32> revisions(0xFFFFFFFF);
+ for (size_t i = 0; i < revisions.size(); ++i) {
+ if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i) ||
+ encrypted_keyblobs[i] == std::array<u8, 0xB0>{}) {
+ revisions.reset(i);
+ }
+ }
+
+ if (!revisions.any())
+ return;
+
+ const auto sbk = GetKey(S128KeyType::SecureBoot);
+ const auto tsec = GetKey(S128KeyType::TSEC);
+
+ for (size_t i = 0; i < revisions.size(); ++i) {
+ if (!revisions[i])
+ continue;
+
+ // Derive keyblob key
+ const auto key = DeriveKeyblobKey(
+ sbk, tsec, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Keyblob), i));
+
+ SetKey(S128KeyType::Keyblob, key, i);
+
+ // Derive keyblob MAC key
+ if (!HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)))
+ continue;
+
+ const auto mac_key = DeriveKeyblobMACKey(
+ key, GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)));
+ SetKey(S128KeyType::KeyblobMAC, mac_key, i);
+
+ Key128 cmac = CalculateCMAC(encrypted_keyblobs[i].data() + 0x10, 0xA0, mac_key);
+ if (std::memcmp(cmac.data(), encrypted_keyblobs[i].data(), cmac.size()) != 0)
+ continue;
+
+ // Decrypt keyblob
+ if (keyblobs[i] == std::array<u8, 0x90>{}) {
+ keyblobs[i] = DecryptKeyblob(encrypted_keyblobs[i], key);
+ WriteKeyToFile<0x90>(KeyCategory::Console, fmt::format("keyblob_{:02X}", i),
+ keyblobs[i]);
+ }
+
+ Key128 package1;
+ std::memcpy(package1.data(), keyblobs[i].data() + 0x80, sizeof(Key128));
+ SetKey(S128KeyType::Package1, package1, i);
+
+ // Derive master key
+ if (HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master))) {
+ SetKey(S128KeyType::Master,
+ DeriveMasterKey(keyblobs[i], GetKey(S128KeyType::Source,
+ static_cast<u64>(SourceKeyType::Master))),
+ i);
+ }
+ }
+
+ revisions.set();
+ for (size_t i = 0; i < revisions.size(); ++i) {
+ if (!HasKey(S128KeyType::Master, i))
+ revisions.reset(i);
+ }
+
+ if (!revisions.any())
+ return;
+
+ for (size_t i = 0; i < revisions.size(); ++i) {
+ if (!revisions[i])
+ continue;
+
+ // Derive general purpose keys
+ DeriveGeneralPurposeKeys(i);
+ }
+
+ if (HasKey(S128KeyType::Master, 0) &&
+ HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)) &&
+ HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)) &&
+ HasKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)) &&
+ HasKey(S256KeyType::HeaderSource)) {
+ const auto header_kek = GenerateKeyEncryptionKey(
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek)),
+ GetKey(S128KeyType::Master, 0),
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration)),
+ GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration)));
+ SetKey(S128KeyType::HeaderKek, header_kek);
+
+ AESCipher<Key128> header_cipher(header_kek, Mode::ECB);
+ Key256 out = GetKey(S256KeyType::HeaderSource);
+ header_cipher.Transcode(out.data(), out.size(), out.data(), Op::Decrypt);
+ SetKey(S256KeyType::Header, out);
+ }
+}
+
+void KeyManager::DeriveETicket(PartitionDataManager& data) {
+ // ETicket keys
+ const auto es = Service::FileSystem::GetUnionContents()->GetEntry(
+ 0x0100000000000033, FileSys::ContentRecordType::Program);
+
+ if (es == nullptr)
+ return;
+
+ const auto exefs = es->GetExeFS();
+ if (exefs == nullptr)
+ return;
+
+ const auto main = exefs->GetFile("main");
+ if (main == nullptr)
+ return;
+
+ const auto bytes = main->ReadAllBytes();
+
+ const auto eticket_kek = FindKeyFromHex16(bytes, eticket_source_hashes[0]);
+ const auto eticket_kekek = FindKeyFromHex16(bytes, eticket_source_hashes[1]);
+
+ const auto seed3 = data.GetRSAKekSeed3();
+ const auto mask0 = data.GetRSAKekMask0();
+
+ if (eticket_kek != Key128{})
+ SetKey(S128KeyType::Source, eticket_kek, static_cast<size_t>(SourceKeyType::ETicketKek));
+ if (eticket_kekek != Key128{}) {
+ SetKey(S128KeyType::Source, eticket_kekek,
+ static_cast<size_t>(SourceKeyType::ETicketKekek));
+ }
+ if (seed3 != Key128{})
+ SetKey(S128KeyType::RSAKek, seed3, static_cast<size_t>(RSAKekType::Seed3));
+ if (mask0 != Key128{})
+ SetKey(S128KeyType::RSAKek, mask0, static_cast<size_t>(RSAKekType::Mask0));
+ if (eticket_kek == Key128{} || eticket_kekek == Key128{} || seed3 == Key128{} ||
+ mask0 == Key128{}) {
+ return;
+ }
+
+ Key128 rsa_oaep_kek{};
+ std::transform(seed3.begin(), seed3.end(), mask0.begin(), rsa_oaep_kek.begin(),
+ std::bit_xor<>());
+
+ if (rsa_oaep_kek == Key128{})
+ return;
+
+ SetKey(S128KeyType::Source, rsa_oaep_kek,
+ static_cast<u64>(SourceKeyType::RSAOaepKekGeneration));
+
+ Key128 temp_kek{};
+ Key128 temp_kekek{};
+ Key128 eticket_final{};
+
+ // Derive ETicket RSA Kek
+ AESCipher<Key128> es_master(GetKey(S128KeyType::Master), Mode::ECB);
+ es_master.Transcode(rsa_oaep_kek.data(), rsa_oaep_kek.size(), temp_kek.data(), Op::Decrypt);
+ AESCipher<Key128> es_kekek(temp_kek, Mode::ECB);
+ es_kekek.Transcode(eticket_kekek.data(), eticket_kekek.size(), temp_kekek.data(), Op::Decrypt);
+ AESCipher<Key128> es_kek(temp_kekek, Mode::ECB);
+ es_kek.Transcode(eticket_kek.data(), eticket_kek.size(), eticket_final.data(), Op::Decrypt);
+
+ if (eticket_final == Key128{})
+ return;
+
+ SetKey(S128KeyType::ETicketRSAKek, eticket_final);
+
+ // Titlekeys
+ data.DecryptProdInfo(GetBISKey(0));
+
+ const auto eticket_extended_kek = data.GetETicketExtendedKek();
+
+ std::vector<u8> extended_iv(0x10);
+ std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size());
+ std::array<u8, 0x230> extended_dec{};
+ AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
+ rsa_1.SetIV(extended_iv);
+ rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
+ extended_dec.data(), Op::Decrypt);
+
+ RSAKeyPair<2048> rsa_key{};
+ std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
+ std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
+ std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
+
+ const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/80000000000000e1",
+ "rb+");
+ const FileUtil::IOFile save2(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/80000000000000e2",
+ "rb+");
+
+ const auto blob2 = GetTicketblob(save2);
+ auto res = GetTicketblob(save1);
+ res.insert(res.end(), blob2.begin(), blob2.end());
+
+ for (const auto& raw : res) {
+ const auto pair = ParseTicket(raw, rsa_key);
+ if (!pair)
+ continue;
+ const auto& [rid, key] = *pair;
+ u128 rights_id;
+ std::memcpy(rights_id.data(), rid.data(), rid.size());
+ SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+ }
+}
+
+void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
+ if (key == Key128{})
+ return;
+ SetKey(id, key, field1, field2);
+}
+
+void KeyManager::SetKeyWrapped(S256KeyType id, Key256 key, u64 field1, u64 field2) {
+ if (key == Key256{})
+ return;
+ SetKey(id, key, field1, field2);
+}
+
+void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
+ if (!BaseDeriveNecessary())
+ return;
+
+ if (!data.HasBoot0())
+ return;
+
+ for (size_t i = 0; i < encrypted_keyblobs.size(); ++i) {
+ if (encrypted_keyblobs[i] != std::array<u8, 0xB0>{})
+ continue;
+ encrypted_keyblobs[i] = data.GetEncryptedKeyblob(i);
+ WriteKeyToFile<0xB0>(KeyCategory::Console, fmt::format("encrypted_keyblob_{:02X}", i),
+ encrypted_keyblobs[i]);
+ }
+
+ SetKeyWrapped(S128KeyType::Source, data.GetPackage2KeySource(),
+ static_cast<u64>(SourceKeyType::Package2));
+ SetKeyWrapped(S128KeyType::Source, data.GetAESKekGenerationSource(),
+ static_cast<u64>(SourceKeyType::AESKekGeneration));
+ SetKeyWrapped(S128KeyType::Source, data.GetTitlekekSource(),
+ static_cast<u64>(SourceKeyType::Titlekek));
+ SetKeyWrapped(S128KeyType::Source, data.GetMasterKeySource(),
+ static_cast<u64>(SourceKeyType::Master));
+ SetKeyWrapped(S128KeyType::Source, data.GetKeyblobMACKeySource(),
+ static_cast<u64>(SourceKeyType::KeyblobMAC));
+
+ for (size_t i = 0; i < PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH; ++i) {
+ SetKeyWrapped(S128KeyType::Source, data.GetKeyblobKeySource(i),
+ static_cast<u64>(SourceKeyType::Keyblob), i);
+ }
+
+ if (data.HasFuses())
+ SetKeyWrapped(S128KeyType::SecureBoot, data.GetSecureBootKey());
+
+ DeriveBase();
+
+ Key128 latest_master{};
+ for (s8 i = 0x1F; i >= 0; --i) {
+ if (GetKey(S128KeyType::Master, static_cast<u8>(i)) != Key128{}) {
+ latest_master = GetKey(S128KeyType::Master, static_cast<u8>(i));
+ break;
+ }
+ }
+
+ const auto masters = data.GetTZMasterKeys(latest_master);
+ for (size_t i = 0; i < masters.size(); ++i) {
+ if (masters[i] != Key128{} && !HasKey(S128KeyType::Master, i))
+ SetKey(S128KeyType::Master, masters[i], i);
+ }
+
+ DeriveBase();
+
+ if (!data.HasPackage2())
+ return;
+
+ std::array<Key128, 0x20> package2_keys{};
+ for (size_t i = 0; i < package2_keys.size(); ++i) {
+ if (HasKey(S128KeyType::Package2, i))
+ package2_keys[i] = GetKey(S128KeyType::Package2, i);
+ }
+ data.DecryptPackage2(package2_keys, Package2Type::NormalMain);
+
+ SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyApplicationSource(),
+ static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Application));
+ SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeyOceanSource(),
+ static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Ocean));
+ SetKeyWrapped(S128KeyType::Source, data.GetKeyAreaKeySystemSource(),
+ static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::System));
+ SetKeyWrapped(S128KeyType::Source, data.GetSDKekSource(),
+ static_cast<u64>(SourceKeyType::SDKek));
+ SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDSaveKeySource(),
+ static_cast<u64>(SDKeyType::Save));
+ SetKeyWrapped(S256KeyType::SDKeySource, data.GetSDNCAKeySource(),
+ static_cast<u64>(SDKeyType::NCA));
+ SetKeyWrapped(S128KeyType::Source, data.GetHeaderKekSource(),
+ static_cast<u64>(SourceKeyType::HeaderKek));
+ SetKeyWrapped(S256KeyType::HeaderSource, data.GetHeaderKeySource());
+ SetKeyWrapped(S128KeyType::Source, data.GetAESKeyGenerationSource(),
+ static_cast<u64>(SourceKeyType::AESKeyGeneration));
+
+ DeriveBase();
}
const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
- {"master_key_00", {S128KeyType::Master, 0, 0}},
- {"master_key_01", {S128KeyType::Master, 1, 0}},
- {"master_key_02", {S128KeyType::Master, 2, 0}},
- {"master_key_03", {S128KeyType::Master, 3, 0}},
- {"master_key_04", {S128KeyType::Master, 4, 0}},
- {"package1_key_00", {S128KeyType::Package1, 0, 0}},
- {"package1_key_01", {S128KeyType::Package1, 1, 0}},
- {"package1_key_02", {S128KeyType::Package1, 2, 0}},
- {"package1_key_03", {S128KeyType::Package1, 3, 0}},
- {"package1_key_04", {S128KeyType::Package1, 4, 0}},
- {"package2_key_00", {S128KeyType::Package2, 0, 0}},
- {"package2_key_01", {S128KeyType::Package2, 1, 0}},
- {"package2_key_02", {S128KeyType::Package2, 2, 0}},
- {"package2_key_03", {S128KeyType::Package2, 3, 0}},
- {"package2_key_04", {S128KeyType::Package2, 4, 0}},
- {"titlekek_00", {S128KeyType::Titlekek, 0, 0}},
- {"titlekek_01", {S128KeyType::Titlekek, 1, 0}},
- {"titlekek_02", {S128KeyType::Titlekek, 2, 0}},
- {"titlekek_03", {S128KeyType::Titlekek, 3, 0}},
- {"titlekek_04", {S128KeyType::Titlekek, 4, 0}},
{"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
- {"key_area_key_application_00",
- {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_application_01",
- {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_application_02",
- {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_application_03",
- {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_application_04",
- {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Application)}},
- {"key_area_key_ocean_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_ocean_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_ocean_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_ocean_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_ocean_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::Ocean)}},
- {"key_area_key_system_00", {S128KeyType::KeyArea, 0, static_cast<u64>(KeyAreaKeyType::System)}},
- {"key_area_key_system_01", {S128KeyType::KeyArea, 1, static_cast<u64>(KeyAreaKeyType::System)}},
- {"key_area_key_system_02", {S128KeyType::KeyArea, 2, static_cast<u64>(KeyAreaKeyType::System)}},
- {"key_area_key_system_03", {S128KeyType::KeyArea, 3, static_cast<u64>(KeyAreaKeyType::System)}},
- {"key_area_key_system_04", {S128KeyType::KeyArea, 4, static_cast<u64>(KeyAreaKeyType::System)}},
- {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKEK), 0}},
+ {"eticket_rsa_kek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKek), 0}},
+ {"eticket_rsa_kekek_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::ETicketKekek), 0}},
+ {"rsa_kek_mask_0", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Mask0), 0}},
+ {"rsa_kek_seed_3", {S128KeyType::RSAKek, static_cast<u64>(RSAKekType::Seed3), 0}},
+ {"rsa_oaep_kek_generation_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::RSAOaepKekGeneration), 0}},
+ {"sd_card_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::SDKek), 0}},
{"aes_kek_generation_source",
- {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKEKGeneration), 0}},
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKekGeneration), 0}},
{"aes_key_generation_source",
{S128KeyType::Source, static_cast<u64>(SourceKeyType::AESKeyGeneration), 0}},
+ {"package2_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Package2), 0}},
+ {"master_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Master), 0}},
+ {"header_kek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::HeaderKek), 0}},
+ {"key_area_key_application_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Application)}},
+ {"key_area_key_ocean_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::Ocean)}},
+ {"key_area_key_system_source",
+ {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyAreaKey),
+ static_cast<u64>(KeyAreaKeyType::System)}},
+ {"titlekek_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::Titlekek), 0}},
+ {"keyblob_mac_key_source", {S128KeyType::Source, static_cast<u64>(SourceKeyType::KeyblobMAC)}},
+ {"tsec_key", {S128KeyType::TSEC, 0, 0}},
+ {"secure_boot_key", {S128KeyType::SecureBoot, 0, 0}},
{"sd_seed", {S128KeyType::SDSeed, 0, 0}},
+ {"bis_key_0_crypt", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_0_tweak", {S128KeyType::BIS, 0, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_1_crypt", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_1_tweak", {S128KeyType::BIS, 1, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_2_crypt", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_2_tweak", {S128KeyType::BIS, 2, static_cast<u64>(BISKeyType::Tweak)}},
+ {"bis_key_3_crypt", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Crypto)}},
+ {"bis_key_3_tweak", {S128KeyType::BIS, 3, static_cast<u64>(BISKeyType::Tweak)}},
+ {"header_kek", {S128KeyType::HeaderKek, 0, 0}},
+ {"sd_card_kek", {S128KeyType::SDKek, 0, 0}},
};
const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> KeyManager::s256_file_id = {
{"header_key", {S256KeyType::Header, 0, 0}},
{"sd_card_save_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::Save), 0}},
{"sd_card_nca_key_source", {S256KeyType::SDKeySource, static_cast<u64>(SDKeyType::NCA), 0}},
+ {"header_key_source", {S256KeyType::HeaderSource, 0, 0}},
+ {"sd_card_save_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::Save), 0}},
+ {"sd_card_nca_key", {S256KeyType::SDKey, static_cast<u64>(SDKeyType::NCA), 0}},
};
} // namespace Core::Crypto
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 978eec8dc..22f268c65 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -5,11 +5,19 @@
#pragma once
#include <array>
+#include <map>
+#include <optional>
#include <string>
+
#include <boost/container/flat_map.hpp>
-#include <boost/optional.hpp>
#include <fmt/format.h>
#include "common/common_types.h"
+#include "core/crypto/partition_data_manager.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace FileUtil {
+class IOFile;
+}
namespace Loader {
enum class ResultStatus : u16;
@@ -22,13 +30,30 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>;
+using TicketRaw = std::array<u8, 0x400>;
static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
-static_assert(sizeof(Key256) == 32, "Key128 must be 128 bytes big.");
+static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
+
+template <size_t bit_size, size_t byte_size = (bit_size >> 3)>
+struct RSAKeyPair {
+ std::array<u8, byte_size> encryption_key;
+ std::array<u8, byte_size> decryption_key;
+ std::array<u8, byte_size> modulus;
+ std::array<u8, 4> exponent;
+};
+
+enum class KeyCategory : u8 {
+ Standard,
+ Title,
+ Console,
+};
enum class S256KeyType : u64 {
- Header, //
- SDKeySource, // f1=SDKeyType
+ SDKey, // f1=SDKeyType
+ Header, //
+ SDKeySource, // f1=SDKeyType
+ HeaderSource, //
};
enum class S128KeyType : u64 {
@@ -41,6 +66,14 @@ enum class S128KeyType : u64 {
SDSeed, //
Titlekey, // f1=rights id LSB f2=rights id MSB
Source, // f1=source type, f2= sub id
+ Keyblob, // f1=crypto revision
+ KeyblobMAC, // f1=crypto revision
+ TSEC, //
+ SecureBoot, //
+ BIS, // f1=partition (0-3), f2=type {crypt, tweak}
+ HeaderKek, //
+ SDKek, //
+ RSAKek, //
};
enum class KeyAreaKeyType : u8 {
@@ -50,9 +83,19 @@ enum class KeyAreaKeyType : u8 {
};
enum class SourceKeyType : u8 {
- SDKEK,
- AESKEKGeneration,
- AESKeyGeneration,
+ SDKek, //
+ AESKekGeneration, //
+ AESKeyGeneration, //
+ RSAOaepKekGeneration, //
+ Master, //
+ Keyblob, // f2=crypto revision
+ KeyAreaKey, // f2=KeyAreaKeyType
+ Titlekek, //
+ Package2, //
+ HeaderKek, //
+ KeyblobMAC, //
+ ETicketKek, //
+ ETicketKekek, //
};
enum class SDKeyType : u8 {
@@ -60,6 +103,16 @@ enum class SDKeyType : u8 {
NCA,
};
+enum class BISKeyType : u8 {
+ Crypto,
+ Tweak,
+};
+
+enum class RSAKekType : u8 {
+ Mask0,
+ Seed3,
+};
+
template <typename KeyType>
struct KeyIndex {
KeyType type;
@@ -91,6 +144,8 @@ public:
Key128 GetKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
Key256 GetKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
+ Key256 GetBISKey(u8 partition_id) const;
+
void SetKey(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
void SetKey(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
@@ -100,23 +155,51 @@ public:
// 8*43 and the private file to exist.
void DeriveSDSeedLazy();
+ bool BaseDeriveNecessary() const;
+ void DeriveBase();
+ void DeriveETicket(PartitionDataManager& data);
+
+ void PopulateFromPartitionData(PartitionDataManager& data);
+
private:
- boost::container::flat_map<KeyIndex<S128KeyType>, Key128> s128_keys;
- boost::container::flat_map<KeyIndex<S256KeyType>, Key256> s256_keys;
+ std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
+ std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
+
+ std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
+ std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
bool dev_mode;
void LoadFromFile(const std::string& filename, bool is_title_keys);
void AttemptLoadKeyFile(const std::string& dir1, const std::string& dir2,
const std::string& filename, bool title);
- template <std::size_t Size>
- void WriteKeyToFile(bool title_key, std::string_view keyname, const std::array<u8, Size>& key);
+ template <size_t Size>
+ void WriteKeyToFile(KeyCategory category, std::string_view keyname,
+ const std::array<u8, Size>& key);
+
+ void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
+
+ void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
+ void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
static const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> s128_file_id;
static const boost::container::flat_map<std::string, KeyIndex<S256KeyType>> s256_file_id;
};
Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed);
-boost::optional<Key128> DeriveSDSeed();
-Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, const KeyManager& keys);
+Key128 DeriveKeyblobKey(const Key128& sbk, const Key128& tsec, Key128 source);
+Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source);
+Key128 DeriveMasterKey(const std::array<u8, 0x90>& keyblob, const Key128& master_source);
+std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblob,
+ const Key128& key);
+
+std::optional<Key128> DeriveSDSeed();
+Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
+
+std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save);
+
+// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset
+// 0x140-0x144 is zero)
+std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
+ const RSAKeyPair<2048>& eticket_extended_key);
} // namespace Core::Crypto
diff --git a/src/core/crypto/partition_data_manager.cpp b/src/core/crypto/partition_data_manager.cpp
new file mode 100644
index 000000000..ed0775444
--- /dev/null
+++ b/src/core/crypto/partition_data_manager.cpp
@@ -0,0 +1,594 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+// NOTE TO FUTURE MAINTAINERS:
+// When a new version of switch cryptography is released,
+// hash the new keyblob source and master key and add the hashes to
+// the arrays below.
+
+#include <algorithm>
+#include <array>
+#include <cctype>
+#include <cstring>
+#include <mbedtls/sha256.h>
+#include "common/assert.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/hex_util.h"
+#include "common/logging/log.h"
+#include "common/string_util.h"
+#include "common/swap.h"
+#include "core/crypto/key_manager.h"
+#include "core/crypto/partition_data_manager.h"
+#include "core/crypto/xts_encryption_layer.h"
+#include "core/file_sys/vfs.h"
+#include "core/file_sys/vfs_offset.h"
+
+using namespace Common;
+
+namespace Core::Crypto {
+
+struct Package2Header {
+ std::array<u8, 0x100> signature;
+ Key128 header_ctr;
+ std::array<Key128, 4> section_ctr;
+ u32_le magic;
+ u32_le base_offset;
+ INSERT_PADDING_BYTES(4);
+ u8 version_max;
+ u8 version_min;
+ INSERT_PADDING_BYTES(2);
+ std::array<u32_le, 4> section_size;
+ std::array<u32_le, 4> section_offset;
+ std::array<SHA256Hash, 4> section_hash;
+};
+static_assert(sizeof(Package2Header) == 0x200, "Package2Header has incorrect size.");
+
+struct INIHeader {
+ u32_le magic;
+ u32_le size;
+ u32_le process_count;
+ INSERT_PADDING_BYTES(4);
+};
+static_assert(sizeof(INIHeader) == 0x10, "INIHeader has incorrect size.");
+
+struct SectionHeader {
+ u32_le offset;
+ u32_le size_decompressed;
+ u32_le size_compressed;
+ u32_le attribute;
+};
+static_assert(sizeof(SectionHeader) == 0x10, "SectionHeader has incorrect size.");
+
+struct KIPHeader {
+ u32_le magic;
+ std::array<char, 12> name;
+ u64_le title_id;
+ u32_le category;
+ u8 priority;
+ u8 core;
+ INSERT_PADDING_BYTES(1);
+ u8 flags;
+ std::array<SectionHeader, 6> sections;
+ std::array<u32, 0x20> capabilities;
+};
+static_assert(sizeof(KIPHeader) == 0x100, "KIPHeader has incorrect size.");
+
+const std::array<SHA256Hash, 0x10> source_hashes{
+ "B24BD293259DBC7AC5D63F88E60C59792498E6FC5443402C7FFE87EE8B61A3F0"_array32, // keyblob_mac_key_source
+ "7944862A3A5C31C6720595EFD302245ABD1B54CCDCF33000557681E65C5664A4"_array32, // master_key_source
+ "21E2DF100FC9E094DB51B47B9B1D6E94ED379DB8B547955BEF8FE08D8DD35603"_array32, // package2_key_source
+ "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // aes_kek_generation_source
+ "FBD10056999EDC7ACDB96098E47E2C3606230270D23281E671F0F389FC5BC585"_array32, // aes_key_generation_source
+ "C48B619827986C7F4E3081D59DB2B460C84312650E9A8E6B458E53E8CBCA4E87"_array32, // titlekek_source
+ "04AD66143C726B2A139FB6B21128B46F56C553B2B3887110304298D8D0092D9E"_array32, // key_area_key_application_source
+ "FD434000C8FF2B26F8E9A9D2D2C12F6BE5773CBB9DC86300E1BD99F8EA33A417"_array32, // key_area_key_ocean_source
+ "1F17B1FD51AD1C2379B58F152CA4912EC2106441E51722F38700D5937A1162F7"_array32, // key_area_key_system_source
+ "6B2ED877C2C52334AC51E59ABFA7EC457F4A7D01E46291E9F2EAA45F011D24B7"_array32, // sd_card_kek_source
+ "D482743563D3EA5DCDC3B74E97C9AC8A342164FA041A1DC80F17F6D31E4BC01C"_array32, // sd_card_save_key_source
+ "2E751CECF7D93A2B957BD5FFCB082FD038CC2853219DD3092C6DAB9838F5A7CC"_array32, // sd_card_nca_key_source
+ "1888CAED5551B3EDE01499E87CE0D86827F80820EFB275921055AA4E2ABDFFC2"_array32, // header_kek_source
+ "8F783E46852DF6BE0BA4E19273C4ADBAEE16380043E1B8C418C4089A8BD64AA6"_array32, // header_key_source
+ "D1757E52F1AE55FA882EC690BC6F954AC46A83DC22F277F8806BD55577C6EED7"_array32, // rsa_kek_seed3
+ "FC02B9D37B42D7A1452E71444F1F700311D1132E301A83B16062E72A78175085"_array32, // rsa_kek_mask0
+};
+
+const std::array<SHA256Hash, 0x20> keyblob_source_hashes{
+ "8A06FE274AC491436791FDB388BCDD3AB9943BD4DEF8094418CDAC150FD73786"_array32, // keyblob_key_source_00
+ "2D5CAEB2521FEF70B47E17D6D0F11F8CE2C1E442A979AD8035832C4E9FBCCC4B"_array32, // keyblob_key_source_01
+ "61C5005E713BAE780641683AF43E5F5C0E03671117F702F401282847D2FC6064"_array32, // keyblob_key_source_02
+ "8E9795928E1C4428E1B78F0BE724D7294D6934689C11B190943923B9D5B85903"_array32, // keyblob_key_source_03
+ "95FA33AF95AFF9D9B61D164655B32710ED8D615D46C7D6CC3CC70481B686B402"_array32, // keyblob_key_source_04
+ "3F5BE7B3C8B1ABD8C10B4B703D44766BA08730562C172A4FE0D6B866B3E2DB3E"_array32, // keyblob_key_source_05
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_06
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_07
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_08
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_09
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0A
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0B
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0C
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0D
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0E
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_0F
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_10
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_11
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_12
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_13
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_14
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_15
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_16
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_17
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_18
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_19
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1A
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1B
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1C
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1D
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1E
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // keyblob_key_source_1F
+};
+
+const std::array<SHA256Hash, 0x20> master_key_hashes{
+ "0EE359BE3C864BB0782E1D70A718A0342C551EED28C369754F9C4F691BECF7CA"_array32, // master_key_00
+ "4FE707B7E4ABDAF727C894AAF13B1351BFE2AC90D875F73B2E20FA94B9CC661E"_array32, // master_key_01
+ "79277C0237A2252EC3DFAC1F7C359C2B3D121E9DB15BB9AB4C2B4408D2F3AE09"_array32, // master_key_02
+ "4F36C565D13325F65EE134073C6A578FFCB0008E02D69400836844EAB7432754"_array32, // master_key_03
+ "75FF1D95D26113550EE6FCC20ACB58E97EDEB3A2FF52543ED5AEC63BDCC3DA50"_array32, // master_key_04
+ "EBE2BCD6704673EC0F88A187BB2AD9F1CC82B718C389425941BDC194DC46B0DD"_array32, // master_key_05
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_06
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_07
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_08
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_09
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0A
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0B
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0C
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0D
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0E
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_0F
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_10
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_11
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_12
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_13
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_14
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_15
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_16
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_17
+
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_18
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_19
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1A
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1B
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1C
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1D
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1E
+ "0000000000000000000000000000000000000000000000000000000000000000"_array32, // master_key_1F
+};
+
+static std::vector<u8> DecompressBLZ(const std::vector<u8>& in) {
+ const auto data_size = in.size() - 0xC;
+
+ u32 compressed_size{};
+ u32 init_index{};
+ u32 additional_size{};
+ std::memcpy(&compressed_size, in.data() + data_size, sizeof(u32));
+ std::memcpy(&init_index, in.data() + data_size + 0x4, sizeof(u32));
+ std::memcpy(&additional_size, in.data() + data_size + 0x8, sizeof(u32));
+
+ std::vector<u8> out(in.size() + additional_size);
+
+ if (compressed_size == in.size())
+ std::memcpy(out.data(), in.data() + in.size() - compressed_size, compressed_size);
+ else
+ std::memcpy(out.data(), in.data(), compressed_size);
+
+ auto index = in.size() - init_index;
+ auto out_index = out.size();
+
+ while (out_index > 0) {
+ --index;
+ auto control = in[index];
+ for (size_t i = 0; i < 8; ++i) {
+ if ((control & 0x80) > 0) {
+ ASSERT(index >= 2);
+ index -= 2;
+ u64 segment_offset = in[index] | in[index + 1] << 8;
+ u64 segment_size = ((segment_offset >> 12) & 0xF) + 3;
+ segment_offset &= 0xFFF;
+ segment_offset += 3;
+
+ if (out_index < segment_size)
+ segment_size = out_index;
+
+ ASSERT(out_index >= segment_size);
+
+ out_index -= segment_size;
+
+ for (size_t j = 0; j < segment_size; ++j) {
+ ASSERT(out_index + j + segment_offset < out.size());
+ out[out_index + j] = out[out_index + j + segment_offset];
+ }
+ } else {
+ ASSERT(out_index >= 1);
+ --out_index;
+ --index;
+ out[out_index] = in[index];
+ }
+
+ control <<= 1;
+ if (out_index == 0)
+ return out;
+ }
+ }
+
+ return out;
+}
+
+static u8 CalculateMaxKeyblobSourceHash() {
+ for (s8 i = 0x1F; i >= 0; --i) {
+ if (keyblob_source_hashes[i] != SHA256Hash{})
+ return static_cast<u8>(i + 1);
+ }
+
+ return 0;
+}
+
+const u8 PartitionDataManager::MAX_KEYBLOB_SOURCE_HASH = CalculateMaxKeyblobSourceHash();
+
+template <size_t key_size = 0x10>
+std::array<u8, key_size> FindKeyFromHex(const std::vector<u8>& binary,
+ const std::array<u8, 0x20>& hash) {
+ if (binary.size() < key_size)
+ return {};
+
+ std::array<u8, 0x20> temp{};
+ for (size_t i = 0; i < binary.size() - key_size; ++i) {
+ mbedtls_sha256(binary.data() + i, key_size, temp.data(), 0);
+
+ if (temp != hash)
+ continue;
+
+ std::array<u8, key_size> out{};
+ std::memcpy(out.data(), binary.data() + i, key_size);
+ return out;
+ }
+
+ return {};
+}
+
+std::array<u8, 16> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 32> hash) {
+ return FindKeyFromHex<0x10>(binary, hash);
+}
+
+static std::array<Key128, 0x20> FindEncryptedMasterKeyFromHex(const std::vector<u8>& binary,
+ const Key128& key) {
+ if (binary.size() < 0x10)
+ return {};
+
+ SHA256Hash temp{};
+ Key128 dec_temp{};
+ std::array<Key128, 0x20> out{};
+ AESCipher<Key128> cipher(key, Mode::ECB);
+ for (size_t i = 0; i < binary.size() - 0x10; ++i) {
+ cipher.Transcode(binary.data() + i, dec_temp.size(), dec_temp.data(), Op::Decrypt);
+ mbedtls_sha256(dec_temp.data(), dec_temp.size(), temp.data(), 0);
+
+ for (size_t k = 0; k < out.size(); ++k) {
+ if (temp == master_key_hashes[k]) {
+ out[k] = dec_temp;
+ break;
+ }
+ }
+ }
+
+ return out;
+}
+
+FileSys::VirtualFile FindFileInDirWithNames(const FileSys::VirtualDir& dir,
+ const std::string& name) {
+ auto upper = name;
+ std::transform(upper.begin(), upper.end(), upper.begin(), [](u8 c) { return std::toupper(c); });
+ for (const auto& fname : {name, name + ".bin", upper, upper + ".BIN"}) {
+ if (dir->GetFile(fname) != nullptr)
+ return dir->GetFile(fname);
+ }
+
+ return nullptr;
+}
+
+PartitionDataManager::PartitionDataManager(const FileSys::VirtualDir& sysdata_dir)
+ : boot0(FindFileInDirWithNames(sysdata_dir, "BOOT0")),
+ fuses(FindFileInDirWithNames(sysdata_dir, "fuses")),
+ kfuses(FindFileInDirWithNames(sysdata_dir, "kfuses")),
+ package2({
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-1-Normal-Main"),
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-2-Normal-Sub"),
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-3-SafeMode-Main"),
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-4-SafeMode-Sub"),
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-5-Repair-Main"),
+ FindFileInDirWithNames(sysdata_dir, "BCPKG2-6-Repair-Sub"),
+ }),
+ prodinfo(FindFileInDirWithNames(sysdata_dir, "PRODINFO")),
+ secure_monitor(FindFileInDirWithNames(sysdata_dir, "secmon")),
+ package1_decrypted(FindFileInDirWithNames(sysdata_dir, "pkg1_decr")),
+ secure_monitor_bytes(secure_monitor == nullptr ? std::vector<u8>{}
+ : secure_monitor->ReadAllBytes()),
+ package1_decrypted_bytes(package1_decrypted == nullptr ? std::vector<u8>{}
+ : package1_decrypted->ReadAllBytes()) {
+}
+
+PartitionDataManager::~PartitionDataManager() = default;
+
+bool PartitionDataManager::HasBoot0() const {
+ return boot0 != nullptr;
+}
+
+FileSys::VirtualFile PartitionDataManager::GetBoot0Raw() const {
+ return boot0;
+}
+
+PartitionDataManager::EncryptedKeyBlob PartitionDataManager::GetEncryptedKeyblob(
+ std::size_t index) const {
+ if (HasBoot0() && index < NUM_ENCRYPTED_KEYBLOBS)
+ return GetEncryptedKeyblobs()[index];
+ return {};
+}
+
+PartitionDataManager::EncryptedKeyBlobs PartitionDataManager::GetEncryptedKeyblobs() const {
+ if (!HasBoot0())
+ return {};
+
+ EncryptedKeyBlobs out{};
+ for (size_t i = 0; i < out.size(); ++i)
+ boot0->Read(out[i].data(), out[i].size(), 0x180000 + i * 0x200);
+ return out;
+}
+
+std::vector<u8> PartitionDataManager::GetSecureMonitor() const {
+ return secure_monitor_bytes;
+}
+
+std::array<u8, 16> PartitionDataManager::GetPackage2KeySource() const {
+ return FindKeyFromHex(secure_monitor_bytes, source_hashes[2]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetAESKekGenerationSource() const {
+ return FindKeyFromHex(secure_monitor_bytes, source_hashes[3]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetTitlekekSource() const {
+ return FindKeyFromHex(secure_monitor_bytes, source_hashes[5]);
+}
+
+std::array<std::array<u8, 16>, 32> PartitionDataManager::GetTZMasterKeys(
+ std::array<u8, 0x10> master_key) const {
+ return FindEncryptedMasterKeyFromHex(secure_monitor_bytes, master_key);
+}
+
+std::array<u8, 16> PartitionDataManager::GetRSAKekSeed3() const {
+ return FindKeyFromHex(secure_monitor_bytes, source_hashes[14]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetRSAKekMask0() const {
+ return FindKeyFromHex(secure_monitor_bytes, source_hashes[15]);
+}
+
+std::vector<u8> PartitionDataManager::GetPackage1Decrypted() const {
+ return package1_decrypted_bytes;
+}
+
+std::array<u8, 16> PartitionDataManager::GetMasterKeySource() const {
+ return FindKeyFromHex(package1_decrypted_bytes, source_hashes[1]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetKeyblobMACKeySource() const {
+ return FindKeyFromHex(package1_decrypted_bytes, source_hashes[0]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetKeyblobKeySource(std::size_t revision) const {
+ if (keyblob_source_hashes[revision] == SHA256Hash{}) {
+ LOG_WARNING(Crypto,
+ "No keyblob source hash for crypto revision {:02X}! Cannot derive keys...",
+ revision);
+ }
+ return FindKeyFromHex(package1_decrypted_bytes, keyblob_source_hashes[revision]);
+}
+
+bool PartitionDataManager::HasFuses() const {
+ return fuses != nullptr;
+}
+
+FileSys::VirtualFile PartitionDataManager::GetFusesRaw() const {
+ return fuses;
+}
+
+std::array<u8, 16> PartitionDataManager::GetSecureBootKey() const {
+ if (!HasFuses())
+ return {};
+ Key128 out{};
+ fuses->Read(out.data(), out.size(), 0xA4);
+ return out;
+}
+
+bool PartitionDataManager::HasKFuses() const {
+ return kfuses != nullptr;
+}
+
+FileSys::VirtualFile PartitionDataManager::GetKFusesRaw() const {
+ return kfuses;
+}
+
+bool PartitionDataManager::HasPackage2(Package2Type type) const {
+ return package2.at(static_cast<size_t>(type)) != nullptr;
+}
+
+FileSys::VirtualFile PartitionDataManager::GetPackage2Raw(Package2Type type) const {
+ return package2.at(static_cast<size_t>(type));
+}
+
+bool AttemptDecrypt(const std::array<u8, 16>& key, Package2Header& header) {
+
+ const std::vector<u8> iv(header.header_ctr.begin(), header.header_ctr.end());
+ Package2Header temp = header;
+ AESCipher<Key128> cipher(key, Mode::CTR);
+ cipher.SetIV(iv);
+ cipher.Transcode(&temp.header_ctr, sizeof(Package2Header) - 0x100, &temp.header_ctr,
+ Op::Decrypt);
+ if (temp.magic == Common::MakeMagic('P', 'K', '2', '1')) {
+ header = temp;
+ return true;
+ }
+
+ return false;
+}
+
+void PartitionDataManager::DecryptPackage2(const std::array<Key128, 0x20>& package2_keys,
+ Package2Type type) {
+ FileSys::VirtualFile file = std::make_shared<FileSys::OffsetVfsFile>(
+ package2[static_cast<size_t>(type)],
+ package2[static_cast<size_t>(type)]->GetSize() - 0x4000, 0x4000);
+
+ Package2Header header{};
+ if (file->ReadObject(&header) != sizeof(Package2Header))
+ return;
+
+ std::size_t revision = 0xFF;
+ if (header.magic != Common::MakeMagic('P', 'K', '2', '1')) {
+ for (std::size_t i = 0; i < package2_keys.size(); ++i) {
+ if (AttemptDecrypt(package2_keys[i], header)) {
+ revision = i;
+ }
+ }
+ }
+
+ if (header.magic != Common::MakeMagic('P', 'K', '2', '1'))
+ return;
+
+ const auto a = std::make_shared<FileSys::OffsetVfsFile>(
+ file, header.section_size[1], header.section_size[0] + sizeof(Package2Header));
+
+ auto c = a->ReadAllBytes();
+
+ AESCipher<Key128> cipher(package2_keys[revision], Mode::CTR);
+ cipher.SetIV({header.section_ctr[1].begin(), header.section_ctr[1].end()});
+ cipher.Transcode(c.data(), c.size(), c.data(), Op::Decrypt);
+
+ INIHeader ini;
+ std::memcpy(&ini, c.data(), sizeof(INIHeader));
+ if (ini.magic != Common::MakeMagic('I', 'N', 'I', '1'))
+ return;
+
+ u64 offset = sizeof(INIHeader);
+ for (size_t i = 0; i < ini.process_count; ++i) {
+ KIPHeader kip;
+ std::memcpy(&kip, c.data() + offset, sizeof(KIPHeader));
+ if (kip.magic != Common::MakeMagic('K', 'I', 'P', '1'))
+ return;
+
+ const auto name =
+ Common::StringFromFixedZeroTerminatedBuffer(kip.name.data(), kip.name.size());
+
+ if (name != "FS" && name != "spl") {
+ offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
+ kip.sections[1].size_compressed + kip.sections[2].size_compressed;
+ continue;
+ }
+
+ const u64 initial_offset = sizeof(KIPHeader) + offset;
+ const auto text_begin = c.cbegin() + initial_offset;
+ const auto text_end = text_begin + kip.sections[0].size_compressed;
+ const std::vector<u8> text = DecompressBLZ({text_begin, text_end});
+
+ const auto rodata_end = text_end + kip.sections[1].size_compressed;
+ const std::vector<u8> rodata = DecompressBLZ({text_end, rodata_end});
+
+ const auto data_end = rodata_end + kip.sections[2].size_compressed;
+ const std::vector<u8> data = DecompressBLZ({rodata_end, data_end});
+
+ std::vector<u8> out;
+ out.reserve(text.size() + rodata.size() + data.size());
+ out.insert(out.end(), text.begin(), text.end());
+ out.insert(out.end(), rodata.begin(), rodata.end());
+ out.insert(out.end(), data.begin(), data.end());
+
+ offset += sizeof(KIPHeader) + kip.sections[0].size_compressed +
+ kip.sections[1].size_compressed + kip.sections[2].size_compressed;
+
+ if (name == "FS")
+ package2_fs[static_cast<size_t>(type)] = std::move(out);
+ else if (name == "spl")
+ package2_spl[static_cast<size_t>(type)] = std::move(out);
+ }
+}
+
+const std::vector<u8>& PartitionDataManager::GetPackage2FSDecompressed(Package2Type type) const {
+ return package2_fs.at(static_cast<size_t>(type));
+}
+
+std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyApplicationSource(Package2Type type) const {
+ return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[6]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetKeyAreaKeyOceanSource(Package2Type type) const {
+ return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[7]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetKeyAreaKeySystemSource(Package2Type type) const {
+ return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[8]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetSDKekSource(Package2Type type) const {
+ return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[9]);
+}
+
+std::array<u8, 32> PartitionDataManager::GetSDSaveKeySource(Package2Type type) const {
+ return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[10]);
+}
+
+std::array<u8, 32> PartitionDataManager::GetSDNCAKeySource(Package2Type type) const {
+ return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[11]);
+}
+
+std::array<u8, 16> PartitionDataManager::GetHeaderKekSource(Package2Type type) const {
+ return FindKeyFromHex(package2_fs.at(static_cast<size_t>(type)), source_hashes[12]);
+}
+
+std::array<u8, 32> PartitionDataManager::GetHeaderKeySource(Package2Type type) const {
+ return FindKeyFromHex<0x20>(package2_fs.at(static_cast<size_t>(type)), source_hashes[13]);
+}
+
+const std::vector<u8>& PartitionDataManager::GetPackage2SPLDecompressed(Package2Type type) const {
+ return package2_spl.at(static_cast<size_t>(type));
+}
+
+std::array<u8, 16> PartitionDataManager::GetAESKeyGenerationSource(Package2Type type) const {
+ return FindKeyFromHex(package2_spl.at(static_cast<size_t>(type)), source_hashes[4]);
+}
+
+bool PartitionDataManager::HasProdInfo() const {
+ return prodinfo != nullptr;
+}
+
+FileSys::VirtualFile PartitionDataManager::GetProdInfoRaw() const {
+ return prodinfo;
+}
+
+void PartitionDataManager::DecryptProdInfo(std::array<u8, 0x20> bis_key) {
+ if (prodinfo == nullptr)
+ return;
+
+ prodinfo_decrypted = std::make_shared<XTSEncryptionLayer>(prodinfo, bis_key);
+}
+
+std::array<u8, 576> PartitionDataManager::GetETicketExtendedKek() const {
+ std::array<u8, 0x240> out{};
+ if (prodinfo_decrypted != nullptr)
+ prodinfo_decrypted->Read(out.data(), out.size(), 0x3890);
+ return out;
+}
+} // namespace Core::Crypto
diff --git a/src/core/crypto/partition_data_manager.h b/src/core/crypto/partition_data_manager.h
new file mode 100644
index 000000000..0ad007c72
--- /dev/null
+++ b/src/core/crypto/partition_data_manager.h
@@ -0,0 +1,109 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
+
+namespace Core::Crypto {
+
+enum class Package2Type {
+ NormalMain,
+ NormalSub,
+ SafeModeMain,
+ SafeModeSub,
+ RepairMain,
+ RepairSub,
+};
+
+class PartitionDataManager {
+public:
+ static const u8 MAX_KEYBLOB_SOURCE_HASH;
+ static constexpr std::size_t NUM_ENCRYPTED_KEYBLOBS = 32;
+ static constexpr std::size_t ENCRYPTED_KEYBLOB_SIZE = 0xB0;
+
+ using EncryptedKeyBlob = std::array<u8, ENCRYPTED_KEYBLOB_SIZE>;
+ using EncryptedKeyBlobs = std::array<EncryptedKeyBlob, NUM_ENCRYPTED_KEYBLOBS>;
+
+ explicit PartitionDataManager(const FileSys::VirtualDir& sysdata_dir);
+ ~PartitionDataManager();
+
+ // BOOT0
+ bool HasBoot0() const;
+ FileSys::VirtualFile GetBoot0Raw() const;
+ EncryptedKeyBlob GetEncryptedKeyblob(std::size_t index) const;
+ EncryptedKeyBlobs GetEncryptedKeyblobs() const;
+ std::vector<u8> GetSecureMonitor() const;
+ std::array<u8, 0x10> GetPackage2KeySource() const;
+ std::array<u8, 0x10> GetAESKekGenerationSource() const;
+ std::array<u8, 0x10> GetTitlekekSource() const;
+ std::array<std::array<u8, 0x10>, 0x20> GetTZMasterKeys(std::array<u8, 0x10> master_key) const;
+ std::array<u8, 0x10> GetRSAKekSeed3() const;
+ std::array<u8, 0x10> GetRSAKekMask0() const;
+ std::vector<u8> GetPackage1Decrypted() const;
+ std::array<u8, 0x10> GetMasterKeySource() const;
+ std::array<u8, 0x10> GetKeyblobMACKeySource() const;
+ std::array<u8, 0x10> GetKeyblobKeySource(std::size_t revision) const;
+
+ // Fuses
+ bool HasFuses() const;
+ FileSys::VirtualFile GetFusesRaw() const;
+ std::array<u8, 0x10> GetSecureBootKey() const;
+
+ // K-Fuses
+ bool HasKFuses() const;
+ FileSys::VirtualFile GetKFusesRaw() const;
+
+ // Package2
+ bool HasPackage2(Package2Type type = Package2Type::NormalMain) const;
+ FileSys::VirtualFile GetPackage2Raw(Package2Type type = Package2Type::NormalMain) const;
+ void DecryptPackage2(const std::array<std::array<u8, 16>, 0x20>& package2_keys,
+ Package2Type type);
+ const std::vector<u8>& GetPackage2FSDecompressed(
+ Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetKeyAreaKeyApplicationSource(
+ Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetKeyAreaKeyOceanSource(
+ Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetKeyAreaKeySystemSource(
+ Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetSDKekSource(Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x20> GetSDSaveKeySource(Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x20> GetSDNCAKeySource(Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetHeaderKekSource(Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x20> GetHeaderKeySource(Package2Type type = Package2Type::NormalMain) const;
+ const std::vector<u8>& GetPackage2SPLDecompressed(
+ Package2Type type = Package2Type::NormalMain) const;
+ std::array<u8, 0x10> GetAESKeyGenerationSource(
+ Package2Type type = Package2Type::NormalMain) const;
+
+ // PRODINFO
+ bool HasProdInfo() const;
+ FileSys::VirtualFile GetProdInfoRaw() const;
+ void DecryptProdInfo(std::array<u8, 0x20> bis_key);
+ std::array<u8, 0x240> GetETicketExtendedKek() const;
+
+private:
+ FileSys::VirtualFile boot0;
+ FileSys::VirtualFile fuses;
+ FileSys::VirtualFile kfuses;
+ std::array<FileSys::VirtualFile, 6> package2;
+ FileSys::VirtualFile prodinfo;
+ FileSys::VirtualFile secure_monitor;
+ FileSys::VirtualFile package1_decrypted;
+
+ // Processed
+ std::array<FileSys::VirtualFile, 6> package2_decrypted;
+ FileSys::VirtualFile prodinfo_decrypted;
+ std::vector<u8> secure_monitor_bytes;
+ std::vector<u8> package1_decrypted_bytes;
+ std::array<std::vector<u8>, 6> package2_fs;
+ std::array<std::vector<u8>, 6> package2_spl;
+};
+
+std::array<u8, 0x10> FindKeyFromHex16(const std::vector<u8>& binary, std::array<u8, 0x20> hash);
+
+} // namespace Core::Crypto
diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 6102ef476..76a2b7e86 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -10,19 +10,19 @@ namespace FileSys {
BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
: nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
- sysnand_cache(std::make_shared<RegisteredCache>(
+ sysnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
- usrnand_cache(std::make_shared<RegisteredCache>(
+ usrnand_cache(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(nand_root, "/user/Contents/registered"))) {}
BISFactory::~BISFactory() = default;
-std::shared_ptr<RegisteredCache> BISFactory::GetSystemNANDContents() const {
- return sysnand_cache;
+RegisteredCache* BISFactory::GetSystemNANDContents() const {
+ return sysnand_cache.get();
}
-std::shared_ptr<RegisteredCache> BISFactory::GetUserNANDContents() const {
- return usrnand_cache;
+RegisteredCache* BISFactory::GetUserNANDContents() const {
+ return usrnand_cache.get();
}
VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index c352e0925..364d309bd 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -20,8 +20,8 @@ public:
explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
~BISFactory();
- std::shared_ptr<RegisteredCache> GetSystemNANDContents() const;
- std::shared_ptr<RegisteredCache> GetUserNANDContents() const;
+ RegisteredCache* GetSystemNANDContents() const;
+ RegisteredCache* GetUserNANDContents() const;
VirtualDir GetModificationLoadRoot(u64 title_id) const;
@@ -29,8 +29,8 @@ private:
VirtualDir nand_root;
VirtualDir load_root;
- std::shared_ptr<RegisteredCache> sysnand_cache;
- std::shared_ptr<RegisteredCache> usrnand_cache;
+ std::unique_ptr<RegisteredCache> sysnand_cache;
+ std::unique_ptr<RegisteredCache> usrnand_cache;
};
} // namespace FileSys
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 8f5142a07..1ece55731 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -122,14 +122,16 @@ u64 XCI::GetProgramTitleID() const {
return secure_partition->GetProgramTitleID();
}
-std::shared_ptr<NCA> XCI::GetProgramNCA() const {
- return program;
+bool XCI::HasProgramNCA() const {
+ return program != nullptr;
}
VirtualFile XCI::GetProgramNCAFile() const {
- if (GetProgramNCA() == nullptr)
+ if (!HasProgramNCA()) {
return nullptr;
- return GetProgramNCA()->GetBaseFile();
+ }
+
+ return program->GetBaseFile();
}
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
@@ -166,10 +168,6 @@ VirtualDir XCI::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool XCI::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
-
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
if (partitions[static_cast<std::size_t>(part)] == nullptr) {
return Loader::ResultStatus::ErrorXCIMissingPartition;
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index ce514dfa0..8f62571cf 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -80,7 +80,7 @@ public:
u64 GetProgramTitleID() const;
- std::shared_ptr<NCA> GetProgramNCA() const;
+ bool HasProgramNCA() const;
VirtualFile GetProgramNCAFile() const;
const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
@@ -94,9 +94,6 @@ public:
VirtualDir GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
Loader::ResultStatus AddNCAFromPartition(XCIPartition part);
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index aa1b3c17d..b46fe893c 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -4,10 +4,9 @@
#include <algorithm>
#include <cstring>
+#include <optional>
#include <utility>
-#include <boost/optional.hpp>
-
#include "common/logging/log.h"
#include "core/crypto/aes_util.h"
#include "core/crypto/ctr_encryption_layer.h"
@@ -97,11 +96,288 @@ union NCASectionHeader {
};
static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
-bool IsValidNCA(const NCAHeader& header) {
+static bool IsValidNCA(const NCAHeader& header) {
// TODO(DarkLordZach): Add NCA2/NCA0 support.
return header.magic == Common::MakeMagic('N', 'C', 'A', '3');
}
+NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
+ : file(std::move(file_)), bktr_base_romfs(std::move(bktr_base_romfs_)) {
+ if (file == nullptr) {
+ status = Loader::ResultStatus::ErrorNullFile;
+ return;
+ }
+
+ if (sizeof(NCAHeader) != file->ReadObject(&header)) {
+ LOG_ERROR(Loader, "File reader errored out during header read.");
+ status = Loader::ResultStatus::ErrorBadNCAHeader;
+ return;
+ }
+
+ if (!HandlePotentialHeaderDecryption()) {
+ return;
+ }
+
+ has_rights_id = std::any_of(header.rights_id.begin(), header.rights_id.end(),
+ [](char c) { return c != '\0'; });
+
+ const std::vector<NCASectionHeader> sections = ReadSectionHeaders();
+ is_update = std::any_of(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
+ return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
+ });
+
+ if (!ReadSections(sections, bktr_base_ivfc_offset)) {
+ return;
+ }
+
+ status = Loader::ResultStatus::Success;
+}
+
+NCA::~NCA() = default;
+
+bool NCA::CheckSupportedNCA(const NCAHeader& nca_header) {
+ if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
+ status = Loader::ResultStatus::ErrorNCA2;
+ return false;
+ }
+
+ if (nca_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
+ status = Loader::ResultStatus::ErrorNCA0;
+ return false;
+ }
+
+ return true;
+}
+
+bool NCA::HandlePotentialHeaderDecryption() {
+ if (IsValidNCA(header)) {
+ return true;
+ }
+
+ if (!CheckSupportedNCA(header)) {
+ return false;
+ }
+
+ NCAHeader dec_header{};
+ Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+ keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
+ cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
+ Core::Crypto::Op::Decrypt);
+ if (IsValidNCA(dec_header)) {
+ header = dec_header;
+ encrypted = true;
+ } else {
+ if (!CheckSupportedNCA(dec_header)) {
+ return false;
+ }
+
+ if (keys.HasKey(Core::Crypto::S256KeyType::Header)) {
+ status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
+ } else {
+ status = Loader::ResultStatus::ErrorMissingHeaderKey;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+std::vector<NCASectionHeader> NCA::ReadSectionHeaders() const {
+ const std::ptrdiff_t number_sections =
+ std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
+ [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
+
+ std::vector<NCASectionHeader> sections(number_sections);
+ const auto length_sections = SECTION_HEADER_SIZE * number_sections;
+
+ if (encrypted) {
+ auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
+ Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
+ keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
+ cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
+ Core::Crypto::Op::Decrypt);
+ } else {
+ file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
+ }
+
+ return sections;
+}
+
+bool NCA::ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset) {
+ for (std::size_t i = 0; i < sections.size(); ++i) {
+ const auto& section = sections[i];
+
+ if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
+ if (!ReadRomFSSection(section, header.section_tables[i], bktr_base_ivfc_offset)) {
+ return false;
+ }
+ } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
+ if (!ReadPFS0Section(section, header.section_tables[i])) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool NCA::ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
+ u64 bktr_base_ivfc_offset) {
+ const std::size_t base_offset = entry.media_offset * MEDIA_OFFSET_MULTIPLIER;
+ ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+ const std::size_t romfs_offset = base_offset + ivfc_offset;
+ const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
+ auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
+ auto dec = Decrypt(section, raw, romfs_offset);
+
+ if (dec == nullptr) {
+ if (status != Loader::ResultStatus::Success)
+ return false;
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+
+ if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
+ if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
+ section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
+ status = Loader::ResultStatus::ErrorBadBKTRHeader;
+ return false;
+ }
+
+ if (section.bktr.relocation.offset + section.bktr.relocation.size !=
+ section.bktr.subsection.offset) {
+ status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
+ return false;
+ }
+
+ const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
+ if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
+ status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
+ return false;
+ }
+
+ const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
+ RelocationBlock relocation_block{};
+ if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
+ sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadRelocationBlock;
+ return false;
+ }
+ SubsectionBlock subsection_block{};
+ if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
+ sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadSubsectionBlock;
+ return false;
+ }
+
+ std::vector<RelocationBucketRaw> relocation_buckets_raw(
+ (section.bktr.relocation.size - sizeof(RelocationBlock)) / sizeof(RelocationBucketRaw));
+ if (dec->ReadBytes(relocation_buckets_raw.data(),
+ section.bktr.relocation.size - sizeof(RelocationBlock),
+ section.bktr.relocation.offset + sizeof(RelocationBlock) - offset) !=
+ section.bktr.relocation.size - sizeof(RelocationBlock)) {
+ status = Loader::ResultStatus::ErrorBadRelocationBuckets;
+ return false;
+ }
+
+ std::vector<SubsectionBucketRaw> subsection_buckets_raw(
+ (section.bktr.subsection.size - sizeof(SubsectionBlock)) / sizeof(SubsectionBucketRaw));
+ if (dec->ReadBytes(subsection_buckets_raw.data(),
+ section.bktr.subsection.size - sizeof(SubsectionBlock),
+ section.bktr.subsection.offset + sizeof(SubsectionBlock) - offset) !=
+ section.bktr.subsection.size - sizeof(SubsectionBlock)) {
+ status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
+ return false;
+ }
+
+ std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
+ std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
+ relocation_buckets.begin(), &ConvertRelocationBucketRaw);
+ std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
+ std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
+ subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
+
+ u32 ctr_low;
+ std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
+ subsection_buckets.back().entries.push_back({section.bktr.relocation.offset, {0}, ctr_low});
+ subsection_buckets.back().entries.push_back({size, {0}, 0});
+
+ std::optional<Core::Crypto::Key128> key = {};
+ if (encrypted) {
+ if (has_rights_id) {
+ status = Loader::ResultStatus::Success;
+ key = GetTitlekey();
+ if (!key) {
+ status = Loader::ResultStatus::ErrorMissingTitlekey;
+ return false;
+ }
+ } else {
+ key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
+ if (!key) {
+ status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
+ return false;
+ }
+ }
+ }
+
+ if (bktr_base_romfs == nullptr) {
+ status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
+ return false;
+ }
+
+ auto bktr = std::make_shared<BKTR>(
+ bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
+ relocation_block, relocation_buckets, subsection_block, subsection_buckets, encrypted,
+ encrypted ? *key : Core::Crypto::Key128{}, base_offset, bktr_base_ivfc_offset,
+ section.raw.section_ctr);
+
+ // BKTR applies to entire IVFC, so make an offset version to level 6
+ files.push_back(std::make_shared<OffsetVfsFile>(
+ bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
+ } else {
+ files.push_back(std::move(dec));
+ }
+
+ romfs = files.back();
+ return true;
+}
+
+bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry) {
+ const u64 offset = (static_cast<u64>(entry.media_offset) * MEDIA_OFFSET_MULTIPLIER) +
+ section.pfs0.pfs0_header_offset;
+ const u64 size = MEDIA_OFFSET_MULTIPLIER * (entry.media_end_offset - entry.media_offset);
+
+ auto dec = Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
+ if (dec != nullptr) {
+ auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
+
+ if (npfs->GetStatus() == Loader::ResultStatus::Success) {
+ dirs.push_back(std::move(npfs));
+ if (IsDirectoryExeFS(dirs.back()))
+ exefs = dirs.back();
+ } else {
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+ } else {
+ if (status != Loader::ResultStatus::Success)
+ return false;
+ if (has_rights_id)
+ status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
+ else
+ status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
+ return false;
+ }
+
+ return true;
+}
+
u8 NCA::GetCryptoRevision() const {
u8 master_key_id = header.crypto_type;
if (header.crypto_type_2 > master_key_id)
@@ -111,11 +387,11 @@ u8 NCA::GetCryptoRevision() const {
return master_key_id;
}
-boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
+std::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType type) const {
const auto master_key_id = GetCryptoRevision();
if (!keys.HasKey(Core::Crypto::S128KeyType::KeyArea, master_key_id, header.key_index))
- return boost::none;
+ return {};
std::vector<u8> key_area(header.key_area.begin(), header.key_area.end());
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -133,31 +409,31 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
static_cast<u8>(type));
u128 out_128{};
memcpy(out_128.data(), out.data(), 16);
- LOG_DEBUG(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
+ LOG_TRACE(Crypto, "called with crypto_rev={:02X}, kak_index={:02X}, key={:016X}{:016X}",
master_key_id, header.key_index, out_128[1], out_128[0]);
return out;
}
-boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
+std::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
const auto master_key_id = GetCryptoRevision();
u128 rights_id{};
memcpy(rights_id.data(), header.rights_id.data(), 16);
if (rights_id == u128{}) {
status = Loader::ResultStatus::ErrorInvalidRightsID;
- return boost::none;
+ return {};
}
auto titlekey = keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
if (titlekey == Core::Crypto::Key128{}) {
status = Loader::ResultStatus::ErrorMissingTitlekey;
- return boost::none;
+ return {};
}
if (!keys.HasKey(Core::Crypto::S128KeyType::Titlekek, master_key_id)) {
status = Loader::ResultStatus::ErrorMissingTitlekek;
- return boost::none;
+ return {};
}
Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(
@@ -167,7 +443,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetTitlekey() {
return titlekey;
}
-VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting_offset) {
+VirtualFile NCA::Decrypt(const NCASectionHeader& s_header, VirtualFile in, u64 starting_offset) {
if (!encrypted)
return in;
@@ -181,25 +457,25 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
case NCASectionCryptoType::BKTR:
LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
{
- boost::optional<Core::Crypto::Key128> key = boost::none;
+ std::optional<Core::Crypto::Key128> key = {};
if (has_rights_id) {
status = Loader::ResultStatus::Success;
key = GetTitlekey();
- if (key == boost::none) {
+ if (!key) {
if (status == Loader::ResultStatus::Success)
status = Loader::ResultStatus::ErrorMissingTitlekey;
return nullptr;
}
} else {
key = GetKeyAreaKey(NCASectionCryptoType::CTR);
- if (key == boost::none) {
+ if (!key) {
status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
return nullptr;
}
}
- auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(
- std::move(in), key.value(), starting_offset);
+ auto out = std::make_shared<Core::Crypto::CTREncryptionLayer>(std::move(in), *key,
+ starting_offset);
std::vector<u8> iv(16);
for (u8 i = 0; i < 8; ++i)
iv[i] = s_header.raw.section_ctr[0x8 - i - 1];
@@ -215,256 +491,6 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
}
}
-NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
- : file(std::move(file_)),
- bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
- status = Loader::ResultStatus::Success;
-
- if (file == nullptr) {
- status = Loader::ResultStatus::ErrorNullFile;
- return;
- }
-
- if (sizeof(NCAHeader) != file->ReadObject(&header)) {
- LOG_ERROR(Loader, "File reader errored out during header read.");
- status = Loader::ResultStatus::ErrorBadNCAHeader;
- return;
- }
-
- encrypted = false;
-
- if (!IsValidNCA(header)) {
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return;
- }
- if (header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return;
- }
-
- NCAHeader dec_header{};
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(&header, sizeof(NCAHeader), &dec_header, 0, 0x200,
- Core::Crypto::Op::Decrypt);
- if (IsValidNCA(dec_header)) {
- header = dec_header;
- encrypted = true;
- } else {
- if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '2')) {
- status = Loader::ResultStatus::ErrorNCA2;
- return;
- }
- if (dec_header.magic == Common::MakeMagic('N', 'C', 'A', '0')) {
- status = Loader::ResultStatus::ErrorNCA0;
- return;
- }
-
- if (!keys.HasKey(Core::Crypto::S256KeyType::Header))
- status = Loader::ResultStatus::ErrorMissingHeaderKey;
- else
- status = Loader::ResultStatus::ErrorIncorrectHeaderKey;
- return;
- }
- }
-
- has_rights_id = std::find_if_not(header.rights_id.begin(), header.rights_id.end(),
- [](char c) { return c == '\0'; }) != header.rights_id.end();
-
- const std::ptrdiff_t number_sections =
- std::count_if(std::begin(header.section_tables), std::end(header.section_tables),
- [](NCASectionTableEntry entry) { return entry.media_offset > 0; });
-
- std::vector<NCASectionHeader> sections(number_sections);
- const auto length_sections = SECTION_HEADER_SIZE * number_sections;
-
- if (encrypted) {
- auto raw = file->ReadBytes(length_sections, SECTION_HEADER_OFFSET);
- Core::Crypto::AESCipher<Core::Crypto::Key256> cipher(
- keys.GetKey(Core::Crypto::S256KeyType::Header), Core::Crypto::Mode::XTS);
- cipher.XTSTranscode(raw.data(), length_sections, sections.data(), 2, SECTION_HEADER_SIZE,
- Core::Crypto::Op::Decrypt);
- } else {
- file->ReadBytes(sections.data(), length_sections, SECTION_HEADER_OFFSET);
- }
-
- is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
- return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
- }) != sections.end();
- ivfc_offset = 0;
-
- for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
- auto section = sections[i];
-
- if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
- const std::size_t base_offset =
- header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
- ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- const std::size_t romfs_offset = base_offset + ivfc_offset;
- const std::size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
- auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
- auto dec = Decrypt(section, raw, romfs_offset);
-
- if (dec == nullptr) {
- if (status != Loader::ResultStatus::Success)
- return;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
-
- if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
- if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
- section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
- status = Loader::ResultStatus::ErrorBadBKTRHeader;
- return;
- }
-
- if (section.bktr.relocation.offset + section.bktr.relocation.size !=
- section.bktr.subsection.offset) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
- return;
- }
-
- const u64 size =
- MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
- header.section_tables[i].media_offset);
- if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
- status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
- return;
- }
-
- const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
- RelocationBlock relocation_block{};
- if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBlock;
- return;
- }
- SubsectionBlock subsection_block{};
- if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
- sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBlock;
- return;
- }
-
- std::vector<RelocationBucketRaw> relocation_buckets_raw(
- (section.bktr.relocation.size - sizeof(RelocationBlock)) /
- sizeof(RelocationBucketRaw));
- if (dec->ReadBytes(relocation_buckets_raw.data(),
- section.bktr.relocation.size - sizeof(RelocationBlock),
- section.bktr.relocation.offset + sizeof(RelocationBlock) -
- offset) !=
- section.bktr.relocation.size - sizeof(RelocationBlock)) {
- status = Loader::ResultStatus::ErrorBadRelocationBuckets;
- return;
- }
-
- std::vector<SubsectionBucketRaw> subsection_buckets_raw(
- (section.bktr.subsection.size - sizeof(SubsectionBlock)) /
- sizeof(SubsectionBucketRaw));
- if (dec->ReadBytes(subsection_buckets_raw.data(),
- section.bktr.subsection.size - sizeof(SubsectionBlock),
- section.bktr.subsection.offset + sizeof(SubsectionBlock) -
- offset) !=
- section.bktr.subsection.size - sizeof(SubsectionBlock)) {
- status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
- return;
- }
-
- std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
- std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
- relocation_buckets.begin(), &ConvertRelocationBucketRaw);
- std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
- std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
- subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
-
- u32 ctr_low;
- std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
- subsection_buckets.back().entries.push_back(
- {section.bktr.relocation.offset, {0}, ctr_low});
- subsection_buckets.back().entries.push_back({size, {0}, 0});
-
- boost::optional<Core::Crypto::Key128> key = boost::none;
- if (encrypted) {
- if (has_rights_id) {
- status = Loader::ResultStatus::Success;
- key = GetTitlekey();
- if (key == boost::none) {
- status = Loader::ResultStatus::ErrorMissingTitlekey;
- return;
- }
- } else {
- key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
- if (key == boost::none) {
- status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
- return;
- }
- }
- }
-
- if (bktr_base_romfs == nullptr) {
- status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
- return;
- }
-
- auto bktr = std::make_shared<BKTR>(
- bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
- relocation_block, relocation_buckets, subsection_block, subsection_buckets,
- encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
- bktr_base_ivfc_offset, section.raw.section_ctr);
-
- // BKTR applies to entire IVFC, so make an offset version to level 6
-
- files.push_back(std::make_shared<OffsetVfsFile>(
- bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
- romfs = files.back();
- } else {
- files.push_back(std::move(dec));
- romfs = files.back();
- }
- } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
- u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
- MEDIA_OFFSET_MULTIPLIER) +
- section.pfs0.pfs0_header_offset;
- u64 size = MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
- header.section_tables[i].media_offset);
- auto dec =
- Decrypt(section, std::make_shared<OffsetVfsFile>(file, size, offset), offset);
- if (dec != nullptr) {
- auto npfs = std::make_shared<PartitionFilesystem>(std::move(dec));
-
- if (npfs->GetStatus() == Loader::ResultStatus::Success) {
- dirs.push_back(std::move(npfs));
- if (IsDirectoryExeFS(dirs.back()))
- exefs = dirs.back();
- } else {
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
- } else {
- if (status != Loader::ResultStatus::Success)
- return;
- if (has_rights_id)
- status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
- else
- status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
- return;
- }
- }
- }
-
- status = Loader::ResultStatus::Success;
-}
-
-NCA::~NCA() = default;
-
Loader::ResultStatus NCA::GetStatus() const {
return status;
}
@@ -519,7 +545,4 @@ u64 NCA::GetBaseIVFCOffset() const {
return ivfc_offset;
}
-bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index f9f66cae9..4bba55607 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -6,9 +6,10 @@
#include <array>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@@ -73,8 +74,6 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
}
-bool IsValidNCA(const NCAHeader& header);
-
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
// After construction, use GetStatus to determine if the file is valid and ready to be used.
class NCA : public ReadOnlyVfsDirectory {
@@ -102,14 +101,20 @@ public:
// Returns the base ivfc offset used in BKTR patching.
u64 GetBaseIVFCOffset() const;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
+ bool CheckSupportedNCA(const NCAHeader& header);
+ bool HandlePotentialHeaderDecryption();
+
+ std::vector<NCASectionHeader> ReadSectionHeaders() const;
+ bool ReadSections(const std::vector<NCASectionHeader>& sections, u64 bktr_base_ivfc_offset);
+ bool ReadRomFSSection(const NCASectionHeader& section, const NCASectionTableEntry& entry,
+ u64 bktr_base_ivfc_offset);
+ bool ReadPFS0Section(const NCASectionHeader& section, const NCASectionTableEntry& entry);
+
u8 GetCryptoRevision() const;
- boost::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
- boost::optional<Core::Crypto::Key128> GetTitlekey();
- VirtualFile Decrypt(NCASectionHeader header, VirtualFile in, u64 starting_offset);
+ std::optional<Core::Crypto::Key128> GetKeyAreaKey(NCASectionCryptoType type) const;
+ std::optional<Core::Crypto::Key128> GetTitlekey();
+ VirtualFile Decrypt(const NCASectionHeader& header, VirtualFile in, u64 starting_offset);
std::vector<VirtualDir> dirs;
std::vector<VirtualFile> files;
@@ -118,15 +123,15 @@ private:
VirtualDir exefs = nullptr;
VirtualFile file;
VirtualFile bktr_base_romfs;
- u64 ivfc_offset;
+ u64 ivfc_offset = 0;
NCAHeader header{};
bool has_rights_id{};
Loader::ResultStatus status{};
- bool encrypted;
- bool is_update;
+ bool encrypted = false;
+ bool is_update = false;
Core::Crypto::KeyManager keys;
};
diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index 5b1177a03..a012c2be9 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -17,11 +17,13 @@ const std::array<const char*, 15> LANGUAGE_NAMES = {
};
std::string LanguageEntry::GetApplicationName() const {
- return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(), 0x200);
+ return Common::StringFromFixedZeroTerminatedBuffer(application_name.data(),
+ application_name.size());
}
std::string LanguageEntry::GetDeveloperName() const {
- return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(), 0x100);
+ return Common::StringFromFixedZeroTerminatedBuffer(developer_name.data(),
+ developer_name.size());
}
NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
@@ -56,7 +58,12 @@ u64 NACP::GetTitleId() const {
return raw->title_id;
}
+u64 NACP::GetDLCBaseTitleId() const {
+ return raw->dlc_base_title_id;
+}
+
std::string NACP::GetVersionString() const {
- return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(), 0x10);
+ return Common::StringFromFixedZeroTerminatedBuffer(raw->version_string.data(),
+ raw->version_string.size());
}
} // namespace FileSys
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 43d6f0719..141f7e056 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -79,6 +79,7 @@ public:
std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const;
+ u64 GetDLCBaseTitleId() const;
std::string GetVersionString() const;
private:
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 2a913ce82..47b7526c7 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -26,6 +26,7 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "core/file_sys/fsmitm_romfsbuild.h"
+#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_vector.h"
@@ -123,7 +124,7 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
return count;
}
-void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
+void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext,
std::shared_ptr<RomFSBuildDirectoryContext> parent) {
std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
@@ -144,6 +145,9 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
+ if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
+ continue;
+
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
@@ -157,11 +161,24 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
child->path = parent->path + "/" + kv.first;
+ if (ext != nullptr && ext->GetFileRelative(child->path + ".stub") != nullptr)
+ continue;
+
// Sanity check on path_len
ASSERT(child->path_len < FS_MAX_PATH);
child->source = root_romfs->GetFileRelative(child->path);
+ if (ext != nullptr) {
+ const auto ips = ext->GetFileRelative(child->path + ".ips");
+
+ if (ips != nullptr) {
+ auto patched = PatchIPS(child->source, ips);
+ if (patched != nullptr)
+ child->source = std::move(patched);
+ }
+ }
+
child->size = child->source->GetSize();
AddFile(parent, child);
@@ -169,7 +186,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs,
}
for (auto& child : child_dirs) {
- this->VisitDirectory(root_romfs, child);
+ this->VisitDirectory(root_romfs, ext, child);
}
}
@@ -208,14 +225,15 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
return true;
}
-RomFSBuildContext::RomFSBuildContext(VirtualDir base_) : base(std::move(base_)) {
+RomFSBuildContext::RomFSBuildContext(VirtualDir base_, VirtualDir ext_)
+ : base(std::move(base_)), ext(std::move(ext_)) {
root = std::make_shared<RomFSBuildDirectoryContext>();
root->path = "\0";
directories.emplace(root->path, root);
num_dirs = 1;
dir_table_size = 0x18;
- VisitDirectory(base, root);
+ VisitDirectory(base, ext, root);
}
RomFSBuildContext::~RomFSBuildContext() = default;
diff --git a/src/core/file_sys/fsmitm_romfsbuild.h b/src/core/file_sys/fsmitm_romfsbuild.h
index b0c3c123b..a62502193 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.h
+++ b/src/core/file_sys/fsmitm_romfsbuild.h
@@ -27,7 +27,6 @@
#include <map>
#include <memory>
#include <string>
-#include <boost/detail/container_fwd.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
@@ -40,7 +39,7 @@ struct RomFSFileEntry;
class RomFSBuildContext {
public:
- explicit RomFSBuildContext(VirtualDir base);
+ explicit RomFSBuildContext(VirtualDir base, VirtualDir ext = nullptr);
~RomFSBuildContext();
// This finalizes the context.
@@ -48,6 +47,7 @@ public:
private:
VirtualDir base;
+ VirtualDir ext;
std::shared_ptr<RomFSBuildDirectoryContext> root;
std::map<std::string, std::shared_ptr<RomFSBuildDirectoryContext>, std::less<>> directories;
std::map<std::string, std::shared_ptr<RomFSBuildFileContext>, std::less<>> files;
@@ -59,7 +59,8 @@ private:
u64 file_hash_table_size = 0;
u64 file_partition_size = 0;
- void VisitDirectory(VirtualDir filesys, std::shared_ptr<RomFSBuildDirectoryContext> parent);
+ void VisitDirectory(VirtualDir filesys, VirtualDir ext,
+ std::shared_ptr<RomFSBuildDirectoryContext> parent);
bool AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx);
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index df933ee36..485c4913a 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -2,7 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "common/assert.h"
+#include <algorithm>
+#include <cstring>
+#include <map>
+#include <sstream>
+#include <string>
+#include <utility>
+
+#include "common/hex_util.h"
+#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/ips_layer.h"
#include "core/file_sys/vfs_vector.h"
@@ -15,16 +23,48 @@ enum class IPSFileType {
Error,
};
+constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{{
+ {"\\a", "\a"},
+ {"\\b", "\b"},
+ {"\\f", "\f"},
+ {"\\n", "\n"},
+ {"\\r", "\r"},
+ {"\\t", "\t"},
+ {"\\v", "\v"},
+ {"\\\\", "\\"},
+ {"\\\'", "\'"},
+ {"\\\"", "\""},
+ {"\\\?", "\?"},
+}};
+
static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
- if (magic.size() != 5)
+ if (magic.size() != 5) {
return IPSFileType::Error;
- if (magic == std::vector<u8>{'P', 'A', 'T', 'C', 'H'})
+ }
+
+ constexpr std::array<u8, 5> patch_magic{{'P', 'A', 'T', 'C', 'H'}};
+ if (std::equal(magic.begin(), magic.end(), patch_magic.begin())) {
return IPSFileType::IPS;
- if (magic == std::vector<u8>{'I', 'P', 'S', '3', '2'})
+ }
+
+ constexpr std::array<u8, 5> ips32_magic{{'I', 'P', 'S', '3', '2'}};
+ if (std::equal(magic.begin(), magic.end(), ips32_magic.begin())) {
return IPSFileType::IPS32;
+ }
+
return IPSFileType::Error;
}
+static bool IsEOF(IPSFileType type, const std::vector<u8>& data) {
+ constexpr std::array<u8, 3> eof{{'E', 'O', 'F'}};
+ if (type == IPSFileType::IPS && std::equal(data.begin(), data.end(), eof.begin())) {
+ return true;
+ }
+
+ constexpr std::array<u8, 4> eeof{{'E', 'E', 'O', 'F'}};
+ return type == IPSFileType::IPS32 && std::equal(data.begin(), data.end(), eeof.begin());
+}
+
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
if (in == nullptr || ips == nullptr)
return nullptr;
@@ -39,8 +79,7 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
u64 offset = 5; // After header
while (ips->Read(temp.data(), temp.size(), offset) == temp.size()) {
offset += temp.size();
- if (type == IPSFileType::IPS32 && temp == std::vector<u8>{'E', 'E', 'O', 'F'} ||
- type == IPSFileType::IPS && temp == std::vector<u8>{'E', 'O', 'F'}) {
+ if (IsEOF(type, temp)) {
break;
}
@@ -60,29 +99,240 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
u16 rle_size{};
if (ips->ReadObject(&rle_size, offset) != sizeof(u16))
return nullptr;
- rle_size = Common::swap16(data_size);
+ rle_size = Common::swap16(rle_size);
offset += sizeof(u16);
const auto data = ips->ReadByte(offset++);
- if (data == boost::none)
+ if (!data)
return nullptr;
if (real_offset + rle_size > in_data.size())
- rle_size = in_data.size() - real_offset;
- std::memset(in_data.data() + real_offset, data.get(), rle_size);
+ rle_size = static_cast<u16>(in_data.size() - real_offset);
+ std::memset(in_data.data() + real_offset, *data, rle_size);
} else { // Standard Patch
auto read = data_size;
if (real_offset + read > in_data.size())
- read = in_data.size() - real_offset;
+ read = static_cast<u16>(in_data.size() - real_offset);
if (ips->Read(in_data.data() + real_offset, read, offset) != data_size)
return nullptr;
offset += data_size;
}
}
- if (temp != std::vector<u8>{'E', 'E', 'O', 'F'} && temp != std::vector<u8>{'E', 'O', 'F'})
+ if (!IsEOF(type, temp)) {
return nullptr;
- return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
+ }
+
+ return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
+ in->GetContainingDirectory());
+}
+
+struct IPSwitchCompiler::IPSwitchPatch {
+ std::string name;
+ bool enabled;
+ std::map<u32, std::vector<u8>> records;
+};
+
+IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
+ Parse();
+}
+
+IPSwitchCompiler::~IPSwitchCompiler() = default;
+
+std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
+ return nso_build_id;
+}
+
+bool IPSwitchCompiler::IsValid() const {
+ return valid;
+}
+
+static bool StartsWith(std::string_view base, std::string_view check) {
+ return base.size() >= check.size() && base.substr(0, check.size()) == check;
+}
+
+static std::string EscapeStringSequences(std::string in) {
+ for (const auto& seq : ESCAPE_CHARACTER_MAP) {
+ for (auto index = in.find(seq.first); index != std::string::npos;
+ index = in.find(seq.first, index)) {
+ in.replace(index, std::strlen(seq.first), seq.second);
+ index += std::strlen(seq.second);
+ }
+ }
+
+ return in;
+}
+
+void IPSwitchCompiler::ParseFlag(const std::string& line) {
+ if (StartsWith(line, "@flag offset_shift ")) {
+ // Offset Shift Flag
+ offset_shift = std::stoll(line.substr(19), nullptr, 0);
+ } else if (StartsWith(line, "@little-endian")) {
+ // Set values to read as little endian
+ is_little_endian = true;
+ } else if (StartsWith(line, "@big-endian")) {
+ // Set values to read as big endian
+ is_little_endian = false;
+ } else if (StartsWith(line, "@flag print_values")) {
+ // Force printing of applied values
+ print_values = true;
+ }
+}
+
+void IPSwitchCompiler::Parse() {
+ const auto bytes = patch_text->ReadAllBytes();
+ std::stringstream s;
+ s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
+
+ std::vector<std::string> lines;
+ std::string stream_line;
+ while (std::getline(s, stream_line)) {
+ // Remove a trailing \r
+ if (!stream_line.empty() && stream_line.back() == '\r')
+ stream_line.pop_back();
+ lines.push_back(std::move(stream_line));
+ }
+
+ for (std::size_t i = 0; i < lines.size(); ++i) {
+ auto line = lines[i];
+
+ // Remove midline comments
+ std::size_t comment_index = std::string::npos;
+ bool within_string = false;
+ for (std::size_t k = 0; k < line.size(); ++k) {
+ if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
+ within_string = !within_string;
+ } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
+ comment_index = k;
+ break;
+ }
+ }
+
+ if (!StartsWith(line, "//") && comment_index != std::string::npos) {
+ last_comment = line.substr(comment_index + 2);
+ line = line.substr(0, comment_index);
+ }
+
+ if (StartsWith(line, "@stop")) {
+ // Force stop
+ break;
+ } else if (StartsWith(line, "@nsobid-")) {
+ // NSO Build ID Specifier
+ auto raw_build_id = line.substr(8);
+ if (raw_build_id.size() != 0x40)
+ raw_build_id.resize(0x40, '0');
+ nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
+ } else if (StartsWith(line, "#")) {
+ // Mandatory Comment
+ LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
+ patch_text->GetName(), line.substr(1));
+ } else if (StartsWith(line, "//")) {
+ // Normal Comment
+ last_comment = line.substr(2);
+ if (last_comment.find_first_not_of(' ') == std::string::npos)
+ continue;
+ if (last_comment.find_first_not_of(' ') != 0)
+ last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
+ } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
+ // Start of patch
+ const auto enabled = StartsWith(line, "@enabled");
+ if (i == 0)
+ return;
+ LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
+ patch_text->GetName(), last_comment, line.substr(1));
+
+ IPSwitchPatch patch{last_comment, enabled, {}};
+
+ // Read rest of patch
+ while (true) {
+ if (i + 1 >= lines.size())
+ break;
+ const auto patch_line = lines[++i];
+
+ // Start of new patch
+ if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
+ --i;
+ break;
+ }
+
+ // Check for a flag
+ if (StartsWith(patch_line, "@")) {
+ ParseFlag(patch_line);
+ continue;
+ }
+
+ // 11 - 8 hex digit offset + space + minimum two digit overwrite val
+ if (patch_line.length() < 11)
+ break;
+ auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
+ offset += static_cast<unsigned long>(offset_shift);
+
+ std::vector<u8> replace;
+ // 9 - first char of replacement val
+ if (patch_line[9] == '\"') {
+ // string replacement
+ auto end_index = patch_line.find('\"', 10);
+ if (end_index == std::string::npos || end_index < 10)
+ return;
+ while (patch_line[end_index - 1] == '\\') {
+ end_index = patch_line.find('\"', end_index + 1);
+ if (end_index == std::string::npos || end_index < 10)
+ return;
+ }
+
+ auto value = patch_line.substr(10, end_index - 10);
+ value = EscapeStringSequences(value);
+ replace.reserve(value.size());
+ std::copy(value.begin(), value.end(), std::back_inserter(replace));
+ } else {
+ // hex replacement
+ const auto value = patch_line.substr(9);
+ replace.reserve(value.size() / 2);
+ replace = Common::HexStringToVector(value, is_little_endian);
+ }
+
+ if (print_values) {
+ LOG_INFO(Loader,
+ "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
+ "with byte string '{}'",
+ patch_text->GetName(), offset, Common::HexVectorToString(replace));
+ }
+
+ patch.records.insert_or_assign(offset, std::move(replace));
+ }
+
+ patches.push_back(std::move(patch));
+ } else if (StartsWith(line, "@")) {
+ ParseFlag(line);
+ }
+ }
+
+ valid = true;
+}
+
+VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
+ if (in == nullptr || !valid)
+ return nullptr;
+
+ auto in_data = in->ReadAllBytes();
+
+ for (const auto& patch : patches) {
+ if (!patch.enabled)
+ continue;
+
+ for (const auto& record : patch.records) {
+ if (record.first >= in_data.size())
+ continue;
+ auto replace_size = record.second.size();
+ if (record.first + replace_size > in_data.size())
+ replace_size = in_data.size() - record.first;
+ for (std::size_t i = 0; i < replace_size; ++i)
+ in_data[i + record.first] = record.second[i];
+ }
+ }
+
+ return std::make_shared<VectorVfsFile>(std::move(in_data), in->GetName(),
+ in->GetContainingDirectory());
}
} // namespace FileSys
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
index 81c163494..450b2f71e 100644
--- a/src/core/file_sys/ips_layer.h
+++ b/src/core/file_sys/ips_layer.h
@@ -4,12 +4,41 @@
#pragma once
+#include <array>
#include <memory>
+#include <vector>
+#include "common/common_types.h"
#include "core/file_sys/vfs.h"
namespace FileSys {
VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
+class IPSwitchCompiler {
+public:
+ explicit IPSwitchCompiler(VirtualFile patch_text);
+ ~IPSwitchCompiler();
+
+ std::array<u8, 0x20> GetBuildID() const;
+ bool IsValid() const;
+ VirtualFile Apply(const VirtualFile& in) const;
+
+private:
+ struct IPSwitchPatch;
+
+ void ParseFlag(const std::string& flag);
+ void Parse();
+
+ bool valid = false;
+
+ VirtualFile patch_text;
+ std::vector<IPSwitchPatch> patches;
+ std::array<u8, 0x20> nso_build_id{};
+ bool is_little_endian = false;
+ s64 offset_shift = 0;
+ bool print_values = false;
+ std::string last_comment = "";
+};
+
} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.cpp b/src/core/file_sys/partition_filesystem.cpp
index 5791c76ff..a5259a593 100644
--- a/src/core/file_sys/partition_filesystem.cpp
+++ b/src/core/file_sys/partition_filesystem.cpp
@@ -83,7 +83,7 @@ std::vector<std::shared_ptr<VfsFile>> PartitionFilesystem::GetFiles() const {
}
std::vector<std::shared_ptr<VfsDirectory>> PartitionFilesystem::GetSubdirectories() const {
- return pfs_dirs;
+ return {};
}
std::string PartitionFilesystem::GetName() const {
@@ -103,18 +103,4 @@ void PartitionFilesystem::PrintDebugInfo() const {
pfs_files[i]->GetName(), pfs_files[i]->GetSize());
}
}
-
-bool PartitionFilesystem::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- const auto iter = std::find(pfs_files.begin(), pfs_files.end(), file);
- if (iter == pfs_files.end())
- return false;
-
- const std::ptrdiff_t offset = std::distance(pfs_files.begin(), iter);
- pfs_files[offset] = std::move(pfs_files.back());
- pfs_files.pop_back();
-
- pfs_dirs.emplace_back(std::move(dir));
-
- return true;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/partition_filesystem.h b/src/core/file_sys/partition_filesystem.h
index 739c63a7f..248fdfdeb 100644
--- a/src/core/file_sys/partition_filesystem.h
+++ b/src/core/file_sys/partition_filesystem.h
@@ -35,9 +35,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
void PrintDebugInfo() const;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
struct Header {
u32_le magic;
@@ -84,7 +81,6 @@ private:
std::size_t content_offset = 0;
std::vector<VirtualFile> pfs_files;
- std::vector<VirtualDir> pfs_dirs;
};
} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 539698f6e..0c1156989 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -61,39 +61,49 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
// Game Updates
const auto update_tid = GetUpdateTitleID(title_id);
const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
- if (update != nullptr) {
- if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
- update->GetExeFS() != nullptr) {
- LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
- exefs = update->GetExeFS();
- }
+
+ if (update != nullptr && update->GetExeFS() != nullptr &&
+ update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
+ LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
+ FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
+ exefs = update->GetExeFS();
}
return exefs;
}
-static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs,
- const std::string& build_id) {
- std::vector<VirtualFile> ips;
- ips.reserve(patch_dirs.size());
+static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
+ const std::string& build_id) {
+ std::vector<VirtualFile> out;
+ out.reserve(patch_dirs.size());
for (const auto& subdir : patch_dirs) {
auto exefs_dir = subdir->GetSubdirectory("exefs");
if (exefs_dir != nullptr) {
for (const auto& file : exefs_dir->GetFiles()) {
- if (file->GetExtension() != "ips")
- continue;
- auto name = file->GetName();
- const auto p1 = name.substr(0, name.find('.'));
- const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
-
- if (build_id == this_build_id)
- ips.push_back(file);
+ if (file->GetExtension() == "ips") {
+ auto name = file->GetName();
+ const auto p1 = name.substr(0, name.find('.'));
+ const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
+
+ if (build_id == this_build_id)
+ out.push_back(file);
+ } else if (file->GetExtension() == "pchtxt") {
+ IPSwitchCompiler compiler{file};
+ if (!compiler.IsValid())
+ continue;
+
+ auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
+ this_build_id =
+ this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
+
+ if (build_id == this_build_id)
+ out.push_back(file);
+ }
}
}
}
- return ips;
+ return out;
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
@@ -115,15 +125,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
auto patch_dirs = load_dir->GetSubdirectories();
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- const auto ips = CollectIPSPatches(patch_dirs, build_id);
+ const auto patches = CollectPatches(patch_dirs, build_id);
auto out = nso;
- for (const auto& ips_file : ips) {
- LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"",
- ips_file->GetContainingDirectory()->GetParentDirectory()->GetName());
- const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file);
- if (patched != nullptr)
- out = patched->ReadAllBytes();
+ for (const auto& patch_file : patches) {
+ if (patch_file->GetExtension() == "ips") {
+ LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
+ patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
+ const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
+ if (patched != nullptr)
+ out = patched->ReadAllBytes();
+ } else if (patch_file->GetExtension() == "pchtxt") {
+ LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
+ patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
+ const IPSwitchCompiler compiler{patch_file};
+ const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
+ if (patched != nullptr)
+ out = patched->ReadAllBytes();
+ }
}
if (out.size() < 0x100)
@@ -143,12 +162,13 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
std::sort(patch_dirs.begin(), patch_dirs.end(),
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
- return !CollectIPSPatches(patch_dirs, build_id).empty();
+ return !CollectPatches(patch_dirs, build_id).empty();
}
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
- if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) {
+ if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
+ load_dir == nullptr || load_dir->GetSize() <= 0) {
return;
}
@@ -162,11 +182,17 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
std::vector<VirtualDir> layers;
+ std::vector<VirtualDir> layers_ext;
layers.reserve(patch_dirs.size() + 1);
+ layers_ext.reserve(patch_dirs.size() + 1);
for (const auto& subdir : patch_dirs) {
auto romfs_dir = subdir->GetSubdirectory("romfs");
if (romfs_dir != nullptr)
layers.push_back(std::move(romfs_dir));
+
+ auto ext_dir = subdir->GetSubdirectory("romfs_ext");
+ if (ext_dir != nullptr)
+ layers_ext.push_back(std::move(ext_dir));
}
layers.push_back(std::move(extracted));
@@ -175,7 +201,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
return;
}
- auto packed = CreateRomFS(std::move(layered));
+ auto layered_ext = LayeredVfsDirectory::MakeLayeredDirectory(std::move(layers_ext));
+
+ auto packed = CreateRomFS(std::move(layered), std::move(layered_ext));
if (packed == nullptr) {
return;
}
@@ -184,10 +212,16 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
romfs = std::move(packed);
}
-VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
- ContentRecordType type) const {
- LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
- static_cast<u8>(type));
+VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, ContentRecordType type,
+ VirtualFile update_raw) const {
+ const auto log_string = fmt::format("Patching RomFS for title_id={:016X}, type={:02X}",
+ title_id, static_cast<u8>(type))
+ .c_str();
+
+ if (type == ContentRecordType::Program || type == ContentRecordType::Data)
+ LOG_INFO(Loader, log_string);
+ else
+ LOG_DEBUG(Loader, log_string);
if (romfs == nullptr)
return romfs;
@@ -202,7 +236,14 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
new_nca->GetRomFS() != nullptr) {
LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
- FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
+ FormatTitleVersion(installed->GetEntryVersion(update_tid).value_or(0)));
+ romfs = new_nca->GetRomFS();
+ }
+ } else if (update_raw != nullptr) {
+ const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
+ if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
+ new_nca->GetRomFS() != nullptr) {
+ LOG_INFO(Loader, " RomFS: Update (PACKED) applied successfully");
romfs = new_nca->GetRomFS();
}
}
@@ -224,7 +265,8 @@ static bool IsDirValidAndNonEmpty(const VirtualDir& dir) {
return dir != nullptr && (!dir->GetFiles().empty() || !dir->GetSubdirectories().empty());
}
-std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames() const {
+std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNames(
+ VirtualFile update_raw) const {
std::map<std::string, std::string, std::less<>> out;
const auto installed = Service::FileSystem::GetUnionContents();
@@ -238,13 +280,14 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
} else {
if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
const auto meta_ver = installed->GetEntryVersion(update_tid);
- if (meta_ver == boost::none || meta_ver.get() == 0) {
+ if (meta_ver.value_or(0) == 0) {
out.insert_or_assign("Update", "");
} else {
out.insert_or_assign(
- "Update",
- FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements));
+ "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
}
+ } else if (update_raw != nullptr) {
+ out.insert_or_assign("Update", "PACKED");
}
}
@@ -253,8 +296,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
for (const auto& mod : mod_dir->GetSubdirectories()) {
std::string types;
- if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs")))
- AppendCommaIfNotEmpty(types, "IPS");
+
+ const auto exefs_dir = mod->GetSubdirectory("exefs");
+ if (IsDirValidAndNonEmpty(exefs_dir)) {
+ bool ips = false;
+ bool ipswitch = false;
+
+ for (const auto& file : exefs_dir->GetFiles()) {
+ if (file->GetExtension() == "ips")
+ ips = true;
+ else if (file->GetExtension() == "pchtxt")
+ ipswitch = true;
+ }
+
+ if (ips)
+ AppendCommaIfNotEmpty(types, "IPS");
+ if (ipswitch)
+ AppendCommaIfNotEmpty(types, "IPSwitch");
+ }
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
@@ -291,23 +350,22 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
return out;
}
-std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
- const auto& installed{Service::FileSystem::GetUnionContents()};
+std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
+ const auto installed{Service::FileSystem::GetUnionContents()};
const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
if (base_control_nca == nullptr)
return {};
- return ParseControlNCA(base_control_nca);
+ return ParseControlNCA(*base_control_nca);
}
-std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
- const std::shared_ptr<NCA>& nca) const {
- const auto base_romfs = nca->GetRomFS();
+std::pair<std::unique_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(const NCA& nca) const {
+ const auto base_romfs = nca.GetRomFS();
if (base_romfs == nullptr)
return {};
- const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
+ const auto romfs = PatchRomFS(base_romfs, nca.GetBaseIVFCOffset(), ContentRecordType::Control);
if (romfs == nullptr)
return {};
@@ -319,7 +377,7 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
if (nacp_file == nullptr)
nacp_file = extracted->GetFile("Control.nacp");
- const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
+ auto nacp = nacp_file == nullptr ? nullptr : std::make_unique<NACP>(nacp_file);
VirtualFile icon_file;
for (const auto& language : FileSys::LANGUAGE_NAMES) {
@@ -328,6 +386,6 @@ std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
break;
}
- return {nacp, icon_file};
+ return {std::move(nacp), icon_file};
}
} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 6a864ec43..7d168837f 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -36,6 +36,7 @@ public:
// Currently tracked NSO patches:
// - IPS
+ // - IPSwitch
std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
@@ -46,19 +47,20 @@ public:
// - Game Updates
// - LayeredFS
VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
- ContentRecordType type = ContentRecordType::Program) const;
+ ContentRecordType type = ContentRecordType::Program,
+ VirtualFile update_raw = nullptr) const;
// Returns a vector of pairs between patch names and patch versions.
// i.e. Update 3.2.2 will return {"Update", "3.2.2"}
- std::map<std::string, std::string, std::less<>> GetPatchVersionNames() const;
+ std::map<std::string, std::string, std::less<>> GetPatchVersionNames(
+ VirtualFile update_raw = nullptr) const;
// Given title_id of the program, attempts to get the control data of the update and parse it,
// falling back to the base control data.
- std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
+ std::pair<std::unique_ptr<NACP>, VirtualFile> GetControlMetadata() const;
// Version of GetControlMetadata that takes an arbitrary NCA
- std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
- const std::shared_ptr<NCA>& nca) const;
+ std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
private:
u64 title_id;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index e9b040689..96302a241 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <regex>
#include <mbedtls/sha256.h>
#include "common/assert.h"
@@ -30,6 +31,14 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type);
}
+bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+ return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type);
+}
+
+bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) {
+ return !operator==(lhs, rhs);
+}
+
static bool FollowsTwoDigitDirFormat(std::string_view name) {
static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript |
std::regex_constants::icase);
@@ -150,28 +159,28 @@ VirtualFile RegisteredCache::GetFileAtID(NcaID id) const {
return file;
}
-static boost::optional<NcaID> CheckMapForContentRecord(
+static std::optional<NcaID> CheckMapForContentRecord(
const boost::container::flat_map<u64, CNMT>& map, u64 title_id, ContentRecordType type) {
if (map.find(title_id) == map.end())
- return boost::none;
+ return {};
const auto& cnmt = map.at(title_id);
const auto iter = std::find_if(cnmt.GetContentRecords().begin(), cnmt.GetContentRecords().end(),
[type](const ContentRecord& rec) { return rec.type == type; });
if (iter == cnmt.GetContentRecords().end())
- return boost::none;
+ return {};
- return boost::make_optional(iter->nca_id);
+ return std::make_optional(iter->nca_id);
}
-boost::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
- ContentRecordType type) const {
+std::optional<NcaID> RegisteredCache::GetNcaIDFromMetadata(u64 title_id,
+ ContentRecordType type) const {
if (type == ContentRecordType::Meta && meta_id.find(title_id) != meta_id.end())
return meta_id.at(title_id);
const auto res1 = CheckMapForContentRecord(yuzu_meta, title_id, type);
- if (res1 != boost::none)
+ if (res1)
return res1;
return CheckMapForContentRecord(meta, title_id, type);
}
@@ -274,17 +283,14 @@ bool RegisteredCache::HasEntry(RegisteredCacheEntry entry) const {
VirtualFile RegisteredCache::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return GetFileAtID(id.get());
+ return id ? GetFileAtID(*id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const {
return GetEntryUnparsed(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
const auto meta_iter = meta.find(title_id);
if (meta_iter != meta.end())
return meta_iter->second.GetTitleVersion();
@@ -293,29 +299,26 @@ boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
if (yuzu_meta_iter != yuzu_meta.end())
return yuzu_meta_iter->second.GetTitleVersion();
- return boost::none;
+ return {};
}
VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
const auto id = GetNcaIDFromMetadata(title_id, type);
- if (id == boost::none)
- return nullptr;
-
- return parser(GetFileAtID(id.get()), id.get());
+ return id ? parser(GetFileAtID(*id), *id) : nullptr;
}
VirtualFile RegisteredCache::GetEntryRaw(RegisteredCacheEntry entry) const {
return GetEntryRaw(entry.title_id, entry.type);
}
-std::shared_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
+std::unique_ptr<NCA> RegisteredCache::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_shared<NCA>(raw);
+ return std::make_unique<NCA>(raw);
}
-std::shared_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
+std::unique_ptr<NCA> RegisteredCache::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
@@ -355,8 +358,8 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntries() const {
}
std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
IterateAllMetadata<RegisteredCacheEntry>(
out,
@@ -364,11 +367,11 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
@@ -450,7 +453,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType
InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
bool overwrite_if_exists,
- boost::optional<NcaID> override_id) {
+ std::optional<NcaID> override_id) {
const auto in = nca->GetBaseFile();
Core::Crypto::SHA256Hash hash{};
@@ -459,12 +462,12 @@ InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const Vfs
// game is massive), we're going to cheat and only hash the first MB of the NCA.
// Also, for XCIs the NcaID matters, so if the override id isn't none, use that.
NcaID id{};
- if (override_id == boost::none) {
+ if (override_id) {
+ id = *override_id;
+ } else {
const auto& data = in->ReadBytes(0x100000);
mbedtls_sha256(data.data(), data.size(), hash.data(), 0);
memcpy(id.data(), hash.data(), 16);
- } else {
- id = override_id.get();
}
std::string path = GetRelativePathFromNcaID(id, false, true);
@@ -516,7 +519,7 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
}) != yuzu_meta.end();
}
-RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
+RegisteredCacheUnion::RegisteredCacheUnion(std::vector<RegisteredCache*> caches)
: caches(std::move(caches)) {}
void RegisteredCacheUnion::Refresh() {
@@ -534,14 +537,14 @@ bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
return HasEntry(entry.title_id, entry.type);
}
-boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
+std::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
for (const auto& c : caches) {
const auto res = c->GetEntryVersion(title_id);
- if (res != boost::none)
+ if (res)
return res;
}
- return boost::none;
+ return {};
}
VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
@@ -572,14 +575,14 @@ VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const
return GetEntryRaw(entry.title_id, entry.type);
}
-std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
+std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
const auto raw = GetEntryRaw(title_id, type);
if (raw == nullptr)
return nullptr;
- return std::make_shared<NCA>(raw);
+ return std::make_unique<NCA>(raw);
}
-std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
+std::unique_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
return GetEntry(entry.title_id, entry.type);
}
@@ -593,12 +596,15 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
},
[](const CNMT& c, const ContentRecord& r) { return true; });
}
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
- boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
- boost::optional<u64> title_id) const {
+ std::optional<TitleType> title_type, std::optional<ContentRecordType> record_type,
+ std::optional<u64> title_id) const {
std::vector<RegisteredCacheEntry> out;
for (const auto& c : caches) {
c->IterateAllMetadata<RegisteredCacheEntry>(
@@ -607,15 +613,18 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
return RegisteredCacheEntry{c.GetTitleID(), r.type};
},
[&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
- if (title_type != boost::none && title_type.get() != c.GetType())
+ if (title_type && *title_type != c.GetType())
return false;
- if (record_type != boost::none && record_type.get() != r.type)
+ if (record_type && *record_type != r.type)
return false;
- if (title_id != boost::none && title_id.get() != c.GetTitleID())
+ if (title_id && *title_id != c.GetTitleID())
return false;
return true;
});
}
+
+ std::sort(out.begin(), out.end());
+ out.erase(std::unique(out.begin(), out.end()), out.end());
return out;
}
} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index c0cd59fc5..6cfb16017 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -50,6 +50,10 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) {
// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+// std unique requires operator== to identify duplicates.
+bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
+
/*
* A class that catalogues NCAs in the registered directory structure.
* Nintendo's registered format follows this structure:
@@ -60,8 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
* | 00
* | 01 <- Actual content split along 4GB boundaries. (optional)
*
- * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when
- * 4GB splitting can be ignored.)
+ * (This impl also supports substituting the nca dir for an nca file, as that's more convenient
+ * when 4GB splitting can be ignored.)
*/
class RegisteredCache {
friend class RegisteredCacheUnion;
@@ -80,7 +84,7 @@ public:
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -88,15 +92,14 @@ public:
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
- std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
+ std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
// Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// there is a meta NCA and all of them are accessible.
@@ -121,12 +124,11 @@ private:
std::vector<NcaID> AccumulateFiles() const;
void ProcessFiles(const std::vector<NcaID>& ids);
void AccumulateYuzuMeta();
- boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
+ std::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
VirtualFile GetFileAtID(NcaID id) const;
VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
- bool overwrite_if_exists,
- boost::optional<NcaID> override_id = boost::none);
+ bool overwrite_if_exists, std::optional<NcaID> override_id = {});
bool RawInstallYuzuMeta(const CNMT& cnmt);
VirtualDir dir;
@@ -142,14 +144,14 @@ private:
// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
class RegisteredCacheUnion {
public:
- explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
+ explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);
void Refresh();
bool HasEntry(u64 title_id, ContentRecordType type) const;
bool HasEntry(RegisteredCacheEntry entry) const;
- boost::optional<u32> GetEntryVersion(u64 title_id) const;
+ std::optional<u32> GetEntryVersion(u64 title_id) const;
VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
@@ -157,18 +159,17 @@ public:
VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
- std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
- std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
+ std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
+ std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
std::vector<RegisteredCacheEntry> ListEntries() const;
- // If a parameter is not boost::none, it will be filtered for from all entries.
+ // If a parameter is not std::nullopt, it will be filtered for from all entries.
std::vector<RegisteredCacheEntry> ListEntriesFilter(
- boost::optional<TitleType> title_type = boost::none,
- boost::optional<ContentRecordType> record_type = boost::none,
- boost::optional<u64> title_id = boost::none) const;
+ std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
+ std::optional<u64> title_id = {}) const;
private:
- std::vector<std::shared_ptr<RegisteredCache>> caches;
+ std::vector<RegisteredCache*> caches;
};
} // namespace FileSys
diff --git a/src/core/file_sys/romfs.cpp b/src/core/file_sys/romfs.cpp
index 5910f7046..81e1f66ac 100644
--- a/src/core/file_sys/romfs.cpp
+++ b/src/core/file_sys/romfs.cpp
@@ -129,11 +129,11 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
return out;
}
-VirtualFile CreateRomFS(VirtualDir dir) {
+VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext) {
if (dir == nullptr)
return nullptr;
- RomFSBuildContext ctx{dir};
+ RomFSBuildContext ctx{dir, ext};
return ConcatenatedVfsFile::MakeConcatenatedFile(0, ctx.Build(), dir->GetName());
}
diff --git a/src/core/file_sys/romfs.h b/src/core/file_sys/romfs.h
index ecd1eb725..0ec404731 100644
--- a/src/core/file_sys/romfs.h
+++ b/src/core/file_sys/romfs.h
@@ -44,6 +44,6 @@ VirtualDir ExtractRomFS(VirtualFile file,
// Converts a VFS filesystem into a RomFS binary
// Returns nullptr on failure
-VirtualFile CreateRomFS(VirtualDir dir);
+VirtualFile CreateRomFS(VirtualDir dir, VirtualDir ext = nullptr);
} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 4994c2532..0b645b106 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -30,12 +30,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
RomFSFactory::~RomFSFactory() = default;
+void RomFSFactory::SetPackedUpdate(VirtualFile update_raw) {
+ this->update_raw = std::move(update_raw);
+}
+
ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
if (!updatable)
return MakeResult<VirtualFile>(file);
const PatchManager patch_manager(Core::CurrentProcess()->GetTitleID());
- return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
+ return MakeResult<VirtualFile>(
+ patch_manager.PatchRomFS(file, ivfc_offset, ContentRecordType::Program, update_raw));
}
ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index 2cace8180..7724c0b23 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -32,11 +32,13 @@ public:
explicit RomFSFactory(Loader::AppLoader& app_loader);
~RomFSFactory();
+ void SetPackedUpdate(VirtualFile update_raw);
ResultVal<VirtualFile> OpenCurrentProcess();
ResultVal<VirtualFile> Open(u64 title_id, StorageId storage, ContentRecordType type);
private:
VirtualFile file;
+ VirtualFile update_raw;
bool updatable;
u64 ivfc_offset;
};
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 47f2ab9e0..ef1aaebbb 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -51,6 +51,13 @@ ResultVal<VirtualDir> SaveDataFactory::Open(SaveDataSpaceId space, SaveDataDescr
meta.title_id);
}
+ if (meta.type == SaveDataType::DeviceSaveData && meta.user_id != u128{0, 0}) {
+ LOG_WARNING(Service_FS,
+ "Possibly incorrect SaveDataDescriptor, type is DeviceSaveData but user_id is "
+ "non-zero ({:016X}{:016X})",
+ meta.user_id[1], meta.user_id[0]);
+ }
+
std::string save_directory =
GetFullPath(space, meta.type, meta.title_id, meta.user_id, meta.save_id);
@@ -92,6 +99,9 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataSpaceId::NandUser:
out = "/user/";
break;
+ case SaveDataSpaceId::TemporaryStorage:
+ out = "/temp/";
+ break;
default:
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
}
@@ -100,10 +110,11 @@ std::string SaveDataFactory::GetFullPath(SaveDataSpaceId space, SaveDataType typ
case SaveDataType::SystemSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}", out, save_id, user_id[1], user_id[0]);
case SaveDataType::SaveData:
+ case SaveDataType::DeviceSaveData:
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
case SaveDataType::TemporaryStorage:
- return fmt::format("{}temp/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
+ return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
title_id);
default:
ASSERT_MSG(false, "Unrecognized SaveDataType: {:02X}", static_cast<u8>(type));
diff --git a/src/core/file_sys/sdmc_factory.cpp b/src/core/file_sys/sdmc_factory.cpp
index d66a9c9a4..bd3a57058 100644
--- a/src/core/file_sys/sdmc_factory.cpp
+++ b/src/core/file_sys/sdmc_factory.cpp
@@ -10,10 +10,10 @@
namespace FileSys {
SDMCFactory::SDMCFactory(VirtualDir dir_)
- : dir(std::move(dir_)), contents(std::make_shared<RegisteredCache>(
+ : dir(std::move(dir_)), contents(std::make_unique<RegisteredCache>(
GetOrCreateDirectoryRelative(dir, "/Nintendo/Contents/registered"),
[](const VirtualFile& file, const NcaID& id) {
- return std::make_shared<NAX>(file, id)->GetDecrypted();
+ return NAX{file, id}.GetDecrypted();
})) {}
SDMCFactory::~SDMCFactory() = default;
@@ -22,8 +22,8 @@ ResultVal<VirtualDir> SDMCFactory::Open() {
return MakeResult<VirtualDir>(dir);
}
-std::shared_ptr<RegisteredCache> SDMCFactory::GetSDMCContents() const {
- return contents;
+RegisteredCache* SDMCFactory::GetSDMCContents() const {
+ return contents.get();
}
} // namespace FileSys
diff --git a/src/core/file_sys/sdmc_factory.h b/src/core/file_sys/sdmc_factory.h
index ea12149de..42794ba5b 100644
--- a/src/core/file_sys/sdmc_factory.h
+++ b/src/core/file_sys/sdmc_factory.h
@@ -19,12 +19,12 @@ public:
~SDMCFactory();
ResultVal<VirtualDir> Open();
- std::shared_ptr<RegisteredCache> GetSDMCContents() const;
+ RegisteredCache* GetSDMCContents() const;
private:
VirtualDir dir;
- std::shared_ptr<RegisteredCache> contents;
+ std::unique_ptr<RegisteredCache> contents;
};
} // namespace FileSys
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 09bf077cd..2aaba4179 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -205,10 +205,6 @@ VirtualDir NSP::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
-
void NSP::InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files) {
exefs = pfs;
@@ -259,8 +255,11 @@ void NSP::ReadNCAs(const std::vector<VirtualFile>& files) {
auto next_nca = std::make_shared<NCA>(next_file);
if (next_nca->GetType() == NCAContentType::Program)
program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
- if (next_nca->GetStatus() == Loader::ResultStatus::Success)
+ if (next_nca->GetStatus() == Loader::ResultStatus::Success ||
+ (next_nca->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
+ (cnmt.GetTitleID() & 0x800) != 0)) {
ncas_title[rec.type] = std::move(next_nca);
+ }
}
break;
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index da3dc5e9f..338080b7e 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -55,9 +55,6 @@ public:
VirtualDir GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
void InitializeExeFSAndRomFS(const std::vector<VirtualFile>& files);
void ReadNCAs(const std::vector<VirtualFile>& files);
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp
index bfe50da73..7b584de7f 100644
--- a/src/core/file_sys/vfs.cpp
+++ b/src/core/file_sys/vfs.cpp
@@ -167,13 +167,13 @@ std::string VfsFile::GetExtension() const {
VfsDirectory::~VfsDirectory() = default;
-boost::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
+std::optional<u8> VfsFile::ReadByte(std::size_t offset) const {
u8 out{};
std::size_t size = Read(&out, 1, offset);
if (size == 1)
return out;
- return boost::none;
+ return {};
}
std::vector<u8> VfsFile::ReadBytes(std::size_t size, std::size_t offset) const {
@@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo
std::vector<u8> temp(std::min(block_size, src->GetSize()));
for (std::size_t i = 0; i < src->GetSize(); i += block_size) {
const auto read = std::min(block_size, src->GetSize() - i);
- const auto block = src->Read(temp.data(), read, i);
- if (dest->Write(temp.data(), read, i) != read)
+ if (src->Read(temp.data(), read, i) != read) {
return false;
+ }
+
+ if (dest->Write(temp.data(), read, i) != read) {
+ return false;
+ }
}
return true;
diff --git a/src/core/file_sys/vfs.h b/src/core/file_sys/vfs.h
index 270291631..002f99d4e 100644
--- a/src/core/file_sys/vfs.h
+++ b/src/core/file_sys/vfs.h
@@ -4,28 +4,22 @@
#pragma once
+#include <functional>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_types.h"
+#include "core/file_sys/vfs_types.h"
namespace FileSys {
-class VfsDirectory;
-class VfsFile;
-class VfsFilesystem;
-
enum class Mode : u32;
-// Convenience typedefs to use Vfs* interfaces
-using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
-using VirtualDir = std::shared_ptr<VfsDirectory>;
-using VirtualFile = std::shared_ptr<VfsFile>;
-
// An enumeration representing what can be at the end of a path in a VfsFilesystem
enum class VfsEntryType {
None,
@@ -111,8 +105,8 @@ public:
// into file. Returns number of bytes successfully written.
virtual std::size_t Write(const u8* data, std::size_t length, std::size_t offset = 0) = 0;
- // Reads exactly one byte at the offset provided, returning boost::none on error.
- virtual boost::optional<u8> ReadByte(std::size_t offset = 0) const;
+ // Reads exactly one byte at the offset provided, returning std::nullopt on error.
+ virtual std::optional<u8> ReadByte(std::size_t offset = 0) const;
// Reads size bytes starting at offset in file into a vector.
virtual std::vector<u8> ReadBytes(std::size_t size, std::size_t offset = 0) const;
// Reads all the bytes from the file into a vector. Equivalent to 'file->Read(file->GetSize(),
@@ -270,36 +264,8 @@ public:
// item name -> type.
virtual std::map<std::string, VfsEntryType, std::less<>> GetEntries() const;
- // Interprets the file with name file instead as a directory of type directory.
- // The directory must have a constructor that takes a single argument of type
- // std::shared_ptr<VfsFile>. Allows to reinterpret container files (i.e NCA, zip, XCI, etc) as a
- // subdirectory in one call.
- template <typename Directory>
- bool InterpretAsDirectory(std::string_view file) {
- auto file_p = GetFile(file);
-
- if (file_p == nullptr) {
- return false;
- }
-
- return ReplaceFileWithSubdirectory(file_p, std::make_shared<Directory>(file_p));
- }
-
- bool InterpretAsDirectory(const std::function<VirtualDir(VirtualFile)>& function,
- const std::string& file) {
- auto file_p = GetFile(file);
- if (file_p == nullptr)
- return false;
- return ReplaceFileWithSubdirectory(file_p, function(file_p));
- }
-
// Returns the full path of this directory as a string, recursively
virtual std::string GetFullPath() const;
-
-protected:
- // Backend for InterpretAsDirectory.
- // Removes all references to file and adds a reference to dir in the directory's implementation.
- virtual bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) = 0;
};
// A convenience partial-implementation of VfsDirectory that stubs out methods that should only work
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index bfee01725..338e398da 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -126,7 +126,4 @@ bool LayeredVfsDirectory::Rename(std::string_view name_) {
return true;
}
-bool LayeredVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_layered.h b/src/core/file_sys/vfs_layered.h
index d85310f57..8a25c3428 100644
--- a/src/core/file_sys/vfs_layered.h
+++ b/src/core/file_sys/vfs_layered.h
@@ -39,9 +39,6 @@ public:
bool DeleteFile(std::string_view name) override;
bool Rename(std::string_view name) override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
std::vector<VirtualDir> dirs;
std::string name;
diff --git a/src/core/file_sys/vfs_offset.cpp b/src/core/file_sys/vfs_offset.cpp
index a4c6719a0..c96f88488 100644
--- a/src/core/file_sys/vfs_offset.cpp
+++ b/src/core/file_sys/vfs_offset.cpp
@@ -57,11 +57,11 @@ std::size_t OffsetVfsFile::Write(const u8* data, std::size_t length, std::size_t
return file->Write(data, TrimToFit(length, r_offset), offset + r_offset);
}
-boost::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
+std::optional<u8> OffsetVfsFile::ReadByte(std::size_t r_offset) const {
if (r_offset < size)
return file->ReadByte(offset + r_offset);
- return boost::none;
+ return {};
}
std::vector<u8> OffsetVfsFile::ReadBytes(std::size_t r_size, std::size_t r_offset) const {
diff --git a/src/core/file_sys/vfs_offset.h b/src/core/file_sys/vfs_offset.h
index 8062702a7..f7b7a3256 100644
--- a/src/core/file_sys/vfs_offset.h
+++ b/src/core/file_sys/vfs_offset.h
@@ -29,7 +29,7 @@ public:
bool IsReadable() const override;
std::size_t Read(u8* data, std::size_t length, std::size_t offset) const override;
std::size_t Write(const u8* data, std::size_t length, std::size_t offset) override;
- boost::optional<u8> ReadByte(std::size_t offset) const override;
+ std::optional<u8> ReadByte(std::size_t offset) const override;
std::vector<u8> ReadBytes(std::size_t size, std::size_t offset) const override;
std::vector<u8> ReadAllBytes() const override;
bool WriteByte(u8 data, std::size_t offset) override;
diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp
index 9defad04c..e21300a7c 100644
--- a/src/core/file_sys/vfs_real.cpp
+++ b/src/core/file_sys/vfs_real.cpp
@@ -430,7 +430,4 @@ std::map<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
return out;
}
-bool RealVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h
index 5b61db90d..a0a857a31 100644
--- a/src/core/file_sys/vfs_real.h
+++ b/src/core/file_sys/vfs_real.h
@@ -100,9 +100,6 @@ public:
std::string GetFullPath() const override;
std::map<std::string, VfsEntryType, std::less<>> GetEntries() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
RealVfsDirectory(RealVfsFilesystem& base, const std::string& path, Mode perms = Mode::Read);
diff --git a/src/core/file_sys/vfs_static.h b/src/core/file_sys/vfs_static.h
index 44fab51d1..9f5a90b1b 100644
--- a/src/core/file_sys/vfs_static.h
+++ b/src/core/file_sys/vfs_static.h
@@ -53,10 +53,10 @@ public:
return 0;
}
- boost::optional<u8> ReadByte(std::size_t offset) const override {
+ std::optional<u8> ReadByte(std::size_t offset) const override {
if (offset < size)
return value;
- return boost::none;
+ return {};
}
std::vector<u8> ReadBytes(std::size_t length, std::size_t offset) const override {
diff --git a/src/core/file_sys/vfs_types.h b/src/core/file_sys/vfs_types.h
new file mode 100644
index 000000000..6215ed7af
--- /dev/null
+++ b/src/core/file_sys/vfs_types.h
@@ -0,0 +1,21 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+
+namespace FileSys {
+
+class VfsDirectory;
+class VfsFile;
+class VfsFilesystem;
+
+// Declarations for Vfs* pointer types
+
+using VirtualDir = std::shared_ptr<VfsDirectory>;
+using VirtualFile = std::shared_ptr<VfsFile>;
+using VirtualFilesystem = std::shared_ptr<VfsFilesystem>;
+
+} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 389c7e003..808f31e81 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -132,11 +132,4 @@ void VectorVfsDirectory::AddFile(VirtualFile file) {
void VectorVfsDirectory::AddDirectory(VirtualDir dir) {
dirs.push_back(std::move(dir));
}
-
-bool VectorVfsDirectory::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- if (!DeleteFile(file->GetName()))
- return false;
- dirs.emplace_back(std::move(dir));
- return true;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index 48a414c98..3e3f790c3 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -57,9 +57,6 @@ public:
virtual void AddFile(VirtualFile file);
virtual void AddDirectory(VirtualDir dir);
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
std::vector<VirtualFile> files;
std::vector<VirtualDir> dirs;
diff --git a/src/core/file_sys/xts_archive.cpp b/src/core/file_sys/xts_archive.cpp
index b2b164368..eec51c64e 100644
--- a/src/core/file_sys/xts_archive.cpp
+++ b/src/core/file_sys/xts_archive.cpp
@@ -163,7 +163,4 @@ std::shared_ptr<VfsDirectory> NAX::GetParentDirectory() const {
return file->GetContainingDirectory();
}
-bool NAX::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
- return false;
-}
} // namespace FileSys
diff --git a/src/core/file_sys/xts_archive.h b/src/core/file_sys/xts_archive.h
index 8fedd8585..7704dee90 100644
--- a/src/core/file_sys/xts_archive.h
+++ b/src/core/file_sys/xts_archive.h
@@ -51,9 +51,6 @@ public:
std::shared_ptr<VfsDirectory> GetParentDirectory() const override;
-protected:
- bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
-
private:
Loader::ResultStatus Parse(std::string_view path);
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index e961ef121..bdcc889e0 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -207,7 +207,7 @@ void RegisterModule(std::string name, VAddr beg, VAddr end, bool add_elf_ext) {
static Kernel::Thread* FindThreadById(int id) {
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
- const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
+ const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (auto& thread : threads) {
if (thread->GetThreadID() == static_cast<u32>(id)) {
current_core = core;
@@ -597,7 +597,7 @@ static void HandleQuery() {
} else if (strncmp(query, "fThreadInfo", strlen("fThreadInfo")) == 0) {
std::string val = "m";
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
- const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
+ const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (const auto& thread : threads) {
val += fmt::format("{:x}", thread->GetThreadID());
val += ",";
@@ -612,7 +612,7 @@ static void HandleQuery() {
buffer += "l<?xml version=\"1.0\"?>";
buffer += "<threads>";
for (u32 core = 0; core < Core::NUM_CPU_CORES; core++) {
- const auto& threads = Core::System::GetInstance().Scheduler(core)->GetThreadList();
+ const auto& threads = Core::System::GetInstance().Scheduler(core).GetThreadList();
for (const auto& thread : threads) {
buffer +=
fmt::format(R"*(<thread id="{:x}" core="{:d}" name="Thread {:x}"></thread>)*",
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 419f45896..ed84197b3 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -14,11 +14,6 @@ namespace IPC {
/// Size of the command buffer area, in 32-bit words.
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
-// These errors are commonly returned by invalid IPC translations, so alias them here for
-// convenience.
-// TODO(yuriks): These will probably go away once translation is implemented inside the kernel.
-constexpr auto ERR_INVALID_HANDLE = Kernel::ERR_INVALID_HANDLE_OS;
-
enum class ControlCommand : u32 {
ConvertSessionToDomain = 0,
ConvertDomainToSession = 1,
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index a4bfe2eb0..0a7142ada 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -117,8 +117,7 @@ public:
AlignWithPadding();
- const bool request_has_domain_header{context.GetDomainMessageHeader() != nullptr};
- if (context.Session()->IsDomain() && request_has_domain_header) {
+ if (context.Session()->IsDomain() && context.HasDomainMessageHeader()) {
IPC::DomainMessageHeader domain_header{};
domain_header.num_objects = num_domain_objects;
PushRaw(domain_header);
diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp
index ebf193930..57157beb4 100644
--- a/src/core/hle/kernel/address_arbiter.cpp
+++ b/src/core/hle/kernel/address_arbiter.cpp
@@ -39,7 +39,7 @@ static std::vector<SharedPtr<Thread>> GetThreadsWaitingOnAddress(VAddr address)
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr arb_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
- const auto& thread_list = scheduler->GetThreadList();
+ const auto& thread_list = scheduler.GetThreadList();
for (const auto& thread : thread_list) {
if (thread->GetArbiterWaitAddress() == arb_addr)
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index e5fa67ae8..ee698c8a7 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -10,11 +10,6 @@ namespace Kernel {
namespace ErrCodes {
enum {
- // TODO(Subv): Remove these 3DS OS error codes.
- SessionClosedByRemote = 26,
- NoPendingSessions = 35,
- InvalidBufferDescriptor = 48,
-
// Confirmed Switch OS error codes
MaxConnectionsReached = 7,
InvalidSize = 101,
@@ -22,9 +17,11 @@ enum {
HandleTableFull = 105,
InvalidMemoryState = 106,
InvalidMemoryPermissions = 108,
+ InvalidMemoryRange = 110,
InvalidThreadPriority = 112,
InvalidProcessorId = 113,
InvalidHandle = 114,
+ InvalidPointer = 115,
InvalidCombination = 116,
Timeout = 117,
SynchronizationCanceled = 118,
@@ -32,6 +29,7 @@ enum {
InvalidEnumValue = 120,
NoSuchEntry = 121,
AlreadyRegistered = 122,
+ SessionClosed = 123,
InvalidState = 125,
ResourceLimitExceeded = 132,
};
@@ -40,22 +38,19 @@ enum {
// WARNING: The kernel is quite inconsistent in it's usage of errors code. Make sure to always
// double check that the code matches before re-using the constant.
-// TODO(bunnei): Replace -1 with correct errors for Switch OS
constexpr ResultCode ERR_HANDLE_TABLE_FULL(ErrorModule::Kernel, ErrCodes::HandleTableFull);
-constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(-1);
+constexpr ResultCode ERR_SESSION_CLOSED_BY_REMOTE(ErrorModule::Kernel, ErrCodes::SessionClosed);
constexpr ResultCode ERR_PORT_NAME_TOO_LONG(ErrorModule::Kernel, ErrCodes::TooLarge);
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(ErrorModule::Kernel,
ErrCodes::MaxConnectionsReached);
constexpr ResultCode ERR_INVALID_ENUM_VALUE(ErrorModule::Kernel, ErrCodes::InvalidEnumValue);
-constexpr ResultCode ERR_INVALID_ENUM_VALUE_FND(-1);
-constexpr ResultCode ERR_INVALID_COMBINATION(-1);
constexpr ResultCode ERR_INVALID_COMBINATION_KERNEL(ErrorModule::Kernel,
ErrCodes::InvalidCombination);
-constexpr ResultCode ERR_OUT_OF_MEMORY(-1);
constexpr ResultCode ERR_INVALID_ADDRESS(ErrorModule::Kernel, ErrCodes::InvalidAddress);
constexpr ResultCode ERR_INVALID_ADDRESS_STATE(ErrorModule::Kernel, ErrCodes::InvalidMemoryState);
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS(ErrorModule::Kernel,
ErrCodes::InvalidMemoryPermissions);
+constexpr ResultCode ERR_INVALID_MEMORY_RANGE(ErrorModule::Kernel, ErrCodes::InvalidMemoryRange);
constexpr ResultCode ERR_INVALID_HANDLE(ErrorModule::Kernel, ErrCodes::InvalidHandle);
constexpr ResultCode ERR_INVALID_PROCESSOR_ID(ErrorModule::Kernel, ErrCodes::InvalidProcessorId);
constexpr ResultCode ERR_INVALID_SIZE(ErrorModule::Kernel, ErrCodes::InvalidSize);
@@ -63,14 +58,8 @@ constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorModule::Kernel, ErrCodes::Alrea
constexpr ResultCode ERR_INVALID_STATE(ErrorModule::Kernel, ErrCodes::InvalidState);
constexpr ResultCode ERR_INVALID_THREAD_PRIORITY(ErrorModule::Kernel,
ErrCodes::InvalidThreadPriority);
-constexpr ResultCode ERR_INVALID_POINTER(-1);
-constexpr ResultCode ERR_INVALID_OBJECT_ADDR(-1);
-constexpr ResultCode ERR_NOT_AUTHORIZED(-1);
-/// Alternate code returned instead of ERR_INVALID_HANDLE in some code paths.
-constexpr ResultCode ERR_INVALID_HANDLE_OS(-1);
+constexpr ResultCode ERR_INVALID_POINTER(ErrorModule::Kernel, ErrCodes::InvalidPointer);
constexpr ResultCode ERR_NOT_FOUND(ErrorModule::Kernel, ErrCodes::NoSuchEntry);
constexpr ResultCode RESULT_TIMEOUT(ErrorModule::Kernel, ErrCodes::Timeout);
-/// Returned when Accept() is called on a port with no sessions to be accepted.
-constexpr ResultCode ERR_NO_PENDING_SESSIONS(-1);
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index edad5f1b1..68d5376cb 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -77,7 +77,8 @@ HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_ses
HLERequestContext::~HLERequestContext() = default;
-void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
+void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf,
+ bool incoming) {
IPC::RequestParser rp(src_cmdbuf);
command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>());
@@ -94,8 +95,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
rp.Skip(2, false);
}
if (incoming) {
- auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
-
// Populate the object lists with the data in the IPC request.
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
copy_objects.push_back(handle_table.GetGeneric(rp.Pop<Handle>()));
@@ -189,10 +188,9 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
rp.Skip(1, false); // The command is actually an u64, but we don't use the high part.
}
-ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf,
- Process& src_process,
- HandleTable& src_table) {
- ParseCommandBuffer(src_cmdbuf, true);
+ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const HandleTable& handle_table,
+ u32_le* src_cmdbuf) {
+ ParseCommandBuffer(handle_table, src_cmdbuf, true);
if (command_header->type == IPC::CommandType::Close) {
// Close does not populate the rest of the IPC header
return RESULT_SUCCESS;
@@ -207,14 +205,17 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb
return RESULT_SUCCESS;
}
-ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) {
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) {
+ auto& owner_process = *thread.GetOwnerProcess();
+ auto& handle_table = owner_process.GetHandleTable();
+
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf;
- Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
+ Memory::ReadBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
// The header was already built in the internal command buffer. Attempt to parse it to verify
// the integrity and then copy it over to the target command buffer.
- ParseCommandBuffer(cmd_buf.data(), false);
+ ParseCommandBuffer(handle_table, cmd_buf.data(), false);
// The data_size already includes the payload header, the padding and the domain header.
std::size_t size = data_payload_offset + command_header->data_size -
@@ -236,8 +237,6 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
ASSERT(copy_objects.size() == handle_descriptor_header->num_handles_to_copy);
ASSERT(move_objects.size() == handle_descriptor_header->num_handles_to_move);
- auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
-
// We don't make a distinction between copy and move handles when translating since HLE
// services don't deal with handles directly. However, the guest applications might check
// for specific values in each of these descriptors.
@@ -268,7 +267,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread)
}
// Copy the translated command buffer back into the thread's command buffer area.
- Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(),
+ Memory::WriteBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(),
dst_cmdbuf.size() * sizeof(u32));
return RESULT_SUCCESS;
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 894479ee0..a38e34b74 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -24,10 +24,10 @@ class ServiceFrameworkBase;
namespace Kernel {
class Domain;
+class Event;
class HandleTable;
class HLERequestContext;
class Process;
-class Event;
/**
* Interface implemented by HLE Session handlers.
@@ -126,13 +126,12 @@ public:
u64 timeout, WakeupCallback&& callback,
Kernel::SharedPtr<Kernel::Event> event = nullptr);
- void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming);
-
/// Populates this context with data from the requesting process/thread.
- ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process,
- HandleTable& src_table);
+ ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table,
+ u32_le* src_cmdbuf);
+
/// Writes data from this context back to the requesting process/thread.
- ResultCode WriteToOutgoingCommandBuffer(const Thread& thread);
+ ResultCode WriteToOutgoingCommandBuffer(Thread& thread);
u32_le GetCommand() const {
return command;
@@ -162,8 +161,12 @@ public:
return buffer_c_desciptors;
}
- const std::shared_ptr<IPC::DomainMessageHeader>& GetDomainMessageHeader() const {
- return domain_message_header;
+ const IPC::DomainMessageHeader* GetDomainMessageHeader() const {
+ return domain_message_header.get();
+ }
+
+ bool HasDomainMessageHeader() const {
+ return domain_message_header != nullptr;
}
/// Helper function to read a buffer using the appropriate buffer descriptor
@@ -255,6 +258,8 @@ public:
std::string Description() const;
private:
+ void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming);
+
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
SharedPtr<Kernel::ServerSession> server_session;
// TODO(yuriks): Check common usage of this and optimize size accordingly
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 98eb74298..1fd4ba5d2 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -32,7 +32,7 @@ namespace Kernel {
*/
static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_late) {
const auto proper_handle = static_cast<Handle>(thread_handle);
- auto& system = Core::System::GetInstance();
+ const auto& system = Core::System::GetInstance();
// Lock the global kernel mutex when we enter the kernel HLE.
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
@@ -90,7 +90,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
/// The timer callback event, called when a timer is fired
static void TimerCallback(u64 timer_handle, int cycles_late) {
const auto proper_handle = static_cast<Handle>(timer_handle);
- auto& system = Core::System::GetInstance();
+ const auto& system = Core::System::GetInstance();
SharedPtr<Timer> timer = system.Kernel().RetrieveTimerFromCallbackHandleTable(proper_handle);
if (timer == nullptr) {
@@ -116,9 +116,8 @@ struct KernelCore::Impl {
next_thread_id = 1;
process_list.clear();
- current_process.reset();
+ current_process = nullptr;
- handle_table.Clear();
resource_limits.fill(nullptr);
thread_wakeup_callback_handle_table.Clear();
@@ -207,9 +206,8 @@ struct KernelCore::Impl {
// Lists all processes that exist in the current session.
std::vector<SharedPtr<Process>> process_list;
- SharedPtr<Process> current_process;
+ Process* current_process = nullptr;
- Kernel::HandleTable handle_table;
std::array<SharedPtr<ResourceLimit>, 4> resource_limits;
/// The event type of the generic timer callback event
@@ -241,14 +239,6 @@ void KernelCore::Shutdown() {
impl->Shutdown();
}
-Kernel::HandleTable& KernelCore::HandleTable() {
- return impl->handle_table;
-}
-
-const Kernel::HandleTable& KernelCore::HandleTable() const {
- return impl->handle_table;
-}
-
SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory(
ResourceLimitCategory category) const {
return impl->resource_limits.at(static_cast<std::size_t>(category));
@@ -266,15 +256,15 @@ void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
impl->process_list.push_back(std::move(process));
}
-void KernelCore::MakeCurrentProcess(SharedPtr<Process> process) {
- impl->current_process = std::move(process);
+void KernelCore::MakeCurrentProcess(Process* process) {
+ impl->current_process = process;
}
-SharedPtr<Process>& KernelCore::CurrentProcess() {
+Process* KernelCore::CurrentProcess() {
return impl->current_process;
}
-const SharedPtr<Process>& KernelCore::CurrentProcess() const {
+const Process* KernelCore::CurrentProcess() const {
return impl->current_process;
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index c0771ecf0..7f822d524 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -47,12 +47,6 @@ public:
/// Clears all resources in use by the kernel instance.
void Shutdown();
- /// Provides a reference to the handle table.
- Kernel::HandleTable& HandleTable();
-
- /// Provides a const reference to the handle table.
- const Kernel::HandleTable& HandleTable() const;
-
/// Retrieves a shared pointer to a ResourceLimit identified by the given category.
SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const;
@@ -66,13 +60,13 @@ public:
void AppendNewProcess(SharedPtr<Process> process);
/// Makes the given process the new current process.
- void MakeCurrentProcess(SharedPtr<Process> process);
+ void MakeCurrentProcess(Process* process);
- /// Retrieves a reference to the current process.
- SharedPtr<Process>& CurrentProcess();
+ /// Retrieves a pointer to the current process.
+ Process* CurrentProcess();
- /// Retrieves a const reference to the current process.
- const SharedPtr<Process>& CurrentProcess() const;
+ /// Retrieves a const pointer to the current process.
+ const Process* CurrentProcess() const;
/// Adds a port to the named port table
void AddNamedPort(std::string name, SharedPtr<ClientPort> port);
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index dd541ffcc..0743670ad 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -6,8 +6,6 @@
#include <utility>
#include <vector>
-#include <boost/range/algorithm_ext/erase.hpp>
-
#include "common/assert.h"
#include "core/core.h"
#include "core/hle/kernel/errors.h"
diff --git a/src/core/hle/kernel/object.cpp b/src/core/hle/kernel/object.cpp
index d51562d92..d87a62bb9 100644
--- a/src/core/hle/kernel/object.cpp
+++ b/src/core/hle/kernel/object.cpp
@@ -25,7 +25,6 @@ bool Object::IsWaitable() const {
case HandleType::Process:
case HandleType::AddressArbiter:
case HandleType::ResourceLimit:
- case HandleType::CodeSet:
case HandleType::ClientPort:
case HandleType::ClientSession:
return false;
diff --git a/src/core/hle/kernel/object.h b/src/core/hle/kernel/object.h
index 9eb72315c..c9f4d0bb3 100644
--- a/src/core/hle/kernel/object.h
+++ b/src/core/hle/kernel/object.h
@@ -26,7 +26,6 @@ enum class HandleType : u32 {
AddressArbiter,
Timer,
ResourceLimit,
- CodeSet,
ClientPort,
ServerPort,
ClientSession,
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index fb0027a71..420218d59 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -20,13 +20,7 @@
namespace Kernel {
-SharedPtr<CodeSet> CodeSet::Create(KernelCore& kernel, std::string name) {
- SharedPtr<CodeSet> codeset(new CodeSet(kernel));
- codeset->name = std::move(name);
- return codeset;
-}
-
-CodeSet::CodeSet(KernelCore& kernel) : Object{kernel} {}
+CodeSet::CodeSet() = default;
CodeSet::~CodeSet() = default;
SharedPtr<Process> Process::Create(KernelCore& kernel, std::string&& name) {
@@ -159,11 +153,11 @@ void Process::PrepareForTermination() {
}
};
- auto& system = Core::System::GetInstance();
- stop_threads(system.Scheduler(0)->GetThreadList());
- stop_threads(system.Scheduler(1)->GetThreadList());
- stop_threads(system.Scheduler(2)->GetThreadList());
- stop_threads(system.Scheduler(3)->GetThreadList());
+ const auto& system = Core::System::GetInstance();
+ stop_threads(system.Scheduler(0).GetThreadList());
+ stop_threads(system.Scheduler(1).GetThreadList());
+ stop_threads(system.Scheduler(2).GetThreadList());
+ stop_threads(system.Scheduler(3).GetThreadList());
}
/**
@@ -224,20 +218,26 @@ void Process::FreeTLSSlot(VAddr tls_address) {
tls_slots[tls_page].reset(tls_slot);
}
-void Process::LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr) {
+void Process::LoadModule(CodeSet module_, VAddr base_addr) {
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
- auto vma = vm_manager
- .MapMemoryBlock(segment.addr + base_addr, module_->memory, segment.offset,
- segment.size, memory_state)
- .Unwrap();
+ const auto vma = vm_manager
+ .MapMemoryBlock(segment.addr + base_addr, module_.memory,
+ segment.offset, segment.size, memory_state)
+ .Unwrap();
vm_manager.Reprotect(vma, permissions);
};
// Map CodeSet segments
- MapSegment(module_->CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
- MapSegment(module_->RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
- MapSegment(module_->DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
+ MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
+ MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
+ MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
+
+ // Clear instruction cache in CPU JIT
+ Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
+ Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
+ Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
+ Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
}
ResultVal<VAddr> Process::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 590e0c73d..8d2616c79 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -13,6 +13,7 @@
#include <boost/container/static_vector.hpp>
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/vm_manager.h"
@@ -24,6 +25,7 @@ class ProgramMetadata;
namespace Kernel {
class KernelCore;
+class ResourceLimit;
struct AddressMapping {
// Address and size must be page-aligned
@@ -57,30 +59,33 @@ union ProcessFlags {
BitField<12, 1, u16> loaded_high; ///< Application loaded high (not at 0x00100000).
};
-enum class ProcessStatus { Created, Running, Exited };
-
-class ResourceLimit;
+/**
+ * Indicates the status of a Process instance.
+ *
+ * @note These match the values as used by kernel,
+ * so new entries should only be added if RE
+ * shows that a new value has been introduced.
+ */
+enum class ProcessStatus {
+ Created,
+ CreatedWithDebuggerAttached,
+ Running,
+ WaitingForDebuggerToAttach,
+ DebuggerAttached,
+ Exiting,
+ Exited,
+ DebugBreak,
+};
-struct CodeSet final : public Object {
+struct CodeSet final {
struct Segment {
std::size_t offset = 0;
VAddr addr = 0;
u32 size = 0;
};
- static SharedPtr<CodeSet> Create(KernelCore& kernel, std::string name);
-
- std::string GetTypeName() const override {
- return "CodeSet";
- }
- std::string GetName() const override {
- return name;
- }
-
- static const HandleType HANDLE_TYPE = HandleType::CodeSet;
- HandleType GetHandleType() const override {
- return HANDLE_TYPE;
- }
+ explicit CodeSet();
+ ~CodeSet();
Segment& CodeSegment() {
return segments[0];
@@ -109,14 +114,7 @@ struct CodeSet final : public Object {
std::shared_ptr<std::vector<u8>> memory;
std::array<Segment, 3> segments;
- VAddr entrypoint;
-
- /// Name of the process
- std::string name;
-
-private:
- explicit CodeSet(KernelCore& kernel);
- ~CodeSet() override;
+ VAddr entrypoint = 0;
};
class Process final : public Object {
@@ -145,6 +143,16 @@ public:
return vm_manager;
}
+ /// Gets a reference to the process' handle table.
+ HandleTable& GetHandleTable() {
+ return handle_table;
+ }
+
+ /// Gets a const reference to the process' handle table.
+ const HandleTable& GetHandleTable() const {
+ return handle_table;
+ }
+
/// Gets the current status of the process
ProcessStatus GetStatus() const {
return status;
@@ -194,6 +202,16 @@ public:
return is_64bit_process;
}
+ /// Gets the total running time of the process instance in ticks.
+ u64 GetCPUTimeTicks() const {
+ return total_process_running_time_ticks;
+ }
+
+ /// Updates the total running time, adding the given ticks to it.
+ void UpdateCPUTimeTicks(u64 ticks) {
+ total_process_running_time_ticks += ticks;
+ }
+
/**
* Loads process-specifics configuration info with metadata provided
* by an executable.
@@ -219,7 +237,7 @@ public:
*/
void PrepareForTermination();
- void LoadModule(SharedPtr<CodeSet> module_, VAddr base_addr);
+ void LoadModule(CodeSet module_, VAddr base_addr);
///////////////////////////////////////////////////////////////////////////////////////////////
// Memory Management
@@ -297,6 +315,12 @@ private:
/// specified by metadata provided to the process during loading.
bool is_64bit_process = true;
+ /// Total running time for the process in ticks.
+ u64 total_process_running_time_ticks = 0;
+
+ /// Per-process handle table for storing created object handles in.
+ HandleTable handle_table;
+
std::string name;
};
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index cfd6e1bad..5a5f4cef1 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -10,6 +10,7 @@
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_timing.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
@@ -34,6 +35,10 @@ Thread* Scheduler::GetCurrentThread() const {
return current_thread.get();
}
+u64 Scheduler::GetLastContextSwitchTicks() const {
+ return last_context_switch_time;
+}
+
Thread* Scheduler::PopNextReadyThread() {
Thread* next = nullptr;
Thread* thread = GetCurrentThread();
@@ -54,7 +59,10 @@ Thread* Scheduler::PopNextReadyThread() {
}
void Scheduler::SwitchContext(Thread* new_thread) {
- Thread* previous_thread = GetCurrentThread();
+ Thread* const previous_thread = GetCurrentThread();
+ Process* const previous_process = Core::CurrentProcess();
+
+ UpdateLastContextSwitchTime(previous_thread, previous_process);
// Save context for previous thread
if (previous_thread) {
@@ -78,16 +86,14 @@ void Scheduler::SwitchContext(Thread* new_thread) {
// Cancel any outstanding wakeup events for this thread
new_thread->CancelWakeupTimer();
- auto previous_process = Core::CurrentProcess();
-
current_thread = new_thread;
ready_queue.remove(new_thread->GetPriority(), new_thread);
new_thread->SetStatus(ThreadStatus::Running);
- const auto thread_owner_process = current_thread->GetOwnerProcess();
+ auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
- Core::CurrentProcess() = thread_owner_process;
+ Core::System::GetInstance().Kernel().MakeCurrentProcess(thread_owner_process);
SetCurrentPageTable(&Core::CurrentProcess()->VMManager().page_table);
}
@@ -102,6 +108,22 @@ void Scheduler::SwitchContext(Thread* new_thread) {
}
}
+void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
+ const u64 prev_switch_ticks = last_context_switch_time;
+ const u64 most_recent_switch_ticks = CoreTiming::GetTicks();
+ const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
+
+ if (thread != nullptr) {
+ thread->UpdateCPUTimeTicks(update_ticks);
+ }
+
+ if (process != nullptr) {
+ process->UpdateCPUTimeTicks(update_ticks);
+ }
+
+ last_context_switch_time = most_recent_switch_ticks;
+}
+
void Scheduler::Reschedule() {
std::lock_guard<std::mutex> lock(scheduler_mutex);
diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h
index 2c94641ec..c63032b7d 100644
--- a/src/core/hle/kernel/scheduler.h
+++ b/src/core/hle/kernel/scheduler.h
@@ -17,6 +17,8 @@ class ARM_Interface;
namespace Kernel {
+class Process;
+
class Scheduler final {
public:
explicit Scheduler(Core::ARM_Interface& cpu_core);
@@ -31,6 +33,9 @@ public:
/// Gets the current running thread
Thread* GetCurrentThread() const;
+ /// Gets the timestamp for the last context switch in ticks.
+ u64 GetLastContextSwitchTicks() const;
+
/// Adds a new thread to the scheduler
void AddThread(SharedPtr<Thread> thread, u32 priority);
@@ -64,6 +69,19 @@ private:
*/
void SwitchContext(Thread* new_thread);
+ /**
+ * Called on every context switch to update the internal timestamp
+ * This also updates the running time ticks for the given thread and
+ * process using the following difference:
+ *
+ * ticks += most_recent_ticks - last_context_switch_ticks
+ *
+ * The internal tick timestamp for the scheduler is simply the
+ * most recent tick count retrieved. No special arithmetic is
+ * applied to it.
+ */
+ void UpdateLastContextSwitchTime(Thread* thread, Process* process);
+
/// Lists all thread ids that aren't deleted/etc.
std::vector<SharedPtr<Thread>> thread_list;
@@ -73,6 +91,7 @@ private:
SharedPtr<Thread> current_thread = nullptr;
Core::ARM_Interface& cpu_core;
+ u64 last_context_switch_time = 0;
static std::mutex scheduler_mutex;
};
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 3792e3e18..d6ceeb2da 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -18,7 +18,7 @@ ServerPort::~ServerPort() = default;
ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
if (pending_sessions.empty()) {
- return ERR_NO_PENDING_SESSIONS;
+ return ERR_NOT_FOUND;
}
auto session = std::move(pending_sessions.back());
@@ -28,7 +28,7 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
- return pending_sessions.size() == 0;
+ return pending_sessions.empty();
}
void ServerPort::Acquire(Thread* thread) {
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 1ece691c7..80897f3a4 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -63,7 +63,7 @@ void ServerSession::Acquire(Thread* thread) {
}
ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& context) {
- auto& domain_message_header = context.GetDomainMessageHeader();
+ auto* const domain_message_header = context.GetDomainMessageHeader();
if (domain_message_header) {
// Set domain handlers in HLE context, used for domain objects (IPC interfaces) as inputs
context.SetDomainRequestHandlers(domain_request_handlers);
@@ -107,12 +107,11 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// similar.
Kernel::HLERequestContext context(this);
u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress());
- context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(),
- kernel.HandleTable());
+ context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
ResultCode result = RESULT_SUCCESS;
// If the session has been converted to a domain, handle the domain request
- if (IsDomain() && context.GetDomainMessageHeader()) {
+ if (IsDomain() && context.HasDomainMessageHeader()) {
result = HandleDomainSyncRequest(context);
// If there is no domain header, the regular session handler is used
} else if (hle_handler != nullptr) {
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index d061e6155..a016a86b6 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -80,20 +80,19 @@ SharedPtr<SharedMemory> SharedMemory::CreateForApplet(
ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermission permissions,
MemoryPermission other_permissions) {
-
- MemoryPermission own_other_permissions =
+ const MemoryPermission own_other_permissions =
target_process == owner_process ? this->permissions : this->other_permissions;
// Automatically allocated memory blocks can only be mapped with other_permissions = DontCare
if (base_address == 0 && other_permissions != MemoryPermission::DontCare) {
- return ERR_INVALID_COMBINATION;
+ return ERR_INVALID_MEMORY_PERMISSIONS;
}
// Error out if the requested permissions don't match what the creator process allows.
if (static_cast<u32>(permissions) & ~static_cast<u32>(own_other_permissions)) {
LOG_ERROR(Kernel, "cannot map id={}, address=0x{:X} name={}, permissions don't match",
GetObjectId(), address, name);
- return ERR_INVALID_COMBINATION;
+ return ERR_INVALID_MEMORY_PERMISSIONS;
}
// Error out if the provided permissions are not compatible with what the creator process needs.
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index 6c4af7e47..c7c579aaf 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -8,6 +8,7 @@
#include <mutex>
#include <vector>
+#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
@@ -36,8 +37,72 @@
namespace Kernel {
namespace {
-constexpr bool Is4KBAligned(VAddr address) {
- return (address & 0xFFF) == 0;
+
+// Checks if address + size is greater than the given address
+// This can return false if the size causes an overflow of a 64-bit type
+// or if the given size is zero.
+constexpr bool IsValidAddressRange(VAddr address, u64 size) {
+ return address + size > address;
+}
+
+// Checks if a given address range lies within a larger address range.
+constexpr bool IsInsideAddressRange(VAddr address, u64 size, VAddr address_range_begin,
+ VAddr address_range_end) {
+ const VAddr end_address = address + size - 1;
+ return address_range_begin <= address && end_address <= address_range_end - 1;
+}
+
+bool IsInsideAddressSpace(const VMManager& vm, VAddr address, u64 size) {
+ return IsInsideAddressRange(address, size, vm.GetAddressSpaceBaseAddress(),
+ vm.GetAddressSpaceEndAddress());
+}
+
+bool IsInsideNewMapRegion(const VMManager& vm, VAddr address, u64 size) {
+ return IsInsideAddressRange(address, size, vm.GetNewMapRegionBaseAddress(),
+ vm.GetNewMapRegionEndAddress());
+}
+
+// Helper function that performs the common sanity checks for svcMapMemory
+// and svcUnmapMemory. This is doable, as both functions perform their sanitizing
+// in the same order.
+ResultCode MapUnmapMemorySanityChecks(const VMManager& vm_manager, VAddr dst_addr, VAddr src_addr,
+ u64 size) {
+ if (!Common::Is4KBAligned(dst_addr) || !Common::Is4KBAligned(src_addr)) {
+ return ERR_INVALID_ADDRESS;
+ }
+
+ if (size == 0 || !Common::Is4KBAligned(size)) {
+ return ERR_INVALID_SIZE;
+ }
+
+ if (!IsValidAddressRange(dst_addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!IsValidAddressRange(src_addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!IsInsideAddressSpace(vm_manager, src_addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
+ if (!IsInsideNewMapRegion(vm_manager, dst_addr, size)) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ const VAddr dst_end_address = dst_addr + size;
+ if (dst_end_address > vm_manager.GetHeapRegionBaseAddress() &&
+ vm_manager.GetHeapRegionEndAddress() > dst_addr) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ if (dst_end_address > vm_manager.GetMapRegionBaseAddress() &&
+ vm_manager.GetMapRegionEndAddress() > dst_addr) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return RESULT_SUCCESS;
}
} // Anonymous namespace
@@ -69,15 +134,15 @@ static ResultCode MapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
- return ERR_INVALID_ADDRESS;
- }
+ auto* const current_process = Core::CurrentProcess();
+ const auto& vm_manager = current_process->VMManager();
- if (size == 0 || !Is4KBAligned(size)) {
- return ERR_INVALID_SIZE;
+ const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
+ if (result != RESULT_SUCCESS) {
+ return result;
}
- return Core::CurrentProcess()->MirrorMemory(dst_addr, src_addr, size);
+ return current_process->MirrorMemory(dst_addr, src_addr, size);
}
/// Unmaps a region that was previously mapped with svcMapMemory
@@ -85,15 +150,15 @@ static ResultCode UnmapMemory(VAddr dst_addr, VAddr src_addr, u64 size) {
LOG_TRACE(Kernel_SVC, "called, dst_addr=0x{:X}, src_addr=0x{:X}, size=0x{:X}", dst_addr,
src_addr, size);
- if (!Is4KBAligned(dst_addr) || !Is4KBAligned(src_addr)) {
- return ERR_INVALID_ADDRESS;
- }
+ auto* const current_process = Core::CurrentProcess();
+ const auto& vm_manager = current_process->VMManager();
- if (size == 0 || !Is4KBAligned(size)) {
- return ERR_INVALID_SIZE;
+ const auto result = MapUnmapMemorySanityChecks(vm_manager, dst_addr, src_addr, size);
+ if (result != RESULT_SUCCESS) {
+ return result;
}
- return Core::CurrentProcess()->UnmapMemory(dst_addr, src_addr, size);
+ return current_process->UnmapMemory(dst_addr, src_addr, size);
}
/// Connect to an OS service given the port name, returns the handle to the port to out
@@ -124,14 +189,15 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address
CASCADE_RESULT(client_session, client_port->Connect());
// Return the client session
- CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session));
+ auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ CASCADE_RESULT(*out_handle, handle_table.Create(client_session));
return RESULT_SUCCESS;
}
/// Makes a blocking IPC call to an OS service.
static ResultCode SendSyncRequest(Handle handle) {
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<ClientSession> session = kernel.HandleTable().Get<ClientSession>(handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ SharedPtr<ClientSession> session = handle_table.Get<ClientSession>(handle);
if (!session) {
LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle);
return ERR_INVALID_HANDLE;
@@ -150,8 +216,8 @@ static ResultCode SendSyncRequest(Handle handle) {
static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
@@ -164,8 +230,8 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) {
static ResultCode GetProcessId(u32* process_id, Handle process_handle) {
LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
return ERR_INVALID_HANDLE;
}
@@ -208,11 +274,11 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
using ObjectPtr = Thread::ThreadWaitObjects::value_type;
Thread::ThreadWaitObjects objects(handle_count);
- auto& kernel = Core::System::GetInstance().Kernel();
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
for (u64 i = 0; i < handle_count; ++i) {
const Handle handle = Memory::Read32(handles_address + i * sizeof(Handle));
- const auto object = kernel.HandleTable().Get<WaitObject>(handle);
+ const auto object = handle_table.Get<WaitObject>(handle);
if (object == nullptr) {
return ERR_INVALID_HANDLE;
@@ -260,8 +326,8 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64
static ResultCode CancelSynchronization(Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
@@ -285,7 +351,11 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
return ERR_INVALID_ADDRESS_STATE;
}
- auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+ if (!Common::IsWordAligned(mutex_addr)) {
+ return ERR_INVALID_ADDRESS;
+ }
+
+ auto& handle_table = Core::CurrentProcess()->GetHandleTable();
return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
requesting_thread_handle);
}
@@ -298,16 +368,87 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
return ERR_INVALID_ADDRESS_STATE;
}
+ if (!Common::IsWordAligned(mutex_addr)) {
+ return ERR_INVALID_ADDRESS;
+ }
+
return Mutex::Release(mutex_addr);
}
+enum class BreakType : u32 {
+ Panic = 0,
+ AssertionFailed = 1,
+ PreNROLoad = 3,
+ PostNROLoad = 4,
+ PreNROUnload = 5,
+ PostNROUnload = 6,
+};
+
+struct BreakReason {
+ union {
+ u32 raw;
+ BitField<0, 30, BreakType> break_type;
+ BitField<31, 1, u32> signal_debugger;
+ };
+};
+
/// Break program execution
-static void Break(u64 reason, u64 info1, u64 info2) {
- LOG_CRITICAL(
- Debug_Emulated,
- "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
- reason, info1, info2);
- ASSERT(false);
+static void Break(u32 reason, u64 info1, u64 info2) {
+ BreakReason break_reason{reason};
+
+ switch (break_reason.break_type) {
+ case BreakType::Panic:
+ LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}",
+ info1, info2);
+ break;
+ case BreakType::AssertionFailed:
+ LOG_CRITICAL(Debug_Emulated,
+ "Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}",
+ info1, info2);
+ break;
+ case BreakType::PreNROLoad:
+ LOG_WARNING(
+ Debug_Emulated,
+ "Signalling debugger, Attempting to load an NRO at 0x{:016X} with size 0x{:016X}",
+ info1, info2);
+ break;
+ case BreakType::PostNROLoad:
+ LOG_WARNING(Debug_Emulated,
+ "Signalling debugger, Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
+ info2);
+ break;
+ case BreakType::PreNROUnload:
+ LOG_WARNING(
+ Debug_Emulated,
+ "Signalling debugger, Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}",
+ info1, info2);
+ break;
+ case BreakType::PostNROUnload:
+ LOG_WARNING(Debug_Emulated,
+ "Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
+ info2);
+ break;
+ default:
+ LOG_WARNING(
+ Debug_Emulated,
+ "Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}",
+ static_cast<u32>(break_reason.break_type.Value()), info1, info2);
+ break;
+ }
+
+ if (!break_reason.signal_debugger) {
+ LOG_CRITICAL(
+ Debug_Emulated,
+ "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}",
+ reason, info1, info2);
+ ASSERT(false);
+
+ Core::CurrentProcess()->PrepareForTermination();
+
+ // Kill the current thread
+ GetCurrentThread()->Stop();
+ Core::System::GetInstance().PrepareReschedule();
+ }
}
/// Used to output a message on a debug hardware unit - does nothing on a retail unit
@@ -326,7 +467,38 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
- const auto& current_process = Core::CurrentProcess();
+ enum class GetInfoType : u64 {
+ // 1.0.0+
+ AllowedCpuIdBitmask = 0,
+ AllowedThreadPrioBitmask = 1,
+ MapRegionBaseAddr = 2,
+ MapRegionSize = 3,
+ HeapRegionBaseAddr = 4,
+ HeapRegionSize = 5,
+ TotalMemoryUsage = 6,
+ TotalHeapUsage = 7,
+ IsCurrentProcessBeingDebugged = 8,
+ ResourceHandleLimit = 9,
+ IdleTickCount = 10,
+ RandomEntropy = 11,
+ PerformanceCounter = 0xF0000002,
+ // 2.0.0+
+ ASLRRegionBaseAddr = 12,
+ ASLRRegionSize = 13,
+ NewMapRegionBaseAddr = 14,
+ NewMapRegionSize = 15,
+ // 3.0.0+
+ IsVirtualAddressMemoryEnabled = 16,
+ PersonalMmHeapUsage = 17,
+ TitleId = 18,
+ // 4.0.0+
+ PrivilegedProcessId = 19,
+ // 5.0.0+
+ UserExceptionContextAddr = 20,
+ ThreadTickCount = 0xF0000002,
+ };
+
+ const auto* current_process = Core::CurrentProcess();
const auto& vm_manager = current_process->VMManager();
switch (static_cast<GetInfoType>(info_id)) {
@@ -360,25 +532,12 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
case GetInfoType::RandomEntropy:
*result = 0;
break;
- case GetInfoType::AddressSpaceBaseAddr:
- *result = vm_manager.GetCodeRegionBaseAddress();
+ case GetInfoType::ASLRRegionBaseAddr:
+ *result = vm_manager.GetASLRRegionBaseAddress();
break;
- case GetInfoType::AddressSpaceSize: {
- const u64 width = vm_manager.GetAddressSpaceWidth();
-
- switch (width) {
- case 32:
- *result = 0xFFE00000;
- break;
- case 36:
- *result = 0xFF8000000;
- break;
- case 39:
- *result = 0x7FF8000000;
- break;
- }
+ case GetInfoType::ASLRRegionSize:
+ *result = vm_manager.GetASLRRegionSize();
break;
- }
case GetInfoType::NewMapRegionBaseAddr:
*result = vm_manager.GetNewMapRegionBaseAddress();
break;
@@ -401,6 +560,36 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
"(STUBBED) Attempted to query user exception context address, returned 0");
*result = 0;
break;
+ case GetInfoType::ThreadTickCount: {
+ constexpr u64 num_cpus = 4;
+ if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
+ return ERR_INVALID_COMBINATION_KERNEL;
+ }
+
+ const auto thread =
+ current_process->GetHandleTable().Get<Thread>(static_cast<Handle>(handle));
+ if (!thread) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ const auto& system = Core::System::GetInstance();
+ const auto& scheduler = system.CurrentScheduler();
+ const auto* const current_thread = scheduler.GetCurrentThread();
+ const bool same_thread = current_thread == thread;
+
+ const u64 prev_ctx_ticks = scheduler.GetLastContextSwitchTicks();
+ u64 out_ticks = 0;
+ if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
+ const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
+
+ out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
+ } else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
+ out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
+ }
+
+ *result = out_ticks;
+ break;
+ }
default:
UNIMPLEMENTED();
}
@@ -418,13 +607,12 @@ static ResultCode SetThreadActivity(Handle handle, u32 unknown) {
static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
+ const auto* current_process = Core::CurrentProcess();
+ const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
- const auto current_process = Core::CurrentProcess();
if (thread->GetOwnerProcess() != current_process) {
return ERR_INVALID_HANDLE;
}
@@ -450,10 +638,11 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) {
/// Gets the priority for the specified thread
static ResultCode GetThreadPriority(u32* priority, Handle handle) {
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
- if (!thread)
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle);
+ if (!thread) {
return ERR_INVALID_HANDLE;
+ }
*priority = thread->GetPriority();
return RESULT_SUCCESS;
@@ -465,16 +654,18 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
return ERR_INVALID_THREAD_PRIORITY;
}
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle);
- if (!thread)
- return ERR_INVALID_HANDLE;
+ const auto* const current_process = Core::CurrentProcess();
// Note: The kernel uses the current process's resource limit instead of
// the one from the thread owner's resource limit.
- const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
+ const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
- return ERR_NOT_AUTHORIZED;
+ return ERR_INVALID_THREAD_PRIORITY;
+ }
+
+ SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle);
+ if (!thread) {
+ return ERR_INVALID_HANDLE;
}
thread->SetPriority(priority);
@@ -495,14 +686,18 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
"called, shared_memory_handle=0x{:X}, addr=0x{:X}, size=0x{:X}, permissions=0x{:08X}",
shared_memory_handle, addr, size, permissions);
- if (!Is4KBAligned(addr)) {
+ if (!Common::Is4KBAligned(addr)) {
return ERR_INVALID_ADDRESS;
}
- if (size == 0 || !Is4KBAligned(size)) {
+ if (size == 0 || !Common::Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
+ if (!IsValidAddressRange(addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
+
const auto permissions_type = static_cast<MemoryPermission>(permissions);
if (permissions_type != MemoryPermission::Read &&
permissions_type != MemoryPermission::ReadWrite) {
@@ -510,46 +705,61 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s
return ERR_INVALID_MEMORY_PERMISSIONS;
}
- auto& kernel = Core::System::GetInstance().Kernel();
- auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
+ auto* const current_process = Core::CurrentProcess();
+ auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
if (!shared_memory) {
return ERR_INVALID_HANDLE;
}
- return shared_memory->Map(Core::CurrentProcess().get(), addr, permissions_type,
- MemoryPermission::DontCare);
+ const auto& vm_manager = current_process->VMManager();
+ if (!vm_manager.IsWithinASLRRegion(addr, size)) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return shared_memory->Map(current_process, addr, permissions_type, MemoryPermission::DontCare);
}
static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 size) {
LOG_WARNING(Kernel_SVC, "called, shared_memory_handle=0x{:08X}, addr=0x{:X}, size=0x{:X}",
shared_memory_handle, addr, size);
- if (!Is4KBAligned(addr)) {
+ if (!Common::Is4KBAligned(addr)) {
return ERR_INVALID_ADDRESS;
}
- if (size == 0 || !Is4KBAligned(size)) {
+ if (size == 0 || !Common::Is4KBAligned(size)) {
return ERR_INVALID_SIZE;
}
- auto& kernel = Core::System::GetInstance().Kernel();
- auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle);
+ if (!IsValidAddressRange(addr, size)) {
+ return ERR_INVALID_ADDRESS_STATE;
+ }
- return shared_memory->Unmap(Core::CurrentProcess().get(), addr);
+ auto* const current_process = Core::CurrentProcess();
+ auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle);
+ if (!shared_memory) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ const auto& vm_manager = current_process->VMManager();
+ if (!vm_manager.IsWithinASLRRegion(addr, size)) {
+ return ERR_INVALID_MEMORY_RANGE;
+ }
+
+ return shared_memory->Unmap(current_process, addr);
}
/// Query process memory
static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/,
Handle process_handle, u64 addr) {
-
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ SharedPtr<Process> process = handle_table.Get<Process>(process_handle);
if (!process) {
return ERR_INVALID_HANDLE;
}
auto vma = process->VMManager().FindVMA(addr);
memory_info->attributes = 0;
- if (vma == Core::CurrentProcess()->VMManager().vma_map.end()) {
+ if (vma == process->VMManager().vma_map.end()) {
memory_info->base_address = 0;
memory_info->permission = static_cast<u32>(VMAPermission::None);
memory_info->size = 0;
@@ -573,7 +783,7 @@ static ResultCode QueryMemory(MemoryInfo* memory_info, PageInfo* page_info, VAdd
/// Exits the current process
static void ExitProcess() {
- auto& current_process = Core::CurrentProcess();
+ auto* current_process = Core::CurrentProcess();
LOG_INFO(Kernel_SVC, "Process {} exiting", current_process->GetProcessID());
ASSERT_MSG(current_process->GetStatus() == ProcessStatus::Running,
@@ -590,20 +800,19 @@ static void ExitProcess() {
/// Creates a new thread
static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top,
u32 priority, s32 processor_id) {
- std::string name = fmt::format("thread-{:X}", entry_point);
-
if (priority > THREADPRIO_LOWEST) {
return ERR_INVALID_THREAD_PRIORITY;
}
- const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit();
+ auto* const current_process = Core::CurrentProcess();
+ const ResourceLimit& resource_limit = current_process->GetResourceLimit();
if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) {
- return ERR_NOT_AUTHORIZED;
+ return ERR_INVALID_THREAD_PRIORITY;
}
if (processor_id == THREADPROCESSORID_DEFAULT) {
// Set the target CPU to the one specified in the process' exheader.
- processor_id = Core::CurrentProcess()->GetDefaultProcessorID();
+ processor_id = current_process->GetDefaultProcessorID();
ASSERT(processor_id != THREADPROCESSORID_DEFAULT);
}
@@ -618,11 +827,13 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
return ERR_INVALID_PROCESSOR_ID;
}
+ const std::string name = fmt::format("thread-{:X}", entry_point);
auto& kernel = Core::System::GetInstance().Kernel();
CASCADE_RESULT(SharedPtr<Thread> thread,
Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top,
- Core::CurrentProcess()));
- const auto new_guest_handle = kernel.HandleTable().Create(thread);
+ *current_process));
+
+ const auto new_guest_handle = current_process->GetHandleTable().Create(thread);
if (new_guest_handle.Failed()) {
return new_guest_handle.Code();
}
@@ -643,8 +854,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
static ResultCode StartThread(Handle thread_handle) {
LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
@@ -691,8 +902,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
CASCADE_CODE(Mutex::Release(mutex_addr));
@@ -721,7 +932,7 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
std::vector<SharedPtr<Thread>>& waiting_threads,
VAddr condvar_addr) {
const auto& scheduler = Core::System::GetInstance().Scheduler(core_index);
- const auto& thread_list = scheduler->GetThreadList();
+ const auto& thread_list = scheduler.GetThreadList();
for (const auto& thread : thread_list) {
if (thread->GetCondVarWaitAddress() == condvar_addr)
@@ -803,9 +1014,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
mutex_val | Mutex::MutexHasWaitersFlag));
// The mutex is already owned by some other thread, make this thread wait on it.
- auto& kernel = Core::System::GetInstance().Kernel();
- Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
- auto owner = kernel.HandleTable().Get<Thread>(owner_handle);
+ const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto owner = handle_table.Get<Thread>(owner_handle);
ASSERT(owner);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
thread->InvalidateWakeupCallback();
@@ -884,16 +1095,16 @@ static u64 GetSystemTick() {
static ResultCode CloseHandle(Handle handle) {
LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- return kernel.HandleTable().Close(handle);
+ auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ return handle_table.Close(handle);
}
/// Reset an event
static ResultCode ResetSignal(Handle handle) {
LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- auto event = kernel.HandleTable().Get<Event>(handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto event = handle_table.Get<Event>(handle);
ASSERT(event != nullptr);
@@ -912,8 +1123,8 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32
static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) {
LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
@@ -928,8 +1139,8 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) {
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle,
mask, core);
- auto& kernel = Core::System::GetInstance().Kernel();
- const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle);
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
if (!thread) {
return ERR_INVALID_HANDLE;
}
@@ -990,7 +1201,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
}
auto& kernel = Core::System::GetInstance().Kernel();
- auto& handle_table = kernel.HandleTable();
+ auto& handle_table = Core::CurrentProcess()->GetHandleTable();
auto shared_mem_handle =
SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size,
local_perms, remote_perms);
@@ -1002,14 +1213,39 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss
static ResultCode ClearEvent(Handle handle) {
LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle);
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<Event> evt = kernel.HandleTable().Get<Event>(handle);
- if (evt == nullptr)
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ SharedPtr<Event> evt = handle_table.Get<Event>(handle);
+ if (evt == nullptr) {
return ERR_INVALID_HANDLE;
+ }
+
evt->Clear();
return RESULT_SUCCESS;
}
+static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) {
+ LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, type=0x{:X}", process_handle, type);
+
+ // This function currently only allows retrieving a process' status.
+ enum class InfoType {
+ Status,
+ };
+
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ const auto process = handle_table.Get<Process>(process_handle);
+ if (!process) {
+ return ERR_INVALID_HANDLE;
+ }
+
+ const auto info_type = static_cast<InfoType>(type);
+ if (info_type != InfoType::Status) {
+ return ERR_INVALID_ENUM_VALUE;
+ }
+
+ *out = static_cast<u64>(process->GetStatus());
+ return RESULT_SUCCESS;
+}
+
namespace {
struct FunctionDef {
using Func = void();
@@ -1145,7 +1381,7 @@ static const FunctionDef SVC_Table[] = {
{0x79, nullptr, "CreateProcess"},
{0x7A, nullptr, "StartProcess"},
{0x7B, nullptr, "TerminateProcess"},
- {0x7C, nullptr, "GetProcessInfo"},
+ {0x7C, SvcWrap<GetProcessInfo>, "GetProcessInfo"},
{0x7D, nullptr, "CreateResourceLimit"},
{0x7E, nullptr, "SetResourceLimitLimitValue"},
{0x7F, nullptr, "CallSecureMonitor"},
diff --git a/src/core/hle/kernel/svc.h b/src/core/hle/kernel/svc.h
index 70148c4fe..b06aac4ec 100644
--- a/src/core/hle/kernel/svc.h
+++ b/src/core/hle/kernel/svc.h
@@ -24,37 +24,6 @@ struct PageInfo {
u64 flags;
};
-/// Values accepted by svcGetInfo
-enum class GetInfoType : u64 {
- // 1.0.0+
- AllowedCpuIdBitmask = 0,
- AllowedThreadPrioBitmask = 1,
- MapRegionBaseAddr = 2,
- MapRegionSize = 3,
- HeapRegionBaseAddr = 4,
- HeapRegionSize = 5,
- TotalMemoryUsage = 6,
- TotalHeapUsage = 7,
- IsCurrentProcessBeingDebugged = 8,
- ResourceHandleLimit = 9,
- IdleTickCount = 10,
- RandomEntropy = 11,
- PerformanceCounter = 0xF0000002,
- // 2.0.0+
- AddressSpaceBaseAddr = 12,
- AddressSpaceSize = 13,
- NewMapRegionBaseAddr = 14,
- NewMapRegionSize = 15,
- // 3.0.0+
- IsVirtualAddressMemoryEnabled = 16,
- PersonalMmHeapUsage = 17,
- TitleId = 18,
- // 4.0.0+
- PrivilegedProcessId = 19,
- // 5.0.0+
- UserExceptionContextAddr = 20,
-};
-
void CallSVC(u32 immediate);
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc_wrap.h b/src/core/hle/kernel/svc_wrap.h
index 22712e64f..b09753c80 100644
--- a/src/core/hle/kernel/svc_wrap.h
+++ b/src/core/hle/kernel/svc_wrap.h
@@ -35,18 +35,18 @@ void SvcWrap() {
template <ResultCode func(u32)>
void SvcWrap() {
- FuncReturn(func((u32)Param(0)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0))).raw);
}
template <ResultCode func(u32, u32)>
void SvcWrap() {
- FuncReturn(func((u32)Param(0), (u32)Param(1)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1))).raw);
}
template <ResultCode func(u32*, u32)>
void SvcWrap() {
u32 param_1 = 0;
- u32 retval = func(&param_1, (u32)Param(1)).raw;
+ u32 retval = func(&param_1, static_cast<u32>(Param(1))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -61,7 +61,7 @@ void SvcWrap() {
template <ResultCode func(u64, s32)>
void SvcWrap() {
- FuncReturn(func(Param(0), (s32)Param(1)).raw);
+ FuncReturn(func(Param(0), static_cast<s32>(Param(1))).raw);
}
template <ResultCode func(u64, u32)>
@@ -77,21 +77,29 @@ void SvcWrap() {
FuncReturn(retval);
}
+template <ResultCode func(u64*, u32, u32)>
+void SvcWrap() {
+ u64 param_1 = 0;
+ u32 retval = func(&param_1, static_cast<u32>(Param(1)), static_cast<u32>(Param(2))).raw;
+ Core::CurrentArmInterface().SetReg(1, param_1);
+ FuncReturn(retval);
+}
+
template <ResultCode func(u32, u64)>
void SvcWrap() {
- FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), Param(1)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0)), Param(1)).raw);
}
template <ResultCode func(u32, u32, u64)>
void SvcWrap() {
- FuncReturn(func((u32)(Param(0) & 0xFFFFFFFF), (u32)(Param(1) & 0xFFFFFFFF), Param(2)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0)), static_cast<u32>(Param(1)), Param(2)).raw);
}
template <ResultCode func(u32, u32*, u64*)>
void SvcWrap() {
u32 param_1 = 0;
u64 param_2 = 0;
- ResultCode retval = func((u32)(Param(2) & 0xFFFFFFFF), &param_1, &param_2);
+ ResultCode retval = func(static_cast<u32>(Param(2)), &param_1, &param_2);
Core::CurrentArmInterface().SetReg(1, param_1);
Core::CurrentArmInterface().SetReg(2, param_2);
FuncReturn(retval.raw);
@@ -100,12 +108,12 @@ void SvcWrap() {
template <ResultCode func(u64, u64, u32, u32)>
void SvcWrap() {
FuncReturn(
- func(Param(0), Param(1), (u32)(Param(3) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw);
+ func(Param(0), Param(1), static_cast<u32>(Param(3)), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u32, u64, u32)>
void SvcWrap() {
- FuncReturn(func((u32)Param(0), Param(1), (u32)Param(2)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0)), Param(1), static_cast<u32>(Param(2))).raw);
}
template <ResultCode func(u64, u64, u64)>
@@ -115,25 +123,28 @@ void SvcWrap() {
template <ResultCode func(u32, u64, u64, u32)>
void SvcWrap() {
- FuncReturn(func((u32)Param(0), Param(1), Param(2), (u32)Param(3)).raw);
+ FuncReturn(
+ func(static_cast<u32>(Param(0)), Param(1), Param(2), static_cast<u32>(Param(3))).raw);
}
template <ResultCode func(u32, u64, u64)>
void SvcWrap() {
- FuncReturn(func((u32)Param(0), Param(1), Param(2)).raw);
+ FuncReturn(func(static_cast<u32>(Param(0)), Param(1), Param(2)).raw);
}
template <ResultCode func(u32*, u64, u64, s64)>
void SvcWrap() {
u32 param_1 = 0;
- ResultCode retval = func(&param_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (s64)Param(3));
+ ResultCode retval =
+ func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3)));
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval.raw);
}
template <ResultCode func(u64, u64, u32, s64)>
void SvcWrap() {
- FuncReturn(func(Param(0), Param(1), (u32)Param(2), (s64)Param(3)).raw);
+ FuncReturn(
+ func(Param(0), Param(1), static_cast<u32>(Param(2)), static_cast<s64>(Param(3))).raw);
}
template <ResultCode func(u64*, u64, u64, u64)>
@@ -147,9 +158,9 @@ void SvcWrap() {
template <ResultCode func(u32*, u64, u64, u64, u32, s32)>
void SvcWrap() {
u32 param_1 = 0;
- u32 retval =
- func(&param_1, Param(1), Param(2), Param(3), (u32)Param(4), (s32)(Param(5) & 0xFFFFFFFF))
- .raw;
+ u32 retval = func(&param_1, Param(1), Param(2), Param(3), static_cast<u32>(Param(4)),
+ static_cast<s32>(Param(5)))
+ .raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -172,7 +183,7 @@ void SvcWrap() {
template <ResultCode func(u32*, u64, u64, u32)>
void SvcWrap() {
u32 param_1 = 0;
- u32 retval = func(&param_1, Param(1), Param(2), (u32)(Param(3) & 0xFFFFFFFF)).raw;
+ u32 retval = func(&param_1, Param(1), Param(2), static_cast<u32>(Param(3))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
@@ -181,22 +192,22 @@ template <ResultCode func(Handle*, u64, u32, u32)>
void SvcWrap() {
u32 param_1 = 0;
u32 retval =
- func(&param_1, Param(1), (u32)(Param(2) & 0xFFFFFFFF), (u32)(Param(3) & 0xFFFFFFFF)).raw;
+ func(&param_1, Param(1), static_cast<u32>(Param(2)), static_cast<u32>(Param(3))).raw;
Core::CurrentArmInterface().SetReg(1, param_1);
FuncReturn(retval);
}
template <ResultCode func(u64, u32, s32, s64)>
void SvcWrap() {
- FuncReturn(
- func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF), (s64)Param(3))
- .raw);
+ FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
+ static_cast<s64>(Param(3)))
+ .raw);
}
template <ResultCode func(u64, u32, s32, s32)>
void SvcWrap() {
- FuncReturn(func(Param(0), (u32)(Param(1) & 0xFFFFFFFF), (s32)(Param(2) & 0xFFFFFFFF),
- (s32)(Param(3) & 0xFFFFFFFF))
+ FuncReturn(func(Param(0), static_cast<u32>(Param(1)), static_cast<s32>(Param(2)),
+ static_cast<s32>(Param(3)))
.raw);
}
@@ -226,7 +237,7 @@ void SvcWrap() {
template <void func(s64)>
void SvcWrap() {
- func((s64)Param(0));
+ func(static_cast<s64>(Param(0)));
}
template <void func(u64, u64 len)>
@@ -239,4 +250,9 @@ void SvcWrap() {
func(Param(0), Param(1), Param(2));
}
+template <void func(u32, u64, u64)>
+void SvcWrap() {
+ func(static_cast<u32>(Param(0)), Param(1), Param(2));
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 8e514cf9a..dd5cd9ced 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -4,9 +4,9 @@
#include <algorithm>
#include <cinttypes>
+#include <optional>
#include <vector>
-#include <boost/optional.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
@@ -94,10 +94,10 @@ void Thread::CancelWakeupTimer() {
CoreTiming::UnscheduleEventThreadsafe(kernel.ThreadWakeupCallbackEventType(), callback_handle);
}
-static boost::optional<s32> GetNextProcessorId(u64 mask) {
+static std::optional<s32> GetNextProcessorId(u64 mask) {
for (s32 index = 0; index < Core::NUM_CPU_CORES; ++index) {
if (mask & (1ULL << index)) {
- if (!Core::System::GetInstance().Scheduler(index)->GetCurrentThread()) {
+ if (!Core::System::GetInstance().Scheduler(index).GetCurrentThread()) {
// Core is enabled and not running any threads, use this one
return index;
}
@@ -142,19 +142,19 @@ void Thread::ResumeFromWait() {
status = ThreadStatus::Ready;
- boost::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
+ std::optional<s32> new_processor_id = GetNextProcessorId(affinity_mask);
if (!new_processor_id) {
new_processor_id = processor_id;
}
if (ideal_core != -1 &&
- Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
+ Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
new_processor_id = ideal_core;
}
ASSERT(*new_processor_id < 4);
// Add thread to new core's scheduler
- auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
+ auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
if (*new_processor_id != processor_id) {
// Remove thread from previous core's scheduler
@@ -169,7 +169,7 @@ void Thread::ResumeFromWait() {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
- scheduler = next_scheduler.get();
+ scheduler = next_scheduler;
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
@@ -183,18 +183,15 @@ void Thread::ResumeFromWait() {
*/
static void ResetThreadContext(Core::ARM_Interface::ThreadContext& context, VAddr stack_top,
VAddr entry_point, u64 arg) {
- memset(&context, 0, sizeof(Core::ARM_Interface::ThreadContext));
-
+ context = {};
context.cpu_registers[0] = arg;
context.pc = entry_point;
context.sp = stack_top;
- context.pstate = 0;
- context.fpcr = 0;
}
ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name, VAddr entry_point,
u32 priority, u64 arg, s32 processor_id,
- VAddr stack_top, SharedPtr<Process> owner_process) {
+ VAddr stack_top, Process& owner_process) {
// Check if priority is in ranged. Lowest priority -> highest priority id.
if (priority > THREADPRIO_LOWEST) {
LOG_ERROR(Kernel_SVC, "Invalid thread priority: {}", priority);
@@ -208,7 +205,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
// TODO(yuriks): Other checks, returning 0xD9001BEA
- if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) {
+ if (!Memory::IsValidVirtualAddress(owner_process, entry_point)) {
LOG_ERROR(Kernel_SVC, "(name={}): invalid entry {:016X}", name, entry_point);
// TODO (bunnei): Find the correct error code to use here
return ResultCode(-1);
@@ -232,8 +229,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
thread->wait_handle = 0;
thread->name = std::move(name);
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
- thread->owner_process = owner_process;
- thread->scheduler = Core::System::GetInstance().Scheduler(processor_id).get();
+ thread->owner_process = &owner_process;
+ thread->scheduler = &Core::System::GetInstance().Scheduler(processor_id);
thread->scheduler->AddThread(thread, priority);
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
@@ -264,12 +261,12 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri
// Initialize new "main" thread
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
auto thread_res = Thread::Create(kernel, "main", entry_point, priority, 0, THREADPROCESSORID_0,
- stack_top, &owner_process);
+ stack_top, owner_process);
SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
// Register 1 must be a handle to the main thread
- const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap();
+ const Handle guest_handle = owner_process.GetHandleTable().Create(thread).Unwrap();
thread->SetGuestHandle(guest_handle);
thread->GetContext().cpu_registers[1] = guest_handle;
@@ -372,20 +369,20 @@ void Thread::ChangeCore(u32 core, u64 mask) {
return;
}
- boost::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
+ std::optional<s32> new_processor_id{GetNextProcessorId(affinity_mask)};
if (!new_processor_id) {
new_processor_id = processor_id;
}
if (ideal_core != -1 &&
- Core::System::GetInstance().Scheduler(ideal_core)->GetCurrentThread() == nullptr) {
+ Core::System::GetInstance().Scheduler(ideal_core).GetCurrentThread() == nullptr) {
new_processor_id = ideal_core;
}
ASSERT(*new_processor_id < 4);
// Add thread to new core's scheduler
- auto& next_scheduler = Core::System::GetInstance().Scheduler(*new_processor_id);
+ auto* next_scheduler = &Core::System::GetInstance().Scheduler(*new_processor_id);
if (*new_processor_id != processor_id) {
// Remove thread from previous core's scheduler
@@ -400,7 +397,7 @@ void Thread::ChangeCore(u32 core, u64 mask) {
next_scheduler->ScheduleThread(this, current_priority);
// Change thread's scheduler
- scheduler = next_scheduler.get();
+ scheduler = next_scheduler;
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
}
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index c6ffbd28c..4a6e11239 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -89,7 +89,7 @@ public:
static ResultVal<SharedPtr<Thread>> Create(KernelCore& kernel, std::string name,
VAddr entry_point, u32 priority, u64 arg,
s32 processor_id, VAddr stack_top,
- SharedPtr<Process> owner_process);
+ Process& owner_process);
std::string GetName() const override {
return name;
@@ -258,15 +258,23 @@ public:
return last_running_ticks;
}
+ u64 GetTotalCPUTimeTicks() const {
+ return total_cpu_time_ticks;
+ }
+
+ void UpdateCPUTimeTicks(u64 ticks) {
+ total_cpu_time_ticks += ticks;
+ }
+
s32 GetProcessorID() const {
return processor_id;
}
- SharedPtr<Process>& GetOwnerProcess() {
+ Process* GetOwnerProcess() {
return owner_process;
}
- const SharedPtr<Process>& GetOwnerProcess() const {
+ const Process* GetOwnerProcess() const {
return owner_process;
}
@@ -378,7 +386,8 @@ private:
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
- u64 last_running_ticks = 0; ///< CPU tick when thread was last running
+ u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
+ u64 last_running_ticks = 0; ///< CPU tick when thread was last running
s32 processor_id = 0;
@@ -386,7 +395,7 @@ private:
u64 tpidr_el0 = 0; ///< TPIDR_EL0 read/write system register.
/// Process that owns this thread
- SharedPtr<Process> owner_process;
+ Process* owner_process;
/// Objects that the thread is waiting on, in the same order as they were
/// passed to WaitSynchronization1/N.
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index e412309fd..1a92c8f70 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -143,6 +143,26 @@ ResultVal<VMManager::VMAHandle> VMManager::MapBackingMemory(VAddr target, u8* me
return MakeResult<VMAHandle>(MergeAdjacent(vma_handle));
}
+ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
+ // Find the first Free VMA.
+ const VAddr base = GetASLRRegionBaseAddress();
+ const VMAHandle vma_handle = std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
+ if (vma.second.type != VMAType::Free)
+ return false;
+
+ const VAddr vma_end = vma.second.base + vma.second.size;
+ return vma_end > base && vma_end >= base + size;
+ });
+
+ if (vma_handle == vma_map.end()) {
+ // TODO(Subv): Find the correct error code here.
+ return ResultCode(-1);
+ }
+
+ const VAddr target = std::max(base, vma_handle->second.base);
+ return MakeResult<VAddr>(target);
+}
+
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
MemoryState state,
Memory::MemoryHookPointer mmio_handler) {
@@ -393,30 +413,35 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
switch (type) {
case FileSys::ProgramAddressSpaceType::Is32Bit:
+ case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
address_space_width = 32;
code_region_base = 0x200000;
code_region_end = code_region_base + 0x3FE00000;
- map_region_size = 0x40000000;
- heap_region_size = 0x40000000;
+ aslr_region_base = 0x200000;
+ aslr_region_end = aslr_region_base + 0xFFE00000;
+ if (type == FileSys::ProgramAddressSpaceType::Is32Bit) {
+ map_region_size = 0x40000000;
+ heap_region_size = 0x40000000;
+ } else {
+ map_region_size = 0;
+ heap_region_size = 0x80000000;
+ }
break;
case FileSys::ProgramAddressSpaceType::Is36Bit:
address_space_width = 36;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x78000000;
+ aslr_region_base = 0x8000000;
+ aslr_region_end = aslr_region_base + 0xFF8000000;
map_region_size = 0x180000000;
heap_region_size = 0x180000000;
break;
- case FileSys::ProgramAddressSpaceType::Is32BitNoMap:
- address_space_width = 32;
- code_region_base = 0x200000;
- code_region_end = code_region_base + 0x3FE00000;
- map_region_size = 0;
- heap_region_size = 0x80000000;
- break;
case FileSys::ProgramAddressSpaceType::Is39Bit:
address_space_width = 39;
code_region_base = 0x8000000;
code_region_end = code_region_base + 0x80000000;
+ aslr_region_base = 0x8000000;
+ aslr_region_end = aslr_region_base + 0x7FF8000000;
map_region_size = 0x1000000000;
heap_region_size = 0x180000000;
new_map_region_size = 0x80000000;
@@ -490,6 +515,38 @@ u64 VMManager::GetAddressSpaceWidth() const {
return address_space_width;
}
+VAddr VMManager::GetASLRRegionBaseAddress() const {
+ return aslr_region_base;
+}
+
+VAddr VMManager::GetASLRRegionEndAddress() const {
+ return aslr_region_end;
+}
+
+u64 VMManager::GetASLRRegionSize() const {
+ return aslr_region_end - aslr_region_base;
+}
+
+bool VMManager::IsWithinASLRRegion(VAddr begin, u64 size) const {
+ const VAddr range_end = begin + size;
+ const VAddr aslr_start = GetASLRRegionBaseAddress();
+ const VAddr aslr_end = GetASLRRegionEndAddress();
+
+ if (aslr_start > begin || begin > range_end || range_end - 1 > aslr_end - 1) {
+ return false;
+ }
+
+ if (range_end > heap_region_base && heap_region_end > begin) {
+ return false;
+ }
+
+ if (range_end > map_region_base && map_region_end > begin) {
+ return false;
+ }
+
+ return true;
+}
+
VAddr VMManager::GetCodeRegionBaseAddress() const {
return code_region_base;
}
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 015559a64..2447cbb8f 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -158,6 +158,14 @@ public:
ResultVal<VMAHandle> MapBackingMemory(VAddr target, u8* memory, u64 size, MemoryState state);
/**
+ * Finds the first free address that can hold a region of the desired size.
+ *
+ * @param size Size of the desired region.
+ * @return The found free address.
+ */
+ ResultVal<VAddr> FindFreeRegion(u64 size) const;
+
+ /**
* Maps a memory-mapped IO region at a given address.
*
* @param target The guest address to start the mapping at.
@@ -205,6 +213,18 @@ public:
/// Gets the address space width in bits.
u64 GetAddressSpaceWidth() const;
+ /// Gets the base address of the ASLR region.
+ VAddr GetASLRRegionBaseAddress() const;
+
+ /// Gets the end address of the ASLR region.
+ VAddr GetASLRRegionEndAddress() const;
+
+ /// Determines whether or not the specified address range is within the ASLR region.
+ bool IsWithinASLRRegion(VAddr address, u64 size) const;
+
+ /// Gets the size of the ASLR region
+ u64 GetASLRRegionSize() const;
+
/// Gets the base address of the code region.
VAddr GetCodeRegionBaseAddress() const;
@@ -306,6 +326,9 @@ private:
VAddr address_space_base = 0;
VAddr address_space_end = 0;
+ VAddr aslr_region_base = 0;
+ VAddr aslr_region_end = 0;
+
VAddr code_region_base = 0;
VAddr code_region_end = 0;
diff --git a/src/core/hle/service/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index e61748ca3..c6437a671 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -2,9 +2,13 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
+#include "common/common_paths.h"
#include "common/common_types.h"
+#include "common/file_util.h"
#include "common/logging/log.h"
+#include "common/string_util.h"
#include "common/swap.h"
#include "core/core_timing.h"
#include "core/hle/ipc_helpers.h"
@@ -16,6 +20,7 @@
#include "core/hle/service/acc/profile_manager.h"
namespace Service::Account {
+
// TODO: RE this structure
struct UserData {
INSERT_PADDING_WORDS(1);
@@ -27,6 +32,29 @@ struct UserData {
};
static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size");
+// Smallest JPEG https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
+// used as a backup should the one on disk not exist
+constexpr u32 backup_jpeg_size = 107;
+constexpr std::array<u8, backup_jpeg_size> backup_jpeg{{
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
+ 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
+ 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
+ 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
+ 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+}};
+
+static std::string GetImagePath(UUID uuid) {
+ return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
+}
+
+static constexpr u32 SanitizeJPEGSize(std::size_t size) {
+ constexpr std::size_t max_jpeg_image_size = 0x20000;
+ return static_cast<u32>(std::min(size, max_jpeg_image_size));
+}
+
class IProfile final : public ServiceFramework<IProfile> {
public:
explicit IProfile(UUID user_id, ProfileManager& profile_manager)
@@ -73,32 +101,42 @@ private:
}
void LoadImage(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- // smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg
- // TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000
- constexpr u32 jpeg_size = 107;
- static constexpr std::array<u8, jpeg_size> jpeg{
- 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03,
- 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04,
- 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a,
- 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f,
- 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10,
- 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, 0x01, 0x01, 0x11, 0x00,
- 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01,
- 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
- };
- ctx.WriteBuffer(jpeg);
+ LOG_DEBUG(Service_ACC, "called");
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(jpeg_size);
+
+ const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+ if (!image.IsOpen()) {
+ LOG_WARNING(Service_ACC,
+ "Failed to load user provided image! Falling back to built-in backup...");
+ ctx.WriteBuffer(backup_jpeg);
+ rb.Push<u32>(backup_jpeg_size);
+ return;
+ }
+
+ const u32 size = SanitizeJPEGSize(image.GetSize());
+ std::vector<u8> buffer(size);
+ image.ReadBytes(buffer.data(), buffer.size());
+
+ ctx.WriteBuffer(buffer.data(), buffer.size());
+ rb.Push<u32>(size);
}
void GetImageSize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_ACC, "(STUBBED) called");
- constexpr u32 jpeg_size = 107;
+ LOG_DEBUG(Service_ACC, "called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(jpeg_size);
+
+ const FileUtil::IOFile image(GetImagePath(user_id), "rb");
+
+ if (!image.IsOpen()) {
+ LOG_WARNING(Service_ACC,
+ "Failed to load user provided image! Falling back to built-in backup...");
+ rb.Push<u32>(backup_jpeg_size);
+ } else {
+ rb.Push<u32>(SanitizeJPEGSize(image.GetSize()));
+ }
}
const ProfileManager& profile_manager;
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index bcb3475db..c08394e4c 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -3,41 +3,66 @@
// Refer to the license.txt file included.
#include <random>
-#include <boost/optional.hpp>
+
+#include "common/file_util.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
namespace Service::Account {
+
+struct UserRaw {
+ UUID uuid;
+ UUID uuid2;
+ u64 timestamp;
+ ProfileUsername username;
+ INSERT_PADDING_BYTES(0x80);
+};
+static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size.");
+
+struct ProfileDataRaw {
+ INSERT_PADDING_BYTES(0x10);
+ std::array<UserRaw, MAX_USERS> users;
+};
+static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size.");
+
// TODO(ogniK): Get actual error codes
constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1);
constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2);
constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20);
-const UUID& UUID::Generate() {
+constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/";
+
+UUID UUID::Generate() {
std::random_device device;
std::mt19937 gen(device());
std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max());
- uuid[0] = distribution(gen);
- uuid[1] = distribution(gen);
- return *this;
+ return UUID{distribution(gen), distribution(gen)};
}
ProfileManager::ProfileManager() {
- // TODO(ogniK): Create the default user we have for now until loading/saving users is added
- auto user_uuid = UUID{1, 0};
- ASSERT(CreateNewUser(user_uuid, Settings::values.username).IsSuccess());
- OpenUser(user_uuid);
+ ParseUserSaveFile();
+
+ if (user_count == 0)
+ CreateNewUser(UUID::Generate(), "yuzu");
+
+ auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1);
+ if (UserExistsIndex(current))
+ current = 0;
+
+ OpenUser(*GetUser(current));
}
-ProfileManager::~ProfileManager() = default;
+ProfileManager::~ProfileManager() {
+ WriteUserSaveFile();
+}
/// After a users creation it needs to be "registered" to the system. AddToProfiles handles the
/// internal management of the users profiles
-boost::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& user) {
+std::optional<std::size_t> ProfileManager::AddToProfiles(const ProfileInfo& profile) {
if (user_count >= MAX_USERS) {
- return boost::none;
+ return {};
}
- profiles[user_count] = user;
+ profiles[user_count] = profile;
return user_count++;
}
@@ -56,7 +81,7 @@ bool ProfileManager::RemoveProfileAtIndex(std::size_t index) {
/// Helper function to register a user to the system
ResultCode ProfileManager::AddUser(const ProfileInfo& user) {
- if (AddToProfiles(user) == boost::none) {
+ if (!AddToProfiles(user)) {
return ERROR_TOO_MANY_USERS;
}
return RESULT_SUCCESS;
@@ -101,31 +126,40 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username)
return CreateNewUser(uuid, username_output);
}
+std::optional<UUID> ProfileManager::GetUser(std::size_t index) const {
+ if (index >= MAX_USERS) {
+ return {};
+ }
+
+ return profiles[index].user_uuid;
+}
+
/// Returns a users profile index based on their user id.
-boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
+std::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const {
if (!uuid) {
- return boost::none;
+ return {};
}
- auto iter = std::find_if(profiles.begin(), profiles.end(),
- [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
+
+ const auto iter = std::find_if(profiles.begin(), profiles.end(),
+ [&uuid](const ProfileInfo& p) { return p.user_uuid == uuid; });
if (iter == profiles.end()) {
- return boost::none;
+ return {};
}
+
return static_cast<std::size_t>(std::distance(profiles.begin(), iter));
}
/// Returns a users profile index based on their profile
-boost::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
+std::optional<std::size_t> ProfileManager::GetUserIndex(const ProfileInfo& user) const {
return GetUserIndex(user.user_uuid);
}
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
-bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
- ProfileBase& profile) const {
- if (index == boost::none || index >= MAX_USERS) {
+bool ProfileManager::GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const {
+ if (!index || index >= MAX_USERS) {
return false;
}
- const auto& prof_info = profiles[index.get()];
+ const auto& prof_info = profiles[*index];
profile.user_uuid = prof_info.user_uuid;
profile.username = prof_info.username;
profile.timestamp = prof_info.creation_time;
@@ -134,7 +168,7 @@ bool ProfileManager::GetProfileBase(boost::optional<std::size_t> index,
/// Returns the data structure used by the switch when GetProfileBase is called on acc:*
bool ProfileManager::GetProfileBase(UUID uuid, ProfileBase& profile) const {
- auto idx = GetUserIndex(uuid);
+ const auto idx = GetUserIndex(uuid);
return GetProfileBase(idx, profile);
}
@@ -161,26 +195,34 @@ std::size_t ProfileManager::GetOpenUserCount() const {
/// Checks if a user id exists in our profile manager
bool ProfileManager::UserExists(UUID uuid) const {
- return (GetUserIndex(uuid) != boost::none);
+ return GetUserIndex(uuid).has_value();
+}
+
+bool ProfileManager::UserExistsIndex(std::size_t index) const {
+ if (index >= MAX_USERS)
+ return false;
+ return profiles[index].user_uuid.uuid != INVALID_UUID;
}
/// Opens a specific user
void ProfileManager::OpenUser(UUID uuid) {
- auto idx = GetUserIndex(uuid);
- if (idx == boost::none) {
+ const auto idx = GetUserIndex(uuid);
+ if (!idx) {
return;
}
- profiles[idx.get()].is_open = true;
+
+ profiles[*idx].is_open = true;
last_opened_user = uuid;
}
/// Closes a specific user
void ProfileManager::CloseUser(UUID uuid) {
- auto idx = GetUserIndex(uuid);
- if (idx == boost::none) {
+ const auto idx = GetUserIndex(uuid);
+ if (!idx) {
return;
}
- profiles[idx.get()].is_open = false;
+
+ profiles[*idx].is_open = false;
}
/// Gets all valid user ids on the system
@@ -210,10 +252,10 @@ UUID ProfileManager::GetLastOpenedUser() const {
}
/// Return the users profile base and the unknown arbitary data.
-bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
+bool ProfileManager::GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const {
if (GetProfileBase(index, profile)) {
- data = profiles[index.get()].data;
+ data = profiles[*index].data;
return true;
}
return false;
@@ -222,7 +264,7 @@ bool ProfileManager::GetProfileBaseAndData(boost::optional<std::size_t> index, P
/// Return the users profile base and the unknown arbitary data.
bool ProfileManager::GetProfileBaseAndData(UUID uuid, ProfileBase& profile,
ProfileData& data) const {
- auto idx = GetUserIndex(uuid);
+ const auto idx = GetUserIndex(uuid);
return GetProfileBaseAndData(idx, profile, data);
}
@@ -239,4 +281,96 @@ bool ProfileManager::CanSystemRegisterUser() const {
// emulate qlaunch. Update this to dynamically change.
}
+bool ProfileManager::RemoveUser(UUID uuid) {
+ const auto index = GetUserIndex(uuid);
+ if (!index) {
+ return false;
+ }
+
+ profiles[*index] = ProfileInfo{};
+ std::stable_partition(profiles.begin(), profiles.end(),
+ [](const ProfileInfo& profile) { return profile.user_uuid; });
+ return true;
+}
+
+bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
+ const auto index = GetUserIndex(uuid);
+ if (!index || profile_new.user_uuid == UUID(INVALID_UUID)) {
+ return false;
+ }
+
+ auto& profile = profiles[*index];
+ profile.user_uuid = profile_new.user_uuid;
+ profile.username = profile_new.username;
+ profile.creation_time = profile_new.timestamp;
+
+ return true;
+}
+
+void ProfileManager::ParseUserSaveFile() {
+ FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
+ "rb");
+
+ if (!save.IsOpen()) {
+ LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new "
+ "user 'yuzu' with random UUID.");
+ return;
+ }
+
+ ProfileDataRaw data;
+ if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) {
+ LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user "
+ "'yuzu' with random UUID.");
+ return;
+ }
+
+ for (std::size_t i = 0; i < MAX_USERS; ++i) {
+ const auto& user = data.users[i];
+
+ if (user.uuid != UUID(INVALID_UUID))
+ AddUser({user.uuid, user.username, user.timestamp, {}, false});
+ }
+
+ std::stable_partition(profiles.begin(), profiles.end(),
+ [](const ProfileInfo& profile) { return profile.user_uuid; });
+}
+
+void ProfileManager::WriteUserSaveFile() {
+ ProfileDataRaw raw{};
+
+ for (std::size_t i = 0; i < MAX_USERS; ++i) {
+ raw.users[i].username = profiles[i].username;
+ raw.users[i].uuid2 = profiles[i].user_uuid;
+ raw.users[i].uuid = profiles[i].user_uuid;
+ raw.users[i].timestamp = profiles[i].creation_time;
+ }
+
+ const auto raw_path =
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010";
+ if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path))
+ FileUtil::Delete(raw_path);
+
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat";
+
+ if (!FileUtil::CreateFullPath(path)) {
+ LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory "
+ "nand/system/save/8000000000000010/su/avators to mitigate this "
+ "issue.");
+ return;
+ }
+
+ FileUtil::IOFile save(path, "wb");
+
+ if (!save.IsOpen()) {
+ LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data "
+ "made in current session will be saved.");
+ return;
+ }
+
+ save.Resize(sizeof(ProfileDataRaw));
+ save.WriteBytes(&raw, sizeof(ProfileDataRaw));
+}
+
}; // namespace Service::Account
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index bffd4cf4d..747c46c20 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -5,8 +5,8 @@
#pragma once
#include <array>
+#include <optional>
-#include "boost/optional.hpp"
#include "common/common_types.h"
#include "common/swap.h"
#include "core/hle/result.h"
@@ -36,7 +36,7 @@ struct UUID {
}
// TODO(ogniK): Properly generate uuids based on RFC-4122
- const UUID& Generate();
+ static UUID Generate();
// Set the UUID to {0,0} to be considered an invalid user
void Invalidate() {
@@ -45,10 +45,20 @@ struct UUID {
std::string Format() const {
return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]);
}
+
+ std::string FormatSwitch() const {
+ std::array<u8, 16> s{};
+ std::memcpy(s.data(), uuid.data(), sizeof(u128));
+ return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{"
+ ":02x}{:02x}{:02x}{:02x}{:02x}",
+ s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11],
+ s[12], s[13], s[14], s[15]);
+ }
};
static_assert(sizeof(UUID) == 16, "UUID is an invalid size!");
-using ProfileUsername = std::array<u8, 0x20>;
+constexpr std::size_t profile_username_size = 32;
+using ProfileUsername = std::array<u8, profile_username_size>;
using ProfileData = std::array<u8, MAX_DATA>;
using UserIDArray = std::array<UUID, MAX_USERS>;
@@ -81,18 +91,19 @@ static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size");
/// objects
class ProfileManager {
public:
- ProfileManager(); // TODO(ogniK): Load from system save
+ ProfileManager();
~ProfileManager();
ResultCode AddUser(const ProfileInfo& user);
ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username);
ResultCode CreateNewUser(UUID uuid, const std::string& username);
- boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
- boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
- bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const;
+ std::optional<UUID> GetUser(std::size_t index) const;
+ std::optional<std::size_t> GetUserIndex(const UUID& uuid) const;
+ std::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const;
+ bool GetProfileBase(std::optional<std::size_t> index, ProfileBase& profile) const;
bool GetProfileBase(UUID uuid, ProfileBase& profile) const;
bool GetProfileBase(const ProfileInfo& user, ProfileBase& profile) const;
- bool GetProfileBaseAndData(boost::optional<std::size_t> index, ProfileBase& profile,
+ bool GetProfileBaseAndData(std::optional<std::size_t> index, ProfileBase& profile,
ProfileData& data) const;
bool GetProfileBaseAndData(UUID uuid, ProfileBase& profile, ProfileData& data) const;
bool GetProfileBaseAndData(const ProfileInfo& user, ProfileBase& profile,
@@ -100,6 +111,7 @@ public:
std::size_t GetUserCount() const;
std::size_t GetOpenUserCount() const;
bool UserExists(UUID uuid) const;
+ bool UserExistsIndex(std::size_t index) const;
void OpenUser(UUID uuid);
void CloseUser(UUID uuid);
UserIDArray GetOpenUsers() const;
@@ -108,11 +120,17 @@ public:
bool CanSystemRegisterUser() const;
+ bool RemoveUser(UUID uuid);
+ bool SetProfileBase(UUID uuid, const ProfileBase& profile_new);
+
private:
+ void ParseUserSaveFile();
+ void WriteUserSaveFile();
+ std::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
+ bool RemoveProfileAtIndex(std::size_t index);
+
std::array<ProfileInfo, MAX_USERS> profiles{};
std::size_t user_count = 0;
- boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile);
- bool RemoveProfileAtIndex(std::size_t index);
UUID last_opened_user{INVALID_UUID};
};
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 69bfce1c1..ac3ff9f20 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -4,17 +4,20 @@
#include <array>
#include <cinttypes>
+#include <cstring>
#include <stack>
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
#include "core/hle/service/am/applet_oe.h"
#include "core/hle/service/am/idle.h"
#include "core/hle/service/am/omm.h"
#include "core/hle/service/am/spsm.h"
+#include "core/hle/service/am/tcap.h"
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nvflinger/nvflinger.h"
@@ -25,14 +28,29 @@
namespace Service::AM {
+constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
+
+struct LaunchParameters {
+ u32_le magic;
+ u32_le is_account_selected;
+ u128 current_user;
+ INSERT_PADDING_BYTES(0x70);
+};
+static_assert(sizeof(LaunchParameters) == 0x88);
+
IWindowController::IWindowController() : ServiceFramework("IWindowController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateWindow"},
{1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"},
{10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"},
{11, nullptr, "ReleaseForegroundRights"},
{12, nullptr, "RejectToChangeIntoBackground"},
+ {20, nullptr, "SetAppletWindowVisibility"},
+ {21, nullptr, "SetAppletGpuTimeSlice"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -87,6 +105,7 @@ void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestCo
}
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLastForegroundCaptureImage"},
{1, nullptr, "UpdateLastForegroundCaptureImage"},
@@ -117,7 +136,11 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
{25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"},
{26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"},
{27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"},
+ // 6.0.0+
+ {28, nullptr, "TakeScreenShotOfOwnLayerEx"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -128,6 +151,7 @@ IDebugFunctions::~IDebugFunctions() = default;
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Exit"},
{1, &ISelfController::LockExit, "LockExit"},
@@ -136,10 +160,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{4, nullptr, "LeaveFatalSection"},
{9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"},
{10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"},
- {11, &ISelfController::SetOperationModeChangedNotification,
- "SetOperationModeChangedNotification"},
- {12, &ISelfController::SetPerformanceModeChangedNotification,
- "SetPerformanceModeChangedNotification"},
+ {11, &ISelfController::SetOperationModeChangedNotification, "SetOperationModeChangedNotification"},
+ {12, &ISelfController::SetPerformanceModeChangedNotification, "SetPerformanceModeChangedNotification"},
{13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"},
{14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"},
{15, nullptr, "SetScreenShotAppletIdentityInfo"},
@@ -165,7 +187,12 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
{69, nullptr, "IsAutoSleepDisabled"},
{70, nullptr, "ReportMultimediaError"},
{80, nullptr, "SetWirelessPriorityMode"},
+ {90, nullptr, "GetAccumulatedSuspendedTickValue"},
+ {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"},
+ {1000, nullptr, "GetDebugStorageChannel"},
};
+ // clang-format on
+
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
@@ -312,6 +339,7 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c
}
ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"},
{1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"},
@@ -336,11 +364,12 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter"
{52, nullptr, "SwitchLcdBacklight"},
{55, nullptr, "IsInControllerFirmwareUpdateSection"},
{60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"},
- {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent,
- "GetDefaultDisplayResolutionChangeEvent"},
+ {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, "GetDefaultDisplayResolutionChangeEvent"},
{62, nullptr, "GetHdcpAuthenticationState"},
{63, nullptr, "GetHdcpAuthenticationStateChangeEvent"},
};
+ // clang-format on
+
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
@@ -432,11 +461,14 @@ class IStorageAccessor final : public ServiceFramework<IStorageAccessor> {
public:
explicit IStorageAccessor(std::vector<u8> buffer)
: ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IStorageAccessor::GetSize, "GetSize"},
{10, &IStorageAccessor::Write, "Write"},
{11, &IStorageAccessor::Read, "Read"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -489,10 +521,13 @@ class IStorage final : public ServiceFramework<IStorage> {
public:
explicit IStorage(std::vector<u8> buffer)
: ServiceFramework("IStorage"), buffer(std::move(buffer)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IStorage::Open, "Open"},
{1, nullptr, "OpenTransferStorage"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -512,6 +547,7 @@ private:
class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> {
public:
explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"},
{1, nullptr, "IsCompleted"},
@@ -532,6 +568,8 @@ public:
{150, nullptr, "RequestForAppletToGetForeground"},
{160, nullptr, "GetIndirectLayerConsumerHandle"},
};
+ // clang-format on
+
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
@@ -624,13 +662,13 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) {
}
IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") {
+ // clang-format off
static const FunctionInfo functions[] = {
{1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
{10, nullptr, "CreateApplicationAndPushAndRequestToStart"},
{11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"},
{12, nullptr, "CreateApplicationAndRequestToStart"},
- {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest,
- "CreateApplicationAndRequestToStartForQuest"},
+ {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest, "CreateApplicationAndRequestToStartForQuest"},
{20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"},
{21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"},
{22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"},
@@ -638,10 +676,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
{24, nullptr, "GetLaunchStorageInfoForDebug"},
{25, nullptr, "ExtendSaveData"},
{26, nullptr, "GetSaveDataSize"},
- {30, nullptr, "BeginBlockingHomeButtonShortAndLongPressed"},
- {31, nullptr, "EndBlockingHomeButtonShortAndLongPressed"},
- {32, nullptr, "BeginBlockingHomeButton"},
- {33, nullptr, "EndBlockingHomeButton"},
+ {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
+ {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
+ {32, &IApplicationFunctions::BeginBlockingHomeButton, "BeginBlockingHomeButton"},
+ {33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"},
{40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"},
{50, &IApplicationFunctions::GetPseudoDeviceId, "GetPseudoDeviceId"},
{60, nullptr, "SetMediaPlaybackStateForApplication"},
@@ -664,26 +702,57 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
{1000, nullptr, "CreateMovieMaker"},
{1001, nullptr, "PrepareForJit"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
IApplicationFunctions::~IApplicationFunctions() = default;
+void IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed(
+ Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed(
+ Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::BeginBlockingHomeButton(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
+void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_AM, "(STUBBED) called");
+}
+
void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) {
- constexpr std::array<u8, 0x88> data{{
- 0xca, 0x97, 0x94, 0xc7, // Magic
- 1, 0, 0, 0, // IsAccountSelected (bool)
- 1, 0, 0, 0, // User Id (word 0)
- 0, 0, 0, 0, // User Id (word 1)
- 0, 0, 0, 0, // User Id (word 2)
- 0, 0, 0, 0 // User Id (word 3)
- }};
+ LaunchParameters params{};
- std::vector<u8> buffer(data.begin(), data.end());
+ params.magic = POP_LAUNCH_PARAMETER_MAGIC;
+ params.is_account_selected = 1;
+
+ Account::ProfileManager profile_manager{};
+ const auto uuid = profile_manager.GetUser(Settings::values.current_user);
+ ASSERT(uuid);
+ params.current_user = uuid->uuid;
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
+
+ std::vector<u8> buffer(sizeof(LaunchParameters));
+ std::memcpy(buffer.data(), &params, buffer.size());
+
rb.PushIpcInterface<AM::IStorage>(buffer);
LOG_DEBUG(Service_AM, "called");
@@ -776,9 +845,11 @@ void InstallInterfaces(SM::ServiceManager& service_manager,
std::make_shared<IdleSys>()->InstallAsService(service_manager);
std::make_shared<OMM>()->InstallAsService(service_manager);
std::make_shared<SPSM>()->InstallAsService(service_manager);
+ std::make_shared<TCAP>()->InstallAsService(service_manager);
}
IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") {
+ // clang-format off
static const FunctionInfo functions[] = {
{10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"},
{11, nullptr, "LockForeground"},
@@ -787,7 +858,10 @@ IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions"
{21, nullptr, "GetPopFromGeneralChannelEvent"},
{30, nullptr, "GetHomeButtonWriterLockAccessor"},
{31, nullptr, "GetWriterLockAccessorEx"},
+ {100, nullptr, "PopRequestLaunchApplicationForDebug"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -800,6 +874,7 @@ void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx)
}
IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "RequestToEnterSleep"},
{1, nullptr, "EnterSleep"},
@@ -813,18 +888,23 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat
{14, nullptr, "ShouldSleepOnBoot"},
{15, nullptr, "GetHdcpAuthenticationFailedEvent"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
IGlobalStateController::~IGlobalStateController() = default;
IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "CreateApplication"},
{1, nullptr, "PopLaunchRequestedApplication"},
{10, nullptr, "CreateSystemApplication"},
{100, nullptr, "PopFloatingApplicationForDevelopment"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
@@ -832,6 +912,7 @@ IApplicationCreator::~IApplicationCreator() = default;
IProcessWindingController::IProcessWindingController()
: ServiceFramework("IProcessWindingController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "GetLaunchReason"},
{11, nullptr, "OpenCallingLibraryApplet"},
@@ -842,6 +923,8 @@ IProcessWindingController::IProcessWindingController()
{40, nullptr, "ReserveToStartAndWaitAndUnwindThis"},
{41, nullptr, "ReserveToStartAndWait"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index b39b0d838..095f94851 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -154,6 +154,10 @@ private:
void SetGamePlayRecordingState(Kernel::HLERequestContext& ctx);
void NotifyRunning(Kernel::HLERequestContext& ctx);
void GetPseudoDeviceId(Kernel::HLERequestContext& ctx);
+ void BeginBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx);
+ void EndBlockingHomeButtonShortAndLongPressed(Kernel::HLERequestContext& ctx);
+ void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
+ void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
};
class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index 4296c255e..68ea778e8 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -211,6 +211,7 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) {
AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"},
{200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"},
@@ -218,7 +219,10 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
{300, nullptr, "OpenOverlayAppletProxy"},
{350, nullptr, "OpenSystemApplicationProxy"},
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
+ {401, nullptr, "GetSystemAppletControllerForDebug"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index e45cf6e20..60717afd9 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -14,6 +14,7 @@ class IApplicationProxy final : public ServiceFramework<IApplicationProxy> {
public:
explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
: ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)) {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"},
{1, &IApplicationProxy::GetSelfController, "GetSelfController"},
@@ -25,6 +26,8 @@ public:
{20, &IApplicationProxy::GetApplicationFunctions, "GetApplicationFunctions"},
{1000, &IApplicationProxy::GetDebugFunctions, "GetDebugFunctions"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp
index 0e3088bc8..f814fe2c0 100644
--- a/src/core/hle/service/am/idle.cpp
+++ b/src/core/hle/service/am/idle.cpp
@@ -12,9 +12,9 @@ IdleSys::IdleSys() : ServiceFramework{"idle:sys"} {
{0, nullptr, "GetAutoPowerDownEvent"},
{1, nullptr, "Unknown1"},
{2, nullptr, "Unknown2"},
- {3, nullptr, "Unknown3"},
- {4, nullptr, "Unknown4"},
- {5, nullptr, "Unknown5"},
+ {3, nullptr, "SetHandlingContext"},
+ {4, nullptr, "LoadAndApplySettings"},
+ {5, nullptr, "ReportUserIsActive"},
};
// clang-format on
diff --git a/src/core/hle/service/am/omm.cpp b/src/core/hle/service/am/omm.cpp
index 1c37f849f..6ab3fb906 100644
--- a/src/core/hle/service/am/omm.cpp
+++ b/src/core/hle/service/am/omm.cpp
@@ -17,22 +17,24 @@ OMM::OMM() : ServiceFramework{"omm"} {
{5, nullptr, "GetCradleStatus"},
{6, nullptr, "FadeInDisplay"},
{7, nullptr, "FadeOutDisplay"},
- {8, nullptr, "Unknown1"},
- {9, nullptr, "Unknown2"},
- {10, nullptr, "Unknown3"},
- {11, nullptr, "Unknown4"},
- {12, nullptr, "Unknown5"},
- {13, nullptr, "Unknown6"},
- {14, nullptr, "Unknown7"},
- {15, nullptr, "Unknown8"},
- {16, nullptr, "Unknown9"},
- {17, nullptr, "Unknown10"},
- {18, nullptr, "Unknown11"},
- {19, nullptr, "Unknown12"},
- {20, nullptr, "Unknown13"},
- {21, nullptr, "Unknown14"},
- {22, nullptr, "Unknown15"},
- {23, nullptr, "Unknown16"},
+ {8, nullptr, "GetCradleFwVersion"},
+ {9, nullptr, "NotifyCecSettingsChanged"},
+ {10, nullptr, "SetOperationModePolicy"},
+ {11, nullptr, "GetDefaultDisplayResolution"},
+ {12, nullptr, "GetDefaultDisplayResolutionChangeEvent"},
+ {13, nullptr, "UpdateDefaultDisplayResolution"},
+ {14, nullptr, "ShouldSleepOnBoot"},
+ {15, nullptr, "NotifyHdcpApplicationExecutionStarted"},
+ {16, nullptr, "NotifyHdcpApplicationExecutionFinished"},
+ {17, nullptr, "NotifyHdcpApplicationDrawingStarted"},
+ {18, nullptr, "NotifyHdcpApplicationDrawingFinished"},
+ {19, nullptr, "GetHdcpAuthenticationFailedEvent"},
+ {20, nullptr, "GetHdcpAuthenticationFailedEmulationEnabled"},
+ {21, nullptr, "SetHdcpAuthenticationFailedEmulation"},
+ {22, nullptr, "GetHdcpStateChangeEvent"},
+ {23, nullptr, "GetHdcpState"},
+ {24, nullptr, "ShowCardUpdateProcessing"},
+ {25, nullptr, "SetApplicationCecSettingsAndNotifyChanged"},
};
// clang-format on
diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp
new file mode 100644
index 000000000..a75cbdda8
--- /dev/null
+++ b/src/core/hle/service/am/tcap.cpp
@@ -0,0 +1,23 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/am/tcap.h"
+
+namespace Service::AM {
+
+TCAP::TCAP() : ServiceFramework{"tcap"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "GetContinuousHighSkinTemperatureEvent"},
+ {1, nullptr, "SetOperationMode"},
+ {2, nullptr, "LoadAndApplySettings"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
+TCAP::~TCAP() = default;
+
+} // namespace Service::AM
diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h
new file mode 100644
index 000000000..2021b55d1
--- /dev/null
+++ b/src/core/hle/service/am/tcap.h
@@ -0,0 +1,17 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service::AM {
+
+class TCAP final : public ServiceFramework<TCAP> {
+public:
+ explicit TCAP();
+ ~TCAP() override;
+};
+
+} // namespace Service::AM
diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp
index 79580bcd9..54305cf05 100644
--- a/src/core/hle/service/aoc/aoc_u.cpp
+++ b/src/core/hle/service/aoc/aoc_u.cpp
@@ -7,10 +7,13 @@
#include <vector>
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/patch_manager.h"
#include "core/file_sys/registered_cache.h"
#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/event.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/aoc/aoc_u.h"
#include "core/hle/service/filesystem/filesystem.h"
@@ -19,10 +22,10 @@
namespace Service::AOC {
constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
-constexpr u64 DLC_BASE_TO_AOC_ID_MASK = 0x1000;
+constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000;
-static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) {
- return (aoc & DLC_BASE_TITLE_ID_MASK) == base;
+static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) {
+ return (title_id & DLC_BASE_TITLE_ID_MASK) == base;
}
static std::vector<u64> AccumulateAOCTitleIDs() {
@@ -53,21 +56,25 @@ AOC_U::AOC_U() : ServiceFramework("aoc:u"), add_on_content(AccumulateAOCTitleIDs
{5, &AOC_U::GetAddOnContentBaseId, "GetAddOnContentBaseId"},
{6, nullptr, "PrepareAddOnContentByApplicationId"},
{7, &AOC_U::PrepareAddOnContent, "PrepareAddOnContent"},
- {8, nullptr, "GetAddOnContentListChangedEvent"},
+ {8, &AOC_U::GetAddOnContentListChangedEvent, "GetAddOnContentListChangedEvent"},
};
RegisterHandlers(functions);
+
+ auto& kernel = Core::System::GetInstance().Kernel();
+ aoc_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::Sticky,
+ "GetAddOnContentListChanged:Event");
}
AOC_U::~AOC_U() = default;
void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 4};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
- rb.Push<u32>(std::count_if(add_on_content.begin(), add_on_content.end(), [&current](u64 tid) {
- return (tid & DLC_BASE_TITLE_ID_MASK) == current;
- }));
+ rb.Push<u32>(static_cast<u32>(
+ std::count_if(add_on_content.begin(), add_on_content.end(),
+ [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
}
void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
@@ -91,20 +98,30 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
return;
}
- count = std::min<size_t>(out.size() - offset, count);
+ count = static_cast<u32>(std::min<size_t>(out.size() - offset, count));
std::rotate(out.begin(), out.begin() + offset, out.end());
out.resize(count);
ctx.WriteBuffer(out);
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
+ rb.Push(count);
}
void AOC_U::GetAddOnContentBaseId(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(Core::System::GetInstance().CurrentProcess()->GetTitleID() | DLC_BASE_TO_AOC_ID_MASK);
+ const auto title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID();
+ FileSys::PatchManager pm{title_id};
+
+ const auto res = pm.GetControlMetadata();
+ if (res.first == nullptr) {
+ rb.Push(title_id + DLC_BASE_TO_AOC_ID);
+ return;
+ }
+
+ rb.Push(res.first->GetDLCBaseTitleId());
}
void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
@@ -118,6 +135,14 @@ void AOC_U::PrepareAddOnContent(Kernel::HLERequestContext& ctx) {
rb.Push(RESULT_SUCCESS);
}
+void AOC_U::GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_AOC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyObjects(aoc_change_event);
+}
+
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<AOC_U>()->InstallAsService(service_manager);
}
diff --git a/src/core/hle/service/aoc/aoc_u.h b/src/core/hle/service/aoc/aoc_u.h
index b3c7cab7a..68d94fdaa 100644
--- a/src/core/hle/service/aoc/aoc_u.h
+++ b/src/core/hle/service/aoc/aoc_u.h
@@ -18,8 +18,10 @@ private:
void ListAddOnContent(Kernel::HLERequestContext& ctx);
void GetAddOnContentBaseId(Kernel::HLERequestContext& ctx);
void PrepareAddOnContent(Kernel::HLERequestContext& ctx);
+ void GetAddOnContentListChangedEvent(Kernel::HLERequestContext& ctx);
std::vector<u64> add_on_content;
+ Kernel::SharedPtr<Kernel::Event> aoc_change_event;
};
/// Registers all AOC services with the specified service manager.
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 6073f4ecd..fac6785a5 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -22,20 +22,22 @@ class IAudioRenderer final : public ServiceFramework<IAudioRenderer> {
public:
explicit IAudioRenderer(AudioCore::AudioRendererParameter audren_params)
: ServiceFramework("IAudioRenderer") {
+ // clang-format off
static const FunctionInfo functions[] = {
- {0, &IAudioRenderer::GetAudioRendererSampleRate, "GetAudioRendererSampleRate"},
- {1, &IAudioRenderer::GetAudioRendererSampleCount, "GetAudioRendererSampleCount"},
- {2, &IAudioRenderer::GetAudioRendererMixBufferCount, "GetAudioRendererMixBufferCount"},
- {3, &IAudioRenderer::GetAudioRendererState, "GetAudioRendererState"},
- {4, &IAudioRenderer::RequestUpdateAudioRenderer, "RequestUpdateAudioRenderer"},
- {5, &IAudioRenderer::StartAudioRenderer, "StartAudioRenderer"},
- {6, &IAudioRenderer::StopAudioRenderer, "StopAudioRenderer"},
+ {0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
+ {1, &IAudioRenderer::GetSampleCount, "GetSampleCount"},
+ {2, &IAudioRenderer::GetMixBufferCount, "GetMixBufferCount"},
+ {3, &IAudioRenderer::GetState, "GetState"},
+ {4, &IAudioRenderer::RequestUpdate, "RequestUpdate"},
+ {5, &IAudioRenderer::Start, "Start"},
+ {6, &IAudioRenderer::Stop, "Stop"},
{7, &IAudioRenderer::QuerySystemEvent, "QuerySystemEvent"},
- {8, nullptr, "SetAudioRendererRenderingTimeLimit"},
- {9, nullptr, "GetAudioRendererRenderingTimeLimit"},
- {10, nullptr, "RequestUpdateAudioRendererAuto"},
+ {8, nullptr, "SetRenderingTimeLimit"},
+ {9, nullptr, "GetRenderingTimeLimit"},
+ {10, nullptr, "RequestUpdateAuto"},
{11, nullptr, "ExecuteAudioRendererRendering"},
};
+ // clang-format on
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
@@ -49,42 +51,42 @@ private:
system_event->Signal();
}
- void GetAudioRendererSampleRate(Kernel::HLERequestContext& ctx) {
+ void GetSampleRate(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleRate());
LOG_DEBUG(Service_Audio, "called");
}
- void GetAudioRendererSampleCount(Kernel::HLERequestContext& ctx) {
+ void GetSampleCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetSampleCount());
LOG_DEBUG(Service_Audio, "called");
}
- void GetAudioRendererState(Kernel::HLERequestContext& ctx) {
+ void GetState(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(renderer->GetStreamState()));
LOG_DEBUG(Service_Audio, "called");
}
- void GetAudioRendererMixBufferCount(Kernel::HLERequestContext& ctx) {
+ void GetMixBufferCount(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(renderer->GetMixBufferCount());
LOG_DEBUG(Service_Audio, "called");
}
- void RequestUpdateAudioRenderer(Kernel::HLERequestContext& ctx) {
+ void RequestUpdate(Kernel::HLERequestContext& ctx) {
ctx.WriteBuffer(renderer->UpdateAudioRenderer(ctx.ReadBuffer()));
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_Audio, "(STUBBED) called");
}
- void StartAudioRenderer(Kernel::HLERequestContext& ctx) {
+ void Start(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -92,7 +94,7 @@ private:
LOG_WARNING(Service_Audio, "(STUBBED) called");
}
- void StopAudioRenderer(Kernel::HLERequestContext& ctx) {
+ void Stop(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -129,6 +131,7 @@ public:
{10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
{11, nullptr, "QueryAudioDeviceInputEvent"},
{12, nullptr, "QueryAudioDeviceOutputEvent"},
+ {13, nullptr, "GetAudioSystemMasterVolumeSetting"},
};
RegisterHandlers(functions);
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index fc6067e59..783c39503 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -2,8 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <chrono>
#include <cstring>
#include <memory>
+#include <optional>
#include <vector>
#include <opus.h>
@@ -33,7 +35,8 @@ public:
{1, nullptr, "SetContext"},
{2, nullptr, "DecodeInterleavedForMultiStream"},
{3, nullptr, "SetContextForMultiStream"},
- {4, nullptr, "Unknown4"},
+ {4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerformance,
+ "DecodeInterleavedWithPerformance"},
{5, nullptr, "Unknown5"},
{6, nullptr, "Unknown6"},
{7, nullptr, "Unknown7"},
@@ -59,8 +62,31 @@ private:
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
}
- bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
- std::vector<opus_int16>& output) {
+ void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) {
+ u32 consumed = 0;
+ u32 sample_count = 0;
+ u64 performance = 0;
+ std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
+ if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
+ performance)) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ // TODO(ogniK): Use correct error code
+ rb.Push(ResultCode(-1));
+ return;
+ }
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(consumed);
+ rb.Push<u64>(performance);
+ rb.Push<u32>(sample_count);
+ ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
+ }
+
+ bool Decoder_DecodeInterleaved(
+ u32& consumed, u32& sample_count, const std::vector<u8>& input,
+ std::vector<opus_int16>& output,
+ std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) {
+ const auto start_time = std::chrono::high_resolution_clock::now();
std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
if (sizeof(OpusHeader) > input.size())
return false;
@@ -80,8 +106,13 @@ private:
(static_cast<int>(raw_output_sz / sizeof(s16) / channel_count)), 0);
if (out_sample_count < 0)
return false;
+ const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
sample_count = out_sample_count;
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
+ if (performance_time.has_value()) {
+ performance_time->get() =
+ std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
+ }
return true;
}
@@ -130,7 +161,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
std::size_t worker_sz = WorkerBufferSize(channel_count);
- ASSERT_MSG(buffer_sz < worker_sz, "Worker buffer too large");
+ ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
static_cast<OpusDecoder*>(operator new(worker_sz))};
if (opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index d40f18565..6701cb913 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -9,6 +9,7 @@ namespace Service::ES {
class ETicket final : public ServiceFramework<ETicket> {
public:
explicit ETicket() : ServiceFramework{"es"} {
+ // clang-format off
static const FunctionInfo functions[] = {
{1, nullptr, "ImportTicket"},
{2, nullptr, "ImportTicketCertificateSet"},
@@ -37,15 +38,18 @@ public:
{25, nullptr, "DeletePrepurchaseRecord"},
{26, nullptr, "DeleteAllPrepurchaseRecord"},
{27, nullptr, "CountPrepurchaseRecord"},
- {28, nullptr, "ListPrepurchaseRecord"},
+ {28, nullptr, "ListPrepurchaseRecordRightsIds"},
{29, nullptr, "ListPrepurchaseRecordInfo"},
- {30, nullptr, "Unknown1"},
- {31, nullptr, "Unknown2"},
- {32, nullptr, "Unknown3"},
- {33, nullptr, "Unknown4"},
- {34, nullptr, "Unknown5"},
- {35, nullptr, "Unknown6"},
+ {30, nullptr, "CountTicket"},
+ {31, nullptr, "ListTicketRightsIds"},
+ {32, nullptr, "CountPrepurchaseRecordEx"},
+ {33, nullptr, "ListPrepurchaseRecordRightsIdsEx"},
+ {34, nullptr, "GetEncryptedTicketSize"},
+ {35, nullptr, "GetEncryptedTicketData"},
+ {36, nullptr, "DeleteAllInactiveELicenseRequiredPersonalizedTicket"},
+ {503, nullptr, "GetTitleKey"},
};
+ // clang-format on
RegisterHandlers(functions);
}
};
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index aed2abb71..e32a7c48e 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -264,6 +264,15 @@ ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory) {
return RESULT_SUCCESS;
}
+void SetPackedUpdate(FileSys::VirtualFile update_raw) {
+ LOG_TRACE(Service_FS, "Setting packed update for romfs");
+
+ if (romfs_factory == nullptr)
+ return;
+
+ romfs_factory->SetPackedUpdate(std::move(update_raw));
+}
+
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess() {
LOG_TRACE(Service_FS, "Opening RomFS for current process");
@@ -310,13 +319,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
return sdmc_factory->Open();
}
-std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
- return std::make_shared<FileSys::RegisteredCacheUnion>(
- std::vector<std::shared_ptr<FileSys::RegisteredCache>>{
- GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
+std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
+ return std::make_unique<FileSys::RegisteredCacheUnion>(std::vector<FileSys::RegisteredCache*>{
+ GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
}
-std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
+FileSys::RegisteredCache* GetSystemNANDContents() {
LOG_TRACE(Service_FS, "Opening System NAND Contents");
if (bis_factory == nullptr)
@@ -325,7 +333,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
return bis_factory->GetSystemNANDContents();
}
-std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
+FileSys::RegisteredCache* GetUserNANDContents() {
LOG_TRACE(Service_FS, "Opening User NAND Contents");
if (bis_factory == nullptr)
@@ -334,7 +342,7 @@ std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents() {
return bis_factory->GetUserNANDContents();
}
-std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents() {
+FileSys::RegisteredCache* GetSDMCContents() {
LOG_TRACE(Service_FS, "Opening SDMC Contents");
if (sdmc_factory == nullptr)
@@ -352,19 +360,19 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
return bis_factory->GetModificationLoadRoot(title_id);
}
-void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
+void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
if (overwrite) {
bis_factory = nullptr;
save_data_factory = nullptr;
sdmc_factory = nullptr;
}
- auto nand_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
- FileSys::Mode::ReadWrite);
- auto sd_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
- FileSys::Mode::ReadWrite);
- auto load_directory = vfs->OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
- FileSys::Mode::ReadWrite);
+ auto nand_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir),
+ FileSys::Mode::ReadWrite);
+ auto sd_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir),
+ FileSys::Mode::ReadWrite);
+ auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
+ FileSys::Mode::ReadWrite);
if (bis_factory == nullptr)
bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
@@ -374,7 +382,7 @@ void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite) {
sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
}
-void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs) {
+void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) {
romfs_factory = nullptr;
CreateFactories(vfs, false);
std::make_shared<FSP_LDR>()->InstallAsService(service_manager);
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 7039a2247..6ca5c5636 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -39,6 +39,7 @@ ResultCode RegisterSaveData(std::unique_ptr<FileSys::SaveDataFactory>&& factory)
ResultCode RegisterSDMC(std::unique_ptr<FileSys::SDMCFactory>&& factory);
ResultCode RegisterBIS(std::unique_ptr<FileSys::BISFactory>&& factory);
+void SetPackedUpdate(FileSys::VirtualFile update_raw);
ResultVal<FileSys::VirtualFile> OpenRomFSCurrentProcess();
ResultVal<FileSys::VirtualFile> OpenRomFS(u64 title_id, FileSys::StorageId storage_id,
FileSys::ContentRecordType type);
@@ -46,19 +47,19 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
FileSys::SaveDataDescriptor save_struct);
ResultVal<FileSys::VirtualDir> OpenSDMC();
-std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
+std::unique_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
-std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
-std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
-std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
+FileSys::RegisteredCache* GetSystemNANDContents();
+FileSys::RegisteredCache* GetUserNANDContents();
+FileSys::RegisteredCache* GetSDMCContents();
FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
// Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
// above is called.
-void CreateFactories(const FileSys::VirtualFilesystem& vfs, bool overwrite = true);
+void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite = true);
-void InstallInterfaces(SM::ServiceManager& service_manager, const FileSys::VirtualFilesystem& vfs);
+void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs);
// A class that wraps a VfsDirectory with methods that return ResultVal and ResultCode instead of
// pointers and booleans. This makes using a VfsDirectory with switch services much easier and
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index d5dced429..c1c83a11d 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -17,6 +17,7 @@
#include "core/file_sys/errors.h"
#include "core/file_sys/mode.h"
#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/patch_manager.h"
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/vfs.h"
#include "core/hle/ipc_helpers.h"
@@ -272,8 +273,8 @@ public:
{0, &IFileSystem::CreateFile, "CreateFile"},
{1, &IFileSystem::DeleteFile, "DeleteFile"},
{2, &IFileSystem::CreateDirectory, "CreateDirectory"},
- {3, nullptr, "DeleteDirectory"},
- {4, nullptr, "DeleteDirectoryRecursively"},
+ {3, &IFileSystem::DeleteDirectory, "DeleteDirectory"},
+ {4, &IFileSystem::DeleteDirectoryRecursively, "DeleteDirectoryRecursively"},
{5, &IFileSystem::RenameFile, "RenameFile"},
{6, nullptr, "RenameDirectory"},
{7, &IFileSystem::GetEntryType, "GetEntryType"},
@@ -328,6 +329,30 @@ public:
rb.Push(backend.CreateDirectory(name));
}
+ void DeleteDirectory(Kernel::HLERequestContext& ctx) {
+ const IPC::RequestParser rp{ctx};
+
+ const auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ LOG_DEBUG(Service_FS, "called directory {}", name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend.DeleteDirectory(name));
+ }
+
+ void DeleteDirectoryRecursively(Kernel::HLERequestContext& ctx) {
+ const IPC::RequestParser rp{ctx};
+
+ const auto file_buffer = ctx.ReadBuffer();
+ std::string name = Common::StringFromBuffer(file_buffer);
+
+ LOG_DEBUG(Service_FS, "called directory {}", name);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(backend.DeleteDirectoryRecursively(name));
+ }
+
void RenameFile(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
@@ -630,6 +655,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
static_cast<u8>(storage_id), unknown, title_id);
auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data);
+
if (data.Failed()) {
// TODO(DarkLordZach): Find the right error code to use here
LOG_ERROR(Service_FS,
@@ -640,7 +666,9 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) {
return;
}
- IStorage storage(std::move(data.Unwrap()));
+ FileSys::PatchManager pm{title_id};
+
+ IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data));
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/hid/controllers/controller_base.cpp b/src/core/hle/service/hid/controllers/controller_base.cpp
new file mode 100644
index 000000000..0993a7815
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/controller_base.cpp
@@ -0,0 +1,30 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+
+ControllerBase::ControllerBase() = default;
+ControllerBase::~ControllerBase() = default;
+
+void ControllerBase::ActivateController() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = true;
+ OnInit();
+}
+
+void ControllerBase::DeactivateController() {
+ if (is_activated) {
+ OnRelease();
+ }
+ is_activated = false;
+}
+
+bool ControllerBase::IsControllerActivated() const {
+ return is_activated;
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/controller_base.h b/src/core/hle/service/hid/controllers/controller_base.h
new file mode 100644
index 000000000..f0e092b1b
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/controller_base.h
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/swap.h"
+
+namespace Service::HID {
+class ControllerBase {
+public:
+ ControllerBase();
+ virtual ~ControllerBase();
+
+ // Called when the controller is initialized
+ virtual void OnInit() = 0;
+
+ // When the controller is released
+ virtual void OnRelease() = 0;
+
+ // When the controller is requesting an update for the shared memory
+ virtual void OnUpdate(u8* data, std::size_t size) = 0;
+
+ // Called when input devices should be loaded
+ virtual void OnLoadInputDevices() = 0;
+
+ void ActivateController();
+
+ void DeactivateController();
+
+ bool IsControllerActivated() const;
+
+protected:
+ bool is_activated{false};
+
+ struct CommonHeader {
+ s64_le timestamp;
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ };
+ static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.cpp b/src/core/hle/service/hid/controllers/debug_pad.cpp
new file mode 100644
index 000000000..3d100763f
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/debug_pad.cpp
@@ -0,0 +1,42 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+
+namespace Service::HID {
+
+Controller_DebugPad::Controller_DebugPad() = default;
+Controller_DebugPad::~Controller_DebugPad() = default;
+
+void Controller_DebugPad::OnInit() {}
+
+void Controller_DebugPad::OnRelease() {}
+
+void Controller_DebugPad::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update debug pad states
+
+ std::memcpy(data, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_DebugPad::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
new file mode 100644
index 000000000..62b4f2682
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -0,0 +1,56 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_DebugPad final : public ControllerBase {
+public:
+ Controller_DebugPad();
+ ~Controller_DebugPad() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct AnalogStick {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogStick) == 0x8);
+
+ struct PadStates {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ u32_le attribute;
+ u32_le button_state;
+ AnalogStick r_stick;
+ AnalogStick l_stick;
+ };
+ static_assert(sizeof(PadStates) == 0x28, "PadStates is an invalid state");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<PadStates, 17> pad_states;
+ INSERT_PADDING_BYTES(0x138);
+ };
+ static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.cpp b/src/core/hle/service/hid/controllers/gesture.cpp
new file mode 100644
index 000000000..898572277
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/gesture.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3BA00;
+
+Controller_Gesture::Controller_Gesture() = default;
+Controller_Gesture::~Controller_Gesture() = default;
+
+void Controller_Gesture::OnInit() {}
+
+void Controller_Gesture::OnRelease() {}
+
+void Controller_Gesture::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.gesture_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update gesture states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Gesture::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/gesture.h b/src/core/hle/service/hid/controllers/gesture.h
new file mode 100644
index 000000000..1056ffbcd
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/gesture.h
@@ -0,0 +1,63 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Gesture final : public ControllerBase {
+public:
+ Controller_Gesture();
+ ~Controller_Gesture() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct Locations {
+ s32_le x;
+ s32_le y;
+ };
+
+ struct GestureState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+
+ s64_le detection_count;
+ s32_le type;
+ s32_le dir;
+ s32_le x;
+ s32_le y;
+ s32_le delta_x;
+ s32_le delta_y;
+ f32 vel_x;
+ f32 vel_y;
+ s32_le attributes;
+ f32 scale;
+ f32 rotation;
+ s32_le location_count;
+ std::array<Locations, 4> locations;
+ };
+ static_assert(sizeof(GestureState) == 0x68, "GestureState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<GestureState, 17> gesture_states;
+ };
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.cpp b/src/core/hle/service/hid/controllers/keyboard.cpp
new file mode 100644
index 000000000..ccfbce9ac
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/keyboard.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3800;
+
+Controller_Keyboard::Controller_Keyboard() = default;
+Controller_Keyboard::~Controller_Keyboard() = default;
+
+void Controller_Keyboard::OnInit() {}
+
+void Controller_Keyboard::OnRelease() {}
+
+void Controller_Keyboard::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.pad_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update keyboard states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Keyboard::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/keyboard.h b/src/core/hle/service/hid/controllers/keyboard.h
new file mode 100644
index 000000000..493e68fce
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/keyboard.h
@@ -0,0 +1,50 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Keyboard final : public ControllerBase {
+public:
+ Controller_Keyboard();
+ ~Controller_Keyboard() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct KeyboardState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+
+ s32_le modifier;
+ s32_le attribute;
+ std::array<u8, 32> key;
+ };
+ static_assert(sizeof(KeyboardState) == 0x38, "KeyboardState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<KeyboardState, 17> pad_states;
+ INSERT_PADDING_BYTES(0x28);
+ };
+ static_assert(sizeof(SharedMemory) == 0x400, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.cpp b/src/core/hle/service/hid/controllers/mouse.cpp
new file mode 100644
index 000000000..4e246a57d
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/mouse.cpp
@@ -0,0 +1,43 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3400;
+
+Controller_Mouse::Controller_Mouse() = default;
+Controller_Mouse::~Controller_Mouse() = default;
+
+void Controller_Mouse::OnInit() {}
+
+void Controller_Mouse::OnRelease() {}
+
+void Controller_Mouse::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ auto& last_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.mouse_states[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ // TODO(ogniK): Update mouse states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_Mouse::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/mouse.h b/src/core/hle/service/hid/controllers/mouse.h
new file mode 100644
index 000000000..543b0b71f
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/mouse.h
@@ -0,0 +1,50 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Mouse final : public ControllerBase {
+public:
+ Controller_Mouse();
+ ~Controller_Mouse() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct MouseState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le x;
+ s32_le y;
+ s32_le delta_x;
+ s32_le delta_y;
+ s32_le mouse_wheel;
+ s32_le button;
+ s32_le attribute;
+ };
+ static_assert(sizeof(MouseState) == 0x30, "MouseState is an invalid size");
+
+ struct SharedMemory {
+ CommonHeader header;
+ std::array<MouseState, 17> mouse_states;
+ };
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
new file mode 100644
index 000000000..4b4d1324f
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -0,0 +1,620 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include "common/assert.h"
+#include "common/bit_field.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/frontend/input.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+
+constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
+constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
+constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
+constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
+constexpr s32 HID_JOYSTICK_MAX = 0x7fff;
+constexpr s32 HID_JOYSTICK_MIN = -0x7fff;
+constexpr std::size_t NPAD_OFFSET = 0x9A00;
+constexpr u32 BATTERY_FULL = 2;
+constexpr u32 NPAD_HANDHELD = 32;
+constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this?
+constexpr u32 MAX_NPAD_ID = 7;
+constexpr Controller_NPad::NPadControllerType PREFERRED_CONTROLLER =
+ Controller_NPad::NPadControllerType::JoyDual;
+constexpr std::array<u32, 10> npad_id_list{
+ 0, 1, 2, 3, 4, 5, 6, 7, 32, 16,
+};
+
+enum class JoystickId : std::size_t {
+ Joystick_Left,
+ Joystick_Right,
+};
+
+Controller_NPad::Controller_NPad() = default;
+Controller_NPad::~Controller_NPad() = default;
+
+void Controller_NPad::InitNewlyAddedControler(std::size_t controller_idx) {
+ const auto controller_type = connected_controllers[controller_idx].type;
+ auto& controller = shared_memory_entries[controller_idx];
+ if (controller_type == NPadControllerType::None) {
+ return;
+ }
+ controller.joy_styles.raw = 0; // Zero out
+ controller.device_type.raw = 0;
+ switch (controller_type) {
+ case NPadControllerType::Handheld:
+ controller.joy_styles.handheld.Assign(1);
+ controller.device_type.handheld.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyDual:
+ controller.joy_styles.joycon_dual.Assign(1);
+ controller.device_type.joycon_left.Assign(1);
+ controller.device_type.joycon_right.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyLeft:
+ controller.joy_styles.joycon_left.Assign(1);
+ controller.device_type.joycon_left.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::JoyRight:
+ controller.joy_styles.joycon_right.Assign(1);
+ controller.device_type.joycon_right.Assign(1);
+ controller.pad_assignment = NPadAssignments::Dual;
+ break;
+ case NPadControllerType::Pokeball:
+ controller.joy_styles.pokeball.Assign(1);
+ controller.device_type.pokeball.Assign(1);
+ controller.pad_assignment = NPadAssignments::Single;
+ break;
+ case NPadControllerType::ProController:
+ controller.joy_styles.pro_controller.Assign(1);
+ controller.device_type.pro_controller.Assign(1);
+ controller.pad_assignment = NPadAssignments::Single;
+ break;
+ }
+
+ controller.single_color_error = ColorReadError::ReadOk;
+ controller.single_color.body_color = 0;
+ controller.single_color.button_color = 0;
+
+ controller.dual_color_error = ColorReadError::ReadOk;
+ controller.left_color.body_color = JOYCON_BODY_NEON_BLUE;
+ controller.left_color.button_color = JOYCON_BUTTONS_NEON_BLUE;
+ controller.right_color.body_color = JOYCON_BODY_NEON_RED;
+ controller.right_color.button_color = JOYCON_BUTTONS_NEON_RED;
+
+ controller.properties.is_vertical.Assign(1); // TODO(ogniK): Swap joycons orientations
+ controller.properties.use_plus.Assign(1);
+ controller.properties.use_minus.Assign(1);
+ controller.battery_level[0] = BATTERY_FULL;
+ controller.battery_level[1] = BATTERY_FULL;
+ controller.battery_level[2] = BATTERY_FULL;
+}
+
+void Controller_NPad::OnInit() {
+ auto& kernel = Core::System::GetInstance().Kernel();
+ styleset_changed_event =
+ Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "npad:NpadStyleSetChanged");
+
+ if (!IsControllerActivated()) {
+ return;
+ }
+
+ if (style.raw == 0) {
+ // We want to support all controllers
+ style.handheld.Assign(1);
+ style.joycon_left.Assign(1);
+ style.joycon_right.Assign(1);
+ style.joycon_dual.Assign(1);
+ style.pro_controller.Assign(1);
+ style.pokeball.Assign(1);
+ }
+ if (std::none_of(connected_controllers.begin(), connected_controllers.end(),
+ [](const ControllerHolder& controller) { return controller.is_connected; })) {
+ supported_npad_id_types.resize(npad_id_list.size());
+ std::memcpy(supported_npad_id_types.data(), npad_id_list.data(),
+ npad_id_list.size() * sizeof(u32));
+ AddNewController(PREFERRED_CONTROLLER);
+ }
+}
+
+void Controller_NPad::OnLoadInputDevices() {
+ std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
+ Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
+ buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
+ std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
+ Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
+ sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
+}
+
+void Controller_NPad::OnRelease() {}
+
+void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) {
+ if (!IsControllerActivated())
+ return;
+ for (std::size_t i = 0; i < shared_memory_entries.size(); i++) {
+ auto& npad = shared_memory_entries[i];
+ const std::array<NPadGeneric*, 7> controller_npads{&npad.main_controller_states,
+ &npad.handheld_states,
+ &npad.dual_states,
+ &npad.left_joy_states,
+ &npad.right_joy_states,
+ &npad.pokeball_states,
+ &npad.libnx};
+
+ for (auto* main_controller : controller_npads) {
+ main_controller->common.entry_count = 16;
+ main_controller->common.total_entry_count = 17;
+
+ const auto& last_entry =
+ main_controller->npad[main_controller->common.last_entry_index];
+
+ main_controller->common.timestamp = CoreTiming::GetTicks();
+ main_controller->common.last_entry_index =
+ (main_controller->common.last_entry_index + 1) % 17;
+
+ auto& cur_entry = main_controller->npad[main_controller->common.last_entry_index];
+
+ cur_entry.timestamp = last_entry.timestamp + 1;
+ cur_entry.timestamp2 = cur_entry.timestamp;
+ }
+
+ const auto& controller_type = connected_controllers[i].type;
+
+ if (controller_type == NPadControllerType::None || !connected_controllers[i].is_connected) {
+ continue;
+ }
+
+ // Pad states
+ ControllerPadState pad_state{};
+ using namespace Settings::NativeButton;
+ pad_state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.d_left.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_up.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_right.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.d_down.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.l_stick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.l_stick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.r_stick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.r_stick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
+
+ pad_state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
+ pad_state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
+
+ AnalogPosition lstick_entry{};
+ AnalogPosition rstick_entry{};
+
+ const auto [stick_l_x_f, stick_l_y_f] =
+ sticks[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetStatus();
+ const auto [stick_r_x_f, stick_r_y_f] =
+ sticks[static_cast<std::size_t>(JoystickId::Joystick_Right)]->GetStatus();
+ lstick_entry.x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
+ lstick_entry.y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
+ rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
+ rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+
+ if (controller_type == NPadControllerType::JoyLeft ||
+ controller_type == NPadControllerType::JoyRight) {
+ if (npad.properties.is_horizontal) {
+ ControllerPadState state{};
+ AnalogPosition temp_lstick_entry{};
+ AnalogPosition temp_rstick_entry{};
+ if (controller_type == NPadControllerType::JoyLeft) {
+ state.d_down.Assign(pad_state.d_left.Value());
+ state.d_left.Assign(pad_state.d_up.Value());
+ state.d_right.Assign(pad_state.d_down.Value());
+ state.d_up.Assign(pad_state.d_right.Value());
+ state.l.Assign(pad_state.l.Value() | pad_state.sl.Value());
+ state.r.Assign(pad_state.r.Value() | pad_state.sr.Value());
+
+ state.zl.Assign(pad_state.zl.Value());
+ state.plus.Assign(pad_state.minus.Value());
+
+ temp_lstick_entry = lstick_entry;
+ temp_rstick_entry = rstick_entry;
+ std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
+ std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
+ temp_lstick_entry.y *= -1;
+ } else if (controller_type == NPadControllerType::JoyRight) {
+ state.x.Assign(pad_state.a.Value());
+ state.a.Assign(pad_state.b.Value());
+ state.b.Assign(pad_state.y.Value());
+ state.y.Assign(pad_state.b.Value());
+
+ state.l.Assign(pad_state.l.Value() | pad_state.sl.Value());
+ state.r.Assign(pad_state.r.Value() | pad_state.sr.Value());
+ state.zr.Assign(pad_state.zr.Value());
+ state.plus.Assign(pad_state.plus.Value());
+
+ temp_lstick_entry = lstick_entry;
+ temp_rstick_entry = rstick_entry;
+ std::swap(temp_lstick_entry.x, temp_lstick_entry.y);
+ std::swap(temp_rstick_entry.x, temp_rstick_entry.y);
+ temp_rstick_entry.x *= -1;
+ }
+ pad_state.raw = state.raw;
+ lstick_entry = temp_lstick_entry;
+ rstick_entry = temp_rstick_entry;
+ }
+ }
+
+ auto& main_controller =
+ npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index];
+ auto& handheld_entry =
+ npad.handheld_states.npad[npad.handheld_states.common.last_entry_index];
+ auto& dual_entry = npad.dual_states.npad[npad.dual_states.common.last_entry_index];
+ auto& left_entry = npad.left_joy_states.npad[npad.left_joy_states.common.last_entry_index];
+ auto& right_entry =
+ npad.right_joy_states.npad[npad.right_joy_states.common.last_entry_index];
+ auto& pokeball_entry =
+ npad.pokeball_states.npad[npad.pokeball_states.common.last_entry_index];
+ auto& libnx_entry = npad.libnx.npad[npad.libnx.common.last_entry_index];
+
+ if (hold_type == NpadHoldType::Horizontal) {
+ // TODO(ogniK): Remap buttons for different orientations
+ }
+ libnx_entry.connection_status.raw = 0;
+
+ switch (controller_type) {
+ case NPadControllerType::Handheld:
+ handheld_entry.connection_status.raw = 0;
+ handheld_entry.connection_status.IsConnected.Assign(1);
+ if (!Settings::values.use_docked_mode) {
+ handheld_entry.connection_status.IsWired.Assign(1);
+ }
+ handheld_entry.pad_states.raw = pad_state.raw;
+ handheld_entry.l_stick = lstick_entry;
+ handheld_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::JoyDual:
+ dual_entry.connection_status.raw = 0;
+
+ dual_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ dual_entry.connection_status.IsRightJoyConnected.Assign(1);
+ dual_entry.connection_status.IsConnected.Assign(1);
+
+ libnx_entry.connection_status.IsLeftJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsRightJoyConnected.Assign(1);
+ libnx_entry.connection_status.IsConnected.Assign(1);
+
+ dual_entry.pad_states.raw = pad_state.raw;
+ dual_entry.l_stick = lstick_entry;
+ dual_entry.r_stick = rstick_entry;
+ case NPadControllerType::JoyLeft:
+ left_entry.connection_status.raw = 0;
+
+ left_entry.connection_status.IsConnected.Assign(1);
+ left_entry.pad_states.raw = pad_state.raw;
+ left_entry.l_stick = lstick_entry;
+ left_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::JoyRight:
+ right_entry.connection_status.raw = 0;
+
+ right_entry.connection_status.IsConnected.Assign(1);
+ right_entry.pad_states.raw = pad_state.raw;
+ right_entry.l_stick = lstick_entry;
+ right_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::Pokeball:
+ pokeball_entry.connection_status.raw = 0;
+
+ pokeball_entry.connection_status.IsConnected.Assign(1);
+ pokeball_entry.connection_status.IsWired.Assign(1);
+
+ pokeball_entry.pad_states.raw = pad_state.raw;
+ pokeball_entry.l_stick = lstick_entry;
+ pokeball_entry.r_stick = rstick_entry;
+ break;
+ case NPadControllerType::ProController:
+ main_controller.connection_status.raw = 0;
+
+ main_controller.connection_status.IsConnected.Assign(1);
+ main_controller.connection_status.IsWired.Assign(1);
+ main_controller.pad_states.raw = pad_state.raw;
+ main_controller.l_stick = lstick_entry;
+ main_controller.r_stick = rstick_entry;
+ break;
+ }
+
+ // LibNX exclusively uses this section, so we always update it since LibNX doesn't activate
+ // any controllers.
+ libnx_entry.pad_states.raw = pad_state.raw;
+ libnx_entry.l_stick = lstick_entry;
+ libnx_entry.r_stick = rstick_entry;
+ }
+ std::memcpy(data + NPAD_OFFSET, shared_memory_entries.data(),
+ shared_memory_entries.size() * sizeof(NPadEntry));
+} // namespace Service::HID
+
+void Controller_NPad::SetSupportedStyleSet(NPadType style_set) {
+ style.raw = style_set.raw;
+}
+
+Controller_NPad::NPadType Controller_NPad::GetSupportedStyleSet() const {
+ return style;
+}
+
+void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) {
+ ASSERT(length > 0 && (length % sizeof(u32)) == 0);
+ supported_npad_id_types.clear();
+ supported_npad_id_types.resize(length / sizeof(u32));
+ std::memcpy(supported_npad_id_types.data(), data, length);
+ for (std::size_t i = 0; i < connected_controllers.size(); i++) {
+ auto& controller = connected_controllers[i];
+ if (!controller.is_connected) {
+ continue;
+ }
+ if (!IsControllerSupported(PREFERRED_CONTROLLER)) {
+ controller.type = DecideBestController(PREFERRED_CONTROLLER);
+ InitNewlyAddedControler(i);
+ }
+ }
+}
+
+void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) {
+ ASSERT(max_length < supported_npad_id_types.size());
+ std::memcpy(data, supported_npad_id_types.data(), supported_npad_id_types.size());
+}
+
+std::size_t Controller_NPad::GetSupportedNPadIdTypesSize() const {
+ return supported_npad_id_types.size();
+}
+
+void Controller_NPad::SetHoldType(NpadHoldType joy_hold_type) {
+ hold_type = joy_hold_type;
+}
+Controller_NPad::NpadHoldType Controller_NPad::GetHoldType() const {
+ return hold_type;
+}
+
+void Controller_NPad::SetNpadMode(u32 npad_id, NPadAssignments assignment_mode) {
+ ASSERT(npad_id < shared_memory_entries.size());
+ shared_memory_entries[npad_id].pad_assignment = assignment_mode;
+}
+
+void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids,
+ const std::vector<Vibration>& vibrations) {
+ if (!can_controllers_vibrate) {
+ return;
+ }
+ for (std::size_t i = 0; i < controller_ids.size(); i++) {
+ std::size_t controller_pos = i;
+ // Handheld controller conversion
+ if (controller_pos == NPAD_HANDHELD) {
+ controller_pos = 8;
+ }
+ // Unknown controller conversion
+ if (controller_pos == NPAD_UNKNOWN) {
+ controller_pos = 9;
+ }
+ if (connected_controllers[controller_pos].is_connected) {
+ // TODO(ogniK): Vibrate the physical controller
+ }
+ }
+ LOG_WARNING(Service_HID, "(STUBBED) called");
+ last_processed_vibration = vibrations.back();
+}
+
+Kernel::SharedPtr<Kernel::Event> Controller_NPad::GetStyleSetChangedEvent() const {
+ return styleset_changed_event;
+}
+
+Controller_NPad::Vibration Controller_NPad::GetLastVibration() const {
+ return last_processed_vibration;
+}
+void Controller_NPad::AddNewController(NPadControllerType controller) {
+ if (controller == NPadControllerType::Handheld) {
+ connected_controllers[8] = {controller, true};
+ InitNewlyAddedControler(8);
+ return;
+ }
+ const auto pos =
+ std::find_if(connected_controllers.begin(), connected_controllers.end() - 2,
+ [](const ControllerHolder& holder) { return !holder.is_connected; });
+ if (pos == connected_controllers.end() - 2) {
+ LOG_ERROR(Service_HID, "Cannot connect any more controllers!");
+ return;
+ }
+ const auto controller_id = std::distance(connected_controllers.begin(), pos);
+ connected_controllers[controller_id] = {controller, true};
+ InitNewlyAddedControler(controller_id);
+}
+
+void Controller_NPad::ConnectNPad(u32 npad_id) {
+ if (npad_id >= connected_controllers.size())
+ return;
+ connected_controllers[npad_id].is_connected = true;
+}
+
+void Controller_NPad::DisconnectNPad(u32 npad_id) {
+ if (npad_id >= connected_controllers.size())
+ return;
+ connected_controllers[npad_id].is_connected = false;
+}
+
+Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
+ if (npad_id == npad_id_list.back() || npad_id == npad_id_list[npad_id_list.size() - 2]) {
+ // These are controllers without led patterns
+ return LedPattern{0, 0, 0, 0};
+ }
+ switch (npad_id) {
+ case 0:
+ return LedPattern{1, 0, 0, 0};
+ case 1:
+ return LedPattern{0, 1, 0, 0};
+ case 2:
+ return LedPattern{0, 0, 1, 0};
+ case 3:
+ return LedPattern{0, 0, 0, 1};
+ case 4:
+ return LedPattern{1, 0, 0, 1};
+ case 5:
+ return LedPattern{1, 0, 1, 0};
+ case 6:
+ return LedPattern{1, 0, 1, 1};
+ case 7:
+ return LedPattern{0, 1, 1, 0};
+ default:
+ UNIMPLEMENTED_MSG("Unhandled npad_id {}", npad_id);
+ return LedPattern{0, 0, 0, 0};
+ };
+}
+void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
+ can_controllers_vibrate = can_vibrate;
+}
+
+bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const {
+ const bool support_handheld =
+ std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) !=
+ supported_npad_id_types.end();
+ if (controller == NPadControllerType::Handheld) {
+ // Handheld is not even a supported type, lets stop here
+ if (!support_handheld) {
+ return false;
+ }
+ // Handheld should not be supported in docked mode
+ if (Settings::values.use_docked_mode) {
+ return false;
+ }
+
+ return true;
+ }
+ if (std::any_of(supported_npad_id_types.begin(), supported_npad_id_types.end(),
+ [](u32 npad_id) { return npad_id <= MAX_NPAD_ID; })) {
+ switch (controller) {
+ case NPadControllerType::ProController:
+ return style.pro_controller;
+ case NPadControllerType::JoyDual:
+ return style.joycon_dual;
+ case NPadControllerType::JoyLeft:
+ return style.joycon_left;
+ case NPadControllerType::JoyRight:
+ return style.joycon_right;
+ case NPadControllerType::Pokeball:
+ return style.pokeball;
+ default:
+ return false;
+ }
+ }
+ return false;
+}
+
+Controller_NPad::NPadControllerType Controller_NPad::DecideBestController(
+ NPadControllerType priority) const {
+ if (IsControllerSupported(priority)) {
+ return priority;
+ }
+ const auto is_docked = Settings::values.use_docked_mode;
+ if (is_docked && priority == NPadControllerType::Handheld) {
+ priority = NPadControllerType::JoyDual;
+ if (IsControllerSupported(priority)) {
+ return priority;
+ }
+ }
+ std::vector<NPadControllerType> priority_list;
+ switch (priority) {
+ case NPadControllerType::ProController:
+ priority_list.push_back(NPadControllerType::JoyDual);
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::Pokeball);
+ break;
+ case NPadControllerType::Handheld:
+ priority_list.push_back(NPadControllerType::JoyDual);
+ priority_list.push_back(NPadControllerType::ProController);
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::Pokeball);
+ break;
+ case NPadControllerType::JoyDual:
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::ProController);
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::Pokeball);
+ break;
+ case NPadControllerType::JoyLeft:
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::JoyDual);
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::ProController);
+ priority_list.push_back(NPadControllerType::Pokeball);
+ break;
+ case NPadControllerType::JoyRight:
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyDual);
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::ProController);
+ priority_list.push_back(NPadControllerType::Pokeball);
+ break;
+ case NPadControllerType::Pokeball:
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::JoyDual);
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::ProController);
+ break;
+ default:
+ priority_list.push_back(NPadControllerType::JoyDual);
+ if (!is_docked) {
+ priority_list.push_back(NPadControllerType::Handheld);
+ }
+ priority_list.push_back(NPadControllerType::ProController);
+ priority_list.push_back(NPadControllerType::JoyLeft);
+ priority_list.push_back(NPadControllerType::JoyRight);
+ priority_list.push_back(NPadControllerType::JoyDual);
+ }
+
+ const auto iter = std::find_if(priority_list.begin(), priority_list.end(),
+ [this](auto type) { return IsControllerSupported(type); });
+ if (iter == priority_list.end()) {
+ UNIMPLEMENTED_MSG("Could not find supported controller!");
+ return priority;
+ }
+
+ return *iter;
+}
+
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
new file mode 100644
index 000000000..ac86985ff
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -0,0 +1,289 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/common_types.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+
+class Controller_NPad final : public ControllerBase {
+public:
+ Controller_NPad();
+ ~Controller_NPad() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+ struct NPadType {
+ union {
+ u32_le raw{};
+
+ BitField<0, 1, u32_le> pro_controller;
+ BitField<1, 1, u32_le> handheld;
+ BitField<2, 1, u32_le> joycon_dual;
+ BitField<3, 1, u32_le> joycon_left;
+ BitField<4, 1, u32_le> joycon_right;
+
+ BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
+ };
+ };
+ static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
+
+ struct Vibration {
+ f32 amp_low;
+ f32 freq_low;
+ f32 amp_high;
+ f32 freq_high;
+ };
+ static_assert(sizeof(Vibration) == 0x10, "Vibration is an invalid size");
+
+ enum class NpadHoldType : u64 {
+ Vertical = 0,
+ Horizontal = 1,
+ };
+
+ enum class NPadAssignments : u32_le {
+ Dual = 0,
+ Single = 1,
+ };
+
+ enum class NPadControllerType {
+ None,
+ ProController,
+ Handheld,
+ JoyDual,
+ JoyLeft,
+ JoyRight,
+ Pokeball,
+ };
+
+ struct LedPattern {
+ explicit LedPattern(u64 light1, u64 light2, u64 light3, u64 light4) {
+ position1.Assign(light1);
+ position1.Assign(light2);
+ position1.Assign(light3);
+ position1.Assign(light4);
+ }
+ union {
+ u64 raw{};
+ BitField<0, 1, u64> position1;
+ BitField<1, 1, u64> position2;
+ BitField<2, 1, u64> position3;
+ BitField<3, 1, u64> position4;
+ };
+ };
+
+ void SetSupportedStyleSet(NPadType style_set);
+ NPadType GetSupportedStyleSet() const;
+
+ void SetSupportedNPadIdTypes(u8* data, std::size_t length);
+ void GetSupportedNpadIdTypes(u32* data, std::size_t max_length);
+ std::size_t GetSupportedNPadIdTypesSize() const;
+
+ void SetHoldType(NpadHoldType joy_hold_type);
+ NpadHoldType GetHoldType() const;
+
+ void SetNpadMode(u32 npad_id, NPadAssignments assignment_mode);
+
+ void VibrateController(const std::vector<u32>& controller_ids,
+ const std::vector<Vibration>& vibrations);
+
+ Kernel::SharedPtr<Kernel::Event> GetStyleSetChangedEvent() const;
+ Vibration GetLastVibration() const;
+
+ void AddNewController(NPadControllerType controller);
+
+ void ConnectNPad(u32 npad_id);
+ void DisconnectNPad(u32 npad_id);
+ LedPattern GetLedPattern(u32 npad_id);
+ void SetVibrationEnabled(bool can_vibrate);
+
+private:
+ struct CommonHeader {
+ s64_le timestamp;
+ s64_le total_entry_count;
+ s64_le last_entry_index;
+ s64_le entry_count;
+ };
+ static_assert(sizeof(CommonHeader) == 0x20, "CommonHeader is an invalid size");
+
+ struct ControllerColor {
+ u32_le body_color;
+ u32_le button_color;
+ };
+ static_assert(sizeof(ControllerColor) == 8, "ControllerColor is an invalid size");
+
+ struct ControllerPadState {
+ union {
+ u64_le raw{};
+ // Button states
+ BitField<0, 1, u64_le> a;
+ BitField<1, 1, u64_le> b;
+ BitField<2, 1, u64_le> x;
+ BitField<3, 1, u64_le> y;
+ BitField<4, 1, u64_le> l_stick;
+ BitField<5, 1, u64_le> r_stick;
+ BitField<6, 1, u64_le> l;
+ BitField<7, 1, u64_le> r;
+ BitField<8, 1, u64_le> zl;
+ BitField<9, 1, u64_le> zr;
+ BitField<10, 1, u64_le> plus;
+ BitField<11, 1, u64_le> minus;
+
+ // D-Pad
+ BitField<12, 1, u64_le> d_left;
+ BitField<13, 1, u64_le> d_up;
+ BitField<14, 1, u64_le> d_right;
+ BitField<15, 1, u64_le> d_down;
+
+ // Left JoyStick
+ BitField<16, 1, u64_le> l_stick_left;
+ BitField<17, 1, u64_le> l_stick_up;
+ BitField<18, 1, u64_le> l_stick_right;
+ BitField<19, 1, u64_le> l_stick_down;
+
+ // Right JoyStick
+ BitField<20, 1, u64_le> r_stick_left;
+ BitField<21, 1, u64_le> r_stick_up;
+ BitField<22, 1, u64_le> r_stick_right;
+ BitField<23, 1, u64_le> r_stick_down;
+
+ // Not always active?
+ BitField<24, 1, u64_le> sl;
+ BitField<25, 1, u64_le> sr;
+ };
+ };
+ static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
+
+ struct AnalogPosition {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogPosition) == 8, "AnalogPosition is an invalid size");
+
+ struct ConnectionState {
+ union {
+ u32_le raw{};
+ BitField<0, 1, u32_le> IsConnected;
+ BitField<1, 1, u32_le> IsWired;
+ BitField<2, 1, u32_le> IsLeftJoyConnected;
+ BitField<3, 1, u32_le> IsLeftJoyWired;
+ BitField<4, 1, u32_le> IsRightJoyConnected;
+ BitField<5, 1, u32_le> IsRightJoyWired;
+ };
+ };
+ static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
+
+ struct GenericStates {
+ s64_le timestamp;
+ s64_le timestamp2;
+ ControllerPadState pad_states;
+ AnalogPosition l_stick;
+ AnalogPosition r_stick;
+ ConnectionState connection_status;
+ };
+ static_assert(sizeof(GenericStates) == 0x30, "NPadGenericStates is an invalid size");
+
+ struct NPadGeneric {
+ CommonHeader common;
+ std::array<GenericStates, 17> npad;
+ };
+ static_assert(sizeof(NPadGeneric) == 0x350, "NPadGeneric is an invalid size");
+
+ enum class ColorReadError : u32_le {
+ ReadOk = 0,
+ ColorDoesntExist = 1,
+ NoController = 2,
+ };
+
+ struct NPadProperties {
+ union {
+ s64_le raw{};
+ BitField<11, 1, s64_le> is_vertical;
+ BitField<12, 1, s64_le> is_horizontal;
+ BitField<13, 1, s64_le> use_plus;
+ BitField<14, 1, s64_le> use_minus;
+ };
+ };
+
+ struct NPadDevice {
+ union {
+ u32_le raw{};
+ BitField<0, 1, s32_le> pro_controller;
+ BitField<1, 1, s32_le> handheld;
+ BitField<2, 1, s32_le> handheld_left;
+ BitField<3, 1, s32_le> handheld_right;
+ BitField<4, 1, s32_le> joycon_left;
+ BitField<5, 1, s32_le> joycon_right;
+ BitField<6, 1, s32_le> pokeball;
+ };
+ };
+
+ struct NPadEntry {
+ NPadType joy_styles;
+ NPadAssignments pad_assignment;
+
+ ColorReadError single_color_error;
+ ControllerColor single_color;
+
+ ColorReadError dual_color_error;
+ ControllerColor left_color;
+ ControllerColor right_color;
+
+ NPadGeneric main_controller_states;
+ NPadGeneric handheld_states;
+ NPadGeneric dual_states;
+ NPadGeneric left_joy_states;
+ NPadGeneric right_joy_states;
+ NPadGeneric pokeball_states;
+ NPadGeneric libnx; // TODO(ogniK): Find out what this actually is, libnx seems to only be
+ // relying on this for the time being
+ INSERT_PADDING_BYTES(
+ 0x708 *
+ 6); // TODO(ogniK): SixAxis states, require more information before implementation
+ NPadDevice device_type;
+ NPadProperties properties;
+ INSERT_PADDING_WORDS(1);
+ std::array<u32, 3> battery_level;
+ INSERT_PADDING_BYTES(0x5c);
+ INSERT_PADDING_BYTES(0xdf8);
+ };
+ static_assert(sizeof(NPadEntry) == 0x5000, "NPadEntry is an invalid size");
+
+ struct ControllerHolder {
+ Controller_NPad::NPadControllerType type;
+ bool is_connected;
+ };
+
+ NPadType style{};
+ std::array<NPadEntry, 10> shared_memory_entries{};
+ std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
+ buttons;
+ std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
+ std::vector<u32> supported_npad_id_types{};
+ NpadHoldType hold_type{NpadHoldType::Vertical};
+ Kernel::SharedPtr<Kernel::Event> styleset_changed_event;
+ Vibration last_processed_vibration{};
+ std::array<ControllerHolder, 10> connected_controllers{};
+ bool can_controllers_vibrate{true};
+
+ void InitNewlyAddedControler(std::size_t controller_idx);
+ bool IsControllerSupported(NPadControllerType controller) const;
+ NPadControllerType DecideBestController(NPadControllerType priority) const;
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.cpp b/src/core/hle/service/hid/controllers/stubbed.cpp
new file mode 100644
index 000000000..02fcfadd9
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/stubbed.cpp
@@ -0,0 +1,39 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/stubbed.h"
+
+namespace Service::HID {
+
+Controller_Stubbed::Controller_Stubbed() = default;
+Controller_Stubbed::~Controller_Stubbed() = default;
+
+void Controller_Stubbed::OnInit() {}
+
+void Controller_Stubbed::OnRelease() {}
+
+void Controller_Stubbed::OnUpdate(u8* data, std::size_t size) {
+ if (!smart_update) {
+ return;
+ }
+
+ CommonHeader header{};
+ header.timestamp = CoreTiming::GetTicks();
+ header.total_entry_count = 17;
+ header.entry_count = 0;
+ header.last_entry_index = 0;
+
+ std::memcpy(data + common_offset, &header, sizeof(CommonHeader));
+}
+
+void Controller_Stubbed::OnLoadInputDevices() {}
+
+void Controller_Stubbed::SetCommonHeaderOffset(std::size_t off) {
+ common_offset = off;
+ smart_update = true;
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/stubbed.h b/src/core/hle/service/hid/controllers/stubbed.h
new file mode 100644
index 000000000..4a21c643e
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/stubbed.h
@@ -0,0 +1,34 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Stubbed final : public ControllerBase {
+public:
+ Controller_Stubbed();
+ ~Controller_Stubbed() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+ void SetCommonHeaderOffset(std::size_t off);
+
+private:
+ bool smart_update{};
+ std::size_t common_offset{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.cpp b/src/core/hle/service/hid/controllers/touchscreen.cpp
new file mode 100644
index 000000000..43efef803
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/touchscreen.cpp
@@ -0,0 +1,65 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/frontend/emu_window.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/settings.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x400;
+
+Controller_Touchscreen::Controller_Touchscreen() = default;
+Controller_Touchscreen::~Controller_Touchscreen() = default;
+
+void Controller_Touchscreen::OnInit() {}
+
+void Controller_Touchscreen::OnRelease() {}
+
+void Controller_Touchscreen::OnUpdate(u8* data, std::size_t size) {
+ shared_memory.header.timestamp = CoreTiming::GetTicks();
+ shared_memory.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ shared_memory.header.entry_count = 0;
+ shared_memory.header.last_entry_index = 0;
+ return;
+ }
+ shared_memory.header.entry_count = 16;
+
+ const auto& last_entry =
+ shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
+ shared_memory.header.last_entry_index = (shared_memory.header.last_entry_index + 1) % 17;
+ auto& cur_entry = shared_memory.shared_memory_entries[shared_memory.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+
+ const auto [x, y, pressed] = touch_device->GetStatus();
+ auto& touch_entry = cur_entry.states[0];
+ if (pressed) {
+ touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
+ touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
+ touch_entry.diameter_x = 15;
+ touch_entry.diameter_y = 15;
+ touch_entry.rotation_angle = 0;
+ const u64 tick = CoreTiming::GetTicks();
+ touch_entry.delta_time = tick - last_touch;
+ last_touch = tick;
+ touch_entry.finger = 0;
+ cur_entry.entry_count = 1;
+ } else {
+ cur_entry.entry_count = 0;
+ }
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(TouchScreenSharedMemory));
+}
+
+void Controller_Touchscreen::OnLoadInputDevices() {
+ touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
+}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
new file mode 100644
index 000000000..e5db6e6ba
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -0,0 +1,63 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/frontend/input.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_Touchscreen final : public ControllerBase {
+public:
+ Controller_Touchscreen();
+ ~Controller_Touchscreen() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct TouchState {
+ u64_le delta_time;
+ u32_le attribute;
+ u32_le finger;
+ u32_le x;
+ u32_le y;
+ u32_le diameter_x;
+ u32_le diameter_y;
+ u32_le rotation_angle;
+ };
+ static_assert(sizeof(TouchState) == 0x28, "Touchstate is an invalid size");
+
+ struct TouchScreenEntry {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le entry_count;
+ std::array<TouchState, 16> states;
+ };
+ static_assert(sizeof(TouchScreenEntry) == 0x298, "TouchScreenEntry is an invalid size");
+
+ struct TouchScreenSharedMemory {
+ CommonHeader header;
+ std::array<TouchScreenEntry, 17> shared_memory_entries{};
+ INSERT_PADDING_BYTES(0x3c8);
+ };
+ static_assert(sizeof(TouchScreenSharedMemory) == 0x3000,
+ "TouchScreenSharedMemory is an invalid size");
+ TouchScreenSharedMemory shared_memory{};
+ std::unique_ptr<Input::TouchDevice> touch_device;
+ s64_le last_touch{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.cpp b/src/core/hle/service/hid/controllers/xpad.cpp
new file mode 100644
index 000000000..cd397c70b
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/xpad.cpp
@@ -0,0 +1,45 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/common_types.h"
+#include "core/core_timing.h"
+#include "core/hle/service/hid/controllers/xpad.h"
+
+namespace Service::HID {
+constexpr std::size_t SHARED_MEMORY_OFFSET = 0x3C00;
+
+Controller_XPad::Controller_XPad() = default;
+Controller_XPad::~Controller_XPad() = default;
+
+void Controller_XPad::OnInit() {}
+
+void Controller_XPad::OnRelease() {}
+
+void Controller_XPad::OnUpdate(u8* data, std::size_t size) {
+ for (auto& xpad_entry : shared_memory.shared_memory_entries) {
+ xpad_entry.header.timestamp = CoreTiming::GetTicks();
+ xpad_entry.header.total_entry_count = 17;
+
+ if (!IsControllerActivated()) {
+ xpad_entry.header.entry_count = 0;
+ xpad_entry.header.last_entry_index = 0;
+ return;
+ }
+ xpad_entry.header.entry_count = 16;
+
+ const auto& last_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
+ xpad_entry.header.last_entry_index = (xpad_entry.header.last_entry_index + 1) % 17;
+ auto& cur_entry = xpad_entry.pad_states[xpad_entry.header.last_entry_index];
+
+ cur_entry.sampling_number = last_entry.sampling_number + 1;
+ cur_entry.sampling_number2 = cur_entry.sampling_number;
+ }
+ // TODO(ogniK): Update xpad states
+
+ std::memcpy(data + SHARED_MEMORY_OFFSET, &shared_memory, sizeof(SharedMemory));
+}
+
+void Controller_XPad::OnLoadInputDevices() {}
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/xpad.h b/src/core/hle/service/hid/controllers/xpad.h
new file mode 100644
index 000000000..ff836989f
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/xpad.h
@@ -0,0 +1,60 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+
+namespace Service::HID {
+class Controller_XPad final : public ControllerBase {
+public:
+ Controller_XPad();
+ ~Controller_XPad() override;
+
+ // Called when the controller is initialized
+ void OnInit() override;
+
+ // When the controller is released
+ void OnRelease() override;
+
+ // When the controller is requesting an update for the shared memory
+ void OnUpdate(u8* data, std::size_t size) override;
+
+ // Called when input devices should be loaded
+ void OnLoadInputDevices() override;
+
+private:
+ struct AnalogStick {
+ s32_le x;
+ s32_le y;
+ };
+ static_assert(sizeof(AnalogStick) == 0x8, "AnalogStick is an invalid size");
+
+ struct XPadState {
+ s64_le sampling_number;
+ s64_le sampling_number2;
+ s32_le attributes;
+ u32_le pad_states;
+ AnalogStick x_stick;
+ AnalogStick y_stick;
+ };
+ static_assert(sizeof(XPadState) == 0x28, "XPadState is an invalid size");
+
+ struct XPadEntry {
+ CommonHeader header;
+ std::array<XPadState, 17> pad_states{};
+ INSERT_PADDING_BYTES(0x138);
+ };
+ static_assert(sizeof(XPadEntry) == 0x400, "XPadEntry is an invalid size");
+
+ struct SharedMemory {
+ std::array<XPadEntry, 4> shared_memory_entries{};
+ };
+ static_assert(sizeof(SharedMemory) == 0x1000, "SharedMemory is an invalid size");
+ SharedMemory shared_memory{};
+};
+} // namespace Service::HID
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 7c6b0a4e6..a9aa9ec78 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -19,6 +21,16 @@
#include "core/hle/service/service.h"
#include "core/settings.h"
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/hid/controllers/debug_pad.h"
+#include "core/hle/service/hid/controllers/gesture.h"
+#include "core/hle/service/hid/controllers/keyboard.h"
+#include "core/hle/service/hid/controllers/mouse.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/controllers/stubbed.h"
+#include "core/hle/service/hid/controllers/touchscreen.h"
+#include "core/hle/service/hid/controllers/xpad.h"
+
namespace Service::HID {
// Updating period for each HID device.
@@ -26,6 +38,22 @@ namespace Service::HID {
constexpr u64 pad_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 accelerometer_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
constexpr u64 gyroscope_update_ticks = CoreTiming::BASE_CLOCK_RATE / 100;
+constexpr std::size_t SHARED_MEMORY_SIZE = 0x40000;
+enum class HidController : std::size_t {
+ DebugPad,
+ Touchscreen,
+ Mouse,
+ Keyboard,
+ XPad,
+ Unknown1,
+ Unknown2,
+ Unknown3,
+ SixAxisSensor,
+ NPad,
+ Gesture,
+
+ MaxControllers,
+};
class IAppletResource final : public ServiceFramework<IAppletResource> {
public:
@@ -37,19 +65,57 @@ public:
auto& kernel = Core::System::GetInstance().Kernel();
shared_mem = Kernel::SharedMemory::Create(
- kernel, nullptr, 0x40000, Kernel::MemoryPermission::ReadWrite,
+ kernel, nullptr, SHARED_MEMORY_SIZE, Kernel::MemoryPermission::ReadWrite,
Kernel::MemoryPermission::Read, 0, Kernel::MemoryRegion::BASE, "HID:SharedMemory");
+ MakeController<Controller_DebugPad>(HidController::DebugPad);
+ MakeController<Controller_Touchscreen>(HidController::Touchscreen);
+ MakeController<Controller_Mouse>(HidController::Mouse);
+ MakeController<Controller_Keyboard>(HidController::Keyboard);
+ MakeController<Controller_XPad>(HidController::XPad);
+ MakeController<Controller_Stubbed>(HidController::Unknown1);
+ MakeController<Controller_Stubbed>(HidController::Unknown2);
+ MakeController<Controller_Stubbed>(HidController::Unknown3);
+ MakeController<Controller_Stubbed>(HidController::SixAxisSensor);
+ MakeController<Controller_NPad>(HidController::NPad);
+ MakeController<Controller_Gesture>(HidController::Gesture);
+
+ // Homebrew doesn't try to activate some controllers, so we activate them by default
+ GetController<Controller_NPad>(HidController::NPad).ActivateController();
+ GetController<Controller_Touchscreen>(HidController::Touchscreen).ActivateController();
+
+ GetController<Controller_Stubbed>(HidController::Unknown1).SetCommonHeaderOffset(0x4c00);
+ GetController<Controller_Stubbed>(HidController::Unknown2).SetCommonHeaderOffset(0x4e00);
+ GetController<Controller_Stubbed>(HidController::Unknown3).SetCommonHeaderOffset(0x5000);
+
// Register update callbacks
pad_update_event = CoreTiming::RegisterEvent(
"HID::UpdatePadCallback",
- [this](u64 userdata, int cycles_late) { UpdatePadCallback(userdata, cycles_late); });
+ [this](u64 userdata, int cycles_late) { UpdateControllers(userdata, cycles_late); });
// TODO(shinyquagsire23): Other update callbacks? (accel, gyro?)
CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
+ void ActivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->ActivateController();
+ }
+
+ void DeactivateController(HidController controller) {
+ controllers[static_cast<size_t>(controller)]->DeactivateController();
+ }
+
+ template <typename T>
+ void MakeController(HidController controller) {
+ controllers[static_cast<std::size_t>(controller)] = std::make_unique<T>();
+ }
+
+ template <typename T>
+ T& GetController(HidController controller) {
+ return static_cast<T&>(*controllers[static_cast<size_t>(controller)]);
+ }
+
~IAppletResource() {
CoreTiming::UnscheduleEvent(pad_update_event, 0);
}
@@ -62,200 +128,15 @@ private:
LOG_DEBUG(Service_HID, "called");
}
- void LoadInputDevices() {
- std::transform(Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_BEGIN,
- Settings::values.buttons.begin() + Settings::NativeButton::BUTTON_HID_END,
- buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
- std::transform(Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_BEGIN,
- Settings::values.analogs.begin() + Settings::NativeAnalog::STICK_HID_END,
- sticks.begin(), Input::CreateDevice<Input::AnalogDevice>);
- touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
- // TODO(shinyquagsire23): gyro, mouse, keyboard
- }
-
- void UpdatePadCallback(u64 userdata, int cycles_late) {
- SharedMemory mem{};
- std::memcpy(&mem, shared_mem->GetPointer(), sizeof(SharedMemory));
-
- if (Settings::values.is_device_reload_pending.exchange(false))
- LoadInputDevices();
-
- // Set up controllers as neon red+blue Joy-Con attached to console
- ControllerHeader& controller_header = mem.controllers[Controller_Handheld].header;
- controller_header.type = ControllerType_Handheld;
- controller_header.single_colors_descriptor = ColorDesc_ColorsNonexistent;
- controller_header.right_color_body = JOYCON_BODY_NEON_RED;
- controller_header.right_color_buttons = JOYCON_BUTTONS_NEON_RED;
- controller_header.left_color_body = JOYCON_BODY_NEON_BLUE;
- controller_header.left_color_buttons = JOYCON_BUTTONS_NEON_BLUE;
-
- for (std::size_t controller = 0; controller < mem.controllers.size(); controller++) {
- for (auto& layout : mem.controllers[controller].layouts) {
- layout.header.num_entries = HID_NUM_ENTRIES;
- layout.header.max_entry_index = HID_NUM_ENTRIES - 1;
-
- // HID shared memory stores the state of the past 17 samples in a circlular buffer,
- // each with a timestamp in number of samples since boot.
- const ControllerInputEntry& last_entry = layout.entries[layout.header.latest_entry];
-
- layout.header.timestamp_ticks = CoreTiming::GetTicks();
- layout.header.latest_entry = (layout.header.latest_entry + 1) % HID_NUM_ENTRIES;
-
- ControllerInputEntry& entry = layout.entries[layout.header.latest_entry];
- entry.timestamp = last_entry.timestamp + 1;
- // TODO(shinyquagsire23): Is this always identical to timestamp?
- entry.timestamp_2 = entry.timestamp;
-
- // TODO(shinyquagsire23): More than just handheld input
- if (controller != Controller_Handheld)
- continue;
-
- entry.connection_state = ConnectionState_Connected | ConnectionState_Wired;
-
- // TODO(shinyquagsire23): Set up some LUTs for each layout mapping in the future?
- // For now everything is just the default handheld layout, but split Joy-Con will
- // rotate the face buttons and directions for certain layouts.
- ControllerPadState& state = entry.buttons;
- using namespace Settings::NativeButton;
- state.a.Assign(buttons[A - BUTTON_HID_BEGIN]->GetStatus());
- state.b.Assign(buttons[B - BUTTON_HID_BEGIN]->GetStatus());
- state.x.Assign(buttons[X - BUTTON_HID_BEGIN]->GetStatus());
- state.y.Assign(buttons[Y - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick.Assign(buttons[LStick - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick.Assign(buttons[RStick - BUTTON_HID_BEGIN]->GetStatus());
- state.l.Assign(buttons[L - BUTTON_HID_BEGIN]->GetStatus());
- state.r.Assign(buttons[R - BUTTON_HID_BEGIN]->GetStatus());
- state.zl.Assign(buttons[ZL - BUTTON_HID_BEGIN]->GetStatus());
- state.zr.Assign(buttons[ZR - BUTTON_HID_BEGIN]->GetStatus());
- state.plus.Assign(buttons[Plus - BUTTON_HID_BEGIN]->GetStatus());
- state.minus.Assign(buttons[Minus - BUTTON_HID_BEGIN]->GetStatus());
-
- state.dleft.Assign(buttons[DLeft - BUTTON_HID_BEGIN]->GetStatus());
- state.dup.Assign(buttons[DUp - BUTTON_HID_BEGIN]->GetStatus());
- state.dright.Assign(buttons[DRight - BUTTON_HID_BEGIN]->GetStatus());
- state.ddown.Assign(buttons[DDown - BUTTON_HID_BEGIN]->GetStatus());
-
- state.lstick_left.Assign(buttons[LStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_up.Assign(buttons[LStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_right.Assign(buttons[LStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.lstick_down.Assign(buttons[LStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.rstick_left.Assign(buttons[RStick_Left - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_up.Assign(buttons[RStick_Up - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_right.Assign(buttons[RStick_Right - BUTTON_HID_BEGIN]->GetStatus());
- state.rstick_down.Assign(buttons[RStick_Down - BUTTON_HID_BEGIN]->GetStatus());
-
- state.sl.Assign(buttons[SL - BUTTON_HID_BEGIN]->GetStatus());
- state.sr.Assign(buttons[SR - BUTTON_HID_BEGIN]->GetStatus());
-
- const auto [stick_l_x_f, stick_l_y_f] = sticks[Joystick_Left]->GetStatus();
- const auto [stick_r_x_f, stick_r_y_f] = sticks[Joystick_Right]->GetStatus();
- entry.joystick_left_x = static_cast<s32>(stick_l_x_f * HID_JOYSTICK_MAX);
- entry.joystick_left_y = static_cast<s32>(stick_l_y_f * HID_JOYSTICK_MAX);
- entry.joystick_right_x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX);
- entry.joystick_right_y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX);
+ void UpdateControllers(u64 userdata, int cycles_late) {
+ const bool should_reload = Settings::values.is_device_reload_pending.exchange(false);
+ for (const auto& controller : controllers) {
+ if (should_reload) {
+ controller->OnLoadInputDevices();
}
+ controller->OnUpdate(shared_mem->GetPointer(), SHARED_MEMORY_SIZE);
}
- TouchScreen& touchscreen = mem.touchscreen;
- const u64 last_entry = touchscreen.header.latest_entry;
- const u64 curr_entry = (last_entry + 1) % touchscreen.entries.size();
- const u64 timestamp = CoreTiming::GetTicks();
- const u64 sample_counter = touchscreen.entries[last_entry].header.timestamp + 1;
- touchscreen.header.timestamp_ticks = timestamp;
- touchscreen.header.num_entries = touchscreen.entries.size();
- touchscreen.header.latest_entry = curr_entry;
- touchscreen.header.max_entry_index = touchscreen.entries.size();
- touchscreen.header.timestamp = timestamp;
- touchscreen.entries[curr_entry].header.timestamp = sample_counter;
-
- TouchScreenEntryTouch touch_entry{};
- auto [x, y, pressed] = touch_device->GetStatus();
- touch_entry.timestamp = timestamp;
- touch_entry.x = static_cast<u16>(x * Layout::ScreenUndocked::Width);
- touch_entry.y = static_cast<u16>(y * Layout::ScreenUndocked::Height);
- touch_entry.touch_index = 0;
-
- // TODO(DarkLordZach): Maybe try to derive these from EmuWindow?
- touch_entry.diameter_x = 15;
- touch_entry.diameter_y = 15;
- touch_entry.angle = 0;
-
- // TODO(DarkLordZach): Implement multi-touch support
- if (pressed) {
- touchscreen.entries[curr_entry].header.num_touches = 1;
- touchscreen.entries[curr_entry].touches[0] = touch_entry;
- } else {
- touchscreen.entries[curr_entry].header.num_touches = 0;
- }
-
- // TODO(shinyquagsire23): Properly implement mouse
- Mouse& mouse = mem.mouse;
- const u64 last_mouse_entry = mouse.header.latest_entry;
- const u64 curr_mouse_entry = (mouse.header.latest_entry + 1) % mouse.entries.size();
- const u64 mouse_sample_counter = mouse.entries[last_mouse_entry].timestamp + 1;
- mouse.header.timestamp_ticks = timestamp;
- mouse.header.num_entries = mouse.entries.size();
- mouse.header.max_entry_index = mouse.entries.size();
- mouse.header.latest_entry = curr_mouse_entry;
-
- mouse.entries[curr_mouse_entry].timestamp = mouse_sample_counter;
- mouse.entries[curr_mouse_entry].timestamp_2 = mouse_sample_counter;
-
- // TODO(shinyquagsire23): Properly implement keyboard
- Keyboard& keyboard = mem.keyboard;
- const u64 last_keyboard_entry = keyboard.header.latest_entry;
- const u64 curr_keyboard_entry =
- (keyboard.header.latest_entry + 1) % keyboard.entries.size();
- const u64 keyboard_sample_counter = keyboard.entries[last_keyboard_entry].timestamp + 1;
- keyboard.header.timestamp_ticks = timestamp;
- keyboard.header.num_entries = keyboard.entries.size();
- keyboard.header.latest_entry = last_keyboard_entry;
- keyboard.header.max_entry_index = keyboard.entries.size();
-
- keyboard.entries[curr_keyboard_entry].timestamp = keyboard_sample_counter;
- keyboard.entries[curr_keyboard_entry].timestamp_2 = keyboard_sample_counter;
-
- // TODO(shinyquagsire23): Figure out what any of these are
- for (auto& input : mem.unk_input_1) {
- const u64 last_input_entry = input.header.latest_entry;
- const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
- const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
-
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = input.entries.size();
- input.header.latest_entry = last_input_entry;
- input.header.max_entry_index = input.entries.size();
-
- input.entries[curr_input_entry].timestamp = input_sample_counter;
- input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
- }
-
- for (auto& input : mem.unk_input_2) {
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = 17;
- input.header.latest_entry = 0;
- input.header.max_entry_index = 0;
- }
-
- UnkInput3& input = mem.unk_input_3;
- const u64 last_input_entry = input.header.latest_entry;
- const u64 curr_input_entry = (input.header.latest_entry + 1) % input.entries.size();
- const u64 input_sample_counter = input.entries[last_input_entry].timestamp + 1;
-
- input.header.timestamp_ticks = timestamp;
- input.header.num_entries = input.entries.size();
- input.header.latest_entry = last_input_entry;
- input.header.max_entry_index = input.entries.size();
-
- input.entries[curr_input_entry].timestamp = input_sample_counter;
- input.entries[curr_input_entry].timestamp_2 = input_sample_counter;
-
- // TODO(shinyquagsire23): Signal events
-
- std::memcpy(shared_mem->GetPointer(), &mem, sizeof(SharedMemory));
-
- // Reschedule recurrent event
CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
}
@@ -265,11 +146,8 @@ private:
// CoreTiming update events
CoreTiming::EventType* pad_update_event;
- // Stored input state info
- std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
- buttons;
- std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID> sticks;
- std::unique_ptr<Input::TouchDevice> touch_device;
+ std::array<std::unique_ptr<ControllerBase>, static_cast<size_t>(HidController::MaxControllers)>
+ controllers{};
};
class IActiveVibrationDeviceList final : public ServiceFramework<IActiveVibrationDeviceList> {
@@ -299,9 +177,10 @@ public:
{11, &Hid::ActivateTouchScreen, "ActivateTouchScreen"},
{21, &Hid::ActivateMouse, "ActivateMouse"},
{31, &Hid::ActivateKeyboard, "ActivateKeyboard"},
+ {32, nullptr, "SendKeyboardLockKeyEvent"},
{40, nullptr, "AcquireXpadIdEventHandle"},
{41, nullptr, "ReleaseXpadIdEventHandle"},
- {51, nullptr, "ActivateXpad"},
+ {51, &Hid::ActivateXpad, "ActivateXpad"},
{55, nullptr, "GetXpadIds"},
{56, nullptr, "ActivateJoyXpad"},
{58, nullptr, "GetJoyXpadLifoHandle"},
@@ -329,6 +208,7 @@ public:
{80, nullptr, "GetGyroscopeZeroDriftMode"},
{81, nullptr, "ResetGyroscopeZeroDriftMode"},
{82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
+ {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
{91, &Hid::ActivateGesture, "ActivateGesture"},
{100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
{101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
@@ -362,8 +242,8 @@ public:
{206, &Hid::SendVibrationValues, "SendVibrationValues"},
{207, nullptr, "SendVibrationGcErmCommand"},
{208, nullptr, "GetActualVibrationGcErmCommand"},
- {209, nullptr, "BeginPermitVibrationSession"},
- {210, nullptr, "EndPermitVibrationSession"},
+ {209, &Hid::BeginPermitVibrationSession, "BeginPermitVibrationSession"},
+ {210, &Hid::EndPermitVibrationSession, "EndPermitVibrationSession"},
{300, &Hid::ActivateConsoleSixAxisSensor, "ActivateConsoleSixAxisSensor"},
{301, &Hid::StartConsoleSixAxisSensor, "StartConsoleSixAxisSensor"},
{302, nullptr, "StopConsoleSixAxisSensor"},
@@ -374,6 +254,7 @@ public:
{307, nullptr, "FinalizeSevenSixAxisSensor"},
{308, nullptr, "SetSevenSixAxisSensorFusionStrength"},
{309, nullptr, "GetSevenSixAxisSensorFusionStrength"},
+ {310, nullptr, "ResetSevenSixAxisSensorTimestamp"},
{400, nullptr, "IsUsbFullKeyControllerEnabled"},
{401, nullptr, "EnableUsbFullKeyController"},
{402, nullptr, "IsUsbFullKeyControllerConnected"},
@@ -389,28 +270,35 @@ public:
{505, nullptr, "SetPalmaFrModeType"},
{506, nullptr, "ReadPalmaStep"},
{507, nullptr, "EnablePalmaStep"},
- {508, nullptr, "SuspendPalmaStep"},
- {509, nullptr, "ResetPalmaStep"},
- {510, nullptr, "ReadPalmaApplicationSection"},
- {511, nullptr, "WritePalmaApplicationSection"},
- {512, nullptr, "ReadPalmaUniqueCode"},
- {513, nullptr, "SetPalmaUniqueCodeInvalid"},
+ {508, nullptr, "ResetPalmaStep"},
+ {509, nullptr, "ReadPalmaApplicationSection"},
+ {510, nullptr, "WritePalmaApplicationSection"},
+ {511, nullptr, "ReadPalmaUniqueCode"},
+ {512, nullptr, "SetPalmaUniqueCodeInvalid"},
+ {513, nullptr, "WritePalmaActivityEntry"},
+ {514, nullptr, "WritePalmaRgbLedPatternEntry"},
+ {515, nullptr, "WritePalmaWaveEntry"},
+ {516, nullptr, "SetPalmaDataBaseIdentificationVersion"},
+ {517, nullptr, "GetPalmaDataBaseIdentificationVersion"},
+ {518, nullptr, "SuspendPalmaFeature"},
+ {519, nullptr, "GetPalmaOperationResult"},
+ {520, nullptr, "ReadPalmaPlayLog"},
+ {521, nullptr, "ResetPalmaPlayLog"},
+ {522, nullptr, "SetIsPalmaAllConnectable"},
+ {523, nullptr, "SetIsPalmaPairedConnectable"},
+ {524, nullptr, "PairPalma"},
+ {525, nullptr, "SetPalmaBoostMode"},
{1000, nullptr, "SetNpadCommunicationMode"},
{1001, nullptr, "GetNpadCommunicationMode"},
};
// clang-format on
RegisterHandlers(functions);
-
- auto& kernel = Core::System::GetInstance().Kernel();
- event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "hid:EventHandle");
}
~Hid() = default;
private:
std::shared_ptr<IAppletResource> applet_resource;
- u32 joy_hold_type{0};
- Kernel::SharedPtr<Kernel::Event> event;
void CreateAppletResource(Kernel::HLERequestContext& ctx) {
if (applet_resource == nullptr) {
@@ -423,31 +311,59 @@ private:
LOG_DEBUG(Service_HID, "called");
}
+ void ActivateXpad(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::XPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
void ActivateDebugPad(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::DebugPad);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateTouchScreen(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Touchscreen);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateMouse(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Mouse);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateKeyboard(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Keyboard);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void ActivateGesture(Kernel::HLERequestContext& ctx) {
+ applet_resource->ActivateController(HidController::Gesture);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
+ // Should have no effect with how our npad sets up the data
+ applet_resource->ActivateController(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
}
void StartSixAxisSensor(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto handle = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -468,84 +384,168 @@ private:
}
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto supported_styleset = rp.PopRaw<u32>();
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedStyleSet({supported_styleset});
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ LOG_DEBUG(Service_HID, "called");
}
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx) {
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u32>(controller.GetSupportedStyleSet().raw);
+ LOG_DEBUG(Service_HID, "called");
}
void SetSupportedNpadIdType(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetSupportedNPadIdTypes(ctx.ReadBuffer().data(), ctx.GetReadBufferSize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void ActivateNpad(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ applet_resource->ActivateController(HidController::NPad);
+ LOG_DEBUG(Service_HID, "called");
}
void AcquireNpadStyleSetUpdateEventHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(event);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.PushCopyObjects(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetStyleSetChangedEvent());
+ LOG_DEBUG(Service_HID, "called");
}
void DisconnectNpad(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .DisconnectNPad(npad_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void GetPlayerLedPattern(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.PushRaw<u64>(applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLedPattern(npad_id)
+ .raw);
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::RequestParser rp{ctx};
+ const auto hold_type = rp.PopRaw<u64>();
+ controller.SetHoldType(Controller_NPad::NpadHoldType{hold_type});
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void GetNpadJoyHoldType(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 3};
+ const auto& controller =
+ applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push(joy_hold_type);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u64>(static_cast<u64>(controller.GetHoldType()));
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeSingleByDefault(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto npad_id = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
+ void BeginPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetVibrationEnabled(true);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void EndPermitVibrationSession(Kernel::HLERequestContext& ctx) {
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .SetVibrationEnabled(false);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_DEBUG(Service_HID, "called");
+ }
+
void SendVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto controller_id = rp.PopRaw<u32>();
+ const auto vibration_values = rp.PopRaw<Controller_NPad::Vibration>();
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController({controller_id}, {vibration_values});
+ LOG_DEBUG(Service_HID, "called");
}
- void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ void SendVibrationValues(Kernel::HLERequestContext& ctx) {
+ const auto controllers = ctx.ReadBuffer(0);
+ const auto vibrations = ctx.ReadBuffer(1);
+
+ std::vector<u32> controller_list(controllers.size() / sizeof(u32));
+ std::vector<Controller_NPad::Vibration> vibration_list(vibrations.size() /
+ sizeof(Controller_NPad::Vibration));
+
+ std::memcpy(controller_list.data(), controllers.data(), controllers.size());
+ std::memcpy(vibration_list.data(), vibrations.data(), vibrations.size());
+ std::transform(controller_list.begin(), controller_list.end(), controller_list.begin(),
+ [](u32 controller_id) { return controller_id - 3; });
+
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .VibrateController(controller_list, vibration_list);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
+ }
+
+ void GetActualVibrationValue(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<Controller_NPad::Vibration>(
+ applet_resource->GetController<Controller_NPad>(HidController::NPad)
+ .GetLastVibration());
+ LOG_DEBUG(Service_HID, "called");
}
void SetNpadJoyAssignmentModeDual(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto npad_id = rp.PopRaw<u32>();
+ auto& controller = applet_resource->GetController<Controller_NPad>(HidController::NPad);
+ controller.SetNpadMode(npad_id, Controller_NPad::NPadAssignments::Dual);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ LOG_DEBUG(Service_HID, "called");
}
void MergeSingleJoyAsDualJoy(Kernel::HLERequestContext& ctx) {
@@ -555,6 +555,8 @@ private:
}
void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto mode = rp.PopRaw<u32>();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
@@ -563,8 +565,9 @@ private:
void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 4};
rb.Push(RESULT_SUCCESS);
- rb.Push<u64>(0);
- LOG_WARNING(Service_HID, "(STUBBED) called");
+ rb.Push<u32>(1);
+ rb.Push<u32>(0);
+ LOG_DEBUG(Service_HID, "called");
}
void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
@@ -574,12 +577,6 @@ private:
LOG_DEBUG(Service_HID, "called");
}
- void SendVibrationValues(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
-
void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
@@ -597,18 +594,6 @@ private:
rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_HID, "(STUBBED) called");
}
-
- void ActivateGesture(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
-
- void ActivateNpadWithRevision(Kernel::HLERequestContext& ctx) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(RESULT_SUCCESS);
- LOG_WARNING(Service_HID, "(STUBBED) called");
- }
};
class HidDbg final : public ServiceFramework<HidDbg> {
@@ -650,6 +635,7 @@ public:
{140, nullptr, "DeactivateConsoleSixAxisSensor"},
{141, nullptr, "GetConsoleSixAxisSensorSamplingFrequency"},
{142, nullptr, "DeactivateSevenSixAxisSensor"},
+ {143, nullptr, "GetConsoleSixAxisSensorCountStates"},
{201, nullptr, "ActivateFirmwareUpdate"},
{202, nullptr, "DeactivateFirmwareUpdate"},
{203, nullptr, "StartFirmwareUpdate"},
@@ -660,12 +646,23 @@ public:
{208, nullptr, "StartFirmwareUpdateForRevert"},
{209, nullptr, "GetAvailableFirmwareVersionForRevert"},
{210, nullptr, "IsFirmwareUpdatingDevice"},
+ {211, nullptr, "StartFirmwareUpdateIndividual"},
+ {215, nullptr, "SetUsbFirmwareForceUpdateEnabled"},
+ {216, nullptr, "SetAllKuinaDevicesToFirmwareUpdateMode"},
{221, nullptr, "UpdateControllerColor"},
{222, nullptr, "ConnectUsbPadsAsync"},
{223, nullptr, "DisconnectUsbPadsAsync"},
{224, nullptr, "UpdateDesignInfo"},
{225, nullptr, "GetUniquePadDriverState"},
{226, nullptr, "GetSixAxisSensorDriverStates"},
+ {227, nullptr, "GetRxPacketHistory"},
+ {228, nullptr, "AcquireOperationEventHandle"},
+ {229, nullptr, "ReadSerialFlash"},
+ {230, nullptr, "WriteSerialFlash"},
+ {231, nullptr, "GetOperationResult"},
+ {232, nullptr, "EnableShipmentMode"},
+ {233, nullptr, "ClearPairingInfo"},
+ {234, nullptr, "GetUniquePadDeviceTypeSetInternal"},
{301, nullptr, "GetAbstractedPadHandles"},
{302, nullptr, "GetAbstractedPadState"},
{303, nullptr, "GetAbstractedPadsState"},
@@ -673,6 +670,8 @@ public:
{322, nullptr, "UnsetAutoPilotVirtualPadState"},
{323, nullptr, "UnsetAllAutoPilotVirtualPadState"},
{350, nullptr, "AddRegisteredDevice"},
+ {400, nullptr, "DisableExternalMcuOnNxDevice"},
+ {401, nullptr, "DisableRailDeviceFiltering"},
};
// clang-format on
@@ -708,7 +707,9 @@ public:
{307, nullptr, "GetNpadSystemExtStyle"},
{308, nullptr, "ApplyNpadSystemCommonPolicyFull"},
{309, nullptr, "GetNpadFullKeyGripColor"},
+ {310, nullptr, "GetMaskedSupportedNpadStyleSet"},
{311, nullptr, "SetNpadPlayerLedBlinkingDevice"},
+ {312, nullptr, "SetSupportedNpadStyleSetAll"},
{321, nullptr, "GetUniquePadsFromNpad"},
{322, nullptr, "GetIrSensorState"},
{323, nullptr, "GetXcdHandleForNpadWithIrSensor"},
@@ -733,6 +734,7 @@ public:
{546, nullptr, "AcquireDeviceRegisteredEventForControllerSupport"},
{547, nullptr, "GetAllowedBluetoothLinksCount"},
{548, nullptr, "GetRegisteredDevices"},
+ {549, nullptr, "GetConnectableRegisteredDevices"},
{700, nullptr, "ActivateUniquePad"},
{702, nullptr, "AcquireUniquePadConnectionEventHandle"},
{703, nullptr, "GetUniquePadIds"},
@@ -761,6 +763,7 @@ public:
{850, nullptr, "IsUsbFullKeyControllerEnabled"},
{851, nullptr, "EnableUsbFullKeyController"},
{852, nullptr, "IsUsbConnected"},
+ {870, nullptr, "IsHandheldButtonPressedOnConsoleMode"},
{900, nullptr, "ActivateInputDetector"},
{901, nullptr, "NotifyInputDetector"},
{1000, nullptr, "InitializeFirmwareUpdate"},
@@ -780,6 +783,12 @@ public:
{1052, nullptr, "CancelSixAxisSensorAccurateUserCalibration"},
{1053, nullptr, "GetSixAxisSensorAccurateUserCalibrationState"},
{1100, nullptr, "GetHidbusSystemServiceObject"},
+ {1120, nullptr, "SetFirmwareHotfixUpdateSkipEnabled"},
+ {1130, nullptr, "InitializeUsbFirmwareUpdate"},
+ {1131, nullptr, "FinalizeUsbFirmwareUpdate"},
+ {1132, nullptr, "CheckUsbFirmwareUpdateRequired"},
+ {1133, nullptr, "StartUsbFirmwareUpdate"},
+ {1134, nullptr, "GetUsbFirmwareUpdateState"},
};
// clang-format on
@@ -818,6 +827,7 @@ public:
{11, nullptr, "EnableJoyPollingReceiveMode"},
{12, nullptr, "DisableJoyPollingReceiveMode"},
{13, nullptr, "GetPollingData"},
+ {14, nullptr, "SetStatusManagerType"},
};
// clang-format on
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 88d926808..773035460 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,408 +4,12 @@
#pragma once
-#include <array>
-#include "common/bit_field.h"
-#include "common/common_types.h"
-#include "core/hle/service/service.h"
+namespace SM {
+class ServiceManager;
+}
namespace Service::HID {
-// Begin enums and output structs
-
-constexpr u32 HID_NUM_ENTRIES = 17;
-constexpr u32 HID_NUM_LAYOUTS = 7;
-constexpr s32 HID_JOYSTICK_MAX = 0x8000;
-constexpr s32 HID_JOYSTICK_MIN = -0x8000;
-
-constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28;
-constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A;
-constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6;
-constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E;
-
-enum ControllerType : u32 {
- ControllerType_ProController = 1 << 0,
- ControllerType_Handheld = 1 << 1,
- ControllerType_JoyconPair = 1 << 2,
- ControllerType_JoyconLeft = 1 << 3,
- ControllerType_JoyconRight = 1 << 4,
-};
-
-enum ControllerLayoutType : u32 {
- Layout_ProController = 0, // Pro Controller or HID gamepad
- Layout_Handheld = 1, // Two Joy-Con docked to rails
- Layout_Single = 2, // Horizontal single Joy-Con or pair of Joy-Con, adjusted for orientation
- Layout_Left = 3, // Only raw left Joy-Con state, no orientation adjustment
- Layout_Right = 4, // Only raw right Joy-Con state, no orientation adjustment
- Layout_DefaultDigital = 5, // Same as next, but sticks have 8-direction values only
- Layout_Default = 6, // Safe default, single Joy-Con have buttons/sticks rotated for orientation
-};
-
-enum ControllerColorDescription {
- ColorDesc_ColorsNonexistent = 1 << 1,
-};
-
-enum ControllerConnectionState {
- ConnectionState_Connected = 1 << 0,
- ConnectionState_Wired = 1 << 1,
-};
-
-enum ControllerJoystick {
- Joystick_Left = 0,
- Joystick_Right = 1,
-};
-
-enum ControllerID {
- Controller_Player1 = 0,
- Controller_Player2 = 1,
- Controller_Player3 = 2,
- Controller_Player4 = 3,
- Controller_Player5 = 4,
- Controller_Player6 = 5,
- Controller_Player7 = 6,
- Controller_Player8 = 7,
- Controller_Handheld = 8,
- Controller_Unknown = 9,
-};
-
-// End enums and output structs
-
-// Begin UnkInput3
-
-struct UnkInput3Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput3Header) == 0x20, "HID UnkInput3 header structure has incorrect size");
-
-struct UnkInput3Entry {
- u64 timestamp;
- u64 timestamp_2;
- u64 unk_8;
- u64 unk_10;
- u64 unk_18;
-};
-static_assert(sizeof(UnkInput3Entry) == 0x28, "HID UnkInput3 entry structure has incorrect size");
-
-struct UnkInput3 {
- UnkInput3Header header;
- std::array<UnkInput3Entry, 17> entries;
- std::array<u8, 0x138> padding;
-};
-static_assert(sizeof(UnkInput3) == 0x400, "HID UnkInput3 structure has incorrect size");
-
-// End UnkInput3
-
-// Begin TouchScreen
-
-struct TouchScreenHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
- u64 timestamp;
-};
-static_assert(sizeof(TouchScreenHeader) == 0x28,
- "HID touch screen header structure has incorrect size");
-
-struct TouchScreenEntryHeader {
- u64 timestamp;
- u64 num_touches;
-};
-static_assert(sizeof(TouchScreenEntryHeader) == 0x10,
- "HID touch screen entry header structure has incorrect size");
-
-struct TouchScreenEntryTouch {
- u64 timestamp;
- u32 padding;
- u32 touch_index;
- u32 x;
- u32 y;
- u32 diameter_x;
- u32 diameter_y;
- u32 angle;
- u32 padding_2;
-};
-static_assert(sizeof(TouchScreenEntryTouch) == 0x28,
- "HID touch screen touch structure has incorrect size");
-
-struct TouchScreenEntry {
- TouchScreenEntryHeader header;
- std::array<TouchScreenEntryTouch, 16> touches;
- u64 unk;
-};
-static_assert(sizeof(TouchScreenEntry) == 0x298,
- "HID touch screen entry structure has incorrect size");
-
-struct TouchScreen {
- TouchScreenHeader header;
- std::array<TouchScreenEntry, 17> entries;
- std::array<u8, 0x3c0> padding;
-};
-static_assert(sizeof(TouchScreen) == 0x3000, "HID touch screen structure has incorrect size");
-
-// End TouchScreen
-
-// Begin Mouse
-
-struct MouseHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(MouseHeader) == 0x20, "HID mouse header structure has incorrect size");
-
-struct MouseButtonState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> left;
- BitField<1, 1, u64> right;
- BitField<2, 1, u64> middle;
- BitField<3, 1, u64> forward;
- BitField<4, 1, u64> back;
- };
-};
-
-struct MouseEntry {
- u64 timestamp;
- u64 timestamp_2;
- u32 x;
- u32 y;
- u32 velocity_x;
- u32 velocity_y;
- u32 scroll_velocity_x;
- u32 scroll_velocity_y;
- MouseButtonState buttons;
-};
-static_assert(sizeof(MouseEntry) == 0x30, "HID mouse entry structure has incorrect size");
-
-struct Mouse {
- MouseHeader header;
- std::array<MouseEntry, 17> entries;
- std::array<u8, 0xB0> padding;
-};
-static_assert(sizeof(Mouse) == 0x400, "HID mouse structure has incorrect size");
-
-// End Mouse
-
-// Begin Keyboard
-
-struct KeyboardHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(KeyboardHeader) == 0x20, "HID keyboard header structure has incorrect size");
-
-struct KeyboardModifierKeyState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> lctrl;
- BitField<1, 1, u64> lshift;
- BitField<2, 1, u64> lalt;
- BitField<3, 1, u64> lmeta;
- BitField<4, 1, u64> rctrl;
- BitField<5, 1, u64> rshift;
- BitField<6, 1, u64> ralt;
- BitField<7, 1, u64> rmeta;
- BitField<8, 1, u64> capslock;
- BitField<9, 1, u64> scrolllock;
- BitField<10, 1, u64> numlock;
- };
-};
-
-struct KeyboardEntry {
- u64 timestamp;
- u64 timestamp_2;
- KeyboardModifierKeyState modifier;
- u32 keys[8];
-};
-static_assert(sizeof(KeyboardEntry) == 0x38, "HID keyboard entry structure has incorrect size");
-
-struct Keyboard {
- KeyboardHeader header;
- std::array<KeyboardEntry, 17> entries;
- std::array<u8, 0x28> padding;
-};
-static_assert(sizeof(Keyboard) == 0x400, "HID keyboard structure has incorrect size");
-
-// End Keyboard
-
-// Begin UnkInput1
-
-struct UnkInput1Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput1Header) == 0x20, "HID UnkInput1 header structure has incorrect size");
-
-struct UnkInput1Entry {
- u64 timestamp;
- u64 timestamp_2;
- u64 unk_8;
- u64 unk_10;
- u64 unk_18;
-};
-static_assert(sizeof(UnkInput1Entry) == 0x28, "HID UnkInput1 entry structure has incorrect size");
-
-struct UnkInput1 {
- UnkInput1Header header;
- std::array<UnkInput1Entry, 17> entries;
- std::array<u8, 0x138> padding;
-};
-static_assert(sizeof(UnkInput1) == 0x400, "HID UnkInput1 structure has incorrect size");
-
-// End UnkInput1
-
-// Begin UnkInput2
-
-struct UnkInput2Header {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(UnkInput2Header) == 0x20, "HID UnkInput2 header structure has incorrect size");
-
-struct UnkInput2 {
- UnkInput2Header header;
- std::array<u8, 0x1E0> padding;
-};
-static_assert(sizeof(UnkInput2) == 0x200, "HID UnkInput2 structure has incorrect size");
-
-// End UnkInput2
-
-// Begin Controller
-
-struct ControllerMAC {
- u64 timestamp;
- std::array<u8, 0x8> mac;
- u64 unk;
- u64 timestamp_2;
-};
-static_assert(sizeof(ControllerMAC) == 0x20, "HID controller MAC structure has incorrect size");
-
-struct ControllerHeader {
- u32 type;
- u32 is_half;
- u32 single_colors_descriptor;
- u32 single_color_body;
- u32 single_color_buttons;
- u32 split_colors_descriptor;
- u32 left_color_body;
- u32 left_color_buttons;
- u32 right_color_body;
- u32 right_color_buttons;
-};
-static_assert(sizeof(ControllerHeader) == 0x28,
- "HID controller header structure has incorrect size");
-
-struct ControllerLayoutHeader {
- u64 timestamp_ticks;
- u64 num_entries;
- u64 latest_entry;
- u64 max_entry_index;
-};
-static_assert(sizeof(ControllerLayoutHeader) == 0x20,
- "HID controller layout header structure has incorrect size");
-
-struct ControllerPadState {
- union {
- u64 hex{};
-
- // Buttons
- BitField<0, 1, u64> a;
- BitField<1, 1, u64> b;
- BitField<2, 1, u64> x;
- BitField<3, 1, u64> y;
- BitField<4, 1, u64> lstick;
- BitField<5, 1, u64> rstick;
- BitField<6, 1, u64> l;
- BitField<7, 1, u64> r;
- BitField<8, 1, u64> zl;
- BitField<9, 1, u64> zr;
- BitField<10, 1, u64> plus;
- BitField<11, 1, u64> minus;
-
- // D-pad buttons
- BitField<12, 1, u64> dleft;
- BitField<13, 1, u64> dup;
- BitField<14, 1, u64> dright;
- BitField<15, 1, u64> ddown;
-
- // Left stick directions
- BitField<16, 1, u64> lstick_left;
- BitField<17, 1, u64> lstick_up;
- BitField<18, 1, u64> lstick_right;
- BitField<19, 1, u64> lstick_down;
-
- // Right stick directions
- BitField<20, 1, u64> rstick_left;
- BitField<21, 1, u64> rstick_up;
- BitField<22, 1, u64> rstick_right;
- BitField<23, 1, u64> rstick_down;
-
- BitField<24, 1, u64> sl;
- BitField<25, 1, u64> sr;
- };
-};
-
-struct ControllerInputEntry {
- u64 timestamp;
- u64 timestamp_2;
- ControllerPadState buttons;
- s32 joystick_left_x;
- s32 joystick_left_y;
- s32 joystick_right_x;
- s32 joystick_right_y;
- u64 connection_state;
-};
-static_assert(sizeof(ControllerInputEntry) == 0x30,
- "HID controller input entry structure has incorrect size");
-
-struct ControllerLayout {
- ControllerLayoutHeader header;
- std::array<ControllerInputEntry, 17> entries;
-};
-static_assert(sizeof(ControllerLayout) == 0x350,
- "HID controller layout structure has incorrect size");
-
-struct Controller {
- ControllerHeader header;
- std::array<ControllerLayout, HID_NUM_LAYOUTS> layouts;
- std::array<u8, 0x2a70> unk_1;
- ControllerMAC mac_left;
- ControllerMAC mac_right;
- std::array<u8, 0xdf8> unk_2;
-};
-static_assert(sizeof(Controller) == 0x5000, "HID controller structure has incorrect size");
-
-// End Controller
-
-struct SharedMemory {
- UnkInput3 unk_input_3;
- TouchScreen touchscreen;
- Mouse mouse;
- Keyboard keyboard;
- std::array<UnkInput1, 4> unk_input_1;
- std::array<UnkInput2, 3> unk_input_2;
- std::array<u8, 0x800> unk_section_8;
- std::array<u8, 0x4000> controller_serials;
- std::array<Controller, 10> controllers;
- std::array<u8, 0x4600> unk_section_9;
-};
-static_assert(sizeof(SharedMemory) == 0x40000, "HID Shared Memory structure has incorrect size");
-
/// Reload input devices. Used when input configuration changed
void ReloadInputDevices();
diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp
index 7321584e1..164c57e18 100644
--- a/src/core/hle/service/lbl/lbl.cpp
+++ b/src/core/hle/service/lbl/lbl.cpp
@@ -18,35 +18,35 @@ public:
explicit LBL() : ServiceFramework{"lbl"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "GetCurrentBacklightLevel"},
- {4, nullptr, "Unknown4"},
- {5, nullptr, "GetAlsComputedBacklightLevel"},
- {6, nullptr, "TurnOffBacklight"},
- {7, nullptr, "TurnOnBacklight"},
- {8, nullptr, "GetBacklightStatus"},
- {9, nullptr, "Unknown5"},
- {10, nullptr, "Unknown6"},
- {11, nullptr, "Unknown7"},
- {12, nullptr, "Unknown8"},
- {13, nullptr, "Unknown9"},
- {14, nullptr, "Unknown10"},
- {15, nullptr, "GetAutoBrightnessSetting"},
- {16, nullptr, "ReadRawLightSensor"},
- {17, nullptr, "Unknown11"},
- {18, nullptr, "Unknown12"},
- {19, nullptr, "Unknown13"},
- {20, nullptr, "Unknown14"},
- {21, nullptr, "Unknown15"},
- {22, nullptr, "Unknown16"},
- {23, nullptr, "Unknown17"},
- {24, nullptr, "Unknown18"},
- {25, nullptr, "Unknown19"},
+ {0, nullptr, "SaveCurrentSetting"},
+ {1, nullptr, "LoadCurrentSetting"},
+ {2, nullptr, "SetCurrentBrightnessSetting"},
+ {3, nullptr, "GetCurrentBrightnessSetting"},
+ {4, nullptr, "ApplyCurrentBrightnessSettingToBacklight"},
+ {5, nullptr, "GetBrightnessSettingAppliedToBacklight"},
+ {6, nullptr, "SwitchBacklightOn"},
+ {7, nullptr, "SwitchBacklightOff"},
+ {8, nullptr, "GetBacklightSwitchStatus"},
+ {9, nullptr, "EnableDimming"},
+ {10, nullptr, "DisableDimming"},
+ {11, nullptr, "IsDimmingEnabled"},
+ {12, nullptr, "EnableAutoBrightnessControl"},
+ {13, nullptr, "DisableAutoBrightnessControl"},
+ {14, nullptr, "IsAutoBrightnessControlEnabled"},
+ {15, nullptr, "SetAmbientLightSensorValue"},
+ {16, nullptr, "GetAmbientLightSensorValue"},
+ {17, nullptr, "SetBrightnessReflectionDelayLevel"},
+ {18, nullptr, "GetBrightnessReflectionDelayLevel"},
+ {19, nullptr, "SetCurrentBrightnessMapping"},
+ {20, nullptr, "GetCurrentBrightnessMapping"},
+ {21, nullptr, "SetCurrentAmbientLightSensorMapping"},
+ {22, nullptr, "GetCurrentAmbientLightSensorMapping"},
+ {23, nullptr, "IsAmbientLightSensorAvailable"},
+ {24, nullptr, "SetCurrentBrightnessSettingForVrMode"},
+ {25, nullptr, "GetCurrentBrightnessSettingForVrMode"},
{26, &LBL::EnableVrMode, "EnableVrMode"},
{27, &LBL::DisableVrMode, "DisableVrMode"},
- {28, &LBL::GetVrMode, "GetVrMode"},
+ {28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"},
};
// clang-format on
@@ -72,7 +72,7 @@ private:
LOG_DEBUG(Service_LBL, "called");
}
- void GetVrMode(Kernel::HLERequestContext& ctx) {
+ void IsVrModeEnabled(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push(vr_mode_enabled);
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index ec32faf15..d607d985e 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -3,9 +3,13 @@
// Refer to the license.txt file included.
#include <memory>
+#include <fmt/format.h>
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/service/ldr/ldr.h"
#include "core/hle/service/service.h"
+#include "core/loader/nro.h"
namespace Service::LDR {
@@ -59,16 +63,58 @@ public:
explicit RelocatableObject() : ServiceFramework{"ldr:ro"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "LoadNro"},
+ {0, &RelocatableObject::LoadNro, "LoadNro"},
{1, nullptr, "UnloadNro"},
- {2, nullptr, "LoadNrr"},
+ {2, &RelocatableObject::LoadNrr, "LoadNrr"},
{3, nullptr, "UnloadNrr"},
- {4, nullptr, "Initialize"},
+ {4, &RelocatableObject::Initialize, "Initialize"},
};
// clang-format on
RegisterHandlers(functions);
}
+
+ void LoadNrr(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_LDR, "(STUBBED) called");
+ }
+
+ void LoadNro(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ rp.Skip(2, false);
+ const VAddr nro_addr{rp.Pop<VAddr>()};
+ const u64 nro_size{rp.Pop<u64>()};
+ const VAddr bss_addr{rp.Pop<VAddr>()};
+ const u64 bss_size{rp.Pop<u64>()};
+
+ // Read NRO data from memory
+ std::vector<u8> nro_data(nro_size);
+ Memory::ReadBlock(nro_addr, nro_data.data(), nro_size);
+
+ // Load NRO as new executable module
+ const VAddr addr{*Core::CurrentProcess()->VMManager().FindFreeRegion(nro_size + bss_size)};
+ Loader::AppLoader_NRO::LoadNro(nro_data, fmt::format("nro-{:08x}", addr), addr);
+
+ // TODO(bunnei): This is an incomplete implementation. It was tested with Super Mario Party.
+ // It is currently missing:
+ // - Signature checks with LoadNRR
+ // - Checking if a module has already been loaded
+ // - Using/validating BSS, etc. params (these are used from NRO header instead)
+ // - Error checking
+ // - ...Probably other things
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(addr);
+ LOG_WARNING(Service_LDR, "(STUBBED) called");
+ }
+
+ void Initialize(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ LOG_WARNING(Service_LDR, "(STUBBED) called");
+ }
};
void InstallInterfaces(SM::ServiceManager& sm) {
diff --git a/src/core/hle/service/mm/mm_u.cpp b/src/core/hle/service/mm/mm_u.cpp
index 7b91bb258..e1f17a926 100644
--- a/src/core/hle/service/mm/mm_u.cpp
+++ b/src/core/hle/service/mm/mm_u.cpp
@@ -14,14 +14,14 @@ public:
explicit MM_U() : ServiceFramework{"mm:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &MM_U::Initialize, "InitializeOld"},
- {1, &MM_U::Finalize, "FinalizeOld"},
- {2, &MM_U::SetAndWait, "SetAndWaitOld"},
- {3, &MM_U::Get, "GetOld"},
- {4, &MM_U::Initialize, "Initialize"},
- {5, &MM_U::Finalize, "Finalize"},
- {6, &MM_U::SetAndWait, "SetAndWait"},
- {7, &MM_U::Get, "Get"},
+ {0, &MM_U::Initialize, "Initialize"},
+ {1, &MM_U::Finalize, "Finalize"},
+ {2, &MM_U::SetAndWait, "SetAndWait"},
+ {3, &MM_U::Get, "Get"},
+ {4, &MM_U::InitializeWithId, "InitializeWithId"},
+ {5, &MM_U::FinalizeWithId, "FinalizeWithId"},
+ {6, &MM_U::SetAndWaitWithId, "SetAndWaitWithId"},
+ {7, &MM_U::GetWithId, "GetWithId"},
};
// clang-format on
@@ -59,9 +59,43 @@ private:
rb.Push(current);
}
+ void InitializeWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(id); // Any non zero value
+ }
+
+ void FinalizeWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void SetAndWaitWithId(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ u32 input_id = rp.Pop<u32>();
+ min = rp.Pop<u32>();
+ max = rp.Pop<u32>();
+ current = min;
+
+ LOG_WARNING(Service_MM, "(STUBBED) called, input_id=0x{:X}, min=0x{:X}, max=0x{:X}",
+ input_id, min, max);
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetWithId(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_MM, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push(current);
+ }
+
u32 min{0};
u32 max{0};
u32 current{0};
+ u32 id{1};
};
void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 8fec97db8..30e542542 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -10,12 +10,13 @@
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/service.h"
#include "core/hle/service/sm/sm.h"
+#include "core/settings.h"
namespace Service::NFC {
class IAm final : public ServiceFramework<IAm> {
public:
- explicit IAm() : ServiceFramework{"IAm"} {
+ explicit IAm() : ServiceFramework{"NFC::IAm"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -52,7 +53,7 @@ private:
class MFIUser final : public ServiceFramework<MFIUser> {
public:
- explicit MFIUser() : ServiceFramework{"IUser"} {
+ explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Initialize"},
@@ -100,13 +101,13 @@ private:
class IUser final : public ServiceFramework<IUser> {
public:
- explicit IUser() : ServiceFramework{"IUser"} {
+ explicit IUser() : ServiceFramework{"NFC::IUser"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
- {1, nullptr, "Finalize"},
- {2, nullptr, "GetState"},
- {3, nullptr, "IsNfcEnabled"},
+ {0, &IUser::InitializeOld, "InitializeOld"},
+ {1, &IUser::FinalizeOld, "FinalizeOld"},
+ {2, &IUser::GetStateOld, "GetStateOld"},
+ {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
{400, nullptr, "Initialize"},
{401, nullptr, "Finalize"},
{402, nullptr, "GetState"},
@@ -130,11 +131,47 @@ public:
RegisterHandlers(functions);
}
+
+private:
+ enum class NfcStates : u32 {
+ Finalized = 6,
+ };
+
+ void InitializeOld(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(RESULT_SUCCESS);
+
+ // We don't deal with hardware initialization so we can just stub this.
+ LOG_DEBUG(Service_NFC, "called");
+ }
+
+ void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u8>(Settings::values.enable_nfc);
+
+ LOG_DEBUG(Service_NFC, "IsNfcEnabledOld");
+ }
+
+ void GetStateOld(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushEnum(NfcStates::Finalized); // TODO(ogniK): Figure out if this matches nfp
+ }
+
+ void FinalizeOld(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
};
class NFC_U final : public ServiceFramework<NFC_U> {
public:
- explicit NFC_U() : ServiceFramework{"nfc:u"} {
+ explicit NFC_U() : ServiceFramework{"nfc:user"} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &NFC_U::CreateUserInterface, "CreateUserInterface"},
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 8c07a05c2..c1af878fe 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -2,56 +2,67 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <atomic>
+
#include "common/logging/log.h"
#include "core/core.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
+#include "core/hle/lock.h"
#include "core/hle/service/hid/hid.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP {
+namespace ErrCodes {
+constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP,
+ -1); // TODO(ogniK): Find the actual error code
+}
+
Module::Interface::Interface(std::shared_ptr<Module> module, const char* name)
- : ServiceFramework(name), module(std::move(module)) {}
+ : ServiceFramework(name), module(std::move(module)) {
+ auto& kernel = Core::System::GetInstance().Kernel();
+ nfc_tag_load =
+ Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:NFCTagDetected");
+}
Module::Interface::~Interface() = default;
class IUser final : public ServiceFramework<IUser> {
public:
- IUser() : ServiceFramework("IUser") {
+ IUser(Module::Interface& nfp_interface)
+ : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) {
static const FunctionInfo functions[] = {
{0, &IUser::Initialize, "Initialize"},
- {1, nullptr, "Finalize"},
+ {1, &IUser::Finalize, "Finalize"},
{2, &IUser::ListDevices, "ListDevices"},
- {3, nullptr, "StartDetection"},
- {4, nullptr, "StopDetection"},
- {5, nullptr, "Mount"},
- {6, nullptr, "Unmount"},
- {7, nullptr, "OpenApplicationArea"},
- {8, nullptr, "GetApplicationArea"},
+ {3, &IUser::StartDetection, "StartDetection"},
+ {4, &IUser::StopDetection, "StopDetection"},
+ {5, &IUser::Mount, "Mount"},
+ {6, &IUser::Unmount, "Unmount"},
+ {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
+ {8, &IUser::GetApplicationArea, "GetApplicationArea"},
{9, nullptr, "SetApplicationArea"},
{10, nullptr, "Flush"},
{11, nullptr, "Restore"},
{12, nullptr, "CreateApplicationArea"},
- {13, nullptr, "GetTagInfo"},
- {14, nullptr, "GetRegisterInfo"},
- {15, nullptr, "GetCommonInfo"},
- {16, nullptr, "GetModelInfo"},
+ {13, &IUser::GetTagInfo, "GetTagInfo"},
+ {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
+ {15, &IUser::GetCommonInfo, "GetCommonInfo"},
+ {16, &IUser::GetModelInfo, "GetModelInfo"},
{17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
{18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
{19, &IUser::GetState, "GetState"},
{20, &IUser::GetDeviceState, "GetDeviceState"},
{21, &IUser::GetNpadId, "GetNpadId"},
- {22, nullptr, "GetApplicationArea2"},
+ {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, nullptr, "RecreateApplicationArea"},
};
RegisterHandlers(functions);
auto& kernel = Core::System::GetInstance().Kernel();
- activate_event =
- Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:ActivateEvent");
deactivate_event =
Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent");
availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot,
@@ -59,6 +70,17 @@ public:
}
private:
+ struct TagInfo {
+ std::array<u8, 10> uuid;
+ u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it
+ // mean something else
+ INSERT_PADDING_BYTES(0x15);
+ u32_le protocol;
+ u32_le tag_type;
+ INSERT_PADDING_BYTES(0x2c);
+ };
+ static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size");
+
enum class State : u32 {
NonInitialized = 0,
Initialized = 1,
@@ -66,15 +88,40 @@ private:
enum class DeviceState : u32 {
Initialized = 0,
+ SearchingForTag = 1,
+ TagFound = 2,
+ TagRemoved = 3,
+ TagNearby = 4,
+ Unknown5 = 5,
+ Finalized = 6
+ };
+
+ struct CommonInfo {
+ u16_be last_write_year;
+ u8 last_write_month;
+ u8 last_write_day;
+ u16_be write_counter;
+ u16_be version;
+ u32_be application_area_size;
+ INSERT_PADDING_BYTES(0x34);
};
+ static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
void Initialize(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(RESULT_SUCCESS);
state = State::Initialized;
- IPC::ResponseBuilder rb{ctx, 2};
+ LOG_DEBUG(Service_NFC, "called");
+ }
+
+ void GetState(Kernel::HLERequestContext& ctx) {
+ IPC::ResponseBuilder rb{ctx, 3, 0};
rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u32>(static_cast<u32>(state));
+
+ LOG_DEBUG(Service_NFC, "called");
}
void ListDevices(Kernel::HLERequestContext& ctx) {
@@ -83,80 +130,219 @@ private:
ctx.WriteBuffer(&device_handle, sizeof(device_handle));
- LOG_WARNING(Service_NFP, "(STUBBED) called, array_size={}", array_size);
+ LOG_DEBUG(Service_NFP, "called, array_size={}", array_size);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(0);
+ rb.Push<u32>(1);
}
- void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ void GetNpadId(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 dev_handle = rp.Pop<u64>();
- LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+ LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(npad_id);
+ }
+ void AttachActivateEvent(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const u64 dev_handle = rp.Pop<u64>();
+ LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushCopyObjects(activate_event);
+ rb.PushCopyObjects(nfp_interface.GetNFCEvent());
+ has_attached_handle = true;
}
void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const u64 dev_handle = rp.Pop<u64>();
- LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+ LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle);
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(deactivate_event);
}
- void GetState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
- IPC::ResponseBuilder rb{ctx, 3};
+ void StopDetection(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+ switch (device_state) {
+ case DeviceState::TagFound:
+ case DeviceState::TagNearby:
+ deactivate_event->Signal();
+ device_state = DeviceState::Initialized;
+ break;
+ case DeviceState::SearchingForTag:
+ case DeviceState::TagRemoved:
+ device_state = DeviceState::Initialized;
+ break;
+ }
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(static_cast<u32>(state));
}
void GetDeviceState(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_NFP, "(STUBBED) called");
+ LOG_DEBUG(Service_NFP, "called");
+ auto nfc_event = nfp_interface.GetNFCEvent();
+ if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) {
+ device_state = DeviceState::TagFound;
+ nfc_event->Clear();
+ }
+
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.Push<u32>(static_cast<u32>(device_state));
}
- void GetNpadId(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 dev_handle = rp.Pop<u64>();
- LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
- IPC::ResponseBuilder rb{ctx, 3};
+ void StartDetection(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
+ device_state = DeviceState::SearchingForTag;
+ }
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetTagInfo(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ auto amiibo = nfp_interface.GetAmiiboBuffer();
+ TagInfo tag_info{};
+ std::memcpy(tag_info.uuid.data(), amiibo.uuid.data(), sizeof(tag_info.uuid.size()));
+ tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size());
+
+ tag_info.protocol = 1; // TODO(ogniK): Figure out actual values
+ tag_info.tag_type = 2;
+ ctx.WriteBuffer(&tag_info, sizeof(TagInfo));
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Mount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ device_state = DeviceState::TagNearby;
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetModelInfo(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ auto amiibo = nfp_interface.GetAmiiboBuffer();
+ ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info));
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Unmount(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ device_state = DeviceState::TagFound;
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void Finalize(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+
+ device_state = DeviceState::Finalized;
+
+ IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
- rb.Push<u32>(npad_id);
}
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const u64 dev_handle = rp.Pop<u64>();
- LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle);
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 2, 1};
rb.Push(RESULT_SUCCESS);
rb.PushCopyObjects(availability_change_event);
}
- const u64 device_handle{0xDEAD};
- const HID::ControllerID npad_id{HID::Controller_Player1};
+ void GetRegisterInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+ // TODO(ogniK): Pull Mii and owner data from amiibo
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetCommonInfo(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+ // TODO(ogniK): Pull common information from amiibo
+
+ CommonInfo common_info{};
+ common_info.application_area_size = 0;
+ ctx.WriteBuffer(&common_info, sizeof(CommonInfo));
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void OpenApplicationArea(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFP, "called");
+ // We don't need to worry about this since we can just open the file
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+ }
+
+ void GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
+ // We don't need to worry about this since we can just open the file
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
+ }
+
+ void GetApplicationArea(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_NFP, "(STUBBED) called");
+
+ // TODO(ogniK): Pull application area from amiibo
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub
+ }
+
+ bool has_attached_handle{};
+ const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')};
+ const u32 npad_id{0}; // Player 1 controller
State state{State::NonInitialized};
DeviceState device_state{DeviceState::Initialized};
- Kernel::SharedPtr<Kernel::Event> activate_event;
Kernel::SharedPtr<Kernel::Event> deactivate_event;
Kernel::SharedPtr<Kernel::Event> availability_change_event;
+ const Module::Interface& nfp_interface;
};
void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IUser>();
+ rb.PushIpcInterface<IUser>(*this);
+}
+
+bool Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) {
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ if (buffer.size() < sizeof(AmiiboFile)) {
+ return false;
+ }
+
+ std::memcpy(&amiibo, buffer.data(), sizeof(amiibo));
+ nfc_tag_load->Signal();
+ return true;
+}
+const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const {
+ return nfc_tag_load;
+}
+const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const {
+ return amiibo;
}
void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 77df343c4..5c0ae8a54 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -4,6 +4,9 @@
#pragma once
+#include <array>
+#include <vector>
+#include "core/hle/kernel/event.h"
#include "core/hle/service/service.h"
namespace Service::NFP {
@@ -15,7 +18,27 @@ public:
explicit Interface(std::shared_ptr<Module> module, const char* name);
~Interface() override;
+ struct ModelInfo {
+ std::array<u8, 0x8> amiibo_identification_block;
+ INSERT_PADDING_BYTES(0x38);
+ };
+ static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+
+ struct AmiiboFile {
+ std::array<u8, 10> uuid;
+ INSERT_PADDING_BYTES(0x4a);
+ ModelInfo model_info;
+ };
+ static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size");
+
void CreateUserInterface(Kernel::HLERequestContext& ctx);
+ bool LoadAmiibo(const std::vector<u8>& buffer);
+ const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const;
+ const AmiiboFile& GetAmiiboBuffer() const;
+
+ private:
+ Kernel::SharedPtr<Kernel::Event> nfc_tag_load{};
+ AmiiboFile amiibo{};
protected:
std::shared_ptr<Module> module;
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 10611ed6a..75dcd94a3 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -219,6 +219,7 @@ IGeneralService::IGeneralService() : ServiceFramework("IGeneralService") {
{35, nullptr, "GetScanData"},
{36, nullptr, "GetCurrentAccessPoint"},
{37, nullptr, "Shutdown"},
+ {38, nullptr, "GetAllowedChannels"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index 261ad539c..18091c9bb 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -71,6 +71,22 @@ public:
}
};
+class NIM_ECA final : public ServiceFramework<NIM_ECA> {
+public:
+ explicit NIM_ECA() : ServiceFramework{"nim:eca"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "CreateServerInterface"},
+ {1, nullptr, "RefreshDebugAvailability"},
+ {2, nullptr, "ClearDebugResponse"},
+ {3, nullptr, "RegisterDebugResponse"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
class NIM_SHP final : public ServiceFramework<NIM_SHP> {
public:
explicit NIM_SHP() : ServiceFramework{"nim:shp"} {
@@ -214,6 +230,7 @@ private:
void InstallInterfaces(SM::ServiceManager& sm) {
std::make_shared<NIM>()->InstallAsService(sm);
+ std::make_shared<NIM_ECA>()->InstallAsService(sm);
std::make_shared<NIM_SHP>()->InstallAsService(sm);
std::make_shared<NTC>()->InstallAsService(sm);
}
diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp
new file mode 100644
index 000000000..ccb6f9da9
--- /dev/null
+++ b/src/core/hle/service/npns/npns.cpp
@@ -0,0 +1,88 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+
+#include "core/hle/service/npns/npns.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::NPNS {
+
+class NPNS_S final : public ServiceFramework<NPNS_S> {
+public:
+ explicit NPNS_S() : ServiceFramework{"npns:s"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, nullptr, "ListenAll"},
+ {2, nullptr, "ListenTo"},
+ {3, nullptr, "Receive"},
+ {4, nullptr, "ReceiveRaw"},
+ {5, nullptr, "GetReceiveEvent"},
+ {6, nullptr, "ListenUndelivered"},
+ {7, nullptr, "GetStateChangeEVent"},
+ {11, nullptr, "SubscribeTopic"},
+ {12, nullptr, "UnsubscribeTopic"},
+ {13, nullptr, "QueryIsTopicExist"},
+ {21, nullptr, "CreateToken"},
+ {22, nullptr, "CreateTokenWithApplicationId"},
+ {23, nullptr, "DestroyToken"},
+ {24, nullptr, "DestroyTokenWithApplicationId"},
+ {25, nullptr, "QueryIsTokenValid"},
+ {31, nullptr, "UploadTokenToBaaS"},
+ {32, nullptr, "DestroyTokenForBaaS"},
+ {33, nullptr, "CreateTokenForBaaS"},
+ {34, nullptr, "SetBaaSDeviceAccountIdList"},
+ {101, nullptr, "Suspend"},
+ {102, nullptr, "Resume"},
+ {103, nullptr, "GetState"},
+ {104, nullptr, "GetStatistics"},
+ {105, nullptr, "GetPlayReportRequestEvent"},
+ {111, nullptr, "GetJid"},
+ {112, nullptr, "CreateJid"},
+ {113, nullptr, "DestroyJid"},
+ {114, nullptr, "AttachJid"},
+ {115, nullptr, "DetachJid"},
+ {201, nullptr, "RequestChangeStateForceTimed"},
+ {102, nullptr, "RequestChangeStateForceAsync"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class NPNS_U final : public ServiceFramework<NPNS_U> {
+public:
+ explicit NPNS_U() : ServiceFramework{"npns:u"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {1, nullptr, "ListenAll"},
+ {2, nullptr, "ListenTo"},
+ {3, nullptr, "Receive"},
+ {4, nullptr, "ReceiveRaw"},
+ {5, nullptr, "GetReceiveEvent"},
+ {7, nullptr, "GetStateChangeEVent"},
+ {21, nullptr, "CreateToken"},
+ {23, nullptr, "DestroyToken"},
+ {25, nullptr, "QueryIsTokenValid"},
+ {26, nullptr, "ListenToMyApplicationId"},
+ {101, nullptr, "Suspend"},
+ {102, nullptr, "Resume"},
+ {103, nullptr, "GetState"},
+ {104, nullptr, "GetStatistics"},
+ {111, nullptr, "GetJid"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+void InstallInterfaces(SM::ServiceManager& sm) {
+ std::make_shared<NPNS_S>()->InstallAsService(sm);
+ std::make_shared<NPNS_U>()->InstallAsService(sm);
+}
+
+} // namespace Service::NPNS
diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h
new file mode 100644
index 000000000..861cd3e48
--- /dev/null
+++ b/src/core/hle/service/npns/npns.h
@@ -0,0 +1,15 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::NPNS {
+
+void InstallInterfaces(SM::ServiceManager& sm);
+
+} // namespace Service::NPNS
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
index 98017267c..07c1381fe 100644
--- a/src/core/hle/service/ns/ns.cpp
+++ b/src/core/hle/service/ns/ns.cpp
@@ -93,13 +93,23 @@ public:
{86, nullptr, "EnableApplicationCrashReport"},
{87, nullptr, "IsApplicationCrashReportEnabled"},
{90, nullptr, "BoostSystemMemoryResourceLimit"},
+ {91, nullptr, "Unknown1"},
+ {92, nullptr, "Unknown2"},
+ {93, nullptr, "GetMainApplicationProgramIndex"},
+ {94, nullptr, "LaunchApplication2"},
+ {95, nullptr, "GetApplicationLaunchInfo"},
+ {96, nullptr, "AcquireApplicationLaunchInfo"},
+ {97, nullptr, "GetMainApplicationProgramIndex2"},
+ {98, nullptr, "EnableApplicationAllThreadDumpOnCrash"},
{100, nullptr, "ResetToFactorySettings"},
{101, nullptr, "ResetToFactorySettingsWithoutUserSaveData"},
{102, nullptr, "ResetToFactorySettingsForRefurbishment"},
{200, nullptr, "CalculateUserSaveDataStatistics"},
{201, nullptr, "DeleteUserSaveDataAll"},
{210, nullptr, "DeleteUserSystemSaveData"},
+ {211, nullptr, "DeleteSaveData"},
{220, nullptr, "UnregisterNetworkServiceAccount"},
+ {221, nullptr, "UnregisterNetworkServiceAccountWithUserSaveDataDeletion"},
{300, nullptr, "GetApplicationShellEvent"},
{301, nullptr, "PopApplicationShellEventInfo"},
{302, nullptr, "LaunchLibraryApplet"},
@@ -114,6 +124,7 @@ public:
{403, nullptr, "GetMaxApplicationControlCacheCount"},
{404, nullptr, "InvalidateApplicationControlCache"},
{405, nullptr, "ListApplicationControlCacheEntryInfo"},
+ {406, nullptr, "GetApplicationControlProperty"},
{502, nullptr, "RequestCheckGameCardRegistration"},
{503, nullptr, "RequestGameCardRegistrationGoldPoint"},
{504, nullptr, "RequestRegisterGameCard"},
@@ -129,6 +140,7 @@ public:
{604, nullptr, "RegisterContentsExternalKey"},
{605, nullptr, "ListApplicationContentMetaStatusWithRightsCheck"},
{606, nullptr, "GetContentMetaStorage"},
+ {607, nullptr, "ListAvailableAddOnContent"},
{700, nullptr, "PushDownloadTaskList"},
{701, nullptr, "ClearTaskStatusList"},
{702, nullptr, "RequestDownloadTaskList"},
@@ -148,6 +160,9 @@ public:
{907, nullptr, "WithdrawApplicationUpdateRequest"},
{908, nullptr, "ListApplicationRecordInstalledContentMeta"},
{909, nullptr, "WithdrawCleanupAddOnContentsWithNoRightsRecommendation"},
+ {910, nullptr, "Unknown3"},
+ {911, nullptr, "SetPreInstalledApplication"},
+ {912, nullptr, "ClearPreInstalledApplicationFlag"},
{1000, nullptr, "RequestVerifyApplicationDeprecated"},
{1001, nullptr, "CorruptApplicationForDebug"},
{1002, nullptr, "RequestVerifyAddOnContentsRights"},
@@ -162,6 +177,8 @@ public:
{1305, nullptr, "TryDeleteRunningApplicationEntity"},
{1306, nullptr, "TryDeleteRunningApplicationCompletely"},
{1307, nullptr, "TryDeleteRunningApplicationContentEntities"},
+ {1308, nullptr, "DeleteApplicationCompletelyForDebug"},
+ {1309, nullptr, "CleanupUnavailableAddOnContents"},
{1400, nullptr, "PrepareShutdown"},
{1500, nullptr, "FormatSdCard"},
{1501, nullptr, "NeedsSystemUpdateToFormatSdCard"},
@@ -199,6 +216,28 @@ public:
{2015, nullptr, "CompareSystemDeliveryInfo"},
{2016, nullptr, "ListNotCommittedContentMeta"},
{2017, nullptr, "CreateDownloadTask"},
+ {2018, nullptr, "Unknown4"},
+ {2050, nullptr, "Unknown5"},
+ {2100, nullptr, "Unknown6"},
+ {2101, nullptr, "Unknown7"},
+ {2150, nullptr, "CreateRightsEnvironment"},
+ {2151, nullptr, "DestroyRightsEnvironment"},
+ {2152, nullptr, "ActivateRightsEnvironment"},
+ {2153, nullptr, "DeactivateRightsEnvironment"},
+ {2154, nullptr, "ForceActivateRightsContextForExit"},
+ {2160, nullptr, "AddTargetApplicationToRightsEnvironment"},
+ {2161, nullptr, "SetUsersToRightsEnvironment"},
+ {2170, nullptr, "GetRightsEnvironmentStatus"},
+ {2171, nullptr, "GetRightsEnvironmentStatusChangedEvent"},
+ {2180, nullptr, "RequestExtendRightsInRightsEnvironment"},
+ {2181, nullptr, "GetLastResultOfExtendRightsInRightsEnvironment"},
+ {2182, nullptr, "SetActiveRightsContextUsingStateToRightsEnvironment"},
+ {2190, nullptr, "GetRightsEnvironmentHandleForApplication"},
+ {2199, nullptr, "GetRightsEnvironmentCountForDebug"},
+ {2200, nullptr, "Unknown8"},
+ {2201, nullptr, "Unknown9"},
+ {2250, nullptr, "Unknown10"},
+ {2300, nullptr, "Unknown11"},
};
// clang-format on
@@ -348,12 +387,15 @@ public:
{0, nullptr, "LaunchProgram"},
{1, nullptr, "TerminateProcess"},
{2, nullptr, "TerminateProgram"},
- {3, nullptr, "GetShellEventHandle"},
- {4, nullptr, "GetShellEventInfo"},
- {5, nullptr, "TerminateApplication"},
- {6, nullptr, "PrepareLaunchProgramFromHost"},
- {7, nullptr, "LaunchApplication"},
- {8, nullptr, "LaunchApplicationWithStorageId"},
+ {4, nullptr, "GetShellEventHandle"},
+ {5, nullptr, "GetShellEventInfo"},
+ {6, nullptr, "TerminateApplication"},
+ {7, nullptr, "PrepareLaunchProgramFromHost"},
+ {8, nullptr, "LaunchApplication"},
+ {9, nullptr, "LaunchApplicationWithStorageId"},
+ {10, nullptr, "TerminateApplication2"},
+ {11, nullptr, "GetRunningApplicationProcessId"},
+ {12, nullptr, "SetCurrentApplicationRightsEnvironmentCanBeActive"},
};
// clang-format on
@@ -388,6 +430,7 @@ public:
{19, nullptr, "GetReceivedEulaDataSize"},
{20, nullptr, "GetReceivedEulaData"},
{21, nullptr, "SetupToReceiveSystemUpdate"},
+ {22, nullptr, "RequestCheckLatestUpdateIncludesRebootlessUpdate"},
};
// clang-format on
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp
index 4b2f758a8..44accecb7 100644
--- a/src/core/hle/service/ns/pl_u.cpp
+++ b/src/core/hle/service/ns/pl_u.cpp
@@ -161,7 +161,7 @@ PL_U::PL_U() : ServiceFramework("pl:u"), impl{std::make_unique<Impl>()} {
};
RegisterHandlers(functions);
// Attempt to load shared font data from disk
- const auto nand = FileSystem::GetSystemNANDContents();
+ const auto* nand = FileSystem::GetSystemNANDContents();
std::size_t offset = 0;
// Rebuild shared fonts from data ncas
if (nand->HasEntry(static_cast<u64>(FontArchives::Standard),
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index d8b8037a8..c41ef7058 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -15,6 +15,11 @@
#include "video_core/renderer_base.h"
namespace Service::Nvidia::Devices {
+namespace NvErrCodes {
+enum {
+ InvalidNmapHandle = -22,
+};
+}
nvhost_as_gpu::nvhost_as_gpu(std::shared_ptr<nvmap> nvmap_dev) : nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default;
@@ -79,14 +84,16 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
std::memcpy(entries.data(), input.data(), input.size());
auto& gpu = Core::System::GetInstance().GPU();
-
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
-
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
- ASSERT(object);
+ if (!object) {
+ LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
+ std::memcpy(output.data(), entries.data(), output.size());
+ return static_cast<u32>(NvErrCodes::InvalidNmapHandle);
+ }
ASSERT(object->status == nvmap::Object::Status::Allocated);
@@ -157,15 +164,21 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
const auto itr = buffer_mappings.find(params.offset);
- ASSERT_MSG(itr != buffer_mappings.end(), "Tried to unmap invalid mapping");
+ if (itr == buffer_mappings.end()) {
+ LOG_WARNING(Service_NVDRV, "Tried to unmap an invalid offset 0x{:X}", params.offset);
+ // Hardware tests shows that unmapping an already unmapped buffer always returns successful
+ // and doesn't fail.
+ return 0;
+ }
auto& system_instance = Core::System::GetInstance();
// Remove this memory region from the rasterizer cache.
- system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(params.offset,
- itr->second.size);
-
auto& gpu = system_instance.GPU();
+ auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
+ ASSERT(cpu_addr);
+ system_instance.Renderer().Rasterizer().FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
+
params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
buffer_mappings.erase(itr->second.offset);
diff --git a/src/core/hle/service/nvdrv/devices/nvmap.cpp b/src/core/hle/service/nvdrv/devices/nvmap.cpp
index a2287cc1b..43651d8a6 100644
--- a/src/core/hle/service/nvdrv/devices/nvmap.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvmap.cpp
@@ -11,6 +11,13 @@
namespace Service::Nvidia::Devices {
+namespace NvErrCodes {
+enum {
+ OperationNotPermitted = -1,
+ InvalidValue = -22,
+};
+}
+
nvmap::nvmap() = default;
nvmap::~nvmap() = default;
@@ -44,7 +51,11 @@ u32 nvmap::ioctl(Ioctl command, const std::vector<u8>& input, std::vector<u8>& o
u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
IocCreateParams params;
std::memcpy(&params, input.data(), sizeof(params));
+ LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
+ if (!params.size) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
// Create a new nvmap object and obtain a handle to it.
auto object = std::make_shared<Object>();
object->id = next_id++;
@@ -55,8 +66,6 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
u32 handle = next_handle++;
handles[handle] = std::move(object);
- LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
-
params.handle = handle;
std::memcpy(output.data(), &params, sizeof(params));
@@ -66,9 +75,29 @@ u32 nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
IocAllocParams params;
std::memcpy(&params, input.data(), sizeof(params));
+ LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
+
+ if (!params.handle) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
+ if ((params.align - 1) & params.align) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
+ const u32 min_alignment = 0x1000;
+ if (params.align < min_alignment) {
+ params.align = min_alignment;
+ }
auto object = GetObject(params.handle);
- ASSERT(object);
+ if (!object) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
+ if (object->status == Object::Status::Allocated) {
+ return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ }
object->flags = params.flags;
object->align = params.align;
@@ -76,8 +105,6 @@ u32 nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
object->addr = params.addr;
object->status = Object::Status::Allocated;
- LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
-
std::memcpy(output.data(), &params, sizeof(params));
return 0;
}
@@ -88,8 +115,14 @@ u32 nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "called");
+ if (!params.handle) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
auto object = GetObject(params.handle);
- ASSERT(object);
+ if (!object) {
+ return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ }
params.id = object->id;
@@ -105,7 +138,14 @@ u32 nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output) {
auto itr = std::find_if(handles.begin(), handles.end(),
[&](const auto& entry) { return entry.second->id == params.id; });
- ASSERT(itr != handles.end());
+ if (itr == handles.end()) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
+ auto& object = itr->second;
+ if (object->status != Object::Status::Allocated) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
itr->second->refcount++;
@@ -125,8 +165,13 @@ u32 nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called type={}", params.param);
auto object = GetObject(params.handle);
- ASSERT(object);
- ASSERT(object->status == Object::Status::Allocated);
+ if (!object) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+
+ if (object->status != Object::Status::Allocated) {
+ return static_cast<u32>(NvErrCodes::OperationNotPermitted);
+ }
switch (static_cast<ParamTypes>(params.param)) {
case ParamTypes::Size:
@@ -163,9 +208,12 @@ u32 nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
auto itr = handles.find(params.handle);
- ASSERT(itr != handles.end());
-
- ASSERT(itr->second->refcount > 0);
+ if (itr == handles.end()) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
+ if (!itr->second->refcount) {
+ return static_cast<u32>(NvErrCodes::InvalidValue);
+ }
itr->second->refcount--;
diff --git a/src/core/hle/service/nvflinger/buffer_queue.cpp b/src/core/hle/service/nvflinger/buffer_queue.cpp
index fd98d541d..630ebbfc7 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.cpp
+++ b/src/core/hle/service/nvflinger/buffer_queue.cpp
@@ -31,7 +31,7 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
buffer_wait_event->Signal();
}
-boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
+std::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
auto itr = std::find_if(queue.begin(), queue.end(), [&](const Buffer& buffer) {
// Only consider free buffers. Buffers become free once again after they've been Acquired
// and Released by the compositor, see the NVFlinger::Compose method.
@@ -44,7 +44,7 @@ boost::optional<u32> BufferQueue::DequeueBuffer(u32 width, u32 height) {
});
if (itr == queue.end()) {
- return boost::none;
+ return {};
}
itr->status = Buffer::Status::Dequeued;
@@ -70,12 +70,12 @@ void BufferQueue::QueueBuffer(u32 slot, BufferTransformFlags transform,
itr->crop_rect = crop_rect;
}
-boost::optional<const BufferQueue::Buffer&> BufferQueue::AcquireBuffer() {
+std::optional<std::reference_wrapper<const BufferQueue::Buffer>> BufferQueue::AcquireBuffer() {
auto itr = std::find_if(queue.begin(), queue.end(), [](const Buffer& buffer) {
return buffer.status == Buffer::Status::Queued;
});
if (itr == queue.end())
- return boost::none;
+ return {};
itr->status = Buffer::Status::Acquired;
return *itr;
}
diff --git a/src/core/hle/service/nvflinger/buffer_queue.h b/src/core/hle/service/nvflinger/buffer_queue.h
index 50b767732..8cff5eb71 100644
--- a/src/core/hle/service/nvflinger/buffer_queue.h
+++ b/src/core/hle/service/nvflinger/buffer_queue.h
@@ -4,8 +4,9 @@
#pragma once
+#include <optional>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_funcs.h"
#include "common/math_util.h"
#include "common/swap.h"
@@ -57,9 +58,9 @@ public:
/// Rotate source image 90 degrees clockwise
Rotate90 = 0x04,
/// Rotate source image 180 degrees
- Roate180 = 0x03,
+ Rotate180 = 0x03,
/// Rotate source image 270 degrees clockwise
- Roate270 = 0x07,
+ Rotate270 = 0x07,
};
struct Buffer {
@@ -73,11 +74,11 @@ public:
};
void SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer);
- boost::optional<u32> DequeueBuffer(u32 width, u32 height);
+ std::optional<u32> DequeueBuffer(u32 width, u32 height);
const IGBPBuffer& RequestBuffer(u32 slot) const;
void QueueBuffer(u32 slot, BufferTransformFlags transform,
const MathUtil::Rectangle<int>& crop_rect);
- boost::optional<const Buffer&> AcquireBuffer();
+ std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot);
u32 Query(QueryType type);
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index d47b6f659..214e6d1b3 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include <boost/optional.hpp>
+#include <optional>
#include "common/alignment.h"
#include "common/assert.h"
@@ -134,7 +134,7 @@ void NVFlinger::Compose() {
MicroProfileFlip();
- if (buffer == boost::none) {
+ if (!buffer) {
auto& system_instance = Core::System::GetInstance();
// There was no queued buffer to draw, render previous frame
@@ -143,7 +143,7 @@ void NVFlinger::Compose() {
continue;
}
- auto& igbp_buffer = buffer->igbp_buffer;
+ auto& igbp_buffer = buffer->get().igbp_buffer;
// Now send the buffer to the GPU for drawing.
// TODO(Subv): Support more than just disp0. The display device selection is probably based
@@ -152,10 +152,10 @@ void NVFlinger::Compose() {
ASSERT(nvdisp);
nvdisp->flip(igbp_buffer.gpu_buffer_id, igbp_buffer.offset, igbp_buffer.format,
- igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride, buffer->transform,
- buffer->crop_rect);
+ igbp_buffer.width, igbp_buffer.height, igbp_buffer.stride,
+ buffer->get().transform, buffer->get().crop_rect);
- buffer_queue->ReleaseBuffer(buffer->slot);
+ buffer_queue->ReleaseBuffer(buffer->get().slot);
}
}
diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp
index 6a9eccfb5..e4fcee9f8 100644
--- a/src/core/hle/service/prepo/prepo.cpp
+++ b/src/core/hle/service/prepo/prepo.cpp
@@ -14,20 +14,24 @@ public:
explicit PlayReport(const char* name) : ServiceFramework{name} {
// clang-format off
static const FunctionInfo functions[] = {
- {10100, nullptr, "SaveReport"},
- {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"},
+ {10100, nullptr, "SaveReportOld"},
+ {10101, &PlayReport::SaveReportWithUserOld, "SaveReportWithUserOld"},
+ {10102, nullptr, "SaveReport"},
+ {10103, nullptr, "SaveReportWithUser"},
{10200, nullptr, "RequestImmediateTransmission"},
{10300, nullptr, "GetTransmissionStatus"},
{20100, nullptr, "SaveSystemReport"},
- {20200, nullptr, "SetOperationMode"},
{20101, nullptr, "SaveSystemReportWithUser"},
+ {20200, nullptr, "SetOperationMode"},
{30100, nullptr, "ClearStorage"},
+ {30200, nullptr, "ClearStatistics"},
+ {30300, nullptr, "GetStorageUsage"},
+ {30400, nullptr, "GetStatistics"},
+ {30401, nullptr, "GetThroughputHistory"},
+ {30500, nullptr, "GetLastUploadError"},
{40100, nullptr, "IsUserAgreementCheckEnabled"},
{40101, nullptr, "SetUserAgreementCheckEnabled"},
- {90100, nullptr, "GetStorageUsage"},
- {90200, nullptr, "GetStatistics"},
- {90201, nullptr, "GetThroughputHistory"},
- {90300, nullptr, "GetLastUploadError"},
+ {90100, nullptr, "ReadAllReportFiles"},
};
// clang-format on
@@ -35,7 +39,7 @@ public:
}
private:
- void SaveReportWithUser(Kernel::HLERequestContext& ctx) {
+ void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) {
// TODO(ogniK): Do we want to add play report?
LOG_WARNING(Service_PREPO, "(STUBBED) called");
@@ -46,6 +50,7 @@ private:
void InstallInterfaces(SM::ServiceManager& service_manager) {
std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager);
+ std::make_shared<PlayReport>("prepo:a2")->InstallAsService(service_manager);
std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager);
std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager);
std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager);
diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp
new file mode 100644
index 000000000..c2d5fda94
--- /dev/null
+++ b/src/core/hle/service/ptm/psm.cpp
@@ -0,0 +1,71 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+
+#include "common/logging/log.h"
+#include "core/hle/ipc_helpers.h"
+#include "core/hle/service/ptm/psm.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace Service::PSM {
+
+constexpr u32 BATTERY_FULLY_CHARGED = 100; // 100% Full
+constexpr u32 BATTERY_CURRENTLY_CHARGING = 1; // Plugged into an official dock
+
+class PSM final : public ServiceFramework<PSM> {
+public:
+ explicit PSM() : ServiceFramework{"psm"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"},
+ {1, &PSM::GetChargerType, "GetChargerType"},
+ {2, nullptr, "EnableBatteryCharging"},
+ {3, nullptr, "DisableBatteryCharging"},
+ {4, nullptr, "IsBatteryChargingEnabled"},
+ {5, nullptr, "AcquireControllerPowerSupply"},
+ {6, nullptr, "ReleaseControllerPowerSupply"},
+ {7, nullptr, "OpenSession"},
+ {8, nullptr, "EnableEnoughPowerChargeEmulation"},
+ {9, nullptr, "DisableEnoughPowerChargeEmulation"},
+ {10, nullptr, "EnableFastBatteryCharging"},
+ {11, nullptr, "DisableFastBatteryCharging"},
+ {12, nullptr, "GetBatteryVoltageState"},
+ {13, nullptr, "GetRawBatteryChargePercentage"},
+ {14, nullptr, "IsEnoughPowerSupplied"},
+ {15, nullptr, "GetBatteryAgePercentage"},
+ {16, nullptr, "GetBatteryChargeInfoEvent"},
+ {17, nullptr, "GetBatteryChargeInfoFields"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+
+ ~PSM() override = default;
+
+private:
+ void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PSM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(BATTERY_FULLY_CHARGED);
+ }
+
+ void GetChargerType(Kernel::HLERequestContext& ctx) {
+ LOG_WARNING(Service_PSM, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(RESULT_SUCCESS);
+ rb.Push<u32>(BATTERY_CURRENTLY_CHARGING);
+ }
+};
+
+void InstallInterfaces(SM::ServiceManager& sm) {
+ std::make_shared<PSM>()->InstallAsService(sm);
+}
+
+} // namespace Service::PSM
diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h
new file mode 100644
index 000000000..a286793ae
--- /dev/null
+++ b/src/core/hle/service/ptm/psm.h
@@ -0,0 +1,15 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Service::SM {
+class ServiceManager;
+}
+
+namespace Service::PSM {
+
+void InstallInterfaces(SM::ServiceManager& sm);
+
+} // namespace Service::PSM
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 62f049660..a4cf45267 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -22,7 +22,7 @@
#include "core/hle/service/apm/apm.h"
#include "core/hle/service/arp/arp.h"
#include "core/hle/service/audio/audio.h"
-#include "core/hle/service/bcat/bcat.h"
+#include "core/hle/service/bcat/module.h"
#include "core/hle/service/bpc/bpc.h"
#include "core/hle/service/btdrv/btdrv.h"
#include "core/hle/service/btm/btm.h"
@@ -48,15 +48,17 @@
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nifm/nifm.h"
#include "core/hle/service/nim/nim.h"
+#include "core/hle/service/npns/npns.h"
#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/nvflinger.h"
#include "core/hle/service/pcie/pcie.h"
-#include "core/hle/service/pctl/pctl.h"
+#include "core/hle/service/pctl/module.h"
#include "core/hle/service/pcv/pcv.h"
#include "core/hle/service/pm/pm.h"
#include "core/hle/service/prepo/prepo.h"
#include "core/hle/service/psc/psc.h"
+#include "core/hle/service/ptm/psm.h"
#include "core/hle/service/service.h"
#include "core/hle/service/set/settings.h"
#include "core/hle/service/sm/sm.h"
@@ -197,7 +199,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
// Module interface
/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesystem& rfs) {
+void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) {
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
// here and pass it into the respective InstallInterfaces functions.
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
@@ -220,7 +222,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
EUPLD::InstallInterfaces(*sm);
Fatal::InstallInterfaces(*sm);
FGM::InstallInterfaces(*sm);
- FileSystem::InstallInterfaces(*sm, rfs);
+ FileSystem::InstallInterfaces(*sm, vfs);
Friend::InstallInterfaces(*sm);
GRC::InstallInterfaces(*sm);
HID::InstallInterfaces(*sm);
@@ -236,6 +238,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
NFP::InstallInterfaces(*sm);
NIFM::InstallInterfaces(*sm);
NIM::InstallInterfaces(*sm);
+ NPNS::InstallInterfaces(*sm);
NS::InstallInterfaces(*sm);
Nvidia::InstallInterfaces(*sm, *nv_flinger);
PCIe::InstallInterfaces(*sm);
@@ -244,6 +247,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, const FileSys::VirtualFilesys
PlayReport::InstallInterfaces(*sm);
PM::InstallInterfaces(*sm);
PSC::InstallInterfaces(*sm);
+ PSM::InstallInterfaces(*sm);
Set::InstallInterfaces(*sm);
Sockets::InstallInterfaces(*sm);
SPL::InstallInterfaces(*sm);
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 2fc57a82e..98483ecf1 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -180,8 +180,7 @@ private:
};
/// Initialize ServiceManager
-void Init(std::shared_ptr<SM::ServiceManager>& sm,
- const std::shared_ptr<FileSys::VfsFilesystem>& vfs);
+void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs);
/// Shutdown ServiceManager
void Shutdown();
diff --git a/src/core/hle/service/set/set_cal.cpp b/src/core/hle/service/set/set_cal.cpp
index 5af356d10..34654bb07 100644
--- a/src/core/hle/service/set/set_cal.cpp
+++ b/src/core/hle/service/set/set_cal.cpp
@@ -39,7 +39,8 @@ SET_CAL::SET_CAL() : ServiceFramework("set:cal") {
{29, nullptr, "GetAmiiboEcqvBlsKey"},
{30, nullptr, "GetAmiiboEcqvBlsCertificate"},
{31, nullptr, "GetAmiiboEcqvBlsRootCertificate"},
- {32, nullptr, "GetUnknownId"},
+ {32, nullptr, "GetUsbTypeCPowerSourceCircuitVersion"},
+ {33, nullptr, "GetBatteryVersion"},
};
RegisterHandlers(functions);
}
diff --git a/src/core/hle/service/usb/usb.cpp b/src/core/hle/service/usb/usb.cpp
index e7fb5a419..f0a831d45 100644
--- a/src/core/hle/service/usb/usb.cpp
+++ b/src/core/hle/service/usb/usb.cpp
@@ -67,15 +67,15 @@ public:
explicit IClientEpSession() : ServiceFramework{"IClientEpSession"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
+ {0, nullptr, "Open"},
+ {1, nullptr, "Close"},
+ {2, nullptr, "Unknown1"},
+ {3, nullptr, "Populate"},
{4, nullptr, "PostBufferAsync"},
- {5, nullptr, "Unknown5"},
- {6, nullptr, "Unknown6"},
- {7, nullptr, "Unknown7"},
- {8, nullptr, "Unknown8"},
+ {5, nullptr, "GetXferReport"},
+ {6, nullptr, "Unknown2"},
+ {7, nullptr, "Unknown3"},
+ {8, nullptr, "Unknown4"},
};
// clang-format on
@@ -89,15 +89,15 @@ public:
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "Unknown1"},
- {1, nullptr, "Unknown2"},
- {2, nullptr, "Unknown3"},
- {3, nullptr, "Unknown4"},
- {4, nullptr, "Unknown5"},
+ {1, nullptr, "SetInterface"},
+ {2, nullptr, "GetInterface"},
+ {3, nullptr, "GetAlternateInterface"},
+ {4, nullptr, "GetCurrentFrame"},
{5, nullptr, "CtrlXferAsync"},
- {6, nullptr, "Unknown6"},
+ {6, nullptr, "Unknown2"},
{7, nullptr, "GetCtrlXferReport"},
- {8, nullptr, "Unknown7"},
- {9, nullptr, "GetClientEpSession"},
+ {8, nullptr, "ResetDevice"},
+ {9, nullptr, "OpenUsbEp"},
};
// clang-format on
@@ -111,13 +111,14 @@ public:
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindClientProcess"},
- {1, nullptr, "Unknown1"},
- {2, nullptr, "Unknown2"},
- {3, nullptr, "Unknown3"},
- {4, nullptr, "Unknown4"},
- {5, nullptr, "Unknown5"},
+ {1, nullptr, "QueryAllInterfaces"},
+ {2, nullptr, "QueryAvailableInterfaces"},
+ {3, nullptr, "QueryAcquiredInterfaces"},
+ {4, nullptr, "CreateInterfaceAvailableEvent"},
+ {5, nullptr, "DestroyInterfaceAvailableEvent"},
{6, nullptr, "GetInterfaceStateChangeEvent"},
- {7, nullptr, "GetClientIfSession"},
+ {7, nullptr, "AcquireUsbIf"},
+ {8, nullptr, "Unknown1"},
};
// clang-format on
@@ -131,11 +132,11 @@ public:
// clang-format off
static const FunctionInfo functions[] = {
{0, nullptr, "BindNoticeEvent"},
- {1, nullptr, "Unknown1"},
+ {1, nullptr, "UnbindNoticeEvent"},
{2, nullptr, "GetStatus"},
{3, nullptr, "GetNotice"},
- {4, nullptr, "Unknown2"},
- {5, nullptr, "Unknown3"},
+ {4, nullptr, "EnablePowerRequestNotice"},
+ {5, nullptr, "DisablePowerRequestNotice"},
{6, nullptr, "ReplyPowerRequest"},
};
// clang-format on
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index bbc02abcc..d764b2406 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -6,9 +6,10 @@
#include <array>
#include <cstring>
#include <memory>
+#include <optional>
#include <type_traits>
#include <utility>
-#include <boost/optional.hpp>
+
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_funcs.h"
@@ -506,9 +507,9 @@ private:
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
const u32 width{request.data.width};
const u32 height{request.data.height};
- boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+ std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
- if (slot != boost::none) {
+ if (slot) {
// Buffer is available
IGBPDequeueBufferResponseParcel response{*slot};
ctx.WriteBuffer(response.Serialize());
@@ -520,7 +521,7 @@ private:
Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
auto buffer_queue = nv_flinger->GetBufferQueue(id);
- boost::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
+ std::optional<u32> slot = buffer_queue->DequeueBuffer(width, height);
IGBPDequeueBufferResponseParcel response{*slot};
ctx.WriteBuffer(response.Serialize());
IPC::ResponseBuilder rb{ctx, 2};
@@ -968,6 +969,54 @@ private:
rb.PushCopyObjects(vsync_event);
}
+ enum class ConvertedScaleMode : u64 {
+ None = 0, // VI seems to name this as "Unknown" but lots of games pass it, assume it's no
+ // scaling/default
+ Freeze = 1,
+ ScaleToWindow = 2,
+ Crop = 3,
+ NoCrop = 4,
+ };
+
+ // This struct is different, currently it's 1:1 but this might change in the future.
+ enum class NintendoScaleMode : u32 {
+ None = 0,
+ Freeze = 1,
+ ScaleToWindow = 2,
+ Crop = 3,
+ NoCrop = 4,
+ };
+
+ void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ auto mode = rp.PopEnum<NintendoScaleMode>();
+ LOG_DEBUG(Service_VI, "called mode={}", static_cast<u32>(mode));
+
+ IPC::ResponseBuilder rb{ctx, 4};
+ rb.Push(RESULT_SUCCESS);
+ switch (mode) {
+ case NintendoScaleMode::None:
+ rb.PushEnum(ConvertedScaleMode::None);
+ break;
+ case NintendoScaleMode::Freeze:
+ rb.PushEnum(ConvertedScaleMode::Freeze);
+ break;
+ case NintendoScaleMode::ScaleToWindow:
+ rb.PushEnum(ConvertedScaleMode::ScaleToWindow);
+ break;
+ case NintendoScaleMode::Crop:
+ rb.PushEnum(ConvertedScaleMode::Crop);
+ break;
+ case NintendoScaleMode::NoCrop:
+ rb.PushEnum(ConvertedScaleMode::NoCrop);
+ break;
+ default:
+ UNIMPLEMENTED_MSG("Unknown scaling mode {}", static_cast<u32>(mode));
+ rb.PushEnum(ConvertedScaleMode::None);
+ break;
+ }
+ }
+
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
@@ -991,7 +1040,7 @@ IApplicationDisplayService::IApplicationDisplayService(
{2030, &IApplicationDisplayService::CreateStrayLayer, "CreateStrayLayer"},
{2031, &IApplicationDisplayService::DestroyStrayLayer, "DestroyStrayLayer"},
{2101, &IApplicationDisplayService::SetLayerScalingMode, "SetLayerScalingMode"},
- {2102, nullptr, "ConvertScalingMode"},
+ {2102, &IApplicationDisplayService::ConvertScalingMode, "ConvertScalingMode"},
{2450, nullptr, "GetIndirectLayerImageMap"},
{2451, nullptr, "GetIndirectLayerImageCropMap"},
{2460, nullptr, "GetIndirectLayerImageRequiredMemoryInfo"},
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 9a86e5824..8518dddcb 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
+#include <cstring>
#include "common/common_funcs.h"
#include "common/file_util.h"
#include "common/logging/log.h"
@@ -138,13 +139,22 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(Kernel::Process& process)
for (const auto& module : {"rtld", "main", "subsdk0", "subsdk1", "subsdk2", "subsdk3",
"subsdk4", "subsdk5", "subsdk6", "subsdk7", "sdk"}) {
const FileSys::VirtualFile module_file = dir->GetFile(module);
- if (module_file != nullptr) {
- const VAddr load_addr = next_load_addr;
- next_load_addr = AppLoader_NSO::LoadModule(module_file, load_addr, pm);
- LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
- // Register module with GDBStub
- GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
+ if (module_file == nullptr) {
+ continue;
}
+
+ const VAddr load_addr = next_load_addr;
+ const bool should_pass_arguments = std::strcmp(module, "rtld") == 0;
+ const auto tentative_next_load_addr =
+ AppLoader_NSO::LoadModule(*module_file, load_addr, should_pass_arguments, pm);
+ if (!tentative_next_load_addr) {
+ return ResultStatus::ErrorLoadingNSO;
+ }
+
+ next_load_addr = *tentative_next_load_addr;
+ LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", module, load_addr);
+ // Register module with GDBStub
+ GDBStub::RegisterModule(module, load_addr, next_load_addr - 1, false);
}
process.Run(base_address, metadata.GetMainThreadPriority(), metadata.GetMainThreadStackSize());
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index e67b49fc9..6057c7f26 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -9,16 +9,11 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/elf.h"
#include "core/memory.h"
-using Kernel::CodeSet;
-using Kernel::SharedPtr;
-
////////////////////////////////////////////////////////////////////////////////////////////////////
// ELF Header Constants
@@ -211,7 +206,7 @@ public:
u32 GetFlags() const {
return (u32)(header->e_flags);
}
- SharedPtr<CodeSet> LoadInto(VAddr vaddr);
+ Kernel::CodeSet LoadInto(VAddr vaddr);
int GetNumSegments() const {
return (int)(header->e_phnum);
@@ -274,7 +269,7 @@ const char* ElfReader::GetSectionName(int section) const {
return nullptr;
}
-SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
+Kernel::CodeSet ElfReader::LoadInto(VAddr vaddr) {
LOG_DEBUG(Loader, "String section: {}", header->e_shstrndx);
// Should we relocate?
@@ -302,8 +297,7 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
std::vector<u8> program_image(total_image_size);
std::size_t current_image_position = 0;
- auto& kernel = Core::System::GetInstance().Kernel();
- SharedPtr<CodeSet> codeset = CodeSet::Create(kernel, "");
+ Kernel::CodeSet codeset;
for (unsigned int i = 0; i < header->e_phnum; ++i) {
const Elf32_Phdr* p = &segments[i];
@@ -311,14 +305,14 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
p->p_vaddr, p->p_filesz, p->p_memsz);
if (p->p_type == PT_LOAD) {
- CodeSet::Segment* codeset_segment;
+ Kernel::CodeSet::Segment* codeset_segment;
u32 permission_flags = p->p_flags & (PF_R | PF_W | PF_X);
if (permission_flags == (PF_R | PF_X)) {
- codeset_segment = &codeset->CodeSegment();
+ codeset_segment = &codeset.CodeSegment();
} else if (permission_flags == (PF_R)) {
- codeset_segment = &codeset->RODataSegment();
+ codeset_segment = &codeset.RODataSegment();
} else if (permission_flags == (PF_R | PF_W)) {
- codeset_segment = &codeset->DataSegment();
+ codeset_segment = &codeset.DataSegment();
} else {
LOG_ERROR(Loader, "Unexpected ELF PT_LOAD segment id {} with flags {:X}", i,
p->p_flags);
@@ -345,8 +339,8 @@ SharedPtr<CodeSet> ElfReader::LoadInto(VAddr vaddr) {
}
}
- codeset->entrypoint = base_addr + header->e_entry;
- codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ codeset.entrypoint = base_addr + header->e_entry;
+ codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
LOG_DEBUG(Loader, "Done loading.");
@@ -397,11 +391,11 @@ ResultStatus AppLoader_ELF::Load(Kernel::Process& process) {
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
ElfReader elf_reader(&buffer[0]);
- SharedPtr<CodeSet> codeset = elf_reader.LoadInto(base_address);
- codeset->name = file->GetName();
+ Kernel::CodeSet codeset = elf_reader.LoadInto(base_address);
+ const VAddr entry_point = codeset.entrypoint;
- process.LoadModule(codeset, codeset->entrypoint);
- process.Run(codeset->entrypoint, 48, Memory::DEFAULT_STACK_SIZE);
+ process.LoadModule(std::move(codeset), entry_point);
+ process.Run(entry_point, 48, Memory::DEFAULT_STACK_SIZE);
is_loaded = true;
return ResultStatus::Success;
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index f2a183ba1..9cd0b0ccd 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown";
}
-constexpr std::array<const char*, 58> RESULT_MESSAGES{
+constexpr std::array<const char*, 60> RESULT_MESSAGES{
"The operation completed successfully.",
"The loader requested to load is already loaded.",
"The operation is not implemented.",
@@ -128,6 +128,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{
"The RomFS could not be found.",
"The ELF file has incorrect size as determined by the header.",
"There was a general error loading the NRO into emulated memory.",
+ "There was a general error loading the NSO into emulated memory.",
"There is no icon available.",
"There is no control data available.",
"The NAX file has a bad header.",
@@ -152,6 +153,7 @@ constexpr std::array<const char*, 58> RESULT_MESSAGES{
"The BKTR-type NCA has a bad Relocation bucket.",
"The BKTR-type NCA has a bad Subsection bucket.",
"The BKTR-type NCA is missing the base RomFS.",
+ "The NSP or XCI does not contain an update in addition to the base game.",
};
std::ostream& operator<<(std::ostream& os, ResultStatus status) {
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 20e66109b..7686634bf 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -6,10 +6,11 @@
#include <iosfwd>
#include <memory>
+#include <optional>
#include <string>
#include <utility>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/common_types.h"
#include "core/file_sys/vfs.h"
@@ -90,6 +91,7 @@ enum class ResultStatus : u16 {
ErrorNoRomFS,
ErrorIncorrectELFFileSize,
ErrorLoadingNRO,
+ ErrorLoadingNSO,
ErrorNoIcon,
ErrorNoControl,
ErrorBadNAXHeader,
@@ -114,6 +116,7 @@ enum class ResultStatus : u16 {
ErrorBadRelocationBuckets,
ErrorBadSubsectionBuckets,
ErrorMissingBKTRBaseRomFS,
+ ErrorNoPackedUpdate,
};
std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -143,7 +146,7 @@ public:
* information.
* @returns A pair with the optional system mode, and and the status.
*/
- virtual std::pair<boost::optional<u32>, ResultStatus> LoadKernelSystemMode() {
+ virtual std::pair<std::optional<u32>, ResultStatus> LoadKernelSystemMode() {
// 96MB allocated to the application.
return std::make_pair(2, ResultStatus::Success);
}
@@ -196,10 +199,19 @@ public:
/**
* Get the RomFS of the application
* Since the RomFS can be huge, we return a file reference instead of copying to a buffer
- * @param dir The directory containing the RomFS
+ * @param file The directory containing the RomFS
+ * @return ResultStatus result of function
+ */
+ virtual ResultStatus ReadRomFS(FileSys::VirtualFile& file) {
+ return ResultStatus::ErrorNotImplemented;
+ }
+
+ /**
+ * Get the raw update of the application, should it come packed with one
+ * @param file The raw update NCA file (Program-type
* @return ResultStatus result of function
*/
- virtual ResultStatus ReadRomFS(FileSys::VirtualFile& dir) {
+ virtual ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) {
return ResultStatus::ErrorNotImplemented;
}
diff --git a/src/core/loader/nax.cpp b/src/core/loader/nax.cpp
index 073fb9d2f..42f4a777b 100644
--- a/src/core/loader/nax.cpp
+++ b/src/core/loader/nax.cpp
@@ -72,6 +72,10 @@ ResultStatus AppLoader_NAX::ReadRomFS(FileSys::VirtualFile& dir) {
return nca_loader->ReadRomFS(dir);
}
+u64 AppLoader_NAX::ReadRomFSIVFCOffset() const {
+ return nca_loader->ReadRomFSIVFCOffset();
+}
+
ResultStatus AppLoader_NAX::ReadProgramId(u64& out_program_id) {
return nca_loader->ReadProgramId(out_program_id);
}
diff --git a/src/core/loader/nax.h b/src/core/loader/nax.h
index fc3c01876..b4d93bd01 100644
--- a/src/core/loader/nax.h
+++ b/src/core/loader/nax.h
@@ -36,6 +36,7 @@ public:
ResultStatus Load(Kernel::Process& process) override;
ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+ u64 ReadRomFSIVFCOffset() const override;
ResultStatus ReadProgramId(u64& out_program_id) override;
private:
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index c10f826a4..bc8e402a8 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -14,11 +14,12 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/vfs_offset.h"
#include "core/gdbstub/gdbstub.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nro.h"
+#include "core/loader/nso.h"
#include "core/memory.h"
+#include "core/settings.h"
namespace Loader {
@@ -126,28 +127,45 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
- // Read NSO header
- NroHeader nro_header{};
- if (sizeof(NroHeader) != file->ReadObject(&nro_header)) {
+/*static*/ bool AppLoader_NRO::LoadNro(const std::vector<u8>& data, const std::string& name,
+ VAddr load_base) {
+
+ if (data.size() < sizeof(NroHeader)) {
return {};
}
+
+ // Read NSO header
+ NroHeader nro_header{};
+ std::memcpy(&nro_header, data.data(), sizeof(NroHeader));
if (nro_header.magic != Common::MakeMagic('N', 'R', 'O', '0')) {
return {};
}
// Build program image
- auto& kernel = Core::System::GetInstance().Kernel();
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
- std::vector<u8> program_image = file->ReadBytes(PageAlignSize(nro_header.file_size));
+ std::vector<u8> program_image(PageAlignSize(nro_header.file_size));
+ std::memcpy(program_image.data(), data.data(), program_image.size());
if (program_image.size() != PageAlignSize(nro_header.file_size)) {
return {};
}
+ Kernel::CodeSet codeset;
for (std::size_t i = 0; i < nro_header.segments.size(); ++i) {
- codeset->segments[i].addr = nro_header.segments[i].offset;
- codeset->segments[i].offset = nro_header.segments[i].offset;
- codeset->segments[i].size = PageAlignSize(nro_header.segments[i].size);
+ codeset.segments[i].addr = nro_header.segments[i].offset;
+ codeset.segments[i].offset = nro_header.segments[i].offset;
+ codeset.segments[i].size = PageAlignSize(nro_header.segments[i].size);
+ }
+
+ if (!Settings::values.program_args.empty()) {
+ const auto arg_data = Settings::values.program_args;
+ codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
+ NSOArgumentHeader args_header{
+ NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
+ const auto end_offset = program_image.size();
+ program_image.resize(static_cast<u32>(program_image.size()) +
+ NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
+ std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
+ std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
+ arg_data.size());
}
// Read MOD header
@@ -161,20 +179,23 @@ bool AppLoader_NRO::LoadNro(FileSys::VirtualFile file, VAddr load_base) {
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
- codeset->DataSegment().size += bss_size;
+ codeset.DataSegment().size += bss_size;
program_image.resize(static_cast<u32>(program_image.size()) + bss_size);
// Load codeset for current process
- codeset->name = file->GetName();
- codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Core::CurrentProcess()->LoadModule(codeset, load_base);
+ codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
- GDBStub::RegisterModule(codeset->name, load_base, load_base);
+ GDBStub::RegisterModule(name, load_base, load_base);
return true;
}
+bool AppLoader_NRO::LoadNro(const FileSys::VfsFile& file, VAddr load_base) {
+ return AppLoader_NRO::LoadNro(file.ReadAllBytes(), file.GetName(), load_base);
+}
+
ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
if (is_loaded) {
return ResultStatus::ErrorAlreadyLoaded;
@@ -183,7 +204,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::Process& process) {
// Load NRO
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- if (!LoadNro(file, base_address)) {
+ if (!LoadNro(*file, base_address)) {
return ResultStatus::ErrorLoadingNRO;
}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 04b46119a..3e6959302 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -5,6 +5,7 @@
#pragma once
#include <string>
+#include <vector>
#include "common/common_types.h"
#include "core/loader/linker.h"
#include "core/loader/loader.h"
@@ -40,8 +41,10 @@ public:
ResultStatus ReadTitle(std::string& title) override;
bool IsRomFSUpdatable() const override;
+ static bool LoadNro(const std::vector<u8>& data, const std::string& name, VAddr load_base);
+
private:
- bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
+ bool LoadNro(const FileSys::VfsFile& file, VAddr load_base);
std::vector<u8> icon_data;
std::unique_ptr<FileSys::NACP> nacp;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 2186b02af..68efca5c0 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -12,11 +12,11 @@
#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
#include "core/memory.h"
+#include "core/settings.h"
namespace Loader {
@@ -93,36 +93,46 @@ static constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
-VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
- boost::optional<FileSys::PatchManager> pm) {
- if (file == nullptr)
- return {};
-
- if (file->GetSize() < sizeof(NsoHeader))
+std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAddr load_base,
+ bool should_pass_arguments,
+ std::optional<FileSys::PatchManager> pm) {
+ if (file.GetSize() < sizeof(NsoHeader))
return {};
NsoHeader nso_header{};
- if (sizeof(NsoHeader) != file->ReadObject(&nso_header))
+ if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
return {};
if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
return {};
// Build program image
- auto& kernel = Core::System::GetInstance().Kernel();
- Kernel::SharedPtr<Kernel::CodeSet> codeset = Kernel::CodeSet::Create(kernel, "");
+ Kernel::CodeSet codeset;
std::vector<u8> program_image;
for (std::size_t i = 0; i < nso_header.segments.size(); ++i) {
std::vector<u8> data =
- file->ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
+ file.ReadBytes(nso_header.segments_compressed_size[i], nso_header.segments[i].offset);
if (nso_header.IsSegmentCompressed(i)) {
data = DecompressSegment(data, nso_header.segments[i]);
}
program_image.resize(nso_header.segments[i].location);
program_image.insert(program_image.end(), data.begin(), data.end());
- codeset->segments[i].addr = nso_header.segments[i].location;
- codeset->segments[i].offset = nso_header.segments[i].location;
- codeset->segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
+ codeset.segments[i].addr = nso_header.segments[i].location;
+ codeset.segments[i].offset = nso_header.segments[i].location;
+ codeset.segments[i].size = PageAlignSize(static_cast<u32>(data.size()));
+ }
+
+ if (should_pass_arguments && !Settings::values.program_args.empty()) {
+ const auto arg_data = Settings::values.program_args;
+ codeset.DataSegment().size += NSO_ARGUMENT_DATA_ALLOCATION_SIZE;
+ NSOArgumentHeader args_header{
+ NSO_ARGUMENT_DATA_ALLOCATION_SIZE, static_cast<u32_le>(arg_data.size()), {}};
+ const auto end_offset = program_image.size();
+ program_image.resize(static_cast<u32>(program_image.size()) +
+ NSO_ARGUMENT_DATA_ALLOCATION_SIZE);
+ std::memcpy(program_image.data() + end_offset, &args_header, sizeof(NSOArgumentHeader));
+ std::memcpy(program_image.data() + end_offset + sizeof(NSOArgumentHeader), arg_data.data(),
+ arg_data.size());
}
// MOD header pointer is at .text offset + 4
@@ -139,12 +149,12 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
// Resize program image to include .bss section and page align each section
bss_size = PageAlignSize(mod_header.bss_end_offset - mod_header.bss_start_offset);
}
- codeset->DataSegment().size += bss_size;
+ codeset.DataSegment().size += bss_size;
const u32 image_size{PageAlignSize(static_cast<u32>(program_image.size()) + bss_size)};
program_image.resize(image_size);
// Apply patches if necessary
- if (pm != boost::none && pm->HasNSOPatch(nso_header.build_id)) {
+ if (pm && pm->HasNSOPatch(nso_header.build_id)) {
std::vector<u8> pi_header(program_image.size() + 0x100);
std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
@@ -155,12 +165,11 @@ VAddr AppLoader_NSO::LoadModule(FileSys::VirtualFile file, VAddr load_base,
}
// Load codeset for current process
- codeset->name = file->GetName();
- codeset->memory = std::make_shared<std::vector<u8>>(std::move(program_image));
- Core::CurrentProcess()->LoadModule(codeset, load_base);
+ codeset.memory = std::make_shared<std::vector<u8>>(std::move(program_image));
+ Core::CurrentProcess()->LoadModule(std::move(codeset), load_base);
// Register module with GDBStub
- GDBStub::RegisterModule(codeset->name, load_base, load_base);
+ GDBStub::RegisterModule(file.GetName(), load_base, load_base);
return load_base + image_size;
}
@@ -172,7 +181,9 @@ ResultStatus AppLoader_NSO::Load(Kernel::Process& process) {
// Load module
const VAddr base_address = process.VMManager().GetCodeRegionBaseAddress();
- LoadModule(file, base_address);
+ if (!LoadModule(*file, base_address, true)) {
+ return ResultStatus::ErrorLoadingNSO;
+ }
LOG_DEBUG(Loader, "loaded module {} @ 0x{:X}", file->GetName(), base_address);
process.Run(base_address, Kernel::THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 05353d4d9..433306139 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -4,6 +4,7 @@
#pragma once
+#include <optional>
#include "common/common_types.h"
#include "core/file_sys/patch_manager.h"
#include "core/loader/linker.h"
@@ -11,6 +12,15 @@
namespace Loader {
+constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
+
+struct NSOArgumentHeader {
+ u32_le allocated_size;
+ u32_le actual_size;
+ INSERT_PADDING_BYTES(0x18);
+};
+static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
+
/// Loads an NSO file
class AppLoader_NSO final : public AppLoader, Linker {
public:
@@ -27,8 +37,9 @@ public:
return IdentifyType(file);
}
- static VAddr LoadModule(FileSys::VirtualFile file, VAddr load_base,
- boost::optional<FileSys::PatchManager> pm = boost::none);
+ static std::optional<VAddr> LoadModule(const FileSys::VfsFile& file, VAddr load_base,
+ bool should_pass_arguments,
+ std::optional<FileSys::PatchManager> pm = {});
ResultStatus Load(Kernel::Process& process) override;
};
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index b7ba77ef4..080d89904 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -10,8 +10,10 @@
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/deconstructed_rom_directory.h"
#include "core/loader/nca.h"
#include "core/loader/nsp.h"
@@ -33,7 +35,17 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
return;
std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca);
+ FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(*control_nca);
+
+ if (nsp->IsExtractedType()) {
+ secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
+ } else {
+ if (title_id == 0)
+ return;
+
+ secondary_loader = std::make_unique<AppLoader_NCA>(
+ nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
+ }
}
AppLoader_NSP::~AppLoader_NSP() = default;
@@ -65,39 +77,58 @@ ResultStatus AppLoader_NSP::Load(Kernel::Process& process) {
return ResultStatus::ErrorAlreadyLoaded;
}
- if (nsp->IsExtractedType()) {
- secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
- } else {
- if (title_id == 0)
- return ResultStatus::ErrorNSPMissingProgramNCA;
-
- secondary_loader = std::make_unique<AppLoader_NCA>(
- nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
+ if (title_id == 0)
+ return ResultStatus::ErrorNSPMissingProgramNCA;
- if (nsp->GetStatus() != ResultStatus::Success)
- return nsp->GetStatus();
+ if (nsp->GetStatus() != ResultStatus::Success)
+ return nsp->GetStatus();
- if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
- return nsp->GetProgramStatus(title_id);
+ if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
+ return nsp->GetProgramStatus(title_id);
- if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
- if (!Core::Crypto::KeyManager::KeyFileExists(false))
- return ResultStatus::ErrorMissingProductionKeyFile;
- return ResultStatus::ErrorNSPMissingProgramNCA;
- }
+ if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
+ if (!Core::Crypto::KeyManager::KeyFileExists(false))
+ return ResultStatus::ErrorMissingProductionKeyFile;
+ return ResultStatus::ErrorNSPMissingProgramNCA;
}
const auto result = secondary_loader->Load(process);
if (result != ResultStatus::Success)
return result;
+ FileSys::VirtualFile update_raw;
+ if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
+ Service::FileSystem::SetPackedUpdate(std::move(update_raw));
+
is_loaded = true;
return ResultStatus::Success;
}
-ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
- return secondary_loader->ReadRomFS(dir);
+ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& file) {
+ return secondary_loader->ReadRomFS(file);
+}
+
+u64 AppLoader_NSP::ReadRomFSIVFCOffset() const {
+ return secondary_loader->ReadRomFSIVFCOffset();
+}
+
+ResultStatus AppLoader_NSP::ReadUpdateRaw(FileSys::VirtualFile& file) {
+ if (nsp->IsExtractedType())
+ return ResultStatus::ErrorNoPackedUpdate;
+
+ const auto read =
+ nsp->GetNCAFile(FileSys::GetUpdateTitleID(title_id), FileSys::ContentRecordType::Program);
+
+ if (read == nullptr)
+ return ResultStatus::ErrorNoPackedUpdate;
+ const auto nca_test = std::make_shared<FileSys::NCA>(read);
+
+ if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
+ return nca_test->GetStatus();
+
+ file = read;
+ return ResultStatus::Success;
}
ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index eac9b819a..db91cd01e 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -37,7 +37,9 @@ public:
ResultStatus Load(Kernel::Process& process) override;
- ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+ ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
+ u64 ReadRomFSIVFCOffset() const override;
+ ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
@@ -47,7 +49,7 @@ private:
std::unique_ptr<AppLoader> secondary_loader;
FileSys::VirtualFile icon_file;
- std::shared_ptr<FileSys::NACP> nacp_file;
+ std::unique_ptr<FileSys::NACP> nacp_file;
u64 title_id;
};
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index eda67a8c8..461607c95 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -9,7 +9,11 @@
#include "core/file_sys/content_archive.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/submission_package.h"
#include "core/hle/kernel/process.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/nca.h"
#include "core/loader/xci.h"
@@ -26,7 +30,7 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
return;
std::tie(nacp_file, icon_file) =
- FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca);
+ FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(*control_nca);
}
AppLoader_XCI::~AppLoader_XCI() = default;
@@ -55,21 +59,48 @@ ResultStatus AppLoader_XCI::Load(Kernel::Process& process) {
if (xci->GetProgramNCAStatus() != ResultStatus::Success)
return xci->GetProgramNCAStatus();
- const auto nca = xci->GetProgramNCA();
- if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
+ if (!xci->HasProgramNCA() && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile;
const auto result = nca_loader->Load(process);
if (result != ResultStatus::Success)
return result;
+ FileSys::VirtualFile update_raw;
+ if (ReadUpdateRaw(update_raw) == ResultStatus::Success && update_raw != nullptr)
+ Service::FileSystem::SetPackedUpdate(std::move(update_raw));
+
is_loaded = true;
return ResultStatus::Success;
}
-ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& dir) {
- return nca_loader->ReadRomFS(dir);
+ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& file) {
+ return nca_loader->ReadRomFS(file);
+}
+
+u64 AppLoader_XCI::ReadRomFSIVFCOffset() const {
+ return nca_loader->ReadRomFSIVFCOffset();
+}
+
+ResultStatus AppLoader_XCI::ReadUpdateRaw(FileSys::VirtualFile& file) {
+ u64 program_id{};
+ nca_loader->ReadProgramId(program_id);
+ if (program_id == 0)
+ return ResultStatus::ErrorXCIMissingProgramNCA;
+
+ const auto read = xci->GetSecurePartitionNSP()->GetNCAFile(
+ FileSys::GetUpdateTitleID(program_id), FileSys::ContentRecordType::Program);
+
+ if (read == nullptr)
+ return ResultStatus::ErrorNoPackedUpdate;
+ const auto nca_test = std::make_shared<FileSys::NCA>(read);
+
+ if (nca_test->GetStatus() != ResultStatus::ErrorMissingBKTRBaseRomFS)
+ return nca_test->GetStatus();
+
+ file = read;
+ return ResultStatus::Success;
}
ResultStatus AppLoader_XCI::ReadProgramId(u64& out_program_id) {
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index 17e47b658..46f8dfc9e 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -37,7 +37,9 @@ public:
ResultStatus Load(Kernel::Process& process) override;
- ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+ ResultStatus ReadRomFS(FileSys::VirtualFile& file) override;
+ u64 ReadRomFSIVFCOffset() const override;
+ ResultStatus ReadUpdateRaw(FileSys::VirtualFile& file) override;
ResultStatus ReadProgramId(u64& out_program_id) override;
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
ResultStatus ReadTitle(std::string& title) override;
@@ -47,7 +49,7 @@ private:
std::unique_ptr<AppLoader_NCA> nca_loader;
FileSys::VirtualFile icon_file;
- std::shared_ptr<FileSys::NACP> nacp_file;
+ std::unique_ptr<FileSys::NACP> nacp_file;
};
} // namespace Loader
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 014298ed6..70abd856a 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -4,9 +4,9 @@
#include <algorithm>
#include <cstring>
+#include <optional>
#include <utility>
-#include <boost/optional.hpp>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
diff --git a/src/core/memory_hook.h b/src/core/memory_hook.h
index 0269c7ff1..940777107 100644
--- a/src/core/memory_hook.h
+++ b/src/core/memory_hook.h
@@ -5,7 +5,8 @@
#pragma once
#include <memory>
-#include <boost/optional.hpp>
+#include <optional>
+
#include "common/common_types.h"
namespace Memory {
@@ -18,19 +19,19 @@ namespace Memory {
*
* A hook may be mapped to multiple regions of memory.
*
- * If a boost::none or false is returned from a function, the read/write request is passed through
+ * If a std::nullopt or false is returned from a function, the read/write request is passed through
* to the underlying memory region.
*/
class MemoryHook {
public:
virtual ~MemoryHook();
- virtual boost::optional<bool> IsValidAddress(VAddr addr) = 0;
+ virtual std::optional<bool> IsValidAddress(VAddr addr) = 0;
- virtual boost::optional<u8> Read8(VAddr addr) = 0;
- virtual boost::optional<u16> Read16(VAddr addr) = 0;
- virtual boost::optional<u32> Read32(VAddr addr) = 0;
- virtual boost::optional<u64> Read64(VAddr addr) = 0;
+ virtual std::optional<u8> Read8(VAddr addr) = 0;
+ virtual std::optional<u16> Read16(VAddr addr) = 0;
+ virtual std::optional<u32> Read32(VAddr addr) = 0;
+ virtual std::optional<u64> Read64(VAddr addr) = 0;
virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) = 0;
diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp
index 7d95816fe..c716a462b 100644
--- a/src/core/perf_stats.cpp
+++ b/src/core/perf_stats.cpp
@@ -74,10 +74,6 @@ double PerfStats::GetLastFrameTimeScale() {
}
void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) {
- // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher
- // values increase the time needed to recover and limit framerate again after spikes.
- constexpr microseconds MAX_LAG_TIME_US = 25000us;
-
if (!Settings::values.use_frame_limit) {
return;
}
diff --git a/src/core/settings.h b/src/core/settings.h
index 1808f5937..b5aeff29b 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -113,7 +113,8 @@ static const std::array<const char*, NumAnalogs> mapping = {{
struct Values {
// System
bool use_docked_mode;
- std::string username;
+ bool enable_nfc;
+ int current_user;
int language_index;
// Controls
@@ -136,7 +137,7 @@ struct Values {
float resolution_factor;
bool use_frame_limit;
u16 frame_limit;
- bool use_accurate_framebuffers;
+ bool use_accurate_gpu_emulation;
float bg_red;
float bg_green;
@@ -155,6 +156,7 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
+ std::string program_args;
// WebService
bool enable_telemetry;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index f29fff1e7..a3b08c740 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -2,12 +2,16 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <array>
+
+#include <mbedtls/ctr_drbg.h>
+#include <mbedtls/entropy.h>
+
#include "common/assert.h"
#include "common/common_types.h"
#include "common/file_util.h"
+#include "common/logging/log.h"
-#include <mbedtls/ctr_drbg.h>
-#include <mbedtls/entropy.h>
#include "core/core.h"
#include "core/file_sys/control_metadata.h"
#include "core/file_sys/patch_manager.h"
@@ -28,11 +32,11 @@ static u64 GenerateTelemetryId() {
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_context ctr_drbg;
- std::string personalization = "yuzu Telemetry ID";
+ constexpr std::array<char, 18> personalization{{"yuzu Telemetry ID"}};
mbedtls_ctr_drbg_init(&ctr_drbg);
ASSERT(mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy,
- reinterpret_cast<const unsigned char*>(personalization.c_str()),
+ reinterpret_cast<const unsigned char*>(personalization.data()),
personalization.size()) == 0);
ASSERT(mbedtls_ctr_drbg_random(&ctr_drbg, reinterpret_cast<unsigned char*>(&telemetry_id),
sizeof(u64)) == 0);
@@ -159,8 +163,8 @@ TelemetrySession::TelemetrySession() {
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
Settings::values.use_frame_limit);
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
- AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateFramebuffers",
- Settings::values.use_accurate_framebuffers);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation",
+ Settings::values.use_accurate_gpu_emulation);
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
Settings::values.use_docked_mode);
}
@@ -180,4 +184,13 @@ TelemetrySession::~TelemetrySession() {
backend = nullptr;
}
+bool TelemetrySession::SubmitTestcase() {
+#ifdef ENABLE_WEB_SERVICE
+ field_collection.Accept(*backend);
+ return backend->SubmitTestcase();
+#else
+ return false;
+#endif
+}
+
} // namespace Core
diff --git a/src/core/telemetry_session.h b/src/core/telemetry_session.h
index cec271df0..023612b79 100644
--- a/src/core/telemetry_session.h
+++ b/src/core/telemetry_session.h
@@ -5,6 +5,7 @@
#pragma once
#include <memory>
+#include <string>
#include "common/telemetry.h"
namespace Core {
@@ -30,7 +31,11 @@ public:
field_collection.AddField(type, name, std::move(value));
}
- static void FinalizeAsyncJob();
+ /**
+ * Submits a Testcase.
+ * @returns A bool indicating whether the submission succeeded
+ */
+ bool SubmitTestcase();
private:
Telemetry::FieldCollection field_collection; ///< Tracks all added fields for the session
@@ -53,7 +58,6 @@ u64 RegenerateTelemetryId();
* Verifies the username and token.
* @param username yuzu username to use for authentication.
* @param token yuzu 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
*/
bool VerifyLogin(const std::string& username, const std::string& token);
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index c0a57e71f..9b8a44fa1 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -15,7 +15,8 @@ namespace ArmTests {
TestEnvironment::TestEnvironment(bool mutable_memory_)
: mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) {
- Core::CurrentProcess() = Kernel::Process::Create(kernel, "");
+ auto process = Kernel::Process::Create(kernel, "");
+ kernel.MakeCurrentProcess(process.get());
page_table = &Core::CurrentProcess()->VMManager().page_table;
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
@@ -63,11 +64,11 @@ void TestEnvironment::ClearWriteRecords() {
TestEnvironment::TestMemory::~TestMemory() {}
-boost::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
+std::optional<bool> TestEnvironment::TestMemory::IsValidAddress(VAddr addr) {
return true;
}
-boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
+std::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
const auto iter = data.find(addr);
if (iter == data.end()) {
@@ -78,15 +79,15 @@ boost::optional<u8> TestEnvironment::TestMemory::Read8(VAddr addr) {
return iter->second;
}
-boost::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
+std::optional<u16> TestEnvironment::TestMemory::Read16(VAddr addr) {
return *Read8(addr) | static_cast<u16>(*Read8(addr + 1)) << 8;
}
-boost::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
+std::optional<u32> TestEnvironment::TestMemory::Read32(VAddr addr) {
return *Read16(addr) | static_cast<u32>(*Read16(addr + 2)) << 16;
}
-boost::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
+std::optional<u64> TestEnvironment::TestMemory::Read64(VAddr addr) {
return *Read32(addr) | static_cast<u64>(*Read32(addr + 4)) << 32;
}
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 5de8dab4e..0b7539601 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -64,12 +64,12 @@ private:
~TestMemory() override;
- boost::optional<bool> IsValidAddress(VAddr addr) override;
+ std::optional<bool> IsValidAddress(VAddr addr) override;
- boost::optional<u8> Read8(VAddr addr) override;
- boost::optional<u16> Read16(VAddr addr) override;
- boost::optional<u32> Read32(VAddr addr) override;
- boost::optional<u64> Read64(VAddr addr) override;
+ std::optional<u8> Read8(VAddr addr) override;
+ std::optional<u16> Read16(VAddr addr) override;
+ std::optional<u32> Read32(VAddr addr) override;
+ std::optional<u64> Read64(VAddr addr) override;
bool ReadBlock(VAddr src_addr, void* dest_buffer, std::size_t size) override;
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 09ecc5bad..ddb1a1d69 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -51,6 +51,10 @@ add_library(video_core STATIC
renderer_opengl/maxwell_to_gl.h
renderer_opengl/renderer_opengl.cpp
renderer_opengl/renderer_opengl.h
+ renderer_opengl/utils.cpp
+ renderer_opengl/utils.h
+ surface.cpp
+ surface.h
textures/astc.cpp
textures/astc.h
textures/decoders.cpp
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index f1aa6091b..28e8c13aa 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -81,7 +81,7 @@ void GPU::ProcessCommandLists(const std::vector<CommandListHeader>& commands) {
for (auto entry : commands) {
Tegra::GPUVAddr address = entry.Address();
u32 size = entry.sz;
- const boost::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
+ const std::optional<VAddr> head_address = memory_manager->GpuToCpuAddress(address);
VAddr current_addr = *head_address;
while (current_addr < *head_address + size * sizeof(CommandHeader)) {
const CommandHeader header = {Memory::Read32(current_addr)};
diff --git a/src/video_core/engines/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp
index ea1555c5d..74e44c7fe 100644
--- a/src/video_core/engines/fermi_2d.cpp
+++ b/src/video_core/engines/fermi_2d.cpp
@@ -4,11 +4,13 @@
#include "core/memory.h"
#include "video_core/engines/fermi_2d.h"
+#include "video_core/rasterizer_interface.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
-Fermi2D::Fermi2D(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+Fermi2D::Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
+ : memory_manager(memory_manager), rasterizer{rasterizer} {}
void Fermi2D::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -44,27 +46,36 @@ void Fermi2D::HandleSurfaceCopy() {
u32 src_bytes_per_pixel = RenderTargetBytesPerPixel(regs.src.format);
u32 dst_bytes_per_pixel = RenderTargetBytesPerPixel(regs.dst.format);
- if (regs.src.linear == regs.dst.linear) {
- // If the input layout and the output layout are the same, just perform a raw copy.
- ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
- Memory::CopyBlock(dest_cpu, source_cpu,
- src_bytes_per_pixel * regs.dst.width * regs.dst.height);
- return;
- }
-
- u8* src_buffer = Memory::GetPointer(source_cpu);
- u8* dst_buffer = Memory::GetPointer(dest_cpu);
-
- if (!regs.src.linear && regs.dst.linear) {
- // If the input is tiled and the output is linear, deswizzle the input and copy it over.
- Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
- dst_bytes_per_pixel, src_buffer, dst_buffer, true,
- regs.src.BlockHeight());
- } else {
- // If the input is linear and the output is tiled, swizzle the input and copy it over.
- Texture::CopySwizzledData(regs.src.width, regs.src.height, src_bytes_per_pixel,
- dst_bytes_per_pixel, dst_buffer, src_buffer, false,
- regs.dst.BlockHeight());
+ if (!rasterizer.AccelerateSurfaceCopy(regs.src, regs.dst)) {
+ rasterizer.FlushRegion(source_cpu, src_bytes_per_pixel * regs.src.width * regs.src.height);
+ // We have to invalidate the destination region to evict any outdated surfaces from the
+ // cache. We do this before actually writing the new data because the destination address
+ // might contain a dirty surface that will have to be written back to memory.
+ rasterizer.InvalidateRegion(dest_cpu,
+ dst_bytes_per_pixel * regs.dst.width * regs.dst.height);
+
+ if (regs.src.linear == regs.dst.linear) {
+ // If the input layout and the output layout are the same, just perform a raw copy.
+ ASSERT(regs.src.BlockHeight() == regs.dst.BlockHeight());
+ Memory::CopyBlock(dest_cpu, source_cpu,
+ src_bytes_per_pixel * regs.dst.width * regs.dst.height);
+ return;
+ }
+ u8* src_buffer = Memory::GetPointer(source_cpu);
+ u8* dst_buffer = Memory::GetPointer(dest_cpu);
+ if (!regs.src.linear && regs.dst.linear) {
+ // If the input is tiled and the output is linear, deswizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
+ src_bytes_per_pixel, dst_bytes_per_pixel, src_buffer,
+ dst_buffer, true, regs.src.BlockHeight(),
+ regs.src.BlockDepth());
+ } else {
+ // If the input is linear and the output is tiled, swizzle the input and copy it over.
+ Texture::CopySwizzledData(regs.src.width, regs.src.height, regs.src.depth,
+ src_bytes_per_pixel, dst_bytes_per_pixel, dst_buffer,
+ src_buffer, false, regs.dst.BlockHeight(),
+ regs.dst.BlockDepth());
+ }
}
}
diff --git a/src/video_core/engines/fermi_2d.h b/src/video_core/engines/fermi_2d.h
index 021b83eaa..2a6e8bbbb 100644
--- a/src/video_core/engines/fermi_2d.h
+++ b/src/video_core/engines/fermi_2d.h
@@ -12,6 +12,10 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
+namespace VideoCore {
+class RasterizerInterface;
+}
+
namespace Tegra::Engines {
#define FERMI2D_REG_INDEX(field_name) \
@@ -19,7 +23,7 @@ namespace Tegra::Engines {
class Fermi2D final {
public:
- explicit Fermi2D(MemoryManager& memory_manager);
+ explicit Fermi2D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
~Fermi2D() = default;
/// Write the value to the register identified by method.
@@ -32,9 +36,9 @@ public:
RenderTargetFormat format;
BitField<0, 1, u32> linear;
union {
- BitField<0, 4, u32> block_depth;
+ BitField<0, 4, u32> block_width;
BitField<4, 4, u32> block_height;
- BitField<8, 4, u32> block_width;
+ BitField<8, 4, u32> block_depth;
};
u32 depth;
u32 layer;
@@ -49,10 +53,20 @@ public:
address_low);
}
+ u32 BlockWidth() const {
+ // The block width is stored in log2 format.
+ return 1 << block_width;
+ }
+
u32 BlockHeight() const {
// The block height is stored in log2 format.
return 1 << block_height;
}
+
+ u32 BlockDepth() const {
+ // The block depth is stored in log2 format.
+ return 1 << block_depth;
+ }
};
static_assert(sizeof(Surface) == 0x28, "Surface has incorrect size");
@@ -94,6 +108,8 @@ public:
MemoryManager& memory_manager;
private:
+ VideoCore::RasterizerInterface& rasterizer;
+
/// Performs the copy from the source surface to the destination surface as configured in the
/// registers.
void HandleSurfaceCopy();
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index 66ae6332d..585290d9f 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -5,10 +5,14 @@
#include "common/logging/log.h"
#include "core/memory.h"
#include "video_core/engines/kepler_memory.h"
+#include "video_core/rasterizer_interface.h"
namespace Tegra::Engines {
-KeplerMemory::KeplerMemory(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+KeplerMemory::KeplerMemory(VideoCore::RasterizerInterface& rasterizer,
+ MemoryManager& memory_manager)
+ : memory_manager(memory_manager), rasterizer{rasterizer} {}
+
KeplerMemory::~KeplerMemory() = default;
void KeplerMemory::WriteReg(u32 method, u32 value) {
@@ -37,6 +41,11 @@ void KeplerMemory::ProcessData(u32 data) {
VAddr dest_address =
*memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32));
+ // We have to invalidate the destination region to evict any outdated surfaces from the cache.
+ // We do this before actually writing the new data because the destination address might contain
+ // a dirty surface that will have to be written back to memory.
+ rasterizer.InvalidateRegion(dest_address, sizeof(u32));
+
Memory::Write32(dest_address, data);
state.write_offset++;
diff --git a/src/video_core/engines/kepler_memory.h b/src/video_core/engines/kepler_memory.h
index b0d0078cf..bf4a13cff 100644
--- a/src/video_core/engines/kepler_memory.h
+++ b/src/video_core/engines/kepler_memory.h
@@ -11,6 +11,10 @@
#include "common/common_types.h"
#include "video_core/memory_manager.h"
+namespace VideoCore {
+class RasterizerInterface;
+}
+
namespace Tegra::Engines {
#define KEPLERMEMORY_REG_INDEX(field_name) \
@@ -18,7 +22,7 @@ namespace Tegra::Engines {
class KeplerMemory final {
public:
- KeplerMemory(MemoryManager& memory_manager);
+ KeplerMemory(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
~KeplerMemory();
/// Write the value to the register identified by method.
@@ -72,6 +76,7 @@ public:
private:
MemoryManager& memory_manager;
+ VideoCore::RasterizerInterface& rasterizer;
void ProcessData(u32 data);
};
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 8afd26fe9..d79c50919 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <cinttypes>
+#include <cstring>
#include "common/assert.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -13,28 +14,46 @@
#include "video_core/renderer_base.h"
#include "video_core/textures/texture.h"
-namespace Tegra {
-namespace Engines {
+namespace Tegra::Engines {
/// First register id that is actually a Macro call.
constexpr u32 MacroRegistersStart = 0xE00;
Maxwell3D::Maxwell3D(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
- : memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {}
+ : memory_manager(memory_manager), rasterizer{rasterizer}, macro_interpreter(*this) {
+ InitializeRegisterDefaults();
+}
+
+void Maxwell3D::InitializeRegisterDefaults() {
+ // Initializes registers to their default values - what games expect them to be at boot. This is
+ // for certain registers that may not be explicitly set by games.
+
+ // Reset all registers to zero
+ std::memset(&regs, 0, sizeof(regs));
+
+ // Depth range near/far is not always set, but is expected to be the default 0.0f, 1.0f. This is
+ // needed for ARMS.
+ for (std::size_t viewport{}; viewport < Regs::NumViewports; ++viewport) {
+ regs.viewport[viewport].depth_range_near = 0.0f;
+ regs.viewport[viewport].depth_range_far = 1.0f;
+ }
+}
void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
// Reset the current macro.
executing_macro = 0;
- // The requested macro must have been uploaded already.
- auto macro_code = uploaded_macros.find(method);
- if (macro_code == uploaded_macros.end()) {
- LOG_ERROR(HW_GPU, "Macro {:04X} was not uploaded", method);
+ // Lookup the macro offset
+ const u32 entry{(method - MacroRegistersStart) >> 1};
+ const auto& search{macro_offsets.find(entry)};
+ if (search == macro_offsets.end()) {
+ LOG_CRITICAL(HW_GPU, "macro not found for method 0x{:X}!", method);
+ UNREACHABLE();
return;
}
// Execute the current macro.
- macro_interpreter.Execute(macro_code->second, std::move(parameters));
+ macro_interpreter.Execute(search->second, std::move(parameters));
}
void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
@@ -80,6 +99,10 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
ProcessMacroUpload(value);
break;
}
+ case MAXWELL3D_REG_INDEX(macros.bind): {
+ ProcessMacroBind(value);
+ break;
+ }
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[0]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[1]):
case MAXWELL3D_REG_INDEX(const_buffer.cb_data[2]):
@@ -141,22 +164,25 @@ void Maxwell3D::WriteReg(u32 method, u32 value, u32 remaining_params) {
}
void Maxwell3D::ProcessMacroUpload(u32 data) {
- // Store the uploaded macro code to interpret them when they're called.
- auto& macro = uploaded_macros[regs.macros.entry * 2 + MacroRegistersStart];
- macro.push_back(data);
+ ASSERT_MSG(regs.macros.upload_address < macro_memory.size(),
+ "upload_address exceeded macro_memory size!");
+ macro_memory[regs.macros.upload_address++] = data;
+}
+
+void Maxwell3D::ProcessMacroBind(u32 data) {
+ macro_offsets[regs.macros.entry] = data;
}
void Maxwell3D::ProcessQueryGet() {
GPUVAddr sequence_address = regs.query.QueryAddress();
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
// VAddr before writing.
- boost::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
+ std::optional<VAddr> address = memory_manager.GpuToCpuAddress(sequence_address);
// TODO(Subv): Support the other query units.
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
"Units other than CROP are unimplemented");
- u32 value = Memory::Read32(*address);
u64 result = 0;
// TODO(Subv): Support the other query variables
@@ -269,7 +295,7 @@ void Maxwell3D::ProcessCBData(u32 value) {
// Don't allow writing past the end of the buffer.
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
- boost::optional<VAddr> address =
+ std::optional<VAddr> address =
memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
Memory::Write32(*address, value);
@@ -282,7 +308,7 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
GPUVAddr tic_base_address = regs.tic.TICAddress();
GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
- boost::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
+ std::optional<VAddr> tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
Texture::TICEntry tic_entry;
Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
@@ -306,7 +332,7 @@ Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
- boost::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
+ std::optional<VAddr> tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
Texture::TSCEntry tsc_entry;
Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
@@ -370,7 +396,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
- boost::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
+ std::optional<VAddr> tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
Texture::FullTextureInfo tex_info{};
@@ -408,5 +434,4 @@ void Maxwell3D::ProcessClearBuffers() {
rasterizer.Clear();
}
-} // namespace Engines
-} // namespace Tegra
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 4290da33f..50873813e 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -347,6 +347,16 @@ public:
DecrWrap = 8,
};
+ enum class MemoryLayout : u32 {
+ Linear = 0,
+ BlockLinear = 1,
+ };
+
+ enum class InvMemoryLayout : u32 {
+ BlockLinear = 0,
+ Linear = 1,
+ };
+
struct Cull {
enum class FrontFace : u32 {
ClockWise = 0x0900,
@@ -432,8 +442,16 @@ public:
u32 width;
u32 height;
Tegra::RenderTargetFormat format;
- u32 block_dimensions;
- u32 array_mode;
+ union {
+ BitField<0, 3, u32> block_width;
+ BitField<4, 3, u32> block_height;
+ BitField<8, 3, u32> block_depth;
+ BitField<12, 1, InvMemoryLayout> type;
+ } memory_layout;
+ union {
+ BitField<0, 16, u32> array_mode;
+ BitField<16, 1, u32> volume;
+ };
u32 layer_stride;
u32 base_layer;
INSERT_PADDING_WORDS(7);
@@ -457,12 +475,13 @@ public:
INSERT_PADDING_WORDS(0x45);
struct {
- INSERT_PADDING_WORDS(1);
+ u32 upload_address;
u32 data;
u32 entry;
+ u32 bind;
} macros;
- INSERT_PADDING_WORDS(0x189);
+ INSERT_PADDING_WORDS(0x188);
u32 tfb_enabled;
@@ -532,7 +551,21 @@ public:
INSERT_PADDING_WORDS(0x3);
s32 clear_stencil;
- INSERT_PADDING_WORDS(0x6C);
+ INSERT_PADDING_WORDS(0x17);
+
+ struct {
+ u32 enable;
+ union {
+ BitField<0, 16, u32> min_x;
+ BitField<16, 16, u32> max_x;
+ };
+ union {
+ BitField<0, 16, u32> min_y;
+ BitField<16, 16, u32> max_y;
+ };
+ } scissor_test;
+
+ INSERT_PADDING_WORDS(0x52);
s32 stencil_back_func_ref;
u32 stencil_back_mask;
@@ -548,7 +581,12 @@ public:
u32 address_high;
u32 address_low;
Tegra::DepthFormat format;
- u32 block_dimensions;
+ union {
+ BitField<0, 4, u32> block_width;
+ BitField<4, 4, u32> block_height;
+ BitField<8, 4, u32> block_depth;
+ BitField<20, 1, InvMemoryLayout> type;
+ } memory_layout;
u32 layer_stride;
GPUVAddr Address() const {
@@ -606,8 +644,10 @@ public:
u32 d3d_cull_mode;
ComparisonOp depth_test_func;
+ float alpha_test_ref;
+ ComparisonOp alpha_test_func;
- INSERT_PADDING_WORDS(0xB);
+ INSERT_PADDING_WORDS(0x9);
struct {
u32 separate_alpha;
@@ -684,7 +724,11 @@ public:
StencilOp stencil_back_op_zpass;
ComparisonOp stencil_back_func_func;
- INSERT_PADDING_WORDS(0x17);
+ INSERT_PADDING_WORDS(0x4);
+
+ u32 framebuffer_srgb;
+
+ INSERT_PADDING_WORDS(0x12);
union {
BitField<2, 1, u32> coord_origin;
@@ -712,7 +756,14 @@ public:
};
} draw;
- INSERT_PADDING_WORDS(0x6B);
+ INSERT_PADDING_WORDS(0xA);
+
+ struct {
+ u32 enabled;
+ u32 index;
+ } primitive_restart;
+
+ INSERT_PADDING_WORDS(0x5F);
struct {
u32 start_addr_high;
@@ -944,10 +995,25 @@ public:
/// Returns the texture information for a specific texture in a specific shader stage.
Texture::FullTextureInfo GetStageTexture(Regs::ShaderStage stage, std::size_t offset) const;
+ /// Memory for macro code - it's undetermined how big this is, however 1MB is much larger than
+ /// we've seen used.
+ using MacroMemory = std::array<u32, 0x40000>;
+
+ /// Gets a reference to macro memory.
+ const MacroMemory& GetMacroMemory() const {
+ return macro_memory;
+ }
+
private:
+ void InitializeRegisterDefaults();
+
VideoCore::RasterizerInterface& rasterizer;
- std::unordered_map<u32, std::vector<u32>> uploaded_macros;
+ /// Start offsets of each macro in macro_memory
+ std::unordered_map<u32, u32> macro_offsets;
+
+ /// Memory for macro code
+ MacroMemory macro_memory;
/// Macro method that is currently being executed / being fed parameters.
u32 executing_macro = 0;
@@ -970,9 +1036,12 @@ private:
*/
void CallMacroMethod(u32 method, std::vector<u32> parameters);
- /// Handles writes to the macro uploading registers.
+ /// Handles writes to the macro uploading register.
void ProcessMacroUpload(u32 data);
+ /// Handles writes to the macro bind register.
+ void ProcessMacroBind(u32 data);
+
/// Handles a write to the CLEAR_BUFFERS register.
void ProcessClearBuffers();
@@ -1002,6 +1071,7 @@ ASSERT_REG_POSITION(vertex_buffer, 0x35D);
ASSERT_REG_POSITION(clear_color[0], 0x360);
ASSERT_REG_POSITION(clear_depth, 0x364);
ASSERT_REG_POSITION(clear_stencil, 0x368);
+ASSERT_REG_POSITION(scissor_test, 0x380);
ASSERT_REG_POSITION(stencil_back_func_ref, 0x3D5);
ASSERT_REG_POSITION(stencil_back_mask, 0x3D6);
ASSERT_REG_POSITION(stencil_back_func_mask, 0x3D7);
@@ -1037,9 +1107,11 @@ ASSERT_REG_POSITION(stencil_back_op_fail, 0x566);
ASSERT_REG_POSITION(stencil_back_op_zfail, 0x567);
ASSERT_REG_POSITION(stencil_back_op_zpass, 0x568);
ASSERT_REG_POSITION(stencil_back_func_func, 0x569);
+ASSERT_REG_POSITION(framebuffer_srgb, 0x56E);
ASSERT_REG_POSITION(point_coord_replace, 0x581);
ASSERT_REG_POSITION(code_address, 0x582);
ASSERT_REG_POSITION(draw, 0x585);
+ASSERT_REG_POSITION(primitive_restart, 0x591);
ASSERT_REG_POSITION(index_array, 0x5F2);
ASSERT_REG_POSITION(instanced_arrays, 0x620);
ASSERT_REG_POSITION(cull, 0x646);
diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp
index 59e28b22d..8b5f08351 100644
--- a/src/video_core/engines/maxwell_compute.cpp
+++ b/src/video_core/engines/maxwell_compute.cpp
@@ -6,8 +6,7 @@
#include "core/core.h"
#include "video_core/engines/maxwell_compute.h"
-namespace Tegra {
-namespace Engines {
+namespace Tegra::Engines {
void MaxwellCompute::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -26,5 +25,4 @@ void MaxwellCompute::WriteReg(u32 method, u32 value) {
}
}
-} // namespace Engines
-} // namespace Tegra
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index aa7481b8c..b8a78cf82 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -4,12 +4,13 @@
#include "core/memory.h"
#include "video_core/engines/maxwell_dma.h"
+#include "video_core/rasterizer_interface.h"
#include "video_core/textures/decoders.h"
-namespace Tegra {
-namespace Engines {
+namespace Tegra::Engines {
-MaxwellDMA::MaxwellDMA(MemoryManager& memory_manager) : memory_manager(memory_manager) {}
+MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager)
+ : memory_manager(memory_manager), rasterizer{rasterizer} {}
void MaxwellDMA::WriteReg(u32 method, u32 value) {
ASSERT_MSG(method < Regs::NUM_REGS,
@@ -44,38 +45,77 @@ void MaxwellDMA::HandleCopy() {
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
ASSERT(regs.exec.query_intr == Regs::QueryIntr::None);
ASSERT(regs.exec.copy_mode == Regs::CopyMode::Unk2);
- ASSERT(regs.src_params.pos_x == 0);
- ASSERT(regs.src_params.pos_y == 0);
ASSERT(regs.dst_params.pos_x == 0);
ASSERT(regs.dst_params.pos_y == 0);
- if (regs.exec.is_dst_linear == regs.exec.is_src_linear) {
- std::size_t copy_size = regs.x_count;
+ if (!regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
+ // If both the source and the destination are in block layout, assert.
+ UNREACHABLE_MSG("Tiled->Tiled DMA transfers are not yet implemented");
+ return;
+ }
+ if (regs.exec.is_dst_linear && regs.exec.is_src_linear) {
// When the enable_2d bit is disabled, the copy is performed as if we were copying a 1D
- // buffer of length `x_count`, otherwise we copy a 2D buffer of size (x_count, y_count).
- if (regs.exec.enable_2d) {
- copy_size = copy_size * regs.y_count;
+ // buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
+ // y_count).
+ if (!regs.exec.enable_2d) {
+ Memory::CopyBlock(dest_cpu, source_cpu, regs.x_count);
+ return;
}
- Memory::CopyBlock(dest_cpu, source_cpu, copy_size);
+ // If both the source and the destination are in linear layout, perform a line-by-line
+ // copy. We're going to take a subrect of size (x_count, y_count) from the source
+ // rectangle. There is no need to manually flush/invalidate the regions because
+ // CopyBlock does that for us.
+ for (u32 line = 0; line < regs.y_count; ++line) {
+ const VAddr source_line = source_cpu + line * regs.src_pitch;
+ const VAddr dest_line = dest_cpu + line * regs.dst_pitch;
+ Memory::CopyBlock(dest_line, source_line, regs.x_count);
+ }
return;
}
ASSERT(regs.exec.enable_2d == 1);
- u8* src_buffer = Memory::GetPointer(source_cpu);
- u8* dst_buffer = Memory::GetPointer(dest_cpu);
+
+ const std::size_t copy_size = regs.x_count * regs.y_count;
+
+ const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) {
+ // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated
+ // copying.
+ rasterizer.FlushRegion(source_cpu, src_size);
+
+ // We have to invalidate the destination region to evict any outdated surfaces from the
+ // cache. We do this before actually writing the new data because the destination address
+ // might contain a dirty surface that will have to be written back to memory.
+ rasterizer.InvalidateRegion(dest_cpu, dst_size);
+ };
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
+ ASSERT(regs.src_params.size_z == 1);
// If the input is tiled and the output is linear, deswizzle the input and copy it over.
- Texture::CopySwizzledData(regs.src_params.size_x, regs.src_params.size_y, 1, 1, src_buffer,
- dst_buffer, true, regs.src_params.BlockHeight());
+
+ const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x;
+
+ FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y,
+ copy_size * src_bytes_per_pixel);
+
+ Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
+ regs.src_params.size_x, src_bytes_per_pixel, source_cpu, dest_cpu,
+ regs.src_params.BlockHeight(), regs.src_params.pos_x,
+ regs.src_params.pos_y);
} else {
+ ASSERT(regs.dst_params.size_z == 1);
+ ASSERT(regs.src_pitch == regs.x_count);
+
+ const u32 src_bpp = regs.src_pitch / regs.x_count;
+
+ FlushAndInvalidate(regs.src_pitch * regs.y_count,
+ regs.dst_params.size_x * regs.dst_params.size_y * src_bpp);
+
// If the input is linear and the output is tiled, swizzle the input and copy it over.
- Texture::CopySwizzledData(regs.dst_params.size_x, regs.dst_params.size_y, 1, 1, dst_buffer,
- src_buffer, false, regs.dst_params.BlockHeight());
+ Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
+ src_bpp, dest_cpu, source_cpu, regs.dst_params.BlockHeight());
}
}
-} // namespace Engines
-} // namespace Tegra
+} // namespace Tegra::Engines
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index 311ccb616..5f3704f05 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -12,11 +12,15 @@
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
+namespace VideoCore {
+class RasterizerInterface;
+}
+
namespace Tegra::Engines {
class MaxwellDMA final {
public:
- explicit MaxwellDMA(MemoryManager& memory_manager);
+ explicit MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager);
~MaxwellDMA() = default;
/// Write the value to the register identified by method.
@@ -43,6 +47,10 @@ public:
u32 BlockHeight() const {
return 1 << block_height;
}
+
+ u32 BlockDepth() const {
+ return 1 << block_depth;
+ }
};
static_assert(sizeof(Parameters) == 24, "Parameters has wrong size");
@@ -129,6 +137,8 @@ public:
MemoryManager& memory_manager;
private:
+ VideoCore::RasterizerInterface& rasterizer;
+
/// Performs the copy from the source buffer to the destination buffer as configured in the
/// registers.
void HandleCopy();
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index b1f137b9c..83a6fd875 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -5,12 +5,11 @@
#pragma once
#include <bitset>
+#include <optional>
#include <string>
#include <tuple>
#include <vector>
-#include <boost/optional.hpp>
-
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -79,6 +78,7 @@ union Attribute {
constexpr explicit Attribute(u64 value) : value(value) {}
enum class Index : u64 {
+ PointSize = 6,
Position = 7,
Attribute_0 = 8,
Attribute_31 = 39,
@@ -207,6 +207,16 @@ enum class UniformType : u64 {
Double = 5,
};
+enum class StoreType : u64 {
+ Unsigned8 = 0,
+ Signed8 = 1,
+ Unsigned16 = 2,
+ Signed16 = 3,
+ Bytes32 = 4,
+ Bytes64 = 5,
+ Bytes128 = 6,
+};
+
enum class IMinMaxExchange : u64 {
None = 0,
XLo = 1,
@@ -214,6 +224,18 @@ enum class IMinMaxExchange : u64 {
XHi = 3,
};
+enum class VideoType : u64 {
+ Size16_Low = 0,
+ Size16_High = 1,
+ Size32 = 2,
+ Invalid = 3,
+};
+
+enum class VmadShr : u64 {
+ Shr7 = 1,
+ Shr15 = 2,
+};
+
enum class XmadMode : u64 {
None = 0,
CLo = 1,
@@ -255,7 +277,7 @@ enum class ControlCode : u64 {
GTU = 12,
NEU = 13,
GEU = 14,
- //
+ T = 15,
OFF = 16,
LO = 17,
SFF = 18,
@@ -314,6 +336,35 @@ enum class TextureMiscMode : u64 {
PTP,
};
+enum class IsberdMode : u64 {
+ None = 0,
+ Patch = 1,
+ Prim = 2,
+ Attr = 3,
+};
+
+enum class IsberdShift : u64 { None = 0, U16 = 1, B32 = 2 };
+
+enum class HalfType : u64 {
+ H0_H1 = 0,
+ F32 = 1,
+ H0_H0 = 2,
+ H1_H1 = 3,
+};
+
+enum class HalfMerge : u64 {
+ H0_H1 = 0,
+ F32 = 1,
+ Mrg_H0 = 2,
+ Mrg_H1 = 3,
+};
+
+enum class HalfPrecision : u64 {
+ None = 0,
+ FTZ = 1,
+ FMZ = 2,
+};
+
enum class IpaInterpMode : u64 {
Linear = 0,
Perspective = 1,
@@ -340,6 +391,87 @@ struct IpaMode {
}
};
+enum class SystemVariable : u64 {
+ LaneId = 0x00,
+ VirtCfg = 0x02,
+ VirtId = 0x03,
+ Pm0 = 0x04,
+ Pm1 = 0x05,
+ Pm2 = 0x06,
+ Pm3 = 0x07,
+ Pm4 = 0x08,
+ Pm5 = 0x09,
+ Pm6 = 0x0a,
+ Pm7 = 0x0b,
+ OrderingTicket = 0x0f,
+ PrimType = 0x10,
+ InvocationId = 0x11,
+ Ydirection = 0x12,
+ ThreadKill = 0x13,
+ ShaderType = 0x14,
+ DirectBeWriteAddressLow = 0x15,
+ DirectBeWriteAddressHigh = 0x16,
+ DirectBeWriteEnabled = 0x17,
+ MachineId0 = 0x18,
+ MachineId1 = 0x19,
+ MachineId2 = 0x1a,
+ MachineId3 = 0x1b,
+ Affinity = 0x1c,
+ InvocationInfo = 0x1d,
+ WscaleFactorXY = 0x1e,
+ WscaleFactorZ = 0x1f,
+ Tid = 0x20,
+ TidX = 0x21,
+ TidY = 0x22,
+ TidZ = 0x23,
+ CtaParam = 0x24,
+ CtaIdX = 0x25,
+ CtaIdY = 0x26,
+ CtaIdZ = 0x27,
+ NtId = 0x28,
+ CirQueueIncrMinusOne = 0x29,
+ Nlatc = 0x2a,
+ SmSpaVersion = 0x2c,
+ MultiPassShaderInfo = 0x2d,
+ LwinHi = 0x2e,
+ SwinHi = 0x2f,
+ SwinLo = 0x30,
+ SwinSz = 0x31,
+ SmemSz = 0x32,
+ SmemBanks = 0x33,
+ LwinLo = 0x34,
+ LwinSz = 0x35,
+ LmemLosz = 0x36,
+ LmemHioff = 0x37,
+ EqMask = 0x38,
+ LtMask = 0x39,
+ LeMask = 0x3a,
+ GtMask = 0x3b,
+ GeMask = 0x3c,
+ RegAlloc = 0x3d,
+ CtxAddr = 0x3e, // .fmask = F_SM50
+ BarrierAlloc = 0x3e, // .fmask = F_SM60
+ GlobalErrorStatus = 0x40,
+ WarpErrorStatus = 0x42,
+ WarpErrorStatusClear = 0x43,
+ PmHi0 = 0x48,
+ PmHi1 = 0x49,
+ PmHi2 = 0x4a,
+ PmHi3 = 0x4b,
+ PmHi4 = 0x4c,
+ PmHi5 = 0x4d,
+ PmHi6 = 0x4e,
+ PmHi7 = 0x4f,
+ ClockLo = 0x50,
+ ClockHi = 0x51,
+ GlobalTimerLo = 0x52,
+ GlobalTimerHi = 0x53,
+ HwTaskId = 0x60,
+ CircularQueueEntryIndex = 0x61,
+ CircularQueueEntryAddressLow = 0x62,
+ CircularQueueEntryAddressHigh = 0x63,
+};
+
union Instruction {
Instruction& operator=(const Instruction& instr) {
value = instr.value;
@@ -362,6 +494,7 @@ union Instruction {
BitField<48, 16, u64> opcode;
union {
+ BitField<20, 16, u64> imm20_16;
BitField<20, 19, u64> imm20_19;
BitField<20, 32, s64> imm20_32;
BitField<45, 1, u64> negate_b;
@@ -403,6 +536,10 @@ union Instruction {
}
} lop3;
+ u16 GetImm20_16() const {
+ return static_cast<u16>(imm20_16);
+ }
+
u32 GetImm20_19() const {
u32 imm{static_cast<u32>(imm20_19)};
imm <<= 12;
@@ -437,6 +574,14 @@ union Instruction {
} fmul;
union {
+ BitField<55, 1, u64> saturate;
+ } fmul32;
+
+ union {
+ BitField<52, 1, u64> generates_cc;
+ } op_32;
+
+ union {
BitField<48, 1, u64> is_signed;
} shift;
@@ -447,6 +592,70 @@ union Instruction {
} alu_integer;
union {
+ BitField<39, 1, u64> ftz;
+ BitField<32, 1, u64> saturate;
+ BitField<49, 2, HalfMerge> merge;
+
+ BitField<43, 1, u64> negate_a;
+ BitField<44, 1, u64> abs_a;
+ BitField<47, 2, HalfType> type_a;
+
+ BitField<31, 1, u64> negate_b;
+ BitField<30, 1, u64> abs_b;
+ BitField<47, 2, HalfType> type_b;
+
+ BitField<35, 2, HalfType> type_c;
+ } alu_half;
+
+ union {
+ BitField<39, 2, HalfPrecision> precision;
+ BitField<39, 1, u64> ftz;
+ BitField<52, 1, u64> saturate;
+ BitField<49, 2, HalfMerge> merge;
+
+ BitField<43, 1, u64> negate_a;
+ BitField<44, 1, u64> abs_a;
+ BitField<47, 2, HalfType> type_a;
+ } alu_half_imm;
+
+ union {
+ BitField<29, 1, u64> first_negate;
+ BitField<20, 9, u64> first;
+
+ BitField<56, 1, u64> second_negate;
+ BitField<30, 9, u64> second;
+
+ u32 PackImmediates() const {
+ // Immediates are half floats shifted.
+ constexpr u32 imm_shift = 6;
+ return static_cast<u32>((first << imm_shift) | (second << (16 + imm_shift)));
+ }
+ } half_imm;
+
+ union {
+ union {
+ BitField<37, 2, HalfPrecision> precision;
+ BitField<32, 1, u64> saturate;
+
+ BitField<30, 1, u64> negate_c;
+ BitField<35, 2, HalfType> type_c;
+ } rr;
+
+ BitField<57, 2, HalfPrecision> precision;
+ BitField<52, 1, u64> saturate;
+
+ BitField<49, 2, HalfMerge> merge;
+
+ BitField<47, 2, HalfType> type_a;
+
+ BitField<56, 1, u64> negate_b;
+ BitField<28, 2, HalfType> type_b;
+
+ BitField<51, 1, u64> negate_c;
+ BitField<53, 2, HalfType> type_reg39;
+ } hfma2;
+
+ union {
BitField<40, 1, u64> invert;
} popc;
@@ -552,6 +761,18 @@ union Instruction {
} ld_c;
union {
+ BitField<48, 3, StoreType> type;
+ } ldst_sl;
+
+ union {
+ BitField<44, 2, u64> unknown;
+ } ld_l;
+
+ union {
+ BitField<44, 2, u64> unknown;
+ } st_l;
+
+ union {
BitField<0, 3, u64> pred0;
BitField<3, 3, u64> pred3;
BitField<7, 1, u64> abs_a;
@@ -562,7 +783,6 @@ union Instruction {
BitField<45, 2, PredOperation> op;
BitField<47, 1, u64> ftz;
BitField<48, 4, PredCondition> cond;
- BitField<56, 1, u64> neg_b;
} fsetp;
union {
@@ -589,6 +809,14 @@ union Instruction {
} psetp;
union {
+ BitField<43, 4, PredCondition> cond;
+ BitField<45, 2, PredOperation> op;
+ BitField<3, 3, u64> pred3;
+ BitField<0, 3, u64> pred0;
+ BitField<39, 3, u64> pred39;
+ } vsetp;
+
+ union {
BitField<12, 3, u64> pred12;
BitField<15, 1, u64> neg_pred12;
BitField<24, 2, PredOperation> cond;
@@ -610,6 +838,23 @@ union Instruction {
} csetp;
union {
+ BitField<35, 4, PredCondition> cond;
+ BitField<49, 1, u64> h_and;
+ BitField<6, 1, u64> ftz;
+ BitField<45, 2, PredOperation> op;
+ BitField<3, 3, u64> pred3;
+ BitField<0, 3, u64> pred0;
+ BitField<43, 1, u64> negate_a;
+ BitField<44, 1, u64> abs_a;
+ BitField<47, 2, HalfType> type_a;
+ BitField<31, 1, u64> negate_b;
+ BitField<30, 1, u64> abs_b;
+ BitField<28, 2, HalfType> type_b;
+ BitField<42, 1, u64> neg_pred;
+ BitField<39, 3, u64> pred39;
+ } hsetp2;
+
+ union {
BitField<39, 3, u64> pred39;
BitField<42, 1, u64> neg_pred;
BitField<43, 1, u64> neg_a;
@@ -620,10 +865,24 @@ union Instruction {
BitField<53, 1, u64> neg_b;
BitField<54, 1, u64> abs_a;
BitField<55, 1, u64> ftz;
- BitField<56, 1, u64> neg_imm;
} fset;
union {
+ BitField<49, 1, u64> bf;
+ BitField<35, 3, PredCondition> cond;
+ BitField<50, 1, u64> ftz;
+ BitField<45, 2, PredOperation> op;
+ BitField<43, 1, u64> negate_a;
+ BitField<44, 1, u64> abs_a;
+ BitField<47, 2, HalfType> type_a;
+ BitField<31, 1, u64> negate_b;
+ BitField<30, 1, u64> abs_b;
+ BitField<28, 2, HalfType> type_b;
+ BitField<42, 1, u64> neg_pred;
+ BitField<39, 3, u64> pred39;
+ } hset2;
+
+ union {
BitField<39, 3, u64> pred39;
BitField<42, 1, u64> neg_pred;
BitField<44, 1, u64> bf;
@@ -915,6 +1174,37 @@ union Instruction {
} bra;
union {
+ BitField<39, 1, u64> emit; // EmitVertex
+ BitField<40, 1, u64> cut; // EndPrimitive
+ } out;
+
+ union {
+ BitField<31, 1, u64> skew;
+ BitField<32, 1, u64> o;
+ BitField<33, 2, IsberdMode> mode;
+ BitField<47, 2, IsberdShift> shift;
+ } isberd;
+
+ union {
+ BitField<48, 1, u64> signed_a;
+ BitField<38, 1, u64> is_byte_chunk_a;
+ BitField<36, 2, VideoType> type_a;
+ BitField<36, 2, u64> byte_height_a;
+
+ BitField<49, 1, u64> signed_b;
+ BitField<50, 1, u64> use_register_b;
+ BitField<30, 1, u64> is_byte_chunk_b;
+ BitField<28, 2, VideoType> type_b;
+ BitField<28, 2, u64> byte_height_b;
+ } video;
+
+ union {
+ BitField<51, 2, VmadShr> shr;
+ BitField<55, 1, u64> saturate; // Saturates the result (a * b + c)
+ BitField<47, 1, u64> cc;
+ } vmad;
+
+ union {
BitField<20, 16, u64> imm20_16;
BitField<36, 1, u64> product_shift_left;
BitField<37, 1, u64> merge_37;
@@ -936,10 +1226,16 @@ union Instruction {
BitField<36, 5, u64> index;
} cbuf36;
+ // Unsure about the size of this one.
+ // It's always used with a gpr0, so any size should be fine.
+ BitField<20, 8, SystemVariable> sys20;
+
BitField<47, 1, u64> generates_cc;
BitField<61, 1, u64> is_b_imm;
BitField<60, 1, u64> is_b_gpr;
BitField<59, 1, u64> is_c_gpr;
+ BitField<20, 24, s64> smem_imm;
+ BitField<0, 5, ControlCode> flow_control_code;
Attribute attribute;
Sampler sampler;
@@ -955,14 +1251,20 @@ public:
KIL,
SSY,
SYNC,
+ BRK,
DEPBAR,
BFE_C,
BFE_R,
BFE_IMM,
BRA,
+ PBK,
LD_A,
+ LD_L,
+ LD_S,
LD_C,
ST_A,
+ ST_L,
+ ST_S,
LDG, // Load from global memory
STG, // Store in global memory
TEX,
@@ -975,6 +1277,10 @@ public:
TMML, // Texture Mip Map Level
EXIT,
IPA,
+ OUT_R, // Emit vertex/primitive
+ ISBERD,
+ VMAD,
+ VSETP,
FFMA_IMM, // Fused Multiply and Add
FFMA_CR,
FFMA_RC,
@@ -1002,6 +1308,18 @@ public:
LEA_RZ,
LEA_IMM,
LEA_HI,
+ HADD2_C,
+ HADD2_R,
+ HADD2_IMM,
+ HMUL2_C,
+ HMUL2_R,
+ HMUL2_IMM,
+ HFMA2_CR,
+ HFMA2_RC,
+ HFMA2_RR,
+ HFMA2_IMM_R,
+ HSETP2_R,
+ HSET2_R,
POPC_C,
POPC_R,
POPC_IMM,
@@ -1034,6 +1352,7 @@ public:
MOV_C,
MOV_R,
MOV_IMM,
+ MOV_SYS,
MOV32_IMM,
SHL_C,
SHL_R,
@@ -1074,9 +1393,12 @@ public:
ArithmeticImmediate,
ArithmeticInteger,
ArithmeticIntegerImmediate,
+ ArithmeticHalf,
+ ArithmeticHalfImmediate,
Bfe,
Shift,
Ffma,
+ Hfma2,
Flow,
Synch,
Memory,
@@ -1084,6 +1406,8 @@ public:
FloatSetPredicate,
IntegerSet,
IntegerSetPredicate,
+ HalfSet,
+ HalfSetPredicate,
PredicateSetPredicate,
PredicateSetRegister,
Conversion,
@@ -1095,7 +1419,7 @@ public:
/// conditionally executed).
static bool IsPredicatedInstruction(Id opcode) {
// TODO(Subv): Add the rest of unpredicated instructions.
- return opcode != Id::SSY;
+ return opcode != Id::SSY && opcode != Id::PBK;
}
class Matcher {
@@ -1136,7 +1460,7 @@ public:
Type type;
};
- static boost::optional<const Matcher&> Decode(Instruction instr) {
+ static std::optional<std::reference_wrapper<const Matcher>> Decode(Instruction instr) {
static const auto table{GetDecodeTable()};
const auto matches_instruction = [instr](const auto& matcher) {
@@ -1144,7 +1468,8 @@ public:
};
auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
- return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none;
+ return iter != table.end() ? std::optional<std::reference_wrapper<const Matcher>>(*iter)
+ : std::nullopt;
}
private:
@@ -1191,12 +1516,18 @@ private:
#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
INST("111000101001----", Id::SSY, Type::Flow, "SSY"),
+ INST("111000101010----", Id::PBK, Type::Flow, "PBK"),
INST("111000100100----", Id::BRA, Type::Flow, "BRA"),
+ INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"),
+ INST("111000110100---", Id::BRK, Type::Flow, "BRK"),
INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"),
- INST("1111000011111---", Id::SYNC, Type::Synch, "SYNC"),
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
+ INST("1110111101001---", Id::LD_S, Type::Memory, "LD_S"),
+ INST("1110111101000---", Id::LD_L, Type::Memory, "LD_L"),
INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"),
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
+ INST("1110111101011---", Id::ST_S, Type::Memory, "ST_S"),
+ INST("1110111101010---", Id::ST_L, Type::Memory, "ST_L"),
INST("1110111011010---", Id::LDG, Type::Memory, "LDG"),
INST("1110111011011---", Id::STG, Type::Memory, "STG"),
INST("110000----111---", Id::TEX, Type::Memory, "TEX"),
@@ -1209,6 +1540,10 @@ private:
INST("1101111101011---", Id::TMML, Type::Memory, "TMML"),
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
+ INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
+ INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
+ INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"),
+ INST("0101000011110---", Id::VSETP, Type::Trivial, "VSETP"),
INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
@@ -1242,6 +1577,18 @@ private:
INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"),
INST("010010111101----", Id::LEA_RZ, Type::ArithmeticInteger, "LEA_RZ"),
INST("00011000--------", Id::LEA_HI, Type::ArithmeticInteger, "LEA_HI"),
+ INST("0111101-1-------", Id::HADD2_C, Type::ArithmeticHalf, "HADD2_C"),
+ INST("0101110100010---", Id::HADD2_R, Type::ArithmeticHalf, "HADD2_R"),
+ INST("0111101-0-------", Id::HADD2_IMM, Type::ArithmeticHalfImmediate, "HADD2_IMM"),
+ INST("0111100-1-------", Id::HMUL2_C, Type::ArithmeticHalf, "HMUL2_C"),
+ INST("0101110100001---", Id::HMUL2_R, Type::ArithmeticHalf, "HMUL2_R"),
+ INST("0111100-0-------", Id::HMUL2_IMM, Type::ArithmeticHalfImmediate, "HMUL2_IMM"),
+ INST("01110---1-------", Id::HFMA2_CR, Type::Hfma2, "HFMA2_CR"),
+ INST("01100---1-------", Id::HFMA2_RC, Type::Hfma2, "HFMA2_RC"),
+ INST("0101110100000---", Id::HFMA2_RR, Type::Hfma2, "HFMA2_RR"),
+ INST("01110---0-------", Id::HFMA2_IMM_R, Type::Hfma2, "HFMA2_R_IMM"),
+ INST("0101110100100---", Id::HSETP2_R, Type::HalfSetPredicate, "HSETP_R"),
+ INST("0101110100011---", Id::HSET2_R, Type::HalfSet, "HSET2_R"),
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
INST("0100110010010---", Id::RRO_C, Type::Arithmetic, "RRO_C"),
INST("0101110010010---", Id::RRO_R, Type::Arithmetic, "RRO_R"),
@@ -1255,6 +1602,7 @@ private:
INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
+ INST("1111000011001---", Id::MOV_SYS, Type::Trivial, "MOV_SYS"),
INST("000000010000----", Id::MOV32_IMM, Type::ArithmeticImmediate, "MOV32_IMM"),
INST("0100110001100---", Id::FMNMX_C, Type::Arithmetic, "FMNMX_C"),
INST("0101110001100---", Id::FMNMX_R, Type::Arithmetic, "FMNMX_R"),
diff --git a/src/video_core/engines/shader_header.h b/src/video_core/engines/shader_header.h
index a885ee3cf..a0e015c4b 100644
--- a/src/video_core/engines/shader_header.h
+++ b/src/video_core/engines/shader_header.h
@@ -96,6 +96,11 @@ struct Header {
}
} ps;
};
+
+ u64 GetLocalMemorySize() {
+ return (common1.shader_local_memory_low_size |
+ (common2.shader_local_memory_high_size << 24));
+ }
};
static_assert(sizeof(Header) == 0x50, "Incorrect structure size");
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index baa8b63b7..83c7e5b0b 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -25,10 +25,10 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
GPU::GPU(VideoCore::RasterizerInterface& rasterizer) {
memory_manager = std::make_unique<Tegra::MemoryManager>();
maxwell_3d = std::make_unique<Engines::Maxwell3D>(rasterizer, *memory_manager);
- fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
+ fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
maxwell_compute = std::make_unique<Engines::MaxwellCompute>();
- maxwell_dma = std::make_unique<Engines::MaxwellDMA>(*memory_manager);
- kepler_memory = std::make_unique<Engines::KeplerMemory>(*memory_manager);
+ maxwell_dma = std::make_unique<Engines::MaxwellDMA>(rasterizer, *memory_manager);
+ kepler_memory = std::make_unique<Engines::KeplerMemory>(rasterizer, *memory_manager);
}
GPU::~GPU() = default;
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 377bd66ab..335a8d407 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -11,7 +11,7 @@ namespace Tegra {
MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
-void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> parameters) {
+void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) {
Reset();
registers[1] = parameters[0];
this->parameters = std::move(parameters);
@@ -19,7 +19,7 @@ void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> pa
// Execute the code until we hit an exit condition.
bool keep_executing = true;
while (keep_executing) {
- keep_executing = Step(code, false);
+ keep_executing = Step(offset, false);
}
// Assert the the macro used all the input parameters
@@ -29,7 +29,7 @@ void MacroInterpreter::Execute(const std::vector<u32>& code, std::vector<u32> pa
void MacroInterpreter::Reset() {
registers = {};
pc = 0;
- delayed_pc = boost::none;
+ delayed_pc = {};
method_address.raw = 0;
parameters.clear();
// The next parameter index starts at 1, because $r1 already has the value of the first
@@ -37,17 +37,17 @@ void MacroInterpreter::Reset() {
next_parameter_index = 1;
}
-bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
+bool MacroInterpreter::Step(u32 offset, bool is_delay_slot) {
u32 base_address = pc;
- Opcode opcode = GetOpcode(code);
+ Opcode opcode = GetOpcode(offset);
pc += 4;
// Update the program counter if we were delayed
- if (delayed_pc != boost::none) {
+ if (delayed_pc) {
ASSERT(is_delay_slot);
pc = *delayed_pc;
- delayed_pc = boost::none;
+ delayed_pc = {};
}
switch (opcode.operation) {
@@ -108,7 +108,7 @@ bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
delayed_pc = base_address + opcode.GetBranchTarget();
// Execute one more instruction due to the delay slot.
- return Step(code, true);
+ return Step(offset, true);
}
break;
}
@@ -121,17 +121,18 @@ bool MacroInterpreter::Step(const std::vector<u32>& code, bool is_delay_slot) {
// Exit has a delay slot, execute the next instruction
// Note: Executing an exit during a branch delay slot will cause the instruction at the
// branch target to be executed before exiting.
- Step(code, true);
+ Step(offset, true);
return false;
}
return true;
}
-MacroInterpreter::Opcode MacroInterpreter::GetOpcode(const std::vector<u32>& code) const {
+MacroInterpreter::Opcode MacroInterpreter::GetOpcode(u32 offset) const {
+ const auto& macro_memory{maxwell3d.GetMacroMemory()};
ASSERT((pc % sizeof(u32)) == 0);
- ASSERT(pc < code.size() * sizeof(u32));
- return {code[pc / sizeof(u32)]};
+ ASSERT((pc + offset) < macro_memory.size() * sizeof(u32));
+ return {macro_memory[offset + pc / sizeof(u32)]};
}
u32 MacroInterpreter::GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const {
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
index cee0baaf3..62d1ce289 100644
--- a/src/video_core/macro_interpreter.h
+++ b/src/video_core/macro_interpreter.h
@@ -5,8 +5,9 @@
#pragma once
#include <array>
+#include <optional>
#include <vector>
-#include <boost/optional.hpp>
+
#include "common/bit_field.h"
#include "common/common_types.h"
@@ -21,10 +22,10 @@ public:
/**
* Executes the macro code with the specified input parameters.
- * @param code The macro byte code to execute
- * @param parameters The parameters of the macro
+ * @param offset Offset to start execution at.
+ * @param parameters The parameters of the macro.
*/
- void Execute(const std::vector<u32>& code, std::vector<u32> parameters);
+ void Execute(u32 offset, std::vector<u32> parameters);
private:
enum class Operation : u32 {
@@ -109,11 +110,11 @@ private:
/**
* Executes a single macro instruction located at the current program counter. Returns whether
* the interpreter should keep running.
- * @param code The macro code to execute.
+ * @param offset Offset to start execution at.
* @param is_delay_slot Whether the current step is being executed due to a delay slot in a
* previous instruction.
*/
- bool Step(const std::vector<u32>& code, bool is_delay_slot);
+ bool Step(u32 offset, bool is_delay_slot);
/// Calculates the result of an ALU operation. src_a OP src_b;
u32 GetALUResult(ALUOperation operation, u32 src_a, u32 src_b) const;
@@ -126,7 +127,7 @@ private:
bool EvaluateBranchCondition(BranchCondition cond, u32 value) const;
/// Reads an opcode at the current program counter location.
- Opcode GetOpcode(const std::vector<u32>& code) const;
+ Opcode GetOpcode(u32 offset) const;
/// Returns the specified register's value. Register 0 is hardcoded to always return 0.
u32 GetRegister(u32 register_id) const;
@@ -149,7 +150,7 @@ private:
Engines::Maxwell3D& maxwell3d;
u32 pc; ///< Current program counter
- boost::optional<u32>
+ std::optional<u32>
delayed_pc; ///< Program counter to execute at after the delay slot is executed.
static constexpr std::size_t NumMacroRegisters = 8;
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index ca923d17d..90a8e825d 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -9,7 +9,7 @@
namespace Tegra {
GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
+ std::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, align);
ASSERT(gpu_addr);
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
@@ -34,7 +34,7 @@ GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
}
GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
- boost::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
+ std::optional<GPUVAddr> gpu_addr = FindFreeBlock(size, PAGE_SIZE);
ASSERT(gpu_addr);
for (u64 offset = 0; offset < size; offset += PAGE_SIZE) {
@@ -87,7 +87,17 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
return gpu_addr;
}
-boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
+GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
+ for (const auto& region : mapped_regions) {
+ const GPUVAddr region_end{region.gpu_addr + region.size};
+ if (region_start >= region.gpu_addr && region_start < region_end) {
+ return region_end;
+ }
+ }
+ return {};
+}
+
+std::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
GPUVAddr gpu_addr = 0;
u64 free_space = 0;
align = (align + PAGE_MASK) & ~PAGE_MASK;
@@ -108,7 +118,7 @@ boost::optional<GPUVAddr> MemoryManager::FindFreeBlock(u64 size, u64 align) {
return {};
}
-boost::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
VAddr base_addr = PageSlot(gpu_addr);
if (base_addr == static_cast<u64>(PageStatus::Allocated) ||
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index 86765e72a..b1255fd56 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -6,10 +6,9 @@
#include <array>
#include <memory>
+#include <optional>
#include <vector>
-#include <boost/optional.hpp>
-
#include "common/common_types.h"
namespace Tegra {
@@ -26,7 +25,8 @@ public:
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
- boost::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
+ GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
+ std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
static constexpr u64 PAGE_BITS = 16;
@@ -34,7 +34,7 @@ public:
static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
private:
- boost::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1);
+ std::optional<GPUVAddr> FindFreeBlock(u64 size, u64 align = 1);
bool IsPageMapped(GPUVAddr gpu_addr);
VAddr& PageSlot(GPUVAddr gpu_addr);
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
index 083b283b0..0a3b3951e 100644
--- a/src/video_core/rasterizer_cache.h
+++ b/src/video_core/rasterizer_cache.h
@@ -11,32 +11,77 @@
#include "common/common_types.h"
#include "core/core.h"
+#include "core/settings.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
+class RasterizerCacheObject {
+public:
+ /// Gets the address of the shader in guest memory, required for cache management
+ virtual VAddr GetAddr() const = 0;
+
+ /// Gets the size of the shader in guest memory, required for cache management
+ virtual std::size_t GetSizeInBytes() const = 0;
+
+ /// Wriets any cached resources back to memory
+ virtual void Flush() = 0;
+
+ /// Sets whether the cached object should be considered registered
+ void SetIsRegistered(bool registered) {
+ is_registered = registered;
+ }
+
+ /// Returns true if the cached object is registered
+ bool IsRegistered() const {
+ return is_registered;
+ }
+
+ /// Returns true if the cached object is dirty
+ bool IsDirty() const {
+ return is_dirty;
+ }
+
+ /// Returns ticks from when this cached object was last modified
+ u64 GetLastModifiedTicks() const {
+ return last_modified_ticks;
+ }
+
+ /// Marks an object as recently modified, used to specify whether it is clean or dirty
+ template <class T>
+ void MarkAsModified(bool dirty, T& cache) {
+ is_dirty = dirty;
+ last_modified_ticks = cache.GetModifiedTicks();
+ }
+
+private:
+ bool is_registered{}; ///< Whether the object is currently registered with the cache
+ bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
+ u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
+};
+
template <class T>
class RasterizerCache : NonCopyable {
+ friend class RasterizerCacheObject;
+
public:
+ /// Write any cached resources overlapping the specified region back to memory
+ void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
+ const auto& objects{GetSortedObjectsFromRegion(addr, size)};
+ for (auto& object : objects) {
+ FlushObject(object);
+ }
+ }
+
/// Mark the specified region as being invalidated
void InvalidateRegion(VAddr addr, u64 size) {
- if (size == 0)
- return;
-
- const ObjectInterval interval{addr, addr + size};
- for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
- for (auto& cached_object : pair.second) {
- if (!cached_object)
- continue;
-
- remove_objects.emplace(cached_object);
+ const auto& objects{GetSortedObjectsFromRegion(addr, size)};
+ for (auto& object : objects) {
+ if (!object->IsRegistered()) {
+ // Skip duplicates
+ continue;
}
+ Unregister(object);
}
-
- for (auto& remove_object : remove_objects) {
- Unregister(remove_object);
- }
-
- remove_objects.clear();
}
/// Invalidates everything in the cache
@@ -62,6 +107,7 @@ protected:
/// Register an object into the cache
void Register(const T& object) {
+ object->SetIsRegistered(true);
object_cache.add({GetInterval(object), ObjectSet{object}});
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
@@ -69,12 +115,57 @@ protected:
/// Unregisters an object from the cache
void Unregister(const T& object) {
+ object->SetIsRegistered(false);
auto& rasterizer = Core::System::GetInstance().Renderer().Rasterizer();
rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
+
+ // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
+ if (Settings::values.use_accurate_gpu_emulation) {
+ FlushObject(object);
+ }
+
object_cache.subtract({GetInterval(object), ObjectSet{object}});
}
+ /// Returns a ticks counter used for tracking when cached objects were last modified
+ u64 GetModifiedTicks() {
+ return ++modified_ticks;
+ }
+
private:
+ /// Returns a list of cached objects from the specified memory region, ordered by access time
+ std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
+ if (size == 0) {
+ return {};
+ }
+
+ std::vector<T> objects;
+ const ObjectInterval interval{addr, addr + size};
+ for (auto& pair : boost::make_iterator_range(object_cache.equal_range(interval))) {
+ for (auto& cached_object : pair.second) {
+ if (!cached_object) {
+ continue;
+ }
+ objects.push_back(cached_object);
+ }
+ }
+
+ std::sort(objects.begin(), objects.end(), [](const T& a, const T& b) -> bool {
+ return a->GetLastModifiedTicks() < b->GetLastModifiedTicks();
+ });
+
+ return objects;
+ }
+
+ /// Flushes the specified object, updating appropriate cache state as needed
+ void FlushObject(const T& object) {
+ if (!object->IsDirty()) {
+ return;
+ }
+ object->Flush();
+ object->MarkAsModified(false, *this);
+ }
+
using ObjectSet = std::set<T>;
using ObjectCache = boost::icl::interval_map<VAddr, ObjectSet>;
using ObjectInterval = typename ObjectCache::interval_type;
@@ -84,6 +175,6 @@ private:
object->GetAddr() + object->GetSizeInBytes());
}
- ObjectCache object_cache;
- ObjectSet remove_objects;
+ ObjectCache object_cache; ///< Cache of objects
+ u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
};
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index cd819d69f..06fc59dbe 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -5,6 +5,7 @@
#pragma once
#include "common/common_types.h"
+#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
@@ -33,13 +34,9 @@ public:
/// and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
- /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 0
- virtual bool AccelerateDisplayTransfer(const void* config) {
- return false;
- }
-
- /// Attempt to use a faster method to perform a display transfer with is_texture_copy = 1
- virtual bool AccelerateTextureCopy(const void* config) {
+ /// Attempt to use a faster method to perform a surface copy
+ virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
return false;
}
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 2cd0738ff..669e26e15 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -6,7 +6,8 @@
#include <atomic>
#include <memory>
-#include <boost/optional.hpp>
+#include <optional>
+
#include "common/common_types.h"
#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
@@ -28,7 +29,8 @@ public:
virtual ~RendererBase();
/// Swap buffers (render frame)
- virtual void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) = 0;
+ virtual void SwapBuffers(
+ std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
/// Initialize the renderer
virtual bool Init() = 0;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index c142095c5..41a54b3e7 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -17,7 +17,7 @@ OGLBufferCache::OGLBufferCache(std::size_t size) : stream_buffer(GL_ARRAY_BUFFER
GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
std::size_t alignment, bool cache) {
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
+ const std::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index 965976334..be29dc8be 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -15,15 +15,18 @@
namespace OpenGL {
-struct CachedBufferEntry final {
- VAddr GetAddr() const {
+struct CachedBufferEntry final : public RasterizerCacheObject {
+ VAddr GetAddr() const override {
return addr;
}
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return size;
}
+ // We do not have to flush this cache as things in it are never modified by us.
+ void Flush() override {}
+
VAddr addr;
std::size_t size;
GLintptr offset;
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
index ee1d9601b..741f14bc3 100644
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
@@ -45,7 +45,7 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const boost::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
+ const std::optional<VAddr> cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
const u8* source{Memory::GetPointer(*cpu_addr)};
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index a91bc6dee..a0527fe57 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -30,8 +30,8 @@
namespace OpenGL {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-using PixelFormat = SurfaceParams::PixelFormat;
-using SurfaceType = SurfaceParams::SurfaceType;
+using PixelFormat = VideoCore::Surface::PixelFormat;
+using SurfaceType = VideoCore::Surface::SurfaceType;
MICROPROFILE_DEFINE(OpenGL_VAO, "OpenGL", "Vertex Array Setup", MP_RGB(128, 128, 192));
MICROPROFILE_DEFINE(OpenGL_Shader, "OpenGL", "Shader Setup", MP_RGB(128, 128, 192));
@@ -104,7 +104,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
}
ASSERT_MSG(has_ARB_separate_shader_objects, "has_ARB_separate_shader_objects is unsupported");
-
+ OpenGLState::ApplyDefaultState();
// Clipping plane 0 is always enabled for PICA fixed clip plane z <= 0
state.clip_distance[0] = true;
@@ -115,8 +115,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, ScreenInfo
state.draw.shader_program = 0;
state.Apply();
- glEnable(GL_BLEND);
-
glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniform_buffer_alignment);
LOG_CRITICAL(Render_OpenGL, "Sync fixed function OpenGL state here!");
@@ -255,7 +253,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
return params;
}
-void RasterizerOpenGL::SetupShaders() {
+void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
@@ -270,6 +268,11 @@ void RasterizerOpenGL::SetupShaders() {
// Skip stages that are not enabled
if (!gpu.regs.IsShaderConfigEnabled(index)) {
+ switch (program) {
+ case Maxwell::ShaderProgram::Geometry:
+ shader_program_manager->UseTrivialGeometryShader();
+ break;
+ }
continue;
}
@@ -281,18 +284,26 @@ void RasterizerOpenGL::SetupShaders() {
&ubo, sizeof(ubo), static_cast<std::size_t>(uniform_buffer_alignment));
// Bind the buffer
- glBindBufferRange(GL_UNIFORM_BUFFER, stage, buffer_cache.GetHandle(), offset, sizeof(ubo));
+ glBindBufferRange(GL_UNIFORM_BUFFER, static_cast<GLuint>(stage), buffer_cache.GetHandle(),
+ offset, static_cast<GLsizeiptr>(sizeof(ubo)));
Shader shader{shader_cache.GetStageProgram(program)};
switch (program) {
case Maxwell::ShaderProgram::VertexA:
case Maxwell::ShaderProgram::VertexB: {
- shader_program_manager->UseProgrammableVertexShader(shader->GetProgramHandle());
+ shader_program_manager->UseProgrammableVertexShader(
+ shader->GetProgramHandle(primitive_mode));
+ break;
+ }
+ case Maxwell::ShaderProgram::Geometry: {
+ shader_program_manager->UseProgrammableGeometryShader(
+ shader->GetProgramHandle(primitive_mode));
break;
}
case Maxwell::ShaderProgram::Fragment: {
- shader_program_manager->UseProgrammableFragmentShader(shader->GetProgramHandle());
+ shader_program_manager->UseProgrammableFragmentShader(
+ shader->GetProgramHandle(primitive_mode));
break;
}
default:
@@ -302,12 +313,13 @@ void RasterizerOpenGL::SetupShaders() {
}
// Configure the const buffers for this shader stage.
- current_constbuffer_bindpoint = SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage),
- shader, current_constbuffer_bindpoint);
+ current_constbuffer_bindpoint =
+ SetupConstBuffers(static_cast<Maxwell::ShaderStage>(stage), shader, primitive_mode,
+ current_constbuffer_bindpoint);
// Configure the textures for this shader stage.
current_texture_bindpoint = SetupTextures(static_cast<Maxwell::ShaderStage>(stage), shader,
- current_texture_bindpoint);
+ primitive_mode, current_texture_bindpoint);
// When VertexA is enabled, we have dual vertex shaders
if (program == Maxwell::ShaderProgram::VertexA) {
@@ -317,8 +329,6 @@ void RasterizerOpenGL::SetupShaders() {
}
state.Apply();
-
- shader_program_manager->UseTrivialGeometryShader();
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
@@ -389,7 +399,7 @@ void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_depth_fb,
bool preserve_contents,
- boost::optional<std::size_t> single_color_target) {
+ std::optional<std::size_t> single_color_target) {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
@@ -406,12 +416,23 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
// Bind the framebuffer surfaces
state.draw.draw_framebuffer = framebuffer.handle;
state.Apply();
+ state.framebuffer_srgb.enabled = regs.framebuffer_srgb != 0;
if (using_color_fb) {
if (single_color_target) {
// Used when just a single color attachment is enabled, e.g. for clearing a color buffer
Surface color_surface =
res_cache.GetColorBufferSurface(*single_color_target, preserve_contents);
+
+ if (color_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even if
+ // the shader doesn't actually write to it.
+ color_surface->MarkAsModified(true, res_cache);
+ // Workaround for and issue in nvidia drivers
+ // https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
+ state.framebuffer_srgb.enabled |= color_surface->GetSurfaceParams().srgb_conversion;
+ }
+
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(*single_color_target), GL_TEXTURE_2D,
@@ -422,6 +443,18 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
std::array<GLenum, Maxwell::NumRenderTargets> buffers;
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
Surface color_surface = res_cache.GetColorBufferSurface(index, preserve_contents);
+
+ if (color_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even
+ // if the shader doesn't actually write to it.
+ color_surface->MarkAsModified(true, res_cache);
+ // Enable sRGB only for supported formats
+ // Workaround for and issue in nvidia drivers
+ // https://devtalk.nvidia.com/default/topic/776591/opengl/gl_framebuffer_srgb-functions-incorrectly/
+ state.framebuffer_srgb.enabled |=
+ color_surface->GetSurfaceParams().srgb_conversion;
+ }
+
buffers[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
@@ -441,6 +474,10 @@ void RasterizerOpenGL::ConfigureFramebuffers(bool using_color_fb, bool using_dep
}
if (depth_surface) {
+ // Assume that a surface will be written to if it is used as a framebuffer, even if
+ // the shader doesn't actually write to it.
+ depth_surface->MarkAsModified(true, res_cache);
+
if (regs.stencil_enable) {
// Attach both depth and stencil
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
@@ -507,7 +544,9 @@ void RasterizerOpenGL::Clear() {
ConfigureFramebuffers(use_color, use_depth || use_stencil, false,
regs.clear_buffers.RT.Value());
-
+ // Copy the sRGB setting to the clear state to avoid problem with
+ // specific driver implementations
+ clear_state.framebuffer_srgb.enabled = state.framebuffer_srgb.enabled;
clear_state.Apply();
if (use_color) {
@@ -540,9 +579,13 @@ void RasterizerOpenGL::DrawArrays() {
SyncBlendState();
SyncLogicOpState();
SyncCullMode();
- SyncAlphaTest();
+ SyncPrimitiveRestart();
+ SyncDepthRange();
+ SyncScissorTest();
+ // Alpha Testing is synced on shaders.
SyncTransformFeedback();
SyncPointState();
+ CheckAlphaTests();
// TODO(bunnei): Sync framebuffer_scale uniform here
// TODO(bunnei): Sync scissorbox uniform(s) here
@@ -580,7 +623,7 @@ void RasterizerOpenGL::DrawArrays() {
SetupVertexArrays();
DrawParameters params = SetupDraw();
- SetupShaders();
+ SetupShaders(params.primitive_mode);
buffer_cache.Unmap();
@@ -604,7 +647,14 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
-void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {}
+void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
+ MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+
+ if (Settings::values.use_accurate_gpu_emulation) {
+ // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
+ res_cache.FlushRegion(addr, size);
+ }
+}
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
@@ -614,17 +664,20 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
}
void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+ FlushRegion(addr, size);
InvalidateRegion(addr, size);
}
-bool RasterizerOpenGL::AccelerateDisplayTransfer(const void* config) {
+bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst) {
MICROPROFILE_SCOPE(OpenGL_Blits);
- UNREACHABLE();
- return true;
-}
-bool RasterizerOpenGL::AccelerateTextureCopy(const void* config) {
- UNREACHABLE();
+ if (Settings::values.use_accurate_gpu_emulation) {
+ // Skip the accelerated copy and perform a slow but more accurate copy
+ return false;
+ }
+
+ res_cache.FermiCopySurface(src, dst);
return true;
}
@@ -648,7 +701,8 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
// Verify that the cached surface is the same size and format as the requested framebuffer
const auto& params{surface->GetSurfaceParams()};
- const auto& pixel_format{SurfaceParams::PixelFormatFromGPUPixelFormat(config.pixel_format)};
+ const auto& pixel_format{
+ VideoCore::Surface::PixelFormatFromGPUPixelFormat(config.pixel_format)};
ASSERT_MSG(params.width == config.width, "Framebuffer width is different");
ASSERT_MSG(params.height == config.height, "Framebuffer height is different");
ASSERT_MSG(params.pixel_format == pixel_format, "Framebuffer pixel_format is different");
@@ -662,10 +716,13 @@ void RasterizerOpenGL::SamplerInfo::Create() {
sampler.Create();
mag_filter = min_filter = Tegra::Texture::TextureFilter::Linear;
wrap_u = wrap_v = wrap_p = Tegra::Texture::WrapMode::Wrap;
+ uses_depth_compare = false;
+ depth_compare_func = Tegra::Texture::DepthCompareFunc::Never;
// default is GL_LINEAR_MIPMAP_LINEAR
glSamplerParameteri(sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Other attributes have correct defaults
+ glSamplerParameteri(sampler.handle, GL_TEXTURE_COMPARE_FUNC, GL_NEVER);
}
void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntry& config) {
@@ -673,11 +730,15 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
if (mag_filter != config.mag_filter) {
mag_filter = config.mag_filter;
- glSamplerParameteri(s, GL_TEXTURE_MAG_FILTER, MaxwellToGL::TextureFilterMode(mag_filter));
+ glSamplerParameteri(
+ s, GL_TEXTURE_MAG_FILTER,
+ MaxwellToGL::TextureFilterMode(mag_filter, Tegra::Texture::TextureMipmapFilter::None));
}
- if (min_filter != config.min_filter) {
+ if (min_filter != config.min_filter || mip_filter != config.mip_filter) {
min_filter = config.min_filter;
- glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER, MaxwellToGL::TextureFilterMode(min_filter));
+ mip_filter = config.mip_filter;
+ glSamplerParameteri(s, GL_TEXTURE_MIN_FILTER,
+ MaxwellToGL::TextureFilterMode(min_filter, mip_filter));
}
if (wrap_u != config.wrap_u) {
@@ -693,6 +754,21 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
glSamplerParameteri(s, GL_TEXTURE_WRAP_R, MaxwellToGL::WrapMode(wrap_p));
}
+ if (uses_depth_compare != (config.depth_compare_enabled == 1)) {
+ uses_depth_compare = (config.depth_compare_enabled == 1);
+ if (uses_depth_compare) {
+ glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
+ } else {
+ glSamplerParameteri(s, GL_TEXTURE_COMPARE_MODE, GL_NONE);
+ }
+ }
+
+ if (depth_compare_func != config.depth_compare_func) {
+ depth_compare_func = config.depth_compare_func;
+ glSamplerParameteri(s, GL_TEXTURE_COMPARE_FUNC,
+ MaxwellToGL::DepthCompareFunc(depth_compare_func));
+ }
+
if (wrap_u == Tegra::Texture::WrapMode::Border || wrap_v == Tegra::Texture::WrapMode::Border ||
wrap_p == Tegra::Texture::WrapMode::Border) {
const GLvec4 new_border_color = {{config.border_color_r, config.border_color_g,
@@ -705,7 +781,7 @@ void RasterizerOpenGL::SamplerInfo::SyncWithConfig(const Tegra::Texture::TSCEntr
}
u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shader,
- u32 current_bindpoint) {
+ GLenum primitive_mode, u32 current_bindpoint) {
MICROPROFILE_SCOPE(OpenGL_UBO);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
@@ -757,7 +833,7 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
buffer.address, size, static_cast<std::size_t>(uniform_buffer_alignment));
// Now configure the bindpoint of the buffer inside the shader
- glUniformBlockBinding(shader->GetProgramHandle(),
+ glUniformBlockBinding(shader->GetProgramHandle(primitive_mode),
shader->GetProgramResourceIndex(used_buffer),
current_bindpoint + bindpoint);
@@ -773,7 +849,8 @@ u32 RasterizerOpenGL::SetupConstBuffers(Maxwell::ShaderStage stage, Shader& shad
return current_bindpoint + static_cast<u32>(entries.size());
}
-u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader, u32 current_unit) {
+u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
+ GLenum primitive_mode, u32 current_unit) {
MICROPROFILE_SCOPE(OpenGL_Texture);
const auto& gpu = Core::System::GetInstance().GPU();
const auto& maxwell3d = gpu.Maxwell3D();
@@ -788,8 +865,8 @@ u32 RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, Shader& shader,
// Bind the uniform to the sampler.
- glProgramUniform1i(shader->GetProgramHandle(), shader->GetUniformLocation(entry),
- current_bindpoint);
+ glProgramUniform1i(shader->GetProgramHandle(primitive_mode),
+ shader->GetUniformLocation(entry), current_bindpoint);
const auto texture = maxwell3d.GetStageTexture(entry.GetStage(), entry.GetOffset());
@@ -862,12 +939,18 @@ void RasterizerOpenGL::SyncCullMode() {
}
}
-void RasterizerOpenGL::SyncDepthScale() {
- UNREACHABLE();
+void RasterizerOpenGL::SyncPrimitiveRestart() {
+ const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+
+ state.primitive_restart.enabled = regs.primitive_restart.enabled;
+ state.primitive_restart.index = regs.primitive_restart.index;
}
-void RasterizerOpenGL::SyncDepthOffset() {
- UNREACHABLE();
+void RasterizerOpenGL::SyncDepthRange() {
+ const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+
+ state.depth.depth_range_near = regs.viewport->depth_range_near;
+ state.depth.depth_range_far = regs.viewport->depth_range_far;
}
void RasterizerOpenGL::SyncDepthTestState() {
@@ -947,14 +1030,19 @@ void RasterizerOpenGL::SyncLogicOpState() {
state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation);
}
-void RasterizerOpenGL::SyncAlphaTest() {
+void RasterizerOpenGL::SyncScissorTest() {
const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
- // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be
- // implemented with a test+discard in fragment shaders.
- if (regs.alpha_test_enabled != 0) {
- LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented");
- UNREACHABLE();
+ state.scissor.enabled = (regs.scissor_test.enable != 0);
+ // TODO(Blinkhawk): Figure if the hardware supports scissor testing per viewport and how it's
+ // implemented.
+ if (regs.scissor_test.enable != 0) {
+ const u32 width = regs.scissor_test.max_x - regs.scissor_test.min_x;
+ const u32 height = regs.scissor_test.max_y - regs.scissor_test.min_y;
+ state.scissor.x = regs.scissor_test.min_x;
+ state.scissor.y = regs.scissor_test.min_y;
+ state.scissor.width = width;
+ state.scissor.height = height;
}
}
@@ -976,4 +1064,15 @@ void RasterizerOpenGL::SyncPointState() {
state.point.size = regs.point_size == 0 ? 1 : regs.point_size;
}
+void RasterizerOpenGL::CheckAlphaTests() {
+ const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+
+ if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) {
+ LOG_CRITICAL(
+ Render_OpenGL,
+ "Alpha Testing is enabled with Multiple Render Targets, this behavior is undefined.");
+ UNREACHABLE();
+ }
+}
+
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index bf954bb5d..47097c569 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -8,12 +8,12 @@
#include <cstddef>
#include <map>
#include <memory>
+#include <optional>
#include <tuple>
#include <utility>
#include <vector>
#include <boost/icl/interval_map.hpp>
-#include <boost/optional.hpp>
#include <boost/range/iterator_range.hpp>
#include <glad/glad.h>
@@ -52,8 +52,8 @@ public:
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
- bool AccelerateDisplayTransfer(const void* config) override;
- bool AccelerateTextureCopy(const void* config) override;
+ bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst) override;
bool AccelerateFill(const void* config) override;
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
@@ -93,9 +93,12 @@ private:
private:
Tegra::Texture::TextureFilter mag_filter;
Tegra::Texture::TextureFilter min_filter;
+ Tegra::Texture::TextureMipmapFilter mip_filter;
Tegra::Texture::WrapMode wrap_u;
Tegra::Texture::WrapMode wrap_v;
Tegra::Texture::WrapMode wrap_p;
+ bool uses_depth_compare;
+ Tegra::Texture::DepthCompareFunc depth_compare_func;
GLvec4 border_color;
};
@@ -108,7 +111,7 @@ private:
*/
void ConfigureFramebuffers(bool use_color_fb = true, bool using_depth_fb = true,
bool preserve_contents = true,
- boost::optional<std::size_t> single_color_target = {});
+ std::optional<std::size_t> single_color_target = {});
/*
* Configures the current constbuffers to use for the draw command.
@@ -118,7 +121,7 @@ private:
* @returns The next available bindpoint for use in the next shader stage.
*/
u32 SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
- u32 current_bindpoint);
+ GLenum primitive_mode, u32 current_bindpoint);
/*
* Configures the current textures to use for the draw command.
@@ -128,7 +131,7 @@ private:
* @returns The next available bindpoint for use in the next shader stage.
*/
u32 SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, Shader& shader,
- u32 current_unit);
+ GLenum primitive_mode, u32 current_unit);
/// Syncs the viewport to match the guest state
void SyncViewport();
@@ -142,11 +145,11 @@ private:
/// Syncs the cull mode to match the guest state
void SyncCullMode();
- /// Syncs the depth scale to match the guest state
- void SyncDepthScale();
+ /// Syncs the primitve restart to match the guest state
+ void SyncPrimitiveRestart();
- /// Syncs the depth offset to match the guest state
- void SyncDepthOffset();
+ /// Syncs the depth range to match the guest state
+ void SyncDepthRange();
/// Syncs the depth test state to match the guest state
void SyncDepthTestState();
@@ -160,8 +163,8 @@ private:
/// Syncs the LogicOp state to match the guest state
void SyncLogicOpState();
- /// Syncs the alpha test state to match the guest state
- void SyncAlphaTest();
+ /// Syncs the scissor test state to match the guest state
+ void SyncScissorTest();
/// Syncs the transform feedback state to match the guest state
void SyncTransformFeedback();
@@ -169,6 +172,9 @@ private:
/// Syncs the point state to match the guest state
void SyncPointState();
+ /// Check asserts for alpha testing.
+ void CheckAlphaTests();
+
bool has_ARB_direct_state_access = false;
bool has_ARB_multi_bind = false;
bool has_ARB_separate_shader_objects = false;
@@ -205,7 +211,7 @@ private:
DrawParameters SetupDraw();
- void SetupShaders();
+ void SetupShaders(GLenum primitive_mode);
enum class AccelDraw { Disabled, Arrays, Indexed };
AccelDraw accelerate_draw = AccelDraw::Disabled;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index ce967c4d6..0e11557c4 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -16,15 +16,21 @@
#include "core/settings.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
+#include "video_core/renderer_opengl/utils.h"
+#include "video_core/surface.h"
#include "video_core/textures/astc.h"
#include "video_core/textures/decoders.h"
#include "video_core/utils.h"
namespace OpenGL {
-using SurfaceType = SurfaceParams::SurfaceType;
-using PixelFormat = SurfaceParams::PixelFormat;
-using ComponentType = SurfaceParams::ComponentType;
+using VideoCore::Surface::ComponentTypeFromDepthFormat;
+using VideoCore::Surface::ComponentTypeFromRenderTarget;
+using VideoCore::Surface::ComponentTypeFromTexture;
+using VideoCore::Surface::PixelFormatFromDepthFormat;
+using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
+using VideoCore::Surface::PixelFormatFromTextureFormat;
+using VideoCore::Surface::SurfaceTargetFromTextureType;
struct FormatTuple {
GLint internal_format;
@@ -34,20 +40,62 @@ struct FormatTuple {
bool compressed;
};
-static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
- auto& gpu{Core::System::GetInstance().GPU()};
- const auto cpu_addr{gpu.MemoryManager().GpuToCpuAddress(gpu_addr)};
- return cpu_addr ? *cpu_addr : 0;
+void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
+
+ addr = cpu_addr ? *cpu_addr : 0;
+ gpu_addr = gpu_addr_;
+ size_in_bytes = SizeInBytesRaw();
+
+ if (IsPixelFormatASTC(pixel_format)) {
+ // ASTC is uncompressed in software, in emulated as RGBA8
+ size_in_bytes_gl = width * height * depth * 4;
+ } else {
+ size_in_bytes_gl = SizeInBytesGL();
+ }
+}
+
+std::size_t SurfaceParams::InnerMipmapMemorySize(u32 mip_level, bool force_gl, bool layer_only,
+ bool uncompressed) const {
+ const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
+ const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
+ const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
+ u32 m_depth = (layer_only ? 1U : depth);
+ u32 m_width = MipWidth(mip_level);
+ u32 m_height = MipHeight(mip_level);
+ m_width = uncompressed ? m_width : std::max(1U, (m_width + tile_x - 1) / tile_x);
+ m_height = uncompressed ? m_height : std::max(1U, (m_height + tile_y - 1) / tile_y);
+ m_depth = std::max(1U, m_depth >> mip_level);
+ u32 m_block_height = MipBlockHeight(mip_level);
+ u32 m_block_depth = MipBlockDepth(mip_level);
+ return Tegra::Texture::CalculateSize(force_gl ? false : is_tiled, bytes_per_pixel, m_width,
+ m_height, m_depth, m_block_height, m_block_depth);
+}
+
+std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
+ bool uncompressed) const {
+ std::size_t block_size_bytes = Tegra::Texture::GetGOBSize() * block_height * block_depth;
+ std::size_t size = 0;
+ for (u32 i = 0; i < max_mip_level; i++) {
+ size += InnerMipmapMemorySize(i, force_gl, layer_only, uncompressed);
+ }
+ if (!force_gl && is_tiled) {
+ size = Common::AlignUp(size, block_size_bytes);
+ }
+ return size;
}
/*static*/ SurfaceParams SurfaceParams::CreateForTexture(
const Tegra::Texture::FullTextureInfo& config, const GLShader::SamplerEntry& entry) {
SurfaceParams params{};
- params.addr = TryGetCpuAddr(config.tic.Address());
params.is_tiled = config.tic.IsTiled();
+ params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
- params.pixel_format =
- PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value());
+ params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
+ params.srgb_conversion = config.tic.IsSrgbConversionEnabled();
+ params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(),
+ params.srgb_conversion);
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
params.type = GetFormatType(params.pixel_format);
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
@@ -85,21 +133,27 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
break;
}
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
+ params.is_layered = SurfaceTargetIsLayered(params.target);
params.max_mip_level = config.tic.max_mip_level + 1;
params.rt = {};
+ params.InitCacheParameters(config.tic.Address());
+
return params;
}
/*static*/ SurfaceParams SurfaceParams::CreateForFramebuffer(std::size_t index) {
const auto& config{Core::System::GetInstance().GPU().Maxwell3D().regs.rt[index]};
SurfaceParams params{};
- params.addr = TryGetCpuAddr(config.Address());
- params.is_tiled = true;
- params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
+
+ params.is_tiled =
+ config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
+ params.block_width = 1 << config.memory_layout.block_width;
+ params.block_height = 1 << config.memory_layout.block_height;
+ params.block_depth = 1 << config.memory_layout.block_depth;
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
+ config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
params.component_type = ComponentTypeFromRenderTarget(config.format);
params.type = GetFormatType(params.pixel_format);
params.width = config.width;
@@ -107,43 +161,76 @@ static VAddr TryGetCpuAddr(Tegra::GPUVAddr gpu_addr) {
params.unaligned_height = config.height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
- params.max_mip_level = 0;
+ params.max_mip_level = 1;
+ params.is_layered = false;
// Render target specific parameters, not used for caching
params.rt.index = static_cast<u32>(index);
params.rt.array_mode = config.array_mode;
params.rt.layer_stride = config.layer_stride;
+ params.rt.volume = config.volume;
params.rt.base_layer = config.base_layer;
+ params.InitCacheParameters(config.Address());
+
return params;
}
-/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
- Tegra::GPUVAddr zeta_address,
- Tegra::DepthFormat format) {
+/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
+ u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
- params.addr = TryGetCpuAddr(zeta_address);
- params.is_tiled = true;
- params.block_height = Tegra::Texture::TICEntry::DefaultBlockHeight;
+
+ params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
+ params.block_width = 1 << std::min(block_width, 5U);
+ params.block_height = 1 << std::min(block_height, 5U);
+ params.block_depth = 1 << std::min(block_depth, 5U);
params.pixel_format = PixelFormatFromDepthFormat(format);
params.component_type = ComponentTypeFromDepthFormat(format);
params.type = GetFormatType(params.pixel_format);
+ params.srgb_conversion = false;
params.width = zeta_width;
params.height = zeta_height;
params.unaligned_height = zeta_height;
params.target = SurfaceTarget::Texture2D;
params.depth = 1;
- params.size_in_bytes_total = params.SizeInBytesTotal();
- params.size_in_bytes_2d = params.SizeInBytes2D();
- params.max_mip_level = 0;
+ params.max_mip_level = 1;
+ params.is_layered = false;
+ params.rt = {};
+
+ params.InitCacheParameters(zeta_address);
+
+ return params;
+}
+
+/*static*/ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& config) {
+ SurfaceParams params{};
+
+ params.is_tiled = !config.linear;
+ params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
+ params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
+ params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
+ params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
+ params.srgb_conversion = config.format == Tegra::RenderTargetFormat::BGRA8_SRGB ||
+ config.format == Tegra::RenderTargetFormat::RGBA8_SRGB;
+ params.component_type = ComponentTypeFromRenderTarget(config.format);
+ params.type = GetFormatType(params.pixel_format);
+ params.width = config.width;
+ params.height = config.height;
+ params.unaligned_height = config.height;
+ params.target = SurfaceTarget::Texture2D;
+ params.depth = 1;
+ params.max_mip_level = 1;
params.rt = {};
+ params.InitCacheParameters(config.Address());
+
return params;
}
-static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_format_tuples = {{
+static constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format_tuples = {{
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // ABGR8U
{GL_RGBA8, GL_RGBA, GL_BYTE, ComponentType::SNorm, false}, // ABGR8S
{GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE, ComponentType::UInt, false}, // ABGR8UI
@@ -159,7 +246,7 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV, ComponentType::Float,
false}, // R11FG11FB10F
{GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RGBA32UI
- {GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL_RGB, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
+ {GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT1
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
true}, // DXT23
@@ -193,12 +280,31 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
{GL_RG16I, GL_RG_INTEGER, GL_SHORT, ComponentType::SInt, false}, // RG16I
{GL_RG16_SNORM, GL_RG, GL_SHORT, ComponentType::SNorm, false}, // RG16S
{GL_RGB32F, GL_RGB, GL_FLOAT, ComponentType::Float, false}, // RGB32F
- {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm, false}, // SRGBA8
- {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
- {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
- {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
- {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
- {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, ComponentType::UNorm,
+ false}, // RGBA8_SRGB
+ {GL_RG8, GL_RG, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // RG8U
+ {GL_RG8, GL_RG, GL_BYTE, ComponentType::SNorm, false}, // RG8S
+ {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // RG32UI
+ {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, ComponentType::UInt, false}, // R32UI
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4
+ {GL_SRGB8_ALPHA8, GL_BGRA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // BGRA8
+ // Compressed sRGB formats
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
+ true}, // DXT1_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
+ true}, // DXT23_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, ComponentType::UNorm,
+ true}, // DXT45_SRGB
+ {GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8,
+ ComponentType::UNorm, true}, // BC7U_SRGB
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_4X4_SRGB
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X8_SRGB
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_8X5_SRGB
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X4_SRGB
+ {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5
+ {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE, ComponentType::UNorm, false}, // ASTC_2D_5X5_SRGB
// Depth formats
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT, ComponentType::Float, false}, // Z32F
@@ -214,19 +320,19 @@ static constexpr std::array<FormatTuple, SurfaceParams::MaxPixelFormat> tex_form
ComponentType::Float, false}, // Z32FS8
}};
-static GLenum SurfaceTargetToGL(SurfaceParams::SurfaceTarget target) {
+static GLenum SurfaceTargetToGL(SurfaceTarget target) {
switch (target) {
- case SurfaceParams::SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture1D:
return GL_TEXTURE_1D;
- case SurfaceParams::SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture2D:
return GL_TEXTURE_2D;
- case SurfaceParams::SurfaceTarget::Texture3D:
+ case SurfaceTarget::Texture3D:
return GL_TEXTURE_3D;
- case SurfaceParams::SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture1DArray:
return GL_TEXTURE_1D_ARRAY;
- case SurfaceParams::SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::Texture2DArray:
return GL_TEXTURE_2D_ARRAY;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap:
return GL_TEXTURE_CUBE_MAP;
}
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture target={}", static_cast<u32>(target));
@@ -242,81 +348,44 @@ static const FormatTuple& GetFormatTuple(PixelFormat pixel_format, ComponentType
return format;
}
-static bool IsPixelFormatASTC(PixelFormat format) {
- switch (format) {
- case PixelFormat::ASTC_2D_4X4:
- case PixelFormat::ASTC_2D_8X8:
- return true;
- default:
- return false;
- }
-}
-
-static std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
- switch (format) {
- case PixelFormat::ASTC_2D_4X4:
- return {4, 4};
- case PixelFormat::ASTC_2D_8X8:
- return {8, 8};
- default:
- LOG_CRITICAL(HW_GPU, "Unhandled format: {}", static_cast<u32>(format));
- UNREACHABLE();
- }
-}
-
-MathUtil::Rectangle<u32> SurfaceParams::GetRect() const {
- u32 actual_height{unaligned_height};
+MathUtil::Rectangle<u32> SurfaceParams::GetRect(u32 mip_level) const {
+ u32 actual_height{std::max(1U, unaligned_height >> mip_level)};
if (IsPixelFormatASTC(pixel_format)) {
// ASTC formats must stop at the ATSC block size boundary
actual_height = Common::AlignDown(actual_height, GetASTCBlockSize(pixel_format).second);
}
- return {0, actual_height, width, 0};
-}
-
-/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN
-static bool IsFormatBCn(PixelFormat format) {
- switch (format) {
- case PixelFormat::DXT1:
- case PixelFormat::DXT23:
- case PixelFormat::DXT45:
- case PixelFormat::DXN1:
- case PixelFormat::DXN2SNORM:
- case PixelFormat::DXN2UNORM:
- case PixelFormat::BC7U:
- case PixelFormat::BC6H_UF16:
- case PixelFormat::BC6H_SF16:
- return true;
- }
- return false;
+ return {0, actual_height, MipWidth(mip_level), 0};
}
template <bool morton_to_gl, PixelFormat format>
-void MortonCopy(u32 stride, u32 block_height, u32 height, u8* gl_buffer, std::size_t gl_buffer_size,
- VAddr addr) {
- constexpr u32 bytes_per_pixel = SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
- constexpr u32 gl_bytes_per_pixel = CachedSurface::GetGLBytesPerPixel(format);
+void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth, u8* gl_buffer,
+ std::size_t gl_buffer_size, VAddr addr) {
+ constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
+
+ // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
+ // pixel values.
+ const u32 tile_size_x{GetDefaultBlockWidth(format)};
+ const u32 tile_size_y{GetDefaultBlockHeight(format)};
if (morton_to_gl) {
- // With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
- // pixel values.
- const u32 tile_size{IsFormatBCn(format) ? 4U : 1U};
- const std::vector<u8> data = Tegra::Texture::UnswizzleTexture(
- addr, tile_size, bytes_per_pixel, stride, height, block_height);
+ const std::vector<u8> data =
+ Tegra::Texture::UnswizzleTexture(addr, tile_size_x, tile_size_y, bytes_per_pixel,
+ stride, height, depth, block_height, block_depth);
const std::size_t size_to_copy{std::min(gl_buffer_size, data.size())};
memcpy(gl_buffer, data.data(), size_to_copy);
} else {
- // TODO(bunnei): Assumes the default rendering GOB size of 16 (128 lines). We should
- // check the configuration for this and perform more generic un/swizzle
- LOG_WARNING(Render_OpenGL, "need to use correct swizzle/GOB parameters!");
- VideoCore::MortonCopyPixels128(stride, height, bytes_per_pixel, gl_bytes_per_pixel,
- Memory::GetPointer(addr), gl_buffer, morton_to_gl);
+ Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
+ (height + tile_size_y - 1) / tile_size_y, depth,
+ bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr),
+ gl_buffer, false, block_height, block_depth);
}
}
-static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
- SurfaceParams::MaxPixelFormat>
- morton_to_gl_fns = {
- // clang-format off
+using GLConversionArray = std::array<void (*)(u32, u32, u32, u32, u32, u8*, std::size_t, VAddr),
+ VideoCore::Surface::MaxPixelFormat>;
+
+static constexpr GLConversionArray morton_to_gl_fns = {
+ // clang-format off
MortonCopy<true, PixelFormat::ABGR8U>,
MortonCopy<true, PixelFormat::ABGR8S>,
MortonCopy<true, PixelFormat::ABGR8UI>,
@@ -357,24 +426,35 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<true, PixelFormat::RG16I>,
MortonCopy<true, PixelFormat::RG16S>,
MortonCopy<true, PixelFormat::RGB32F>,
- MortonCopy<true, PixelFormat::SRGBA8>,
+ MortonCopy<true, PixelFormat::RGBA8_SRGB>,
MortonCopy<true, PixelFormat::RG8U>,
MortonCopy<true, PixelFormat::RG8S>,
MortonCopy<true, PixelFormat::RG32UI>,
MortonCopy<true, PixelFormat::R32UI>,
MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
+ MortonCopy<true, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<true, PixelFormat::DXT1_SRGB>,
+ MortonCopy<true, PixelFormat::DXT23_SRGB>,
+ MortonCopy<true, PixelFormat::DXT45_SRGB>,
+ MortonCopy<true, PixelFormat::BC7U_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
MortonCopy<true, PixelFormat::Z32F>,
MortonCopy<true, PixelFormat::Z16>,
MortonCopy<true, PixelFormat::Z24S8>,
MortonCopy<true, PixelFormat::S8Z24>,
MortonCopy<true, PixelFormat::Z32FS8>,
- // clang-format on
+ // clang-format on
};
-static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
- SurfaceParams::MaxPixelFormat>
- gl_to_morton_fns = {
- // clang-format off
+static constexpr GLConversionArray gl_to_morton_fns = {
+ // clang-format off
MortonCopy<false, PixelFormat::ABGR8U>,
MortonCopy<false, PixelFormat::ABGR8S>,
MortonCopy<false, PixelFormat::ABGR8UI>,
@@ -388,17 +468,16 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<false, PixelFormat::RGBA16UI>,
MortonCopy<false, PixelFormat::R11FG11FB10F>,
MortonCopy<false, PixelFormat::RGBA32UI>,
- // TODO(Subv): Swizzling DXT1/DXT23/DXT45/DXN1/DXN2/BC7U/BC6H_UF16/BC6H_SF16/ASTC_2D_4X4
- // formats are not supported
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
+ MortonCopy<false, PixelFormat::DXT1>,
+ MortonCopy<false, PixelFormat::DXT23>,
+ MortonCopy<false, PixelFormat::DXT45>,
+ MortonCopy<false, PixelFormat::DXN1>,
+ MortonCopy<false, PixelFormat::DXN2UNORM>,
+ MortonCopy<false, PixelFormat::DXN2SNORM>,
+ MortonCopy<false, PixelFormat::BC7U>,
+ MortonCopy<false, PixelFormat::BC6H_UF16>,
+ MortonCopy<false, PixelFormat::BC6H_SF16>,
+ // TODO(Subv): Swizzling ASTC formats are not supported
nullptr,
MortonCopy<false, PixelFormat::G8R8U>,
MortonCopy<false, PixelFormat::G8R8S>,
@@ -417,20 +496,62 @@ static constexpr std::array<void (*)(u32, u32, u32, u8*, std::size_t, VAddr),
MortonCopy<false, PixelFormat::RG16I>,
MortonCopy<false, PixelFormat::RG16S>,
MortonCopy<false, PixelFormat::RGB32F>,
- MortonCopy<false, PixelFormat::SRGBA8>,
+ MortonCopy<false, PixelFormat::RGBA8_SRGB>,
MortonCopy<false, PixelFormat::RG8U>,
MortonCopy<false, PixelFormat::RG8S>,
MortonCopy<false, PixelFormat::RG32UI>,
MortonCopy<false, PixelFormat::R32UI>,
nullptr,
+ nullptr,
+ nullptr,
+ MortonCopy<false, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<false, PixelFormat::DXT1_SRGB>,
+ MortonCopy<false, PixelFormat::DXT23_SRGB>,
+ MortonCopy<false, PixelFormat::DXT45_SRGB>,
+ MortonCopy<false, PixelFormat::BC7U_SRGB>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
MortonCopy<false, PixelFormat::Z32F>,
MortonCopy<false, PixelFormat::Z16>,
MortonCopy<false, PixelFormat::Z24S8>,
MortonCopy<false, PixelFormat::S8Z24>,
MortonCopy<false, PixelFormat::Z32FS8>,
- // clang-format on
+ // clang-format on
};
+void SwizzleFunc(const GLConversionArray& functions, const SurfaceParams& params,
+ std::vector<u8>& gl_buffer, u32 mip_level) {
+ u32 depth = params.MipDepth(mip_level);
+ if (params.target == SurfaceTarget::Texture2D) {
+ // TODO(Blinkhawk): Eliminate this condition once all texture types are implemented.
+ depth = 1U;
+ }
+ if (params.is_layered) {
+ u64 offset = params.GetMipmapLevelOffset(mip_level);
+ u64 offset_gl = 0;
+ u64 layer_size = params.LayerMemorySize();
+ u64 gl_size = params.LayerSizeGL(mip_level);
+ for (u32 i = 0; i < params.depth; i++) {
+ functions[static_cast<std::size_t>(params.pixel_format)](
+ params.MipWidth(mip_level), params.MipBlockHeight(mip_level),
+ params.MipHeight(mip_level), params.MipBlockDepth(mip_level), 1,
+ gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
+ offset += layer_size;
+ offset_gl += gl_size;
+ }
+ } else {
+ u64 offset = params.GetMipmapLevelOffset(mip_level);
+ functions[static_cast<std::size_t>(params.pixel_format)](
+ params.MipWidth(mip_level), params.MipBlockHeight(mip_level),
+ params.MipHeight(mip_level), params.MipBlockDepth(mip_level), depth, gl_buffer.data(),
+ gl_buffer.size(), params.addr + offset);
+ }
+}
+
static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
GLuint read_fb_handle, GLuint draw_fb_handle, GLenum src_attachment = 0,
GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
@@ -444,19 +565,21 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
OpenGLState state;
state.draw.read_framebuffer = read_fb_handle;
state.draw.draw_framebuffer = draw_fb_handle;
+ // Set sRGB enabled if the destination surfaces need it
+ state.framebuffer_srgb.enabled = dst_params.srgb_conversion;
state.Apply();
u32 buffers{};
if (src_params.type == SurfaceType::ColorTexture) {
switch (src_params.target) {
- case SurfaceParams::SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture2D:
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment,
GL_TEXTURE_2D, src_surface->Texture().handle, 0);
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap:
glFramebufferTexture2D(
GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment,
static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face),
@@ -465,12 +588,12 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0);
break;
- case SurfaceParams::SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::Texture2DArray:
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment,
src_surface->Texture().handle, 0, 0);
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0);
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
+ case SurfaceTarget::Texture3D:
glFramebufferTexture3D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + src_attachment,
SurfaceTargetToGL(src_params.target),
src_surface->Texture().handle, 0, 0);
@@ -486,13 +609,13 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
}
switch (dst_params.target) {
- case SurfaceParams::SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture2D:
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment,
GL_TEXTURE_2D, dst_surface->Texture().handle, 0);
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
0, 0);
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap:
glFramebufferTexture2D(
GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment,
static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face),
@@ -501,13 +624,13 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + cubemap_face), 0, 0);
break;
- case SurfaceParams::SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::Texture2DArray:
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment,
dst_surface->Texture().handle, 0, 0);
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, 0, 0, 0);
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
+ case SurfaceTarget::Texture3D:
glFramebufferTexture3D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + dst_attachment,
SurfaceTargetToGL(dst_params.target),
dst_surface->Texture().handle, 0, 0);
@@ -559,6 +682,18 @@ static bool BlitSurface(const Surface& src_surface, const Surface& dst_surface,
return true;
}
+static void FastCopySurface(const Surface& src_surface, const Surface& dst_surface) {
+ const auto& src_params{src_surface->GetSurfaceParams()};
+ const auto& dst_params{dst_surface->GetSurfaceParams()};
+
+ const u32 width{std::min(src_params.width, dst_params.width)};
+ const u32 height{std::min(src_params.height, dst_params.height)};
+
+ glCopyImageSubData(src_surface->Texture().handle, SurfaceTargetToGL(src_params.target), 0, 0, 0,
+ 0, dst_surface->Texture().handle, SurfaceTargetToGL(dst_params.target), 0, 0,
+ 0, 0, width, height, 1);
+}
+
static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
GLuint copy_pbo_handle, GLenum src_attachment = 0,
GLenum dst_attachment = 0, std::size_t cubemap_face = 0) {
@@ -570,22 +705,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
auto source_format = GetFormatTuple(src_params.pixel_format, src_params.component_type);
auto dest_format = GetFormatTuple(dst_params.pixel_format, dst_params.component_type);
- std::size_t buffer_size =
- std::max(src_params.size_in_bytes_total, dst_params.size_in_bytes_total);
+ std::size_t buffer_size = std::max(src_params.size_in_bytes, dst_params.size_in_bytes);
glBindBuffer(GL_PIXEL_PACK_BUFFER, copy_pbo_handle);
glBufferData(GL_PIXEL_PACK_BUFFER, buffer_size, nullptr, GL_STREAM_DRAW_ARB);
if (source_format.compressed) {
glGetCompressedTextureImage(src_surface->Texture().handle, src_attachment,
- static_cast<GLsizei>(src_params.size_in_bytes_total), nullptr);
+ static_cast<GLsizei>(src_params.size_in_bytes), nullptr);
} else {
glGetTextureImage(src_surface->Texture().handle, src_attachment, source_format.format,
- source_format.type, static_cast<GLsizei>(src_params.size_in_bytes_total),
+ source_format.type, static_cast<GLsizei>(src_params.size_in_bytes),
nullptr);
}
// If the new texture is bigger than the previous one, we need to fill in the rest with data
// from the CPU.
- if (src_params.size_in_bytes_total < dst_params.size_in_bytes_total) {
+ if (src_params.size_in_bytes < dst_params.size_in_bytes) {
// Upload the rest of the memory.
if (dst_params.is_tiled) {
// TODO(Subv): We might have to de-tile the subtexture and re-tile it with the rest
@@ -595,12 +729,12 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
LOG_DEBUG(HW_GPU, "Trying to upload extra texture data from the CPU during "
"reinterpretation but the texture is tiled.");
}
- std::size_t remaining_size =
- dst_params.size_in_bytes_total - src_params.size_in_bytes_total;
+ std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
std::vector<u8> data(remaining_size);
- Memory::ReadBlock(dst_params.addr + src_params.size_in_bytes_total, data.data(),
- data.size());
- glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes_total, remaining_size,
+ std::memcpy(data.data(), Memory::GetPointer(dst_params.addr + src_params.size_in_bytes),
+ data.size());
+
+ glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
data.data());
}
@@ -617,21 +751,21 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
UNREACHABLE();
} else {
switch (dst_params.target) {
- case SurfaceParams::SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture1D:
glTextureSubImage1D(dst_surface->Texture().handle, 0, 0, width, dest_format.format,
dest_format.type, nullptr);
break;
- case SurfaceParams::SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture2D:
glTextureSubImage2D(dst_surface->Texture().handle, 0, 0, 0, width, height,
dest_format.format, dest_format.type, nullptr);
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
- case SurfaceParams::SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::Texture3D:
+ case SurfaceTarget::Texture2DArray:
glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0, 0, width, height,
static_cast<GLsizei>(dst_params.depth), dest_format.format,
dest_format.type, nullptr);
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap:
glTextureSubImage3D(dst_surface->Texture().handle, 0, 0, 0,
static_cast<GLint>(cubemap_face), width, height, 1,
dest_format.format, dest_format.type, nullptr);
@@ -646,7 +780,8 @@ static void CopySurface(const Surface& src_surface, const Surface& dst_surface,
}
CachedSurface::CachedSurface(const SurfaceParams& params)
- : params(params), gl_target(SurfaceTargetToGL(params.target)) {
+ : params(params), gl_target(SurfaceTargetToGL(params.target)),
+ cached_size_in_bytes(params.size_in_bytes) {
texture.Create();
const auto& rect{params.GetRect()};
@@ -667,38 +802,57 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
if (!format_tuple.compressed) {
// Only pre-create the texture for non-compressed textures.
switch (params.target) {
- case SurfaceParams::SurfaceTarget::Texture1D:
- glTexStorage1D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format,
- rect.GetWidth());
+ case SurfaceTarget::Texture1D:
+ glTexStorage1D(SurfaceTargetToGL(params.target), params.max_mip_level,
+ format_tuple.internal_format, rect.GetWidth());
break;
- case SurfaceParams::SurfaceTarget::Texture2D:
- case SurfaceParams::SurfaceTarget::TextureCubemap:
- glTexStorage2D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format,
- rect.GetWidth(), rect.GetHeight());
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::TextureCubemap:
+ glTexStorage2D(SurfaceTargetToGL(params.target), params.max_mip_level,
+ format_tuple.internal_format, rect.GetWidth(), rect.GetHeight());
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
- case SurfaceParams::SurfaceTarget::Texture2DArray:
- glTexStorage3D(SurfaceTargetToGL(params.target), 1, format_tuple.internal_format,
- rect.GetWidth(), rect.GetHeight(), params.depth);
+ case SurfaceTarget::Texture3D:
+ case SurfaceTarget::Texture2DArray:
+ glTexStorage3D(SurfaceTargetToGL(params.target), params.max_mip_level,
+ format_tuple.internal_format, rect.GetWidth(), rect.GetHeight(),
+ params.depth);
break;
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(params.target));
UNREACHABLE();
- glTexStorage2D(GL_TEXTURE_2D, 1, format_tuple.internal_format, rect.GetWidth(),
- rect.GetHeight());
+ glTexStorage2D(GL_TEXTURE_2D, params.max_mip_level, format_tuple.internal_format,
+ rect.GetWidth(), rect.GetHeight());
}
}
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(SurfaceTargetToGL(params.target), GL_TEXTURE_MAX_LEVEL,
+ params.max_mip_level - 1);
+ if (params.max_mip_level == 1) {
+ glTexParameterf(SurfaceTargetToGL(params.target), GL_TEXTURE_LOD_BIAS, 1000.0);
+ }
+
+ LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
+ SurfaceParams::SurfaceTargetName(params.target));
+
+ // Clamp size to mapped GPU memory region
+ // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
+ // R32F render buffer. We do not yet know if this is a game bug or something else, but this
+ // check is necessary to prevent flushing from overwriting unmapped memory.
- VideoCore::LabelGLObject(GL_TEXTURE, texture.handle, params.addr,
- SurfaceParams::SurfaceTargetName(params.target));
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
+ if (cached_size_in_bytes > max_size) {
+ LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
+ cached_size_in_bytes = max_size;
+ }
}
-static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
+static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height, bool reverse) {
union S8Z24 {
BitField<0, 24, u32> z24;
BitField<24, 8, u32> s8;
@@ -711,22 +865,29 @@ static void ConvertS8Z24ToZ24S8(std::vector<u8>& data, u32 width, u32 height) {
};
static_assert(sizeof(Z24S8) == 4, "Z24S8 is incorrect size");
- S8Z24 input_pixel{};
- Z24S8 output_pixel{};
- constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::S8Z24)};
+ S8Z24 s8z24_pixel{};
+ Z24S8 z24s8_pixel{};
+ constexpr auto bpp{GetBytesPerPixel(PixelFormat::S8Z24)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
- std::memcpy(&input_pixel, &data[offset], sizeof(S8Z24));
- output_pixel.s8.Assign(input_pixel.s8);
- output_pixel.z24.Assign(input_pixel.z24);
- std::memcpy(&data[offset], &output_pixel, sizeof(Z24S8));
+ if (reverse) {
+ std::memcpy(&z24s8_pixel, &data[offset], sizeof(Z24S8));
+ s8z24_pixel.s8.Assign(z24s8_pixel.s8);
+ s8z24_pixel.z24.Assign(z24s8_pixel.z24);
+ std::memcpy(&data[offset], &s8z24_pixel, sizeof(S8Z24));
+ } else {
+ std::memcpy(&s8z24_pixel, &data[offset], sizeof(S8Z24));
+ z24s8_pixel.s8.Assign(s8z24_pixel.s8);
+ z24s8_pixel.z24.Assign(s8z24_pixel.z24);
+ std::memcpy(&data[offset], &z24s8_pixel, sizeof(Z24S8));
+ }
}
}
}
static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
- constexpr auto bpp{CachedSurface::GetGLBytesPerPixel(PixelFormat::G8R8U)};
+ constexpr auto bpp{GetBytesPerPixel(PixelFormat::G8R8U)};
for (std::size_t y = 0; y < height; ++y) {
for (std::size_t x = 0; x < width; ++x) {
const std::size_t offset{bpp * (y * width + x)};
@@ -743,20 +904,29 @@ static void ConvertG8R8ToR8G8(std::vector<u8>& data, u32 width, u32 height) {
* typical desktop GPUs.
*/
static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
- u32 width, u32 height) {
+ u32 width, u32 height, u32 depth) {
switch (pixel_format) {
case PixelFormat::ASTC_2D_4X4:
- case PixelFormat::ASTC_2D_8X8: {
+ case PixelFormat::ASTC_2D_8X8:
+ case PixelFormat::ASTC_2D_8X5:
+ case PixelFormat::ASTC_2D_5X4:
+ case PixelFormat::ASTC_2D_5X5:
+ case PixelFormat::ASTC_2D_4X4_SRGB:
+ case PixelFormat::ASTC_2D_8X8_SRGB:
+ case PixelFormat::ASTC_2D_8X5_SRGB:
+ case PixelFormat::ASTC_2D_5X4_SRGB:
+ case PixelFormat::ASTC_2D_5X5_SRGB: {
// Convert ASTC pixel formats to RGBA8, as most desktop GPUs do not support ASTC.
u32 block_width{};
u32 block_height{};
std::tie(block_width, block_height) = GetASTCBlockSize(pixel_format);
- data = Tegra::Texture::ASTC::Decompress(data, width, height, block_width, block_height);
+ data =
+ Tegra::Texture::ASTC::Decompress(data, width, height, depth, block_width, block_height);
break;
}
case PixelFormat::S8Z24:
// Convert the S8Z24 depth format to Z24S8, as OpenGL does not support S8Z24.
- ConvertS8Z24ToZ24S8(data, width, height);
+ ConvertS8Z24ToZ24S8(data, width, height, false);
break;
case PixelFormat::G8R8U:
@@ -767,79 +937,98 @@ static void ConvertFormatAsNeeded_LoadGLBuffer(std::vector<u8>& data, PixelForma
}
}
+/**
+ * Helper function to perform software conversion (as needed) when flushing a buffer from OpenGL to
+ * Switch memory. This is for Maxwell pixel formats that cannot be represented as-is in OpenGL or
+ * with typical desktop GPUs.
+ */
+static void ConvertFormatAsNeeded_FlushGLBuffer(std::vector<u8>& data, PixelFormat pixel_format,
+ u32 width, u32 height) {
+ switch (pixel_format) {
+ case PixelFormat::G8R8U:
+ case PixelFormat::G8R8S:
+ case PixelFormat::ASTC_2D_4X4:
+ case PixelFormat::ASTC_2D_8X8:
+ case PixelFormat::ASTC_2D_4X4_SRGB:
+ case PixelFormat::ASTC_2D_8X8_SRGB: {
+ LOG_CRITICAL(HW_GPU, "Conversion of format {} after texture flushing is not implemented",
+ static_cast<u32>(pixel_format));
+ UNREACHABLE();
+ break;
+ }
+ case PixelFormat::S8Z24:
+ // Convert the Z24S8 depth format to S8Z24, as OpenGL does not support S8Z24.
+ ConvertS8Z24ToZ24S8(data, width, height, true);
+ break;
+ }
+}
+
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 64, 192));
void CachedSurface::LoadGLBuffer() {
- ASSERT(params.type != SurfaceType::Fill);
-
- const u8* const texture_src_data = Memory::GetPointer(params.addr);
-
- ASSERT(texture_src_data);
-
- const u32 bytes_per_pixel = GetGLBytesPerPixel(params.pixel_format);
- const u32 copy_size = params.width * params.height * bytes_per_pixel;
- const std::size_t total_size = copy_size * params.depth;
-
MICROPROFILE_SCOPE(OpenGL_SurfaceLoad);
-
+ gl_buffer.resize(params.max_mip_level);
+ for (u32 i = 0; i < params.max_mip_level; i++)
+ gl_buffer[i].resize(params.GetMipmapSizeGL(i));
if (params.is_tiled) {
- gl_buffer.resize(total_size);
-
- // TODO(bunnei): This only unswizzles and copies a 2D texture - we do not yet know how to do
- // this for 3D textures, etc.
- switch (params.target) {
- case SurfaceParams::SurfaceTarget::Texture2D:
- // Pass impl. to the fallback code below
- break;
- case SurfaceParams::SurfaceTarget::Texture2DArray:
- case SurfaceParams::SurfaceTarget::TextureCubemap:
- for (std::size_t index = 0; index < params.depth; ++index) {
- const std::size_t offset{index * copy_size};
- morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
- params.width, params.block_height, params.height, gl_buffer.data() + offset,
- copy_size, params.addr + offset);
- }
- break;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented tiled load for target={}",
- static_cast<u32>(params.target));
- UNREACHABLE();
- }
-
- morton_to_gl_fns[static_cast<std::size_t>(params.pixel_format)](
- params.width, params.block_height, params.height, gl_buffer.data(), copy_size,
- params.addr);
+ ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
+ params.block_width, static_cast<u32>(params.target));
+ for (u32 i = 0; i < params.max_mip_level; i++)
+ SwizzleFunc(morton_to_gl_fns, params, gl_buffer[i], i);
} else {
- const u8* const texture_src_data_end{texture_src_data + total_size};
- gl_buffer.assign(texture_src_data, texture_src_data_end);
+ const auto texture_src_data{Memory::GetPointer(params.addr)};
+ const auto texture_src_data_end{texture_src_data + params.size_in_bytes_gl};
+ gl_buffer[0].assign(texture_src_data, texture_src_data_end);
}
-
- ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer, params.pixel_format, params.width, params.height);
+ for (u32 i = 0; i < params.max_mip_level; i++)
+ ConvertFormatAsNeeded_LoadGLBuffer(gl_buffer[i], params.pixel_format, params.MipWidth(i),
+ params.MipHeight(i), params.MipDepth(i));
}
MICROPROFILE_DEFINE(OpenGL_SurfaceFlush, "OpenGL", "Surface Flush", MP_RGB(128, 192, 64));
void CachedSurface::FlushGLBuffer() {
- ASSERT_MSG(false, "Unimplemented");
-}
+ MICROPROFILE_SCOPE(OpenGL_SurfaceFlush);
-MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
-void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
- if (params.type == SurfaceType::Fill)
- return;
+ ASSERT_MSG(!IsPixelFormatASTC(params.pixel_format), "Unimplemented");
- MICROPROFILE_SCOPE(OpenGL_TextureUL);
+ // OpenGL temporary buffer needs to be big enough to store raw texture size
+ gl_buffer.resize(1);
+ gl_buffer[0].resize(GetSizeInBytes());
- ASSERT(gl_buffer.size() == static_cast<std::size_t>(params.width) * params.height *
- GetGLBytesPerPixel(params.pixel_format) * params.depth);
+ const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
+ // Ensure no bad interactions with GL_UNPACK_ALIGNMENT
+ ASSERT(params.width * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ glPixelStorei(GL_PACK_ROW_LENGTH, static_cast<GLint>(params.width));
+ ASSERT(!tuple.compressed);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+ glGetTextureImage(texture.handle, 0, tuple.format, tuple.type,
+ static_cast<GLsizei>(gl_buffer[0].size()), gl_buffer[0].data());
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ ConvertFormatAsNeeded_FlushGLBuffer(gl_buffer[0], params.pixel_format, params.width,
+ params.height);
+ ASSERT(params.type != SurfaceType::Fill);
+ const u8* const texture_src_data = Memory::GetPointer(params.addr);
+ ASSERT(texture_src_data);
+ if (params.is_tiled) {
+ ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
+ params.block_width, static_cast<u32>(params.target));
- const auto& rect{params.GetRect()};
+ SwizzleFunc(gl_to_morton_fns, params, gl_buffer[0], 0);
+ } else {
+ std::memcpy(Memory::GetPointer(GetAddr()), gl_buffer[0].data(), GetSizeInBytes());
+ }
+}
+
+void CachedSurface::UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle,
+ GLuint draw_fb_handle) {
+ const auto& rect{params.GetRect(mip_map)};
// Load data from memory to the surface
const GLint x0 = static_cast<GLint>(rect.left);
const GLint y0 = static_cast<GLint>(rect.bottom);
std::size_t buffer_offset =
- static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.width +
+ static_cast<std::size_t>(static_cast<std::size_t>(y0) * params.MipWidth(mip_map) +
static_cast<std::size_t>(x0)) *
- GetGLBytesPerPixel(params.pixel_format);
+ GetBytesPerPixel(params.pixel_format);
const FormatTuple& tuple = GetFormatTuple(params.pixel_format, params.component_type);
const GLuint target_tex = texture.handle;
@@ -855,88 +1044,116 @@ void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle
cur_state.Apply();
// Ensure no bad interactions with GL_UNPACK_ALIGNMENT
- ASSERT(params.width * GetGLBytesPerPixel(params.pixel_format) % 4 == 0);
- glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.width));
+ ASSERT(params.MipWidth(mip_map) * GetBytesPerPixel(params.pixel_format) % 4 == 0);
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(params.MipWidth(mip_map)));
+ GLsizei image_size = static_cast<GLsizei>(params.GetMipmapSizeGL(mip_map, false));
glActiveTexture(GL_TEXTURE0);
if (tuple.compressed) {
switch (params.target) {
- case SurfaceParams::SurfaceTarget::Texture2D:
- glCompressedTexImage2D(
- SurfaceTargetToGL(params.target), 0, tuple.internal_format,
- static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
+ case SurfaceTarget::Texture2D:
+ glCompressedTexImage2D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
+ static_cast<GLsizei>(params.MipWidth(mip_map)),
+ static_cast<GLsizei>(params.MipHeight(mip_map)), 0, image_size,
+ &gl_buffer[mip_map][buffer_offset]);
+ break;
+ case SurfaceTarget::Texture3D:
+ glCompressedTexImage3D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
+ static_cast<GLsizei>(params.MipWidth(mip_map)),
+ static_cast<GLsizei>(params.MipHeight(mip_map)),
+ static_cast<GLsizei>(params.MipDepth(mip_map)), 0, image_size,
+ &gl_buffer[mip_map][buffer_offset]);
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
- case SurfaceParams::SurfaceTarget::Texture2DArray:
- glCompressedTexImage3D(
- SurfaceTargetToGL(params.target), 0, tuple.internal_format,
- static_cast<GLsizei>(params.width), static_cast<GLsizei>(params.height),
- static_cast<GLsizei>(params.depth), 0,
- static_cast<GLsizei>(params.size_in_bytes_total), &gl_buffer[buffer_offset]);
+ case SurfaceTarget::Texture2DArray:
+ glCompressedTexImage3D(SurfaceTargetToGL(params.target), mip_map, tuple.internal_format,
+ static_cast<GLsizei>(params.MipWidth(mip_map)),
+ static_cast<GLsizei>(params.MipHeight(mip_map)),
+ static_cast<GLsizei>(params.depth), 0, image_size,
+ &gl_buffer[mip_map][buffer_offset]);
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap: {
+ GLsizei layer_size = static_cast<GLsizei>(params.LayerSizeGL(mip_map));
for (std::size_t face = 0; face < params.depth; ++face) {
glCompressedTexImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face),
- 0, tuple.internal_format, static_cast<GLsizei>(params.width),
- static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d),
- &gl_buffer[buffer_offset]);
- buffer_offset += params.size_in_bytes_2d;
+ mip_map, tuple.internal_format,
+ static_cast<GLsizei>(params.MipWidth(mip_map)),
+ static_cast<GLsizei>(params.MipHeight(mip_map)), 0,
+ layer_size, &gl_buffer[mip_map][buffer_offset]);
+ buffer_offset += layer_size;
}
break;
+ }
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(params.target));
UNREACHABLE();
- glCompressedTexImage2D(
- GL_TEXTURE_2D, 0, tuple.internal_format, static_cast<GLsizei>(params.width),
- static_cast<GLsizei>(params.height), 0,
- static_cast<GLsizei>(params.size_in_bytes_2d), &gl_buffer[buffer_offset]);
+ glCompressedTexImage2D(GL_TEXTURE_2D, mip_map, tuple.internal_format,
+ static_cast<GLsizei>(params.MipWidth(mip_map)),
+ static_cast<GLsizei>(params.MipHeight(mip_map)), 0,
+ static_cast<GLsizei>(params.size_in_bytes_gl),
+ &gl_buffer[mip_map][buffer_offset]);
}
} else {
switch (params.target) {
- case SurfaceParams::SurfaceTarget::Texture1D:
- glTexSubImage1D(SurfaceTargetToGL(params.target), 0, x0,
+ case SurfaceTarget::Texture1D:
+ glTexSubImage1D(SurfaceTargetToGL(params.target), mip_map, x0,
static_cast<GLsizei>(rect.GetWidth()), tuple.format, tuple.type,
- &gl_buffer[buffer_offset]);
+ &gl_buffer[mip_map][buffer_offset]);
break;
- case SurfaceParams::SurfaceTarget::Texture2D:
- glTexSubImage2D(SurfaceTargetToGL(params.target), 0, x0, y0,
+ case SurfaceTarget::Texture2D:
+ glTexSubImage2D(SurfaceTargetToGL(params.target), mip_map, x0, y0,
static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
- &gl_buffer[buffer_offset]);
+ &gl_buffer[mip_map][buffer_offset]);
+ break;
+ case SurfaceTarget::Texture3D:
+ glTexSubImage3D(SurfaceTargetToGL(params.target), mip_map, x0, y0, 0,
+ static_cast<GLsizei>(rect.GetWidth()),
+ static_cast<GLsizei>(rect.GetHeight()), params.MipDepth(mip_map),
+ tuple.format, tuple.type, &gl_buffer[mip_map][buffer_offset]);
break;
- case SurfaceParams::SurfaceTarget::Texture3D:
- case SurfaceParams::SurfaceTarget::Texture2DArray:
- glTexSubImage3D(SurfaceTargetToGL(params.target), 0, x0, y0, 0,
+ case SurfaceTarget::Texture2DArray:
+ glTexSubImage3D(SurfaceTargetToGL(params.target), mip_map, x0, y0, 0,
static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), params.depth, tuple.format,
- tuple.type, &gl_buffer[buffer_offset]);
+ tuple.type, &gl_buffer[mip_map][buffer_offset]);
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::TextureCubemap: {
+ std::size_t start = buffer_offset;
for (std::size_t face = 0; face < params.depth; ++face) {
- glTexSubImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, x0,
- y0, static_cast<GLsizei>(rect.GetWidth()),
+ glTexSubImage2D(static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face), mip_map,
+ x0, y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
- &gl_buffer[buffer_offset]);
- buffer_offset += params.size_in_bytes_2d;
+ &gl_buffer[mip_map][buffer_offset]);
+ buffer_offset += params.LayerSizeGL(mip_map);
}
break;
+ }
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(params.target));
UNREACHABLE();
- glTexSubImage2D(GL_TEXTURE_2D, 0, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
+ glTexSubImage2D(GL_TEXTURE_2D, mip_map, x0, y0, static_cast<GLsizei>(rect.GetWidth()),
static_cast<GLsizei>(rect.GetHeight()), tuple.format, tuple.type,
- &gl_buffer[buffer_offset]);
+ &gl_buffer[mip_map][buffer_offset]);
}
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
}
+MICROPROFILE_DEFINE(OpenGL_TextureUL, "OpenGL", "Texture Upload", MP_RGB(128, 64, 192));
+void CachedSurface::UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle) {
+ if (params.type == SurfaceType::Fill)
+ return;
+
+ MICROPROFILE_SCOPE(OpenGL_TextureUL);
+
+ for (u32 i = 0; i < params.max_mip_level; i++)
+ UploadGLMipmapTexture(i, read_fb_handle, draw_fb_handle);
+}
+
RasterizerCacheOpenGL::RasterizerCacheOpenGL() {
read_framebuffer.Create();
draw_framebuffer.Create();
@@ -955,7 +1172,9 @@ Surface RasterizerCacheOpenGL::GetDepthBufferSurface(bool preserve_contents) {
}
SurfaceParams depth_params{SurfaceParams::CreateForDepthBuffer(
- regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format)};
+ regs.zeta_width, regs.zeta_height, regs.zeta.Address(), regs.zeta.format,
+ regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
+ regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
return GetSurface(depth_params, preserve_contents);
}
@@ -981,10 +1200,7 @@ Surface RasterizerCacheOpenGL::GetColorBufferSurface(std::size_t index, bool pre
void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
surface->LoadGLBuffer();
surface->UploadGLTexture(read_framebuffer.handle, draw_framebuffer.handle);
-}
-
-void RasterizerCacheOpenGL::FlushSurface(const Surface& surface) {
- surface->FlushGLBuffer();
+ surface->MarkAsModified(false, *this);
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
@@ -1001,8 +1217,8 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
} else if (preserve_contents) {
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
- Unregister(surface);
Surface new_surface{RecreateSurface(surface, params)};
+ Unregister(surface);
Register(new_surface);
return new_surface;
} else {
@@ -1033,6 +1249,34 @@ Surface RasterizerCacheOpenGL::GetUncachedSurface(const SurfaceParams& params) {
return surface;
}
+void RasterizerCacheOpenGL::FermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst_config) {
+
+ const auto& src_params = SurfaceParams::CreateForFermiCopySurface(src_config);
+ const auto& dst_params = SurfaceParams::CreateForFermiCopySurface(dst_config);
+
+ ASSERT(src_params.width == dst_params.width);
+ ASSERT(src_params.height == dst_params.height);
+ ASSERT(src_params.pixel_format == dst_params.pixel_format);
+ ASSERT(src_params.block_height == dst_params.block_height);
+ ASSERT(src_params.is_tiled == dst_params.is_tiled);
+ ASSERT(src_params.depth == dst_params.depth);
+ ASSERT(src_params.depth == 1); // Currently, FastCopySurface only works with 2D surfaces
+ ASSERT(src_params.target == dst_params.target);
+ ASSERT(src_params.rt.index == dst_params.rt.index);
+
+ FastCopySurface(GetSurface(src_params, true), GetSurface(dst_params, false));
+}
+
+void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
+ const Surface& dst_surface) {
+ const auto& src_params{src_surface->GetSurfaceParams()};
+ const auto& dst_params{dst_surface->GetSurfaceParams()};
+ FlushRegion(src_params.addr, dst_params.MemorySize());
+ LoadSurface(dst_surface);
+}
+
Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
const SurfaceParams& new_params) {
// Verify surface is compatible for blitting
@@ -1041,57 +1285,39 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
// Get a new surface with the new parameters, and blit the previous surface to it
Surface new_surface{GetUncachedSurface(new_params)};
+ // With use_accurate_gpu_emulation enabled, do an accurate surface copy
+ if (Settings::values.use_accurate_gpu_emulation) {
+ AccurateCopySurface(old_surface, new_surface);
+ return new_surface;
+ }
+
+ // For compatible surfaces, we can just do fast glCopyImageSubData based copy
+ if (old_params.target == new_params.target && old_params.type == new_params.type &&
+ old_params.depth == new_params.depth && old_params.depth == 1 &&
+ GetFormatBpp(old_params.pixel_format) == GetFormatBpp(new_params.pixel_format)) {
+ FastCopySurface(old_surface, new_surface);
+ return new_surface;
+ }
+
// If the format is the same, just do a framebuffer blit. This is significantly faster than
// using PBOs. The is also likely less accurate, as textures will be converted rather than
- // reinterpreted. When use_accurate_framebuffers setting is enabled, perform a more accurate
+ // reinterpreted. When use_accurate_gpu_emulation setting is enabled, perform a more accurate
// surface copy, where pixels are reinterpreted as a new format (without conversion). This
// code path uses OpenGL PBOs and is quite slow.
- const bool is_blit{old_params.pixel_format == new_params.pixel_format ||
- !Settings::values.use_accurate_framebuffers};
+ const bool is_blit{old_params.pixel_format == new_params.pixel_format};
switch (new_params.target) {
- case SurfaceParams::SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture2D:
if (is_blit) {
BlitSurface(old_surface, new_surface, read_framebuffer.handle, draw_framebuffer.handle);
} else {
CopySurface(old_surface, new_surface, copy_pbo.handle);
}
break;
- case SurfaceParams::SurfaceTarget::TextureCubemap: {
- if (old_params.rt.array_mode != 1) {
- // TODO(bunnei): This is used by Breath of the Wild, I'm not sure how to implement this
- // yet (array rendering used as a cubemap texture).
- LOG_CRITICAL(HW_GPU, "Unhandled rendertarget array_mode {}", old_params.rt.array_mode);
- UNREACHABLE();
- return new_surface;
- }
-
- // This seems to be used for render-to-cubemap texture
- ASSERT_MSG(old_params.target == SurfaceParams::SurfaceTarget::Texture2D, "Unexpected");
- ASSERT_MSG(old_params.pixel_format == new_params.pixel_format, "Unexpected");
- ASSERT_MSG(old_params.rt.base_layer == 0, "Unimplemented");
-
- // TODO(bunnei): Verify the below - this stride seems to be in 32-bit words, not pixels.
- // Tested with Splatoon 2, Super Mario Odyssey, and Breath of the Wild.
- const std::size_t byte_stride{old_params.rt.layer_stride * sizeof(u32)};
-
- for (std::size_t index = 0; index < new_params.depth; ++index) {
- Surface face_surface{TryGetReservedSurface(old_params)};
- ASSERT_MSG(face_surface, "Unexpected");
-
- if (is_blit) {
- BlitSurface(face_surface, new_surface, read_framebuffer.handle,
- draw_framebuffer.handle, face_surface->GetSurfaceParams().rt.index,
- new_params.rt.index, index);
- } else {
- CopySurface(face_surface, new_surface, copy_pbo.handle,
- face_surface->GetSurfaceParams().rt.index, new_params.rt.index, index);
- }
-
- old_params.addr += byte_stride;
- }
+ case SurfaceTarget::TextureCubemap:
+ case SurfaceTarget::Texture3D:
+ AccurateCopySurface(old_surface, new_surface);
break;
- }
default:
LOG_CRITICAL(Render_OpenGL, "Unimplemented surface target={}",
static_cast<u32>(new_params.target));
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 49025a3fe..46ad37897 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -7,16 +7,20 @@
#include <array>
#include <map>
#include <memory>
+#include <string>
#include <vector>
#include "common/alignment.h"
#include "common/common_types.h"
#include "common/hash.h"
#include "common/math_util.h"
+#include "video_core/engines/fermi_2d.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
+#include "video_core/surface.h"
+#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
namespace OpenGL {
@@ -25,122 +29,12 @@ class CachedSurface;
using Surface = std::shared_ptr<CachedSurface>;
using SurfaceSurfaceRect_Tuple = std::tuple<Surface, Surface, MathUtil::Rectangle<u32>>;
-struct SurfaceParams {
- enum class PixelFormat {
- ABGR8U = 0,
- ABGR8S = 1,
- ABGR8UI = 2,
- B5G6R5U = 3,
- A2B10G10R10U = 4,
- A1B5G5R5U = 5,
- R8U = 6,
- R8UI = 7,
- RGBA16F = 8,
- RGBA16U = 9,
- RGBA16UI = 10,
- R11FG11FB10F = 11,
- RGBA32UI = 12,
- DXT1 = 13,
- DXT23 = 14,
- DXT45 = 15,
- DXN1 = 16, // This is also known as BC4
- DXN2UNORM = 17,
- DXN2SNORM = 18,
- BC7U = 19,
- BC6H_UF16 = 20,
- BC6H_SF16 = 21,
- ASTC_2D_4X4 = 22,
- G8R8U = 23,
- G8R8S = 24,
- BGRA8 = 25,
- RGBA32F = 26,
- RG32F = 27,
- R32F = 28,
- R16F = 29,
- R16U = 30,
- R16S = 31,
- R16UI = 32,
- R16I = 33,
- RG16 = 34,
- RG16F = 35,
- RG16UI = 36,
- RG16I = 37,
- RG16S = 38,
- RGB32F = 39,
- SRGBA8 = 40,
- RG8U = 41,
- RG8S = 42,
- RG32UI = 43,
- R32UI = 44,
- ASTC_2D_8X8 = 45,
-
- MaxColorFormat,
-
- // Depth formats
- Z32F = 46,
- Z16 = 47,
-
- MaxDepthFormat,
-
- // DepthStencil formats
- Z24S8 = 48,
- S8Z24 = 49,
- Z32FS8 = 50,
-
- MaxDepthStencilFormat,
-
- Max = MaxDepthStencilFormat,
- Invalid = 255,
- };
-
- static constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max);
-
- enum class ComponentType {
- Invalid = 0,
- SNorm = 1,
- UNorm = 2,
- SInt = 3,
- UInt = 4,
- Float = 5,
- };
-
- enum class SurfaceType {
- ColorTexture = 0,
- Depth = 1,
- DepthStencil = 2,
- Fill = 3,
- Invalid = 4,
- };
-
- enum class SurfaceTarget {
- Texture1D,
- Texture2D,
- Texture3D,
- Texture1DArray,
- Texture2DArray,
- TextureCubemap,
- };
-
- static SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type) {
- switch (texture_type) {
- case Tegra::Texture::TextureType::Texture1D:
- return SurfaceTarget::Texture1D;
- case Tegra::Texture::TextureType::Texture2D:
- case Tegra::Texture::TextureType::Texture2DNoMipmap:
- return SurfaceTarget::Texture2D;
- case Tegra::Texture::TextureType::TextureCubemap:
- return SurfaceTarget::TextureCubemap;
- case Tegra::Texture::TextureType::Texture1DArray:
- return SurfaceTarget::Texture1DArray;
- case Tegra::Texture::TextureType::Texture2DArray:
- return SurfaceTarget::Texture2DArray;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type));
- UNREACHABLE();
- return SurfaceTarget::Texture2D;
- }
- }
+using SurfaceTarget = VideoCore::Surface::SurfaceTarget;
+using SurfaceType = VideoCore::Surface::SurfaceType;
+using PixelFormat = VideoCore::Surface::PixelFormat;
+using ComponentType = VideoCore::Surface::ComponentType;
+struct SurfaceParams {
static std::string SurfaceTargetName(SurfaceTarget target) {
switch (target) {
case SurfaceTarget::Texture1D:
@@ -162,549 +56,123 @@ struct SurfaceParams {
}
}
- /**
- * Gets the compression factor for the specified PixelFormat. This applies to just the
- * "compressed width" and "compressed height", not the overall compression factor of a
- * compressed image. This is used for maintaining proper surface sizes for compressed
- * texture formats.
- */
- static constexpr u32 GetCompressionFactor(PixelFormat format) {
- if (format == PixelFormat::Invalid)
- return 0;
-
- constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
- 1, // ABGR8U
- 1, // ABGR8S
- 1, // ABGR8UI
- 1, // B5G6R5U
- 1, // A2B10G10R10U
- 1, // A1B5G5R5U
- 1, // R8U
- 1, // R8UI
- 1, // RGBA16F
- 1, // RGBA16U
- 1, // RGBA16UI
- 1, // R11FG11FB10F
- 1, // RGBA32UI
- 4, // DXT1
- 4, // DXT23
- 4, // DXT45
- 4, // DXN1
- 4, // DXN2UNORM
- 4, // DXN2SNORM
- 4, // BC7U
- 4, // BC6H_UF16
- 4, // BC6H_SF16
- 4, // ASTC_2D_4X4
- 1, // G8R8U
- 1, // G8R8S
- 1, // BGRA8
- 1, // RGBA32F
- 1, // RG32F
- 1, // R32F
- 1, // R16F
- 1, // R16U
- 1, // R16S
- 1, // R16UI
- 1, // R16I
- 1, // RG16
- 1, // RG16F
- 1, // RG16UI
- 1, // RG16I
- 1, // RG16S
- 1, // RGB32F
- 1, // SRGBA8
- 1, // RG8U
- 1, // RG8S
- 1, // RG32UI
- 1, // R32UI
- 4, // ASTC_2D_8X8
- 1, // Z32F
- 1, // Z16
- 1, // Z24S8
- 1, // S8Z24
- 1, // Z32FS8
- }};
-
- ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size());
- return compression_factor_table[static_cast<std::size_t>(format)];
+ u32 GetFormatBpp() const {
+ return VideoCore::Surface::GetFormatBpp(pixel_format);
}
- static constexpr u32 GetFormatBpp(PixelFormat format) {
- if (format == PixelFormat::Invalid)
- return 0;
-
- constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
- 32, // ABGR8U
- 32, // ABGR8S
- 32, // ABGR8UI
- 16, // B5G6R5U
- 32, // A2B10G10R10U
- 16, // A1B5G5R5U
- 8, // R8U
- 8, // R8UI
- 64, // RGBA16F
- 64, // RGBA16U
- 64, // RGBA16UI
- 32, // R11FG11FB10F
- 128, // RGBA32UI
- 64, // DXT1
- 128, // DXT23
- 128, // DXT45
- 64, // DXN1
- 128, // DXN2UNORM
- 128, // DXN2SNORM
- 128, // BC7U
- 128, // BC6H_UF16
- 128, // BC6H_SF16
- 32, // ASTC_2D_4X4
- 16, // G8R8U
- 16, // G8R8S
- 32, // BGRA8
- 128, // RGBA32F
- 64, // RG32F
- 32, // R32F
- 16, // R16F
- 16, // R16U
- 16, // R16S
- 16, // R16UI
- 16, // R16I
- 32, // RG16
- 32, // RG16F
- 32, // RG16UI
- 32, // RG16I
- 32, // RG16S
- 96, // RGB32F
- 32, // SRGBA8
- 16, // RG8U
- 16, // RG8S
- 64, // RG32UI
- 32, // R32UI
- 16, // ASTC_2D_8X8
- 32, // Z32F
- 16, // Z16
- 32, // Z24S8
- 32, // S8Z24
- 64, // Z32FS8
- }};
-
- ASSERT(static_cast<std::size_t>(format) < bpp_table.size());
- return bpp_table[static_cast<std::size_t>(format)];
- }
+ /// Returns the rectangle corresponding to this surface
+ MathUtil::Rectangle<u32> GetRect(u32 mip_level = 0) const;
- u32 GetFormatBpp() const {
- return GetFormatBpp(pixel_format);
- }
+ /// Returns the total size of this surface in bytes, adjusted for compression
+ std::size_t SizeInBytesRaw(bool ignore_tiled = false) const {
+ const u32 compression_factor{GetCompressionFactor(pixel_format)};
+ const u32 bytes_per_pixel{GetBytesPerPixel(pixel_format)};
+ const size_t uncompressed_size{
+ Tegra::Texture::CalculateSize((ignore_tiled ? false : is_tiled), bytes_per_pixel, width,
+ height, depth, block_height, block_depth)};
- static PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
- switch (format) {
- case Tegra::DepthFormat::S8_Z24_UNORM:
- return PixelFormat::S8Z24;
- case Tegra::DepthFormat::Z24_S8_UNORM:
- return PixelFormat::Z24S8;
- case Tegra::DepthFormat::Z32_FLOAT:
- return PixelFormat::Z32F;
- case Tegra::DepthFormat::Z16_UNORM:
- return PixelFormat::Z16;
- case Tegra::DepthFormat::Z32_S8_X24_FLOAT:
- return PixelFormat::Z32FS8;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- }
+ // Divide by compression_factor^2, as height and width are factored by this
+ return uncompressed_size / (compression_factor * compression_factor);
}
- static PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
- switch (format) {
- // TODO (Hexagon12): Converting SRGBA to RGBA is a hack and doesn't completely correct the
- // gamma.
- case Tegra::RenderTargetFormat::RGBA8_SRGB:
- case Tegra::RenderTargetFormat::RGBA8_UNORM:
- return PixelFormat::ABGR8U;
- case Tegra::RenderTargetFormat::RGBA8_SNORM:
- return PixelFormat::ABGR8S;
- case Tegra::RenderTargetFormat::RGBA8_UINT:
- return PixelFormat::ABGR8UI;
- case Tegra::RenderTargetFormat::BGRA8_SRGB:
- case Tegra::RenderTargetFormat::BGRA8_UNORM:
- return PixelFormat::BGRA8;
- case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
- return PixelFormat::A2B10G10R10U;
- case Tegra::RenderTargetFormat::RGBA16_FLOAT:
- return PixelFormat::RGBA16F;
- case Tegra::RenderTargetFormat::RGBA16_UNORM:
- return PixelFormat::RGBA16U;
- case Tegra::RenderTargetFormat::RGBA16_UINT:
- return PixelFormat::RGBA16UI;
- case Tegra::RenderTargetFormat::RGBA32_FLOAT:
- return PixelFormat::RGBA32F;
- case Tegra::RenderTargetFormat::RG32_FLOAT:
- return PixelFormat::RG32F;
- case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
- return PixelFormat::R11FG11FB10F;
- case Tegra::RenderTargetFormat::B5G6R5_UNORM:
- return PixelFormat::B5G6R5U;
- case Tegra::RenderTargetFormat::BGR5A1_UNORM:
- return PixelFormat::A1B5G5R5U;
- case Tegra::RenderTargetFormat::RGBA32_UINT:
- return PixelFormat::RGBA32UI;
- case Tegra::RenderTargetFormat::R8_UNORM:
- return PixelFormat::R8U;
- case Tegra::RenderTargetFormat::R8_UINT:
- return PixelFormat::R8UI;
- case Tegra::RenderTargetFormat::RG16_FLOAT:
- return PixelFormat::RG16F;
- case Tegra::RenderTargetFormat::RG16_UINT:
- return PixelFormat::RG16UI;
- case Tegra::RenderTargetFormat::RG16_SINT:
- return PixelFormat::RG16I;
- case Tegra::RenderTargetFormat::RG16_UNORM:
- return PixelFormat::RG16;
- case Tegra::RenderTargetFormat::RG16_SNORM:
- return PixelFormat::RG16S;
- case Tegra::RenderTargetFormat::RG8_UNORM:
- return PixelFormat::RG8U;
- case Tegra::RenderTargetFormat::RG8_SNORM:
- return PixelFormat::RG8S;
- case Tegra::RenderTargetFormat::R16_FLOAT:
- return PixelFormat::R16F;
- case Tegra::RenderTargetFormat::R16_UNORM:
- return PixelFormat::R16U;
- case Tegra::RenderTargetFormat::R16_SNORM:
- return PixelFormat::R16S;
- case Tegra::RenderTargetFormat::R16_UINT:
- return PixelFormat::R16UI;
- case Tegra::RenderTargetFormat::R16_SINT:
- return PixelFormat::R16I;
- case Tegra::RenderTargetFormat::R32_FLOAT:
- return PixelFormat::R32F;
- case Tegra::RenderTargetFormat::R32_UINT:
- return PixelFormat::R32UI;
- case Tegra::RenderTargetFormat::RG32_UINT:
- return PixelFormat::RG32UI;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- }
+ /// Returns the size of this surface as an OpenGL texture in bytes
+ std::size_t SizeInBytesGL() const {
+ return SizeInBytesRaw(true);
}
- static PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
- Tegra::Texture::ComponentType component_type) {
- // TODO(Subv): Properly implement this
- switch (format) {
- case Tegra::Texture::TextureFormat::A8R8G8B8:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::ABGR8U;
- case Tegra::Texture::ComponentType::SNORM:
- return PixelFormat::ABGR8S;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::ABGR8UI;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::B5G6R5:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::B5G6R5U;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::A2B10G10R10:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::A2B10G10R10U;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::A1B5G5R5:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::A1B5G5R5U;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R8:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::R8U;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::R8UI;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::G8R8:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::G8R8U;
- case Tegra::Texture::ComponentType::SNORM:
- return PixelFormat::G8R8S;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::RGBA16U;
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::RGBA16F;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::BF10GF11RF11:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::R11FG11FB10F;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R32_G32_B32_A32:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::RGBA32F;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::RGBA32UI;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R32_G32:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::RG32F;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::RG32UI;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R32_G32_B32:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::RGB32F;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R16:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::R16F;
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::R16U;
- case Tegra::Texture::ComponentType::SNORM:
- return PixelFormat::R16S;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::R16UI;
- case Tegra::Texture::ComponentType::SINT:
- return PixelFormat::R16I;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::R32:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::R32F;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::R32UI;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::ZF32:
- return PixelFormat::Z32F;
- case Tegra::Texture::TextureFormat::Z16:
- return PixelFormat::Z16;
- case Tegra::Texture::TextureFormat::Z24S8:
- return PixelFormat::Z24S8;
- case Tegra::Texture::TextureFormat::DXT1:
- return PixelFormat::DXT1;
- case Tegra::Texture::TextureFormat::DXT23:
- return PixelFormat::DXT23;
- case Tegra::Texture::TextureFormat::DXT45:
- return PixelFormat::DXT45;
- case Tegra::Texture::TextureFormat::DXN1:
- return PixelFormat::DXN1;
- case Tegra::Texture::TextureFormat::DXN2:
- switch (component_type) {
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::DXN2UNORM;
- case Tegra::Texture::ComponentType::SNORM:
- return PixelFormat::DXN2SNORM;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- case Tegra::Texture::TextureFormat::BC7U:
- return PixelFormat::BC7U;
- case Tegra::Texture::TextureFormat::BC6H_UF16:
- return PixelFormat::BC6H_UF16;
- case Tegra::Texture::TextureFormat::BC6H_SF16:
- return PixelFormat::BC6H_SF16;
- case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
- return PixelFormat::ASTC_2D_4X4;
- case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
- return PixelFormat::ASTC_2D_8X8;
- case Tegra::Texture::TextureFormat::R16_G16:
- switch (component_type) {
- case Tegra::Texture::ComponentType::FLOAT:
- return PixelFormat::RG16F;
- case Tegra::Texture::ComponentType::UNORM:
- return PixelFormat::RG16;
- case Tegra::Texture::ComponentType::SNORM:
- return PixelFormat::RG16S;
- case Tegra::Texture::ComponentType::UINT:
- return PixelFormat::RG16UI;
- case Tegra::Texture::ComponentType::SINT:
- return PixelFormat::RG16I;
- }
- LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}",
- static_cast<u32>(component_type));
- UNREACHABLE();
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}",
- static_cast<u32>(format), static_cast<u32>(component_type));
- UNREACHABLE();
- }
+ /// Returns the size of this surface as a cube face in bytes
+ std::size_t SizeInBytesCubeFace() const {
+ return size_in_bytes / 6;
}
- static ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
- // TODO(Subv): Implement more component types
- switch (type) {
- case Tegra::Texture::ComponentType::UNORM:
- return ComponentType::UNorm;
- case Tegra::Texture::ComponentType::FLOAT:
- return ComponentType::Float;
- case Tegra::Texture::ComponentType::SNORM:
- return ComponentType::SNorm;
- case Tegra::Texture::ComponentType::UINT:
- return ComponentType::UInt;
- case Tegra::Texture::ComponentType::SINT:
- return ComponentType::SInt;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));
- UNREACHABLE();
- }
+ /// Returns the size of this surface as an OpenGL cube face in bytes
+ std::size_t SizeInBytesCubeFaceGL() const {
+ return size_in_bytes_gl / 6;
}
- static ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
- // TODO(Subv): Implement more render targets
- switch (format) {
- case Tegra::RenderTargetFormat::RGBA8_UNORM:
- case Tegra::RenderTargetFormat::RGBA8_SRGB:
- case Tegra::RenderTargetFormat::BGRA8_UNORM:
- case Tegra::RenderTargetFormat::BGRA8_SRGB:
- case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
- case Tegra::RenderTargetFormat::R8_UNORM:
- case Tegra::RenderTargetFormat::RG16_UNORM:
- case Tegra::RenderTargetFormat::R16_UNORM:
- case Tegra::RenderTargetFormat::B5G6R5_UNORM:
- case Tegra::RenderTargetFormat::BGR5A1_UNORM:
- case Tegra::RenderTargetFormat::RG8_UNORM:
- case Tegra::RenderTargetFormat::RGBA16_UNORM:
- return ComponentType::UNorm;
- case Tegra::RenderTargetFormat::RGBA8_SNORM:
- case Tegra::RenderTargetFormat::RG16_SNORM:
- case Tegra::RenderTargetFormat::R16_SNORM:
- case Tegra::RenderTargetFormat::RG8_SNORM:
- return ComponentType::SNorm;
- case Tegra::RenderTargetFormat::RGBA16_FLOAT:
- case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
- case Tegra::RenderTargetFormat::RGBA32_FLOAT:
- case Tegra::RenderTargetFormat::RG32_FLOAT:
- case Tegra::RenderTargetFormat::RG16_FLOAT:
- case Tegra::RenderTargetFormat::R16_FLOAT:
- case Tegra::RenderTargetFormat::R32_FLOAT:
- return ComponentType::Float;
- case Tegra::RenderTargetFormat::RGBA32_UINT:
- case Tegra::RenderTargetFormat::RGBA16_UINT:
- case Tegra::RenderTargetFormat::RG16_UINT:
- case Tegra::RenderTargetFormat::R8_UINT:
- case Tegra::RenderTargetFormat::R16_UINT:
- case Tegra::RenderTargetFormat::RG32_UINT:
- case Tegra::RenderTargetFormat::R32_UINT:
- case Tegra::RenderTargetFormat::RGBA8_UINT:
- return ComponentType::UInt;
- case Tegra::RenderTargetFormat::RG16_SINT:
- case Tegra::RenderTargetFormat::R16_SINT:
- return ComponentType::SInt;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- }
+ /// Returns the exact size of memory occupied by the texture in VRAM, including mipmaps.
+ std::size_t MemorySize() const {
+ std::size_t size = InnerMemorySize(false, is_layered);
+ if (is_layered)
+ return size * depth;
+ return size;
}
- static PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
- switch (format) {
- case Tegra::FramebufferConfig::PixelFormat::ABGR8:
- return PixelFormat::ABGR8U;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- }
+ /// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including
+ /// mipmaps.
+ std::size_t LayerMemorySize() const {
+ return InnerMemorySize(false, true);
}
- static ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {
- switch (format) {
- case Tegra::DepthFormat::Z16_UNORM:
- case Tegra::DepthFormat::S8_Z24_UNORM:
- case Tegra::DepthFormat::Z24_S8_UNORM:
- return ComponentType::UNorm;
- case Tegra::DepthFormat::Z32_FLOAT:
- case Tegra::DepthFormat::Z32_S8_X24_FLOAT:
- return ComponentType::Float;
- default:
- LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
- UNREACHABLE();
- }
+ /// Returns the size of a layer of this surface in OpenGL.
+ std::size_t LayerSizeGL(u32 mip_level) const {
+ return InnerMipmapMemorySize(mip_level, true, is_layered, false);
}
- static SurfaceType GetFormatType(PixelFormat pixel_format) {
- if (static_cast<std::size_t>(pixel_format) <
- static_cast<std::size_t>(PixelFormat::MaxColorFormat)) {
- return SurfaceType::ColorTexture;
- }
+ std::size_t GetMipmapSizeGL(u32 mip_level, bool ignore_compressed = true) const {
+ std::size_t size = InnerMipmapMemorySize(mip_level, true, is_layered, ignore_compressed);
+ if (is_layered)
+ return size * depth;
+ return size;
+ }
- if (static_cast<std::size_t>(pixel_format) <
- static_cast<std::size_t>(PixelFormat::MaxDepthFormat)) {
- return SurfaceType::Depth;
- }
+ std::size_t GetMipmapLevelOffset(u32 mip_level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < mip_level; i++)
+ offset += InnerMipmapMemorySize(i, false, is_layered);
+ return offset;
+ }
- if (static_cast<std::size_t>(pixel_format) <
- static_cast<std::size_t>(PixelFormat::MaxDepthStencilFormat)) {
- return SurfaceType::DepthStencil;
- }
+ std::size_t GetMipmapLevelOffsetGL(u32 mip_level) const {
+ std::size_t offset = 0;
+ for (u32 i = 0; i < mip_level; i++)
+ offset += InnerMipmapMemorySize(i, true, is_layered);
+ return offset;
+ }
- // TODO(Subv): Implement the other formats
- ASSERT(false);
+ u32 MipWidth(u32 mip_level) const {
+ return std::max(1U, width >> mip_level);
+ }
- return SurfaceType::Invalid;
+ u32 MipHeight(u32 mip_level) const {
+ return std::max(1U, height >> mip_level);
}
- /// Returns the rectangle corresponding to this surface
- MathUtil::Rectangle<u32> GetRect() const;
+ u32 MipDepth(u32 mip_level) const {
+ return is_layered ? depth : std::max(1U, depth >> mip_level);
+ }
- /// Returns the size of this surface as a 2D texture in bytes, adjusted for compression
- std::size_t SizeInBytes2D() const {
- const u32 compression_factor{GetCompressionFactor(pixel_format)};
- ASSERT(width % compression_factor == 0);
- ASSERT(height % compression_factor == 0);
- return (width / compression_factor) * (height / compression_factor) *
- GetFormatBpp(pixel_format) / CHAR_BIT;
+ // Auto block resizing algorithm from:
+ // https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
+ u32 MipBlockHeight(u32 mip_level) const {
+ if (mip_level == 0)
+ return block_height;
+ u32 alt_height = MipHeight(mip_level);
+ u32 h = GetDefaultBlockHeight(pixel_format);
+ u32 blocks_in_y = (alt_height + h - 1) / h;
+ u32 bh = 16;
+ while (bh > 1 && blocks_in_y <= bh * 4) {
+ bh >>= 1;
+ }
+ return bh;
}
- /// Returns the total size of this surface in bytes, adjusted for compression
- std::size_t SizeInBytesTotal() const {
- return SizeInBytes2D() * depth;
+ u32 MipBlockDepth(u32 mip_level) const {
+ if (mip_level == 0)
+ return block_depth;
+ if (is_layered)
+ return 1;
+ u32 depth = MipDepth(mip_level);
+ u32 bd = 32;
+ while (bd > 1 && depth * 2 <= bd) {
+ bd >>= 1;
+ }
+ if (bd == 32) {
+ u32 bh = MipBlockHeight(mip_level);
+ if (bh >= 4)
+ return 16;
+ }
+ return bd;
}
/// Creates SurfaceParams from a texture configuration
@@ -715,9 +183,14 @@ struct SurfaceParams {
static SurfaceParams CreateForFramebuffer(std::size_t index);
/// Creates SurfaceParams for a depth buffer configuration
- static SurfaceParams CreateForDepthBuffer(u32 zeta_width, u32 zeta_height,
- Tegra::GPUVAddr zeta_address,
- Tegra::DepthFormat format);
+ static SurfaceParams CreateForDepthBuffer(
+ u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 block_width, u32 block_height, u32 block_depth,
+ Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
+
+ /// Creates SurfaceParams for a Fermi2D surface copy
+ static SurfaceParams CreateForFermiCopySurface(
+ const Tegra::Engines::Fermi2D::Regs::Surface& config);
/// Checks if surfaces are compatible for caching
bool IsCompatibleSurface(const SurfaceParams& other) const {
@@ -726,9 +199,13 @@ struct SurfaceParams {
other.depth);
}
- VAddr addr;
+ /// Initializes parameters for caching, should be called after everything has been initialized
+ void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
+
bool is_tiled;
+ u32 block_width;
u32 block_height;
+ u32 block_depth;
PixelFormat pixel_format;
ComponentType component_type;
SurfaceType type;
@@ -736,18 +213,30 @@ struct SurfaceParams {
u32 height;
u32 depth;
u32 unaligned_height;
- std::size_t size_in_bytes_total;
- std::size_t size_in_bytes_2d;
SurfaceTarget target;
u32 max_mip_level;
+ bool is_layered;
+ bool srgb_conversion;
+ // Parameters used for caching
+ VAddr addr;
+ Tegra::GPUVAddr gpu_addr;
+ std::size_t size_in_bytes;
+ std::size_t size_in_bytes_gl;
// Render target specific parameters, not used in caching
struct {
u32 index;
u32 array_mode;
+ u32 volume;
u32 layer_stride;
u32 base_layer;
} rt;
+
+private:
+ std::size_t InnerMipmapMemorySize(u32 mip_level, bool force_gl = false, bool layer_only = false,
+ bool uncompressed = false) const;
+ std::size_t InnerMemorySize(bool force_gl = false, bool layer_only = false,
+ bool uncompressed = false) const;
};
}; // namespace OpenGL
@@ -757,7 +246,8 @@ struct SurfaceReserveKey : Common::HashableStruct<OpenGL::SurfaceParams> {
static SurfaceReserveKey Create(const OpenGL::SurfaceParams& params) {
SurfaceReserveKey res;
res.state = params;
- res.state.rt = {}; // Ignore rt config in caching
+ res.state.gpu_addr = {}; // Ignore GPU vaddr in caching
+ res.state.rt = {}; // Ignore rt config in caching
return res;
}
};
@@ -772,16 +262,20 @@ struct hash<SurfaceReserveKey> {
namespace OpenGL {
-class CachedSurface final {
+class CachedSurface final : public RasterizerCacheObject {
public:
CachedSurface(const SurfaceParams& params);
- VAddr GetAddr() const {
+ VAddr GetAddr() const override {
return params.addr;
}
- std::size_t GetSizeInBytes() const {
- return params.size_in_bytes_total;
+ std::size_t GetSizeInBytes() const override {
+ return cached_size_in_bytes;
+ }
+
+ void Flush() override {
+ FlushGLBuffer();
}
const OGLTexture& Texture() const {
@@ -792,13 +286,6 @@ public:
return gl_target;
}
- static constexpr unsigned int GetGLBytesPerPixel(SurfaceParams::PixelFormat format) {
- if (format == SurfaceParams::PixelFormat::Invalid)
- return 0;
-
- return SurfaceParams::GetFormatBpp(format) / CHAR_BIT;
- }
-
const SurfaceParams& GetSurfaceParams() const {
return params;
}
@@ -811,10 +298,13 @@ public:
void UploadGLTexture(GLuint read_fb_handle, GLuint draw_fb_handle);
private:
+ void UploadGLMipmapTexture(u32 mip_map, GLuint read_fb_handle, GLuint draw_fb_handle);
+
OGLTexture texture;
- std::vector<u8> gl_buffer;
+ std::vector<std::vector<u8>> gl_buffer;
SurfaceParams params;
GLenum gl_target;
+ std::size_t cached_size_in_bytes;
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -831,12 +321,13 @@ public:
/// Get the color surface based on the framebuffer configuration and the specified render target
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
- /// Flushes the surface to Switch memory
- void FlushSurface(const Surface& surface);
-
/// Tries to find a framebuffer using on the provided CPU address
Surface TryFindFramebufferSurface(VAddr addr) const;
+ /// Copies the contents of one surface to another
+ void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
+ const Tegra::Engines::Fermi2D::Regs::Surface& dst_config);
+
private:
void LoadSurface(const Surface& surface);
Surface GetSurface(const SurfaceParams& params, bool preserve_contents = true);
@@ -853,6 +344,9 @@ private:
/// Tries to get a reserved surface for the specified parameters
Surface TryGetReservedSurface(const SurfaceParams& params);
+ /// Performs a slow but accurate surface copy, flushing to RAM and reinterpreting the data
+ void AccurateCopySurface(const Surface& src_surface, const Surface& dst_surface);
+
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
/// previously been used. This is to prevent surfaces from being constantly created and
/// destroyed when used with different surface parameters.
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 7cd8f91e4..9522fd344 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -8,6 +8,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_cache.h"
#include "video_core/renderer_opengl/gl_shader_manager.h"
+#include "video_core/renderer_opengl/utils.h"
#include "video_core/utils.h"
namespace OpenGL {
@@ -68,6 +69,10 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
program_result = GLShader::GenerateVertexShader(setup);
gl_type = GL_VERTEX_SHADER;
break;
+ case Maxwell::ShaderProgram::Geometry:
+ program_result = GLShader::GenerateGeometryShader(setup);
+ gl_type = GL_GEOMETRY_SHADER;
+ break;
case Maxwell::ShaderProgram::Fragment:
program_result = GLShader::GenerateFragmentShader(setup);
gl_type = GL_FRAGMENT_SHADER;
@@ -80,11 +85,16 @@ CachedShader::CachedShader(VAddr addr, Maxwell::ShaderProgram program_type)
entries = program_result.second;
- OGLShader shader;
- shader.Create(program_result.first.c_str(), gl_type);
- program.Create(true, shader.handle);
- SetShaderUniformBlockBindings(program.handle);
- VideoCore::LabelGLObject(GL_PROGRAM, program.handle, addr);
+ if (program_type != Maxwell::ShaderProgram::Geometry) {
+ OGLShader shader;
+ shader.Create(program_result.first.c_str(), gl_type);
+ program.Create(true, shader.handle);
+ SetShaderUniformBlockBindings(program.handle);
+ LabelGLObject(GL_PROGRAM, program.handle, addr);
+ } else {
+ // Store shader's code to lazily build it on draw
+ geometry_programs.code = program_result.first;
+ }
}
GLuint CachedShader::GetProgramResourceIndex(const GLShader::ConstBufferEntry& buffer) {
@@ -110,6 +120,21 @@ GLint CachedShader::GetUniformLocation(const GLShader::SamplerEntry& sampler) {
return search->second;
}
+GLuint CachedShader::LazyGeometryProgram(OGLProgram& target_program,
+ const std::string& glsl_topology,
+ const std::string& debug_name) {
+ if (target_program.handle != 0) {
+ return target_program.handle;
+ }
+ const std::string source{geometry_programs.code + "layout (" + glsl_topology + ") in;\n"};
+ OGLShader shader;
+ shader.Create(source.c_str(), GL_GEOMETRY_SHADER);
+ target_program.Create(true, shader.handle);
+ SetShaderUniformBlockBindings(target_program.handle);
+ LabelGLObject(GL_PROGRAM, target_program.handle, addr, debug_name);
+ return target_program.handle;
+};
+
Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
const VAddr program_addr{GetShaderAddress(program)};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 9bafe43a9..a210f1731 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -7,6 +7,7 @@
#include <map>
#include <memory>
+#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -18,28 +19,52 @@ class CachedShader;
using Shader = std::shared_ptr<CachedShader>;
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-class CachedShader final {
+class CachedShader final : public RasterizerCacheObject {
public:
CachedShader(VAddr addr, Maxwell::ShaderProgram program_type);
- /// Gets the address of the shader in guest memory, required for cache management
- VAddr GetAddr() const {
+ VAddr GetAddr() const override {
return addr;
}
- /// Gets the size of the shader in guest memory, required for cache management
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return GLShader::MAX_PROGRAM_CODE_LENGTH * sizeof(u64);
}
+ // We do not have to flush this cache as things in it are never modified by us.
+ void Flush() override {}
+
/// Gets the shader entries for the shader
const GLShader::ShaderEntries& GetShaderEntries() const {
return entries;
}
/// Gets the GL program handle for the shader
- GLuint GetProgramHandle() const {
- return program.handle;
+ GLuint GetProgramHandle(GLenum primitive_mode) {
+ if (program_type != Maxwell::ShaderProgram::Geometry) {
+ return program.handle;
+ }
+ switch (primitive_mode) {
+ case GL_POINTS:
+ return LazyGeometryProgram(geometry_programs.points, "points", "ShaderPoints");
+ case GL_LINES:
+ case GL_LINE_STRIP:
+ return LazyGeometryProgram(geometry_programs.lines, "lines", "ShaderLines");
+ case GL_LINES_ADJACENCY:
+ case GL_LINE_STRIP_ADJACENCY:
+ return LazyGeometryProgram(geometry_programs.lines_adjacency, "lines_adjacency",
+ "ShaderLinesAdjacency");
+ case GL_TRIANGLES:
+ case GL_TRIANGLE_STRIP:
+ case GL_TRIANGLE_FAN:
+ return LazyGeometryProgram(geometry_programs.triangles, "triangles", "ShaderTriangles");
+ case GL_TRIANGLES_ADJACENCY:
+ case GL_TRIANGLE_STRIP_ADJACENCY:
+ return LazyGeometryProgram(geometry_programs.triangles_adjacency, "triangles_adjacency",
+ "ShaderLines");
+ default:
+ UNREACHABLE_MSG("Unknown primitive mode.");
+ }
}
/// Gets the GL program resource location for the specified resource, caching as needed
@@ -49,12 +74,30 @@ public:
GLint GetUniformLocation(const GLShader::SamplerEntry& sampler);
private:
+ /// Generates a geometry shader or returns one that already exists.
+ GLuint LazyGeometryProgram(OGLProgram& target_program, const std::string& glsl_topology,
+ const std::string& debug_name);
+
VAddr addr;
Maxwell::ShaderProgram program_type;
GLShader::ShaderSetup setup;
GLShader::ShaderEntries entries;
+
+ // Non-geometry program.
OGLProgram program;
+ // Geometry programs. These are needed because GLSL needs an input topology but it's not
+ // declared by the hardware. Workaround this issue by generating a different shader per input
+ // topology class.
+ struct {
+ std::string code;
+ OGLProgram points;
+ OGLProgram lines;
+ OGLProgram lines_adjacency;
+ OGLProgram triangles;
+ OGLProgram triangles_adjacency;
+ } geometry_programs;
+
std::map<u32, GLuint> resource_cache;
std::map<u32, GLint> uniform_cache;
};
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 579a78702..09b003c59 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -3,9 +3,11 @@
// Refer to the license.txt file included.
#include <map>
+#include <optional>
#include <set>
#include <string>
#include <string_view>
+#include <unordered_set>
#include <fmt/format.h>
@@ -29,11 +31,30 @@ using Tegra::Shader::SubOp;
constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
constexpr u32 PROGRAM_HEADER_SIZE = sizeof(Tegra::Shader::Header);
+constexpr u32 MAX_GEOMETRY_BUFFERS = 6;
+constexpr u32 MAX_ATTRIBUTES = 0x100; // Size in vec4s, this value is untested
+
class DecompileFail : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
+/// Translate topology
+static std::string GetTopologyName(Tegra::Shader::OutputTopology topology) {
+ switch (topology) {
+ case Tegra::Shader::OutputTopology::PointList:
+ return "points";
+ case Tegra::Shader::OutputTopology::LineStrip:
+ return "line_strip";
+ case Tegra::Shader::OutputTopology::TriangleStrip:
+ return "triangle_strip";
+ default:
+ LOG_CRITICAL(Render_OpenGL, "Unknown output topology {}", static_cast<u32>(topology));
+ UNREACHABLE();
+ return "points";
+ }
+}
+
/// Describes the behaviour of code path of a given entry point and a return point.
enum class ExitMethod {
Undetermined, ///< Internal value. Only occur when analyzing JMP loop.
@@ -123,7 +144,7 @@ private:
for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
const Instruction instr = {program_code[offset]};
if (const auto opcode = OpCode::Decode(instr)) {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::EXIT: {
// The EXIT instruction can be predicated, which means that the shader can
// conditionally end on this instruction. We have to consider the case where the
@@ -143,10 +164,11 @@ private:
const ExitMethod jmp = Scan(target, end, labels);
return exit_method = ParallelExit(no_jmp, jmp);
}
- case OpCode::Id::SSY: {
- // The SSY instruction uses a similar encoding as the BRA instruction.
+ case OpCode::Id::SSY:
+ case OpCode::Id::PBK: {
+ // The SSY and PBK use a similar encoding as the BRA instruction.
ASSERT_MSG(instr.bra.constant_buffer == 0,
- "Constant buffer SSY is not supported");
+ "Constant buffer branching is not supported");
const u32 target = offset + instr.bra.GetBranchTarget();
labels.insert(target);
// Continue scanning for an exit method.
@@ -253,8 +275,10 @@ enum class InternalFlag : u64 {
class GLSLRegisterManager {
public:
GLSLRegisterManager(ShaderWriter& shader, ShaderWriter& declarations,
- const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix)
- : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix} {
+ const Maxwell3D::Regs::ShaderStage& stage, const std::string& suffix,
+ const Tegra::Shader::Header& header)
+ : shader{shader}, declarations{declarations}, stage{stage}, suffix{suffix}, header{header},
+ fixed_pipeline_output_attributes_used{}, local_memory_size{0} {
BuildRegisterList();
BuildInputList();
}
@@ -317,10 +341,10 @@ public:
*/
void SetRegisterToFloat(const Register& reg, u64 elem, const std::string& value,
u64 dest_num_components, u64 value_num_components,
- bool is_saturated = false, u64 dest_elem = 0) {
+ bool is_saturated = false, u64 dest_elem = 0, bool precise = false) {
SetRegister(reg, elem, is_saturated ? "clamp(" + value + ", 0.0, 1.0)" : value,
- dest_num_components, value_num_components, dest_elem);
+ dest_num_components, value_num_components, dest_elem, precise);
}
/**
@@ -344,28 +368,94 @@ public:
const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
SetRegister(reg, elem, func + '(' + ConvertIntegerSize(value, size) + ')',
- dest_num_components, value_num_components, dest_elem);
+ dest_num_components, value_num_components, dest_elem, false);
if (sets_cc) {
const std::string zero_condition = "( " + ConvertIntegerSize(value, size) + " == 0 )";
SetInternalFlag(InternalFlag::ZeroFlag, zero_condition);
+ LOG_WARNING(HW_GPU, "Control Codes Imcomplete.");
}
}
/**
+ * Writes code that does a register assignment to a half float value operation.
+ * @param reg The destination register to use.
+ * @param elem The element to use for the operation.
+ * @param value The code representing the value to assign. Type has to be half float.
+ * @param merge Half float kind of assignment.
+ * @param dest_num_components Number of components in the destination.
+ * @param value_num_components Number of components in the value.
+ * @param is_saturated Optional, when True, saturates the provided value.
+ * @param dest_elem Optional, the destination element to use for the operation.
+ */
+ void SetRegisterToHalfFloat(const Register& reg, u64 elem, const std::string& value,
+ Tegra::Shader::HalfMerge merge, u64 dest_num_components,
+ u64 value_num_components, bool is_saturated = false,
+ u64 dest_elem = 0) {
+ ASSERT_MSG(!is_saturated, "Unimplemented");
+
+ const std::string result = [&]() {
+ switch (merge) {
+ case Tegra::Shader::HalfMerge::H0_H1:
+ return "uintBitsToFloat(packHalf2x16(" + value + "))";
+ case Tegra::Shader::HalfMerge::F32:
+ // Half float instructions take the first component when doing a float cast.
+ return "float(" + value + ".x)";
+ case Tegra::Shader::HalfMerge::Mrg_H0:
+ // TODO(Rodrigo): I guess Mrg_H0 and Mrg_H1 take their respective component from the
+ // pack. I couldn't test this on hardware but it shouldn't really matter since most
+ // of the time when a Mrg_* flag is used both components will be mirrored. That
+ // being said, it deserves a test.
+ return "((" + GetRegisterAsInteger(reg, 0, false) +
+ " & 0xffff0000) | (packHalf2x16(" + value + ") & 0x0000ffff))";
+ case Tegra::Shader::HalfMerge::Mrg_H1:
+ return "((" + GetRegisterAsInteger(reg, 0, false) +
+ " & 0x0000ffff) | (packHalf2x16(" + value + ") & 0xffff0000))";
+ default:
+ UNREACHABLE();
+ return std::string("0");
+ }
+ }();
+
+ SetRegister(reg, elem, result, dest_num_components, value_num_components, dest_elem, false);
+ }
+
+ /**
* Writes code that does a register assignment to input attribute operation. Input attributes
* are stored as floats, so this may require conversion.
* @param reg The destination register to use.
* @param elem The element to use for the operation.
* @param attribute The input attribute to use as the source value.
+ * @param input_mode The input mode.
+ * @param vertex The register that decides which vertex to read from (used in GS).
*/
void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute,
- const Tegra::Shader::IpaMode& input_mode) {
+ const Tegra::Shader::IpaMode& input_mode,
+ std::optional<Register> vertex = {}) {
const std::string dest = GetRegisterAsFloat(reg);
- const std::string src = GetInputAttribute(attribute, input_mode) + GetSwizzle(elem);
+ const std::string src = GetInputAttribute(attribute, input_mode, vertex) + GetSwizzle(elem);
shader.AddLine(dest + " = " + src + ';');
}
+ std::string GetLocalMemoryAsFloat(const std::string& index) {
+ return "lmem[" + index + ']';
+ }
+
+ std::string GetLocalMemoryAsInteger(const std::string& index, bool is_signed = false) {
+ const std::string func{is_signed ? "floatToIntBits" : "floatBitsToUint"};
+ return func + "(lmem[" + index + "])";
+ }
+
+ void SetLocalMemoryAsFloat(const std::string& index, const std::string& value) {
+ shader.AddLine("lmem[" + index + "] = " + value + ';');
+ }
+
+ void SetLocalMemoryAsInteger(const std::string& index, const std::string& value,
+ bool is_signed = false) {
+ const std::string func{is_signed ? "intBitsToFloat" : "uintBitsToFloat"};
+ shader.AddLine("lmem[" + index + "] = " + func + '(' + value + ");");
+ }
+
std::string GetControlCode(const Tegra::Shader::ControlCode cc) const {
switch (cc) {
case Tegra::Shader::ControlCode::NEU:
@@ -391,16 +481,34 @@ public:
* are stored as floats, so this may require conversion.
* @param attribute The destination output attribute.
* @param elem The element to use for the operation.
- * @param reg The register to use as the source value.
+ * @param val_reg The register to use as the source value.
+ * @param buf_reg The register that tells which buffer to write to (used in geometry shaders).
*/
- void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& reg) {
+ void SetOutputAttributeToRegister(Attribute::Index attribute, u64 elem, const Register& val_reg,
+ const Register& buf_reg) {
const std::string dest = GetOutputAttribute(attribute);
- const std::string src = GetRegisterAsFloat(reg);
+ const std::string src = GetRegisterAsFloat(val_reg);
if (!dest.empty()) {
// Can happen with unknown/unimplemented output attributes, in which case we ignore the
// instruction for now.
- shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
+ if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
+ // TODO(Rodrigo): nouveau sets some attributes after setting emitting a geometry
+ // shader. These instructions use a dirty register as buffer index. To avoid some
+ // drivers from complaining for the out of boundary writes, guard them.
+ const std::string buf_index{"min(" + GetRegisterAsInteger(buf_reg) + ", " +
+ std::to_string(MAX_GEOMETRY_BUFFERS - 1) + ')'};
+ shader.AddLine("amem[" + buf_index + "][" +
+ std::to_string(static_cast<u32>(attribute)) + ']' +
+ GetSwizzle(elem) + " = " + src + ';');
+ } else {
+ if (attribute == Attribute::Index::PointSize) {
+ fixed_pipeline_output_attributes_used.insert(attribute);
+ shader.AddLine(dest + " = " + src + ';');
+ } else {
+ shader.AddLine(dest + GetSwizzle(elem) + " = " + src + ';');
+ }
+ }
}
}
@@ -441,41 +549,127 @@ public:
}
}
- /// Add declarations for registers
+ /// Add declarations.
void GenerateDeclarations(const std::string& suffix) {
+ GenerateVertex();
+ GenerateRegisters(suffix);
+ GenerateLocalMemory();
+ GenerateInternalFlags();
+ GenerateInputAttrs();
+ GenerateOutputAttrs();
+ GenerateConstBuffers();
+ GenerateSamplers();
+ GenerateGeometry();
+ }
+
+ /// Returns a list of constant buffer declarations.
+ std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
+ std::vector<ConstBufferEntry> result;
+ std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
+ std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
+ return result;
+ }
+
+ /// Returns a list of samplers used in the shader.
+ const std::vector<SamplerEntry>& GetSamplers() const {
+ return used_samplers;
+ }
+
+ /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
+ /// necessary.
+ std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
+ bool is_array, bool is_shadow) {
+ const auto offset = static_cast<std::size_t>(sampler.index.Value());
+
+ // If this sampler has already been used, return the existing mapping.
+ const auto itr =
+ std::find_if(used_samplers.begin(), used_samplers.end(),
+ [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
+
+ if (itr != used_samplers.end()) {
+ ASSERT(itr->GetType() == type && itr->IsArray() == is_array &&
+ itr->IsShadow() == is_shadow);
+ return itr->GetName();
+ }
+
+ // Otherwise create a new mapping for this sampler
+ const std::size_t next_index = used_samplers.size();
+ const SamplerEntry entry{stage, offset, next_index, type, is_array, is_shadow};
+ used_samplers.emplace_back(entry);
+ return entry.GetName();
+ }
+
+ void SetLocalMemory(u64 lmem) {
+ local_memory_size = lmem;
+ }
+
+private:
+ /// Generates declarations for registers.
+ void GenerateRegisters(const std::string& suffix) {
for (const auto& reg : regs) {
declarations.AddLine(GLSLRegister::GetTypeString() + ' ' + reg.GetPrefixString() +
std::to_string(reg.GetIndex()) + '_' + suffix + " = 0;");
}
declarations.AddNewLine();
+ }
+
+ /// Generates declarations for local memory.
+ void GenerateLocalMemory() {
+ if (local_memory_size > 0) {
+ declarations.AddLine("float lmem[" + std::to_string((local_memory_size - 1 + 4) / 4) +
+ "];");
+ declarations.AddNewLine();
+ }
+ }
+ /// Generates declarations for internal flags.
+ void GenerateInternalFlags() {
for (u32 ii = 0; ii < static_cast<u64>(InternalFlag::Amount); ii++) {
const InternalFlag code = static_cast<InternalFlag>(ii);
declarations.AddLine("bool " + GetInternalFlag(code) + " = false;");
}
declarations.AddNewLine();
+ }
+ /// Generates declarations for input attributes.
+ void GenerateInputAttrs() {
for (const auto element : declr_input_attribute) {
// TODO(bunnei): Use proper number of elements for these
u32 idx =
static_cast<u32>(element.first) - static_cast<u32>(Attribute::Index::Attribute_0);
- declarations.AddLine("layout(location = " + std::to_string(idx) + ")" +
- GetInputFlags(element.first) + "in vec4 " +
- GetInputAttribute(element.first, element.second) + ';');
+ if (stage != Maxwell3D::Regs::ShaderStage::Vertex) {
+ // If inputs are varyings, add an offset
+ idx += GENERIC_VARYING_START_LOCATION;
+ }
+
+ std::string attr{GetInputAttribute(element.first, element.second)};
+ if (stage == Maxwell3D::Regs::ShaderStage::Geometry) {
+ attr = "gs_" + attr + "[]";
+ }
+ declarations.AddLine("layout (location = " + std::to_string(idx) + ") " +
+ GetInputFlags(element.first) + "in vec4 " + attr + ';');
}
+
declarations.AddNewLine();
+ }
+ /// Generates declarations for output attributes.
+ void GenerateOutputAttrs() {
for (const auto& index : declr_output_attribute) {
// TODO(bunnei): Use proper number of elements for these
- declarations.AddLine("layout(location = " +
- std::to_string(static_cast<u32>(index) -
- static_cast<u32>(Attribute::Index::Attribute_0)) +
- ") out vec4 " + GetOutputAttribute(index) + ';');
+ const u32 idx = static_cast<u32>(index) -
+ static_cast<u32>(Attribute::Index::Attribute_0) +
+ GENERIC_VARYING_START_LOCATION;
+ declarations.AddLine("layout (location = " + std::to_string(idx) + ") out vec4 " +
+ GetOutputAttribute(index) + ';');
}
declarations.AddNewLine();
+ }
+ /// Generates declarations for constant buffers.
+ void GenerateConstBuffers() {
for (const auto& entry : GetConstBuffersDeclarations()) {
- declarations.AddLine("layout(std140) uniform " + entry.GetName());
+ declarations.AddLine("layout (std140) uniform " + entry.GetName());
declarations.AddLine('{');
declarations.AddLine(" vec4 c" + std::to_string(entry.GetIndex()) +
"[MAX_CONSTBUFFER_ELEMENTS];");
@@ -483,7 +677,10 @@ public:
declarations.AddNewLine();
}
declarations.AddNewLine();
+ }
+ /// Generates declarations for samplers.
+ void GenerateSamplers() {
const auto& samplers = GetSamplers();
for (const auto& sampler : samplers) {
declarations.AddLine("uniform " + sampler.GetTypeString() + ' ' + sampler.GetName() +
@@ -492,43 +689,56 @@ public:
declarations.AddNewLine();
}
- /// Returns a list of constant buffer declarations
- std::vector<ConstBufferEntry> GetConstBuffersDeclarations() const {
- std::vector<ConstBufferEntry> result;
- std::copy_if(declr_const_buffers.begin(), declr_const_buffers.end(),
- std::back_inserter(result), [](const auto& entry) { return entry.IsUsed(); });
- return result;
- }
+ /// Generates declarations used for geometry shaders.
+ void GenerateGeometry() {
+ if (stage != Maxwell3D::Regs::ShaderStage::Geometry)
+ return;
- /// Returns a list of samplers used in the shader
- const std::vector<SamplerEntry>& GetSamplers() const {
- return used_samplers;
- }
+ declarations.AddLine(
+ "layout (" + GetTopologyName(header.common3.output_topology) +
+ ", max_vertices = " + std::to_string(header.common4.max_output_vertices) + ") out;");
+ declarations.AddNewLine();
- /// Returns the GLSL sampler used for the input shader sampler, and creates a new one if
- /// necessary.
- std::string AccessSampler(const Sampler& sampler, Tegra::Shader::TextureType type,
- bool is_array) {
- const std::size_t offset = static_cast<std::size_t>(sampler.index.Value());
+ declarations.AddLine("vec4 amem[" + std::to_string(MAX_GEOMETRY_BUFFERS) + "][" +
+ std::to_string(MAX_ATTRIBUTES) + "];");
+ declarations.AddNewLine();
- // If this sampler has already been used, return the existing mapping.
- const auto itr =
- std::find_if(used_samplers.begin(), used_samplers.end(),
- [&](const SamplerEntry& entry) { return entry.GetOffset() == offset; });
+ constexpr char buffer[] = "amem[output_buffer]";
+ declarations.AddLine("void emit_vertex(uint output_buffer) {");
+ ++declarations.scope;
+ for (const auto element : declr_output_attribute) {
+ declarations.AddLine(GetOutputAttribute(element) + " = " + buffer + '[' +
+ std::to_string(static_cast<u32>(element)) + "];");
+ }
+
+ declarations.AddLine("position = " + std::string(buffer) + '[' +
+ std::to_string(static_cast<u32>(Attribute::Index::Position)) + "];");
+
+ // If a geometry shader is attached, it will always flip (it's the last stage before
+ // fragment). For more info about flipping, refer to gl_shader_gen.cpp.
+ declarations.AddLine("position.xy *= viewport_flip.xy;");
+ declarations.AddLine("gl_Position = position;");
+ declarations.AddLine("position.w = 1.0;");
+ declarations.AddLine("EmitVertex();");
+ --declarations.scope;
+ declarations.AddLine('}');
+ declarations.AddNewLine();
+ }
- if (itr != used_samplers.end()) {
- ASSERT(itr->GetType() == type && itr->IsArray() == is_array);
- return itr->GetName();
+ void GenerateVertex() {
+ if (stage != Maxwell3D::Regs::ShaderStage::Vertex)
+ return;
+ declarations.AddLine("out gl_PerVertex {");
+ ++declarations.scope;
+ declarations.AddLine("vec4 gl_Position;");
+ for (auto& o : fixed_pipeline_output_attributes_used) {
+ if (o == Attribute::Index::PointSize)
+ declarations.AddLine("float gl_PointSize;");
}
-
- // Otherwise create a new mapping for this sampler
- const std::size_t next_index = used_samplers.size();
- const SamplerEntry entry{stage, offset, next_index, type, is_array};
- used_samplers.emplace_back(entry);
- return entry.GetName();
+ --declarations.scope;
+ declarations.AddLine("};");
}
-private:
/// Generates code representing a temporary (GPR) register.
std::string GetRegister(const Register& reg, unsigned elem) {
if (reg == Register::ZeroIndex) {
@@ -548,7 +758,8 @@ private:
* @param dest_elem Optional, the destination element to use for the operation.
*/
void SetRegister(const Register& reg, u64 elem, const std::string& value,
- u64 dest_num_components, u64 value_num_components, u64 dest_elem) {
+ u64 dest_num_components, u64 value_num_components, u64 dest_elem,
+ bool precise) {
if (reg == Register::ZeroIndex) {
LOG_CRITICAL(HW_GPU, "Cannot set Register::ZeroIndex");
UNREACHABLE();
@@ -565,7 +776,18 @@ private:
src += GetSwizzle(elem);
}
- shader.AddLine(dest + " = " + src + ';');
+ if (precise && stage != Maxwell3D::Regs::ShaderStage::Fragment) {
+ shader.AddLine('{');
+ ++shader.scope;
+ // This avoids optimizations of constant propagation and keeps the code as the original
+ // Sadly using the precise keyword causes "linking" errors on fragment shaders.
+ shader.AddLine("precise float tmp = " + src + ';');
+ shader.AddLine(dest + " = tmp;");
+ --shader.scope;
+ shader.AddLine('}');
+ } else {
+ shader.AddLine(dest + " = " + src + ';');
+ }
}
/// Build the GLSL register list.
@@ -585,11 +807,19 @@ private:
/// Generates code representing an input attribute register.
std::string GetInputAttribute(Attribute::Index attribute,
- const Tegra::Shader::IpaMode& input_mode) {
+ const Tegra::Shader::IpaMode& input_mode,
+ std::optional<Register> vertex = {}) {
+ auto GeometryPass = [&](const std::string& name) {
+ if (stage == Maxwell3D::Regs::ShaderStage::Geometry && vertex) {
+ return "gs_" + name + '[' + GetRegisterAsInteger(*vertex, 0, false) + ']';
+ }
+ return name;
+ };
+
switch (attribute) {
case Attribute::Index::Position:
if (stage != Maxwell3D::Regs::ShaderStage::Fragment) {
- return "position";
+ return GeometryPass("position");
} else {
return "vec4(gl_FragCoord.x, gl_FragCoord.y, gl_FragCoord.z, 1.0)";
}
@@ -618,7 +848,7 @@ private:
UNREACHABLE();
}
}
- return "input_attribute_" + std::to_string(index);
+ return GeometryPass("input_attribute_" + std::to_string(index));
}
LOG_CRITICAL(HW_GPU, "Unhandled input attribute: {}", static_cast<u32>(attribute));
@@ -671,9 +901,11 @@ private:
return out;
}
- /// Generates code representing an output attribute register.
+ /// Generates code representing the declaration name of an output attribute register.
std::string GetOutputAttribute(Attribute::Index attribute) {
switch (attribute) {
+ case Attribute::Index::PointSize:
+ return "gl_PointSize";
case Attribute::Index::Position:
return "position";
default:
@@ -707,6 +939,9 @@ private:
std::vector<SamplerEntry> used_samplers;
const Maxwell3D::Regs::ShaderStage& stage;
const std::string& suffix;
+ const Tegra::Shader::Header& header;
+ std::unordered_set<Attribute::Index> fixed_pipeline_output_attributes_used;
+ u64 local_memory_size;
};
class GLSLGenerator {
@@ -716,6 +951,8 @@ public:
: subroutines(subroutines), program_code(program_code), main_offset(main_offset),
stage(stage), suffix(suffix) {
std::memcpy(&header, program_code.data(), sizeof(Tegra::Shader::Header));
+ local_memory_size = header.GetLocalMemorySize();
+ regs.SetLocalMemory(local_memory_size);
Generate(suffix);
}
@@ -746,9 +983,23 @@ private:
return fmt::format("uintBitsToFloat({})", instr.alu.GetImm20_32());
}
+ /// Generates code representing a vec2 pair unpacked from a half float immediate
+ static std::string UnpackHalfImmediate(const Instruction& instr, bool negate) {
+ const std::string immediate = GetHalfFloat(std::to_string(instr.half_imm.PackImmediates()));
+ if (!negate) {
+ return immediate;
+ }
+ const std::string negate_first = instr.half_imm.first_negate != 0 ? "-" : "";
+ const std::string negate_second = instr.half_imm.second_negate != 0 ? "-" : "";
+ const std::string negate_vec = "vec2(" + negate_first + "1, " + negate_second + "1)";
+
+ return '(' + immediate + " * " + negate_vec + ')';
+ }
+
/// Generates code representing a texture sampler.
- std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array) {
- return regs.AccessSampler(sampler, type, is_array);
+ std::string GetSampler(const Sampler& sampler, Tegra::Shader::TextureType type, bool is_array,
+ bool is_shadow) {
+ return regs.AccessSampler(sampler, type, is_array, is_shadow);
}
/**
@@ -776,7 +1027,7 @@ private:
// Can't assign to the constant predicate.
ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
- const std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
+ std::string variable = 'p' + std::to_string(pred) + '_' + suffix;
shader.AddLine(variable + " = " + value + ';');
declr_predicates.insert(std::move(variable));
}
@@ -881,6 +1132,41 @@ private:
}
/*
+ * Transforms the input string GLSL operand into an unpacked half float pair.
+ * @note This function returns a float type pair instead of a half float pair. This is because
+ * real half floats are not standardized in GLSL but unpackHalf2x16 (which returns a vec2) is.
+ * @param operand Input operand. It has to be an unsigned integer.
+ * @param type How to unpack the unsigned integer to a half float pair.
+ * @param abs Get the absolute value of unpacked half floats.
+ * @param neg Get the negative value of unpacked half floats.
+ * @returns String corresponding to a half float pair.
+ */
+ static std::string GetHalfFloat(const std::string& operand,
+ Tegra::Shader::HalfType type = Tegra::Shader::HalfType::H0_H1,
+ bool abs = false, bool neg = false) {
+ // "vec2" calls emitted in this function are intended to alias components.
+ const std::string value = [&]() {
+ switch (type) {
+ case Tegra::Shader::HalfType::H0_H1:
+ return "unpackHalf2x16(" + operand + ')';
+ case Tegra::Shader::HalfType::F32:
+ return "vec2(uintBitsToFloat(" + operand + "))";
+ case Tegra::Shader::HalfType::H0_H0:
+ case Tegra::Shader::HalfType::H1_H1: {
+ const bool high = type == Tegra::Shader::HalfType::H1_H1;
+ const char unpack_index = "xy"[high ? 1 : 0];
+ return "vec2(unpackHalf2x16(" + operand + ")." + unpack_index + ')';
+ }
+ default:
+ UNREACHABLE();
+ return std::string("vec2(0)");
+ }
+ }();
+
+ return GetOperandAbsNeg(value, abs, neg);
+ }
+
+ /*
* Returns whether the instruction at the specified offset is a 'sched' instruction.
* Sched instructions always appear before a sequence of 3 instructions.
*/
@@ -1002,28 +1288,47 @@ private:
shader.AddLine('}');
}
+ static u32 TextureCoordinates(Tegra::Shader::TextureType texture_type) {
+ switch (texture_type) {
+ case Tegra::Shader::TextureType::Texture1D: {
+ return 1;
+ }
+ case Tegra::Shader::TextureType::Texture2D: {
+ return 2;
+ }
+ case Tegra::Shader::TextureType::Texture3D:
+ case Tegra::Shader::TextureType::TextureCube: {
+ return 3;
+ }
+ default:
+ LOG_CRITICAL(HW_GPU, "Unhandled texture type {}", static_cast<u32>(texture_type));
+ UNREACHABLE();
+ return 0;
+ }
+ }
+
/*
- * Emits code to push the input target address to the SSY address stack, incrementing the stack
+ * Emits code to push the input target address to the flow address stack, incrementing the stack
* top.
*/
- void EmitPushToSSYStack(u32 target) {
+ void EmitPushToFlowStack(u32 target) {
shader.AddLine('{');
++shader.scope;
- shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;");
- shader.AddLine("ssy_stack_top++;");
+ shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;");
+ shader.AddLine("flow_stack_top++;");
--shader.scope;
shader.AddLine('}');
}
/*
- * Emits code to pop an address from the SSY address stack, setting the jump address to the
+ * Emits code to pop an address from the flow address stack, setting the jump address to the
* popped address and decrementing the stack top.
*/
- void EmitPopFromSSYStack() {
+ void EmitPopFromFlowStack() {
shader.AddLine('{');
++shader.scope;
- shader.AddLine("ssy_stack_top--;");
- shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];");
+ shader.AddLine("flow_stack_top--;");
+ shader.AddLine("jmp_to = flow_stack[flow_stack_top];");
shader.AddLine("break;");
--shader.scope;
shader.AddLine('}');
@@ -1035,9 +1340,29 @@ private:
ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented");
+ shader.AddLine("if (alpha_test[0] != 0) {");
+ ++shader.scope;
+ // We start on the register containing the alpha value in the first RT.
+ u32 current_reg = 3;
+ for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets;
+ ++render_target) {
+ // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when
+ // multiple render targets are used.
+ if (header.ps.IsColorComponentOutputEnabled(render_target, 0) ||
+ header.ps.IsColorComponentOutputEnabled(render_target, 1) ||
+ header.ps.IsColorComponentOutputEnabled(render_target, 2) ||
+ header.ps.IsColorComponentOutputEnabled(render_target, 3)) {
+ shader.AddLine(fmt::format("if (!AlphaFunc({})) discard;",
+ regs.GetRegisterAsFloat(current_reg)));
+ current_reg += 4;
+ }
+ }
+ --shader.scope;
+ shader.AddLine('}');
+
// Write the color outputs using the data in the shader registers, disabled
// rendertargets/components are skipped in the register assignment.
- u32 current_reg = 0;
+ current_reg = 0;
for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets;
++render_target) {
// TODO(Subv): Figure out how dual-source blending is configured in the Switch.
@@ -1061,6 +1386,63 @@ private:
}
}
+ /// Unpacks a video instruction operand (e.g. VMAD).
+ std::string GetVideoOperand(const std::string& op, bool is_chunk, bool is_signed,
+ Tegra::Shader::VideoType type, u64 byte_height) {
+ const std::string value = [&]() {
+ if (!is_chunk) {
+ const auto offset = static_cast<u32>(byte_height * 8);
+ return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)";
+ }
+ const std::string zero = "0";
+
+ switch (type) {
+ case Tegra::Shader::VideoType::Size16_Low:
+ return '(' + op + " & 0xffff)";
+ case Tegra::Shader::VideoType::Size16_High:
+ return '(' + op + " >> 16)";
+ case Tegra::Shader::VideoType::Size32:
+ // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when
+ // this type is used (1 * 1 + 0 == 0x5b800000). Until a better
+ // explanation is found: assert.
+ UNIMPLEMENTED();
+ return zero;
+ case Tegra::Shader::VideoType::Invalid:
+ UNREACHABLE_MSG("Invalid instruction encoding");
+ return zero;
+ default:
+ UNREACHABLE();
+ return zero;
+ }
+ }();
+
+ if (is_signed) {
+ return "int(" + value + ')';
+ }
+ return value;
+ };
+
+ /// Gets the A operand for a video instruction.
+ std::string GetVideoOperandA(Instruction instr) {
+ return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr8, 0, false),
+ instr.video.is_byte_chunk_a != 0, instr.video.signed_a,
+ instr.video.type_a, instr.video.byte_height_a);
+ }
+
+ /// Gets the B operand for a video instruction.
+ std::string GetVideoOperandB(Instruction instr) {
+ if (instr.video.use_register_b) {
+ return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
+ instr.video.is_byte_chunk_b != 0, instr.video.signed_b,
+ instr.video.type_b, instr.video.byte_height_b);
+ } else {
+ return '(' +
+ std::to_string(instr.video.signed_b ? static_cast<s16>(instr.alu.GetImm20_16())
+ : instr.alu.GetImm20_16()) +
+ ')';
+ }
+ }
+
/**
* Compiles a single instruction from Tegra to GLSL.
* @param offset the offset of the Tegra shader instruction.
@@ -1083,8 +1465,8 @@ private:
return offset + 1;
}
- shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName() + " (" +
- std::to_string(instr.value) + ')');
+ shader.AddLine(
+ fmt::format("// {}: {} (0x{:016x})", offset, opcode->get().GetName(), instr.value));
using Tegra::Shader::Pred;
ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
@@ -1092,7 +1474,7 @@ private:
// Some instructions (like SSY) don't have a predicate field, they are always
// unconditionally executed.
- bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->GetId());
+ bool can_be_predicated = OpCode::IsPredicatedInstruction(opcode->get().GetId());
if (can_be_predicated && instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
shader.AddLine("if (" +
@@ -1102,7 +1484,7 @@ private:
++shader.scope;
}
- switch (opcode->GetType()) {
+ switch (opcode->get().GetType()) {
case OpCode::Type::Arithmetic: {
std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
@@ -1119,7 +1501,7 @@ private:
}
}
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::MOV_C:
case OpCode::Id::MOV_R: {
// MOV does not have neither 'abs' nor 'neg' bits.
@@ -1141,8 +1523,13 @@ private:
ASSERT_MSG(instr.fmul.cc == 0, "FMUL cc is not implemented");
op_b = GetOperandAbsNeg(op_b, false, instr.fmul.negate_b);
+
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b, 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FMUL Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::FADD_C:
@@ -1150,8 +1537,13 @@ private:
case OpCode::Id::FADD_IMM: {
op_a = GetOperandAbsNeg(op_a, instr.alu.abs_a, instr.alu.negate_a);
op_b = GetOperandAbsNeg(op_b, instr.alu.abs_b, instr.alu.negate_b);
+
regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FADD Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::MUFU: {
@@ -1159,31 +1551,31 @@ private:
switch (instr.sub_op) {
case SubOp::Cos:
regs.SetRegisterToFloat(instr.gpr0, 0, "cos(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Sin:
regs.SetRegisterToFloat(instr.gpr0, 0, "sin(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Ex2:
regs.SetRegisterToFloat(instr.gpr0, 0, "exp2(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Lg2:
regs.SetRegisterToFloat(instr.gpr0, 0, "log2(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Rcp:
regs.SetRegisterToFloat(instr.gpr0, 0, "1.0 / " + op_a, 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Rsq:
regs.SetRegisterToFloat(instr.gpr0, 0, "inversesqrt(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
case SubOp::Sqrt:
regs.SetRegisterToFloat(instr.gpr0, 0, "sqrt(" + op_a + ')', 1, 1,
- instr.alu.saturate_d);
+ instr.alu.saturate_d, 0, true);
break;
default:
LOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}",
@@ -1204,7 +1596,11 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0,
'(' + condition + ") ? min(" + parameters + ") : max(" +
parameters + ')',
- 1, 1);
+ 1, 1, false, 0, true);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FMNMX Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::RRO_C:
@@ -1217,22 +1613,28 @@ private:
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}",
+ opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
case OpCode::Type::ArithmeticImmediate: {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::MOV32_IMM: {
regs.SetRegisterToFloat(instr.gpr0, 0, GetImmediate32(instr), 1, 1);
break;
}
case OpCode::Id::FMUL32_IMM: {
- regs.SetRegisterToFloat(
- instr.gpr0, 0,
- regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1);
+ regs.SetRegisterToFloat(instr.gpr0, 0,
+ regs.GetRegisterAsFloat(instr.gpr8) + " * " +
+ GetImmediate32(instr),
+ 1, 1, instr.fmul32.saturate, 0, true);
+ if (instr.op_32.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FMUL32 Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::FADD32I: {
@@ -1255,7 +1657,11 @@ private:
op_b = "-(" + op_b + ')';
}
- regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1);
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " + " + op_b, 1, 1, false, 0, true);
+ if (instr.op_32.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FADD32 Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
}
@@ -1267,7 +1673,7 @@ private:
std::string op_a = instr.bfe.negate_a ? "-" : "";
op_a += regs.GetRegisterAsInteger(instr.gpr8);
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::BFE_IMM: {
std::string inner_shift =
'(' + op_a + " << " + std::to_string(instr.bfe.GetLeftShiftValue()) + ')';
@@ -1276,17 +1682,20 @@ private:
std::to_string(instr.bfe.GetLeftShiftValue() + instr.bfe.shift_position) + ')';
regs.SetRegisterToInteger(instr.gpr0, true, 0, outer_shift, 1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "BFE Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled BFE instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
-
case OpCode::Type::Shift: {
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8, 0, true);
std::string op_b;
@@ -1302,7 +1711,7 @@ private:
}
}
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::SHR_C:
case OpCode::Id::SHR_R:
case OpCode::Id::SHR_IMM: {
@@ -1314,32 +1723,43 @@ private:
// Cast to int is superfluous for arithmetic shift, it's only for a logical shift
regs.SetRegisterToInteger(instr.gpr0, true, 0, "int(" + op_a + " >> " + op_b + ')',
1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "SHR Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::SHL_C:
case OpCode::Id::SHL_R:
case OpCode::Id::SHL_IMM:
regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " << " + op_b, 1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "SHL Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled shift instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
-
case OpCode::Type::ArithmeticIntegerImmediate: {
std::string op_a = regs.GetRegisterAsInteger(instr.gpr8);
std::string op_b = std::to_string(instr.alu.imm20_32.Value());
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::IADD32I:
if (instr.iadd32i.negate_a)
op_a = "-(" + op_a + ')';
regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1,
instr.iadd32i.saturate != 0);
+ if (instr.op_32.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "IADD32 Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
case OpCode::Id::LOP32I: {
if (instr.alu.lop32i.invert_a)
@@ -1351,11 +1771,15 @@ private:
WriteLogicOperation(instr.gpr0, instr.alu.lop32i.operation, op_a, op_b,
Tegra::Shader::PredicateResultMode::None,
Tegra::Shader::Pred::UnusedIndex);
+ if (instr.op_32.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "LOP32I Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticIntegerImmediate instruction: {}",
- opcode->GetName());
+ opcode->get().GetName());
UNREACHABLE();
}
}
@@ -1375,7 +1799,7 @@ private:
}
}
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::IADD_C:
case OpCode::Id::IADD_R:
case OpCode::Id::IADD_IMM: {
@@ -1387,6 +1811,10 @@ private:
regs.SetRegisterToInteger(instr.gpr0, true, 0, op_a + " + " + op_b, 1, 1,
instr.alu.saturate_d);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "IADD Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::IADD3_C:
@@ -1411,7 +1839,7 @@ private:
}
};
- if (opcode->GetId() == OpCode::Id::IADD3_R) {
+ if (opcode->get().GetId() == OpCode::Id::IADD3_R) {
apply_height(instr.iadd3.height_a, op_a);
apply_height(instr.iadd3.height_b, op_b);
apply_height(instr.iadd3.height_c, op_c);
@@ -1427,7 +1855,7 @@ private:
op_c = "-(" + op_c + ')';
std::string result;
- if (opcode->GetId() == OpCode::Id::IADD3_R) {
+ if (opcode->get().GetId() == OpCode::Id::IADD3_R) {
switch (instr.iadd3.mode) {
case Tegra::Shader::IAdd3Mode::RightShift:
// TODO(tech4me): According to
@@ -1448,6 +1876,11 @@ private:
}
regs.SetRegisterToInteger(instr.gpr0, true, 0, result, 1, 1);
+
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "IADD3 Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::ISCADD_C:
@@ -1463,6 +1896,10 @@ private:
regs.SetRegisterToInteger(instr.gpr0, true, 0,
"((" + op_a + " << " + shift + ") + " + op_b + ')', 1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "ISCADD Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::POPC_C:
@@ -1494,6 +1931,10 @@ private:
WriteLogicOperation(instr.gpr0, instr.alu.lop.operation, op_a, op_b,
instr.alu.lop.pred_result_mode, instr.alu.lop.pred48);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "LOP Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::LOP3_C:
@@ -1502,13 +1943,17 @@ private:
const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39);
std::string lut;
- if (opcode->GetId() == OpCode::Id::LOP3_R) {
+ if (opcode->get().GetId() == OpCode::Id::LOP3_R) {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut28()) + ')';
} else {
lut = '(' + std::to_string(instr.alu.lop3.GetImmLut48()) + ')';
}
WriteLop3Instruction(instr.gpr0, op_a, op_b, op_c, lut);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "LOP3 Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::IMNMX_C:
@@ -1523,6 +1968,10 @@ private:
'(' + condition + ") ? min(" + parameters + ") : max(" +
parameters + ')',
1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "IMNMX Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::LEA_R2:
@@ -1532,7 +1981,7 @@ private:
case OpCode::Id::LEA_HI: {
std::string op_c;
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::LEA_R2: {
op_a = regs.GetRegisterAsInteger(instr.gpr20);
op_b = regs.GetRegisterAsInteger(instr.gpr39);
@@ -1577,7 +2026,8 @@ private:
op_b = regs.GetRegisterAsInteger(instr.gpr8);
op_a = std::to_string(instr.lea.imm.entry_a);
op_c = std::to_string(instr.lea.imm.entry_b);
- LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled LEA subinstruction: {}",
+ opcode->get().GetName());
UNREACHABLE();
}
}
@@ -1592,13 +2042,95 @@ private:
}
default: {
LOG_CRITICAL(HW_GPU, "Unhandled ArithmeticInteger instruction: {}",
- opcode->GetName());
+ opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
+ case OpCode::Type::ArithmeticHalf: {
+ if (opcode->get().GetId() == OpCode::Id::HADD2_C ||
+ opcode->get().GetId() == OpCode::Id::HADD2_R) {
+ ASSERT_MSG(instr.alu_half.ftz == 0, "Unimplemented");
+ }
+ const bool negate_a =
+ opcode->get().GetId() != OpCode::Id::HMUL2_R && instr.alu_half.negate_a != 0;
+ const bool negate_b =
+ opcode->get().GetId() != OpCode::Id::HMUL2_C && instr.alu_half.negate_b != 0;
+
+ const std::string op_a =
+ GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.alu_half.type_a,
+ instr.alu_half.abs_a != 0, negate_a);
+
+ std::string op_b;
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HADD2_C:
+ case OpCode::Id::HMUL2_C:
+ op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::UnsignedInteger);
+ break;
+ case OpCode::Id::HADD2_R:
+ case OpCode::Id::HMUL2_R:
+ op_b = regs.GetRegisterAsInteger(instr.gpr20, 0, false);
+ break;
+ default:
+ UNREACHABLE();
+ op_b = "0";
+ break;
+ }
+ op_b = GetHalfFloat(op_b, instr.alu_half.type_b, instr.alu_half.abs_b != 0, negate_b);
+
+ const std::string result = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HADD2_C:
+ case OpCode::Id::HADD2_R:
+ return '(' + op_a + " + " + op_b + ')';
+ case OpCode::Id::HMUL2_C:
+ case OpCode::Id::HMUL2_R:
+ return '(' + op_a + " * " + op_b + ')';
+ default:
+ LOG_CRITICAL(HW_GPU, "Unhandled half float instruction: {}",
+ opcode->get().GetName());
+ UNREACHABLE();
+ return std::string("0");
+ }
+ }();
+
+ regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.alu_half.merge, 1, 1,
+ instr.alu_half.saturate != 0);
+ break;
+ }
+ case OpCode::Type::ArithmeticHalfImmediate: {
+ if (opcode->get().GetId() == OpCode::Id::HADD2_IMM) {
+ ASSERT_MSG(instr.alu_half_imm.ftz == 0, "Unimplemented");
+ } else {
+ ASSERT_MSG(instr.alu_half_imm.precision == Tegra::Shader::HalfPrecision::None,
+ "Unimplemented");
+ }
+
+ const std::string op_a = GetHalfFloat(
+ regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.alu_half_imm.type_a,
+ instr.alu_half_imm.abs_a != 0, instr.alu_half_imm.negate_a != 0);
+
+ const std::string op_b = UnpackHalfImmediate(instr, true);
+
+ const std::string result = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HADD2_IMM:
+ return op_a + " + " + op_b;
+ case OpCode::Id::HMUL2_IMM:
+ return op_a + " * " + op_b;
+ default:
+ UNREACHABLE();
+ return std::string("0");
+ }
+ }();
+
+ regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.alu_half_imm.merge, 1, 1,
+ instr.alu_half_imm.saturate != 0);
+ break;
+ }
case OpCode::Type::Ffma: {
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
std::string op_b = instr.ffma.negate_b ? "-" : "";
@@ -1610,7 +2142,7 @@ private:
ASSERT_MSG(instr.ffma.tab5980_1 == 0, "FFMA tab5980_1({}) not implemented",
instr.ffma.tab5980_1.Value());
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::FFMA_CR: {
op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
GLSLRegister::Type::Float);
@@ -1634,17 +2166,75 @@ private:
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
- regs.SetRegisterToFloat(instr.gpr0, 0, op_a + " * " + op_b + " + " + op_c, 1, 1,
- instr.alu.saturate_d);
+ regs.SetRegisterToFloat(instr.gpr0, 0, "fma(" + op_a + ", " + op_b + ", " + op_c + ')',
+ 1, 1, instr.alu.saturate_d, 0, true);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "FFMA Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
+
+ break;
+ }
+ case OpCode::Type::Hfma2: {
+ if (opcode->get().GetId() == OpCode::Id::HFMA2_RR) {
+ ASSERT_MSG(instr.hfma2.rr.precision == Tegra::Shader::HalfPrecision::None,
+ "Unimplemented");
+ } else {
+ ASSERT_MSG(instr.hfma2.precision == Tegra::Shader::HalfPrecision::None,
+ "Unimplemented");
+ }
+ const bool saturate = opcode->get().GetId() == OpCode::Id::HFMA2_RR
+ ? instr.hfma2.rr.saturate != 0
+ : instr.hfma2.saturate != 0;
+
+ const std::string op_a =
+ GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hfma2.type_a);
+ std::string op_b, op_c;
+
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HFMA2_CR:
+ op_b = GetHalfFloat(regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::UnsignedInteger),
+ instr.hfma2.type_b, false, instr.hfma2.negate_b);
+ op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false),
+ instr.hfma2.type_reg39, false, instr.hfma2.negate_c);
+ break;
+ case OpCode::Id::HFMA2_RC:
+ op_b = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false),
+ instr.hfma2.type_reg39, false, instr.hfma2.negate_b);
+ op_c = GetHalfFloat(regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::UnsignedInteger),
+ instr.hfma2.type_b, false, instr.hfma2.negate_c);
+ break;
+ case OpCode::Id::HFMA2_RR:
+ op_b = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
+ instr.hfma2.type_b, false, instr.hfma2.negate_b);
+ op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false),
+ instr.hfma2.rr.type_c, false, instr.hfma2.rr.negate_c);
+ break;
+ case OpCode::Id::HFMA2_IMM_R:
+ op_b = UnpackHalfImmediate(instr, true);
+ op_c = GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr39, 0, false),
+ instr.hfma2.type_reg39, false, instr.hfma2.negate_c);
+ break;
+ default:
+ UNREACHABLE();
+ op_c = op_b = "vec2(0)";
+ break;
+ }
+
+ const std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
+
+ regs.SetRegisterToHalfFloat(instr.gpr0, 0, result, instr.hfma2.merge, 1, 1, saturate);
break;
}
case OpCode::Type::Conversion: {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::I2I_R: {
ASSERT_MSG(!instr.conversion.selector, "Unimplemented");
@@ -1692,6 +2282,11 @@ private:
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
+
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "I2F Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::F2F_R: {
@@ -1730,6 +2325,11 @@ private:
}
regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1, instr.alu.saturate_d);
+
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "F2F Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
case OpCode::Id::F2I_R:
@@ -1779,17 +2379,22 @@ private:
regs.SetRegisterToInteger(instr.gpr0, instr.conversion.is_output_signed, 0, op_a, 1,
1, false, 0, instr.conversion.dest_size);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "F2I Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled conversion instruction: {}",
+ opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
case OpCode::Type::Memory: {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::LD_A: {
// Note: Shouldn't this be interp mode flat? As in no interpolation made.
ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
@@ -1806,7 +2411,7 @@ private:
const auto LoadNextElement = [&](u32 reg_offset) {
regs.SetRegisterToInputAttibute(instr.gpr0.Value() + reg_offset, next_element,
static_cast<Attribute::Index>(next_index),
- input_mode);
+ input_mode, instr.gpr39.Value());
// Load the next attribute element into the following register. If the element
// to load goes beyond the vec4 size, load the first element of the next
@@ -1859,6 +2464,39 @@ private:
shader.AddLine("}");
break;
}
+ case OpCode::Id::LD_L: {
+ // Add an extra scope and declare the index register inside to prevent
+ // overwriting it in case it is used as an output of the LD instruction.
+ shader.AddLine('{');
+ ++shader.scope;
+
+ std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
+ std::to_string(instr.smem_imm.Value()) + ')';
+
+ shader.AddLine("uint index = (" + op + " / 4);");
+
+ const std::string op_a = regs.GetLocalMemoryAsFloat("index");
+
+ if (instr.ld_l.unknown != 1) {
+ LOG_CRITICAL(HW_GPU, "LD_L Unhandled mode: {}",
+ static_cast<unsigned>(instr.ld_l.unknown.Value()));
+ UNREACHABLE();
+ }
+
+ switch (instr.ldst_sl.type.Value()) {
+ case Tegra::Shader::StoreType::Bytes32:
+ regs.SetRegisterToFloat(instr.gpr0, 0, op_a, 1, 1);
+ break;
+ default:
+ LOG_CRITICAL(HW_GPU, "LD_L Unhandled type: {}",
+ static_cast<unsigned>(instr.ldst_sl.type.Value()));
+ UNREACHABLE();
+ }
+
+ --shader.scope;
+ shader.AddLine('}');
+ break;
+ }
case OpCode::Id::ST_A: {
ASSERT_MSG(instr.gpr8.Value() == Register::ZeroIndex,
"Indirect attribute loads are not supported");
@@ -1870,8 +2508,8 @@ private:
const auto StoreNextElement = [&](u32 reg_offset) {
regs.SetOutputAttributeToRegister(static_cast<Attribute::Index>(next_index),
- next_element,
- instr.gpr0.Value() + reg_offset);
+ next_element, instr.gpr0.Value() + reg_offset,
+ instr.gpr39.Value());
// Load the next attribute element into the following register. If the element
// to load goes beyond the vec4 size, load the first element of the next
@@ -1887,33 +2525,113 @@ private:
break;
}
+ case OpCode::Id::ST_L: {
+ // Add an extra scope and declare the index register inside to prevent
+ // overwriting it in case it is used as an output of the LD instruction.
+ shader.AddLine('{');
+ ++shader.scope;
+
+ std::string op = '(' + regs.GetRegisterAsInteger(instr.gpr8, 0, false) + " + " +
+ std::to_string(instr.smem_imm.Value()) + ')';
+
+ shader.AddLine("uint index = (" + op + " / 4);");
+
+ if (instr.st_l.unknown != 0) {
+ LOG_CRITICAL(HW_GPU, "ST_L Unhandled mode: {}",
+ static_cast<unsigned>(instr.st_l.unknown.Value()));
+ UNREACHABLE();
+ }
+
+ switch (instr.ldst_sl.type.Value()) {
+ case Tegra::Shader::StoreType::Bytes32:
+ regs.SetLocalMemoryAsFloat("index", regs.GetRegisterAsFloat(instr.gpr0));
+ break;
+ default:
+ LOG_CRITICAL(HW_GPU, "ST_L Unhandled type: {}",
+ static_cast<unsigned>(instr.ldst_sl.type.Value()));
+ UNREACHABLE();
+ }
+
+ --shader.scope;
+ shader.AddLine('}');
+ break;
+ }
case OpCode::Id::TEX: {
- ASSERT_MSG(instr.tex.array == 0, "TEX arrays unimplemented");
Tegra::Shader::TextureType texture_type{instr.tex.texture_type};
std::string coord;
+ const bool is_array = instr.tex.array != 0;
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
"AOFFI is not implemented");
- ASSERT_MSG(!instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
- "DC is not implemented");
- switch (texture_type) {
- case Tegra::Shader::TextureType::Texture1D: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- coord = "float coords = " + x + ';';
+ const bool depth_compare =
+ instr.tex.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
+ u32 num_coordinates = TextureCoordinates(texture_type);
+ if (depth_compare)
+ num_coordinates += 1;
+
+ switch (num_coordinates) {
+ case 1: {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec2 coords = vec2(" + x + ", " + index + ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ coord = "float coords = " + x + ';';
+ }
break;
}
- case Tegra::Shader::TextureType::Texture2D: {
- const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ case 2: {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + index + ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ }
+ break;
+ }
+ case 3: {
+ if (depth_compare) {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
+ ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ }
+ } else {
+ if (is_array) {
+ const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 3);
+ coord = "vec4 coords = vec4(" + x + ", " + y + ", " + z + ", " + index +
+ ");";
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ }
+ }
break;
}
default:
- LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
- static_cast<u32>(texture_type));
+ LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
+ static_cast<u32>(num_coordinates));
UNREACHABLE();
// Fallback to interpreting as a 2D texture for now
@@ -1924,9 +2642,10 @@ private:
}
// TODO: make sure coordinates are always indexed to gpr8 and gpr20 is always bias
// or lod.
- const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20);
+ std::string op_c;
- const std::string sampler = GetSampler(instr.sampler, texture_type, false);
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
@@ -1935,7 +2654,7 @@ private:
shader.AddLine(coord);
std::string texture;
- switch (instr.tex.process_mode) {
+ switch (instr.tex.GetTextureProcessMode()) {
case Tegra::Shader::TextureProcessMode::None: {
texture = "texture(" + sampler + ", coords)";
break;
@@ -1946,12 +2665,25 @@ private:
}
case Tegra::Shader::TextureProcessMode::LB:
case Tegra::Shader::TextureProcessMode::LBA: {
+ if (depth_compare) {
+ if (is_array)
+ op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 2);
+ else
+ op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ } else {
+ op_c = regs.GetRegisterAsFloat(instr.gpr20);
+ }
// TODO: Figure if A suffix changes the equation at all.
texture = "texture(" + sampler + ", coords, " + op_c + ')';
break;
}
case Tegra::Shader::TextureProcessMode::LL:
case Tegra::Shader::TextureProcessMode::LLA: {
+ if (num_coordinates <= 2) {
+ op_c = regs.GetRegisterAsFloat(instr.gpr20);
+ } else {
+ op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ }
// TODO: Figure if A suffix changes the equation at all.
texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
break;
@@ -1959,18 +2691,22 @@ private:
default: {
texture = "texture(" + sampler + ", coords)";
LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
- static_cast<u32>(instr.tex.process_mode.Value()));
+ static_cast<u32>(instr.tex.GetTextureProcessMode()));
UNREACHABLE();
}
}
- std::size_t dest_elem{};
- for (std::size_t elem = 0; elem < 4; ++elem) {
- if (!instr.tex.IsComponentEnabled(elem)) {
- // Skip disabled components
- continue;
+ if (!depth_compare) {
+ std::size_t dest_elem{};
+ for (std::size_t elem = 0; elem < 4; ++elem) {
+ if (!instr.tex.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
+ }
+ regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
+ ++dest_elem;
}
- regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
- ++dest_elem;
+ } else {
+ regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
}
--shader.scope;
shader.AddLine("}");
@@ -1983,11 +2719,15 @@ private:
ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::NODEP),
"NODEP is not implemented");
- ASSERT_MSG(!instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
- "DC is not implemented");
- switch (texture_type) {
- case Tegra::Shader::TextureType::Texture2D: {
+ const bool depth_compare =
+ instr.texs.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
+ u32 num_coordinates = TextureCoordinates(texture_type);
+ if (depth_compare)
+ num_coordinates += 1;
+
+ switch (num_coordinates) {
+ case 2: {
if (is_array) {
const std::string index = regs.GetRegisterAsInteger(instr.gpr8);
const std::string x = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
@@ -2000,17 +2740,25 @@ private:
}
break;
}
- case Tegra::Shader::TextureType::TextureCube: {
- ASSERT_MSG(!is_array, "Unimplemented");
- std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
- std::string z = regs.GetRegisterAsFloat(instr.gpr20);
- coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ case 3: {
+ if (is_array) {
+ UNIMPLEMENTED_MSG("3-coordinate arrays not fully implemented");
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr20);
+ coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ texture_type = Tegra::Shader::TextureType::Texture2D;
+ is_array = false;
+ } else {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr20);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ }
break;
}
default:
- LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
- static_cast<u32>(texture_type));
+ LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
+ static_cast<u32>(num_coordinates));
UNREACHABLE();
// Fallback to interpreting as a 2D texture for now
@@ -2020,9 +2768,35 @@ private:
texture_type = Tegra::Shader::TextureType::Texture2D;
is_array = false;
}
- const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
- const std::string texture = "texture(" + sampler + ", coords)";
- WriteTexsInstruction(instr, coord, texture);
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, depth_compare);
+ std::string texture;
+ switch (instr.texs.GetTextureProcessMode()) {
+ case Tegra::Shader::TextureProcessMode::None: {
+ texture = "texture(" + sampler + ", coords)";
+ break;
+ }
+ case Tegra::Shader::TextureProcessMode::LZ: {
+ texture = "textureLod(" + sampler + ", coords, 0.0)";
+ break;
+ }
+ case Tegra::Shader::TextureProcessMode::LL: {
+ const std::string op_c = regs.GetRegisterAsFloat(instr.gpr20.Value() + 1);
+ texture = "textureLod(" + sampler + ", coords, " + op_c + ')';
+ break;
+ }
+ default: {
+ texture = "texture(" + sampler + ", coords)";
+ LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
+ static_cast<u32>(instr.texs.GetTextureProcessMode()));
+ UNREACHABLE();
+ }
+ }
+ if (!depth_compare) {
+ WriteTexsInstruction(instr, coord, texture);
+ } else {
+ WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
+ }
break;
}
case OpCode::Id::TLDS: {
@@ -2040,6 +2814,8 @@ private:
ASSERT_MSG(!instr.tlds.UsesMiscMode(Tegra::Shader::TextureMiscMode::MZ),
"MZ is not implemented");
+ u32 op_c_offset = 0;
+
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
@@ -2054,6 +2830,7 @@ private:
const std::string x = regs.GetRegisterAsInteger(instr.gpr8);
const std::string y = regs.GetRegisterAsInteger(instr.gpr20);
coord = "ivec2 coords = ivec2(" + x + ", " + y + ");";
+ op_c_offset = 1;
}
break;
}
@@ -2062,9 +2839,27 @@ private:
static_cast<u32>(texture_type));
UNREACHABLE();
}
-
- const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
- const std::string texture = "texelFetch(" + sampler + ", coords, 0)";
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, false);
+ std::string texture = "texelFetch(" + sampler + ", coords, 0)";
+ switch (instr.tlds.GetTextureProcessMode()) {
+ case Tegra::Shader::TextureProcessMode::LZ: {
+ texture = "texelFetch(" + sampler + ", coords, 0)";
+ break;
+ }
+ case Tegra::Shader::TextureProcessMode::LL: {
+ const std::string op_c =
+ regs.GetRegisterAsInteger(instr.gpr20.Value() + op_c_offset);
+ texture = "texelFetch(" + sampler + ", coords, " + op_c + ')';
+ break;
+ }
+ default: {
+ texture = "texelFetch(" + sampler + ", coords, 0)";
+ LOG_CRITICAL(HW_GPU, "Unhandled texture process mode {}",
+ static_cast<u32>(instr.tlds.GetTextureProcessMode()));
+ UNREACHABLE();
+ }
+ }
WriteTexsInstruction(instr, coord, texture);
break;
}
@@ -2077,28 +2872,43 @@ private:
"NODEP is not implemented");
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
"AOFFI is not implemented");
- ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
- "DC is not implemented");
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
"NDV is not implemented");
ASSERT_MSG(!instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::PTP),
"PTP is not implemented");
-
- switch (instr.tld4.texture_type) {
- case Tegra::Shader::TextureType::Texture2D: {
+ const bool depth_compare =
+ instr.tld4.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
+ auto texture_type = instr.tld4.texture_type.Value();
+ u32 num_coordinates = TextureCoordinates(texture_type);
+ if (depth_compare)
+ num_coordinates += 1;
+
+ switch (num_coordinates) {
+ case 2: {
const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
break;
}
+ case 3: {
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string z = regs.GetRegisterAsFloat(instr.gpr8.Value() + 2);
+ coord = "vec3 coords = vec3(" + x + ", " + y + ", " + z + ");";
+ break;
+ }
default:
- LOG_CRITICAL(HW_GPU, "Unhandled texture type {}",
- static_cast<u32>(instr.tld4.texture_type.Value()));
+ LOG_CRITICAL(HW_GPU, "Unhandled coordinates number {}",
+ static_cast<u32>(num_coordinates));
UNREACHABLE();
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec2 coords = vec2(" + x + ", " + y + ");";
+ texture_type = Tegra::Shader::TextureType::Texture2D;
}
const std::string sampler =
- GetSampler(instr.sampler, instr.tld4.texture_type, false);
+ GetSampler(instr.sampler, texture_type, false, depth_compare);
// Add an extra scope and declare the texture coords inside to prevent
// overwriting them in case they are used as outputs of the texs instruction.
shader.AddLine("{");
@@ -2106,15 +2916,18 @@ private:
shader.AddLine(coord);
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4.component) + ')';
-
- std::size_t dest_elem{};
- for (std::size_t elem = 0; elem < 4; ++elem) {
- if (!instr.tex.IsComponentEnabled(elem)) {
- // Skip disabled components
- continue;
+ if (!depth_compare) {
+ std::size_t dest_elem{};
+ for (std::size_t elem = 0; elem < 4; ++elem) {
+ if (!instr.tex.IsComponentEnabled(elem)) {
+ // Skip disabled components
+ continue;
+ }
+ regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
+ ++dest_elem;
}
- regs.SetRegisterToFloat(instr.gpr0, elem, texture, 1, 4, false, dest_elem);
- ++dest_elem;
+ } else {
+ regs.SetRegisterToFloat(instr.gpr0, 0, texture, 1, 1, false);
}
--shader.scope;
shader.AddLine("}");
@@ -2125,18 +2938,30 @@ private:
"NODEP is not implemented");
ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::AOFFI),
"AOFFI is not implemented");
- ASSERT_MSG(!instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC),
- "DC is not implemented");
+ const bool depth_compare =
+ instr.tld4s.UsesMiscMode(Tegra::Shader::TextureMiscMode::DC);
const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
const std::string op_b = regs.GetRegisterAsFloat(instr.gpr20);
// TODO(Subv): Figure out how the sampler type is encoded in the TLD4S instruction.
- const std::string sampler =
- GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
- const std::string coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
+ const std::string sampler = GetSampler(
+ instr.sampler, Tegra::Shader::TextureType::Texture2D, false, depth_compare);
+ std::string coord;
+ if (!depth_compare) {
+ coord = "vec2 coords = vec2(" + op_a + ", " + op_b + ");";
+ } else {
+ // Note: TLD4S coordinate encoding works just like TEXS's
+ const std::string op_c = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ coord = "vec3 coords = vec3(" + op_a + ", " + op_c + ", " + op_b + ");";
+ }
const std::string texture = "textureGather(" + sampler + ", coords, " +
std::to_string(instr.tld4s.component) + ')';
- WriteTexsInstruction(instr, coord, texture);
+
+ if (!depth_compare) {
+ WriteTexsInstruction(instr, coord, texture);
+ } else {
+ WriteTexsInstruction(instr, coord, "vec4(" + texture + ')');
+ }
break;
}
case OpCode::Id::TXQ: {
@@ -2147,7 +2972,7 @@ private:
// Sadly, not all texture instructions specify the type of texture their sampler
// uses. This must be fixed at a later instance.
const std::string sampler =
- GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false);
+ GetSampler(instr.sampler, Tegra::Shader::TextureType::Texture2D, false, false);
switch (instr.txq.query_type) {
case Tegra::Shader::TextureQueryType::Dimension: {
const std::string texture = "textureQueryLevels(" + sampler + ')';
@@ -2168,24 +2993,22 @@ private:
ASSERT_MSG(!instr.tmml.UsesMiscMode(Tegra::Shader::TextureMiscMode::NDV),
"NDV is not implemented");
- const std::string op_a = regs.GetRegisterAsFloat(instr.gpr8);
- const std::string op_b = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string x = regs.GetRegisterAsFloat(instr.gpr8);
const bool is_array = instr.tmml.array != 0;
auto texture_type = instr.tmml.texture_type.Value();
- const std::string sampler = GetSampler(instr.sampler, texture_type, is_array);
+ const std::string sampler =
+ GetSampler(instr.sampler, texture_type, is_array, false);
// TODO: add coordinates for different samplers once other texture types are
// implemented.
std::string coord;
switch (texture_type) {
case Tegra::Shader::TextureType::Texture1D: {
- std::string x = regs.GetRegisterAsFloat(instr.gpr8);
coord = "float coords = " + x + ';';
break;
}
case Tegra::Shader::TextureType::Texture2D: {
- std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
break;
}
@@ -2195,8 +3018,7 @@ private:
UNREACHABLE();
// Fallback to interpreting as a 2D texture for now
- std::string x = regs.GetRegisterAsFloat(instr.gpr8);
- std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
+ const std::string y = regs.GetRegisterAsFloat(instr.gpr8.Value() + 1);
coord = "vec2 coords = vec2(" + x + ", " + y + ");";
texture_type = Tegra::Shader::TextureType::Texture2D;
}
@@ -2216,27 +3038,20 @@ private:
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
case OpCode::Type::FloatSetPredicate: {
- std::string op_a = instr.fsetp.neg_a ? "-" : "";
- op_a += regs.GetRegisterAsFloat(instr.gpr8);
-
- if (instr.fsetp.abs_a) {
- op_a = "abs(" + op_a + ')';
- }
+ const std::string op_a =
+ GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), instr.fsetp.abs_a != 0,
+ instr.fsetp.neg_a != 0);
- std::string op_b{};
+ std::string op_b;
if (instr.is_b_imm) {
- if (instr.fsetp.neg_b) {
- // Only the immediate version of fsetp has a neg_b bit.
- op_b += '-';
- }
op_b += '(' + GetImmediate19(instr) + ')';
} else {
if (instr.is_b_gpr) {
@@ -2309,6 +3124,51 @@ private:
}
break;
}
+ case OpCode::Type::HalfSetPredicate: {
+ ASSERT_MSG(instr.hsetp2.ftz == 0, "Unimplemented");
+
+ const std::string op_a =
+ GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hsetp2.type_a,
+ instr.hsetp2.abs_a, instr.hsetp2.negate_a);
+
+ const std::string op_b = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HSETP2_R:
+ return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
+ instr.hsetp2.type_b, instr.hsetp2.abs_a,
+ instr.hsetp2.negate_b);
+ default:
+ UNREACHABLE();
+ return std::string("vec2(0)");
+ }
+ }();
+
+ // We can't use the constant predicate as destination.
+ ASSERT(instr.hsetp2.pred3 != static_cast<u64>(Pred::UnusedIndex));
+
+ const std::string second_pred =
+ GetPredicateCondition(instr.hsetp2.pred39, instr.hsetp2.neg_pred != 0);
+
+ const std::string combiner = GetPredicateCombiner(instr.hsetp2.op);
+
+ const std::string component_combiner = instr.hsetp2.h_and ? "&&" : "||";
+ const std::string predicate =
+ '(' + GetPredicateComparison(instr.hsetp2.cond, op_a + ".x", op_b + ".x") + ' ' +
+ component_combiner + ' ' +
+ GetPredicateComparison(instr.hsetp2.cond, op_a + ".y", op_b + ".y") + ')';
+
+ // Set the primary predicate to the result of Predicate OP SecondPredicate
+ SetPredicate(instr.hsetp2.pred3,
+ '(' + predicate + ") " + combiner + " (" + second_pred + ')');
+
+ if (instr.hsetp2.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
+ // Set the secondary predicate to the result of !Predicate OP SecondPredicate,
+ // if enabled
+ SetPredicate(instr.hsetp2.pred0,
+ "!(" + predicate + ") " + combiner + " (" + second_pred + ')');
+ }
+ break;
+ }
case OpCode::Type::PredicateSetRegister: {
const std::string op_a =
GetPredicateCondition(instr.pset.pred12, instr.pset.neg_pred12 != 0);
@@ -2331,10 +3191,15 @@ private:
regs.SetRegisterToFloat(instr.gpr0, 0, value, 1, 1);
}
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "PSET Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
+
break;
}
case OpCode::Type::PredicateSetPredicate: {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::PSETP: {
const std::string op_a =
GetPredicateCondition(instr.psetp.pred12, instr.psetp.neg_pred12 != 0);
@@ -2368,52 +3233,44 @@ private:
const std::string pred =
GetPredicateCondition(instr.csetp.pred39, instr.csetp.neg_pred39 != 0);
const std::string combiner = GetPredicateCombiner(instr.csetp.op);
- const std::string controlCode = regs.GetControlCode(instr.csetp.cc);
+ const std::string control_code = regs.GetControlCode(instr.csetp.cc);
if (instr.csetp.pred3 != static_cast<u64>(Pred::UnusedIndex)) {
SetPredicate(instr.csetp.pred3,
- '(' + controlCode + ") " + combiner + " (" + pred + ')');
+ '(' + control_code + ") " + combiner + " (" + pred + ')');
}
if (instr.csetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
SetPredicate(instr.csetp.pred0,
- "!(" + controlCode + ") " + combiner + " (" + pred + ')');
+ "!(" + control_code + ") " + combiner + " (" + pred + ')');
}
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled predicate instruction: {}",
+ opcode->get().GetName());
UNREACHABLE();
}
}
break;
}
case OpCode::Type::FloatSet: {
- std::string op_a = instr.fset.neg_a ? "-" : "";
- op_a += regs.GetRegisterAsFloat(instr.gpr8);
-
- if (instr.fset.abs_a) {
- op_a = "abs(" + op_a + ')';
- }
+ const std::string op_a = GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8),
+ instr.fset.abs_a != 0, instr.fset.neg_a != 0);
- std::string op_b = instr.fset.neg_b ? "-" : "";
+ std::string op_b;
if (instr.is_b_imm) {
const std::string imm = GetImmediate19(instr);
- if (instr.fset.neg_imm)
- op_b += "(-" + imm + ')';
- else
- op_b += imm;
+ op_b = imm;
} else {
if (instr.is_b_gpr) {
- op_b += regs.GetRegisterAsFloat(instr.gpr20);
+ op_b = regs.GetRegisterAsFloat(instr.gpr20);
} else {
- op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
- GLSLRegister::Type::Float);
+ op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
+ GLSLRegister::Type::Float);
}
}
- if (instr.fset.abs_b) {
- op_b = "abs(" + op_b + ')';
- }
+ op_b = GetOperandAbsNeg(op_b, instr.fset.abs_b != 0, instr.fset.neg_b != 0);
// The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the
// condition is true, and to 0 otherwise.
@@ -2469,6 +3326,50 @@ private:
}
break;
}
+ case OpCode::Type::HalfSet: {
+ ASSERT_MSG(instr.hset2.ftz == 0, "Unimplemented");
+
+ const std::string op_a =
+ GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr8, 0, false), instr.hset2.type_a,
+ instr.hset2.abs_a != 0, instr.hset2.negate_a != 0);
+
+ const std::string op_b = [&]() {
+ switch (opcode->get().GetId()) {
+ case OpCode::Id::HSET2_R:
+ return GetHalfFloat(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
+ instr.hset2.type_b, instr.hset2.abs_b != 0,
+ instr.hset2.negate_b != 0);
+ default:
+ UNREACHABLE();
+ return std::string("vec2(0)");
+ }
+ }();
+
+ const std::string second_pred =
+ GetPredicateCondition(instr.hset2.pred39, instr.hset2.neg_pred != 0);
+
+ const std::string combiner = GetPredicateCombiner(instr.hset2.op);
+
+ // HSET2 operates on each half float in the pack.
+ std::string result;
+ for (int i = 0; i < 2; ++i) {
+ const std::string float_value = i == 0 ? "0x00003c00" : "0x3c000000";
+ const std::string integer_value = i == 0 ? "0x0000ffff" : "0xffff0000";
+ const std::string value = instr.hset2.bf == 1 ? float_value : integer_value;
+
+ const std::string comp = std::string(".") + "xy"[i];
+ const std::string predicate =
+ "((" + GetPredicateComparison(instr.hset2.cond, op_a + comp, op_b + comp) +
+ ") " + combiner + " (" + second_pred + "))";
+
+ result += '(' + predicate + " ? " + value + " : 0)";
+ if (i == 0) {
+ result += " | ";
+ }
+ }
+ regs.SetRegisterToInteger(instr.gpr0, false, 0, '(' + result + ')', 1, 1);
+ break;
+ }
case OpCode::Type::Xmad: {
ASSERT_MSG(!instr.xmad.sign_a, "Unimplemented");
ASSERT_MSG(!instr.xmad.sign_b, "Unimplemented");
@@ -2482,7 +3383,7 @@ private:
const bool is_signed{instr.xmad.sign_a == 1};
bool is_merge{};
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::XMAD_CR: {
is_merge = instr.xmad.merge_56;
op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset,
@@ -2511,7 +3412,7 @@ private:
break;
}
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled XMAD instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
@@ -2560,15 +3461,25 @@ private:
}
regs.SetRegisterToInteger(instr.gpr0, is_signed, 0, sum, 1, 1);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "XMAD Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
break;
}
default: {
- switch (opcode->GetId()) {
+ switch (opcode->get().GetId()) {
case OpCode::Id::EXIT: {
if (stage == Maxwell3D::Regs::ShaderStage::Fragment) {
EmitFragmentOutputsWrite();
}
+ const Tegra::Shader::ControlCode cc = instr.flow_control_code;
+ if (cc != Tegra::Shader::ControlCode::T) {
+ LOG_CRITICAL(HW_GPU, "EXIT Control Code used: {}", static_cast<u32>(cc));
+ UNREACHABLE();
+ }
+
switch (instr.flow.cond) {
case Tegra::Shader::FlowCondition::Always:
shader.AddLine("return true;");
@@ -2598,6 +3509,11 @@ private:
// Enclose "discard" in a conditional, so that GLSL compilation does not complain
// about unexecuted instructions that may follow this.
+ const Tegra::Shader::ControlCode cc = instr.flow_control_code;
+ if (cc != Tegra::Shader::ControlCode::T) {
+ LOG_CRITICAL(HW_GPU, "KIL Control Code used: {}", static_cast<u32>(cc));
+ UNREACHABLE();
+ }
shader.AddLine("if (true) {");
++shader.scope;
shader.AddLine("discard;");
@@ -2606,9 +3522,60 @@ private:
break;
}
+ case OpCode::Id::OUT_R: {
+ ASSERT(instr.gpr20.Value() == Register::ZeroIndex);
+ ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
+ "OUT is expected to be used in a geometry shader.");
+
+ if (instr.out.emit) {
+ // gpr0 is used to store the next address. Hardware returns a pointer but
+ // we just return the next index with a cyclic cap.
+ const std::string current{regs.GetRegisterAsInteger(instr.gpr8, 0, false)};
+ const std::string next = "((" + current + " + 1" + ") % " +
+ std::to_string(MAX_GEOMETRY_BUFFERS) + ')';
+ shader.AddLine("emit_vertex(" + current + ");");
+ regs.SetRegisterToInteger(instr.gpr0, false, 0, next, 1, 1);
+ }
+ if (instr.out.cut) {
+ shader.AddLine("EndPrimitive();");
+ }
+
+ break;
+ }
+ case OpCode::Id::MOV_SYS: {
+ switch (instr.sys20) {
+ case Tegra::Shader::SystemVariable::InvocationInfo: {
+ LOG_WARNING(HW_GPU, "MOV_SYS instruction with InvocationInfo is incomplete");
+ regs.SetRegisterToInteger(instr.gpr0, false, 0, "0u", 1, 1);
+ break;
+ }
+ default: {
+ LOG_CRITICAL(HW_GPU, "Unhandled system move: {}",
+ static_cast<u32>(instr.sys20.Value()));
+ UNREACHABLE();
+ }
+ }
+ break;
+ }
+ case OpCode::Id::ISBERD: {
+ ASSERT(instr.isberd.o == 0);
+ ASSERT(instr.isberd.skew == 0);
+ ASSERT(instr.isberd.shift == Tegra::Shader::IsberdShift::None);
+ ASSERT(instr.isberd.mode == Tegra::Shader::IsberdMode::None);
+ ASSERT_MSG(stage == Maxwell3D::Regs::ShaderStage::Geometry,
+ "ISBERD is expected to be used in a geometry shader.");
+ LOG_WARNING(HW_GPU, "ISBERD instruction is incomplete");
+ regs.SetRegisterToFloat(instr.gpr0, 0, regs.GetRegisterAsFloat(instr.gpr8), 1, 1);
+ break;
+ }
case OpCode::Id::BRA: {
ASSERT_MSG(instr.bra.constant_buffer == 0,
"BRA with constant buffers are not implemented");
+ const Tegra::Shader::ControlCode cc = instr.flow_control_code;
+ if (cc != Tegra::Shader::ControlCode::T) {
+ LOG_CRITICAL(HW_GPU, "BRA Control Code used: {}", static_cast<u32>(cc));
+ UNREACHABLE();
+ }
const u32 target = offset + instr.bra.GetBranchTarget();
shader.AddLine("{ jmp_to = " + std::to_string(target) + "u; break; }");
break;
@@ -2631,16 +3598,40 @@ private:
// The SSY opcode tells the GPU where to re-converge divergent execution paths, it
// sets the target of the jump that the SYNC instruction will make. The SSY opcode
// has a similar structure to the BRA opcode.
- ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported");
+ ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported");
+
+ const u32 target = offset + instr.bra.GetBranchTarget();
+ EmitPushToFlowStack(target);
+ break;
+ }
+ case OpCode::Id::PBK: {
+ // PBK pushes to a stack the address where BRK will jump to. This shares stack with
+ // SSY but using SYNC on a PBK address will kill the shader execution. We don't
+ // emulate this because it's very unlikely a driver will emit such invalid shader.
+ ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported");
const u32 target = offset + instr.bra.GetBranchTarget();
- EmitPushToSSYStack(target);
+ EmitPushToFlowStack(target);
break;
}
case OpCode::Id::SYNC: {
// The SYNC opcode jumps to the address previously set by the SSY opcode
- ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always);
- EmitPopFromSSYStack();
+ const Tegra::Shader::ControlCode cc = instr.flow_control_code;
+ if (cc != Tegra::Shader::ControlCode::T) {
+ LOG_CRITICAL(HW_GPU, "SYNC Control Code used: {}", static_cast<u32>(cc));
+ UNREACHABLE();
+ }
+ EmitPopFromFlowStack();
+ break;
+ }
+ case OpCode::Id::BRK: {
+ // The BRK opcode jumps to the address previously set by the PBK opcode
+ const Tegra::Shader::ControlCode cc = instr.flow_control_code;
+ if (cc != Tegra::Shader::ControlCode::T) {
+ LOG_CRITICAL(HW_GPU, "BRK Control Code used: {}", static_cast<u32>(cc));
+ UNREACHABLE();
+ }
+ EmitPopFromFlowStack();
break;
}
case OpCode::Id::DEPBAR: {
@@ -2649,8 +3640,59 @@ private:
LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed");
break;
}
+ case OpCode::Id::VMAD: {
+ const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1;
+ const std::string op_a = GetVideoOperandA(instr);
+ const std::string op_b = GetVideoOperandB(instr);
+ const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed);
+
+ std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
+
+ switch (instr.vmad.shr) {
+ case Tegra::Shader::VmadShr::Shr7:
+ result = '(' + result + " >> 7)";
+ break;
+ case Tegra::Shader::VmadShr::Shr15:
+ result = '(' + result + " >> 15)";
+ break;
+ }
+
+ regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1,
+ instr.vmad.saturate == 1, 0, Register::Size::Word,
+ instr.vmad.cc);
+ if (instr.generates_cc) {
+ LOG_CRITICAL(HW_GPU, "VMAD Generates an unhandled Control Code");
+ UNREACHABLE();
+ }
+
+ break;
+ }
+ case OpCode::Id::VSETP: {
+ const std::string op_a = GetVideoOperandA(instr);
+ const std::string op_b = GetVideoOperandB(instr);
+
+ // We can't use the constant predicate as destination.
+ ASSERT(instr.vsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
+
+ const std::string second_pred = GetPredicateCondition(instr.vsetp.pred39, false);
+
+ const std::string combiner = GetPredicateCombiner(instr.vsetp.op);
+
+ const std::string predicate = GetPredicateComparison(instr.vsetp.cond, op_a, op_b);
+ // Set the primary predicate to the result of Predicate OP SecondPredicate
+ SetPredicate(instr.vsetp.pred3,
+ '(' + predicate + ") " + combiner + " (" + second_pred + ')');
+
+ if (instr.vsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) {
+ // Set the secondary predicate to the result of !Predicate OP SecondPredicate,
+ // if enabled
+ SetPredicate(instr.vsetp.pred0,
+ "!(" + predicate + ") " + combiner + " (" + second_pred + ')');
+ }
+ break;
+ }
default: {
- LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
+ LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->get().GetName());
UNREACHABLE();
}
}
@@ -2712,11 +3754,11 @@ private:
labels.insert(subroutine.begin);
shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;");
- // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems
- // unlikely that shaders will use 20 nested SSYs.
- constexpr u32 SSY_STACK_SIZE = 20;
- shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];");
- shader.AddLine("uint ssy_stack_top = 0u;");
+ // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
+ // unlikely that shaders will use 20 nested SSYs and PBKs.
+ constexpr u32 FLOW_STACK_SIZE = 20;
+ shader.AddLine("uint flow_stack[" + std::to_string(FLOW_STACK_SIZE) + "];");
+ shader.AddLine("uint flow_stack_top = 0u;");
shader.AddLine("while (true) {");
++shader.scope;
@@ -2776,23 +3818,24 @@ private:
const u32 main_offset;
Maxwell3D::Regs::ShaderStage stage;
const std::string& suffix;
+ u64 local_memory_size;
ShaderWriter shader;
ShaderWriter declarations;
- GLSLRegisterManager regs{shader, declarations, stage, suffix};
+ GLSLRegisterManager regs{shader, declarations, stage, suffix, header};
// Declarations
std::set<std::string> declr_predicates;
-}; // namespace Decompiler
+}; // namespace OpenGL::GLShader::Decompiler
std::string GetCommonDeclarations() {
return fmt::format("#define MAX_CONSTBUFFER_ELEMENTS {}\n",
RasterizerOpenGL::MaxConstbufferSize / sizeof(GLvec4));
}
-boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
- Maxwell3D::Regs::ShaderStage stage,
- const std::string& suffix) {
+std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+ Maxwell3D::Regs::ShaderStage stage,
+ const std::string& suffix) {
try {
const auto subroutines =
ControlFlowAnalyzer(program_code, main_offset, suffix).GetSubroutines();
@@ -2801,7 +3844,7 @@ boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code,
} catch (const DecompileFail& exception) {
LOG_ERROR(HW_GPU, "Shader decompilation failed: {}", exception.what());
}
- return boost::none;
+ return {};
}
} // namespace OpenGL::GLShader::Decompiler
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.h b/src/video_core/renderer_opengl/gl_shader_decompiler.h
index b20cc4bfa..d01a4a7ee 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.h
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.h
@@ -6,8 +6,8 @@
#include <array>
#include <functional>
+#include <optional>
#include <string>
-#include <boost/optional.hpp>
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_opengl/gl_shader_gen.h"
@@ -18,8 +18,8 @@ using Tegra::Engines::Maxwell3D;
std::string GetCommonDeclarations();
-boost::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
- Maxwell3D::Regs::ShaderStage stage,
- const std::string& suffix);
+std::optional<ProgramResult> DecompileProgram(const ProgramCode& program_code, u32 main_offset,
+ Maxwell3D::Regs::ShaderStage stage,
+ const std::string& suffix);
} // namespace OpenGL::GLShader::Decompiler
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index b0466c18f..9d17edd63 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -17,7 +17,18 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
std::string out = "#version 430 core\n";
out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
out += Decompiler::GetCommonDeclarations();
- out += "bool exec_vertex();\n";
+
+ out += R"(
+
+layout (location = 0) out vec4 position;
+
+layout(std140) uniform vs_config {
+ vec4 viewport_flip;
+ uvec4 instance_id;
+ uvec4 flip_stage;
+ uvec4 alpha_test;
+};
+)";
if (setup.IsDualProgram()) {
out += "bool exec_vertex_b();\n";
@@ -26,20 +37,19 @@ ProgramResult GenerateVertexShader(const ShaderSetup& setup) {
ProgramResult program =
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
Maxwell3D::Regs::ShaderStage::Vertex, "vertex")
- .get_value_or({});
+ .value_or(ProgramResult());
- out += R"(
-
-out gl_PerVertex {
- vec4 gl_Position;
-};
+ out += program.first;
-out vec4 position;
+ if (setup.IsDualProgram()) {
+ ProgramResult program_b =
+ Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
+ Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
+ .value_or(ProgramResult());
+ out += program_b.first;
+ }
-layout (std140) uniform vs_config {
- vec4 viewport_flip;
- uvec4 instance_id;
-};
+ out += R"(
void main() {
position = vec4(0.0, 0.0, 0.0, 0.0);
@@ -52,27 +62,56 @@ void main() {
out += R"(
- // Viewport can be flipped, which is unsupported by glViewport
- position.xy *= viewport_flip.xy;
+ // Check if the flip stage is VertexB
+ if (flip_stage[0] == 1) {
+ // Viewport can be flipped, which is unsupported by glViewport
+ position.xy *= viewport_flip.xy;
+ }
gl_Position = position;
// TODO(bunnei): This is likely a hack, position.w should be interpolated as 1.0
// For now, this is here to bring order in lieu of proper emulation
- position.w = 1.0;
+ if (flip_stage[0] == 1) {
+ position.w = 1.0;
+ }
}
)";
- out += program.first;
+ return {out, program.second};
+}
- if (setup.IsDualProgram()) {
- ProgramResult program_b =
- Decompiler::DecompileProgram(setup.program.code_b, PROGRAM_OFFSET,
- Maxwell3D::Regs::ShaderStage::Vertex, "vertex_b")
- .get_value_or({});
- out += program_b.first;
- }
+ProgramResult GenerateGeometryShader(const ShaderSetup& setup) {
+ std::string out = "#version 430 core\n";
+ out += "#extension GL_ARB_separate_shader_objects : enable\n\n";
+ out += Decompiler::GetCommonDeclarations();
+ out += "bool exec_geometry();\n";
+
+ ProgramResult program =
+ Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
+ Maxwell3D::Regs::ShaderStage::Geometry, "geometry")
+ .value_or(ProgramResult());
+ out += R"(
+out gl_PerVertex {
+ vec4 gl_Position;
+};
+layout (location = 0) in vec4 gs_position[];
+layout (location = 0) out vec4 position;
+
+layout (std140) uniform gs_config {
+ vec4 viewport_flip;
+ uvec4 instance_id;
+ uvec4 flip_stage;
+ uvec4 alpha_test;
+};
+
+void main() {
+ exec_geometry();
+}
+
+)";
+ out += program.first;
return {out, program.second};
}
@@ -85,9 +124,8 @@ ProgramResult GenerateFragmentShader(const ShaderSetup& setup) {
ProgramResult program =
Decompiler::DecompileProgram(setup.program.code, PROGRAM_OFFSET,
Maxwell3D::Regs::ShaderStage::Fragment, "fragment")
- .get_value_or({});
+ .value_or(ProgramResult());
out += R"(
-in vec4 position;
layout(location = 0) out vec4 FragColor0;
layout(location = 1) out vec4 FragColor1;
layout(location = 2) out vec4 FragColor2;
@@ -97,11 +135,39 @@ layout(location = 5) out vec4 FragColor5;
layout(location = 6) out vec4 FragColor6;
layout(location = 7) out vec4 FragColor7;
+layout (location = 0) in vec4 position;
+
layout (std140) uniform fs_config {
vec4 viewport_flip;
uvec4 instance_id;
+ uvec4 flip_stage;
+ uvec4 alpha_test;
};
+bool AlphaFunc(in float value) {
+ float ref = uintBitsToFloat(alpha_test[2]);
+ switch (alpha_test[1]) {
+ case 1:
+ return false;
+ case 2:
+ return value < ref;
+ case 3:
+ return value == ref;
+ case 4:
+ return value <= ref;
+ case 5:
+ return value > ref;
+ case 6:
+ return value != ref;
+ case 7:
+ return value >= ref;
+ case 8:
+ return true;
+ default:
+ return false;
+ }
+}
+
void main() {
exec_fragment();
}
@@ -110,5 +176,4 @@ void main() {
out += program.first;
return {out, program.second};
}
-
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index d53b93ad5..520b9d4e3 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -16,6 +16,8 @@ namespace OpenGL::GLShader {
constexpr std::size_t MAX_PROGRAM_CODE_LENGTH{0x1000};
using ProgramCode = std::vector<u64>;
+enum : u32 { POSITION_VARYING_LOCATION = 0, GENERIC_VARYING_START_LOCATION = 1 };
+
class ConstBufferEntry {
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
@@ -75,8 +77,9 @@ class SamplerEntry {
public:
SamplerEntry(Maxwell::ShaderStage stage, std::size_t offset, std::size_t index,
- Tegra::Shader::TextureType type, bool is_array)
- : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array) {}
+ Tegra::Shader::TextureType type, bool is_array, bool is_shadow)
+ : offset(offset), stage(stage), sampler_index(index), type(type), is_array(is_array),
+ is_shadow(is_shadow) {}
std::size_t GetOffset() const {
return offset;
@@ -117,6 +120,8 @@ public:
}
if (is_array)
glsl_type += "Array";
+ if (is_shadow)
+ glsl_type += "Shadow";
return glsl_type;
}
@@ -128,6 +133,10 @@ public:
return is_array;
}
+ bool IsShadow() const {
+ return is_shadow;
+ }
+
u32 GetHash() const {
return (static_cast<u32>(stage) << 16) | static_cast<u32>(sampler_index);
}
@@ -147,7 +156,8 @@ private:
Maxwell::ShaderStage stage; ///< Shader stage where this sampler was used.
std::size_t sampler_index; ///< Value used to index into the generated GLSL sampler array.
Tegra::Shader::TextureType type; ///< The type used to sample this texture (Texture2D, etc)
- bool is_array; ///< Whether the texture is being sampled as an array texture or not.
+ bool is_array; ///< Whether the texture is being sampled as an array texture or not.
+ bool is_shadow; ///< Whether the texture is being sampled as a depth texture or not.
};
struct ShaderEntries {
@@ -188,6 +198,12 @@ private:
ProgramResult GenerateVertexShader(const ShaderSetup& setup);
/**
+ * Generates the GLSL geometry shader program source code for the given GS program
+ * @returns String of the shader source code
+ */
+ProgramResult GenerateGeometryShader(const ShaderSetup& setup);
+
+/**
* Generates the GLSL fragment shader program source code for the given FS program
* @returns String of the shader source code
*/
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp
index 022d32a86..8b8869ecb 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp
@@ -16,8 +16,27 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh
viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f;
viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f;
+ u32 func = static_cast<u32>(regs.alpha_test_func);
+ // Normalize the gl variants of opCompare to be the same as the normal variants
+ u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never);
+ if (func >= op_gl_variant_base) {
+ func = func - op_gl_variant_base + 1U;
+ }
+
+ alpha_test.enabled = regs.alpha_test_enabled;
+ alpha_test.func = func;
+ alpha_test.ref = regs.alpha_test_ref;
+
// We only assign the instance to the first component of the vector, the rest is just padding.
instance_id[0] = state.current_instance;
+
+ // Assign in which stage the position has to be flipped
+ // (the last stage before the fragment shader).
+ if (gpu.regs.shader_config[static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry)].enable) {
+ flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::Geometry);
+ } else {
+ flip_stage[0] = static_cast<u32>(Maxwell3D::Regs::ShaderProgram::VertexB);
+ }
}
} // namespace OpenGL::GLShader
diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h
index 3de15ba9b..36fe1f04c 100644
--- a/src/video_core/renderer_opengl/gl_shader_manager.h
+++ b/src/video_core/renderer_opengl/gl_shader_manager.h
@@ -21,8 +21,15 @@ struct MaxwellUniformData {
void SetFromRegs(const Maxwell3D::State::ShaderStageInfo& shader_stage);
alignas(16) GLvec4 viewport_flip;
alignas(16) GLuvec4 instance_id;
+ alignas(16) GLuvec4 flip_stage;
+ struct alignas(16) {
+ GLuint enabled;
+ GLuint func;
+ GLfloat ref;
+ GLuint padding;
+ } alpha_test;
};
-static_assert(sizeof(MaxwellUniformData) == 32, "MaxwellUniformData structure size is incorrect");
+static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect");
static_assert(sizeof(MaxwellUniformData) < 16384,
"MaxwellUniformData structure must be less than 16kb as per the OpenGL spec");
@@ -36,6 +43,10 @@ public:
vs = program;
}
+ void UseProgrammableGeometryShader(GLuint program) {
+ gs = program;
+ }
+
void UseProgrammableFragmentShader(GLuint program) {
fs = program;
}
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 1fe26a2a9..b6b426f34 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -11,9 +11,10 @@
namespace OpenGL {
OpenGLState OpenGLState::cur_state;
-
+bool OpenGLState::s_rgb_used;
OpenGLState::OpenGLState() {
// These all match default OpenGL values
+ framebuffer_srgb.enabled = false;
cull.enabled = false;
cull.mode = GL_BACK;
cull.front_face = GL_CCW;
@@ -21,6 +22,11 @@ OpenGLState::OpenGLState() {
depth.test_enabled = false;
depth.test_func = GL_LESS;
depth.write_mask = GL_TRUE;
+ depth.depth_range_near = 0.0f;
+ depth.depth_range_far = 1.0f;
+
+ primitive_restart.enabled = false;
+ primitive_restart.index = 0;
color_mask.red_enabled = GL_TRUE;
color_mask.green_enabled = GL_TRUE;
@@ -83,79 +89,144 @@ OpenGLState::OpenGLState() {
point.size = 1;
}
-void OpenGLState::Apply() const {
+void OpenGLState::ApplyDefaultState() {
+ glDisable(GL_FRAMEBUFFER_SRGB);
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ glDisable(GL_PRIMITIVE_RESTART);
+ glDisable(GL_STENCIL_TEST);
+ glEnable(GL_BLEND);
+ glDisable(GL_COLOR_LOGIC_OP);
+ glDisable(GL_SCISSOR_TEST);
+}
+
+void OpenGLState::ApplySRgb() const {
+ // sRGB
+ if (framebuffer_srgb.enabled != cur_state.framebuffer_srgb.enabled) {
+ if (framebuffer_srgb.enabled) {
+ // Track if sRGB is used
+ s_rgb_used = true;
+ glEnable(GL_FRAMEBUFFER_SRGB);
+ } else {
+ glDisable(GL_FRAMEBUFFER_SRGB);
+ }
+ }
+}
+
+void OpenGLState::ApplyCulling() const {
// Culling
- if (cull.enabled != cur_state.cull.enabled) {
+ const bool cull_changed = cull.enabled != cur_state.cull.enabled;
+ if (cull_changed) {
if (cull.enabled) {
glEnable(GL_CULL_FACE);
} else {
glDisable(GL_CULL_FACE);
}
}
+ if (cull.enabled) {
+ if (cull_changed || cull.mode != cur_state.cull.mode) {
+ glCullFace(cull.mode);
+ }
- if (cull.mode != cur_state.cull.mode) {
- glCullFace(cull.mode);
- }
-
- if (cull.front_face != cur_state.cull.front_face) {
- glFrontFace(cull.front_face);
+ if (cull_changed || cull.front_face != cur_state.cull.front_face) {
+ glFrontFace(cull.front_face);
+ }
}
+}
+void OpenGLState::ApplyDepth() const {
// Depth test
- if (depth.test_enabled != cur_state.depth.test_enabled) {
+ const bool depth_test_changed = depth.test_enabled != cur_state.depth.test_enabled;
+ if (depth_test_changed) {
if (depth.test_enabled) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
}
-
- if (depth.test_func != cur_state.depth.test_func) {
+ if (depth.test_enabled &&
+ (depth_test_changed || depth.test_func != cur_state.depth.test_func)) {
glDepthFunc(depth.test_func);
}
-
// Depth mask
if (depth.write_mask != cur_state.depth.write_mask) {
glDepthMask(depth.write_mask);
}
+ // Depth range
+ if (depth.depth_range_near != cur_state.depth.depth_range_near ||
+ depth.depth_range_far != cur_state.depth.depth_range_far) {
+ glDepthRange(depth.depth_range_near, depth.depth_range_far);
+ }
+}
- // Color mask
- if (color_mask.red_enabled != cur_state.color_mask.red_enabled ||
- color_mask.green_enabled != cur_state.color_mask.green_enabled ||
- color_mask.blue_enabled != cur_state.color_mask.blue_enabled ||
- color_mask.alpha_enabled != cur_state.color_mask.alpha_enabled) {
- glColorMask(color_mask.red_enabled, color_mask.green_enabled, color_mask.blue_enabled,
- color_mask.alpha_enabled);
+void OpenGLState::ApplyPrimitiveRestart() const {
+ const bool primitive_restart_changed =
+ primitive_restart.enabled != cur_state.primitive_restart.enabled;
+ if (primitive_restart_changed) {
+ if (primitive_restart.enabled) {
+ glEnable(GL_PRIMITIVE_RESTART);
+ } else {
+ glDisable(GL_PRIMITIVE_RESTART);
+ }
+ }
+ if (primitive_restart_changed ||
+ (primitive_restart.enabled &&
+ primitive_restart.index != cur_state.primitive_restart.index)) {
+ glPrimitiveRestartIndex(primitive_restart.index);
}
+}
- // Stencil test
- if (stencil.test_enabled != cur_state.stencil.test_enabled) {
+void OpenGLState::ApplyStencilTest() const {
+ const bool stencil_test_changed = stencil.test_enabled != cur_state.stencil.test_enabled;
+ if (stencil_test_changed) {
if (stencil.test_enabled) {
glEnable(GL_STENCIL_TEST);
} else {
glDisable(GL_STENCIL_TEST);
}
}
- auto config_stencil = [](GLenum face, const auto& config, const auto& prev_config) {
- if (config.test_func != prev_config.test_func || config.test_ref != prev_config.test_ref ||
- config.test_mask != prev_config.test_mask) {
- glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
- }
- if (config.action_depth_fail != prev_config.action_depth_fail ||
- config.action_depth_pass != prev_config.action_depth_pass ||
- config.action_stencil_fail != prev_config.action_stencil_fail) {
- glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
- config.action_depth_pass);
- }
- if (config.write_mask != prev_config.write_mask) {
- glStencilMaskSeparate(face, config.write_mask);
+ if (stencil.test_enabled) {
+ auto config_stencil = [stencil_test_changed](GLenum face, const auto& config,
+ const auto& prev_config) {
+ if (stencil_test_changed || config.test_func != prev_config.test_func ||
+ config.test_ref != prev_config.test_ref ||
+ config.test_mask != prev_config.test_mask) {
+ glStencilFuncSeparate(face, config.test_func, config.test_ref, config.test_mask);
+ }
+ if (stencil_test_changed || config.action_depth_fail != prev_config.action_depth_fail ||
+ config.action_depth_pass != prev_config.action_depth_pass ||
+ config.action_stencil_fail != prev_config.action_stencil_fail) {
+ glStencilOpSeparate(face, config.action_stencil_fail, config.action_depth_fail,
+ config.action_depth_pass);
+ }
+ if (config.write_mask != prev_config.write_mask) {
+ glStencilMaskSeparate(face, config.write_mask);
+ }
+ };
+ config_stencil(GL_FRONT, stencil.front, cur_state.stencil.front);
+ config_stencil(GL_BACK, stencil.back, cur_state.stencil.back);
+ }
+}
+
+void OpenGLState::ApplyScissorTest() const {
+ const bool scissor_changed = scissor.enabled != cur_state.scissor.enabled;
+ if (scissor_changed) {
+ if (scissor.enabled) {
+ glEnable(GL_SCISSOR_TEST);
+ } else {
+ glDisable(GL_SCISSOR_TEST);
}
- };
- config_stencil(GL_FRONT, stencil.front, cur_state.stencil.front);
- config_stencil(GL_BACK, stencil.back, cur_state.stencil.back);
+ }
+ if (scissor_changed || scissor_changed || scissor.x != cur_state.scissor.x ||
+ scissor.y != cur_state.scissor.y || scissor.width != cur_state.scissor.width ||
+ scissor.height != cur_state.scissor.height) {
+ glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
+ }
+}
- // Blending
- if (blend.enabled != cur_state.blend.enabled) {
+void OpenGLState::ApplyBlending() const {
+ const bool blend_changed = blend.enabled != cur_state.blend.enabled;
+ if (blend_changed) {
if (blend.enabled) {
ASSERT(!logic_op.enabled);
glEnable(GL_BLEND);
@@ -163,29 +234,32 @@ void OpenGLState::Apply() const {
glDisable(GL_BLEND);
}
}
+ if (blend.enabled) {
+ if (blend_changed || blend.color.red != cur_state.blend.color.red ||
+ blend.color.green != cur_state.blend.color.green ||
+ blend.color.blue != cur_state.blend.color.blue ||
+ blend.color.alpha != cur_state.blend.color.alpha) {
+ glBlendColor(blend.color.red, blend.color.green, blend.color.blue, blend.color.alpha);
+ }
- if (blend.color.red != cur_state.blend.color.red ||
- blend.color.green != cur_state.blend.color.green ||
- blend.color.blue != cur_state.blend.color.blue ||
- blend.color.alpha != cur_state.blend.color.alpha) {
- glBlendColor(blend.color.red, blend.color.green, blend.color.blue, blend.color.alpha);
- }
-
- if (blend.src_rgb_func != cur_state.blend.src_rgb_func ||
- blend.dst_rgb_func != cur_state.blend.dst_rgb_func ||
- blend.src_a_func != cur_state.blend.src_a_func ||
- blend.dst_a_func != cur_state.blend.dst_a_func) {
- glBlendFuncSeparate(blend.src_rgb_func, blend.dst_rgb_func, blend.src_a_func,
- blend.dst_a_func);
- }
+ if (blend_changed || blend.src_rgb_func != cur_state.blend.src_rgb_func ||
+ blend.dst_rgb_func != cur_state.blend.dst_rgb_func ||
+ blend.src_a_func != cur_state.blend.src_a_func ||
+ blend.dst_a_func != cur_state.blend.dst_a_func) {
+ glBlendFuncSeparate(blend.src_rgb_func, blend.dst_rgb_func, blend.src_a_func,
+ blend.dst_a_func);
+ }
- if (blend.rgb_equation != cur_state.blend.rgb_equation ||
- blend.a_equation != cur_state.blend.a_equation) {
- glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
+ if (blend_changed || blend.rgb_equation != cur_state.blend.rgb_equation ||
+ blend.a_equation != cur_state.blend.a_equation) {
+ glBlendEquationSeparate(blend.rgb_equation, blend.a_equation);
+ }
}
+}
- // Logic Operation
- if (logic_op.enabled != cur_state.logic_op.enabled) {
+void OpenGLState::ApplyLogicOp() const {
+ const bool logic_op_changed = logic_op.enabled != cur_state.logic_op.enabled;
+ if (logic_op_changed) {
if (logic_op.enabled) {
ASSERT(!blend.enabled);
glEnable(GL_COLOR_LOGIC_OP);
@@ -194,11 +268,13 @@ void OpenGLState::Apply() const {
}
}
- if (logic_op.operation != cur_state.logic_op.operation) {
+ if (logic_op.enabled &&
+ (logic_op_changed || logic_op.operation != cur_state.logic_op.operation)) {
glLogicOp(logic_op.operation);
}
+}
- // Textures
+void OpenGLState::ApplyTextures() const {
for (std::size_t i = 0; i < std::size(texture_units); ++i) {
const auto& texture_unit = texture_units[i];
const auto& cur_state_texture_unit = cur_state.texture_units[i];
@@ -217,28 +293,29 @@ void OpenGLState::Apply() const {
glTexParameteriv(texture_unit.target, GL_TEXTURE_SWIZZLE_RGBA, mask.data());
}
}
+}
- // Samplers
- {
- bool has_delta{};
- std::size_t first{}, last{};
- std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers;
- for (std::size_t i = 0; i < std::size(samplers); ++i) {
- samplers[i] = texture_units[i].sampler;
- if (samplers[i] != cur_state.texture_units[i].sampler) {
- if (!has_delta) {
- first = i;
- has_delta = true;
- }
- last = i;
+void OpenGLState::ApplySamplers() const {
+ bool has_delta{};
+ std::size_t first{}, last{};
+ std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers;
+ for (std::size_t i = 0; i < std::size(samplers); ++i) {
+ samplers[i] = texture_units[i].sampler;
+ if (samplers[i] != cur_state.texture_units[i].sampler) {
+ if (!has_delta) {
+ first = i;
+ has_delta = true;
}
- }
- if (has_delta) {
- glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
- samplers.data());
+ last = i;
}
}
+ if (has_delta) {
+ glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
+ samplers.data());
+ }
+}
+void OpenGLState::Apply() const {
// Framebuffer
if (draw.read_framebuffer != cur_state.draw.read_framebuffer) {
glBindFramebuffer(GL_READ_FRAMEBUFFER, draw.read_framebuffer);
@@ -271,27 +348,12 @@ void OpenGLState::Apply() const {
if (draw.program_pipeline != cur_state.draw.program_pipeline) {
glBindProgramPipeline(draw.program_pipeline);
}
-
- // Scissor test
- if (scissor.enabled != cur_state.scissor.enabled) {
- if (scissor.enabled) {
- glEnable(GL_SCISSOR_TEST);
- } else {
- glDisable(GL_SCISSOR_TEST);
- }
- }
-
- if (scissor.x != cur_state.scissor.x || scissor.y != cur_state.scissor.y ||
- scissor.width != cur_state.scissor.width || scissor.height != cur_state.scissor.height) {
- glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
- }
-
+ // Viewport
if (viewport.x != cur_state.viewport.x || viewport.y != cur_state.viewport.y ||
viewport.width != cur_state.viewport.width ||
viewport.height != cur_state.viewport.height) {
glViewport(viewport.x, viewport.y, viewport.width, viewport.height);
}
-
// Clip distance
for (std::size_t i = 0; i < clip_distance.size(); ++i) {
if (clip_distance[i] != cur_state.clip_distance[i]) {
@@ -302,12 +364,28 @@ void OpenGLState::Apply() const {
}
}
}
-
+ // Color mask
+ if (color_mask.red_enabled != cur_state.color_mask.red_enabled ||
+ color_mask.green_enabled != cur_state.color_mask.green_enabled ||
+ color_mask.blue_enabled != cur_state.color_mask.blue_enabled ||
+ color_mask.alpha_enabled != cur_state.color_mask.alpha_enabled) {
+ glColorMask(color_mask.red_enabled, color_mask.green_enabled, color_mask.blue_enabled,
+ color_mask.alpha_enabled);
+ }
// Point
if (point.size != cur_state.point.size) {
glPointSize(point.size);
}
-
+ ApplyScissorTest();
+ ApplyStencilTest();
+ ApplySRgb();
+ ApplyCulling();
+ ApplyDepth();
+ ApplyPrimitiveRestart();
+ ApplyBlending();
+ ApplyLogicOp();
+ ApplyTextures();
+ ApplySamplers();
cur_state = *this;
}
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index dc21a2ee3..fe648aff6 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -36,18 +36,29 @@ constexpr TextureUnit ProcTexDiffLUT{9};
class OpenGLState {
public:
struct {
+ bool enabled; // GL_FRAMEBUFFER_SRGB
+ } framebuffer_srgb;
+
+ struct {
bool enabled; // GL_CULL_FACE
GLenum mode; // GL_CULL_FACE_MODE
GLenum front_face; // GL_FRONT_FACE
} cull;
struct {
- bool test_enabled; // GL_DEPTH_TEST
- GLenum test_func; // GL_DEPTH_FUNC
- GLboolean write_mask; // GL_DEPTH_WRITEMASK
+ bool test_enabled; // GL_DEPTH_TEST
+ GLenum test_func; // GL_DEPTH_FUNC
+ GLboolean write_mask; // GL_DEPTH_WRITEMASK
+ GLfloat depth_range_near; // GL_DEPTH_RANGE
+ GLfloat depth_range_far; // GL_DEPTH_RANGE
} depth;
struct {
+ bool enabled;
+ GLuint index;
+ } primitive_restart; // GL_PRIMITIVE_RESTART
+
+ struct {
GLboolean red_enabled;
GLboolean green_enabled;
GLboolean blue_enabled;
@@ -154,10 +165,16 @@ public:
static OpenGLState GetCurState() {
return cur_state;
}
-
+ static bool GetsRGBUsed() {
+ return s_rgb_used;
+ }
+ static void ClearsRGBUsed() {
+ s_rgb_used = false;
+ }
/// Apply this state as the current OpenGL state
void Apply() const;
-
+ /// Set the initial OpenGL state
+ static void ApplyDefaultState();
/// Resets any references to the given resource
OpenGLState& UnbindTexture(GLuint handle);
OpenGLState& ResetSampler(GLuint handle);
@@ -169,6 +186,19 @@ public:
private:
static OpenGLState cur_state;
+ // Workaround for sRGB problems caused by
+ // QT not supporting srgb output
+ static bool s_rgb_used;
+ void ApplySRgb() const;
+ void ApplyCulling() const;
+ void ApplyDepth() const;
+ void ApplyPrimitiveRestart() const;
+ void ApplyStencilTest() const;
+ void ApplyScissorTest() const;
+ void ApplyBlending() const;
+ void ApplyLogicOp() const;
+ void ApplyTextures() const;
+ void ApplySamplers() const;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index 67273e164..87d511c38 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -82,8 +82,20 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) {
return {};
}
- case Maxwell::VertexAttribute::Type::Float:
- return GL_FLOAT;
+ case Maxwell::VertexAttribute::Type::Float: {
+ switch (attrib.size) {
+ case Maxwell::VertexAttribute::Size::Size_16:
+ case Maxwell::VertexAttribute::Size::Size_16_16:
+ case Maxwell::VertexAttribute::Size::Size_16_16_16:
+ case Maxwell::VertexAttribute::Size::Size_16_16_16_16:
+ return GL_HALF_FLOAT;
+ case Maxwell::VertexAttribute::Size::Size_32:
+ case Maxwell::VertexAttribute::Size::Size_32_32:
+ case Maxwell::VertexAttribute::Size::Size_32_32_32:
+ case Maxwell::VertexAttribute::Size::Size_32_32_32_32:
+ return GL_FLOAT;
+ }
+ }
}
LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString());
@@ -123,12 +135,29 @@ inline GLenum PrimitiveTopology(Maxwell::PrimitiveTopology topology) {
return {};
}
-inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode) {
+inline GLenum TextureFilterMode(Tegra::Texture::TextureFilter filter_mode,
+ Tegra::Texture::TextureMipmapFilter mip_filter_mode) {
switch (filter_mode) {
- case Tegra::Texture::TextureFilter::Linear:
- return GL_LINEAR;
- case Tegra::Texture::TextureFilter::Nearest:
- return GL_NEAREST;
+ case Tegra::Texture::TextureFilter::Linear: {
+ switch (mip_filter_mode) {
+ case Tegra::Texture::TextureMipmapFilter::None:
+ return GL_LINEAR;
+ case Tegra::Texture::TextureMipmapFilter::Nearest:
+ return GL_NEAREST_MIPMAP_LINEAR;
+ case Tegra::Texture::TextureMipmapFilter::Linear:
+ return GL_LINEAR_MIPMAP_LINEAR;
+ }
+ }
+ case Tegra::Texture::TextureFilter::Nearest: {
+ switch (mip_filter_mode) {
+ case Tegra::Texture::TextureMipmapFilter::None:
+ return GL_NEAREST;
+ case Tegra::Texture::TextureMipmapFilter::Nearest:
+ return GL_NEAREST_MIPMAP_NEAREST;
+ case Tegra::Texture::TextureMipmapFilter::Linear:
+ return GL_LINEAR_MIPMAP_NEAREST;
+ }
+ }
}
LOG_CRITICAL(Render_OpenGL, "Unimplemented texture filter mode={}",
static_cast<u32>(filter_mode));
@@ -159,6 +188,31 @@ inline GLenum WrapMode(Tegra::Texture::WrapMode wrap_mode) {
return {};
}
+inline GLenum DepthCompareFunc(Tegra::Texture::DepthCompareFunc func) {
+ switch (func) {
+ case Tegra::Texture::DepthCompareFunc::Never:
+ return GL_NEVER;
+ case Tegra::Texture::DepthCompareFunc::Less:
+ return GL_LESS;
+ case Tegra::Texture::DepthCompareFunc::LessEqual:
+ return GL_LEQUAL;
+ case Tegra::Texture::DepthCompareFunc::Equal:
+ return GL_EQUAL;
+ case Tegra::Texture::DepthCompareFunc::NotEqual:
+ return GL_NOTEQUAL;
+ case Tegra::Texture::DepthCompareFunc::Greater:
+ return GL_GREATER;
+ case Tegra::Texture::DepthCompareFunc::GreaterEqual:
+ return GL_GEQUAL;
+ case Tegra::Texture::DepthCompareFunc::Always:
+ return GL_ALWAYS;
+ }
+ LOG_CRITICAL(Render_OpenGL, "Unimplemented texture depth compare function ={}",
+ static_cast<u32>(func));
+ UNREACHABLE();
+ return {};
+}
+
inline GLenum BlendEquation(Maxwell::Blend::Equation equation) {
switch (equation) {
case Maxwell::Blend::Equation::Add:
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 96d916b07..ea38da932 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -115,7 +115,8 @@ RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& window)
RendererOpenGL::~RendererOpenGL() = default;
/// Swap buffers (render frame)
-void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) {
+void RendererOpenGL::SwapBuffers(
+ std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
ScopeAcquireGLContext acquire_context{render_window};
Core::System::GetInstance().GetPerfStats().EndSystemFrame();
@@ -124,11 +125,11 @@ void RendererOpenGL::SwapBuffers(boost::optional<const Tegra::FramebufferConfig&
OpenGLState prev_state = OpenGLState::GetCurState();
state.Apply();
- if (framebuffer != boost::none) {
+ if (framebuffer) {
// If framebuffer is provided, reload it from memory to a texture
- if (screen_info.texture.width != (GLsizei)framebuffer->width ||
- screen_info.texture.height != (GLsizei)framebuffer->height ||
- screen_info.texture.pixel_format != framebuffer->pixel_format) {
+ if (screen_info.texture.width != (GLsizei)framebuffer->get().width ||
+ screen_info.texture.height != (GLsizei)framebuffer->get().height ||
+ screen_info.texture.pixel_format != framebuffer->get().pixel_format) {
// Reallocate texture if the framebuffer size has changed.
// This is expected to not happen very often and hence should not be a
// performance problem.
@@ -283,7 +284,8 @@ void RendererOpenGL::CreateRasterizer() {
if (rasterizer) {
return;
}
-
+ // Initialize sRGB Usage
+ OpenGLState::ClearsRGBUsed();
rasterizer = std::make_unique<RasterizerOpenGL>(render_window, screen_info);
}
@@ -356,13 +358,20 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
state.texture_units[0].texture = screen_info.display_texture;
state.texture_units[0].swizzle = {GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA};
+ // Workaround brigthness problems in SMO by enabling sRGB in the final output
+ // if it has been used in the frame
+ // Needed because of this bug in QT
+ // QTBUG-50987
+ state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed();
state.Apply();
-
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices.data());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
+ // restore default state
+ state.framebuffer_srgb.enabled = false;
state.texture_units[0].texture = 0;
state.Apply();
+ // Clear sRGB state for the next frame
+ OpenGLState::ClearsRGBUsed();
}
/**
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index 961467a62..c0868c0e4 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -51,7 +51,8 @@ public:
~RendererOpenGL() override;
/// Swap buffers (render frame)
- void SwapBuffers(boost::optional<const Tegra::FramebufferConfig&> framebuffer) override;
+ void SwapBuffers(
+ std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
/// Initialize the renderer
bool Init() override;
diff --git a/src/video_core/renderer_opengl/utils.cpp b/src/video_core/renderer_opengl/utils.cpp
new file mode 100644
index 000000000..d84634cb3
--- /dev/null
+++ b/src/video_core/renderer_opengl/utils.cpp
@@ -0,0 +1,38 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <string>
+#include <fmt/format.h>
+#include <glad/glad.h>
+#include "common/common_types.h"
+#include "video_core/renderer_opengl/utils.h"
+
+namespace OpenGL {
+
+void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info) {
+ if (!GLAD_GL_KHR_debug) {
+ return; // We don't need to throw an error as this is just for debugging
+ }
+ const std::string nice_addr = fmt::format("0x{:016x}", addr);
+ std::string object_label;
+
+ if (extra_info.empty()) {
+ switch (identifier) {
+ case GL_TEXTURE:
+ object_label = "Texture@" + nice_addr;
+ break;
+ case GL_PROGRAM:
+ object_label = "Shader@" + nice_addr;
+ break;
+ default:
+ object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
+ break;
+ }
+ } else {
+ object_label = extra_info + '@' + nice_addr;
+ }
+ glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
+}
+
+} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/renderer_opengl/utils.h b/src/video_core/renderer_opengl/utils.h
new file mode 100644
index 000000000..1fcb6fc11
--- /dev/null
+++ b/src/video_core/renderer_opengl/utils.h
@@ -0,0 +1,15 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <glad/glad.h>
+#include "common/common_types.h"
+
+namespace OpenGL {
+
+void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr, std::string extra_info = "");
+
+} // namespace OpenGL \ No newline at end of file
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
new file mode 100644
index 000000000..e284a4604
--- /dev/null
+++ b/src/video_core/surface.cpp
@@ -0,0 +1,483 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "video_core/surface.h"
+
+namespace VideoCore::Surface {
+
+SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type) {
+ switch (texture_type) {
+ case Tegra::Texture::TextureType::Texture1D:
+ return SurfaceTarget::Texture1D;
+ case Tegra::Texture::TextureType::Texture2D:
+ case Tegra::Texture::TextureType::Texture2DNoMipmap:
+ return SurfaceTarget::Texture2D;
+ case Tegra::Texture::TextureType::Texture3D:
+ return SurfaceTarget::Texture3D;
+ case Tegra::Texture::TextureType::TextureCubemap:
+ return SurfaceTarget::TextureCubemap;
+ case Tegra::Texture::TextureType::Texture1DArray:
+ return SurfaceTarget::Texture1DArray;
+ case Tegra::Texture::TextureType::Texture2DArray:
+ return SurfaceTarget::Texture2DArray;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented texture_type={}", static_cast<u32>(texture_type));
+ UNREACHABLE();
+ return SurfaceTarget::Texture2D;
+ }
+}
+
+bool SurfaceTargetIsLayered(SurfaceTarget target) {
+ switch (target) {
+ case SurfaceTarget::Texture1D:
+ case SurfaceTarget::Texture2D:
+ case SurfaceTarget::Texture3D:
+ return false;
+ case SurfaceTarget::Texture1DArray:
+ case SurfaceTarget::Texture2DArray:
+ case SurfaceTarget::TextureCubemap:
+ return true;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented surface_target={}", static_cast<u32>(target));
+ UNREACHABLE();
+ return false;
+ }
+}
+
+PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format) {
+ switch (format) {
+ case Tegra::DepthFormat::S8_Z24_UNORM:
+ return PixelFormat::S8Z24;
+ case Tegra::DepthFormat::Z24_S8_UNORM:
+ return PixelFormat::Z24S8;
+ case Tegra::DepthFormat::Z32_FLOAT:
+ return PixelFormat::Z32F;
+ case Tegra::DepthFormat::Z16_UNORM:
+ return PixelFormat::Z16;
+ case Tegra::DepthFormat::Z32_S8_X24_FLOAT:
+ return PixelFormat::Z32FS8;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) {
+ switch (format) {
+ // TODO (Hexagon12): Converting SRGBA to RGBA is a hack and doesn't completely correct the
+ // gamma.
+ case Tegra::RenderTargetFormat::RGBA8_SRGB:
+ return PixelFormat::RGBA8_SRGB;
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ return PixelFormat::ABGR8U;
+ case Tegra::RenderTargetFormat::RGBA8_SNORM:
+ return PixelFormat::ABGR8S;
+ case Tegra::RenderTargetFormat::RGBA8_UINT:
+ return PixelFormat::ABGR8UI;
+ case Tegra::RenderTargetFormat::BGRA8_SRGB:
+ return PixelFormat::BGRA8_SRGB;
+ case Tegra::RenderTargetFormat::BGRA8_UNORM:
+ return PixelFormat::BGRA8;
+ case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
+ return PixelFormat::A2B10G10R10U;
+ case Tegra::RenderTargetFormat::RGBA16_FLOAT:
+ return PixelFormat::RGBA16F;
+ case Tegra::RenderTargetFormat::RGBA16_UNORM:
+ return PixelFormat::RGBA16U;
+ case Tegra::RenderTargetFormat::RGBA16_UINT:
+ return PixelFormat::RGBA16UI;
+ case Tegra::RenderTargetFormat::RGBA32_FLOAT:
+ return PixelFormat::RGBA32F;
+ case Tegra::RenderTargetFormat::RG32_FLOAT:
+ return PixelFormat::RG32F;
+ case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
+ return PixelFormat::R11FG11FB10F;
+ case Tegra::RenderTargetFormat::B5G6R5_UNORM:
+ return PixelFormat::B5G6R5U;
+ case Tegra::RenderTargetFormat::BGR5A1_UNORM:
+ return PixelFormat::A1B5G5R5U;
+ case Tegra::RenderTargetFormat::RGBA32_UINT:
+ return PixelFormat::RGBA32UI;
+ case Tegra::RenderTargetFormat::R8_UNORM:
+ return PixelFormat::R8U;
+ case Tegra::RenderTargetFormat::R8_UINT:
+ return PixelFormat::R8UI;
+ case Tegra::RenderTargetFormat::RG16_FLOAT:
+ return PixelFormat::RG16F;
+ case Tegra::RenderTargetFormat::RG16_UINT:
+ return PixelFormat::RG16UI;
+ case Tegra::RenderTargetFormat::RG16_SINT:
+ return PixelFormat::RG16I;
+ case Tegra::RenderTargetFormat::RG16_UNORM:
+ return PixelFormat::RG16;
+ case Tegra::RenderTargetFormat::RG16_SNORM:
+ return PixelFormat::RG16S;
+ case Tegra::RenderTargetFormat::RG8_UNORM:
+ return PixelFormat::RG8U;
+ case Tegra::RenderTargetFormat::RG8_SNORM:
+ return PixelFormat::RG8S;
+ case Tegra::RenderTargetFormat::R16_FLOAT:
+ return PixelFormat::R16F;
+ case Tegra::RenderTargetFormat::R16_UNORM:
+ return PixelFormat::R16U;
+ case Tegra::RenderTargetFormat::R16_SNORM:
+ return PixelFormat::R16S;
+ case Tegra::RenderTargetFormat::R16_UINT:
+ return PixelFormat::R16UI;
+ case Tegra::RenderTargetFormat::R16_SINT:
+ return PixelFormat::R16I;
+ case Tegra::RenderTargetFormat::R32_FLOAT:
+ return PixelFormat::R32F;
+ case Tegra::RenderTargetFormat::R32_UINT:
+ return PixelFormat::R32UI;
+ case Tegra::RenderTargetFormat::RG32_UINT:
+ return PixelFormat::RG32UI;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
+ Tegra::Texture::ComponentType component_type,
+ bool is_srgb) {
+ // TODO(Subv): Properly implement this
+ switch (format) {
+ case Tegra::Texture::TextureFormat::A8R8G8B8:
+ if (is_srgb) {
+ return PixelFormat::RGBA8_SRGB;
+ }
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::ABGR8U;
+ case Tegra::Texture::ComponentType::SNORM:
+ return PixelFormat::ABGR8S;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::ABGR8UI;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::B5G6R5:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::B5G6R5U;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::A2B10G10R10:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::A2B10G10R10U;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::A1B5G5R5:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::A1B5G5R5U;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R8:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::R8U;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::R8UI;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::G8R8:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::G8R8U;
+ case Tegra::Texture::ComponentType::SNORM:
+ return PixelFormat::G8R8S;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R16_G16_B16_A16:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::RGBA16U;
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::RGBA16F;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::BF10GF11RF11:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::R11FG11FB10F;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R32_G32_B32_A32:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::RGBA32F;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::RGBA32UI;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R32_G32:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::RG32F;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::RG32UI;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R32_G32_B32:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::RGB32F;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R16:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::R16F;
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::R16U;
+ case Tegra::Texture::ComponentType::SNORM:
+ return PixelFormat::R16S;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::R16UI;
+ case Tegra::Texture::ComponentType::SINT:
+ return PixelFormat::R16I;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::R32:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::R32F;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::R32UI;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::ZF32:
+ return PixelFormat::Z32F;
+ case Tegra::Texture::TextureFormat::Z16:
+ return PixelFormat::Z16;
+ case Tegra::Texture::TextureFormat::Z24S8:
+ return PixelFormat::Z24S8;
+ case Tegra::Texture::TextureFormat::DXT1:
+ return is_srgb ? PixelFormat::DXT1_SRGB : PixelFormat::DXT1;
+ case Tegra::Texture::TextureFormat::DXT23:
+ return is_srgb ? PixelFormat::DXT23_SRGB : PixelFormat::DXT23;
+ case Tegra::Texture::TextureFormat::DXT45:
+ return is_srgb ? PixelFormat::DXT45_SRGB : PixelFormat::DXT45;
+ case Tegra::Texture::TextureFormat::DXN1:
+ return PixelFormat::DXN1;
+ case Tegra::Texture::TextureFormat::DXN2:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::DXN2UNORM;
+ case Tegra::Texture::ComponentType::SNORM:
+ return PixelFormat::DXN2SNORM;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ case Tegra::Texture::TextureFormat::BC7U:
+ return is_srgb ? PixelFormat::BC7U_SRGB : PixelFormat::BC7U;
+ case Tegra::Texture::TextureFormat::BC6H_UF16:
+ return PixelFormat::BC6H_UF16;
+ case Tegra::Texture::TextureFormat::BC6H_SF16:
+ return PixelFormat::BC6H_SF16;
+ case Tegra::Texture::TextureFormat::ASTC_2D_4X4:
+ return is_srgb ? PixelFormat::ASTC_2D_4X4_SRGB : PixelFormat::ASTC_2D_4X4;
+ case Tegra::Texture::TextureFormat::ASTC_2D_5X4:
+ return is_srgb ? PixelFormat::ASTC_2D_5X4_SRGB : PixelFormat::ASTC_2D_5X4;
+ case Tegra::Texture::TextureFormat::ASTC_2D_5X5:
+ return is_srgb ? PixelFormat::ASTC_2D_5X5_SRGB : PixelFormat::ASTC_2D_5X5;
+ case Tegra::Texture::TextureFormat::ASTC_2D_8X8:
+ return is_srgb ? PixelFormat::ASTC_2D_8X8_SRGB : PixelFormat::ASTC_2D_8X8;
+ case Tegra::Texture::TextureFormat::ASTC_2D_8X5:
+ return is_srgb ? PixelFormat::ASTC_2D_8X5_SRGB : PixelFormat::ASTC_2D_8X5;
+ case Tegra::Texture::TextureFormat::R16_G16:
+ switch (component_type) {
+ case Tegra::Texture::ComponentType::FLOAT:
+ return PixelFormat::RG16F;
+ case Tegra::Texture::ComponentType::UNORM:
+ return PixelFormat::RG16;
+ case Tegra::Texture::ComponentType::SNORM:
+ return PixelFormat::RG16S;
+ case Tegra::Texture::ComponentType::UINT:
+ return PixelFormat::RG16UI;
+ case Tegra::Texture::ComponentType::SINT:
+ return PixelFormat::RG16I;
+ }
+ LOG_CRITICAL(HW_GPU, "Unimplemented component_type={}", static_cast<u32>(component_type));
+ UNREACHABLE();
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}, component_type={}", static_cast<u32>(format),
+ static_cast<u32>(component_type));
+ UNREACHABLE();
+ }
+}
+
+ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type) {
+ // TODO(Subv): Implement more component types
+ switch (type) {
+ case Tegra::Texture::ComponentType::UNORM:
+ return ComponentType::UNorm;
+ case Tegra::Texture::ComponentType::FLOAT:
+ return ComponentType::Float;
+ case Tegra::Texture::ComponentType::SNORM:
+ return ComponentType::SNorm;
+ case Tegra::Texture::ComponentType::UINT:
+ return ComponentType::UInt;
+ case Tegra::Texture::ComponentType::SINT:
+ return ComponentType::SInt;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented component type={}", static_cast<u32>(type));
+ UNREACHABLE();
+ }
+}
+
+ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format) {
+ // TODO(Subv): Implement more render targets
+ switch (format) {
+ case Tegra::RenderTargetFormat::RGBA8_UNORM:
+ case Tegra::RenderTargetFormat::RGBA8_SRGB:
+ case Tegra::RenderTargetFormat::BGRA8_UNORM:
+ case Tegra::RenderTargetFormat::BGRA8_SRGB:
+ case Tegra::RenderTargetFormat::RGB10_A2_UNORM:
+ case Tegra::RenderTargetFormat::R8_UNORM:
+ case Tegra::RenderTargetFormat::RG16_UNORM:
+ case Tegra::RenderTargetFormat::R16_UNORM:
+ case Tegra::RenderTargetFormat::B5G6R5_UNORM:
+ case Tegra::RenderTargetFormat::BGR5A1_UNORM:
+ case Tegra::RenderTargetFormat::RG8_UNORM:
+ case Tegra::RenderTargetFormat::RGBA16_UNORM:
+ return ComponentType::UNorm;
+ case Tegra::RenderTargetFormat::RGBA8_SNORM:
+ case Tegra::RenderTargetFormat::RG16_SNORM:
+ case Tegra::RenderTargetFormat::R16_SNORM:
+ case Tegra::RenderTargetFormat::RG8_SNORM:
+ return ComponentType::SNorm;
+ case Tegra::RenderTargetFormat::RGBA16_FLOAT:
+ case Tegra::RenderTargetFormat::R11G11B10_FLOAT:
+ case Tegra::RenderTargetFormat::RGBA32_FLOAT:
+ case Tegra::RenderTargetFormat::RG32_FLOAT:
+ case Tegra::RenderTargetFormat::RG16_FLOAT:
+ case Tegra::RenderTargetFormat::R16_FLOAT:
+ case Tegra::RenderTargetFormat::R32_FLOAT:
+ return ComponentType::Float;
+ case Tegra::RenderTargetFormat::RGBA32_UINT:
+ case Tegra::RenderTargetFormat::RGBA16_UINT:
+ case Tegra::RenderTargetFormat::RG16_UINT:
+ case Tegra::RenderTargetFormat::R8_UINT:
+ case Tegra::RenderTargetFormat::R16_UINT:
+ case Tegra::RenderTargetFormat::RG32_UINT:
+ case Tegra::RenderTargetFormat::R32_UINT:
+ case Tegra::RenderTargetFormat::RGBA8_UINT:
+ return ComponentType::UInt;
+ case Tegra::RenderTargetFormat::RG16_SINT:
+ case Tegra::RenderTargetFormat::R16_SINT:
+ return ComponentType::SInt;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format) {
+ switch (format) {
+ case Tegra::FramebufferConfig::PixelFormat::ABGR8:
+ return PixelFormat::ABGR8U;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format) {
+ switch (format) {
+ case Tegra::DepthFormat::Z16_UNORM:
+ case Tegra::DepthFormat::S8_Z24_UNORM:
+ case Tegra::DepthFormat::Z24_S8_UNORM:
+ return ComponentType::UNorm;
+ case Tegra::DepthFormat::Z32_FLOAT:
+ case Tegra::DepthFormat::Z32_S8_X24_FLOAT:
+ return ComponentType::Float;
+ default:
+ LOG_CRITICAL(HW_GPU, "Unimplemented format={}", static_cast<u32>(format));
+ UNREACHABLE();
+ }
+}
+
+SurfaceType GetFormatType(PixelFormat pixel_format) {
+ if (static_cast<std::size_t>(pixel_format) <
+ static_cast<std::size_t>(PixelFormat::MaxColorFormat)) {
+ return SurfaceType::ColorTexture;
+ }
+
+ if (static_cast<std::size_t>(pixel_format) <
+ static_cast<std::size_t>(PixelFormat::MaxDepthFormat)) {
+ return SurfaceType::Depth;
+ }
+
+ if (static_cast<std::size_t>(pixel_format) <
+ static_cast<std::size_t>(PixelFormat::MaxDepthStencilFormat)) {
+ return SurfaceType::DepthStencil;
+ }
+
+ // TODO(Subv): Implement the other formats
+ ASSERT(false);
+
+ return SurfaceType::Invalid;
+}
+
+bool IsPixelFormatASTC(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::ASTC_2D_4X4:
+ case PixelFormat::ASTC_2D_5X4:
+ case PixelFormat::ASTC_2D_5X5:
+ case PixelFormat::ASTC_2D_8X8:
+ case PixelFormat::ASTC_2D_8X5:
+ case PixelFormat::ASTC_2D_4X4_SRGB:
+ case PixelFormat::ASTC_2D_5X4_SRGB:
+ case PixelFormat::ASTC_2D_5X5_SRGB:
+ case PixelFormat::ASTC_2D_8X8_SRGB:
+ case PixelFormat::ASTC_2D_8X5_SRGB:
+ return true;
+ default:
+ return false;
+ }
+}
+
+std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
+ return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)};
+}
+
+bool IsFormatBCn(PixelFormat format) {
+ switch (format) {
+ case PixelFormat::DXT1:
+ case PixelFormat::DXT23:
+ case PixelFormat::DXT45:
+ case PixelFormat::DXN1:
+ case PixelFormat::DXN2SNORM:
+ case PixelFormat::DXN2UNORM:
+ case PixelFormat::BC7U:
+ case PixelFormat::BC6H_UF16:
+ case PixelFormat::BC6H_SF16:
+ case PixelFormat::DXT1_SRGB:
+ case PixelFormat::DXT23_SRGB:
+ case PixelFormat::DXT45_SRGB:
+ case PixelFormat::BC7U_SRGB:
+ return true;
+ }
+ return false;
+}
+
+} // namespace VideoCore::Surface
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
new file mode 100644
index 000000000..0ef7849a4
--- /dev/null
+++ b/src/video_core/surface.h
@@ -0,0 +1,466 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <climits>
+#include <utility>
+#include "common/assert.h"
+#include "common/common_types.h"
+#include "common/logging/log.h"
+#include "video_core/gpu.h"
+#include "video_core/textures/texture.h"
+
+namespace VideoCore::Surface {
+
+enum class PixelFormat {
+ ABGR8U = 0,
+ ABGR8S = 1,
+ ABGR8UI = 2,
+ B5G6R5U = 3,
+ A2B10G10R10U = 4,
+ A1B5G5R5U = 5,
+ R8U = 6,
+ R8UI = 7,
+ RGBA16F = 8,
+ RGBA16U = 9,
+ RGBA16UI = 10,
+ R11FG11FB10F = 11,
+ RGBA32UI = 12,
+ DXT1 = 13,
+ DXT23 = 14,
+ DXT45 = 15,
+ DXN1 = 16, // This is also known as BC4
+ DXN2UNORM = 17,
+ DXN2SNORM = 18,
+ BC7U = 19,
+ BC6H_UF16 = 20,
+ BC6H_SF16 = 21,
+ ASTC_2D_4X4 = 22,
+ G8R8U = 23,
+ G8R8S = 24,
+ BGRA8 = 25,
+ RGBA32F = 26,
+ RG32F = 27,
+ R32F = 28,
+ R16F = 29,
+ R16U = 30,
+ R16S = 31,
+ R16UI = 32,
+ R16I = 33,
+ RG16 = 34,
+ RG16F = 35,
+ RG16UI = 36,
+ RG16I = 37,
+ RG16S = 38,
+ RGB32F = 39,
+ RGBA8_SRGB = 40,
+ RG8U = 41,
+ RG8S = 42,
+ RG32UI = 43,
+ R32UI = 44,
+ ASTC_2D_8X8 = 45,
+ ASTC_2D_8X5 = 46,
+ ASTC_2D_5X4 = 47,
+ BGRA8_SRGB = 48,
+ DXT1_SRGB = 49,
+ DXT23_SRGB = 50,
+ DXT45_SRGB = 51,
+ BC7U_SRGB = 52,
+ ASTC_2D_4X4_SRGB = 53,
+ ASTC_2D_8X8_SRGB = 54,
+ ASTC_2D_8X5_SRGB = 55,
+ ASTC_2D_5X4_SRGB = 56,
+ ASTC_2D_5X5 = 57,
+ ASTC_2D_5X5_SRGB = 58,
+
+ MaxColorFormat,
+
+ // Depth formats
+ Z32F = 59,
+ Z16 = 60,
+
+ MaxDepthFormat,
+
+ // DepthStencil formats
+ Z24S8 = 61,
+ S8Z24 = 62,
+ Z32FS8 = 63,
+
+ MaxDepthStencilFormat,
+
+ Max = MaxDepthStencilFormat,
+ Invalid = 255,
+};
+
+static constexpr std::size_t MaxPixelFormat = static_cast<std::size_t>(PixelFormat::Max);
+
+enum class ComponentType {
+ Invalid = 0,
+ SNorm = 1,
+ UNorm = 2,
+ SInt = 3,
+ UInt = 4,
+ Float = 5,
+};
+
+enum class SurfaceType {
+ ColorTexture = 0,
+ Depth = 1,
+ DepthStencil = 2,
+ Fill = 3,
+ Invalid = 4,
+};
+
+enum class SurfaceTarget {
+ Texture1D,
+ Texture2D,
+ Texture3D,
+ Texture1DArray,
+ Texture2DArray,
+ TextureCubemap,
+};
+
+/**
+ * Gets the compression factor for the specified PixelFormat. This applies to just the
+ * "compressed width" and "compressed height", not the overall compression factor of a
+ * compressed image. This is used for maintaining proper surface sizes for compressed
+ * texture formats.
+ */
+static constexpr u32 GetCompressionFactor(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ constexpr std::array<u32, MaxPixelFormat> compression_factor_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 4, // ASTC_2D_8X8
+ 4, // ASTC_2D_8X5
+ 4, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 4, // ASTC_2D_8X8_SRGB
+ 4, // ASTC_2D_8X5_SRGB
+ 4, // ASTC_2D_5X4_SRGB
+ 4, // ASTC_2D_5X5
+ 4, // ASTC_2D_5X5_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+ }};
+
+ ASSERT(static_cast<std::size_t>(format) < compression_factor_table.size());
+ return compression_factor_table[static_cast<std::size_t>(format)];
+}
+
+static constexpr u32 GetDefaultBlockWidth(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 8, // ASTC_2D_8X8
+ 8, // ASTC_2D_8X5
+ 5, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 8, // ASTC_2D_8X8_SRGB
+ 8, // ASTC_2D_8X5_SRGB
+ 5, // ASTC_2D_5X4_SRGB
+ 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+ }};
+ ASSERT(static_cast<std::size_t>(format) < block_width_table.size());
+ return block_width_table[static_cast<std::size_t>(format)];
+}
+
+static constexpr u32 GetDefaultBlockHeight(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{
+ 1, // ABGR8U
+ 1, // ABGR8S
+ 1, // ABGR8UI
+ 1, // B5G6R5U
+ 1, // A2B10G10R10U
+ 1, // A1B5G5R5U
+ 1, // R8U
+ 1, // R8UI
+ 1, // RGBA16F
+ 1, // RGBA16U
+ 1, // RGBA16UI
+ 1, // R11FG11FB10F
+ 1, // RGBA32UI
+ 4, // DXT1
+ 4, // DXT23
+ 4, // DXT45
+ 4, // DXN1
+ 4, // DXN2UNORM
+ 4, // DXN2SNORM
+ 4, // BC7U
+ 4, // BC6H_UF16
+ 4, // BC6H_SF16
+ 4, // ASTC_2D_4X4
+ 1, // G8R8U
+ 1, // G8R8S
+ 1, // BGRA8
+ 1, // RGBA32F
+ 1, // RG32F
+ 1, // R32F
+ 1, // R16F
+ 1, // R16U
+ 1, // R16S
+ 1, // R16UI
+ 1, // R16I
+ 1, // RG16
+ 1, // RG16F
+ 1, // RG16UI
+ 1, // RG16I
+ 1, // RG16S
+ 1, // RGB32F
+ 1, // RGBA8_SRGB
+ 1, // RG8U
+ 1, // RG8S
+ 1, // RG32UI
+ 1, // R32UI
+ 8, // ASTC_2D_8X8
+ 5, // ASTC_2D_8X5
+ 4, // ASTC_2D_5X4
+ 1, // BGRA8_SRGB
+ 4, // DXT1_SRGB
+ 4, // DXT23_SRGB
+ 4, // DXT45_SRGB
+ 4, // BC7U_SRGB
+ 4, // ASTC_2D_4X4_SRGB
+ 8, // ASTC_2D_8X8_SRGB
+ 5, // ASTC_2D_8X5_SRGB
+ 4, // ASTC_2D_5X4_SRGB
+ 5, // ASTC_2D_5X5
+ 5, // ASTC_2D_5X5_SRGB
+ 1, // Z32F
+ 1, // Z16
+ 1, // Z24S8
+ 1, // S8Z24
+ 1, // Z32FS8
+ }};
+
+ ASSERT(static_cast<std::size_t>(format) < block_height_table.size());
+ return block_height_table[static_cast<std::size_t>(format)];
+}
+
+static constexpr u32 GetFormatBpp(PixelFormat format) {
+ if (format == PixelFormat::Invalid)
+ return 0;
+
+ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{
+ 32, // ABGR8U
+ 32, // ABGR8S
+ 32, // ABGR8UI
+ 16, // B5G6R5U
+ 32, // A2B10G10R10U
+ 16, // A1B5G5R5U
+ 8, // R8U
+ 8, // R8UI
+ 64, // RGBA16F
+ 64, // RGBA16U
+ 64, // RGBA16UI
+ 32, // R11FG11FB10F
+ 128, // RGBA32UI
+ 64, // DXT1
+ 128, // DXT23
+ 128, // DXT45
+ 64, // DXN1
+ 128, // DXN2UNORM
+ 128, // DXN2SNORM
+ 128, // BC7U
+ 128, // BC6H_UF16
+ 128, // BC6H_SF16
+ 128, // ASTC_2D_4X4
+ 16, // G8R8U
+ 16, // G8R8S
+ 32, // BGRA8
+ 128, // RGBA32F
+ 64, // RG32F
+ 32, // R32F
+ 16, // R16F
+ 16, // R16U
+ 16, // R16S
+ 16, // R16UI
+ 16, // R16I
+ 32, // RG16
+ 32, // RG16F
+ 32, // RG16UI
+ 32, // RG16I
+ 32, // RG16S
+ 96, // RGB32F
+ 32, // RGBA8_SRGB
+ 16, // RG8U
+ 16, // RG8S
+ 64, // RG32UI
+ 32, // R32UI
+ 128, // ASTC_2D_8X8
+ 128, // ASTC_2D_8X5
+ 128, // ASTC_2D_5X4
+ 32, // BGRA8_SRGB
+ 64, // DXT1_SRGB
+ 128, // DXT23_SRGB
+ 128, // DXT45_SRGB
+ 128, // BC7U
+ 128, // ASTC_2D_4X4_SRGB
+ 128, // ASTC_2D_8X8_SRGB
+ 128, // ASTC_2D_8X5_SRGB
+ 128, // ASTC_2D_5X4_SRGB
+ 128, // ASTC_2D_5X5
+ 128, // ASTC_2D_5X5_SRGB
+ 32, // Z32F
+ 16, // Z16
+ 32, // Z24S8
+ 32, // S8Z24
+ 64, // Z32FS8
+ }};
+
+ ASSERT(static_cast<std::size_t>(format) < bpp_table.size());
+ return bpp_table[static_cast<std::size_t>(format)];
+}
+
+/// Returns the sizer in bytes of the specified pixel format
+static constexpr u32 GetBytesPerPixel(PixelFormat pixel_format) {
+ if (pixel_format == PixelFormat::Invalid) {
+ return 0;
+ }
+ return GetFormatBpp(pixel_format) / CHAR_BIT;
+}
+
+SurfaceTarget SurfaceTargetFromTextureType(Tegra::Texture::TextureType texture_type);
+
+bool SurfaceTargetIsLayered(SurfaceTarget target);
+
+PixelFormat PixelFormatFromDepthFormat(Tegra::DepthFormat format);
+
+PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format);
+
+PixelFormat PixelFormatFromTextureFormat(Tegra::Texture::TextureFormat format,
+ Tegra::Texture::ComponentType component_type,
+ bool is_srgb);
+
+ComponentType ComponentTypeFromTexture(Tegra::Texture::ComponentType type);
+
+ComponentType ComponentTypeFromRenderTarget(Tegra::RenderTargetFormat format);
+
+PixelFormat PixelFormatFromGPUPixelFormat(Tegra::FramebufferConfig::PixelFormat format);
+
+ComponentType ComponentTypeFromDepthFormat(Tegra::DepthFormat format);
+
+SurfaceType GetFormatType(PixelFormat pixel_format);
+
+bool IsPixelFormatASTC(PixelFormat format);
+
+std::pair<u32, u32> GetASTCBlockSize(PixelFormat format);
+
+/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN
+bool IsFormatBCn(PixelFormat format);
+
+} // namespace VideoCore::Surface
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b1feacae9..bc50a4876 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -1598,27 +1598,29 @@ static void DecompressBlock(uint8_t inBuf[16], const uint32_t blockWidth,
namespace Tegra::Texture::ASTC {
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
- uint32_t block_width, uint32_t block_height) {
+ uint32_t depth, uint32_t block_width, uint32_t block_height) {
uint32_t blockIdx = 0;
- std::vector<uint8_t> outData(height * width * 4);
- for (uint32_t j = 0; j < height; j += block_height) {
- for (uint32_t i = 0; i < width; i += block_width) {
+ std::vector<uint8_t> outData(height * width * depth * 4);
+ for (uint32_t k = 0; k < depth; k++) {
+ for (uint32_t j = 0; j < height; j += block_height) {
+ for (uint32_t i = 0; i < width; i += block_width) {
- uint8_t* blockPtr = data.data() + blockIdx * 16;
+ uint8_t* blockPtr = data.data() + blockIdx * 16;
- // Blocks can be at most 12x12
- uint32_t uncompData[144];
- ASTCC::DecompressBlock(blockPtr, block_width, block_height, uncompData);
+ // Blocks can be at most 12x12
+ uint32_t uncompData[144];
+ ASTCC::DecompressBlock(blockPtr, block_width, block_height, uncompData);
- uint32_t decompWidth = std::min(block_width, width - i);
- uint32_t decompHeight = std::min(block_height, height - j);
+ uint32_t decompWidth = std::min(block_width, width - i);
+ uint32_t decompHeight = std::min(block_height, height - j);
- uint8_t* outRow = outData.data() + (j * width + i) * 4;
- for (uint32_t jj = 0; jj < decompHeight; jj++) {
- memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4);
- }
+ uint8_t* outRow = outData.data() + (j * width + i) * 4;
+ for (uint32_t jj = 0; jj < decompHeight; jj++) {
+ memcpy(outRow + jj * width * 4, uncompData + jj * block_width, decompWidth * 4);
+ }
- blockIdx++;
+ blockIdx++;
+ }
}
}
diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h
index f0d7c0e56..d419dd025 100644
--- a/src/video_core/textures/astc.h
+++ b/src/video_core/textures/astc.h
@@ -10,6 +10,6 @@
namespace Tegra::Texture::ASTC {
std::vector<uint8_t> Decompress(std::vector<uint8_t>& data, uint32_t width, uint32_t height,
- uint32_t block_width, uint32_t block_height);
+ uint32_t depth, uint32_t block_width, uint32_t block_height);
} // namespace Tegra::Texture::ASTC
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index 3d5476e5d..3066abf61 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -4,6 +4,7 @@
#include <cmath>
#include <cstring>
+#include "common/alignment.h"
#include "common/assert.h"
#include "core/memory.h"
#include "video_core/gpu.h"
@@ -39,72 +40,145 @@ struct alignas(64) SwizzleTable {
constexpr auto legacy_swizzle_table = SwizzleTable<8, 64, 1>();
constexpr auto fast_swizzle_table = SwizzleTable<8, 4, 16>();
-static void LegacySwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
- u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
- u32 block_height) {
+/**
+ * This function manages ALL the GOBs(Group of Bytes) Inside a single block.
+ * Instead of going gob by gob, we map the coordinates inside a block and manage from
+ * those. Block_Width is assumed to be 1.
+ */
+void PreciseProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
+ const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
+ const u32 y_end, const u32 z_end, const u32 tile_offset,
+ const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
+ const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
std::array<u8*, 2> data_ptrs;
- const std::size_t stride = width * bytes_per_pixel;
- const std::size_t gobs_in_x = 64;
- const std::size_t gobs_in_y = 8;
- const std::size_t gobs_size = gobs_in_x * gobs_in_y;
- const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
- for (std::size_t y = 0; y < height; ++y) {
- const std::size_t gob_y_address =
- (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
- (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
- const auto& table = legacy_swizzle_table[y % gobs_in_y];
- for (std::size_t x = 0; x < width; ++x) {
- const std::size_t gob_address =
- gob_y_address + (x * bytes_per_pixel / gobs_in_x) * gobs_size * block_height;
- const std::size_t x2 = x * bytes_per_pixel;
- const std::size_t swizzle_offset = gob_address + table[x2 % gobs_in_x];
- const std::size_t pixel_index = (x + y * width) * out_bytes_per_pixel;
-
- data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
- data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
-
- std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ u32 z_address = tile_offset;
+ const u32 gob_size_x = 64;
+ const u32 gob_size_y = 8;
+ const u32 gob_size_z = 1;
+ const u32 gob_size = gob_size_x * gob_size_y * gob_size_z;
+ for (u32 z = z_start; z < z_end; z++) {
+ u32 y_address = z_address;
+ u32 pixel_base = layer_z * z + y_start * stride_x;
+ for (u32 y = y_start; y < y_end; y++) {
+ const auto& table = legacy_swizzle_table[y % gob_size_y];
+ for (u32 x = x_start; x < x_end; x++) {
+ const u32 swizzle_offset{y_address + table[x * bytes_per_pixel % gob_size_x]};
+ const u32 pixel_index{x * out_bytes_per_pixel + pixel_base};
+ data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
+ data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
+ std::memcpy(data_ptrs[0], data_ptrs[1], bytes_per_pixel);
+ }
+ pixel_base += stride_x;
+ if ((y + 1) % gob_size_y == 0)
+ y_address += gob_size;
}
+ z_address += xy_block_size;
}
}
-static void FastSwizzleData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
- u8* swizzled_data, u8* unswizzled_data, bool unswizzle,
- u32 block_height) {
+/**
+ * This function manages ALL the GOBs(Group of Bytes) Inside a single block.
+ * Instead of going gob by gob, we map the coordinates inside a block and manage from
+ * those. Block_Width is assumed to be 1.
+ */
+void FastProcessBlock(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle,
+ const u32 x_start, const u32 y_start, const u32 z_start, const u32 x_end,
+ const u32 y_end, const u32 z_end, const u32 tile_offset,
+ const u32 xy_block_size, const u32 layer_z, const u32 stride_x,
+ const u32 bytes_per_pixel, const u32 out_bytes_per_pixel) {
std::array<u8*, 2> data_ptrs;
- const std::size_t stride{width * bytes_per_pixel};
- const std::size_t gobs_in_x = 64;
- const std::size_t gobs_in_y = 8;
- const std::size_t gobs_size = gobs_in_x * gobs_in_y;
- const std::size_t image_width_in_gobs{(stride + gobs_in_x - 1) / gobs_in_x};
- const std::size_t copy_size{16};
- for (std::size_t y = 0; y < height; ++y) {
- const std::size_t initial_gob =
- (y / (gobs_in_y * block_height)) * gobs_size * block_height * image_width_in_gobs +
- (y % (gobs_in_y * block_height) / gobs_in_y) * gobs_size;
- const std::size_t pixel_base{y * width * out_bytes_per_pixel};
- const auto& table = fast_swizzle_table[y % gobs_in_y];
- for (std::size_t xb = 0; xb < stride; xb += copy_size) {
- const std::size_t gob_address{initial_gob +
- (xb / gobs_in_x) * gobs_size * block_height};
- const std::size_t swizzle_offset{gob_address + table[(xb / 16) % 4]};
- const std::size_t out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
- const std::size_t pixel_index{out_x + pixel_base};
- data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
- data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
- std::memcpy(data_ptrs[0], data_ptrs[1], copy_size);
+ u32 z_address = tile_offset;
+ const u32 x_startb = x_start * bytes_per_pixel;
+ const u32 x_endb = x_end * bytes_per_pixel;
+ const u32 copy_size = 16;
+ const u32 gob_size_x = 64;
+ const u32 gob_size_y = 8;
+ const u32 gob_size_z = 1;
+ const u32 gob_size = gob_size_x * gob_size_y * gob_size_z;
+ for (u32 z = z_start; z < z_end; z++) {
+ u32 y_address = z_address;
+ u32 pixel_base = layer_z * z + y_start * stride_x;
+ for (u32 y = y_start; y < y_end; y++) {
+ const auto& table = fast_swizzle_table[y % gob_size_y];
+ for (u32 xb = x_startb; xb < x_endb; xb += copy_size) {
+ const u32 swizzle_offset{y_address + table[(xb / copy_size) % 4]};
+ const u32 out_x = xb * out_bytes_per_pixel / bytes_per_pixel;
+ const u32 pixel_index{out_x + pixel_base};
+ data_ptrs[unswizzle] = swizzled_data + swizzle_offset;
+ data_ptrs[!unswizzle] = unswizzled_data + pixel_index;
+ std::memcpy(data_ptrs[0], data_ptrs[1], copy_size);
+ }
+ pixel_base += stride_x;
+ if ((y + 1) % gob_size_y == 0)
+ y_address += gob_size;
+ }
+ z_address += xy_block_size;
+ }
+}
+
+/**
+ * This function unswizzles or swizzles a texture by mapping Linear to BlockLinear Textue.
+ * The body of this function takes care of splitting the swizzled texture into blocks,
+ * and managing the extents of it. Once all the parameters of a single block are obtained,
+ * the function calls 'ProcessBlock' to process that particular Block.
+ *
+ * Documentation for the memory layout and decoding can be found at:
+ * https://envytools.readthedocs.io/en/latest/hw/memory/g80-surface.html#blocklinear-surfaces
+ */
+template <bool fast>
+void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, const u32 width,
+ const u32 height, const u32 depth, const u32 bytes_per_pixel,
+ const u32 out_bytes_per_pixel, const u32 block_height, const u32 block_depth) {
+ auto div_ceil = [](const u32 x, const u32 y) { return ((x + y - 1) / y); };
+ const u32 stride_x = width * out_bytes_per_pixel;
+ const u32 layer_z = height * stride_x;
+ const u32 gob_x_bytes = 64;
+ const u32 gob_elements_x = gob_x_bytes / bytes_per_pixel;
+ const u32 gob_elements_y = 8;
+ const u32 gob_elements_z = 1;
+ const u32 block_x_elements = gob_elements_x;
+ const u32 block_y_elements = gob_elements_y * block_height;
+ const u32 block_z_elements = gob_elements_z * block_depth;
+ const u32 blocks_on_x = div_ceil(width, block_x_elements);
+ const u32 blocks_on_y = div_ceil(height, block_y_elements);
+ const u32 blocks_on_z = div_ceil(depth, block_z_elements);
+ const u32 gob_size = gob_x_bytes * gob_elements_y * gob_elements_z;
+ const u32 xy_block_size = gob_size * block_height;
+ const u32 block_size = xy_block_size * block_depth;
+ u32 tile_offset = 0;
+ for (u32 zb = 0; zb < blocks_on_z; zb++) {
+ const u32 z_start = zb * block_z_elements;
+ const u32 z_end = std::min(depth, z_start + block_z_elements);
+ for (u32 yb = 0; yb < blocks_on_y; yb++) {
+ const u32 y_start = yb * block_y_elements;
+ const u32 y_end = std::min(height, y_start + block_y_elements);
+ for (u32 xb = 0; xb < blocks_on_x; xb++) {
+ const u32 x_start = xb * block_x_elements;
+ const u32 x_end = std::min(width, x_start + block_x_elements);
+ if (fast) {
+ FastProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
+ z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
+ layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
+ } else {
+ PreciseProcessBlock(swizzled_data, unswizzled_data, unswizzle, x_start, y_start,
+ z_start, x_end, y_end, z_end, tile_offset, xy_block_size,
+ layer_z, stride_x, bytes_per_pixel, out_bytes_per_pixel);
+ }
+ tile_offset += block_size;
+ }
}
}
}
-void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
- u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height) {
+void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
+ u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
+ bool unswizzle, u32 block_height, u32 block_depth) {
if (bytes_per_pixel % 3 != 0 && (width * bytes_per_pixel) % 16 == 0) {
- FastSwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
- unswizzled_data, unswizzle, block_height);
+ SwizzledData<true>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
+ bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth);
} else {
- LegacySwizzleData(width, height, bytes_per_pixel, out_bytes_per_pixel, swizzled_data,
- unswizzled_data, unswizzle, block_height);
+ SwizzledData<false>(swizzled_data, unswizzled_data, unswizzle, width, height, depth,
+ bytes_per_pixel, out_bytes_per_pixel, block_height, block_depth);
}
}
@@ -125,7 +199,9 @@ u32 BytesPerPixel(TextureFormat format) {
case TextureFormat::R32_G32_B32:
return 12;
case TextureFormat::ASTC_2D_4X4:
+ case TextureFormat::ASTC_2D_5X4:
case TextureFormat::ASTC_2D_8X8:
+ case TextureFormat::ASTC_2D_8X5:
case TextureFormat::A8R8G8B8:
case TextureFormat::A2B10G10R10:
case TextureFormat::BF10GF11RF11:
@@ -151,14 +227,57 @@ u32 BytesPerPixel(TextureFormat format) {
}
}
-std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
- u32 height, u32 block_height) {
- std::vector<u8> unswizzled_data(width * height * bytes_per_pixel);
- CopySwizzledData(width / tile_size, height / tile_size, bytes_per_pixel, bytes_per_pixel,
- Memory::GetPointer(address), unswizzled_data.data(), true, block_height);
+std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
+ u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+ u32 block_height, u32 block_depth) {
+ std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
+ CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
+ (height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
+ bytes_per_pixel, Memory::GetPointer(address), unswizzled_data.data(), true,
+ block_height, block_depth);
return unswizzled_data;
}
+void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
+ u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
+ u32 block_height) {
+ const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + 63) / 64};
+ for (u32 line = 0; line < subrect_height; ++line) {
+ const u32 gob_address_y =
+ (line / (8 * block_height)) * 512 * block_height * image_width_in_gobs +
+ (line % (8 * block_height) / 8) * 512;
+ const auto& table = legacy_swizzle_table[line % 8];
+ for (u32 x = 0; x < subrect_width; ++x) {
+ const u32 gob_address = gob_address_y + (x * bytes_per_pixel / 64) * 512 * block_height;
+ const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % 64];
+ const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
+ const VAddr dest_addr = swizzled_data + swizzled_offset;
+
+ Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel);
+ }
+ }
+}
+
+void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
+ u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
+ u32 block_height, u32 offset_x, u32 offset_y) {
+ for (u32 line = 0; line < subrect_height; ++line) {
+ const u32 y2 = line + offset_y;
+ const u32 gob_address_y =
+ (y2 / (8 * block_height)) * 512 * block_height + (y2 % (8 * block_height) / 8) * 512;
+ const auto& table = legacy_swizzle_table[y2 % 8];
+ for (u32 x = 0; x < subrect_width; ++x) {
+ const u32 x2 = (x + offset_x) * bytes_per_pixel;
+ const u32 gob_address = gob_address_y + (x2 / 64) * 512 * block_height;
+ const u32 swizzled_offset = gob_address + table[x2 % 64];
+ const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
+ const VAddr source_addr = swizzled_data + swizzled_offset;
+
+ Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel);
+ }
+ }
+}
+
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height) {
std::vector<u8> rgba_data;
@@ -199,4 +318,19 @@ std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat
return rgba_data;
}
+std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+ u32 block_height, u32 block_depth) {
+ if (tiled) {
+ const u32 gobs_in_x = 64;
+ const u32 gobs_in_y = 8;
+ const u32 gobs_in_z = 1;
+ const u32 aligned_width = Common::AlignUp(width * bytes_per_pixel, gobs_in_x);
+ const u32 aligned_height = Common::AlignUp(height, gobs_in_y * block_height);
+ const u32 aligned_depth = Common::AlignUp(depth, gobs_in_z * block_depth);
+ return aligned_width * aligned_height * aligned_depth;
+ } else {
+ return width * height * depth * bytes_per_pixel;
+ }
+}
+
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 1f7b731be..ba065510b 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -10,21 +10,24 @@
namespace Tegra::Texture {
-/**
- * Unswizzles a swizzled texture without changing its format.
- */
-std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size, u32 bytes_per_pixel, u32 width,
- u32 height, u32 block_height = TICEntry::DefaultBlockHeight);
+// GOBSize constant. Calculated by 64 bytes in x multiplied by 8 y coords, represents
+// an small rect of (64/bytes_per_pixel)X8.
+inline std::size_t GetGOBSize() {
+ return 512;
+}
/**
- * Unswizzles a swizzled depth texture without changing its format.
+ * Unswizzles a swizzled texture without changing its format.
*/
-std::vector<u8> UnswizzleDepthTexture(VAddr address, DepthFormat format, u32 width, u32 height,
- u32 block_height = TICEntry::DefaultBlockHeight);
+std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
+ u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+ u32 block_height = TICEntry::DefaultBlockHeight,
+ u32 block_depth = TICEntry::DefaultBlockHeight);
/// Copies texture data from a buffer and performs swizzling/unswizzling as necessary.
-void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_per_pixel,
- u8* swizzled_data, u8* unswizzled_data, bool unswizzle, u32 block_height);
+void CopySwizzledData(u32 width, u32 height, u32 depth, u32 bytes_per_pixel,
+ u32 out_bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data,
+ bool unswizzle, u32 block_height, u32 block_depth);
/**
* Decodes an unswizzled texture into a A8R8G8B8 texture.
@@ -32,4 +35,19 @@ void CopySwizzledData(u32 width, u32 height, u32 bytes_per_pixel, u32 out_bytes_
std::vector<u8> DecodeTexture(const std::vector<u8>& texture_data, TextureFormat format, u32 width,
u32 height);
+/**
+ * This function calculates the correct size of a texture depending if it's tiled or not.
+ */
+std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+ u32 block_height, u32 block_depth);
+
+/// Copies an untiled subrectangle into a tiled surface.
+void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
+ u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
+ u32 block_height);
+/// Copies a tiled subrectangle into a linear surface.
+void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
+ u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
+ u32 block_height, u32 offset_x, u32 offset_y);
+
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 14aea4838..d12d2ecb8 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -141,6 +141,7 @@ static_assert(sizeof(TextureHandle) == 4, "TextureHandle has wrong size");
struct TICEntry {
static constexpr u32 DefaultBlockHeight = 16;
+ static constexpr u32 DefaultBlockDepth = 1;
union {
u32 raw;
@@ -161,7 +162,9 @@ struct TICEntry {
BitField<21, 3, TICHeaderVersion> header_version;
};
union {
+ BitField<0, 3, u32> block_width;
BitField<3, 3, u32> block_height;
+ BitField<6, 3, u32> block_depth;
// High 16 bits of the pitch value
BitField<0, 16, u32> pitch_high;
@@ -170,6 +173,7 @@ struct TICEntry {
};
union {
BitField<0, 16, u32> width_minus_1;
+ BitField<22, 1, u32> srgb_conversion;
BitField<23, 4, TextureType> texture_type;
};
union {
@@ -202,17 +206,32 @@ struct TICEntry {
return depth_minus_1 + 1;
}
+ u32 BlockWidth() const {
+ ASSERT(IsTiled());
+ // The block height is stored in log2 format.
+ return 1 << block_width;
+ }
+
u32 BlockHeight() const {
- ASSERT(header_version == TICHeaderVersion::BlockLinear ||
- header_version == TICHeaderVersion::BlockLinearColorKey);
+ ASSERT(IsTiled());
// The block height is stored in log2 format.
return 1 << block_height;
}
+ u32 BlockDepth() const {
+ ASSERT(IsTiled());
+ // The block height is stored in log2 format.
+ return 1 << block_depth;
+ }
+
bool IsTiled() const {
return header_version == TICHeaderVersion::BlockLinear ||
header_version == TICHeaderVersion::BlockLinearColorKey;
}
+
+ bool IsSrgbConversionEnabled() const {
+ return srgb_conversion != 0;
+ }
};
static_assert(sizeof(TICEntry) == 0x20, "TICEntry has wrong size");
@@ -227,6 +246,17 @@ enum class WrapMode : u32 {
MirrorOnceClampOGL = 7,
};
+enum class DepthCompareFunc : u32 {
+ Never = 0,
+ Less = 1,
+ Equal = 2,
+ LessEqual = 3,
+ Greater = 4,
+ NotEqual = 5,
+ GreaterEqual = 6,
+ Always = 7,
+};
+
enum class TextureFilter : u32 {
Nearest = 1,
Linear = 2,
@@ -244,7 +274,7 @@ struct TSCEntry {
BitField<3, 3, WrapMode> wrap_v;
BitField<6, 3, WrapMode> wrap_p;
BitField<9, 1, u32> depth_compare_enabled;
- BitField<10, 3, u32> depth_compare_func;
+ BitField<10, 3, DepthCompareFunc> depth_compare_func;
};
union {
BitField<0, 2, TextureFilter> mag_filter;
diff --git a/src/video_core/utils.h b/src/video_core/utils.h
index 681919ae3..e0a14d48f 100644
--- a/src/video_core/utils.h
+++ b/src/video_core/utils.h
@@ -161,26 +161,4 @@ static inline void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixe
}
}
-static void LabelGLObject(GLenum identifier, GLuint handle, VAddr addr,
- std::string extra_info = "") {
- if (!GLAD_GL_KHR_debug) {
- return; // We don't need to throw an error as this is just for debugging
- }
- const std::string nice_addr = fmt::format("0x{:016x}", addr);
- std::string object_label;
-
- switch (identifier) {
- case GL_TEXTURE:
- object_label = extra_info + "@" + nice_addr;
- break;
- case GL_PROGRAM:
- object_label = "ShaderProgram@" + nice_addr;
- break;
- default:
- object_label = fmt::format("Object(0x{:x})@{}", identifier, nice_addr);
- break;
- }
- glObjectLabel(identifier, handle, -1, static_cast<const GLchar*>(object_label.c_str()));
-}
-
} // namespace VideoCore
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
index 1c83e9c34..01f2d129d 100644
--- a/src/web_service/CMakeLists.txt
+++ b/src/web_service/CMakeLists.txt
@@ -10,7 +10,7 @@ add_library(web_service STATIC
create_target_directory_groups(web_service)
get_directory_property(OPENSSL_LIBS
- DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl
+ DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl
DEFINITION OPENSSL_LIBS)
-target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT)
+target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT)
target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
index 033ea1ea4..9156ce802 100644
--- a/src/web_service/telemetry_json.cpp
+++ b/src/web_service/telemetry_json.cpp
@@ -2,98 +2,127 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <thread>
-#include "common/assert.h"
+#include <json.hpp>
#include "common/detached_tasks.h"
+#include "common/web_result.h"
#include "web_service/telemetry_json.h"
#include "web_service/web_backend.h"
namespace WebService {
-TelemetryJson::TelemetryJson(const std::string& host, const std::string& username,
- const std::string& token)
- : host(std::move(host)), username(std::move(username)), token(std::move(token)) {}
-TelemetryJson::~TelemetryJson() = default;
+struct TelemetryJson::Impl {
+ Impl(std::string host, std::string username, std::string token)
+ : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {}
-template <class T>
-void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
- sections[static_cast<u8>(type)][name] = value;
-}
+ nlohmann::json& TopSection() {
+ return sections[static_cast<u8>(Telemetry::FieldType::None)];
+ }
-void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
- TopSection()[name] = sections[static_cast<unsigned>(type)];
-}
+ const nlohmann::json& TopSection() const {
+ return sections[static_cast<u8>(Telemetry::FieldType::None)];
+ }
+
+ template <class T>
+ void Serialize(Telemetry::FieldType type, const std::string& name, T value) {
+ sections[static_cast<u8>(type)][name] = value;
+ }
+
+ void SerializeSection(Telemetry::FieldType type, const std::string& name) {
+ TopSection()[name] = sections[static_cast<unsigned>(type)];
+ }
+
+ nlohmann::json output;
+ std::array<nlohmann::json, 7> sections;
+ std::string host;
+ std::string username;
+ std::string token;
+};
+
+TelemetryJson::TelemetryJson(std::string host, std::string username, std::string token)
+ : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
+TelemetryJson::~TelemetryJson() = default;
void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue());
}
void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
- Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
+ impl->Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
}
void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
- Serialize(field.GetType(), field.GetName(), field.GetValue().count());
+ impl->Serialize(field.GetType(), field.GetName(), field.GetValue().count());
}
void TelemetryJson::Complete() {
- SerializeSection(Telemetry::FieldType::App, "App");
- SerializeSection(Telemetry::FieldType::Session, "Session");
- SerializeSection(Telemetry::FieldType::Performance, "Performance");
- SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
- SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
- SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
-
- auto content = TopSection().dump();
+ impl->SerializeSection(Telemetry::FieldType::App, "App");
+ impl->SerializeSection(Telemetry::FieldType::Session, "Session");
+ impl->SerializeSection(Telemetry::FieldType::Performance, "Performance");
+ impl->SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
+ impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+
+ auto content = impl->TopSection().dump();
// Send the telemetry async but don't handle the errors since they were written to the log
- Common::DetachedTasks::AddTask(
- [host{this->host}, username{this->username}, token{this->token}, content]() {
- Client{host, username, token}.PostJson("/telemetry", content, true);
- });
+ Common::DetachedTasks::AddTask([host{impl->host}, content]() {
+ Client{host, "", ""}.PostJson("/telemetry", content, true);
+ });
+}
+
+bool TelemetryJson::SubmitTestcase() {
+ impl->SerializeSection(Telemetry::FieldType::App, "App");
+ impl->SerializeSection(Telemetry::FieldType::Session, "Session");
+ impl->SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
+ impl->SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+
+ auto content = impl->TopSection().dump();
+ Client client(impl->host, impl->username, impl->token);
+ auto value = client.PostJson("/gamedb/testcase", content, false);
+
+ return value.result_code == Common::WebResult::Code::Success;
}
} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
index 0fe6f9a3e..dfd202829 100644
--- a/src/web_service/telemetry_json.h
+++ b/src/web_service/telemetry_json.h
@@ -4,11 +4,9 @@
#pragma once
-#include <array>
+#include <chrono>
#include <string>
-#include <json.hpp>
#include "common/telemetry.h"
-#include "common/web_result.h"
namespace WebService {
@@ -18,8 +16,8 @@ namespace WebService {
*/
class TelemetryJson : public Telemetry::VisitorInterface {
public:
- TelemetryJson(const std::string& host, const std::string& username, const std::string& token);
- ~TelemetryJson();
+ TelemetryJson(std::string host, std::string username, std::string token);
+ ~TelemetryJson() override;
void Visit(const Telemetry::Field<bool>& field) override;
void Visit(const Telemetry::Field<double>& field) override;
@@ -37,22 +35,11 @@ public:
void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
void Complete() override;
+ bool SubmitTestcase() override;
private:
- nlohmann::json& TopSection() {
- return sections[static_cast<u8>(Telemetry::FieldType::None)];
- }
-
- template <class T>
- void Serialize(Telemetry::FieldType type, const std::string& name, T value);
-
- void SerializeSection(Telemetry::FieldType type, const std::string& name);
-
- nlohmann::json output;
- std::array<nlohmann::json, 7> sections;
- std::string host;
- std::string username;
- std::string token;
+ struct Impl;
+ std::unique_ptr<Impl> impl;
};
} // namespace WebService
diff --git a/src/web_service/verify_login.cpp b/src/web_service/verify_login.cpp
index 124aa3863..ca4b43b93 100644
--- a/src/web_service/verify_login.cpp
+++ b/src/web_service/verify_login.cpp
@@ -3,6 +3,7 @@
// Refer to the license.txt file included.
#include <json.hpp>
+#include "common/web_result.h"
#include "web_service/verify_login.h"
#include "web_service/web_backend.h"
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
index 787b0fbcb..b7737b615 100644
--- a/src/web_service/web_backend.cpp
+++ b/src/web_service/web_backend.cpp
@@ -3,9 +3,11 @@
// Refer to the license.txt file included.
#include <cstdlib>
+#include <mutex>
#include <string>
-#include <thread>
#include <LUrlParser.h>
+#include <httplib.h>
+#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/web_result.h"
#include "core/settings.h"
@@ -20,99 +22,132 @@ constexpr u32 HTTPS_PORT = 443;
constexpr u32 TIMEOUT_SECONDS = 30;
-Client::JWTCache Client::jwt_cache{};
+struct Client::Impl {
+ Impl(std::string host, std::string username, std::string token)
+ : host{std::move(host)}, username{std::move(username)}, token{std::move(token)} {
+ std::lock_guard<std::mutex> lock(jwt_cache.mutex);
+ if (this->username == jwt_cache.username && this->token == jwt_cache.token) {
+ jwt = jwt_cache.jwt;
+ }
+ }
+
+ /// A generic function handles POST, GET and DELETE request together
+ Common::WebResult GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, bool allow_anonymous) {
+ if (jwt.empty()) {
+ UpdateJWT();
+ }
+
+ if (jwt.empty() && !allow_anonymous) {
+ LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
+ return Common::WebResult{Common::WebResult::Code::CredentialsMissing,
+ "Credentials needed"};
+ }
+
+ auto result = GenericJson(method, path, data, jwt);
+ if (result.result_string == "401") {
+ // Try again with new JWT
+ UpdateJWT();
+ result = GenericJson(method, path, data, jwt);
+ }
-Client::Client(const std::string& host, const std::string& username, const std::string& token)
- : host(host), username(username), token(token) {
- std::lock_guard<std::mutex> lock(jwt_cache.mutex);
- if (username == jwt_cache.username && token == jwt_cache.token) {
- jwt = jwt_cache.jwt;
+ return result;
}
-}
-Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
- const std::string& data, const std::string& jwt,
- const std::string& username, const std::string& token) {
- if (cli == nullptr) {
- auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
- int port;
- if (parsedUrl.m_Scheme == "http") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTP_PORT;
- }
- cli =
- std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port, TIMEOUT_SECONDS);
- } else if (parsedUrl.m_Scheme == "https") {
- if (!parsedUrl.GetPort(&port)) {
- port = HTTPS_PORT;
+ /**
+ * A generic function with explicit authentication method specified
+ * JWT is used if the jwt parameter is not empty
+ * username + token is used if jwt is empty but username and token are not empty
+ * anonymous if all of jwt, username and token are empty
+ */
+ Common::WebResult GenericJson(const std::string& method, const std::string& path,
+ const std::string& data, const std::string& jwt = "",
+ const std::string& username = "", const std::string& token = "") {
+ if (cli == nullptr) {
+ auto parsedUrl = LUrlParser::clParseURL::ParseURL(host);
+ int port;
+ if (parsedUrl.m_Scheme == "http") {
+ if (!parsedUrl.GetPort(&port)) {
+ port = HTTP_PORT;
+ }
+ cli = std::make_unique<httplib::Client>(parsedUrl.m_Host.c_str(), port,
+ TIMEOUT_SECONDS);
+ } else if (parsedUrl.m_Scheme == "https") {
+ if (!parsedUrl.GetPort(&port)) {
+ port = HTTPS_PORT;
+ }
+ cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
+ TIMEOUT_SECONDS);
+ } else {
+ LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
+ return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
}
- cli = std::make_unique<httplib::SSLClient>(parsedUrl.m_Host.c_str(), port,
- TIMEOUT_SECONDS);
- } else {
- LOG_ERROR(WebService, "Bad URL scheme {}", parsedUrl.m_Scheme);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Bad URL scheme"};
}
- }
- if (cli == nullptr) {
- LOG_ERROR(WebService, "Invalid URL {}", host + path);
- return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
- }
+ if (cli == nullptr) {
+ LOG_ERROR(WebService, "Invalid URL {}", host + path);
+ return Common::WebResult{Common::WebResult::Code::InvalidURL, "Invalid URL"};
+ }
- httplib::Headers params;
- if (!jwt.empty()) {
- params = {
- {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
- };
- } else if (!username.empty()) {
- params = {
- {std::string("x-username"), username},
- {std::string("x-token"), token},
+ httplib::Headers params;
+ if (!jwt.empty()) {
+ params = {
+ {std::string("Authorization"), fmt::format("Bearer {}", jwt)},
+ };
+ } else if (!username.empty()) {
+ params = {
+ {std::string("x-username"), username},
+ {std::string("x-token"), token},
+ };
+ }
+
+ params.emplace(std::string("api-version"),
+ std::string(API_VERSION.begin(), API_VERSION.end()));
+ if (method != "GET") {
+ params.emplace(std::string("Content-Type"), std::string("application/json"));
};
- }
- params.emplace(std::string("api-version"), std::string(API_VERSION.begin(), API_VERSION.end()));
- if (method != "GET") {
- params.emplace(std::string("Content-Type"), std::string("application/json"));
- };
+ httplib::Request request;
+ request.method = method;
+ request.path = path;
+ request.headers = params;
+ request.body = data;
- httplib::Request request;
- request.method = method;
- request.path = path;
- request.headers = params;
- request.body = data;
+ httplib::Response response;
- httplib::Response response;
+ if (!cli->send(request, response)) {
+ LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
+ return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
+ }
- if (!cli->send(request, response)) {
- LOG_ERROR(WebService, "{} to {} returned null", method, host + path);
- return Common::WebResult{Common::WebResult::Code::LibError, "Null response"};
- }
+ if (response.status >= 400) {
+ LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
+ response.status);
+ return Common::WebResult{Common::WebResult::Code::HttpError,
+ std::to_string(response.status)};
+ }
- if (response.status >= 400) {
- LOG_ERROR(WebService, "{} to {} returned error status code: {}", method, host + path,
- response.status);
- return Common::WebResult{Common::WebResult::Code::HttpError,
- std::to_string(response.status)};
- }
+ auto content_type = response.headers.find("content-type");
- auto content_type = response.headers.find("content-type");
+ if (content_type == response.headers.end()) {
+ LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
+ return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+ }
- if (content_type == response.headers.end()) {
- LOG_ERROR(WebService, "{} to {} returned no content", method, host + path);
- return Common::WebResult{Common::WebResult::Code::WrongContent, ""};
+ if (content_type->second.find("application/json") == std::string::npos &&
+ content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
+ LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
+ content_type->second);
+ return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
+ }
+ return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
}
- if (content_type->second.find("application/json") == std::string::npos &&
- content_type->second.find("text/html; charset=utf-8") == std::string::npos) {
- LOG_ERROR(WebService, "{} to {} returned wrong content: {}", method, host + path,
- content_type->second);
- return Common::WebResult{Common::WebResult::Code::WrongContent, "Wrong content"};
- }
- return Common::WebResult{Common::WebResult::Code::Success, "", response.body};
-}
+ // Retrieve a new JWT from given username and token
+ void UpdateJWT() {
+ if (username.empty() || token.empty()) {
+ return;
+ }
-void Client::UpdateJWT() {
- if (!username.empty() && !token.empty()) {
auto result = GenericJson("POST", "/jwt/internal", "", "", username, token);
if (result.result_code != Common::WebResult::Code::Success) {
LOG_ERROR(WebService, "UpdateJWT failed");
@@ -123,27 +158,39 @@ void Client::UpdateJWT() {
jwt_cache.jwt = jwt = result.returned_data;
}
}
-}
-Common::WebResult Client::GenericJson(const std::string& method, const std::string& path,
- const std::string& data, bool allow_anonymous) {
- if (jwt.empty()) {
- UpdateJWT();
- }
+ std::string host;
+ std::string username;
+ std::string token;
+ std::string jwt;
+ std::unique_ptr<httplib::Client> cli;
+
+ struct JWTCache {
+ std::mutex mutex;
+ std::string username;
+ std::string token;
+ std::string jwt;
+ };
+ static inline JWTCache jwt_cache;
+};
- if (jwt.empty() && !allow_anonymous) {
- LOG_ERROR(WebService, "Credentials must be provided for authenticated requests");
- return Common::WebResult{Common::WebResult::Code::CredentialsMissing, "Credentials needed"};
- }
+Client::Client(std::string host, std::string username, std::string token)
+ : impl{std::make_unique<Impl>(std::move(host), std::move(username), std::move(token))} {}
- auto result = GenericJson(method, path, data, jwt);
- if (result.result_string == "401") {
- // Try again with new JWT
- UpdateJWT();
- result = GenericJson(method, path, data, jwt);
- }
+Client::~Client() = default;
+
+Common::WebResult Client::PostJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
+ return impl->GenericJson("POST", path, data, allow_anonymous);
+}
+
+Common::WebResult Client::GetJson(const std::string& path, bool allow_anonymous) {
+ return impl->GenericJson("GET", path, "", allow_anonymous);
+}
- return result;
+Common::WebResult Client::DeleteJson(const std::string& path, const std::string& data,
+ bool allow_anonymous) {
+ return impl->GenericJson("DELETE", path, data, allow_anonymous);
}
} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
index d75fbcc15..c637e09df 100644
--- a/src/web_service/web_backend.h
+++ b/src/web_service/web_backend.h
@@ -4,23 +4,19 @@
#pragma once
-#include <functional>
-#include <mutex>
+#include <memory>
#include <string>
-#include <tuple>
-#include <httplib.h>
-#include "common/common_types.h"
-#include "common/web_result.h"
-namespace httplib {
-class Client;
+namespace Common {
+struct WebResult;
}
namespace WebService {
class Client {
public:
- Client(const std::string& host, const std::string& username, const std::string& token);
+ Client(std::string host, std::string username, std::string token);
+ ~Client();
/**
* Posts JSON to the specified path.
@@ -30,9 +26,7 @@ public:
* @return the result of the request.
*/
Common::WebResult PostJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
- return GenericJson("POST", path, data, allow_anonymous);
- }
+ bool allow_anonymous);
/**
* Gets JSON from the specified path.
@@ -40,9 +34,7 @@ public:
* @param allow_anonymous If true, allow anonymous unauthenticated requests.
* @return the result of the request.
*/
- Common::WebResult GetJson(const std::string& path, bool allow_anonymous) {
- return GenericJson("GET", path, "", allow_anonymous);
- }
+ Common::WebResult GetJson(const std::string& path, bool allow_anonymous);
/**
* Deletes JSON to the specified path.
@@ -52,41 +44,11 @@ public:
* @return the result of the request.
*/
Common::WebResult DeleteJson(const std::string& path, const std::string& data,
- bool allow_anonymous) {
- return GenericJson("DELETE", path, data, allow_anonymous);
- }
+ bool allow_anonymous);
private:
- /// A generic function handles POST, GET and DELETE request together
- Common::WebResult GenericJson(const std::string& method, const std::string& path,
- const std::string& data, bool allow_anonymous);
-
- /**
- * A generic function with explicit authentication method specified
- * JWT is used if the jwt parameter is not empty
- * username + token is used if jwt is empty but username and token are not empty
- * anonymous if all of jwt, username and token are empty
- */
- Common::WebResult GenericJson(const std::string& method, const std::string& path,
- const std::string& data, const std::string& jwt = "",
- const std::string& username = "", const std::string& token = "");
-
- // Retrieve a new JWT from given username and token
- void UpdateJWT();
-
- std::string host;
- std::string username;
- std::string token;
- std::string jwt;
- std::unique_ptr<httplib::Client> cli;
-
- struct JWTCache {
- std::mutex mutex;
- std::string username;
- std::string token;
- std::string jwt;
- };
- static JWTCache jwt_cache;
+ struct Impl;
+ std::unique_ptr<Impl> impl;
};
} // namespace WebService
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 04464ad5e..f9ca2948e 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -56,6 +56,8 @@ add_executable(yuzu
main.h
ui_settings.cpp
ui_settings.h
+ util/limitable_input_dialog.cpp
+ util/limitable_input_dialog.h
util/spinbox.cpp
util/spinbox.h
util/util.cpp
@@ -82,10 +84,10 @@ set(UIS
)
file(GLOB COMPAT_LIST
- ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
- ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
-file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*)
-file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
+ ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
+ ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json)
+file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*)
+file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*)
qt5_wrap_ui(UI_HDRS ${UIS})
@@ -121,7 +123,7 @@ target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (YUZU_ENABLE_COMPATIBILITY_REPORTING)
- add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING)
+ target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING)
endif()
if (USE_DISCORD_PRESENCE)
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 4e4c108ab..39eef8858 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -8,7 +8,6 @@
#include "common/microprofile.h"
#include "common/scm_rev.h"
-#include "common/string_util.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "core/settings.h"
@@ -107,9 +106,9 @@ private:
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
: QWidget(parent), child(nullptr), emu_thread(emu_thread) {
- std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name,
- Common::g_scm_branch, Common::g_scm_desc);
- setWindowTitle(QString::fromStdString(window_title));
+ setWindowTitle(QStringLiteral("yuzu %1 | %2-%3")
+ .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc));
+ setAttribute(Qt::WA_AcceptTouchEvents);
InputCommon::Init();
InputCommon::StartJoystickEventHandler();
@@ -190,11 +189,17 @@ QByteArray GRenderWindow::saveGeometry() {
return geometry;
}
-qreal GRenderWindow::windowPixelRatio() {
+qreal GRenderWindow::windowPixelRatio() const {
// windowHandle() might not be accessible until the window is displayed to screen.
return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f;
}
+std::pair<unsigned, unsigned> GRenderWindow::ScaleTouch(const QPointF pos) const {
+ const qreal pixel_ratio = windowPixelRatio();
+ return {static_cast<unsigned>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})),
+ static_cast<unsigned>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))};
+}
+
void GRenderWindow::closeEvent(QCloseEvent* event) {
emit Closed();
QWidget::closeEvent(event);
@@ -209,31 +214,81 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchBeginEvent
+
auto pos = event->pos();
if (event->button() == Qt::LeftButton) {
- qreal pixelRatio = windowPixelRatio();
- this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
- static_cast<unsigned>(pos.y() * pixelRatio));
+ const auto [x, y] = ScaleTouch(pos);
+ this->TouchPressed(x, y);
} else if (event->button() == Qt::RightButton) {
InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y());
}
}
void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchUpdateEvent
+
auto pos = event->pos();
- qreal pixelRatio = windowPixelRatio();
- this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
- std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
+ const auto [x, y] = ScaleTouch(pos);
+ this->TouchMoved(x, y);
InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y());
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
+ if (event->source() == Qt::MouseEventSynthesizedBySystem)
+ return; // touch input is handled in TouchEndEvent
+
if (event->button() == Qt::LeftButton)
this->TouchReleased();
else if (event->button() == Qt::RightButton)
InputCommon::GetMotionEmu()->EndTilt();
}
+void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
+ // TouchBegin always has exactly one touch point, so take the .first()
+ const auto [x, y] = ScaleTouch(event->touchPoints().first().pos());
+ this->TouchPressed(x, y);
+}
+
+void GRenderWindow::TouchUpdateEvent(const QTouchEvent* event) {
+ QPointF pos;
+ int active_points = 0;
+
+ // average all active touch points
+ for (const auto tp : event->touchPoints()) {
+ if (tp.state() & (Qt::TouchPointPressed | Qt::TouchPointMoved | Qt::TouchPointStationary)) {
+ active_points++;
+ pos += tp.pos();
+ }
+ }
+
+ pos /= active_points;
+
+ const auto [x, y] = ScaleTouch(pos);
+ this->TouchMoved(x, y);
+}
+
+void GRenderWindow::TouchEndEvent() {
+ this->TouchReleased();
+}
+
+bool GRenderWindow::event(QEvent* event) {
+ if (event->type() == QEvent::TouchBegin) {
+ TouchBeginEvent(static_cast<QTouchEvent*>(event));
+ return true;
+ } else if (event->type() == QEvent::TouchUpdate) {
+ TouchUpdateEvent(static_cast<QTouchEvent*>(event));
+ return true;
+ } else if (event->type() == QEvent::TouchEnd || event->type() == QEvent::TouchCancel) {
+ TouchEndEvent();
+ return true;
+ }
+
+ return QWidget::event(event);
+}
+
void GRenderWindow::focusOutEvent(QFocusEvent* event) {
QWidget::focusOutEvent(event);
InputCommon::GetKeyboard()->ReleaseAllKeys();
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index f133bfadf..873985564 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -15,6 +15,7 @@
class QKeyEvent;
class QScreen;
+class QTouchEvent;
class GGLWidgetInternal;
class GMainWindow;
@@ -119,7 +120,7 @@ public:
void restoreGeometry(const QByteArray& geometry); // overridden
QByteArray saveGeometry(); // overridden
- qreal windowPixelRatio();
+ qreal windowPixelRatio() const;
void closeEvent(QCloseEvent* event) override;
@@ -130,6 +131,8 @@ public:
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
+ bool event(QEvent* event) override;
+
void focusOutEvent(QFocusEvent* event) override;
void OnClientAreaResized(unsigned width, unsigned height);
@@ -148,6 +151,11 @@ signals:
void Closed();
private:
+ std::pair<unsigned, unsigned> ScaleTouch(const QPointF pos) const;
+ void TouchBeginEvent(const QTouchEvent* event);
+ void TouchUpdateEvent(const QTouchEvent* event);
+ void TouchEndEvent();
+
void OnMinimalClientAreaChangeRequest(
const std::pair<unsigned, unsigned>& minimal_size) override;
diff --git a/src/yuzu/compatdb.cpp b/src/yuzu/compatdb.cpp
index 91e754274..5f0896f84 100644
--- a/src/yuzu/compatdb.cpp
+++ b/src/yuzu/compatdb.cpp
@@ -5,6 +5,7 @@
#include <QButtonGroup>
#include <QMessageBox>
#include <QPushButton>
+#include <QtConcurrent/qtconcurrentrun.h>
#include "common/logging/log.h"
#include "common/telemetry.h"
#include "core/core.h"
@@ -23,6 +24,8 @@ CompatDB::CompatDB(QWidget* parent)
connect(ui->radioButton_IntroMenu, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(ui->radioButton_WontBoot, &QRadioButton::clicked, this, &CompatDB::EnableNext);
connect(button(NextButton), &QPushButton::clicked, this, &CompatDB::Submit);
+ connect(&testcase_watcher, &QFutureWatcher<bool>::finished, this,
+ &CompatDB::OnTestcaseSubmitted);
}
CompatDB::~CompatDB() = default;
@@ -48,18 +51,38 @@ void CompatDB::Submit() {
}
break;
case CompatDBPage::Final:
+ back();
LOG_DEBUG(Frontend, "Compatibility Rating: {}", compatibility->checkedId());
Core::Telemetry().AddField(Telemetry::FieldType::UserFeedback, "Compatibility",
compatibility->checkedId());
- // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
- // workaround
+
+ button(NextButton)->setEnabled(false);
+ button(NextButton)->setText(tr("Submitting"));
button(QWizard::CancelButton)->setVisible(false);
+
+ testcase_watcher.setFuture(QtConcurrent::run(
+ [this]() { return Core::System::GetInstance().TelemetrySession().SubmitTestcase(); }));
break;
default:
LOG_ERROR(Frontend, "Unexpected page: {}", currentId());
}
}
+void CompatDB::OnTestcaseSubmitted() {
+ if (!testcase_watcher.result()) {
+ QMessageBox::critical(this, tr("Communication error"),
+ tr("An error occured while sending the Testcase"));
+ button(NextButton)->setEnabled(true);
+ button(NextButton)->setText(tr("Next"));
+ button(QWizard::CancelButton)->setVisible(true);
+ } else {
+ next();
+ // older versions of QT don't support the "NoCancelButtonOnLastPage" option, this is a
+ // workaround
+ button(QWizard::CancelButton)->setVisible(false);
+ }
+}
+
void CompatDB::EnableNext() {
button(NextButton)->setEnabled(true);
}
diff --git a/src/yuzu/compatdb.h b/src/yuzu/compatdb.h
index ca0dd11d6..5381f67f7 100644
--- a/src/yuzu/compatdb.h
+++ b/src/yuzu/compatdb.h
@@ -5,6 +5,7 @@
#pragma once
#include <memory>
+#include <QFutureWatcher>
#include <QWizard>
namespace Ui {
@@ -19,8 +20,11 @@ public:
~CompatDB();
private:
+ QFutureWatcher<bool> testcase_watcher;
+
std::unique_ptr<Ui::CompatDB> ui;
void Submit();
+ void OnTestcaseSubmitted();
void EnableNext();
};
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 650dd03c0..d4fd60a73 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -4,6 +4,7 @@
#include <QSettings>
#include "common/file_util.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "input_common/main.h"
#include "yuzu/configuration/config.h"
#include "yuzu/ui_settings.h"
@@ -12,11 +13,16 @@ Config::Config() {
// TODO: Don't hardcode the path; let the frontend decide where to put the config files.
qt_config_loc = FileUtil::GetUserPath(FileUtil::UserPath::ConfigDir) + "qt-config.ini";
FileUtil::CreateFullPath(qt_config_loc);
- qt_config = new QSettings(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
+ qt_config =
+ std::make_unique<QSettings>(QString::fromStdString(qt_config_loc), QSettings::IniFormat);
Reload();
}
+Config::~Config() {
+ Save();
+}
+
const std::array<int, Settings::NativeButton::NumButtons> Config::default_buttons = {
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X, Qt::Key_3, Qt::Key_4, Qt::Key_Q,
Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_N, Qt::Key_M, Qt::Key_F, Qt::Key_T,
@@ -85,8 +91,8 @@ void Config::ReadValues() {
Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
- Settings::values.use_accurate_framebuffers =
- qt_config->value("use_accurate_framebuffers", false).toBool();
+ Settings::values.use_accurate_gpu_emulation =
+ qt_config->value("use_accurate_gpu_emulation", false).toBool();
Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
@@ -122,7 +128,11 @@ void Config::ReadValues() {
qt_config->beginGroup("System");
Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
- Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString();
+ Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
+
+ Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0,
+ Service::Account::MAX_USERS - 1);
+
Settings::values.language_index = qt_config->value("language_index", 1).toInt();
qt_config->endGroup();
@@ -134,6 +144,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Debugging");
Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
+ Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
qt_config->endGroup();
qt_config->beginGroup("WebService");
@@ -232,7 +243,7 @@ void Config::SaveValues() {
qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
qt_config->setValue("frame_limit", Settings::values.frame_limit);
- qt_config->setValue("use_accurate_framebuffers", Settings::values.use_accurate_framebuffers);
+ qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
// Cast to double because Qt's written float values are not human-readable
qt_config->setValue("bg_red", (double)Settings::values.bg_red);
@@ -257,7 +268,9 @@ void Config::SaveValues() {
qt_config->beginGroup("System");
qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
- qt_config->setValue("username", QString::fromStdString(Settings::values.username));
+ qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
+ qt_config->setValue("current_user", Settings::values.current_user);
+
qt_config->setValue("language_index", Settings::values.language_index);
qt_config->endGroup();
@@ -269,6 +282,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Debugging");
qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
+ qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
qt_config->endGroup();
qt_config->beginGroup("WebService");
@@ -333,9 +347,3 @@ void Config::Reload() {
void Config::Save() {
SaveValues();
}
-
-Config::~Config() {
- Save();
-
- delete qt_config;
-}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index cbf745ea2..9c99c1b75 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include <memory>
#include <string>
#include <QVariant>
#include "core/settings.h"
@@ -12,12 +13,6 @@
class QSettings;
class Config {
- QSettings* qt_config;
- std::string qt_config_loc;
-
- void ReadValues();
- void SaveValues();
-
public:
Config();
~Config();
@@ -27,4 +22,11 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
+
+private:
+ void ReadValues();
+ void SaveValues();
+
+ std::unique_ptr<QSettings> qt_config;
+ std::string qt_config_loc;
};
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 45d84f19a..9e765fc93 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -33,6 +33,7 @@ void ConfigureDebug::setConfiguration() {
ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn());
ui->toggle_console->setChecked(UISettings::values.show_console);
ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
+ ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
}
void ConfigureDebug::applyConfiguration() {
@@ -40,6 +41,7 @@ void ConfigureDebug::applyConfiguration() {
Settings::values.gdbstub_port = ui->gdbport_spinbox->value();
UISettings::values.show_console = ui->toggle_console->isChecked();
Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
+ Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
Debugger::ToggleConsole();
Log::Filter filter;
filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index 5ae7276bd..ff4987604 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -107,6 +107,29 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Homebrew</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Arguments String</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="homebrew_args_edit"/>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index f5db9e55b..537d6e576 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -31,6 +31,7 @@ void ConfigureGeneral::setConfiguration() {
ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit);
ui->use_docked_mode->setChecked(Settings::values.use_docked_mode);
+ ui->enable_nfc->setChecked(Settings::values.enable_nfc);
}
void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) {
@@ -45,4 +46,5 @@ void ConfigureGeneral::applyConfiguration() {
Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked();
Settings::values.use_docked_mode = ui->use_docked_mode->isChecked();
+ Settings::values.enable_nfc = ui->enable_nfc->isChecked();
}
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 1775c4d40..b82fffde8 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -68,19 +68,26 @@
<property name="title">
<string>Emulation</string>
</property>
- <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
+ <layout class="QHBoxLayout" name="EmulationHorizontalLayout">
+ <item>
+ <layout class="QVBoxLayout" name="EmulationVerticalLayout">
+ <item>
+ <widget class="QCheckBox" name="use_docked_mode">
+ <property name="text">
+ <string>Enable docked mode</string>
+ </property>
+ </widget>
+ </item>
<item>
- <layout class="QVBoxLayout" name="EmulationVerticalLayout">
- <item>
- <widget class="QCheckBox" name="use_docked_mode">
- <property name="text">
- <string>Enable docked mode</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QCheckBox" name="enable_nfc">
+ <property name="text">
+ <string>Enable NFC</string>
+ </property>
+ </widget>
</item>
- </layout>
+ </layout>
+ </item>
+ </layout>
</widget>
</item>
<item>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index cd1549462..8290b4384 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -75,7 +75,7 @@ void ConfigureGraphics::setConfiguration() {
static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
ui->frame_limit->setValue(Settings::values.frame_limit);
- ui->use_accurate_framebuffers->setChecked(Settings::values.use_accurate_framebuffers);
+ ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
bg_color = QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green,
Settings::values.bg_blue);
ui->bg_button->setStyleSheet(
@@ -87,7 +87,7 @@ void ConfigureGraphics::applyConfiguration() {
ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
Settings::values.frame_limit = ui->frame_limit->value();
- Settings::values.use_accurate_framebuffers = ui->use_accurate_framebuffers->isChecked();
+ Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
Settings::values.bg_red = static_cast<float>(bg_color.redF());
Settings::values.bg_green = static_cast<float>(bg_color.greenF());
Settings::values.bg_blue = static_cast<float>(bg_color.blueF());
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 8fc00af1b..91fcad994 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -50,9 +50,9 @@
</layout>
</item>
<item>
- <widget class="QCheckBox" name="use_accurate_framebuffers">
+ <widget class="QCheckBox" name="use_accurate_gpu_emulation">
<property name="text">
- <string>Use accurate framebuffers (slow)</string>
+ <string>Use accurate GPU emulation (slow)</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index 473937ea9..42a7beac6 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <memory>
#include <utility>
+#include <QMenu>
#include <QMessageBox>
#include <QTimer>
#include "common/param_package.h"
@@ -128,28 +129,63 @@ ConfigureInput::ConfigureInput(QWidget* parent)
analog_map_stick = {ui->buttonLStickAnalog, ui->buttonRStickAnalog};
for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
- if (button_map[button_id])
- connect(button_map[button_id], &QPushButton::released, [=]() {
- handleClick(
- button_map[button_id],
- [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
- InputCommon::Polling::DeviceType::Button);
- });
+ if (!button_map[button_id])
+ continue;
+ button_map[button_id]->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(button_map[button_id], &QPushButton::released, [=]() {
+ handleClick(
+ button_map[button_id],
+ [=](const Common::ParamPackage& params) { buttons_param[button_id] = params; },
+ InputCommon::Polling::DeviceType::Button);
+ });
+ connect(button_map[button_id], &QPushButton::customContextMenuRequested,
+ [=](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ buttons_param[button_id].Clear();
+ button_map[button_id]->setText(tr("[not set]"));
+ });
+ context_menu.addAction(tr("Restore Default"), [&] {
+ buttons_param[button_id] = Common::ParamPackage{
+ InputCommon::GenerateKeyboardParam(Config::default_buttons[button_id])};
+ button_map[button_id]->setText(ButtonToText(buttons_param[button_id]));
+ });
+ context_menu.exec(button_map[button_id]->mapToGlobal(menu_location));
+ });
}
for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
- if (analog_map_buttons[analog_id][sub_button_id] != nullptr) {
- connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released,
- [=]() {
- handleClick(analog_map_buttons[analog_id][sub_button_id],
- [=](const Common::ParamPackage& params) {
- SetAnalogButton(params, analogs_param[analog_id],
- analog_sub_buttons[sub_button_id]);
- },
- InputCommon::Polling::DeviceType::Button);
+ if (!analog_map_buttons[analog_id][sub_button_id])
+ continue;
+ analog_map_buttons[analog_id][sub_button_id]->setContextMenuPolicy(
+ Qt::CustomContextMenu);
+ connect(analog_map_buttons[analog_id][sub_button_id], &QPushButton::released, [=]() {
+ handleClick(analog_map_buttons[analog_id][sub_button_id],
+ [=](const Common::ParamPackage& params) {
+ SetAnalogButton(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ },
+ InputCommon::Polling::DeviceType::Button);
+ });
+ connect(analog_map_buttons[analog_id][sub_button_id],
+ &QPushButton::customContextMenuRequested, [=](const QPoint& menu_location) {
+ QMenu context_menu;
+ context_menu.addAction(tr("Clear"), [&] {
+ analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ analog_map_buttons[analog_id][sub_button_id]->setText(tr("[not set]"));
});
- }
+ context_menu.addAction(tr("Restore Default"), [&] {
+ Common::ParamPackage params{InputCommon::GenerateKeyboardParam(
+ Config::default_analogs[analog_id][sub_button_id])};
+ SetAnalogButton(params, analogs_param[analog_id],
+ analog_sub_buttons[sub_button_id]);
+ analog_map_buttons[analog_id][sub_button_id]->setText(AnalogToText(
+ analogs_param[analog_id], analog_sub_buttons[sub_button_id]));
+ });
+ context_menu.exec(analog_map_buttons[analog_id][sub_button_id]->mapToGlobal(
+ menu_location));
+ });
}
connect(analog_map_stick[analog_id], &QPushButton::released, [=]() {
QMessageBox::information(this, tr("Information"),
@@ -162,6 +198,7 @@ ConfigureInput::ConfigureInput(QWidget* parent)
});
}
+ connect(ui->buttonClearAll, &QPushButton::released, [this] { ClearAll(); });
connect(ui->buttonRestoreDefaults, &QPushButton::released, [this]() { restoreDefaults(); });
timeout_timer->setSingleShot(true);
@@ -215,7 +252,21 @@ void ConfigureInput::restoreDefaults() {
}
}
updateButtonLabels();
- applyConfiguration();
+}
+
+void ConfigureInput::ClearAll() {
+ for (int button_id = 0; button_id < Settings::NativeButton::NumButtons; button_id++) {
+ if (button_map[button_id] && button_map[button_id]->isEnabled())
+ buttons_param[button_id].Clear();
+ }
+ for (int analog_id = 0; analog_id < Settings::NativeAnalog::NumAnalogs; analog_id++) {
+ for (int sub_button_id = 0; sub_button_id < ANALOG_SUB_BUTTONS_NUM; sub_button_id++) {
+ if (analog_map_buttons[analog_id][sub_button_id] &&
+ analog_map_buttons[analog_id][sub_button_id]->isEnabled())
+ analogs_param[analog_id].Erase(analog_sub_buttons[sub_button_id]);
+ }
+ }
+ updateButtonLabels();
}
void ConfigureInput::updateButtonLabels() {
@@ -271,7 +322,7 @@ void ConfigureInput::setPollingResult(const Common::ParamPackage& params, bool a
}
updateButtonLabels();
- input_setter = boost::none;
+ input_setter = {};
}
void ConfigureInput::keyPressEvent(QKeyEvent* event) {
diff --git a/src/yuzu/configuration/configure_input.h b/src/yuzu/configuration/configure_input.h
index a0bef86d5..32c7183f9 100644
--- a/src/yuzu/configuration/configure_input.h
+++ b/src/yuzu/configuration/configure_input.h
@@ -7,11 +7,13 @@
#include <array>
#include <functional>
#include <memory>
+#include <optional>
#include <string>
#include <unordered_map>
+
#include <QKeyEvent>
#include <QWidget>
-#include <boost/optional.hpp>
+
#include "common/param_package.h"
#include "core/settings.h"
#include "input_common/main.h"
@@ -41,7 +43,7 @@ private:
std::unique_ptr<QTimer> poll_timer;
/// This will be the the setting function when an input is awaiting configuration.
- boost::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
+ std::optional<std::function<void(const Common::ParamPackage&)>> input_setter;
std::array<Common::ParamPackage, Settings::NativeButton::NumButtons> buttons_param;
std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs> analogs_param;
@@ -72,6 +74,9 @@ private:
void loadConfiguration();
/// Restore all buttons to their default values.
void restoreDefaults();
+ /// Clear all input configuration
+ void ClearAll();
+
/// Update UI to reflect current configuration.
void updateButtonLabels();
diff --git a/src/yuzu/configuration/configure_input.ui b/src/yuzu/configuration/configure_input.ui
index 8bfa5df62..8a019a693 100644
--- a/src/yuzu/configuration/configure_input.ui
+++ b/src/yuzu/configuration/configure_input.ui
@@ -695,6 +695,34 @@ Capture:</string>
</spacer>
</item>
<item>
+ <widget class="QPushButton" name="buttonClearAll">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="sizeIncrement">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="baseSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="layoutDirection">
+ <enum>Qt::LeftToRight</enum>
+ </property>
+ <property name="text">
+ <string>Clear All</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QPushButton" name="buttonRestoreDefaults">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index e9ed9c38f..b4b4a4a56 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -2,14 +2,27 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <QFileDialog>
+#include <QGraphicsItem>
+#include <QGraphicsScene>
+#include <QHeaderView>
#include <QMessageBox>
+#include <QStandardItemModel>
+#include <QTreeView>
+#include <QVBoxLayout>
+#include "common/assert.h"
+#include "common/file_util.h"
+#include "common/string_util.h"
#include "core/core.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "ui_configure_system.h"
#include "yuzu/configuration/configure_system.h"
-#include "yuzu/main.h"
+#include "yuzu/util/limitable_input_dialog.h"
-static const std::array<int, 12> days_in_month = {{
+namespace {
+constexpr std::array<int, 12> days_in_month = {{
31,
29,
31,
@@ -24,13 +37,108 @@ static const std::array<int, 12> days_in_month = {{
31,
}};
-ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) {
+// Same backup JPEG used by acc IProfile::GetImage if no jpeg found
+constexpr std::array<u8, 107> backup_jpeg{
+ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05,
+ 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e,
+ 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13,
+ 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01,
+ 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08,
+ 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9,
+};
+
+QString GetImagePath(Service::Account::UUID uuid) {
+ const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
+ "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
+ return QString::fromStdString(path);
+}
+
+QString GetAccountUsername(const Service::Account::ProfileManager& manager,
+ Service::Account::UUID uuid) {
+ Service::Account::ProfileBase profile;
+ if (!manager.GetProfileBase(uuid, profile)) {
+ return {};
+ }
+
+ const auto text = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+ return QString::fromStdString(text);
+}
+
+QString FormatUserEntryText(const QString& username, Service::Account::UUID uuid) {
+ return ConfigureSystem::tr("%1\n%2",
+ "%1 is the profile username, %2 is the formatted UUID (e.g. "
+ "00112233-4455-6677-8899-AABBCCDDEEFF))")
+ .arg(username, QString::fromStdString(uuid.FormatSwitch()));
+}
+
+QPixmap GetIcon(Service::Account::UUID uuid) {
+ QPixmap icon{GetImagePath(uuid)};
+
+ if (!icon) {
+ icon.fill(Qt::black);
+ icon.loadFromData(backup_jpeg.data(), static_cast<u32>(backup_jpeg.size()));
+ }
+
+ return icon.scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+}
+
+QString GetProfileUsernameFromUser(QWidget* parent, const QString& description_text) {
+ return LimitableInputDialog::GetText(parent, ConfigureSystem::tr("Enter Username"),
+ description_text, 1,
+ static_cast<int>(Service::Account::profile_username_size));
+}
+} // Anonymous namespace
+
+ConfigureSystem::ConfigureSystem(QWidget* parent)
+ : QWidget(parent), ui(new Ui::ConfigureSystem),
+ profile_manager(std::make_unique<Service::Account::ProfileManager>()) {
ui->setupUi(this);
connect(ui->combo_birthmonth,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
- &ConfigureSystem::updateBirthdayComboBox);
+ &ConfigureSystem::UpdateBirthdayComboBox);
connect(ui->button_regenerate_console_id, &QPushButton::clicked, this,
- &ConfigureSystem::refreshConsoleID);
+ &ConfigureSystem::RefreshConsoleID);
+
+ layout = new QVBoxLayout;
+ tree_view = new QTreeView;
+ item_model = new QStandardItemModel(tree_view);
+ tree_view->setModel(item_model);
+
+ tree_view->setAlternatingRowColors(true);
+ tree_view->setSelectionMode(QHeaderView::SingleSelection);
+ tree_view->setSelectionBehavior(QHeaderView::SelectRows);
+ tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
+ tree_view->setSortingEnabled(true);
+ tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
+ tree_view->setUniformRowHeights(true);
+ tree_view->setIconSize({64, 64});
+ tree_view->setContextMenuPolicy(Qt::NoContextMenu);
+
+ item_model->insertColumns(0, 1);
+ item_model->setHeaderData(0, Qt::Horizontal, "Users");
+
+ // We must register all custom types with the Qt Automoc system so that we are able to use it
+ // with signals/slots. In this case, QList falls under the umbrells of custom types.
+ qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
+
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->setSpacing(0);
+ layout->addWidget(tree_view);
+
+ ui->scrollArea->setLayout(layout);
+
+ connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser);
+
+ connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser);
+ connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser);
+ connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser);
+ connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage);
+
+ scene = new QGraphicsScene;
+ ui->current_user_icon->setScene(scene);
this->setConfiguration();
}
@@ -39,8 +147,45 @@ ConfigureSystem::~ConfigureSystem() = default;
void ConfigureSystem::setConfiguration() {
enabled = !Core::System::GetInstance().IsPoweredOn();
- ui->edit_username->setText(QString::fromStdString(Settings::values.username));
+
ui->combo_language->setCurrentIndex(Settings::values.language_index);
+
+ item_model->removeRows(0, item_model->rowCount());
+ list_items.clear();
+
+ PopulateUserList();
+ UpdateCurrentUser();
+}
+
+void ConfigureSystem::PopulateUserList() {
+ const auto& profiles = profile_manager->GetAllUsers();
+ for (const auto& user : profiles) {
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(user, profile))
+ continue;
+
+ const auto username = Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
+
+ list_items.push_back(QList<QStandardItem*>{new QStandardItem{
+ GetIcon(user), FormatUserEntryText(QString::fromStdString(username), user)}});
+ }
+
+ for (const auto& item : list_items)
+ item_model->appendRow(item);
+}
+
+void ConfigureSystem::UpdateCurrentUser() {
+ ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS);
+
+ const auto& current_user = profile_manager->GetUser(Settings::values.current_user);
+ ASSERT(current_user);
+ const auto username = GetAccountUsername(*profile_manager, *current_user);
+
+ scene->clear();
+ scene->addPixmap(
+ GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+ ui->current_user_username->setText(username);
}
void ConfigureSystem::ReadSystemSettings() {}
@@ -48,12 +193,12 @@ void ConfigureSystem::ReadSystemSettings() {}
void ConfigureSystem::applyConfiguration() {
if (!enabled)
return;
- Settings::values.username = ui->edit_username->text().toStdString();
+
Settings::values.language_index = ui->combo_language->currentIndex();
Settings::Apply();
}
-void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
+void ConfigureSystem::UpdateBirthdayComboBox(int birthmonth_index) {
if (birthmonth_index < 0 || birthmonth_index >= 12)
return;
@@ -78,7 +223,7 @@ void ConfigureSystem::updateBirthdayComboBox(int birthmonth_index) {
ui->combo_birthday->setCurrentIndex(birthday_index);
}
-void ConfigureSystem::refreshConsoleID() {
+void ConfigureSystem::RefreshConsoleID() {
QMessageBox::StandardButton reply;
QString warning_text = tr("This will replace your current virtual Switch with a new one. "
"Your current virtual Switch will not be recoverable. "
@@ -92,3 +237,130 @@ void ConfigureSystem::refreshConsoleID() {
ui->label_console_id->setText(
tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper()));
}
+
+void ConfigureSystem::SelectUser(const QModelIndex& index) {
+ Settings::values.current_user =
+ std::clamp<std::size_t>(index.row(), 0, profile_manager->GetUserCount() - 1);
+
+ UpdateCurrentUser();
+
+ ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2);
+ ui->pm_rename->setEnabled(true);
+ ui->pm_set_image->setEnabled(true);
+}
+
+void ConfigureSystem::AddUser() {
+ const auto username =
+ GetProfileUsernameFromUser(this, tr("Enter a username for the new user:"));
+ if (username.isEmpty()) {
+ return;
+ }
+
+ const auto uuid = Service::Account::UUID::Generate();
+ profile_manager->CreateNewUser(uuid, username.toStdString());
+
+ item_model->appendRow(new QStandardItem{GetIcon(uuid), FormatUserEntryText(username, uuid)});
+}
+
+void ConfigureSystem::RenameUser() {
+ const auto user = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(user);
+ ASSERT(uuid);
+
+ Service::Account::ProfileBase profile;
+ if (!profile_manager->GetProfileBase(*uuid, profile))
+ return;
+
+ const auto new_username = GetProfileUsernameFromUser(this, tr("Enter a new username:"));
+ if (new_username.isEmpty()) {
+ return;
+ }
+
+ const auto username_std = new_username.toStdString();
+ std::fill(profile.username.begin(), profile.username.end(), '\0');
+ std::copy(username_std.begin(), username_std.end(), profile.username.begin());
+
+ profile_manager->SetProfileBase(*uuid, profile);
+
+ item_model->setItem(
+ user, 0,
+ new QStandardItem{GetIcon(*uuid),
+ FormatUserEntryText(QString::fromStdString(username_std), *uuid)});
+ UpdateCurrentUser();
+}
+
+void ConfigureSystem::DeleteUser() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+
+ const auto confirm = QMessageBox::question(
+ this, tr("Confirm Delete"),
+ tr("You are about to delete user with name \"%1\". Are you sure?").arg(username));
+
+ if (confirm == QMessageBox::No)
+ return;
+
+ if (Settings::values.current_user == tree_view->currentIndex().row())
+ Settings::values.current_user = 0;
+ UpdateCurrentUser();
+
+ if (!profile_manager->RemoveUser(*uuid))
+ return;
+
+ item_model->removeRows(tree_view->currentIndex().row(), 1);
+ tree_view->clearSelection();
+
+ ui->pm_remove->setEnabled(false);
+ ui->pm_rename->setEnabled(false);
+}
+
+void ConfigureSystem::SetUserImage() {
+ const auto index = tree_view->currentIndex().row();
+ const auto uuid = profile_manager->GetUser(index);
+ ASSERT(uuid);
+
+ const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(),
+ tr("JPEG Images (*.jpg *.jpeg)"));
+
+ if (file.isEmpty()) {
+ return;
+ }
+
+ const auto image_path = GetImagePath(*uuid);
+ if (QFile::exists(image_path) && !QFile::remove(image_path)) {
+ QMessageBox::warning(
+ this, tr("Error deleting image"),
+ tr("Error occurred attempting to overwrite previous image at: %1.").arg(image_path));
+ return;
+ }
+
+ const auto raw_path = QString::fromStdString(
+ FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010");
+ const QFileInfo raw_info{raw_path};
+ if (raw_info.exists() && !raw_info.isDir() && !QFile::remove(raw_path)) {
+ QMessageBox::warning(this, tr("Error deleting file"),
+ tr("Unable to delete existing file: %1.").arg(raw_path));
+ return;
+ }
+
+ const QString absolute_dst_path = QFileInfo{image_path}.absolutePath();
+ if (!QDir{raw_path}.mkpath(absolute_dst_path)) {
+ QMessageBox::warning(
+ this, tr("Error creating user image directory"),
+ tr("Unable to create directory %1 for storing user images.").arg(absolute_dst_path));
+ return;
+ }
+
+ if (!QFile::copy(file, image_path)) {
+ QMessageBox::warning(this, tr("Error copying user image"),
+ tr("Unable to copy image from %1 to %2").arg(file, image_path));
+ return;
+ }
+
+ const auto username = GetAccountUsername(*profile_manager, *uuid);
+ item_model->setItem(index, 0,
+ new QStandardItem{GetIcon(*uuid), FormatUserEntryText(username, *uuid)});
+ UpdateCurrentUser();
+}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index f13de17d4..07764e1f7 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -5,8 +5,20 @@
#pragma once
#include <memory>
+
+#include <QList>
#include <QWidget>
+class QGraphicsScene;
+class QStandardItem;
+class QStandardItemModel;
+class QTreeView;
+class QVBoxLayout;
+
+namespace Service::Account {
+class ProfileManager;
+}
+
namespace Ui {
class ConfigureSystem;
}
@@ -16,23 +28,39 @@ class ConfigureSystem : public QWidget {
public:
explicit ConfigureSystem(QWidget* parent = nullptr);
- ~ConfigureSystem();
+ ~ConfigureSystem() override;
void applyConfiguration();
void setConfiguration();
-public slots:
- void updateBirthdayComboBox(int birthmonth_index);
- void refreshConsoleID();
-
private:
void ReadSystemSettings();
+ void UpdateBirthdayComboBox(int birthmonth_index);
+ void RefreshConsoleID();
+
+ void PopulateUserList();
+ void UpdateCurrentUser();
+ void SelectUser(const QModelIndex& index);
+ void AddUser();
+ void RenameUser();
+ void DeleteUser();
+ void SetUserImage();
+
+ QVBoxLayout* layout;
+ QTreeView* tree_view;
+ QStandardItemModel* item_model;
+ QGraphicsScene* scene;
+
+ std::vector<QList<QStandardItem*>> list_items;
+
std::unique_ptr<Ui::ConfigureSystem> ui;
- bool enabled;
+ bool enabled = false;
+
+ int birthmonth = 0;
+ int birthday = 0;
+ int language_index = 0;
+ int sound_index = 0;
- std::u16string username;
- int birthmonth, birthday;
- int language_index;
- int sound_index;
+ std::unique_ptr<Service::Account::ProfileManager> profile_manager;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index f3f8db038..020b32a37 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>360</width>
- <height>377</height>
+ <height>483</height>
</rect>
</property>
<property name="windowTitle">
@@ -22,34 +22,28 @@
<string>System Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout">
- <item row="0" column="0">
- <widget class="QLabel" name="label_username">
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_language">
<property name="text">
- <string>Username</string>
+ <string>Language</string>
</property>
</widget>
</item>
- <item row="0" column="1">
- <widget class="QLineEdit" name="edit_username">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="maxLength">
- <number>32</number>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_birthday">
+ <property name="text">
+ <string>Birthday</string>
</property>
</widget>
</item>
- <item row="1" column="0">
- <widget class="QLabel" name="label_birthday">
+ <item row="3" column="0">
+ <widget class="QLabel" name="label_console_id">
<property name="text">
- <string>Birthday</string>
+ <string>Console ID:</string>
</property>
</widget>
</item>
- <item row="1" column="1">
+ <item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_birthday2">
<item>
<widget class="QComboBox" name="combo_birthmonth">
@@ -120,14 +114,7 @@
</item>
</layout>
</item>
- <item row="2" column="0">
- <widget class="QLabel" name="label_language">
- <property name="text">
- <string>Language</string>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
+ <item row="1" column="1">
<widget class="QComboBox" name="combo_language">
<property name="toolTip">
<string>Note: this can be overridden when region setting is auto-select</string>
@@ -187,31 +174,31 @@
<string>Russian (Русский)</string>
</property>
</item>
- <item>
- <property name="text">
- <string>Taiwanese</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>British English</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Canadian French</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Latin American Spanish</string>
- </property>
- </item>
- <item>
- <property name="text">
- <string>Simplified Chinese</string>
- </property>
- </item>
+ <item>
+ <property name="text">
+ <string>Taiwanese</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>British English</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Canadian French</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Latin American Spanish</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Simplified Chinese</string>
+ </property>
+ </item>
<item>
<property name="text">
<string>Traditional Chinese (正體中文)</string>
@@ -219,14 +206,14 @@
</item>
</widget>
</item>
- <item row="3" column="0">
+ <item row="2" column="0">
<widget class="QLabel" name="label_sound">
<property name="text">
<string>Sound output mode</string>
</property>
</widget>
</item>
- <item row="3" column="1">
+ <item row="2" column="1">
<widget class="QComboBox" name="combo_sound">
<item>
<property name="text">
@@ -245,14 +232,7 @@
</item>
</widget>
</item>
- <item row="4" column="0">
- <widget class="QLabel" name="label_console_id">
- <property name="text">
- <string>Console ID:</string>
- </property>
- </widget>
- </item>
- <item row="4" column="1">
+ <item row="3" column="1">
<widget class="QPushButton" name="button_regenerate_console_id">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
@@ -272,6 +252,143 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="gridGroupBox">
+ <property name="title">
+ <string>Profile Manager</string>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <property name="sizeConstraint">
+ <enum>QLayout::SetNoConstraint</enum>
+ </property>
+ <item row="0" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Current User</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGraphicsView" name="current_user_icon">
+ <property name="minimumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>48</width>
+ <height>48</height>
+ </size>
+ </property>
+ <property name="verticalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="horizontalScrollBarPolicy">
+ <enum>Qt::ScrollBarAlwaysOff</enum>
+ </property>
+ <property name="interactive">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="current_user_username">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="0">
+ <widget class="QScrollArea" name="scrollArea">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="widgetResizable">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QPushButton" name="pm_set_image">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Set Image</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_add">
+ <property name="text">
+ <string>Add</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_rename">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Rename</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="pm_remove">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Remove</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QLabel" name="label_disable_info">
<property name="text">
<string>System settings are available only when game is not running.</string>
@@ -281,19 +398,6 @@
</property>
</widget>
</item>
- <item>
- <spacer name="verticalSpacer">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
index b5c88f944..67ed0ba6d 100644
--- a/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <QLabel>
#include <QMetaType>
#include <QPushButton>
@@ -32,21 +31,8 @@ QVariant BreakPointModel::data(const QModelIndex& index, int role) const {
switch (role) {
case Qt::DisplayRole: {
if (index.column() == 0) {
- static const std::map<Tegra::DebugContext::Event, QString> map = {
- {Tegra::DebugContext::Event::MaxwellCommandLoaded, tr("Maxwell command loaded")},
- {Tegra::DebugContext::Event::MaxwellCommandProcessed,
- tr("Maxwell command processed")},
- {Tegra::DebugContext::Event::IncomingPrimitiveBatch,
- tr("Incoming primitive batch")},
- {Tegra::DebugContext::Event::FinishedPrimitiveBatch,
- tr("Finished primitive batch")},
- };
-
- DEBUG_ASSERT(map.size() ==
- static_cast<std::size_t>(Tegra::DebugContext::Event::NumEvents));
- return (map.find(event) != map.end()) ? map.at(event) : QString();
+ return DebugContextEventToString(event);
}
-
break;
}
@@ -128,6 +114,23 @@ void BreakPointModel::OnResumed() {
active_breakpoint = context->active_breakpoint;
}
+QString BreakPointModel::DebugContextEventToString(Tegra::DebugContext::Event event) {
+ switch (event) {
+ case Tegra::DebugContext::Event::MaxwellCommandLoaded:
+ return tr("Maxwell command loaded");
+ case Tegra::DebugContext::Event::MaxwellCommandProcessed:
+ return tr("Maxwell command processed");
+ case Tegra::DebugContext::Event::IncomingPrimitiveBatch:
+ return tr("Incoming primitive batch");
+ case Tegra::DebugContext::Event::FinishedPrimitiveBatch:
+ return tr("Finished primitive batch");
+ case Tegra::DebugContext::Event::NumEvents:
+ break;
+ }
+
+ return tr("Unknown debug context event");
+}
+
GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(
std::shared_ptr<Tegra::DebugContext> debug_context, QWidget* parent)
: QDockWidget(tr("Maxwell Breakpoints"), parent), Tegra::DebugContext::BreakPointObserver(
diff --git a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
index 7112b87e6..fb488e38f 100644
--- a/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
+++ b/src/yuzu/debugger/graphics/graphics_breakpoints_p.h
@@ -29,6 +29,8 @@ public:
void OnResumed();
private:
+ static QString DebugContextEventToString(Tegra::DebugContext::Event event);
+
std::weak_ptr<Tegra::DebugContext> context_weak;
bool at_breakpoint;
Tegra::DebugContext::Event active_breakpoint;
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
index cbcd5dd5f..707747422 100644
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -382,12 +382,13 @@ void GraphicsSurfaceWidget::OnUpdate() {
// TODO: Implement a good way to visualize alpha components!
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
- boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
+ std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
- *address, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height);
+ *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width,
+ surface_height, 1U);
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
surface_width, surface_height);
@@ -443,7 +444,7 @@ void GraphicsSurfaceWidget::SaveSurface() {
pixmap->save(&file, "PNG");
} else if (selectedFilter == bin_filter) {
auto& gpu = Core::System::GetInstance().GPU();
- boost::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
+ std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
const u8* buffer = Memory::GetPointer(*address);
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp
index 4a09da685..0c831c9f4 100644
--- a/src/yuzu/debugger/wait_tree.cpp
+++ b/src/yuzu/debugger/wait_tree.cpp
@@ -9,8 +9,8 @@
#include "core/core.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/handle_table.h"
-#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
@@ -66,10 +66,11 @@ std::vector<std::unique_ptr<WaitTreeThread>> WaitTreeItem::MakeThreadItemList()
}
};
- add_threads(Core::System::GetInstance().Scheduler(0)->GetThreadList());
- add_threads(Core::System::GetInstance().Scheduler(1)->GetThreadList());
- add_threads(Core::System::GetInstance().Scheduler(2)->GetThreadList());
- add_threads(Core::System::GetInstance().Scheduler(3)->GetThreadList());
+ const auto& system = Core::System::GetInstance();
+ add_threads(system.Scheduler(0).GetThreadList());
+ add_threads(system.Scheduler(1).GetThreadList());
+ add_threads(system.Scheduler(2).GetThreadList());
+ add_threads(system.Scheduler(3).GetThreadList());
return item_list;
}
@@ -82,7 +83,7 @@ QString WaitTreeText::GetText() const {
}
WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
- auto& handle_table = Core::System::GetInstance().Kernel().HandleTable();
+ const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
mutex_value = Memory::Read32(mutex_address);
owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
diff --git a/src/yuzu/debugger/wait_tree.h b/src/yuzu/debugger/wait_tree.h
index defbf734f..331f89885 100644
--- a/src/yuzu/debugger/wait_tree.h
+++ b/src/yuzu/debugger/wait_tree.h
@@ -11,7 +11,6 @@
#include <QAbstractItemModel>
#include <QDockWidget>
#include <QTreeView>
-#include <boost/container/flat_set.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/object.h"
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 67890455a..a5a4aa432 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -16,7 +16,6 @@
#include <fmt/format.h>
#include "common/common_paths.h"
#include "common/common_types.h"
-#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/file_sys/patch_manager.h"
#include "yuzu/compatibility_list.h"
@@ -217,11 +216,11 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
item_model->insertColumns(0, COLUMN_COUNT);
- item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
- item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
- item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons");
- item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
- item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
+ item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
+ item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, tr("Compatibility"));
+ item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, tr("Add-ons"));
+ item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, tr("File type"));
+ item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, tr("Size"));
connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
@@ -387,9 +386,9 @@ void GameList::LoadCompatibilityList() {
}
void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
- if (!FileUtil::Exists(dir_path.toStdString()) ||
- !FileUtil::IsDirectory(dir_path.toStdString())) {
- LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toLocal8Bit().data());
+ const QFileInfo dir_info{dir_path};
+ if (!dir_info.exists() || !dir_info.isDir()) {
+ LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
search_field->setFilterResult(0, 0);
return;
}
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 1947bdb93..3d865a12d 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -27,9 +27,8 @@
#include "yuzu/ui_settings.h"
namespace {
-void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
- const std::shared_ptr<FileSys::NCA>& nca, std::vector<u8>& icon,
- std::string& name) {
+void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
+ std::vector<u8>& icon, std::string& name) {
auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
if (icon_file != nullptr)
icon = icon_file->ReadAllBytes();
@@ -57,16 +56,30 @@ QString FormatGameName(const std::string& physical_name) {
return physical_name_as_qstring;
}
-QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager, bool updatable = true) {
+QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
+ Loader::AppLoader& loader, bool updatable = true) {
QString out;
- for (const auto& kv : patch_manager.GetPatchVersionNames()) {
- if (!updatable && kv.first == "Update")
+ FileSys::VirtualFile update_raw;
+ loader.ReadUpdateRaw(update_raw);
+ for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
+ const bool is_update = kv.first == "Update";
+ if (!updatable && is_update) {
continue;
+ }
+
+ const QString type = QString::fromStdString(kv.first);
if (kv.second.empty()) {
- out.append(fmt::format("{}\n", kv.first).c_str());
+ out.append(QStringLiteral("%1\n").arg(type));
} else {
- out.append(fmt::format("{} ({})\n", kv.first, kv.second).c_str());
+ auto ver = kv.second;
+
+ // Display container name for packed updates
+ if (is_update && ver == "PACKED") {
+ ver = Loader::GetFileTypeString(loader.GetFileType());
+ }
+
+ out.append(QStringLiteral("%1 (%2)\n").arg(type, QString::fromStdString(ver)));
}
}
@@ -88,7 +101,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
FileSys::ContentRecordType::Program);
for (const auto& game : installed_games) {
- const auto& file = cache->GetEntryUnparsed(game);
+ const auto file = cache->GetEntryUnparsed(game);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(file);
if (!loader)
continue;
@@ -99,9 +112,9 @@ void GameListWorker::AddInstalledTitlesToGameList() {
loader->ReadProgramId(program_id);
const FileSys::PatchManager patch{program_id};
- const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
+ const auto control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
if (control != nullptr)
- GetMetadataFromControlNCA(patch, control, icon, name);
+ GetMetadataFromControlNCA(patch, *control, icon, name);
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -116,7 +129,7 @@ void GameListWorker::AddInstalledTitlesToGameList() {
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItemCompat(compatibility),
- new GameListItem(FormatPatchNameVersions(patch)),
+ new GameListItem(FormatPatchNameVersions(patch, *loader)),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(file->GetSize()),
@@ -127,9 +140,10 @@ void GameListWorker::AddInstalledTitlesToGameList() {
FileSys::ContentRecordType::Control);
for (const auto& entry : control_data) {
- const auto nca = cache->GetEntry(entry);
- if (nca != nullptr)
- nca_control_map.insert_or_assign(entry.title_id, nca);
+ auto nca = cache->GetEntry(entry);
+ if (nca != nullptr) {
+ nca_control_map.insert_or_assign(entry.title_id, std::move(nca));
+ }
}
}
@@ -145,9 +159,11 @@ void GameListWorker::FillControlMap(const std::string& dir_path) {
QFileInfo file_info(physical_name.c_str());
if (!is_dir && file_info.suffix().toStdString() == "nca") {
auto nca =
- std::make_shared<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
- if (nca->GetType() == FileSys::NCAContentType::Control)
- nca_control_map.insert_or_assign(nca->GetTitleId(), nca);
+ std::make_unique<FileSys::NCA>(vfs->OpenFile(physical_name, FileSys::Mode::Read));
+ if (nca->GetType() == FileSys::NCAContentType::Control) {
+ const u64 title_id = nca->GetTitleId();
+ nca_control_map.insert_or_assign(title_id, std::move(nca));
+ }
}
return true;
};
@@ -188,8 +204,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
res2 == Loader::ResultStatus::Success) {
// Use from metadata pool.
if (nca_control_map.find(program_id) != nca_control_map.end()) {
- const auto nca = nca_control_map[program_id];
- GetMetadataFromControlNCA(patch, nca, icon, name);
+ const auto& nca = nca_control_map[program_id];
+ GetMetadataFromControlNCA(patch, *nca, icon, name);
}
}
@@ -206,7 +222,8 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
program_id),
new GameListItemCompat(compatibility),
- new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
+ new GameListItem(
+ FormatPatchNameVersions(patch, *loader, loader->IsRomFSUpdatable())),
new GameListItem(
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 09d20c42f..0e42d0bde 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -63,7 +63,7 @@ private:
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
std::shared_ptr<FileSys::VfsFilesystem> vfs;
- std::map<u64, std::shared_ptr<FileSys::NCA>> nca_control_map;
+ std::map<u64, std::unique_ptr<FileSys::NCA>> nca_control_map;
QStringList watch_list;
QString dir_path;
bool deep_scan;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 1d06d6c95..74a44be37 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -10,6 +10,7 @@
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
+#include "core/hle/service/acc/profile_manager.h"
// These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows
// defines.
@@ -29,8 +30,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#define QT_NO_OPENGL
#include <QDesktopWidget>
#include <QDialogButtonBox>
+#include <QFile>
#include <QFileDialog>
#include <QMessageBox>
+#include <QtConcurrent/QtConcurrent>
#include <QtGui>
#include <QtWidgets>
#include <fmt/format.h>
@@ -59,6 +62,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_ldr.h"
+#include "core/hle/service/nfp/nfp.h"
+#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/settings.h"
@@ -99,6 +104,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
+constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000;
+
/**
* "Callouts" are one-time instructional messages shown to the user. In the config settings, there
* is a bitfield "callout_flags" options, used to track if a message has already been shown to the
@@ -174,8 +181,11 @@ GMainWindow::GMainWindow()
.arg(Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc));
show();
+ // Gen keys if necessary
+ OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
+
// Necessary to load titles from nand in gamelist.
- Service::FileSystem::CreateFactories(vfs);
+ Service::FileSystem::CreateFactories(*vfs);
game_list->LoadCompatibilityList();
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
@@ -421,6 +431,7 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
[this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); });
connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close);
+ connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo);
// Emulation
connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame);
@@ -446,6 +457,9 @@ void GMainWindow::ConnectMenuEvents() {
connect(ui.action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
// Help
+ connect(ui.action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
+ connect(ui.action_Rederive, &QAction::triggered, this,
+ std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
connect(ui.action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
}
@@ -488,6 +502,8 @@ QStringList GMainWindow::GetUnsupportedGLExtensions() {
unsupported_ext.append("ARB_texture_storage");
if (!GLAD_GL_ARB_multi_bind)
unsupported_ext.append("ARB_multi_bind");
+ if (!GLAD_GL_ARB_copy_image)
+ unsupported_ext.append("ARB_copy_image");
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)
@@ -685,6 +701,7 @@ void GMainWindow::ShutdownGame() {
ui.action_Stop->setEnabled(false);
ui.action_Restart->setEnabled(false);
ui.action_Report_Compatibility->setEnabled(false);
+ ui.action_Load_Amiibo->setEnabled(false);
render_window->hide();
game_list->show();
game_list->setFilterFocus();
@@ -746,12 +763,43 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target
open_target = "Save Data";
const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir);
ASSERT(program_id != 0);
- // TODO(tech4me): Update this to work with arbitrary user profile
- // Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor
- constexpr u128 user_id = {1, 0};
+
+ Service::Account::ProfileManager manager{};
+ const auto user_ids = manager.GetAllUsers();
+ QStringList list;
+ for (const auto& user_id : user_ids) {
+ if (user_id == Service::Account::UUID{})
+ continue;
+ Service::Account::ProfileBase base;
+ if (!manager.GetProfileBase(user_id, base))
+ continue;
+
+ list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer(
+ reinterpret_cast<const char*>(base.username.data()), base.username.size())));
+ }
+
+ bool ok = false;
+ const auto index_string =
+ QInputDialog::getItem(this, tr("Select User"),
+ tr("Please select the user's save data you would like to open."),
+ list, Settings::values.current_user, false, &ok);
+ if (!ok)
+ return;
+
+ const auto index = list.indexOf(index_string);
+ ASSERT(index != -1 && index < 8);
+
+ const auto user_id = manager.GetUser(index);
+ ASSERT(user_id);
path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser,
FileSys::SaveDataType::SaveData,
- program_id, user_id, 0);
+ program_id, user_id->uuid, 0);
+
+ if (!FileUtil::Exists(path)) {
+ FileUtil::CreateFullPath(path);
+ FileUtil::CreateDir(path);
+ }
+
break;
}
case GameListOpenTarget::ModData: {
@@ -818,14 +866,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
}
void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) {
- const auto path = fmt::format("{}{:016X}/romfs",
- FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id);
-
- const auto failed = [this, &path] {
+ const auto failed = [this] {
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
tr("There was an error copying the RomFS files or the user "
"cancelled the operation."));
- vfs->DeleteDirectory(path);
};
const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read));
@@ -840,10 +884,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
return;
}
- const auto romfs =
- loader->IsRomFSUpdatable()
- ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset())
- : file;
+ const auto installed = Service::FileSystem::GetUnionContents();
+ auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id);
+
+ if (!romfs_title_id) {
+ failed();
+ return;
+ }
+
+ const auto path = fmt::format(
+ "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id);
+
+ FileSys::VirtualFile romfs;
+
+ if (*romfs_title_id == program_id) {
+ romfs = file;
+ } else {
+ romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS();
+ }
const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full);
if (extracted == nullptr) {
@@ -855,6 +913,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
if (out == nullptr) {
failed();
+ vfs->DeleteDirectory(path);
return;
}
@@ -865,8 +924,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
"files into the new directory while <br>skeleton will only create the directory "
"structure."),
{"Full", "Skeleton"}, 0, false, &ok);
- if (!ok)
+ if (!ok) {
failed();
+ vfs->DeleteDirectory(path);
+ return;
+ }
const auto full = res == "Full";
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
@@ -883,6 +945,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
} else {
progress.close();
failed();
+ vfs->DeleteDirectory(path);
}
}
@@ -903,22 +966,20 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
}
void GMainWindow::OnMenuLoadFile() {
- QString extensions;
- for (const auto& piece : game_list->supported_file_extensions)
- extensions += "*." + piece + " ";
-
- extensions += "main ";
-
- QString file_filter = tr("Switch Executable") + " (" + extensions + ")";
- file_filter += ";;" + tr("All Files (*.*)");
-
- QString filename = QFileDialog::getOpenFileName(this, tr("Load File"),
- UISettings::values.roms_path, file_filter);
- if (!filename.isEmpty()) {
- UISettings::values.roms_path = QFileInfo(filename).path();
+ const QString extensions =
+ QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
+ const QString file_filter = tr("Switch Executable (%1);;All Files (*.*)",
+ "%1 is an identifier for the Switch executable file extensions.")
+ .arg(extensions);
+ const QString filename = QFileDialog::getOpenFileName(
+ this, tr("Load File"), UISettings::values.roms_path, file_filter);
- BootGame(filename);
+ if (filename.isEmpty()) {
+ return;
}
+
+ UISettings::values.roms_path = QFileInfo(filename).path();
+ BootGame(filename);
}
void GMainWindow::OnMenuLoadFolder() {
@@ -1134,7 +1195,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
FileUtil::GetUserPath(target == EmulatedDirectoryTarget::SDMC ? FileUtil::UserPath::SDMCDir
: FileUtil::UserPath::NANDDir,
dir_path.toStdString());
- Service::FileSystem::CreateFactories(vfs);
+ Service::FileSystem::CreateFactories(*vfs);
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
}
}
@@ -1171,6 +1232,7 @@ void GMainWindow::OnStartGame() {
ui.action_Report_Compatibility->setEnabled(true);
discord_rpc->Update();
+ ui.action_Load_Amiibo->setEnabled(true);
}
void GMainWindow::OnPauseGame() {
@@ -1275,6 +1337,52 @@ void GMainWindow::OnConfigure() {
}
}
+void GMainWindow::OnLoadAmiibo() {
+ const QString extensions{"*.bin"};
+ const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions);
+ const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter);
+
+ if (filename.isEmpty()) {
+ return;
+ }
+
+ Core::System& system{Core::System::GetInstance()};
+ Service::SM::ServiceManager& sm = system.ServiceManager();
+ auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
+ if (nfc == nullptr) {
+ return;
+ }
+
+ QFile nfc_file{filename};
+ if (!nfc_file.open(QIODevice::ReadOnly)) {
+ QMessageBox::warning(this, tr("Error opening Amiibo data file"),
+ tr("Unable to open Amiibo file \"%1\" for reading.").arg(filename));
+ return;
+ }
+
+ const u64 nfc_file_size = nfc_file.size();
+ std::vector<u8> buffer(nfc_file_size);
+ const u64 read_size = nfc_file.read(reinterpret_cast<char*>(buffer.data()), nfc_file_size);
+ if (nfc_file_size != read_size) {
+ QMessageBox::warning(this, tr("Error reading Amiibo data file"),
+ tr("Unable to fully read Amiibo data. Expected to read %1 bytes, but "
+ "was only able to read %2 bytes.")
+ .arg(nfc_file_size)
+ .arg(read_size));
+ return;
+ }
+
+ if (!nfc->LoadAmiibo(buffer)) {
+ QMessageBox::warning(this, tr("Error loading Amiibo data"),
+ tr("Unable to load Amiibo data."));
+ }
+}
+
+void GMainWindow::OnOpenYuzuFolder() {
+ QDesktopServices::openUrl(QUrl::fromLocalFile(
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::UserDir))));
+}
+
void GMainWindow::OnAbout() {
AboutDialog aboutDialog(this);
aboutDialog.exec();
@@ -1315,15 +1423,17 @@ void GMainWindow::UpdateStatusBar() {
void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) {
QMessageBox::StandardButton answer;
QString status_message;
- const QString common_message = tr(
- "The game you are trying to load requires additional files from your Switch to be dumped "
- "before playing.<br/><br/>For more information on dumping these files, please see the "
- "following wiki page: <a "
- "href='https://yuzu-emu.org/wiki/"
- "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
- "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit "
- "back to the game list? Continuing emulation may result in crashes, corrupted save "
- "data, or other bugs.");
+ const QString common_message =
+ tr("The game you are trying to load requires additional files from your Switch to be "
+ "dumped "
+ "before playing.<br/><br/>For more information on dumping these files, please see the "
+ "following wiki page: <a "
+ "href='https://yuzu-emu.org/wiki/"
+ "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
+ "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
+ "quit "
+ "back to the game list? Continuing emulation may result in crashes, corrupted save "
+ "data, or other bugs.");
switch (result) {
case Core::System::ResultStatus::ErrorSystemFiles: {
QString message = "yuzu was unable to locate a Switch system archive";
@@ -1354,9 +1464,12 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
this, tr("Fatal Error"),
tr("yuzu has encountered a fatal error, please see the log for more details. "
"For more information on accessing the log, please see the following page: "
- "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to "
- "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? "
- "Continuing emulation may result in crashes, corrupted save data, or other bugs."),
+ "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
+ "to "
+ "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
+ "list? "
+ "Continuing emulation may result in crashes, corrupted save data, or other "
+ "bugs."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = "Fatal Error encountered";
break;
@@ -1376,6 +1489,122 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det
}
}
+void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
+ if (behavior == ReinitializeKeyBehavior::Warning) {
+ const auto res = QMessageBox::information(
+ this, tr("Confirm Key Rederivation"),
+ tr("You are about to force rederive all of your keys. \nIf you do not know what this "
+ "means or what you are doing, \nthis is a potentially destructive action. \nPlease "
+ "make "
+ "sure this is what you want \nand optionally make backups.\n\nThis will delete your "
+ "autogenerated key files and re-run the key derivation module."),
+ QMessageBox::StandardButtons{QMessageBox::Ok, QMessageBox::Cancel});
+
+ if (res == QMessageBox::Cancel)
+ return;
+
+ FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
+ "prod.keys_autogenerated");
+ FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
+ "console.keys_autogenerated");
+ FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::KeysDir) +
+ "title.keys_autogenerated");
+ }
+
+ Core::Crypto::KeyManager keys{};
+ if (keys.BaseDeriveNecessary()) {
+ Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory(
+ FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir), FileSys::Mode::Read)};
+
+ const auto function = [this, &keys, &pdm] {
+ keys.PopulateFromPartitionData(pdm);
+ Service::FileSystem::CreateFactories(*vfs);
+ keys.DeriveETicket(pdm);
+ };
+
+ QString errors;
+
+ if (!pdm.HasFuses())
+ errors += tr("- Missing fuses - Cannot derive SBK\n");
+ if (!pdm.HasBoot0())
+ errors += tr("- Missing BOOT0 - Cannot derive master keys\n");
+ if (!pdm.HasPackage2())
+ errors += tr("- Missing BCPKG2-1-Normal-Main - Cannot derive general keys\n");
+ if (!pdm.HasProdInfo())
+ errors += tr("- Missing PRODINFO - Cannot derive title keys\n");
+
+ if (!errors.isEmpty()) {
+
+ QMessageBox::warning(
+ this, tr("Warning Missing Derivation Components"),
+ tr("The following are missing from your configuration that may hinder key "
+ "derivation. It will be attempted but may not complete.<br><br>") +
+ errors +
+ tr("<br><br>You can get all of these and dump all of your games easily by "
+ "following <a href='https://yuzu-emu.org/help/quickstart/'>the "
+ "quickstart guide</a>. Alternatively, you can use another method of dumping "
+ "to obtain all of your keys."));
+ }
+
+ QProgressDialog prog;
+ prog.setRange(0, 0);
+ prog.setLabelText(tr("Deriving keys...\nThis may take up to a minute depending \non your "
+ "system's performance."));
+ prog.setWindowTitle(tr("Deriving Keys"));
+
+ prog.show();
+
+ auto future = QtConcurrent::run(function);
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ }
+
+ prog.close();
+ }
+
+ Service::FileSystem::CreateFactories(*vfs);
+
+ if (behavior == ReinitializeKeyBehavior::Warning) {
+ game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ }
+}
+
+std::optional<u64> GMainWindow::SelectRomFSDumpTarget(
+ const FileSys::RegisteredCacheUnion& installed, u64 program_id) {
+ const auto dlc_entries =
+ installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data);
+ std::vector<FileSys::RegisteredCacheEntry> dlc_match;
+ dlc_match.reserve(dlc_entries.size());
+ std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match),
+ [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) {
+ return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id &&
+ installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success;
+ });
+
+ std::vector<u64> romfs_tids;
+ romfs_tids.push_back(program_id);
+ for (const auto& entry : dlc_match)
+ romfs_tids.push_back(entry.title_id);
+
+ if (romfs_tids.size() > 1) {
+ QStringList list{"Base"};
+ for (std::size_t i = 1; i < romfs_tids.size(); ++i)
+ list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF));
+
+ bool ok;
+ const auto res = QInputDialog::getItem(
+ this, tr("Select RomFS Dump Target"),
+ tr("Please select which RomFS you would like to dump."), list, 0, false, &ok);
+ if (!ok) {
+ return {};
+ }
+
+ return romfs_tids[list.indexOf(res)];
+ }
+
+ return program_id;
+}
+
bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true;
@@ -1484,7 +1713,7 @@ void GMainWindow::UpdateUITheme() {
emit UpdateThemedIcons();
}
-void GMainWindow::SetDiscordEnabled(bool state) {
+void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
#ifdef USE_DISCORD_PRESENCE
if (state) {
discord_rpc = std::make_unique<DiscordRPC::DiscordImpl>();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index fe0e9a50a..929250e8c 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -5,6 +5,7 @@
#pragma once
#include <memory>
+#include <optional>
#include <unordered_map>
#include <QMainWindow>
@@ -29,8 +30,9 @@ class WaitTreeWidget;
enum class GameListOpenTarget;
namespace FileSys {
+class RegisteredCacheUnion;
class VfsFilesystem;
-}
+} // namespace FileSys
namespace Tegra {
class DebugContext;
@@ -41,6 +43,11 @@ enum class EmulatedDirectoryTarget {
SDMC,
};
+enum class ReinitializeKeyBehavior {
+ NoWarning,
+ Warning,
+};
+
namespace DiscordRPC {
class DiscordInterface;
}
@@ -159,6 +166,8 @@ private slots:
void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
void OnMenuRecentFile();
void OnConfigure();
+ void OnLoadAmiibo();
+ void OnOpenYuzuFolder();
void OnAbout();
void OnToggleFilterBar();
void OnDisplayTitleBars(bool);
@@ -167,8 +176,10 @@ private slots:
void HideFullscreen();
void ToggleWindowMode();
void OnCoreError(Core::System::ResultStatus, std::string);
+ void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
private:
+ std::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, u64 program_id);
void UpdateStatusBar();
Ui::MainWindow ui;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index cb1664b21..28cf269e7 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -57,8 +57,8 @@
<string>Recent Files</string>
</property>
</widget>
- <addaction name="action_Install_File_NAND" />
- <addaction name="separator"/>
+ <addaction name="action_Install_File_NAND"/>
+ <addaction name="separator"/>
<addaction name="action_Load_File"/>
<addaction name="action_Load_Folder"/>
<addaction name="separator"/>
@@ -68,6 +68,8 @@
<addaction name="action_Select_NAND_Directory"/>
<addaction name="action_Select_SDMC_Directory"/>
<addaction name="separator"/>
+ <addaction name="action_Load_Amiibo"/>
+ <addaction name="separator"/>
<addaction name="action_Exit"/>
</widget>
<widget class="QMenu" name="menu_Emulation">
@@ -97,24 +99,35 @@
<addaction name="action_Show_Status_Bar"/>
<addaction name="menu_View_Debugging"/>
</widget>
+ <widget class ="QMenu" name="menu_Tools">
+ <property name="title">
+ <string>Tools</string>
+ </property>
+ <addaction name="action_Rederive" />
+ </widget>
<widget class="QMenu" name="menu_Help">
<property name="title">
<string>&amp;Help</string>
</property>
<addaction name="action_Report_Compatibility"/>
+ <addaction name="action_Open_yuzu_Folder" />
<addaction name="separator"/>
<addaction name="action_About"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_Emulation"/>
<addaction name="menu_View"/>
+ <addaction name="menu_Tools" />
<addaction name="menu_Help"/>
</widget>
- <action name="action_Install_File_NAND">
- <property name="text">
- <string>Install File to NAND...</string>
- </property>
- </action>
+ <action name="action_Install_File_NAND">
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Install File to NAND...</string>
+ </property>
+ </action>
<action name="action_Load_File">
<property name="text">
<string>Load File...</string>
@@ -159,6 +172,11 @@
<string>&amp;Stop</string>
</property>
</action>
+ <action name="action_Rederive">
+ <property name="text">
+ <string>Reinitialize keys...</string>
+ </property>
+ </action>
<action name="action_About">
<property name="text">
<string>About yuzu</string>
@@ -241,6 +259,14 @@
<string>Restart</string>
</property>
</action>
+ <action name="action_Load_Amiibo">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Load Amiibo...</string>
+ </property>
+ </action>
<action name="action_Report_Compatibility">
<property name="enabled">
<bool>false</bool>
@@ -252,6 +278,11 @@
<bool>false</bool>
</property>
</action>
+ <action name="action_Open_yuzu_Folder">
+ <property name="text">
+ <string>Open yuzu Folder</string>
+ </property>
+ </action>
</widget>
<resources/>
<connections/>
diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp
new file mode 100644
index 000000000..edd78e579
--- /dev/null
+++ b/src/yuzu/util/limitable_input_dialog.cpp
@@ -0,0 +1,59 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <QDialogButtonBox>
+#include <QLabel>
+#include <QLineEdit>
+#include <QPushButton>
+#include <QVBoxLayout>
+#include "yuzu/util/limitable_input_dialog.h"
+
+LimitableInputDialog::LimitableInputDialog(QWidget* parent) : QDialog{parent} {
+ CreateUI();
+ ConnectEvents();
+}
+
+LimitableInputDialog::~LimitableInputDialog() = default;
+
+void LimitableInputDialog::CreateUI() {
+ setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+ text_label = new QLabel(this);
+ text_entry = new QLineEdit(this);
+ buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
+
+ auto* const layout = new QVBoxLayout;
+ layout->addWidget(text_label);
+ layout->addWidget(text_entry);
+ layout->addWidget(buttons);
+
+ setLayout(layout);
+}
+
+void LimitableInputDialog::ConnectEvents() {
+ connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text,
+ int min_character_limit, int max_character_limit) {
+ Q_ASSERT(min_character_limit <= max_character_limit);
+
+ LimitableInputDialog dialog{parent};
+ dialog.setWindowTitle(title);
+ dialog.text_label->setText(text);
+ dialog.text_entry->setMaxLength(max_character_limit);
+
+ auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok);
+ ok_button->setEnabled(false);
+ connect(dialog.text_entry, &QLineEdit::textEdited, [&](const QString& new_text) {
+ ok_button->setEnabled(new_text.length() >= min_character_limit);
+ });
+
+ if (dialog.exec() != QDialog::Accepted) {
+ return {};
+ }
+
+ return dialog.text_entry->text();
+}
diff --git a/src/yuzu/util/limitable_input_dialog.h b/src/yuzu/util/limitable_input_dialog.h
new file mode 100644
index 000000000..164ad7301
--- /dev/null
+++ b/src/yuzu/util/limitable_input_dialog.h
@@ -0,0 +1,31 @@
+// Copyright 2018 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <QDialog>
+
+class QDialogButtonBox;
+class QLabel;
+class QLineEdit;
+
+/// A QDialog that functions similarly to QInputDialog, however, it allows
+/// restricting the minimum and total number of characters that can be entered.
+class LimitableInputDialog final : public QDialog {
+ Q_OBJECT
+public:
+ explicit LimitableInputDialog(QWidget* parent = nullptr);
+ ~LimitableInputDialog() override;
+
+ static QString GetText(QWidget* parent, const QString& title, const QString& text,
+ int min_character_limit, int max_character_limit);
+
+private:
+ void CreateUI();
+ void ConnectEvents();
+
+ QLabel* text_label;
+ QLineEdit* text_entry;
+ QDialogButtonBox* buttons;
+};
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 9d934e220..b456266a6 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -8,6 +8,7 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "common/param_package.h"
+#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "yuzu_cmd/config.h"
@@ -99,8 +100,8 @@ void Config::ReadValues() {
Settings::values.use_frame_limit = sdl2_config->GetBoolean("Renderer", "use_frame_limit", true);
Settings::values.frame_limit =
static_cast<u16>(sdl2_config->GetInteger("Renderer", "frame_limit", 100));
- Settings::values.use_accurate_framebuffers =
- sdl2_config->GetBoolean("Renderer", "use_accurate_framebuffers", false);
+ Settings::values.use_accurate_gpu_emulation =
+ sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false);
Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
@@ -125,10 +126,11 @@ void Config::ReadValues() {
// System
Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false);
- Settings::values.username = sdl2_config->Get("System", "username", "yuzu");
- if (Settings::values.username.empty()) {
- Settings::values.username = "yuzu";
- }
+ Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true);
+ const auto size = sdl2_config->GetInteger("System", "users_size", 0);
+
+ Settings::values.current_user = std::clamp<int>(
+ sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1);
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace");
@@ -138,6 +140,7 @@ void Config::ReadValues() {
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
Settings::values.gdbstub_port =
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
+ Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");
// Web Service
Settings::values.enable_telemetry =
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 762396e3b..e0b223cd6 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -110,9 +110,9 @@ use_frame_limit =
# 1 - 9999: Speed limit as a percentage of target game speed. 100 (default)
frame_limit =
-# Whether to use accurate framebuffers
+# Whether to use accurate GPU emulation
# 0 (default): Off (fast), 1 : On (slow)
-use_accurate_framebuffers =
+use_accurate_gpu_emulation =
# The clear color for the renderer. What shows up on the sides of the bottom screen.
# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
@@ -174,6 +174,10 @@ use_virtual_sd =
# 1: Yes, 0 (default): No
use_docked_mode =
+# Allow the use of NFC in games
+# 1 (default): Yes, 0 : No
+enable_nfc =
+
# Sets the account username, max length is 32 characters
# yuzu (default)
username = yuzu
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 0733301b2..a9ad92a80 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -40,6 +40,35 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
}
}
+std::pair<unsigned, unsigned> EmuWindow_SDL2::TouchToPixelPos(float touch_x, float touch_y) const {
+ int w, h;
+ SDL_GetWindowSize(render_window, &w, &h);
+
+ touch_x *= w;
+ touch_y *= h;
+
+ return {static_cast<unsigned>(std::max(std::round(touch_x), 0.0f)),
+ static_cast<unsigned>(std::max(std::round(touch_y), 0.0f))};
+}
+
+void EmuWindow_SDL2::OnFingerDown(float x, float y) {
+ // TODO(NeatNit): keep track of multitouch using the fingerID and a dictionary of some kind
+ // This isn't critical because the best we can do when we have that is to average them, like the
+ // 3DS does
+
+ const auto [px, py] = TouchToPixelPos(x, y);
+ TouchPressed(px, py);
+}
+
+void EmuWindow_SDL2::OnFingerMotion(float x, float y) {
+ const auto [px, py] = TouchToPixelPos(x, y);
+ TouchMoved(px, py);
+}
+
+void EmuWindow_SDL2::OnFingerUp() {
+ TouchReleased();
+}
+
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
InputCommon::GetKeyboard()->PressKey(key);
@@ -98,6 +127,8 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
unsupported_ext.push_back("ARB_texture_storage");
if (!GLAD_GL_ARB_multi_bind)
unsupported_ext.push_back("ARB_multi_bind");
+ if (!GLAD_GL_ARB_copy_image)
+ unsupported_ext.push_back("ARB_copy_image");
// Extensions required to support some texture formats.
if (!GLAD_GL_EXT_texture_compression_s3tc)
@@ -217,11 +248,26 @@ void EmuWindow_SDL2::PollEvents() {
OnKeyEvent(static_cast<int>(event.key.keysym.scancode), event.key.state);
break;
case SDL_MOUSEMOTION:
- OnMouseMotion(event.motion.x, event.motion.y);
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID)
+ OnMouseMotion(event.motion.x, event.motion.y);
break;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
- OnMouseButton(event.button.button, event.button.state, event.button.x, event.button.y);
+ // ignore if it came from touch
+ if (event.button.which != SDL_TOUCH_MOUSEID) {
+ OnMouseButton(event.button.button, event.button.state, event.button.x,
+ event.button.y);
+ }
+ break;
+ case SDL_FINGERDOWN:
+ OnFingerDown(event.tfinger.x, event.tfinger.y);
+ break;
+ case SDL_FINGERMOTION:
+ OnFingerMotion(event.tfinger.x, event.tfinger.y);
+ break;
+ case SDL_FINGERUP:
+ OnFingerUp();
break;
case SDL_QUIT:
is_open = false;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
index d34902109..b0d4116cc 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h
@@ -40,6 +40,18 @@ private:
/// Called by PollEvents when a mouse button is pressed or released
void OnMouseButton(u32 button, u8 state, s32 x, s32 y);
+ /// Translates pixel position (0..1) to pixel positions
+ std::pair<unsigned, unsigned> TouchToPixelPos(float touch_x, float touch_y) const;
+
+ /// Called by PollEvents when a finger starts touching the touchscreen
+ void OnFingerDown(float x, float y);
+
+ /// Called by PollEvents when a finger moves while touching the touchscreen
+ void OnFingerMotion(float x, float y);
+
+ /// Called by PollEvents when a finger stops touching the touchscreen
+ void OnFingerUp();
+
/// Called by PollEvents when any event that may cause the window to be resized occurs
void OnResize();
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index bab465c1d..806127b12 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -56,9 +56,10 @@ static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
- "-f, --fullscreen Start in fullscreen mode\n"
+ "-f, --fullscreen Start in fullscreen mode\n"
"-h, --help Display this help and exit\n"
- "-v, --version Output version information and exit\n";
+ "-v, --version Output version information and exit\n"
+ "-p, --program Pass following string as arguments to executable\n";
}
static void PrintVersion() {
@@ -106,15 +107,13 @@ int main(int argc, char** argv) {
bool fullscreen = false;
static struct option long_options[] = {
- {"gdbport", required_argument, 0, 'g'},
- {"fullscreen", no_argument, 0, 'f'},
- {"help", no_argument, 0, 'h'},
- {"version", no_argument, 0, 'v'},
- {0, 0, 0, 0},
+ {"gdbport", required_argument, 0, 'g'}, {"fullscreen", no_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'v'},
+ {"program", optional_argument, 0, 'p'}, {0, 0, 0, 0},
};
while (optind < argc) {
- char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index);
+ char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
if (arg != -1) {
switch (arg) {
case 'g':
@@ -138,6 +137,10 @@ int main(int argc, char** argv) {
case 'v':
PrintVersion();
return 0;
+ case 'p':
+ Settings::values.program_args = argv[optind];
+ ++optind;
+ break;
}
} else {
#ifdef _WIN32
@@ -175,7 +178,7 @@ int main(int argc, char** argv) {
Core::System& system{Core::System::GetInstance()};
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
- Service::FileSystem::CreateFactories(system.GetFilesystem());
+ Service::FileSystem::CreateFactories(*system.GetFilesystem());
SCOPE_EXIT({ system.Shutdown(); });