mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-10-30 23:49:01 -05:00 
			
		
		
		
	applets: Implement LibAppletOff (Web) applet
This commit is contained in:
		| @@ -23,6 +23,7 @@ | ||||
| #include "core/hle/service/am/applets/profile_select.h" | ||||
| #include "core/hle/service/am/applets/software_keyboard.h" | ||||
| #include "core/hle/service/am/applets/stub_applet.h" | ||||
| #include "core/hle/service/am/applets/web_browser.h" | ||||
| #include "core/hle/service/am/idle.h" | ||||
| #include "core/hle/service/am/omm.h" | ||||
| #include "core/hle/service/am/spsm.h" | ||||
| @@ -43,6 +44,7 @@ constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; | ||||
| enum class AppletId : u32 { | ||||
|     ProfileSelect = 0x10, | ||||
|     SoftwareKeyboard = 0x11, | ||||
|     LibAppletOff = 0x17, | ||||
| }; | ||||
|  | ||||
| constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; | ||||
| @@ -782,6 +784,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { | ||||
|         return std::make_shared<Applets::ProfileSelect>(); | ||||
|     case AppletId::SoftwareKeyboard: | ||||
|         return std::make_shared<Applets::SoftwareKeyboard>(); | ||||
|     case AppletId::LibAppletOff: | ||||
|         return std::make_shared<Applets::WebBrowser>(); | ||||
|     default: | ||||
|         LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!", | ||||
|                   static_cast<u32>(id)); | ||||
|   | ||||
							
								
								
									
										184
									
								
								src/core/hle/service/am/applets/web_browser.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/core/hle/service/am/applets/web_browser.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
|     std::memcpy(&header, data.data(), sizeof(WebBufferHeader)); | ||||
|  | ||||
|     u64 offset = sizeof(WebBufferHeader); | ||||
|     for (u16 i = 0; i < header.count; ++i) { | ||||
|         WebArgumentHeader arg; | ||||
|         std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader)); | ||||
|         offset += sizeof(WebArgumentHeader); | ||||
|  | ||||
|         if (arg.type == type) { | ||||
|             std::vector<u8> out(arg.size); | ||||
|             offset += arg.offset; | ||||
|             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() { | ||||
|     complete = false; | ||||
|     temporary_dir.clear(); | ||||
|     filename.clear(); | ||||
|     status = RESULT_SUCCESS; | ||||
|  | ||||
|     Applet::Initialize(); | ||||
|  | ||||
|     const auto web_arg_storage = broker.PopNormalDataToApplet(); | ||||
|     ASSERT(web_arg_storage != nullptr); | ||||
|     const auto& web_arg = web_arg_storage->GetData(); | ||||
|  | ||||
|     LOG_CRITICAL(Service_AM, "{}", Common::HexVectorToString(web_arg)); | ||||
|  | ||||
|     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(Service_AM, "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 | ||||
							
								
								
									
										44
									
								
								src/core/hle/service/am/applets/web_browser.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/core/hle/service/am/applets/web_browser.h
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
		Reference in New Issue
	
	Block a user
	 Zach Hilman
					Zach Hilman