diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 9b0d5a709..3bae37cfa 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -850,19 +850,25 @@ bool CIAFile::Close() { current_content_install_result.type = InstallResult::Type::NONE; } - bool complete = - from_cdn ? is_done - : (install_state >= CIAInstallState::TMDLoaded && - content_written.size() == container.GetTitleMetadata().GetContentCount() && - std::all_of(content_written.begin(), content_written.end(), - [this, i = 0](auto& bytes_written) mutable { - return bytes_written >= - container.GetContentSize(static_cast(i++)); - })); + bool complete; + + if (is_cancel) { + complete = false; + } else { + complete = + from_cdn ? is_done + : (install_state >= CIAInstallState::TMDLoaded && + content_written.size() == container.GetTitleMetadata().GetContentCount() && + std::all_of(content_written.begin(), content_written.end(), + [this, i = 0](auto& bytes_written) mutable { + return bytes_written >= + container.GetContentSize(static_cast(i++)); + })); + } // Install aborted if (!complete) { - LOG_ERROR(Service_AM, "CIAFile closed prematurely, aborting install..."); + LOG_ERROR(Service_AM, "CIAFile closed prematurely or cancelled, aborting install..."); if (!is_additional_content) { // Only delete the content folder as there may be user save data in the title folder. const std::string title_content_path = @@ -3214,97 +3220,6 @@ void Module::Interface::CheckContentRightsIgnorePlatform(Kernel::HLERequestConte LOG_DEBUG(Service_AM, "tid={:016x}, content_index={}", tid, content_index); } -void Module::Interface::BeginImportProgram(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - auto media_type = static_cast(rp.Pop()); - - if (am->cia_installing) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState, - ErrorLevel::Permanent)); - return; - } - - // Create our CIAFile handle for the app to write to, and while the app writes - // Citra will store contents out to sdmc/nand - const FileSys::Path cia_path = {}; - auto file = std::make_shared( - am->system.Kernel(), std::make_unique(am->system, media_type), cia_path); - - am->cia_installing = true; - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(ResultSuccess); // No error - rb.PushCopyObjects(file->Connect()); - - LOG_WARNING(Service_AM, "(STUBBED) media_type={}", media_type); -} - -void Module::Interface::BeginImportProgramTemporarily(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - - if (am->cia_installing) { - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState, - ErrorLevel::Permanent)); - return; - } - - // Note: This function should register the title in the temp_i.db database, but we can get away - // with not doing that because we traverse the file system to detect installed titles. - // Create our CIAFile handle for the app to write to, and while the app writes Citra will store - // contents out to sdmc/nand - const FileSys::Path cia_path = {}; - std::shared_ptr file; - { - auto cia_file = std::make_unique(am->system, FS::MediaType::NAND); - - AuthorizeCIAFileDecryption(cia_file.get(), ctx); - - file = - std::make_shared(am->system.Kernel(), std::move(cia_file), cia_path); - } - am->cia_installing = true; - - IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); - rb.Push(ResultSuccess); // No error - rb.PushCopyObjects(file->Connect()); - - LOG_WARNING(Service_AM, "(STUBBED)"); -} - -void Module::Interface::EndImportProgram(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - [[maybe_unused]] const auto cia = rp.PopObject(); - - LOG_DEBUG(Service_AM, ""); - - am->ScanForAllTitles(); - - am->cia_installing = false; - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); -} - -void Module::Interface::EndImportProgramWithoutCommit(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp(ctx); - [[maybe_unused]] const auto cia = rp.PopObject(); - - // Note: This function is basically a no-op for us since we don't use title.db or ticket.db - // files to keep track of installed titles. - am->ScanForAllTitles(); - - am->cia_installing = false; - IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); - rb.Push(ResultSuccess); - - LOG_WARNING(Service_AM, "(STUBBED)"); -} - -void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) { - CommitImportTitlesImpl(ctx, false, false); -} - /// Wraps all File operations to allow adding an offset to them. class AMFileWrapper : public FileSys::FileBackend { public: @@ -3420,6 +3335,123 @@ ResultVal GetFileBackendFromSession(std::shared_ptr f return Kernel::ResultNotImplemented; } +void Module::Interface::BeginImportProgram(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + auto media_type = static_cast(rp.Pop()); + + if (am->cia_installing) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + return; + } + + // Create our CIAFile handle for the app to write to, and while the app writes + // Citra will store contents out to sdmc/nand + const FileSys::Path cia_path = {}; + auto file = std::make_shared( + am->system.Kernel(), std::make_unique(am->system, media_type), cia_path); + + am->cia_installing = true; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); // No error + rb.PushCopyObjects(file->Connect()); + + LOG_WARNING(Service_AM, "(STUBBED) media_type={}", media_type); +} + +void Module::Interface::BeginImportProgramTemporarily(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + + if (am->cia_installing) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(Result(ErrCodes::InvalidImportState, ErrorModule::AM, ErrorSummary::InvalidState, + ErrorLevel::Permanent)); + return; + } + + // Note: This function should register the title in the temp_i.db database, but we can get away + // with not doing that because we traverse the file system to detect installed titles. + // Create our CIAFile handle for the app to write to, and while the app writes Citra will store + // contents out to sdmc/nand + const FileSys::Path cia_path = {}; + std::shared_ptr file; + { + auto cia_file = std::make_unique(am->system, FS::MediaType::NAND); + + AuthorizeCIAFileDecryption(cia_file.get(), ctx); + + file = + std::make_shared(am->system.Kernel(), std::move(cia_file), cia_path); + } + am->cia_installing = true; + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(ResultSuccess); // No error + rb.PushCopyObjects(file->Connect()); + + LOG_WARNING(Service_AM, "(STUBBED)"); +} + +void Module::Interface::CancelImportProgram(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const auto cia = rp.PopObject(); + + LOG_DEBUG(Service_AM, ""); + + auto cia_file = GetFileBackendFromSession(cia); + if (cia_file.Succeeded()) { + cia_file.Unwrap()->Cancel(); + } + + am->cia_installing = false; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void Module::Interface::EndImportProgram(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const auto cia = rp.PopObject(); + + LOG_DEBUG(Service_AM, ""); + + auto cia_file = GetFileBackendFromSession(cia); + if (cia_file.Succeeded()) { + cia_file.Unwrap()->Close(); + } + + am->ScanForAllTitles(); + + am->cia_installing = false; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); +} + +void Module::Interface::EndImportProgramWithoutCommit(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp(ctx); + const auto cia = rp.PopObject(); + + auto cia_file = GetFileBackendFromSession(cia); + if (cia_file.Succeeded()) { + cia_file.Unwrap()->Close(); + } + + // Note: This function is basically a no-op for us since we don't use title.db or ticket.db + // files to keep track of installed titles. + am->ScanForAllTitles(); + + am->cia_installing = false; + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ResultSuccess); + + LOG_WARNING(Service_AM, "(STUBBED)"); +} + +void Module::Interface::CommitImportPrograms(Kernel::HLERequestContext& ctx) { + CommitImportTitlesImpl(ctx, false, false); +} + void Module::Interface::GetProgramInfoFromCia(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp(ctx); [[maybe_unused]] const auto media_type = static_cast(rp.Pop()); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 5f92d7adc..4dec69e80 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -224,6 +224,11 @@ public: is_done = true; } + void Cancel() { + is_cancel = true; + Close(); + } + const std::vector& GetInstallResults() const { return install_results; } @@ -237,6 +242,7 @@ private: bool decryption_authorized; bool is_done = false; bool is_closed = false; + bool is_cancel = false; bool is_additional_content = false; // Whether it's installing an update, and what step of installation it is at @@ -815,6 +821,17 @@ public: */ void BeginImportProgramTemporarily(Kernel::HLERequestContext& ctx); + /** + * AM::CancelImportProgram service function + * Cancel importing a CTR Installable Archive + * Inputs: + * 0 : Command header (0x04040002) + * 1-2 : CIAFile handle application wrote to + * Outputs: + * 1 : Result, 0 on success, otherwise error code + */ + void CancelImportProgram(Kernel::HLERequestContext& ctx); + /** * AM::EndImportProgram service function * Finish importing from a CTR Installable Archive diff --git a/src/core/hle/service/am/am_net.cpp b/src/core/hle/service/am/am_net.cpp index 1ed88ea8b..039ea91b3 100644 --- a/src/core/hle/service/am/am_net.cpp +++ b/src/core/hle/service/am/am_net.cpp @@ -58,7 +58,7 @@ AM_NET::AM_NET(std::shared_ptr am) : Module::Interface(std::move(am), "a {0x0401, nullptr, "UpdateFirmwareTo"}, {0x0402, &AM_NET::BeginImportProgram, "BeginImportProgram"}, {0x0403, &AM_NET::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"}, - {0x0404, nullptr, "CancelImportProgram"}, + {0x0404, &AM_NET::CancelImportProgram, "CancelImportProgram"}, {0x0405, &AM_NET::EndImportProgram, "EndImportProgram"}, {0x0406, &AM_NET::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"}, {0x0407, &AM_NET::CommitImportPrograms, "CommitImportPrograms"}, diff --git a/src/core/hle/service/am/am_u.cpp b/src/core/hle/service/am/am_u.cpp index 32395d75a..0356999d8 100644 --- a/src/core/hle/service/am/am_u.cpp +++ b/src/core/hle/service/am/am_u.cpp @@ -58,7 +58,7 @@ AM_U::AM_U(std::shared_ptr am) : Module::Interface(std::move(am), "am:u" {0x0401, nullptr, "UpdateFirmwareTo"}, {0x0402, &AM_U::BeginImportProgram, "BeginImportProgram"}, {0x0403, &AM_U::BeginImportProgramTemporarily, "BeginImportProgramTemporarily"}, - {0x0404, nullptr, "CancelImportProgram"}, + {0x0404, &AM_U::CancelImportProgram, "CancelImportProgram"}, {0x0405, &AM_U::EndImportProgram, "EndImportProgram"}, {0x0406, &AM_U::EndImportProgramWithoutCommit, "EndImportProgramWithoutCommit"}, {0x0407, &AM_U::CommitImportPrograms, "CommitImportPrograms"},