diff options
Diffstat (limited to '')
29 files changed, 691 insertions, 33 deletions
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp index 81a3abe3f..00d00905a 100644 --- a/src/citra/emu_window/emu_window_sdl2.cpp +++ b/src/citra/emu_window/emu_window_sdl2.cpp @@ -79,8 +79,8 @@ EmuWindow_SDL2::EmuWindow_SDL2() { SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s ", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); render_window = SDL_CreateWindow( window_title.c_str(), SDL_WINDOWPOS_UNDEFINED, // x position diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 948db384d..69d18cf0c 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -101,8 +101,8 @@ private: GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) { - std::string window_title = - Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); + std::string window_title = Common::StringFromFormat("Citra %s| %s-%s", Common::g_build_name, + Common::g_scm_branch, Common::g_scm_desc); setWindowTitle(QString::fromStdString(window_title)); keyboard_id = KeyMap::NewDeviceId(); diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui index 0f3352a1d..c739605a4 100644 --- a/src/citra_qt/configure_general.ui +++ b/src/citra_qt/configure_general.ui @@ -27,7 +27,7 @@ <item> <widget class="QCheckBox" name="toggle_deepscan"> <property name="text"> - <string>Recursive scan for game folder</string> + <string>Search sub-directories for games</string> </property> </widget> </item> diff --git a/src/citra_qt/game_list.cpp b/src/citra_qt/game_list.cpp index f15083b0a..a9ec9e830 100644 --- a/src/citra_qt/game_list.cpp +++ b/src/citra_qt/game_list.cpp @@ -39,6 +39,7 @@ GameList::GameList(QWidget* parent) : QWidget{parent} { connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); + connect(&watcher, &QFileSystemWatcher::directoryChanged, this, &GameList::RefreshGameDirectory); // 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. @@ -104,6 +105,12 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { item_model->removeRows(0, item_model->rowCount()); emit ShouldCancelWorker(); + + auto watch_dirs = watcher.directories(); + if (!watch_dirs.isEmpty()) { + watcher.removePaths(watch_dirs); + } + UpdateWatcherList(dir_path.toStdString(), deep_scan ? 256 : 0); GameListWorker* worker = new GameListWorker(dir_path, deep_scan); connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); @@ -141,6 +148,45 @@ static bool HasSupportedFileExtension(const std::string& file_name) { return GameList::supported_file_extensions.contains(file.suffix(), Qt::CaseInsensitive); } +void GameList::RefreshGameDirectory() { + if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { + LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); + PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); + } +} + +/** + * Adds the game list folder to the QFileSystemWatcher to check for updates. + * + * The file watcher will fire off an update to the game list when a change is detected in the game + * list folder. + * + * Notice: This method is run on the UI thread because QFileSystemWatcher is not thread safe and + * this function is fast enough to not stall the UI thread. If performance is an issue, it should + * be moved to another thread and properly locked to prevent concurrency issues. + * + * @param dir folder to check for changes in + * @param recursion 0 if recursion is disabled. Any positive number passed to this will add each + * directory recursively to the watcher and will update the file list if any of the folders + * change. The number determines how deep the recursion should traverse. + */ +void GameList::UpdateWatcherList(const std::string& dir, unsigned int recursion) { + const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, + const std::string& virtual_name) -> bool { + std::string physical_name = directory + DIR_SEP + virtual_name; + + if (FileUtil::IsDirectory(physical_name)) { + UpdateWatcherList(physical_name, recursion - 1); + } + return true; + }; + + watcher.addPath(QString::fromStdString(dir)); + if (recursion > 0) { + FileUtil::ForeachDirectoryEntry(nullptr, dir, callback); + } +} + void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion) { const auto callback = [this, recursion](unsigned* num_entries_out, const std::string& directory, const std::string& virtual_name) -> bool { @@ -183,6 +229,6 @@ void GameListWorker::run() { } void GameListWorker::Cancel() { - disconnect(this, nullptr, nullptr, nullptr); + this->disconnect(); stop_processing = true; } diff --git a/src/citra_qt/game_list.h b/src/citra_qt/game_list.h index e6b7eea0b..b141fa3a5 100644 --- a/src/citra_qt/game_list.h +++ b/src/citra_qt/game_list.h @@ -4,6 +4,7 @@ #pragma once +#include <QFileSystemWatcher> #include <QModelIndex> #include <QSettings> #include <QStandardItem> @@ -46,8 +47,11 @@ private: void DonePopulating(); void PopupContextMenu(const QPoint& menu_location); + void UpdateWatcherList(const std::string& path, unsigned int recursion); + void RefreshGameDirectory(); QTreeView* tree_view = nullptr; QStandardItemModel* item_model = nullptr; GameListWorker* current_worker = nullptr; + QFileSystemWatcher watcher; }; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index e1661ca9a..fd51659b9 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -69,7 +69,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) { ConnectMenuEvents(); ConnectWidgetEvents(); - setWindowTitle(QString("Citra | %1-%2").arg(Common::g_scm_branch, Common::g_scm_desc)); + setWindowTitle(QString("Citra %1| %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); show(); game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index aca0ebe38..8a6170257 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -1,4 +1,27 @@ # Generate cpp with Git revision from template +# Also if this is a CI build, add the build name (ie: Nightly, Bleeding Edge) to the scm_rev file as well +set(REPO_NAME "") +if ($ENV{CI}) + if ($ENV{TRAVIS}) + set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG}) + elseif($ENV{APPVEYOR}) + set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME}) + endif() + # regex capture the string nightly or bleeding-edge into CMAKE_MATCH_1 + string(REGEX MATCH "citra-emu/citra-?(.*)" OUTVAR ${BUILD_REPOSITORY}) + if (${CMAKE_MATCH_COUNT} GREATER 0) + # capitalize the first letter of each word in the repo name. + string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1}) + foreach(WORD ${REPO_NAME_LIST}) + string(SUBSTRING ${WORD} 0 1 FIRST_LETTER) + string(SUBSTRING ${WORD} 1 -1 REMAINDER) + string(TOUPPER ${FIRST_LETTER} FIRST_LETTER) + # this leaves a trailing space on the last word, but we actually want that + # because of how its styled in the title bar. + set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER} ") + endforeach() + endif() +endif() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY) set(SRCS diff --git a/src/common/common_paths.h b/src/common/common_paths.h index b56105306..d5b510cdb 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -45,3 +45,4 @@ // Sys files #define SHARED_FONT "shared_font.bin" +#define AES_KEYS "aes_keys.txt" diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 0f0354821..737e1d57f 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -63,6 +63,7 @@ namespace Log { SUB(HW, Memory) \ SUB(HW, LCD) \ SUB(HW, GPU) \ + SUB(HW, AES) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Software) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index f0ec922d2..4b0f8ff03 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -80,6 +80,7 @@ enum class Class : ClassType { HW_Memory, ///< Memory-map and address translation HW_LCD, ///< LCD register emulation HW_GPU, ///< GPU control emulation + HW_AES, ///< AES engine emulation Frontend, ///< Emulator UI Render, ///< Emulator video output and hardware acceleration Render_Software, ///< Software renderer backend diff --git a/src/common/scm_rev.cpp.in b/src/common/scm_rev.cpp.in index 79b404bb8..0080db5d5 100644 --- a/src/common/scm_rev.cpp.in +++ b/src/common/scm_rev.cpp.in @@ -7,12 +7,14 @@ #define GIT_REV "@GIT_REV@" #define GIT_BRANCH "@GIT_BRANCH@" #define GIT_DESC "@GIT_DESC@" +#define BUILD_NAME "@REPO_NAME@" namespace Common { const char g_scm_rev[] = GIT_REV; const char g_scm_branch[] = GIT_BRANCH; const char g_scm_desc[] = GIT_DESC; +const char g_build_name[] = BUILD_NAME; } // namespace diff --git a/src/common/scm_rev.h b/src/common/scm_rev.h index 0ef190afa..e22389803 100644 --- a/src/common/scm_rev.h +++ b/src/common/scm_rev.h @@ -9,5 +9,6 @@ namespace Common { extern const char g_scm_rev[]; extern const char g_scm_branch[]; extern const char g_scm_desc[]; +extern const char g_build_name[]; } // namespace diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 1adc78d8d..ffd67f074 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -159,6 +159,9 @@ set(SRCS hle/service/y2r_u.cpp hle/shared_page.cpp hle/svc.cpp + hw/aes/arithmetic128.cpp + hw/aes/ccm.cpp + hw/aes/key.cpp hw/gpu.cpp hw/hw.cpp hw/lcd.cpp @@ -344,6 +347,9 @@ set(HEADERS hle/service/y2r_u.h hle/shared_page.h hle/svc.h + hw/aes/arithmetic128.h + hw/aes/ccm.h + hw/aes/key.h hw/gpu.h hw/hw.h hw/lcd.h diff --git a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp index 67c45640a..273bc8167 100644 --- a/src/core/arm/dyncom/arm_dyncom_interpreter.cpp +++ b/src/core/arm/dyncom/arm_dyncom_interpreter.cpp @@ -3928,13 +3928,13 @@ SXTB16_INST : { if (inst_cream->Rn == 15) { u32 lo = (u32)(s8)rm_val; u32 hi = (u32)(s8)(rm_val >> 16); - RD = (lo | (hi << 16)); + RD = (lo & 0xFFFF) | (hi << 16); } // SXTAB16 else { - u32 lo = (rn_val & 0xFFFF) + (u32)(s8)(rm_val & 0xFF); - u32 hi = ((rn_val >> 16) & 0xFFFF) + (u32)(s8)((rm_val >> 16) & 0xFF); - RD = (lo | (hi << 16)); + u32 lo = rn_val + (u32)(s8)(rm_val & 0xFF); + u32 hi = (rn_val >> 16) + (u32)(s8)((rm_val >> 16) & 0xFF); + RD = (lo & 0xFFFF) | (hi << 16); } } diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp index 60537f355..c42003e9d 100644 --- a/src/core/hle/kernel/timer.cpp +++ b/src/core/hle/kernel/timer.cpp @@ -52,9 +52,14 @@ void Timer::Set(s64 initial, s64 interval) { initial_delay = initial; interval_delay = interval; - u64 initial_microseconds = initial / 1000; - CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, - callback_handle); + if (initial == 0) { + // Immediately invoke the callback + Signal(0); + } else { + u64 initial_microseconds = initial / 1000; + CoreTiming::ScheduleEvent(usToCycles(initial_microseconds), timer_callback_event_type, + callback_handle); + } } void Timer::Cancel() { @@ -72,6 +77,20 @@ void Timer::WakeupAllWaitingThreads() { signaled = false; } +void Timer::Signal(int cycles_late) { + LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle); + + // Resume all waiting threads + WakeupAllWaitingThreads(); + + if (interval_delay != 0) { + // Reschedule the timer with the interval delay + u64 interval_microseconds = interval_delay / 1000; + CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, + timer_callback_event_type, callback_handle); + } +} + /// The timer callback event, called when a timer is fired static void TimerCallback(u64 timer_handle, int cycles_late) { SharedPtr<Timer> timer = @@ -82,19 +101,7 @@ static void TimerCallback(u64 timer_handle, int cycles_late) { return; } - LOG_TRACE(Kernel, "Timer %08" PRIx64 " fired", timer_handle); - - timer->signaled = true; - - // Resume all waiting threads - timer->WakeupAllWaitingThreads(); - - if (timer->interval_delay != 0) { - // Reschedule the timer with the interval delay - u64 interval_microseconds = timer->interval_delay / 1000; - CoreTiming::ScheduleEvent(usToCycles(interval_microseconds) - cycles_late, - timer_callback_event_type, timer_handle); - } + timer->Signal(cycles_late); } void TimersInit() { diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h index c174f5664..b0f818933 100644 --- a/src/core/hle/kernel/timer.h +++ b/src/core/hle/kernel/timer.h @@ -54,6 +54,14 @@ public: void Cancel(); void Clear(); + /** + * Signals the timer, waking up any waiting threads and rescheduling it + * for the next interval. + * This method should not be called from outside the timer callback handler, + * lest multiple callback events get scheduled. + */ + void Signal(int cycles_late); + private: Timer(); ~Timer() override; diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 615fe31ea..e57b19c2d 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp @@ -18,6 +18,8 @@ #include "core/hle/service/fs/archive.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/service/service.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" namespace Service { namespace APT { @@ -470,6 +472,107 @@ void GetStartupArgument(Service::Interface* self) { cmd_buff[2] = 0; } +void Wrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x46, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size + HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and concatenates the rest of the input as plaintext + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input + nonce_offset, nonce.data(), nonce_size); + u32 pdata_size = input_size - nonce_size; + std::vector<u8> pdata(pdata_size); + Memory::ReadBlock(input, pdata.data(), nonce_offset); + Memory::ReadBlock(input + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata_size - nonce_offset); + + // Encrypts the plaintext using AES-CCM + auto cipher = HW::AES::EncryptSignCCM(pdata, nonce, HW::AES::KeySlotID::APTWrap); + + // Puts the nonce to the beginning of the output, with ciphertext followed + Memory::WriteBlock(output, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_size, cipher.data(), cipher.size()); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 4); + rb.Push(RESULT_SUCCESS); + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + +void Unwrap(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x47, 4, 4); + const u32 output_size = rp.Pop<u32>(); + const u32 input_size = rp.Pop<u32>(); + const u32 nonce_offset = rp.Pop<u32>(); + u32 nonce_size = rp.Pop<u32>(); + size_t desc_size; + IPC::MappedBufferPermissions desc_permission; + const VAddr input = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == input_size && desc_permission == IPC::MappedBufferPermissions::R); + const VAddr output = rp.PopMappedBuffer(&desc_size, &desc_permission); + ASSERT(desc_size == output_size && desc_permission == IPC::MappedBufferPermissions::W); + + // Note: real 3DS still returns SUCCESS when the sizes don't match. It seems that it doesn't + // check the buffer size and writes data with potential overflow. + ASSERT_MSG(output_size == input_size - HW::AES::CCM_MAC_SIZE, + "input_size (%d) doesn't match to output_size (%d)", input_size, output_size); + + LOG_DEBUG(Service_APT, "called, output_size=%u, input_size=%u, nonce_offset=%u, nonce_size=%u", + output_size, input_size, nonce_offset, nonce_size); + + // Note: This weird nonce size modification is verified against real 3DS + nonce_size = std::min<u32>(nonce_size & ~3, HW::AES::CCM_NONCE_SIZE); + + // Reads nonce and cipher text + HW::AES::CCMNonce nonce{}; + Memory::ReadBlock(input, nonce.data(), nonce_size); + u32 cipher_size = input_size - nonce_size; + std::vector<u8> cipher(cipher_size); + Memory::ReadBlock(input + nonce_size, cipher.data(), cipher_size); + + // Decrypts the ciphertext using AES-CCM + auto pdata = HW::AES::DecryptVerifyCCM(cipher, nonce, HW::AES::KeySlotID::APTWrap); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + if (!pdata.empty()) { + // Splits the plaintext and put the nonce in between + Memory::WriteBlock(output, pdata.data(), nonce_offset); + Memory::WriteBlock(output + nonce_offset, nonce.data(), nonce_size); + Memory::WriteBlock(output + nonce_offset + nonce_size, pdata.data() + nonce_offset, + pdata.size() - nonce_offset); + rb.Push(RESULT_SUCCESS); + } else { + LOG_ERROR(Service_APT, "Failed to decrypt data"); + rb.Push(ResultCode(static_cast<ErrorDescription>(1), ErrorModule::PS, + ErrorSummary::WrongArgument, ErrorLevel::Status)); + } + + // Unmap buffer + rb.PushMappedBuffer(input, input_size, IPC::MappedBufferPermissions::R); + rb.PushMappedBuffer(output, output_size, IPC::MappedBufferPermissions::W); +} + void CheckNew3DSApp(Service::Interface* self) { u32* cmd_buff = Kernel::GetCommandBuffer(); diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 80325361f..e63b61450 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h @@ -137,6 +137,46 @@ void Initialize(Service::Interface* self); void GetSharedFont(Service::Interface* self); /** + * APT::Wrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the input buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Wrap(Service::Interface* self); + +/** + * APT::Unwrap service function + * Inputs: + * 1 : Output buffer size + * 2 : Input buffer size + * 3 : Nonce offset to the output buffer + * 4 : Nonce size + * 5 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xA) + * 6 : Input buffer address + * 7 : Buffer mapping descriptor ((input_buffer_size << 4) | 0xC) + * 8 : Output buffer address + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xA) + * 3 : Input buffer address + * 4 : Buffer unmapping descriptor ((input_buffer_size << 4) | 0xC) + * 5 : Output buffer address + */ +void Unwrap(Service::Interface* self); + +/** * APT::NotifyToWait service function * Inputs: * 1 : AppID diff --git a/src/core/hle/service/apt/apt_a.cpp b/src/core/hle/service/apt/apt_a.cpp index 62dc2d61d..c496cba8d 100644 --- a/src/core/hle/service/apt/apt_a.cpp +++ b/src/core/hle/service/apt/apt_a.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index effd23dce..ec5668d05 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/service/apt/apt_u.cpp b/src/core/hle/service/apt/apt_u.cpp index e06084a1e..9dd002590 100644 --- a/src/core/hle/service/apt/apt_u.cpp +++ b/src/core/hle/service/apt/apt_u.cpp @@ -78,8 +78,8 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00430040, NotifyToWait, "NotifyToWait"}, {0x00440000, GetSharedFont, "GetSharedFont"}, {0x00450040, nullptr, "GetWirelessRebootInfo"}, - {0x00460104, nullptr, "Wrap"}, - {0x00470104, nullptr, "Unwrap"}, + {0x00460104, Wrap, "Wrap"}, + {0x00470104, Unwrap, "Unwrap"}, {0x00480100, nullptr, "GetProgramInfo"}, {0x00490180, nullptr, "Reboot"}, {0x004A0040, nullptr, "GetCaptureInfo"}, diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index 96db39ad9..1baa80671 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp @@ -837,6 +837,11 @@ static ResultCode SetTimer(Kernel::Handle handle, s64 initial, s64 interval) { LOG_TRACE(Kernel_SVC, "called timer=0x%08X", handle); + if (initial < 0 || interval < 0) { + return ResultCode(ErrorDescription::OutOfRange, ErrorModule::Kernel, + ErrorSummary::InvalidArgument, ErrorLevel::Permanent); + } + SharedPtr<Timer> timer = Kernel::g_handle_table.Get<Timer>(handle); if (timer == nullptr) return ERR_INVALID_HANDLE; diff --git a/src/core/hw/aes/arithmetic128.cpp b/src/core/hw/aes/arithmetic128.cpp new file mode 100644 index 000000000..55b954a52 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.cpp @@ -0,0 +1,47 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <functional> +#include "core/hw/aes/arithmetic128.h" + +namespace HW { +namespace AES { + +AESKey Lrot128(const AESKey& in, u32 rot) { + AESKey out; + rot %= 128; + const u32 byte_shift = rot / 8; + const u32 bit_shift = rot % 8; + + for (u32 i = 0; i < 16; i++) { + const u32 wrap_index_a = (i + byte_shift) % 16; + const u32 wrap_index_b = (i + byte_shift + 1) % 16; + out[i] = ((in[wrap_index_a] << bit_shift) | (in[wrap_index_b] >> (8 - bit_shift))) & 0xFF; + } + return out; +} + +AESKey Add128(const AESKey& a, const AESKey& b) { + AESKey out; + u32 carry = 0; + u32 sum = 0; + + for (int i = 15; i >= 0; i--) { + sum = a[i] + b[i] + carry; + carry = sum >> 8; + out[i] = static_cast<u8>(sum & 0xff); + } + + return out; +} + +AESKey Xor128(const AESKey& a, const AESKey& b) { + AESKey out; + std::transform(a.cbegin(), a.cend(), b.cbegin(), out.begin(), std::bit_xor<>()); + return out; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/arithmetic128.h b/src/core/hw/aes/arithmetic128.h new file mode 100644 index 000000000..d670e2ce2 --- /dev/null +++ b/src/core/hw/aes/arithmetic128.h @@ -0,0 +1,17 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { +AESKey Lrot128(const AESKey& in, u32 rot); +AESKey Add128(const AESKey& a, const AESKey& b); +AESKey Xor128(const AESKey& a, const AESKey& b); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.cpp b/src/core/hw/aes/ccm.cpp new file mode 100644 index 000000000..dc7035ab6 --- /dev/null +++ b/src/core/hw/aes/ccm.cpp @@ -0,0 +1,95 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <cryptopp/aes.h> +#include <cryptopp/ccm.h> +#include <cryptopp/cryptlib.h> +#include <cryptopp/filters.h> +#include "common/alignment.h" +#include "common/logging/log.h" +#include "core/hw/aes/ccm.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +// 3DS uses a non-standard AES-CCM algorithm, so we need to derive a sub class from the standard one +// and override with the non-standard part. +using CryptoPP::lword; +using CryptoPP::AES; +using CryptoPP::CCM_Final; +using CryptoPP::CCM_Base; +template <bool T_IsEncryption> +class CCM_3DSVariant_Final : public CCM_Final<AES, CCM_MAC_SIZE, T_IsEncryption> { +public: + void UncheckedSpecifyDataLengths(lword header_length, lword message_length, + lword footer_length) override { + // 3DS uses the aligned size to generate B0 for authentication, instead of the original size + lword aligned_message_length = Common::AlignUp(message_length, AES_BLOCK_SIZE); + CCM_Base::UncheckedSpecifyDataLengths(header_length, aligned_message_length, footer_length); + CCM_Base::m_messageLength = message_length; // restore the actual message size + } +}; + +class CCM_3DSVariant { +public: + using Encryption = CCM_3DSVariant_Final<true>; + using Decryption = CCM_3DSVariant_Final<false>; +}; + +} // namespace + +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + std::vector<u8> cipher(pdata.size() + CCM_MAC_SIZE); + + try { + CCM_3DSVariant::Encryption e; + e.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + e.SpecifyDataLengths(0, pdata.size(), 0); + CryptoPP::ArraySource as(pdata.data(), pdata.size(), true, + new CryptoPP::AuthenticatedEncryptionFilter( + e, new CryptoPP::ArraySink(cipher.data(), cipher.size()))); + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + } + return cipher; +} + +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id) { + if (!IsNormalKeyAvailable(slot_id)) { + LOG_ERROR(HW_AES, "Key slot %d not available. Will use zero key.", slot_id); + } + const AESKey normal = GetNormalKey(slot_id); + const std::size_t pdata_size = cipher.size() - CCM_MAC_SIZE; + std::vector<u8> pdata(pdata_size); + + try { + CCM_3DSVariant::Decryption d; + d.SetKeyWithIV(normal.data(), AES_BLOCK_SIZE, nonce.data(), CCM_NONCE_SIZE); + d.SpecifyDataLengths(0, pdata_size, 0); + CryptoPP::AuthenticatedDecryptionFilter df( + d, new CryptoPP::ArraySink(pdata.data(), pdata_size)); + CryptoPP::ArraySource as(cipher.data(), cipher.size(), true, new CryptoPP::Redirector(df)); + if (!df.GetLastResult()) { + LOG_ERROR(HW_AES, "FAILED"); + return {}; + } + } catch (const CryptoPP::Exception& e) { + LOG_ERROR(HW_AES, "FAILED with: %s", e.what()); + return {}; + } + return pdata; +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/ccm.h b/src/core/hw/aes/ccm.h new file mode 100644 index 000000000..bf4146e80 --- /dev/null +++ b/src/core/hw/aes/ccm.h @@ -0,0 +1,40 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include <vector> +#include "common/common_types.h" + +namespace HW { +namespace AES { + +constexpr size_t CCM_NONCE_SIZE = 12; +constexpr size_t CCM_MAC_SIZE = 16; + +using CCMNonce = std::array<u8, CCM_NONCE_SIZE>; + +/** + * Encrypts and adds a MAC to the given data using AES-CCM algorithm. + * @param pdata The plain text data to encrypt + * @param nonce The nonce data to use for encryption + * @param slot_id The slot ID of the key to use for encryption + * @returns a vector of u8 containing the encrypted data with MAC at the end + */ +std::vector<u8> EncryptSignCCM(const std::vector<u8>& pdata, const CCMNonce& nonce, size_t slot_id); + +/** + * Decrypts and verify the MAC of the given data using AES-CCM algorithm. + * @param cipher The cipher text data to decrypt, with MAC at the end to verify + * @param nonce The nonce data to use for decryption + * @param slot_id The slot ID of the key to use for decryption + * @returns a vector of u8 containing the decrypted data; an empty vector if the verification fails + */ +std::vector<u8> DecryptVerifyCCM(const std::vector<u8>& cipher, const CCMNonce& nonce, + size_t slot_id); + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp new file mode 100644 index 000000000..4e8a8a59a --- /dev/null +++ b/src/core/hw/aes/key.cpp @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include <exception> +#include <sstream> +#include <boost/optional.hpp> +#include "common/common_paths.h" +#include "common/file_util.h" +#include "common/logging/log.h" +#include "common/string_util.h" +#include "core/hw/aes/arithmetic128.h" +#include "core/hw/aes/key.h" + +namespace HW { +namespace AES { + +namespace { + +boost::optional<AESKey> generator_constant; + +struct KeySlot { + boost::optional<AESKey> x; + boost::optional<AESKey> y; + boost::optional<AESKey> normal; + + void SetKeyX(const AESKey& key) { + x = key; + if (y && generator_constant) { + GenerateNormalKey(); + } + } + + void SetKeyY(const AESKey& key) { + y = key; + if (x && generator_constant) { + GenerateNormalKey(); + } + } + + void SetNormalKey(const AESKey& key) { + normal = key; + } + + void GenerateNormalKey() { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + } + + void Clear() { + x.reset(); + y.reset(); + normal.reset(); + } +}; + +std::array<KeySlot, KeySlotID::MaxKeySlotID> key_slots; + +void ClearAllKeys() { + for (KeySlot& slot : key_slots) { + slot.Clear(); + } + generator_constant.reset(); +} + +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast<u8>(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + +void LoadPresetKeys() { + const std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + AES_KEYS; + FileUtil::CreateFullPath(filepath); // Create path if not already created + std::ifstream file; + OpenFStream(file, filepath, std::ios_base::in); + if (!file) { + return; + } + + while (!file.eof()) { + std::string line; + std::getline(file, line); + std::vector<std::string> parts; + Common::SplitString(line, '=', parts); + if (parts.size() != 2) { + LOG_ERROR(HW_AES, "Failed to parse %s", line.c_str()); + continue; + } + + const std::string& name = parts[0]; + AESKey key; + try { + key = HexToKey(parts[1]); + } catch (const std::logic_error& e) { + LOG_ERROR(HW_AES, "Invalid key %s: %s", parts[1].c_str(), e.what()); + continue; + } + + if (name == "generator") { + generator_constant = key; + continue; + } + + size_t slot_id; + char key_type; + if (std::sscanf(name.c_str(), "slot0x%zXKey%c", &slot_id, &key_type) != 2) { + LOG_ERROR(HW_AES, "Invalid key name %s", name.c_str()); + continue; + } + + if (slot_id >= MaxKeySlotID) { + LOG_ERROR(HW_AES, "Out of range slot ID 0x%zX", slot_id); + continue; + } + + switch (key_type) { + case 'X': + key_slots.at(slot_id).SetKeyX(key); + break; + case 'Y': + key_slots.at(slot_id).SetKeyY(key); + break; + case 'N': + key_slots.at(slot_id).SetNormalKey(key); + break; + default: + LOG_ERROR(HW_AES, "Invalid key type %c", key_type); + break; + } + } +} + +} // namespace + +void InitKeys() { + ClearAllKeys(); + LoadPresetKeys(); +} + +void SetGeneratorConstant(const AESKey& key) { + generator_constant = key; +} + +void SetKeyX(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyX(key); +} + +void SetKeyY(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetKeyY(key); +} + +void SetNormalKey(size_t slot_id, const AESKey& key) { + key_slots.at(slot_id).SetNormalKey(key); +} + +bool IsNormalKeyAvailable(size_t slot_id) { + return key_slots.at(slot_id).normal.is_initialized(); +} + +AESKey GetNormalKey(size_t slot_id) { + return key_slots.at(slot_id).normal.value_or(AESKey{}); +} + +} // namespace AES +} // namespace HW diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h new file mode 100644 index 000000000..b01d04f13 --- /dev/null +++ b/src/core/hw/aes/key.h @@ -0,0 +1,35 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <cstddef> +#include "common/common_types.h" + +namespace HW { +namespace AES { + +enum KeySlotID : size_t { + APTWrap = 0x31, + + MaxKeySlotID = 0x40, +}; + +constexpr size_t AES_BLOCK_SIZE = 16; + +using AESKey = std::array<u8, AES_BLOCK_SIZE>; + +void InitKeys(); + +void SetGeneratorConstant(const AESKey& key); +void SetKeyX(size_t slot_id, const AESKey& key); +void SetKeyY(size_t slot_id, const AESKey& key); +void SetNormalKey(size_t slot_id, const AESKey& key); + +bool IsNormalKeyAvailable(size_t slot_id); +AESKey GetNormalKey(size_t slot_id); + +} // namspace AES +} // namespace HW diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index 9ff8825b2..8499f2ce6 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -4,6 +4,7 @@ #include "common/common_types.h" #include "common/logging/log.h" +#include "core/hw/aes/key.h" #include "core/hw/gpu.h" #include "core/hw/hw.h" #include "core/hw/lcd.h" @@ -85,6 +86,7 @@ void Update() {} /// Initialize hardware void Init() { + AES::InitKeys(); GPU::Init(); LCD::Init(); LOG_DEBUG(HW, "initialized OK"); |