diff options
Diffstat (limited to 'src/core')
26 files changed, 812 insertions, 162 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index ea09819e5..360f407f3 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -60,6 +60,7 @@ set(SRCS hle/kernel/timer.cpp hle/kernel/vm_manager.cpp hle/kernel/wait_object.cpp + hle/romfs.cpp hle/service/ac/ac.cpp hle/service/ac/ac_i.cpp hle/service/ac/ac_u.cpp @@ -258,6 +259,7 @@ set(HEADERS hle/kernel/vm_manager.h hle/kernel/wait_object.h hle/result.h + hle/romfs.h hle/service/ac/ac.h hle/service/ac/ac_i.h hle/service/ac/ac_u.h @@ -388,3 +390,6 @@ create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) +if (ENABLE_WEB_SERVICE) + target_link_libraries(core PUBLIC json-headers web_service) +endif() diff --git a/src/core/core.cpp b/src/core/core.cpp index 5429bcb26..d08f18623 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -168,6 +168,16 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { } void System::Shutdown() { + // Log last frame performance stats + auto perf_results = GetAndResetPerfStats(); + Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed", + perf_results.emulation_speed * 100.0); + Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate", + perf_results.game_fps); + Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime", + perf_results.frametime * 1000.0); + + // Shutdown emulation session GDBStub::Shutdown(); AudioCore::Shutdown(); VideoCore::Shutdown(); diff --git a/src/core/core.h b/src/core/core.h index 4e3b6b409..9805cc694 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -7,6 +7,7 @@ #include <memory> #include <string> #include "common/common_types.h" +#include "core/loader/loader.h" #include "core/memory.h" #include "core/perf_stats.h" #include "core/telemetry_session.h" @@ -14,10 +15,6 @@ class EmuWindow; class ARM_Interface; -namespace Loader { -class AppLoader; -} - namespace Core { class System { @@ -119,6 +116,10 @@ public: return status_details; } + Loader::AppLoader& GetAppLoader() const { + return *app_loader; + } + private: /** * Initialize the emulated system. diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 679909d06..fe3dce5d4 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -121,7 +121,25 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const { } ResultCode SDMCArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { + const PathParser path_parser_src(src_path); + + // TODO: Verify these return codes with HW + if (!path_parser_src.IsValid()) { + LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const PathParser path_parser_dest(dest_path); + + if (!path_parser_dest.IsValid()) { + LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto src_path_full = path_parser_src.BuildHostPath(mount_point); + const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); + + if (FileUtil::Rename(src_path_full, dest_path_full)) { return RESULT_SUCCESS; } @@ -260,8 +278,27 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const { } ResultCode SDMCArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + const PathParser path_parser_src(src_path); + + // TODO: Verify these return codes with HW + if (!path_parser_src.IsValid()) { + LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const PathParser path_parser_dest(dest_path); + + if (!path_parser_dest.IsValid()) { + LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto src_path_full = path_parser_src.BuildHostPath(mount_point); + const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); + + if (FileUtil::Rename(src_path_full, dest_path_full)) { return RESULT_SUCCESS; + } // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't // exist or similar. Verify. diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp index f540c4a93..f8f811ba0 100644 --- a/src/core/file_sys/savedata_archive.cpp +++ b/src/core/file_sys/savedata_archive.cpp @@ -106,7 +106,25 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const { } ResultCode SaveDataArchive::RenameFile(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) { + const PathParser path_parser_src(src_path); + + // TODO: Verify these return codes with HW + if (!path_parser_src.IsValid()) { + LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const PathParser path_parser_dest(dest_path); + + if (!path_parser_dest.IsValid()) { + LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto src_path_full = path_parser_src.BuildHostPath(mount_point); + const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); + + if (FileUtil::Rename(src_path_full, dest_path_full)) { return RESULT_SUCCESS; } @@ -247,8 +265,27 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const { } ResultCode SaveDataArchive::RenameDirectory(const Path& src_path, const Path& dest_path) const { - if (FileUtil::Rename(mount_point + src_path.AsString(), mount_point + dest_path.AsString())) + const PathParser path_parser_src(src_path); + + // TODO: Verify these return codes with HW + if (!path_parser_src.IsValid()) { + LOG_ERROR(Service_FS, "Invalid src path %s", src_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const PathParser path_parser_dest(dest_path); + + if (!path_parser_dest.IsValid()) { + LOG_ERROR(Service_FS, "Invalid dest path %s", dest_path.DebugStr().c_str()); + return ERROR_INVALID_PATH; + } + + const auto src_path_full = path_parser_src.BuildHostPath(mount_point); + const auto dest_path_full = path_parser_dest.BuildHostPath(mount_point); + + if (FileUtil::Rename(src_path_full, dest_path_full)) { return RESULT_SUCCESS; + } // TODO(yuriks): This code probably isn't right, it'll return a Status even if the file didn't // exist or similar. Verify. diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index 922e5ab58..a7b66142f 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp @@ -149,7 +149,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi if (base_address == 0 && target_address == 0) { // Calculate the address at which to map the memory block. - target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address); + target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address).value(); } // Map the memory block into the target process diff --git a/src/core/hle/romfs.cpp b/src/core/hle/romfs.cpp new file mode 100644 index 000000000..3157df71d --- /dev/null +++ b/src/core/hle/romfs.cpp @@ -0,0 +1,102 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/swap.h" +#include "core/hle/romfs.h" + +namespace RomFS { + +struct Header { + u32_le header_length; + u32_le dir_hash_table_offset; + u32_le dir_hash_table_length; + u32_le dir_table_offset; + u32_le dir_table_length; + u32_le file_hash_table_offset; + u32_le file_hash_table_length; + u32_le file_table_offset; + u32_le file_table_length; + u32_le data_offset; +}; + +static_assert(sizeof(Header) == 0x28, "Header has incorrect size"); + +struct DirectoryMetadata { + u32_le parent_dir_offset; + u32_le next_dir_offset; + u32_le first_child_dir_offset; + u32_le first_file_offset; + u32_le same_hash_next_dir_offset; + u32_le name_length; // in bytes + // followed by directory name +}; + +static_assert(sizeof(DirectoryMetadata) == 0x18, "DirectoryMetadata has incorrect size"); + +struct FileMetadata { + u32_le parent_dir_offset; + u32_le next_file_offset; + u64_le data_offset; + u64_le data_length; + u32_le same_hash_next_file_offset; + u32_le name_length; // in bytes + // followed by file name +}; + +static_assert(sizeof(FileMetadata) == 0x20, "FileMetadata has incorrect size"); + +static bool MatchName(const u8* buffer, u32 name_length, const std::u16string& name) { + std::vector<char16_t> name_buffer(name_length / sizeof(char16_t)); + std::memcpy(name_buffer.data(), buffer, name_length); + return name == std::u16string(name_buffer.begin(), name_buffer.end()); +} + +const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path) { + constexpr u32 INVALID_FIELD = 0xFFFFFFFF; + + // Split path into directory names and file name + std::vector<std::u16string> dir_names = path; + dir_names.pop_back(); + const std::u16string& file_name = path.back(); + + Header header; + std::memcpy(&header, romfs, sizeof(header)); + + // Find directories of each level + DirectoryMetadata dir; + const u8* current_dir = romfs + header.dir_table_offset; + std::memcpy(&dir, current_dir, sizeof(dir)); + for (const std::u16string& dir_name : dir_names) { + u32 child_dir_offset; + child_dir_offset = dir.first_child_dir_offset; + while (true) { + if (child_dir_offset == INVALID_FIELD) { + return nullptr; + } + const u8* current_child_dir = romfs + header.dir_table_offset + child_dir_offset; + std::memcpy(&dir, current_child_dir, sizeof(dir)); + if (MatchName(current_child_dir + sizeof(dir), dir.name_length, dir_name)) { + current_dir = current_child_dir; + break; + } + child_dir_offset = dir.next_dir_offset; + } + } + + // Find the file + FileMetadata file; + u32 file_offset = dir.first_file_offset; + while (file_offset != INVALID_FIELD) { + const u8* current_file = romfs + header.file_table_offset + file_offset; + std::memcpy(&file, current_file, sizeof(file)); + if (MatchName(current_file + sizeof(file), file.name_length, file_name)) { + return romfs + header.data_offset + file.data_offset; + } + file_offset = file.next_file_offset; + } + return nullptr; +} + +} // namespace RomFS diff --git a/src/core/hle/romfs.h b/src/core/hle/romfs.h new file mode 100644 index 000000000..ee9f29760 --- /dev/null +++ b/src/core/hle/romfs.h @@ -0,0 +1,22 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <string> +#include <vector> +#include "common/common_types.h" + +namespace RomFS { + +/** + * Gets the pointer to a file in a RomFS image. + * @param romfs The pointer to the RomFS image + * @param path A vector containing the directory names and file name of the path to the file + * @return the pointer to the file + * @todo reimplement this with a full RomFS manager + */ +const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path); + +} // namespace RomFS diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 25e7b777d..0109fa2b2 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -2,15 +2,18 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <boost/optional.hpp> #include "common/common_paths.h" #include "common/file_util.h" #include "common/logging/log.h" #include "core/core.h" +#include "core/file_sys/file_backend.h" #include "core/hle/applets/applet.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/mutex.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/romfs.h" #include "core/hle/service/apt/apt.h" #include "core/hle/service/apt/apt_a.h" #include "core/hle/service/apt/apt_s.h" @@ -27,6 +30,7 @@ namespace APT { /// Handle to shared memory region designated to for shared system font static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem; +static bool shared_font_loaded = false; static bool shared_font_relocated = false; static Kernel::SharedPtr<Kernel::Mutex> lock; @@ -41,7 +45,7 @@ static u8 unknown_ns_state_field; static ScreencapPostPermission screen_capture_post_permission; /// Parameter data to be returned in the next call to Glance/ReceiveParameter -static MessageParameter next_parameter; +static boost::optional<MessageParameter> next_parameter; void SendParameter(const MessageParameter& parameter) { next_parameter = parameter; @@ -71,7 +75,11 @@ void Initialize(Service::Interface* self) { void GetSharedFont(Service::Interface* self) { IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); - if (!shared_font_mem) { + + // Log in telemetry if the game uses the shared font + Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true); + + if (!shared_font_loaded) { LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); rb.Push<u32>(-1); // TODO: Find the right error code rb.Skip(1 + 2, true); @@ -82,7 +90,7 @@ void GetSharedFont(Service::Interface* self) { // The shared font has to be relocated to the new address before being passed to the // application. VAddr target_address = - Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address); + Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address).value(); if (!shared_font_relocated) { BCFNT::RelocateSharedFont(shared_font_mem, target_address); shared_font_relocated = true; @@ -186,8 +194,20 @@ void SendParameter(Service::Interface* self) { std::shared_ptr<HLE::Applets::Applet> dest_applet = HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id)); + LOG_DEBUG(Service_APT, + "called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," + "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X", + src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + // A new parameter can not be sent if the previous one hasn't been consumed yet + if (next_parameter) { + rb.Push(ResultCode(ErrCodes::ParameterPresent, ErrorModule::Applet, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + if (dest_applet == nullptr) { LOG_ERROR(Service_APT, "Unknown applet id=0x%08X", dst_app_id); rb.Push<u32>(-1); // TODO(Subv): Find the right error code @@ -203,11 +223,6 @@ void SendParameter(Service::Interface* self) { Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size()); rb.Push(dest_applet->ReceiveParameter(param)); - - LOG_WARNING(Service_APT, - "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X," - "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X", - src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer); } void ReceiveParameter(Service::Interface* self) { @@ -223,21 +238,40 @@ void ReceiveParameter(Service::Interface* self) { "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", buffer_size, static_buff_size); + LOG_DEBUG(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); + + if (!next_parameter) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Applet, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + if (next_parameter->destination_id != app_id) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); + rb.Push(RESULT_SUCCESS); // No error - rb.Push(next_parameter.sender_id); - rb.Push(next_parameter.signal); // Signal type - ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); - rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size + rb.Push(next_parameter->sender_id); + rb.Push(next_parameter->signal); // Signal type + ASSERT_MSG(next_parameter->buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter->buffer.size())); // Parameter buffer size - rb.PushMoveHandles((next_parameter.object != nullptr) - ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap() + rb.PushMoveHandles((next_parameter->object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() : 0); - rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); - Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter->buffer.size()), 0); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); + Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); + + // Clear the parameter + next_parameter = boost::none; } void GlanceParameter(Service::Interface* self) { @@ -253,37 +287,74 @@ void GlanceParameter(Service::Interface* self) { "buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)", buffer_size, static_buff_size); + LOG_DEBUG(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); + + if (!next_parameter) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Applet, + ErrorSummary::InvalidState, ErrorLevel::Status)); + return; + } + + if (next_parameter->destination_id != app_id) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound, + ErrorLevel::Status)); + return; + } + IPC::RequestBuilder rb = rp.MakeBuilder(4, 4); rb.Push(RESULT_SUCCESS); // No error - rb.Push(next_parameter.sender_id); - rb.Push(next_parameter.signal); // Signal type - ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !"); - rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size + rb.Push(next_parameter->sender_id); + rb.Push(next_parameter->signal); // Signal type + ASSERT_MSG(next_parameter->buffer.size() <= buffer_size, "Input static buffer is too small !"); + rb.Push(static_cast<u32>(next_parameter->buffer.size())); // Parameter buffer size - rb.PushCopyHandles((next_parameter.object != nullptr) - ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap() + rb.PushMoveHandles((next_parameter->object != nullptr) + ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() : 0); - rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0); - Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size()); + rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter->buffer.size()), 0); + + Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); - LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size); + // Note: The NS module always clears the DSPSleep and DSPWakeup signals even in GlanceParameter. + if (next_parameter->signal == static_cast<u32>(SignalType::DspSleep) || + next_parameter->signal == static_cast<u32>(SignalType::DspWakeup)) + next_parameter = boost::none; } void CancelParameter(Service::Interface* self) { IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 4, 0); // 0xF0100 - u32 check_sender = rp.Pop<u32>(); + bool check_sender = rp.Pop<bool>(); u32 sender_appid = rp.Pop<u32>(); - u32 check_receiver = rp.Pop<u32>(); + bool check_receiver = rp.Pop<bool>(); u32 receiver_appid = rp.Pop<u32>(); + + bool cancellation_success = true; + + if (!next_parameter) { + cancellation_success = false; + } else { + if (check_sender && next_parameter->sender_id != sender_appid) + cancellation_success = false; + + if (check_receiver && next_parameter->destination_id != receiver_appid) + cancellation_success = false; + } + + if (cancellation_success) + next_parameter = boost::none; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); // No error - rb.Push(true); // Set to Success + rb.Push(cancellation_success); - LOG_WARNING(Service_APT, "(STUBBED) called check_sender=0x%08X, sender_appid=0x%08X, " - "check_receiver=0x%08X, receiver_appid=0x%08X", - check_sender, sender_appid, check_receiver, receiver_appid); + LOG_DEBUG(Service_APT, "called check_sender=%u, sender_appid=0x%08X, " + "check_receiver=%u, receiver_appid=0x%08X", + check_sender, sender_appid, check_receiver, receiver_appid); } void PrepareToStartApplication(Service::Interface* self) { @@ -644,36 +715,146 @@ void CheckNew3DS(Service::Interface* self) { LOG_WARNING(Service_APT, "(STUBBED) called"); } -void Init() { - AddService(new APT_A_Interface); - AddService(new APT_S_Interface); - AddService(new APT_U_Interface); - - HLE::Applets::Init(); - - // Load the shared system font (if available). +static u32 DecompressLZ11(const u8* in, u8* out) { + u32_le decompressed_size; + memcpy(&decompressed_size, in, sizeof(u32)); + in += 4; + + u8 type = decompressed_size & 0xFF; + ASSERT(type == 0x11); + decompressed_size >>= 8; + + u32 current_out_size = 0; + u8 flags = 0, mask = 1; + while (current_out_size < decompressed_size) { + if (mask == 1) { + flags = *(in++); + mask = 0x80; + } else { + mask >>= 1; + } + + if (flags & mask) { + u8 byte1 = *(in++); + u32 length = byte1 >> 4; + u32 offset; + if (length == 0) { + u8 byte2 = *(in++); + u8 byte3 = *(in++); + length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; + offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1; + } else if (length == 1) { + u8 byte2 = *(in++); + u8 byte3 = *(in++); + u8 byte4 = *(in++); + length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; + offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1; + } else { + u8 byte2 = *(in++); + length = (byte1 >> 4) + 0x1; + offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1; + } + + for (u32 i = 0; i < length; i++) { + *out = *(out - offset); + ++out; + } + + current_out_size += length; + } else { + *(out++) = *(in++); + current_out_size++; + } + } + return decompressed_size; +} + +static bool LoadSharedFont() { + // TODO (wwylele): load different font archive for region CHN/KOR/TWN + const u64_le shared_font_archive_id_low = 0x0004009b00014002; + const u64_le shared_font_archive_id_high = 0x00000001ffffff00; + std::vector<u8> shared_font_archive_id(16); + std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64)); + std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64)); + FileSys::Path archive_path(shared_font_archive_id); + auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path); + if (archive_result.Failed()) + return false; + + std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS + FileSys::Path file_path(romfs_path); + FileSys::Mode open_mode = {}; + open_mode.read_flag.Assign(1); + auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode); + if (file_result.Failed()) + return false; + + auto romfs = std::move(file_result).Unwrap(); + std::vector<u8> romfs_buffer(romfs->backend->GetSize()); + romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data()); + romfs->backend->Close(); + + const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"}); + if (font_file == nullptr) + return false; + + struct { + u32_le status; + u32_le region; + u32_le decompressed_size; + INSERT_PADDING_WORDS(0x1D); + } shared_font_header{}; + static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size"); + + shared_font_header.status = 2; // successfully loaded + shared_font_header.region = 1; // region JPN/EUR/USA + shared_font_header.decompressed_size = + DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80)); + std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header)); + *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU" + + return true; +} + +static bool LoadLegacySharedFont() { + // This is the legacy method to load shared font. // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file // "shared_font.bin" in the Citra "sysdata" directory. - std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; FileUtil::CreateFullPath(filepath); // Create path if not already created FileUtil::IOFile file(filepath, "rb"); - if (file.IsOpen()) { - // Create shared font memory object - using Kernel::MemoryPermission; - shared_font_mem = - Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB - MemoryPermission::ReadWrite, MemoryPermission::Read, 0, - Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); - // Read shared font data file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); + return true; + } + + return false; +} + +void Init() { + AddService(new APT_A_Interface); + AddService(new APT_S_Interface); + AddService(new APT_U_Interface); + + HLE::Applets::Init(); + + using Kernel::MemoryPermission; + shared_font_mem = + Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB + MemoryPermission::ReadWrite, MemoryPermission::Read, 0, + Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); + + if (LoadSharedFont()) { + shared_font_loaded = true; + } else if (LoadLegacySharedFont()) { + LOG_WARNING(Service_APT, "Loaded shared font by legacy method"); + shared_font_loaded = true; } else { - LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str()); - shared_font_mem = nullptr; + LOG_WARNING(Service_APT, "Unable to load shared font"); + shared_font_loaded = false; } lock = Kernel::Mutex::Create(false, "APT_U:Lock"); @@ -687,18 +868,21 @@ void Init() { notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification"); parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start"); - next_parameter.signal = static_cast<u32>(SignalType::Wakeup); - next_parameter.destination_id = 0x300; + // Initialize the parameter to wake up the application. + next_parameter.emplace(); + next_parameter->signal = static_cast<u32>(SignalType::Wakeup); + next_parameter->destination_id = static_cast<u32>(AppletId::Application); } void Shutdown() { shared_font_mem = nullptr; + shared_font_loaded = false; shared_font_relocated = false; lock = nullptr; notification_event = nullptr; parameter_event = nullptr; - next_parameter.object = nullptr; + next_parameter = boost::none; HLE::Applets::Shutdown(); } diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index ee80926d2..106754853 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -116,6 +116,12 @@ enum class ScreencapPostPermission : u32 { DisableScreenshotPostingToMiiverse = 3 }; +namespace ErrCodes { +enum { + ParameterPresent = 2, +}; +} + /// Send a parameter to the currently-running application, which will read it via ReceiveParameter void SendParameter(const MessageParameter& parameter); diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp index 57eb39d75..6d2474702 100644 --- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp +++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp @@ -78,7 +78,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd memcpy(&cmap, data, sizeof(cmap)); // Relocate the offsets in the CMAP section - cmap.next_cmap_offset += offset; + if (cmap.next_cmap_offset != 0) + cmap.next_cmap_offset += offset; memcpy(data, &cmap, sizeof(cmap)); } else if (memcmp(section_header.magic, "CWDH", 4) == 0) { @@ -86,7 +87,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd memcpy(&cwdh, data, sizeof(cwdh)); // Relocate the offsets in the CWDH section - cwdh.next_cwdh_offset += offset; + if (cwdh.next_cwdh_offset != 0) + cwdh.next_cwdh_offset += offset; memcpy(data, &cwdh, sizeof(cwdh)); } else if (memcmp(section_header.magic, "TGLP", 4) == 0) { diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp index 56f934b3f..6f2bf2061 100644 --- a/src/core/hle/service/dlp/dlp_clnt.cpp +++ b/src/core/hle/service/dlp/dlp_clnt.cpp @@ -8,7 +8,26 @@ namespace Service { namespace DLP { const Interface::FunctionInfo FunctionTable[] = { - {0x000100C3, nullptr, "Initialize"}, {0x00110000, nullptr, "GetWirelessRebootPassphrase"}, + {0x000100C3, nullptr, "Initialize"}, + {0x00020000, nullptr, "Finalize"}, + {0x00030000, nullptr, "GetEventDesc"}, + {0x00040000, nullptr, "GetChannel"}, + {0x00050180, nullptr, "StartScan"}, + {0x00060000, nullptr, "StopScan"}, + {0x00070080, nullptr, "GetServerInfo"}, + {0x00080100, nullptr, "GetTitleInfo"}, + {0x00090040, nullptr, "GetTitleInfoInOrder"}, + {0x000A0080, nullptr, "DeleteScanInfo"}, + {0x000B0100, nullptr, "PrepareForSystemDownload"}, + {0x000C0000, nullptr, "StartSystemDownload"}, + {0x000D0100, nullptr, "StartTitleDownload"}, + {0x000E0000, nullptr, "GetMyStatus"}, + {0x000F0040, nullptr, "GetConnectingNodes"}, + {0x00100040, nullptr, "GetNodeInfo"}, + {0x00110000, nullptr, "GetWirelessRebootPassphrase"}, + {0x00120000, nullptr, "StopSession"}, + {0x00130100, nullptr, "GetCupVersion"}, + {0x00140100, nullptr, "GetDupAvailability"}, }; DLP_CLNT_Interface::DLP_CLNT_Interface() { diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp index 29b9d52e0..fe6be7d32 100644 --- a/src/core/hle/service/dlp/dlp_fkcl.cpp +++ b/src/core/hle/service/dlp/dlp_fkcl.cpp @@ -8,7 +8,23 @@ namespace Service { namespace DLP { const Interface::FunctionInfo FunctionTable[] = { - {0x00010083, nullptr, "Initialize"}, {0x000F0000, nullptr, "GetWirelessRebootPassphrase"}, + {0x00010083, nullptr, "Initialize"}, + {0x00020000, nullptr, "Finalize"}, + {0x00030000, nullptr, "GetEventDesc"}, + {0x00040000, nullptr, "GetChannels"}, + {0x00050180, nullptr, "StartScan"}, + {0x00060000, nullptr, "StopScan"}, + {0x00070080, nullptr, "GetServerInfo"}, + {0x00080100, nullptr, "GetTitleInfo"}, + {0x00090040, nullptr, "GetTitleInfoInOrder"}, + {0x000A0080, nullptr, "DeleteScanInfo"}, + {0x000B0100, nullptr, "StartFakeSession"}, + {0x000C0000, nullptr, "GetMyStatus"}, + {0x000D0040, nullptr, "GetConnectingNodes"}, + {0x000E0040, nullptr, "GetNodeInfo"}, + {0x000F0000, nullptr, "GetWirelessRebootPassphrase"}, + {0x00100000, nullptr, "StopSession"}, + {0x00110203, nullptr, "Initialize2"}, }; DLP_FKCL_Interface::DLP_FKCL_Interface() { diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp index 32cfa2c44..1bcea43d3 100644 --- a/src/core/hle/service/dlp/dlp_srvr.cpp +++ b/src/core/hle/service/dlp/dlp_srvr.cpp @@ -11,7 +11,7 @@ namespace Service { namespace DLP { -static void unk_0x000E0040(Interface* self) { +static void IsChild(Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); cmd_buff[1] = RESULT_SUCCESS.raw; @@ -24,14 +24,19 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00010183, nullptr, "Initialize"}, {0x00020000, nullptr, "Finalize"}, {0x00030000, nullptr, "GetServerState"}, + {0x00040000, nullptr, "GetEventDescription"}, {0x00050080, nullptr, "StartAccepting"}, + {0x00060000, nullptr, "EndAccepting"}, {0x00070000, nullptr, "StartDistribution"}, {0x000800C0, nullptr, "SendWirelessRebootPassphrase"}, {0x00090040, nullptr, "AcceptClient"}, + {0x000A0040, nullptr, "DisconnectClient"}, {0x000B0042, nullptr, "GetConnectingClients"}, {0x000C0040, nullptr, "GetClientInfo"}, {0x000D0040, nullptr, "GetClientState"}, - {0x000E0040, unk_0x000E0040, "unk_0x000E0040"}, + {0x000E0040, IsChild, "IsChild"}, + {0x000F0303, nullptr, "InitializeWithName"}, + {0x00100000, nullptr, "GetDupNoticeNeed"}, }; DLP_SRVR_Interface::DLP_SRVR_Interface() { diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp index 76ecda8b7..7ad7798da 100644 --- a/src/core/hle/service/frd/frd.cpp +++ b/src/core/hle/service/frd/frd.cpp @@ -6,6 +6,7 @@ #include "common/logging/log.h" #include "common/string_util.h" #include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/result.h" #include "core/hle/service/frd/frd.h" #include "core/hle/service/frd/frd_a.h" @@ -105,6 +106,48 @@ void GetMyScreenName(Service::Interface* self) { LOG_WARNING(Service_FRD, "(STUBBED) called"); } +void UnscrambleLocalFriendCode(Service::Interface* self) { + const size_t scrambled_friend_code_size = 12; + const size_t friend_code_size = 8; + + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 2); + const u32 friend_code_count = rp.Pop<u32>(); + size_t in_buffer_size; + const VAddr scrambled_friend_codes = rp.PopStaticBuffer(&in_buffer_size, false); + ASSERT_MSG(in_buffer_size == (friend_code_count * scrambled_friend_code_size), + "Wrong input buffer size"); + + size_t out_buffer_size; + VAddr unscrambled_friend_codes = rp.PeekStaticBuffer(0, &out_buffer_size); + ASSERT_MSG(out_buffer_size == (friend_code_count * friend_code_size), + "Wrong output buffer size"); + + for (u32 current = 0; current < friend_code_count; ++current) { + // TODO(B3N30): Unscramble the codes and compare them against the friend list + // Only write 0 if the code isn't in friend list, otherwise write the + // unscrambled one + // + // Code for unscrambling (should be compared to HW): + // std::array<u16, 6> scambled_friend_code; + // Memory::ReadBlock(scrambled_friend_codes+(current*scrambled_friend_code_size), + // scambled_friend_code.data(), scrambled_friend_code_size); std::array<u16, 4> + // unscrambled_friend_code; unscrambled_friend_code[0] = scambled_friend_code[0] ^ + // scambled_friend_code[5]; unscrambled_friend_code[1] = scambled_friend_code[1] ^ + // scambled_friend_code[5]; unscrambled_friend_code[2] = scambled_friend_code[2] ^ + // scambled_friend_code[5]; unscrambled_friend_code[3] = scambled_friend_code[3] ^ + // scambled_friend_code[5]; + + u64 result = 0ull; + Memory::WriteBlock(unscrambled_friend_codes + (current * sizeof(result)), &result, + sizeof(result)); + } + + LOG_WARNING(Service_FRD, "(STUBBED) called"); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushStaticBuffer(unscrambled_friend_codes, out_buffer_size, 0); +} + void SetClientSdkVersion(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h index e61940ea0..66a87c8cd 100644 --- a/src/core/hle/service/frd/frd.h +++ b/src/core/hle/service/frd/frd.h @@ -96,6 +96,19 @@ void GetMyFriendKey(Service::Interface* self); void GetMyScreenName(Service::Interface* self); /** + * FRD::UnscrambleLocalFriendCode service function + * Inputs: + * 1 : Friend code count + * 2 : ((count * 12) << 14) | 0x402 + * 3 : Pointer to encoded friend codes. Each is 12 bytes large + * 64 : ((count * 8) << 14) | 2 + * 65 : Pointer to write decoded local friend codes to. Each is 8 bytes large. + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + */ +void UnscrambleLocalFriendCode(Service::Interface* self); + +/** * FRD::SetClientSdkVersion service function * Inputs: * 1 : Used SDK Version diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp index 496f29ca9..6970ff768 100644 --- a/src/core/hle/service/frd/frd_u.cpp +++ b/src/core/hle/service/frd/frd_u.cpp @@ -36,7 +36,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00190042, nullptr, "GetFriendFavoriteGame"}, {0x001A00C4, nullptr, "GetFriendInfo"}, {0x001B0080, nullptr, "IsIncludedInFriendList"}, - {0x001C0042, nullptr, "UnscrambleLocalFriendCode"}, + {0x001C0042, UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"}, {0x001D0002, nullptr, "UpdateGameModeDescription"}, {0x001E02C2, nullptr, "UpdateGameMode"}, {0x001F0042, nullptr, "SendInvitation"}, diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index bc964ec60..88684b82d 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -475,12 +475,11 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { // TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever // possible/likely - Memory::RasterizerFlushRegion( - Memory::VirtualToPhysicalAddress(command.dma_request.source_address), - command.dma_request.size); - Memory::RasterizerFlushAndInvalidateRegion( - Memory::VirtualToPhysicalAddress(command.dma_request.dest_address), - command.dma_request.size); + Memory::RasterizerFlushVirtualRegion(command.dma_request.source_address, + command.dma_request.size, Memory::FlushMode::Flush); + Memory::RasterizerFlushVirtualRegion(command.dma_request.dest_address, + command.dma_request.size, + Memory::FlushMode::FlushAndInvalidate); // TODO(Subv): These memory accesses should not go through the application's memory mapping. // They should go through the GSP module's memory mapping. diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp index e73971d5f..57172ddd6 100644 --- a/src/core/hle/service/y2r_u.cpp +++ b/src/core/hle/service/y2r_u.cpp @@ -587,8 +587,8 @@ static void StartConversion(Interface* self) { // dst_image_size would seem to be perfect for this, but it doesn't include the gap :( u32 total_output_size = conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap); - Memory::RasterizerFlushAndInvalidateRegion( - Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size); + Memory::RasterizerFlushVirtualRegion(conversion.dst.address, total_output_size, + Memory::FlushMode::FlushAndInvalidate); HW::Y2R::PerformConversion(conversion); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 48bbf687d..e731888a2 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -166,6 +166,15 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the title of the application + * @param title Reference to store the application title into + * @return ResultStatus result of function + */ + virtual ResultStatus ReadTitle(std::string& title) { + return ResultStatus::ErrorNotImplemented; + } + protected: FileUtil::IOFile file; bool is_loaded = false; diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index ffc019560..c007069a9 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -4,7 +4,9 @@ #include <algorithm> #include <cinttypes> +#include <codecvt> #include <cstring> +#include <locale> #include <memory> #include "common/logging/log.h" #include "common/string_util.h" @@ -342,9 +344,11 @@ ResultStatus AppLoader_NCCH::Load() { if (result != ResultStatus::Success) return result; - LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); + std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)}; - Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id); + LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); + + Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); is_loaded = true; // Set state to loaded @@ -418,4 +422,22 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_ return ResultStatus::ErrorNotUsed; } +ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { + std::vector<u8> data; + Loader::SMDH smdh; + ReadIcon(data); + + if (!Loader::IsValidSMDH(data)) { + return ResultStatus::ErrorInvalidFormat; + } + + memcpy(&smdh, data.data(), sizeof(Loader::SMDH)); + + const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English); + auto title_end = std::find(short_title.begin(), short_title.end(), u'\0'); + title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end}); + + return ResultStatus::Success; +} + } // namespace Loader diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 0ebd47fd5..e40cef764 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -191,23 +191,13 @@ public: ResultStatus ReadLogo(std::vector<u8>& buffer) override; - /** - * Get the program id of the application - * @param out_program_id Reference to store program id into - * @return ResultStatus result of function - */ ResultStatus ReadProgramId(u64& out_program_id) override; - /** - * Get the RomFS of the application - * @param romfs_file Reference to buffer to store data - * @param offset Offset in the file to the RomFS - * @param size Size of the RomFS in bytes - * @return ResultStatus result of function - */ ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, u64& size) override; + ResultStatus ReadTitle(std::string& title) override; + private: /** * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9024f4922..65649d9d7 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -83,19 +83,13 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); - u32 end = base + size; + RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE, + FlushMode::FlushAndInvalidate); + u32 end = base + size; while (base != end) { ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base); - // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be - // null here - if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory || - current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) { - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS), - PAGE_SIZE); - } - current_page_table->attributes[base] = type; current_page_table->pointers[base] = memory; current_page_table->cached_res_count[base] = 0; @@ -196,7 +190,7 @@ T Read(const VAddr vaddr) { ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); break; case PageType::RasterizerCachedMemory: { - RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); T value; std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T)); @@ -205,8 +199,7 @@ T Read(const VAddr vaddr) { case PageType::Special: return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); case PageType::RasterizerCachedSpecial: { - RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); - + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush); return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr); } default: @@ -236,8 +229,7 @@ void Write(const VAddr vaddr, const T data) { ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr); break; case PageType::RasterizerCachedMemory: { - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); - + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate); std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T)); break; } @@ -245,8 +237,7 @@ void Write(const VAddr vaddr, const T data) { WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); break; case PageType::RasterizerCachedSpecial: { - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T)); - + RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate); WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data); break; } @@ -275,7 +266,8 @@ bool IsValidVirtualAddress(const VAddr vaddr) { } bool IsValidPhysicalAddress(const PAddr paddr) { - return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr)); + boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(paddr); + return vaddr && IsValidVirtualAddress(*vaddr); } u8* GetPointer(const VAddr vaddr) { @@ -308,7 +300,8 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) { u8* GetPhysicalPointer(PAddr address) { // TODO(Subv): This call should not go through the application's memory mapping. - return GetPointer(PhysicalToVirtualAddress(address)); + boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(address); + return vaddr ? GetPointer(*vaddr) : nullptr; } void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { @@ -319,8 +312,12 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { u32 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1; PAddr paddr = start; - for (unsigned i = 0; i < num_pages; ++i) { - VAddr vaddr = PhysicalToVirtualAddress(paddr); + for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) { + boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr); + if (!maybe_vaddr) + continue; + VAddr vaddr = *maybe_vaddr; + u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS]; ASSERT_MSG(count_delta <= UINT8_MAX - res_count, "Rasterizer resource cache counter overflow!"); @@ -368,7 +365,6 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { UNREACHABLE(); } } - paddr += PAGE_SIZE; } } @@ -379,11 +375,48 @@ void RasterizerFlushRegion(PAddr start, u32 size) { } void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) { + // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be + // null here if (VideoCore::g_renderer != nullptr) { VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size); } } +void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) { + // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be + // null here + if (VideoCore::g_renderer != nullptr) { + VAddr end = start + size; + + auto CheckRegion = [&](VAddr region_start, VAddr region_end) { + if (start >= region_end || end <= region_start) { + // No overlap with region + return; + } + + VAddr overlap_start = std::max(start, region_start); + VAddr overlap_end = std::min(end, region_end); + + PAddr physical_start = TryVirtualToPhysicalAddress(overlap_start).value(); + u32 overlap_size = overlap_end - overlap_start; + + auto* rasterizer = VideoCore::g_renderer->Rasterizer(); + switch (mode) { + case FlushMode::Flush: + rasterizer->FlushRegion(physical_start, overlap_size); + break; + case FlushMode::FlushAndInvalidate: + rasterizer->FlushAndInvalidateRegion(physical_start, overlap_size); + break; + } + }; + + CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END); + CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END); + CheckRegion(VRAM_VADDR, VRAM_VADDR_END); + } +} + u8 Read8(const VAddr addr) { return Read<u8>(addr); } @@ -430,16 +463,13 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { break; } case PageType::RasterizerCachedMemory: { - RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount); break; } case PageType::RasterizerCachedSpecial: { DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount); break; } @@ -500,18 +530,13 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size break; } case PageType::RasterizerCachedMemory: { - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), - copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount); break; } case PageType::RasterizerCachedSpecial: { DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), - copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount); break; } @@ -557,18 +582,13 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { break; } case PageType::RasterizerCachedMemory: { - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), - copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount); break; } case PageType::RasterizerCachedSpecial: { DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), - copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); break; } @@ -613,15 +633,13 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { break; } case PageType::RasterizerCachedMemory: { - RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); - + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount); break; } case PageType::RasterizerCachedSpecial: { DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); - - RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount); + RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); std::vector<u8> buffer(copy_amount); GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); @@ -680,7 +698,7 @@ void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data) mmio_handler->Write64(addr, data); } -PAddr VirtualToPhysicalAddress(const VAddr addr) { +boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) { if (addr == 0) { return 0; } else if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) { @@ -697,12 +715,20 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) { return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR; } - LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); - // To help with debugging, set bit on address so that it's obviously invalid. - return addr | 0x80000000; + return boost::none; +} + +PAddr VirtualToPhysicalAddress(const VAddr addr) { + auto paddr = TryVirtualToPhysicalAddress(addr); + if (!paddr) { + LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr); + // To help with debugging, set bit on address so that it's obviously invalid. + return addr | 0x80000000; + } + return *paddr; } -VAddr PhysicalToVirtualAddress(const PAddr addr) { +boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) { if (addr == 0) { return 0; } else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) { @@ -717,9 +743,7 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) { return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR; } - LOG_ERROR(HW_Memory, "Unknown physical address @ 0x%08X", addr); - // To help with debugging, set bit on address so that it's obviously invalid. - return addr | 0x80000000; + return boost::none; } } // namespace diff --git a/src/core/memory.h b/src/core/memory.h index 71fb278ad..c8c56babd 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -7,6 +7,7 @@ #include <array> #include <cstddef> #include <string> +#include <boost/optional.hpp> #include "common/common_types.h" namespace Memory { @@ -148,15 +149,23 @@ u8* GetPointer(VAddr virtual_address); std::string ReadCString(VAddr virtual_address, std::size_t max_length); /** -* Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical -* address. This should be used by services to translate addresses for use by the hardware. -*/ + * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical + * address. This should be used by services to translate addresses for use by the hardware. + */ +boost::optional<PAddr> TryVirtualToPhysicalAddress(VAddr addr); + +/** + * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical + * address. This should be used by services to translate addresses for use by the hardware. + * + * @deprecated Use TryVirtualToPhysicalAddress(), which reports failure. + */ PAddr VirtualToPhysicalAddress(VAddr addr); /** -* Undoes a mapping performed by VirtualToPhysicalAddress(). -*/ -VAddr PhysicalToVirtualAddress(PAddr addr); + * Undoes a mapping performed by VirtualToPhysicalAddress(). + */ +boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr); /** * Gets a pointer to the memory region beginning at the specified physical address. @@ -181,6 +190,19 @@ void RasterizerFlushRegion(PAddr start, u32 size); */ void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size); +enum class FlushMode { + /// Write back modified surfaces to RAM + Flush, + /// Write back modified surfaces to RAM, and also remove them from the cache + FlushAndInvalidate, +}; + +/** + * Flushes and invalidates any externally cached rasterizer resources touching the given virtual + * address region. + */ +void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); + /** * Dynarmic has an optimization to memory accesses when the pointer to the page exists that * can be used by setting up the current page table as a callback. This function is used to diff --git a/src/core/settings.h b/src/core/settings.h index 03c64c94c..ee16bb90a 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -126,6 +126,9 @@ struct Values { // Debugging bool use_gdbstub; u16 gdbstub_port; + + // WebService + std::string telemetry_endpoint_url; } extern values; // a special value for Values::region_value indicating that citra will automatically select a region diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index ddc8b262e..94483f385 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -4,32 +4,111 @@ #include <cstring> +#include "common/assert.h" #include "common/scm_rev.h" +#include "common/x64/cpu_detect.h" +#include "core/core.h" +#include "core/settings.h" #include "core/telemetry_session.h" +#ifdef ENABLE_WEB_SERVICE +#include "web_service/telemetry_json.h" +#endif + namespace Core { +static const char* CpuVendorToStr(Common::CPUVendor vendor) { + switch (vendor) { + case Common::CPUVendor::INTEL: + return "Intel"; + case Common::CPUVendor::AMD: + return "Amd"; + case Common::CPUVendor::OTHER: + return "Other"; + } + UNREACHABLE(); +} + TelemetrySession::TelemetrySession() { - // TODO(bunnei): Replace with a backend that logs to our web service +#ifdef ENABLE_WEB_SERVICE + backend = std::make_unique<WebService::TelemetryJson>(); +#else backend = std::make_unique<Telemetry::NullVisitor>(); - +#endif // Log one-time session start information - const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; - const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; - AddField(Telemetry::FieldType::Session, "StartTime", start_time); + const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count()}; + AddField(Telemetry::FieldType::Session, "Init_Time", init_time); + std::string program_name; + const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadTitle(program_name)}; + if (res == Loader::ResultStatus::Success) { + AddField(Telemetry::FieldType::Session, "ProgramName", program_name); + } - // Log one-time application information + // Log application information const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr}; - AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty); - AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch); - AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev); + AddField(Telemetry::FieldType::App, "Git_IsDirty", is_git_dirty); + AddField(Telemetry::FieldType::App, "Git_Branch", Common::g_scm_branch); + AddField(Telemetry::FieldType::App, "Git_Revision", Common::g_scm_rev); + AddField(Telemetry::FieldType::App, "BuildDate", Common::g_build_date); + AddField(Telemetry::FieldType::App, "BuildName", Common::g_build_name); + + // Log user system information + AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string); + AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString", + Common::GetCPUCaps().brand_string); + AddField(Telemetry::FieldType::UserSystem, "CPU_Vendor", + CpuVendorToStr(Common::GetCPUCaps().vendor)); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSSE3", + Common::GetCPUCaps().ssse3); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE41", + Common::GetCPUCaps().sse4_1); + AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42", + Common::GetCPUCaps().sse4_2); +#ifdef __APPLE__ + AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Apple"); +#elif defined(_WIN32) + AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Windows"); +#elif defined(__linux__) || defined(linux) || defined(__linux) + AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Linux"); +#else + AddField(Telemetry::FieldType::UserSystem, "OsPlatform", "Unknown"); +#endif + + // Log user configuration information + AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching", + Settings::values.enable_audio_stretching); + AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit); + AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor", + Settings::values.resolution_factor); + AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit", + Settings::values.toggle_framelimit); + AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwRenderer", + Settings::values.use_hw_renderer); + AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit", + Settings::values.use_shader_jit); + AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.use_vsync); + AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds", Settings::values.is_new_3ds); + AddField(Telemetry::FieldType::UserConfig, "System_RegionValue", Settings::values.region_value); } TelemetrySession::~TelemetrySession() { // Log one-time session end information - const auto duration{std::chrono::steady_clock::now().time_since_epoch()}; - const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()}; - AddField(Telemetry::FieldType::Session, "EndTime", end_time); + const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>( + std::chrono::system_clock::now().time_since_epoch()) + .count()}; + AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time); // Complete the session, submitting to web service if necessary // This is just a placeholder to wrap up the session once the core completes and this is |