Browse Source
Merge pull request #1939 from DarkLordZach/web-applet
Merge pull request #1939 from DarkLordZach/web-applet
applets: Implement HLE web browser applet (LibAppletOff)nce_cpp
committed by
GitHub
30 changed files with 1381 additions and 591 deletions
-
4.travis/linux/docker.sh
-
2.travis/macos/build.sh
-
8CMakeLists.txt
-
26CMakeModules/CopyYuzuQt5Deps.cmake
-
3appveyor.yml
-
4src/core/CMakeLists.txt
-
18src/core/core.cpp
-
7src/core/core.h
-
3src/core/file_sys/romfs.cpp
-
5src/core/file_sys/romfs.h
-
24src/core/frontend/applets/web_browser.cpp
-
28src/core/frontend/applets/web_browser.h
-
12src/core/hle/service/am/am.cpp
-
2src/core/hle/service/am/applets/profile_select.cpp
-
184src/core/hle/service/am/applets/web_browser.cpp
-
44src/core/hle/service/am/applets/web_browser.h
-
6src/core/hle/service/hid/controllers/npad.cpp
-
6src/core/hle/service/hid/controllers/npad.h
-
1117src/core/hle/service/hid/hid.cpp
-
110src/core/hle/service/hid/hid.h
-
9src/core/loader/loader.h
-
8src/core/loader/nsp.cpp
-
1src/core/loader/nsp.h
-
9src/core/loader/xci.cpp
-
1src/core/loader/xci.h
-
7src/yuzu/CMakeLists.txt
-
113src/yuzu/applets/web_browser.cpp
-
53src/yuzu/applets/web_browser.h
-
151src/yuzu/main.cpp
-
7src/yuzu/main.h
@ -0,0 +1,24 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "core/frontend/applets/web_browser.h"
|
|||
|
|||
namespace Core::Frontend { |
|||
|
|||
WebBrowserApplet::~WebBrowserApplet() = default; |
|||
|
|||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default; |
|||
|
|||
void DefaultWebBrowserApplet::OpenPage(std::string_view filename, |
|||
std::function<void()> unpack_romfs_callback, |
|||
std::function<void()> finished_callback) const { |
|||
LOG_INFO(Service_AM, |
|||
"(STUBBED) called - No suitable web browser implementation found to open website page " |
|||
"at '{}'!", |
|||
filename); |
|||
finished_callback(); |
|||
} |
|||
|
|||
} // namespace Core::Frontend
|
|||
@ -0,0 +1,28 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <functional> |
|||
#include <string_view> |
|||
|
|||
namespace Core::Frontend { |
|||
|
|||
class WebBrowserApplet { |
|||
public: |
|||
virtual ~WebBrowserApplet(); |
|||
|
|||
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, |
|||
std::function<void()> finished_callback) const = 0; |
|||
}; |
|||
|
|||
class DefaultWebBrowserApplet final : public WebBrowserApplet { |
|||
public: |
|||
~DefaultWebBrowserApplet() override; |
|||
|
|||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, |
|||
std::function<void()> finished_callback) const override; |
|||
}; |
|||
|
|||
} // namespace Core::Frontend |
|||
@ -0,0 +1,184 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/common_paths.h"
|
|||
#include "common/hex_util.h"
|
|||
#include "common/logging/backend.h"
|
|||
#include "common/string_util.h"
|
|||
#include "core/core.h"
|
|||
#include "core/file_sys/content_archive.h"
|
|||
#include "core/file_sys/mode.h"
|
|||
#include "core/file_sys/nca_metadata.h"
|
|||
#include "core/file_sys/registered_cache.h"
|
|||
#include "core/file_sys/romfs.h"
|
|||
#include "core/file_sys/romfs_factory.h"
|
|||
#include "core/file_sys/vfs_types.h"
|
|||
#include "core/frontend/applets/web_browser.h"
|
|||
#include "core/hle/kernel/process.h"
|
|||
#include "core/hle/service/am/applets/web_browser.h"
|
|||
#include "core/hle/service/filesystem/filesystem.h"
|
|||
#include "core/loader/loader.h"
|
|||
|
|||
namespace Service::AM::Applets { |
|||
|
|||
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
|
|||
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
|
|||
// but some may be worth an implementation.
|
|||
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6; |
|||
|
|||
struct WebBufferHeader { |
|||
u16 count; |
|||
INSERT_PADDING_BYTES(6); |
|||
}; |
|||
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size."); |
|||
|
|||
struct WebArgumentHeader { |
|||
u16 type; |
|||
u16 size; |
|||
u32 offset; |
|||
}; |
|||
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size."); |
|||
|
|||
struct WebArgumentResult { |
|||
u32 result_code; |
|||
std::array<char, 0x1000> last_url; |
|||
u64 last_url_size; |
|||
}; |
|||
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size."); |
|||
|
|||
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) { |
|||
WebBufferHeader header; |
|||
ASSERT(sizeof(WebBufferHeader) <= data.size()); |
|||
std::memcpy(&header, data.data(), sizeof(WebBufferHeader)); |
|||
|
|||
u64 offset = sizeof(WebBufferHeader); |
|||
for (u16 i = 0; i < header.count; ++i) { |
|||
WebArgumentHeader arg; |
|||
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size()); |
|||
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader)); |
|||
offset += sizeof(WebArgumentHeader); |
|||
|
|||
if (arg.type == type) { |
|||
std::vector<u8> out(arg.size); |
|||
offset += arg.offset; |
|||
ASSERT(offset + arg.size <= data.size()); |
|||
std::memcpy(out.data(), data.data() + offset, out.size()); |
|||
return out; |
|||
} |
|||
|
|||
offset += arg.offset + arg.size; |
|||
} |
|||
|
|||
return {}; |
|||
} |
|||
|
|||
static FileSys::VirtualFile GetManualRomFS() { |
|||
auto& loader{Core::System::GetInstance().GetAppLoader()}; |
|||
|
|||
FileSys::VirtualFile out; |
|||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success) |
|||
return out; |
|||
|
|||
const auto& installed{FileSystem::GetUnionContents()}; |
|||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(), |
|||
FileSys::ContentRecordType::Manual); |
|||
|
|||
if (res != nullptr) |
|||
return res->GetRomFS(); |
|||
return nullptr; |
|||
} |
|||
|
|||
WebBrowser::WebBrowser() = default; |
|||
|
|||
WebBrowser::~WebBrowser() = default; |
|||
|
|||
void WebBrowser::Initialize() { |
|||
Applet::Initialize(); |
|||
|
|||
complete = false; |
|||
temporary_dir.clear(); |
|||
filename.clear(); |
|||
status = RESULT_SUCCESS; |
|||
|
|||
const auto web_arg_storage = broker.PopNormalDataToApplet(); |
|||
ASSERT(web_arg_storage != nullptr); |
|||
const auto& web_arg = web_arg_storage->GetData(); |
|||
|
|||
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE); |
|||
filename = Common::StringFromFixedZeroTerminatedBuffer( |
|||
reinterpret_cast<const char*>(url_data.data()), url_data.size()); |
|||
|
|||
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + |
|||
"web_applet_manual", |
|||
FileUtil::DirectorySeparator::PlatformDefault); |
|||
FileUtil::DeleteDirRecursively(temporary_dir); |
|||
|
|||
manual_romfs = GetManualRomFS(); |
|||
if (manual_romfs == nullptr) { |
|||
status = ResultCode(-1); |
|||
LOG_ERROR(Service_AM, "Failed to find manual for current process!"); |
|||
} |
|||
|
|||
filename = |
|||
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename, |
|||
FileUtil::DirectorySeparator::PlatformDefault); |
|||
} |
|||
|
|||
bool WebBrowser::TransactionComplete() const { |
|||
return complete; |
|||
} |
|||
|
|||
ResultCode WebBrowser::GetStatus() const { |
|||
return status; |
|||
} |
|||
|
|||
void WebBrowser::ExecuteInteractive() { |
|||
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!"); |
|||
} |
|||
|
|||
void WebBrowser::Execute() { |
|||
if (complete) |
|||
return; |
|||
|
|||
if (status != RESULT_SUCCESS) { |
|||
complete = true; |
|||
return; |
|||
} |
|||
|
|||
const auto& frontend{Core::System::GetInstance().GetWebBrowser()}; |
|||
|
|||
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); }); |
|||
} |
|||
|
|||
void WebBrowser::UnpackRomFS() { |
|||
if (unpacked) |
|||
return; |
|||
|
|||
ASSERT(manual_romfs != nullptr); |
|||
const auto dir = |
|||
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard); |
|||
const auto& vfs{Core::System::GetInstance().GetFilesystem()}; |
|||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); |
|||
FileSys::VfsRawCopyD(dir, temp_dir); |
|||
|
|||
unpacked = true; |
|||
} |
|||
|
|||
void WebBrowser::Finalize() { |
|||
complete = true; |
|||
|
|||
WebArgumentResult out{}; |
|||
out.result_code = 0; |
|||
out.last_url_size = 0; |
|||
|
|||
std::vector<u8> data(sizeof(WebArgumentResult)); |
|||
std::memcpy(data.data(), &out, sizeof(WebArgumentResult)); |
|||
|
|||
broker.PushNormalDataFromApplet(IStorage{data}); |
|||
broker.SignalStateChanged(); |
|||
|
|||
FileUtil::DeleteDirRecursively(temporary_dir); |
|||
} |
|||
|
|||
} // namespace Service::AM::Applets
|
|||
@ -0,0 +1,44 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "core/file_sys/vfs_types.h" |
|||
#include "core/hle/service/am/am.h" |
|||
#include "core/hle/service/am/applets/applets.h" |
|||
|
|||
namespace Service::AM::Applets { |
|||
|
|||
class WebBrowser final : public Applet { |
|||
public: |
|||
WebBrowser(); |
|||
~WebBrowser() override; |
|||
|
|||
void Initialize() override; |
|||
|
|||
bool TransactionComplete() const override; |
|||
ResultCode GetStatus() const override; |
|||
void ExecuteInteractive() override; |
|||
void Execute() override; |
|||
|
|||
// Callback to be fired when the frontend needs the manual RomFS unpacked to temporary |
|||
// directory. This is a blocking call and may take a while as some manuals can be up to 100MB in |
|||
// size. Attempting to access files at filename before invocation is likely to not work. |
|||
void UnpackRomFS(); |
|||
|
|||
// Callback to be fired when the frontend is finished browsing. This will delete the temporary |
|||
// manual RomFS extracted files, so ensure this is only called at actual finalization. |
|||
void Finalize(); |
|||
|
|||
private: |
|||
bool complete = false; |
|||
bool unpacked = false; |
|||
ResultCode status = RESULT_SUCCESS; |
|||
|
|||
FileSys::VirtualFile manual_romfs; |
|||
std::string temporary_dir; |
|||
std::string filename; |
|||
}; |
|||
|
|||
} // namespace Service::AM::Applets |
|||
1117
src/core/hle/service/hid/hid.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,113 @@ |
|||
// Copyright 2018 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <mutex>
|
|||
|
|||
#include <QKeyEvent>
|
|||
|
|||
#include "core/hle/lock.h"
|
|||
#include "yuzu/applets/web_browser.h"
|
|||
#include "yuzu/main.h"
|
|||
|
|||
#ifdef YUZU_USE_QT_WEB_ENGINE
|
|||
|
|||
constexpr char NX_SHIM_INJECT_SCRIPT[] = R"( |
|||
window.nx = {}; |
|||
window.nx.playReport = {}; |
|||
window.nx.playReport.setCounterSetIdentifier = function () { |
|||
console.log("nx.playReport.setCounterSetIdentifier called - unimplemented"); |
|||
}; |
|||
|
|||
window.nx.playReport.incrementCounter = function () { |
|||
console.log("nx.playReport.incrementCounter called - unimplemented"); |
|||
}; |
|||
|
|||
window.nx.footer = {}; |
|||
window.nx.footer.unsetAssign = function () { |
|||
console.log("nx.footer.unsetAssign called - unimplemented"); |
|||
}; |
|||
|
|||
var yuzu_key_callbacks = []; |
|||
window.nx.footer.setAssign = function(key, discard1, func, discard2) { |
|||
switch (key) { |
|||
case 'A': |
|||
yuzu_key_callbacks[0] = func; |
|||
break; |
|||
case 'B': |
|||
yuzu_key_callbacks[1] = func; |
|||
break; |
|||
case 'X': |
|||
yuzu_key_callbacks[2] = func; |
|||
break; |
|||
case 'Y': |
|||
yuzu_key_callbacks[3] = func; |
|||
break; |
|||
case 'L': |
|||
yuzu_key_callbacks[6] = func; |
|||
break; |
|||
case 'R': |
|||
yuzu_key_callbacks[7] = func; |
|||
break; |
|||
} |
|||
}; |
|||
|
|||
var applet_done = false; |
|||
window.nx.endApplet = function() { |
|||
applet_done = true; |
|||
}; |
|||
)"; |
|||
|
|||
QString GetNXShimInjectionScript() { |
|||
return QString::fromStdString(NX_SHIM_INJECT_SCRIPT); |
|||
} |
|||
|
|||
NXInputWebEngineView::NXInputWebEngineView(QWidget* parent) : QWebEngineView(parent) {} |
|||
|
|||
void NXInputWebEngineView::keyPressEvent(QKeyEvent* event) { |
|||
parent()->event(event); |
|||
} |
|||
|
|||
void NXInputWebEngineView::keyReleaseEvent(QKeyEvent* event) { |
|||
parent()->event(event); |
|||
} |
|||
|
|||
#endif
|
|||
|
|||
QtWebBrowser::QtWebBrowser(GMainWindow& main_window) { |
|||
connect(this, &QtWebBrowser::MainWindowOpenPage, &main_window, &GMainWindow::WebBrowserOpenPage, |
|||
Qt::QueuedConnection); |
|||
connect(&main_window, &GMainWindow::WebBrowserUnpackRomFS, this, |
|||
&QtWebBrowser::MainWindowUnpackRomFS, Qt::QueuedConnection); |
|||
connect(&main_window, &GMainWindow::WebBrowserFinishedBrowsing, this, |
|||
&QtWebBrowser::MainWindowFinishedBrowsing, Qt::QueuedConnection); |
|||
} |
|||
|
|||
QtWebBrowser::~QtWebBrowser() = default; |
|||
|
|||
void QtWebBrowser::OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, |
|||
std::function<void()> finished_callback) const { |
|||
this->unpack_romfs_callback = unpack_romfs_callback; |
|||
this->finished_callback = finished_callback; |
|||
|
|||
const auto index = url.find('?'); |
|||
if (index == std::string::npos) { |
|||
emit MainWindowOpenPage(url, ""); |
|||
} else { |
|||
const auto front = url.substr(0, index); |
|||
const auto back = url.substr(index); |
|||
emit MainWindowOpenPage(front, back); |
|||
} |
|||
} |
|||
|
|||
void QtWebBrowser::MainWindowUnpackRomFS() { |
|||
// Acquire the HLE mutex
|
|||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
|||
unpack_romfs_callback(); |
|||
} |
|||
|
|||
void QtWebBrowser::MainWindowFinishedBrowsing() { |
|||
// Acquire the HLE mutex
|
|||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
|||
finished_callback(); |
|||
} |
|||
@ -0,0 +1,53 @@ |
|||
// Copyright 2018 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <functional> |
|||
#include <QObject> |
|||
|
|||
#ifdef YUZU_USE_QT_WEB_ENGINE |
|||
#include <QWebEngineView> |
|||
#endif |
|||
|
|||
#include "core/frontend/applets/web_browser.h" |
|||
|
|||
class GMainWindow; |
|||
|
|||
#ifdef YUZU_USE_QT_WEB_ENGINE |
|||
|
|||
QString GetNXShimInjectionScript(); |
|||
|
|||
class NXInputWebEngineView : public QWebEngineView { |
|||
public: |
|||
explicit NXInputWebEngineView(QWidget* parent = nullptr); |
|||
|
|||
protected: |
|||
void keyPressEvent(QKeyEvent* event) override; |
|||
void keyReleaseEvent(QKeyEvent* event) override; |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
class QtWebBrowser final : public QObject, public Core::Frontend::WebBrowserApplet { |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit QtWebBrowser(GMainWindow& main_window); |
|||
~QtWebBrowser() override; |
|||
|
|||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback, |
|||
std::function<void()> finished_callback) const override; |
|||
|
|||
signals: |
|||
void MainWindowOpenPage(std::string_view filename, std::string_view additional_args) const; |
|||
|
|||
public slots: |
|||
void MainWindowUnpackRomFS(); |
|||
void MainWindowFinishedBrowsing(); |
|||
|
|||
private: |
|||
mutable std::function<void()> unpack_romfs_callback; |
|||
mutable std::function<void()> finished_callback; |
|||
}; |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue