From e5f283960597d0b7d613a71cddcc3786ae6661d0 Mon Sep 17 00:00:00 2001 From: bunnei Date: Tue, 1 Aug 2017 19:51:44 -0400 Subject: loader: Expose program title. --- src/core/loader/ncch.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index fc4d14a595..c007069a9e 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -4,7 +4,9 @@ #include #include +#include #include +#include #include #include "common/logging/log.h" #include "common/string_util.h" @@ -420,4 +422,22 @@ ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr& romfs_ return ResultStatus::ErrorNotUsed; } +ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { + std::vector data; + Loader::SMDH smdh; + ReadIcon(data); + + if (!Loader::IsValidSMDH(data)) { + return ResultStatus::ErrorInvalidFormat; + } + + memcpy(&smdh, data.data(), sizeof(Loader::SMDH)); + + const auto& short_title = smdh.GetShortTitle(SMDH::TitleLanguage::English); + auto title_end = std::find(short_title.begin(), short_title.end(), u'\0'); + title = Common::UTF16ToUTF8(std::u16string{short_title.begin(), title_end}); + + return ResultStatus::Success; +} + } // namespace Loader -- cgit v1.2.3 From 95fd4a5e9d2c03c014ba9c7f753cb3b85001da27 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sat, 19 Aug 2017 19:14:33 +0200 Subject: Added missing parts in libnetwork (#2838) * Network: Set and send the game information over enet Added Callbacks for RoomMember and GetMemberList to Room in preparation for web_services. --- src/core/CMakeLists.txt | 2 +- src/core/core.cpp | 5 ++ src/core/loader/ncch.cpp | 8 +++ src/network/packet.cpp | 38 +++++++++++++ src/network/packet.h | 4 ++ src/network/room.cpp | 84 +++++++++++++++++++++-------- src/network/room.h | 19 ++++++- src/network/room_member.cpp | 128 ++++++++++++++++++++++++++++++++++++++++---- src/network/room_member.h | 59 ++++++++++++++++++-- 9 files changed, 310 insertions(+), 37 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 360f407f3d..0a6f97e4b5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -388,7 +388,7 @@ set(HEADERS create_directory_groups(${SRCS} ${HEADERS}) add_library(core STATIC ${SRCS} ${HEADERS}) -target_link_libraries(core PUBLIC common PRIVATE audio_core video_core) +target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core) target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt) if (ENABLE_WEB_SERVICE) target_link_libraries(core PUBLIC json-headers web_service) diff --git a/src/core/core.cpp b/src/core/core.cpp index d08f18623f..5332318cff 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -19,6 +19,7 @@ #include "core/loader/loader.h" #include "core/memory_setup.h" #include "core/settings.h" +#include "network/network.h" #include "video_core/video_core.h" namespace Core { @@ -188,6 +189,10 @@ void System::Shutdown() { cpu_core = nullptr; app_loader = nullptr; telemetry_session = nullptr; + if (auto room_member = Network::GetRoomMember().lock()) { + Network::GameInfo game_info{}; + room_member->SendGameInfo(game_info); + } LOG_DEBUG(Core, "Shutdown OK"); } diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c007069a9e..7aff7f29b3 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -20,6 +20,7 @@ #include "core/loader/ncch.h" #include "core/loader/smdh.h" #include "core/memory.h" +#include "network/network.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // Loader namespace @@ -350,6 +351,13 @@ ResultStatus AppLoader_NCCH::Load() { Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); + if (auto room_member = Network::GetRoomMember().lock()) { + Network::GameInfo game_info; + ReadTitle(game_info.name); + game_info.id = ncch_header.program_id; + room_member->SendGameInfo(game_info); + } + is_loaded = true; // Set state to loaded result = LoadExec(); // Load the executable into memory for booting diff --git a/src/network/packet.cpp b/src/network/packet.cpp index 660e92c0dc..cc60f2fbca 100644 --- a/src/network/packet.cpp +++ b/src/network/packet.cpp @@ -13,6 +13,18 @@ namespace Network { +#ifndef htonll +u64 htonll(u64 x) { + return ((1 == htonl(1)) ? (x) : ((uint64_t)htonl((x)&0xFFFFFFFF) << 32) | htonl((x) >> 32)); +} +#endif + +#ifndef ntohll +u64 ntohll(u64 x) { + return ((1 == ntohl(1)) ? (x) : ((uint64_t)ntohl((x)&0xFFFFFFFF) << 32) | ntohl((x) >> 32)); +} +#endif + void Packet::Append(const void* in_data, std::size_t size_in_bytes) { if (in_data && (size_in_bytes > 0)) { std::size_t start = data.size(); @@ -100,6 +112,20 @@ Packet& Packet::operator>>(u32& out_data) { return *this; } +Packet& Packet::operator>>(s64& out_data) { + s64 value; + Read(&value, sizeof(value)); + out_data = ntohll(value); + return *this; +} + +Packet& Packet::operator>>(u64& out_data) { + u64 value; + Read(&value, sizeof(value)); + out_data = ntohll(value); + return *this; +} + Packet& Packet::operator>>(float& out_data) { Read(&out_data, sizeof(out_data)); return *this; @@ -183,6 +209,18 @@ Packet& Packet::operator<<(u32 in_data) { return *this; } +Packet& Packet::operator<<(s64 in_data) { + s64 toWrite = htonll(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + +Packet& Packet::operator<<(u64 in_data) { + u64 toWrite = htonll(in_data); + Append(&toWrite, sizeof(toWrite)); + return *this; +} + Packet& Packet::operator<<(float in_data) { Append(&in_data, sizeof(in_data)); return *this; diff --git a/src/network/packet.h b/src/network/packet.h index 94b351ab12..5a2e58dc20 100644 --- a/src/network/packet.h +++ b/src/network/packet.h @@ -72,6 +72,8 @@ public: Packet& operator>>(u16& out_data); Packet& operator>>(s32& out_data); Packet& operator>>(u32& out_data); + Packet& operator>>(s64& out_data); + Packet& operator>>(u64& out_data); Packet& operator>>(float& out_data); Packet& operator>>(double& out_data); Packet& operator>>(char* out_data); @@ -89,6 +91,8 @@ public: Packet& operator<<(u16 in_data); Packet& operator<<(s32 in_data); Packet& operator<<(u32 in_data); + Packet& operator<<(s64 in_data); + Packet& operator<<(u64 in_data); Packet& operator<<(float in_data); Packet& operator<<(double in_data); Packet& operator<<(const char* in_data); diff --git a/src/network/room.cpp b/src/network/room.cpp index fbbaf8b93a..261049ab02 100644 --- a/src/network/room.cpp +++ b/src/network/room.cpp @@ -4,9 +4,9 @@ #include #include +#include #include #include -#include #include "enet/enet.h" #include "network/packet.h" #include "network/room.h" @@ -29,12 +29,14 @@ public: struct Member { std::string nickname; ///< The nickname of the member. - std::string game_name; ///< The current game of the member + GameInfo game_info; ///< The current game of the member MacAddress mac_address; ///< The assigned mac address of the member. ENetPeer* peer; ///< The remote peer. }; using MemberList = std::vector; - MemberList members; ///< Information about the members of this room. + MemberList members; ///< Information about the members of this room + mutable std::mutex member_mutex; ///< Mutex for locking the members list + /// This should be a std::shared_mutex as soon as C++17 is supported RoomImpl() : random_gen(std::random_device()()), NintendoOUI{0x00, 0x1F, 0x32, 0x00, 0x00, 0x00} {} @@ -147,7 +149,7 @@ void Room::RoomImpl::ServerLoop() { case IdJoinRequest: HandleJoinRequest(&event); break; - case IdSetGameName: + case IdSetGameInfo: HandleGameNamePacket(&event); break; case IdWifiPacket: @@ -213,7 +215,10 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { member.nickname = nickname; member.peer = event->peer; - members.push_back(std::move(member)); + { + std::lock_guard lock(member_mutex); + members.push_back(std::move(member)); + } // Notify everyone that the room information has changed. BroadcastRoomInformation(); @@ -223,12 +228,14 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const { // A nickname is valid if it is not already taken by anybody else in the room. // TODO(B3N30): Check for empty names, spaces, etc. + std::lock_guard lock(member_mutex); return std::all_of(members.begin(), members.end(), [&nickname](const auto& member) { return member.nickname != nickname; }); } bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const { // A MAC address is valid if it is not already taken by anybody else in the room. + std::lock_guard lock(member_mutex); return std::all_of(members.begin(), members.end(), [&address](const auto& member) { return member.mac_address != address; }); } @@ -279,6 +286,7 @@ void Room::RoomImpl::SendCloseMessage() { packet << static_cast(IdCloseRoom); ENetPacket* enet_packet = enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + std::lock_guard lock(member_mutex); for (auto& member : members) { enet_peer_send(member.peer, 0, enet_packet); } @@ -295,10 +303,14 @@ void Room::RoomImpl::BroadcastRoomInformation() { packet << room_information.member_slots; packet << static_cast(members.size()); - for (const auto& member : members) { - packet << member.nickname; - packet << member.mac_address; - packet << member.game_name; + { + std::lock_guard lock(member_mutex); + for (const auto& member : members) { + packet << member.nickname; + packet << member.mac_address; + packet << member.game_info.name; + packet << member.game_info.id; + } } ENetPacket* enet_packet = @@ -335,11 +347,13 @@ void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) { ENET_PACKET_FLAG_RELIABLE); if (destination_address == BroadcastMac) { // Send the data to everyone except the sender + std::lock_guard lock(member_mutex); for (const auto& member : members) { if (member.peer != event->peer) enet_peer_send(member.peer, 0, enet_packet); } } else { // Send the data only to the destination client + std::lock_guard lock(member_mutex); auto member = std::find_if(members.begin(), members.end(), [destination_address](const Member& member) -> bool { return member.mac_address == destination_address; @@ -361,6 +375,8 @@ void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { auto CompareNetworkAddress = [event](const Member member) -> bool { return member.peer == event->peer; }; + + std::lock_guard lock(member_mutex); const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress); if (sending_member == members.end()) { return; // Received a chat message from a unknown sender @@ -385,22 +401,32 @@ void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) { in_packet.Append(event->packet->data, event->packet->dataLength); in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type - std::string game_name; - in_packet >> game_name; - auto member = - std::find_if(members.begin(), members.end(), - [event](const Member& member) -> bool { return member.peer == event->peer; }); - if (member != members.end()) { - member->game_name = game_name; - BroadcastRoomInformation(); + GameInfo game_info; + in_packet >> game_info.name; + in_packet >> game_info.id; + + { + std::lock_guard lock(member_mutex); + auto member = + std::find_if(members.begin(), members.end(), [event](const Member& member) -> bool { + return member.peer == event->peer; + }); + if (member != members.end()) { + member->game_info = game_info; + } } + BroadcastRoomInformation(); } void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) { // Remove the client from the members list. - members.erase(std::remove_if(members.begin(), members.end(), - [client](const Member& member) { return member.peer == client; }), - members.end()); + { + std::lock_guard lock(member_mutex); + members.erase( + std::remove_if(members.begin(), members.end(), + [client](const Member& member) { return member.peer == client; }), + members.end()); + } // Announce the change to all clients. enet_peer_disconnect(client, 0); @@ -437,6 +463,19 @@ const RoomInformation& Room::GetRoomInformation() const { return room_impl->room_information; } +std::vector Room::GetRoomMemberList() const { + std::vector member_list; + std::lock_guard lock(room_impl->member_mutex); + for (const auto& member_impl : room_impl->members) { + Member member; + member.nickname = member_impl.nickname; + member.mac_address = member_impl.mac_address; + member.game_info = member_impl.game_info; + member_list.push_back(member); + } + return member_list; +}; + void Room::Destroy() { room_impl->state = State::Closed; room_impl->room_thread->join(); @@ -447,7 +486,10 @@ void Room::Destroy() { } room_impl->room_information = {}; room_impl->server = nullptr; - room_impl->members.clear(); + { + std::lock_guard lock(room_impl->member_mutex); + room_impl->members.clear(); + } room_impl->room_information.member_slots = 0; room_impl->room_information.name.clear(); } diff --git a/src/network/room.h b/src/network/room.h index 65b0d008a1..8285a4d0cd 100644 --- a/src/network/room.h +++ b/src/network/room.h @@ -7,6 +7,7 @@ #include #include #include +#include #include "common/common_types.h" namespace Network { @@ -21,6 +22,11 @@ struct RoomInformation { u32 member_slots; ///< Maximum number of members in this room }; +struct GameInfo { + std::string name{""}; + u64 id{0}; +}; + using MacAddress = std::array; /// A special MAC address that tells the room we're joining to assign us a MAC address /// automatically. @@ -34,7 +40,7 @@ enum RoomMessageTypes : u8 { IdJoinRequest = 1, IdJoinSuccess, IdRoomInformation, - IdSetGameName, + IdSetGameInfo, IdWifiPacket, IdChatMessage, IdNameCollision, @@ -51,6 +57,12 @@ public: Closed, ///< The room is not opened and can not accept connections. }; + struct Member { + std::string nickname; ///< The nickname of the member. + GameInfo game_info; ///< The current game of the member + MacAddress mac_address; ///< The assigned mac address of the member. + }; + Room(); ~Room(); @@ -64,6 +76,11 @@ public: */ const RoomInformation& GetRoomInformation() const; + /** + * Gets a list of the mbmers connected to the room. + */ + std::vector GetRoomMemberList() const; + /** * Creates the socket for this room. Will bind to default address if * server is empty string. diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp index dac9bacaeb..f229ec6fd4 100644 --- a/src/network/room_member.cpp +++ b/src/network/room_member.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "common/assert.h" #include "enet/enet.h" @@ -25,6 +26,9 @@ public: /// Information about the room we're connected to. RoomInformation room_information; + /// The current game name, id and version + GameInfo current_game_info; + std::atomic state{State::Idle}; ///< Current state of the RoomMember. void SetState(const State new_state); bool IsConnected() const; @@ -37,6 +41,24 @@ public: std::unique_ptr loop_thread; std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable. std::list send_list; ///< A list that stores all packets to send the async + + template + using CallbackSet = std::set>; + std::mutex callback_mutex; ///< The mutex used for handling callbacks + + class Callbacks { + public: + template + CallbackSet& Get(); + + private: + CallbackSet callback_set_wifi_packet; + CallbackSet callback_set_chat_messages; + CallbackSet callback_set_room_information; + CallbackSet callback_set_state; + }; + Callbacks callbacks; ///< All CallbackSets to all events + void MemberLoop(); void StartLoop(); @@ -84,12 +106,20 @@ public: * Disconnects the RoomMember from the Room */ void Disconnect(); + + template + void Invoke(const T& data); + + template + CallbackHandle Bind(std::function callback); }; // RoomMemberImpl void RoomMember::RoomMemberImpl::SetState(const State new_state) { - state = new_state; - // TODO(B3N30): Invoke the callback functions + if (state != new_state) { + state = new_state; + Invoke(state); + } } bool RoomMember::RoomMemberImpl::IsConnected() const { @@ -195,9 +225,10 @@ void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* ev for (auto& member : member_information) { packet >> member.nickname; packet >> member.mac_address; - packet >> member.game_name; + packet >> member.game_info.name; + packet >> member.game_info.id; } - // TODO(B3N30): Invoke callbacks + Invoke(room_information); } void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { @@ -209,7 +240,7 @@ void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) { // Parse the MAC Address from the packet packet >> mac_address; - // TODO(B3N30): Invoke callbacks + SetState(State::Joined); } void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { @@ -235,7 +266,7 @@ void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) { packet >> wifi_packet.data; - // TODO(B3N30): Invoke callbacks + Invoke(wifi_packet); } void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { @@ -248,7 +279,7 @@ void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { ChatEntry chat_entry{}; packet >> chat_entry.nickname; packet >> chat_entry.message; - // TODO(B3N30): Invoke callbacks + Invoke(chat_entry); } void RoomMember::RoomMemberImpl::Disconnect() { @@ -276,6 +307,46 @@ void RoomMember::RoomMemberImpl::Disconnect() { server = nullptr; } +template <> +RoomMember::RoomMemberImpl::CallbackSet& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_wifi_packet; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_state; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet& +RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_room_information; +} + +template <> +RoomMember::RoomMemberImpl::CallbackSet& RoomMember::RoomMemberImpl::Callbacks::Get() { + return callback_set_chat_messages; +} + +template +void RoomMember::RoomMemberImpl::Invoke(const T& data) { + std::lock_guard lock(callback_mutex); + CallbackSet callback_set = callbacks.Get(); + for (auto const& callback : callback_set) + (*callback)(data); +} + +template +RoomMember::CallbackHandle RoomMember::RoomMemberImpl::Bind( + std::function callback) { + std::lock_guard lock(callback_mutex); + CallbackHandle handle; + handle = std::make_shared>(callback); + callbacks.Get().insert(handle); + return handle; +} + // RoomMember RoomMember::RoomMember() : room_member_impl{std::make_unique()} { room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0); @@ -339,6 +410,7 @@ void RoomMember::Join(const std::string& nick, const char* server_addr, u16 serv room_member_impl->SetState(State::Joining); room_member_impl->StartLoop(); room_member_impl->SendJoinRequest(nick, preferred_mac); + SendGameInfo(room_member_impl->current_game_info); } else { room_member_impl->SetState(State::CouldNotConnect); } @@ -366,17 +438,53 @@ void RoomMember::SendChatMessage(const std::string& message) { room_member_impl->Send(std::move(packet)); } -void RoomMember::SendGameName(const std::string& game_name) { +void RoomMember::SendGameInfo(const GameInfo& game_info) { + room_member_impl->current_game_info = game_info; + if (!IsConnected()) + return; + Packet packet; - packet << static_cast(IdSetGameName); - packet << game_name; + packet << static_cast(IdSetGameInfo); + packet << game_info.name; + packet << game_info.id; room_member_impl->Send(std::move(packet)); } +RoomMember::CallbackHandle RoomMember::BindOnStateChanged( + std::function callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle RoomMember::BindOnWifiPacketReceived( + std::function callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle RoomMember::BindOnRoomInformationChanged( + std::function callback) { + return room_member_impl->Bind(callback); +} + +RoomMember::CallbackHandle RoomMember::BindOnChatMessageRecieved( + std::function callback) { + return room_member_impl->Bind(callback); +} + +template +void RoomMember::Unbind(CallbackHandle handle) { + std::lock_guard lock(room_member_impl->callback_mutex); + room_member_impl->callbacks.Get().erase(handle); +} + void RoomMember::Leave() { room_member_impl->SetState(State::Idle); room_member_impl->loop_thread->join(); room_member_impl->loop_thread.reset(); } +template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); +template void RoomMember::Unbind(CallbackHandle); + } // namespace Network diff --git a/src/network/room_member.h b/src/network/room_member.h index bc1af3a7e8..98770a2341 100644 --- a/src/network/room_member.h +++ b/src/network/room_member.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -53,12 +54,23 @@ public: struct MemberInformation { std::string nickname; ///< Nickname of the member. - std::string game_name; ///< Name of the game they're currently playing, or empty if they're + GameInfo game_info; ///< Name of the game they're currently playing, or empty if they're /// not playing anything. MacAddress mac_address; ///< MAC address associated with this member. }; using MemberList = std::vector; + // The handle for the callback functions + template + using CallbackHandle = std::shared_ptr>; + + /** + * Unbinds a callback function from the events. + * @param handle The connection handle to disconnect + */ + template + void Unbind(CallbackHandle handle); + RoomMember(); ~RoomMember(); @@ -113,10 +125,49 @@ public: void SendChatMessage(const std::string& message); /** - * Sends the current game name to the room. - * @param game_name The game name. + * Sends the current game info to the room. + * @param game_info The game information. + */ + void SendGameInfo(const GameInfo& game_info); + + /** + * Binds a function to an event that will be triggered every time the State of the member + * changed. The function wil be called every time the event is triggered. The callback function + * must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle BindOnStateChanged(std::function callback); + + /** + * Binds a function to an event that will be triggered every time a WifiPacket is received. + * The function wil be called everytime the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle BindOnWifiPacketReceived( + std::function callback); + + /** + * Binds a function to an event that will be triggered every time the RoomInformation changes. + * The function wil be called every time the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list + */ + CallbackHandle BindOnRoomInformationChanged( + std::function callback); + + /** + * Binds a function to an event that will be triggered every time a ChatMessage is received. + * The function wil be called every time the event is triggered. + * The callback function must not bind or unbind a function. Doing so will cause a deadlock + * @param callback The function to call + * @return A handle used for removing the function from the registered list */ - void SendGameName(const std::string& game_name); + CallbackHandle BindOnChatMessageRecieved( + std::function callback); /** * Leaves the current room. -- cgit v1.2.3 From 36100ec791313e4653664d68119c0fa29f550fce Mon Sep 17 00:00:00 2001 From: Subv Date: Fri, 21 Jul 2017 21:17:57 -0500 Subject: Kernel/Memory: Give each Process its own page table. The loader is in charge of setting the newly created process's page table as the main one during the loading process. --- src/core/core.cpp | 1 - src/core/hle/kernel/vm_manager.cpp | 13 ++++-- src/core/hle/kernel/vm_manager.h | 6 ++- src/core/loader/3dsx.cpp | 1 + src/core/loader/elf.cpp | 1 + src/core/loader/ncch.cpp | 1 + src/core/memory.cpp | 87 ++++++-------------------------------- src/core/memory.h | 60 +++++++++++++++++++++++++- src/core/memory_setup.h | 10 ++--- 9 files changed, 93 insertions(+), 87 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/core.cpp b/src/core/core.cpp index 5332318cff..59b8768e71 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -137,7 +137,6 @@ void System::Reschedule() { } System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { - Memory::InitMemoryMap(); LOG_DEBUG(HW_Memory, "initialized OK"); if (Settings::values.use_cpu_jit) { diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index cef1f7fa83..7a007c0656 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp @@ -56,6 +56,10 @@ void VMManager::Reset() { initial_vma.size = MAX_ADDRESS; vma_map.emplace(initial_vma.base, initial_vma); + page_table.pointers.fill(nullptr); + page_table.attributes.fill(Memory::PageType::Unmapped); + page_table.cached_res_count.fill(0); + UpdatePageTableForVMA(initial_vma); } @@ -328,16 +332,17 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { switch (vma.type) { case VMAType::Free: - Memory::UnmapRegion(vma.base, vma.size); + Memory::UnmapRegion(page_table, vma.base, vma.size); break; case VMAType::AllocatedMemoryBlock: - Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_block->data() + vma.offset); + Memory::MapMemoryRegion(page_table, vma.base, vma.size, + vma.backing_block->data() + vma.offset); break; case VMAType::BackingMemory: - Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_memory); + Memory::MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory); break; case VMAType::MMIO: - Memory::MapIoRegion(vma.base, vma.size, vma.mmio_handler); + Memory::MapIoRegion(page_table, vma.base, vma.size, vma.mmio_handler); break; } } diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 38e0d74d0d..1302527bb9 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h @@ -9,6 +9,7 @@ #include #include "common/common_types.h" #include "core/hle/result.h" +#include "core/memory.h" #include "core/mmio.h" namespace Kernel { @@ -102,7 +103,6 @@ struct VirtualMemoryArea { * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ */ class VMManager final { - // TODO(yuriks): Make page tables switchable to support multiple VMManagers public: /** * The maximum amount of address space managed by the kernel. Addresses above this are never @@ -184,6 +184,10 @@ public: /// Dumps the address space layout to the log, for debugging void LogLayout(Log::Level log_level) const; + /// Each VMManager has its own page table, which is set as the main one when the owning process + /// is scheduled. + Memory::PageTable page_table; + private: using VMAIter = decltype(vma_map)::iterator; diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 74e3364874..69cdc0867e 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -270,6 +270,7 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; + Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index cfcde91673..2f27606a18 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -397,6 +397,7 @@ ResultStatus AppLoader_ELF::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; + Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 7aff7f29b3..79ea501471 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -172,6 +172,7 @@ ResultStatus AppLoader_NCCH::LoadExec() { codeset->memory = std::make_shared>(std::move(code)); Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); + Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; // Attach a resource limit to the process based on the resource limit category Kernel::g_current_process->resource_limit = diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 65649d9d76..ea46b6ead8 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -11,75 +11,18 @@ #include "core/hle/kernel/process.h" #include "core/memory.h" #include "core/memory_setup.h" -#include "core/mmio.h" #include "video_core/renderer_base.h" #include "video_core/video_core.h" namespace Memory { -enum class PageType { - /// Page is unmapped and should cause an access error. - Unmapped, - /// Page is mapped to regular memory. This is the only type you can get pointers to. - Memory, - /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and - /// invalidation - RasterizerCachedMemory, - /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. - Special, - /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and - /// invalidation - RasterizerCachedSpecial, -}; - -struct SpecialRegion { - VAddr base; - u32 size; - MMIORegionPointer handler; -}; - -/** - * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely - * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and - * fetching requirements when accessing. In the usual case of an access to regular memory, it only - * requires an indexed fetch and a check for NULL. - */ -struct PageTable { - /** - * Array of memory pointers backing each page. An entry can only be non-null if the - * corresponding entry in the `attributes` array is of type `Memory`. - */ - std::array pointers; - - /** - * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of - * type `Special`. - */ - std::vector special_regions; - - /** - * Array of fine grained page attributes. If it is set to any value other than `Memory`, then - * the corresponding entry in `pointers` MUST be set to null. - */ - std::array attributes; - - /** - * Indicates the number of externally cached resources touching a page that should be - * flushed before the memory is accessed - */ - std::array cached_res_count; -}; - -/// Singular page table used for the singleton process -static PageTable main_page_table; -/// Currently active page table -static PageTable* current_page_table = &main_page_table; +PageTable* current_page_table = nullptr; std::array* GetCurrentPageTablePointers() { return ¤t_page_table->pointers; } -static void MapPages(u32 base, u32 size, u8* memory, PageType type) { +static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); @@ -90,9 +33,9 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { while (base != end) { ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base); - current_page_table->attributes[base] = type; - current_page_table->pointers[base] = memory; - current_page_table->cached_res_count[base] = 0; + page_table.attributes[base] = type; + page_table.pointers[base] = memory; + page_table.cached_res_count[base] = 0; base += 1; if (memory != nullptr) @@ -100,30 +43,24 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { } } -void InitMemoryMap() { - main_page_table.pointers.fill(nullptr); - main_page_table.attributes.fill(PageType::Unmapped); - main_page_table.cached_res_count.fill(0); -} - -void MapMemoryRegion(VAddr base, u32 size, u8* target) { +void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); - MapPages(base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); } -void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler) { +void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); - MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); - current_page_table->special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); + page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); } -void UnmapRegion(VAddr base, u32 size) { +void UnmapRegion(PageTable& page_table, VAddr base, u32 size) { ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); - MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); + MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); } /** diff --git a/src/core/memory.h b/src/core/memory.h index c8c56babd9..859a142022 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -7,8 +7,10 @@ #include #include #include +#include #include #include "common/common_types.h" +#include "core/mmio.h" namespace Memory { @@ -21,6 +23,59 @@ const u32 PAGE_MASK = PAGE_SIZE - 1; const int PAGE_BITS = 12; const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS); +enum class PageType { + /// Page is unmapped and should cause an access error. + Unmapped, + /// Page is mapped to regular memory. This is the only type you can get pointers to. + Memory, + /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and + /// invalidation + RasterizerCachedMemory, + /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. + Special, + /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and + /// invalidation + RasterizerCachedSpecial, +}; + +struct SpecialRegion { + VAddr base; + u32 size; + MMIORegionPointer handler; +}; + +/** + * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely + * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and + * fetching requirements when accessing. In the usual case of an access to regular memory, it only + * requires an indexed fetch and a check for NULL. + */ +struct PageTable { + /** + * Array of memory pointers backing each page. An entry can only be non-null if the + * corresponding entry in the `attributes` array is of type `Memory`. + */ + std::array pointers; + + /** + * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of + * type `Special`. + */ + std::vector special_regions; + + /** + * Array of fine grained page attributes. If it is set to any value other than `Memory`, then + * the corresponding entry in `pointers` MUST be set to null. + */ + std::array attributes; + + /** + * Indicates the number of externally cached resources touching a page that should be + * flushed before the memory is accessed + */ + std::array cached_res_count; +}; + /// Physical memory regions as seen from the ARM11 enum : PAddr { /// IO register area @@ -126,6 +181,9 @@ enum : VAddr { NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, }; +/// Currently active page table +extern PageTable* current_page_table; + bool IsValidVirtualAddress(const VAddr addr); bool IsValidPhysicalAddress(const PAddr addr); @@ -209,4 +267,4 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); * retrieve the current page table for that purpose. */ std::array* GetCurrentPageTablePointers(); -} +} // namespace Memory diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 3fdf3a87dc..c58baa50b6 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h @@ -9,24 +9,24 @@ namespace Memory { -void InitMemoryMap(); - /** * Maps an allocated buffer onto a region of the emulated process address space. * + * @param page_table The page table of the emulated process. * @param base The address to start mapping at. Must be page-aligned. * @param size The amount of bytes to map. Must be page-aligned. * @param target Buffer with the memory backing the mapping. Must be of length at least `size`. */ -void MapMemoryRegion(VAddr base, u32 size, u8* target); +void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target); /** * Maps a region of the emulated process address space as a IO region. + * @param page_table The page table of the emulated process. * @param base The address to start mapping at. Must be page-aligned. * @param size The amount of bytes to map. Must be page-aligned. * @param mmio_handler The handler that backs the mapping. */ -void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler); +void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler); -void UnmapRegion(VAddr base, u32 size); +void UnmapRegion(PageTable& page_table, VAddr base, u32 size); } -- cgit v1.2.3 From 6b8ad273fc79a78eddcb1fc2df440039ef0f5bf6 Mon Sep 17 00:00:00 2001 From: MerryMage Date: Sun, 24 Sep 2017 22:42:42 +0100 Subject: memory: Add GetCurrentPageTable/SetCurrentPageTable Don't expose Memory::current_page_table as a global. --- src/core/hle/kernel/thread.cpp | 11 ++++------- src/core/loader/3dsx.cpp | 2 +- src/core/loader/elf.cpp | 2 +- src/core/loader/ncch.cpp | 2 +- src/core/memory.cpp | 10 +++++++++- src/core/memory.h | 3 ++- src/tests/core/arm/arm_test_common.cpp | 2 +- 7 files changed, 19 insertions(+), 13 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 324415a367..61378211fc 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -178,16 +178,13 @@ static void SwitchContext(Thread* new_thread) { ready_queue.remove(new_thread->current_priority, new_thread); new_thread->status = THREADSTATUS_RUNNING; - Core::CPU().LoadContext(new_thread->context); - Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); - if (previous_process != current_thread->owner_process) { Kernel::g_current_process = current_thread->owner_process; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; - // We have switched processes and thus, page tables, clear the instruction cache so we - // don't keep stale data from the previous process. - Core::CPU().ClearInstructionCache(); + SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); } + + Core::CPU().LoadContext(new_thread->context); + Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); } else { current_thread = nullptr; // Note: We do not reset the current process and current page table when idling because diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 69cdc0867e..a03515e6ec 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -270,7 +270,7 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 2f27606a18..2de1f4e816 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -397,7 +397,7 @@ ResultStatus AppLoader_ELF::Load() { Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); Kernel::g_current_process->svc_access_mask.set(); Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach the default resource limit (APPLICATION) to the process Kernel::g_current_process->resource_limit = diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 79ea501471..ca282f9352 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -172,7 +172,7 @@ ResultStatus AppLoader_NCCH::LoadExec() { codeset->memory = std::make_shared>(std::move(code)); Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); - Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); // Attach a resource limit to the process based on the resource limit category Kernel::g_current_process->resource_limit = diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 68a6b1ac26..17fa10b499 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -22,12 +22,20 @@ namespace Memory { static std::array vram; static std::array n3ds_extra_ram; -PageTable* current_page_table = nullptr; +static PageTable* current_page_table = nullptr; std::array* GetCurrentPageTablePointers() { return ¤t_page_table->pointers; } +void SetCurrentPageTable(PageTable* page_table) { + current_page_table = page_table; +} + +PageTable* GetCurrentPageTable() { + return current_page_table; +} + static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, (base + size) * PAGE_SIZE); diff --git a/src/core/memory.h b/src/core/memory.h index b228a48c2e..db5a704d0f 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -182,7 +182,8 @@ enum : VAddr { }; /// Currently active page table -extern PageTable* current_page_table; +void SetCurrentPageTable(PageTable* page_table); +PageTable* GetCurrentPageTable(); bool IsValidVirtualAddress(const VAddr addr); bool IsValidPhysicalAddress(const PAddr addr); diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 8384ce7440..cfe0d503ae 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp @@ -21,7 +21,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_) Memory::MapIoRegion(page_table, 0x00000000, 0x80000000, test_memory); Memory::MapIoRegion(page_table, 0x80000000, 0x80000000, test_memory); - Memory::current_page_table = &page_table; + Memory::SetCurrentPageTable(&page_table); } TestEnvironment::~TestEnvironment() { -- cgit v1.2.3 From 042b389b571e94a7b3c0878fcfa19c58b6730c43 Mon Sep 17 00:00:00 2001 From: Max Thomas Date: Mon, 25 Sep 2017 00:17:38 -0600 Subject: Loader/NCCH: Add support for loading application updates (#2927) * loader/ncch: split NCCH parsing into its own file * loader/ncch: add support for loading update NCCHs from the SD card * loader/ncch: fix formatting * file_sys/ncch_container: Return a value for OpenFile * loader/ncch: cleanup, always instantiate overlay_ncch to base_ncch * file_sys/ncch_container: better encryption checks, allow non-app NCCHs to load properly and for the existence of NCCH structures to be checked * file_sys/ncch_container: pass filepath as a const reference --- src/core/CMakeLists.txt | 1 + src/core/file_sys/archive_selfncch.cpp | 28 ++- src/core/file_sys/archive_selfncch.h | 4 + src/core/file_sys/ncch_container.cpp | 316 ++++++++++++++++++++++++++++++++ src/core/file_sys/ncch_container.h | 244 +++++++++++++++++++++++++ src/core/loader/loader.h | 13 ++ src/core/loader/ncch.cpp | 319 +++++++-------------------------- src/core/loader/ncch.h | 184 +------------------ 8 files changed, 670 insertions(+), 439 deletions(-) create mode 100644 src/core/file_sys/ncch_container.cpp create mode 100644 src/core/file_sys/ncch_container.h (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index cd1a8de2d2..3ed6199916 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -26,6 +26,7 @@ set(SRCS file_sys/archive_systemsavedata.cpp file_sys/disk_archive.cpp file_sys/ivfc_archive.cpp + file_sys/ncch_container.cpp file_sys/path_parser.cpp file_sys/savedata_archive.cpp frontend/camera/blank_camera.cpp diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp index 298a37a448..7dc91a4050 100644 --- a/src/core/file_sys/archive_selfncch.cpp +++ b/src/core/file_sys/archive_selfncch.cpp @@ -102,8 +102,7 @@ public: switch (static_cast(file_path.type)) { case SelfNCCHFilePathType::UpdateRomFS: - LOG_WARNING(Service_FS, "(STUBBED) open update RomFS"); - return OpenRomFS(); + return OpenUpdateRomFS(); case SelfNCCHFilePathType::RomFS: return OpenRomFS(); @@ -179,6 +178,17 @@ private: } } + ResultVal> OpenUpdateRomFS() const { + if (ncch_data.update_romfs_file) { + return MakeResult>(std::make_unique( + ncch_data.update_romfs_file, ncch_data.update_romfs_offset, + ncch_data.update_romfs_size)); + } else { + LOG_INFO(Service_FS, "Unable to read update RomFS"); + return ERROR_ROMFS_NOT_FOUND; + } + } + ResultVal> OpenExeFS(const std::string& filename) const { if (filename == "icon") { if (ncch_data.icon) { @@ -218,11 +228,19 @@ private: }; ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { - std::shared_ptr romfs_file_; + std::shared_ptr romfs_file; + if (Loader::ResultStatus::Success == + app_loader.ReadRomFS(romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)) { + + ncch_data.romfs_file = std::move(romfs_file); + } + + std::shared_ptr update_romfs_file; if (Loader::ResultStatus::Success == - app_loader.ReadRomFS(romfs_file_, ncch_data.romfs_offset, ncch_data.romfs_size)) { + app_loader.ReadUpdateRomFS(update_romfs_file, ncch_data.update_romfs_offset, + ncch_data.update_romfs_size)) { - ncch_data.romfs_file = std::move(romfs_file_); + ncch_data.update_romfs_file = std::move(update_romfs_file); } std::vector buffer; diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h index f1b9712964..f1c6599481 100644 --- a/src/core/file_sys/archive_selfncch.h +++ b/src/core/file_sys/archive_selfncch.h @@ -24,6 +24,10 @@ struct NCCHData { std::shared_ptr romfs_file; u64 romfs_offset = 0; u64 romfs_size = 0; + + std::shared_ptr update_romfs_file; + u64 update_romfs_offset = 0; + u64 update_romfs_size = 0; }; /// File system interface to the SelfNCCH archive diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp new file mode 100644 index 0000000000..59c72f3e91 --- /dev/null +++ b/src/core/file_sys/ncch_container.cpp @@ -0,0 +1,316 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include "common/common_types.h" +#include "common/logging/log.h" +#include "core/core.h" +#include "core/file_sys/ncch_container.h" +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs +static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) + +/** + * Get the decompressed size of an LZSS compressed ExeFS file + * @param buffer Buffer of compressed file + * @param size Size of compressed buffer + * @return Size of decompressed buffer + */ +static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { + u32 offset_size = *(u32*)(buffer + size - 4); + return offset_size + size; +} + +/** + * Decompress ExeFS file (compressed with LZSS) + * @param compressed Compressed buffer + * @param compressed_size Size of compressed buffer + * @param decompressed Decompressed buffer + * @param decompressed_size Size of decompressed buffer + * @return True on success, otherwise false + */ +static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, + u32 decompressed_size) { + const u8* footer = compressed + compressed_size - 8; + u32 buffer_top_and_bottom = *reinterpret_cast(footer); + u32 out = decompressed_size; + u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); + u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); + + memset(decompressed, 0, decompressed_size); + memcpy(decompressed, compressed, compressed_size); + + while (index > stop_index) { + u8 control = compressed[--index]; + + for (unsigned i = 0; i < 8; i++) { + if (index <= stop_index) + break; + if (index <= 0) + break; + if (out <= 0) + break; + + if (control & 0x80) { + // Check if compression is out of bounds + if (index < 2) + return false; + index -= 2; + + u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); + u32 segment_size = ((segment_offset >> 12) & 15) + 3; + segment_offset &= 0x0FFF; + segment_offset += 2; + + // Check if compression is out of bounds + if (out < segment_size) + return false; + + for (unsigned j = 0; j < segment_size; j++) { + // Check if compression is out of bounds + if (out + segment_offset >= decompressed_size) + return false; + + u8 data = decompressed[out + segment_offset]; + decompressed[--out] = data; + } + } else { + // Check if compression is out of bounds + if (out < 1) + return false; + decompressed[--out] = compressed[--index]; + } + control <<= 1; + } + } + return true; +} + +NCCHContainer::NCCHContainer(const std::string& filepath) : filepath(filepath) { + file = FileUtil::IOFile(filepath, "rb"); +} + +Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath) { + this->filepath = filepath; + file = FileUtil::IOFile(filepath, "rb"); + + if (!file.IsOpen()) { + LOG_WARNING(Service_FS, "Failed to open %s", filepath.c_str()); + return Loader::ResultStatus::Error; + } + + LOG_DEBUG(Service_FS, "Opened %s", filepath.c_str()); + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::Load() { + if (is_loaded) + return Loader::ResultStatus::Success; + + // Reset read pointer in case this file has been read before. + file.Seek(0, SEEK_SET); + + if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) + return Loader::ResultStatus::Error; + + // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... + if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { + LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!"); + ncch_offset = 0x4000; + file.Seek(ncch_offset, SEEK_SET); + file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); + } + + // Verify we are loading the correct file type... + if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) + return Loader::ResultStatus::ErrorInvalidFormat; + + // System archives and DLC don't have an extended header but have RomFS + if (ncch_header.extended_header_size) { + if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) + return Loader::ResultStatus::Error; + + is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; + u32 entry_point = exheader_header.codeset_info.text.address; + u32 code_size = exheader_header.codeset_info.text.code_size; + u32 stack_size = exheader_header.codeset_info.stack_size; + u32 bss_size = exheader_header.codeset_info.bss_size; + u32 core_version = exheader_header.arm11_system_local_caps.core_version; + u8 priority = exheader_header.arm11_system_local_caps.priority; + u8 resource_limit_category = + exheader_header.arm11_system_local_caps.resource_limit_category; + + LOG_DEBUG(Service_FS, "Name: %s", exheader_header.codeset_info.name); + LOG_DEBUG(Service_FS, "Program ID: %016" PRIX64, ncch_header.program_id); + LOG_DEBUG(Service_FS, "Code compressed: %s", is_compressed ? "yes" : "no"); + LOG_DEBUG(Service_FS, "Entry point: 0x%08X", entry_point); + LOG_DEBUG(Service_FS, "Code size: 0x%08X", code_size); + LOG_DEBUG(Service_FS, "Stack size: 0x%08X", stack_size); + LOG_DEBUG(Service_FS, "Bss size: 0x%08X", bss_size); + LOG_DEBUG(Service_FS, "Core version: %d", core_version); + LOG_DEBUG(Service_FS, "Thread priority: 0x%X", priority); + LOG_DEBUG(Service_FS, "Resource limit category: %d", resource_limit_category); + LOG_DEBUG(Service_FS, "System Mode: %d", + static_cast(exheader_header.arm11_system_local_caps.system_mode)); + + if (exheader_header.system_info.jump_id != ncch_header.program_id) { + LOG_ERROR(Service_FS, "ExHeader Program ID mismatch: the ROM is probably encrypted."); + return Loader::ResultStatus::ErrorEncrypted; + } + + has_exheader = true; + } + + // DLC can have an ExeFS and a RomFS but no extended header + if (ncch_header.exefs_size) { + exefs_offset = ncch_header.exefs_offset * kBlockSize; + u32 exefs_size = ncch_header.exefs_size * kBlockSize; + + LOG_DEBUG(Service_FS, "ExeFS offset: 0x%08X", exefs_offset); + LOG_DEBUG(Service_FS, "ExeFS size: 0x%08X", exefs_size); + + file.Seek(exefs_offset + ncch_offset, SEEK_SET); + if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) + return Loader::ResultStatus::Error; + + has_exefs = true; + } + + if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) + has_romfs = true; + + is_loaded = true; + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector& buffer) { + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + if (!has_exefs) + return Loader::ResultStatus::ErrorNotUsed; + + LOG_DEBUG(Service_FS, "%d sections:", kMaxSections); + // Iterate through the ExeFs archive until we find a section with the specified name... + for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { + const auto& section = exefs_header.section[section_number]; + + // Load the specified section... + if (strcmp(section.name, name) == 0) { + LOG_DEBUG(Service_FS, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, + section.offset, section.size, section.name); + + s64 section_offset = + (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); + file.Seek(section_offset, SEEK_SET); + + if (strcmp(section.name, ".code") == 0 && is_compressed) { + // Section is compressed, read compressed .code section... + std::unique_ptr temp_buffer; + try { + temp_buffer.reset(new u8[section.size]); + } catch (std::bad_alloc&) { + return Loader::ResultStatus::ErrorMemoryAllocationFailed; + } + + if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) + return Loader::ResultStatus::Error; + + // Decompress .code section... + u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); + buffer.resize(decompressed_size); + if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) + return Loader::ResultStatus::ErrorInvalidFormat; + } else { + // Section is uncompressed... + buffer.resize(section.size); + if (file.ReadBytes(&buffer[0], section.size) != section.size) + return Loader::ResultStatus::Error; + } + return Loader::ResultStatus::Success; + } + } + return Loader::ResultStatus::ErrorNotUsed; +} + +Loader::ResultStatus NCCHContainer::ReadRomFS(std::shared_ptr& romfs_file, + u64& offset, u64& size) { + if (!file.IsOpen()) + return Loader::ResultStatus::Error; + + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + if (!has_romfs) { + LOG_DEBUG(Service_FS, "RomFS requested from NCCH which has no RomFS"); + return Loader::ResultStatus::ErrorNotUsed; + } + + u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; + u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; + + LOG_DEBUG(Service_FS, "RomFS offset: 0x%08X", romfs_offset); + LOG_DEBUG(Service_FS, "RomFS size: 0x%08X", romfs_size); + + if (file.GetSize() < romfs_offset + romfs_size) + return Loader::ResultStatus::Error; + + // We reopen the file, to allow its position to be independent from file's + romfs_file = std::make_shared(filepath, "rb"); + if (!romfs_file->IsOpen()) + return Loader::ResultStatus::Error; + + offset = romfs_offset; + size = romfs_size; + + return Loader::ResultStatus::Success; +} + +Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return result; + + program_id = ncch_header.program_id; + return Loader::ResultStatus::Success; +} + +bool NCCHContainer::HasExeFS() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_exefs; +} + +bool NCCHContainer::HasRomFS() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_romfs; +} + +bool NCCHContainer::HasExHeader() { + Loader::ResultStatus result = Load(); + if (result != Loader::ResultStatus::Success) + return false; + + return has_exheader; +} + +} // 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 0000000000..8af9032b42 --- /dev/null +++ b/src/core/file_sys/ncch_container.h @@ -0,0 +1,244 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include "common/bit_field.h" +#include "common/common_types.h" +#include "common/file_util.h" +#include "common/swap.h" +#include "core/core.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) + +struct NCCH_Header { + u8 signature[0x100]; + u32_le magic; + u32_le content_size; + u8 partition_id[8]; + u16_le maker_code; + u16_le version; + u8 reserved_0[4]; + u64_le program_id; + u8 reserved_1[0x10]; + u8 logo_region_hash[0x20]; + u8 product_code[0x10]; + u8 extended_header_hash[0x20]; + u32_le extended_header_size; + u8 reserved_2[4]; + u8 flags[8]; + u32_le plain_region_offset; + u32_le plain_region_size; + u32_le logo_region_offset; + u32_le logo_region_size; + u32_le exefs_offset; + u32_le exefs_size; + u32_le exefs_hash_region_size; + u8 reserved_3[4]; + u32_le romfs_offset; + u32_le romfs_size; + u32_le romfs_hash_region_size; + u8 reserved_4[4]; + u8 exefs_super_block_hash[0x20]; + u8 romfs_super_block_hash[0x20]; +}; + +static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExeFS (executable file system) headers + +struct ExeFs_SectionHeader { + char name[8]; + u32 offset; + u32 size; +}; + +struct ExeFs_Header { + ExeFs_SectionHeader section[8]; + u8 reserved[0x80]; + u8 hashes[8][0x20]; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// ExHeader (executable file system header) headers + +struct ExHeader_SystemInfoFlags { + u8 reserved[5]; + u8 flag; + u8 remaster_version[2]; +}; + +struct ExHeader_CodeSegmentInfo { + u32 address; + u32 num_max_pages; + u32 code_size; +}; + +struct ExHeader_CodeSetInfo { + u8 name[8]; + ExHeader_SystemInfoFlags flags; + ExHeader_CodeSegmentInfo text; + u32 stack_size; + ExHeader_CodeSegmentInfo ro; + u8 reserved[4]; + ExHeader_CodeSegmentInfo data; + u32 bss_size; +}; + +struct ExHeader_DependencyList { + u8 program_id[0x30][8]; +}; + +struct ExHeader_SystemInfo { + u64 save_data_size; + u64_le jump_id; + u8 reserved_2[0x30]; +}; + +struct ExHeader_StorageInfo { + u8 ext_save_data_id[8]; + u8 system_save_data_id[8]; + u8 reserved[8]; + u8 access_info[7]; + u8 other_attributes; +}; + +struct ExHeader_ARM11_SystemLocalCaps { + u64_le program_id; + u32_le core_version; + u8 reserved_flags[2]; + union { + u8 flags0; + BitField<0, 2, u8> ideal_processor; + BitField<2, 2, u8> affinity_mask; + BitField<4, 4, u8> system_mode; + }; + u8 priority; + u8 resource_limit_descriptor[0x10][2]; + ExHeader_StorageInfo storage_info; + u8 service_access_control[0x20][8]; + u8 ex_service_access_control[0x2][8]; + u8 reserved[0xf]; + u8 resource_limit_category; +}; + +struct ExHeader_ARM11_KernelCaps { + u32_le descriptors[28]; + u8 reserved[0x10]; +}; + +struct ExHeader_ARM9_AccessControl { + u8 descriptors[15]; + u8 descversion; +}; + +struct ExHeader_Header { + ExHeader_CodeSetInfo codeset_info; + ExHeader_DependencyList dependency_list; + ExHeader_SystemInfo system_info; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + struct { + u8 signature[0x100]; + u8 ncch_public_key_modulus[0x100]; + ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; + ExHeader_ARM11_KernelCaps arm11_kernel_caps; + ExHeader_ARM9_AccessControl arm9_access_control; + } access_desc; +}; + +static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// FileSys namespace + +namespace FileSys { + +/** + * Helper which implements an interface to deal with NCCH containers which can + * contain ExeFS archives or RomFS archives for games or other applications. + */ +class NCCHContainer { +public: + NCCHContainer(const std::string& filepath); + NCCHContainer() {} + + Loader::ResultStatus OpenFile(const std::string& filepath); + + /** + * Ensure ExeFS and exheader is loaded and ready for reading sections + * @return ResultStatus result of function + */ + Loader::ResultStatus Load(); + + /** + * Reads an application ExeFS section of an NCCH file (e.g. .code, .logo, etc.) + * @param name Name of section to read out of NCCH file + * @param buffer Vector to read data into + * @return ResultStatus result of function + */ + Loader::ResultStatus LoadSectionExeFS(const char* name, std::vector& buffer); + + /** + * Get the RomFS of the NCCH container + * Since the RomFS can be huge, we return a file reference instead of copying to a buffer + * @param romfs_file The file containing the RomFS + * @param offset The offset the romfs begins on + * @param size The size of the romfs + * @return ResultStatus result of function + */ + Loader::ResultStatus ReadRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size); + + /** + * Get the Program ID of the NCCH container + * @return ResultStatus result of function + */ + Loader::ResultStatus ReadProgramId(u64_le& program_id); + + /** + * Checks whether the NCCH container contains an ExeFS + * @return bool check result + */ + bool HasExeFS(); + + /** + * Checks whether the NCCH container contains a RomFS + * @return bool check result + */ + bool HasRomFS(); + + /** + * Checks whether the NCCH container contains an ExHeader + * @return bool check result + */ + bool HasExHeader(); + + NCCH_Header ncch_header; + ExeFs_Header exefs_header; + ExHeader_Header exheader_header; + +private: + bool has_exheader = false; + bool has_exefs = false; + bool has_romfs = false; + + bool is_loaded = false; + bool is_compressed = false; + + u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header + u32 exefs_offset = 0; + + std::string filepath; + FileUtil::IOFile file; +}; + +} // namespace FileSys diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index e731888a27..3160fd2fdc 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -166,6 +166,19 @@ public: return ResultStatus::ErrorNotImplemented; } + /** + * Get the update RomFS of the application + * Since the RomFS can be huge, we return a file reference instead of copying to a buffer + * @param romfs_file The file containing the RomFS + * @param offset The offset the romfs begins on + * @param size The size of the romfs + * @return ResultStatus result of function + */ + virtual ResultStatus ReadUpdateRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size) { + return ResultStatus::ErrorNotImplemented; + } + /** * Get the title of the application * @param title Reference to store the application title into diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 79ea501471..bef7fa567d 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -13,6 +13,7 @@ #include "common/swap.h" #include "core/core.h" #include "core/file_sys/archive_selfncch.h" +#include "core/file_sys/ncch_container.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/cfg/cfg.h" @@ -27,87 +28,7 @@ namespace Loader { -static const int kMaxSections = 8; ///< Maximum number of sections (files) in an ExeFs -static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) - -/** - * Get the decompressed size of an LZSS compressed ExeFS file - * @param buffer Buffer of compressed file - * @param size Size of compressed buffer - * @return Size of decompressed buffer - */ -static u32 LZSS_GetDecompressedSize(const u8* buffer, u32 size) { - u32 offset_size = *(u32*)(buffer + size - 4); - return offset_size + size; -} - -/** - * Decompress ExeFS file (compressed with LZSS) - * @param compressed Compressed buffer - * @param compressed_size Size of compressed buffer - * @param decompressed Decompressed buffer - * @param decompressed_size Size of decompressed buffer - * @return True on success, otherwise false - */ -static bool LZSS_Decompress(const u8* compressed, u32 compressed_size, u8* decompressed, - u32 decompressed_size) { - const u8* footer = compressed + compressed_size - 8; - u32 buffer_top_and_bottom = *reinterpret_cast(footer); - u32 out = decompressed_size; - u32 index = compressed_size - ((buffer_top_and_bottom >> 24) & 0xFF); - u32 stop_index = compressed_size - (buffer_top_and_bottom & 0xFFFFFF); - - memset(decompressed, 0, decompressed_size); - memcpy(decompressed, compressed, compressed_size); - - while (index > stop_index) { - u8 control = compressed[--index]; - - for (unsigned i = 0; i < 8; i++) { - if (index <= stop_index) - break; - if (index <= 0) - break; - if (out <= 0) - break; - - if (control & 0x80) { - // Check if compression is out of bounds - if (index < 2) - return false; - index -= 2; - - u32 segment_offset = compressed[index] | (compressed[index + 1] << 8); - u32 segment_size = ((segment_offset >> 12) & 15) + 3; - segment_offset &= 0x0FFF; - segment_offset += 2; - - // Check if compression is out of bounds - if (out < segment_size) - return false; - - for (unsigned j = 0; j < segment_size; j++) { - // Check if compression is out of bounds - if (out + segment_offset >= decompressed_size) - return false; - - u8 data = decompressed[out + segment_offset]; - decompressed[--out] = data; - } - } else { - // Check if compression is out of bounds - if (out < 1) - return false; - decompressed[--out] = compressed[--index]; - } - control <<= 1; - } - } - return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// AppLoader_NCCH class +static const u64 UPDATE_MASK = 0x0000000e00000000; FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { u32 magic; @@ -124,15 +45,25 @@ FileType AppLoader_NCCH::IdentifyType(FileUtil::IOFile& file) { return FileType::Error; } +static std::string GetUpdateNCCHPath(u64_le program_id) { + u32 high = static_cast((program_id | UPDATE_MASK) >> 32); + u32 low = static_cast((program_id | UPDATE_MASK) & 0xFFFFFFFF); + + return Common::StringFromFormat("%sNintendo 3DS/%s/%s/title/%08x/%08x/content/00000000.app", + FileUtil::GetUserPath(D_SDMC_IDX).c_str(), SYSTEM_ID, SDCARD_ID, + high, low); +} + std::pair, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { if (!is_loaded) { - ResultStatus res = LoadExeFS(); + ResultStatus res = base_ncch.Load(); if (res != ResultStatus::Success) { return std::make_pair(boost::none, res); } } + // Set the system mode as the one from the exheader. - return std::make_pair(exheader_header.arm11_system_local_caps.system_mode.Value(), + return std::make_pair(overlay_ncch->exheader_header.arm11_system_local_caps.system_mode.Value(), ResultStatus::Success); } @@ -144,29 +75,34 @@ ResultStatus AppLoader_NCCH::LoadExec() { return ResultStatus::ErrorNotLoaded; std::vector code; - if (ResultStatus::Success == ReadCode(code)) { + u64_le program_id; + if (ResultStatus::Success == ReadCode(code) && + ResultStatus::Success == ReadProgramId(program_id)) { std::string process_name = Common::StringFromFixedZeroTerminatedBuffer( - (const char*)exheader_header.codeset_info.name, 8); + (const char*)overlay_ncch->exheader_header.codeset_info.name, 8); - SharedPtr codeset = CodeSet::Create(process_name, ncch_header.program_id); + SharedPtr codeset = CodeSet::Create(process_name, program_id); codeset->code.offset = 0; - codeset->code.addr = exheader_header.codeset_info.text.address; - codeset->code.size = exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; + codeset->code.addr = overlay_ncch->exheader_header.codeset_info.text.address; + codeset->code.size = + overlay_ncch->exheader_header.codeset_info.text.num_max_pages * Memory::PAGE_SIZE; codeset->rodata.offset = codeset->code.offset + codeset->code.size; - codeset->rodata.addr = exheader_header.codeset_info.ro.address; - codeset->rodata.size = exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; + codeset->rodata.addr = overlay_ncch->exheader_header.codeset_info.ro.address; + codeset->rodata.size = + overlay_ncch->exheader_header.codeset_info.ro.num_max_pages * Memory::PAGE_SIZE; // TODO(yuriks): Not sure if the bss size is added to the page-aligned .data size or just // to the regular size. Playing it safe for now. - u32 bss_page_size = (exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; + u32 bss_page_size = (overlay_ncch->exheader_header.codeset_info.bss_size + 0xFFF) & ~0xFFF; code.resize(code.size() + bss_page_size, 0); codeset->data.offset = codeset->rodata.offset + codeset->rodata.size; - codeset->data.addr = exheader_header.codeset_info.data.address; + codeset->data.addr = overlay_ncch->exheader_header.codeset_info.data.address; codeset->data.size = - exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + bss_page_size; + overlay_ncch->exheader_header.codeset_info.data.num_max_pages * Memory::PAGE_SIZE + + bss_page_size; codeset->entrypoint = codeset->code.addr; codeset->memory = std::make_shared>(std::move(code)); @@ -177,150 +113,27 @@ ResultStatus AppLoader_NCCH::LoadExec() { // Attach a resource limit to the process based on the resource limit category Kernel::g_current_process->resource_limit = Kernel::ResourceLimit::GetForCategory(static_cast( - exheader_header.arm11_system_local_caps.resource_limit_category)); + overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); // Set the default CPU core for this process Kernel::g_current_process->ideal_processor = - exheader_header.arm11_system_local_caps.ideal_processor; + overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; // Copy data while converting endianness - std::array kernel_caps; - std::copy_n(exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), + std::arrayexheader_header.arm11_kernel_caps.descriptors)> + kernel_caps; + std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps)); Kernel::g_current_process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); - s32 priority = exheader_header.arm11_system_local_caps.priority; - u32 stack_size = exheader_header.codeset_info.stack_size; + s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; + u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; Kernel::g_current_process->Run(priority, stack_size); return ResultStatus::Success; } return ResultStatus::Error; } -ResultStatus AppLoader_NCCH::LoadSectionExeFS(const char* name, std::vector& buffer) { - if (!file.IsOpen()) - return ResultStatus::Error; - - ResultStatus result = LoadExeFS(); - if (result != ResultStatus::Success) - return result; - - LOG_DEBUG(Loader, "%d sections:", kMaxSections); - // Iterate through the ExeFs archive until we find a section with the specified name... - for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { - const auto& section = exefs_header.section[section_number]; - - // Load the specified section... - if (strcmp(section.name, name) == 0) { - LOG_DEBUG(Loader, "%d - offset: 0x%08X, size: 0x%08X, name: %s", section_number, - section.offset, section.size, section.name); - - s64 section_offset = - (section.offset + exefs_offset + sizeof(ExeFs_Header) + ncch_offset); - file.Seek(section_offset, SEEK_SET); - - if (strcmp(section.name, ".code") == 0 && is_compressed) { - // Section is compressed, read compressed .code section... - std::unique_ptr temp_buffer; - try { - temp_buffer.reset(new u8[section.size]); - } catch (std::bad_alloc&) { - return ResultStatus::ErrorMemoryAllocationFailed; - } - - if (file.ReadBytes(&temp_buffer[0], section.size) != section.size) - return ResultStatus::Error; - - // Decompress .code section... - u32 decompressed_size = LZSS_GetDecompressedSize(&temp_buffer[0], section.size); - buffer.resize(decompressed_size); - if (!LZSS_Decompress(&temp_buffer[0], section.size, &buffer[0], decompressed_size)) - return ResultStatus::ErrorInvalidFormat; - } else { - // Section is uncompressed... - buffer.resize(section.size); - if (file.ReadBytes(&buffer[0], section.size) != section.size) - return ResultStatus::Error; - } - return ResultStatus::Success; - } - } - return ResultStatus::ErrorNotUsed; -} - -ResultStatus AppLoader_NCCH::LoadExeFS() { - if (is_exefs_loaded) - return ResultStatus::Success; - - if (!file.IsOpen()) - return ResultStatus::Error; - - // Reset read pointer in case this file has been read before. - file.Seek(0, SEEK_SET); - - if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) - return ResultStatus::Error; - - // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)... - if (MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) { - LOG_DEBUG(Loader, "Only loading the first (bootable) NCCH within the NCSD file!"); - ncch_offset = 0x4000; - file.Seek(ncch_offset, SEEK_SET); - file.ReadBytes(&ncch_header, sizeof(NCCH_Header)); - } - - // Verify we are loading the correct file type... - if (MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) - return ResultStatus::ErrorInvalidFormat; - - // Read ExHeader... - - if (file.ReadBytes(&exheader_header, sizeof(ExHeader_Header)) != sizeof(ExHeader_Header)) - return ResultStatus::Error; - - is_compressed = (exheader_header.codeset_info.flags.flag & 1) == 1; - entry_point = exheader_header.codeset_info.text.address; - code_size = exheader_header.codeset_info.text.code_size; - stack_size = exheader_header.codeset_info.stack_size; - bss_size = exheader_header.codeset_info.bss_size; - core_version = exheader_header.arm11_system_local_caps.core_version; - priority = exheader_header.arm11_system_local_caps.priority; - resource_limit_category = exheader_header.arm11_system_local_caps.resource_limit_category; - - LOG_DEBUG(Loader, "Name: %s", exheader_header.codeset_info.name); - LOG_DEBUG(Loader, "Program ID: %016" PRIX64, ncch_header.program_id); - LOG_DEBUG(Loader, "Code compressed: %s", is_compressed ? "yes" : "no"); - LOG_DEBUG(Loader, "Entry point: 0x%08X", entry_point); - LOG_DEBUG(Loader, "Code size: 0x%08X", code_size); - LOG_DEBUG(Loader, "Stack size: 0x%08X", stack_size); - LOG_DEBUG(Loader, "Bss size: 0x%08X", bss_size); - LOG_DEBUG(Loader, "Core version: %d", core_version); - LOG_DEBUG(Loader, "Thread priority: 0x%X", priority); - LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category); - LOG_DEBUG(Loader, "System Mode: %d", - static_cast(exheader_header.arm11_system_local_caps.system_mode)); - - if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) { - LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted."); - return ResultStatus::ErrorEncrypted; - } - - // Read ExeFS... - - exefs_offset = ncch_header.exefs_offset * kBlockSize; - u32 exefs_size = ncch_header.exefs_size * kBlockSize; - - LOG_DEBUG(Loader, "ExeFS offset: 0x%08X", exefs_offset); - LOG_DEBUG(Loader, "ExeFS size: 0x%08X", exefs_size); - - file.Seek(exefs_offset + ncch_offset, SEEK_SET); - if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) - return ResultStatus::Error; - - is_exefs_loaded = true; - return ResultStatus::Success; -} - void AppLoader_NCCH::ParseRegionLockoutInfo() { std::vector smdh_buffer; if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) { @@ -339,23 +152,32 @@ void AppLoader_NCCH::ParseRegionLockoutInfo() { } ResultStatus AppLoader_NCCH::Load() { + u64_le ncch_program_id; + if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; - ResultStatus result = LoadExeFS(); + ResultStatus result = base_ncch.Load(); if (result != ResultStatus::Success) return result; - std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)}; + ReadProgramId(ncch_program_id); + std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_program_id)}; LOG_INFO(Loader, "Program ID: %s", program_id.c_str()); + update_ncch.OpenFile(GetUpdateNCCHPath(ncch_program_id)); + result = update_ncch.Load(); + if (result == ResultStatus::Success) { + overlay_ncch = &update_ncch; + } + Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id); if (auto room_member = Network::GetRoomMember().lock()) { Network::GameInfo game_info; ReadTitle(game_info.name); - game_info.id = ncch_header.program_id; + game_info.id = ncch_program_id; room_member->SendGameInfo(game_info); } @@ -374,61 +196,40 @@ ResultStatus AppLoader_NCCH::Load() { } ResultStatus AppLoader_NCCH::ReadCode(std::vector& buffer) { - return LoadSectionExeFS(".code", buffer); + return overlay_ncch->LoadSectionExeFS(".code", buffer); } ResultStatus AppLoader_NCCH::ReadIcon(std::vector& buffer) { - return LoadSectionExeFS("icon", buffer); + return overlay_ncch->LoadSectionExeFS("icon", buffer); } ResultStatus AppLoader_NCCH::ReadBanner(std::vector& buffer) { - return LoadSectionExeFS("banner", buffer); + return overlay_ncch->LoadSectionExeFS("banner", buffer); } ResultStatus AppLoader_NCCH::ReadLogo(std::vector& buffer) { - return LoadSectionExeFS("logo", buffer); + return overlay_ncch->LoadSectionExeFS("logo", buffer); } ResultStatus AppLoader_NCCH::ReadProgramId(u64& out_program_id) { - if (!file.IsOpen()) - return ResultStatus::Error; - - ResultStatus result = LoadExeFS(); + ResultStatus result = base_ncch.ReadProgramId(out_program_id); if (result != ResultStatus::Success) return result; - out_program_id = ncch_header.program_id; return ResultStatus::Success; } ResultStatus AppLoader_NCCH::ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) { - if (!file.IsOpen()) - return ResultStatus::Error; - - // Check if the NCCH has a RomFS... - if (ncch_header.romfs_offset != 0 && ncch_header.romfs_size != 0) { - u32 romfs_offset = ncch_offset + (ncch_header.romfs_offset * kBlockSize) + 0x1000; - u32 romfs_size = (ncch_header.romfs_size * kBlockSize) - 0x1000; - - LOG_DEBUG(Loader, "RomFS offset: 0x%08X", romfs_offset); - LOG_DEBUG(Loader, "RomFS size: 0x%08X", romfs_size); - - if (file.GetSize() < romfs_offset + romfs_size) - return ResultStatus::Error; - - // We reopen the file, to allow its position to be independent from file's - romfs_file = std::make_shared(filepath, "rb"); - if (!romfs_file->IsOpen()) - return ResultStatus::Error; + return base_ncch.ReadRomFS(romfs_file, offset, size); +} - offset = romfs_offset; - size = romfs_size; +ResultStatus AppLoader_NCCH::ReadUpdateRomFS(std::shared_ptr& romfs_file, + u64& offset, u64& size) { + ResultStatus result = update_ncch.ReadRomFS(romfs_file, offset, size); - return ResultStatus::Success; - } - LOG_DEBUG(Loader, "NCCH has no RomFS"); - return ResultStatus::ErrorNotUsed; + if (result != ResultStatus::Success) + return base_ncch.ReadRomFS(romfs_file, offset, size); } ResultStatus AppLoader_NCCH::ReadTitle(std::string& title) { diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index e40cef7649..9b56465cb2 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -5,154 +5,11 @@ #pragma once #include -#include "common/bit_field.h" #include "common/common_types.h" #include "common/swap.h" +#include "core/file_sys/ncch_container.h" #include "core/loader/loader.h" -//////////////////////////////////////////////////////////////////////////////////////////////////// -/// NCCH header (Note: "NCCH" appears to be a publicly unknown acronym) - -struct NCCH_Header { - u8 signature[0x100]; - u32_le magic; - u32_le content_size; - u8 partition_id[8]; - u16_le maker_code; - u16_le version; - u8 reserved_0[4]; - u64_le program_id; - u8 reserved_1[0x10]; - u8 logo_region_hash[0x20]; - u8 product_code[0x10]; - u8 extended_header_hash[0x20]; - u32_le extended_header_size; - u8 reserved_2[4]; - u8 flags[8]; - u32_le plain_region_offset; - u32_le plain_region_size; - u32_le logo_region_offset; - u32_le logo_region_size; - u32_le exefs_offset; - u32_le exefs_size; - u32_le exefs_hash_region_size; - u8 reserved_3[4]; - u32_le romfs_offset; - u32_le romfs_size; - u32_le romfs_hash_region_size; - u8 reserved_4[4]; - u8 exefs_super_block_hash[0x20]; - u8 romfs_super_block_hash[0x20]; -}; - -static_assert(sizeof(NCCH_Header) == 0x200, "NCCH header structure size is wrong"); - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ExeFS (executable file system) headers - -struct ExeFs_SectionHeader { - char name[8]; - u32 offset; - u32 size; -}; - -struct ExeFs_Header { - ExeFs_SectionHeader section[8]; - u8 reserved[0x80]; - u8 hashes[8][0x20]; -}; - -//////////////////////////////////////////////////////////////////////////////////////////////////// -// ExHeader (executable file system header) headers - -struct ExHeader_SystemInfoFlags { - u8 reserved[5]; - u8 flag; - u8 remaster_version[2]; -}; - -struct ExHeader_CodeSegmentInfo { - u32 address; - u32 num_max_pages; - u32 code_size; -}; - -struct ExHeader_CodeSetInfo { - u8 name[8]; - ExHeader_SystemInfoFlags flags; - ExHeader_CodeSegmentInfo text; - u32 stack_size; - ExHeader_CodeSegmentInfo ro; - u8 reserved[4]; - ExHeader_CodeSegmentInfo data; - u32 bss_size; -}; - -struct ExHeader_DependencyList { - u8 program_id[0x30][8]; -}; - -struct ExHeader_SystemInfo { - u64 save_data_size; - u8 jump_id[8]; - u8 reserved_2[0x30]; -}; - -struct ExHeader_StorageInfo { - u8 ext_save_data_id[8]; - u8 system_save_data_id[8]; - u8 reserved[8]; - u8 access_info[7]; - u8 other_attributes; -}; - -struct ExHeader_ARM11_SystemLocalCaps { - u64_le program_id; - u32_le core_version; - u8 reserved_flags[2]; - union { - u8 flags0; - BitField<0, 2, u8> ideal_processor; - BitField<2, 2, u8> affinity_mask; - BitField<4, 4, u8> system_mode; - }; - u8 priority; - u8 resource_limit_descriptor[0x10][2]; - ExHeader_StorageInfo storage_info; - u8 service_access_control[0x20][8]; - u8 ex_service_access_control[0x2][8]; - u8 reserved[0xf]; - u8 resource_limit_category; -}; - -struct ExHeader_ARM11_KernelCaps { - u32_le descriptors[28]; - u8 reserved[0x10]; -}; - -struct ExHeader_ARM9_AccessControl { - u8 descriptors[15]; - u8 descversion; -}; - -struct ExHeader_Header { - ExHeader_CodeSetInfo codeset_info; - ExHeader_DependencyList dependency_list; - ExHeader_SystemInfo system_info; - ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; - ExHeader_ARM11_KernelCaps arm11_kernel_caps; - ExHeader_ARM9_AccessControl arm9_access_control; - struct { - u8 signature[0x100]; - u8 ncch_public_key_modulus[0x100]; - ExHeader_ARM11_SystemLocalCaps arm11_system_local_caps; - ExHeader_ARM11_KernelCaps arm11_kernel_caps; - ExHeader_ARM9_AccessControl arm9_access_control; - } access_desc; -}; - -static_assert(sizeof(ExHeader_Header) == 0x800, "ExHeader structure size is wrong"); - //////////////////////////////////////////////////////////////////////////////////////////////////// // Loader namespace @@ -162,7 +19,8 @@ namespace Loader { class AppLoader_NCCH final : public AppLoader { public: AppLoader_NCCH(FileUtil::IOFile&& file, const std::string& filepath) - : AppLoader(std::move(file)), filepath(filepath) {} + : AppLoader(std::move(file)), filepath(filepath), base_ncch(filepath), + overlay_ncch(&base_ncch) {} /** * Returns the type of the file @@ -196,48 +54,24 @@ public: ResultStatus ReadRomFS(std::shared_ptr& romfs_file, u64& offset, u64& size) override; + ResultStatus ReadUpdateRomFS(std::shared_ptr& romfs_file, u64& offset, + u64& size) override; + ResultStatus ReadTitle(std::string& title) override; private: - /** - * Reads an application ExeFS section of an NCCH file into AppLoader (e.g. .code, .logo, etc.) - * @param name Name of section to read out of NCCH file - * @param buffer Vector to read data into - * @return ResultStatus result of function - */ - ResultStatus LoadSectionExeFS(const char* name, std::vector& buffer); - /** * Loads .code section into memory for booting * @return ResultStatus result of function */ ResultStatus LoadExec(); - /** - * Ensure ExeFS is loaded and ready for reading sections - * @return ResultStatus result of function - */ - ResultStatus LoadExeFS(); - /// Reads the region lockout info in the SMDH and send it to CFG service void ParseRegionLockoutInfo(); - bool is_exefs_loaded = false; - bool is_compressed = false; - - u32 entry_point = 0; - u32 code_size = 0; - u32 stack_size = 0; - u32 bss_size = 0; - u32 core_version = 0; - u8 priority = 0; - u8 resource_limit_category = 0; - u32 ncch_offset = 0; // Offset to NCCH header, can be 0 or after NCSD header - u32 exefs_offset = 0; - - NCCH_Header ncch_header; - ExeFs_Header exefs_header; - ExHeader_Header exheader_header; + FileSys::NCCHContainer base_ncch; + FileSys::NCCHContainer update_ncch; + FileSys::NCCHContainer* overlay_ncch; std::string filepath; }; -- cgit v1.2.3 From f2b635a73da3a6508cea59dd4d52874b5cf94d09 Mon Sep 17 00:00:00 2001 From: Subv Date: Sat, 23 Sep 2017 20:32:18 -0500 Subject: HLE/Archives: Allow multiple loaded applications to access their SelfNCCH archive independently. The loaders now register each loaded ROM with the SelfNCCH factory, which keeps the data around for the duration of the emulation session. When opening the SelfNCCH archive, the factory queries the current program's programid and uses that as a key to the map that contains the NCCHData structure (RomFS, Icon, Banner, etc). 3dsx files do not have a programid and will use a default of 0 for this value, thus, only 1 3dsx file with RomFS is loadable at the same time. --- src/core/file_sys/archive_selfncch.cpp | 43 +++++++++++++++++++++++++--------- src/core/file_sys/archive_selfncch.h | 9 +++++-- src/core/hle/service/fs/archive.cpp | 18 +++++++++++++- src/core/hle/service/fs/archive.h | 7 ++++++ src/core/loader/3dsx.cpp | 3 +-- src/core/loader/ncch.cpp | 3 +-- 6 files changed, 65 insertions(+), 18 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/file_sys/archive_selfncch.cpp b/src/core/file_sys/archive_selfncch.cpp index 7dc91a4050..a16941c70e 100644 --- a/src/core/file_sys/archive_selfncch.cpp +++ b/src/core/file_sys/archive_selfncch.cpp @@ -3,12 +3,14 @@ // Refer to the license.txt file included. #include +#include #include "common/common_types.h" #include "common/logging/log.h" #include "common/swap.h" #include "core/file_sys/archive_selfncch.h" #include "core/file_sys/errors.h" #include "core/file_sys/ivfc_archive.h" +#include "core/hle/kernel/process.h" //////////////////////////////////////////////////////////////////////////////////////////////////// // FileSys namespace @@ -227,38 +229,57 @@ private: NCCHData ncch_data; }; -ArchiveFactory_SelfNCCH::ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader) { - std::shared_ptr romfs_file; +void ArchiveFactory_SelfNCCH::Register(Loader::AppLoader& app_loader) { + u64 program_id = 0; + if (app_loader.ReadProgramId(program_id) != Loader::ResultStatus::Success) { + LOG_WARNING( + Service_FS, + "Could not read program id when registering with SelfNCCH, this might be a 3dsx file"); + } + + LOG_DEBUG(Service_FS, "Registering program %016" PRIX64 " with the SelfNCCH archive factory", + program_id); + + if (ncch_data.find(program_id) != ncch_data.end()) { + LOG_WARNING(Service_FS, "Registering program %016" PRIX64 + " with SelfNCCH will override existing mapping", + program_id); + } + + NCCHData& data = ncch_data[program_id]; + + std::shared_ptr romfs_file_; if (Loader::ResultStatus::Success == - app_loader.ReadRomFS(romfs_file, ncch_data.romfs_offset, ncch_data.romfs_size)) { + app_loader.ReadRomFS(romfs_file_, data.romfs_offset, data.romfs_size)) { - ncch_data.romfs_file = std::move(romfs_file); + data.romfs_file = std::move(romfs_file_); } std::shared_ptr update_romfs_file; if (Loader::ResultStatus::Success == - app_loader.ReadUpdateRomFS(update_romfs_file, ncch_data.update_romfs_offset, - ncch_data.update_romfs_size)) { + app_loader.ReadUpdateRomFS(update_romfs_file, data.update_romfs_offset, + data.update_romfs_size)) { - ncch_data.update_romfs_file = std::move(update_romfs_file); + data.update_romfs_file = std::move(update_romfs_file); } std::vector buffer; if (Loader::ResultStatus::Success == app_loader.ReadIcon(buffer)) - ncch_data.icon = std::make_shared>(std::move(buffer)); + data.icon = std::make_shared>(std::move(buffer)); buffer.clear(); if (Loader::ResultStatus::Success == app_loader.ReadLogo(buffer)) - ncch_data.logo = std::make_shared>(std::move(buffer)); + data.logo = std::make_shared>(std::move(buffer)); buffer.clear(); if (Loader::ResultStatus::Success == app_loader.ReadBanner(buffer)) - ncch_data.banner = std::make_shared>(std::move(buffer)); + data.banner = std::make_shared>(std::move(buffer)); } ResultVal> ArchiveFactory_SelfNCCH::Open(const Path& path) { - auto archive = std::make_unique(ncch_data); + auto archive = std::make_unique( + ncch_data[Kernel::g_current_process->codeset->program_id]); return MakeResult>(std::move(archive)); } diff --git a/src/core/file_sys/archive_selfncch.h b/src/core/file_sys/archive_selfncch.h index f1c6599481..0d6d6766ee 100644 --- a/src/core/file_sys/archive_selfncch.h +++ b/src/core/file_sys/archive_selfncch.h @@ -6,6 +6,7 @@ #include #include +#include #include #include "common/common_types.h" #include "core/file_sys/archive_backend.h" @@ -33,7 +34,10 @@ struct NCCHData { /// File system interface to the SelfNCCH archive class ArchiveFactory_SelfNCCH final : public ArchiveFactory { public: - explicit ArchiveFactory_SelfNCCH(Loader::AppLoader& app_loader); + ArchiveFactory_SelfNCCH() = default; + + /// Registers a loaded application so that we can open its SelfNCCH archive when requested. + void Register(Loader::AppLoader& app_loader); std::string GetName() const override { return "SelfNCCH"; @@ -43,7 +47,8 @@ public: ResultVal GetFormatInfo(const Path& path) const override; private: - NCCHData ncch_data; + /// Mapping of ProgramId -> NCCHData + std::unordered_map ncch_data; }; } // namespace FileSys diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp index 033fbc9aa8..4ccb3cd32a 100644 --- a/src/core/hle/service/fs/archive.cpp +++ b/src/core/hle/service/fs/archive.cpp @@ -20,6 +20,7 @@ #include "core/file_sys/archive_savedata.h" #include "core/file_sys/archive_sdmc.h" #include "core/file_sys/archive_sdmcwriteonly.h" +#include "core/file_sys/archive_selfncch.h" #include "core/file_sys/archive_systemsavedata.h" #include "core/file_sys/directory_backend.h" #include "core/file_sys/errors.h" @@ -48,7 +49,7 @@ struct hash { return std::hash()(static_cast(id_code)); } }; -} +} // namespace std static constexpr Kernel::Handle INVALID_HANDLE{}; @@ -564,6 +565,21 @@ void RegisterArchiveTypes() { auto systemsavedata_factory = std::make_unique(nand_directory); RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData); + + auto selfncch_factory = std::make_unique(); + RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH); +} + +void RegisterSelfNCCH(Loader::AppLoader& app_loader) { + auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH); + if (itr == id_code_map.end()) { + LOG_ERROR(Service_FS, + "Could not register a new NCCH because the SelfNCCH archive hasn't been created"); + return; + } + + auto* factory = static_cast(itr->second.get()); + factory->Register(app_loader); } void UnregisterArchiveTypes() { diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h index 3a3371c882..e3c8fc2ef4 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"}; /// The scrambled SD card CID, also known as ID1 static constexpr char SDCARD_ID[]{"00000000000000000000000000000000"}; +namespace Loader { +class AppLoader; +} + namespace Service { namespace FS { @@ -259,6 +263,9 @@ void ArchiveInit(); /// Shutdown archives void ArchiveShutdown(); +/// Registers a new NCCH file with the SelfNCCH archive factory +void RegisterSelfNCCH(Loader::AppLoader& app_loader); + /// Register all archive types void RegisterArchiveTypes(); diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index a03515e6ec..5ad5c52876 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp @@ -278,8 +278,7 @@ ResultStatus AppLoader_THREEDSX::Load() { Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); - Service::FS::RegisterArchiveType(std::make_unique(*this), - Service::FS::ArchiveIdCode::SelfNCCH); + Service::FS::RegisterSelfNCCH(*this); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index c46d7cfc66..5107135f98 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -187,8 +187,7 @@ ResultStatus AppLoader_NCCH::Load() { if (ResultStatus::Success != result) return result; - Service::FS::RegisterArchiveType(std::make_unique(*this), - Service::FS::ArchiveIdCode::SelfNCCH); + Service::FS::RegisterSelfNCCH(*this); ParseRegionLockoutInfo(); -- cgit v1.2.3 From 11920a6bd1500d9d3a13d9eaedc922ce36198cb2 Mon Sep 17 00:00:00 2001 From: Subv Date: Tue, 26 Sep 2017 18:17:47 -0500 Subject: Loaders: Don't automatically set the current process every time we load an application. The loaders will now just create a Kernel::Process, construct it and return it to the caller, which is responsible for setting it as the current process and configuring the global page table. --- src/core/core.cpp | 6 ++++-- src/core/loader/3dsx.cpp | 15 +++++++-------- src/core/loader/3dsx.h | 2 +- src/core/loader/elf.cpp | 15 +++++++-------- src/core/loader/elf.h | 2 +- src/core/loader/loader.h | 13 ++++++++----- src/core/loader/ncch.cpp | 19 +++++++++---------- src/core/loader/ncch.h | 5 +++-- 8 files changed, 40 insertions(+), 37 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/core.cpp b/src/core/core.cpp index 59b8768e71..0c7a729871 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -13,6 +13,7 @@ #include "core/core_timing.h" #include "core/gdbstub/gdbstub.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/thread.h" #include "core/hle/service/service.h" #include "core/hw/hw.h" @@ -100,7 +101,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file return init_result; } - const Loader::ResultStatus load_result{app_loader->Load()}; + const Loader::ResultStatus load_result{app_loader->Load(Kernel::g_current_process)}; if (Loader::ResultStatus::Success != load_result) { LOG_CRITICAL(Core, "Failed to load ROM (Error %i)!", load_result); System::Shutdown(); @@ -114,6 +115,7 @@ System::ResultStatus System::Load(EmuWindow* emu_window, const std::string& file return ResultStatus::ErrorLoader; } } + Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); status = ResultStatus::Success; return status; } @@ -196,4 +198,4 @@ void System::Shutdown() { LOG_DEBUG(Core, "Shutdown OK"); } -} // namespace +} // namespace Core diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 5ad5c52876..918038f1e3 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) return loadinfo->seg_addrs[2] + addr - offsets[1]; } -using Kernel::SharedPtr; using Kernel::CodeSet; +using Kernel::SharedPtr; static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr, SharedPtr* out_codeset) { @@ -255,7 +255,7 @@ FileType AppLoader_THREEDSX::IdentifyType(FileUtil::IOFile& file) { return FileType::Error; } -ResultStatus AppLoader_THREEDSX::Load() { +ResultStatus AppLoader_THREEDSX::Load(Kernel::SharedPtr& process) { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -267,16 +267,15 @@ ResultStatus AppLoader_THREEDSX::Load() { return ResultStatus::Error; codeset->name = filename; - Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); - Kernel::g_current_process->svc_access_mask.set(); - Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + process = Kernel::Process::Create(std::move(codeset)); + process->svc_access_mask.set(); + process->address_mappings = default_address_mappings; // Attach the default resource limit (APPLICATION) to the process - Kernel::g_current_process->resource_limit = + process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); + process->Run(48, Kernel::DEFAULT_STACK_SIZE); Service::FS::RegisterSelfNCCH(*this); diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h index 3f376778a3..1e59bbb9d1 100644 --- a/src/core/loader/3dsx.h +++ b/src/core/loader/3dsx.h @@ -31,7 +31,7 @@ public: return IdentifyType(file); } - ResultStatus Load() override; + ResultStatus Load(Kernel::SharedPtr& process) override; ResultStatus ReadIcon(std::vector& buffer) override; diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index 2de1f4e816..e36e421200 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp @@ -13,8 +13,8 @@ #include "core/loader/elf.h" #include "core/memory.h" -using Kernel::SharedPtr; using Kernel::CodeSet; +using Kernel::SharedPtr; //////////////////////////////////////////////////////////////////////////////////////////////////// // ELF Header Constants @@ -375,7 +375,7 @@ FileType AppLoader_ELF::IdentifyType(FileUtil::IOFile& file) { return FileType::Error; } -ResultStatus AppLoader_ELF::Load() { +ResultStatus AppLoader_ELF::Load(Kernel::SharedPtr& process) { if (is_loaded) return ResultStatus::ErrorAlreadyLoaded; @@ -394,16 +394,15 @@ ResultStatus AppLoader_ELF::Load() { SharedPtr codeset = elf_reader.LoadInto(Memory::PROCESS_IMAGE_VADDR); codeset->name = filename; - Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); - Kernel::g_current_process->svc_access_mask.set(); - Kernel::g_current_process->address_mappings = default_address_mappings; - Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + process = Kernel::Process::Create(std::move(codeset)); + process->svc_access_mask.set(); + process->address_mappings = default_address_mappings; // Attach the default resource limit (APPLICATION) to the process - Kernel::g_current_process->resource_limit = + process->resource_limit = Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION); - Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); + process->Run(48, Kernel::DEFAULT_STACK_SIZE); is_loaded = true; return ResultStatus::Success; diff --git a/src/core/loader/elf.h b/src/core/loader/elf.h index 862aa90d87..113da59179 100644 --- a/src/core/loader/elf.h +++ b/src/core/loader/elf.h @@ -30,7 +30,7 @@ public: return IdentifyType(file); } - ResultStatus Load() override; + ResultStatus Load(Kernel::SharedPtr& process) override; private: std::string filename; diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 3160fd2fdc..82b2be6a37 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -13,10 +13,12 @@ #include #include "common/common_types.h" #include "common/file_util.h" +#include "core/hle/kernel/kernel.h" namespace Kernel { struct AddressMapping; -} +class Process; +} // namespace Kernel //////////////////////////////////////////////////////////////////////////////////////////////////// // Loader namespace @@ -92,10 +94,11 @@ public: virtual FileType GetFileType() = 0; /** - * Load the application - * @return ResultStatus result of function + * Load the application and return the created Process instance + * @param process The newly created process. + * @return The status result of the operation. */ - virtual ResultStatus Load() = 0; + virtual ResultStatus Load(Kernel::SharedPtr& process) = 0; /** * Loads the system mode that this application needs. @@ -206,4 +209,4 @@ extern const std::initializer_list default_address_mappi */ std::unique_ptr GetLoader(const std::string& filename); -} // namespace +} // namespace Loader diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 5107135f98..66bc5823d2 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -67,9 +67,9 @@ std::pair, ResultStatus> AppLoader_NCCH::LoadKernelSystemMo ResultStatus::Success); } -ResultStatus AppLoader_NCCH::LoadExec() { - using Kernel::SharedPtr; +ResultStatus AppLoader_NCCH::LoadExec(Kernel::SharedPtr& process) { using Kernel::CodeSet; + using Kernel::SharedPtr; if (!is_loaded) return ResultStatus::ErrorNotLoaded; @@ -107,16 +107,15 @@ ResultStatus AppLoader_NCCH::LoadExec() { codeset->entrypoint = codeset->code.addr; codeset->memory = std::make_shared>(std::move(code)); - Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); - Memory::SetCurrentPageTable(&Kernel::g_current_process->vm_manager.page_table); + process = Kernel::Process::Create(std::move(codeset)); // Attach a resource limit to the process based on the resource limit category - Kernel::g_current_process->resource_limit = + process->resource_limit = Kernel::ResourceLimit::GetForCategory(static_cast( overlay_ncch->exheader_header.arm11_system_local_caps.resource_limit_category)); // Set the default CPU core for this process - Kernel::g_current_process->ideal_processor = + process->ideal_processor = overlay_ncch->exheader_header.arm11_system_local_caps.ideal_processor; // Copy data while converting endianness @@ -124,11 +123,11 @@ ResultStatus AppLoader_NCCH::LoadExec() { kernel_caps; std::copy_n(overlay_ncch->exheader_header.arm11_kernel_caps.descriptors, kernel_caps.size(), begin(kernel_caps)); - Kernel::g_current_process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); + process->ParseKernelCaps(kernel_caps.data(), kernel_caps.size()); s32 priority = overlay_ncch->exheader_header.arm11_system_local_caps.priority; u32 stack_size = overlay_ncch->exheader_header.codeset_info.stack_size; - Kernel::g_current_process->Run(priority, stack_size); + process->Run(priority, stack_size); return ResultStatus::Success; } return ResultStatus::Error; @@ -151,7 +150,7 @@ void AppLoader_NCCH::ParseRegionLockoutInfo() { } } -ResultStatus AppLoader_NCCH::Load() { +ResultStatus AppLoader_NCCH::Load(Kernel::SharedPtr& process) { u64_le ncch_program_id; if (is_loaded) @@ -183,7 +182,7 @@ ResultStatus AppLoader_NCCH::Load() { is_loaded = true; // Set state to loaded - result = LoadExec(); // Load the executable into memory for booting + result = LoadExec(process); // Load the executable into memory for booting if (ResultStatus::Success != result) return result; diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h index 9b56465cb2..09230ae33f 100644 --- a/src/core/loader/ncch.h +++ b/src/core/loader/ncch.h @@ -33,7 +33,7 @@ public: return IdentifyType(file); } - ResultStatus Load() override; + ResultStatus Load(Kernel::SharedPtr& process) override; /** * Loads the Exheader and returns the system mode for this application. @@ -62,9 +62,10 @@ public: private: /** * Loads .code section into memory for booting + * @param process The newly created process * @return ResultStatus result of function */ - ResultStatus LoadExec(); + ResultStatus LoadExec(Kernel::SharedPtr& process); /// Reads the region lockout info in the SMDH and send it to CFG service void ParseRegionLockoutInfo(); -- cgit v1.2.3 From 9c8cdede44cbe7b276a42d1cb1135b83b286d9e3 Mon Sep 17 00:00:00 2001 From: shinyquagsire23 Date: Sun, 1 Oct 2017 10:41:40 -0600 Subject: file_sys, loader: add support for reading TMDs to determine app paths --- src/core/file_sys/archive_ncch.cpp | 15 +++++++++++++-- src/core/loader/ncch.cpp | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 5 deletions(-) (limited to 'src/core/loader/ncch.cpp') diff --git a/src/core/file_sys/archive_ncch.cpp b/src/core/file_sys/archive_ncch.cpp index 19e1eb981f..e8c5be983f 100644 --- a/src/core/file_sys/archive_ncch.cpp +++ b/src/core/file_sys/archive_ncch.cpp @@ -14,6 +14,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/ivfc_archive.h" #include "core/file_sys/ncch_container.h" +#include "core/file_sys/title_metadata.h" #include "core/hle/service/fs/archive.h" #include "core/loader/loader.h" @@ -27,8 +28,18 @@ static std::string GetNCCHContainerPath(const std::string& nand_directory) { } static std::string GetNCCHPath(const std::string& mount_point, u32 high, u32 low) { - return Common::StringFromFormat("%s%08x/%08x/content/00000000.app", mount_point.c_str(), high, - low); + u32 content_id = 0; + + // TODO(shinyquagsire23): Title database should be doing this path lookup + std::string content_path = + Common::StringFromFormat("%s%08x/%08x/content/", mount_point.c_str(), high, low); + std::string tmd_path = content_path + "00000000.tmd"; + TitleMetadata tmd(tmd_path); + if (tmd.Load() == Loader::ResultStatus::Success) { + content_id = tmd.GetBootContentID(); + } + + return Common::StringFromFormat("%s%08x.app", content_path.c_str(), content_id); } ArchiveFactory_NCCH::ArchiveFactory_NCCH(const std::string& nand_directory) diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 66bc5823d2..52686e3641 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp @@ -14,6 +14,7 @@ #include "core/core.h" #include "core/file_sys/archive_selfncch.h" #include "core/file_sys/ncch_container.h" +#include "core/file_sys/title_metadata.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/resource_limit.h" #include "core/hle/service/cfg/cfg.h" @@ -49,9 +50,19 @@ static std::string GetUpdateNCCHPath(u64_le program_id) { u32 high = static_cast((program_id | UPDATE_MASK) >> 32); u32 low = static_cast((program_id | UPDATE_MASK) & 0xFFFFFFFF); - return Common::StringFromFormat("%sNintendo 3DS/%s/%s/title/%08x/%08x/content/00000000.app", - FileUtil::GetUserPath(D_SDMC_IDX).c_str(), SYSTEM_ID, SDCARD_ID, - high, low); + // TODO(shinyquagsire23): Title database should be doing this path lookup + std::string content_path = Common::StringFromFormat( + "%sNintendo 3DS/%s/%s/title/%08x/%08x/content/", FileUtil::GetUserPath(D_SDMC_IDX).c_str(), + SYSTEM_ID, SDCARD_ID, high, low); + std::string tmd_path = content_path + "00000000.tmd"; + + u32 content_id = 0; + FileSys::TitleMetadata tmd(tmd_path); + if (tmd.Load() == ResultStatus::Success) { + content_id = tmd.GetBootContentID(); + } + + return Common::StringFromFormat("%s%08x.app", content_path.c_str(), content_id); } std::pair, ResultStatus> AppLoader_NCCH::LoadKernelSystemMode() { -- cgit v1.2.3