Compare commits
69 Commits
1700_suppo
...
1.6.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7bf379cfe | ||
|
|
9f26419b1a | ||
|
|
1b057d48c6 | ||
|
|
0c3afff4d3 | ||
|
|
274f6b63f2 | ||
|
|
2ed8450446 | ||
|
|
60974a5f4e | ||
|
|
fa384fd920 | ||
|
|
3f19db0d96 | ||
|
|
a84f725e21 | ||
|
|
7f61dfdb8d | ||
|
|
c44da84869 | ||
|
|
7f4450f930 | ||
|
|
edb4e2ea56 | ||
|
|
183f3e0d7e | ||
|
|
7650c5eb96 | ||
|
|
0ff197b300 | ||
|
|
d9fff85bc4 | ||
|
|
c866c15856 | ||
|
|
e8ac23e2ee | ||
|
|
3a8cffef57 | ||
|
|
13411902c9 | ||
|
|
693fb423cb | ||
|
|
8a9eb85e05 | ||
|
|
d389ef639e | ||
|
|
719858ad18 | ||
|
|
e4d08ae0c5 | ||
|
|
0c063db926 | ||
|
|
02e987819b | ||
|
|
2ec3e141c7 | ||
|
|
71d0274884 | ||
|
|
05259b7519 | ||
|
|
59a24fa646 | ||
|
|
f5b2eab4a8 | ||
|
|
e96e1063e2 | ||
|
|
aa170a72a9 | ||
|
|
9d4cb685a7 | ||
|
|
c95741142e | ||
|
|
ef9b111bbf | ||
|
|
114b82284d | ||
|
|
c5d7ca5159 | ||
|
|
6d0bf70783 | ||
|
|
aba6ca7329 | ||
|
|
06a840e550 | ||
|
|
11c02e22e0 | ||
|
|
f93aea4c06 | ||
|
|
4ddfb6183c | ||
|
|
3737151a2f | ||
|
|
2a4d68f916 | ||
|
|
7b523cfc8d | ||
|
|
39a95d4023 | ||
|
|
2c5002ce50 | ||
|
|
b7384a8667 | ||
|
|
85b5f20395 | ||
|
|
ad5bd81d3f | ||
|
|
777b6d285c | ||
|
|
ae2c25e9c8 | ||
|
|
3b8f65d502 | ||
|
|
cfd2d5b012 | ||
|
|
c72ba35684 | ||
|
|
ec96203cb7 | ||
|
|
1491a7b159 | ||
|
|
0daef4a6e8 | ||
|
|
4ca3c44e5f | ||
|
|
add4b3fdc3 | ||
|
|
159f8d384b | ||
|
|
92a8c8eb88 | ||
|
|
9e0daff46e | ||
|
|
6b72dbd22d |
@@ -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
|
||||
|
||||
@@ -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
4
emummc/.gitrepo
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
17
libraries/libstratosphere/include/stratosphere/fat.hpp
Normal file
17
libraries/libstratosphere/include/stratosphere/fat.hpp
Normal 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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
57
libraries/libstratosphere/include/stratosphere/fs/fs_mmc.hpp
Normal file
57
libraries/libstratosphere/include/stratosphere/fs/fs_mmc.hpp
Normal 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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) \
|
||||
|
||||
@@ -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>
|
||||
|
||||
30
libraries/libstratosphere/include/stratosphere/gc/gc.hpp
Normal file
30
libraries/libstratosphere/include/stratosphere/gc/gc.hpp
Normal 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);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ namespace ams::pm {
|
||||
LaunchFlagsDeprecated_SignalOnStart = (1 << 5),
|
||||
};
|
||||
|
||||
struct ResourceLimitValues {
|
||||
struct ResourceLimitValue {
|
||||
u64 physical_memory;
|
||||
u32 thread_count;
|
||||
u32 event_count;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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>);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
35
libraries/libstratosphere/source/fs/fs_error_info.cpp
Normal file
35
libraries/libstratosphere/source/fs/fs_error_info.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
109
libraries/libstratosphere/source/fs/fs_mmc.cpp
Normal file
109
libraries/libstratosphere/source/fs/fs_mmc.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
193
stratosphere/pm/source/impl/pm_process_tracker.cpp
Normal file
193
stratosphere/pm/source/impl/pm_process_tracker.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
57
stratosphere/pm/source/impl/pm_process_tracker.hpp
Normal file
57
stratosphere/pm/source/impl/pm_process_tracker.hpp
Normal 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();
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
@@ -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);
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace ams::pm {
|
||||
namespace {
|
||||
|
||||
/* Global bootmode. */
|
||||
BootMode g_boot_mode = BootMode::Normal;
|
||||
constinit BootMode g_boot_mode = BootMode::Normal;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,6 +42,4 @@ namespace haze {
|
||||
|
||||
using Result = ::ams::Result;
|
||||
|
||||
static constexpr u32 UsbBulkPacketBufferSize = 1_MB;
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
183
troposphere/haze/include/haze/ptp_responder_types.hpp
Normal file
183
troposphere/haze/include/haze/ptp_responder_types.hpp
Normal 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];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
15
troposphere/haze/source/console_fsh.glsl
Normal file
15
troposphere/haze/source/console_fsh.glsl
Normal 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);
|
||||
}
|
||||
35
troposphere/haze/source/console_vsh.glsl
Normal file
35
troposphere/haze/source/console_vsh.glsl
Normal 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];
|
||||
}
|
||||
487
troposphere/haze/source/gpu_console.c
Normal file
487
troposphere/haze/source/gpu_console.c
Normal 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, ©Src, &tilesetView, ©Dst, 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;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
203
troposphere/haze/source/ptp_responder_android_operations.cpp
Normal file
203
troposphere/haze/source/ptp_responder_android_operations.cpp
Normal 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));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
418
troposphere/haze/source/ptp_responder_mtp_operations.cpp
Normal file
418
troposphere/haze/source/ptp_responder_mtp_operations.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
507
troposphere/haze/source/ptp_responder_ptp_operations.cpp
Normal file
507
troposphere/haze/source/ptp_responder_ptp_operations.cpp
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user