diff options
86 files changed, 2126 insertions, 1111 deletions
diff --git a/.gitignore b/.gitignore index ec74b0fa4..7999a40e1 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -9,12 +9,16 @@ src/common/scm_rev.cpp | |||
| 9 | # Project/editor files | 9 | # Project/editor files |
| 10 | *.swp | 10 | *.swp |
| 11 | .idea/ | 11 | .idea/ |
| 12 | .vs/ | ||
| 12 | .vscode/ | 13 | .vscode/ |
| 13 | 14 | ||
| 14 | # *nix related | 15 | # *nix related |
| 15 | # Common convention for backup or temporary files | 16 | # Common convention for backup or temporary files |
| 16 | *~ | 17 | *~ |
| 17 | 18 | ||
| 19 | # Visual Studio CMake settings | ||
| 20 | CMakeSettings.json | ||
| 21 | |||
| 18 | # OSX global filetypes | 22 | # OSX global filetypes |
| 19 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) | 23 | # Created by Finder or Spotlight in directories for various OS functionality (indexing, etc) |
| 20 | .DS_Store | 24 | .DS_Store |
diff --git a/.travis-build.sh b/.travis-build.sh index bb4e6fc47..fc5a5f8b2 100755 --- a/.travis-build.sh +++ b/.travis-build.sh | |||
| @@ -52,8 +52,8 @@ elif [ "$TRAVIS_OS_NAME" = "osx" ]; then | |||
| 52 | export Qt5_DIR=$(brew --prefix)/opt/qt5 | 52 | export Qt5_DIR=$(brew --prefix)/opt/qt5 |
| 53 | 53 | ||
| 54 | mkdir build && cd build | 54 | mkdir build && cd build |
| 55 | cmake .. -DUSE_SYSTEM_CURL=ON -GXcode | 55 | cmake .. -DUSE_SYSTEM_CURL=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;x86_64h" -DCMAKE_BUILD_TYPE=Release |
| 56 | xcodebuild -configuration Release | 56 | make -j4 |
| 57 | 57 | ||
| 58 | ctest -VV -C Release | 58 | ctest -VV -C Release |
| 59 | fi | 59 | fi |
diff --git a/.travis-upload.sh b/.travis-upload.sh index 8c1fa21c5..edf195f7d 100755 --- a/.travis-upload.sh +++ b/.travis-upload.sh | |||
| @@ -16,8 +16,8 @@ elif [ "$TRAVIS_OS_NAME" = "osx" ]; then | |||
| 16 | COMPRESSION_FLAGS="-czvf" | 16 | COMPRESSION_FLAGS="-czvf" |
| 17 | mkdir "$REV_NAME" | 17 | mkdir "$REV_NAME" |
| 18 | 18 | ||
| 19 | cp build/src/citra/Release/citra "$REV_NAME" | 19 | cp build/src/citra/citra "$REV_NAME" |
| 20 | cp -r build/src/citra_qt/Release/citra-qt.app "$REV_NAME" | 20 | cp -r build/src/citra_qt/citra-qt.app "$REV_NAME" |
| 21 | 21 | ||
| 22 | # move qt libs into app bundle for deployment | 22 | # move qt libs into app bundle for deployment |
| 23 | $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" | 23 | $(brew --prefix)/opt/qt5/bin/macdeployqt "${REV_NAME}/citra-qt.app" |
diff --git a/src/audio_core/codec.cpp b/src/audio_core/codec.cpp index 7a3bd7eb3..6fba9fdae 100644 --- a/src/audio_core/codec.cpp +++ b/src/audio_core/codec.cpp | |||
| @@ -117,7 +117,9 @@ StereoBuffer16 DecodePCM16(const unsigned num_channels, const u8* const data, | |||
| 117 | ret[i].fill(sample); | 117 | ret[i].fill(sample); |
| 118 | } | 118 | } |
| 119 | } else { | 119 | } else { |
| 120 | std::memcpy(ret.data(), data, sample_count * 2 * sizeof(u16)); | 120 | for (size_t i = 0; i < sample_count; ++i) { |
| 121 | std::memcpy(&ret[i], data + i * sizeof(s16) * 2, 2 * sizeof(s16)); | ||
| 122 | } | ||
| 121 | } | 123 | } |
| 122 | 124 | ||
| 123 | return ret; | 125 | return ret; |
diff --git a/src/audio_core/codec.h b/src/audio_core/codec.h index 2b0c395e6..877b2202d 100644 --- a/src/audio_core/codec.h +++ b/src/audio_core/codec.h | |||
| @@ -5,13 +5,13 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <vector> | 8 | #include <deque> |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | 10 | ||
| 11 | namespace Codec { | 11 | namespace Codec { |
| 12 | 12 | ||
| 13 | /// A variable length buffer of signed PCM16 stereo samples. | 13 | /// A variable length buffer of signed PCM16 stereo samples. |
| 14 | using StereoBuffer16 = std::vector<std::array<s16, 2>>; | 14 | using StereoBuffer16 = std::deque<std::array<s16, 2>>; |
| 15 | 15 | ||
| 16 | /// See: Codec::DecodeADPCM | 16 | /// See: Codec::DecodeADPCM |
| 17 | struct ADPCMState { | 17 | struct ADPCMState { |
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index de4e88cae..c12287700 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp | |||
| @@ -264,7 +264,7 @@ void Source::GenerateFrame() { | |||
| 264 | break; | 264 | break; |
| 265 | } | 265 | } |
| 266 | } | 266 | } |
| 267 | state.next_sample_number += frame_position; | 267 | state.next_sample_number += static_cast<u32>(frame_position); |
| 268 | 268 | ||
| 269 | state.filters.ProcessFrame(current_frame); | 269 | state.filters.ProcessFrame(current_frame); |
| 270 | } | 270 | } |
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index ccb7f064f..c4d2debc2 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h | |||
| @@ -108,7 +108,7 @@ private: | |||
| 108 | 108 | ||
| 109 | u32 current_sample_number = 0; | 109 | u32 current_sample_number = 0; |
| 110 | u32 next_sample_number = 0; | 110 | u32 next_sample_number = 0; |
| 111 | std::vector<std::array<s16, 2>> current_buffer; | 111 | AudioInterp::StereoBuffer16 current_buffer; |
| 112 | 112 | ||
| 113 | // buffer_id state | 113 | // buffer_id state |
| 114 | 114 | ||
diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp index 16e68bc5c..83573d772 100644 --- a/src/audio_core/interpolate.cpp +++ b/src/audio_core/interpolate.cpp | |||
| @@ -47,7 +47,7 @@ static void StepOverSamples(State& state, StereoBuffer16& input, float rate, | |||
| 47 | state.xn1 = input[inputi + 1]; | 47 | state.xn1 = input[inputi + 1]; |
| 48 | state.fposition = fposition - inputi * scale_factor; | 48 | state.fposition = fposition - inputi * scale_factor; |
| 49 | 49 | ||
| 50 | input.erase(input.begin(), input.begin() + inputi + 2); | 50 | input.erase(input.begin(), std::next(input.begin(), inputi + 2)); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, | 53 | void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, |
diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h index 59f59bc14..8dff6111a 100644 --- a/src/audio_core/interpolate.h +++ b/src/audio_core/interpolate.h | |||
| @@ -5,14 +5,14 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <vector> | 8 | #include <deque> |
| 9 | #include "audio_core/hle/common.h" | 9 | #include "audio_core/hle/common.h" |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | 11 | ||
| 12 | namespace AudioInterp { | 12 | namespace AudioInterp { |
| 13 | 13 | ||
| 14 | /// A variable length buffer of signed PCM16 stereo samples. | 14 | /// A variable length buffer of signed PCM16 stereo samples. |
| 15 | using StereoBuffer16 = std::vector<std::array<s16, 2>>; | 15 | using StereoBuffer16 = std::deque<std::array<s16, 2>>; |
| 16 | 16 | ||
| 17 | struct State { | 17 | struct State { |
| 18 | /// Two historical samples. | 18 | /// Two historical samples. |
diff --git a/src/citra_qt/configuration/configure_graphics.ui b/src/citra_qt/configuration/configure_graphics.ui index b340149d5..5667b14b6 100644 --- a/src/citra_qt/configuration/configure_graphics.ui +++ b/src/citra_qt/configuration/configure_graphics.ui | |||
| @@ -63,57 +63,57 @@ | |||
| 63 | <widget class="QComboBox" name="resolution_factor_combobox"> | 63 | <widget class="QComboBox" name="resolution_factor_combobox"> |
| 64 | <item> | 64 | <item> |
| 65 | <property name="text"> | 65 | <property name="text"> |
| 66 | <string notr="true">Auto (Window Size)</string> | 66 | <string>Auto (Window Size)</string> |
| 67 | </property> | 67 | </property> |
| 68 | </item> | 68 | </item> |
| 69 | <item> | 69 | <item> |
| 70 | <property name="text"> | 70 | <property name="text"> |
| 71 | <string notr="true">Native (400x240)</string> | 71 | <string>Native (400x240)</string> |
| 72 | </property> | 72 | </property> |
| 73 | </item> | 73 | </item> |
| 74 | <item> | 74 | <item> |
| 75 | <property name="text"> | 75 | <property name="text"> |
| 76 | <string notr="true">2x Native (800x480)</string> | 76 | <string>2x Native (800x480)</string> |
| 77 | </property> | 77 | </property> |
| 78 | </item> | 78 | </item> |
| 79 | <item> | 79 | <item> |
| 80 | <property name="text"> | 80 | <property name="text"> |
| 81 | <string notr="true">3x Native (1200x720)</string> | 81 | <string>3x Native (1200x720)</string> |
| 82 | </property> | 82 | </property> |
| 83 | </item> | 83 | </item> |
| 84 | <item> | 84 | <item> |
| 85 | <property name="text"> | 85 | <property name="text"> |
| 86 | <string notr="true">4x Native (1600x960)</string> | 86 | <string>4x Native (1600x960)</string> |
| 87 | </property> | 87 | </property> |
| 88 | </item> | 88 | </item> |
| 89 | <item> | 89 | <item> |
| 90 | <property name="text"> | 90 | <property name="text"> |
| 91 | <string notr="true">5x Native (2000x1200)</string> | 91 | <string>5x Native (2000x1200)</string> |
| 92 | </property> | 92 | </property> |
| 93 | </item> | 93 | </item> |
| 94 | <item> | 94 | <item> |
| 95 | <property name="text"> | 95 | <property name="text"> |
| 96 | <string notr="true">6x Native (2400x1440)</string> | 96 | <string>6x Native (2400x1440)</string> |
| 97 | </property> | 97 | </property> |
| 98 | </item> | 98 | </item> |
| 99 | <item> | 99 | <item> |
| 100 | <property name="text"> | 100 | <property name="text"> |
| 101 | <string notr="true">7x Native (2800x1680)</string> | 101 | <string>7x Native (2800x1680)</string> |
| 102 | </property> | 102 | </property> |
| 103 | </item> | 103 | </item> |
| 104 | <item> | 104 | <item> |
| 105 | <property name="text"> | 105 | <property name="text"> |
| 106 | <string notr="true">8x Native (3200x1920)</string> | 106 | <string>8x Native (3200x1920)</string> |
| 107 | </property> | 107 | </property> |
| 108 | </item> | 108 | </item> |
| 109 | <item> | 109 | <item> |
| 110 | <property name="text"> | 110 | <property name="text"> |
| 111 | <string notr="true">9x Native (3600x2160)</string> | 111 | <string>9x Native (3600x2160)</string> |
| 112 | </property> | 112 | </property> |
| 113 | </item> | 113 | </item> |
| 114 | <item> | 114 | <item> |
| 115 | <property name="text"> | 115 | <property name="text"> |
| 116 | <string notr="true">10x Native (4000x2400)</string> | 116 | <string>10x Native (4000x2400)</string> |
| 117 | </property> | 117 | </property> |
| 118 | </item> | 118 | </item> |
| 119 | </widget> | 119 | </widget> |
diff --git a/src/citra_qt/configuration/configure_system.cpp b/src/citra_qt/configuration/configure_system.cpp index 9b1e6711d..88a067c12 100644 --- a/src/citra_qt/configuration/configure_system.cpp +++ b/src/citra_qt/configuration/configure_system.cpp | |||
| @@ -78,7 +78,8 @@ void ConfigureSystem::ReadSystemSettings() { | |||
| 78 | 78 | ||
| 79 | // set the console id | 79 | // set the console id |
| 80 | u64 console_id = Service::CFG::GetConsoleUniqueId(); | 80 | u64 console_id = Service::CFG::GetConsoleUniqueId(); |
| 81 | ui->label_console_id->setText("Console ID: 0x" + QString::number(console_id, 16).toUpper()); | 81 | ui->label_console_id->setText( |
| 82 | tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); | ||
| 82 | } | 83 | } |
| 83 | 84 | ||
| 84 | void ConfigureSystem::applyConfiguration() { | 85 | void ConfigureSystem::applyConfiguration() { |
diff --git a/src/citra_qt/configuration/configure_web.cpp b/src/citra_qt/configuration/configure_web.cpp index 38ce19c0f..bf8c21ac7 100644 --- a/src/citra_qt/configuration/configure_web.cpp +++ b/src/citra_qt/configuration/configure_web.cpp | |||
| @@ -24,15 +24,15 @@ ConfigureWeb::~ConfigureWeb() {} | |||
| 24 | void ConfigureWeb::setConfiguration() { | 24 | void ConfigureWeb::setConfiguration() { |
| 25 | ui->web_credentials_disclaimer->setWordWrap(true); | 25 | ui->web_credentials_disclaimer->setWordWrap(true); |
| 26 | ui->telemetry_learn_more->setOpenExternalLinks(true); | 26 | ui->telemetry_learn_more->setOpenExternalLinks(true); |
| 27 | ui->telemetry_learn_more->setText("<a " | 27 | ui->telemetry_learn_more->setText(tr("<a " |
| 28 | "href='https://citra-emu.org/entry/" | 28 | "href='https://citra-emu.org/entry/" |
| 29 | "telemetry-and-why-thats-a-good-thing/'>Learn more</a>"); | 29 | "telemetry-and-why-thats-a-good-thing/'>Learn more</a>")); |
| 30 | 30 | ||
| 31 | ui->web_signup_link->setOpenExternalLinks(true); | 31 | ui->web_signup_link->setOpenExternalLinks(true); |
| 32 | ui->web_signup_link->setText("<a href='https://services.citra-emu.org/'>Sign up</a>"); | 32 | ui->web_signup_link->setText(tr("<a href='https://services.citra-emu.org/'>Sign up</a>")); |
| 33 | ui->web_token_info_link->setOpenExternalLinks(true); | 33 | ui->web_token_info_link->setOpenExternalLinks(true); |
| 34 | ui->web_token_info_link->setText( | 34 | ui->web_token_info_link->setText( |
| 35 | "<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>"); | 35 | tr("<a href='https://citra-emu.org/wiki/citra-web-service/'>What is my token?</a>")); |
| 36 | 36 | ||
| 37 | ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); | 37 | ui->toggle_telemetry->setChecked(Settings::values.enable_telemetry); |
| 38 | ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username)); | 38 | ui->edit_username->setText(QString::fromStdString(Settings::values.citra_username)); |
| @@ -40,8 +40,8 @@ void ConfigureWeb::setConfiguration() { | |||
| 40 | // Connect after setting the values, to avoid calling OnLoginChanged now | 40 | // Connect after setting the values, to avoid calling OnLoginChanged now |
| 41 | connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); | 41 | connect(ui->edit_token, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); |
| 42 | connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); | 42 | connect(ui->edit_username, &QLineEdit::textChanged, this, &ConfigureWeb::OnLoginChanged); |
| 43 | ui->label_telemetry_id->setText("Telemetry ID: 0x" + | 43 | ui->label_telemetry_id->setText( |
| 44 | QString::number(Core::GetTelemetryId(), 16).toUpper()); | 44 | tr("Telemetry ID: 0x%1").arg(QString::number(Core::GetTelemetryId(), 16).toUpper())); |
| 45 | user_verified = true; | 45 | user_verified = true; |
| 46 | } | 46 | } |
| 47 | 47 | ||
| @@ -60,8 +60,8 @@ void ConfigureWeb::applyConfiguration() { | |||
| 60 | 60 | ||
| 61 | void ConfigureWeb::RefreshTelemetryID() { | 61 | void ConfigureWeb::RefreshTelemetryID() { |
| 62 | const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; | 62 | const u64 new_telemetry_id{Core::RegenerateTelemetryId()}; |
| 63 | ui->label_telemetry_id->setText("Telemetry ID: 0x" + | 63 | ui->label_telemetry_id->setText( |
| 64 | QString::number(new_telemetry_id, 16).toUpper()); | 64 | tr("Telemetry ID: 0x%1").arg(QString::number(new_telemetry_id, 16).toUpper())); |
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | void ConfigureWeb::OnLoginChanged() { | 67 | void ConfigureWeb::OnLoginChanged() { |
diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp index 7d06ec28a..ce2b9fa50 100644 --- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp | |||
| @@ -26,8 +26,8 @@ | |||
| 26 | namespace { | 26 | namespace { |
| 27 | QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) { | 27 | QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) { |
| 28 | QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); | 28 | QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); |
| 29 | for (int y = 0; y < info.height; ++y) { | 29 | for (u32 y = 0; y < info.height; ++y) { |
| 30 | for (int x = 0; x < info.width; ++x) { | 30 | for (u32 x = 0; x < info.width; ++x) { |
| 31 | Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true); | 31 | Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true); |
| 32 | decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); | 32 | decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); |
| 33 | } | 33 | } |
diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp index 47d9924e1..c974545ef 100644 --- a/src/citra_qt/debugger/graphics/graphics_surface.cpp +++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp | |||
| @@ -273,7 +273,8 @@ void GraphicsSurfaceWidget::Pick(int x, int y) { | |||
| 273 | surface_picker_x_control->setValue(x); | 273 | surface_picker_x_control->setValue(x); |
| 274 | surface_picker_y_control->setValue(y); | 274 | surface_picker_y_control->setValue(y); |
| 275 | 275 | ||
| 276 | if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) { | 276 | if (x < 0 || x >= static_cast<int>(surface_width) || y < 0 || |
| 277 | y >= static_cast<int>(surface_height)) { | ||
| 277 | surface_info_label->setText(tr("Pixel out of bounds")); | 278 | surface_info_label->setText(tr("Pixel out of bounds")); |
| 278 | surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); | 279 | surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); |
| 279 | return; | 280 | return; |
diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index bad311793..6959915fa 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp | |||
| @@ -117,7 +117,7 @@ std::string StringFromFormat(const char* format, ...) { | |||
| 117 | } | 117 | } |
| 118 | 118 | ||
| 119 | // For Debugging. Read out an u8 array. | 119 | // For Debugging. Read out an u8 array. |
| 120 | std::string ArrayToString(const u8* data, u32 size, int line_len, bool spaces) { | 120 | std::string ArrayToString(const u8* data, size_t size, int line_len, bool spaces) { |
| 121 | std::ostringstream oss; | 121 | std::ostringstream oss; |
| 122 | oss << std::setfill('0') << std::hex; | 122 | oss << std::setfill('0') << std::hex; |
| 123 | 123 | ||
diff --git a/src/common/string_util.h b/src/common/string_util.h index 075bf4ecb..259360aec 100644 --- a/src/common/string_util.h +++ b/src/common/string_util.h | |||
| @@ -33,7 +33,7 @@ inline void CharArrayFromFormat(char (&out)[Count], const char* format, ...) { | |||
| 33 | } | 33 | } |
| 34 | 34 | ||
| 35 | // Good | 35 | // Good |
| 36 | std::string ArrayToString(const u8* data, u32 size, int line_len = 20, bool spaces = true); | 36 | std::string ArrayToString(const u8* data, size_t size, int line_len = 20, bool spaces = true); |
| 37 | 37 | ||
| 38 | std::string StripSpaces(const std::string& s); | 38 | std::string StripSpaces(const std::string& s); |
| 39 | std::string StripQuotes(const std::string& s); | 39 | std::string StripQuotes(const std::string& s); |
diff --git a/src/common/vector_math.h b/src/common/vector_math.h index 6e2a5ad60..3f0057d9e 100644 --- a/src/common/vector_math.h +++ b/src/common/vector_math.h | |||
| @@ -31,6 +31,7 @@ | |||
| 31 | #pragma once | 31 | #pragma once |
| 32 | 32 | ||
| 33 | #include <cmath> | 33 | #include <cmath> |
| 34 | #include <type_traits> | ||
| 34 | 35 | ||
| 35 | namespace Math { | 36 | namespace Math { |
| 36 | 37 | ||
| @@ -90,7 +91,8 @@ public: | |||
| 90 | y -= other.y; | 91 | y -= other.y; |
| 91 | } | 92 | } |
| 92 | 93 | ||
| 93 | Vec2<decltype(-T{})> operator-() const { | 94 | template <typename U = T> |
| 95 | Vec2<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { | ||
| 94 | return MakeVec(-x, -y); | 96 | return MakeVec(-x, -y); |
| 95 | } | 97 | } |
| 96 | Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { | 98 | Vec2<decltype(T{} * T{})> operator*(const Vec2& other) const { |
| @@ -102,8 +104,7 @@ public: | |||
| 102 | } | 104 | } |
| 103 | template <typename V> | 105 | template <typename V> |
| 104 | void operator*=(const V& f) { | 106 | void operator*=(const V& f) { |
| 105 | x *= f; | 107 | *this = *this * f; |
| 106 | y *= f; | ||
| 107 | } | 108 | } |
| 108 | template <typename V> | 109 | template <typename V> |
| 109 | Vec2<decltype(T{} / V{})> operator/(const V& f) const { | 110 | Vec2<decltype(T{} / V{})> operator/(const V& f) const { |
| @@ -247,7 +248,8 @@ public: | |||
| 247 | z -= other.z; | 248 | z -= other.z; |
| 248 | } | 249 | } |
| 249 | 250 | ||
| 250 | Vec3<decltype(-T{})> operator-() const { | 251 | template <typename U = T> |
| 252 | Vec3<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { | ||
| 251 | return MakeVec(-x, -y, -z); | 253 | return MakeVec(-x, -y, -z); |
| 252 | } | 254 | } |
| 253 | Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { | 255 | Vec3<decltype(T{} * T{})> operator*(const Vec3& other) const { |
| @@ -259,9 +261,7 @@ public: | |||
| 259 | } | 261 | } |
| 260 | template <typename V> | 262 | template <typename V> |
| 261 | void operator*=(const V& f) { | 263 | void operator*=(const V& f) { |
| 262 | x *= f; | 264 | *this = *this * f; |
| 263 | y *= f; | ||
| 264 | z *= f; | ||
| 265 | } | 265 | } |
| 266 | template <typename V> | 266 | template <typename V> |
| 267 | Vec3<decltype(T{} / V{})> operator/(const V& f) const { | 267 | Vec3<decltype(T{} / V{})> operator/(const V& f) const { |
| @@ -462,7 +462,8 @@ public: | |||
| 462 | w -= other.w; | 462 | w -= other.w; |
| 463 | } | 463 | } |
| 464 | 464 | ||
| 465 | Vec4<decltype(-T{})> operator-() const { | 465 | template <typename U = T> |
| 466 | Vec4<std::enable_if_t<std::is_signed<U>::value, U>> operator-() const { | ||
| 466 | return MakeVec(-x, -y, -z, -w); | 467 | return MakeVec(-x, -y, -z, -w); |
| 467 | } | 468 | } |
| 468 | Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { | 469 | Vec4<decltype(T{} * T{})> operator*(const Vec4& other) const { |
| @@ -474,10 +475,7 @@ public: | |||
| 474 | } | 475 | } |
| 475 | template <typename V> | 476 | template <typename V> |
| 476 | void operator*=(const V& f) { | 477 | void operator*=(const V& f) { |
| 477 | x *= f; | 478 | *this = *this * f; |
| 478 | y *= f; | ||
| 479 | z *= f; | ||
| 480 | w *= f; | ||
| 481 | } | 479 | } |
| 482 | template <typename V> | 480 | template <typename V> |
| 483 | Vec4<decltype(T{} / V{})> operator/(const V& f) const { | 481 | Vec4<decltype(T{} / V{})> operator/(const V& f) const { |
| @@ -720,4 +718,4 @@ static inline Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) { | |||
| 720 | return MakeVec(x, yzw[0], yzw[1], yzw[2]); | 718 | return MakeVec(x, yzw[0], yzw[1], yzw[2]); |
| 721 | } | 719 | } |
| 722 | 720 | ||
| 723 | } // namespace | 721 | } // namespace Math |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index cd1a8de2d..3ed619991 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -26,6 +26,7 @@ set(SRCS | |||
| 26 | file_sys/archive_systemsavedata.cpp | 26 | file_sys/archive_systemsavedata.cpp |
| 27 | file_sys/disk_archive.cpp | 27 | file_sys/disk_archive.cpp |
| 28 | file_sys/ivfc_archive.cpp | 28 | file_sys/ivfc_archive.cpp |
| 29 | file_sys/ncch_container.cpp | ||
| 29 | file_sys/path_parser.cpp | 30 | file_sys/path_parser.cpp |
| 30 | file_sys/savedata_archive.cpp | 31 | file_sys/savedata_archive.cpp |
| 31 | frontend/camera/blank_camera.cpp | 32 | frontend/camera/blank_camera.cpp |
diff --git a/src/core/arm/arm_interface.h b/src/core/arm/arm_interface.h index ccd43f431..ba528403c 100644 --- a/src/core/arm/arm_interface.h +++ b/src/core/arm/arm_interface.h | |||
| @@ -41,6 +41,9 @@ public: | |||
| 41 | /// Clear all instruction cache | 41 | /// Clear all instruction cache |
| 42 | virtual void ClearInstructionCache() = 0; | 42 | virtual void ClearInstructionCache() = 0; |
| 43 | 43 | ||
| 44 | /// Notify CPU emulation that page tables have changed | ||
| 45 | virtual void PageTableChanged() = 0; | ||
| 46 | |||
| 44 | /** | 47 | /** |
| 45 | * Set the Program Counter to an address | 48 | * Set the Program Counter to an address |
| 46 | * @param addr Address to set PC to | 49 | * @param addr Address to set PC to |
| @@ -122,12 +125,6 @@ public: | |||
| 122 | virtual void SetCP15Register(CP15Register reg, u32 value) = 0; | 125 | virtual void SetCP15Register(CP15Register reg, u32 value) = 0; |
| 123 | 126 | ||
| 124 | /** | 127 | /** |
| 125 | * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time) | ||
| 126 | * @param ticks Number of ticks to advance the CPU core | ||
| 127 | */ | ||
| 128 | virtual void AddTicks(u64 ticks) = 0; | ||
| 129 | |||
| 130 | /** | ||
| 131 | * Saves the current CPU context | 128 | * Saves the current CPU context |
| 132 | * @param ctx Thread context to save | 129 | * @param ctx Thread context to save |
| 133 | */ | 130 | */ |
| @@ -147,9 +144,6 @@ public: | |||
| 147 | return num_instructions; | 144 | return num_instructions; |
| 148 | } | 145 | } |
| 149 | 146 | ||
| 150 | s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event, | ||
| 151 | /// decreased by the cpu run loop | ||
| 152 | |||
| 153 | protected: | 147 | protected: |
| 154 | /** | 148 | /** |
| 155 | * Executes the given number of instructions | 149 | * Executes the given number of instructions |
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 34c5aa381..2cb56d12f 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp | |||
| @@ -41,7 +41,7 @@ static bool IsReadOnlyMemory(u32 vaddr) { | |||
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | static Dynarmic::UserCallbacks GetUserCallbacks( | 43 | static Dynarmic::UserCallbacks GetUserCallbacks( |
| 44 | const std::shared_ptr<ARMul_State>& interpeter_state) { | 44 | const std::shared_ptr<ARMul_State>& interpeter_state, Memory::PageTable* current_page_table) { |
| 45 | Dynarmic::UserCallbacks user_callbacks{}; | 45 | Dynarmic::UserCallbacks user_callbacks{}; |
| 46 | user_callbacks.InterpreterFallback = &InterpreterFallback; | 46 | user_callbacks.InterpreterFallback = &InterpreterFallback; |
| 47 | user_callbacks.user_arg = static_cast<void*>(interpeter_state.get()); | 47 | user_callbacks.user_arg = static_cast<void*>(interpeter_state.get()); |
| @@ -56,16 +56,14 @@ static Dynarmic::UserCallbacks GetUserCallbacks( | |||
| 56 | user_callbacks.memory.Write16 = &Memory::Write16; | 56 | user_callbacks.memory.Write16 = &Memory::Write16; |
| 57 | user_callbacks.memory.Write32 = &Memory::Write32; | 57 | user_callbacks.memory.Write32 = &Memory::Write32; |
| 58 | user_callbacks.memory.Write64 = &Memory::Write64; | 58 | user_callbacks.memory.Write64 = &Memory::Write64; |
| 59 | // TODO(Subv): Re-add the page table pointers once dynarmic supports switching page tables at | 59 | user_callbacks.page_table = ¤t_page_table->pointers; |
| 60 | // runtime. | ||
| 61 | user_callbacks.page_table = nullptr; | ||
| 62 | user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); | 60 | user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); |
| 63 | return user_callbacks; | 61 | return user_callbacks; |
| 64 | } | 62 | } |
| 65 | 63 | ||
| 66 | ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) { | 64 | ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) { |
| 67 | interpreter_state = std::make_shared<ARMul_State>(initial_mode); | 65 | interpreter_state = std::make_shared<ARMul_State>(initial_mode); |
| 68 | jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state)); | 66 | PageTableChanged(); |
| 69 | } | 67 | } |
| 70 | 68 | ||
| 71 | void ARM_Dynarmic::SetPC(u32 pc) { | 69 | void ARM_Dynarmic::SetPC(u32 pc) { |
| @@ -126,21 +124,15 @@ void ARM_Dynarmic::SetCP15Register(CP15Register reg, u32 value) { | |||
| 126 | interpreter_state->CP15[reg] = value; | 124 | interpreter_state->CP15[reg] = value; |
| 127 | } | 125 | } |
| 128 | 126 | ||
| 129 | void ARM_Dynarmic::AddTicks(u64 ticks) { | ||
| 130 | down_count -= ticks; | ||
| 131 | if (down_count < 0) { | ||
| 132 | CoreTiming::Advance(); | ||
| 133 | } | ||
| 134 | } | ||
| 135 | |||
| 136 | MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); | 127 | MICROPROFILE_DEFINE(ARM_Jit, "ARM JIT", "ARM JIT", MP_RGB(255, 64, 64)); |
| 137 | 128 | ||
| 138 | void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { | 129 | void ARM_Dynarmic::ExecuteInstructions(int num_instructions) { |
| 130 | ASSERT(Memory::GetCurrentPageTable() == current_page_table); | ||
| 139 | MICROPROFILE_SCOPE(ARM_Jit); | 131 | MICROPROFILE_SCOPE(ARM_Jit); |
| 140 | 132 | ||
| 141 | std::size_t ticks_executed = jit->Run(static_cast<unsigned>(num_instructions)); | 133 | std::size_t ticks_executed = jit->Run(static_cast<unsigned>(num_instructions)); |
| 142 | 134 | ||
| 143 | AddTicks(ticks_executed); | 135 | CoreTiming::AddTicks(ticks_executed); |
| 144 | } | 136 | } |
| 145 | 137 | ||
| 146 | void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) { | 138 | void ARM_Dynarmic::SaveContext(ARM_Interface::ThreadContext& ctx) { |
| @@ -178,3 +170,16 @@ void ARM_Dynarmic::PrepareReschedule() { | |||
| 178 | void ARM_Dynarmic::ClearInstructionCache() { | 170 | void ARM_Dynarmic::ClearInstructionCache() { |
| 179 | jit->ClearCache(); | 171 | jit->ClearCache(); |
| 180 | } | 172 | } |
| 173 | |||
| 174 | void ARM_Dynarmic::PageTableChanged() { | ||
| 175 | current_page_table = Memory::GetCurrentPageTable(); | ||
| 176 | |||
| 177 | auto iter = jits.find(current_page_table); | ||
| 178 | if (iter != jits.end()) { | ||
| 179 | jit = iter->second.get(); | ||
| 180 | return; | ||
| 181 | } | ||
| 182 | |||
| 183 | jit = new Dynarmic::Jit(GetUserCallbacks(interpreter_state, current_page_table)); | ||
| 184 | jits.emplace(current_page_table, std::unique_ptr<Dynarmic::Jit>(jit)); | ||
| 185 | } | ||
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h index 834dc989e..0b00158a5 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.h +++ b/src/core/arm/dynarmic/arm_dynarmic.h | |||
| @@ -4,12 +4,17 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <map> | ||
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <dynarmic/dynarmic.h> | 9 | #include <dynarmic/dynarmic.h> |
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 10 | #include "core/arm/arm_interface.h" | 11 | #include "core/arm/arm_interface.h" |
| 11 | #include "core/arm/skyeye_common/armstate.h" | 12 | #include "core/arm/skyeye_common/armstate.h" |
| 12 | 13 | ||
| 14 | namespace Memory { | ||
| 15 | struct PageTable; | ||
| 16 | } // namespace Memory | ||
| 17 | |||
| 13 | class ARM_Dynarmic final : public ARM_Interface { | 18 | class ARM_Dynarmic final : public ARM_Interface { |
| 14 | public: | 19 | public: |
| 15 | ARM_Dynarmic(PrivilegeMode initial_mode); | 20 | ARM_Dynarmic(PrivilegeMode initial_mode); |
| @@ -27,8 +32,6 @@ public: | |||
| 27 | u32 GetCP15Register(CP15Register reg) override; | 32 | u32 GetCP15Register(CP15Register reg) override; |
| 28 | void SetCP15Register(CP15Register reg, u32 value) override; | 33 | void SetCP15Register(CP15Register reg, u32 value) override; |
| 29 | 34 | ||
| 30 | void AddTicks(u64 ticks) override; | ||
| 31 | |||
| 32 | void SaveContext(ThreadContext& ctx) override; | 35 | void SaveContext(ThreadContext& ctx) override; |
| 33 | void LoadContext(const ThreadContext& ctx) override; | 36 | void LoadContext(const ThreadContext& ctx) override; |
| 34 | 37 | ||
| @@ -36,8 +39,11 @@ public: | |||
| 36 | void ExecuteInstructions(int num_instructions) override; | 39 | void ExecuteInstructions(int num_instructions) override; |
| 37 | 40 | ||
| 38 | void ClearInstructionCache() override; | 41 | void ClearInstructionCache() override; |
| 42 | void PageTableChanged() override; | ||
| 39 | 43 | ||
| 40 | private: | 44 | private: |
| 41 | std::unique_ptr<Dynarmic::Jit> jit; | 45 | Dynarmic::Jit* jit = nullptr; |
| 46 | Memory::PageTable* current_page_table = nullptr; | ||
| 47 | std::map<Memory::PageTable*, std::unique_ptr<Dynarmic::Jit>> jits; | ||
| 42 | std::shared_ptr<ARMul_State> interpreter_state; | 48 | std::shared_ptr<ARMul_State> interpreter_state; |
| 43 | }; | 49 | }; |
diff --git a/src/core/arm/dyncom/arm_dyncom.cpp b/src/core/arm/dyncom/arm_dyncom.cpp index 81f9bf99e..4d72aef77 100644 --- a/src/core/arm/dyncom/arm_dyncom.cpp +++ b/src/core/arm/dyncom/arm_dyncom.cpp | |||
| @@ -25,6 +25,10 @@ void ARM_DynCom::ClearInstructionCache() { | |||
| 25 | trans_cache_buf_top = 0; | 25 | trans_cache_buf_top = 0; |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | void ARM_DynCom::PageTableChanged() { | ||
| 29 | ClearInstructionCache(); | ||
| 30 | } | ||
| 31 | |||
| 28 | void ARM_DynCom::SetPC(u32 pc) { | 32 | void ARM_DynCom::SetPC(u32 pc) { |
| 29 | state->Reg[15] = pc; | 33 | state->Reg[15] = pc; |
| 30 | } | 34 | } |
| @@ -73,12 +77,6 @@ void ARM_DynCom::SetCP15Register(CP15Register reg, u32 value) { | |||
| 73 | state->CP15[reg] = value; | 77 | state->CP15[reg] = value; |
| 74 | } | 78 | } |
| 75 | 79 | ||
| 76 | void ARM_DynCom::AddTicks(u64 ticks) { | ||
| 77 | down_count -= ticks; | ||
| 78 | if (down_count < 0) | ||
| 79 | CoreTiming::Advance(); | ||
| 80 | } | ||
| 81 | |||
| 82 | void ARM_DynCom::ExecuteInstructions(int num_instructions) { | 80 | void ARM_DynCom::ExecuteInstructions(int num_instructions) { |
| 83 | state->NumInstrsToExecute = num_instructions; | 81 | state->NumInstrsToExecute = num_instructions; |
| 84 | 82 | ||
| @@ -86,7 +84,7 @@ void ARM_DynCom::ExecuteInstructions(int num_instructions) { | |||
| 86 | // executing one instruction at a time. Otherwise, if a block is being executed, more | 84 | // executing one instruction at a time. Otherwise, if a block is being executed, more |
| 87 | // instructions may actually be executed than specified. | 85 | // instructions may actually be executed than specified. |
| 88 | unsigned ticks_executed = InterpreterMainLoop(state.get()); | 86 | unsigned ticks_executed = InterpreterMainLoop(state.get()); |
| 89 | AddTicks(ticks_executed); | 87 | CoreTiming::AddTicks(ticks_executed); |
| 90 | } | 88 | } |
| 91 | 89 | ||
| 92 | void ARM_DynCom::SaveContext(ThreadContext& ctx) { | 90 | void ARM_DynCom::SaveContext(ThreadContext& ctx) { |
diff --git a/src/core/arm/dyncom/arm_dyncom.h b/src/core/arm/dyncom/arm_dyncom.h index 62c174f3c..fc1ffed6a 100644 --- a/src/core/arm/dyncom/arm_dyncom.h +++ b/src/core/arm/dyncom/arm_dyncom.h | |||
| @@ -16,6 +16,7 @@ public: | |||
| 16 | ~ARM_DynCom(); | 16 | ~ARM_DynCom(); |
| 17 | 17 | ||
| 18 | void ClearInstructionCache() override; | 18 | void ClearInstructionCache() override; |
| 19 | void PageTableChanged() override; | ||
| 19 | 20 | ||
| 20 | void SetPC(u32 pc) override; | 21 | void SetPC(u32 pc) override; |
| 21 | u32 GetPC() const override; | 22 | u32 GetPC() const override; |
| @@ -30,8 +31,6 @@ public: | |||
| 30 | u32 GetCP15Register(CP15Register reg) override; | 31 | u32 GetCP15Register(CP15Register reg) override; |
| 31 | void SetCP15Register(CP15Register reg, u32 value) override; | 32 | void SetCP15Register(CP15Register reg, u32 value) override; |
| 32 | 33 | ||
| 33 | void AddTicks(u64 ticks) override; | ||
| 34 | |||
| 35 | void SaveContext(ThreadContext& ctx) override; | 34 | void SaveContext(ThreadContext& ctx) override; |
| 36 | void LoadContext(const ThreadContext& ctx) override; | 35 | void LoadContext(const ThreadContext& ctx) override; |
| 37 | 36 | ||
diff --git a/src/core/core.cpp b/src/core/core.cpp index 59b8768e7..0c7a72987 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "core/core_timing.h" | 13 | #include "core/core_timing.h" |
| 14 | #include "core/gdbstub/gdbstub.h" | 14 | #include "core/gdbstub/gdbstub.h" |
| 15 | #include "core/hle/kernel/kernel.h" | 15 | #include "core/hle/kernel/kernel.h" |
| 16 | #include "core/hle/kernel/process.h" | ||
| 16 | #include "core/hle/kernel/thread.h" | 17 | #include "core/hle/kernel/thread.h" |
| 17 | #include "core/hle/service/service.h" | 18 | #include "core/hle/service/service.h" |
| 18 | #include "core/hw/hw.h" | 19 | #include "core/hw/hw.h" |
| @@ -100,7 +101,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file | |||
| 100 | return init_result; | 101 | return init_result; |
| 101 | } | 102 | } |
| 102 | 103 | ||
| 103 | const Loader::ResultStatus load_result{app_loader->Load()}; | 104 | const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)}; |
| 104 | if (Loader::ResultStatus::Success != load_result) { | 105 | if (Loader::ResultStatus::Success != load_result) { |
| 105 | LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result); | 106 | LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result); |
| 106 | System::Shutdown(); | 107 | System::Shutdown(); |
| @@ -114,6 +115,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file | |||
| 114 | return ResultStatus::ErrorLoader; | 115 | return ResultStatus::ErrorLoader; |
| 115 | } | 116 | } |
| 116 | } | 117 | } |
| 118 | Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); | ||
| 117 | status = ResultStatus::Success; | 119 | status = ResultStatus::Success; |
| 118 | return status; | 120 | return status; |
| 119 | } | 121 | } |
| @@ -196,4 +198,4 @@ void System::Shutdown() { | |||
| 196 | LOG_DEBUG(Core, "Shutdown OK"); | 198 | LOG_DEBUG(Core, "Shutdown OK"); |
| 197 | } | 199 | } |
| 198 | 200 | ||
| 199 | } // namespace | 201 | } // namespace Core |
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 276ecfdf6..5e2a5d00f 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp | |||
| @@ -57,6 +57,9 @@ static s64 idled_cycles; | |||
| 57 | static s64 last_global_time_ticks; | 57 | static s64 last_global_time_ticks; |
| 58 | static s64 last_global_time_us; | 58 | static s64 last_global_time_us; |
| 59 | 59 | ||
| 60 | static s64 down_count = 0; ///< A decreasing counter of remaining cycles before the next event, | ||
| 61 | /// decreased by the cpu run loop | ||
| 62 | |||
| 60 | static std::recursive_mutex external_event_section; | 63 | static std::recursive_mutex external_event_section; |
| 61 | 64 | ||
| 62 | // Warning: not included in save state. | 65 | // Warning: not included in save state. |
| @@ -146,7 +149,7 @@ void UnregisterAllEvents() { | |||
| 146 | } | 149 | } |
| 147 | 150 | ||
| 148 | void Init() { | 151 | void Init() { |
| 149 | Core::CPU().down_count = INITIAL_SLICE_LENGTH; | 152 | down_count = INITIAL_SLICE_LENGTH; |
| 150 | g_slice_length = INITIAL_SLICE_LENGTH; | 153 | g_slice_length = INITIAL_SLICE_LENGTH; |
| 151 | global_timer = 0; | 154 | global_timer = 0; |
| 152 | idled_cycles = 0; | 155 | idled_cycles = 0; |
| @@ -185,8 +188,15 @@ void Shutdown() { | |||
| 185 | } | 188 | } |
| 186 | } | 189 | } |
| 187 | 190 | ||
| 191 | void AddTicks(u64 ticks) { | ||
| 192 | down_count -= ticks; | ||
| 193 | if (down_count < 0) { | ||
| 194 | Advance(); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 188 | u64 GetTicks() { | 198 | u64 GetTicks() { |
| 189 | return (u64)global_timer + g_slice_length - Core::CPU().down_count; | 199 | return (u64)global_timer + g_slice_length - down_count; |
| 190 | } | 200 | } |
| 191 | 201 | ||
| 192 | u64 GetIdleTicks() { | 202 | u64 GetIdleTicks() { |
| @@ -460,18 +470,18 @@ void MoveEvents() { | |||
| 460 | } | 470 | } |
| 461 | 471 | ||
| 462 | void ForceCheck() { | 472 | void ForceCheck() { |
| 463 | s64 cycles_executed = g_slice_length - Core::CPU().down_count; | 473 | s64 cycles_executed = g_slice_length - down_count; |
| 464 | global_timer += cycles_executed; | 474 | global_timer += cycles_executed; |
| 465 | // This will cause us to check for new events immediately. | 475 | // This will cause us to check for new events immediately. |
| 466 | Core::CPU().down_count = 0; | 476 | down_count = 0; |
| 467 | // But let's not eat a bunch more time in Advance() because of this. | 477 | // But let's not eat a bunch more time in Advance() because of this. |
| 468 | g_slice_length = 0; | 478 | g_slice_length = 0; |
| 469 | } | 479 | } |
| 470 | 480 | ||
| 471 | void Advance() { | 481 | void Advance() { |
| 472 | s64 cycles_executed = g_slice_length - Core::CPU().down_count; | 482 | s64 cycles_executed = g_slice_length - down_count; |
| 473 | global_timer += cycles_executed; | 483 | global_timer += cycles_executed; |
| 474 | Core::CPU().down_count = g_slice_length; | 484 | down_count = g_slice_length; |
| 475 | 485 | ||
| 476 | if (has_ts_events) | 486 | if (has_ts_events) |
| 477 | MoveEvents(); | 487 | MoveEvents(); |
| @@ -480,7 +490,7 @@ void Advance() { | |||
| 480 | if (!first) { | 490 | if (!first) { |
| 481 | if (g_slice_length < 10000) { | 491 | if (g_slice_length < 10000) { |
| 482 | g_slice_length += 10000; | 492 | g_slice_length += 10000; |
| 483 | Core::CPU().down_count += g_slice_length; | 493 | down_count += g_slice_length; |
| 484 | } | 494 | } |
| 485 | } else { | 495 | } else { |
| 486 | // Note that events can eat cycles as well. | 496 | // Note that events can eat cycles as well. |
| @@ -490,7 +500,7 @@ void Advance() { | |||
| 490 | 500 | ||
| 491 | const int diff = target - g_slice_length; | 501 | const int diff = target - g_slice_length; |
| 492 | g_slice_length += diff; | 502 | g_slice_length += diff; |
| 493 | Core::CPU().down_count += diff; | 503 | down_count += diff; |
| 494 | } | 504 | } |
| 495 | if (advance_callback) | 505 | if (advance_callback) |
| 496 | advance_callback(static_cast<int>(cycles_executed)); | 506 | advance_callback(static_cast<int>(cycles_executed)); |
| @@ -506,12 +516,12 @@ void LogPendingEvents() { | |||
| 506 | } | 516 | } |
| 507 | 517 | ||
| 508 | void Idle(int max_idle) { | 518 | void Idle(int max_idle) { |
| 509 | s64 cycles_down = Core::CPU().down_count; | 519 | s64 cycles_down = down_count; |
| 510 | if (max_idle != 0 && cycles_down > max_idle) | 520 | if (max_idle != 0 && cycles_down > max_idle) |
| 511 | cycles_down = max_idle; | 521 | cycles_down = max_idle; |
| 512 | 522 | ||
| 513 | if (first && cycles_down > 0) { | 523 | if (first && cycles_down > 0) { |
| 514 | s64 cycles_executed = g_slice_length - Core::CPU().down_count; | 524 | s64 cycles_executed = g_slice_length - down_count; |
| 515 | s64 cycles_next_event = first->time - global_timer; | 525 | s64 cycles_next_event = first->time - global_timer; |
| 516 | 526 | ||
| 517 | if (cycles_next_event < cycles_executed + cycles_down) { | 527 | if (cycles_next_event < cycles_executed + cycles_down) { |
| @@ -526,9 +536,9 @@ void Idle(int max_idle) { | |||
| 526 | cycles_down / (float)(g_clock_rate_arm11 * 0.001f)); | 536 | cycles_down / (float)(g_clock_rate_arm11 * 0.001f)); |
| 527 | 537 | ||
| 528 | idled_cycles += cycles_down; | 538 | idled_cycles += cycles_down; |
| 529 | Core::CPU().down_count -= cycles_down; | 539 | down_count -= cycles_down; |
| 530 | if (Core::CPU().down_count == 0) | 540 | if (down_count == 0) |
| 531 | Core::CPU().down_count = -1; | 541 | down_count = -1; |
| 532 | } | 542 | } |
| 533 | 543 | ||
| 534 | std::string GetScheduledEventsSummary() { | 544 | std::string GetScheduledEventsSummary() { |
diff --git a/src/core/core_timing.h b/src/core/core_timing.h index d2f85cd4d..897350801 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h | |||
| @@ -67,6 +67,12 @@ void Shutdown(); | |||
| 67 | typedef void (*MHzChangeCallback)(); | 67 | typedef void (*MHzChangeCallback)(); |
| 68 | typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback; | 68 | typedef std::function<void(u64 userdata, int cycles_late)> TimedCallback; |
| 69 | 69 | ||
| 70 | /** | ||
| 71 | * Advance the CPU core by the specified number of ticks (e.g. to simulate CPU execution time) | ||
| 72 | * @param ticks Number of ticks to advance the CPU core | ||
| 73 | */ | ||
| 74 | void AddTicks(u64 ticks); | ||
| 75 | |||
| 70 | u64 GetTicks(); | 76 | u64 GetTicks(); |
| 71 | u64 GetIdleTicks(); | 77 | u64 GetIdleTicks(); |
| 72 | u64 GetGlobalTimeUs(); | 78 | u64 GetGlobalTimeUs(); |
diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp index 298a37a44..a16941c70 100644 --- a/src/core/file_sys/archive_selfncch.cpp +++ b/src/core/file_sys/archive_selfncch.cpp | |||
| @@ -3,12 +3,14 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cinttypes> | ||
| 6 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 7 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 8 | #include "common/swap.h" | 9 | #include "common/swap.h" |
| 9 | #include "core/file_sys/archive_selfncch.h" | 10 | #include "core/file_sys/archive_selfncch.h" |
| 10 | #include "core/file_sys/errors.h" | 11 | #include "core/file_sys/errors.h" |
| 11 | #include "core/file_sys/ivfc_archive.h" | 12 | #include "core/file_sys/ivfc_archive.h" |
| 13 | #include "core/hle/kernel/process.h" | ||
| 12 | 14 | ||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 15 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 14 | // FileSys namespace | 16 | // FileSys namespace |
| @@ -102,8 +104,7 @@ public: | |||
| 102 | 104 | ||
| 103 | switch (static_cast<SelfNCCHFilePathType>(file_path.type)) { | 105 | switch (static_cast<SelfNCCHFilePathType>(file_path.type)) { |
| 104 | case SelfNCCHFilePathType::UpdateRomFS: | 106 | case SelfNCCHFilePathType::UpdateRomFS: |
| 105 | LOG_WARNING(Service_FS, "(STUBBED) open update RomFS"); | 107 | return OpenUpdateRomFS(); |
| 106 | return OpenRomFS(); | ||
| 107 | 108 | ||
| 108 | case SelfNCCHFilePathType::RomFS: | 109 | case SelfNCCHFilePathType::RomFS: |
| 109 | return OpenRomFS(); | 110 | return OpenRomFS(); |
| @@ -179,6 +180,17 @@ private: | |||
| 179 | } | 180 | } |
| 180 | } | 181 | } |
| 181 | 182 | ||
| 183 | ResultVal<std::unique_ptr<FileBackend>> OpenUpdateRomFS() const { | ||
| 184 | if (ncch_data.update_romfs_file) { | ||
| 185 | return MakeResult<std::unique_ptr<FileBackend>>(std::make_unique<IVFCFile>( | ||
| 186 | ncch_data.update_romfs_file, ncch_data.update_romfs_offset, | ||
| 187 | ncch_data.update_romfs_size)); | ||
| 188 | } else { | ||
| 189 | LOG_INFO(Service_FS, "Unable to read update RomFS"); | ||
| 190 | return ERROR_ROMFS_NOT_FOUND; | ||
| 191 | } | ||
| 192 | } | ||
| 193 | |||
| 182 | ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const { | 194 | ResultVal<std::unique_ptr<FileBackend>> OpenExeFS(const std::string& filename) const { |
| 183 | if (filename == "icon") { | 195 | if (filename == "icon") { |
| 184 | if (ncch_data.icon) { | 196 | if (ncch_data.icon) { |
| @@ -217,30 +229,57 @@ private: | |||
| 217 | NCCHData ncch_data; | 229 | NCCHData ncch_data; |
| 218 | }; | 230 | }; |
| 219 | 231 | ||
| 220 | ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { | 232 | void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) { |
| 233 | u64 program_id = 0; | ||
| 234 | if (app_loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) { | ||
| 235 | LOG_WARNING( | ||
| 236 | Service_FS, | ||
| 237 | "Could not read program id when registering with SelfNCCH, this might be a 3dsx file"); | ||
| 238 | } | ||
| 239 | |||
| 240 | LOG_DEBUG(Service_FS, "Registering program %016" PRIX64 " with the SelfNCCH archive factory", | ||
| 241 | program_id); | ||
| 242 | |||
| 243 | if (ncch_data.find(program_id) != ncch_data.end()) { | ||
| 244 | LOG_WARNING(Service_FS, "Registering program %016" PRIX64 | ||
| 245 | " with SelfNCCH will override existing mapping", | ||
| 246 | program_id); | ||
| 247 | } | ||
| 248 | |||
| 249 | NCCHData& data = ncch_data[program_id]; | ||
| 250 | |||
| 221 | std::shared_ptr<FileUtil::IOFile> romfs_file_; | 251 | std::shared_ptr<FileUtil::IOFile> romfs_file_; |
| 222 | if (Loader::ResultStatus::Success == | 252 | if (Loader::ResultStatus::Success == |
| 223 | app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) { | 253 | app_loader.ReadRomFS(romfs_file_, data.romfs_offset, data.romfs_size)) { |
| 254 | |||
| 255 | data.romfs_file = std::move(romfs_file_); | ||
| 256 | } | ||
| 257 | |||
| 258 | std::shared_ptr<FileUtil::IOFile> update_romfs_file; | ||
| 259 | if (Loader::ResultStatus::Success == | ||
| 260 | app_loader.ReadUpdateRomFS(update_romfs_file, data.update_romfs_offset, | ||
| 261 | data.update_romfs_size)) { | ||
| 224 | 262 | ||
| 225 | ncch_data.romfs_file = std::move(romfs_file_); | 263 | data.update_romfs_file = std::move(update_romfs_file); |
| 226 | } | 264 | } |
| 227 | 265 | ||
| 228 | std::vector<u8> buffer; | 266 | std::vector<u8> buffer; |
| 229 | 267 | ||
| 230 | if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) | 268 | if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) |
| 231 | ncch_data.icon = std::make_shared<std::vector<u8>>(std::move(buffer)); | 269 | data.icon = std::make_shared<std::vector<u8>>(std::move(buffer)); |
| 232 | 270 | ||
| 233 | buffer.clear(); | 271 | buffer.clear(); |
| 234 | if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) | 272 | if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) |
| 235 | ncch_data.logo = std::make_shared<std::vector<u8>>(std::move(buffer)); | 273 | data.logo = std::make_shared<std::vector<u8>>(std::move(buffer)); |
| 236 | 274 | ||
| 237 | buffer.clear(); | 275 | buffer.clear(); |
| 238 | if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) | 276 | if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) |
| 239 | ncch_data.banner = std::make_shared<std::vector<u8>>(std::move(buffer)); | 277 | data.banner = std::make_shared<std::vector<u8>>(std::move(buffer)); |
| 240 | } | 278 | } |
| 241 | 279 | ||
| 242 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { | 280 | ResultVal<std::unique_ptr<ArchiveBackend>> ArchiveFactory_SelfNCCH::Open(const Path& path) { |
| 243 | auto archive = std::make_unique<SelfNCCHArchive>(ncch_data); | 281 | auto archive = std::make_unique<SelfNCCHArchive>( |
| 282 | ncch_data[Kernel::g_current_process->codeset->program_id]); | ||
| 244 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); | 283 | return MakeResult<std::unique_ptr<ArchiveBackend>>(std::move(archive)); |
| 245 | } | 284 | } |
| 246 | 285 | ||
diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h index f1b971296..0d6d6766e 100644 --- a/src/core/file_sys/archive_selfncch.h +++ b/src/core/file_sys/archive_selfncch.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include <string> | 8 | #include <string> |
| 9 | #include <unordered_map> | ||
| 9 | #include <vector> | 10 | #include <vector> |
| 10 | #include "common/common_types.h" | 11 | #include "common/common_types.h" |
| 11 | #include "core/file_sys/archive_backend.h" | 12 | #include "core/file_sys/archive_backend.h" |
| @@ -24,12 +25,19 @@ struct NCCHData { | |||
| 24 | std::shared_ptr<FileUtil::IOFile> romfs_file; | 25 | std::shared_ptr<FileUtil::IOFile> romfs_file; |
| 25 | u64 romfs_offset = 0; | 26 | u64 romfs_offset = 0; |
| 26 | u64 romfs_size = 0; | 27 | u64 romfs_size = 0; |
| 28 | |||
| 29 | std::shared_ptr<FileUtil::IOFile> update_romfs_file; | ||
| 30 | u64 update_romfs_offset = 0; | ||
| 31 | u64 update_romfs_size = 0; | ||
| 27 | }; | 32 | }; |
| 28 | 33 | ||
| 29 | /// File system interface to the SelfNCCH archive | 34 | /// File system interface to the SelfNCCH archive |
| 30 | class ArchiveFactory_SelfNCCH final : public ArchiveFactory { | 35 | class ArchiveFactory_SelfNCCH final : public ArchiveFactory { |
| 31 | public: | 36 | public: |
| 32 | explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader); | 37 | ArchiveFactory_SelfNCCH() = default; |
| 38 | |||
| 39 | /// Registers a loaded application so that we can open its SelfNCCH archive when requested. | ||
| 40 | void Register(Loader::AppLoader& app_loader); | ||
| 33 | 41 | ||
| 34 | std::string GetName() const override { | 42 | std::string GetName() const override { |
| 35 | return "SelfNCCH"; | 43 | return "SelfNCCH"; |
| @@ -39,7 +47,8 @@ public: | |||
| 39 | ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; | 47 | ResultVal<ArchiveFormatInfo> GetFormatInfo(const Path& path) const override; |
| 40 | 48 | ||
| 41 | private: | 49 | private: |
| 42 | NCCHData ncch_data; | 50 | /// Mapping of ProgramId -> NCCHData |
| 51 | std::unordered_map<u64, NCCHData> ncch_data; | ||
| 43 | }; | 52 | }; |
| 44 | 53 | ||
| 45 | } // namespace FileSys | 54 | } // namespace FileSys |
diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp new file mode 100644 index 000000000..59c72f3e9 --- /dev/null +++ b/src/core/file_sys/ncch_container.cpp | |||
| @@ -0,0 +1,316 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <cinttypes> | ||
| 6 | #include <cstring> | ||
| 7 | #include <memory> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "common/logging/log.h" | ||
| 10 | #include "core/core.h" | ||
| 11 | #include "core/file_sys/ncch_container.h" | ||
| 12 | #include "core/loader/loader.h" | ||
| 13 | |||
| 14 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 15 | // FileSys namespace | ||
| 16 | |||
| 17 | namespace FileSys { | ||
| 18 | |||
| 19 | static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs | ||
| 20 | static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Get the decompressed size of an LZSS compressed ExeFS file | ||
| 24 | * @param buffer Buffer of compressed file | ||
| 25 | * @param size Size of compressed buffer | ||
| 26 | * @return Size of decompressed buffer | ||
| 27 | */ | ||
| 28 | static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { | ||
| 29 | u32 offset_size = *(u32*)(buffer + size - 4); | ||
| 30 | return offset_size + size; | ||
| 31 | } | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Decompress ExeFS file (compressed with LZSS) | ||
| 35 | * @param compressed Compressed buffer | ||
| 36 | * @param compressed_size Size of compressed buffer | ||
| 37 | * @param decompressed Decompressed buffer | ||
| 38 | * @param decompressed_size Size of decompressed buffer | ||
| 39 | * @return True on success, otherwise false | ||
| 40 | */ | ||
| 41 | static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, | ||
| 42 | u32 decompressed_size) { | ||
| 43 | const u8* footer = compressed + compressed_size - 8; | ||
| 44 | u32 buffer_top_and_bottom = *reinterpret_cast<const u32*>(footer); | ||
| 45 | u32 out = decompressed_size; | ||
| 46 | u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); | ||
| 47 | u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); | ||
| 48 | |||
| 49 | memset(decompressed, 0, decompressed_size); | ||
| 50 | memcpy(decompressed, compressed, compressed_size); | ||
| 51 | |||
| 52 | while (index > stop_index) { | ||
| 53 | u8 control = compressed[--index]; | ||
| 54 | |||
| 55 | for (unsigned i = 0; i < 8; i++) { | ||
| 56 | if (index <= stop_index) | ||
| 57 | break; | ||
| 58 | if (index <= 0) | ||
| 59 | break; | ||
| 60 | if (out <= 0) | ||
| 61 | break; | ||
| 62 | |||
| 63 | if (control & 0x80) { | ||
| 64 | // Check if compression is out of bounds | ||
| 65 | if (index < 2) | ||
| 66 | return false; | ||
| 67 | index -= 2; | ||
| 68 | |||
| 69 | u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); | ||
| 70 | u32 segment_size = ((segment_offset >> 12) & 15) + 3; | ||
| 71 | segment_offset &= 0x0FFF; | ||
| 72 | segment_offset += 2; | ||
| 73 | |||
| 74 | // Check if compression is out of bounds | ||
| 75 | if (out < segment_size) | ||
| 76 | return false; | ||
| 77 | |||
| 78 | for (unsigned j = 0; j < segment_size; j++) { | ||
| 79 | // Check if compression is out of bounds | ||
| 80 | if (out + segment_offset >= decompressed_size) | ||
| 81 | return false; | ||
| 82 | |||
| 83 | u8 data = decompressed[out + segment_offset]; | ||
| 84 | decompressed[--out] = data; | ||
| 85 | } | ||
| 86 | } else { | ||
| 87 | // Check if compression is out of bounds | ||
| 88 | if (out < 1) | ||
| 89 | return false; | ||
| 90 | decompressed[--out] = compressed[--index]; | ||
| 91 | } | ||
| 92 | control <<= 1; | ||
| 93 | } | ||
| 94 | } | ||
| 95 | return true; | ||
| 96 | } | ||
| 97 | |||
| 98 | NCCHContainer::NCCHContainer(const std::string& filepath) : filepath(filepath) { | ||
| 99 | file = FileUtil::IOFile(filepath, "rb"); | ||
| 100 | } | ||
| 101 | |||
| 102 | Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath) { | ||
| 103 | this->filepath = filepath; | ||
| 104 | file = FileUtil::IOFile(filepath, "rb"); | ||
| 105 | |||
| 106 | if (!file.IsOpen()) { | ||
| 107 | LOG_WARNING(Service_FS, "Failed to open %s", filepath.c_str()); | ||
| 108 | return Loader::ResultStatus::Error; | ||
| 109 | } | ||
| 110 | |||
| 111 | LOG_DEBUG(Service_FS, "Opened %s", filepath.c_str()); | ||
| 112 | return Loader::ResultStatus::Success; | ||
| 113 | } | ||
| 114 | |||
| 115 | Loader::ResultStatus NCCHContainer::Load() { | ||
| 116 | if (is_loaded) | ||
| 117 | return Loader::ResultStatus::Success; | ||
| 118 | |||
| 119 | // Reset read pointer in case this file has been read before. | ||
| 120 | file.Seek(0, SEEK_SET); | ||
| 121 | |||
| 122 | if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) | ||
| 123 | return Loader::ResultStatus::Error; | ||
| 124 | |||
| 125 | // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... | ||
| 126 | if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { | ||
| 127 | LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); | ||
| 128 | ncch_offset = 0x4000; | ||
| 129 | file.Seek(ncch_offset, SEEK_SET); | ||
| 130 | file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); | ||
| 131 | } | ||
| 132 | |||
| 133 | // Verify we are loading the correct file type... | ||
| 134 | if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) | ||
| 135 | return Loader::ResultStatus::ErrorInvalidFormat; | ||
| 136 | |||
| 137 | // System archives and DLC don't have an extended header but have RomFS | ||
| 138 | if (ncch_header.extended_header_size) { | ||
| 139 | if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) | ||
| 140 | return Loader::ResultStatus::Error; | ||
| 141 | |||
| 142 | is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; | ||
| 143 | u32 entry_point = exheader_header.codeset_info.text.address; | ||
| 144 | u32 code_size = exheader_header.codeset_info.text.code_size; | ||
| 145 | u32 stack_size = exheader_header.codeset_info.stack_size; | ||
| 146 | u32 bss_size = exheader_header.codeset_info.bss_size; | ||
| 147 | u32 core_version = exheader_header.arm11_system_local_caps.core_version; | ||
| 148 | u8 priority = exheader_header.arm11_system_local_caps.priority; | ||
| 149 | u8 resource_limit_category = | ||
| 150 | exheader_header.arm11_system_local_caps.resource_limit_category; | ||
| 151 | |||
| 152 | LOG_DEBUG(Service_FS, "Name: %s", exheader_header.codeset_info.name); | ||
| 153 | LOG_DEBUG(Service_FS, "Program ID: %016" PRIX64, ncch_header.program_id); | ||
| 154 | LOG_DEBUG(Service_FS, "Code compressed: %s", is_compressed ? "yes" : "no"); | ||
| 155 | LOG_DEBUG(Service_FS, "Entry point: 0x%08X", entry_point); | ||
| 156 | LOG_DEBUG(Service_FS, "Code size: 0x%08X", code_size); | ||
| 157 | LOG_DEBUG(Service_FS, "Stack size: 0x%08X", stack_size); | ||
| 158 | LOG_DEBUG(Service_FS, "Bss size: 0x%08X", bss_size); | ||
| 159 | LOG_DEBUG(Service_FS, "Core version: %d", core_version); | ||
| 160 | LOG_DEBUG(Service_FS, "Thread priority: 0x%X", priority); | ||
| 161 | LOG_DEBUG(Service_FS, "Resource limit category: %d", resource_limit_category); | ||
| 162 | LOG_DEBUG(Service_FS, "System Mode: %d", | ||
| 163 | static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); | ||
| 164 | |||
| 165 | if (exheader_header.system_info.jump_id != ncch_header.program_id) { | ||
| 166 | LOG_ERROR(Service_FS, "ExHeader Program ID mismatch: the ROM is probably encrypted."); | ||
| 167 | return Loader::ResultStatus::ErrorEncrypted; | ||
| 168 | } | ||
| 169 | |||
| 170 | has_exheader = true; | ||
| 171 | } | ||
| 172 | |||
| 173 | // DLC can have an ExeFS and a RomFS but no extended header | ||
| 174 | if (ncch_header.exefs_size) { | ||
| 175 | exefs_offset = ncch_header.exefs_offset * kBlockSize; | ||
| 176 | u32 exefs_size = ncch_header.exefs_size * kBlockSize; | ||
| 177 | |||
| 178 | LOG_DEBUG(Service_FS, "ExeFS offset: 0x%08X", exefs_offset); | ||
| 179 | LOG_DEBUG(Service_FS, "ExeFS size: 0x%08X", exefs_size); | ||
| 180 | |||
| 181 | file.Seek(exefs_offset + ncch_offset, SEEK_SET); | ||
| 182 | if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) | ||
| 183 | return Loader::ResultStatus::Error; | ||
| 184 | |||
| 185 | has_exefs = true; | ||
| 186 | } | ||
| 187 | |||
| 188 | if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) | ||
| 189 | has_romfs = true; | ||
| 190 | |||
| 191 | is_loaded = true; | ||
| 192 | return Loader::ResultStatus::Success; | ||
| 193 | } | ||
| 194 | |||
| 195 | Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector<u8>& buffer) { | ||
| 196 | if (!file.IsOpen()) | ||
| 197 | return Loader::ResultStatus::Error; | ||
| 198 | |||
| 199 | Loader::ResultStatus result = Load(); | ||
| 200 | if (result != Loader::ResultStatus::Success) | ||
| 201 | return result; | ||
| 202 | |||
| 203 | if (!has_exefs) | ||
| 204 | return Loader::ResultStatus::ErrorNotUsed; | ||
| 205 | |||
| 206 | LOG_DEBUG(Service_FS, "%d sections:", kMaxSections); | ||
| 207 | // Iterate through the ExeFs archive until we find a section with the specified name... | ||
| 208 | for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { | ||
| 209 | const auto& section = exefs_header.section[section_number]; | ||
| 210 | |||
| 211 | // Load the specified section... | ||
| 212 | if (strcmp(section.name, name) == 0) { | ||
| 213 | LOG_DEBUG(Service_FS, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, | ||
| 214 | section.offset, section.size, section.name); | ||
| 215 | |||
| 216 | s64 section_offset = | ||
| 217 | (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); | ||
| 218 | file.Seek(section_offset, SEEK_SET); | ||
| 219 | |||
| 220 | if (strcmp(section.name, ".code") == 0 && is_compressed) { | ||
| 221 | // Section is compressed, read compressed .code section... | ||
| 222 | std::unique_ptr<u8[]> temp_buffer; | ||
| 223 | try { | ||
| 224 | temp_buffer.reset(new u8[section.size]); | ||
| 225 | } catch (std::bad_alloc&) { | ||
| 226 | return Loader::ResultStatus::ErrorMemoryAllocationFailed; | ||
| 227 | } | ||
| 228 | |||
| 229 | if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) | ||
| 230 | return Loader::ResultStatus::Error; | ||
| 231 | |||
| 232 | // Decompress .code section... | ||
| 233 | u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); | ||
| 234 | buffer.resize(decompressed_size); | ||
| 235 | if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) | ||
| 236 | return Loader::ResultStatus::ErrorInvalidFormat; | ||
| 237 | } else { | ||
| 238 | // Section is uncompressed... | ||
| 239 | buffer.resize(section.size); | ||
| 240 | if (file.ReadBytes(&buffer[0], section.size) != section.size) | ||
| 241 | return Loader::ResultStatus::Error; | ||
| 242 | } | ||
| 243 | return Loader::ResultStatus::Success; | ||
| 244 | } | ||
| 245 | } | ||
| 246 | return Loader::ResultStatus::ErrorNotUsed; | ||
| 247 | } | ||
| 248 | |||
| 249 | Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, | ||
| 250 | u64& offset, u64& size) { | ||
| 251 | if (!file.IsOpen()) | ||
| 252 | return Loader::ResultStatus::Error; | ||
| 253 | |||
| 254 | Loader::ResultStatus result = Load(); | ||
| 255 | if (result != Loader::ResultStatus::Success) | ||
| 256 | return result; | ||
| 257 | |||
| 258 | if (!has_romfs) { | ||
| 259 | LOG_DEBUG(Service_FS, "RomFS requested from NCCH which has no RomFS"); | ||
| 260 | return Loader::ResultStatus::ErrorNotUsed; | ||
| 261 | } | ||
| 262 | |||
| 263 | u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; | ||
| 264 | u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; | ||
| 265 | |||
| 266 | LOG_DEBUG(Service_FS, "RomFS offset: 0x%08X", romfs_offset); | ||
| 267 | LOG_DEBUG(Service_FS, "RomFS size: 0x%08X", romfs_size); | ||
| 268 | |||
| 269 | if (file.GetSize() < romfs_offset + romfs_size) | ||
| 270 | return Loader::ResultStatus::Error; | ||
| 271 | |||
| 272 | // We reopen the file, to allow its position to be independent from file's | ||
| 273 | romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); | ||
| 274 | if (!romfs_file->IsOpen()) | ||
| 275 | return Loader::ResultStatus::Error; | ||
| 276 | |||
| 277 | offset = romfs_offset; | ||
| 278 | size = romfs_size; | ||
| 279 | |||
| 280 | return Loader::ResultStatus::Success; | ||
| 281 | } | ||
| 282 | |||
| 283 | Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) { | ||
| 284 | Loader::ResultStatus result = Load(); | ||
| 285 | if (result != Loader::ResultStatus::Success) | ||
| 286 | return result; | ||
| 287 | |||
| 288 | program_id = ncch_header.program_id; | ||
| 289 | return Loader::ResultStatus::Success; | ||
| 290 | } | ||
| 291 | |||
| 292 | bool NCCHContainer::HasExeFS() { | ||
| 293 | Loader::ResultStatus result = Load(); | ||
| 294 | if (result != Loader::ResultStatus::Success) | ||
| 295 | return false; | ||
| 296 | |||
| 297 | return has_exefs; | ||
| 298 | } | ||
| 299 | |||
| 300 | bool NCCHContainer::HasRomFS() { | ||
| 301 | Loader::ResultStatus result = Load(); | ||
| 302 | if (result != Loader::ResultStatus::Success) | ||
| 303 | return false; | ||
| 304 | |||
| 305 | return has_romfs; | ||
| 306 | } | ||
| 307 | |||
| 308 | bool NCCHContainer::HasExHeader() { | ||
| 309 | Loader::ResultStatus result = Load(); | ||
| 310 | if (result != Loader::ResultStatus::Success) | ||
| 311 | return false; | ||
| 312 | |||
| 313 | return has_exheader; | ||
| 314 | } | ||
| 315 | |||
| 316 | } // namespace FileSys | ||
diff --git a/src/core/file_sys/ncch_container.h b/src/core/file_sys/ncch_container.h new file mode 100644 index 000000000..8af9032b4 --- /dev/null +++ b/src/core/file_sys/ncch_container.h | |||
| @@ -0,0 +1,244 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <cstddef> | ||
| 8 | #include <memory> | ||
| 9 | #include <string> | ||
| 10 | #include <vector> | ||
| 11 | #include "common/bit_field.h" | ||
| 12 | #include "common/common_types.h" | ||
| 13 | #include "common/file_util.h" | ||
| 14 | #include "common/swap.h" | ||
| 15 | #include "core/core.h" | ||
| 16 | |||
| 17 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 18 | /// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) | ||
| 19 | |||
| 20 | struct NCCH_Header { | ||
| 21 | u8 signature[0x100]; | ||
| 22 | u32_le magic; | ||
| 23 | u32_le content_size; | ||
| 24 | u8 partition_id[8]; | ||
| 25 | u16_le maker_code; | ||
| 26 | u16_le version; | ||
| 27 | u8 reserved_0[4]; | ||
| 28 | u64_le program_id; | ||
| 29 | u8 reserved_1[0x10]; | ||
| 30 | u8 logo_region_hash[0x20]; | ||
| 31 | u8 product_code[0x10]; | ||
| 32 | u8 extended_header_hash[0x20]; | ||
| 33 | u32_le extended_header_size; | ||
| 34 | u8 reserved_2[4]; | ||
| 35 | u8 flags[8]; | ||
| 36 | u32_le plain_region_offset; | ||
| 37 | u32_le plain_region_size; | ||
| 38 | u32_le logo_region_offset; | ||
| 39 | u32_le logo_region_size; | ||
| 40 | u32_le exefs_offset; | ||
| 41 | u32_le exefs_size; | ||
| 42 | u32_le exefs_hash_region_size; | ||
| 43 | u8 reserved_3[4]; | ||
| 44 | u32_le romfs_offset; | ||
| 45 | u32_le romfs_size; | ||
| 46 | u32_le romfs_hash_region_size; | ||
| 47 | u8 reserved_4[4]; | ||
| 48 | u8 exefs_super_block_hash[0x20]; | ||
| 49 | u8 romfs_super_block_hash[0x20]; | ||
| 50 | }; | ||
| 51 | |||
| 52 | static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); | ||
| 53 | |||
| 54 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 55 | // ExeFS (executable file system) headers | ||
| 56 | |||
| 57 | struct ExeFs_SectionHeader { | ||
| 58 | char name[8]; | ||
| 59 | u32 offset; | ||
| 60 | u32 size; | ||
| 61 | }; | ||
| 62 | |||
| 63 | struct ExeFs_Header { | ||
| 64 | ExeFs_SectionHeader section[8]; | ||
| 65 | u8 reserved[0x80]; | ||
| 66 | u8 hashes[8][0x20]; | ||
| 67 | }; | ||
| 68 | |||
| 69 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 70 | // ExHeader (executable file system header) headers | ||
| 71 | |||
| 72 | struct ExHeader_SystemInfoFlags { | ||
| 73 | u8 reserved[5]; | ||
| 74 | u8 flag; | ||
| 75 | u8 remaster_version[2]; | ||
| 76 | }; | ||
| 77 | |||
| 78 | struct ExHeader_CodeSegmentInfo { | ||
| 79 | u32 address; | ||
| 80 | u32 num_max_pages; | ||
| 81 | u32 code_size; | ||
| 82 | }; | ||
| 83 | |||
| 84 | struct ExHeader_CodeSetInfo { | ||
| 85 | u8 name[8]; | ||
| 86 | ExHeader_SystemInfoFlags flags; | ||
| 87 | ExHeader_CodeSegmentInfo text; | ||
| 88 | u32 stack_size; | ||
| 89 | ExHeader_CodeSegmentInfo ro; | ||
| 90 | u8 reserved[4]; | ||
| 91 | ExHeader_CodeSegmentInfo data; | ||
| 92 | u32 bss_size; | ||
| 93 | }; | ||
| 94 | |||
| 95 | struct ExHeader_DependencyList { | ||
| 96 | u8 program_id[0x30][8]; | ||
| 97 | }; | ||
| 98 | |||
| 99 | struct ExHeader_SystemInfo { | ||
| 100 | u64 save_data_size; | ||
| 101 | u64_le jump_id; | ||
| 102 | u8 reserved_2[0x30]; | ||
| 103 | }; | ||
| 104 | |||
| 105 | struct ExHeader_StorageInfo { | ||
| 106 | u8 ext_save_data_id[8]; | ||
| 107 | u8 system_save_data_id[8]; | ||
| 108 | u8 reserved[8]; | ||
| 109 | u8 access_info[7]; | ||
| 110 | u8 other_attributes; | ||
| 111 | }; | ||
| 112 | |||
| 113 | struct ExHeader_ARM11_SystemLocalCaps { | ||
| 114 | u64_le program_id; | ||
| 115 | u32_le core_version; | ||
| 116 | u8 reserved_flags[2]; | ||
| 117 | union { | ||
| 118 | u8 flags0; | ||
| 119 | BitField<0, 2, u8> ideal_processor; | ||
| 120 | BitField<2, 2, u8> affinity_mask; | ||
| 121 | BitField<4, 4, u8> system_mode; | ||
| 122 | }; | ||
| 123 | u8 priority; | ||
| 124 | u8 resource_limit_descriptor[0x10][2]; | ||
| 125 | ExHeader_StorageInfo storage_info; | ||
| 126 | u8 service_access_control[0x20][8]; | ||
| 127 | u8 ex_service_access_control[0x2][8]; | ||
| 128 | u8 reserved[0xf]; | ||
| 129 | u8 resource_limit_category; | ||
| 130 | }; | ||
| 131 | |||
| 132 | struct ExHeader_ARM11_KernelCaps { | ||
| 133 | u32_le descriptors[28]; | ||
| 134 | u8 reserved[0x10]; | ||
| 135 | }; | ||
| 136 | |||
| 137 | struct ExHeader_ARM9_AccessControl { | ||
| 138 | u8 descriptors[15]; | ||
| 139 | u8 descversion; | ||
| 140 | }; | ||
| 141 | |||
| 142 | struct ExHeader_Header { | ||
| 143 | ExHeader_CodeSetInfo codeset_info; | ||
| 144 | ExHeader_DependencyList dependency_list; | ||
| 145 | ExHeader_SystemInfo system_info; | ||
| 146 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 147 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 148 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 149 | struct { | ||
| 150 | u8 signature[0x100]; | ||
| 151 | u8 ncch_public_key_modulus[0x100]; | ||
| 152 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 153 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 154 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 155 | } access_desc; | ||
| 156 | }; | ||
| 157 | |||
| 158 | static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); | ||
| 159 | |||
| 160 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 161 | // FileSys namespace | ||
| 162 | |||
| 163 | namespace FileSys { | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Helper which implements an interface to deal with NCCH containers which can | ||
| 167 | * contain ExeFS archives or RomFS archives for games or other applications. | ||
| 168 | */ | ||
| 169 | class NCCHContainer { | ||
| 170 | public: | ||
| 171 | NCCHContainer(const std::string& filepath); | ||
| 172 | NCCHContainer() {} | ||
| 173 | |||
| 174 | Loader::ResultStatus OpenFile(const std::string& filepath); | ||
| 175 | |||
| 176 | /** | ||
| 177 | * Ensure ExeFS and exheader is loaded and ready for reading sections | ||
| 178 | * @return ResultStatus result of function | ||
| 179 | */ | ||
| 180 | Loader::ResultStatus Load(); | ||
| 181 | |||
| 182 | /** | ||
| 183 | * Reads an application ExeFS section of an NCCH file (e.g. .code, .logo, etc.) | ||
| 184 | * @param name Name of section to read out of NCCH file | ||
| 185 | * @param buffer Vector to read data into | ||
| 186 | * @return ResultStatus result of function | ||
| 187 | */ | ||
| 188 | Loader::ResultStatus LoadSectionExeFS(const char* name, std::vector<u8>& buffer); | ||
| 189 | |||
| 190 | /** | ||
| 191 | * Get the RomFS of the NCCH container | ||
| 192 | * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||
| 193 | * @param romfs_file The file containing the RomFS | ||
| 194 | * @param offset The offset the romfs begins on | ||
| 195 | * @param size The size of the romfs | ||
| 196 | * @return ResultStatus result of function | ||
| 197 | */ | ||
| 198 | Loader::ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||
| 199 | u64& size); | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Get the Program ID of the NCCH container | ||
| 203 | * @return ResultStatus result of function | ||
| 204 | */ | ||
| 205 | Loader::ResultStatus ReadProgramId(u64_le& program_id); | ||
| 206 | |||
| 207 | /** | ||
| 208 | * Checks whether the NCCH container contains an ExeFS | ||
| 209 | * @return bool check result | ||
| 210 | */ | ||
| 211 | bool HasExeFS(); | ||
| 212 | |||
| 213 | /** | ||
| 214 | * Checks whether the NCCH container contains a RomFS | ||
| 215 | * @return bool check result | ||
| 216 | */ | ||
| 217 | bool HasRomFS(); | ||
| 218 | |||
| 219 | /** | ||
| 220 | * Checks whether the NCCH container contains an ExHeader | ||
| 221 | * @return bool check result | ||
| 222 | */ | ||
| 223 | bool HasExHeader(); | ||
| 224 | |||
| 225 | NCCH_Header ncch_header; | ||
| 226 | ExeFs_Header exefs_header; | ||
| 227 | ExHeader_Header exheader_header; | ||
| 228 | |||
| 229 | private: | ||
| 230 | bool has_exheader = false; | ||
| 231 | bool has_exefs = false; | ||
| 232 | bool has_romfs = false; | ||
| 233 | |||
| 234 | bool is_loaded = false; | ||
| 235 | bool is_compressed = false; | ||
| 236 | |||
| 237 | u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header | ||
| 238 | u32 exefs_offset = 0; | ||
| 239 | |||
| 240 | std::string filepath; | ||
| 241 | FileUtil::IOFile file; | ||
| 242 | }; | ||
| 243 | |||
| 244 | } // namespace FileSys | ||
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp index 123fe7cd4..be2b2e25f 100644 --- a/src/core/gdbstub/gdbstub.cpp +++ b/src/core/gdbstub/gdbstub.cpp | |||
| @@ -946,7 +946,7 @@ static void Init(u16 port) { | |||
| 946 | WSAStartup(MAKEWORD(2, 2), &InitData); | 946 | WSAStartup(MAKEWORD(2, 2), &InitData); |
| 947 | #endif | 947 | #endif |
| 948 | 948 | ||
| 949 | int tmpsock = socket(PF_INET, SOCK_STREAM, 0); | 949 | int tmpsock = static_cast<int>(socket(PF_INET, SOCK_STREAM, 0)); |
| 950 | if (tmpsock == -1) { | 950 | if (tmpsock == -1) { |
| 951 | LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); | 951 | LOG_ERROR(Debug_GDBStub, "Failed to create gdb socket"); |
| 952 | } | 952 | } |
| @@ -973,7 +973,7 @@ static void Init(u16 port) { | |||
| 973 | sockaddr_in saddr_client; | 973 | sockaddr_in saddr_client; |
| 974 | sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); | 974 | sockaddr* client_addr = reinterpret_cast<sockaddr*>(&saddr_client); |
| 975 | socklen_t client_addrlen = sizeof(saddr_client); | 975 | socklen_t client_addrlen = sizeof(saddr_client); |
| 976 | gdbserver_socket = accept(tmpsock, client_addr, &client_addrlen); | 976 | gdbserver_socket = static_cast<int>(accept(tmpsock, client_addr, &client_addrlen)); |
| 977 | if (gdbserver_socket < 0) { | 977 | if (gdbserver_socket < 0) { |
| 978 | // In the case that we couldn't start the server for whatever reason, just start CPU | 978 | // In the case that we couldn't start the server for whatever reason, just start CPU |
| 979 | // execution like normal. | 979 | // execution like normal. |
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index f7f96125a..87ed85df6 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h | |||
| @@ -122,11 +122,11 @@ union StaticBufferDescInfo { | |||
| 122 | BitField<14, 18, u32> size; | 122 | BitField<14, 18, u32> size; |
| 123 | }; | 123 | }; |
| 124 | 124 | ||
| 125 | inline u32 StaticBufferDesc(u32 size, u8 buffer_id) { | 125 | inline u32 StaticBufferDesc(size_t size, u8 buffer_id) { |
| 126 | StaticBufferDescInfo info{}; | 126 | StaticBufferDescInfo info{}; |
| 127 | info.descriptor_type.Assign(StaticBuffer); | 127 | info.descriptor_type.Assign(StaticBuffer); |
| 128 | info.buffer_id.Assign(buffer_id); | 128 | info.buffer_id.Assign(buffer_id); |
| 129 | info.size.Assign(size); | 129 | info.size.Assign(static_cast<u32>(size)); |
| 130 | return info.raw; | 130 | return info.raw; |
| 131 | } | 131 | } |
| 132 | 132 | ||
| @@ -160,11 +160,11 @@ union MappedBufferDescInfo { | |||
| 160 | BitField<4, 28, u32> size; | 160 | BitField<4, 28, u32> size; |
| 161 | }; | 161 | }; |
| 162 | 162 | ||
| 163 | inline u32 MappedBufferDesc(u32 size, MappedBufferPermissions perms) { | 163 | inline u32 MappedBufferDesc(size_t size, MappedBufferPermissions perms) { |
| 164 | MappedBufferDescInfo info{}; | 164 | MappedBufferDescInfo info{}; |
| 165 | info.flags.Assign(MappedBuffer); | 165 | info.flags.Assign(MappedBuffer); |
| 166 | info.perms.Assign(perms); | 166 | info.perms.Assign(perms); |
| 167 | info.size.Assign(size); | 167 | info.size.Assign(static_cast<u32>(size)); |
| 168 | return info.raw; | 168 | return info.raw; |
| 169 | } | 169 | } |
| 170 | 170 | ||
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index f0d89cffe..7cb95cbac 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h | |||
| @@ -117,9 +117,9 @@ public: | |||
| 117 | 117 | ||
| 118 | void PushCurrentPIDHandle(); | 118 | void PushCurrentPIDHandle(); |
| 119 | 119 | ||
| 120 | void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); | 120 | void PushStaticBuffer(VAddr buffer_vaddr, size_t size, u8 buffer_id); |
| 121 | 121 | ||
| 122 | void PushMappedBuffer(VAddr buffer_vaddr, u32 size, MappedBufferPermissions perms); | 122 | void PushMappedBuffer(VAddr buffer_vaddr, size_t size, MappedBufferPermissions perms); |
| 123 | }; | 123 | }; |
| 124 | 124 | ||
| 125 | /// Push /// | 125 | /// Push /// |
| @@ -190,12 +190,12 @@ inline void RequestBuilder::PushCurrentPIDHandle() { | |||
| 190 | Push(u32(0)); | 190 | Push(u32(0)); |
| 191 | } | 191 | } |
| 192 | 192 | ||
| 193 | inline void RequestBuilder::PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id) { | 193 | inline void RequestBuilder::PushStaticBuffer(VAddr buffer_vaddr, size_t size, u8 buffer_id) { |
| 194 | Push(StaticBufferDesc(size, buffer_id)); | 194 | Push(StaticBufferDesc(size, buffer_id)); |
| 195 | Push(buffer_vaddr); | 195 | Push(buffer_vaddr); |
| 196 | } | 196 | } |
| 197 | 197 | ||
| 198 | inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size, | 198 | inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, size_t size, |
| 199 | MappedBufferPermissions perms) { | 199 | MappedBufferPermissions perms) { |
| 200 | Push(MappedBufferDesc(size, perms)); | 200 | Push(MappedBufferDesc(size, perms)); |
| 201 | Push(buffer_vaddr); | 201 | Push(buffer_vaddr); |
| @@ -227,8 +227,8 @@ public: | |||
| 227 | bool validateHeader = true) { | 227 | bool validateHeader = true) { |
| 228 | if (validateHeader) | 228 | if (validateHeader) |
| 229 | ValidateHeader(); | 229 | ValidateHeader(); |
| 230 | Header builderHeader{ | 230 | Header builderHeader{MakeHeader(static_cast<u16>(header.command_id), normal_params_size, |
| 231 | MakeHeader(header.command_id, normal_params_size, translate_params_size)}; | 231 | translate_params_size)}; |
| 232 | if (context != nullptr) | 232 | if (context != nullptr) |
| 233 | return {*context, builderHeader}; | 233 | return {*context, builderHeader}; |
| 234 | else | 234 | else |
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 5ebe2eca4..6020e9764 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp | |||
| @@ -37,7 +37,7 @@ SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const | |||
| 37 | 37 | ||
| 38 | u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) { | 38 | u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) { |
| 39 | request_handles.push_back(std::move(object)); | 39 | request_handles.push_back(std::move(object)); |
| 40 | return request_handles.size() - 1; | 40 | return static_cast<u32>(request_handles.size() - 1); |
| 41 | } | 41 | } |
| 42 | 42 | ||
| 43 | void HLERequestContext::ClearIncomingObjects() { | 43 | void HLERequestContext::ClearIncomingObjects() { |
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp index cef961289..2cbca5e5b 100644 --- a/src/core/hle/kernel/mutex.cpp +++ b/src/core/hle/kernel/mutex.cpp | |||
| @@ -90,7 +90,7 @@ void Mutex::UpdatePriority() { | |||
| 90 | if (!holding_thread) | 90 | if (!holding_thread) |
| 91 | return; | 91 | return; |
| 92 | 92 | ||
| 93 | s32 best_priority = THREADPRIO_LOWEST; | 93 | u32 best_priority = THREADPRIO_LOWEST; |
| 94 | for (auto& waiter : GetWaitingThreads()) { | 94 | for (auto& waiter : GetWaitingThreads()) { |
| 95 | if (waiter->current_priority < best_priority) | 95 | if (waiter->current_priority < best_priority) |
| 96 | best_priority = waiter->current_priority; | 96 | best_priority = waiter->current_priority; |
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 522ad2333..cf3163e0f 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp | |||
| @@ -147,7 +147,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { | |||
| 147 | } | 147 | } |
| 148 | 148 | ||
| 149 | vm_manager.LogLayout(Log::Level::Debug); | 149 | vm_manager.LogLayout(Log::Level::Debug); |
| 150 | Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority); | 150 | Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority, this); |
| 151 | } | 151 | } |
| 152 | 152 | ||
| 153 | VAddr Process::GetLinearHeapAreaAddress() const { | 153 | VAddr Process::GetLinearHeapAreaAddress() const { |
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp index a8f10a3ee..517dc47a8 100644 --- a/src/core/hle/kernel/resource_limit.cpp +++ b/src/core/hle/kernel/resource_limit.cpp | |||
| @@ -61,7 +61,7 @@ s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const { | |||
| 61 | } | 61 | } |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 64 | s32 ResourceLimit::GetMaxResourceValue(u32 resource) const { | 64 | u32 ResourceLimit::GetMaxResourceValue(u32 resource) const { |
| 65 | switch (resource) { | 65 | switch (resource) { |
| 66 | case PRIORITY: | 66 | case PRIORITY: |
| 67 | return max_priority; | 67 | return max_priority; |
diff --git a/src/core/hle/kernel/resource_limit.h b/src/core/hle/kernel/resource_limit.h index 6cdfbcf8d..42874eb8d 100644 --- a/src/core/hle/kernel/resource_limit.h +++ b/src/core/hle/kernel/resource_limit.h | |||
| @@ -67,7 +67,7 @@ public: | |||
| 67 | * @param resource Requested resource type | 67 | * @param resource Requested resource type |
| 68 | * @returns The max value of the resource type | 68 | * @returns The max value of the resource type |
| 69 | */ | 69 | */ |
| 70 | s32 GetMaxResourceValue(u32 resource) const; | 70 | u32 GetMaxResourceValue(u32 resource) const; |
| 71 | 71 | ||
| 72 | /// Name of resource limit object. | 72 | /// Name of resource limit object. |
| 73 | std::string name; | 73 | std::string name; |
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp index a7b66142f..d45daca35 100644 --- a/src/core/hle/kernel/shared_memory.cpp +++ b/src/core/hle/kernel/shared_memory.cpp | |||
| @@ -42,7 +42,8 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u | |||
| 42 | memory_region->used += size; | 42 | memory_region->used += size; |
| 43 | 43 | ||
| 44 | shared_memory->linear_heap_phys_address = | 44 | shared_memory->linear_heap_phys_address = |
| 45 | Memory::FCRAM_PADDR + memory_region->base + shared_memory->backing_block_offset; | 45 | Memory::FCRAM_PADDR + memory_region->base + |
| 46 | static_cast<PAddr>(shared_memory->backing_block_offset); | ||
| 46 | 47 | ||
| 47 | // Increase the amount of used linear heap memory for the owner process. | 48 | // Increase the amount of used linear heap memory for the owner process. |
| 48 | if (shared_memory->owner_process != nullptr) { | 49 | if (shared_memory->owner_process != nullptr) { |
| @@ -54,22 +55,19 @@ SharedPtr<SharedMemory> SharedMemory::Create(SharedPtr<Process> owner_process, u | |||
| 54 | Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); | 55 | Kernel::g_current_process->vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); |
| 55 | } | 56 | } |
| 56 | } else { | 57 | } else { |
| 57 | // TODO(Subv): What happens if an application tries to create multiple memory blocks | ||
| 58 | // pointing to the same address? | ||
| 59 | auto& vm_manager = shared_memory->owner_process->vm_manager; | 58 | auto& vm_manager = shared_memory->owner_process->vm_manager; |
| 60 | // The memory is already available and mapped in the owner process. | 59 | // The memory is already available and mapped in the owner process. |
| 61 | auto vma = vm_manager.FindVMA(address)->second; | 60 | auto vma = vm_manager.FindVMA(address); |
| 62 | // Copy it over to our own storage | 61 | ASSERT_MSG(vma != vm_manager.vma_map.end(), "Invalid memory address"); |
| 63 | shared_memory->backing_block = std::make_shared<std::vector<u8>>( | 62 | ASSERT_MSG(vma->second.backing_block, "Backing block doesn't exist for address"); |
| 64 | vma.backing_block->data() + vma.offset, vma.backing_block->data() + vma.offset + size); | 63 | |
| 65 | shared_memory->backing_block_offset = 0; | 64 | // The returned VMA might be a bigger one encompassing the desired address. |
| 66 | // Unmap the existing pages | 65 | auto vma_offset = address - vma->first; |
| 67 | vm_manager.UnmapRange(address, size); | 66 | ASSERT_MSG(vma_offset + size <= vma->second.size, |
| 68 | // Map our own block into the address space | 67 | "Shared memory exceeds bounds of mapped block"); |
| 69 | vm_manager.MapMemoryBlock(address, shared_memory->backing_block, 0, size, | 68 | |
| 70 | MemoryState::Shared); | 69 | shared_memory->backing_block = vma->second.backing_block; |
| 71 | // Reprotect the block with the new permissions | 70 | shared_memory->backing_block_offset = vma->second.offset + vma_offset; |
| 72 | vm_manager.ReprotectRange(address, size, ConvertPermissions(permissions)); | ||
| 73 | } | 71 | } |
| 74 | 72 | ||
| 75 | shared_memory->base_address = address; | 73 | shared_memory->base_address = address; |
| @@ -183,4 +181,4 @@ u8* SharedMemory::GetPointer(u32 offset) { | |||
| 183 | return backing_block->data() + backing_block_offset + offset; | 181 | return backing_block->data() + backing_block_offset + offset; |
| 184 | } | 182 | } |
| 185 | 183 | ||
| 186 | } // namespace | 184 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/shared_memory.h b/src/core/hle/kernel/shared_memory.h index 94b335ed1..93a6f2182 100644 --- a/src/core/hle/kernel/shared_memory.h +++ b/src/core/hle/kernel/shared_memory.h | |||
| @@ -114,7 +114,7 @@ public: | |||
| 114 | /// Backing memory for this shared memory block. | 114 | /// Backing memory for this shared memory block. |
| 115 | std::shared_ptr<std::vector<u8>> backing_block; | 115 | std::shared_ptr<std::vector<u8>> backing_block; |
| 116 | /// Offset into the backing block for this shared memory. | 116 | /// Offset into the backing block for this shared memory. |
| 117 | u32 backing_block_offset; | 117 | size_t backing_block_offset; |
| 118 | /// Size of the memory block. Page-aligned. | 118 | /// Size of the memory block. Page-aligned. |
| 119 | u32 size; | 119 | u32 size; |
| 120 | /// Permission restrictions applied to the process which created the block. | 120 | /// Permission restrictions applied to the process which created the block. |
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 324415a36..0f7970ebe 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -111,7 +111,7 @@ void Thread::Stop() { | |||
| 111 | 111 | ||
| 112 | Thread* ArbitrateHighestPriorityThread(u32 address) { | 112 | Thread* ArbitrateHighestPriorityThread(u32 address) { |
| 113 | Thread* highest_priority_thread = nullptr; | 113 | Thread* highest_priority_thread = nullptr; |
| 114 | s32 priority = THREADPRIO_LOWEST; | 114 | u32 priority = THREADPRIO_LOWEST; |
| 115 | 115 | ||
| 116 | // Iterate through threads, find highest priority thread that is waiting to be arbitrated... | 116 | // Iterate through threads, find highest priority thread that is waiting to be arbitrated... |
| 117 | for (auto& thread : thread_list) { | 117 | for (auto& thread : thread_list) { |
| @@ -178,16 +178,13 @@ static void SwitchContext(Thread* new_thread) { | |||
| 178 | ready_queue.remove(new_thread->current_priority, new_thread); | 178 | ready_queue.remove(new_thread->current_priority, new_thread); |
| 179 | new_thread->status = THREADSTATUS_RUNNING; | 179 | new_thread->status = THREADSTATUS_RUNNING; |
| 180 | 180 | ||
| 181 | Core::CPU().LoadContext(new_thread->context); | ||
| 182 | Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); | ||
| 183 | |||
| 184 | if (previous_process != current_thread->owner_process) { | 181 | if (previous_process != current_thread->owner_process) { |
| 185 | Kernel::g_current_process = current_thread->owner_process; | 182 | Kernel::g_current_process = current_thread->owner_process; |
| 186 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | 183 | SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); |
| 187 | // We have switched processes and thus, page tables, clear the instruction cache so we | ||
| 188 | // don't keep stale data from the previous process. | ||
| 189 | Core::CPU().ClearInstructionCache(); | ||
| 190 | } | 184 | } |
| 185 | |||
| 186 | Core::CPU().LoadContext(new_thread->context); | ||
| 187 | Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); | ||
| 191 | } else { | 188 | } else { |
| 192 | current_thread = nullptr; | 189 | current_thread = nullptr; |
| 193 | // Note: We do not reset the current process and current page table when idling because | 190 | // Note: We do not reset the current process and current page table when idling because |
| @@ -250,12 +247,15 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) { | |||
| 250 | 247 | ||
| 251 | if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || | 248 | if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY || |
| 252 | thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { | 249 | thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) { |
| 253 | thread->wait_set_output = false; | 250 | |
| 251 | // Invoke the wakeup callback before clearing the wait objects | ||
| 252 | if (thread->wakeup_callback) | ||
| 253 | thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr); | ||
| 254 | |||
| 254 | // Remove the thread from each of its waiting objects' waitlists | 255 | // Remove the thread from each of its waiting objects' waitlists |
| 255 | for (auto& object : thread->wait_objects) | 256 | for (auto& object : thread->wait_objects) |
| 256 | object->RemoveWaitingThread(thread.get()); | 257 | object->RemoveWaitingThread(thread.get()); |
| 257 | thread->wait_objects.clear(); | 258 | thread->wait_objects.clear(); |
| 258 | thread->SetWaitSynchronizationResult(RESULT_TIMEOUT); | ||
| 259 | } | 259 | } |
| 260 | 260 | ||
| 261 | thread->ResumeFromWait(); | 261 | thread->ResumeFromWait(); |
| @@ -281,6 +281,9 @@ void Thread::ResumeFromWait() { | |||
| 281 | break; | 281 | break; |
| 282 | 282 | ||
| 283 | case THREADSTATUS_READY: | 283 | case THREADSTATUS_READY: |
| 284 | // The thread's wakeup callback must have already been cleared when the thread was first | ||
| 285 | // awoken. | ||
| 286 | ASSERT(wakeup_callback == nullptr); | ||
| 284 | // If the thread is waiting on multiple wait objects, it might be awoken more than once | 287 | // If the thread is waiting on multiple wait objects, it might be awoken more than once |
| 285 | // before actually resuming. We can ignore subsequent wakeups if the thread status has | 288 | // before actually resuming. We can ignore subsequent wakeups if the thread status has |
| 286 | // already been set to THREADSTATUS_READY. | 289 | // already been set to THREADSTATUS_READY. |
| @@ -296,6 +299,8 @@ void Thread::ResumeFromWait() { | |||
| 296 | return; | 299 | return; |
| 297 | } | 300 | } |
| 298 | 301 | ||
| 302 | wakeup_callback = nullptr; | ||
| 303 | |||
| 299 | ready_queue.push_back(current_priority, this); | 304 | ready_queue.push_back(current_priority, this); |
| 300 | status = THREADSTATUS_READY; | 305 | status = THREADSTATUS_READY; |
| 301 | Core::System::GetInstance().PrepareReschedule(); | 306 | Core::System::GetInstance().PrepareReschedule(); |
| @@ -314,7 +319,7 @@ static void DebugThreadQueue() { | |||
| 314 | } | 319 | } |
| 315 | 320 | ||
| 316 | for (auto& t : thread_list) { | 321 | for (auto& t : thread_list) { |
| 317 | s32 priority = ready_queue.contains(t.get()); | 322 | u32 priority = ready_queue.contains(t.get()); |
| 318 | if (priority != -1) { | 323 | if (priority != -1) { |
| 319 | LOG_DEBUG(Kernel, "0x%02X %u", priority, t->GetObjectId()); | 324 | LOG_DEBUG(Kernel, "0x%02X %u", priority, t->GetObjectId()); |
| 320 | } | 325 | } |
| @@ -364,7 +369,8 @@ static void ResetThreadContext(ARM_Interface::ThreadContext& context, u32 stack_ | |||
| 364 | } | 369 | } |
| 365 | 370 | ||
| 366 | ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, u32 priority, | 371 | ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, u32 priority, |
| 367 | u32 arg, s32 processor_id, VAddr stack_top) { | 372 | u32 arg, s32 processor_id, VAddr stack_top, |
| 373 | SharedPtr<Process> owner_process) { | ||
| 368 | // Check if priority is in ranged. Lowest priority -> highest priority id. | 374 | // Check if priority is in ranged. Lowest priority -> highest priority id. |
| 369 | if (priority > THREADPRIO_LOWEST) { | 375 | if (priority > THREADPRIO_LOWEST) { |
| 370 | LOG_ERROR(Kernel_SVC, "Invalid thread priority: %d", priority); | 376 | LOG_ERROR(Kernel_SVC, "Invalid thread priority: %d", priority); |
| @@ -378,7 +384,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 378 | 384 | ||
| 379 | // TODO(yuriks): Other checks, returning 0xD9001BEA | 385 | // TODO(yuriks): Other checks, returning 0xD9001BEA |
| 380 | 386 | ||
| 381 | if (!Memory::IsValidVirtualAddress(entry_point)) { | 387 | if (!Memory::IsValidVirtualAddress(*owner_process, entry_point)) { |
| 382 | LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); | 388 | LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point); |
| 383 | // TODO: Verify error | 389 | // TODO: Verify error |
| 384 | return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, | 390 | return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, |
| @@ -397,15 +403,14 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 397 | thread->nominal_priority = thread->current_priority = priority; | 403 | thread->nominal_priority = thread->current_priority = priority; |
| 398 | thread->last_running_ticks = CoreTiming::GetTicks(); | 404 | thread->last_running_ticks = CoreTiming::GetTicks(); |
| 399 | thread->processor_id = processor_id; | 405 | thread->processor_id = processor_id; |
| 400 | thread->wait_set_output = false; | ||
| 401 | thread->wait_objects.clear(); | 406 | thread->wait_objects.clear(); |
| 402 | thread->wait_address = 0; | 407 | thread->wait_address = 0; |
| 403 | thread->name = std::move(name); | 408 | thread->name = std::move(name); |
| 404 | thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap(); | 409 | thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap(); |
| 405 | thread->owner_process = g_current_process; | 410 | thread->owner_process = owner_process; |
| 406 | 411 | ||
| 407 | // Find the next available TLS index, and mark it as used | 412 | // Find the next available TLS index, and mark it as used |
| 408 | auto& tls_slots = Kernel::g_current_process->tls_slots; | 413 | auto& tls_slots = owner_process->tls_slots; |
| 409 | bool needs_allocation = true; | 414 | bool needs_allocation = true; |
| 410 | u32 available_page; // Which allocated page has free space | 415 | u32 available_page; // Which allocated page has free space |
| 411 | u32 available_slot; // Which slot within the page is free | 416 | u32 available_slot; // Which slot within the page is free |
| @@ -424,18 +429,18 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 424 | return ERR_OUT_OF_MEMORY; | 429 | return ERR_OUT_OF_MEMORY; |
| 425 | } | 430 | } |
| 426 | 431 | ||
| 427 | u32 offset = linheap_memory->size(); | 432 | size_t offset = linheap_memory->size(); |
| 428 | 433 | ||
| 429 | // Allocate some memory from the end of the linear heap for this region. | 434 | // Allocate some memory from the end of the linear heap for this region. |
| 430 | linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0); | 435 | linheap_memory->insert(linheap_memory->end(), Memory::PAGE_SIZE, 0); |
| 431 | memory_region->used += Memory::PAGE_SIZE; | 436 | memory_region->used += Memory::PAGE_SIZE; |
| 432 | Kernel::g_current_process->linear_heap_used += Memory::PAGE_SIZE; | 437 | owner_process->linear_heap_used += Memory::PAGE_SIZE; |
| 433 | 438 | ||
| 434 | tls_slots.emplace_back(0); // The page is completely available at the start | 439 | tls_slots.emplace_back(0); // The page is completely available at the start |
| 435 | available_page = tls_slots.size() - 1; | 440 | available_page = static_cast<u32>(tls_slots.size() - 1); |
| 436 | available_slot = 0; // Use the first slot in the new page | 441 | available_slot = 0; // Use the first slot in the new page |
| 437 | 442 | ||
| 438 | auto& vm_manager = Kernel::g_current_process->vm_manager; | 443 | auto& vm_manager = owner_process->vm_manager; |
| 439 | vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); | 444 | vm_manager.RefreshMemoryBlockMappings(linheap_memory.get()); |
| 440 | 445 | ||
| 441 | // Map the page to the current process' address space. | 446 | // Map the page to the current process' address space. |
| @@ -459,7 +464,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, | |||
| 459 | return MakeResult<SharedPtr<Thread>>(std::move(thread)); | 464 | return MakeResult<SharedPtr<Thread>>(std::move(thread)); |
| 460 | } | 465 | } |
| 461 | 466 | ||
| 462 | void Thread::SetPriority(s32 priority) { | 467 | void Thread::SetPriority(u32 priority) { |
| 463 | ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, | 468 | ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST, |
| 464 | "Invalid priority value."); | 469 | "Invalid priority value."); |
| 465 | // If thread was ready, adjust queues | 470 | // If thread was ready, adjust queues |
| @@ -472,7 +477,7 @@ void Thread::SetPriority(s32 priority) { | |||
| 472 | } | 477 | } |
| 473 | 478 | ||
| 474 | void Thread::UpdatePriority() { | 479 | void Thread::UpdatePriority() { |
| 475 | s32 best_priority = nominal_priority; | 480 | u32 best_priority = nominal_priority; |
| 476 | for (auto& mutex : held_mutexes) { | 481 | for (auto& mutex : held_mutexes) { |
| 477 | if (mutex->priority < best_priority) | 482 | if (mutex->priority < best_priority) |
| 478 | best_priority = mutex->priority; | 483 | best_priority = mutex->priority; |
| @@ -480,7 +485,7 @@ void Thread::UpdatePriority() { | |||
| 480 | BoostPriority(best_priority); | 485 | BoostPriority(best_priority); |
| 481 | } | 486 | } |
| 482 | 487 | ||
| 483 | void Thread::BoostPriority(s32 priority) { | 488 | void Thread::BoostPriority(u32 priority) { |
| 484 | // If thread was ready, adjust queues | 489 | // If thread was ready, adjust queues |
| 485 | if (status == THREADSTATUS_READY) | 490 | if (status == THREADSTATUS_READY) |
| 486 | ready_queue.move(this, current_priority, priority); | 491 | ready_queue.move(this, current_priority, priority); |
| @@ -489,10 +494,10 @@ void Thread::BoostPriority(s32 priority) { | |||
| 489 | current_priority = priority; | 494 | current_priority = priority; |
| 490 | } | 495 | } |
| 491 | 496 | ||
| 492 | SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) { | 497 | SharedPtr<Thread> SetupMainThread(u32 entry_point, u32 priority, SharedPtr<Process> owner_process) { |
| 493 | // Initialize new "main" thread | 498 | // Initialize new "main" thread |
| 494 | auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, | 499 | auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0, |
| 495 | Memory::HEAP_VADDR_END); | 500 | Memory::HEAP_VADDR_END, owner_process); |
| 496 | 501 | ||
| 497 | SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); | 502 | SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); |
| 498 | 503 | ||
| @@ -533,7 +538,13 @@ void Thread::SetWaitSynchronizationOutput(s32 output) { | |||
| 533 | s32 Thread::GetWaitObjectIndex(WaitObject* object) const { | 538 | s32 Thread::GetWaitObjectIndex(WaitObject* object) const { |
| 534 | ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); | 539 | ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything"); |
| 535 | auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); | 540 | auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object); |
| 536 | return std::distance(match, wait_objects.rend()) - 1; | 541 | return static_cast<s32>(std::distance(match, wait_objects.rend()) - 1); |
| 542 | } | ||
| 543 | |||
| 544 | VAddr Thread::GetCommandBufferAddress() const { | ||
| 545 | // Offset from the start of TLS at which the IPC command buffer begins. | ||
| 546 | static constexpr int CommandHeaderOffset = 0x80; | ||
| 547 | return GetTLSAddress() + CommandHeaderOffset; | ||
| 537 | } | 548 | } |
| 538 | 549 | ||
| 539 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 550 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 6a3566f15..314fba81f 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h | |||
| @@ -15,7 +15,7 @@ | |||
| 15 | #include "core/hle/kernel/wait_object.h" | 15 | #include "core/hle/kernel/wait_object.h" |
| 16 | #include "core/hle/result.h" | 16 | #include "core/hle/result.h" |
| 17 | 17 | ||
| 18 | enum ThreadPriority : s32 { | 18 | enum ThreadPriority : u32 { |
| 19 | THREADPRIO_HIGHEST = 0, ///< Highest thread priority | 19 | THREADPRIO_HIGHEST = 0, ///< Highest thread priority |
| 20 | THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps | 20 | THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps |
| 21 | THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps | 21 | THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps |
| @@ -41,6 +41,11 @@ enum ThreadStatus { | |||
| 41 | THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated | 41 | THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated |
| 42 | }; | 42 | }; |
| 43 | 43 | ||
| 44 | enum class ThreadWakeupReason { | ||
| 45 | Signal, // The thread was woken up by WakeupAllWaitingThreads due to an object signal. | ||
| 46 | Timeout // The thread was woken up due to a wait timeout. | ||
| 47 | }; | ||
| 48 | |||
| 44 | namespace Kernel { | 49 | namespace Kernel { |
| 45 | 50 | ||
| 46 | class Mutex; | 51 | class Mutex; |
| @@ -56,10 +61,12 @@ public: | |||
| 56 | * @param arg User data to pass to the thread | 61 | * @param arg User data to pass to the thread |
| 57 | * @param processor_id The ID(s) of the processors on which the thread is desired to be run | 62 | * @param processor_id The ID(s) of the processors on which the thread is desired to be run |
| 58 | * @param stack_top The address of the thread's stack top | 63 | * @param stack_top The address of the thread's stack top |
| 64 | * @param owner_process The parent process for the thread | ||
| 59 | * @return A shared pointer to the newly created thread | 65 | * @return A shared pointer to the newly created thread |
| 60 | */ | 66 | */ |
| 61 | static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, u32 priority, | 67 | static ResultVal<SharedPtr<Thread>> Create(std::string name, VAddr entry_point, u32 priority, |
| 62 | u32 arg, s32 processor_id, VAddr stack_top); | 68 | u32 arg, s32 processor_id, VAddr stack_top, |
| 69 | SharedPtr<Process> owner_process); | ||
| 63 | 70 | ||
| 64 | std::string GetName() const override { | 71 | std::string GetName() const override { |
| 65 | return name; | 72 | return name; |
| @@ -80,7 +87,7 @@ public: | |||
| 80 | * Gets the thread's current priority | 87 | * Gets the thread's current priority |
| 81 | * @return The current thread's priority | 88 | * @return The current thread's priority |
| 82 | */ | 89 | */ |
| 83 | s32 GetPriority() const { | 90 | u32 GetPriority() const { |
| 84 | return current_priority; | 91 | return current_priority; |
| 85 | } | 92 | } |
| 86 | 93 | ||
| @@ -88,7 +95,7 @@ public: | |||
| 88 | * Sets the thread's current priority | 95 | * Sets the thread's current priority |
| 89 | * @param priority The new priority | 96 | * @param priority The new priority |
| 90 | */ | 97 | */ |
| 91 | void SetPriority(s32 priority); | 98 | void SetPriority(u32 priority); |
| 92 | 99 | ||
| 93 | /** | 100 | /** |
| 94 | * Boost's a thread's priority to the best priority among the thread's held mutexes. | 101 | * Boost's a thread's priority to the best priority among the thread's held mutexes. |
| @@ -100,7 +107,7 @@ public: | |||
| 100 | * Temporarily boosts the thread's priority until the next time it is scheduled | 107 | * Temporarily boosts the thread's priority until the next time it is scheduled |
| 101 | * @param priority The new priority | 108 | * @param priority The new priority |
| 102 | */ | 109 | */ |
| 103 | void BoostPriority(s32 priority); | 110 | void BoostPriority(u32 priority); |
| 104 | 111 | ||
| 105 | /** | 112 | /** |
| 106 | * Gets the thread's thread ID | 113 | * Gets the thread's thread ID |
| @@ -116,9 +123,9 @@ public: | |||
| 116 | void ResumeFromWait(); | 123 | void ResumeFromWait(); |
| 117 | 124 | ||
| 118 | /** | 125 | /** |
| 119 | * Schedules an event to wake up the specified thread after the specified delay | 126 | * Schedules an event to wake up the specified thread after the specified delay |
| 120 | * @param nanoseconds The time this thread will be allowed to sleep for | 127 | * @param nanoseconds The time this thread will be allowed to sleep for |
| 121 | */ | 128 | */ |
| 122 | void WakeAfterDelay(s64 nanoseconds); | 129 | void WakeAfterDelay(s64 nanoseconds); |
| 123 | 130 | ||
| 124 | /** | 131 | /** |
| @@ -157,6 +164,12 @@ public: | |||
| 157 | return tls_address; | 164 | return tls_address; |
| 158 | } | 165 | } |
| 159 | 166 | ||
| 167 | /* | ||
| 168 | * Returns the address of the current thread's command buffer, located in the TLS. | ||
| 169 | * @returns VAddr of the thread's command buffer. | ||
| 170 | */ | ||
| 171 | VAddr GetCommandBufferAddress() const; | ||
| 172 | |||
| 160 | /** | 173 | /** |
| 161 | * Returns whether this thread is waiting for all the objects in | 174 | * Returns whether this thread is waiting for all the objects in |
| 162 | * its wait list to become ready, as a result of a WaitSynchronizationN call | 175 | * its wait list to become ready, as a result of a WaitSynchronizationN call |
| @@ -174,8 +187,8 @@ public: | |||
| 174 | u32 entry_point; | 187 | u32 entry_point; |
| 175 | u32 stack_top; | 188 | u32 stack_top; |
| 176 | 189 | ||
| 177 | s32 nominal_priority; ///< Nominal thread priority, as set by the emulated application | 190 | u32 nominal_priority; ///< Nominal thread priority, as set by the emulated application |
| 178 | s32 current_priority; ///< Current thread priority, can be temporarily changed | 191 | u32 current_priority; ///< Current thread priority, can be temporarily changed |
| 179 | 192 | ||
| 180 | u64 last_running_ticks; ///< CPU tick when thread was last running | 193 | u64 last_running_ticks; ///< CPU tick when thread was last running |
| 181 | 194 | ||
| @@ -197,14 +210,18 @@ public: | |||
| 197 | 210 | ||
| 198 | VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address | 211 | VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address |
| 199 | 212 | ||
| 200 | /// True if the WaitSynchronizationN output parameter should be set on thread wakeup. | ||
| 201 | bool wait_set_output; | ||
| 202 | |||
| 203 | std::string name; | 213 | std::string name; |
| 204 | 214 | ||
| 205 | /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. | 215 | /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. |
| 206 | Handle callback_handle; | 216 | Handle callback_handle; |
| 207 | 217 | ||
| 218 | using WakeupCallback = void(ThreadWakeupReason reason, SharedPtr<Thread> thread, | ||
| 219 | SharedPtr<WaitObject> object); | ||
| 220 | // Callback that will be invoked when the thread is resumed from a waiting state. If the thread | ||
| 221 | // was waiting via WaitSynchronizationN then the object will be the last object that became | ||
| 222 | // available. In case of a timeout, the object will be nullptr. | ||
| 223 | std::function<WakeupCallback> wakeup_callback; | ||
| 224 | |||
| 208 | private: | 225 | private: |
| 209 | Thread(); | 226 | Thread(); |
| 210 | ~Thread() override; | 227 | ~Thread() override; |
| @@ -214,9 +231,10 @@ private: | |||
| 214 | * Sets up the primary application thread | 231 | * Sets up the primary application thread |
| 215 | * @param entry_point The address at which the thread should start execution | 232 | * @param entry_point The address at which the thread should start execution |
| 216 | * @param priority The priority to give the main thread | 233 | * @param priority The priority to give the main thread |
| 234 | * @param owner_process The parent process for the main thread | ||
| 217 | * @return A shared pointer to the main thread | 235 | * @return A shared pointer to the main thread |
| 218 | */ | 236 | */ |
| 219 | SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority); | 237 | SharedPtr<Thread> SetupMainThread(u32 entry_point, u32 priority, SharedPtr<Process> owner_process); |
| 220 | 238 | ||
| 221 | /** | 239 | /** |
| 222 | * Returns whether there are any threads that are ready to run. | 240 | * Returns whether there are any threads that are ready to run. |
| @@ -276,4 +294,4 @@ void ThreadingShutdown(); | |||
| 276 | */ | 294 | */ |
| 277 | const std::vector<SharedPtr<Thread>>& GetThreadList(); | 295 | const std::vector<SharedPtr<Thread>>& GetThreadList(); |
| 278 | 296 | ||
| 279 | } // namespace | 297 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/wait_object.cpp b/src/core/hle/kernel/wait_object.cpp index f245eda6c..469554908 100644 --- a/src/core/hle/kernel/wait_object.cpp +++ b/src/core/hle/kernel/wait_object.cpp | |||
| @@ -34,7 +34,7 @@ void WaitObject::RemoveWaitingThread(Thread* thread) { | |||
| 34 | 34 | ||
| 35 | SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { | 35 | SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() { |
| 36 | Thread* candidate = nullptr; | 36 | Thread* candidate = nullptr; |
| 37 | s32 candidate_priority = THREADPRIO_LOWEST + 1; | 37 | u32 candidate_priority = THREADPRIO_LOWEST + 1; |
| 38 | 38 | ||
| 39 | for (const auto& thread : waiting_threads) { | 39 | for (const auto& thread : waiting_threads) { |
| 40 | // The list of waiting threads must not contain threads that are not waiting to be awakened. | 40 | // The list of waiting threads must not contain threads that are not waiting to be awakened. |
| @@ -71,23 +71,20 @@ void WaitObject::WakeupAllWaitingThreads() { | |||
| 71 | while (auto thread = GetHighestPriorityReadyThread()) { | 71 | while (auto thread = GetHighestPriorityReadyThread()) { |
| 72 | if (!thread->IsSleepingOnWaitAll()) { | 72 | if (!thread->IsSleepingOnWaitAll()) { |
| 73 | Acquire(thread.get()); | 73 | Acquire(thread.get()); |
| 74 | // Set the output index of the WaitSynchronizationN call to the index of this object. | ||
| 75 | if (thread->wait_set_output) { | ||
| 76 | thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this)); | ||
| 77 | thread->wait_set_output = false; | ||
| 78 | } | ||
| 79 | } else { | 74 | } else { |
| 80 | for (auto& object : thread->wait_objects) { | 75 | for (auto& object : thread->wait_objects) { |
| 81 | object->Acquire(thread.get()); | 76 | object->Acquire(thread.get()); |
| 82 | } | 77 | } |
| 83 | // Note: This case doesn't update the output index of WaitSynchronizationN. | ||
| 84 | } | 78 | } |
| 85 | 79 | ||
| 80 | // Invoke the wakeup callback before clearing the wait objects | ||
| 81 | if (thread->wakeup_callback) | ||
| 82 | thread->wakeup_callback(ThreadWakeupReason::Signal, thread, this); | ||
| 83 | |||
| 86 | for (auto& object : thread->wait_objects) | 84 | for (auto& object : thread->wait_objects) |
| 87 | object->RemoveWaitingThread(thread.get()); | 85 | object->RemoveWaitingThread(thread.get()); |
| 88 | thread->wait_objects.clear(); | 86 | thread->wait_objects.clear(); |
| 89 | 87 | ||
| 90 | thread->SetWaitSynchronizationResult(RESULT_SUCCESS); | ||
| 91 | thread->ResumeFromWait(); | 88 | thread->ResumeFromWait(); |
| 92 | } | 89 | } |
| 93 | } | 90 | } |
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index c36775473..59ea9823d 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp | |||
| @@ -65,6 +65,7 @@ union AppletAttributes { | |||
| 65 | u32 raw; | 65 | u32 raw; |
| 66 | 66 | ||
| 67 | BitField<0, 3, u32> applet_pos; | 67 | BitField<0, 3, u32> applet_pos; |
| 68 | BitField<29, 1, u32> is_home_menu; | ||
| 68 | 69 | ||
| 69 | AppletAttributes() : raw(0) {} | 70 | AppletAttributes() : raw(0) {} |
| 70 | AppletAttributes(u32 attributes) : raw(attributes) {} | 71 | AppletAttributes(u32 attributes) : raw(attributes) {} |
| @@ -158,6 +159,11 @@ static AppletSlotData* GetAppletSlotData(AppletAttributes attributes) { | |||
| 158 | if (slot == AppletSlot::Error) | 159 | if (slot == AppletSlot::Error) |
| 159 | return nullptr; | 160 | return nullptr; |
| 160 | 161 | ||
| 162 | // The Home Menu is a system applet, however, it has its own applet slot so that it can run | ||
| 163 | // concurrently with other system applets. | ||
| 164 | if (slot == AppletSlot::SystemApplet && attributes.is_home_menu) | ||
| 165 | return &applet_slots[static_cast<size_t>(AppletSlot::HomeMenu)]; | ||
| 166 | |||
| 161 | return &applet_slots[static_cast<size_t>(slot)]; | 167 | return &applet_slots[static_cast<size_t>(slot)]; |
| 162 | } | 168 | } |
| 163 | 169 | ||
| @@ -201,6 +207,19 @@ void Initialize(Service::Interface* self) { | |||
| 201 | rb.Push(RESULT_SUCCESS); | 207 | rb.Push(RESULT_SUCCESS); |
| 202 | rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(), | 208 | rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(), |
| 203 | Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap()); | 209 | Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap()); |
| 210 | |||
| 211 | if (slot_data->applet_id == AppletId::Application || | ||
| 212 | slot_data->applet_id == AppletId::HomeMenu) { | ||
| 213 | // Initialize the APT parameter to wake up the application. | ||
| 214 | next_parameter.emplace(); | ||
| 215 | next_parameter->signal = static_cast<u32>(SignalType::Wakeup); | ||
| 216 | next_parameter->sender_id = static_cast<u32>(AppletId::None); | ||
| 217 | next_parameter->destination_id = app_id; | ||
| 218 | // Not signaling the parameter event will cause the application (or Home Menu) to hang | ||
| 219 | // during startup. In the real console, it is usually the Kernel and Home Menu who cause NS | ||
| 220 | // to signal the HomeMenu and Application parameter events, respectively. | ||
| 221 | slot_data->parameter_event->Signal(); | ||
| 222 | } | ||
| 204 | } | 223 | } |
| 205 | 224 | ||
| 206 | static u32 DecompressLZ11(const u8* in, u8* out) { | 225 | static u32 DecompressLZ11(const u8* in, u8* out) { |
| @@ -563,7 +582,7 @@ void ReceiveParameter(Service::Interface* self) { | |||
| 563 | ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() | 582 | ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() |
| 564 | : 0); | 583 | : 0); |
| 565 | 584 | ||
| 566 | rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter->buffer.size()), 0); | 585 | rb.PushStaticBuffer(buffer, next_parameter->buffer.size(), 0); |
| 567 | 586 | ||
| 568 | Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); | 587 | Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); |
| 569 | 588 | ||
| @@ -611,7 +630,7 @@ void GlanceParameter(Service::Interface* self) { | |||
| 611 | ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() | 630 | ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap() |
| 612 | : 0); | 631 | : 0); |
| 613 | 632 | ||
| 614 | rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter->buffer.size()), 0); | 633 | rb.PushStaticBuffer(buffer, next_parameter->buffer.size(), 0); |
| 615 | 634 | ||
| 616 | Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); | 635 | Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size()); |
| 617 | 636 | ||
| @@ -763,6 +782,20 @@ void PrepareToStartLibraryApplet(Service::Interface* self) { | |||
| 763 | } | 782 | } |
| 764 | } | 783 | } |
| 765 | 784 | ||
| 785 | void PrepareToStartNewestHomeMenu(Service::Interface* self) { | ||
| 786 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); // 0x1A0000 | ||
| 787 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||
| 788 | |||
| 789 | // TODO(Subv): This command can only be called by a System Applet (return 0xC8A0CC04 otherwise). | ||
| 790 | |||
| 791 | // This command must return an error when called, otherwise the Home Menu will try to reboot the | ||
| 792 | // system. | ||
| 793 | rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet, | ||
| 794 | ErrorSummary::InvalidState, ErrorLevel::Status)); | ||
| 795 | |||
| 796 | LOG_DEBUG(Service_APT, "called"); | ||
| 797 | } | ||
| 798 | |||
| 766 | void PreloadLibraryApplet(Service::Interface* self) { | 799 | void PreloadLibraryApplet(Service::Interface* self) { |
| 767 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040 | 800 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040 |
| 768 | AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); | 801 | AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>()); |
| @@ -1058,12 +1091,6 @@ void Init() { | |||
| 1058 | slot_data.parameter_event = | 1091 | slot_data.parameter_event = |
| 1059 | Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter"); | 1092 | Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter"); |
| 1060 | } | 1093 | } |
| 1061 | |||
| 1062 | // Initialize the parameter to wake up the application. | ||
| 1063 | next_parameter.emplace(); | ||
| 1064 | next_parameter->signal = static_cast<u32>(SignalType::Wakeup); | ||
| 1065 | next_parameter->destination_id = static_cast<u32>(AppletId::Application); | ||
| 1066 | applet_slots[static_cast<size_t>(AppletSlot::Application)].parameter_event->Signal(); | ||
| 1067 | } | 1094 | } |
| 1068 | 1095 | ||
| 1069 | void Shutdown() { | 1096 | void Shutdown() { |
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h index 96b28b438..7b79e1f3e 100644 --- a/src/core/hle/service/apt/apt.h +++ b/src/core/hle/service/apt/apt.h | |||
| @@ -420,6 +420,16 @@ void GetAppCpuTimeLimit(Service::Interface* self); | |||
| 420 | void PrepareToStartLibraryApplet(Service::Interface* self); | 420 | void PrepareToStartLibraryApplet(Service::Interface* self); |
| 421 | 421 | ||
| 422 | /** | 422 | /** |
| 423 | * APT::PrepareToStartNewestHomeMenu service function | ||
| 424 | * Inputs: | ||
| 425 | * 0 : Command header [0x001A0000] | ||
| 426 | * Outputs: | ||
| 427 | * 0 : Return header | ||
| 428 | * 1 : Result of function | ||
| 429 | */ | ||
| 430 | void PrepareToStartNewestHomeMenu(Service::Interface* self); | ||
| 431 | |||
| 432 | /** | ||
| 423 | * APT::PreloadLibraryApplet service function | 433 | * APT::PreloadLibraryApplet service function |
| 424 | * Inputs: | 434 | * Inputs: |
| 425 | * 0 : Command header [0x00160040] | 435 | * 0 : Command header [0x00160040] |
diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp index cf74c2a36..bb78ee7d7 100644 --- a/src/core/hle/service/apt/apt_s.cpp +++ b/src/core/hle/service/apt/apt_s.cpp | |||
| @@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 17 | {0x00060040, GetAppletInfo, "GetAppletInfo"}, | 17 | {0x00060040, GetAppletInfo, "GetAppletInfo"}, |
| 18 | {0x00070000, nullptr, "GetLastSignaledAppletId"}, | 18 | {0x00070000, nullptr, "GetLastSignaledAppletId"}, |
| 19 | {0x00080000, nullptr, "CountRegisteredApplet"}, | 19 | {0x00080000, nullptr, "CountRegisteredApplet"}, |
| 20 | {0x00090040, nullptr, "IsRegistered"}, | 20 | {0x00090040, IsRegistered, "IsRegistered"}, |
| 21 | {0x000A0040, nullptr, "GetAttribute"}, | 21 | {0x000A0040, nullptr, "GetAttribute"}, |
| 22 | {0x000B0040, InquireNotification, "InquireNotification"}, | 22 | {0x000B0040, InquireNotification, "InquireNotification"}, |
| 23 | {0x000C0104, SendParameter, "SendParameter"}, | 23 | {0x000C0104, SendParameter, "SendParameter"}, |
| @@ -34,7 +34,7 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 34 | {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, | 34 | {0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, |
| 35 | {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, | 35 | {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, |
| 36 | {0x00190040, nullptr, "PrepareToStartSystemApplet"}, | 36 | {0x00190040, nullptr, "PrepareToStartSystemApplet"}, |
| 37 | {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"}, | 37 | {0x001A0000, PrepareToStartNewestHomeMenu, "PrepareToStartNewestHomeMenu"}, |
| 38 | {0x001B00C4, nullptr, "StartApplication"}, | 38 | {0x001B00C4, nullptr, "StartApplication"}, |
| 39 | {0x001C0000, nullptr, "WakeupApplication"}, | 39 | {0x001C0000, nullptr, "WakeupApplication"}, |
| 40 | {0x001D0000, nullptr, "CancelApplication"}, | 40 | {0x001D0000, nullptr, "CancelApplication"}, |
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp index c9f9e9d95..8172edae8 100644 --- a/src/core/hle/service/cam/cam.cpp +++ b/src/core/hle/service/cam/cam.cpp | |||
| @@ -177,7 +177,7 @@ void CompletionEventCallBack(u64 port_id, int) { | |||
| 177 | LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!", | 177 | LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!", |
| 178 | port.dest_size, buffer_size); | 178 | port.dest_size, buffer_size); |
| 179 | } | 179 | } |
| 180 | Memory::WriteBlock(port.dest, buffer.data(), std::min<u32>(port.dest_size, buffer_size)); | 180 | Memory::WriteBlock(port.dest, buffer.data(), std::min<size_t>(port.dest_size, buffer_size)); |
| 181 | } | 181 | } |
| 182 | 182 | ||
| 183 | port.is_receiving = false; | 183 | port.is_receiving = false; |
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index f26a1f65f..f78c25fb2 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp | |||
| @@ -141,7 +141,7 @@ void GetCountryCodeString(Service::Interface* self) { | |||
| 141 | 141 | ||
| 142 | void GetCountryCodeID(Service::Interface* self) { | 142 | void GetCountryCodeID(Service::Interface* self) { |
| 143 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 143 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 144 | u16 country_code = cmd_buff[1]; | 144 | u16 country_code = static_cast<u16>(cmd_buff[1]); |
| 145 | u16 country_code_id = 0; | 145 | u16 country_code_id = 0; |
| 146 | 146 | ||
| 147 | // The following algorithm will fail if the first country code isn't 0. | 147 | // The following algorithm will fail if the first country code isn't 0. |
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 033fbc9aa..4ee7df73c 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp | |||
| @@ -20,6 +20,7 @@ | |||
| 20 | #include "core/file_sys/archive_savedata.h" | 20 | #include "core/file_sys/archive_savedata.h" |
| 21 | #include "core/file_sys/archive_sdmc.h" | 21 | #include "core/file_sys/archive_sdmc.h" |
| 22 | #include "core/file_sys/archive_sdmcwriteonly.h" | 22 | #include "core/file_sys/archive_sdmcwriteonly.h" |
| 23 | #include "core/file_sys/archive_selfncch.h" | ||
| 23 | #include "core/file_sys/archive_systemsavedata.h" | 24 | #include "core/file_sys/archive_systemsavedata.h" |
| 24 | #include "core/file_sys/directory_backend.h" | 25 | #include "core/file_sys/directory_backend.h" |
| 25 | #include "core/file_sys/errors.h" | 26 | #include "core/file_sys/errors.h" |
| @@ -48,7 +49,7 @@ struct hash<Service::FS::ArchiveIdCode> { | |||
| 48 | return std::hash<Type>()(static_cast<Type>(id_code)); | 49 | return std::hash<Type>()(static_cast<Type>(id_code)); |
| 49 | } | 50 | } |
| 50 | }; | 51 | }; |
| 51 | } | 52 | } // namespace std |
| 52 | 53 | ||
| 53 | static constexpr Kernel::Handle INVALID_HANDLE{}; | 54 | static constexpr Kernel::Handle INVALID_HANDLE{}; |
| 54 | 55 | ||
| @@ -216,7 +217,7 @@ void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> serve | |||
| 216 | LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count); | 217 | LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count); |
| 217 | 218 | ||
| 218 | // Number of entries actually read | 219 | // Number of entries actually read |
| 219 | u32 read = backend->Read(entries.size(), entries.data()); | 220 | u32 read = backend->Read(static_cast<u32>(entries.size()), entries.data()); |
| 220 | cmd_buff[2] = read; | 221 | cmd_buff[2] = read; |
| 221 | Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry)); | 222 | Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry)); |
| 222 | break; | 223 | break; |
| @@ -564,6 +565,21 @@ void RegisterArchiveTypes() { | |||
| 564 | auto systemsavedata_factory = | 565 | auto systemsavedata_factory = |
| 565 | std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); | 566 | std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory); |
| 566 | RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); | 567 | RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); |
| 568 | |||
| 569 | auto selfncch_factory = std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(); | ||
| 570 | RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH); | ||
| 571 | } | ||
| 572 | |||
| 573 | void RegisterSelfNCCH(Loader::AppLoader& app_loader) { | ||
| 574 | auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH); | ||
| 575 | if (itr == id_code_map.end()) { | ||
| 576 | LOG_ERROR(Service_FS, | ||
| 577 | "Could not register a new NCCH because the SelfNCCH archive hasn't been created"); | ||
| 578 | return; | ||
| 579 | } | ||
| 580 | |||
| 581 | auto* factory = static_cast<FileSys::ArchiveFactory_SelfNCCH*>(itr->second.get()); | ||
| 582 | factory->Register(app_loader); | ||
| 567 | } | 583 | } |
| 568 | 584 | ||
| 569 | void UnregisterArchiveTypes() { | 585 | void UnregisterArchiveTypes() { |
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 3a3371c88..e3c8fc2ef 100644 --- a/src/core/hle/service/fs/archive.h +++ b/src/core/hle/service/fs/archive.h | |||
| @@ -21,6 +21,10 @@ static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"}; | |||
| 21 | /// The scrambled SD card CID, also known as ID1 | 21 | /// The scrambled SD card CID, also known as ID1 |
| 22 | static constexpr char SDCARD_ID[]{"00000000000000000000000000000000"}; | 22 | static constexpr char SDCARD_ID[]{"00000000000000000000000000000000"}; |
| 23 | 23 | ||
| 24 | namespace Loader { | ||
| 25 | class AppLoader; | ||
| 26 | } | ||
| 27 | |||
| 24 | namespace Service { | 28 | namespace Service { |
| 25 | namespace FS { | 29 | namespace FS { |
| 26 | 30 | ||
| @@ -259,6 +263,9 @@ void ArchiveInit(); | |||
| 259 | /// Shutdown archives | 263 | /// Shutdown archives |
| 260 | void ArchiveShutdown(); | 264 | void ArchiveShutdown(); |
| 261 | 265 | ||
| 266 | /// Registers a new NCCH file with the SelfNCCH archive factory | ||
| 267 | void RegisterSelfNCCH(Loader::AppLoader& app_loader); | ||
| 268 | |||
| 262 | /// Register all archive types | 269 | /// Register all archive types |
| 263 | void RegisterArchiveTypes(); | 270 | void RegisterArchiveTypes(); |
| 264 | 271 | ||
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index aa5d821f9..379fbd71c 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -251,7 +251,7 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) { | |||
| 251 | Math::Vec3<float> gyro; | 251 | Math::Vec3<float> gyro; |
| 252 | std::tie(std::ignore, gyro) = motion_device->GetStatus(); | 252 | std::tie(std::ignore, gyro) = motion_device->GetStatus(); |
| 253 | double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); | 253 | double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale(); |
| 254 | gyro *= gyroscope_coef * stretch; | 254 | gyro *= gyroscope_coef * static_cast<float>(stretch); |
| 255 | gyroscope_entry.x = static_cast<s16>(gyro.x); | 255 | gyroscope_entry.x = static_cast<s16>(gyro.x); |
| 256 | gyroscope_entry.y = static_cast<s16>(gyro.y); | 256 | gyroscope_entry.y = static_cast<s16>(gyro.y); |
| 257 | gyroscope_entry.z = static_cast<s16>(gyro.z); | 257 | gyroscope_entry.z = static_cast<s16>(gyro.z); |
diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h index 3bc10dbdc..57b4fb6df 100644 --- a/src/core/hle/service/ldr_ro/cro_helper.h +++ b/src/core/hle/service/ldr_ro/cro_helper.h | |||
| @@ -413,7 +413,8 @@ private: | |||
| 413 | */ | 413 | */ |
| 414 | template <typename T> | 414 | template <typename T> |
| 415 | void GetEntry(std::size_t index, T& data) const { | 415 | void GetEntry(std::size_t index, T& data) const { |
| 416 | Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); | 416 | Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + static_cast<u32>(index * sizeof(T)), |
| 417 | &data, sizeof(T)); | ||
| 417 | } | 418 | } |
| 418 | 419 | ||
| 419 | /** | 420 | /** |
| @@ -425,7 +426,8 @@ private: | |||
| 425 | */ | 426 | */ |
| 426 | template <typename T> | 427 | template <typename T> |
| 427 | void SetEntry(std::size_t index, const T& data) { | 428 | void SetEntry(std::size_t index, const T& data) { |
| 428 | Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T)); | 429 | Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + static_cast<u32>(index * sizeof(T)), |
| 430 | &data, sizeof(T)); | ||
| 429 | } | 431 | } |
| 430 | 432 | ||
| 431 | /** | 433 | /** |
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp index d5624fe54..b10d5852b 100644 --- a/src/core/hle/service/nim/nim.cpp +++ b/src/core/hle/service/nim/nim.cpp | |||
| @@ -5,6 +5,8 @@ | |||
| 5 | #include "common/common_types.h" | 5 | #include "common/common_types.h" |
| 6 | #include "common/logging/log.h" | 6 | #include "common/logging/log.h" |
| 7 | #include "core/hle/ipc.h" | 7 | #include "core/hle/ipc.h" |
| 8 | #include "core/hle/ipc_helpers.h" | ||
| 9 | #include "core/hle/kernel/event.h" | ||
| 8 | #include "core/hle/service/nim/nim.h" | 10 | #include "core/hle/service/nim/nim.h" |
| 9 | #include "core/hle/service/nim/nim_aoc.h" | 11 | #include "core/hle/service/nim/nim_aoc.h" |
| 10 | #include "core/hle/service/nim/nim_s.h" | 12 | #include "core/hle/service/nim/nim_s.h" |
| @@ -14,6 +16,16 @@ | |||
| 14 | namespace Service { | 16 | namespace Service { |
| 15 | namespace NIM { | 17 | namespace NIM { |
| 16 | 18 | ||
| 19 | static Kernel::SharedPtr<Kernel::Event> nim_system_update_event; | ||
| 20 | |||
| 21 | void CheckForSysUpdateEvent(Service::Interface* self) { | ||
| 22 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); // 0x50000 | ||
| 23 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||
| 24 | rb.Push(RESULT_SUCCESS); | ||
| 25 | rb.PushCopyHandles(Kernel::g_handle_table.Create(nim_system_update_event).Unwrap()); | ||
| 26 | LOG_TRACE(Service_NIM, "called"); | ||
| 27 | } | ||
| 28 | |||
| 17 | void CheckSysUpdateAvailable(Service::Interface* self) { | 29 | void CheckSysUpdateAvailable(Service::Interface* self) { |
| 18 | u32* cmd_buff = Kernel::GetCommandBuffer(); | 30 | u32* cmd_buff = Kernel::GetCommandBuffer(); |
| 19 | 31 | ||
| @@ -29,9 +41,13 @@ void Init() { | |||
| 29 | AddService(new NIM_AOC_Interface); | 41 | AddService(new NIM_AOC_Interface); |
| 30 | AddService(new NIM_S_Interface); | 42 | AddService(new NIM_S_Interface); |
| 31 | AddService(new NIM_U_Interface); | 43 | AddService(new NIM_U_Interface); |
| 44 | |||
| 45 | nim_system_update_event = Kernel::Event::Create(ResetType::OneShot, "NIM System Update Event"); | ||
| 32 | } | 46 | } |
| 33 | 47 | ||
| 34 | void Shutdown() {} | 48 | void Shutdown() { |
| 49 | nim_system_update_event = nullptr; | ||
| 50 | } | ||
| 35 | 51 | ||
| 36 | } // namespace NIM | 52 | } // namespace NIM |
| 37 | 53 | ||
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h index c3106f18b..dbf605e5a 100644 --- a/src/core/hle/service/nim/nim.h +++ b/src/core/hle/service/nim/nim.h | |||
| @@ -11,6 +11,17 @@ class Interface; | |||
| 11 | namespace NIM { | 11 | namespace NIM { |
| 12 | 12 | ||
| 13 | /** | 13 | /** |
| 14 | * NIM::CheckForSysUpdateEvent service function | ||
| 15 | * Inputs: | ||
| 16 | * 1 : None | ||
| 17 | * Outputs: | ||
| 18 | * 1 : Result of function, 0 on success, otherwise error code | ||
| 19 | * 2 : Copy handle descriptor | ||
| 20 | * 3 : System Update event handle | ||
| 21 | */ | ||
| 22 | void CheckForSysUpdateEvent(Service::Interface* self); | ||
| 23 | |||
| 24 | /** | ||
| 14 | * NIM::CheckSysUpdateAvailable service function | 25 | * NIM::CheckSysUpdateAvailable service function |
| 15 | * Inputs: | 26 | * Inputs: |
| 16 | * 1 : None | 27 | * 1 : None |
diff --git a/src/core/hle/service/nim/nim_u.cpp b/src/core/hle/service/nim/nim_u.cpp index 7664bad60..569660278 100644 --- a/src/core/hle/service/nim/nim_u.cpp +++ b/src/core/hle/service/nim/nim_u.cpp | |||
| @@ -12,7 +12,7 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 12 | {0x00010000, nullptr, "StartSysUpdate"}, | 12 | {0x00010000, nullptr, "StartSysUpdate"}, |
| 13 | {0x00020000, nullptr, "GetUpdateDownloadProgress"}, | 13 | {0x00020000, nullptr, "GetUpdateDownloadProgress"}, |
| 14 | {0x00040000, nullptr, "FinishTitlesInstall"}, | 14 | {0x00040000, nullptr, "FinishTitlesInstall"}, |
| 15 | {0x00050000, nullptr, "CheckForSysUpdateEvent"}, | 15 | {0x00050000, CheckForSysUpdateEvent, "CheckForSysUpdateEvent"}, |
| 16 | {0x00090000, CheckSysUpdateAvailable, "CheckSysUpdateAvailable"}, | 16 | {0x00090000, CheckSysUpdateAvailable, "CheckSysUpdateAvailable"}, |
| 17 | {0x000A0000, nullptr, "GetState"}, | 17 | {0x000A0000, nullptr, "GetState"}, |
| 18 | {0x000B0000, nullptr, "GetSystemTitleHash"}, | 18 | {0x000B0000, nullptr, "GetSystemTitleHash"}, |
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 893bbb1e7..0aa63cc1e 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp | |||
| @@ -2,8 +2,10 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | ||
| 5 | #include <array> | 6 | #include <array> |
| 6 | #include <cstring> | 7 | #include <cstring> |
| 8 | #include <list> | ||
| 7 | #include <mutex> | 9 | #include <mutex> |
| 8 | #include <unordered_map> | 10 | #include <unordered_map> |
| 9 | #include <vector> | 11 | #include <vector> |
| @@ -13,6 +15,7 @@ | |||
| 13 | #include "core/hle/ipc_helpers.h" | 15 | #include "core/hle/ipc_helpers.h" |
| 14 | #include "core/hle/kernel/event.h" | 16 | #include "core/hle/kernel/event.h" |
| 15 | #include "core/hle/kernel/shared_memory.h" | 17 | #include "core/hle/kernel/shared_memory.h" |
| 18 | #include "core/hle/lock.h" | ||
| 16 | #include "core/hle/result.h" | 19 | #include "core/hle/result.h" |
| 17 | #include "core/hle/service/nwm/nwm_uds.h" | 20 | #include "core/hle/service/nwm/nwm_uds.h" |
| 18 | #include "core/hle/service/nwm/uds_beacon.h" | 21 | #include "core/hle/service/nwm/uds_beacon.h" |
| @@ -37,9 +40,12 @@ static ConnectionStatus connection_status{}; | |||
| 37 | /* Node information about the current network. | 40 | /* Node information about the current network. |
| 38 | * The amount of elements in this vector is always the maximum number | 41 | * The amount of elements in this vector is always the maximum number |
| 39 | * of nodes specified in the network configuration. | 42 | * of nodes specified in the network configuration. |
| 40 | * The first node is always the host, so this always contains at least 1 entry. | 43 | * The first node is always the host. |
| 41 | */ | 44 | */ |
| 42 | static NodeList node_info(1); | 45 | static NodeList node_info; |
| 46 | |||
| 47 | // Node information about our own system. | ||
| 48 | static NodeInfo current_node; | ||
| 43 | 49 | ||
| 44 | // Mapping of bind node ids to their respective events. | 50 | // Mapping of bind node ids to their respective events. |
| 45 | static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; | 51 | static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; |
| @@ -54,6 +60,10 @@ static NetworkInfo network_info; | |||
| 54 | // Event that will generate and send the 802.11 beacon frames. | 60 | // Event that will generate and send the 802.11 beacon frames. |
| 55 | static int beacon_broadcast_event; | 61 | static int beacon_broadcast_event; |
| 56 | 62 | ||
| 63 | // Mutex to synchronize access to the connection status between the emulation thread and the | ||
| 64 | // network thread. | ||
| 65 | static std::mutex connection_status_mutex; | ||
| 66 | |||
| 57 | // Mutex to synchronize access to the list of received beacons between the emulation thread and the | 67 | // Mutex to synchronize access to the list of received beacons between the emulation thread and the |
| 58 | // network thread. | 68 | // network thread. |
| 59 | static std::mutex beacon_mutex; | 69 | static std::mutex beacon_mutex; |
| @@ -63,14 +73,26 @@ static std::mutex beacon_mutex; | |||
| 63 | constexpr size_t MaxBeaconFrames = 15; | 73 | constexpr size_t MaxBeaconFrames = 15; |
| 64 | 74 | ||
| 65 | // List of the last <MaxBeaconFrames> beacons received from the network. | 75 | // List of the last <MaxBeaconFrames> beacons received from the network. |
| 66 | static std::deque<Network::WifiPacket> received_beacons; | 76 | static std::list<Network::WifiPacket> received_beacons; |
| 67 | 77 | ||
| 68 | /** | 78 | /** |
| 69 | * Returns a list of received 802.11 beacon frames from the specified sender since the last call. | 79 | * Returns a list of received 802.11 beacon frames from the specified sender since the last call. |
| 70 | */ | 80 | */ |
| 71 | std::deque<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) { | 81 | std::list<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) { |
| 72 | std::lock_guard<std::mutex> lock(beacon_mutex); | 82 | std::lock_guard<std::mutex> lock(beacon_mutex); |
| 73 | // TODO(Subv): Filter by sender. | 83 | if (sender != Network::BroadcastMac) { |
| 84 | std::list<Network::WifiPacket> filtered_list; | ||
| 85 | const auto beacon = std::find_if(received_beacons.begin(), received_beacons.end(), | ||
| 86 | [&sender](const Network::WifiPacket& packet) { | ||
| 87 | return packet.transmitter_address == sender; | ||
| 88 | }); | ||
| 89 | if (beacon != received_beacons.end()) { | ||
| 90 | filtered_list.push_back(*beacon); | ||
| 91 | // TODO(B3N30): Check if the complete deque is cleared or just the fetched entries | ||
| 92 | received_beacons.erase(beacon); | ||
| 93 | } | ||
| 94 | return filtered_list; | ||
| 95 | } | ||
| 74 | return std::move(received_beacons); | 96 | return std::move(received_beacons); |
| 75 | } | 97 | } |
| 76 | 98 | ||
| @@ -79,10 +101,33 @@ void SendPacket(Network::WifiPacket& packet) { | |||
| 79 | // TODO(Subv): Implement. | 101 | // TODO(Subv): Implement. |
| 80 | } | 102 | } |
| 81 | 103 | ||
| 104 | /* | ||
| 105 | * Returns an available index in the nodes array for the | ||
| 106 | * currently-hosted UDS network. | ||
| 107 | */ | ||
| 108 | static u16 GetNextAvailableNodeId() { | ||
| 109 | for (u16 index = 0; index < connection_status.max_nodes; ++index) { | ||
| 110 | if ((connection_status.node_bitmask & (1 << index)) == 0) | ||
| 111 | return index; | ||
| 112 | } | ||
| 113 | |||
| 114 | // Any connection attempts to an already full network should have been refused. | ||
| 115 | ASSERT_MSG(false, "No available connection slots in the network"); | ||
| 116 | } | ||
| 117 | |||
| 82 | // Inserts the received beacon frame in the beacon queue and removes any older beacons if the size | 118 | // Inserts the received beacon frame in the beacon queue and removes any older beacons if the size |
| 83 | // limit is exceeded. | 119 | // limit is exceeded. |
| 84 | void HandleBeaconFrame(const Network::WifiPacket& packet) { | 120 | void HandleBeaconFrame(const Network::WifiPacket& packet) { |
| 85 | std::lock_guard<std::mutex> lock(beacon_mutex); | 121 | std::lock_guard<std::mutex> lock(beacon_mutex); |
| 122 | const auto unique_beacon = | ||
| 123 | std::find_if(received_beacons.begin(), received_beacons.end(), | ||
| 124 | [&packet](const Network::WifiPacket& new_packet) { | ||
| 125 | return new_packet.transmitter_address == packet.transmitter_address; | ||
| 126 | }); | ||
| 127 | if (unique_beacon != received_beacons.end()) { | ||
| 128 | // We already have a beacon from the same mac in the deque, remove the old one; | ||
| 129 | received_beacons.erase(unique_beacon); | ||
| 130 | } | ||
| 86 | 131 | ||
| 87 | received_beacons.emplace_back(packet); | 132 | received_beacons.emplace_back(packet); |
| 88 | 133 | ||
| @@ -91,21 +136,110 @@ void HandleBeaconFrame(const Network::WifiPacket& packet) { | |||
| 91 | received_beacons.pop_front(); | 136 | received_beacons.pop_front(); |
| 92 | } | 137 | } |
| 93 | 138 | ||
| 94 | /* | 139 | void HandleAssociationResponseFrame(const Network::WifiPacket& packet) { |
| 95 | * Returns an available index in the nodes array for the | 140 | auto assoc_result = GetAssociationResult(packet.data); |
| 96 | * currently-hosted UDS network. | ||
| 97 | */ | ||
| 98 | static u16 GetNextAvailableNodeId() { | ||
| 99 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost), | ||
| 100 | "Can not accept clients if we're not hosting a network"); | ||
| 101 | 141 | ||
| 102 | for (u16 index = 0; index < connection_status.max_nodes; ++index) { | 142 | ASSERT_MSG(std::get<AssocStatus>(assoc_result) == AssocStatus::Successful, |
| 103 | if ((connection_status.node_bitmask & (1 << index)) == 0) | 143 | "Could not join network"); |
| 104 | return index; | 144 | { |
| 145 | std::lock_guard<std::mutex> lock(connection_status_mutex); | ||
| 146 | ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::Connecting)); | ||
| 105 | } | 147 | } |
| 106 | 148 | ||
| 107 | // Any connection attempts to an already full network should have been refused. | 149 | // Send the EAPoL-Start packet to the server. |
| 108 | ASSERT_MSG(false, "No available connection slots in the network"); | 150 | using Network::WifiPacket; |
| 151 | WifiPacket eapol_start; | ||
| 152 | eapol_start.channel = network_channel; | ||
| 153 | eapol_start.data = GenerateEAPoLStartFrame(std::get<u16>(assoc_result), current_node); | ||
| 154 | // TODO(B3N30): Encrypt the packet. | ||
| 155 | eapol_start.destination_address = packet.transmitter_address; | ||
| 156 | eapol_start.type = WifiPacket::PacketType::Data; | ||
| 157 | |||
| 158 | SendPacket(eapol_start); | ||
| 159 | } | ||
| 160 | |||
| 161 | static void HandleEAPoLPacket(const Network::WifiPacket& packet) { | ||
| 162 | std::lock_guard<std::mutex> lock(connection_status_mutex); | ||
| 163 | |||
| 164 | if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) { | ||
| 165 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) { | ||
| 166 | LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u", | ||
| 167 | connection_status.status); | ||
| 168 | return; | ||
| 169 | } | ||
| 170 | |||
| 171 | auto node = DeserializeNodeInfoFromFrame(packet.data); | ||
| 172 | |||
| 173 | if (connection_status.max_nodes == connection_status.total_nodes) { | ||
| 174 | // Reject connection attempt | ||
| 175 | LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent."); | ||
| 176 | // TODO(B3N30): Figure out what packet is sent here | ||
| 177 | return; | ||
| 178 | } | ||
| 179 | |||
| 180 | // Get an unused network node id | ||
| 181 | u16 node_id = GetNextAvailableNodeId(); | ||
| 182 | node.network_node_id = node_id + 1; | ||
| 183 | |||
| 184 | connection_status.node_bitmask |= 1 << node_id; | ||
| 185 | connection_status.changed_nodes |= 1 << node_id; | ||
| 186 | connection_status.nodes[node_id] = node.network_node_id; | ||
| 187 | connection_status.total_nodes++; | ||
| 188 | |||
| 189 | u8 current_nodes = network_info.total_nodes; | ||
| 190 | node_info[current_nodes] = node; | ||
| 191 | |||
| 192 | network_info.total_nodes++; | ||
| 193 | |||
| 194 | // Send the EAPoL-Logoff packet. | ||
| 195 | using Network::WifiPacket; | ||
| 196 | WifiPacket eapol_logoff; | ||
| 197 | eapol_logoff.channel = network_channel; | ||
| 198 | eapol_logoff.data = | ||
| 199 | GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info, | ||
| 200 | network_info.max_nodes, network_info.total_nodes); | ||
| 201 | // TODO(Subv): Encrypt the packet. | ||
| 202 | eapol_logoff.destination_address = packet.transmitter_address; | ||
| 203 | eapol_logoff.type = WifiPacket::PacketType::Data; | ||
| 204 | |||
| 205 | SendPacket(eapol_logoff); | ||
| 206 | // TODO(B3N30): Broadcast updated node list | ||
| 207 | // The 3ds does this presumably to support spectators. | ||
| 208 | std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); | ||
| 209 | connection_status_event->Signal(); | ||
| 210 | } else { | ||
| 211 | if (connection_status.status != static_cast<u32>(NetworkStatus::NotConnected)) { | ||
| 212 | LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u", | ||
| 213 | connection_status.status); | ||
| 214 | return; | ||
| 215 | } | ||
| 216 | auto logoff = ParseEAPoLLogoffFrame(packet.data); | ||
| 217 | |||
| 218 | network_info.total_nodes = logoff.connected_nodes; | ||
| 219 | network_info.max_nodes = logoff.max_nodes; | ||
| 220 | |||
| 221 | connection_status.network_node_id = logoff.assigned_node_id; | ||
| 222 | connection_status.total_nodes = logoff.connected_nodes; | ||
| 223 | connection_status.max_nodes = logoff.max_nodes; | ||
| 224 | |||
| 225 | node_info.clear(); | ||
| 226 | node_info.reserve(network_info.max_nodes); | ||
| 227 | for (size_t index = 0; index < logoff.connected_nodes; ++index) { | ||
| 228 | connection_status.node_bitmask |= 1 << index; | ||
| 229 | connection_status.changed_nodes |= 1 << index; | ||
| 230 | connection_status.nodes[index] = logoff.nodes[index].network_node_id; | ||
| 231 | |||
| 232 | node_info.emplace_back(DeserializeNodeInfo(logoff.nodes[index])); | ||
| 233 | } | ||
| 234 | |||
| 235 | // We're now connected, signal the application | ||
| 236 | connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsClient); | ||
| 237 | // Some games require ConnectToNetwork to block, for now it doesn't | ||
| 238 | // If blocking is implemented this lock needs to be changed, | ||
| 239 | // otherwise it might cause deadlocks | ||
| 240 | std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); | ||
| 241 | connection_status_event->Signal(); | ||
| 242 | } | ||
| 109 | } | 243 | } |
| 110 | 244 | ||
| 111 | /* | 245 | /* |
| @@ -113,35 +247,46 @@ static u16 GetNextAvailableNodeId() { | |||
| 113 | * authentication frame with SEQ1. | 247 | * authentication frame with SEQ1. |
| 114 | */ | 248 | */ |
| 115 | void StartConnectionSequence(const MacAddress& server) { | 249 | void StartConnectionSequence(const MacAddress& server) { |
| 116 | ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected)); | ||
| 117 | |||
| 118 | // TODO(Subv): Handle timeout. | ||
| 119 | |||
| 120 | // Send an authentication frame with SEQ1 | ||
| 121 | using Network::WifiPacket; | 250 | using Network::WifiPacket; |
| 122 | WifiPacket auth_request; | 251 | WifiPacket auth_request; |
| 123 | auth_request.channel = network_channel; | 252 | { |
| 124 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); | 253 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 125 | auth_request.destination_address = server; | 254 | ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected)); |
| 126 | auth_request.type = WifiPacket::PacketType::Authentication; | 255 | |
| 256 | // TODO(Subv): Handle timeout. | ||
| 257 | |||
| 258 | // Send an authentication frame with SEQ1 | ||
| 259 | auth_request.channel = network_channel; | ||
| 260 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); | ||
| 261 | auth_request.destination_address = server; | ||
| 262 | auth_request.type = WifiPacket::PacketType::Authentication; | ||
| 263 | } | ||
| 127 | 264 | ||
| 128 | SendPacket(auth_request); | 265 | SendPacket(auth_request); |
| 129 | } | 266 | } |
| 130 | 267 | ||
| 131 | /// Sends an Association Response frame to the specified mac address | 268 | /// Sends an Association Response frame to the specified mac address |
| 132 | void SendAssociationResponseFrame(const MacAddress& address) { | 269 | void SendAssociationResponseFrame(const MacAddress& address) { |
| 133 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost)); | ||
| 134 | |||
| 135 | using Network::WifiPacket; | 270 | using Network::WifiPacket; |
| 136 | WifiPacket assoc_response; | 271 | WifiPacket assoc_response; |
| 137 | assoc_response.channel = network_channel; | 272 | |
| 138 | // TODO(Subv): This will cause multiple clients to end up with the same association id, but | 273 | { |
| 139 | // we're not using that for anything. | 274 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 140 | u16 association_id = 1; | 275 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) { |
| 141 | assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, | 276 | LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is %u", |
| 142 | network_info.network_id); | 277 | connection_status.status); |
| 143 | assoc_response.destination_address = address; | 278 | return; |
| 144 | assoc_response.type = WifiPacket::PacketType::AssociationResponse; | 279 | } |
| 280 | |||
| 281 | assoc_response.channel = network_channel; | ||
| 282 | // TODO(Subv): This will cause multiple clients to end up with the same association id, but | ||
| 283 | // we're not using that for anything. | ||
| 284 | u16 association_id = 1; | ||
| 285 | assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, | ||
| 286 | network_info.network_id); | ||
| 287 | assoc_response.destination_address = address; | ||
| 288 | assoc_response.type = WifiPacket::PacketType::AssociationResponse; | ||
| 289 | } | ||
| 145 | 290 | ||
| 146 | SendPacket(assoc_response); | 291 | SendPacket(assoc_response); |
| 147 | } | 292 | } |
| @@ -155,22 +300,40 @@ void SendAssociationResponseFrame(const MacAddress& address) { | |||
| 155 | void HandleAuthenticationFrame(const Network::WifiPacket& packet) { | 300 | void HandleAuthenticationFrame(const Network::WifiPacket& packet) { |
| 156 | // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior | 301 | // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior |
| 157 | if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { | 302 | if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { |
| 158 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost)); | ||
| 159 | |||
| 160 | // Respond with an authentication response frame with SEQ2 | ||
| 161 | using Network::WifiPacket; | 303 | using Network::WifiPacket; |
| 162 | WifiPacket auth_request; | 304 | WifiPacket auth_request; |
| 163 | auth_request.channel = network_channel; | 305 | { |
| 164 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); | 306 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 165 | auth_request.destination_address = packet.transmitter_address; | 307 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) { |
| 166 | auth_request.type = WifiPacket::PacketType::Authentication; | 308 | LOG_ERROR(Service_NWM, |
| 167 | 309 | "Connection sequence aborted, because connection status is %u", | |
| 310 | connection_status.status); | ||
| 311 | return; | ||
| 312 | } | ||
| 313 | |||
| 314 | // Respond with an authentication response frame with SEQ2 | ||
| 315 | auth_request.channel = network_channel; | ||
| 316 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); | ||
| 317 | auth_request.destination_address = packet.transmitter_address; | ||
| 318 | auth_request.type = WifiPacket::PacketType::Authentication; | ||
| 319 | } | ||
| 168 | SendPacket(auth_request); | 320 | SendPacket(auth_request); |
| 169 | 321 | ||
| 170 | SendAssociationResponseFrame(packet.transmitter_address); | 322 | SendAssociationResponseFrame(packet.transmitter_address); |
| 171 | } | 323 | } |
| 172 | } | 324 | } |
| 173 | 325 | ||
| 326 | static void HandleDataFrame(const Network::WifiPacket& packet) { | ||
| 327 | switch (GetFrameEtherType(packet.data)) { | ||
| 328 | case EtherType::EAPoL: | ||
| 329 | HandleEAPoLPacket(packet); | ||
| 330 | break; | ||
| 331 | case EtherType::SecureData: | ||
| 332 | // TODO(B3N30): Handle SecureData packets | ||
| 333 | break; | ||
| 334 | } | ||
| 335 | } | ||
| 336 | |||
| 174 | /// Callback to parse and handle a received wifi packet. | 337 | /// Callback to parse and handle a received wifi packet. |
| 175 | void OnWifiPacketReceived(const Network::WifiPacket& packet) { | 338 | void OnWifiPacketReceived(const Network::WifiPacket& packet) { |
| 176 | switch (packet.type) { | 339 | switch (packet.type) { |
| @@ -180,6 +343,12 @@ void OnWifiPacketReceived(const Network::WifiPacket& packet) { | |||
| 180 | case Network::WifiPacket::PacketType::Authentication: | 343 | case Network::WifiPacket::PacketType::Authentication: |
| 181 | HandleAuthenticationFrame(packet); | 344 | HandleAuthenticationFrame(packet); |
| 182 | break; | 345 | break; |
| 346 | case Network::WifiPacket::PacketType::AssociationResponse: | ||
| 347 | HandleAssociationResponseFrame(packet); | ||
| 348 | break; | ||
| 349 | case Network::WifiPacket::PacketType::Data: | ||
| 350 | HandleDataFrame(packet); | ||
| 351 | break; | ||
| 183 | } | 352 | } |
| 184 | } | 353 | } |
| 185 | 354 | ||
| @@ -246,7 +415,7 @@ static void RecvBeaconBroadcastData(Interface* self) { | |||
| 246 | auto beacons = GetReceivedBeacons(mac_address); | 415 | auto beacons = GetReceivedBeacons(mac_address); |
| 247 | 416 | ||
| 248 | BeaconDataReplyHeader data_reply_header{}; | 417 | BeaconDataReplyHeader data_reply_header{}; |
| 249 | data_reply_header.total_entries = beacons.size(); | 418 | data_reply_header.total_entries = static_cast<u32>(beacons.size()); |
| 250 | data_reply_header.max_output_size = out_buffer_size; | 419 | data_reply_header.max_output_size = out_buffer_size; |
| 251 | 420 | ||
| 252 | Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); | 421 | Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); |
| @@ -256,8 +425,8 @@ static void RecvBeaconBroadcastData(Interface* self) { | |||
| 256 | for (const auto& beacon : beacons) { | 425 | for (const auto& beacon : beacons) { |
| 257 | BeaconEntryHeader entry{}; | 426 | BeaconEntryHeader entry{}; |
| 258 | // TODO(Subv): Figure out what this size is used for. | 427 | // TODO(Subv): Figure out what this size is used for. |
| 259 | entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size(); | 428 | entry.unk_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size()); |
| 260 | entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size(); | 429 | entry.total_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size()); |
| 261 | entry.wifi_channel = beacon.channel; | 430 | entry.wifi_channel = beacon.channel; |
| 262 | entry.header_size = sizeof(BeaconEntryHeader); | 431 | entry.header_size = sizeof(BeaconEntryHeader); |
| 263 | entry.mac_address = beacon.transmitter_address; | 432 | entry.mac_address = beacon.transmitter_address; |
| @@ -268,9 +437,9 @@ static void RecvBeaconBroadcastData(Interface* self) { | |||
| 268 | current_buffer_pos += sizeof(BeaconEntryHeader); | 437 | current_buffer_pos += sizeof(BeaconEntryHeader); |
| 269 | 438 | ||
| 270 | Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); | 439 | Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); |
| 271 | current_buffer_pos += beacon.data.size(); | 440 | current_buffer_pos += static_cast<VAddr>(beacon.data.size()); |
| 272 | 441 | ||
| 273 | total_size += sizeof(BeaconEntryHeader) + beacon.data.size(); | 442 | total_size += static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size()); |
| 274 | } | 443 | } |
| 275 | 444 | ||
| 276 | // Update the total size in the structure and write it to the buffer again. | 445 | // Update the total size in the structure and write it to the buffer again. |
| @@ -305,7 +474,7 @@ static void InitializeWithVersion(Interface* self) { | |||
| 305 | u32 sharedmem_size = rp.Pop<u32>(); | 474 | u32 sharedmem_size = rp.Pop<u32>(); |
| 306 | 475 | ||
| 307 | // Update the node information with the data the game gave us. | 476 | // Update the node information with the data the game gave us. |
| 308 | rp.PopRaw(node_info[0]); | 477 | rp.PopRaw(current_node); |
| 309 | 478 | ||
| 310 | u16 version = rp.Pop<u16>(); | 479 | u16 version = rp.Pop<u16>(); |
| 311 | 480 | ||
| @@ -315,10 +484,14 @@ static void InitializeWithVersion(Interface* self) { | |||
| 315 | 484 | ||
| 316 | ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size."); | 485 | ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size."); |
| 317 | 486 | ||
| 318 | // Reset the connection status, it contains all zeros after initialization, | 487 | { |
| 319 | // except for the actual status value. | 488 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 320 | connection_status = {}; | 489 | |
| 321 | connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); | 490 | // Reset the connection status, it contains all zeros after initialization, |
| 491 | // except for the actual status value. | ||
| 492 | connection_status = {}; | ||
| 493 | connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); | ||
| 494 | } | ||
| 322 | 495 | ||
| 323 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | 496 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); |
| 324 | rb.Push(RESULT_SUCCESS); | 497 | rb.Push(RESULT_SUCCESS); |
| @@ -348,12 +521,16 @@ static void GetConnectionStatus(Interface* self) { | |||
| 348 | IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); | 521 | IPC::RequestBuilder rb = rp.MakeBuilder(13, 0); |
| 349 | 522 | ||
| 350 | rb.Push(RESULT_SUCCESS); | 523 | rb.Push(RESULT_SUCCESS); |
| 351 | rb.PushRaw(connection_status); | 524 | { |
| 352 | 525 | std::lock_guard<std::mutex> lock(connection_status_mutex); | |
| 353 | // Reset the bitmask of changed nodes after each call to this | 526 | rb.PushRaw(connection_status); |
| 354 | // function to prevent falsely informing games of outstanding | 527 | |
| 355 | // changes in subsequent calls. | 528 | // Reset the bitmask of changed nodes after each call to this |
| 356 | connection_status.changed_nodes = 0; | 529 | // function to prevent falsely informing games of outstanding |
| 530 | // changes in subsequent calls. | ||
| 531 | // TODO(Subv): Find exactly where the NWM module resets this value. | ||
| 532 | connection_status.changed_nodes = 0; | ||
| 533 | } | ||
| 357 | 534 | ||
| 358 | LOG_DEBUG(Service_NWM, "called"); | 535 | LOG_DEBUG(Service_NWM, "called"); |
| 359 | } | 536 | } |
| @@ -434,31 +611,36 @@ static void BeginHostingNetwork(Interface* self) { | |||
| 434 | // The real UDS module throws a fatal error if this assert fails. | 611 | // The real UDS module throws a fatal error if this assert fails. |
| 435 | ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); | 612 | ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member."); |
| 436 | 613 | ||
| 437 | connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); | 614 | { |
| 438 | 615 | std::lock_guard<std::mutex> lock(connection_status_mutex); | |
| 439 | // Ensure the application data size is less than the maximum value. | 616 | connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost); |
| 440 | ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); | 617 | |
| 441 | 618 | // Ensure the application data size is less than the maximum value. | |
| 442 | // Set up basic information for this network. | 619 | ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, |
| 443 | network_info.oui_value = NintendoOUI; | 620 | "Data size is too big."); |
| 444 | network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo); | 621 | |
| 445 | 622 | // Set up basic information for this network. | |
| 446 | connection_status.max_nodes = network_info.max_nodes; | 623 | network_info.oui_value = NintendoOUI; |
| 447 | 624 | network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo); | |
| 448 | // Resize the nodes list to hold max_nodes. | 625 | |
| 449 | node_info.resize(network_info.max_nodes); | 626 | connection_status.max_nodes = network_info.max_nodes; |
| 450 | 627 | ||
| 451 | // There's currently only one node in the network (the host). | 628 | // Resize the nodes list to hold max_nodes. |
| 452 | connection_status.total_nodes = 1; | 629 | node_info.resize(network_info.max_nodes); |
| 453 | network_info.total_nodes = 1; | 630 | |
| 454 | // The host is always the first node | 631 | // There's currently only one node in the network (the host). |
| 455 | connection_status.network_node_id = 1; | 632 | connection_status.total_nodes = 1; |
| 456 | node_info[0].network_node_id = 1; | 633 | network_info.total_nodes = 1; |
| 457 | connection_status.nodes[0] = connection_status.network_node_id; | 634 | // The host is always the first node |
| 458 | // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. | 635 | connection_status.network_node_id = 1; |
| 459 | connection_status.node_bitmask |= 1; | 636 | current_node.network_node_id = 1; |
| 460 | // Notify the application that the first node was set. | 637 | connection_status.nodes[0] = connection_status.network_node_id; |
| 461 | connection_status.changed_nodes |= 1; | 638 | // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. |
| 639 | connection_status.node_bitmask |= 1; | ||
| 640 | // Notify the application that the first node was set. | ||
| 641 | connection_status.changed_nodes |= 1; | ||
| 642 | node_info[0] = current_node; | ||
| 643 | } | ||
| 462 | 644 | ||
| 463 | // If the game has a preferred channel, use that instead. | 645 | // If the game has a preferred channel, use that instead. |
| 464 | if (network_info.channel != 0) | 646 | if (network_info.channel != 0) |
| @@ -495,9 +677,13 @@ static void DestroyNetwork(Interface* self) { | |||
| 495 | // Unschedule the beacon broadcast event. | 677 | // Unschedule the beacon broadcast event. |
| 496 | CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); | 678 | CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); |
| 497 | 679 | ||
| 498 | // TODO(Subv): Check if connection_status is indeed reset after this call. | 680 | { |
| 499 | connection_status = {}; | 681 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 500 | connection_status.status = static_cast<u8>(NetworkStatus::NotConnected); | 682 | |
| 683 | // TODO(Subv): Check if connection_status is indeed reset after this call. | ||
| 684 | connection_status = {}; | ||
| 685 | connection_status.status = static_cast<u8>(NetworkStatus::NotConnected); | ||
| 686 | } | ||
| 501 | connection_status_event->Signal(); | 687 | connection_status_event->Signal(); |
| 502 | 688 | ||
| 503 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | 689 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); |
| @@ -540,17 +726,24 @@ static void SendTo(Interface* self) { | |||
| 540 | 726 | ||
| 541 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | 727 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); |
| 542 | 728 | ||
| 543 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) && | 729 | u16 network_node_id; |
| 544 | connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) { | ||
| 545 | rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, | ||
| 546 | ErrorSummary::InvalidState, ErrorLevel::Status)); | ||
| 547 | return; | ||
| 548 | } | ||
| 549 | 730 | ||
| 550 | if (dest_node_id == connection_status.network_node_id) { | 731 | { |
| 551 | rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS, | 732 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 552 | ErrorSummary::WrongArgument, ErrorLevel::Status)); | 733 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) && |
| 553 | return; | 734 | connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) { |
| 735 | rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS, | ||
| 736 | ErrorSummary::InvalidState, ErrorLevel::Status)); | ||
| 737 | return; | ||
| 738 | } | ||
| 739 | |||
| 740 | if (dest_node_id == connection_status.network_node_id) { | ||
| 741 | rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS, | ||
| 742 | ErrorSummary::WrongArgument, ErrorLevel::Status)); | ||
| 743 | return; | ||
| 744 | } | ||
| 745 | |||
| 746 | network_node_id = connection_status.network_node_id; | ||
| 554 | } | 747 | } |
| 555 | 748 | ||
| 556 | // TODO(Subv): Do something with the flags. | 749 | // TODO(Subv): Do something with the flags. |
| @@ -567,8 +760,8 @@ static void SendTo(Interface* self) { | |||
| 567 | 760 | ||
| 568 | // TODO(Subv): Increment the sequence number after each sent packet. | 761 | // TODO(Subv): Increment the sequence number after each sent packet. |
| 569 | u16 sequence_number = 0; | 762 | u16 sequence_number = 0; |
| 570 | std::vector<u8> data_payload = GenerateDataPayload( | 763 | std::vector<u8> data_payload = |
| 571 | data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number); | 764 | GenerateDataPayload(data, data_channel, dest_node_id, network_node_id, sequence_number); |
| 572 | 765 | ||
| 573 | // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt | 766 | // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt |
| 574 | // and encapsulate the payload. | 767 | // and encapsulate the payload. |
| @@ -595,6 +788,7 @@ static void GetChannel(Interface* self) { | |||
| 595 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); | 788 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); |
| 596 | IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); | 789 | IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); |
| 597 | 790 | ||
| 791 | std::lock_guard<std::mutex> lock(connection_status_mutex); | ||
| 598 | bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected); | 792 | bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected); |
| 599 | 793 | ||
| 600 | u8 channel = is_connected ? network_channel : 0; | 794 | u8 channel = is_connected ? network_channel : 0; |
| @@ -766,6 +960,7 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { | |||
| 766 | * @param network_node_id Network Node Id of the connecting client. | 960 | * @param network_node_id Network Node Id of the connecting client. |
| 767 | */ | 961 | */ |
| 768 | void OnClientConnected(u16 network_node_id) { | 962 | void OnClientConnected(u16 network_node_id) { |
| 963 | std::lock_guard<std::mutex> lock(connection_status_mutex); | ||
| 769 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost), | 964 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost), |
| 770 | "Can not accept clients if we're not hosting a network"); | 965 | "Can not accept clients if we're not hosting a network"); |
| 771 | ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes, | 966 | ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes, |
| @@ -827,8 +1022,11 @@ NWM_UDS::~NWM_UDS() { | |||
| 827 | connection_status_event = nullptr; | 1022 | connection_status_event = nullptr; |
| 828 | recv_buffer_memory = nullptr; | 1023 | recv_buffer_memory = nullptr; |
| 829 | 1024 | ||
| 830 | connection_status = {}; | 1025 | { |
| 831 | connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); | 1026 | std::lock_guard<std::mutex> lock(connection_status_mutex); |
| 1027 | connection_status = {}; | ||
| 1028 | connection_status.status = static_cast<u32>(NetworkStatus::NotConnected); | ||
| 1029 | } | ||
| 832 | 1030 | ||
| 833 | CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); | 1031 | CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); |
| 834 | } | 1032 | } |
diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp index 552eaf65e..73a80d940 100644 --- a/src/core/hle/service/nwm/uds_beacon.cpp +++ b/src/core/hle/service/nwm/uds_beacon.cpp | |||
| @@ -243,7 +243,7 @@ std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network | |||
| 243 | 243 | ||
| 244 | EncryptedDataTag tag{}; | 244 | EncryptedDataTag tag{}; |
| 245 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | 245 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); |
| 246 | tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; | 246 | tag.header.length = static_cast<u8>(sizeof(tag) - sizeof(TagHeader) + payload_size); |
| 247 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); | 247 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); |
| 248 | tag.oui = NintendoOUI; | 248 | tag.oui = NintendoOUI; |
| 249 | 249 | ||
| @@ -279,7 +279,7 @@ std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& networ | |||
| 279 | 279 | ||
| 280 | EncryptedDataTag tag{}; | 280 | EncryptedDataTag tag{}; |
| 281 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); | 281 | tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); |
| 282 | tag.header.length = tag_length; | 282 | tag.header.length = static_cast<u8>(tag_length); |
| 283 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); | 283 | tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); |
| 284 | tag.oui = NintendoOUI; | 284 | tag.oui = NintendoOUI; |
| 285 | 285 | ||
diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp index c8a76ec2a..c74f51253 100644 --- a/src/core/hle/service/nwm/uds_connection.cpp +++ b/src/core/hle/service/nwm/uds_connection.cpp | |||
| @@ -75,5 +75,14 @@ std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_i | |||
| 75 | return data; | 75 | return data; |
| 76 | } | 76 | } |
| 77 | 77 | ||
| 78 | std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body) { | ||
| 79 | AssociationResponseFrame frame; | ||
| 80 | memcpy(&frame, body.data(), sizeof(frame)); | ||
| 81 | |||
| 82 | constexpr u16 AssociationIdMask = 0x3FFF; | ||
| 83 | return std::make_tuple(static_cast<AssocStatus>(frame.status_code), | ||
| 84 | frame.assoc_id & AssociationIdMask); | ||
| 85 | } | ||
| 86 | |||
| 78 | } // namespace NWM | 87 | } // namespace NWM |
| 79 | } // namespace Service | 88 | } // namespace Service |
diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h index 73f55a4fd..a664f8471 100644 --- a/src/core/hle/service/nwm/uds_connection.h +++ b/src/core/hle/service/nwm/uds_connection.h | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <tuple> | ||
| 7 | #include <vector> | 8 | #include <vector> |
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 9 | #include "common/swap.h" | 10 | #include "common/swap.h" |
| @@ -47,5 +48,9 @@ AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body); | |||
| 47 | /// network id, starting at the frame body. | 48 | /// network id, starting at the frame body. |
| 48 | std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id); | 49 | std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id); |
| 49 | 50 | ||
| 51 | /// Returns a tuple of (association status, association id) from the body of an AssociationResponse | ||
| 52 | /// frame. | ||
| 53 | std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body); | ||
| 54 | |||
| 50 | } // namespace NWM | 55 | } // namespace NWM |
| 51 | } // namespace Service | 56 | } // namespace Service |
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp index 8c6742dba..4b389710f 100644 --- a/src/core/hle/service/nwm/uds_data.cpp +++ b/src/core/hle/service/nwm/uds_data.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | ||
| 5 | #include <cstring> | 6 | #include <cstring> |
| 6 | #include <cryptopp/aes.h> | 7 | #include <cryptopp/aes.h> |
| 7 | #include <cryptopp/ccm.h> | 8 | #include <cryptopp/ccm.h> |
| @@ -197,7 +198,7 @@ static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload | |||
| 197 | df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); | 198 | df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL); |
| 198 | df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); | 199 | df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); |
| 199 | 200 | ||
| 200 | int size = df.MaxRetrievable(); | 201 | size_t size = df.MaxRetrievable(); |
| 201 | 202 | ||
| 202 | std::vector<u8> pdata(size); | 203 | std::vector<u8> pdata(size); |
| 203 | df.Get(pdata.data(), size); | 204 | df.Get(pdata.data(), size); |
| @@ -251,7 +252,7 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload, | |||
| 251 | 252 | ||
| 252 | df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); | 253 | df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL); |
| 253 | 254 | ||
| 254 | int size = df.MaxRetrievable(); | 255 | size_t size = df.MaxRetrievable(); |
| 255 | 256 | ||
| 256 | std::vector<u8> cipher(size); | 257 | std::vector<u8> cipher(size); |
| 257 | df.Get(cipher.data(), size); | 258 | df.Get(cipher.data(), size); |
| @@ -266,13 +267,107 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload, | |||
| 266 | std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node, | 267 | std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node, |
| 267 | u16 src_node, u16 sequence_number) { | 268 | u16 src_node, u16 sequence_number) { |
| 268 | std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData); | 269 | std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData); |
| 269 | std::vector<u8> securedata_header = | 270 | std::vector<u8> securedata_header = GenerateSecureDataHeader( |
| 270 | GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number); | 271 | static_cast<u16>(data.size()), channel, dest_node, src_node, sequence_number); |
| 271 | 272 | ||
| 272 | buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end()); | 273 | buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end()); |
| 273 | buffer.insert(buffer.end(), data.begin(), data.end()); | 274 | buffer.insert(buffer.end(), data.begin(), data.end()); |
| 274 | return buffer; | 275 | return buffer; |
| 275 | } | 276 | } |
| 276 | 277 | ||
| 278 | std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) { | ||
| 279 | EAPoLStartPacket eapol_start{}; | ||
| 280 | eapol_start.association_id = association_id; | ||
| 281 | eapol_start.node.friend_code_seed = node_info.friend_code_seed; | ||
| 282 | |||
| 283 | std::copy(node_info.username.begin(), node_info.username.end(), | ||
| 284 | eapol_start.node.username.begin()); | ||
| 285 | |||
| 286 | // Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module. | ||
| 287 | // TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in | ||
| 288 | // EAPoL-Start packets from different 3DSs to the same host during a Super Smash Bros. 4 game. | ||
| 289 | // Find out what that means. | ||
| 290 | |||
| 291 | std::vector<u8> eapol_buffer(sizeof(EAPoLStartPacket)); | ||
| 292 | std::memcpy(eapol_buffer.data(), &eapol_start, sizeof(eapol_start)); | ||
| 293 | |||
| 294 | std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL); | ||
| 295 | buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end()); | ||
| 296 | return buffer; | ||
| 297 | } | ||
| 298 | |||
| 299 | EtherType GetFrameEtherType(const std::vector<u8>& frame) { | ||
| 300 | LLCHeader header; | ||
| 301 | std::memcpy(&header, frame.data(), sizeof(header)); | ||
| 302 | |||
| 303 | u16 ethertype = header.protocol; | ||
| 304 | return static_cast<EtherType>(ethertype); | ||
| 305 | } | ||
| 306 | |||
| 307 | u16 GetEAPoLFrameType(const std::vector<u8>& frame) { | ||
| 308 | // Ignore the LLC header | ||
| 309 | u16_be eapol_type; | ||
| 310 | std::memcpy(&eapol_type, frame.data() + sizeof(LLCHeader), sizeof(eapol_type)); | ||
| 311 | return eapol_type; | ||
| 312 | } | ||
| 313 | |||
| 314 | NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame) { | ||
| 315 | EAPoLStartPacket eapol_start; | ||
| 316 | |||
| 317 | // Skip the LLC header | ||
| 318 | std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start)); | ||
| 319 | |||
| 320 | NodeInfo node{}; | ||
| 321 | node.friend_code_seed = eapol_start.node.friend_code_seed; | ||
| 322 | |||
| 323 | std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(), | ||
| 324 | node.username.begin()); | ||
| 325 | |||
| 326 | return node; | ||
| 327 | } | ||
| 328 | |||
| 329 | NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) { | ||
| 330 | NodeInfo node_info{}; | ||
| 331 | node_info.friend_code_seed = node.friend_code_seed; | ||
| 332 | node_info.network_node_id = node.network_node_id; | ||
| 333 | |||
| 334 | std::copy(node.username.begin(), node.username.end(), node_info.username.begin()); | ||
| 335 | |||
| 336 | return node_info; | ||
| 337 | } | ||
| 338 | |||
| 339 | std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id, | ||
| 340 | const NodeList& nodes, u8 max_nodes, u8 total_nodes) { | ||
| 341 | EAPoLLogoffPacket eapol_logoff{}; | ||
| 342 | eapol_logoff.assigned_node_id = network_node_id; | ||
| 343 | eapol_logoff.connected_nodes = total_nodes; | ||
| 344 | eapol_logoff.max_nodes = max_nodes; | ||
| 345 | |||
| 346 | for (size_t index = 0; index < total_nodes; ++index) { | ||
| 347 | const auto& node_info = nodes[index]; | ||
| 348 | auto& node = eapol_logoff.nodes[index]; | ||
| 349 | |||
| 350 | node.friend_code_seed = node_info.friend_code_seed; | ||
| 351 | node.network_node_id = node_info.network_node_id; | ||
| 352 | |||
| 353 | std::copy(node_info.username.begin(), node_info.username.end(), node.username.begin()); | ||
| 354 | } | ||
| 355 | |||
| 356 | std::vector<u8> eapol_buffer(sizeof(EAPoLLogoffPacket)); | ||
| 357 | std::memcpy(eapol_buffer.data(), &eapol_logoff, sizeof(eapol_logoff)); | ||
| 358 | |||
| 359 | std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL); | ||
| 360 | buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end()); | ||
| 361 | return buffer; | ||
| 362 | } | ||
| 363 | |||
| 364 | EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame) { | ||
| 365 | EAPoLLogoffPacket eapol_logoff; | ||
| 366 | |||
| 367 | // Skip the LLC header | ||
| 368 | std::memcpy(&eapol_logoff, frame.data() + sizeof(LLCHeader), sizeof(eapol_logoff)); | ||
| 369 | return eapol_logoff; | ||
| 370 | } | ||
| 371 | |||
| 277 | } // namespace NWM | 372 | } // namespace NWM |
| 278 | } // namespace Service | 373 | } // namespace Service |
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h index a23520a41..76bccb1bf 100644 --- a/src/core/hle/service/nwm/uds_data.h +++ b/src/core/hle/service/nwm/uds_data.h | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/swap.h" | 10 | #include "common/swap.h" |
| 11 | #include "core/hle/service/nwm/uds_beacon.h" | ||
| 11 | #include "core/hle/service/service.h" | 12 | #include "core/hle/service/service.h" |
| 12 | 13 | ||
| 13 | namespace Service { | 14 | namespace Service { |
| @@ -67,6 +68,49 @@ struct DataFrameCryptoCTR { | |||
| 67 | 68 | ||
| 68 | static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size"); | 69 | static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size"); |
| 69 | 70 | ||
| 71 | struct EAPoLNodeInfo { | ||
| 72 | u64_be friend_code_seed; | ||
| 73 | std::array<u16_be, 10> username; | ||
| 74 | INSERT_PADDING_BYTES(4); | ||
| 75 | u16_be network_node_id; | ||
| 76 | INSERT_PADDING_BYTES(6); | ||
| 77 | }; | ||
| 78 | |||
| 79 | static_assert(sizeof(EAPoLNodeInfo) == 0x28, "EAPoLNodeInfo has the wrong size"); | ||
| 80 | |||
| 81 | constexpr u16 EAPoLStartMagic = 0x201; | ||
| 82 | |||
| 83 | /* | ||
| 84 | * Nintendo EAPoLStartPacket, is used to initaliaze a connection between client and host | ||
| 85 | */ | ||
| 86 | struct EAPoLStartPacket { | ||
| 87 | u16_be magic = EAPoLStartMagic; | ||
| 88 | u16_be association_id; | ||
| 89 | // This value is hardcoded to 1 in the NWM module. | ||
| 90 | u16_be unknown = 1; | ||
| 91 | INSERT_PADDING_BYTES(2); | ||
| 92 | EAPoLNodeInfo node; | ||
| 93 | }; | ||
| 94 | |||
| 95 | static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size"); | ||
| 96 | |||
| 97 | constexpr u16 EAPoLLogoffMagic = 0x202; | ||
| 98 | |||
| 99 | struct EAPoLLogoffPacket { | ||
| 100 | u16_be magic = EAPoLLogoffMagic; | ||
| 101 | INSERT_PADDING_BYTES(2); | ||
| 102 | u16_be assigned_node_id; | ||
| 103 | MacAddress client_mac_address; | ||
| 104 | INSERT_PADDING_BYTES(6); | ||
| 105 | u8 connected_nodes; | ||
| 106 | u8 max_nodes; | ||
| 107 | INSERT_PADDING_BYTES(4); | ||
| 108 | |||
| 109 | std::array<EAPoLNodeInfo, UDSMaxNodes> nodes; | ||
| 110 | }; | ||
| 111 | |||
| 112 | static_assert(sizeof(EAPoLLogoffPacket) == 0x298, "EAPoLLogoffPacket has the wrong size"); | ||
| 113 | |||
| 70 | /** | 114 | /** |
| 71 | * Generates an unencrypted 802.11 data payload. | 115 | * Generates an unencrypted 802.11 data payload. |
| 72 | * @returns The generated frame payload. | 116 | * @returns The generated frame payload. |
| @@ -74,5 +118,47 @@ static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wron | |||
| 74 | std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node, | 118 | std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node, |
| 75 | u16 src_node, u16 sequence_number); | 119 | u16 src_node, u16 sequence_number); |
| 76 | 120 | ||
| 121 | /* | ||
| 122 | * Generates an unencrypted 802.11 data frame body with the EAPoL-Start format for UDS | ||
| 123 | * communication. | ||
| 124 | * @returns The generated frame body. | ||
| 125 | */ | ||
| 126 | std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info); | ||
| 127 | |||
| 128 | /* | ||
| 129 | * Returns the EtherType of the specified 802.11 frame. | ||
| 130 | */ | ||
| 131 | EtherType GetFrameEtherType(const std::vector<u8>& frame); | ||
| 132 | |||
| 133 | /* | ||
| 134 | * Returns the EAPoL type (Start / Logoff) of the specified 802.11 frame. | ||
| 135 | * Note: The frame *must* be an EAPoL frame. | ||
| 136 | */ | ||
| 137 | u16 GetEAPoLFrameType(const std::vector<u8>& frame); | ||
| 138 | |||
| 139 | /* | ||
| 140 | * Returns a deserialized NodeInfo structure from the information inside an EAPoL-Start packet | ||
| 141 | * encapsulated in an 802.11 data frame. | ||
| 142 | */ | ||
| 143 | NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame); | ||
| 144 | |||
| 145 | /* | ||
| 146 | * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo. | ||
| 147 | */ | ||
| 148 | NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node); | ||
| 149 | |||
| 150 | /* | ||
| 151 | * Generates an unencrypted 802.11 data frame body with the EAPoL-Logoff format for UDS | ||
| 152 | * communication. | ||
| 153 | * @returns The generated frame body. | ||
| 154 | */ | ||
| 155 | std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id, | ||
| 156 | const NodeList& nodes, u8 max_nodes, u8 total_nodes); | ||
| 157 | |||
| 158 | /* | ||
| 159 | * Returns a EAPoLLogoffPacket representing the specified 802.11-encapsulated data frame. | ||
| 160 | */ | ||
| 161 | EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame); | ||
| 162 | |||
| 77 | } // namespace NWM | 163 | } // namespace NWM |
| 78 | } // namespace Service | 164 | } // namespace Service |
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index 5e7fc68f9..854ab9a05 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp | |||
| @@ -36,6 +36,10 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService | |||
| 36 | std::string name, unsigned int max_sessions) { | 36 | std::string name, unsigned int max_sessions) { |
| 37 | 37 | ||
| 38 | CASCADE_CODE(ValidateServiceName(name)); | 38 | CASCADE_CODE(ValidateServiceName(name)); |
| 39 | |||
| 40 | if (registered_services.find(name) != registered_services.end()) | ||
| 41 | return ERR_ALREADY_REGISTERED; | ||
| 42 | |||
| 39 | Kernel::SharedPtr<Kernel::ServerPort> server_port; | 43 | Kernel::SharedPtr<Kernel::ServerPort> server_port; |
| 40 | Kernel::SharedPtr<Kernel::ClientPort> client_port; | 44 | Kernel::SharedPtr<Kernel::ClientPort> client_port; |
| 41 | std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name); | 45 | std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name); |
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h index 8f0dbf2db..9f60a7965 100644 --- a/src/core/hle/service/sm/sm.h +++ b/src/core/hle/service/sm/sm.h | |||
| @@ -32,6 +32,9 @@ constexpr ResultCode ERR_ACCESS_DENIED(6, ErrorModule::SRV, ErrorSummary::Invali | |||
| 32 | ErrorLevel::Permanent); // 0xD8E06406 | 32 | ErrorLevel::Permanent); // 0xD8E06406 |
| 33 | constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::WrongArgument, | 33 | constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::WrongArgument, |
| 34 | ErrorLevel::Permanent); // 0xD9006407 | 34 | ErrorLevel::Permanent); // 0xD9006407 |
| 35 | constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorDescription::AlreadyExists, ErrorModule::OS, | ||
| 36 | ErrorSummary::WrongArgument, | ||
| 37 | ErrorLevel::Permanent); // 0xD9001BFC | ||
| 35 | 38 | ||
| 36 | class ServiceManager { | 39 | class ServiceManager { |
| 37 | public: | 40 | public: |
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp index 352941e69..5c955cf54 100644 --- a/src/core/hle/service/sm/srv.cpp +++ b/src/core/hle/service/sm/srv.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "core/hle/kernel/errors.h" | 13 | #include "core/hle/kernel/errors.h" |
| 14 | #include "core/hle/kernel/hle_ipc.h" | 14 | #include "core/hle/kernel/hle_ipc.h" |
| 15 | #include "core/hle/kernel/semaphore.h" | 15 | #include "core/hle/kernel/semaphore.h" |
| 16 | #include "core/hle/kernel/server_port.h" | ||
| 16 | #include "core/hle/kernel/server_session.h" | 17 | #include "core/hle/kernel/server_session.h" |
| 17 | #include "core/hle/service/sm/sm.h" | 18 | #include "core/hle/service/sm/sm.h" |
| 18 | #include "core/hle/service/sm/srv.h" | 19 | #include "core/hle/service/sm/srv.h" |
| @@ -184,12 +185,35 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { | |||
| 184 | flags); | 185 | flags); |
| 185 | } | 186 | } |
| 186 | 187 | ||
| 188 | void SRV::RegisterService(Kernel::HLERequestContext& ctx) { | ||
| 189 | IPC::RequestParser rp(ctx, 0x3, 4, 0); | ||
| 190 | |||
| 191 | auto name_buf = rp.PopRaw<std::array<char, 8>>(); | ||
| 192 | size_t name_len = rp.Pop<u32>(); | ||
| 193 | u32 max_sessions = rp.Pop<u32>(); | ||
| 194 | |||
| 195 | std::string name(name_buf.data(), std::min(name_len, name_buf.size())); | ||
| 196 | |||
| 197 | auto port = service_manager->RegisterService(name, max_sessions); | ||
| 198 | |||
| 199 | if (port.Failed()) { | ||
| 200 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||
| 201 | rb.Push(port.Code()); | ||
| 202 | LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), port.Code().raw); | ||
| 203 | return; | ||
| 204 | } | ||
| 205 | |||
| 206 | IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); | ||
| 207 | rb.Push(RESULT_SUCCESS); | ||
| 208 | rb.PushObjects(port.Unwrap()); | ||
| 209 | } | ||
| 210 | |||
| 187 | SRV::SRV(std::shared_ptr<ServiceManager> service_manager) | 211 | SRV::SRV(std::shared_ptr<ServiceManager> service_manager) |
| 188 | : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) { | 212 | : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) { |
| 189 | static const FunctionInfo functions[] = { | 213 | static const FunctionInfo functions[] = { |
| 190 | {0x00010002, &SRV::RegisterClient, "RegisterClient"}, | 214 | {0x00010002, &SRV::RegisterClient, "RegisterClient"}, |
| 191 | {0x00020000, &SRV::EnableNotification, "EnableNotification"}, | 215 | {0x00020000, &SRV::EnableNotification, "EnableNotification"}, |
| 192 | {0x00030100, nullptr, "RegisterService"}, | 216 | {0x00030100, &SRV::RegisterService, "RegisterService"}, |
| 193 | {0x000400C0, nullptr, "UnregisterService"}, | 217 | {0x000400C0, nullptr, "UnregisterService"}, |
| 194 | {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"}, | 218 | {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"}, |
| 195 | {0x000600C2, nullptr, "RegisterPort"}, | 219 | {0x000600C2, nullptr, "RegisterPort"}, |
diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h index 75cca5184..aad839563 100644 --- a/src/core/hle/service/sm/srv.h +++ b/src/core/hle/service/sm/srv.h | |||
| @@ -28,6 +28,7 @@ private: | |||
| 28 | void Subscribe(Kernel::HLERequestContext& ctx); | 28 | void Subscribe(Kernel::HLERequestContext& ctx); |
| 29 | void Unsubscribe(Kernel::HLERequestContext& ctx); | 29 | void Unsubscribe(Kernel::HLERequestContext& ctx); |
| 30 | void PublishToSubscriber(Kernel::HLERequestContext& ctx); | 30 | void PublishToSubscriber(Kernel::HLERequestContext& ctx); |
| 31 | void RegisterService(Kernel::HLERequestContext& ctx); | ||
| 31 | 32 | ||
| 32 | std::shared_ptr<ServiceManager> service_manager; | 33 | std::shared_ptr<ServiceManager> service_manager; |
| 33 | Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore; | 34 | Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore; |
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index dfc36748c..6be5db13f 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp | |||
| @@ -271,6 +271,24 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds) | |||
| 271 | // Create an event to wake the thread up after the specified nanosecond delay has passed | 271 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 272 | thread->WakeAfterDelay(nano_seconds); | 272 | thread->WakeAfterDelay(nano_seconds); |
| 273 | 273 | ||
| 274 | thread->wakeup_callback = [](ThreadWakeupReason reason, | ||
| 275 | Kernel::SharedPtr<Kernel::Thread> thread, | ||
| 276 | Kernel::SharedPtr<Kernel::WaitObject> object) { | ||
| 277 | |||
| 278 | ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY); | ||
| 279 | |||
| 280 | if (reason == ThreadWakeupReason::Timeout) { | ||
| 281 | thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT); | ||
| 282 | return; | ||
| 283 | } | ||
| 284 | |||
| 285 | ASSERT(reason == ThreadWakeupReason::Signal); | ||
| 286 | thread->SetWaitSynchronizationResult(RESULT_SUCCESS); | ||
| 287 | |||
| 288 | // WaitSynchronization1 doesn't have an output index like WaitSynchronizationN, so we | ||
| 289 | // don't have to do anything else here. | ||
| 290 | }; | ||
| 291 | |||
| 274 | Core::System::GetInstance().PrepareReschedule(); | 292 | Core::System::GetInstance().PrepareReschedule(); |
| 275 | 293 | ||
| 276 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread | 294 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread |
| @@ -344,6 +362,23 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha | |||
| 344 | // Create an event to wake the thread up after the specified nanosecond delay has passed | 362 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 345 | thread->WakeAfterDelay(nano_seconds); | 363 | thread->WakeAfterDelay(nano_seconds); |
| 346 | 364 | ||
| 365 | thread->wakeup_callback = [](ThreadWakeupReason reason, | ||
| 366 | Kernel::SharedPtr<Kernel::Thread> thread, | ||
| 367 | Kernel::SharedPtr<Kernel::WaitObject> object) { | ||
| 368 | |||
| 369 | ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ALL); | ||
| 370 | |||
| 371 | if (reason == ThreadWakeupReason::Timeout) { | ||
| 372 | thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT); | ||
| 373 | return; | ||
| 374 | } | ||
| 375 | |||
| 376 | ASSERT(reason == ThreadWakeupReason::Signal); | ||
| 377 | |||
| 378 | thread->SetWaitSynchronizationResult(RESULT_SUCCESS); | ||
| 379 | // The wait_all case does not update the output index. | ||
| 380 | }; | ||
| 381 | |||
| 347 | Core::System::GetInstance().PrepareReschedule(); | 382 | Core::System::GetInstance().PrepareReschedule(); |
| 348 | 383 | ||
| 349 | // This value gets set to -1 by default in this case, it is not modified after this. | 384 | // This value gets set to -1 by default in this case, it is not modified after this. |
| @@ -361,7 +396,7 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha | |||
| 361 | // We found a ready object, acquire it and set the result value | 396 | // We found a ready object, acquire it and set the result value |
| 362 | Kernel::WaitObject* object = itr->get(); | 397 | Kernel::WaitObject* object = itr->get(); |
| 363 | object->Acquire(thread); | 398 | object->Acquire(thread); |
| 364 | *out = std::distance(objects.begin(), itr); | 399 | *out = static_cast<s32>(std::distance(objects.begin(), itr)); |
| 365 | return RESULT_SUCCESS; | 400 | return RESULT_SUCCESS; |
| 366 | } | 401 | } |
| 367 | 402 | ||
| @@ -389,12 +424,28 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha | |||
| 389 | // Create an event to wake the thread up after the specified nanosecond delay has passed | 424 | // Create an event to wake the thread up after the specified nanosecond delay has passed |
| 390 | thread->WakeAfterDelay(nano_seconds); | 425 | thread->WakeAfterDelay(nano_seconds); |
| 391 | 426 | ||
| 427 | thread->wakeup_callback = [](ThreadWakeupReason reason, | ||
| 428 | Kernel::SharedPtr<Kernel::Thread> thread, | ||
| 429 | Kernel::SharedPtr<Kernel::WaitObject> object) { | ||
| 430 | |||
| 431 | ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY); | ||
| 432 | |||
| 433 | if (reason == ThreadWakeupReason::Timeout) { | ||
| 434 | thread->SetWaitSynchronizationResult(Kernel::RESULT_TIMEOUT); | ||
| 435 | return; | ||
| 436 | } | ||
| 437 | |||
| 438 | ASSERT(reason == ThreadWakeupReason::Signal); | ||
| 439 | |||
| 440 | thread->SetWaitSynchronizationResult(RESULT_SUCCESS); | ||
| 441 | thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get())); | ||
| 442 | }; | ||
| 443 | |||
| 392 | Core::System::GetInstance().PrepareReschedule(); | 444 | Core::System::GetInstance().PrepareReschedule(); |
| 393 | 445 | ||
| 394 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a | 446 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a |
| 395 | // signal in one of its wait objects. | 447 | // signal in one of its wait objects. |
| 396 | // Otherwise we retain the default value of timeout, and -1 in the out parameter | 448 | // Otherwise we retain the default value of timeout, and -1 in the out parameter |
| 397 | thread->wait_set_output = true; | ||
| 398 | *out = -1; | 449 | *out = -1; |
| 399 | return Kernel::RESULT_TIMEOUT; | 450 | return Kernel::RESULT_TIMEOUT; |
| 400 | } | 451 | } |
| @@ -469,7 +520,7 @@ static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handl | |||
| 469 | // We found a ready object, acquire it and set the result value | 520 | // We found a ready object, acquire it and set the result value |
| 470 | Kernel::WaitObject* object = itr->get(); | 521 | Kernel::WaitObject* object = itr->get(); |
| 471 | object->Acquire(thread); | 522 | object->Acquire(thread); |
| 472 | *index = std::distance(objects.begin(), itr); | 523 | *index = static_cast<s32>(std::distance(objects.begin(), itr)); |
| 473 | 524 | ||
| 474 | if (object->GetHandleType() == Kernel::HandleType::ServerSession) { | 525 | if (object->GetHandleType() == Kernel::HandleType::ServerSession) { |
| 475 | auto server_session = static_cast<Kernel::ServerSession*>(object); | 526 | auto server_session = static_cast<Kernel::ServerSession*>(object); |
| @@ -483,8 +534,6 @@ static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handl | |||
| 483 | 534 | ||
| 484 | // No objects were ready to be acquired, prepare to suspend the thread. | 535 | // No objects were ready to be acquired, prepare to suspend the thread. |
| 485 | 536 | ||
| 486 | // TODO(Subv): Perform IPC translation upon wakeup. | ||
| 487 | |||
| 488 | // Put the thread to sleep | 537 | // Put the thread to sleep |
| 489 | thread->status = THREADSTATUS_WAIT_SYNCH_ANY; | 538 | thread->status = THREADSTATUS_WAIT_SYNCH_ANY; |
| 490 | 539 | ||
| @@ -496,12 +545,24 @@ static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handl | |||
| 496 | 545 | ||
| 497 | thread->wait_objects = std::move(objects); | 546 | thread->wait_objects = std::move(objects); |
| 498 | 547 | ||
| 548 | thread->wakeup_callback = [](ThreadWakeupReason reason, | ||
| 549 | Kernel::SharedPtr<Kernel::Thread> thread, | ||
| 550 | Kernel::SharedPtr<Kernel::WaitObject> object) { | ||
| 551 | |||
| 552 | ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY); | ||
| 553 | ASSERT(reason == ThreadWakeupReason::Signal); | ||
| 554 | |||
| 555 | thread->SetWaitSynchronizationResult(RESULT_SUCCESS); | ||
| 556 | thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(object.get())); | ||
| 557 | |||
| 558 | // TODO(Subv): Perform IPC translation upon wakeup. | ||
| 559 | }; | ||
| 560 | |||
| 499 | Core::System::GetInstance().PrepareReschedule(); | 561 | Core::System::GetInstance().PrepareReschedule(); |
| 500 | 562 | ||
| 501 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a | 563 | // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a |
| 502 | // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error. | 564 | // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error. |
| 503 | // By default the index is set to -1. | 565 | // By default the index is set to -1. |
| 504 | thread->wait_set_output = true; | ||
| 505 | *index = -1; | 566 | *index = -1; |
| 506 | return RESULT_SUCCESS; | 567 | return RESULT_SUCCESS; |
| 507 | } | 568 | } |
| @@ -656,8 +717,9 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, u32 priority, u32 ent | |||
| 656 | "Newly created thread must run in the SysCore (Core1), unimplemented."); | 717 | "Newly created thread must run in the SysCore (Core1), unimplemented."); |
| 657 | } | 718 | } |
| 658 | 719 | ||
| 659 | CASCADE_RESULT(SharedPtr<Thread> thread, Kernel::Thread::Create(name, entry_point, priority, | 720 | CASCADE_RESULT(SharedPtr<Thread> thread, |
| 660 | arg, processor_id, stack_top)); | 721 | Kernel::Thread::Create(name, entry_point, priority, arg, processor_id, stack_top, |
| 722 | Kernel::g_current_process)); | ||
| 661 | 723 | ||
| 662 | thread->context.fpscr = | 724 | thread->context.fpscr = |
| 663 | FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO; // 0x03C00000 | 725 | FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO; // 0x03C00000 |
| @@ -682,7 +744,7 @@ static void ExitThread() { | |||
| 682 | } | 744 | } |
| 683 | 745 | ||
| 684 | /// Gets the priority for the specified thread | 746 | /// Gets the priority for the specified thread |
| 685 | static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) { | 747 | static ResultCode GetThreadPriority(u32* priority, Kernel::Handle handle) { |
| 686 | const SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); | 748 | const SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle); |
| 687 | if (thread == nullptr) | 749 | if (thread == nullptr) |
| 688 | return ERR_INVALID_HANDLE; | 750 | return ERR_INVALID_HANDLE; |
| @@ -692,7 +754,7 @@ static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) { | |||
| 692 | } | 754 | } |
| 693 | 755 | ||
| 694 | /// Sets the priority for the specified thread | 756 | /// Sets the priority for the specified thread |
| 695 | static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) { | 757 | static ResultCode SetThreadPriority(Kernel::Handle handle, u32 priority) { |
| 696 | if (priority > THREADPRIO_LOWEST) { | 758 | if (priority > THREADPRIO_LOWEST) { |
| 697 | return Kernel::ERR_OUT_OF_RANGE; | 759 | return Kernel::ERR_OUT_OF_RANGE; |
| 698 | } | 760 | } |
| @@ -977,7 +1039,7 @@ static void SleepThread(s64 nanoseconds) { | |||
| 977 | static s64 GetSystemTick() { | 1039 | static s64 GetSystemTick() { |
| 978 | s64 result = CoreTiming::GetTicks(); | 1040 | s64 result = CoreTiming::GetTicks(); |
| 979 | // Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end. | 1041 | // Advance time to defeat dumb games (like Cubic Ninja) that busy-wait for the frame to end. |
| 980 | Core::CPU().AddTicks(150); // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b | 1042 | CoreTiming::AddTicks(150); // Measured time between two calls on a 9.2 o3DS with Ninjhax 1.1b |
| 981 | return result; | 1043 | return result; |
| 982 | } | 1044 | } |
| 983 | 1045 | ||
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 69cdc0867..918038f1e 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp | |||
| @@ -91,8 +91,8 @@ static u32 TranslateAddr(u32 addr, const THREEloadinfo* loadinfo, u32* offsets) | |||
| 91 | return loadinfo->seg_addrs[2] + addr - offsets[1]; | 91 | return loadinfo->seg_addrs[2] + addr - offsets[1]; |
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | using Kernel::SharedPtr; | ||
| 95 | using Kernel::CodeSet; | 94 | using Kernel::CodeSet; |
| 95 | using Kernel::SharedPtr; | ||
| 96 | 96 | ||
| 97 | static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, | 97 | static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, |
| 98 | SharedPtr<CodeSet>* out_codeset) { | 98 | SharedPtr<CodeSet>* out_codeset) { |
| @@ -255,7 +255,7 @@ FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) { | |||
| 255 | return FileType::Error; | 255 | return FileType::Error; |
| 256 | } | 256 | } |
| 257 | 257 | ||
| 258 | ResultStatus AppLoader_THREEDSX::Load() { | 258 | ResultStatus AppLoader_THREEDSX::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
| 259 | if (is_loaded) | 259 | if (is_loaded) |
| 260 | return ResultStatus::ErrorAlreadyLoaded; | 260 | return ResultStatus::ErrorAlreadyLoaded; |
| 261 | 261 | ||
| @@ -267,19 +267,17 @@ ResultStatus AppLoader_THREEDSX::Load() { | |||
| 267 | return ResultStatus::Error; | 267 | return ResultStatus::Error; |
| 268 | codeset->name = filename; | 268 | codeset->name = filename; |
| 269 | 269 | ||
| 270 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 270 | process = Kernel::Process::Create(std::move(codeset)); |
| 271 | Kernel::g_current_process->svc_access_mask.set(); | 271 | process->svc_access_mask.set(); |
| 272 | Kernel::g_current_process->address_mappings = default_address_mappings; | 272 | process->address_mappings = default_address_mappings; |
| 273 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 274 | 273 | ||
| 275 | // Attach the default resource limit (APPLICATION) to the process | 274 | // Attach the default resource limit (APPLICATION) to the process |
| 276 | Kernel::g_current_process->resource_limit = | 275 | process->resource_limit = |
| 277 | Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); | 276 | Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); |
| 278 | 277 | ||
| 279 | Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); | 278 | process->Run(48, Kernel::DEFAULT_STACK_SIZE); |
| 280 | 279 | ||
| 281 | Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), | 280 | Service::FS::RegisterSelfNCCH(*this); |
| 282 | Service::FS::ArchiveIdCode::SelfNCCH); | ||
| 283 | 281 | ||
| 284 | is_loaded = true; | 282 | is_loaded = true; |
| 285 | return ResultStatus::Success; | 283 | return ResultStatus::Success; |
diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h index 3f376778a..1e59bbb9d 100644 --- a/src/core/loader/3dsx.h +++ b/src/core/loader/3dsx.h | |||
| @@ -31,7 +31,7 @@ public: | |||
| 31 | return IdentifyType(file); | 31 | return IdentifyType(file); |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | ResultStatus Load() override; | 34 | ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
| 35 | 35 | ||
| 36 | ResultStatus ReadIcon(std::vector<u8>& buffer) override; | 36 | ResultStatus ReadIcon(std::vector<u8>& buffer) override; |
| 37 | 37 | ||
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 2f27606a1..e36e42120 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp | |||
| @@ -13,8 +13,8 @@ | |||
| 13 | #include "core/loader/elf.h" | 13 | #include "core/loader/elf.h" |
| 14 | #include "core/memory.h" | 14 | #include "core/memory.h" |
| 15 | 15 | ||
| 16 | using Kernel::SharedPtr; | ||
| 17 | using Kernel::CodeSet; | 16 | using Kernel::CodeSet; |
| 17 | using Kernel::SharedPtr; | ||
| 18 | 18 | ||
| 19 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 19 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 20 | // ELF Header Constants | 20 | // ELF Header Constants |
| @@ -375,7 +375,7 @@ FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file) { | |||
| 375 | return FileType::Error; | 375 | return FileType::Error; |
| 376 | } | 376 | } |
| 377 | 377 | ||
| 378 | ResultStatus AppLoader_ELF::Load() { | 378 | ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
| 379 | if (is_loaded) | 379 | if (is_loaded) |
| 380 | return ResultStatus::ErrorAlreadyLoaded; | 380 | return ResultStatus::ErrorAlreadyLoaded; |
| 381 | 381 | ||
| @@ -394,16 +394,15 @@ ResultStatus AppLoader_ELF::Load() { | |||
| 394 | SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); | 394 | SharedPtr<CodeSet> codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); |
| 395 | codeset->name = filename; | 395 | codeset->name = filename; |
| 396 | 396 | ||
| 397 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 397 | process = Kernel::Process::Create(std::move(codeset)); |
| 398 | Kernel::g_current_process->svc_access_mask.set(); | 398 | process->svc_access_mask.set(); |
| 399 | Kernel::g_current_process->address_mappings = default_address_mappings; | 399 | process->address_mappings = default_address_mappings; |
| 400 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 401 | 400 | ||
| 402 | // Attach the default resource limit (APPLICATION) to the process | 401 | // Attach the default resource limit (APPLICATION) to the process |
| 403 | Kernel::g_current_process->resource_limit = | 402 | process->resource_limit = |
| 404 | Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); | 403 | Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); |
| 405 | 404 | ||
| 406 | Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); | 405 | process->Run(48, Kernel::DEFAULT_STACK_SIZE); |
| 407 | 406 | ||
| 408 | is_loaded = true; | 407 | is_loaded = true; |
| 409 | return ResultStatus::Success; | 408 | return ResultStatus::Success; |
diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 862aa90d8..113da5917 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h | |||
| @@ -30,7 +30,7 @@ public: | |||
| 30 | return IdentifyType(file); | 30 | return IdentifyType(file); |
| 31 | } | 31 | } |
| 32 | 32 | ||
| 33 | ResultStatus Load() override; | 33 | ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
| 34 | 34 | ||
| 35 | private: | 35 | private: |
| 36 | std::string filename; | 36 | std::string filename; |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index e731888a2..82b2be6a3 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -13,10 +13,12 @@ | |||
| 13 | #include <boost/optional.hpp> | 13 | #include <boost/optional.hpp> |
| 14 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 15 | #include "common/file_util.h" | 15 | #include "common/file_util.h" |
| 16 | #include "core/hle/kernel/kernel.h" | ||
| 16 | 17 | ||
| 17 | namespace Kernel { | 18 | namespace Kernel { |
| 18 | struct AddressMapping; | 19 | struct AddressMapping; |
| 19 | } | 20 | class Process; |
| 21 | } // namespace Kernel | ||
| 20 | 22 | ||
| 21 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 22 | // Loader namespace | 24 | // Loader namespace |
| @@ -92,10 +94,11 @@ public: | |||
| 92 | virtual FileType GetFileType() = 0; | 94 | virtual FileType GetFileType() = 0; |
| 93 | 95 | ||
| 94 | /** | 96 | /** |
| 95 | * Load the application | 97 | * Load the application and return the created Process instance |
| 96 | * @return ResultStatus result of function | 98 | * @param process The newly created process. |
| 99 | * @return The status result of the operation. | ||
| 97 | */ | 100 | */ |
| 98 | virtual ResultStatus Load() = 0; | 101 | virtual ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) = 0; |
| 99 | 102 | ||
| 100 | /** | 103 | /** |
| 101 | * Loads the system mode that this application needs. | 104 | * Loads the system mode that this application needs. |
| @@ -167,6 +170,19 @@ public: | |||
| 167 | } | 170 | } |
| 168 | 171 | ||
| 169 | /** | 172 | /** |
| 173 | * Get the update RomFS of the application | ||
| 174 | * Since the RomFS can be huge, we return a file reference instead of copying to a buffer | ||
| 175 | * @param romfs_file The file containing the RomFS | ||
| 176 | * @param offset The offset the romfs begins on | ||
| 177 | * @param size The size of the romfs | ||
| 178 | * @return ResultStatus result of function | ||
| 179 | */ | ||
| 180 | virtual ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||
| 181 | u64& size) { | ||
| 182 | return ResultStatus::ErrorNotImplemented; | ||
| 183 | } | ||
| 184 | |||
| 185 | /** | ||
| 170 | * Get the title of the application | 186 | * Get the title of the application |
| 171 | * @param title Reference to store the application title into | 187 | * @param title Reference to store the application title into |
| 172 | * @return ResultStatus result of function | 188 | * @return ResultStatus result of function |
| @@ -193,4 +209,4 @@ extern const std::initializer_list<Kernel::AddressMapping> default_address_mappi | |||
| 193 | */ | 209 | */ |
| 194 | std::unique_ptr<AppLoader> GetLoader(const std::string& filename); | 210 | std::unique_ptr<AppLoader> GetLoader(const std::string& filename); |
| 195 | 211 | ||
| 196 | } // namespace | 212 | } // namespace Loader |
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 79ea50147..66bc5823d 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "common/swap.h" | 13 | #include "common/swap.h" |
| 14 | #include "core/core.h" | 14 | #include "core/core.h" |
| 15 | #include "core/file_sys/archive_selfncch.h" | 15 | #include "core/file_sys/archive_selfncch.h" |
| 16 | #include "core/file_sys/ncch_container.h" | ||
| 16 | #include "core/hle/kernel/process.h" | 17 | #include "core/hle/kernel/process.h" |
| 17 | #include "core/hle/kernel/resource_limit.h" | 18 | #include "core/hle/kernel/resource_limit.h" |
| 18 | #include "core/hle/service/cfg/cfg.h" | 19 | #include "core/hle/service/cfg/cfg.h" |
| @@ -27,87 +28,7 @@ | |||
| 27 | 28 | ||
| 28 | namespace Loader { | 29 | namespace Loader { |
| 29 | 30 | ||
| 30 | static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs | 31 | static const u64 UPDATE_MASK = 0x0000000e00000000; |
| 31 | static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Get the decompressed size of an LZSS compressed ExeFS file | ||
| 35 | * @param buffer Buffer of compressed file | ||
| 36 | * @param size Size of compressed buffer | ||
| 37 | * @return Size of decompressed buffer | ||
| 38 | */ | ||
| 39 | static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { | ||
| 40 | u32 offset_size = *(u32*)(buffer + size - 4); | ||
| 41 | return offset_size + size; | ||
| 42 | } | ||
| 43 | |||
| 44 | /** | ||
| 45 | * Decompress ExeFS file (compressed with LZSS) | ||
| 46 | * @param compressed Compressed buffer | ||
| 47 | * @param compressed_size Size of compressed buffer | ||
| 48 | * @param decompressed Decompressed buffer | ||
| 49 | * @param decompressed_size Size of decompressed buffer | ||
| 50 | * @return True on success, otherwise false | ||
| 51 | */ | ||
| 52 | static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, | ||
| 53 | u32 decompressed_size) { | ||
| 54 | const u8* footer = compressed + compressed_size - 8; | ||
| 55 | u32 buffer_top_and_bottom = *reinterpret_cast<const u32*>(footer); | ||
| 56 | u32 out = decompressed_size; | ||
| 57 | u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); | ||
| 58 | u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); | ||
| 59 | |||
| 60 | memset(decompressed, 0, decompressed_size); | ||
| 61 | memcpy(decompressed, compressed, compressed_size); | ||
| 62 | |||
| 63 | while (index > stop_index) { | ||
| 64 | u8 control = compressed[--index]; | ||
| 65 | |||
| 66 | for (unsigned i = 0; i < 8; i++) { | ||
| 67 | if (index <= stop_index) | ||
| 68 | break; | ||
| 69 | if (index <= 0) | ||
| 70 | break; | ||
| 71 | if (out <= 0) | ||
| 72 | break; | ||
| 73 | |||
| 74 | if (control & 0x80) { | ||
| 75 | // Check if compression is out of bounds | ||
| 76 | if (index < 2) | ||
| 77 | return false; | ||
| 78 | index -= 2; | ||
| 79 | |||
| 80 | u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); | ||
| 81 | u32 segment_size = ((segment_offset >> 12) & 15) + 3; | ||
| 82 | segment_offset &= 0x0FFF; | ||
| 83 | segment_offset += 2; | ||
| 84 | |||
| 85 | // Check if compression is out of bounds | ||
| 86 | if (out < segment_size) | ||
| 87 | return false; | ||
| 88 | |||
| 89 | for (unsigned j = 0; j < segment_size; j++) { | ||
| 90 | // Check if compression is out of bounds | ||
| 91 | if (out + segment_offset >= decompressed_size) | ||
| 92 | return false; | ||
| 93 | |||
| 94 | u8 data = decompressed[out + segment_offset]; | ||
| 95 | decompressed[--out] = data; | ||
| 96 | } | ||
| 97 | } else { | ||
| 98 | // Check if compression is out of bounds | ||
| 99 | if (out < 1) | ||
| 100 | return false; | ||
| 101 | decompressed[--out] = compressed[--index]; | ||
| 102 | } | ||
| 103 | control <<= 1; | ||
| 104 | } | ||
| 105 | } | ||
| 106 | return true; | ||
| 107 | } | ||
| 108 | |||
| 109 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 110 | // AppLoader_NCCH class | ||
| 111 | 32 | ||
| 112 | FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { | 33 | FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { |
| 113 | u32 magic; | 34 | u32 magic; |
| @@ -124,203 +45,94 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { | |||
| 124 | return FileType::Error; | 45 | return FileType::Error; |
| 125 | } | 46 | } |
| 126 | 47 | ||
| 48 | static std::string GetUpdateNCCHPath(u64_le program_id) { | ||
| 49 | u32 high = static_cast<u32>((program_id | UPDATE_MASK) >> 32); | ||
| 50 | u32 low = static_cast<u32>((program_id | UPDATE_MASK) & 0xFFFFFFFF); | ||
| 51 | |||
| 52 | return Common::StringFromFormat("%sNintendo 3DS/%s/%s/title/%08x/%08x/content/00000000.app", | ||
| 53 | FileUtil::GetUserPath(D_SDMC_IDX).c_str(), SYSTEM_ID, SDCARD_ID, | ||
| 54 | high, low); | ||
| 55 | } | ||
| 56 | |||
| 127 | std::pair<boost::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { | 57 | std::pair<boost::optional<u32>, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { |
| 128 | if (!is_loaded) { | 58 | if (!is_loaded) { |
| 129 | ResultStatus res = LoadExeFS(); | 59 | ResultStatus res = base_ncch.Load(); |
| 130 | if (res != ResultStatus::Success) { | 60 | if (res != ResultStatus::Success) { |
| 131 | return std::make_pair(boost::none, res); | 61 | return std::make_pair(boost::none, res); |
| 132 | } | 62 | } |
| 133 | } | 63 | } |
| 64 | |||
| 134 | // Set the system mode as the one from the exheader. | 65 | // Set the system mode as the one from the exheader. |
| 135 | return std::make_pair(exheader_header.arm11_system_local_caps.system_mode.Value(), | 66 | return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.system_mode.Value(), |
| 136 | ResultStatus::Success); | 67 | ResultStatus::Success); |
| 137 | } | 68 | } |
| 138 | 69 | ||
| 139 | ResultStatus AppLoader_NCCH::LoadExec() { | 70 | ResultStatus AppLoader_NCCH::LoadExec(Kernel::SharedPtr<Kernel::Process>& process) { |
| 140 | using Kernel::SharedPtr; | ||
| 141 | using Kernel::CodeSet; | 71 | using Kernel::CodeSet; |
| 72 | using Kernel::SharedPtr; | ||
| 142 | 73 | ||
| 143 | if (!is_loaded) | 74 | if (!is_loaded) |
| 144 | return ResultStatus::ErrorNotLoaded; | 75 | return ResultStatus::ErrorNotLoaded; |
| 145 | 76 | ||
| 146 | std::vector<u8> code; | 77 | std::vector<u8> code; |
| 147 | if (ResultStatus::Success == ReadCode(code)) { | 78 | u64_le program_id; |
| 79 | if (ResultStatus::Success == ReadCode(code) && | ||
| 80 | ResultStatus::Success == ReadProgramId(program_id)) { | ||
| 148 | std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( | 81 | std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( |
| 149 | (const char*)exheader_header.codeset_info.name, 8); | 82 | (const char*)overlay_ncch->exheader_header.codeset_info.name, 8); |
| 150 | 83 | ||
| 151 | SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, ncch_header.program_id); | 84 | SharedPtr<CodeSet> codeset = CodeSet::Create(process_name, program_id); |
| 152 | 85 | ||
| 153 | codeset->code.offset = 0; | 86 | codeset->code.offset = 0; |
| 154 | codeset->code.addr = exheader_header.codeset_info.text.address; | 87 | codeset->code.addr = overlay_ncch->exheader_header.codeset_info.text.address; |
| 155 | codeset->code.size = exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; | 88 | codeset->code.size = |
| 89 | overlay_ncch->exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; | ||
| 156 | 90 | ||
| 157 | codeset->rodata.offset = codeset->code.offset + codeset->code.size; | 91 | codeset->rodata.offset = codeset->code.offset + codeset->code.size; |
| 158 | codeset->rodata.addr = exheader_header.codeset_info.ro.address; | 92 | codeset->rodata.addr = overlay_ncch->exheader_header.codeset_info.ro.address; |
| 159 | codeset->rodata.size = exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; | 93 | codeset->rodata.size = |
| 94 | overlay_ncch->exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; | ||
| 160 | 95 | ||
| 161 | // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just | 96 | // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just |
| 162 | // to the regular size. Playing it safe for now. | 97 | // to the regular size. Playing it safe for now. |
| 163 | u32 bss_page_size = (exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; | 98 | u32 bss_page_size = (overlay_ncch->exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; |
| 164 | code.resize(code.size() + bss_page_size, 0); | 99 | code.resize(code.size() + bss_page_size, 0); |
| 165 | 100 | ||
| 166 | codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; | 101 | codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; |
| 167 | codeset->data.addr = exheader_header.codeset_info.data.address; | 102 | codeset->data.addr = overlay_ncch->exheader_header.codeset_info.data.address; |
| 168 | codeset->data.size = | 103 | codeset->data.size = |
| 169 | exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + bss_page_size; | 104 | overlay_ncch->exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + |
| 105 | bss_page_size; | ||
| 170 | 106 | ||
| 171 | codeset->entrypoint = codeset->code.addr; | 107 | codeset->entrypoint = codeset->code.addr; |
| 172 | codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); | 108 | codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); |
| 173 | 109 | ||
| 174 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 110 | process = Kernel::Process::Create(std::move(codeset)); |
| 175 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 176 | 111 | ||
| 177 | // Attach a resource limit to the process based on the resource limit category | 112 | // Attach a resource limit to the process based on the resource limit category |
| 178 | Kernel::g_current_process->resource_limit = | 113 | process->resource_limit = |
| 179 | Kernel::ResourceLimit::GetForCategory(static_cast<Kernel::ResourceLimitCategory>( | 114 | Kernel::ResourceLimit::GetForCategory(static_cast<Kernel::ResourceLimitCategory>( |
| 180 | exheader_header.arm11_system_local_caps.resource_limit_category)); | 115 | overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); |
| 181 | 116 | ||
| 182 | // Set the default CPU core for this process | 117 | // Set the default CPU core for this process |
| 183 | Kernel::g_current_process->ideal_processor = | 118 | process->ideal_processor = |
| 184 | exheader_header.arm11_system_local_caps.ideal_processor; | 119 | overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; |
| 185 | 120 | ||
| 186 | // Copy data while converting endianness | 121 | // Copy data while converting endianness |
| 187 | std::array<u32, ARRAY_SIZE(exheader_header.arm11_kernel_caps.descriptors)> kernel_caps; | 122 | std::array<u32, ARRAY_SIZE(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors)> |
| 188 | std::copy_n(exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), | 123 | kernel_caps; |
| 124 | std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), | ||
| 189 | begin(kernel_caps)); | 125 | begin(kernel_caps)); |
| 190 | Kernel::g_current_process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); | 126 | process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); |
| 191 | 127 | ||
| 192 | s32 priority = exheader_header.arm11_system_local_caps.priority; | 128 | s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; |
| 193 | u32 stack_size = exheader_header.codeset_info.stack_size; | 129 | u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; |
| 194 | Kernel::g_current_process->Run(priority, stack_size); | 130 | process->Run(priority, stack_size); |
| 195 | return ResultStatus::Success; | 131 | return ResultStatus::Success; |
| 196 | } | 132 | } |
| 197 | return ResultStatus::Error; | 133 | return ResultStatus::Error; |
| 198 | } | 134 | } |
| 199 | 135 | ||
| 200 | ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector<u8>& buffer) { | ||
| 201 | if (!file.IsOpen()) | ||
| 202 | return ResultStatus::Error; | ||
| 203 | |||
| 204 | ResultStatus result = LoadExeFS(); | ||
| 205 | if (result != ResultStatus::Success) | ||
| 206 | return result; | ||
| 207 | |||
| 208 | LOG_DEBUG(Loader, "%d sections:", kMaxSections); | ||
| 209 | // Iterate through the ExeFs archive until we find a section with the specified name... | ||
| 210 | for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { | ||
| 211 | const auto& section = exefs_header.section[section_number]; | ||
| 212 | |||
| 213 | // Load the specified section... | ||
| 214 | if (strcmp(section.name, name) == 0) { | ||
| 215 | LOG_DEBUG(Loader, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, | ||
| 216 | section.offset, section.size, section.name); | ||
| 217 | |||
| 218 | s64 section_offset = | ||
| 219 | (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); | ||
| 220 | file.Seek(section_offset, SEEK_SET); | ||
| 221 | |||
| 222 | if (strcmp(section.name, ".code") == 0 && is_compressed) { | ||
| 223 | // Section is compressed, read compressed .code section... | ||
| 224 | std::unique_ptr<u8[]> temp_buffer; | ||
| 225 | try { | ||
| 226 | temp_buffer.reset(new u8[section.size]); | ||
| 227 | } catch (std::bad_alloc&) { | ||
| 228 | return ResultStatus::ErrorMemoryAllocationFailed; | ||
| 229 | } | ||
| 230 | |||
| 231 | if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) | ||
| 232 | return ResultStatus::Error; | ||
| 233 | |||
| 234 | // Decompress .code section... | ||
| 235 | u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); | ||
| 236 | buffer.resize(decompressed_size); | ||
| 237 | if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) | ||
| 238 | return ResultStatus::ErrorInvalidFormat; | ||
| 239 | } else { | ||
| 240 | // Section is uncompressed... | ||
| 241 | buffer.resize(section.size); | ||
| 242 | if (file.ReadBytes(&buffer[0], section.size) != section.size) | ||
| 243 | return ResultStatus::Error; | ||
| 244 | } | ||
| 245 | return ResultStatus::Success; | ||
| 246 | } | ||
| 247 | } | ||
| 248 | return ResultStatus::ErrorNotUsed; | ||
| 249 | } | ||
| 250 | |||
| 251 | ResultStatus AppLoader_NCCH::LoadExeFS() { | ||
| 252 | if (is_exefs_loaded) | ||
| 253 | return ResultStatus::Success; | ||
| 254 | |||
| 255 | if (!file.IsOpen()) | ||
| 256 | return ResultStatus::Error; | ||
| 257 | |||
| 258 | // Reset read pointer in case this file has been read before. | ||
| 259 | file.Seek(0, SEEK_SET); | ||
| 260 | |||
| 261 | if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) | ||
| 262 | return ResultStatus::Error; | ||
| 263 | |||
| 264 | // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... | ||
| 265 | if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { | ||
| 266 | LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); | ||
| 267 | ncch_offset = 0x4000; | ||
| 268 | file.Seek(ncch_offset, SEEK_SET); | ||
| 269 | file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); | ||
| 270 | } | ||
| 271 | |||
| 272 | // Verify we are loading the correct file type... | ||
| 273 | if (MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) | ||
| 274 | return ResultStatus::ErrorInvalidFormat; | ||
| 275 | |||
| 276 | // Read ExHeader... | ||
| 277 | |||
| 278 | if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) | ||
| 279 | return ResultStatus::Error; | ||
| 280 | |||
| 281 | is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; | ||
| 282 | entry_point = exheader_header.codeset_info.text.address; | ||
| 283 | code_size = exheader_header.codeset_info.text.code_size; | ||
| 284 | stack_size = exheader_header.codeset_info.stack_size; | ||
| 285 | bss_size = exheader_header.codeset_info.bss_size; | ||
| 286 | core_version = exheader_header.arm11_system_local_caps.core_version; | ||
| 287 | priority = exheader_header.arm11_system_local_caps.priority; | ||
| 288 | resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; | ||
| 289 | |||
| 290 | LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name); | ||
| 291 | LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); | ||
| 292 | LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no"); | ||
| 293 | LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); | ||
| 294 | LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); | ||
| 295 | LOG_DEBUG(Loader, "Stack size: 0x%08X", stack_size); | ||
| 296 | LOG_DEBUG(Loader, "Bss size: 0x%08X", bss_size); | ||
| 297 | LOG_DEBUG(Loader, "Core version: %d", core_version); | ||
| 298 | LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); | ||
| 299 | LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); | ||
| 300 | LOG_DEBUG(Loader, "System Mode: %d", | ||
| 301 | static_cast<int>(exheader_header.arm11_system_local_caps.system_mode)); | ||
| 302 | |||
| 303 | if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { | ||
| 304 | LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); | ||
| 305 | return ResultStatus::ErrorEncrypted; | ||
| 306 | } | ||
| 307 | |||
| 308 | // Read ExeFS... | ||
| 309 | |||
| 310 | exefs_offset = ncch_header.exefs_offset * kBlockSize; | ||
| 311 | u32 exefs_size = ncch_header.exefs_size * kBlockSize; | ||
| 312 | |||
| 313 | LOG_DEBUG(Loader, "ExeFS offset: 0x%08X", exefs_offset); | ||
| 314 | LOG_DEBUG(Loader, "ExeFS size: 0x%08X", exefs_size); | ||
| 315 | |||
| 316 | file.Seek(exefs_offset + ncch_offset, SEEK_SET); | ||
| 317 | if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) | ||
| 318 | return ResultStatus::Error; | ||
| 319 | |||
| 320 | is_exefs_loaded = true; | ||
| 321 | return ResultStatus::Success; | ||
| 322 | } | ||
| 323 | |||
| 324 | void AppLoader_NCCH::ParseRegionLockoutInfo() { | 136 | void AppLoader_NCCH::ParseRegionLockoutInfo() { |
| 325 | std::vector<u8> smdh_buffer; | 137 | std::vector<u8> smdh_buffer; |
| 326 | if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { | 138 | if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { |
| @@ -338,35 +150,43 @@ void AppLoader_NCCH::ParseRegionLockoutInfo() { | |||
| 338 | } | 150 | } |
| 339 | } | 151 | } |
| 340 | 152 | ||
| 341 | ResultStatus AppLoader_NCCH::Load() { | 153 | ResultStatus AppLoader_NCCH::Load(Kernel::SharedPtr<Kernel::Process>& process) { |
| 154 | u64_le ncch_program_id; | ||
| 155 | |||
| 342 | if (is_loaded) | 156 | if (is_loaded) |
| 343 | return ResultStatus::ErrorAlreadyLoaded; | 157 | return ResultStatus::ErrorAlreadyLoaded; |
| 344 | 158 | ||
| 345 | ResultStatus result = LoadExeFS(); | 159 | ResultStatus result = base_ncch.Load(); |
| 346 | if (result != ResultStatus::Success) | 160 | if (result != ResultStatus::Success) |
| 347 | return result; | 161 | return result; |
| 348 | 162 | ||
| 349 | std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)}; | 163 | ReadProgramId(ncch_program_id); |
| 164 | std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_program_id)}; | ||
| 350 | 165 | ||
| 351 | LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); | 166 | LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); |
| 352 | 167 | ||
| 168 | update_ncch.OpenFile(GetUpdateNCCHPath(ncch_program_id)); | ||
| 169 | result = update_ncch.Load(); | ||
| 170 | if (result == ResultStatus::Success) { | ||
| 171 | overlay_ncch = &update_ncch; | ||
| 172 | } | ||
| 173 | |||
| 353 | Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); | 174 | Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); |
| 354 | 175 | ||
| 355 | if (auto room_member = Network::GetRoomMember().lock()) { | 176 | if (auto room_member = Network::GetRoomMember().lock()) { |
| 356 | Network::GameInfo game_info; | 177 | Network::GameInfo game_info; |
| 357 | ReadTitle(game_info.name); | 178 | ReadTitle(game_info.name); |
| 358 | game_info.id = ncch_header.program_id; | 179 | game_info.id = ncch_program_id; |
| 359 | room_member->SendGameInfo(game_info); | 180 | room_member->SendGameInfo(game_info); |
| 360 | } | 181 | } |
| 361 | 182 | ||
| 362 | is_loaded = true; // Set state to loaded | 183 | is_loaded = true; // Set state to loaded |
| 363 | 184 | ||
| 364 | result = LoadExec(); // Load the executable into memory for booting | 185 | result = LoadExec(process); // Load the executable into memory for booting |
| 365 | if (ResultStatus::Success != result) | 186 | if (ResultStatus::Success != result) |
| 366 | return result; | 187 | return result; |
| 367 | 188 | ||
| 368 | Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_SelfNCCH>(*this), | 189 | Service::FS::RegisterSelfNCCH(*this); |
| 369 | Service::FS::ArchiveIdCode::SelfNCCH); | ||
| 370 | 190 | ||
| 371 | ParseRegionLockoutInfo(); | 191 | ParseRegionLockoutInfo(); |
| 372 | 192 | ||
| @@ -374,61 +194,40 @@ ResultStatus AppLoader_NCCH::Load() { | |||
| 374 | } | 194 | } |
| 375 | 195 | ||
| 376 | ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) { | 196 | ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) { |
| 377 | return LoadSectionExeFS(".code", buffer); | 197 | return overlay_ncch->LoadSectionExeFS(".code", buffer); |
| 378 | } | 198 | } |
| 379 | 199 | ||
| 380 | ResultStatus AppLoader_NCCH::ReadIcon(std::vector<u8>& buffer) { | 200 | ResultStatus AppLoader_NCCH::ReadIcon(std::vector<u8>& buffer) { |
| 381 | return LoadSectionExeFS("icon", buffer); | 201 | return overlay_ncch->LoadSectionExeFS("icon", buffer); |
| 382 | } | 202 | } |
| 383 | 203 | ||
| 384 | ResultStatus AppLoader_NCCH::ReadBanner(std::vector<u8>& buffer) { | 204 | ResultStatus AppLoader_NCCH::ReadBanner(std::vector<u8>& buffer) { |
| 385 | return LoadSectionExeFS("banner", buffer); | 205 | return overlay_ncch->LoadSectionExeFS("banner", buffer); |
| 386 | } | 206 | } |
| 387 | 207 | ||
| 388 | ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) { | 208 | ResultStatus AppLoader_NCCH::ReadLogo(std::vector<u8>& buffer) { |
| 389 | return LoadSectionExeFS("logo", buffer); | 209 | return overlay_ncch->LoadSectionExeFS("logo", buffer); |
| 390 | } | 210 | } |
| 391 | 211 | ||
| 392 | ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { | 212 | ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { |
| 393 | if (!file.IsOpen()) | 213 | ResultStatus result = base_ncch.ReadProgramId(out_program_id); |
| 394 | return ResultStatus::Error; | ||
| 395 | |||
| 396 | ResultStatus result = LoadExeFS(); | ||
| 397 | if (result != ResultStatus::Success) | 214 | if (result != ResultStatus::Success) |
| 398 | return result; | 215 | return result; |
| 399 | 216 | ||
| 400 | out_program_id = ncch_header.program_id; | ||
| 401 | return ResultStatus::Success; | 217 | return ResultStatus::Success; |
| 402 | } | 218 | } |
| 403 | 219 | ||
| 404 | ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | 220 | ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
| 405 | u64& size) { | 221 | u64& size) { |
| 406 | if (!file.IsOpen()) | 222 | return base_ncch.ReadRomFS(romfs_file, offset, size); |
| 407 | return ResultStatus::Error; | 223 | } |
| 408 | |||
| 409 | // Check if the NCCH has a RomFS... | ||
| 410 | if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) { | ||
| 411 | u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; | ||
| 412 | u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; | ||
| 413 | |||
| 414 | LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); | ||
| 415 | LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); | ||
| 416 | |||
| 417 | if (file.GetSize() < romfs_offset + romfs_size) | ||
| 418 | return ResultStatus::Error; | ||
| 419 | |||
| 420 | // We reopen the file, to allow its position to be independent from file's | ||
| 421 | romfs_file = std::make_shared<FileUtil::IOFile>(filepath, "rb"); | ||
| 422 | if (!romfs_file->IsOpen()) | ||
| 423 | return ResultStatus::Error; | ||
| 424 | 224 | ||
| 425 | offset = romfs_offset; | 225 | ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, |
| 426 | size = romfs_size; | 226 | u64& offset, u64& size) { |
| 227 | ResultStatus result = update_ncch.ReadRomFS(romfs_file, offset, size); | ||
| 427 | 228 | ||
| 428 | return ResultStatus::Success; | 229 | if (result != ResultStatus::Success) |
| 429 | } | 230 | return base_ncch.ReadRomFS(romfs_file, offset, size); |
| 430 | LOG_DEBUG(Loader, "NCCH has no RomFS"); | ||
| 431 | return ResultStatus::ErrorNotUsed; | ||
| 432 | } | 231 | } |
| 433 | 232 | ||
| 434 | ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { | 233 | ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { |
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index e40cef764..09230ae33 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h | |||
| @@ -5,155 +5,12 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <memory> | 7 | #include <memory> |
| 8 | #include "common/bit_field.h" | ||
| 9 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 10 | #include "common/swap.h" | 9 | #include "common/swap.h" |
| 10 | #include "core/file_sys/ncch_container.h" | ||
| 11 | #include "core/loader/loader.h" | 11 | #include "core/loader/loader.h" |
| 12 | 12 | ||
| 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// | 13 | //////////////////////////////////////////////////////////////////////////////////////////////////// |
| 14 | /// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) | ||
| 15 | |||
| 16 | struct NCCH_Header { | ||
| 17 | u8 signature[0x100]; | ||
| 18 | u32_le magic; | ||
| 19 | u32_le content_size; | ||
| 20 | u8 partition_id[8]; | ||
| 21 | u16_le maker_code; | ||
| 22 | u16_le version; | ||
| 23 | u8 reserved_0[4]; | ||
| 24 | u64_le program_id; | ||
| 25 | u8 reserved_1[0x10]; | ||
| 26 | u8 logo_region_hash[0x20]; | ||
| 27 | u8 product_code[0x10]; | ||
| 28 | u8 extended_header_hash[0x20]; | ||
| 29 | u32_le extended_header_size; | ||
| 30 | u8 reserved_2[4]; | ||
| 31 | u8 flags[8]; | ||
| 32 | u32_le plain_region_offset; | ||
| 33 | u32_le plain_region_size; | ||
| 34 | u32_le logo_region_offset; | ||
| 35 | u32_le logo_region_size; | ||
| 36 | u32_le exefs_offset; | ||
| 37 | u32_le exefs_size; | ||
| 38 | u32_le exefs_hash_region_size; | ||
| 39 | u8 reserved_3[4]; | ||
| 40 | u32_le romfs_offset; | ||
| 41 | u32_le romfs_size; | ||
| 42 | u32_le romfs_hash_region_size; | ||
| 43 | u8 reserved_4[4]; | ||
| 44 | u8 exefs_super_block_hash[0x20]; | ||
| 45 | u8 romfs_super_block_hash[0x20]; | ||
| 46 | }; | ||
| 47 | |||
| 48 | static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); | ||
| 49 | |||
| 50 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 51 | // ExeFS (executable file system) headers | ||
| 52 | |||
| 53 | struct ExeFs_SectionHeader { | ||
| 54 | char name[8]; | ||
| 55 | u32 offset; | ||
| 56 | u32 size; | ||
| 57 | }; | ||
| 58 | |||
| 59 | struct ExeFs_Header { | ||
| 60 | ExeFs_SectionHeader section[8]; | ||
| 61 | u8 reserved[0x80]; | ||
| 62 | u8 hashes[8][0x20]; | ||
| 63 | }; | ||
| 64 | |||
| 65 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 66 | // ExHeader (executable file system header) headers | ||
| 67 | |||
| 68 | struct ExHeader_SystemInfoFlags { | ||
| 69 | u8 reserved[5]; | ||
| 70 | u8 flag; | ||
| 71 | u8 remaster_version[2]; | ||
| 72 | }; | ||
| 73 | |||
| 74 | struct ExHeader_CodeSegmentInfo { | ||
| 75 | u32 address; | ||
| 76 | u32 num_max_pages; | ||
| 77 | u32 code_size; | ||
| 78 | }; | ||
| 79 | |||
| 80 | struct ExHeader_CodeSetInfo { | ||
| 81 | u8 name[8]; | ||
| 82 | ExHeader_SystemInfoFlags flags; | ||
| 83 | ExHeader_CodeSegmentInfo text; | ||
| 84 | u32 stack_size; | ||
| 85 | ExHeader_CodeSegmentInfo ro; | ||
| 86 | u8 reserved[4]; | ||
| 87 | ExHeader_CodeSegmentInfo data; | ||
| 88 | u32 bss_size; | ||
| 89 | }; | ||
| 90 | |||
| 91 | struct ExHeader_DependencyList { | ||
| 92 | u8 program_id[0x30][8]; | ||
| 93 | }; | ||
| 94 | |||
| 95 | struct ExHeader_SystemInfo { | ||
| 96 | u64 save_data_size; | ||
| 97 | u8 jump_id[8]; | ||
| 98 | u8 reserved_2[0x30]; | ||
| 99 | }; | ||
| 100 | |||
| 101 | struct ExHeader_StorageInfo { | ||
| 102 | u8 ext_save_data_id[8]; | ||
| 103 | u8 system_save_data_id[8]; | ||
| 104 | u8 reserved[8]; | ||
| 105 | u8 access_info[7]; | ||
| 106 | u8 other_attributes; | ||
| 107 | }; | ||
| 108 | |||
| 109 | struct ExHeader_ARM11_SystemLocalCaps { | ||
| 110 | u64_le program_id; | ||
| 111 | u32_le core_version; | ||
| 112 | u8 reserved_flags[2]; | ||
| 113 | union { | ||
| 114 | u8 flags0; | ||
| 115 | BitField<0, 2, u8> ideal_processor; | ||
| 116 | BitField<2, 2, u8> affinity_mask; | ||
| 117 | BitField<4, 4, u8> system_mode; | ||
| 118 | }; | ||
| 119 | u8 priority; | ||
| 120 | u8 resource_limit_descriptor[0x10][2]; | ||
| 121 | ExHeader_StorageInfo storage_info; | ||
| 122 | u8 service_access_control[0x20][8]; | ||
| 123 | u8 ex_service_access_control[0x2][8]; | ||
| 124 | u8 reserved[0xf]; | ||
| 125 | u8 resource_limit_category; | ||
| 126 | }; | ||
| 127 | |||
| 128 | struct ExHeader_ARM11_KernelCaps { | ||
| 129 | u32_le descriptors[28]; | ||
| 130 | u8 reserved[0x10]; | ||
| 131 | }; | ||
| 132 | |||
| 133 | struct ExHeader_ARM9_AccessControl { | ||
| 134 | u8 descriptors[15]; | ||
| 135 | u8 descversion; | ||
| 136 | }; | ||
| 137 | |||
| 138 | struct ExHeader_Header { | ||
| 139 | ExHeader_CodeSetInfo codeset_info; | ||
| 140 | ExHeader_DependencyList dependency_list; | ||
| 141 | ExHeader_SystemInfo system_info; | ||
| 142 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 143 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 144 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 145 | struct { | ||
| 146 | u8 signature[0x100]; | ||
| 147 | u8 ncch_public_key_modulus[0x100]; | ||
| 148 | ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; | ||
| 149 | ExHeader_ARM11_KernelCaps arm11_kernel_caps; | ||
| 150 | ExHeader_ARM9_AccessControl arm9_access_control; | ||
| 151 | } access_desc; | ||
| 152 | }; | ||
| 153 | |||
| 154 | static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); | ||
| 155 | |||
| 156 | //////////////////////////////////////////////////////////////////////////////////////////////////// | ||
| 157 | // Loader namespace | 14 | // Loader namespace |
| 158 | 15 | ||
| 159 | namespace Loader { | 16 | namespace Loader { |
| @@ -162,7 +19,8 @@ namespace Loader { | |||
| 162 | class AppLoader_NCCH final : public AppLoader { | 19 | class AppLoader_NCCH final : public AppLoader { |
| 163 | public: | 20 | public: |
| 164 | AppLoader_NCCH(FileUtil::IOFile&& file, const std::string& filepath) | 21 | AppLoader_NCCH(FileUtil::IOFile&& file, const std::string& filepath) |
| 165 | : AppLoader(std::move(file)), filepath(filepath) {} | 22 | : AppLoader(std::move(file)), filepath(filepath), base_ncch(filepath), |
| 23 | overlay_ncch(&base_ncch) {} | ||
| 166 | 24 | ||
| 167 | /** | 25 | /** |
| 168 | * Returns the type of the file | 26 | * Returns the type of the file |
| @@ -175,7 +33,7 @@ public: | |||
| 175 | return IdentifyType(file); | 33 | return IdentifyType(file); |
| 176 | } | 34 | } |
| 177 | 35 | ||
| 178 | ResultStatus Load() override; | 36 | ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; |
| 179 | 37 | ||
| 180 | /** | 38 | /** |
| 181 | * Loads the Exheader and returns the system mode for this application. | 39 | * Loads the Exheader and returns the system mode for this application. |
| @@ -196,48 +54,25 @@ public: | |||
| 196 | ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | 54 | ResultStatus ReadRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, |
| 197 | u64& size) override; | 55 | u64& size) override; |
| 198 | 56 | ||
| 57 | ResultStatus ReadUpdateRomFS(std::shared_ptr<FileUtil::IOFile>& romfs_file, u64& offset, | ||
| 58 | u64& size) override; | ||
| 59 | |||
| 199 | ResultStatus ReadTitle(std::string& title) override; | 60 | ResultStatus ReadTitle(std::string& title) override; |
| 200 | 61 | ||
| 201 | private: | 62 | private: |
| 202 | /** | 63 | /** |
| 203 | * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) | ||
| 204 | * @param name Name of section to read out of NCCH file | ||
| 205 | * @param buffer Vector to read data into | ||
| 206 | * @return ResultStatus result of function | ||
| 207 | */ | ||
| 208 | ResultStatus LoadSectionExeFS(const char* name, std::vector<u8>& buffer); | ||
| 209 | |||
| 210 | /** | ||
| 211 | * Loads .code section into memory for booting | 64 | * Loads .code section into memory for booting |
| 65 | * @param process The newly created process | ||
| 212 | * @return ResultStatus result of function | 66 | * @return ResultStatus result of function |
| 213 | */ | 67 | */ |
| 214 | ResultStatus LoadExec(); | 68 | ResultStatus LoadExec(Kernel::SharedPtr<Kernel::Process>& process); |
| 215 | |||
| 216 | /** | ||
| 217 | * Ensure ExeFS is loaded and ready for reading sections | ||
| 218 | * @return ResultStatus result of function | ||
| 219 | */ | ||
| 220 | ResultStatus LoadExeFS(); | ||
| 221 | 69 | ||
| 222 | /// Reads the region lockout info in the SMDH and send it to CFG service | 70 | /// Reads the region lockout info in the SMDH and send it to CFG service |
| 223 | void ParseRegionLockoutInfo(); | 71 | void ParseRegionLockoutInfo(); |
| 224 | 72 | ||
| 225 | bool is_exefs_loaded = false; | 73 | FileSys::NCCHContainer base_ncch; |
| 226 | bool is_compressed = false; | 74 | FileSys::NCCHContainer update_ncch; |
| 227 | 75 | FileSys::NCCHContainer* overlay_ncch; | |
| 228 | u32 entry_point = 0; | ||
| 229 | u32 code_size = 0; | ||
| 230 | u32 stack_size = 0; | ||
| 231 | u32 bss_size = 0; | ||
| 232 | u32 core_version = 0; | ||
| 233 | u8 priority = 0; | ||
| 234 | u8 resource_limit_category = 0; | ||
| 235 | u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header | ||
| 236 | u32 exefs_offset = 0; | ||
| 237 | |||
| 238 | NCCH_Header ncch_header; | ||
| 239 | ExeFs_Header exefs_header; | ||
| 240 | ExHeader_Header exheader_header; | ||
| 241 | 76 | ||
| 242 | std::string filepath; | 77 | std::string filepath; |
| 243 | }; | 78 | }; |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 68a6b1ac2..7f58be6de 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -9,6 +9,8 @@ | |||
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/swap.h" | 11 | #include "common/swap.h" |
| 12 | #include "core/arm/arm_interface.h" | ||
| 13 | #include "core/core.h" | ||
| 12 | #include "core/hle/kernel/memory.h" | 14 | #include "core/hle/kernel/memory.h" |
| 13 | #include "core/hle/kernel/process.h" | 15 | #include "core/hle/kernel/process.h" |
| 14 | #include "core/hle/lock.h" | 16 | #include "core/hle/lock.h" |
| @@ -22,10 +24,17 @@ namespace Memory { | |||
| 22 | static std::array<u8, Memory::VRAM_SIZE> vram; | 24 | static std::array<u8, Memory::VRAM_SIZE> vram; |
| 23 | static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; | 25 | static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; |
| 24 | 26 | ||
| 25 | PageTable* current_page_table = nullptr; | 27 | static PageTable* current_page_table = nullptr; |
| 26 | 28 | ||
| 27 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() { | 29 | void SetCurrentPageTable(PageTable* page_table) { |
| 28 | return ¤t_page_table->pointers; | 30 | current_page_table = page_table; |
| 31 | if (Core::System::GetInstance().IsPoweredOn()) { | ||
| 32 | Core::CPU().PageTableChanged(); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | PageTable* GetCurrentPageTable() { | ||
| 37 | return current_page_table; | ||
| 29 | } | 38 | } |
| 30 | 39 | ||
| 31 | static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { | 40 | static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { |
| @@ -73,10 +82,10 @@ void UnmapRegion(PageTable& page_table, VAddr base, u32 size) { | |||
| 73 | * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) | 82 | * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) |
| 74 | * using a VMA from the current process | 83 | * using a VMA from the current process |
| 75 | */ | 84 | */ |
| 76 | static u8* GetPointerFromVMA(VAddr vaddr) { | 85 | static u8* GetPointerFromVMA(const Kernel::Process& process, VAddr vaddr) { |
| 77 | u8* direct_pointer = nullptr; | 86 | u8* direct_pointer = nullptr; |
| 78 | 87 | ||
| 79 | auto& vm_manager = Kernel::g_current_process->vm_manager; | 88 | auto& vm_manager = process.vm_manager; |
| 80 | 89 | ||
| 81 | auto it = vm_manager.FindVMA(vaddr); | 90 | auto it = vm_manager.FindVMA(vaddr); |
| 82 | ASSERT(it != vm_manager.vma_map.end()); | 91 | ASSERT(it != vm_manager.vma_map.end()); |
| @@ -99,10 +108,18 @@ static u8* GetPointerFromVMA(VAddr vaddr) { | |||
| 99 | } | 108 | } |
| 100 | 109 | ||
| 101 | /** | 110 | /** |
| 111 | * Gets a pointer to the exact memory at the virtual address (i.e. not page aligned) | ||
| 112 | * using a VMA from the current process. | ||
| 113 | */ | ||
| 114 | static u8* GetPointerFromVMA(VAddr vaddr) { | ||
| 115 | return GetPointerFromVMA(*Kernel::g_current_process, vaddr); | ||
| 116 | } | ||
| 117 | |||
| 118 | /** | ||
| 102 | * This function should only be called for virtual addreses with attribute `PageType::Special`. | 119 | * This function should only be called for virtual addreses with attribute `PageType::Special`. |
| 103 | */ | 120 | */ |
| 104 | static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { | 121 | static MMIORegionPointer GetMMIOHandler(const PageTable& page_table, VAddr vaddr) { |
| 105 | for (const auto& region : current_page_table->special_regions) { | 122 | for (const auto& region : page_table.special_regions) { |
| 106 | if (vaddr >= region.base && vaddr < (region.base + region.size)) { | 123 | if (vaddr >= region.base && vaddr < (region.base + region.size)) { |
| 107 | return region.handler; | 124 | return region.handler; |
| 108 | } | 125 | } |
| @@ -111,6 +128,11 @@ static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { | |||
| 111 | return nullptr; // Should never happen | 128 | return nullptr; // Should never happen |
| 112 | } | 129 | } |
| 113 | 130 | ||
| 131 | static MMIORegionPointer GetMMIOHandler(VAddr vaddr) { | ||
| 132 | const PageTable& page_table = Kernel::g_current_process->vm_manager.page_table; | ||
| 133 | return GetMMIOHandler(page_table, vaddr); | ||
| 134 | } | ||
| 135 | |||
| 114 | template <typename T> | 136 | template <typename T> |
| 115 | T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr); | 137 | T ReadMMIO(MMIORegionPointer mmio_handler, VAddr addr); |
| 116 | 138 | ||
| @@ -195,18 +217,20 @@ void Write(const VAddr vaddr, const T data) { | |||
| 195 | } | 217 | } |
| 196 | } | 218 | } |
| 197 | 219 | ||
| 198 | bool IsValidVirtualAddress(const VAddr vaddr) { | 220 | bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) { |
| 199 | const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; | 221 | auto& page_table = process.vm_manager.page_table; |
| 222 | |||
| 223 | const u8* page_pointer = page_table.pointers[vaddr >> PAGE_BITS]; | ||
| 200 | if (page_pointer) | 224 | if (page_pointer) |
| 201 | return true; | 225 | return true; |
| 202 | 226 | ||
| 203 | if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) | 227 | if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) |
| 204 | return true; | 228 | return true; |
| 205 | 229 | ||
| 206 | if (current_page_table->attributes[vaddr >> PAGE_BITS] != PageType::Special) | 230 | if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special) |
| 207 | return false; | 231 | return false; |
| 208 | 232 | ||
| 209 | MMIORegionPointer mmio_region = GetMMIOHandler(vaddr); | 233 | MMIORegionPointer mmio_region = GetMMIOHandler(page_table, vaddr); |
| 210 | if (mmio_region) { | 234 | if (mmio_region) { |
| 211 | return mmio_region->IsValidAddress(vaddr); | 235 | return mmio_region->IsValidAddress(vaddr); |
| 212 | } | 236 | } |
| @@ -214,6 +238,10 @@ bool IsValidVirtualAddress(const VAddr vaddr) { | |||
| 214 | return false; | 238 | return false; |
| 215 | } | 239 | } |
| 216 | 240 | ||
| 241 | bool IsValidVirtualAddress(const VAddr vaddr) { | ||
| 242 | return IsValidVirtualAddress(*Kernel::g_current_process, vaddr); | ||
| 243 | } | ||
| 244 | |||
| 217 | bool IsValidPhysicalAddress(const PAddr paddr) { | 245 | bool IsValidPhysicalAddress(const PAddr paddr) { |
| 218 | return GetPhysicalPointer(paddr) != nullptr; | 246 | return GetPhysicalPointer(paddr) != nullptr; |
| 219 | } | 247 | } |
| @@ -316,8 +344,15 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { | |||
| 316 | 344 | ||
| 317 | for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) { | 345 | for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) { |
| 318 | boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr); | 346 | boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr); |
| 319 | if (!maybe_vaddr) | 347 | // While the physical <-> virtual mapping is 1:1 for the regions supported by the cache, |
| 348 | // some games (like Pokemon Super Mystery Dungeon) will try to use textures that go beyond | ||
| 349 | // the end address of VRAM, causing the Virtual->Physical translation to fail when flushing | ||
| 350 | // parts of the texture. | ||
| 351 | if (!maybe_vaddr) { | ||
| 352 | LOG_ERROR(HW_Memory, | ||
| 353 | "Trying to flush a cached region to an invalid physical address %08X", paddr); | ||
| 320 | continue; | 354 | continue; |
| 355 | } | ||
| 321 | VAddr vaddr = *maybe_vaddr; | 356 | VAddr vaddr = *maybe_vaddr; |
| 322 | 357 | ||
| 323 | u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS]; | 358 | u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS]; |
| @@ -329,6 +364,10 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { | |||
| 329 | if (res_count == 0) { | 364 | if (res_count == 0) { |
| 330 | PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; | 365 | PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; |
| 331 | switch (page_type) { | 366 | switch (page_type) { |
| 367 | case PageType::Unmapped: | ||
| 368 | // It is not necessary for a process to have this region mapped into its address | ||
| 369 | // space, for example, a system module need not have a VRAM mapping. | ||
| 370 | break; | ||
| 332 | case PageType::Memory: | 371 | case PageType::Memory: |
| 333 | page_type = PageType::RasterizerCachedMemory; | 372 | page_type = PageType::RasterizerCachedMemory; |
| 334 | current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; | 373 | current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr; |
| @@ -347,6 +386,10 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { | |||
| 347 | if (res_count == 0) { | 386 | if (res_count == 0) { |
| 348 | PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; | 387 | PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS]; |
| 349 | switch (page_type) { | 388 | switch (page_type) { |
| 389 | case PageType::Unmapped: | ||
| 390 | // It is not necessary for a process to have this region mapped into its address | ||
| 391 | // space, for example, a system module need not have a VRAM mapping. | ||
| 392 | break; | ||
| 350 | case PageType::RasterizerCachedMemory: { | 393 | case PageType::RasterizerCachedMemory: { |
| 351 | u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); | 394 | u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK); |
| 352 | if (pointer == nullptr) { | 395 | if (pointer == nullptr) { |
| @@ -435,16 +478,19 @@ u64 Read64(const VAddr addr) { | |||
| 435 | return Read<u64_le>(addr); | 478 | return Read<u64_le>(addr); |
| 436 | } | 479 | } |
| 437 | 480 | ||
| 438 | void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { | 481 | void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer, |
| 482 | const size_t size) { | ||
| 483 | auto& page_table = process.vm_manager.page_table; | ||
| 484 | |||
| 439 | size_t remaining_size = size; | 485 | size_t remaining_size = size; |
| 440 | size_t page_index = src_addr >> PAGE_BITS; | 486 | size_t page_index = src_addr >> PAGE_BITS; |
| 441 | size_t page_offset = src_addr & PAGE_MASK; | 487 | size_t page_offset = src_addr & PAGE_MASK; |
| 442 | 488 | ||
| 443 | while (remaining_size > 0) { | 489 | while (remaining_size > 0) { |
| 444 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); | 490 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); |
| 445 | const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; | 491 | const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); |
| 446 | 492 | ||
| 447 | switch (current_page_table->attributes[page_index]) { | 493 | switch (page_table.attributes[page_index]) { |
| 448 | case PageType::Unmapped: { | 494 | case PageType::Unmapped: { |
| 449 | LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", | 495 | LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", |
| 450 | current_vaddr, src_addr, size); | 496 | current_vaddr, src_addr, size); |
| @@ -452,27 +498,30 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { | |||
| 452 | break; | 498 | break; |
| 453 | } | 499 | } |
| 454 | case PageType::Memory: { | 500 | case PageType::Memory: { |
| 455 | DEBUG_ASSERT(current_page_table->pointers[page_index]); | 501 | DEBUG_ASSERT(page_table.pointers[page_index]); |
| 456 | 502 | ||
| 457 | const u8* src_ptr = current_page_table->pointers[page_index] + page_offset; | 503 | const u8* src_ptr = page_table.pointers[page_index] + page_offset; |
| 458 | std::memcpy(dest_buffer, src_ptr, copy_amount); | 504 | std::memcpy(dest_buffer, src_ptr, copy_amount); |
| 459 | break; | 505 | break; |
| 460 | } | 506 | } |
| 461 | case PageType::Special: { | 507 | case PageType::Special: { |
| 462 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 508 | MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); |
| 463 | 509 | DEBUG_ASSERT(handler); | |
| 464 | GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount); | 510 | handler->ReadBlock(current_vaddr, dest_buffer, copy_amount); |
| 465 | break; | 511 | break; |
| 466 | } | 512 | } |
| 467 | case PageType::RasterizerCachedMemory: { | 513 | case PageType::RasterizerCachedMemory: { |
| 468 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); | 514 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 469 | std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount); | 515 | FlushMode::Flush); |
| 516 | std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount); | ||
| 470 | break; | 517 | break; |
| 471 | } | 518 | } |
| 472 | case PageType::RasterizerCachedSpecial: { | 519 | case PageType::RasterizerCachedSpecial: { |
| 473 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 520 | MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); |
| 474 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); | 521 | DEBUG_ASSERT(handler); |
| 475 | GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount); | 522 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 523 | FlushMode::Flush); | ||
| 524 | handler->ReadBlock(current_vaddr, dest_buffer, copy_amount); | ||
| 476 | break; | 525 | break; |
| 477 | } | 526 | } |
| 478 | default: | 527 | default: |
| @@ -486,6 +535,10 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { | |||
| 486 | } | 535 | } |
| 487 | } | 536 | } |
| 488 | 537 | ||
| 538 | void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) { | ||
| 539 | ReadBlock(*Kernel::g_current_process, src_addr, dest_buffer, size); | ||
| 540 | } | ||
| 541 | |||
| 489 | void Write8(const VAddr addr, const u8 data) { | 542 | void Write8(const VAddr addr, const u8 data) { |
| 490 | Write<u8>(addr, data); | 543 | Write<u8>(addr, data); |
| 491 | } | 544 | } |
| @@ -502,16 +555,18 @@ void Write64(const VAddr addr, const u64 data) { | |||
| 502 | Write<u64_le>(addr, data); | 555 | Write<u64_le>(addr, data); |
| 503 | } | 556 | } |
| 504 | 557 | ||
| 505 | void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { | 558 | void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer, |
| 559 | const size_t size) { | ||
| 560 | auto& page_table = process.vm_manager.page_table; | ||
| 506 | size_t remaining_size = size; | 561 | size_t remaining_size = size; |
| 507 | size_t page_index = dest_addr >> PAGE_BITS; | 562 | size_t page_index = dest_addr >> PAGE_BITS; |
| 508 | size_t page_offset = dest_addr & PAGE_MASK; | 563 | size_t page_offset = dest_addr & PAGE_MASK; |
| 509 | 564 | ||
| 510 | while (remaining_size > 0) { | 565 | while (remaining_size > 0) { |
| 511 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); | 566 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); |
| 512 | const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; | 567 | const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); |
| 513 | 568 | ||
| 514 | switch (current_page_table->attributes[page_index]) { | 569 | switch (page_table.attributes[page_index]) { |
| 515 | case PageType::Unmapped: { | 570 | case PageType::Unmapped: { |
| 516 | LOG_ERROR(HW_Memory, | 571 | LOG_ERROR(HW_Memory, |
| 517 | "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", | 572 | "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", |
| @@ -519,27 +574,30 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size | |||
| 519 | break; | 574 | break; |
| 520 | } | 575 | } |
| 521 | case PageType::Memory: { | 576 | case PageType::Memory: { |
| 522 | DEBUG_ASSERT(current_page_table->pointers[page_index]); | 577 | DEBUG_ASSERT(page_table.pointers[page_index]); |
| 523 | 578 | ||
| 524 | u8* dest_ptr = current_page_table->pointers[page_index] + page_offset; | 579 | u8* dest_ptr = page_table.pointers[page_index] + page_offset; |
| 525 | std::memcpy(dest_ptr, src_buffer, copy_amount); | 580 | std::memcpy(dest_ptr, src_buffer, copy_amount); |
| 526 | break; | 581 | break; |
| 527 | } | 582 | } |
| 528 | case PageType::Special: { | 583 | case PageType::Special: { |
| 529 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 584 | MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); |
| 530 | 585 | DEBUG_ASSERT(handler); | |
| 531 | GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount); | 586 | handler->WriteBlock(current_vaddr, src_buffer, copy_amount); |
| 532 | break; | 587 | break; |
| 533 | } | 588 | } |
| 534 | case PageType::RasterizerCachedMemory: { | 589 | case PageType::RasterizerCachedMemory: { |
| 535 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); | 590 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 536 | std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount); | 591 | FlushMode::FlushAndInvalidate); |
| 592 | std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount); | ||
| 537 | break; | 593 | break; |
| 538 | } | 594 | } |
| 539 | case PageType::RasterizerCachedSpecial: { | 595 | case PageType::RasterizerCachedSpecial: { |
| 540 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 596 | MMIORegionPointer handler = GetMMIOHandler(page_table, current_vaddr); |
| 541 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); | 597 | DEBUG_ASSERT(handler); |
| 542 | GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount); | 598 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 599 | FlushMode::FlushAndInvalidate); | ||
| 600 | handler->WriteBlock(current_vaddr, src_buffer, copy_amount); | ||
| 543 | break; | 601 | break; |
| 544 | } | 602 | } |
| 545 | default: | 603 | default: |
| @@ -553,6 +611,10 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size | |||
| 553 | } | 611 | } |
| 554 | } | 612 | } |
| 555 | 613 | ||
| 614 | void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) { | ||
| 615 | WriteBlock(*Kernel::g_current_process, dest_addr, src_buffer, size); | ||
| 616 | } | ||
| 617 | |||
| 556 | void ZeroBlock(const VAddr dest_addr, const size_t size) { | 618 | void ZeroBlock(const VAddr dest_addr, const size_t size) { |
| 557 | size_t remaining_size = size; | 619 | size_t remaining_size = size; |
| 558 | size_t page_index = dest_addr >> PAGE_BITS; | 620 | size_t page_index = dest_addr >> PAGE_BITS; |
| @@ -562,7 +624,7 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { | |||
| 562 | 624 | ||
| 563 | while (remaining_size > 0) { | 625 | while (remaining_size > 0) { |
| 564 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); | 626 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); |
| 565 | const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; | 627 | const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); |
| 566 | 628 | ||
| 567 | switch (current_page_table->attributes[page_index]) { | 629 | switch (current_page_table->attributes[page_index]) { |
| 568 | case PageType::Unmapped: { | 630 | case PageType::Unmapped: { |
| @@ -584,13 +646,15 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) { | |||
| 584 | break; | 646 | break; |
| 585 | } | 647 | } |
| 586 | case PageType::RasterizerCachedMemory: { | 648 | case PageType::RasterizerCachedMemory: { |
| 587 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); | 649 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 650 | FlushMode::FlushAndInvalidate); | ||
| 588 | std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount); | 651 | std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount); |
| 589 | break; | 652 | break; |
| 590 | } | 653 | } |
| 591 | case PageType::RasterizerCachedSpecial: { | 654 | case PageType::RasterizerCachedSpecial: { |
| 592 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 655 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); |
| 593 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate); | 656 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 657 | FlushMode::FlushAndInvalidate); | ||
| 594 | GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); | 658 | GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount); |
| 595 | break; | 659 | break; |
| 596 | } | 660 | } |
| @@ -611,7 +675,7 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { | |||
| 611 | 675 | ||
| 612 | while (remaining_size > 0) { | 676 | while (remaining_size > 0) { |
| 613 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); | 677 | const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size); |
| 614 | const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset; | 678 | const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset); |
| 615 | 679 | ||
| 616 | switch (current_page_table->attributes[page_index]) { | 680 | switch (current_page_table->attributes[page_index]) { |
| 617 | case PageType::Unmapped: { | 681 | case PageType::Unmapped: { |
| @@ -635,13 +699,15 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { | |||
| 635 | break; | 699 | break; |
| 636 | } | 700 | } |
| 637 | case PageType::RasterizerCachedMemory: { | 701 | case PageType::RasterizerCachedMemory: { |
| 638 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); | 702 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 703 | FlushMode::Flush); | ||
| 639 | WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount); | 704 | WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount); |
| 640 | break; | 705 | break; |
| 641 | } | 706 | } |
| 642 | case PageType::RasterizerCachedSpecial: { | 707 | case PageType::RasterizerCachedSpecial: { |
| 643 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); | 708 | DEBUG_ASSERT(GetMMIOHandler(current_vaddr)); |
| 644 | RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush); | 709 | RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount), |
| 710 | FlushMode::Flush); | ||
| 645 | 711 | ||
| 646 | std::vector<u8> buffer(copy_amount); | 712 | std::vector<u8> buffer(copy_amount); |
| 647 | GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); | 713 | GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size()); |
| @@ -654,8 +720,8 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) { | |||
| 654 | 720 | ||
| 655 | page_index++; | 721 | page_index++; |
| 656 | page_offset = 0; | 722 | page_offset = 0; |
| 657 | dest_addr += copy_amount; | 723 | dest_addr += static_cast<VAddr>(copy_amount); |
| 658 | src_addr += copy_amount; | 724 | src_addr += static_cast<VAddr>(copy_amount); |
| 659 | remaining_size -= copy_amount; | 725 | remaining_size -= copy_amount; |
| 660 | } | 726 | } |
| 661 | } | 727 | } |
diff --git a/src/core/memory.h b/src/core/memory.h index b228a48c2..dd599f73e 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -12,6 +12,10 @@ | |||
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "core/mmio.h" | 13 | #include "core/mmio.h" |
| 14 | 14 | ||
| 15 | namespace Kernel { | ||
| 16 | class Process; | ||
| 17 | } | ||
| 18 | |||
| 15 | namespace Memory { | 19 | namespace Memory { |
| 16 | 20 | ||
| 17 | /** | 21 | /** |
| @@ -182,9 +186,13 @@ enum : VAddr { | |||
| 182 | }; | 186 | }; |
| 183 | 187 | ||
| 184 | /// Currently active page table | 188 | /// Currently active page table |
| 185 | extern PageTable* current_page_table; | 189 | void SetCurrentPageTable(PageTable* page_table); |
| 190 | PageTable* GetCurrentPageTable(); | ||
| 186 | 191 | ||
| 192 | /// Determines if the given VAddr is valid for the specified process. | ||
| 193 | bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr); | ||
| 187 | bool IsValidVirtualAddress(const VAddr addr); | 194 | bool IsValidVirtualAddress(const VAddr addr); |
| 195 | |||
| 188 | bool IsValidPhysicalAddress(const PAddr addr); | 196 | bool IsValidPhysicalAddress(const PAddr addr); |
| 189 | 197 | ||
| 190 | u8 Read8(VAddr addr); | 198 | u8 Read8(VAddr addr); |
| @@ -197,7 +205,11 @@ void Write16(VAddr addr, u16 data); | |||
| 197 | void Write32(VAddr addr, u32 data); | 205 | void Write32(VAddr addr, u32 data); |
| 198 | void Write64(VAddr addr, u64 data); | 206 | void Write64(VAddr addr, u64 data); |
| 199 | 207 | ||
| 208 | void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_buffer, | ||
| 209 | size_t size); | ||
| 200 | void ReadBlock(const VAddr src_addr, void* dest_buffer, size_t size); | 210 | void ReadBlock(const VAddr src_addr, void* dest_buffer, size_t size); |
| 211 | void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const void* src_buffer, | ||
| 212 | size_t size); | ||
| 201 | void WriteBlock(const VAddr dest_addr, const void* src_buffer, size_t size); | 213 | void WriteBlock(const VAddr dest_addr, const void* src_buffer, size_t size); |
| 202 | void ZeroBlock(const VAddr dest_addr, const size_t size); | 214 | void ZeroBlock(const VAddr dest_addr, const size_t size); |
| 203 | void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size); | 215 | void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size); |
| @@ -259,10 +271,4 @@ enum class FlushMode { | |||
| 259 | */ | 271 | */ |
| 260 | void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); | 272 | void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); |
| 261 | 273 | ||
| 262 | /** | ||
| 263 | * Dynarmic has an optimization to memory accesses when the pointer to the page exists that | ||
| 264 | * can be used by setting up the current page table as a callback. This function is used to | ||
| 265 | * retrieve the current page table for that purpose. | ||
| 266 | */ | ||
| 267 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers(); | ||
| 268 | } // namespace Memory | 274 | } // namespace Memory |
diff --git a/src/network/packet.cpp b/src/network/packet.cpp index cc60f2fbc..7e1a812f3 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp | |||
| @@ -233,7 +233,7 @@ Packet& Packet::operator<<(double in_data) { | |||
| 233 | 233 | ||
| 234 | Packet& Packet::operator<<(const char* in_data) { | 234 | Packet& Packet::operator<<(const char* in_data) { |
| 235 | // First insert string length | 235 | // First insert string length |
| 236 | u32 length = std::strlen(in_data); | 236 | u32 length = static_cast<u32>(std::strlen(in_data)); |
| 237 | *this << length; | 237 | *this << length; |
| 238 | 238 | ||
| 239 | // Then insert characters | 239 | // Then insert characters |
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 5e9c4c2bf..1aac0daa2 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -4,6 +4,7 @@ set(SRCS | |||
| 4 | core/arm/dyncom/arm_dyncom_vfp_tests.cpp | 4 | core/arm/dyncom/arm_dyncom_vfp_tests.cpp |
| 5 | core/file_sys/path_parser.cpp | 5 | core/file_sys/path_parser.cpp |
| 6 | core/hle/kernel/hle_ipc.cpp | 6 | core/hle/kernel/hle_ipc.cpp |
| 7 | core/memory/memory.cpp | ||
| 7 | glad.cpp | 8 | glad.cpp |
| 8 | tests.cpp | 9 | tests.cpp |
| 9 | ) | 10 | ) |
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 8384ce744..484713a92 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp | |||
| @@ -3,30 +3,34 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "core/core.h" | 5 | #include "core/core.h" |
| 6 | #include "core/hle/kernel/process.h" | ||
| 6 | #include "core/memory.h" | 7 | #include "core/memory.h" |
| 7 | #include "core/memory_setup.h" | 8 | #include "core/memory_setup.h" |
| 8 | #include "tests/core/arm/arm_test_common.h" | 9 | #include "tests/core/arm/arm_test_common.h" |
| 9 | 10 | ||
| 10 | namespace ArmTests { | 11 | namespace ArmTests { |
| 11 | 12 | ||
| 12 | static Memory::PageTable page_table; | 13 | static Memory::PageTable* page_table = nullptr; |
| 13 | 14 | ||
| 14 | TestEnvironment::TestEnvironment(bool mutable_memory_) | 15 | TestEnvironment::TestEnvironment(bool mutable_memory_) |
| 15 | : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { | 16 | : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { |
| 16 | 17 | ||
| 17 | page_table.pointers.fill(nullptr); | 18 | Kernel::g_current_process = Kernel::Process::Create(Kernel::CodeSet::Create("", 0)); |
| 18 | page_table.attributes.fill(Memory::PageType::Unmapped); | 19 | page_table = &Kernel::g_current_process->vm_manager.page_table; |
| 19 | page_table.cached_res_count.fill(0); | ||
| 20 | 20 | ||
| 21 | Memory::MapIoRegion(page_table, 0x00000000, 0x80000000, test_memory); | 21 | page_table->pointers.fill(nullptr); |
| 22 | Memory::MapIoRegion(page_table, 0x80000000, 0x80000000, test_memory); | 22 | page_table->attributes.fill(Memory::PageType::Unmapped); |
| 23 | page_table->cached_res_count.fill(0); | ||
| 23 | 24 | ||
| 24 | Memory::current_page_table = &page_table; | 25 | Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory); |
| 26 | Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory); | ||
| 27 | |||
| 28 | Memory::SetCurrentPageTable(page_table); | ||
| 25 | } | 29 | } |
| 26 | 30 | ||
| 27 | TestEnvironment::~TestEnvironment() { | 31 | TestEnvironment::~TestEnvironment() { |
| 28 | Memory::UnmapRegion(page_table, 0x80000000, 0x80000000); | 32 | Memory::UnmapRegion(*page_table, 0x80000000, 0x80000000); |
| 29 | Memory::UnmapRegion(page_table, 0x00000000, 0x80000000); | 33 | Memory::UnmapRegion(*page_table, 0x00000000, 0x80000000); |
| 30 | } | 34 | } |
| 31 | 35 | ||
| 32 | void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { | 36 | void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { |
diff --git a/src/tests/core/arm/dyncom/arm_dyncom_vfp_tests.cpp b/src/tests/core/arm/dyncom/arm_dyncom_vfp_tests.cpp index 86de41773..83719a58e 100644 --- a/src/tests/core/arm/dyncom/arm_dyncom_vfp_tests.cpp +++ b/src/tests/core/arm/dyncom/arm_dyncom_vfp_tests.cpp | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include <catch.hpp> | 5 | #include <catch.hpp> |
| 6 | 6 | ||
| 7 | #include "core/arm/dyncom/arm_dyncom.h" | 7 | #include "core/arm/dyncom/arm_dyncom.h" |
| 8 | #include "core/core_timing.h" | ||
| 8 | #include "tests/core/arm/arm_test_common.h" | 9 | #include "tests/core/arm/arm_test_common.h" |
| 9 | 10 | ||
| 10 | namespace ArmTests { | 11 | namespace ArmTests { |
| @@ -29,7 +30,6 @@ TEST_CASE("ARM_DynCom (vfp): vadd", "[arm_dyncom]") { | |||
| 29 | }}; | 30 | }}; |
| 30 | 31 | ||
| 31 | for (const auto& test_case : test_cases) { | 32 | for (const auto& test_case : test_cases) { |
| 32 | dyncom.down_count = 1000; // Ensure that CoreTimeing will not be called. | ||
| 33 | dyncom.SetPC(0); | 33 | dyncom.SetPC(0); |
| 34 | dyncom.SetVFPSystemReg(VFP_FPSCR, test_case.initial_fpscr); | 34 | dyncom.SetVFPSystemReg(VFP_FPSCR, test_case.initial_fpscr); |
| 35 | dyncom.SetVFPReg(4, test_case.a); | 35 | dyncom.SetVFPReg(4, test_case.a); |
diff --git a/src/tests/core/memory/memory.cpp b/src/tests/core/memory/memory.cpp new file mode 100644 index 000000000..a01b896f7 --- /dev/null +++ b/src/tests/core/memory/memory.cpp | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <catch.hpp> | ||
| 6 | #include "core/hle/kernel/memory.h" | ||
| 7 | #include "core/hle/kernel/process.h" | ||
| 8 | #include "core/memory.h" | ||
| 9 | |||
| 10 | TEST_CASE("Memory::IsValidVirtualAddress", "[core][memory]") { | ||
| 11 | SECTION("these regions should not be mapped on an empty process") { | ||
| 12 | auto process = Kernel::Process::Create(Kernel::CodeSet::Create("", 0)); | ||
| 13 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::PROCESS_IMAGE_VADDR) == false); | ||
| 14 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::HEAP_VADDR) == false); | ||
| 15 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::LINEAR_HEAP_VADDR) == false); | ||
| 16 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::VRAM_VADDR) == false); | ||
| 17 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == false); | ||
| 18 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::SHARED_PAGE_VADDR) == false); | ||
| 19 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::TLS_AREA_VADDR) == false); | ||
| 20 | } | ||
| 21 | |||
| 22 | SECTION("CONFIG_MEMORY_VADDR and SHARED_PAGE_VADDR should be valid after mapping them") { | ||
| 23 | auto process = Kernel::Process::Create(Kernel::CodeSet::Create("", 0)); | ||
| 24 | Kernel::MapSharedPages(process->vm_manager); | ||
| 25 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == true); | ||
| 26 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::SHARED_PAGE_VADDR) == true); | ||
| 27 | } | ||
| 28 | |||
| 29 | SECTION("special regions should be valid after mapping them") { | ||
| 30 | auto process = Kernel::Process::Create(Kernel::CodeSet::Create("", 0)); | ||
| 31 | SECTION("VRAM") { | ||
| 32 | Kernel::HandleSpecialMapping(process->vm_manager, | ||
| 33 | {Memory::VRAM_VADDR, Memory::VRAM_SIZE, false, false}); | ||
| 34 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::VRAM_VADDR) == true); | ||
| 35 | } | ||
| 36 | |||
| 37 | SECTION("IO (Not yet implemented)") { | ||
| 38 | Kernel::HandleSpecialMapping( | ||
| 39 | process->vm_manager, {Memory::IO_AREA_VADDR, Memory::IO_AREA_SIZE, false, false}); | ||
| 40 | CHECK_FALSE(Memory::IsValidVirtualAddress(*process, Memory::IO_AREA_VADDR) == true); | ||
| 41 | } | ||
| 42 | |||
| 43 | SECTION("DSP") { | ||
| 44 | Kernel::HandleSpecialMapping( | ||
| 45 | process->vm_manager, {Memory::DSP_RAM_VADDR, Memory::DSP_RAM_SIZE, false, false}); | ||
| 46 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::DSP_RAM_VADDR) == true); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | SECTION("Unmapping a VAddr should make it invalid") { | ||
| 51 | auto process = Kernel::Process::Create(Kernel::CodeSet::Create("", 0)); | ||
| 52 | Kernel::MapSharedPages(process->vm_manager); | ||
| 53 | process->vm_manager.UnmapRange(Memory::CONFIG_MEMORY_VADDR, Memory::CONFIG_MEMORY_SIZE); | ||
| 54 | CHECK(Memory::IsValidVirtualAddress(*process, Memory::CONFIG_MEMORY_VADDR) == false); | ||
| 55 | } | ||
| 56 | } | ||
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index fb65a3a0a..caf9f7a06 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp | |||
| @@ -119,6 +119,224 @@ static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup, | |||
| 119 | } | 119 | } |
| 120 | } | 120 | } |
| 121 | 121 | ||
| 122 | static void LoadDefaultVertexAttributes(u32 register_value) { | ||
| 123 | auto& regs = g_state.regs; | ||
| 124 | |||
| 125 | // TODO: Does actual hardware indeed keep an intermediate buffer or does | ||
| 126 | // it directly write the values? | ||
| 127 | default_attr_write_buffer[default_attr_counter++] = register_value; | ||
| 128 | |||
| 129 | // Default attributes are written in a packed format such that four float24 values are encoded | ||
| 130 | // in three 32-bit numbers. | ||
| 131 | // We write to internal memory once a full such vector is written. | ||
| 132 | if (default_attr_counter >= 3) { | ||
| 133 | default_attr_counter = 0; | ||
| 134 | |||
| 135 | auto& setup = regs.pipeline.vs_default_attributes_setup; | ||
| 136 | |||
| 137 | if (setup.index >= 16) { | ||
| 138 | LOG_ERROR(HW_GPU, "Invalid VS default attribute index %d", (int)setup.index); | ||
| 139 | return; | ||
| 140 | } | ||
| 141 | |||
| 142 | Math::Vec4<float24> attribute; | ||
| 143 | |||
| 144 | // NOTE: The destination component order indeed is "backwards" | ||
| 145 | attribute.w = float24::FromRaw(default_attr_write_buffer[0] >> 8); | ||
| 146 | attribute.z = float24::FromRaw(((default_attr_write_buffer[0] & 0xFF) << 16) | | ||
| 147 | ((default_attr_write_buffer[1] >> 16) & 0xFFFF)); | ||
| 148 | attribute.y = float24::FromRaw(((default_attr_write_buffer[1] & 0xFFFF) << 8) | | ||
| 149 | ((default_attr_write_buffer[2] >> 24) & 0xFF)); | ||
| 150 | attribute.x = float24::FromRaw(default_attr_write_buffer[2] & 0xFFFFFF); | ||
| 151 | |||
| 152 | LOG_TRACE(HW_GPU, "Set default VS attribute %x to (%f %f %f %f)", (int)setup.index, | ||
| 153 | attribute.x.ToFloat32(), attribute.y.ToFloat32(), attribute.z.ToFloat32(), | ||
| 154 | attribute.w.ToFloat32()); | ||
| 155 | |||
| 156 | // TODO: Verify that this actually modifies the register! | ||
| 157 | if (setup.index < 15) { | ||
| 158 | g_state.input_default_attributes.attr[setup.index] = attribute; | ||
| 159 | setup.index++; | ||
| 160 | } else { | ||
| 161 | // Put each attribute into an immediate input buffer. When all specified immediate | ||
| 162 | // attributes are present, the Vertex Shader is invoked and everything is sent to | ||
| 163 | // the primitive assembler. | ||
| 164 | |||
| 165 | auto& immediate_input = g_state.immediate.input_vertex; | ||
| 166 | auto& immediate_attribute_id = g_state.immediate.current_attribute; | ||
| 167 | |||
| 168 | immediate_input.attr[immediate_attribute_id] = attribute; | ||
| 169 | |||
| 170 | if (immediate_attribute_id < regs.pipeline.max_input_attrib_index) { | ||
| 171 | immediate_attribute_id += 1; | ||
| 172 | } else { | ||
| 173 | MICROPROFILE_SCOPE(GPU_Drawing); | ||
| 174 | immediate_attribute_id = 0; | ||
| 175 | |||
| 176 | auto* shader_engine = Shader::GetEngine(); | ||
| 177 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); | ||
| 178 | |||
| 179 | // Send to vertex shader | ||
| 180 | if (g_debug_context) | ||
| 181 | g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, | ||
| 182 | static_cast<void*>(&immediate_input)); | ||
| 183 | Shader::UnitState shader_unit; | ||
| 184 | Shader::AttributeBuffer output{}; | ||
| 185 | |||
| 186 | shader_unit.LoadInput(regs.vs, immediate_input); | ||
| 187 | shader_engine->Run(g_state.vs, shader_unit); | ||
| 188 | shader_unit.WriteOutput(regs.vs, output); | ||
| 189 | |||
| 190 | // Send to geometry pipeline | ||
| 191 | if (g_state.immediate.reset_geometry_pipeline) { | ||
| 192 | g_state.geometry_pipeline.Reconfigure(); | ||
| 193 | g_state.immediate.reset_geometry_pipeline = false; | ||
| 194 | } | ||
| 195 | ASSERT(!g_state.geometry_pipeline.NeedIndexInput()); | ||
| 196 | g_state.geometry_pipeline.Setup(shader_engine); | ||
| 197 | g_state.geometry_pipeline.SubmitVertex(output); | ||
| 198 | |||
| 199 | // TODO: If drawing after every immediate mode triangle kills performance, | ||
| 200 | // change it to flush triangles whenever a drawing config register changes | ||
| 201 | // See: https://github.com/citra-emu/citra/pull/2866#issuecomment-327011550 | ||
| 202 | VideoCore::g_renderer->Rasterizer()->DrawTriangles(); | ||
| 203 | if (g_debug_context) { | ||
| 204 | g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | } | ||
| 210 | |||
| 211 | static void Draw(u32 command_id) { | ||
| 212 | MICROPROFILE_SCOPE(GPU_Drawing); | ||
| 213 | auto& regs = g_state.regs; | ||
| 214 | |||
| 215 | #if PICA_LOG_TEV | ||
| 216 | DebugUtils::DumpTevStageConfig(regs.GetTevStages()); | ||
| 217 | #endif | ||
| 218 | if (g_debug_context) | ||
| 219 | g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||
| 220 | |||
| 221 | // Processes information about internal vertex attributes to figure out how a vertex is | ||
| 222 | // loaded. | ||
| 223 | // Later, these can be compiled and cached. | ||
| 224 | const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); | ||
| 225 | VertexLoader loader(regs.pipeline); | ||
| 226 | |||
| 227 | // Load vertices | ||
| 228 | bool is_indexed = (command_id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); | ||
| 229 | |||
| 230 | const auto& index_info = regs.pipeline.index_array; | ||
| 231 | const u8* index_address_8 = Memory::GetPhysicalPointer(base_address + index_info.offset); | ||
| 232 | const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); | ||
| 233 | bool index_u16 = index_info.format != 0; | ||
| 234 | |||
| 235 | PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler; | ||
| 236 | |||
| 237 | if (g_debug_context && g_debug_context->recorder) { | ||
| 238 | for (int i = 0; i < 3; ++i) { | ||
| 239 | const auto texture = regs.texturing.GetTextures()[i]; | ||
| 240 | if (!texture.enabled) | ||
| 241 | continue; | ||
| 242 | |||
| 243 | u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); | ||
| 244 | g_debug_context->recorder->MemoryAccessed( | ||
| 245 | texture_data, Pica::TexturingRegs::NibblesPerPixel(texture.format) * | ||
| 246 | texture.config.width / 2 * texture.config.height, | ||
| 247 | texture.config.GetPhysicalAddress()); | ||
| 248 | } | ||
| 249 | } | ||
| 250 | |||
| 251 | DebugUtils::MemoryAccessTracker memory_accesses; | ||
| 252 | |||
| 253 | // Simple circular-replacement vertex cache | ||
| 254 | // The size has been tuned for optimal balance between hit-rate and the cost of lookup | ||
| 255 | const size_t VERTEX_CACHE_SIZE = 32; | ||
| 256 | std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; | ||
| 257 | std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache; | ||
| 258 | Shader::AttributeBuffer vs_output; | ||
| 259 | |||
| 260 | unsigned int vertex_cache_pos = 0; | ||
| 261 | vertex_cache_ids.fill(-1); | ||
| 262 | |||
| 263 | auto* shader_engine = Shader::GetEngine(); | ||
| 264 | Shader::UnitState shader_unit; | ||
| 265 | |||
| 266 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); | ||
| 267 | |||
| 268 | g_state.geometry_pipeline.Reconfigure(); | ||
| 269 | g_state.geometry_pipeline.Setup(shader_engine); | ||
| 270 | if (g_state.geometry_pipeline.NeedIndexInput()) | ||
| 271 | ASSERT(is_indexed); | ||
| 272 | |||
| 273 | for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { | ||
| 274 | // Indexed rendering doesn't use the start offset | ||
| 275 | unsigned int vertex = is_indexed | ||
| 276 | ? (index_u16 ? index_address_16[index] : index_address_8[index]) | ||
| 277 | : (index + regs.pipeline.vertex_offset); | ||
| 278 | |||
| 279 | // -1 is a common special value used for primitive restart. Since it's unknown if | ||
| 280 | // the PICA supports it, and it would mess up the caching, guard against it here. | ||
| 281 | ASSERT(vertex != -1); | ||
| 282 | |||
| 283 | bool vertex_cache_hit = false; | ||
| 284 | |||
| 285 | if (is_indexed) { | ||
| 286 | if (g_state.geometry_pipeline.NeedIndexInput()) { | ||
| 287 | g_state.geometry_pipeline.SubmitIndex(vertex); | ||
| 288 | continue; | ||
| 289 | } | ||
| 290 | |||
| 291 | if (g_debug_context && Pica::g_debug_context->recorder) { | ||
| 292 | int size = index_u16 ? 2 : 1; | ||
| 293 | memory_accesses.AddAccess(base_address + index_info.offset + size * index, size); | ||
| 294 | } | ||
| 295 | |||
| 296 | for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { | ||
| 297 | if (vertex == vertex_cache_ids[i]) { | ||
| 298 | vs_output = vertex_cache[i]; | ||
| 299 | vertex_cache_hit = true; | ||
| 300 | break; | ||
| 301 | } | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | if (!vertex_cache_hit) { | ||
| 306 | // Initialize data for the current vertex | ||
| 307 | Shader::AttributeBuffer input; | ||
| 308 | loader.LoadVertex(base_address, index, vertex, input, memory_accesses); | ||
| 309 | |||
| 310 | // Send to vertex shader | ||
| 311 | if (g_debug_context) | ||
| 312 | g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, | ||
| 313 | (void*)&input); | ||
| 314 | shader_unit.LoadInput(regs.vs, input); | ||
| 315 | shader_engine->Run(g_state.vs, shader_unit); | ||
| 316 | shader_unit.WriteOutput(regs.vs, vs_output); | ||
| 317 | |||
| 318 | if (is_indexed) { | ||
| 319 | vertex_cache[vertex_cache_pos] = vs_output; | ||
| 320 | vertex_cache_ids[vertex_cache_pos] = vertex; | ||
| 321 | vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | // Send to geometry pipeline | ||
| 326 | g_state.geometry_pipeline.SubmitVertex(vs_output); | ||
| 327 | } | ||
| 328 | |||
| 329 | for (auto& range : memory_accesses.ranges) { | ||
| 330 | g_debug_context->recorder->MemoryAccessed(Memory::GetPhysicalPointer(range.first), | ||
| 331 | range.second, range.first); | ||
| 332 | } | ||
| 333 | |||
| 334 | VideoCore::g_renderer->Rasterizer()->DrawTriangles(); | ||
| 335 | if (g_debug_context) { | ||
| 336 | g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 337 | } | ||
| 338 | } | ||
| 339 | |||
| 122 | static void WritePicaReg(u32 id, u32 value, u32 mask) { | 340 | static void WritePicaReg(u32 id, u32 value, u32 mask) { |
| 123 | auto& regs = g_state.regs; | 341 | auto& regs = g_state.regs; |
| 124 | 342 | ||
| @@ -168,98 +386,12 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 168 | // Load default vertex input attributes | 386 | // Load default vertex input attributes |
| 169 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[0], 0x233): | 387 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[0], 0x233): |
| 170 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[1], 0x234): | 388 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[1], 0x234): |
| 171 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[2], 0x235): { | 389 | case PICA_REG_INDEX_WORKAROUND(pipeline.vs_default_attributes_setup.set_value[2], 0x235): |
| 172 | // TODO: Does actual hardware indeed keep an intermediate buffer or does | 390 | LoadDefaultVertexAttributes(value); |
| 173 | // it directly write the values? | ||
| 174 | default_attr_write_buffer[default_attr_counter++] = value; | ||
| 175 | |||
| 176 | // Default attributes are written in a packed format such that four float24 values are | ||
| 177 | // encoded in | ||
| 178 | // three 32-bit numbers. We write to internal memory once a full such vector is | ||
| 179 | // written. | ||
| 180 | if (default_attr_counter >= 3) { | ||
| 181 | default_attr_counter = 0; | ||
| 182 | |||
| 183 | auto& setup = regs.pipeline.vs_default_attributes_setup; | ||
| 184 | |||
| 185 | if (setup.index >= 16) { | ||
| 186 | LOG_ERROR(HW_GPU, "Invalid VS default attribute index %d", (int)setup.index); | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | |||
| 190 | Math::Vec4<float24> attribute; | ||
| 191 | |||
| 192 | // NOTE: The destination component order indeed is "backwards" | ||
| 193 | attribute.w = float24::FromRaw(default_attr_write_buffer[0] >> 8); | ||
| 194 | attribute.z = float24::FromRaw(((default_attr_write_buffer[0] & 0xFF) << 16) | | ||
| 195 | ((default_attr_write_buffer[1] >> 16) & 0xFFFF)); | ||
| 196 | attribute.y = float24::FromRaw(((default_attr_write_buffer[1] & 0xFFFF) << 8) | | ||
| 197 | ((default_attr_write_buffer[2] >> 24) & 0xFF)); | ||
| 198 | attribute.x = float24::FromRaw(default_attr_write_buffer[2] & 0xFFFFFF); | ||
| 199 | |||
| 200 | LOG_TRACE(HW_GPU, "Set default VS attribute %x to (%f %f %f %f)", (int)setup.index, | ||
| 201 | attribute.x.ToFloat32(), attribute.y.ToFloat32(), attribute.z.ToFloat32(), | ||
| 202 | attribute.w.ToFloat32()); | ||
| 203 | |||
| 204 | // TODO: Verify that this actually modifies the register! | ||
| 205 | if (setup.index < 15) { | ||
| 206 | g_state.input_default_attributes.attr[setup.index] = attribute; | ||
| 207 | setup.index++; | ||
| 208 | } else { | ||
| 209 | // Put each attribute into an immediate input buffer. When all specified immediate | ||
| 210 | // attributes are present, the Vertex Shader is invoked and everything is sent to | ||
| 211 | // the primitive assembler. | ||
| 212 | |||
| 213 | auto& immediate_input = g_state.immediate.input_vertex; | ||
| 214 | auto& immediate_attribute_id = g_state.immediate.current_attribute; | ||
| 215 | |||
| 216 | immediate_input.attr[immediate_attribute_id] = attribute; | ||
| 217 | |||
| 218 | if (immediate_attribute_id < regs.pipeline.max_input_attrib_index) { | ||
| 219 | immediate_attribute_id += 1; | ||
| 220 | } else { | ||
| 221 | MICROPROFILE_SCOPE(GPU_Drawing); | ||
| 222 | immediate_attribute_id = 0; | ||
| 223 | |||
| 224 | auto* shader_engine = Shader::GetEngine(); | ||
| 225 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); | ||
| 226 | |||
| 227 | // Send to vertex shader | ||
| 228 | if (g_debug_context) | ||
| 229 | g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, | ||
| 230 | static_cast<void*>(&immediate_input)); | ||
| 231 | Shader::UnitState shader_unit; | ||
| 232 | Shader::AttributeBuffer output{}; | ||
| 233 | |||
| 234 | shader_unit.LoadInput(regs.vs, immediate_input); | ||
| 235 | shader_engine->Run(g_state.vs, shader_unit); | ||
| 236 | shader_unit.WriteOutput(regs.vs, output); | ||
| 237 | |||
| 238 | // Send to geometry pipeline | ||
| 239 | if (g_state.immediate.reset_geometry_pipeline) { | ||
| 240 | g_state.geometry_pipeline.Reconfigure(); | ||
| 241 | g_state.immediate.reset_geometry_pipeline = false; | ||
| 242 | } | ||
| 243 | ASSERT(!g_state.geometry_pipeline.NeedIndexInput()); | ||
| 244 | g_state.geometry_pipeline.Setup(shader_engine); | ||
| 245 | g_state.geometry_pipeline.SubmitVertex(output); | ||
| 246 | } | ||
| 247 | } | ||
| 248 | } | ||
| 249 | break; | 391 | break; |
| 250 | } | ||
| 251 | 392 | ||
| 252 | case PICA_REG_INDEX(pipeline.gpu_mode): | 393 | case PICA_REG_INDEX(pipeline.gpu_mode): |
| 253 | if (regs.pipeline.gpu_mode == PipelineRegs::GPUMode::Configuring) { | 394 | // This register likely just enables vertex processing and doesn't need any special handling |
| 254 | MICROPROFILE_SCOPE(GPU_Drawing); | ||
| 255 | |||
| 256 | // Draw immediate mode triangles when GPU Mode is set to GPUMode::Configuring | ||
| 257 | VideoCore::g_renderer->Rasterizer()->DrawTriangles(); | ||
| 258 | |||
| 259 | if (g_debug_context) { | ||
| 260 | g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); | ||
| 261 | } | ||
| 262 | } | ||
| 263 | break; | 395 | break; |
| 264 | 396 | ||
| 265 | case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c): | 397 | case PICA_REG_INDEX_WORKAROUND(pipeline.command_buffer.trigger[0], 0x23c): |
| @@ -275,131 +407,9 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 275 | 407 | ||
| 276 | // It seems like these trigger vertex rendering | 408 | // It seems like these trigger vertex rendering |
| 277 | case PICA_REG_INDEX(pipeline.trigger_draw): | 409 | case PICA_REG_INDEX(pipeline.trigger_draw): |
| 278 | case PICA_REG_INDEX(pipeline.trigger_draw_indexed): { | 410 | case PICA_REG_INDEX(pipeline.trigger_draw_indexed): |
| 279 | MICROPROFILE_SCOPE(GPU_Drawing); | 411 | Draw(id); |
| 280 | |||
| 281 | #if PICA_LOG_TEV | ||
| 282 | DebugUtils::DumpTevStageConfig(regs.GetTevStages()); | ||
| 283 | #endif | ||
| 284 | if (g_debug_context) | ||
| 285 | g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); | ||
| 286 | |||
| 287 | // Processes information about internal vertex attributes to figure out how a vertex is | ||
| 288 | // loaded. | ||
| 289 | // Later, these can be compiled and cached. | ||
| 290 | const u32 base_address = regs.pipeline.vertex_attributes.GetPhysicalBaseAddress(); | ||
| 291 | VertexLoader loader(regs.pipeline); | ||
| 292 | |||
| 293 | // Load vertices | ||
| 294 | bool is_indexed = (id == PICA_REG_INDEX(pipeline.trigger_draw_indexed)); | ||
| 295 | |||
| 296 | const auto& index_info = regs.pipeline.index_array; | ||
| 297 | const u8* index_address_8 = Memory::GetPhysicalPointer(base_address + index_info.offset); | ||
| 298 | const u16* index_address_16 = reinterpret_cast<const u16*>(index_address_8); | ||
| 299 | bool index_u16 = index_info.format != 0; | ||
| 300 | |||
| 301 | PrimitiveAssembler<Shader::OutputVertex>& primitive_assembler = g_state.primitive_assembler; | ||
| 302 | |||
| 303 | if (g_debug_context && g_debug_context->recorder) { | ||
| 304 | for (int i = 0; i < 3; ++i) { | ||
| 305 | const auto texture = regs.texturing.GetTextures()[i]; | ||
| 306 | if (!texture.enabled) | ||
| 307 | continue; | ||
| 308 | |||
| 309 | u8* texture_data = Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress()); | ||
| 310 | g_debug_context->recorder->MemoryAccessed( | ||
| 311 | texture_data, Pica::TexturingRegs::NibblesPerPixel(texture.format) * | ||
| 312 | texture.config.width / 2 * texture.config.height, | ||
| 313 | texture.config.GetPhysicalAddress()); | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | DebugUtils::MemoryAccessTracker memory_accesses; | ||
| 318 | |||
| 319 | // Simple circular-replacement vertex cache | ||
| 320 | // The size has been tuned for optimal balance between hit-rate and the cost of lookup | ||
| 321 | const size_t VERTEX_CACHE_SIZE = 32; | ||
| 322 | std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; | ||
| 323 | std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache; | ||
| 324 | Shader::AttributeBuffer vs_output; | ||
| 325 | |||
| 326 | unsigned int vertex_cache_pos = 0; | ||
| 327 | vertex_cache_ids.fill(-1); | ||
| 328 | |||
| 329 | auto* shader_engine = Shader::GetEngine(); | ||
| 330 | Shader::UnitState shader_unit; | ||
| 331 | |||
| 332 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); | ||
| 333 | |||
| 334 | g_state.geometry_pipeline.Reconfigure(); | ||
| 335 | g_state.geometry_pipeline.Setup(shader_engine); | ||
| 336 | if (g_state.geometry_pipeline.NeedIndexInput()) | ||
| 337 | ASSERT(is_indexed); | ||
| 338 | |||
| 339 | for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { | ||
| 340 | // Indexed rendering doesn't use the start offset | ||
| 341 | unsigned int vertex = | ||
| 342 | is_indexed ? (index_u16 ? index_address_16[index] : index_address_8[index]) | ||
| 343 | : (index + regs.pipeline.vertex_offset); | ||
| 344 | |||
| 345 | // -1 is a common special value used for primitive restart. Since it's unknown if | ||
| 346 | // the PICA supports it, and it would mess up the caching, guard against it here. | ||
| 347 | ASSERT(vertex != -1); | ||
| 348 | |||
| 349 | bool vertex_cache_hit = false; | ||
| 350 | |||
| 351 | if (is_indexed) { | ||
| 352 | if (g_state.geometry_pipeline.NeedIndexInput()) { | ||
| 353 | g_state.geometry_pipeline.SubmitIndex(vertex); | ||
| 354 | continue; | ||
| 355 | } | ||
| 356 | |||
| 357 | if (g_debug_context && Pica::g_debug_context->recorder) { | ||
| 358 | int size = index_u16 ? 2 : 1; | ||
| 359 | memory_accesses.AddAccess(base_address + index_info.offset + size * index, | ||
| 360 | size); | ||
| 361 | } | ||
| 362 | |||
| 363 | for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { | ||
| 364 | if (vertex == vertex_cache_ids[i]) { | ||
| 365 | vs_output = vertex_cache[i]; | ||
| 366 | vertex_cache_hit = true; | ||
| 367 | break; | ||
| 368 | } | ||
| 369 | } | ||
| 370 | } | ||
| 371 | |||
| 372 | if (!vertex_cache_hit) { | ||
| 373 | // Initialize data for the current vertex | ||
| 374 | Shader::AttributeBuffer input; | ||
| 375 | loader.LoadVertex(base_address, index, vertex, input, memory_accesses); | ||
| 376 | |||
| 377 | // Send to vertex shader | ||
| 378 | if (g_debug_context) | ||
| 379 | g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, | ||
| 380 | (void*)&input); | ||
| 381 | shader_unit.LoadInput(regs.vs, input); | ||
| 382 | shader_engine->Run(g_state.vs, shader_unit); | ||
| 383 | shader_unit.WriteOutput(regs.vs, vs_output); | ||
| 384 | |||
| 385 | if (is_indexed) { | ||
| 386 | vertex_cache[vertex_cache_pos] = vs_output; | ||
| 387 | vertex_cache_ids[vertex_cache_pos] = vertex; | ||
| 388 | vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; | ||
| 389 | } | ||
| 390 | } | ||
| 391 | |||
| 392 | // Send to geometry pipeline | ||
| 393 | g_state.geometry_pipeline.SubmitVertex(vs_output); | ||
| 394 | } | ||
| 395 | |||
| 396 | for (auto& range : memory_accesses.ranges) { | ||
| 397 | g_debug_context->recorder->MemoryAccessed(Memory::GetPhysicalPointer(range.first), | ||
| 398 | range.second, range.first); | ||
| 399 | } | ||
| 400 | |||
| 401 | break; | 412 | break; |
| 402 | } | ||
| 403 | 413 | ||
| 404 | case PICA_REG_INDEX(gs.bool_uniforms): | 414 | case PICA_REG_INDEX(gs.bool_uniforms): |
| 405 | WriteUniformBoolReg(g_state.gs, g_state.regs.gs.bool_uniforms.Value()); | 415 | WriteUniformBoolReg(g_state.gs, g_state.regs.gs.bool_uniforms.Value()); |
| @@ -632,6 +642,6 @@ void ProcessCommandList(const u32* list, u32 size) { | |||
| 632 | } | 642 | } |
| 633 | } | 643 | } |
| 634 | 644 | ||
| 635 | } // namespace | 645 | } // namespace CommandProcessor |
| 636 | 646 | ||
| 637 | } // namespace | 647 | } // namespace Pica |
diff --git a/src/video_core/geometry_pipeline.cpp b/src/video_core/geometry_pipeline.cpp index b146e2ecb..98ff2ccd3 100644 --- a/src/video_core/geometry_pipeline.cpp +++ b/src/video_core/geometry_pipeline.cpp | |||
| @@ -105,7 +105,7 @@ public: | |||
| 105 | DEBUG_ASSERT(need_index); | 105 | DEBUG_ASSERT(need_index); |
| 106 | 106 | ||
| 107 | // The number of vertex input is put to the uniform register | 107 | // The number of vertex input is put to the uniform register |
| 108 | float24 vertex_num = float24::FromFloat32(val); | 108 | float24 vertex_num = float24::FromFloat32(static_cast<float>(val)); |
| 109 | setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num); | 109 | setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num); |
| 110 | 110 | ||
| 111 | // The second uniform register and so on are used for receiving input vertices | 111 | // The second uniform register and so on are used for receiving input vertices |
diff --git a/src/video_core/pica_types.h b/src/video_core/pica_types.h index 5d7e10066..2eafa7e9e 100644 --- a/src/video_core/pica_types.h +++ b/src/video_core/pica_types.h | |||
| @@ -58,11 +58,12 @@ public: | |||
| 58 | } | 58 | } |
| 59 | 59 | ||
| 60 | Float<M, E> operator*(const Float<M, E>& flt) const { | 60 | Float<M, E> operator*(const Float<M, E>& flt) const { |
| 61 | if ((this->value == 0.f && !std::isnan(flt.value)) || | 61 | float result = value * flt.ToFloat32(); |
| 62 | (flt.value == 0.f && !std::isnan(this->value))) | 62 | // PICA gives 0 instead of NaN when multiplying by inf |
| 63 | // PICA gives 0 instead of NaN when multiplying by inf | 63 | if (!std::isnan(value) && !std::isnan(flt.ToFloat32())) |
| 64 | return Zero(); | 64 | if (std::isnan(result)) |
| 65 | return Float<M, E>::FromFloat32(ToFloat32() * flt.ToFloat32()); | 65 | result = 0.f; |
| 66 | return Float<M, E>::FromFloat32(result); | ||
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | Float<M, E> operator/(const Float<M, E>& flt) const { | 69 | Float<M, E> operator/(const Float<M, E>& flt) const { |
| @@ -78,12 +79,7 @@ public: | |||
| 78 | } | 79 | } |
| 79 | 80 | ||
| 80 | Float<M, E>& operator*=(const Float<M, E>& flt) { | 81 | Float<M, E>& operator*=(const Float<M, E>& flt) { |
| 81 | if ((this->value == 0.f && !std::isnan(flt.value)) || | 82 | value = operator*(flt).value; |
| 82 | (flt.value == 0.f && !std::isnan(this->value))) | ||
| 83 | // PICA gives 0 instead of NaN when multiplying by inf | ||
| 84 | *this = Zero(); | ||
| 85 | else | ||
| 86 | value *= flt.ToFloat32(); | ||
| 87 | return *this; | 83 | return *this; |
| 88 | } | 84 | } |
| 89 | 85 | ||
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index 06a905766..5770ae08f 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp | |||
| @@ -267,9 +267,9 @@ void OpenGLState::Apply() const { | |||
| 267 | for (size_t i = 0; i < clip_distance.size(); ++i) { | 267 | for (size_t i = 0; i < clip_distance.size(); ++i) { |
| 268 | if (clip_distance[i] != cur_state.clip_distance[i]) { | 268 | if (clip_distance[i] != cur_state.clip_distance[i]) { |
| 269 | if (clip_distance[i]) { | 269 | if (clip_distance[i]) { |
| 270 | glEnable(GL_CLIP_DISTANCE0 + i); | 270 | glEnable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i)); |
| 271 | } else { | 271 | } else { |
| 272 | glDisable(GL_CLIP_DISTANCE0 + i); | 272 | glDisable(GL_CLIP_DISTANCE0 + static_cast<GLenum>(i)); |
| 273 | } | 273 | } |
| 274 | } | 274 | } |
| 275 | } | 275 | } |
diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index a52129eb7..c1ed48398 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp | |||
| @@ -98,7 +98,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu | |||
| 98 | 98 | ||
| 99 | auto FlipQuaternionIfOpposite = [](auto& a, const auto& b) { | 99 | auto FlipQuaternionIfOpposite = [](auto& a, const auto& b) { |
| 100 | if (Math::Dot(a, b) < float24::Zero()) | 100 | if (Math::Dot(a, b) < float24::Zero()) |
| 101 | a = -a; | 101 | a = a * float24::FromFloat32(-1.0f); |
| 102 | }; | 102 | }; |
| 103 | 103 | ||
| 104 | // Flip the quaternions if they are opposite to prevent interpolating them over the wrong | 104 | // Flip the quaternions if they are opposite to prevent interpolating them over the wrong |
diff --git a/src/video_core/utils.h b/src/video_core/utils.h index 7ce83a055..d8567f314 100644 --- a/src/video_core/utils.h +++ b/src/video_core/utils.h | |||
| @@ -8,17 +8,11 @@ | |||
| 8 | 8 | ||
| 9 | namespace VideoCore { | 9 | namespace VideoCore { |
| 10 | 10 | ||
| 11 | /** | 11 | // 8x8 Z-Order coordinate from 2D coordinates |
| 12 | * Interleave the lower 3 bits of each coordinate to get the intra-block offsets, which are | ||
| 13 | * arranged in a Z-order curve. More details on the bit manipulation at: | ||
| 14 | * https://fgiesen.wordpress.com/2009/12/13/decoding-morton-codes/ | ||
| 15 | */ | ||
| 16 | static inline u32 MortonInterleave(u32 x, u32 y) { | 12 | static inline u32 MortonInterleave(u32 x, u32 y) { |
| 17 | u32 i = (x & 7) | ((y & 7) << 8); // ---- -210 | 13 | static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15}; |
| 18 | i = (i ^ (i << 2)) & 0x1313; // ---2 --10 | 14 | static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a}; |
| 19 | i = (i ^ (i << 1)) & 0x1515; // ---2 -1-0 | 15 | return xlut[x % 8] + ylut[y % 8]; |
| 20 | i = (i | (i >> 7)) & 0x3F; | ||
| 21 | return i; | ||
| 22 | } | 16 | } |
| 23 | 17 | ||
| 24 | /** | 18 | /** |