core: Implement AM:CancelImportProgram (#1535)

This commit is contained in:
PabloMK7 2025-12-27 12:11:07 +01:00 committed by GitHub
parent 9ed8cdccd4
commit f60f3eed1f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 152 additions and 103 deletions

View File

@ -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<u16>(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<u16>(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<Service::FS::MediaType>(rp.Pop<u8>());
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<Service::FS::File>(
am->system.Kernel(), std::make_unique<CIAFile>(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<Service::FS::File> file;
{
auto cia_file = std::make_unique<CIAFile>(am->system, FS::MediaType::NAND);
AuthorizeCIAFileDecryption(cia_file.get(), ctx);
file =
std::make_shared<Service::FS::File>(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<Kernel::ClientSession>();
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<Kernel::ClientSession>();
// 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<T*> GetFileBackendFromSession(std::shared_ptr<Kernel::ClientSession> f
return Kernel::ResultNotImplemented;
}
void Module::Interface::BeginImportProgram(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
auto media_type = static_cast<Service::FS::MediaType>(rp.Pop<u8>());
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<Service::FS::File>(
am->system.Kernel(), std::make_unique<CIAFile>(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<Service::FS::File> file;
{
auto cia_file = std::make_unique<CIAFile>(am->system, FS::MediaType::NAND);
AuthorizeCIAFileDecryption(cia_file.get(), ctx);
file =
std::make_shared<Service::FS::File>(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<Kernel::ClientSession>();
LOG_DEBUG(Service_AM, "");
auto cia_file = GetFileBackendFromSession<CIAFile>(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<Kernel::ClientSession>();
LOG_DEBUG(Service_AM, "");
auto cia_file = GetFileBackendFromSession<CIAFile>(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<Kernel::ClientSession>();
auto cia_file = GetFileBackendFromSession<CIAFile>(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<FS::MediaType>(rp.Pop<u8>());

View File

@ -224,6 +224,11 @@ public:
is_done = true;
}
void Cancel() {
is_cancel = true;
Close();
}
const std::vector<InstallResult>& 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

View File

@ -58,7 +58,7 @@ AM_NET::AM_NET(std::shared_ptr<Module> 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"},

View File

@ -58,7 +58,7 @@ AM_U::AM_U(std::shared_ptr<Module> 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"},