Compare commits

..

69 Commits

Author SHA1 Message Date
Michael Scire
f7bf379cfe git subrepo push libraries
subrepo:
  subdir:   "libraries"
  merged:   "80bf6aeee"
upstream:
  origin:   "https://github.com/Atmosphere-NX/Atmosphere-libs"
  branch:   "master"
  commit:   "80bf6aeee"
git-subrepo:
  version:  "0.4.1"
  origin:   "???"
  commit:   "???"
2023-10-27 16:22:45 -07:00
Michael Scire
9f26419b1a ams: bump version to 1.6.2, add changelog 2023-10-27 16:21:57 -07:00
Michael Scire
1b057d48c6 sm: fix compat with new service macros 2023-10-26 14:44:45 -07:00
Michael Scire
0c3afff4d3 pm: update to reflect 17.0.0 internal design changes 2023-10-26 14:44:32 -07:00
Michael Scire
274f6b63f2 erpt: add remaining SubmitFsInfo helpers 2023-10-25 16:08:12 -07:00
Michael Scire
2ed8450446 erpt: SubmitFileSystemProxyErrorInfo 2023-10-25 14:21:27 -07:00
Michael Scire
60974a5f4e erpt: GetMmcErrorInfo, GetSdCard*Info 2023-10-25 12:41:18 -07:00
Michael Scire
fa384fd920 erpt: begin SubmitFsinfo (SubmitMmcDetailInfo) 2023-10-25 04:45:41 -07:00
Michael Scire
3f19db0d96 jpegdec: fix abort check on output width 2023-10-18 02:33:59 -07:00
Michael Scire
a84f725e21 jpegdec: update to reflect 17.0.0 changes 2023-10-18 02:31:26 -07:00
Michael Scire
7f61dfdb8d pm: since 15.0.0, WaitApplicationMemoryAvailable is more lenient 2023-10-17 11:25:35 -07:00
Michael Scire
c44da84869 pm: adjust resource limit function names 2023-10-17 11:10:09 -07:00
Liam
7f4450f930 haze: pretend to be able to write MTP server code 2023-10-16 17:31:09 -04:00
Michael Scire
edb4e2ea56 git subrepo push libraries
subrepo:
  subdir:   "libraries"
  merged:   "965e05b3c"
upstream:
  origin:   "https://github.com/Atmosphere-NX/Atmosphere-libs"
  branch:   "master"
  commit:   "965e05b3c"
git-subrepo:
  version:  "0.4.1"
  origin:   "???"
  commit:   "???"
2023-10-16 12:31:15 -07:00
Michael Scire
183f3e0d7e ams: bump version to 1.6.1 2023-10-16 12:30:35 -07:00
Michael Scire
7650c5eb96 docs: add changelog for 1.6.1 2023-10-16 12:30:18 -07:00
Liam
0ff197b300 haze: fix bMaxPacketSize0 2023-10-16 08:36:33 -07:00
Liam
d9fff85bc4 haze: use gpu console for rendering 2023-10-16 08:36:10 -07:00
Liam
c866c15856 haze: implement GetObjectPropList 2023-10-16 08:36:10 -07:00
Michael Scire
e8ac23e2ee ncm: fix two comments 2023-10-16 08:24:07 -07:00
Michael Scire
3a8cffef57 ncm: better detect + fix 17 brick after-the-fact
This adds detection for missing-save or empty-save, and rebuilds in either case.
2023-10-16 02:38:30 -07:00
Liam
13411902c9 fs: add missing stub for GetProgramId 2023-10-14 07:52:47 -07:00
Michael Scire
693fb423cb kern: fix minor sin 2023-10-12 14:25:17 -07:00
Michael Scire
8a9eb85e05 git subrepo push libraries
subrepo:
  subdir:   "libraries"
  merged:   "132558c33"
upstream:
  origin:   "https://github.com/Atmosphere-NX/Atmosphere-libs"
  branch:   "master"
  commit:   "132558c33"
git-subrepo:
  version:  "0.4.1"
  origin:   "???"
  commit:   "???"
2023-10-12 09:23:31 -07:00
Michael Scire
d389ef639e docs: add changelog for 1.6.0 2023-10-12 09:19:26 -07:00
Michael Scire
719858ad18 git subrepo push emummc
subrepo:
  subdir:   "emummc"
  merged:   "9513a5412"
upstream:
  origin:   "https://github.com/m4xw/emummc"
  branch:   "develop"
  commit:   "9513a5412"
git-subrepo:
  version:  "0.4.1"
  origin:   "???"
  commit:   "???"
2023-10-12 09:02:30 -07:00
Michael Scire
e4d08ae0c5 erpt: amend min-version for latest CreateReportWithAttachments 2023-10-12 08:55:58 -07:00
Michael Scire
0c063db926 loader: add usb3 patches for 17.0.0 2023-10-12 08:55:58 -07:00
Michael Scire
02e987819b ncm: work around change in Nintendo save handling behavior
Static save files do not require an entry in the save data indexer to mount.
Prior to 17.0.0, save data files were considered static if userid was 0.
In 17.0.0+, only 8000000000000000 is static.

However, some users using cfw do not have an entry for 8000000000000120 in the indexer,
for various reasons (but mostly manual nand-restore, I think). Thus, on boot of 17.0.0+,
FS will say 8000000000000120 is not present (not in indexer), and NCM will create it anew.

The 8000000000000120 save will then be empty, and then the firmware can't boot.

To workaround this, logic has been re-enabled on 17.0.0+ for building the content meta database.
Thus, if the user encounters this error, the 8000000000000120 save will be emptied, but then
it will be automatically reconstructed, fixing the problem.
2023-10-12 08:55:58 -07:00
Michael Scire
2ec3e141c7 bpc.mitm/exo: support pmic reboot/shutdown on mariko (thanks @CTCaer) 2023-10-12 08:55:58 -07:00
Michael Scire
71d0274884 erpt: remove deprecated fields, they didn't actually change IDs, just the mapping between id and name table index 2023-10-12 08:55:58 -07:00
Michael Scire
05259b7519 emummc: fix offsets for 17.0.0 2023-10-12 08:55:58 -07:00
Michael Scire
59a24fa646 fusee: support parsing 17.0.0+ INI 2023-10-12 08:55:58 -07:00
Michael Scire
f5b2eab4a8 exo: fix up new titlekey option extents 2023-10-12 08:55:58 -07:00
Michael Scire
e96e1063e2 jpegdec: stop bundling (TODO post-prerelease) 2023-10-12 08:55:58 -07:00
Michael Scire
aa170a72a9 erpt: Add basic (TODO-impl post-prerelease) support for 17.0.0 changes 2023-10-12 08:55:58 -07:00
Michael Scire
9d4cb685a7 fs: update OpenCodeFileSystem abi for 17.0.0 2023-10-12 08:55:58 -07:00
Michael Scire
c95741142e ncm: update for new 17.0.0 apis 2023-10-12 08:55:58 -07:00
Michael Scire
ef9b111bbf emummc: update for 17.0.0 2023-10-12 08:55:58 -07:00
Michael Scire
114b82284d exo/spl: Add new EsCommonKeyType 2023-10-12 08:55:58 -07:00
Michael Scire
c5d7ca5159 fusee/exo: implement the usual changes for new firmware support 2023-10-12 08:55:58 -07:00
Michael Scire
6d0bf70783 kern: fix assert usage in process load 2023-10-12 08:55:58 -07:00
Michael Scire
aba6ca7329 kern: bump supported version to 17.x 2023-10-12 08:55:58 -07:00
Michael Scire
06a840e550 kern: fix operation type enum-value whoops 2023-10-12 08:55:58 -07:00
Michael Scire
11c02e22e0 kern: implement support for applying relr relocations 2023-10-12 08:55:58 -07:00
Michael Scire
f93aea4c06 kern: split Process/Thread exit to separate WorkerTaskManagers 2023-10-12 08:55:58 -07:00
Michael Scire
4ddfb6183c kern: split out GetInstructionDataUserMode in exception handler 2023-10-12 08:55:58 -07:00
Michael Scire
3737151a2f kern: Add special-case for InvalidateProcessDataCache on current process 2023-10-12 08:55:58 -07:00
Michael Scire
2a4d68f916 kern: KPageTable: remove MapFirst operation, replace with MapFirstGroup 2023-10-12 08:55:58 -07:00
Michael Scire
7b523cfc8d kern: note OnFinalize calls in KPageTable::Finalize 2023-10-12 08:55:58 -07:00
Michael Scire
39a95d4023 kern: implement new default application system resource field in KProcess 2023-10-12 08:55:58 -07:00
Michael Scire
2c5002ce50 kern: update KMemoryRegionType values for new ids + SecureUnknown region 2023-10-12 08:55:58 -07:00
Michael Scire
b7384a8667 kern: KSupervisorPageTable now checks wxn instead of setting it 2023-10-12 08:55:58 -07:00
Michael Scire
85b5f20395 kern: KPageTable::Initialize no longer takes unused process id 2023-10-12 08:55:58 -07:00
Michael Scire
ad5bd81d3f kern: implement PermissionLock, update KPageTableBase attribute/alignment checks 2023-10-12 08:55:58 -07:00
Michael Scire
777b6d285c kern: KPageTableBase::CheckMemoryState now invokes a helper 2023-10-12 08:55:58 -07:00
Michael Scire
ae2c25e9c8 kern: update KMemoryState, remove bijection (separate IoRegister/IoMemory) 2023-10-12 08:55:58 -07:00
Michael Scire
3b8f65d502 kern: update initial process load logic to do per-segment mapping/decompression 2023-10-12 08:55:58 -07:00
Michael Scire
cfd2d5b012 kern: clear new pages in init page allocator, not init page table 2023-10-12 08:55:58 -07:00
Michael Scire
c72ba35684 kern: add speculation barriers after eret 2023-10-12 08:55:58 -07:00
Michael Scire
ec96203cb7 kern: remove unnecessary fields from InitArgs (0x80 -> 0x40) 2023-10-12 08:55:58 -07:00
Michael Scire
1491a7b159 kern: on second thought, move vectors back to end of text 2023-10-12 08:55:58 -07:00
Michael Scire
0daef4a6e8 kern/ldr: move crt0 into .rodata 2023-10-12 08:55:58 -07:00
Michael Scire
4ca3c44e5f kern: pass ini1 size from loader to kernel, remove slab memset from init0 2023-10-12 08:55:58 -07:00
Michael Scire
add4b3fdc3 utils: update erpt script 2023-10-12 08:55:58 -07:00
Liam
159f8d384b dmnt.gen2: enable attach to arbitrary program id 2023-10-11 19:50:09 -07:00
Liam
92a8c8eb88 haze: implement android operations 2023-10-11 18:57:49 -07:00
Liam
9e0daff46e haze: split operations by type 2023-10-11 18:57:49 -07:00
Liam
6b72dbd22d haze: refactor constant use for cleaner separation 2023-10-11 18:57:49 -07:00
79 changed files with 4227 additions and 1293 deletions

View File

@@ -84,7 +84,7 @@ dist-no-debug: package3 $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000034
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000036
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000037
#mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000042
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000420
mkdir -p $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000b240
@@ -98,7 +98,7 @@ dist-no-debug: package3 $(CURRENT_DIRECTORY)/$(ATMOSPHERE_OUT_DIR)
cp stratosphere/fatal/$(ATMOSPHERE_OUT_DIR)/fatal.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000034/exefs.nsp
cp stratosphere/creport/$(ATMOSPHERE_OUT_DIR)/creport.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000036/exefs.nsp
cp stratosphere/ro/$(ATMOSPHERE_OUT_DIR)/ro.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000037/exefs.nsp
#cp stratosphere/jpegdec/$(ATMOSPHERE_OUT_DIR)/jpegdec.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c/exefs.nsp
cp stratosphere/jpegdec/$(ATMOSPHERE_OUT_DIR)/jpegdec.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000003c/exefs.nsp
cp stratosphere/pgl/$(ATMOSPHERE_OUT_DIR)/pgl.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000042/exefs.nsp
cp stratosphere/LogManager/$(ATMOSPHERE_OUT_DIR)/LogManager.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/0100000000000420/exefs.nsp
cp stratosphere/htc/$(ATMOSPHERE_OUT_DIR)/htc.nsp $(DIST_DIR)/stratosphere_romfs/atmosphere/contents/010000000000b240/exefs.nsp

View File

@@ -1,4 +1,35 @@
# Changelog
## 1.6.2
+ Support was finished for 17.0.0.
+ `erpt` was updated to support the latest official behavior.
+ `jpegdec` was updated to support the latest official behavior.
+ `pm` was updated to support the latest official behavior.
+ General system stability improvements to enhance the user's experience.
## 1.6.1
+ An improved solution to [the problem that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0](https://gist.github.com/SciresM/2ddb708c812ed585c4d99f54e25205ff) was added.
+ In particular, booting atmosphère will now automatically detect the problem and unbrick any consoles which have fallen into this state.
+ Some improvements were made to `haze`, including:
+ Performance was greatly improved:
+ Support was added for GetObjectPropList, which decreases the amount of requests made by ~8x.
+ Haze now performs rendering on the GPU, freeing up the CPU to respond to requests in a more timely manner.
+ An issue was fixed with how `haze` configures `bMaxPacketSize0` which improves support for USB3.
+ General system stability improvements to enhance the user's experience.
## 1.6.0
+ Basic support was added for 17.0.0.
+ The console should boot and atmosphère should be fully functional. However, not all modules have been fully updated to reflect the latest changes.
+ There shouldn't be anything user visible resulting from this, but it will be addressed in a soon-to-come atmosphère update.
+ `exosphère` was updated to reflect the latest official secure monitor behavior.
+ `mesosphère` was updated to reflect the latest official kernel behavior.
+ `ncm` was updated to reflect the latest official behavior.
+ `erpt` was partially updated to support the latest official behavior.
+ Atmosphere's gdbstub now supports waiting to attach to a specific program id on launch (as opposed to any application).
+ The monitor command for this is `monitor wait <hex program id>`, where program id can optionally have an `0x` prefix.
+ Support was added to `haze` for editing files in-place and performing 64-bit transfers (files larger than 4 GB).
+ `bpc.mitm` was enabled on Mariko units, and now triggers pmic-based shutdowns/reboots (thanks @CTCaer).
+ This should cause the console to no longer wake ~15 seconds after shutdown on Mariko.
+ A number of minor issues were fixed and improvements were made, including:
+ A workaround was added for a change in 17.0.0 that would cause consoles which had previously re-built their SYSTEM partition to brick on update-to-17.0.0.
+ General system stability improvements to enhance the user's experience.
## 1.5.5
+ Support was added for 16.1.0.
+ General system stability improvements to enhance the user's experience.

4
emummc/.gitrepo vendored
View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/m4xw/emummc
branch = develop
commit = 30205111ee375bef96f0f76cb6a3130a2f0fc85c
parent = 81e9154a52a976f85317bddd0131426599d26a62
commit = 9513a5412057b1f1bc44ed8e717c57c726763a88
parent = e4d08ae0c5342cdb0875d164522a63ec9d233052
method = merge
cmdver = 0.4.1

View File

@@ -6,7 +6,7 @@
[subrepo]
remote = https://github.com/Atmosphere-NX/Atmosphere-libs
branch = master
commit = c3dc418a28e390bc57426016aa2c9e7e87d7a584
parent = e488b6ee478f5b3a0380e75e4b468e1e4b1d816f
commit = 80bf6aeeed03df0871329e1ec32da073a62bf102
parent = 9f26419b1a669762d995ebf21ff83b6e1cf56153
method = merge
cmdver = 0.4.1

View File

@@ -66,7 +66,7 @@ endif
ifeq ($(ATMOSPHERE_BOARD),nx-hac-001)
export LDFLAGS = -specs=$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/stratosphere.specs -specs=$(DEVKITPRO)/libnx/switch.specs $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-Map,$(notdir $*.map)
export LDFLAGS = -specs=$(ATMOSPHERE_LIBRARIES_DIR)/libstratosphere/stratosphere.specs -specs=$(DEVKITPRO)/libnx/switch.specs $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-Map,$(notdir $*.map) -Wl,-z,relro,-z,now
else ifeq ($(ATMOSPHERE_OS_NAME),macos)
export LDFLAGS = $(CXXFLAGS) $(CXXWRAPS) $(CXXREQUIRED) -Wl,-map,$(notdir $@.map)
else

View File

@@ -583,7 +583,7 @@ namespace ams::kern {
/* Check memory state. */
const KProcessAddress last_addr = addr + size - 1;
KMemoryBlockManager::const_iterator it = m_memory_block_manager.FindIterator(addr);
R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr))
R_TRY(this->CheckMemoryState(out_state, out_perm, out_attr, out_blocks_needed, it, last_addr, state_mask, state, perm_mask, perm, attr_mask, attr, ignore_attr));
/* If the start address isn't aligned, we need a block. */
if (out_blocks_needed != nullptr && util::AlignDown(GetInteger(addr), PageSize) != it->GetAddress()) {

View File

@@ -0,0 +1,17 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fat/fat_file_system.hpp>

View File

@@ -0,0 +1,55 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
namespace ams::fat {
constexpr inline size_t FatErrorNameMaxLength = 0x10;
struct FatError {
int error;
int extra_error;
int drive_id;
char name[FatErrorNameMaxLength];
u8 reserved[4];
};
static_assert(sizeof(FatError) == 0x20);
static_assert(util::is_pod<FatError>::value);
struct FatReportInfo1 {
u16 open_file_peak_count;
u16 open_directory_peak_count;
};
static_assert(sizeof(FatReportInfo1) == 4);
static_assert(util::is_pod<FatReportInfo1>::value);
struct FatReportInfo2 {
u16 open_unique_file_entry_peak_count;
u16 open_unique_directory_entry_peak_count;
};
static_assert(sizeof(FatReportInfo2) == 4);
static_assert(util::is_pod<FatReportInfo2>::value);
struct FatSafeInfo {
u32 result;
u32 error_number;
u32 safe_error_number;
};
static_assert(sizeof(FatSafeInfo) == 12);
static_assert(util::is_pod<FatSafeInfo>::value);
}

View File

@@ -51,9 +51,11 @@
#include <stratosphere/fs/fs_code.hpp>
#include <stratosphere/fs/fs_content.hpp>
#include <stratosphere/fs/fs_content_storage.hpp>
#include <stratosphere/fs/fs_error_info.hpp>
#include <stratosphere/fs/fs_game_card.hpp>
#include <stratosphere/fs/fs_host.hpp>
#include <stratosphere/fs/fs_image_directory.hpp>
#include <stratosphere/fs/fs_mmc.hpp>
#include <stratosphere/fs/fs_save_data_types.hpp>
#include <stratosphere/fs/fs_save_data_management.hpp>
#include <stratosphere/fs/fs_save_data_transaction.hpp>

View File

@@ -0,0 +1,58 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/fs_common.hpp>
#include <stratosphere/fat/fat_file_system.hpp>
namespace ams::fs {
struct StorageErrorInfo {
u32 num_activation_failures;
u32 num_activation_error_corrections;
u32 num_read_write_failures;
u32 num_read_write_error_corrections;
};
static_assert(sizeof(StorageErrorInfo) == 0x10);
static_assert(util::is_pod<StorageErrorInfo>::value);
struct FileSystemProxyErrorInfo {
u32 rom_fs_remount_for_data_corruption_count;
u32 rom_fs_unrecoverable_data_corruption_by_remount_count;
fat::FatError fat_fs_error;
u32 rom_fs_recovered_by_invalidate_cache_count;
u32 save_data_index_count;
fat::FatReportInfo1 bis_system_fat_report_info_1;
fat::FatReportInfo1 bis_user_fat_report_info_1;
fat::FatReportInfo1 sd_card_fat_report_info_1;
fat::FatReportInfo2 bis_system_fat_report_info_2;
fat::FatReportInfo2 bis_user_fat_report_info_2;
fat::FatReportInfo2 sd_card_fat_report_info_2;
u32 rom_fs_deep_retry_start_count;
u32 rom_fs_unrecoverable_by_game_card_access_failed_count;
fat::FatSafeInfo bis_system_fat_safe_info;
fat::FatSafeInfo bis_user_fat_safe_info;
u8 reserved[0x18];
};
static_assert(sizeof(FileSystemProxyErrorInfo) == 0x80);
static_assert(util::is_pod<FileSystemProxyErrorInfo>::value);
Result GetAndClearMmcErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size);
Result GetAndClearSdCardErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size);
Result GetAndClearFileSystemProxyErrorInfo(FileSystemProxyErrorInfo *out);
}

View File

@@ -19,6 +19,9 @@
namespace ams::fs {
/* ACCURATE_TO_VERSION: Unknown */
constexpr inline size_t GameCardCidSize = 0x10;
constexpr inline size_t GameCardDeviceIdSize = 0x10;
enum class GameCardPartition {
Update = 0,
Normal = 1,
@@ -47,9 +50,44 @@ namespace ams::fs {
Terra = 1,
};
struct GameCardErrorReportInfo {
u16 game_card_crc_error_num;
u16 reserved1;
u16 asic_crc_error_num;
u16 reserved2;
u16 refresh_num;
u16 reserved3;
u16 retry_limit_out_num;
u16 timeout_retry_num;
u16 asic_reinitialize_failure_detail;
u16 insertion_count;
u16 removal_count;
u16 asic_reinitialize_num;
u32 initialize_count;
u16 asic_reinitialize_failure_num;
u16 awaken_failure_num;
u16 reserved4;
u16 refresh_succeeded_count;
u32 last_read_error_page_address;
u32 last_read_error_page_count;
u32 awaken_count;
u32 read_count_from_insert;
u32 read_count_from_awaken;
u8 reserved5[8];
};
static_assert(util::is_pod<GameCardErrorReportInfo>::value);
static_assert(sizeof(GameCardErrorReportInfo) == 0x40);
using GameCardHandle = u32;
Result GetGameCardHandle(GameCardHandle *out);
Result MountGameCardPartition(const char *name, GameCardHandle handle, GameCardPartition partition);
Result GetGameCardCid(void *dst, size_t size);
Result GetGameCardDeviceId(void *dst, size_t size);
Result GetGameCardErrorReportInfo(GameCardErrorReportInfo *out);
bool IsGameCardInserted();
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/fs_common.hpp>
namespace ams::fs {
struct MemoryReportInfo {
u64 pooled_buffer_peak_free_size;
u64 pooled_buffer_retried_count;
u64 pooled_buffer_reduce_allocation_count;
u64 buffer_manager_peak_free_size;
u64 buffer_manager_retried_count;
u64 exp_heap_peak_free_size;
u64 buffer_pool_peak_free_size;
u64 patrol_read_allocate_buffer_success_count;
u64 patrol_read_allocate_buffer_failure_count;
u64 buffer_manager_peak_total_allocatable_size;
u64 buffer_pool_max_allocate_size;
u64 pooled_buffer_failed_ideal_allocation_count_on_async_access;
u8 reserved[0x20];
};
static_assert(sizeof(MemoryReportInfo) == 0x80);
static_assert(util::is_pod<MemoryReportInfo>::value);
Result GetAndClearMemoryReportInfo(MemoryReportInfo *out);
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere/fs/fs_common.hpp>
namespace ams::fs {
constexpr inline size_t MmcCidSize = 0x10;
constexpr inline size_t MmcExtendedCsdSize = 0x200;
constexpr inline int MmcExtendedCsdOffsetReEolInfo = 267;
constexpr inline int MmcExtendedCsdOffsetDeviceLifeTimeEstTypA = 268;
constexpr inline int MmcExtendedCsdOffsetDeviceLifeTimeEstTypB = 269;
enum MmcSpeedMode {
MmcSpeedMode_Identification = 0,
MmcSpeedMode_LegacySpeed = 1,
MmcSpeedMode_HighSpeed = 2,
MmcSpeedMode_Hs200 = 3,
MmcSpeedMode_Hs400 = 4,
MmcSpeedMode_Unknown = 5,
};
enum class MmcPartition {
UserData = 0,
BootPartition1 = 1,
BootPartition2 = 2,
};
Result GetMmcCid(void *dst, size_t size);
inline void ClearMmcCidSerialNumber(u8 *cid) {
/* Clear the serial number from the cid. */
std::memset(cid + 1, 0, 4);
}
Result GetMmcSpeedMode(MmcSpeedMode *out);
Result GetMmcPatrolCount(u32 *out);
Result GetMmcExtendedCsd(void *dst, size_t size);
}

View File

@@ -21,12 +21,38 @@ namespace ams::fs {
/* ACCURATE_TO_VERSION: Unknown */
class IEventNotifier;
constexpr inline size_t SdCardCidSize = 0x10;
enum SdCardSpeedMode {
SdCardSpeedMode_Identification = 0,
SdCardSpeedMode_DefaultSpeed = 1,
SdCardSpeedMode_HighSpeed = 2,
SdCardSpeedMode_Sdr12 = 3,
SdCardSpeedMode_Sdr25 = 4,
SdCardSpeedMode_Sdr50 = 5,
SdCardSpeedMode_Sdr104 = 6,
SdCardSpeedMode_Ddr50 = 7,
SdCardSpeedMode_Unknown = 8,
};
struct EncryptionSeed {
char value[0x10];
};
static_assert(util::is_pod<EncryptionSeed>::value);
static_assert(sizeof(EncryptionSeed) == 0x10);
Result GetSdCardCid(void *dst, size_t size);
inline void ClearSdCardCidSerialNumber(u8 *cid) {
/* Clear the serial number from the cid. */
std::memset(cid + 2, 0, 4);
}
Result GetSdCardUserAreaSize(s64 *out);
Result GetSdCardProtectedAreaSize(s64 *out);
Result GetSdCardSpeedMode(SdCardSpeedMode *out);
Result MountSdCard(const char *name);
Result MountSdCardErrorReportDirectoryForAtmosphere(const char *name);

View File

@@ -121,6 +121,7 @@ namespace ams::fssrv {
Result IsSignedSystemPartitionOnSdCardValid(ams::sf::Out<bool> out);
Result OpenAccessFailureDetectionEventNotifier();
/* ... */
Result GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out);
Result RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count);
Result SetBisRootForHost(u32 id, const fssrv::sf::FspPath &path);
Result SetSaveDataSize(s64 size, s64 journal_size);
@@ -131,6 +132,7 @@ namespace ams::fssrv {
Result OutputAccessLogToSdCard(const ams::sf::InBuffer &buf);
Result RegisterUpdatePartition();
Result OpenRegisteredUpdatePartition(ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out);
Result GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out);
/* ... */
Result GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count);
Result GetFsStackUsage(ams::sf::Out<u32> out, u32 type);

View File

@@ -16,12 +16,27 @@
#pragma once
#include <vapours.hpp>
#include <stratosphere/sf.hpp>
#include <stratosphere/fs/fs_error_info.hpp>
#include <stratosphere/fs/fs_game_card.hpp>
/* TODO */
/* ACCURATE_TO_VERSION: 13.4.0.0 */
#define AMS_FSSRV_I_DEVICE_OPERATOR_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, Result, IsSdCardInserted, (ams::sf::Out<bool> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 200, Result, IsGameCardInserted, (ams::sf::Out<bool> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 202, Result, GetGameCardHandle, (ams::sf::Out<u32> out), (out))
AMS_SF_METHOD_INFO(C, H, 0, Result, IsSdCardInserted, (ams::sf::Out<bool> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 1, Result, GetSdCardSpeedMode, (ams::sf::Out<s64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, GetSdCardCid, (ams::sf::OutBuffer out, s64 size), (out, size)) \
AMS_SF_METHOD_INFO(C, H, 3, Result, GetSdCardUserAreaSize, (ams::sf::Out<s64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 4, Result, GetSdCardProtectedAreaSize, (ams::sf::Out<s64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 5, Result, GetAndClearSdCardErrorInfo, (ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size), (out_sei, out_size, out_buf, size)) \
AMS_SF_METHOD_INFO(C, H, 100, Result, GetMmcCid, (ams::sf::OutBuffer out, s64 size), (out, size)) \
AMS_SF_METHOD_INFO(C, H, 101, Result, GetMmcSpeedMode, (ams::sf::Out<s64> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 112, Result, GetMmcPatrolCount, (ams::sf::Out<u32> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 113, Result, GetAndClearMmcErrorInfo, (ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size), (out_sei, out_size, out_buf, size)) \
AMS_SF_METHOD_INFO(C, H, 114, Result, GetMmcExtendedCsd, (ams::sf::OutBuffer out, s64 size), (out, size)) \
AMS_SF_METHOD_INFO(C, H, 200, Result, IsGameCardInserted, (ams::sf::Out<bool> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 202, Result, GetGameCardHandle, (ams::sf::Out<u32> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 208, Result, GetGameCardIdSet, (ams::sf::OutBuffer out, s64 size), (out, size)) \
AMS_SF_METHOD_INFO(C, H, 217, Result, GetGameCardErrorReportInfo, (ams::sf::Out<fs::GameCardErrorReportInfo> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 218, Result, GetGameCardDeviceId, (ams::sf::OutBuffer out, s64 size), (out, size))
AMS_SF_DEFINE_INTERFACE(ams::fssrv::sf, IDeviceOperator, AMS_FSSRV_I_DEVICE_OPERATOR_INTERFACE_INFO, 0x1484E21C)

View File

@@ -20,6 +20,8 @@
#include <stratosphere/fssrv/sf/fssrv_sf_istorage.hpp>
#include <stratosphere/fssrv/sf/fssrv_sf_i_device_operator.hpp>
#include <stratosphere/fssrv/sf/fssrv_sf_i_event_notifier.hpp>
#include <stratosphere/fs/fs_error_info.hpp>
#include <stratosphere/fs/fs_memory_report_info.hpp>
/* ACCURATE_TO_VERSION: 13.4.0.0 */
#define AMS_FSSRV_I_FILE_SYSTEM_PROXY_INTERFACE_INFO(C, H) \
@@ -125,7 +127,7 @@
/* AMS_SF_METHOD_INFO(C, H, 702, Result, IsAccessFailureDetected, (), (), hos::Version_5_0_0) */ \
/* AMS_SF_METHOD_INFO(C, H, 710, Result, ResolveAccessFailure, (), (), hos::Version_5_0_0) */ \
/* AMS_SF_METHOD_INFO(C, H, 720, Result, AbandonAccessFailure, (), (), hos::Version_5_0_0) */ \
/* AMS_SF_METHOD_INFO(C, H, 800, Result, GetAndClearErrorInfo, (), (), hos::Version_2_0_0) */ \
AMS_SF_METHOD_INFO(C, H, 800, Result, GetAndClearErrorInfo, (ams::sf::Out<fs::FileSystemProxyErrorInfo> out), (out), hos::Version_2_0_0) \
AMS_SF_METHOD_INFO(C, H, 810, Result, RegisterProgramIndexMapInfo, (const ams::sf::InBuffer &buffer, s32 count), (buffer, count), hos::Version_7_0_0) \
AMS_SF_METHOD_INFO(C, H, 1000, Result, SetBisRootForHost, (u32 id, const fssrv::sf::FspPath &path), (id, path), hos::Version_Min, hos::Version_9_2_0) \
AMS_SF_METHOD_INFO(C, H, 1001, Result, SetSaveDataSize, (s64 size, s64 journal_size), (size, journal_size)) \
@@ -136,7 +138,7 @@
AMS_SF_METHOD_INFO(C, H, 1006, Result, OutputAccessLogToSdCard, (const ams::sf::InBuffer &buf), (buf)) \
AMS_SF_METHOD_INFO(C, H, 1007, Result, RegisterUpdatePartition, (), (), hos::Version_4_0_0) \
AMS_SF_METHOD_INFO(C, H, 1008, Result, OpenRegisteredUpdatePartition, (ams::sf::Out<ams::sf::SharedPointer<fssrv::sf::IFileSystem>> out), (out), hos::Version_4_0_0) \
/* AMS_SF_METHOD_INFO(C, H, 1009, Result, GetAndClearMemoryReportInfo, (ams::sf::Out<fs::MemoryReportInfo> out), (out), hos::Version_4_0_0) */ \
AMS_SF_METHOD_INFO(C, H, 1009, Result, GetAndClearMemoryReportInfo, (ams::sf::Out<fs::MemoryReportInfo> out), (out), hos::Version_4_0_0) \
/* AMS_SF_METHOD_INFO(C, H, 1010, Result, SetDataStorageRedirectTarget, (), (), hos::Version_5_1_0, hos::Version_6_2_0) */ \
AMS_SF_METHOD_INFO(C, H, 1011, Result, GetProgramIndexForAccessLog, (ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count), (out_idx, out_count), hos::Version_7_0_0) \
AMS_SF_METHOD_INFO(C, H, 1012, Result, GetFsStackUsage, (ams::sf::Out<u32> out, u32 type), (out, type), hos::Version_9_0_0) \

View File

@@ -18,3 +18,4 @@
#include <stratosphere/gc/impl/gc_types.hpp>
#include <stratosphere/gc/impl/gc_gc_crypto.hpp>
#include <stratosphere/gc/impl/gc_embedded_data_holder.hpp>
#include <stratosphere/gc/gc.hpp>

View File

@@ -0,0 +1,30 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vapours.hpp>
#include <stratosphere/gc/impl/gc_types.hpp>
namespace ams::gc {
struct GameCardIdSet {
gc::impl::CardId1 id1;
gc::impl::CardId2 id2;
gc::impl::CardId3 id3;
};
static_assert(util::is_pod<GameCardIdSet>::value);
static_assert(sizeof(GameCardIdSet) == 0xC);
}

View File

@@ -76,6 +76,11 @@ namespace ams::gc::impl {
static_assert(util::is_pod<CardHeaderEncryptedData>::value);
static_assert(sizeof(CardHeaderEncryptedData) == 0x70);
enum MakerCodeForCardId1 : u8 {
MakerCodeForCardId1_MegaChips = 0xC2,
MakerCodeForCardId1_Lapis = 0xAE,
};
enum MemoryCapacity : u8 {
MemoryCapacity_1GB = 0xFA,
MemoryCapacity_2GB = 0xF8,
@@ -85,6 +90,33 @@ namespace ams::gc::impl {
MemoryCapacity_32GB = 0xE2,
};
enum MemoryType : u8 {
MemoryType_T1RomFast = 0x01,
MemoryType_T2RomFast = 0x02,
MemoryType_T1NandFast = 0x09,
MemoryType_T2NandFast = 0x0A,
MemoryType_T1RomLate = 0x21,
MemoryType_T2RomLate = 0x22,
MemoryType_T1NandLate = 0x29,
MemoryType_T2NandLate = 0x2A,
};
enum CardSecurityNumber : u8 {
CardSecurityNumber_0 = 0x00,
CardSecurityNumber_1 = 0x01,
CardSecurityNumber_2 = 0x02,
CardSecurityNumber_3 = 0x03,
CardSecurityNumber_4 = 0x04,
};
enum CardType : u8 {
CardType_Rom = 0x00,
CardType_Writable_Dev_T1 = 0x01,
CardType_Writable_Prod_T1 = 0x02,
CardType_Writable_Dev_T2 = 0x03,
CardType_Writable_Prod_T2 = 0x04,
};
enum AccessControl1ClockRate : u32 {
AccessControl1ClockRate_25MHz = 0x00A10011,
AccessControl1ClockRate_50MHz = 0x00A10010,
@@ -95,6 +127,23 @@ namespace ams::gc::impl {
SelSec_T2 = 2,
};
struct CardId1 {
MakerCodeForCardId1 maker_code;
MemoryCapacity memory_capacity;
u8 reserved;
MemoryType memory_type;
};
struct CardId2 {
CardSecurityNumber card_security_number;
CardType card_type;
u8 reserved[2];
};
struct CardId3 {
u8 reserved[4];
};
struct CardHeader {
static constexpr u32 Magic = util::FourCC<'H','E','A','D'>::Code;

View File

@@ -239,8 +239,12 @@ namespace ams::ncm {
Result InitializeIntegratedContentMetaDatabaseRoot(IntegratedContentMetaDatabaseRoot *out, const IntegratedContentStorageConfig *config, size_t root_idx, size_t root_count);
Result BuildContentMetaDatabase(StorageId storage_id);
Result BuildContentMetaDatabaseImpl(StorageId storage_id);
Result ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition);
Result ImportContentMetaDatabaseImpl(ContentMetaDatabaseRoot *root, const char *import_mount_name);
private:
/* Helpers for unofficial functionality. */
bool IsNeedRebuildSystemContentMetaDatabase();
public:
/* Actual commands. */
Result CreateContentStorage(StorageId storage_id);

View File

@@ -27,7 +27,7 @@
AMS_SF_METHOD_INFO(C, H, 4, Result, GetApplicationProcessId, (sf::Out<os::ProcessId> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 5, Result, HookToCreateApplicationProcess, (sf::OutCopyHandle out_hook), (out_hook)) \
AMS_SF_METHOD_INFO(C, H, 6, Result, ClearHook, (u32 which), (which), hos::Version_6_0_0) \
AMS_SF_METHOD_INFO(C, H, 7, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \
AMS_SF_METHOD_INFO(C, H, 7, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \
AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereGetProcessInfo, (sf::OutCopyHandle out_process_handle, sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id), (out_process_handle, out_loc, out_status, process_id)) \
AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereGetCurrentLimitInfo, (sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource), (out_cur_val, out_lim_val, group, resource))

View File

@@ -21,8 +21,8 @@
#define AMS_PM_I_INFORMATION_INTERFACE_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, Result, GetProgramId, (sf::Out<ncm::ProgramId> out, os::ProcessId process_id), (out, process_id)) \
AMS_SF_METHOD_INFO(C, H, 1, Result, GetAppletCurrentResourceLimitValues, (sf::Out<pm::ResourceLimitValues> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, GetAppletPeakResourceLimitValues, (sf::Out<pm::ResourceLimitValues> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 1, Result, GetAppletResourceLimitCurrentValue, (sf::Out<pm::ResourceLimitValue> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 2, Result, GetAppletResourceLimitPeakValue, (sf::Out<pm::ResourceLimitValue> out), (out)) \
AMS_SF_METHOD_INFO(C, H, 65000, Result, AtmosphereGetProcessId, (sf::Out<os::ProcessId> out, ncm::ProgramId program_id), (out, program_id)) \
AMS_SF_METHOD_INFO(C, H, 65001, Result, AtmosphereHasLaunchedBootProgram, (sf::Out<bool> out, ncm::ProgramId program_id), (out, program_id)) \
AMS_SF_METHOD_INFO(C, H, 65002, Result, AtmosphereGetProcessInfo, (sf::Out<ncm::ProgramLocation> out_loc, sf::Out<cfg::OverrideStatus> out_status, os::ProcessId process_id), (out_loc, out_status, process_id))

View File

@@ -28,6 +28,7 @@ namespace ams::pm::dmnt {
Result GetProcessId(os::ProcessId *out_process_id, const ncm::ProgramId program_id);
Result GetApplicationProcessId(os::ProcessId *out_process_id);
Result HookToCreateApplicationProcess(os::NativeHandle *out_handle);
Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id);
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id);
#if defined(ATMOSPHERE_OS_HORIZON)

View File

@@ -29,8 +29,8 @@ namespace ams::pm::info {
Result GetProcessId(os::ProcessId *out_process_id, ncm::ProgramId program_id);
Result HasLaunchedBootProgram(bool *out, ncm::ProgramId program_id);
Result GetAppletCurrentResourceLimitValues(pm::ResourceLimitValues *out);
Result GetAppletPeakResourceLimitValues(pm::ResourceLimitValues *out);
Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out);
Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out);
Result GetProcessInfo(ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id);

View File

@@ -52,7 +52,7 @@ namespace ams::pm {
LaunchFlagsDeprecated_SignalOnStart = (1 << 5),
};
struct ResourceLimitValues {
struct ResourceLimitValue {
u64 physical_memory;
u32 thread_count;
u32 event_count;

View File

@@ -16,11 +16,16 @@
#include <stratosphere.hpp>
#include "decodersrv_decoder_server_object.hpp"
#include "../jpeg/decodersrv_software_jpeg_decoder.hpp"
#include "../jpeg/decodersrv_software_jpeg_shrinker.hpp"
namespace ams::capsrv::server {
namespace {
constexpr const int JpegShrinkQualities[] = {
98, 95, 90, 80, 70, 60, 50, 40, 30, 20, 10, 0
};
Result DecodeJpegImpl(void *dst, size_t dst_size, const void *src_jpeg, size_t src_jpeg_size, u32 width, u32 height, const ScreenShotDecodeOption &option, void *work, size_t work_size) {
/* Clear the work memory. */
std::memset(work, 0, work_size);
@@ -67,6 +72,61 @@ namespace ams::capsrv::server {
R_SUCCEED();
}
Result ShrinkJpegImpl(u64 *out_size, void *dst, size_t dst_size, const void *src_jpeg, size_t src_jpeg_size, u32 width, u32 height, const ScreenShotDecodeOption &option, void *work, size_t work_size) {
/* Validate parameters. */
R_UNLESS(util::IsAligned(width, 0x10), capsrv::ResultAlbumOutOfRange());
R_UNLESS(util::IsAligned(height, 0x4), capsrv::ResultAlbumOutOfRange());
R_UNLESS(dst != nullptr, capsrv::ResultInternalJpegOutBufferShortage());
R_UNLESS(dst_size != 0, capsrv::ResultAlbumReadBufferShortage());
R_UNLESS(src_jpeg != nullptr, capsrv::ResultAlbumInvalidFileData());
R_UNLESS(src_jpeg_size != 0, capsrv::ResultAlbumInvalidFileData());
/* Create the input. */
const jpeg::SoftwareJpegShrinkerInput shrink_input = {
.jpeg = src_jpeg,
.jpeg_size = src_jpeg_size,
.width = width,
.height = height,
.fancy_upsampling = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableFancyUpsampling),
.block_smoothing = option.HasJpegDecoderFlag(ScreenShotDecoderFlag_EnableBlockSmoothing),
};
/* Create the output. */
u64 shrunk_size = 0;
s32 shrunk_width = 0, shrunk_height = 0;
jpeg::SoftwareJpegShrinkerOutput shrink_output = {
.out_size = std::addressof(shrunk_size),
.out_width = std::addressof(shrunk_width),
.out_height = std::addressof(shrunk_height),
.dst = dst,
.dst_size = dst_size,
};
/* Try to shrink the jpeg at various quality levels. */
for (auto quality : JpegShrinkQualities) {
/* Shrink at the current quality. */
R_TRY_CATCH(jpeg::SoftwareJpegShrinker::ShrinkRgba8(shrink_output, shrink_input, quality, work, work_size)) {
/* If the output buffer isn't large enough to fit the output, we should try at a lower quality. */
R_CATCH(capsrv::ResultInternalJpegOutBufferShortage) {
continue;
}
/* Nintendo doesn't catch this result, but our lack of work buffer use makes me think this may be necessary. */
R_CATCH(capsrv::ResultInternalJpegWorkMemoryShortage) {
continue;
}
} R_END_TRY_CATCH;
/* Write the output size. */
*out_size = shrunk_size;
R_SUCCEED();
}
/* Nintendo aborts if no quality succeeds. */
AMS_ABORT("ShrinkJpeg should succeed before this point\n");
}
}
Result DecoderControlService::DecodeJpeg(const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const ScreenShotDecodeOption &option) {
@@ -78,4 +138,13 @@ namespace ams::capsrv::server {
R_RETURN(DecodeJpegImpl(out.GetPointer(), out.GetSize(), in.GetPointer(), in.GetSize(), width, height, option, work, work_size));
}
Result DecoderControlService::ShrinkJpeg(ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option) {
/* Get the work buffer. */
void *work = g_work_memory.jpeg_decoder_memory;
size_t work_size = sizeof(g_work_memory.jpeg_decoder_memory);
/* Call the shrink implementation. */
R_RETURN(ShrinkJpegImpl(out_size.GetPointer(), out.GetPointer(), out.GetSize(), in.GetPointer(), in.GetSize(), width, height, option, work, work_size));
}
}

View File

@@ -17,7 +17,8 @@
#include <stratosphere.hpp>
#define AMS_CAPSRV_DECODER_CONTROL_SERVICE_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 3001, Result, DecodeJpeg, (const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option), (out, in, width, height, option))
AMS_SF_METHOD_INFO(C, H, 3001, Result, DecodeJpeg, (const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option), (out, in, width, height, option)) \
AMS_SF_METHOD_INFO(C, H, 4001, Result, ShrinkJpeg, (ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option), (out_size, out, in, width, height, option))
AMS_SF_DEFINE_INTERFACE(ams::capsrv::sf, IDecoderControlService, AMS_CAPSRV_DECODER_CONTROL_SERVICE_INTERFACE_INFO, 0xD168E90B)
@@ -26,6 +27,7 @@ namespace ams::capsrv::server {
class DecoderControlService final {
public:
Result DecodeJpeg(const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const ScreenShotDecodeOption &option);
Result ShrinkJpeg(ams::sf::Out<u64> out_size, const ams::sf::OutNonSecureBuffer &out, const ams::sf::InBuffer &in, u32 width, u32 height, const capsrv::ScreenShotDecodeOption &option);
};
static_assert(capsrv::sf::IsIDecoderControlService<DecoderControlService>);

View File

@@ -0,0 +1,236 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "decodersrv_software_jpeg_shrinker.hpp"
#include "capsrv_server_jpeg_library_types.hpp"
#include "capsrv_server_jpeg_error_handler.hpp"
namespace ams::capsrv::server::jpeg {
#define CAPSRV_ABORT_UNLESS(expr) do { \
const bool __capsrv_assert_res = (expr); \
AMS_ASSERT(__capsrv_assert_res); \
AMS_ABORT_UNLESS(__capsrv_assert_res); \
} while (0)
#define CAPSRV_ASSERT(expr) do { \
const bool __capsrv_assert_res = (expr); \
AMS_ASSERT(__capsrv_assert_res); \
R_UNLESS(__capsrv_assert_res, capsrv::ResultAlbumError()); \
} while (0)
namespace {
constexpr s32 ImageSizeHorizonalUnit = 0x10;
constexpr s32 ImageSizeVerticalUnitDecode = 0x4;
constexpr s32 ImageSizeVerticalUnitEncode = 0x8;
constexpr s32 RgbColorComponentCount = 3;
constexpr s32 RgbaColorComponentCount = 4;
Result GetRgbBufferSize(size_t *out_size, size_t *out_stride, s32 width, size_t work_size) {
/* Calculate the space we need and verify we have enough. */
const size_t rgb_width = util::AlignUp(static_cast<size_t>(width), ImageSizeHorizonalUnit);
const size_t rgb_stride = rgb_width * RgbColorComponentCount;
const size_t rgb_size = rgb_stride * ImageSizeVerticalUnitEncode;
R_UNLESS(work_size >= rgb_size, capsrv::ResultInternalJpegWorkMemoryShortage());
/* Return the output to the caller. */
*out_size = rgb_size;
*out_stride = rgb_stride;
R_SUCCEED();
}
void SetupEncodingParameter(JpegLibraryType::jpeg_compress_struct *cinfo, const SoftwareJpegShrinkerInput &input, int quality) {
/* Set parameters. */
cinfo->image_width = static_cast<JpegLibraryType::JDIMENSION>(input.width / 2);
cinfo->image_height = static_cast<JpegLibraryType::JDIMENSION>(input.height / 2);
cinfo->input_components = RgbColorComponentCount;
cinfo->in_color_space = JpegLibraryType::J_COLOR_SPACE::JCS_RGB;
/* Set defaults/color space. */
jpeg_set_defaults(cinfo);
jpeg_set_colorspace(cinfo, JpegLibraryType::J_COLOR_SPACE::JCS_YCbCr);
/* Configure sampling. */
/* libjpeg-turbo doesn't actually have this field, as of now. */
/* cinfo->do_fancy_downsampling = false; */
cinfo->comp_info[0].h_samp_factor = 2;
cinfo->comp_info[0].v_samp_factor = 1;
cinfo->comp_info[1].h_samp_factor = 1;
cinfo->comp_info[1].v_samp_factor = 1;
cinfo->comp_info[2].h_samp_factor = 1;
cinfo->comp_info[2].v_samp_factor = 1;
/* Set the quality. */
jpeg_set_quality(cinfo, quality, true);
/* Configure remaining parameters. */
cinfo->dct_method = JpegLibraryType::J_DCT_METHOD::JDCT_ISLOW;
cinfo->optimize_coding = false;
cinfo->write_JFIF_header = true;
}
}
Result SoftwareJpegShrinker::ShrinkRgba8(SoftwareJpegShrinkerOutput &output, const SoftwareJpegShrinkerInput &input, int quality, void *work, size_t work_size) {
CAPSRV_ABORT_UNLESS(util::IsAligned(input.width, ImageSizeHorizonalUnit));
CAPSRV_ABORT_UNLESS(util::IsAligned(input.height, ImageSizeVerticalUnitDecode));
const u32 shrunk_width = input.width / 2;
const u32 shrunk_height = input.height / 2;
CAPSRV_ABORT_UNLESS(util::IsAligned(shrunk_width, ImageSizeHorizonalUnit));
CAPSRV_ABORT_UNLESS(output.dst != nullptr);
CAPSRV_ABORT_UNLESS(output.out_width != nullptr);
CAPSRV_ABORT_UNLESS(output.out_height != nullptr);
/* Determine work buffer extents. */
char *work_start = static_cast<char *>(work);
char *work_end = work_start + work_size;
/* Determine the buffer extents for our linebuffers. */
u8 *rgb_buffer = static_cast<u8 *>(static_cast<void *>(work_start));
size_t rgb_buffer_size;
size_t rgb_buffer_stride;
R_TRY(GetRgbBufferSize(std::addressof(rgb_buffer_size), std::addressof(rgb_buffer_stride), input.width, work_size));
/* The start of the workbuffer is reserved for linebuffer space. */
work_start += rgb_buffer_size;
/* Create our compression structures. */
JpegLibraryType::jpeg_decompress_struct dcinfo = {};
JpegLibraryType::jpeg_compress_struct ecinfo = {};
/* Here nintendo creates a work buffer structure containing work_start + work_size. */
/* This seems to be a custom patch for/to libjpeg-turbo. */
/* It would be desirable for us to mimic this, because it gives Nintendo strong */
/* fixed memory usage guarantees. */
/* TODO: Determine if it is feasible for us to recreate this ourselves, */
/* Either by adding support to the devkitPro libjpeg-turbo portlib or otherwise. */
AMS_UNUSED(work_end);
/* Create our error managers. */
JpegErrorHandler jerr_dc = { .result = ResultSuccess(), };
JpegErrorHandler jerr_ec = { .result = ResultSuccess(), };
jerr_dc.error_exit = JpegErrorHandler::HandleError,
jerr_ec.error_exit = JpegErrorHandler::HandleError,
/* Link our error managers to our compression structures. */
dcinfo.err = jpeg_std_error(std::addressof(jerr_dc));
ecinfo.err = jpeg_std_error(std::addressof(jerr_ec));
/* Use setjmp, so that on error our handler will longjmp to return an error result. */
if (setjmp(jerr_ec.jmp_buf) == 0) {
if (setjmp(jerr_dc.jmp_buf) == 0) {
/* Create our decompressor. */
jpeg_create_decompress(std::addressof(dcinfo));
ON_SCOPE_EXIT { jpeg_destroy_decompress(std::addressof(dcinfo)); };
/* Setup our memory reader, ensure the header is correct. */
jpeg_mem_src(std::addressof(dcinfo), const_cast<unsigned char *>(static_cast<const unsigned char *>(input.jpeg)), input.jpeg_size);
R_UNLESS(jpeg_read_header(std::addressof(dcinfo), true) == JPEG_HEADER_OK, capsrv::ResultAlbumInvalidFileData());
/* Ensure width and height are correct. */
R_UNLESS(dcinfo.image_width == input.width, capsrv::ResultAlbumInvalidFileData());
R_UNLESS(dcinfo.image_height == input.height, capsrv::ResultAlbumInvalidFileData());
/* Set output parameters. */
dcinfo.out_color_space = JpegLibraryType::J_COLOR_SPACE::JCS_RGB;
dcinfo.dct_method = JpegLibraryType::J_DCT_METHOD::JDCT_ISLOW;
dcinfo.do_fancy_upsampling = input.fancy_upsampling;
dcinfo.do_block_smoothing = input.block_smoothing;
dcinfo.scale_num = 1;
dcinfo.scale_denom = 2;
/* Start decompression. */
R_UNLESS(jpeg_start_decompress(std::addressof(dcinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData());
/* Check the parameters. */
CAPSRV_ASSERT(dcinfo.output_width == shrunk_width);
CAPSRV_ASSERT(dcinfo.output_height == shrunk_height);
CAPSRV_ASSERT(dcinfo.out_color_components == RgbColorComponentCount);
CAPSRV_ASSERT(dcinfo.output_components == RgbColorComponentCount);
/* Create our compressor. */
jpeg_create_compress(std::addressof(ecinfo));
ON_SCOPE_EXIT { jpeg_destroy_compress(std::addressof(ecinfo)); };
/* Setup our memory writer. */
unsigned long out_size = static_cast<unsigned long>(output.dst_size);
jpeg_mem_dest(std::addressof(ecinfo), reinterpret_cast<unsigned char **>(std::addressof(output.dst)), std::addressof(out_size));
/* Setup the encoding parameters. */
SetupEncodingParameter(std::addressof(ecinfo), input, quality);
/* Start compression. */
jpeg_start_compress(std::addressof(ecinfo), true);
/* Parse the scanlines. */
{
/* Create our linebuffer structure. */
JpegLibraryType::JSAMPROW linebuffers[ImageSizeVerticalUnitEncode] = {};
for (int i = 0; i < ImageSizeVerticalUnitEncode; i++) {
linebuffers[i] = rgb_buffer + rgb_buffer_stride * i;
}
/* While we still have scanlines, parse! */
while (dcinfo.output_scanline < shrunk_height) {
/* Determine remaining scanlines. */
const auto remaining_scanlines = shrunk_height - dcinfo.output_scanline;
const auto cur_max_scanlines = std::min<s32>(remaining_scanlines, ImageSizeVerticalUnitEncode);
/* If we have scanlines to decode, try to do so. */
auto writable_scanlines = 0;
while (writable_scanlines < cur_max_scanlines) {
const auto decoded = jpeg_read_scanlines(std::addressof(dcinfo), linebuffers + writable_scanlines, ImageSizeVerticalUnitDecode);
CAPSRV_ASSERT(decoded <= ImageSizeVerticalUnitDecode / 2);
writable_scanlines += decoded;
}
/* If we have scanlines to write, try to do so. */
jpeg_write_scanlines(std::addressof(ecinfo), linebuffers, writable_scanlines);
}
}
/* Finish the decompression. */
R_UNLESS(jpeg_finish_decompress(std::addressof(dcinfo)) == TRUE, capsrv::ResultAlbumInvalidFileData());
/* Finish the compression. */
jpeg_finish_compress(std::addressof(ecinfo));
/* Set the output size. */
*output.out_size = out_size;
} else {
/* Some unknown error was caught by our handler. */
R_THROW(capsrv::ResultAlbumInvalidFileData());
}
} else {
/* Return the encoding result. */
R_THROW(jerr_ec.result);
}
/* Write the size we decoded to output. */
*output.out_width = static_cast<s32>(dcinfo.output_width);
*output.out_width = static_cast<s32>(dcinfo.output_height);
R_SUCCEED();
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::capsrv::server::jpeg {
struct SoftwareJpegShrinkerInput {
const void *jpeg;
size_t jpeg_size;
u32 width;
u32 height;
bool fancy_upsampling;
bool block_smoothing;
};
struct SoftwareJpegShrinkerOutput {
u64 *out_size;
s32 *out_width;
s32 *out_height;
void *dst;
size_t dst_size;
};
class SoftwareJpegShrinker {
public:
static Result ShrinkRgba8(SoftwareJpegShrinkerOutput &output, const SoftwareJpegShrinkerInput &input, int quality, void *work, size_t work_size);
};
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
namespace ams::erpt::srv {
Result SubmitFsInfo();
}

View File

@@ -0,0 +1,499 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "erpt_srv_fs_info.hpp"
#include "erpt_srv_context_record.hpp"
#include "erpt_srv_context.hpp"
namespace ams::erpt::srv {
namespace {
Result SubmitMmcDetailInfo() {
/* Submit the mmc cid. */
{
u8 mmc_cid[fs::MmcCidSize] = {};
if (R_SUCCEEDED(fs::GetMmcCid(mmc_cid, sizeof(mmc_cid)))) {
/* Clear the serial number from the cid. */
fs::ClearMmcCidSerialNumber(mmc_cid);
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDTypeInfo, sizeof(mmc_cid));
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add the cid. */
R_ABORT_UNLESS(record->Add(FieldId_NANDType, mmc_cid, sizeof(mmc_cid)));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
/* Submit the mmc speed mode. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDSpeedModeInfo, 0x20);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Get the speed mode. */
fs::MmcSpeedMode speed_mode{};
const auto res = fs::GetMmcSpeedMode(std::addressof(speed_mode));
if (R_SUCCEEDED(res)) {
const char *speed_mode_name = "None";
switch (speed_mode) {
case fs::MmcSpeedMode_Identification:
speed_mode_name = "Identification";
break;
case fs::MmcSpeedMode_LegacySpeed:
speed_mode_name = "LegacySpeed";
break;
case fs::MmcSpeedMode_HighSpeed:
speed_mode_name = "HighSpeed";
break;
case fs::MmcSpeedMode_Hs200:
speed_mode_name = "Hs200";
break;
case fs::MmcSpeedMode_Hs400:
speed_mode_name = "Hs400";
break;
case fs::MmcSpeedMode_Unknown:
speed_mode_name = "Unknown";
break;
default:
speed_mode_name = "UnDefined";
break;
}
R_ABORT_UNLESS(record->Add(FieldId_NANDSpeedMode, speed_mode_name, std::strlen(speed_mode_name)));
} else {
/* Getting speed mode failed, so add the result. */
char res_str[0x20];
util::SNPrintf(res_str, sizeof(res_str), "0x%08X", res.GetValue());
R_ABORT_UNLESS(record->Add(FieldId_NANDSpeedMode, res_str, std::strlen(res_str)));
}
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
/* Submit the mmc extended csd. */
{
u8 mmc_csd[fs::MmcExtendedCsdSize] = {};
if (R_SUCCEEDED(fs::GetMmcExtendedCsd(mmc_csd, sizeof(mmc_csd)))) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDExtendedCsd, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields from the csd. */
R_ABORT_UNLESS(record->Add(FieldId_NANDPreEolInfo, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetReEolInfo])));
R_ABORT_UNLESS(record->Add(FieldId_NANDDeviceLifeTimeEstTypA, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetDeviceLifeTimeEstTypA])));
R_ABORT_UNLESS(record->Add(FieldId_NANDDeviceLifeTimeEstTypB, static_cast<u32>(mmc_csd[fs::MmcExtendedCsdOffsetDeviceLifeTimeEstTypB])));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
/* Submit the mmc patrol count. */
{
u32 count = 0;
if (R_SUCCEEDED(fs::GetMmcPatrolCount(std::addressof(count)))) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDPatrolInfo, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add the count. */
R_ABORT_UNLESS(record->Add(FieldId_NANDPatrolCount, count));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
R_SUCCEED();
}
Result SubmitMmcErrorInfo() {
/* Get the mmc error info. */
fs::StorageErrorInfo sei = {};
char log_buffer[erpt::ArrayBufferSizeDefault] = {};
size_t log_size = 0;
if (R_SUCCEEDED(fs::GetAndClearMmcErrorInfo(std::addressof(sei), std::addressof(log_size), log_buffer, sizeof(log_buffer)))) {
/* Submit the error info. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDErrorInfo, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_NANDNumActivationFailures, sei.num_activation_failures));
R_ABORT_UNLESS(record->Add(FieldId_NANDNumActivationErrorCorrections, sei.num_activation_error_corrections));
R_ABORT_UNLESS(record->Add(FieldId_NANDNumReadWriteFailures, sei.num_read_write_failures));
R_ABORT_UNLESS(record->Add(FieldId_NANDNumReadWriteErrorCorrections, sei.num_read_write_error_corrections));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
/* If we have a log, submit it. */
if (log_size > 0) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_NANDDriverLog, log_size);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_NANDErrorLog, log_buffer, log_size));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
R_SUCCEED();
}
Result SubmitSdCardDetailInfo() {
/* Submit the sd card cid. */
{
u8 sd_cid[fs::SdCardCidSize] = {};
if (R_SUCCEEDED(fs::GetSdCardCid(sd_cid, sizeof(sd_cid)))) {
/* Clear the serial number from the cid. */
fs::ClearSdCardCidSerialNumber(sd_cid);
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_MicroSDTypeInfo, sizeof(sd_cid));
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add the cid. */
R_ABORT_UNLESS(record->Add(FieldId_MicroSDType, sd_cid, sizeof(sd_cid)));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
/* Submit the sd card speed mode. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_MicroSDSpeedModeInfo, 0x20);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Get the speed mode. */
fs::SdCardSpeedMode speed_mode{};
const auto res = fs::GetSdCardSpeedMode(std::addressof(speed_mode));
if (R_SUCCEEDED(res)) {
const char *speed_mode_name = "None";
switch (speed_mode) {
case fs::SdCardSpeedMode_Identification:
speed_mode_name = "Identification";
break;
case fs::SdCardSpeedMode_DefaultSpeed:
speed_mode_name = "DefaultSpeed";
break;
case fs::SdCardSpeedMode_HighSpeed:
speed_mode_name = "HighSpeed";
break;
case fs::SdCardSpeedMode_Sdr12:
speed_mode_name = "Sdr12";
break;
case fs::SdCardSpeedMode_Sdr25:
speed_mode_name = "Sdr25";
break;
case fs::SdCardSpeedMode_Sdr50:
speed_mode_name = "Sdr50";
break;
case fs::SdCardSpeedMode_Sdr104:
speed_mode_name = "Sdr104";
break;
case fs::SdCardSpeedMode_Ddr50:
speed_mode_name = "Ddr50";
break;
case fs::SdCardSpeedMode_Unknown:
speed_mode_name = "Unknown";
break;
default:
speed_mode_name = "UnDefined";
break;
}
R_ABORT_UNLESS(record->Add(FieldId_MicroSDSpeedMode, speed_mode_name, std::strlen(speed_mode_name)));
} else {
/* Getting speed mode failed, so add the result. */
char res_str[0x20];
util::SNPrintf(res_str, sizeof(res_str), "0x%08X", res.GetValue());
R_ABORT_UNLESS(record->Add(FieldId_MicroSDSpeedMode, res_str, std::strlen(res_str)));
}
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
/* Submit the area sizes. */
{
s64 user_area_size = 0;
s64 prot_area_size = 0;
const Result res_user = fs::GetSdCardUserAreaSize(std::addressof(user_area_size));
const Result res_prot = fs::GetSdCardProtectedAreaSize(std::addressof(prot_area_size));
if (R_SUCCEEDED(res_user) || R_SUCCEEDED(res_prot)) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_SdCardSizeSpec, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add sizes. */
if (R_SUCCEEDED(res_user)) {
R_ABORT_UNLESS(record->Add(FieldId_SdCardUserAreaSize, user_area_size));
}
if (R_SUCCEEDED(res_prot)) {
R_ABORT_UNLESS(record->Add(FieldId_SdCardProtectedAreaSize, prot_area_size));
}
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
R_SUCCEED();
}
Result SubmitSdCardErrorInfo() {
/* Get the sd card error info. */
fs::StorageErrorInfo sei = {};
char log_buffer[erpt::ArrayBufferSizeDefault] = {};
size_t log_size = 0;
if (R_SUCCEEDED(fs::GetAndClearSdCardErrorInfo(std::addressof(sei), std::addressof(log_size), log_buffer, sizeof(log_buffer)))) {
/* Submit the error info. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_SdCardErrorInfo, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_SdCardNumActivationFailures, sei.num_activation_failures));
R_ABORT_UNLESS(record->Add(FieldId_SdCardNumActivationErrorCorrections, sei.num_activation_error_corrections));
R_ABORT_UNLESS(record->Add(FieldId_SdCardNumReadWriteFailures, sei.num_read_write_failures));
R_ABORT_UNLESS(record->Add(FieldId_SdCardNumReadWriteErrorCorrections, sei.num_read_write_error_corrections));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
/* If we have a log, submit it. */
if (log_size > 0) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_SdCardDriverLog, log_size);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_SdCardErrorLog, log_buffer, log_size));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
R_SUCCEED();
}
Result SubmitGameCardDetailInfo() {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_GameCardCIDInfo, fs::GameCardCidSize + fs::GameCardDeviceIdSize);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add the game card cid. */
{
u8 gc_cid[fs::GameCardCidSize] = {};
if (fs::IsGameCardInserted() && R_SUCCEEDED(fs::GetGameCardCid(gc_cid, sizeof(gc_cid)))) {
/* Add the cid. */
R_ABORT_UNLESS(record->Add(FieldId_GameCardCID, gc_cid, sizeof(gc_cid)));
}
}
/* Add the game card device id. */
{
u8 gc_device_id[fs::GameCardDeviceIdSize] = {};
if (fs::IsGameCardInserted() && R_SUCCEEDED(fs::GetGameCardDeviceId(gc_device_id, sizeof(gc_device_id)))) {
/* Add the cid. */
R_ABORT_UNLESS(record->Add(FieldId_GameCardDeviceId, gc_device_id, sizeof(gc_device_id)));
}
}
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
R_SUCCEED();
}
Result SubmitGameCardErrorInfo() {
/* Get the game card error info. */
fs::GameCardErrorReportInfo ei = {};
if (R_SUCCEEDED(fs::GetGameCardErrorReportInfo(std::addressof(ei)))) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_GameCardErrorInfo, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_GameCardCrcErrorCount, static_cast<u32>(ei.game_card_crc_error_num)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicCrcErrorCount, static_cast<u32>(ei.asic_crc_error_num)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardRefreshCount, static_cast<u32>(ei.refresh_num)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardReadRetryCount, static_cast<u32>(ei.retry_limit_out_num)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardTimeoutRetryErrorCount, static_cast<u32>(ei.timeout_retry_num)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardInsertionCount, static_cast<u32>(ei.insertion_count)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardRemovalCount, static_cast<u32>(ei.removal_count)));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicInitializeCount, ei.initialize_count));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeCount, ei.asic_reinitialize_num));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeFailureCount, ei.asic_reinitialize_failure_num));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAsicReinitializeFailureDetail, ei.asic_reinitialize_failure_detail));
R_ABORT_UNLESS(record->Add(FieldId_GameCardRefreshSuccessCount, ei.refresh_succeeded_count));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAwakenCount, ei.awaken_count));
R_ABORT_UNLESS(record->Add(FieldId_GameCardAwakenFailureCount, ei.awaken_failure_num));
R_ABORT_UNLESS(record->Add(FieldId_GameCardReadCountFromInsert, ei.read_count_from_insert));
R_ABORT_UNLESS(record->Add(FieldId_GameCardReadCountFromAwaken, ei.read_count_from_awaken));
R_ABORT_UNLESS(record->Add(FieldId_GameCardLastReadErrorPageAddress, ei.last_read_error_page_address));
R_ABORT_UNLESS(record->Add(FieldId_GameCardLastReadErrorPageCount, ei.last_read_error_page_count));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
R_SUCCEED();
}
Result SubmitFileSystemErrorInfo() {
/* Get the fsp error info. */
fs::FileSystemProxyErrorInfo ei = {};
if (R_SUCCEEDED(fs::GetAndClearFileSystemProxyErrorInfo(std::addressof(ei)))) {
/* Submit FsProxyErrorInfo. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_FsProxyErrorInfo, fat::FatErrorNameMaxLength);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_FsRemountForDataCorruptCount, ei.rom_fs_remount_for_data_corruption_count));
R_ABORT_UNLESS(record->Add(FieldId_FsRemountForDataCorruptRetryOutCount, ei.rom_fs_unrecoverable_data_corruption_by_remount_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsError, ei.fat_fs_error.error));
R_ABORT_UNLESS(record->Add(FieldId_FatFsExtraError, ei.fat_fs_error.extra_error));
R_ABORT_UNLESS(record->Add(FieldId_FatFsErrorDrive, ei.fat_fs_error.drive_id));
R_ABORT_UNLESS(record->Add(FieldId_FatFsErrorName, ei.fat_fs_error.name, fat::FatErrorNameMaxLength));
R_ABORT_UNLESS(record->Add(FieldId_FsRecoveredByInvalidateCacheCount, ei.rom_fs_recovered_by_invalidate_cache_count));
R_ABORT_UNLESS(record->Add(FieldId_FsSaveDataIndexCount, ei.save_data_index_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFilePeakOpenCount, ei.bis_system_fat_report_info_1.open_file_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemDirectoryPeakOpenCount, ei.bis_system_fat_report_info_1.open_directory_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFilePeakOpenCount, ei.bis_user_fat_report_info_1.open_file_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserDirectoryPeakOpenCount, ei.bis_user_fat_report_info_1.open_directory_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardFilePeakOpenCount, ei.sd_card_fat_report_info_1.open_file_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardDirectoryPeakOpenCount, ei.sd_card_fat_report_info_1.open_directory_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemUniqueFileEntryPeakOpenCount, ei.bis_system_fat_report_info_2.open_unique_file_entry_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemUniqueDirectoryEntryPeakOpenCount, ei.bis_system_fat_report_info_2.open_unique_directory_entry_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserUniqueFileEntryPeakOpenCount, ei.bis_user_fat_report_info_2.open_unique_file_entry_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserUniqueDirectoryEntryPeakOpenCount, ei.bis_user_fat_report_info_2.open_unique_directory_entry_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardUniqueFileEntryPeakOpenCount, ei.sd_card_fat_report_info_2.open_unique_file_entry_peak_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsSdCardUniqueDirectoryEntryPeakOpenCount, ei.sd_card_fat_report_info_2.open_unique_directory_entry_peak_count));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
/* Submit FsProxyErrorInfo2. */
{
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_FsProxyErrorInfo2, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_FsDeepRetryStartCount, ei.rom_fs_deep_retry_start_count));
R_ABORT_UNLESS(record->Add(FieldId_FsUnrecoverableByGameCardAccessFailedCount, ei.rom_fs_unrecoverable_by_game_card_access_failed_count));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatSafeControlResult, static_cast<u8>(ei.bis_system_fat_safe_info.result)));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatErrorNumber, ei.bis_system_fat_safe_info.error_number));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisSystemFatSafeErrorNumber, ei.bis_system_fat_safe_info.safe_error_number));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatSafeControlResult, static_cast<u8>(ei.bis_user_fat_safe_info.result)));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatErrorNumber, ei.bis_user_fat_safe_info.error_number));
R_ABORT_UNLESS(record->Add(FieldId_FatFsBisUserFatSafeErrorNumber, ei.bis_user_fat_safe_info.safe_error_number));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
}
R_SUCCEED();
}
Result SubmitMemoryReportInfo() {
/* Get the memory report info. */
fs::MemoryReportInfo mri = {};
if (R_SUCCEEDED(fs::GetAndClearMemoryReportInfo(std::addressof(mri)))) {
/* Create a record. */
auto record = std::make_unique<ContextRecord>(CategoryId_FsMemoryInfo, 0);
R_UNLESS(record != nullptr, erpt::ResultOutOfMemory());
/* Add fields. */
R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferPeakFreeSize, mri.pooled_buffer_peak_free_size));
R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferRetriedCount, mri.pooled_buffer_retried_count));
R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferReduceAllocationCount, mri.pooled_buffer_reduce_allocation_count));
R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerPeakFreeSize, mri.buffer_manager_peak_free_size));
R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerRetriedCount, mri.buffer_manager_retried_count));
R_ABORT_UNLESS(record->Add(FieldId_FsExpHeapPeakFreeSize, mri.exp_heap_peak_free_size));
R_ABORT_UNLESS(record->Add(FieldId_FsBufferPoolPeakFreeSize, mri.buffer_pool_peak_free_size));
R_ABORT_UNLESS(record->Add(FieldId_FsPatrolReadAllocateBufferSuccessCount, mri.patrol_read_allocate_buffer_success_count));
R_ABORT_UNLESS(record->Add(FieldId_FsPatrolReadAllocateBufferFailureCount, mri.patrol_read_allocate_buffer_failure_count));
R_ABORT_UNLESS(record->Add(FieldId_FsBufferManagerPeakTotalAllocatableSize, mri.buffer_manager_peak_total_allocatable_size));
R_ABORT_UNLESS(record->Add(FieldId_FsBufferPoolMaxAllocateSize, mri.buffer_pool_max_allocate_size));
R_ABORT_UNLESS(record->Add(FieldId_FsPooledBufferFailedIdealAllocationCountOnAsyncAccess, mri.pooled_buffer_failed_ideal_allocation_count_on_async_access));
/* Submit the record. */
R_ABORT_UNLESS(Context::SubmitContextRecord(std::move(record)));
}
R_SUCCEED();
}
}
Result SubmitFsInfo() {
/* Temporarily disable auto-abort. */
fs::ScopedAutoAbortDisabler aad;
/* Submit various FS info. */
R_TRY(SubmitMmcDetailInfo());
R_TRY(SubmitMmcErrorInfo());
R_TRY(SubmitSdCardDetailInfo());
R_TRY(SubmitSdCardErrorInfo());
R_TRY(SubmitGameCardDetailInfo());
R_TRY(SubmitGameCardErrorInfo());
R_TRY(SubmitFileSystemErrorInfo());
R_TRY(SubmitMemoryReportInfo());
R_SUCCEED();
}
}

View File

@@ -19,6 +19,7 @@
#include "erpt_srv_journal.hpp"
#include "erpt_srv_context_record.hpp"
#include "erpt_srv_context.hpp"
#include "erpt_srv_fs_info.hpp"
namespace ams::erpt::srv {
@@ -530,9 +531,14 @@ namespace ams::erpt::srv {
SubmitResourceLimitContexts();
#endif
if (flags.Test<CreateReportOptionFlag::SubmitFsInfo>()) {
/* TODO: 17.0.0 SubmitFsInfo() */
/* If we should, submit fs info. */
#if defined(ATMOSPHERE_OS_HORIZON)
if (hos::GetVersion() >= hos::Version_17_0_0 && flags.Test<CreateReportOptionFlag::SubmitFsInfo>()) {
/* NOTE: Nintendo ignores the result of this call. */
SubmitFsInfo();
}
#endif
R_SUCCEED();
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsa/fs_mount_utils.hpp"
#include "impl/fs_file_system_proxy_service_object.hpp"
#include "impl/fs_file_system_service_object_adapter.hpp"
namespace ams::fs {
Result GetAndClearFileSystemProxyErrorInfo(FileSystemProxyErrorInfo *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Get the error info. */
AMS_FS_R_TRY(fsp->GetAndClearErrorInfo(out));
R_SUCCEED();
}
}

View File

@@ -100,4 +100,65 @@ namespace ams::fs {
R_SUCCEED();
}
bool IsGameCardInserted() {
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_ABORT_UNLESS(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get insertion status. */
bool inserted;
AMS_FS_R_ABORT_UNLESS(device_operator->IsGameCardInserted(std::addressof(inserted)));
return inserted;
}
Result GetGameCardCid(void *dst, size_t size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(size >= sizeof(gc::GameCardIdSet), fs::ResultInvalidSize());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the id set. */
gc::GameCardIdSet gc_id_set;
AMS_FS_R_TRY(device_operator->GetGameCardIdSet(sf::OutBuffer(std::addressof(gc_id_set), sizeof(gc_id_set)), static_cast<s64>(sizeof(gc_id_set))));
/* Copy the id set to output. */
std::memcpy(dst, std::addressof(gc_id_set), sizeof(gc_id_set));
R_SUCCEED();
}
Result GetGameCardDeviceId(void *dst, size_t size) {
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the cid. */
AMS_FS_R_TRY(device_operator->GetGameCardDeviceId(sf::OutBuffer(dst, size), static_cast<s64>(size)));
R_SUCCEED();
}
Result GetGameCardErrorReportInfo(GameCardErrorReportInfo *out) {
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the error report info. */
AMS_FS_R_TRY(device_operator->GetGameCardErrorReportInfo(out));
R_SUCCEED();
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsa/fs_mount_utils.hpp"
#include "impl/fs_file_system_proxy_service_object.hpp"
#include "impl/fs_file_system_service_object_adapter.hpp"
namespace ams::fs {
Result GetAndClearMemoryReportInfo(MemoryReportInfo *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Get the memory report info. */
AMS_FS_R_TRY(fsp->GetAndClearMemoryReportInfo(out));
R_SUCCEED();
}
}

View File

@@ -0,0 +1,109 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "fsa/fs_mount_utils.hpp"
#include "impl/fs_file_system_proxy_service_object.hpp"
#include "impl/fs_file_system_service_object_adapter.hpp"
namespace ams::fs {
Result GetMmcCid(void *dst, size_t size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the cid. */
AMS_FS_R_TRY(device_operator->GetMmcCid(sf::OutBuffer(dst, size), static_cast<s64>(size)));
R_SUCCEED();
}
Result GetMmcSpeedMode(MmcSpeedMode *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the speed mode. */
s64 speed_mode = 0;
AMS_FS_R_TRY(device_operator->GetMmcSpeedMode(std::addressof(speed_mode)));
*out = static_cast<MmcSpeedMode>(speed_mode);
R_SUCCEED();
}
Result GetMmcPatrolCount(u32 *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the patrol count. */
AMS_FS_R_TRY(device_operator->GetMmcPatrolCount(out));
R_SUCCEED();
}
Result GetAndClearMmcErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out_sei != nullptr, fs::ResultNullptrArgument());
AMS_FS_R_UNLESS(out_log_size != nullptr, fs::ResultNullptrArgument());
AMS_FS_R_UNLESS(out_log_buffer != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the error info. */
s64 log_size = 0;
AMS_FS_R_TRY(device_operator->GetAndClearMmcErrorInfo(out_sei, std::addressof(log_size), sf::OutBuffer(out_log_buffer, log_buffer_size), static_cast<s64>(log_buffer_size)));
*out_log_size = static_cast<size_t>(log_size);
R_SUCCEED();
}
Result GetMmcExtendedCsd(void *dst, size_t size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the csd. */
AMS_FS_R_TRY(device_operator->GetMmcExtendedCsd(sf::OutBuffer(dst, size), static_cast<s64>(size)));
R_SUCCEED();
}
}

View File

@@ -389,6 +389,11 @@ namespace ams::fs {
/* ... */
Result GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out) {
static_assert(sizeof(fs::FileSystemProxyErrorInfo) == sizeof(::FsFileSystemProxyErrorInfo));
R_RETURN(::fsGetAndClearErrorInfo(reinterpret_cast<::FsFileSystemProxyErrorInfo *>(out.GetPointer())));
}
Result RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count) {
AMS_ABORT("TODO");
}
@@ -429,6 +434,11 @@ namespace ams::fs {
AMS_ABORT("TODO");
}
Result GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out) {
static_assert(sizeof(fs::MemoryReportInfo) == sizeof(::FsMemoryReportInfo));
R_RETURN(::fsGetAndClearMemoryReportInfo(reinterpret_cast<::FsMemoryReportInfo *>(out.GetPointer())));
}
/* ... */
Result GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count) {

View File

@@ -124,4 +124,90 @@ namespace ams::fs {
return inserted;
}
Result GetSdCardSpeedMode(SdCardSpeedMode *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the speed mode. */
s64 speed_mode = 0;
AMS_FS_R_TRY(device_operator->GetSdCardSpeedMode(std::addressof(speed_mode)));
*out = static_cast<SdCardSpeedMode>(speed_mode);
R_SUCCEED();
}
Result GetSdCardCid(void *dst, size_t size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(dst != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the cid. */
AMS_FS_R_TRY(device_operator->GetSdCardCid(sf::OutBuffer(dst, size), static_cast<s64>(size)));
R_SUCCEED();
}
Result GetSdCardUserAreaSize(s64 *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the size. */
AMS_FS_R_TRY(device_operator->GetSdCardUserAreaSize(out));
R_SUCCEED();
}
Result GetSdCardProtectedAreaSize(s64 *out) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the size. */
AMS_FS_R_TRY(device_operator->GetSdCardProtectedAreaSize(out));
R_SUCCEED();
}
Result GetAndClearSdCardErrorInfo(StorageErrorInfo *out_sei, size_t *out_log_size, char *out_log_buffer, size_t log_buffer_size) {
/* Check pre-conditions. */
AMS_FS_R_UNLESS(out_sei != nullptr, fs::ResultNullptrArgument());
AMS_FS_R_UNLESS(out_log_size != nullptr, fs::ResultNullptrArgument());
AMS_FS_R_UNLESS(out_log_buffer != nullptr, fs::ResultNullptrArgument());
auto fsp = impl::GetFileSystemProxyServiceObject();
/* Open a device operator. */
sf::SharedPointer<fssrv::sf::IDeviceOperator> device_operator;
AMS_FS_R_TRY(fsp->OpenDeviceOperator(std::addressof(device_operator)));
/* Get the error info. */
s64 log_size = 0;
AMS_FS_R_TRY(device_operator->GetAndClearSdCardErrorInfo(out_sei, std::addressof(log_size), sf::OutBuffer(out_log_buffer, log_buffer_size), static_cast<s64>(log_buffer_size)));
*out_log_size = static_cast<size_t>(log_size);
R_SUCCEED();
}
}

View File

@@ -33,6 +33,48 @@ namespace ams::fs::impl {
R_RETURN(fsDeviceOperatorIsSdCardInserted(std::addressof(m_operator), out.GetPointer()));
}
Result GetSdCardSpeedMode(ams::sf::Out<s64> out) {
R_RETURN(fsDeviceOperatorGetSdCardSpeedMode(std::addressof(m_operator), out.GetPointer()));
}
Result GetSdCardCid(ams::sf::OutBuffer out, s64 size) {
R_RETURN(fsDeviceOperatorGetSdCardCid(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size));
}
Result GetSdCardUserAreaSize(ams::sf::Out<s64> out) {
R_RETURN(fsDeviceOperatorGetSdCardUserAreaSize(std::addressof(m_operator), out.GetPointer()));
}
Result GetSdCardProtectedAreaSize(ams::sf::Out<s64> out) {
R_RETURN(fsDeviceOperatorGetSdCardProtectedAreaSize(std::addressof(m_operator), out.GetPointer()));
}
Result GetAndClearSdCardErrorInfo(ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size) {
static_assert(sizeof(::FsStorageErrorInfo) == sizeof(fs::StorageErrorInfo));
R_RETURN(fsDeviceOperatorGetAndClearSdCardErrorInfo(std::addressof(m_operator), reinterpret_cast<::FsStorageErrorInfo *>(out_sei.GetPointer()), out_size.GetPointer(), out_buf.GetPointer(), out_buf.GetSize(), size));
}
Result GetMmcCid(ams::sf::OutBuffer out, s64 size) {
R_RETURN(fsDeviceOperatorGetMmcCid(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size));
}
Result GetMmcSpeedMode(ams::sf::Out<s64> out) {
R_RETURN(fsDeviceOperatorGetMmcSpeedMode(std::addressof(m_operator), out.GetPointer()));
}
Result GetMmcPatrolCount(ams::sf::Out<u32> out) {
R_RETURN(fsDeviceOperatorGetMmcPatrolCount(std::addressof(m_operator), out.GetPointer()));
}
Result GetAndClearMmcErrorInfo(ams::sf::Out<fs::StorageErrorInfo> out_sei, ams::sf::Out<s64> out_size, ams::sf::OutBuffer out_buf, s64 size) {
static_assert(sizeof(::FsStorageErrorInfo) == sizeof(fs::StorageErrorInfo));
R_RETURN(fsDeviceOperatorGetAndClearMmcErrorInfo(std::addressof(m_operator), reinterpret_cast<::FsStorageErrorInfo *>(out_sei.GetPointer()), out_size.GetPointer(), out_buf.GetPointer(), out_buf.GetSize(), size));
}
Result GetMmcExtendedCsd(ams::sf::OutBuffer out, s64 size) {
R_RETURN(fsDeviceOperatorGetMmcExtendedCsd(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size));
}
Result IsGameCardInserted(ams::sf::Out<bool> out) {
R_RETURN(fsDeviceOperatorIsGameCardInserted(std::addressof(m_operator), out.GetPointer()));
}
@@ -41,6 +83,19 @@ namespace ams::fs::impl {
static_assert(sizeof(::FsGameCardHandle) == sizeof(u32));
R_RETURN(fsDeviceOperatorGetGameCardHandle(std::addressof(m_operator), reinterpret_cast<::FsGameCardHandle *>(out.GetPointer())));
}
Result GetGameCardIdSet(ams::sf::OutBuffer out, s64 size) {
R_RETURN(fsDeviceOperatorGetGameCardIdSet(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size));
}
Result GetGameCardErrorReportInfo(ams::sf::Out<fs::GameCardErrorReportInfo> out) {
static_assert(sizeof(::FsGameCardErrorReportInfo) == sizeof(fs::GameCardErrorReportInfo));
R_RETURN(fsDeviceOperatorGetGameCardErrorReportInfo(std::addressof(m_operator), reinterpret_cast<::FsGameCardErrorReportInfo *>(out.GetPointer())));
}
Result GetGameCardDeviceId(ams::sf::OutBuffer out, s64 size) {
R_RETURN(fsDeviceOperatorGetGameCardDeviceId(std::addressof(m_operator), out.GetPointer(), out.GetSize(), size));
}
};
static_assert(fssrv::sf::IsIDeviceOperator<RemoteDeviceOperator>);
#endif

View File

@@ -307,6 +307,10 @@ namespace ams::fssrv {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::GetProgramId(ams::sf::Out<ncm::ProgramId> out_program_id, const fssrv::sf::FspPath &path, fs::ContentAttributes attr) {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::GetRightsIdByPath(ams::sf::Out<fs::RightsId> out, const fssrv::sf::FspPath &path) {
AMS_ABORT("TODO");
}
@@ -369,6 +373,10 @@ namespace ams::fssrv {
/* ... */
Result FileSystemProxyImpl::GetAndClearErrorInfo(ams::sf::Out<fs::FileSystemProxyErrorInfo> out) {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::RegisterProgramIndexMapInfo(const ams::sf::InBuffer &buffer, s32 count) {
AMS_ABORT("TODO");
}
@@ -409,6 +417,10 @@ namespace ams::fssrv {
AMS_ABORT("TODO");
}
Result FileSystemProxyImpl::GetAndClearMemoryReportInfo(ams::sf::Out<fs::MemoryReportInfo> out) {
AMS_ABORT("TODO");
}
/* ... */
Result FileSystemProxyImpl::GetProgramIndexForAccessLog(ams::sf::Out<u32> out_idx, ams::sf::Out<u32> out_count) {

View File

@@ -784,27 +784,31 @@ namespace ams::ncm {
}
Result ContentManagerImpl::BuildContentMetaDatabase(StorageId storage_id) {
/* NOTE: we build on 17.0.0+, to work around a change in Nintendo save handling behavior. */
if (hos::GetVersion() < hos::Version_5_0_0 || hos::GetVersion() >= hos::Version_17_0_0) {
/* Temporarily activate the database. */
R_TRY(this->ActivateContentMetaDatabase(storage_id));
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
/* Open the content meta database and storage. */
ContentMetaDatabase meta_db;
ContentStorage storage;
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
/* Create a builder, and build. */
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
R_RETURN(builder.BuildFromStorage(std::addressof(storage)));
if (hos::GetVersion() < hos::Version_5_0_0) {
/* On < 5.0.0, perform an actual build of the database. */
R_RETURN(this->BuildContentMetaDatabaseImpl(storage_id));
} else {
/* On 5.0.0+, building just performs an import. */
R_RETURN(this->ImportContentMetaDatabase(storage_id, false));
}
}
Result ContentManagerImpl::BuildContentMetaDatabaseImpl(StorageId storage_id) {
/* Temporarily activate the database. */
R_TRY(this->ActivateContentMetaDatabase(storage_id));
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(storage_id); };
/* Open the content meta database and storage. */
ContentMetaDatabase meta_db;
ContentStorage storage;
R_TRY(ncm::OpenContentMetaDatabase(std::addressof(meta_db), storage_id));
R_TRY(ncm::OpenContentStorage(std::addressof(storage), storage_id));
/* Create a builder, and build. */
ContentMetaDatabaseBuilder builder(std::addressof(meta_db));
R_RETURN(builder.BuildFromStorage(std::addressof(storage)));
}
Result ContentManagerImpl::ImportContentMetaDatabase(StorageId storage_id, bool from_signed_partition) {
/* Only support importing BuiltInSystem. */
AMS_ABORT_UNLESS(storage_id == StorageId::BuiltInSystem);
@@ -832,6 +836,31 @@ namespace ams::ncm {
R_SUCCEED();
}
bool ContentManagerImpl::IsNeedRebuildSystemContentMetaDatabase() {
/* TODO: Should hos::GetVersion() >= hos::Version_17_0_0 be checked? */
/* If we do not actually have a content meta db, we should re-build. */
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
return true;
}
/* We have a content meta db. Temporarily, activate it. */
if (R_FAILED(this->ActivateContentMetaDatabase(StorageId::BuiltInSystem))) {
return true;
}
ON_SCOPE_EXIT { this->InactivateContentMetaDatabase(StorageId::BuiltInSystem); };
/* Open the content meta db. */
ContentMetaDatabase meta_db;
R_ABORT_UNLESS(ncm::OpenContentMetaDatabase(std::addressof(meta_db), StorageId::BuiltInSystem));
/* List the meta db's contents. */
const auto list_count = meta_db.ListContentMeta(nullptr, 0);
/* We need to rebuild if the db has zero entries. */
return list_count.total == 0;
}
Result ContentManagerImpl::Initialize(const ContentManagerConfig &config) {
/* Initialize based on whether integrated content is enabled. */
if (config.IsIntegratedSystemContentEnabled()) {
@@ -942,13 +971,26 @@ namespace ams::ncm {
}
R_TRY(this->ActivateContentStorage(StorageId::BuiltInSystem));
/* NOTE: This logic is unofficial. */
/* Beginning with 17.0.0+, save management behavior changed. The primary symptom of this is either verify fail */
/* or an empty kvs, both of which we can fix by performing a rebuild. */
if (this->IsNeedRebuildSystemContentMetaDatabase()) {
/* Clean up the system content meta database, to ensure creation can succeed. */
this->CleanupContentMetaDatabase(StorageId::BuiltInSystem);
/* Create the content meta database. */
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* Rebuild the content meta database. */
R_TRY(this->BuildContentMetaDatabaseImpl(StorageId::BuiltInSystem));
}
/* Setup the content meta database for system. */
if (R_FAILED(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem))) {
R_TRY(this->CreateContentMetaDatabase(StorageId::BuiltInSystem));
/* Try to build or import a database, depending on our configuration. */
/* NOTE: To work around a change in save management behavior in 17.0.0+, we build the database if needed. */
if (manager_config.ShouldBuildDatabase() || hos::GetVersion() >= hos::Version_17_0_0) {
if (manager_config.ShouldBuildDatabase()) {
/* If we should build the database, do so. */
R_TRY(this->BuildContentMetaDatabase(StorageId::BuiltInSystem));
R_TRY(this->VerifyContentMetaDatabase(StorageId::BuiltInSystem));

View File

@@ -44,6 +44,13 @@ namespace ams::pm::dmnt {
R_SUCCEED();
}
Result HookToCreateProcess(os::NativeHandle *out_handle, const ncm::ProgramId program_id) {
Event evt;
R_TRY(pmdmntHookToCreateProcess(std::addressof(evt), static_cast<u64>(program_id)));
*out_handle = evt.revent;
R_SUCCEED();
}
Result AtmosphereGetProcessInfo(os::NativeHandle *out_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
*out_handle = os::InvalidNativeHandle;
*out_loc = {};

View File

@@ -28,13 +28,13 @@ namespace ams::pm::info {
R_RETURN(pminfoAtmosphereGetProcessId(reinterpret_cast<u64 *>(out_process_id), static_cast<u64>(program_id)));
}
Result GetAppletCurrentResourceLimitValues(pm::ResourceLimitValues *out) {
static_assert(sizeof(pm::ResourceLimitValues) == sizeof(::PmResourceLimitValues));
Result GetAppletResourceLimitCurrentValue(pm::ResourceLimitValue *out) {
static_assert(sizeof(pm::ResourceLimitValue) == sizeof(::PmResourceLimitValues));
R_RETURN(pminfoGetAppletCurrentResourceLimitValues(reinterpret_cast<PmResourceLimitValues *>(out)));
}
Result GetAppletPeakResourceLimitValues(pm::ResourceLimitValues *out) {
static_assert(sizeof(pm::ResourceLimitValues) == sizeof(::PmResourceLimitValues));
Result GetAppletResourceLimitPeakValue(pm::ResourceLimitValue *out) {
static_assert(sizeof(pm::ResourceLimitValue) == sizeof(::PmResourceLimitValues));
R_RETURN(pminfoGetAppletPeakResourceLimitValues(reinterpret_cast<PmResourceLimitValues *>(out)));
}

View File

@@ -17,7 +17,7 @@
#define ATMOSPHERE_RELEASE_VERSION_MAJOR 1
#define ATMOSPHERE_RELEASE_VERSION_MINOR 6
#define ATMOSPHERE_RELEASE_VERSION_MICRO 0
#define ATMOSPHERE_RELEASE_VERSION_MICRO 2
#define ATMOSPHERE_RELEASE_VERSION ATMOSPHERE_RELEASE_VERSION_MAJOR, ATMOSPHERE_RELEASE_VERSION_MINOR, ATMOSPHERE_RELEASE_VERSION_MICRO

View File

@@ -54,6 +54,7 @@ namespace ams::capsrv {
R_DEFINE_ERROR_RANGE(InternalError, 1024, 2047);
R_DEFINE_ERROR_RESULT(InternalJpegEncoderError, 1210);
R_DEFINE_ERROR_RESULT(InternalJpegOutBufferShortage, 1211);
R_DEFINE_ERROR_RESULT(InternalJpegWorkMemoryShortage, 1212);
R_DEFINE_ERROR_RANGE(InternalFileDataVerificationError, 1300, 1399);

View File

@@ -2105,9 +2105,24 @@ namespace ams::dmnt {
ParsePrefix(command, "0x");
/* Decode program id. */
const u64 program_id = DecodeHex(command);
const ncm::ProgramId program_id(DecodeHex(command));
AppendReplyFormat(reply_cur, reply_end, "[TODO] wait for program id 0x%lx\n", program_id);
/* Wait for the process to be created. */
{
/* Get hook to creation of process with program id. */
os::NativeHandle h;
R_ABORT_UNLESS(pm::dmnt::HookToCreateProcess(std::addressof(h), program_id));
/* Wait for event. */
os::SystemEvent hook_event(h, true, os::InvalidNativeHandle, false, os::EventClearMode_AutoClear);
hook_event.Wait();
}
/* Get process id. */
R_ABORT_UNLESS(pm::dmnt::GetProcessId(std::addressof(m_wait_process_id), program_id));
/* Note that we're attaching. */
AppendReplyFormat(reply_cur, reply_end, "Send `attach 0x%lx` to attach.\n", m_wait_process_id.value);
} else {
std::memcpy(m_reply_cur, command, std::strlen(command) + 1);
AppendReplyFormat(reply_cur, reply_end, "Unknown command `%s`\n", m_reply_cur);

View File

@@ -1,7 +1,7 @@
{
"name": "ProcessManager",
"title_id": "0x0100000000000003",
"main_thread_stack_size": "0x00002000",
"main_thread_stack_size": "0x00003000",
"main_thread_priority": 49,
"default_cpu_id": 3,
"process_category": 1,

View File

@@ -18,6 +18,64 @@
namespace ams::pm::impl {
namespace {
template<size_t MaxProcessInfos>
class ProcessInfoAllocator {
NON_COPYABLE(ProcessInfoAllocator);
NON_MOVEABLE(ProcessInfoAllocator);
static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small.");
private:
util::TypedStorage<ProcessInfo> m_process_info_storages[MaxProcessInfos]{};
bool m_process_info_allocated[MaxProcessInfos]{};
os::SdkMutex m_lock{};
private:
constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const {
return process_info - GetPointer(m_process_info_storages[0]);
}
public:
constexpr ProcessInfoAllocator() = default;
template<typename... Args>
ProcessInfo *AllocateProcessInfo(Args &&... args) {
std::scoped_lock lk(m_lock);
for (size_t i = 0; i < MaxProcessInfos; i++) {
if (!m_process_info_allocated[i]) {
m_process_info_allocated[i] = true;
std::memset(m_process_info_storages + i, 0, sizeof(m_process_info_storages[i]));
return util::ConstructAt(m_process_info_storages[i], std::forward<Args>(args)...);
}
}
return nullptr;
}
void FreeProcessInfo(ProcessInfo *process_info) {
std::scoped_lock lk(m_lock);
const size_t index = this->GetProcessInfoIndex(process_info);
AMS_ABORT_UNLESS(index < MaxProcessInfos);
AMS_ABORT_UNLESS(m_process_info_allocated[index]);
util::DestroyAt(m_process_info_storages[index]);
m_process_info_allocated[index] = false;
}
};
/* Process lists. */
constinit ProcessList g_process_list;
constinit ProcessList g_exit_list;
/* Process Info Allocation. */
/* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */
constexpr size_t MaxProcessCount = 0x60;
constinit ProcessInfoAllocator<MaxProcessCount> g_process_info_allocator;
}
ProcessInfo::ProcessInfo(os::NativeHandle h, os::ProcessId pid, ldr::PinId pin, const ncm::ProgramLocation &l, const cfg::OverrideStatus &s) : m_process_id(pid), m_pin_id(pin), m_loc(l), m_status(s), m_handle(h), m_state(svc::ProcessState_Created), m_flags(0) {
os::InitializeMultiWaitHolder(std::addressof(m_multi_wait_holder), m_handle);
os::SetMultiWaitHolderUserData(std::addressof(m_multi_wait_holder), reinterpret_cast<uintptr_t>(this));
@@ -37,10 +95,27 @@ namespace ams::pm::impl {
/* Close the process's handle. */
os::CloseNativeHandle(m_handle);
m_handle = os::InvalidNativeHandle;
/* Unlink the process from its multi wait. */
os::UnlinkMultiWaitHolder(std::addressof(m_multi_wait_holder));
}
}
ProcessListAccessor GetProcessList() {
return ProcessListAccessor(g_process_list);
}
ProcessListAccessor GetExitList() {
return ProcessListAccessor(g_exit_list);
}
ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status) {
return g_process_info_allocator.AllocateProcessInfo(process_handle, process_id, pin_id, location, override_status);
}
void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) {
/* Remove the process from the list. */
list->Remove(process_info);
/* Delete the process. */
g_process_info_allocator.FreeProcessInfo(process_info);
}
}

View File

@@ -64,8 +64,8 @@ namespace ams::pm::impl {
~ProcessInfo();
void Cleanup();
void LinkToMultiWait(os::MultiWaitType &multi_wait) {
os::LinkMultiWaitHolder(std::addressof(multi_wait), std::addressof(m_multi_wait_holder));
os::MultiWaitHolderType *GetMultiWaitHolder() {
return std::addressof(m_multi_wait_holder);
}
os::NativeHandle GetHandle() const {
@@ -232,4 +232,10 @@ namespace ams::pm::impl {
}
};
ProcessListAccessor GetProcessList();
ProcessListAccessor GetExitList();
ProcessInfo *AllocateProcessInfo(svc::Handle process_handle, os::ProcessId process_id, ldr::PinId pin_id, const ncm::ProgramLocation &location, const cfg::OverrideStatus &override_status);
void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info);
}

View File

@@ -15,9 +15,9 @@
*/
#include <stratosphere.hpp>
#include "pm_process_manager.hpp"
#include "pm_resource_manager.hpp"
#include "pm_process_tracker.hpp"
#include "pm_process_info.hpp"
#include "pm_spec.hpp"
namespace ams::pm::impl {
@@ -29,13 +29,7 @@ namespace ams::pm::impl {
HookType_Application = (1 << 1),
};
struct LaunchProcessArgs {
os::ProcessId *out_process_id;
ncm::ProgramLocation location;
u32 flags;
};
#define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast<u32>(LaunchFlags_##flag) : static_cast<u32>(LaunchFlagsDeprecated_##flag))
#define GET_FLAG_MASK(flag) (hos_version >= hos::Version_5_0_0 ? static_cast<u32>(LaunchFlags_##flag) : static_cast<u32>(LaunchFlagsDeprecated_##flag))
inline bool ShouldSignalOnExit(u32 launch_flags) {
const auto hos_version = hos::GetVersion();
@@ -70,115 +64,26 @@ namespace ams::pm::impl {
return launch_flags & GET_FLAG_MASK(DisableAslr);
}
#undef GET_FLAG_MASK
template<size_t MaxProcessInfos>
class ProcessInfoAllocator {
NON_COPYABLE(ProcessInfoAllocator);
NON_MOVEABLE(ProcessInfoAllocator);
static_assert(MaxProcessInfos >= 0x40, "MaxProcessInfos is too small.");
private:
util::TypedStorage<ProcessInfo> m_process_info_storages[MaxProcessInfos]{};
bool m_process_info_allocated[MaxProcessInfos]{};
os::SdkMutex m_lock{};
private:
constexpr inline size_t GetProcessInfoIndex(ProcessInfo *process_info) const {
return process_info - GetPointer(m_process_info_storages[0]);
}
public:
constexpr ProcessInfoAllocator() = default;
template<typename... Args>
ProcessInfo *AllocateProcessInfo(Args &&... args) {
std::scoped_lock lk(m_lock);
for (size_t i = 0; i < MaxProcessInfos; i++) {
if (!m_process_info_allocated[i]) {
m_process_info_allocated[i] = true;
std::memset(m_process_info_storages + i, 0, sizeof(m_process_info_storages[i]));
return util::ConstructAt(m_process_info_storages[i], std::forward<Args>(args)...);
}
}
return nullptr;
}
void FreeProcessInfo(ProcessInfo *process_info) {
std::scoped_lock lk(m_lock);
const size_t index = this->GetProcessInfoIndex(process_info);
AMS_ABORT_UNLESS(index < MaxProcessInfos);
AMS_ABORT_UNLESS(m_process_info_allocated[index]);
util::DestroyAt(m_process_info_storages[index]);
m_process_info_allocated[index] = false;
}
};
#undef GET_FLAG_MASK
/* Process Tracking globals. */
void ProcessTrackingMain(void *);
constinit os::ThreadType g_process_track_thread;
alignas(os::ThreadStackAlignment) constinit u8 g_process_track_thread_stack[16_KB];
/* Process lists. */
constinit ProcessList g_process_list;
constinit ProcessList g_dead_process_list;
/* Process Info Allocation. */
/* Note: The kernel slabheap is size 0x50 -- we allow slightly larger to account for the dead process list. */
constexpr size_t MaxProcessCount = 0x60;
constinit ProcessInfoAllocator<MaxProcessCount> g_process_info_allocator;
constinit ProcessTracker g_process_tracker;
alignas(os::ThreadStackAlignment) constinit u8 g_process_track_thread_stack[8_KB];
/* Global events. */
constinit os::SystemEventType g_process_event;
constinit os::SystemEventType g_hook_to_create_process_event;
constinit os::SystemEventType g_hook_to_create_application_process_event;
constinit os::SystemEventType g_boot_finished_event;
/* Process Launch synchronization globals. */
constinit os::SdkMutex g_launch_program_lock;
os::Event g_process_launch_start_event(os::EventClearMode_AutoClear);
os::Event g_process_launch_finish_event(os::EventClearMode_AutoClear);
constinit Result g_process_launch_result = ResultSuccess();
constinit LaunchProcessArgs g_process_launch_args = {};
/* Hook globals. */
constinit std::atomic<ncm::ProgramId> g_program_id_hook;
constinit std::atomic<bool> g_application_hook;
/* Forward declarations. */
Result LaunchProcess(os::MultiWaitType &multi_wait, const LaunchProcessArgs &args);
void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info);
/* Helpers. */
void ProcessTrackingMain(void *) {
/* This is the main loop of the process tracking thread. */
/* Setup multi wait/holders. */
os::MultiWaitType process_multi_wait;
os::MultiWaitHolderType start_event_holder;
os::InitializeMultiWait(std::addressof(process_multi_wait));
os::InitializeMultiWaitHolder(std::addressof(start_event_holder), g_process_launch_start_event.GetBase());
os::LinkMultiWaitHolder(std::addressof(process_multi_wait), std::addressof(start_event_holder));
while (true) {
auto signaled_holder = os::WaitAny(std::addressof(process_multi_wait));
if (signaled_holder == std::addressof(start_event_holder)) {
/* Launch start event signaled. */
/* TryWait will clear signaled, preventing duplicate notifications. */
if (g_process_launch_start_event.TryWait()) {
g_process_launch_result = LaunchProcess(process_multi_wait, g_process_launch_args);
g_process_launch_finish_event.Signal();
}
} else {
/* Some process was signaled. */
ProcessListAccessor list(g_process_list);
OnProcessSignaled(list, reinterpret_cast<ProcessInfo *>(os::GetMultiWaitHolderUserData(signaled_holder)));
}
}
void CreateDebuggerEvent() {
/* Create debugger hook events. */
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true));
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true));
}
inline u32 GetLoaderCreateProcessFlags(u32 launch_flags) {
@@ -195,7 +100,7 @@ namespace ams::pm::impl {
}
bool HasApplicationProcess() {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
for (auto &process : *list) {
if (process.IsApplication()) {
@@ -212,19 +117,14 @@ namespace ams::pm::impl {
R_SUCCEED();
}
void CleanupProcessInfo(ProcessListAccessor &list, ProcessInfo *process_info) {
/* Remove the process from the list. */
list->Remove(process_info);
Result LaunchProgramImpl(ProcessInfo **out_process_info, os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) {
/* Set the output to nullptr, if we fail. */
*out_process_info = nullptr;
/* Delete the process. */
g_process_info_allocator.FreeProcessInfo(process_info);
}
Result LaunchProcess(os::MultiWaitType &multi_wait, const LaunchProcessArgs &args) {
/* Get Program Info. */
ldr::ProgramInfo program_info;
cfg::OverrideStatus override_status;
R_TRY(ldr::pm::AtmosphereGetProgramInfo(std::addressof(program_info), std::addressof(override_status), args.location));
R_TRY(ldr::pm::AtmosphereGetProgramInfo(std::addressof(program_info), std::addressof(override_status), loc));
const bool is_application = (program_info.flags & ldr::ProgramInfoFlag_ApplicationTypeMask) == ldr::ProgramInfoFlag_Application;
const bool allow_debug = (program_info.flags & ldr::ProgramInfoFlag_AllowDebug) || hos::GetVersion() < hos::Version_2_0_0;
@@ -232,14 +132,14 @@ namespace ams::pm::impl {
R_UNLESS(!is_application || !HasApplicationProcess(), pm::ResultApplicationRunning());
/* Fix the program location to use the right program id. */
const ncm::ProgramLocation location = ncm::ProgramLocation::Make(program_info.program_id, static_cast<ncm::StorageId>(args.location.storage_id));
const ncm::ProgramLocation fixed_location = ncm::ProgramLocation::Make(program_info.program_id, static_cast<ncm::StorageId>(loc.storage_id));
/* Pin and create the process. */
os::NativeHandle process_handle;
ldr::PinId pin_id;
{
/* Pin the program with loader. */
R_TRY(ldr::pm::AtmospherePinProgram(std::addressof(pin_id), location, override_status));
R_TRY(ldr::pm::AtmospherePinProgram(std::addressof(pin_id), fixed_location, override_status));
/* If we fail after now, unpin. */
ON_RESULT_FAILURE { ldr::pm::UnpinProgram(pin_id); };
@@ -263,29 +163,28 @@ namespace ams::pm::impl {
ON_RESULT_FAILURE_2 { if (mitm_boost_size > 0 || is_application) { R_ABORT_UNLESS(BoostSystemMemoryResourceLimitForMitm(0)); } };
/* Ensure resources are available. */
resource::WaitResourceAvailable(std::addressof(program_info));
WaitResourceAvailable(std::addressof(program_info));
/* Actually create the process. */
R_TRY(ldr::pm::CreateProcess(std::addressof(process_handle), pin_id, GetLoaderCreateProcessFlags(args.flags), resource::GetResourceLimitHandle(std::addressof(program_info))));
R_TRY(ldr::pm::CreateProcess(std::addressof(process_handle), pin_id, GetLoaderCreateProcessFlags(flags), GetResourceLimitHandle(std::addressof(program_info))));
}
/* Get the process id. */
os::ProcessId process_id = os::GetProcessId(process_handle);
/* Make new process info. */
ProcessInfo *process_info = g_process_info_allocator.AllocateProcessInfo(process_handle, process_id, pin_id, location, override_status);
ProcessInfo *process_info = AllocateProcessInfo(process_handle, process_id, pin_id, fixed_location, override_status);
AMS_ABORT_UNLESS(process_info != nullptr);
/* Link new process info. */
/* Add the new process info to the process list. */
{
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
list->push_back(*process_info);
process_info->LinkToMultiWait(multi_wait);
}
/* Prevent resource leakage if register fails. */
ON_RESULT_FAILURE {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
process_info->Cleanup();
CleanupProcessInfo(list, process_info);
};
@@ -296,166 +195,73 @@ namespace ams::pm::impl {
const u8 *aci_fah = acid_fac + program_info.acid_fac_size;
/* Register with FS and SM. */
R_TRY(fsprRegisterProgram(static_cast<u64>(process_id), static_cast<u64>(location.program_id), static_cast<NcmStorageId>(location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size));
R_TRY(sm::manager::RegisterProcess(process_id, location.program_id, override_status, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size));
R_TRY(fsprRegisterProgram(static_cast<u64>(process_id), static_cast<u64>(fixed_location.program_id), static_cast<NcmStorageId>(fixed_location.storage_id), aci_fah, program_info.aci_fah_size, acid_fac, program_info.acid_fac_size));
R_TRY(sm::manager::RegisterProcess(process_id, fixed_location.program_id, override_status, acid_sac, program_info.acid_sac_size, aci_sac, program_info.aci_sac_size));
/* Set flags. */
if (is_application) {
process_info->SetApplication();
}
if (ShouldSignalOnStart(args.flags) && allow_debug) {
if (ShouldSignalOnStart(flags) && allow_debug) {
process_info->SetSignalOnStart();
}
if (ShouldSignalOnExit(args.flags)) {
if (ShouldSignalOnExit(flags)) {
process_info->SetSignalOnExit();
}
if (ShouldSignalOnDebugEvent(args.flags) && allow_debug) {
if (ShouldSignalOnDebugEvent(flags) && allow_debug) {
process_info->SetSignalOnDebugEvent();
}
/* Process hooks/signaling. */
if (location.program_id == g_program_id_hook) {
if (fixed_location.program_id == g_program_id_hook) {
os::SignalSystemEvent(std::addressof(g_hook_to_create_process_event));
g_program_id_hook = ncm::InvalidProgramId;
} else if (is_application && g_application_hook) {
os::SignalSystemEvent(std::addressof(g_hook_to_create_application_process_event));
g_application_hook = false;
} else if (!ShouldStartSuspended(args.flags)) {
} else if (!ShouldStartSuspended(flags)) {
R_TRY(StartProcess(process_info, std::addressof(program_info)));
}
*args.out_process_id = process_id;
*out_process_id = process_id;
*out_process_info = process_info;
R_SUCCEED();
}
void OnProcessSignaled(ProcessListAccessor &list, ProcessInfo *process_info) {
/* Reset the process's signal. */
svc::ResetSignal(process_info->GetHandle());
/* Update the process's state. */
const svc::ProcessState old_state = process_info->GetState();
{
s64 tmp = 0;
R_ABORT_UNLESS(svc::GetProcessInfo(std::addressof(tmp), process_info->GetHandle(), svc::ProcessInfoType_ProcessState));
process_info->SetState(static_cast<svc::ProcessState>(tmp));
}
const svc::ProcessState new_state = process_info->GetState();
/* If we're transitioning away from crashed, clear waiting attached. */
if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) {
process_info->ClearExceptionWaitingAttach();
}
switch (new_state) {
case svc::ProcessState_Created:
case svc::ProcessState_CreatedAttached:
case svc::ProcessState_Terminating:
break;
case svc::ProcessState_Running:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->ClearSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
} else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) {
process_info->SetStartedStateChanged();
process_info->ClearSignalOnStart();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->ClearUnhandledException();
break;
case svc::ProcessState_Crashed:
if (!process_info->HasUnhandledException()) {
process_info->SetExceptionOccurred();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->SetExceptionWaitingAttach();
break;
case svc::ProcessState_RunningAttached:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->ClearSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->ClearUnhandledException();
break;
case svc::ProcessState_Terminated:
/* Free process resources, unlink from multi wait. */
process_info->Cleanup();
if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) {
os::SignalSystemEvent(std::addressof(g_process_event));
} else {
/* Handle the case where we need to keep the process alive some time longer. */
if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) {
/* Remove from the living list. */
list->Remove(process_info);
/* Add the process to the list of dead processes. */
{
ProcessListAccessor dead_list(g_dead_process_list);
dead_list->push_back(*process_info);
}
/* Signal. */
os::SignalSystemEvent(std::addressof(g_process_event));
} else {
/* Actually delete process. */
CleanupProcessInfo(list, process_info);
}
}
break;
case svc::ProcessState_DebugBreak:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->SetSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
}
break;
}
}
}
/* Initialization. */
Result InitializeProcessManager() {
/* Create events. */
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true));
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_process_event), os::EventClearMode_AutoClear, true));
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_hook_to_create_application_process_event), os::EventClearMode_AutoClear, true));
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true));
CreateProcessEvent();
CreateDebuggerEvent();
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_boot_finished_event), os::EventClearMode_AutoClear, true));
/* Initialize resource limits. */
R_TRY(resource::InitializeResourceManager());
R_TRY(InitializeSpec());
/* Create thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(g_process_track_thread), ProcessTrackingMain, nullptr, g_process_track_thread_stack, sizeof(g_process_track_thread_stack), AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack)));
os::SetThreadNamePointer(std::addressof(g_process_track_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack));
/* Initialize the process tracker. */
g_process_tracker.Initialize(g_process_track_thread_stack, sizeof(g_process_track_thread_stack));
/* Start thread. */
os::StartThread(std::addressof(g_process_track_thread));
/* Start the process tracker thread. */
g_process_tracker.StartThread();
R_SUCCEED();
}
/* Process Management. */
Result LaunchProgram(os::ProcessId *out_process_id, const ncm::ProgramLocation &loc, u32 flags) {
/* Ensure we only try to launch one program at a time. */
std::scoped_lock lk(g_launch_program_lock);
/* Launch the program. */
ProcessInfo *process_info = nullptr;
R_TRY(LaunchProgramImpl(std::addressof(process_info), out_process_id, loc, flags));
/* Set global arguments, signal, wait. */
g_process_launch_args = {
.out_process_id = out_process_id,
.location = loc,
.flags = flags,
};
g_process_launch_start_event.Signal();
g_process_launch_finish_event.Wait();
R_RETURN(g_process_launch_result);
/* Register the process info with the tracker. */
g_process_tracker.QueueEntry(process_info);
R_SUCCEED();
}
Result StartProcess(os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -467,7 +273,7 @@ namespace ams::pm::impl {
}
Result TerminateProcess(os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -476,7 +282,7 @@ namespace ams::pm::impl {
}
Result TerminateProgram(ncm::ProgramId program_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(program_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -484,15 +290,10 @@ namespace ams::pm::impl {
R_RETURN(svc::TerminateProcess(process_info->GetHandle()));
}
Result GetProcessEventHandle(os::NativeHandle *out) {
*out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event));
R_SUCCEED();
}
Result GetProcessEventInfo(ProcessEventInfo *out) {
/* Check for event from current process. */
{
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
for (auto &process : *list) {
@@ -528,14 +329,14 @@ namespace ams::pm::impl {
/* Check for event from exited process. */
if (hos::GetVersion() >= hos::Version_5_0_0) {
ProcessListAccessor dead_list(g_dead_process_list);
auto exit_list = GetExitList();
if (!dead_list->empty()) {
auto &process_info = dead_list->front();
out->event = GetProcessEventValue(ProcessEvent::Exited);
if (!exit_list->empty()) {
auto &process_info = exit_list->front();
out->event = GetProcessEventValue(ProcessEvent::Exited);
out->process_id = process_info.GetProcessId();
CleanupProcessInfo(dead_list, std::addressof(process_info));
CleanupProcessInfo(exit_list, std::addressof(process_info));
R_SUCCEED();
}
}
@@ -546,7 +347,7 @@ namespace ams::pm::impl {
}
Result CleanupProcess(os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -557,7 +358,7 @@ namespace ams::pm::impl {
}
Result ClearExceptionOccurred(os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -575,7 +376,7 @@ namespace ams::pm::impl {
}
Result GetExceptionProcessIdList(u32 *out_count, os::ProcessId *out_process_ids, size_t max_out_count) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
size_t count = 0;
@@ -596,7 +397,7 @@ namespace ams::pm::impl {
}
Result GetProcessId(os::ProcessId *out, ncm::ProgramId program_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(program_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -606,7 +407,7 @@ namespace ams::pm::impl {
}
Result GetProgramId(ncm::ProgramId *out, os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -616,7 +417,7 @@ namespace ams::pm::impl {
}
Result GetApplicationProcessId(os::ProcessId *out_process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
for (auto &process : *list) {
if (process.IsApplication()) {
@@ -629,7 +430,7 @@ namespace ams::pm::impl {
}
Result AtmosphereGetProcessInfo(os::NativeHandle *out_process_handle, ncm::ProgramLocation *out_loc, cfg::OverrideStatus *out_status, os::ProcessId process_id) {
ProcessListAccessor list(g_process_list);
auto list = GetProcessList();
auto process_info = list->Find(process_id);
R_UNLESS(process_info != nullptr, pm::ResultProcessNotFound());
@@ -706,33 +507,4 @@ namespace ams::pm::impl {
R_SUCCEED();
}
/* Resource Limit API. */
Result BoostSystemMemoryResourceLimit(u64 boost_size) {
R_RETURN(resource::BoostSystemMemoryResourceLimit(boost_size));
}
Result BoostApplicationThreadResourceLimit() {
R_RETURN(resource::BoostApplicationThreadResourceLimit());
}
Result BoostSystemThreadResourceLimit() {
R_RETURN(resource::BoostSystemThreadResourceLimit());
}
Result GetAppletCurrentResourceLimitValues(pm::ResourceLimitValues *out) {
R_RETURN(resource::GetCurrentResourceLimitValues(ResourceLimitGroup_Applet, out));
}
Result GetAppletPeakResourceLimitValues(pm::ResourceLimitValues *out) {
R_RETURN(resource::GetPeakResourceLimitValues(ResourceLimitGroup_Applet, out));
}
Result AtmosphereGetCurrentLimitInfo(s64 *out_cur_val, s64 *out_lim_val, u32 group, u32 resource) {
R_RETURN(resource::GetResourceLimitValues(out_cur_val, out_lim_val, static_cast<ResourceLimitGroup>(group), static_cast<svc::LimitableResource>(resource)));
}
Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size) {
R_RETURN(resource::BoostSystemMemoryResourceLimitForMitm(boost_size));
}
}

View File

@@ -48,13 +48,4 @@ namespace ams::pm::impl {
Result NotifyBootFinished();
Result GetBootFinishedEventHandle(os::NativeHandle *out);
/* Resource Limit API. */
Result BoostSystemMemoryResourceLimit(u64 boost_size);
Result BoostApplicationThreadResourceLimit();
Result BoostSystemThreadResourceLimit();
Result GetAppletCurrentResourceLimitValues(pm::ResourceLimitValues *out);
Result GetAppletPeakResourceLimitValues(pm::ResourceLimitValues *out);
Result AtmosphereGetCurrentLimitInfo(s64 *out_cur_val, s64 *out_lim_val, u32 group, u32 resource);
Result BoostSystemMemoryResourceLimitForMitm(u64 boost_size);
}

View File

@@ -0,0 +1,193 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "pm_process_tracker.hpp"
#include "pm_process_info.hpp"
#include "pm_spec.hpp"
namespace ams::pm::impl {
namespace {
/* Global process event. */
constinit os::SystemEventType g_process_event;
}
void ProcessTracker::Initialize(void *stack, size_t stack_size) {
/* Initialize our events. */
os::InitializeEvent(std::addressof(m_request_event), false, os::EventClearMode_AutoClear);
os::InitializeEvent(std::addressof(m_reply_event), false, os::EventClearMode_AutoClear);
/* Our process count should initially be zero. */
m_process_count = 0;
/* Create the process tracking thread. */
R_ABORT_UNLESS(os::CreateThread(std::addressof(m_thread), ProcessTracker::ThreadFunction, this, stack, stack_size, AMS_GET_SYSTEM_THREAD_PRIORITY(pm, ProcessTrack)));
os::SetThreadNamePointer(std::addressof(m_thread), AMS_GET_SYSTEM_THREAD_NAME(pm, ProcessTrack));
}
void ProcessTracker::StartThread() {
/* Start thread. */
os::StartThread(std::addressof(m_thread));
}
void ProcessTracker::ThreadBody() {
/* This is the main loop of the process tracking thread. */
/* Setup multi wait/holders. */
os::MultiWaitType process_multi_wait;
os::MultiWaitHolderType enqueue_event_holder;
os::InitializeMultiWait(std::addressof(process_multi_wait));
os::InitializeMultiWaitHolder(std::addressof(enqueue_event_holder), std::addressof(m_request_event));
os::LinkMultiWaitHolder(std::addressof(process_multi_wait), std::addressof(enqueue_event_holder));
while (true) {
auto signaled_holder = os::WaitAny(std::addressof(process_multi_wait));
if (signaled_holder == std::addressof(enqueue_event_holder)) {
/* TryWait will clear signaled, preventing duplicate notifications. */
if (os::TryWaitEvent(std::addressof(m_request_event))) {
/* Link the process to our multi-wait. */
os::LinkMultiWaitHolder(std::addressof(process_multi_wait), m_queued_process_info->GetMultiWaitHolder());
m_queued_process_info = nullptr;
/* Increment our process count. */
++m_process_count;
/* Reply. */
os::SignalEvent(std::addressof(m_reply_event));
}
} else {
/* Some process was signaled. */
this->OnProcessSignaled(reinterpret_cast<ProcessInfo *>(os::GetMultiWaitHolderUserData(signaled_holder)));
}
}
}
void ProcessTracker::QueueEntry(ProcessInfo *process_info) {
/* Lock ourselves. */
std::scoped_lock lk(m_mutex);
/* Request to enqueue the process info. */
m_queued_process_info = process_info;
os::SignalEvent(std::addressof(m_request_event));
/* Wait for acknowledgement. */
os::WaitEvent(std::addressof(m_reply_event));
}
void ProcessTracker::OnProcessSignaled(ProcessInfo *process_info) {
/* Get the process list. */
auto list = GetProcessList();
/* Reset the process's signal. */
svc::ResetSignal(process_info->GetHandle());
/* Update the process's state. */
const svc::ProcessState old_state = process_info->GetState();
{
s64 tmp = 0;
R_ABORT_UNLESS(svc::GetProcessInfo(std::addressof(tmp), process_info->GetHandle(), svc::ProcessInfoType_ProcessState));
process_info->SetState(static_cast<svc::ProcessState>(tmp));
}
const svc::ProcessState new_state = process_info->GetState();
/* If we're transitioning away from crashed, clear waiting attached. */
if (old_state == svc::ProcessState_Crashed && new_state != svc::ProcessState_Crashed) {
process_info->ClearExceptionWaitingAttach();
}
switch (new_state) {
case svc::ProcessState_Created:
case svc::ProcessState_CreatedAttached:
case svc::ProcessState_Terminating:
break;
case svc::ProcessState_Running:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->ClearSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
} else if (hos::GetVersion() >= hos::Version_2_0_0 && process_info->ShouldSignalOnStart()) {
process_info->SetStartedStateChanged();
process_info->ClearSignalOnStart();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->ClearUnhandledException();
break;
case svc::ProcessState_Crashed:
if (!process_info->HasUnhandledException()) {
process_info->SetExceptionOccurred();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->SetExceptionWaitingAttach();
break;
case svc::ProcessState_RunningAttached:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->ClearSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
}
process_info->ClearUnhandledException();
break;
case svc::ProcessState_Terminated:
/* Unlink from multi wait. */
os::UnlinkMultiWaitHolder(process_info->GetMultiWaitHolder());
/* Free process resources. */
process_info->Cleanup();
if (hos::GetVersion() < hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) {
os::SignalSystemEvent(std::addressof(g_process_event));
} else {
/* Handle the case where we need to keep the process alive some time longer. */
if (hos::GetVersion() >= hos::Version_5_0_0 && process_info->ShouldSignalOnExit()) {
/* Remove from the living list. */
list->Remove(process_info);
/* Add the process to the list of dead processes. */
{
GetExitList()->push_back(*process_info);
}
/* Signal. */
os::SignalSystemEvent(std::addressof(g_process_event));
} else {
/* Actually delete process. */
CleanupProcessInfo(list, process_info);
}
}
break;
case svc::ProcessState_DebugBreak:
if (process_info->ShouldSignalOnDebugEvent()) {
process_info->SetSuspended();
process_info->SetSuspendedStateChanged();
os::SignalSystemEvent(std::addressof(g_process_event));
}
break;
}
}
void CreateProcessEvent() {
/* Create process event. */
R_ABORT_UNLESS(os::CreateSystemEvent(std::addressof(g_process_event), os::EventClearMode_AutoClear, true));
}
Result GetProcessEventHandle(os::NativeHandle *out) {
*out = os::GetReadableHandleOfSystemEvent(std::addressof(g_process_event));
R_SUCCEED();
}
}

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stratosphere.hpp>
#include "pm_process_info.hpp"
namespace ams::pm::impl {
class ProcessTracker {
NON_COPYABLE(ProcessTracker);
NON_MOVEABLE(ProcessTracker);
private:
os::ThreadType m_thread;
os::EventType m_request_event;
os::EventType m_reply_event;
os::SdkMutex m_mutex;
ProcessInfo *m_queued_process_info;
util::Atomic<u32> m_process_count;
public:
constexpr ProcessTracker() : m_thread(), m_request_event(), m_reply_event(), m_mutex(), m_queued_process_info(nullptr), m_process_count(0) {
/* ... */
}
void Initialize(void *stack, size_t stack_size);
void StartThread();
void QueueEntry(ProcessInfo *process_info);
u32 GetProcessCount() const {
return m_process_count;
}
private:
void OnProcessSignaled(ProcessInfo *process_info);
private:
static void ThreadFunction(void *_this) {
static_cast<ProcessTracker *>(_this)->ThreadBody();
}
void ThreadBody();
};
void CreateProcessEvent();
}

View File

@@ -14,9 +14,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stratosphere.hpp>
#include "pm_resource_manager.hpp"
#include "pm_spec.hpp"
namespace ams::pm::resource {
namespace ams::pm::impl {
namespace {
@@ -170,10 +170,18 @@ namespace ams::pm::resource {
}
void WaitApplicationMemoryAvailable() {
/* Get firmware version. */
const auto fw_ver = hos::GetVersion();
/* On 15.0.0+, pm considers application memory to be available if there is exactly 96 MB outstanding. */
/* This is probably because this corresponds to the gameplay-recording memory. */
constexpr u64 AllowedUsedApplicationMemory = 96_MB;
/* Wait for memory to be available. */
u64 value = 0;
while (true) {
R_ABORT_UNLESS(svc::GetSystemInfo(&value, svc::SystemInfoType_UsedPhysicalMemorySize, svc::InvalidHandle, svc::PhysicalMemorySystemInfo_Application));
if (value == 0) {
if (value == 0 || (fw_ver >= hos::Version_15_0_0 && value == AllowedUsedApplicationMemory)) {
break;
}
os::SleepThread(TimeSpan::FromMilliSeconds(1));
@@ -201,8 +209,8 @@ namespace ams::pm::resource {
R_SUCCEED();
}
template<auto GetResourceLimitValueImpl>
ALWAYS_INLINE Result GetResourceLimitValuesImpl(ResourceLimitGroup group, pm::ResourceLimitValues *out) {
template<auto ImplFunction>
ALWAYS_INLINE Result GetResourceLimitValueImpl(pm::ResourceLimitValue *out, ResourceLimitGroup group) {
/* Sanity check group. */
AMS_ABORT_UNLESS(group < ResourceLimitGroup_Count);
@@ -211,11 +219,11 @@ namespace ams::pm::resource {
/* Get values. */
int64_t values[svc::LimitableResource_Count];
R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_PhysicalMemoryMax]), handle, svc::LimitableResource_PhysicalMemoryMax));
R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_ThreadCountMax]), handle, svc::LimitableResource_ThreadCountMax));
R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_EventCountMax]), handle, svc::LimitableResource_EventCountMax));
R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_TransferMemoryCountMax]), handle, svc::LimitableResource_TransferMemoryCountMax));
R_ABORT_UNLESS(GetResourceLimitValueImpl(std::addressof(values[svc::LimitableResource_SessionCountMax]), handle, svc::LimitableResource_SessionCountMax));
R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_PhysicalMemoryMax]), handle, svc::LimitableResource_PhysicalMemoryMax));
R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_ThreadCountMax]), handle, svc::LimitableResource_ThreadCountMax));
R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_EventCountMax]), handle, svc::LimitableResource_EventCountMax));
R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_TransferMemoryCountMax]), handle, svc::LimitableResource_TransferMemoryCountMax));
R_ABORT_UNLESS(ImplFunction(std::addressof(values[svc::LimitableResource_SessionCountMax]), handle, svc::LimitableResource_SessionCountMax));
/* Set to output. */
out->physical_memory = values[svc::LimitableResource_PhysicalMemoryMax];
@@ -270,8 +278,7 @@ namespace ams::pm::resource {
}
/* Resource API. */
Result InitializeResourceManager() {
Result InitializeSpec() {
/* Create resource limit handles. */
for (size_t i = 0; i < ResourceLimitGroup_Count; i++) {
if (i == ResourceLimitGroup_System) {
@@ -451,16 +458,16 @@ namespace ams::pm::resource {
}
}
Result GetCurrentResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out) {
R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitCurrentValue>(group, out));
Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) {
R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitCurrentValue>(out, group));
}
Result GetPeakResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out) {
R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitPeakValue>(group, out));
Result GetResourceLimitPeakValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) {
R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitPeakValue>(out, group));
}
Result GetLimitResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out) {
R_RETURN(GetResourceLimitValuesImpl<::ams::svc::GetResourceLimitLimitValue>(group, out));
Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group) {
R_RETURN(GetResourceLimitValueImpl<::ams::svc::GetResourceLimitLimitValue>(out, group));
}
Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource) {

View File

@@ -16,10 +16,10 @@
#pragma once
#include <stratosphere.hpp>
namespace ams::pm::resource {
namespace ams::pm::impl {
Result InitializeSpec();
/* Resource API. */
Result InitializeResourceManager();
Result BoostSystemMemoryResourceLimit(u64 boost_size);
Result BoostApplicationThreadResourceLimit();
Result BoostSystemThreadResourceLimit();
@@ -31,9 +31,9 @@ namespace ams::pm::resource {
void WaitResourceAvailable(const ldr::ProgramInfo *info);
Result GetCurrentResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out);
Result GetPeakResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out);
Result GetLimitResourceLimitValues(ResourceLimitGroup group, pm::ResourceLimitValues *out);
Result GetResourceLimitCurrentValue(pm::ResourceLimitValue *out, ResourceLimitGroup group);
Result GetResourceLimitPeakValue(pm::ResourceLimitValue *outm, ResourceLimitGroup group);
Result GetResourceLimitLimitValue(pm::ResourceLimitValue *out, ResourceLimitGroup group);
Result GetResourceLimitValues(s64 *out_cur, s64 *out_lim, ResourceLimitGroup group, svc::LimitableResource resource);

View File

@@ -21,7 +21,7 @@ namespace ams::pm {
namespace {
/* Global bootmode. */
BootMode g_boot_mode = BootMode::Normal;
constinit BootMode g_boot_mode = BootMode::Normal;
}

View File

@@ -16,6 +16,7 @@
#include <stratosphere.hpp>
#include "pm_debug_monitor_service.hpp"
#include "impl/pm_process_manager.hpp"
#include "impl/pm_spec.hpp"
namespace ams::pm {
@@ -76,7 +77,7 @@ namespace ams::pm {
}
Result DebugMonitorService::AtmosphereGetCurrentLimitInfo(sf::Out<s64> out_cur_val, sf::Out<s64> out_lim_val, u32 group, u32 resource) {
R_RETURN(impl::AtmosphereGetCurrentLimitInfo(out_cur_val.GetPointer(), out_lim_val.GetPointer(), group, resource));
R_RETURN(impl::GetResourceLimitValues(out_cur_val.GetPointer(), out_lim_val.GetPointer(), static_cast<ResourceLimitGroup>(group), static_cast<svc::LimitableResource>(resource)));
}
}

View File

@@ -16,6 +16,7 @@
#include <stratosphere.hpp>
#include "pm_info_service.hpp"
#include "impl/pm_process_manager.hpp"
#include "impl/pm_spec.hpp"
namespace ams::pm {
@@ -33,12 +34,12 @@ namespace ams::pm {
R_RETURN(impl::GetProgramId(out.GetPointer(), process_id));
}
Result InformationService::GetAppletCurrentResourceLimitValues(sf::Out<pm::ResourceLimitValues> out) {
R_RETURN(impl::GetAppletCurrentResourceLimitValues(out.GetPointer()));
Result InformationService::GetAppletResourceLimitCurrentValue(sf::Out<pm::ResourceLimitValue> out) {
R_RETURN(impl::GetResourceLimitCurrentValue(out.GetPointer(), ResourceLimitGroup_Applet));
}
Result InformationService::GetAppletPeakResourceLimitValues(sf::Out<pm::ResourceLimitValues> out) {
R_RETURN(impl::GetAppletPeakResourceLimitValues(out.GetPointer()));
Result InformationService::GetAppletResourceLimitPeakValue(sf::Out<pm::ResourceLimitValue> out) {
R_RETURN(impl::GetResourceLimitPeakValue(out.GetPointer(), ResourceLimitGroup_Applet));
}
/* Atmosphere extension commands. */

View File

@@ -22,8 +22,8 @@ namespace ams::pm {
public:
/* Actual command implementations. */
Result GetProgramId(sf::Out<ncm::ProgramId> out, os::ProcessId process_id);
Result GetAppletCurrentResourceLimitValues(sf::Out<pm::ResourceLimitValues> out);
Result GetAppletPeakResourceLimitValues(sf::Out<pm::ResourceLimitValues> out);
Result GetAppletResourceLimitCurrentValue(sf::Out<pm::ResourceLimitValue> out);
Result GetAppletResourceLimitPeakValue(sf::Out<pm::ResourceLimitValue> out);
/* Atmosphere extension commands. */
Result AtmosphereGetProcessId(sf::Out<os::ProcessId> out, ncm::ProgramId program_id);

View File

@@ -16,6 +16,7 @@
#include <stratosphere.hpp>
#include "pm_shell_service.hpp"
#include "impl/pm_process_manager.hpp"
#include "impl/pm_spec.hpp"
namespace ams::pm {

View File

@@ -437,7 +437,7 @@ namespace ams::sm::impl {
{
/* TODO: Convert mitm internal messaging to use tipc? */
::Service srv { .session = mitm_info->query_h };
R_ABORT_UNLESS(::serviceDispatchInOut(std::addressof(srv), 65000, client_info, should_mitm));
R_ABORT_UNLESS((serviceDispatchInOut(std::addressof(srv), 65000, client_info, should_mitm)));
}
/* If we shouldn't mitm, give normal session. */

View File

@@ -42,7 +42,10 @@ BUILD := build
SOURCES := source
DATA := data
INCLUDES := include ../../libraries/libvapours/include
#ROMFS := romfs
ROMFS := romfs
# Output folders for autogenerated files in romfs
OUT_SHADERS := shaders
APP_TITLE := USB File Transfer
APP_AUTHOR := Atmosphere-NX
@@ -63,7 +66,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++20
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lnx
LIBS := -ldeko3d -lnx -lm
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
@@ -90,6 +93,7 @@ export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
GLSLFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.glsl)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
@@ -155,19 +159,61 @@ ifneq ($(APP_TITLEID),)
export NACPFLAGS += --titleid=$(APP_TITLEID)
endif
ifneq ($(ROMFS),)
ifneq ($(strip $(ROMFS)),)
ROMFS_TARGETS :=
ROMFS_FOLDERS :=
ifneq ($(strip $(OUT_SHADERS)),)
ROMFS_SHADERS := $(ROMFS)/$(OUT_SHADERS)
ROMFS_TARGETS += $(patsubst %.glsl, $(ROMFS_SHADERS)/%.dksh, $(GLSLFILES))
ROMFS_FOLDERS += $(ROMFS_SHADERS)
endif
export ROMFS_DEPS := $(foreach file,$(ROMFS_TARGETS),$(CURDIR)/$(file))
export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
all: $(ROMFS_TARGETS) | $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
ifneq ($(strip $(ROMFS_TARGETS)),)
$(ROMFS_TARGETS): | $(ROMFS_FOLDERS)
$(ROMFS_FOLDERS):
@mkdir -p $@
$(ROMFS_SHADERS)/%_vsh.dksh: %_vsh.glsl
@echo {vert} $(notdir $<)
@uam -s vert -o $@ $<
$(ROMFS_SHADERS)/%_tcsh.dksh: %_tcsh.glsl
@echo {tess_ctrl} $(notdir $<)
@uam -s tess_ctrl -o $@ $<
$(ROMFS_SHADERS)/%_tesh.dksh: %_tesh.glsl
@echo {tess_eval} $(notdir $<)
@uam -s tess_eval -o $@ $<
$(ROMFS_SHADERS)/%_gsh.dksh: %_gsh.glsl
@echo {geom} $(notdir $<)
@uam -s geom -o $@ $<
$(ROMFS_SHADERS)/%_fsh.dksh: %_fsh.glsl
@echo {frag} $(notdir $<)
@uam -s frag -o $@ $<
$(ROMFS_SHADERS)/%.dksh: %.glsl
@echo {comp} $(notdir $<)
@uam -s comp -o $@ $<
endif
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@@ -192,9 +238,9 @@ ifeq ($(strip $(APP_JSON)),)
all : $(OUTPUT).nro
ifeq ($(strip $(NO_NACP)),)
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp
$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp $(ROMFS_DEPS)
else
$(OUTPUT).nro : $(OUTPUT).elf
$(OUTPUT).nro : $(OUTPUT).elf $(ROMFS_DEPS)
endif
else

View File

@@ -42,6 +42,4 @@ namespace haze {
using Result = ::ams::Result;
static constexpr u32 UsbBulkPacketBufferSize = 1_MB;
}

View File

@@ -143,7 +143,7 @@ namespace haze {
if (m_is_applet_mode) {
/* Print "Applet Mode" in red text. */
printf("\n" CONSOLE_ESC(38;5;196m) "Applet Mode" CONSOLE_ESC(0m) "\n");
printf("\n" CONSOLE_ESC(31;1m) "Applet Mode" CONSOLE_ESC(0m) "\n");
}
consoleUpdate(nullptr);

View File

@@ -72,6 +72,11 @@ namespace haze {
PtpOperationCode_GetFilesystemManifest = 0x1023,
PtpOperationCode_GetStreamInfo = 0x1024,
PtpOperationCode_GetStream = 0x1025,
PtpOperationCode_AndroidGetPartialObject64 = 0x95c1,
PtpOperationCode_AndroidSendPartialObject = 0x95c2,
PtpOperationCode_AndroidTruncateObject = 0x95c3,
PtpOperationCode_AndroidBeginEditObject = 0x95c4,
PtpOperationCode_AndroidEndEditObject = 0x95c5,
PtpOperationCode_MtpGetObjectPropsSupported = 0x9801,
PtpOperationCode_MtpGetObjectPropDesc = 0x9802,
PtpOperationCode_MtpGetObjectPropValue = 0x9803,

View File

@@ -111,6 +111,14 @@ namespace haze {
}
constexpr void Deallocate(void *p, size_t n) {
/* Check for overflow in alignment. */
if (!util::CanAddWithoutOverflow(n, alignof(u64) - 1)) {
return;
}
/* Align the amount to satisfy allocation for u64. */
n = util::AlignUp(n, alignof(u64));
/* If the pointer was the last allocation, return the memory to the heap. */
if (static_cast<u8 *>(p) + n == this->GetNextAddress()) {
m_next_address = this->GetNextAddress() - n;

View File

@@ -19,6 +19,7 @@
#include <haze/async_usb_server.hpp>
#include <haze/ptp_object_heap.hpp>
#include <haze/ptp_object_database.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
@@ -30,12 +31,13 @@ namespace haze {
FileSystemProxy m_fs;
PtpUsbBulkContainer m_request_header;
PtpObjectHeap *m_object_heap;
PtpBuffers* m_buffers;
u32 m_send_object_id;
bool m_session_open;
PtpObjectDatabase m_object_database;
public:
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
constexpr explicit PtpResponder() : m_usb_server(), m_fs(), m_request_header(), m_object_heap(), m_buffers(), m_send_object_id(), m_session_open(), m_object_database() { /* ... */ }
Result Initialize(EventReactor *reactor, PtpObjectHeap *object_heap);
void Finalize();
@@ -48,10 +50,14 @@ namespace haze {
Result HandleCommandRequest(PtpDataParser &dp);
void ForceCloseSession();
template <typename Data>
Result WriteResponse(PtpResponseCode code, Data &&data);
Result WriteResponse(PtpResponseCode code, const void* data, size_t size);
Result WriteResponse(PtpResponseCode code);
template <typename Data> requires (util::is_pod<Data>::value)
Result WriteResponse(PtpResponseCode code, const Data &data) {
R_RETURN(this->WriteResponse(code, std::addressof(data), sizeof(data)));
}
/* PTP operations. */
Result GetDeviceInfo(PtpDataParser &dp);
Result OpenSession(PtpDataParser &dp);
@@ -65,11 +71,19 @@ namespace haze {
Result SendObject(PtpDataParser &dp);
Result DeleteObject(PtpDataParser &dp);
/* Android operations. */
Result GetPartialObject64(PtpDataParser &dp);
Result SendPartialObject(PtpDataParser &dp);
Result TruncateObject(PtpDataParser &dp);
Result BeginEditObject(PtpDataParser &dp);
Result EndEditObject(PtpDataParser &dp);
/* MTP operations. */
Result GetObjectPropsSupported(PtpDataParser &dp);
Result GetObjectPropDesc(PtpDataParser &dp);
Result GetObjectPropValue(PtpDataParser &dp);
Result SetObjectPropValue(PtpDataParser &dp);
Result GetObjectPropList(PtpDataParser &dp);
};
}

View File

@@ -0,0 +1,183 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <haze.hpp>
namespace haze {
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
.bInterfaceClass = 0x06,
.bInterfaceSubClass = 0x01,
.bInterfaceProtocol = 0x01,
};
/* This is a VID:PID recognized by libmtp. */
constexpr u16 SwitchMtpIdVendor = 0x057e;
constexpr u16 SwitchMtpIdProduct = 0x201d;
/* Constants used for MTP GetDeviceInfo response. */
constexpr u16 MtpStandardVersion = 100;
constexpr u32 MtpVendorExtensionId = 6;
constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;";
constexpr u16 MtpFunctionalModeDefault = 0;
constexpr auto MtpDeviceManufacturer = "Nintendo";
constexpr auto MtpDeviceModel = "Nintendo Switch";
enum StorageId : u32 {
StorageId_SdmcFs = 0xffffffffu - 1,
};
constexpr PtpOperationCode SupportedOperationCodes[] = {
PtpOperationCode_GetDeviceInfo,
PtpOperationCode_OpenSession,
PtpOperationCode_CloseSession,
PtpOperationCode_GetStorageIds,
PtpOperationCode_GetStorageInfo,
PtpOperationCode_GetObjectHandles,
PtpOperationCode_GetObjectInfo,
PtpOperationCode_GetObject,
PtpOperationCode_SendObjectInfo,
PtpOperationCode_SendObject,
PtpOperationCode_DeleteObject,
PtpOperationCode_MtpGetObjectPropsSupported,
PtpOperationCode_MtpGetObjectPropDesc,
PtpOperationCode_MtpGetObjectPropValue,
PtpOperationCode_MtpSetObjectPropValue,
PtpOperationCode_MtpGetObjPropList,
PtpOperationCode_AndroidGetPartialObject64,
PtpOperationCode_AndroidSendPartialObject,
PtpOperationCode_AndroidTruncateObject,
PtpOperationCode_AndroidBeginEditObject,
PtpOperationCode_AndroidEndEditObject,
};
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = {
PtpObjectFormatCode_Undefined,
PtpObjectFormatCode_Association,
};
constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = {
PtpObjectPropertyCode_StorageId,
PtpObjectPropertyCode_ObjectFormat,
PtpObjectPropertyCode_ObjectSize,
PtpObjectPropertyCode_ObjectFileName,
PtpObjectPropertyCode_ParentObject,
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier,
};
constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) {
for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) {
if (SupportedObjectProperties[i] == c) {
return true;
}
}
return false;
}
constexpr const StorageId SupportedStorageIds[] = {
StorageId_SdmcFs,
};
struct PtpStorageInfo {
PtpStorageType storage_type;
PtpFilesystemType filesystem_type;
PtpAccessCapability access_capability;
u64 max_capacity;
u64 free_space_in_bytes;
u32 free_space_in_images;
const char *storage_description;
const char *volume_label;
};
constexpr PtpStorageInfo DefaultStorageInfo = {
.storage_type = PtpStorageType_FixedRam,
.filesystem_type = PtpFilesystemType_GenericHierarchical,
.access_capability = PtpAccessCapability_ReadWrite,
.max_capacity = 0,
.free_space_in_bytes = 0,
.free_space_in_images = 0,
.storage_description = "",
.volume_label = "",
};
struct PtpObjectInfo {
StorageId storage_id;
PtpObjectFormatCode object_format;
PtpProtectionStatus protection_status;
u32 object_compressed_size;
u16 thumb_format;
u32 thumb_compressed_size;
u32 thumb_width;
u32 thumb_height;
u32 image_width;
u32 image_height;
u32 image_depth;
u32 parent_object;
PtpAssociationType association_type;
u32 association_desc;
u32 sequence_number;
const char *filename;
const char *capture_date;
const char *modification_date;
const char *keywords;
};
constexpr PtpObjectInfo DefaultObjectInfo = {
.storage_id = StorageId_SdmcFs,
.object_format = {},
.protection_status = PtpProtectionStatus_NoProtection,
.object_compressed_size = 0,
.thumb_format = 0,
.thumb_compressed_size = 0,
.thumb_width = 0,
.thumb_height = 0,
.image_width = 0,
.image_height = 0,
.image_depth = 0,
.parent_object = PtpGetObjectHandles_RootParent,
.association_type = PtpAssociationType_Undefined,
.association_desc = 0,
.sequence_number = 0,
.filename = nullptr,
.capture_date = "",
.modification_date = "",
.keywords = "",
};
constexpr u32 UsbBulkPacketBufferSize = 1_MB;
constexpr u64 FsBufferSize = UsbBulkPacketBufferSize;
constexpr s64 DirectoryReadSize = 32;
struct PtpBuffers {
char filename_string_buffer[PtpStringMaxLength + 1];
char capture_date_string_buffer[PtpStringMaxLength + 1];
char modification_date_string_buffer[PtpStringMaxLength + 1];
char keywords_string_buffer[PtpStringMaxLength + 1];
FsDirectoryEntry file_system_entry_buffer[DirectoryReadSize];
u8 file_system_data_buffer[FsBufferSize];
alignas(4_KB) u8 usb_bulk_write_buffer[UsbBulkPacketBufferSize];
alignas(4_KB) u8 usb_bulk_read_buffer[UsbBulkPacketBufferSize];
};
}

View File

@@ -37,5 +37,8 @@ namespace haze {
R_DEFINE_ERROR_RESULT(UnknownRequestType, 13);
R_DEFINE_ERROR_RESULT(UnknownPropertyCode, 14);
R_DEFINE_ERROR_RESULT(InvalidPropertyValue, 15);
R_DEFINE_ERROR_RESULT(InvalidArgument, 16);
R_DEFINE_ERROR_RESULT(GroupSpecified, 17);
R_DEFINE_ERROR_RESULT(DepthSpecified, 18);
}

View File

@@ -0,0 +1,15 @@
#version 460
layout (location = 0) noperspective in vec3 inTexCoord;
layout (location = 1) flat in vec4 inFrontPal;
layout (location = 2) flat in vec4 inBackPal;
layout (location = 0) out vec4 outColor;
layout (binding = 0) uniform sampler2DArray tileset;
void main()
{
float value = texture(tileset, inTexCoord).r;
outColor = mix(inBackPal, inFrontPal, value);
}

View File

@@ -0,0 +1,35 @@
#version 460
layout (location = 0) in float inTileId;
layout (location = 1) in uvec2 inColorId;
layout (location = 0) out vec3 outTexCoord;
layout (location = 1) out vec4 outFrontPal;
layout (location = 2) out vec4 outBackPal;
layout (std140, binding = 0) uniform Config
{
vec4 dimensions;
vec4 vertices[3];
vec4 palettes[24];
} u;
void main()
{
float id = float(gl_InstanceID);
float tileRow = floor(id / u.dimensions.z);
float tileCol = id - tileRow * u.dimensions.z;
vec2 basePos;
basePos.x = 2.0 * (tileCol + 0.5) / u.dimensions.z - 1.0;
basePos.y = 2.0 * (1.0 - (tileRow + 0.5) / u.dimensions.w) - 1.0;
vec2 vtxData = u.vertices[gl_VertexID].xy;
vec2 scale = vec2(1.0) / u.dimensions.zw;
gl_Position.xy = vtxData * scale + basePos;
gl_Position.zw = vec2(0.5, 1.0);
outTexCoord = vec3(u.vertices[gl_VertexID].zw, inTileId);
outFrontPal = u.palettes[inColorId.x];
outBackPal = u.palettes[inColorId.y];
}

View File

@@ -0,0 +1,487 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/iosupport.h>
#include <switch.h>
#include <deko3d.h>
// Define the desired number of framebuffers
#define FB_NUM 2
// Define the size of the memory block that will hold code
#define CODEMEMSIZE (64*1024)
// Define the size of the memory block that will hold command lists
#define CMDMEMSIZE (64*1024)
#define NUM_IMAGE_SLOTS 1
#define NUM_SAMPLER_SLOTS 1
typedef struct {
float pos[2];
float tex[2];
} VertexDef;
typedef struct {
float red;
float green;
float blue;
float alpha;
} PaletteColor;
typedef struct {
float dimensions[4];
VertexDef vertices[3];
PaletteColor palettes[24];
} ConsoleConfig;
static const VertexDef g_vertexData[3] = {
{ { 0.0f, +1.0f }, { 0.5f, 0.0f, } },
{ { -1.0f, -1.0f }, { 0.0f, 1.0f, } },
{ { +1.0f, -1.0f }, { 1.0f, 1.0f, } },
};
static const PaletteColor g_paletteData[24] = {
{ 0.0f, 0.0f, 0.0f, 0.0f }, // black
{ 0.5f, 0.0f, 0.0f, 1.0f }, // red
{ 0.0f, 0.5f, 0.0f, 1.0f }, // green
{ 0.5f, 0.5f, 0.0f, 1.0f }, // yellow
{ 0.0f, 0.0f, 0.5f, 1.0f }, // blue
{ 0.5f, 0.0f, 0.5f, 1.0f }, // magenta
{ 0.0f, 0.5f, 0.5f, 1.0f }, // cyan
{ 0.75f, 0.75f, 0.75f, 1.0f }, // white
{ 0.5f, 0.5f, 0.5f, 1.0f }, // bright black
{ 1.0f, 0.0f, 0.0f, 1.0f }, // bright red
{ 0.0f, 1.0f, 0.0f, 1.0f }, // bright green
{ 1.0f, 1.0f, 0.0f, 1.0f }, // bright yellow
{ 0.0f, 0.0f, 1.0f, 1.0f }, // bright blue
{ 1.0f, 0.0f, 1.0f, 1.0f }, // bright magenta
{ 0.0f, 1.0f, 1.0f, 1.0f }, // bright cyan
{ 1.0f, 1.0f, 1.0f, 1.0f }, // bright white
{ 0.0f, 0.0f, 0.0f, 0.0f }, // faint black
{ 0.25f, 0.0f, 0.0f, 1.0f }, // faint red
{ 0.0f, 0.25f, 0.0f, 1.0f }, // faint green
{ 0.25f, 0.25f, 0.0f, 1.0f }, // faint yellow
{ 0.0f, 0.0f, 0.25f, 1.0f }, // faint blue
{ 0.25f, 0.0f, 0.25f, 1.0f }, // faint magenta
{ 0.0f, 0.25f, 0.25f, 1.0f }, // faint cyan
{ 0.375f, 0.375f, 0.375f, 1.0f }, // faint white
};
typedef struct {
uint16_t tileId;
uint8_t frontPal;
uint8_t backPal;
} ConsoleChar;
static const DkVtxAttribState g_attribState[] = {
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,tileId), .size=DkVtxAttribSize_1x16, .type=DkVtxAttribType_Uscaled, .isBgra=0 },
{ .bufferId=0, .isFixed=0, .offset=offsetof(ConsoleChar,frontPal), .size=DkVtxAttribSize_2x8, .type=DkVtxAttribType_Uint, .isBgra=0 },
};
static const DkVtxBufferState g_vtxbufState[] = {
{ .stride=sizeof(ConsoleChar), .divisor=1 },
};
struct GpuRenderer {
ConsoleRenderer base;
bool initialized;
DkDevice device;
DkQueue queue;
DkMemBlock imageMemBlock;
DkMemBlock codeMemBlock;
DkMemBlock dataMemBlock;
DkSwapchain swapchain;
DkImage framebuffers[FB_NUM];
DkImage tileset;
ConsoleChar* charBuf;
uint32_t codeMemOffset;
DkShader vertexShader;
DkShader fragmentShader;
DkCmdBuf cmdbuf;
DkCmdList cmdsBindFramebuffer[FB_NUM];
DkCmdList cmdsRender;
DkFence lastRenderFence;
};
static struct GpuRenderer* GpuRenderer(PrintConsole* con)
{
return (struct GpuRenderer*)con->renderer;
}
static void GpuRenderer_destroy(struct GpuRenderer* r)
{
// Make sure the queue is idle before destroying anything
dkQueueWaitIdle(r->queue);
// Destroy all the resources we've created
dkQueueDestroy(r->queue);
dkCmdBufDestroy(r->cmdbuf);
dkSwapchainDestroy(r->swapchain);
dkMemBlockDestroy(r->dataMemBlock);
dkMemBlockDestroy(r->codeMemBlock);
dkMemBlockDestroy(r->imageMemBlock);
dkDeviceDestroy(r->device);
// Clear out all state
memset(&r->initialized, 0, sizeof(*r) - offsetof(struct GpuRenderer, initialized));
}
// Simple function for loading a shader from the filesystem
static void GpuRenderer_loadShader(struct GpuRenderer* r, DkShader* pShader, const char* path)
{
// Open the file, and retrieve its size
FILE* f = fopen(path, "rb");
fseek(f, 0, SEEK_END);
uint32_t size = ftell(f);
rewind(f);
// Look for a spot in the code memory block for loading this shader. Note that
// we are just using a simple incremental offset; this isn't a general purpose
// allocation algorithm.
uint32_t codeOffset = r->codeMemOffset;
r->codeMemOffset += (size + DK_SHADER_CODE_ALIGNMENT - 1) &~ (DK_SHADER_CODE_ALIGNMENT - 1);
// Read the file into memory, and close the file
fread((uint8_t*)dkMemBlockGetCpuAddr(r->codeMemBlock) + codeOffset, size, 1, f);
fclose(f);
// Initialize the user provided shader object with the code we've just loaded
DkShaderMaker shaderMaker;
dkShaderMakerDefaults(&shaderMaker, r->codeMemBlock, codeOffset);
dkShaderInitialize(pShader, &shaderMaker);
}
static bool GpuRenderer_init(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
if (r->initialized) {
// We're already initialized
return true;
}
// Create the deko3d device, which is the root object
DkDeviceMaker deviceMaker;
dkDeviceMakerDefaults(&deviceMaker);
r->device = dkDeviceCreate(&deviceMaker);
// Create the queue
DkQueueMaker queueMaker;
dkQueueMakerDefaults(&queueMaker, r->device);
queueMaker.flags = DkQueueFlags_Graphics;
r->queue = dkQueueCreate(&queueMaker);
// Calculate required width/height for the framebuffers
u32 width = con->font.tileWidth * con->consoleWidth;
u32 height = con->font.tileHeight * con->consoleHeight;
u32 totalConSize = con->consoleWidth * con->consoleHeight;
// Calculate layout for the framebuffers
DkImageLayoutMaker imageLayoutMaker;
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
imageLayoutMaker.flags = DkImageFlags_UsageRender | DkImageFlags_UsagePresent | DkImageFlags_HwCompression;
imageLayoutMaker.format = DkImageFormat_RGBA8_Unorm;
imageLayoutMaker.dimensions[0] = width;
imageLayoutMaker.dimensions[1] = height;
// Calculate layout for the framebuffers
DkImageLayout framebufferLayout;
dkImageLayoutInitialize(&framebufferLayout, &imageLayoutMaker);
// Calculate layout for the tileset
dkImageLayoutMakerDefaults(&imageLayoutMaker, r->device);
imageLayoutMaker.type = DkImageType_2DArray;
imageLayoutMaker.format = DkImageFormat_R32_Float;
imageLayoutMaker.dimensions[0] = con->font.tileWidth;
imageLayoutMaker.dimensions[1] = con->font.tileHeight;
imageLayoutMaker.dimensions[2] = con->font.numChars;
// Calculate layout for the tileset
DkImageLayout tilesetLayout;
dkImageLayoutInitialize(&tilesetLayout, &imageLayoutMaker);
// Retrieve necessary size and alignment for the framebuffers
uint32_t framebufferSize = dkImageLayoutGetSize(&framebufferLayout);
uint32_t framebufferAlign = dkImageLayoutGetAlignment(&framebufferLayout);
framebufferSize = (framebufferSize + framebufferAlign - 1) &~ (framebufferAlign - 1);
// Retrieve necessary size and alignment for the tileset
uint32_t tilesetSize = dkImageLayoutGetSize(&tilesetLayout);
uint32_t tilesetAlign = dkImageLayoutGetAlignment(&tilesetLayout);
tilesetSize = (tilesetSize + tilesetAlign - 1) &~ (tilesetAlign - 1);
// Create a memory block that will host the framebuffers and the tileset
DkMemBlockMaker memBlockMaker;
dkMemBlockMakerDefaults(&memBlockMaker, r->device, FB_NUM*framebufferSize + tilesetSize);
memBlockMaker.flags = DkMemBlockFlags_GpuCached | DkMemBlockFlags_Image;
r->imageMemBlock = dkMemBlockCreate(&memBlockMaker);
// Initialize the framebuffers with the layout and backing memory we've just created
DkImage const* swapchainImages[FB_NUM];
for (unsigned i = 0; i < FB_NUM; i ++) {
swapchainImages[i] = &r->framebuffers[i];
dkImageInitialize(&r->framebuffers[i], &framebufferLayout, r->imageMemBlock, i*framebufferSize);
}
// Create a swapchain out of the framebuffers we've just initialized
DkSwapchainMaker swapchainMaker;
dkSwapchainMakerDefaults(&swapchainMaker, r->device, nwindowGetDefault(), swapchainImages, FB_NUM);
r->swapchain = dkSwapchainCreate(&swapchainMaker);
// Initialize the tileset
dkImageInitialize(&r->tileset, &tilesetLayout, r->imageMemBlock, FB_NUM*framebufferSize);
// Create a memory block onto which we will load shader code
dkMemBlockMakerDefaults(&memBlockMaker, r->device, CODEMEMSIZE);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached | DkMemBlockFlags_Code;
r->codeMemBlock = dkMemBlockCreate(&memBlockMaker);
r->codeMemOffset = 0;
// Load our shaders (both vertex and fragment)
romfsInit();
GpuRenderer_loadShader(r, &r->vertexShader, "romfs:/shaders/console_vsh.dksh");
GpuRenderer_loadShader(r, &r->fragmentShader, "romfs:/shaders/console_fsh.dksh");
// Generate the descriptors
struct {
DkImageDescriptor images[NUM_IMAGE_SLOTS];
DkSamplerDescriptor samplers[NUM_SAMPLER_SLOTS];
} descriptors;
// Generate a image descriptor for the tileset
DkImageView tilesetView;
dkImageViewDefaults(&tilesetView, &r->tileset);
dkImageDescriptorInitialize(&descriptors.images[0], &tilesetView, false, false);
// Generate a sampler descriptor for the tileset
DkSampler sampler;
dkSamplerDefaults(&sampler);
sampler.wrapMode[0] = DkWrapMode_ClampToEdge;
sampler.wrapMode[1] = DkWrapMode_ClampToEdge;
sampler.minFilter = DkFilter_Nearest;
sampler.magFilter = DkFilter_Nearest;
dkSamplerDescriptorInitialize(&descriptors.samplers[0], &sampler);
uint32_t descriptorsOffset = CMDMEMSIZE;
uint32_t configOffset = (descriptorsOffset + sizeof(descriptors) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
uint32_t configSize = (sizeof(ConsoleConfig) + DK_UNIFORM_BUF_ALIGNMENT - 1) &~ (DK_UNIFORM_BUF_ALIGNMENT - 1);
uint32_t charBufOffset = configOffset + configSize;
uint32_t charBufSize = totalConSize * sizeof(ConsoleChar);
// Create a memory block which will be used for recording command lists using a command buffer
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
(charBufOffset + charBufSize + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
r->dataMemBlock = dkMemBlockCreate(&memBlockMaker);
// Create a command buffer object
DkCmdBufMaker cmdbufMaker;
dkCmdBufMakerDefaults(&cmdbufMaker, r->device);
r->cmdbuf = dkCmdBufCreate(&cmdbufMaker);
// Feed our memory to the command buffer so that we can start recording commands
dkCmdBufAddMemory(r->cmdbuf, r->dataMemBlock, 0, CMDMEMSIZE);
// Create a temporary buffer that will hold the tileset
dkMemBlockMakerDefaults(&memBlockMaker, r->device,
(sizeof(float)*con->font.tileWidth*con->font.tileHeight*con->font.numChars + DK_MEMBLOCK_ALIGNMENT - 1) &~ (DK_MEMBLOCK_ALIGNMENT - 1)
);
memBlockMaker.flags = DkMemBlockFlags_CpuUncached | DkMemBlockFlags_GpuCached;
DkMemBlock scratchMemBlock = dkMemBlockCreate(&memBlockMaker);
float* scratchMem = (float*)dkMemBlockGetCpuAddr(scratchMemBlock);
// Unpack 1bpp tileset into a texture image the GPU can read
unsigned packedTileWidth = (con->font.tileWidth+7)/8;
for (unsigned tile = 0; tile < con->font.numChars; tile ++) {
const uint8_t* data = (const uint8_t*)con->font.gfx + con->font.tileHeight*packedTileWidth*tile;
for (unsigned y = 0; y < con->font.tileHeight; y ++) {
const uint8_t* row = &data[packedTileWidth*(y+1)];
uint8_t c = 0;
for (unsigned x = 0; x < con->font.tileWidth; x ++) {
if (!(x & 7))
c = *--row;
*scratchMem++ = (c & 0x80) ? 1.0f : 0.0f;
c <<= 1;
}
}
}
// Set up configuration
DkGpuAddr configAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + configOffset;
ConsoleConfig consoleConfig = {};
consoleConfig.dimensions[0] = width;
consoleConfig.dimensions[1] = height;
consoleConfig.dimensions[2] = con->consoleWidth;
consoleConfig.dimensions[3] = con->consoleHeight;
memcpy(consoleConfig.vertices, g_vertexData, sizeof(g_vertexData));
memcpy(consoleConfig.palettes, g_paletteData, sizeof(g_paletteData));
// Generate a temporary command list for uploading stuff and run it
DkGpuAddr descriptorSet = dkMemBlockGetGpuAddr(r->dataMemBlock) + descriptorsOffset;
DkCopyBuf copySrc = { dkMemBlockGetGpuAddr(scratchMemBlock), 0, 0 };
DkImageRect copyDst = { 0, 0, 0, con->font.tileWidth, con->font.tileHeight, con->font.numChars };
dkCmdBufPushData(r->cmdbuf, descriptorSet, &descriptors, sizeof(descriptors));
dkCmdBufPushConstants(r->cmdbuf, configAddr, configSize, 0, sizeof(consoleConfig), &consoleConfig);
dkCmdBufBindImageDescriptorSet(r->cmdbuf, descriptorSet, NUM_IMAGE_SLOTS);
dkCmdBufBindSamplerDescriptorSet(r->cmdbuf, descriptorSet + NUM_IMAGE_SLOTS*sizeof(DkImageDescriptor), NUM_SAMPLER_SLOTS);
dkCmdBufCopyBufferToImage(r->cmdbuf, &copySrc, &tilesetView, &copyDst, 0);
dkQueueSubmitCommands(r->queue, dkCmdBufFinishList(r->cmdbuf));
dkQueueFlush(r->queue);
dkQueueWaitIdle(r->queue);
dkCmdBufClear(r->cmdbuf);
// Destroy the scratch memory block since we don't need it anymore
dkMemBlockDestroy(scratchMemBlock);
// Retrieve the address of the character buffer
DkGpuAddr charBufAddr = dkMemBlockGetGpuAddr(r->dataMemBlock) + charBufOffset;
r->charBuf = (ConsoleChar*)((uint8_t*)dkMemBlockGetCpuAddr(r->dataMemBlock) + charBufOffset);
memset(r->charBuf, 0, charBufSize);
// Generate a command list for each framebuffer, which will bind each of them as a render target
for (unsigned i = 0; i < FB_NUM; i ++) {
DkImageView imageView;
dkImageViewDefaults(&imageView, &r->framebuffers[i]);
dkCmdBufBindRenderTarget(r->cmdbuf, &imageView, NULL);
r->cmdsBindFramebuffer[i] = dkCmdBufFinishList(r->cmdbuf);
}
// Declare structs that will be used for binding state
DkViewport viewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
DkScissor scissor = { 0, 0, width, height };
DkShader const* shaders[] = { &r->vertexShader, &r->fragmentShader };
DkRasterizerState rasterizerState;
DkColorState colorState;
DkColorWriteState colorWriteState;
// Initialize state structs with the deko3d defaults
dkRasterizerStateDefaults(&rasterizerState);
dkColorStateDefaults(&colorState);
dkColorWriteStateDefaults(&colorWriteState);
rasterizerState.fillRectangleEnable = true;
colorState.alphaCompareOp = DkCompareOp_Greater;
// Generate the main rendering command list
dkCmdBufSetViewports(r->cmdbuf, 0, &viewport, 1);
dkCmdBufSetScissors(r->cmdbuf, 0, &scissor, 1);
//dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.125f, 0.294f, 0.478f, 0.0f);
dkCmdBufClearColorFloat(r->cmdbuf, 0, DkColorMask_RGBA, 0.0f, 0.0f, 0.0f, 0.0f);
dkCmdBufBindShaders(r->cmdbuf, DkStageFlag_GraphicsMask, shaders, sizeof(shaders)/sizeof(shaders[0]));
dkCmdBufBindRasterizerState(r->cmdbuf, &rasterizerState);
dkCmdBufBindColorState(r->cmdbuf, &colorState);
dkCmdBufBindColorWriteState(r->cmdbuf, &colorWriteState);
dkCmdBufBindUniformBuffer(r->cmdbuf, DkStage_Vertex, 0, configAddr, configSize);
dkCmdBufBindTexture(r->cmdbuf, DkStage_Fragment, 0, dkMakeTextureHandle(0, 0));
dkCmdBufBindVtxAttribState(r->cmdbuf, g_attribState, sizeof(g_attribState)/sizeof(g_attribState[0]));
dkCmdBufBindVtxBufferState(r->cmdbuf, g_vtxbufState, sizeof(g_vtxbufState)/sizeof(g_vtxbufState[0]));
dkCmdBufBindVtxBuffer(r->cmdbuf, 0, charBufAddr, charBufSize);
dkCmdBufSetAlphaRef(r->cmdbuf, 0.0f);
dkCmdBufDraw(r->cmdbuf, DkPrimitive_Triangles, 3, totalConSize, 0, 0);
r->cmdsRender = dkCmdBufFinishList(r->cmdbuf);
r->initialized = true;
return true;
}
static void GpuRenderer_deinit(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
if (r->initialized) {
GpuRenderer_destroy(r);
}
}
static void GpuRenderer_drawChar(PrintConsole* con, int x, int y, int c)
{
struct GpuRenderer* r = GpuRenderer(con);
int writingColor = con->fg;
int screenColor = con->bg;
if (con->flags & CONSOLE_COLOR_BOLD) {
writingColor += 8;
} else if (con->flags & CONSOLE_COLOR_FAINT) {
writingColor += 16;
}
if (con->flags & CONSOLE_COLOR_REVERSE) {
int tmp = writingColor;
writingColor = screenColor;
screenColor = tmp;
}
// Wait for the fence
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
ConsoleChar* pos = &r->charBuf[y*con->consoleWidth+x];
pos->tileId = c;
pos->frontPal = writingColor;
pos->backPal = screenColor;
}
static void GpuRenderer_scrollWindow(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
// Wait for the fence
dkFenceWait(&r->lastRenderFence, UINT64_MAX);
// Perform the scrolling
for (int y = 0; y < con->windowHeight-1; y ++) {
memcpy(
&r->charBuf[(con->windowY+y+0)*con->consoleWidth + con->windowX],
&r->charBuf[(con->windowY+y+1)*con->consoleWidth + con->windowX],
sizeof(ConsoleChar)*con->windowWidth);
}
}
static void GpuRenderer_flushAndSwap(PrintConsole* con)
{
struct GpuRenderer* r = GpuRenderer(con);
// Acquire a framebuffer from the swapchain (and wait for it to be available)
int slot = dkQueueAcquireImage(r->queue, r->swapchain);
// Run the command list that binds said framebuffer as a render target
dkQueueSubmitCommands(r->queue, r->cmdsBindFramebuffer[slot]);
// Run the main rendering command list
dkQueueSubmitCommands(r->queue, r->cmdsRender);
// Signal the fence
dkQueueSignalFence(r->queue, &r->lastRenderFence, false);
// Now that we are done rendering, present it to the screen
dkQueuePresentImage(r->queue, r->swapchain, slot);
}
static struct GpuRenderer s_gpuRenderer =
{
{
GpuRenderer_init,
GpuRenderer_deinit,
GpuRenderer_drawChar,
GpuRenderer_scrollWindow,
GpuRenderer_flushAndSwap,
}
};
ConsoleRenderer* getDefaultConsoleRenderer(void)
{
return &s_gpuRenderer.base;
}

View File

@@ -19,8 +19,8 @@ namespace haze {
namespace {
/* Allow 20MiB for use by libnx. */
static constexpr size_t LibnxReservedMemorySize = 20_MB;
/* Allow 30MiB for use by libnx. */
static constexpr size_t LibnxReservedMemorySize = 30_MB;
}

View File

@@ -16,167 +16,22 @@
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
namespace {
constexpr UsbCommsInterfaceInfo MtpInterfaceInfo = {
.bInterfaceClass = 0x06,
.bInterfaceSubClass = 0x01,
.bInterfaceProtocol = 0x01,
};
/* This is a VID:PID recognized by libmtp. */
constexpr u16 SwitchMtpIdVendor = 0x057e;
constexpr u16 SwitchMtpIdProduct = 0x201d;
/* Constants used for MTP GetDeviceInfo response. */
constexpr u16 MtpStandardVersion = 100;
constexpr u32 MtpVendorExtensionId = 6;
constexpr auto MtpVendorExtensionDesc = "microsoft.com: 1.0;";
constexpr u16 MtpFunctionalModeDefault = 0;
constexpr auto MtpDeviceManufacturer = "Nintendo";
constexpr auto MtpDeviceModel = "Nintendo Switch";
enum StorageId : u32 {
StorageId_SdmcFs = 0xffffffffu - 1,
};
constexpr PtpOperationCode SupportedOperationCodes[] = {
PtpOperationCode_GetDeviceInfo,
PtpOperationCode_OpenSession,
PtpOperationCode_CloseSession,
PtpOperationCode_GetStorageIds,
PtpOperationCode_GetStorageInfo,
PtpOperationCode_GetObjectHandles,
PtpOperationCode_GetObjectInfo,
PtpOperationCode_GetObject,
PtpOperationCode_SendObjectInfo,
PtpOperationCode_SendObject,
PtpOperationCode_DeleteObject,
PtpOperationCode_MtpGetObjectPropsSupported,
PtpOperationCode_MtpGetObjectPropDesc,
PtpOperationCode_MtpGetObjectPropValue,
PtpOperationCode_MtpSetObjectPropValue,
};
constexpr const PtpEventCode SupportedEventCodes[] = { /* ... */ };
constexpr const PtpDevicePropertyCode SupportedDeviceProperties[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedCaptureFormats[] = { /* ... */ };
constexpr const PtpObjectFormatCode SupportedPlaybackFormats[] = {
PtpObjectFormatCode_Undefined,
PtpObjectFormatCode_Association,
};
constexpr const PtpObjectPropertyCode SupportedObjectProperties[] = {
PtpObjectPropertyCode_StorageId,
PtpObjectPropertyCode_ObjectFormat,
PtpObjectPropertyCode_ObjectSize,
PtpObjectPropertyCode_ObjectFileName,
PtpObjectPropertyCode_ParentObject,
PtpObjectPropertyCode_PersistentUniqueObjectIdentifier,
};
constexpr bool IsSupportedObjectPropertyCode(PtpObjectPropertyCode c) {
for (size_t i = 0; i < util::size(SupportedObjectProperties); i++) {
if (SupportedObjectProperties[i] == c) {
return true;
}
}
return false;
PtpBuffers *GetBuffers() {
static constinit PtpBuffers buffers = {};
return std::addressof(buffers);
}
constexpr const StorageId SupportedStorageIds[] = {
StorageId_SdmcFs,
};
struct PtpStorageInfo {
PtpStorageType storage_type;
PtpFilesystemType filesystem_type;
PtpAccessCapability access_capability;
u64 max_capacity;
u64 free_space_in_bytes;
u32 free_space_in_images;
const char *storage_description;
const char *volume_label;
};
constexpr PtpStorageInfo DefaultStorageInfo = {
.storage_type = PtpStorageType_FixedRam,
.filesystem_type = PtpFilesystemType_GenericHierarchical,
.access_capability = PtpAccessCapability_ReadWrite,
.max_capacity = 0,
.free_space_in_bytes = 0,
.free_space_in_images = 0,
.storage_description = "",
.volume_label = "",
};
struct PtpObjectInfo {
StorageId storage_id;
PtpObjectFormatCode object_format;
PtpProtectionStatus protection_status;
u32 object_compressed_size;
u16 thumb_format;
u32 thumb_compressed_size;
u32 thumb_width;
u32 thumb_height;
u32 image_width;
u32 image_height;
u32 image_depth;
u32 parent_object;
PtpAssociationType association_type;
u32 association_desc;
u32 sequence_number;
const char *filename;
const char *capture_date;
const char *modification_date;
const char *keywords;
};
constexpr PtpObjectInfo DefaultObjectInfo = {
.storage_id = StorageId_SdmcFs,
.object_format = {},
.protection_status = PtpProtectionStatus_NoProtection,
.object_compressed_size = 0,
.thumb_format = 0,
.thumb_compressed_size = 0,
.thumb_width = 0,
.thumb_height = 0,
.image_width = 0,
.image_height = 0,
.image_depth = 0,
.parent_object = PtpGetObjectHandles_RootParent,
.association_type = PtpAssociationType_Undefined,
.association_desc = 0,
.sequence_number = 0,
.filename = nullptr,
.capture_date = "",
.modification_date = "",
.keywords = "",
};
constexpr s64 DirectoryReadSize = 32;
constexpr u64 FsBufferSize = haze::UsbBulkPacketBufferSize;
constinit char g_filename_str[PtpStringMaxLength + 1] = {};
constinit char g_capture_date_str[PtpStringMaxLength + 1] = {};
constinit char g_modification_date_str[PtpStringMaxLength + 1] = {};
constinit char g_keywords_str[PtpStringMaxLength + 1] = {};
constinit FsDirectoryEntry g_dir_entries[DirectoryReadSize] = {};
constinit u8 g_fs_buffer[FsBufferSize] = {};
alignas(4_KB) constinit u8 g_bulk_write_buffer[haze::UsbBulkPacketBufferSize] = {};
alignas(4_KB) constinit u8 g_bulk_read_buffer[haze::UsbBulkPacketBufferSize] = {};
}
Result PtpResponder::Initialize(EventReactor *reactor, PtpObjectHeap *object_heap) {
m_object_heap = object_heap;
m_buffers = GetBuffers();
/* Configure fs proxy. */
m_fs.Initialize(reactor, fsdevGetDeviceFileSystem("sdmc"));
@@ -236,6 +91,15 @@ namespace haze {
R_CATCH(haze::ResultInvalidPropertyValue) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpInvalidObjectPropValue));
}
R_CATCH(haze::ResultGroupSpecified) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByGroupUnsupported));
}
R_CATCH(haze::ResultDepthSpecified) {
R_TRY(this->WriteResponse(PtpResponseCode_MtpSpecificationByDepthUnsupported));
}
R_CATCH(haze::ResultInvalidArgument) {
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
}
R_CATCH_MODULE(fs) {
/* Errors from fs are typically recoverable. */
R_TRY(this->WriteResponse(PtpResponseCode_GeneralError));
@@ -246,7 +110,7 @@ namespace haze {
}
Result PtpResponder::HandleRequestImpl() {
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
R_TRY(dp.Read(std::addressof(m_request_header)));
switch (m_request_header.type) {
@@ -276,6 +140,12 @@ namespace haze {
case PtpOperationCode_MtpGetObjectPropDesc: R_RETURN(this->GetObjectPropDesc(dp)); break;
case PtpOperationCode_MtpGetObjectPropValue: R_RETURN(this->GetObjectPropValue(dp)); break;
case PtpOperationCode_MtpSetObjectPropValue: R_RETURN(this->SetObjectPropValue(dp)); break;
case PtpOperationCode_MtpGetObjPropList: R_RETURN(this->GetObjectPropList(dp)); break;
case PtpOperationCode_AndroidGetPartialObject64: R_RETURN(this->GetPartialObject64(dp)); break;
case PtpOperationCode_AndroidSendPartialObject: R_RETURN(this->SendPartialObject(dp)); break;
case PtpOperationCode_AndroidTruncateObject: R_RETURN(this->TruncateObject(dp)); break;
case PtpOperationCode_AndroidBeginEditObject: R_RETURN(this->BeginEditObject(dp)); break;
case PtpOperationCode_AndroidEndEditObject: R_RETURN(this->EndEditObject(dp)); break;
default: R_THROW(haze::ResultOperationNotSupported());
}
}
@@ -287,756 +157,17 @@ namespace haze {
}
}
template <typename Data>
Result PtpResponder::WriteResponse(PtpResponseCode code, Data &&data) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.AddResponseHeader(m_request_header, code, sizeof(Data)));
R_TRY(db.Add(data));
Result PtpResponder::WriteResponse(PtpResponseCode code, const void* data, size_t size) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.AddResponseHeader(m_request_header, code, size));
R_TRY(db.AddBuffer(reinterpret_cast<const u8*>(data), size));
R_RETURN(db.Commit());
}
Result PtpResponder::WriteResponse(PtpResponseCode code) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.AddResponseHeader(m_request_header, code, 0));
R_RETURN(db.Commit());
}
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the device info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.Add(MtpVendorExtensionId));
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.AddString(MtpVendorExtensionDesc));
R_TRY(db.Add(MtpFunctionalModeDefault));
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
R_TRY(db.AddString(MtpDeviceManufacturer));
R_TRY(db.AddString(MtpDeviceModel));
R_TRY(db.AddString(GetFirmwareVersion()));
R_TRY(db.AddString(GetSerialNumber()));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::OpenSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
/* Close, if we're already open. */
this->ForceCloseSession();
/* Initialize the database. */
m_session_open = true;
m_object_database.Initialize(m_object_heap);
/* Create the root storages. */
PtpObject *object;
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
/* Register the root storages. */
m_object_database.RegisterObject(object, StorageId_SdmcFs);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::CloseSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
this->ForceCloseSession();
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the storage ID array. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
PtpStorageInfo storage_info(DefaultStorageInfo);
/* Get the storage ID the client requested information for. */
u32 storage_id;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Finalize());
/* Get the info from fs. */
switch (storage_id) {
case StorageId_SdmcFs:
{
s64 total_space, free_space;
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
storage_info.max_capacity = total_space;
storage_info.free_space_in_bytes = free_space;
storage_info.free_space_in_images = 0;
storage_info.storage_description = "SD Card";
}
break;
default:
R_THROW(haze::ResultInvalidStorageId());
}
/* Write the storage info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(storage_info.storage_type));
R_TRY(db.Add(storage_info.filesystem_type));
R_TRY(db.Add(storage_info.access_capability));
R_TRY(db.Add(storage_info.max_capacity));
R_TRY(db.Add(storage_info.free_space_in_bytes));
R_TRY(db.Add(storage_info.free_space_in_images));
R_TRY(db.AddString(storage_info.storage_description));
R_TRY(db.AddString(storage_info.volume_label));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested enumeration for. */
u32 storage_id, object_format_code, association_object_handle;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Read(std::addressof(object_format_code)));
R_TRY(dp.Read(std::addressof(association_object_handle)));
R_TRY(dp.Finalize());
/* Handle top-level requests. */
if (storage_id == PtpGetObjectHandles_AllStorage) {
storage_id = StorageId_SdmcFs;
}
/* Rewrite requests for enumerating storage directories. */
if (association_object_handle == PtpGetObjectHandles_RootParent) {
association_object_handle = storage_id;
}
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(association_object_handle);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Try to read the object as a directory. */
FsDir dir;
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
/* Count how many entries are in the directory. */
s64 entry_count = 0;
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
/* Begin writing. */
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
R_TRY(db.Add(static_cast<u32>(entry_count)));
/* Enumerate the directory, writing results to the data builder as we progress. */
/* TODO: How should we handle the directory contents changing during enumeration? */
/* Is this even feasible to handle? */
while (true) {
/* Get the next batch. */
s64 read_count = 0;
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, g_dir_entries));
/* Write to output. */
for (s64 i = 0; i < read_count; i++) {
u32 handle;
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), g_dir_entries[i].name, obj->GetObjectId(), std::addressof(handle)));
R_TRY(db.Add(handle));
}
/* If we read fewer than the batch size, we're done. */
if (read_count < DirectoryReadSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested info for. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Build info about the object. */
PtpObjectInfo object_info(DefaultObjectInfo);
if (object_id == StorageId_SdmcFs) {
/* The SD Card directory has some special properties. */
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
object_info.filename = "SD Card";
} else {
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Get the size, if we are requesting info about a file. */
s64 size = 0;
if (entry_type == FsDirEntryType_File) {
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
}
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
object_info.object_compressed_size = size;
object_info.parent_object = obj->GetParentId();
if (entry_type == FsDirEntryType_Dir) {
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
} else {
object_info.object_format = PtpObjectFormatCode_Undefined;
object_info.association_type = PtpAssociationType_Undefined;
}
}
/* Write the object info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(object_info.storage_id));
R_TRY(db.Add(object_info.object_format));
R_TRY(db.Add(object_info.protection_status));
R_TRY(db.Add(object_info.object_compressed_size));
R_TRY(db.Add(object_info.thumb_format));
R_TRY(db.Add(object_info.thumb_compressed_size));
R_TRY(db.Add(object_info.thumb_width));
R_TRY(db.Add(object_info.thumb_height));
R_TRY(db.Add(object_info.image_width));
R_TRY(db.Add(object_info.image_height));
R_TRY(db.Add(object_info.image_depth));
R_TRY(db.Add(object_info.parent_object));
R_TRY(db.Add(object_info.association_type));
R_TRY(db.Add(object_info.association_desc));
R_TRY(db.Add(object_info.sequence_number));
R_TRY(db.AddString(object_info.filename));
R_TRY(db.AddString(object_info.capture_date));
R_TRY(db.AddString(object_info.modification_date));
R_TRY(db.AddString(object_info.keywords));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObject(PtpDataParser &dp) {
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
/* Send the header and file size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 offset = 0;
while (true) {
/* Get the next batch. */
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, g_fs_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(g_fs_buffer, bytes_read));
/* If we read fewer bytes than the batch size, we're done. */
if (bytes_read < FsBufferSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
/* Get the storage ID and parent object and flush the request packet. */
u32 storage_id, parent_object;
R_TRY(rdp.Read(std::addressof(storage_id)));
R_TRY(rdp.Read(std::addressof(parent_object)));
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
PtpObjectInfo info(DefaultObjectInfo);
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Read in the object info. */
R_TRY(dp.Read(std::addressof(info.storage_id)));
R_TRY(dp.Read(std::addressof(info.object_format)));
R_TRY(dp.Read(std::addressof(info.protection_status)));
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_format)));
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_width)));
R_TRY(dp.Read(std::addressof(info.thumb_height)));
R_TRY(dp.Read(std::addressof(info.image_width)));
R_TRY(dp.Read(std::addressof(info.image_height)));
R_TRY(dp.Read(std::addressof(info.image_depth)));
R_TRY(dp.Read(std::addressof(info.parent_object)));
R_TRY(dp.Read(std::addressof(info.association_type)));
R_TRY(dp.Read(std::addressof(info.association_desc)));
R_TRY(dp.Read(std::addressof(info.sequence_number)));
R_TRY(dp.ReadString(g_filename_str));
R_TRY(dp.ReadString(g_capture_date_str));
R_TRY(dp.ReadString(g_modification_date_str));
R_TRY(dp.ReadString(g_keywords_str));
R_TRY(dp.Finalize());
/* Rewrite requests for creating in storage directories. */
if (parent_object == PtpGetObjectHandles_RootParent) {
parent_object = storage_id;
}
/* Check if we know about the parent object. If we don't, it's an error. */
auto * const parentobj = m_object_database.GetObjectById(parent_object);
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
/* Make a new object with the intended name. */
PtpNewObjectInfo new_object_info;
new_object_info.storage_id = StorageId_SdmcFs;
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
/* Create the object in the database. */
PtpObject *obj;
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), g_filename_str, parentobj->GetObjectId(), std::addressof(obj)));
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
/* Register the object with a new ID. */
m_object_database.RegisterObject(obj);
new_object_info.object_id = obj->GetObjectId();
/* Create the object on the filesystem. */
if (info.object_format == PtpObjectFormatCode_Association) {
R_TRY(m_fs.CreateDirectory(obj->GetName()));
m_send_object_id = 0;
} else {
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
m_send_object_id = new_object_info.object_id;
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
}
Result PtpResponder::SendObject(PtpDataParser &rdp) {
/* Reset SendObject object ID on exit. */
ON_SCOPE_EXIT { m_send_object_id = 0; };
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file after locking for write. */
s64 offset = 0;
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
/* Expand to the needed size. */
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
}
/* Begin writing to the filesystem. */
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(g_fs_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
R_TRY(m_fs.WriteFile(std::addressof(file), offset, g_fs_buffer, bytes_received, 0));
offset += bytes_received;
/* If we received fewer bytes than the batch size, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res)) {
break;
}
R_TRY(read_res);
}
/* Truncate the file to the received size. */
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
/* Get the object ID and flush the request packet. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Disallow deleting the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Remove the object from the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
} else {
R_TRY(m_fs.DeleteFile(obj->GetName()));
}
/* Remove the object from the database. */
m_object_database.DeleteObject(obj);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
/* Write information about all object properties we can support. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
PtpObjectPropertyCode property_code;
u16 object_format;
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Begin writing information about the property code. */
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_TRY(db.Add(property_code));
/* Each property code corresponds to a different pattern, which contains the data type, */
/* whether the property can be set for an object, and the default value of the property. */
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u128>(0));
}
case PtpObjectPropertyCode_ObjectSize:
{
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u64>(0));
}
break;
case PtpObjectPropertyCode_StorageId:
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
R_TRY(db.AddString(""));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
R_TRY(db.Add(PtpPropertyGroupCode_Default));
/* We don't use the form flag. */
R_TRY(db.Add(PtpPropertyFormFlag_None));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Finalize());
/* Disallow renaming the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Begin writing the requested object property. */
PtpDataBuilder db(g_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(property_code)));
R_TRY(rdp.Finalize());
PtpDataParser dp(g_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We are reading a file name. */
R_TRY(dp.ReadString(g_filename_str));
R_TRY(dp.Finalize());
/* Ensure we can actually process the new name. */
const bool is_empty = g_filename_str[0] == '\x00';
const bool contains_slashes = std::strchr(g_filename_str, '/') != nullptr;
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
/* Add a new object in the database with the new name. */
PtpObject *newobj;
{
/* Find the last path separator in the existing object name. */
char *pathsep = std::strrchr(obj->m_name, '/');
HAZE_ASSERT(pathsep != nullptr);
/* Temporarily mark the path separator as null to facilitate processing. */
*pathsep = '\x00';
ON_SCOPE_EXIT { *pathsep = '/'; };
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), g_filename_str, obj->GetParentId(), std::addressof(newobj)));
}
{
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE {
if (!newobj->GetIsRegistered()) {
/* Only delete if the object was not registered. */
/* Otherwise, we would remove an object that still exists. */
m_object_database.DeleteObject(newobj);
}
};
/* Get the old object type. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Attempt to rename the object on the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
} else {
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
}
}
/* Unregister and free the old object. */
m_object_database.DeleteObject(obj);
/* Register the new object. */
m_object_database.RegisterObject(newobj, object_id);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -0,0 +1,203 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetPartialObject64(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID, offset, and size for the file we want to read. */
u32 object_id, size;
u64 offset;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(offset)));
R_TRY(dp.Read(std::addressof(size)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 file_size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
/* Ensure the requested offset and size are within range. */
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
R_UNLESS(static_cast<u64>(file_size) <= offset + size, haze::ResultInvalidArgument());
/* Send the header and data size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 size_remaining = size;
while (true) {
/* Get the next batch. */
u64 bytes_to_read = std::min<s64>(FsBufferSize, size_remaining);
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_read, FsReadOption_None, std::addressof(bytes_read)));
size_remaining -= bytes_read;
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
/* If we read fewer bytes than the batch size, or have read enough data, we're done. */
if (bytes_read < FsBufferSize || size_remaining == 0) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendPartialObject(PtpDataParser &rdp) {
/* Get the object ID, offset, and size for the file we want to write. */
u32 object_id, size;
u64 offset;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(size)));
R_TRY(rdp.Read(std::addressof(offset)));
R_TRY(rdp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 file_size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(file_size)));
/* Ensure the requested offset and size are within range. */
R_UNLESS(offset + size > offset, haze::ResultInvalidArgument());
R_UNLESS(static_cast<u64>(file_size) <= offset, haze::ResultInvalidArgument());
/* Prepare a data parser for the data we are about to receive. */
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Begin writing to the filesystem. */
s64 size_remaining = size;
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
u32 bytes_to_write = std::min<s64>(size_remaining, bytes_received);
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_to_write, 0));
size_remaining -= bytes_to_write;
offset += bytes_to_write;
/* If we received fewer bytes than the batch size, or have written enough data, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res) || size_remaining == 0) {
break;
}
R_TRY(read_res);
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::TruncateObject(PtpDataParser &dp) {
/* Get the object ID and size for the file we want to truncate. */
u32 object_id;
u64 size;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(size)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file. */
R_TRY(m_fs.SetFileSize(std::addressof(file), size));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::BeginEditObject(PtpDataParser &dp) {
/* Get the object ID we are going to begin editing. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We don't implement transactions, so write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::EndEditObject(PtpDataParser &dp) {
/* Get the object ID we are going to finish editing. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We don't implement transactions, so write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -0,0 +1,418 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetObjectPropsSupported(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write information about all object properties we can support. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedObjectProperties, util::size(SupportedObjectProperties)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropDesc(PtpDataParser &dp) {
PtpObjectPropertyCode property_code;
u16 object_format;
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Begin writing information about the property code. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_TRY(db.Add(property_code));
/* Each property code corresponds to a different pattern, which contains the data type, */
/* whether the property can be set for an object, and the default value of the property. */
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u128>(0));
}
case PtpObjectPropertyCode_ObjectSize:
{
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add<u64>(0));
}
break;
case PtpObjectPropertyCode_StorageId:
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(PtpPropertyGetSetFlag_Get));
R_TRY(db.Add(PtpObjectFormatCode_Undefined));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.Add(PtpPropertyGetSetFlag_GetSet));
R_TRY(db.AddString(""));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
/* Group code is a required part of the response, but doesn't seem to be used for anything. */
R_TRY(db.Add(PtpPropertyGroupCode_Default));
/* We don't use the form flag. */
R_TRY(db.Add(PtpPropertyFormFlag_None));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropValue(PtpDataParser &dp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Finalize());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(IsSupportedObjectPropertyCode(property_code), haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Begin writing the requested object property. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
switch (property_code) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectPropList(PtpDataParser &dp) {
u32 object_id;
u32 object_format;
s32 property_code;
s32 group_code;
s32 depth;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Read(std::addressof(object_format)));
R_TRY(dp.Read(std::addressof(property_code)));
R_TRY(dp.Read(std::addressof(group_code)));
R_TRY(dp.Read(std::addressof(depth)));
R_TRY(dp.Finalize());
/* Ensure format is unspecified. */
R_UNLESS(object_format == 0, haze::ResultInvalidArgument());
/* Ensure we have a valid property code. */
R_UNLESS(property_code == -1 || IsSupportedObjectPropertyCode(PtpObjectPropertyCode(property_code)), haze::ResultUnknownPropertyCode());
/* Ensure group code is the default. */
R_UNLESS(group_code == PtpPropertyGroupCode_Default, haze::ResultGroupSpecified());
/* Ensure depth is 0. */
R_UNLESS(depth == 0, haze::ResultDepthSpecified());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Define helper for getting the object type. */
const auto GetObjectType = [&] (FsDirEntryType *out_entry_type) {
R_RETURN(m_fs.GetEntryType(obj->GetName(), out_entry_type));
};
/* Define helper for getting the object size. */
const auto GetObjectSize = [&] (s64 *out_size) {
*out_size = 0;
/* Check if this is a directory. */
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
/* If it is, we're done. */
R_SUCCEED_IF(entry_type == FsDirEntryType_Dir);
/* Otherwise, open as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_RETURN(m_fs.GetFileSize(std::addressof(file), out_size));
};
/* Define helper for determining if the property should be included. */
const auto ShouldIncludeProperty = [&] (PtpObjectPropertyCode code) {
/* If all properties were requested, or it was the requested property, we should include the property. */
return property_code == -1 || code == property_code;
};
/* Determine how many output elements we will report. */
u32 num_output_elements = 0;
for (const auto obj_property : SupportedObjectProperties) {
if (ShouldIncludeProperty(obj_property)) {
num_output_elements++;
}
}
/* Begin writing the requested object properties. */
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
/* Report the number of elements. */
R_TRY(db.Add(num_output_elements));
for (const auto obj_property : SupportedObjectProperties) {
if (!ShouldIncludeProperty(obj_property)) {
continue;
}
/* Write the object handle. */
R_TRY(db.Add<u32>(object_id));
/* Write the property code. */
R_TRY(db.Add<u16>(obj_property));
/* Write the property value. */
switch (obj_property) {
case PtpObjectPropertyCode_PersistentUniqueObjectIdentifier:
{
R_TRY(db.Add(PtpDataTypeCode_U128));
R_TRY(db.Add<u128>(object_id));
}
break;
case PtpObjectPropertyCode_ObjectSize:
{
s64 size;
R_TRY(GetObjectSize(std::addressof(size)));
R_TRY(db.Add(PtpDataTypeCode_U64));
R_TRY(db.Add<u64>(size));
}
break;
case PtpObjectPropertyCode_StorageId:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(StorageId_SdmcFs));
}
break;
case PtpObjectPropertyCode_ParentObject:
{
R_TRY(db.Add(PtpDataTypeCode_U32));
R_TRY(db.Add(obj->GetParentId()));
}
break;
case PtpObjectPropertyCode_ObjectFormat:
{
FsDirEntryType entry_type;
R_TRY(GetObjectType(std::addressof(entry_type)));
R_TRY(db.Add(PtpDataTypeCode_U16));
R_TRY(db.Add(entry_type == FsDirEntryType_File ? PtpObjectFormatCode_Undefined : PtpObjectFormatCode_Association));
}
break;
case PtpObjectPropertyCode_ObjectFileName:
{
R_TRY(db.Add(PtpDataTypeCode_String));
R_TRY(db.AddString(std::strrchr(obj->GetName(), '/') + 1));
}
break;
HAZE_UNREACHABLE_DEFAULT_CASE();
}
}
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SetObjectPropValue(PtpDataParser &rdp) {
u32 object_id;
PtpObjectPropertyCode property_code;
R_TRY(rdp.Read(std::addressof(object_id)));
R_TRY(rdp.Read(std::addressof(property_code)));
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Ensure we have a valid property code before continuing. */
R_UNLESS(property_code == PtpObjectPropertyCode_ObjectFileName, haze::ResultUnknownPropertyCode());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* We are reading a file name. */
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
R_TRY(dp.Finalize());
/* Ensure we can actually process the new name. */
const bool is_empty = m_buffers->filename_string_buffer[0] == '\x00';
const bool contains_slashes = std::strchr(m_buffers->filename_string_buffer, '/') != nullptr;
R_UNLESS(!is_empty && !contains_slashes, haze::ResultInvalidPropertyValue());
/* Add a new object in the database with the new name. */
PtpObject *newobj;
{
/* Find the last path separator in the existing object name. */
char *pathsep = std::strrchr(obj->m_name, '/');
HAZE_ASSERT(pathsep != nullptr);
/* Temporarily mark the path separator as null to facilitate processing. */
*pathsep = '\x00';
ON_SCOPE_EXIT { *pathsep = '/'; };
R_TRY(m_object_database.CreateOrFindObject(obj->GetName(), m_buffers->filename_string_buffer, obj->GetParentId(), std::addressof(newobj)));
}
{
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE {
if (!newobj->GetIsRegistered()) {
/* Only delete if the object was not registered. */
/* Otherwise, we would remove an object that still exists. */
m_object_database.DeleteObject(newobj);
}
};
/* Get the old object type. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Attempt to rename the object on the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.RenameDirectory(obj->GetName(), newobj->GetName()));
} else {
R_TRY(m_fs.RenameFile(obj->GetName(), newobj->GetName()));
}
}
/* Unregister and free the old object. */
m_object_database.DeleteObject(obj);
/* Register the new object. */
m_object_database.RegisterObject(newobj, object_id);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -0,0 +1,507 @@
/*
* Copyright (c) Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <haze.hpp>
#include <haze/ptp_data_builder.hpp>
#include <haze/ptp_data_parser.hpp>
#include <haze/ptp_responder_types.hpp>
namespace haze {
Result PtpResponder::GetDeviceInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the device info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.Add(MtpVendorExtensionId));
R_TRY(db.Add(MtpStandardVersion));
R_TRY(db.AddString(MtpVendorExtensionDesc));
R_TRY(db.Add(MtpFunctionalModeDefault));
R_TRY(db.AddArray(SupportedOperationCodes, util::size(SupportedOperationCodes)));
R_TRY(db.AddArray(SupportedEventCodes, util::size(SupportedEventCodes)));
R_TRY(db.AddArray(SupportedDeviceProperties, util::size(SupportedDeviceProperties)));
R_TRY(db.AddArray(SupportedCaptureFormats, util::size(SupportedCaptureFormats)));
R_TRY(db.AddArray(SupportedPlaybackFormats, util::size(SupportedPlaybackFormats)));
R_TRY(db.AddString(MtpDeviceManufacturer));
R_TRY(db.AddString(MtpDeviceModel));
R_TRY(db.AddString(GetFirmwareVersion()));
R_TRY(db.AddString(GetSerialNumber()));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::OpenSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
/* Close, if we're already open. */
this->ForceCloseSession();
/* Initialize the database. */
m_session_open = true;
m_object_database.Initialize(m_object_heap);
/* Create the root storages. */
PtpObject *object;
R_TRY(m_object_database.CreateOrFindObject("", "", PtpGetObjectHandles_RootParent, std::addressof(object)));
/* Register the root storages. */
m_object_database.RegisterObject(object, StorageId_SdmcFs);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::CloseSession(PtpDataParser &dp) {
R_TRY(dp.Finalize());
this->ForceCloseSession();
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageIds(PtpDataParser &dp) {
R_TRY(dp.Finalize());
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Write the storage ID array. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] {
R_RETURN(db.AddArray(SupportedStorageIds, util::size(SupportedStorageIds)));
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetStorageInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
PtpStorageInfo storage_info(DefaultStorageInfo);
/* Get the storage ID the client requested information for. */
u32 storage_id;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Finalize());
/* Get the info from fs. */
switch (storage_id) {
case StorageId_SdmcFs:
{
s64 total_space, free_space;
R_TRY(m_fs.GetTotalSpace("/", std::addressof(total_space)));
R_TRY(m_fs.GetFreeSpace("/", std::addressof(free_space)));
storage_info.max_capacity = total_space;
storage_info.free_space_in_bytes = free_space;
storage_info.free_space_in_images = 0;
storage_info.storage_description = "SD Card";
}
break;
default:
R_THROW(haze::ResultInvalidStorageId());
}
/* Write the storage info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(storage_info.storage_type));
R_TRY(db.Add(storage_info.filesystem_type));
R_TRY(db.Add(storage_info.access_capability));
R_TRY(db.Add(storage_info.max_capacity));
R_TRY(db.Add(storage_info.free_space_in_bytes));
R_TRY(db.Add(storage_info.free_space_in_images));
R_TRY(db.AddString(storage_info.storage_description));
R_TRY(db.AddString(storage_info.volume_label));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectHandles(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested enumeration for. */
u32 storage_id, object_format_code, association_object_handle;
R_TRY(dp.Read(std::addressof(storage_id)));
R_TRY(dp.Read(std::addressof(object_format_code)));
R_TRY(dp.Read(std::addressof(association_object_handle)));
R_TRY(dp.Finalize());
/* Handle top-level requests. */
if (storage_id == PtpGetObjectHandles_AllStorage) {
storage_id = StorageId_SdmcFs;
}
/* Rewrite requests for enumerating storage directories. */
if (association_object_handle == PtpGetObjectHandles_RootParent) {
association_object_handle = storage_id;
}
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(association_object_handle);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Try to read the object as a directory. */
FsDir dir;
R_TRY(m_fs.OpenDirectory(obj->GetName(), FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles, std::addressof(dir)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseDirectory(std::addressof(dir)); };
/* Count how many entries are in the directory. */
s64 entry_count = 0;
R_TRY(m_fs.GetDirectoryEntryCount(std::addressof(dir), std::addressof(entry_count)));
/* Begin writing. */
R_TRY(db.AddDataHeader(m_request_header, sizeof(u32) + (entry_count * sizeof(u32))));
R_TRY(db.Add(static_cast<u32>(entry_count)));
/* Enumerate the directory, writing results to the data builder as we progress. */
/* TODO: How should we handle the directory contents changing during enumeration? */
/* Is this even feasible to handle? */
while (true) {
/* Get the next batch. */
s64 read_count = 0;
R_TRY(m_fs.ReadDirectory(std::addressof(dir), std::addressof(read_count), DirectoryReadSize, m_buffers->file_system_entry_buffer));
/* Write to output. */
for (s64 i = 0; i < read_count; i++) {
const char *name = m_buffers->file_system_entry_buffer[i].name;
u32 handle;
R_TRY(m_object_database.CreateAndRegisterObjectId(obj->GetName(), name, obj->GetObjectId(), std::addressof(handle)));
R_TRY(db.Add(handle));
}
/* If we read fewer than the batch size, we're done. */
if (read_count < DirectoryReadSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObjectInfo(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested info for. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Build info about the object. */
PtpObjectInfo object_info(DefaultObjectInfo);
if (object_id == StorageId_SdmcFs) {
/* The SD Card directory has some special properties. */
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
object_info.filename = "SD Card";
} else {
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Get the size, if we are requesting info about a file. */
s64 size = 0;
if (entry_type == FsDirEntryType_File) {
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
}
object_info.filename = std::strrchr(obj->GetName(), '/') + 1;
object_info.object_compressed_size = size;
object_info.parent_object = obj->GetParentId();
if (entry_type == FsDirEntryType_Dir) {
object_info.object_format = PtpObjectFormatCode_Association;
object_info.association_type = PtpAssociationType_GenericFolder;
} else {
object_info.object_format = PtpObjectFormatCode_Undefined;
object_info.association_type = PtpAssociationType_Undefined;
}
}
/* Write the object info data. */
R_TRY(db.WriteVariableLengthData(m_request_header, [&] () {
R_TRY(db.Add(object_info.storage_id));
R_TRY(db.Add(object_info.object_format));
R_TRY(db.Add(object_info.protection_status));
R_TRY(db.Add(object_info.object_compressed_size));
R_TRY(db.Add(object_info.thumb_format));
R_TRY(db.Add(object_info.thumb_compressed_size));
R_TRY(db.Add(object_info.thumb_width));
R_TRY(db.Add(object_info.thumb_height));
R_TRY(db.Add(object_info.image_width));
R_TRY(db.Add(object_info.image_height));
R_TRY(db.Add(object_info.image_depth));
R_TRY(db.Add(object_info.parent_object));
R_TRY(db.Add(object_info.association_type));
R_TRY(db.Add(object_info.association_desc));
R_TRY(db.Add(object_info.sequence_number));
R_TRY(db.AddString(object_info.filename));
R_TRY(db.AddString(object_info.capture_date));
R_TRY(db.AddString(object_info.modification_date));
R_TRY(db.AddString(object_info.keywords));
R_SUCCEED();
}));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::GetObject(PtpDataParser &dp) {
PtpDataBuilder db(m_buffers->usb_bulk_write_buffer, std::addressof(m_usb_server));
/* Get the object ID the client requested. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Read, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Get the file's size. */
s64 size = 0;
R_TRY(m_fs.GetFileSize(std::addressof(file), std::addressof(size)));
/* Send the header and file size. */
R_TRY(db.AddDataHeader(m_request_header, size));
/* Begin reading the file, writing data to the builder as we progress. */
s64 offset = 0;
while (true) {
/* Get the next batch. */
u64 bytes_read;
R_TRY(m_fs.ReadFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, FsBufferSize, FsReadOption_None, std::addressof(bytes_read)));
offset += bytes_read;
/* Write to output. */
R_TRY(db.AddBuffer(m_buffers->file_system_data_buffer, bytes_read));
/* If we read fewer bytes than the batch size, we're done. */
if (bytes_read < FsBufferSize) {
break;
}
}
/* Flush the data response. */
R_TRY(db.Commit());
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::SendObjectInfo(PtpDataParser &rdp) {
/* Get the storage ID and parent object and flush the request packet. */
u32 storage_id, parent_object;
R_TRY(rdp.Read(std::addressof(storage_id)));
R_TRY(rdp.Read(std::addressof(parent_object)));
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
PtpObjectInfo info(DefaultObjectInfo);
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Read in the object info. */
R_TRY(dp.Read(std::addressof(info.storage_id)));
R_TRY(dp.Read(std::addressof(info.object_format)));
R_TRY(dp.Read(std::addressof(info.protection_status)));
R_TRY(dp.Read(std::addressof(info.object_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_format)));
R_TRY(dp.Read(std::addressof(info.thumb_compressed_size)));
R_TRY(dp.Read(std::addressof(info.thumb_width)));
R_TRY(dp.Read(std::addressof(info.thumb_height)));
R_TRY(dp.Read(std::addressof(info.image_width)));
R_TRY(dp.Read(std::addressof(info.image_height)));
R_TRY(dp.Read(std::addressof(info.image_depth)));
R_TRY(dp.Read(std::addressof(info.parent_object)));
R_TRY(dp.Read(std::addressof(info.association_type)));
R_TRY(dp.Read(std::addressof(info.association_desc)));
R_TRY(dp.Read(std::addressof(info.sequence_number)));
R_TRY(dp.ReadString(m_buffers->filename_string_buffer));
R_TRY(dp.ReadString(m_buffers->capture_date_string_buffer));
R_TRY(dp.ReadString(m_buffers->modification_date_string_buffer));
R_TRY(dp.ReadString(m_buffers->keywords_string_buffer));
R_TRY(dp.Finalize());
/* Rewrite requests for creating in storage directories. */
if (parent_object == PtpGetObjectHandles_RootParent) {
parent_object = storage_id;
}
/* Check if we know about the parent object. If we don't, it's an error. */
auto * const parentobj = m_object_database.GetObjectById(parent_object);
R_UNLESS(parentobj != nullptr, haze::ResultInvalidObjectId());
/* Make a new object with the intended name. */
PtpNewObjectInfo new_object_info;
new_object_info.storage_id = StorageId_SdmcFs;
new_object_info.parent_object_id = parent_object == storage_id ? 0 : parent_object;
/* Create the object in the database. */
PtpObject *obj;
R_TRY(m_object_database.CreateOrFindObject(parentobj->GetName(), m_buffers->filename_string_buffer, parentobj->GetObjectId(), std::addressof(obj)));
/* Ensure we maintain a clean state on failure. */
ON_RESULT_FAILURE { m_object_database.DeleteObject(obj); };
/* Register the object with a new ID. */
m_object_database.RegisterObject(obj);
new_object_info.object_id = obj->GetObjectId();
/* Create the object on the filesystem. */
if (info.object_format == PtpObjectFormatCode_Association) {
R_TRY(m_fs.CreateDirectory(obj->GetName()));
m_send_object_id = 0;
} else {
R_TRY(m_fs.CreateFile(obj->GetName(), 0, 0));
m_send_object_id = new_object_info.object_id;
}
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok, new_object_info));
}
Result PtpResponder::SendObject(PtpDataParser &rdp) {
/* Reset SendObject object ID on exit. */
ON_SCOPE_EXIT { m_send_object_id = 0; };
R_TRY(rdp.Finalize());
PtpDataParser dp(m_buffers->usb_bulk_read_buffer, std::addressof(m_usb_server));
/* Ensure we have a data header. */
PtpUsbBulkContainer data_header;
R_TRY(dp.Read(std::addressof(data_header)));
R_UNLESS(data_header.type == PtpUsbBulkContainerType_Data, haze::ResultUnknownRequestType());
R_UNLESS(data_header.code == m_request_header.code, haze::ResultOperationNotSupported());
R_UNLESS(data_header.trans_id == m_request_header.trans_id, haze::ResultOperationNotSupported());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(m_send_object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Lock the object as a file. */
FsFile file;
R_TRY(m_fs.OpenFile(obj->GetName(), FsOpenMode_Write | FsOpenMode_Append, std::addressof(file)));
/* Ensure we maintain a clean state on exit. */
ON_SCOPE_EXIT { m_fs.CloseFile(std::addressof(file)); };
/* Truncate the file after locking for write. */
s64 offset = 0;
R_TRY(m_fs.SetFileSize(std::addressof(file), 0));
/* Expand to the needed size. */
if (data_header.length > sizeof(PtpUsbBulkContainer)) {
R_TRY(m_fs.SetFileSize(std::addressof(file), data_header.length - sizeof(PtpUsbBulkContainer)));
}
/* Begin writing to the filesystem. */
while (true) {
/* Read as many bytes as we can. */
u32 bytes_received;
const Result read_res = dp.ReadBuffer(m_buffers->file_system_data_buffer, FsBufferSize, std::addressof(bytes_received));
/* Write to the file. */
R_TRY(m_fs.WriteFile(std::addressof(file), offset, m_buffers->file_system_data_buffer, bytes_received, 0));
offset += bytes_received;
/* If we received fewer bytes than the batch size, we're done. */
if (haze::ResultEndOfTransmission::Includes(read_res)) {
break;
}
R_TRY(read_res);
}
/* Truncate the file to the received size. */
R_TRY(m_fs.SetFileSize(std::addressof(file), offset));
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
Result PtpResponder::DeleteObject(PtpDataParser &dp) {
/* Get the object ID and flush the request packet. */
u32 object_id;
R_TRY(dp.Read(std::addressof(object_id)));
R_TRY(dp.Finalize());
/* Disallow deleting the storage root. */
R_UNLESS(object_id != StorageId_SdmcFs, haze::ResultInvalidObjectId());
/* Check if we know about the object. If we don't, it's an error. */
auto * const obj = m_object_database.GetObjectById(object_id);
R_UNLESS(obj != nullptr, haze::ResultInvalidObjectId());
/* Figure out what type of object this is. */
FsDirEntryType entry_type;
R_TRY(m_fs.GetEntryType(obj->GetName(), std::addressof(entry_type)));
/* Remove the object from the filesystem. */
if (entry_type == FsDirEntryType_Dir) {
R_TRY(m_fs.DeleteDirectoryRecursively(obj->GetName()));
} else {
R_TRY(m_fs.DeleteFile(obj->GetName()));
}
/* Remove the object from the database. */
m_object_database.DeleteObject(obj);
/* Write the success response. */
R_RETURN(this->WriteResponse(PtpResponseCode_Ok));
}
}

View File

@@ -191,6 +191,7 @@ namespace haze {
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, std::addressof(device_descriptor)));
device_descriptor.bcdUSB = 0x0300;
device_descriptor.bMaxPacketSize0 = 0x09;
R_TRY(usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, std::addressof(device_descriptor)));
/* Binary Object Store */