Push current Rust rewrite (working, with some bugs)

This commit is contained in:
XorTroll 2020-09-08 22:52:12 +02:00
parent 1e31f0d241
commit cb57c056f7
47 changed files with 1123 additions and 27376 deletions

4
.gitignore vendored
View File

@ -11,4 +11,6 @@ build/
bin/
obj/
.vs/
SdOut/
SdOut/
Cargo.lock
target/

3
.gitmodules vendored
View File

@ -1,6 +1,3 @@
[submodule "libtesla"]
path = libtesla
url = https://github.com/WerWolv/libtesla
[submodule "Atmosphere-libs"]
path = Atmosphere-libs
url = https://github.com/Atmosphere-NX/Atmosphere-libs

@ -1 +0,0 @@
Subproject commit cac5957d3f4b1417cf76a83cf704a14a254dd4dc

View File

@ -1,35 +1,21 @@
export EMUIIBO_MAJOR := 0
export EMUIIBO_MINOR := 5
export EMUIIBO_MICRO := 2
export EMUIIBO_MINOR := 6
export EMUIIBO_MICRO := 0
.PHONY: all dev clean
.PHONY: all clean
base:
@$(MAKE) -C Atmosphere-libs/libstratosphere/
@$(MAKE) -C emuiibo/
all:
@cd emuiibo; sprinkle nsp --release
@$(MAKE) -C overlay/
@rm -rf $(CURDIR)/SdOut
@mkdir -p $(CURDIR)/SdOut/atmosphere/contents/0100000000000352/flags
@touch $(CURDIR)/SdOut/atmosphere/contents/0100000000000352/flags/boot2.flag
@cp $(CURDIR)/emuiibo/emuiibo.nsp $(CURDIR)/SdOut/atmosphere/contents/0100000000000352/exefs.nsp
@cp $(CURDIR)/emuiibo/target/aarch64-none-elf/release/emuiibo.nsp $(CURDIR)/SdOut/atmosphere/contents/0100000000000352/exefs.nsp
@mkdir -p $(CURDIR)/SdOut/switch/.overlays
@cp $(CURDIR)/overlay/emuiibo.ovl $(CURDIR)/SdOut/switch/.overlays/emuiibo.ovl
setdev:
$(eval export EMUIIBO_DEV := true)
@echo
@echo WARNING - Building in dev mode! use this at your own risk...
@echo
nodev:
$(eval export EMUIIBO_DEV := false)
all: nodev base
dev: setdev base
clean:
@rm -rf $(CURDIR)/SdOut
@$(MAKE) clean -C libstratosphere/
@$(MAKE) clean -C emuiibo/
@cd emuiibo; xargo clean
@$(MAKE) clean -C overlay/

22
emuiibo/Cargo.toml Normal file
View File

@ -0,0 +1,22 @@
[package]
name = "emuiibo"
version = "0.6.0"
authors = ["XorTroll"]
edition = "2018"
[dependencies]
nx = { git = "https://github.com/aarch64-switch-rs/nx" }
paste = "1.0"
[dependencies.serde]
version = ""
default-features = false
features = ["derive"]
[dependencies.serde_json]
version = ""
default-features = false
features = ["alloc"]
[package.metadata.sprinkle.nsp]
npdm = "npdm.json"

View File

@ -1,130 +0,0 @@
#---------------------------------------------------------------------------------
# pull in (modded) common stratosphere sysmodule configuration
#---------------------------------------------------------------------------------
include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../Atmosphere-libs/config/templates/stratosphere.mk
CXXFLAGS += -DEMUIIBO_MAJOR=$(EMUIIBO_MAJOR) -DEMUIIBO_MINOR=$(EMUIIBO_MINOR) -DEMUIIBO_MICRO=$(EMUIIBO_MICRO) -DEMUIIBO_DEV=$(EMUIIBO_DEV) -DEMUIIBO_VERSION=\"$(EMUIIBO_MAJOR).$(EMUIIBO_MINOR).$(EMUIIBO_MICRO)\"
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.c)) $(notdir $(wildcard $(dir)/*.board.*.c)) $(notdir $(wildcard $(dir)/*.os.*.c)), \
$(notdir $(wildcard $(dir)/*.c))))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).c)))
CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).c)))
CPPFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.cpp)) $(notdir $(wildcard $(dir)/*.board.*.cpp)) $(notdir $(wildcard $(dir)/*.os.*.cpp)), \
$(notdir $(wildcard $(dir)/*.cpp))))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).cpp)))
CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).cpp)))
SFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.s)) $(notdir $(wildcard $(dir)/*.board.*.s)) $(notdir $(wildcard $(dir)/*.os.*.s)), \
$(notdir $(wildcard $(dir)/*.s))))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).s)))
SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES := $(addsuffix .o,$(BINFILES)) \
$(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC)
ifeq ($(strip $(CONFIG_JSON)),)
jsons := $(wildcard *.json)
ifneq (,$(findstring $(TARGET).json,$(jsons)))
export APP_JSON := $(TOPDIR)/$(TARGET).json
else
ifneq (,$(findstring config.json,$(jsons)))
export APP_JSON := $(TOPDIR)/config.json
endif
endif
else
export APP_JSON := $(TOPDIR)/$(CONFIG_JSON)
endif
.PHONY: $(BUILD) clean all
#---------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@[ -d $@ ] || mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf
#---------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all : $(OUTPUT).nsp
ifeq ($(strip $(APP_JSON)),)
$(OUTPUT).nsp : $(OUTPUT).nso
else
$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm
endif
$(OUTPUT).nso : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

10
emuiibo/Xargo.toml Normal file
View File

@ -0,0 +1,10 @@
[dependencies.core]
stage = 0
[dependencies.alloc]
stage = 1
[dependencies.libc]
stage = 2
default-features = false
features = ["align", "rustc-dep-of-std"]

View File

@ -0,0 +1,35 @@
{
"abi-blacklist": [
"stdcall",
"fastcall",
"vectorcall",
"thiscall",
"win64",
"sysv64"
],
"arch": "aarch64",
"data-layout": "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
"env": "",
"executables": true,
"disable-redzone": true,
"position-independent-executables": true,
"features": "+a57,+strict-align,+crc,+crypto",
"pre-link-args": {
"ld.lld": ["-Taarch64-none-elf.ld"]
},
"linker": "rust-lld",
"linker-flavor": "ld.lld",
"linker-is-gnu": true,
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"os": "none",
"panic-strategy": "abort",
"relocation-model": "pic",
"target-c-int-width": "32",
"target-endian": "little",
"target-pointer-width": "64",
"exe-suffix": ".elf",
"trap-unreachable": true,
"emit-debug-gdb-scripts": true,
"requires-uwtable": true
}

View File

@ -0,0 +1,83 @@
OUTPUT_FORMAT(elf64-littleaarch64)
OUTPUT_ARCH(aarch64)
ENTRY(_start)
PHDRS
{
text PT_LOAD FLAGS(5);
rodata PT_LOAD FLAGS(4);
data PT_LOAD FLAGS(6);
bss PT_LOAD FLAGS(6);
dynamic PT_DYNAMIC;
}
SECTIONS
{
. = 0;
.text : ALIGN(0x1000) {
HIDDEN(__text_start = .);
KEEP(*(.text.jmp))
. = 0x80;
*(.text .text.*)
*(.plt .plt.*)
}
/* Read-only sections */
. = ALIGN(0x1000);
.module_name : { *(.module_name) } :rodata
.rodata : { *(.rodata .rodata.*) } :rodata
.mod0 : {
KEEP(crt0.nso.o(.data.mod0))
KEEP(crt0.nro.o(.data.mod0))
KEEP(crt0.lib.nro.o(.data.mod0))
}
.hash : { *(.hash) }
.dynsym : { *(.dynsym .dynsym.*) }
.dynstr : { *(.dynstr .dynstr.*) }
.rela.dyn : { *(.rela.dyn) }
.eh_frame : {
HIDDEN(__eh_frame_start = .);
*(.eh_frame .eh_frame.*)
HIDDEN(__eh_frame_end = .);
}
.eh_frame_hdr : {
HIDDEN(__eh_frame_hdr_start = .);
*(.eh_frame_hdr .eh_frame_hdr.*)
HIDDEN(__eh_frame_hdr_end = .);
}
/* Read-write sections */
. = ALIGN(0x1000);
.data : {
*(.data .data.*)
*(.got .got.*)
*(.got.plt .got.plt.*)
} :data
.dynamic : {
HIDDEN(__dynamic_start = .);
*(.dynamic)
}
/* BSS section */
. = ALIGN(0x1000);
.bss : {
HIDDEN(__bss_start = .);
*(.bss .bss.*)
*(COMMON)
. = ALIGN(8);
HIDDEN(__bss_end = .);
} :bss
}

View File

@ -1,56 +0,0 @@
#pragma once
#include <emu_Types.hpp>
#include <fs/fs_FileSystem.hpp>
namespace amiibo {
using AreaId = u32;
class AreaManager {
public:
static inline constexpr u32 DefaultSize = 0xD8;
private:
std::string dir;
inline std::string EncodeAreaDirectory() {
return fs::Concat(this->dir, "areas");
}
inline std::string EncodeAreaName(AreaId id) {
std::stringstream strm;
strm << "0x" << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << id << ".bin";
return strm.str();
}
inline std::string EncodeAreaFilePath(AreaId id) {
return fs::Concat(this->EncodeAreaDirectory(), EncodeAreaName(id));
}
void CreateImpl(AreaId id, const void *data, size_t size, bool recreate);
public:
AreaManager() {}
AreaManager(const std::string &amiibo_dir) : dir(amiibo_dir) {
fs::CreateDirectory(this->EncodeAreaDirectory());
}
inline void Create(AreaId id, const void *data, size_t size) {
this->CreateImpl(id, data, size, false);
}
inline void Recreate(AreaId id, const void *data, size_t size) {
this->CreateImpl(id, data, size, true);
}
bool Exists(AreaId id);
void Read(AreaId id, void *data, size_t size);
void Write(AreaId id, const void *data, size_t size);
size_t GetSize(AreaId id);
};
}

View File

@ -1,456 +0,0 @@
#pragma once
#include <emu_Types.hpp>
#include <amiibo/amiibo_Areas.hpp>
#include <ctime>
namespace amiibo {
struct AmiiboUuidInfo {
bool random_uuid;
u8 uuid[10];
};
// This is the amiibo data sent by IPC
struct VirtualAmiiboData : public ams::sf::LargeData {
AmiiboUuidInfo uuid;
char name[40 + 1];
Date first_write_date;
Date last_write_date;
MiiCharInfo mii_charinfo;
inline bool IsValid() {
return strlen(this->name);
}
};
class IVirtualAmiiboBase {
protected:
std::string path;
bool valid;
template<typename T>
inline T ReadPlain(JSON &json, const std::string &key) {
return json.value<T>(key, T());
}
template<typename T>
inline void WritePlain(JSON &json, const std::string &key, T t) {
json[key] = t;
}
inline bool HasKey(JSON &json, const std::string &key) {
return json.count(key);
}
inline Date MakeCurrentDate() {
Date cur_date = {};
auto cur_time = std::time(nullptr);
auto cur_time_local = std::localtime(&cur_time);
cur_date.year = cur_time_local->tm_year + 1900;
cur_date.month = cur_time_local->tm_mon + 1;
cur_date.day = cur_time_local->tm_mday;
return cur_date;
}
static inline MiiCharInfo GenerateRandomMii() {
MiiCharInfo charinfo = {};
MiiDatabase db;
auto rc = miiOpenDatabase(&db, MiiSpecialKeyCode_Normal);
if(R_SUCCEEDED(rc)) {
miiDatabaseBuildRandom(&db, MiiAge_All, MiiGender_All, MiiFaceColor_All, &charinfo);
miiDatabaseClose(&db);
}
return charinfo;
}
public:
IVirtualAmiiboBase() : valid(false) {}
IVirtualAmiiboBase(const std::string &amiibo_path) : path(amiibo_path), valid(true) {}
virtual std::string GetName() = 0;
virtual AmiiboUuidInfo GetUuidInfo() = 0;
virtual AmiiboId GetAmiiboId() = 0;
virtual std::string GetMiiCharInfoFileName() = 0;
virtual Date GetFirstWriteDate() = 0;
virtual Date GetLastWriteDate() = 0;
virtual u16 GetWriteCounter() = 0;
virtual u32 GetVersion() = 0;
virtual void FullyRemove() = 0;
inline std::string GetPath() {
return this->path;
}
inline std::string GetMiiCharInfoPath() {
// This won't be used for raw bin amiibo format, plus it wouldn't be valid since path is a file :P
return fs::Concat(this->path, this->GetMiiCharInfoFileName());
}
inline MiiCharInfo ReadMiiCharInfo() {
MiiCharInfo charinfo = {};
auto charinfo_path = this->GetMiiCharInfoPath();
if(fs::IsFile(charinfo_path)) {
charinfo = fs::Read<MiiCharInfo>(charinfo_path);
}
else {
// The amiibo has no mii charinfo data
// This might be a new emutool amiibo which needs a mii
// Let's generate a random mii then
charinfo = GenerateRandomMii();
// Save it too, for the next time
fs::Save(charinfo_path, charinfo);
}
return charinfo;
}
inline bool IsValid() {
return this->valid;
}
};
// Old formats supported by emuiibo (how are they named here):
// - Bin (raw binaries, always supported but mainly used in emuiibo 0.1)
// - V3 (format used in emuiibo 0.3.x and 0.4)
class VirtualBinAmiibo;
class VirtualAmiiboV3;
class VirtualAmiibo : public IVirtualAmiiboBase {
friend class IVirtualAmiiboBase;
public:
static inline constexpr u32 DefaultProtocol = UINT32_MAX;
static inline constexpr u32 DefaultTagType = UINT32_MAX;
private:
JSON amiibo_data;
AreaManager area_manager;
inline void ReadByteArray(u8 *out_arr, const std::string &key) {
auto array = this->amiibo_data[key];
for(u32 i = 0; i < array.size(); i++) {
auto item = array[i];
auto value = item.get<u32>();
auto byte = (u8)(value & 0xff);
out_arr[i] = byte;
}
}
inline void WriteByteArray(u8 *arr, u32 len, const std::string &key) {
auto array = JSON::array();
for(u32 i = 0; i < len; i++) {
auto byte = (u32)arr[i];
array[i] = byte;
}
this->amiibo_data[key] = array;
}
inline Date ReadDate(const std::string &key) {
Date date = {};
auto date_item = this->amiibo_data[key];
auto y_item = date_item["y"];
auto m_item = date_item["m"];
auto d_item = date_item["d"];
date.year = y_item.get<u16>();
date.month = m_item.get<u8>();
date.day = m_item.get<u8>();
return date;
}
inline void WriteDate(const std::string &key, Date date) {
auto date_obj = JSON::object();
date_obj["y"] = date.year;
date_obj["m"] = date.month;
date_obj["d"] = date.day;
this->amiibo_data[key] = date_obj;
}
public:
VirtualAmiibo() : IVirtualAmiiboBase() {}
VirtualAmiibo(const std::string &amiibo_dir);
std::string GetName() override;
void SetName(const std::string &name);
AmiiboUuidInfo GetUuidInfo() override;
void SetUuidInfo(AmiiboUuidInfo info);
AmiiboId GetAmiiboId() override;
void SetAmiiboId(AmiiboId id);
std::string GetMiiCharInfoFileName() override;
void SetMiiCharInfoFileName(const std::string &char_info_name);
Date GetFirstWriteDate() override;
void SetFirstWriteDate(Date date);
Date GetLastWriteDate() override;
void SetLastWriteDate(Date date);
u16 GetWriteCounter() override;
void SetWriteCounter(u16 counter);
// Increases the write counter
void NotifyWritten();
void Save();
u32 GetVersion() override;
void SetVersion(u32 version);
void FullyRemove() override;
TagInfo ProduceTagInfo();
RegisterInfo ProduceRegisterInfo();
ModelInfo ProduceModelInfo();
CommonInfo ProduceCommonInfo();
VirtualAmiiboData ProduceData();
inline AreaManager &GetAreaManager() {
return this->area_manager;
}
template<typename V>
static inline constexpr bool HasMiiCharInfo() {
static_assert(std::is_base_of_v<IVirtualAmiiboBase, V>, "Invalid amiibo type");
// All virtual amiibo formats have a mii charinfo file except for raw bin amiibos
if constexpr(std::is_same_v<V, VirtualBinAmiibo>) {
return false;
}
return true;
}
template<typename V>
static inline bool ConvertVirtualAmiibo(const std::string &path) {
static_assert(std::is_base_of_v<IVirtualAmiiboBase, V>, "Invalid amiibo type");
V old_amiibo(path);
VirtualAmiibo amiibo;
if(!old_amiibo.IsValid()) {
return false;
}
amiibo.SetName(old_amiibo.GetName());
amiibo.SetUuidInfo(old_amiibo.GetUuidInfo());
amiibo.SetAmiiboId(old_amiibo.GetAmiiboId());
amiibo.SetFirstWriteDate(old_amiibo.GetFirstWriteDate());
amiibo.SetLastWriteDate(old_amiibo.GetLastWriteDate());
amiibo.SetWriteCounter(old_amiibo.GetWriteCounter());
amiibo.SetVersion(old_amiibo.GetVersion());
auto has_charinfo = HasMiiCharInfo<V>();
if(has_charinfo) {
// If the mii file path is invalid, we must create a new mii
// This should (only?) happen if a virtual amiibo is not properly generated or is corrupted...?
has_charinfo = fs::IsFile(old_amiibo.GetMiiCharInfoPath());
}
MiiCharInfo charinfo = {};
if(has_charinfo) {
charinfo = old_amiibo.ReadMiiCharInfo();
amiibo.SetMiiCharInfoFileName(old_amiibo.GetMiiCharInfoFileName());
}
else {
// Default mii charinfo file name
amiibo.SetMiiCharInfoFileName("mii-charinfo.bin");
// Generate a random mii if the original virtual amiibo doesn't have one
// Otherwise, the charinfo struct should already be populated
charinfo = GenerateRandomMii();
}
// Manually indicate the amiibo is valid
amiibo.path = old_amiibo.GetPath();
amiibo.valid = true;
auto areas_path = fs::Concat(old_amiibo.GetPath(), "areas");
const bool has_areas = fs::IsDirectory(areas_path);
if(has_areas) {
// Move areas to a temp location
fs::RecreateDirectory(consts::TempConversionAreasDir);
FS_FOR(areas_path, entry, area_path, {
if(fs::MatchesExtension(area_path, "bin")) {
auto out_path = fs::Concat(consts::TempConversionAreasDir, entry);
fs::CopyFile(area_path, out_path);
}
});
}
old_amiibo.FullyRemove();
amiibo.Save();
// After creating the new amiibo's layout, save the mii
fs::Save(amiibo.GetMiiCharInfoPath(), charinfo);
if(has_areas) {
fs::CreateDirectory(areas_path);
// Move temp areas back to the new amiibo's dir
FS_FOR(consts::TempConversionAreasDir, entry, area_path, {
auto out_path = fs::Concat(areas_path, entry);
fs::CopyFile(area_path, out_path);
});
fs::RecreateDirectory(consts::TempConversionAreasDir);
}
return true;
}
// This just checks if the files needed are present
template<typename V>
static inline bool IsValidVirtualAmiiboImpl(const std::string &amiibo_path) {
static_assert(std::is_base_of_v<IVirtualAmiiboBase, V>, "Invalid amiibo type");
if constexpr(std::is_same_v<V, VirtualBinAmiibo>) {
// Just check if it's a file and ends in .bin :P
return fs::IsFile(amiibo_path) && fs::MatchesExtension(amiibo_path, "bin");
}
else if constexpr(std::is_same_v<V, VirtualAmiiboV3>) {
// The V3 format was made of tag.json, model.json, common.json and register.json files
return fs::IsDirectory(amiibo_path) && fs::IsFile(fs::Concat(amiibo_path, "tag.json")) && fs::IsFile(fs::Concat(amiibo_path, "common.json")) && fs::IsFile(fs::Concat(amiibo_path, "model.json")) && fs::IsFile(fs::Concat(amiibo_path, "register.json"));
}
else if constexpr(std::is_same_v<V, VirtualAmiibo>) {
// The current format has an amiibo.flag file in case anyone would like to enable/disable a virtual amiibo from being recognized or used by emuiibo
return fs::IsDirectory(amiibo_path) && fs::IsFile(fs::Concat(amiibo_path, "amiibo.json")) && fs::IsFile(fs::Concat(amiibo_path, "amiibo.flag"));
}
return false;
}
// This two also validate that the amiibo contents are correct
template<typename V>
static inline bool IsValidVirtualAmiiboType(const std::string &amiibo_path) {
static_assert(std::is_base_of_v<IVirtualAmiiboBase, V>, "Invalid amiibo type");
if(IsValidVirtualAmiiboImpl<V>(amiibo_path)) {
V amiibo(amiibo_path);
return amiibo.IsValid();
}
return false;
}
static inline bool IsValidVirtualAmiibo(const std::string &amiibo_path) {
return IsValidVirtualAmiiboType<VirtualAmiibo>(amiibo_path);
}
static inline bool GetValidVirtualAmiibo(const std::string &amiibo_path, VirtualAmiibo &out_amiibo) {
if(IsValidVirtualAmiiboImpl<VirtualAmiibo>(amiibo_path)) {
out_amiibo = VirtualAmiibo(amiibo_path);
return out_amiibo.IsValid();
}
return false;
}
};
// 0.3.x/0.4 virtual amiibo format
class VirtualAmiiboV3 : public IVirtualAmiiboBase {
private:
JSON tag_data;
JSON register_data;
JSON common_data;
JSON model_data;
inline std::string GetJSONFileName(const std::string &name) {
return fs::Concat(this->path, name + ".json");
}
inline void ReadStringByteArray(JSON &json, u8 *out_arr, const std::string &key) {
auto array = this->ReadPlain<std::string>(json, key);
if(!array.empty()) {
std::stringstream strm;
for(u32 i = 0; i < array.length(); i += 2) {
strm << std::hex << array.substr(i, 2);
u32 tmpbyte = 0;
strm >> tmpbyte;
out_arr[i / 2] = (u8)(tmpbyte & 0xff);
strm.str("");
strm.clear();
}
}
}
inline Date ReadStringDate(JSON &json, const std::string &key) {
auto date_str = this->ReadPlain<std::string>(json, key);
auto date_year_str = date_str.substr(0, 4);
auto date_month_str = date_str.substr(5, 2);
auto date_day_str = date_str.substr(8, 2);
Date date = {};
date.year = (u16)std::stoi(date_year_str);
date.month = (u8)std::stoi(date_month_str);
date.day = (u8)std::stoi(date_day_str);
return date;
}
public:
VirtualAmiiboV3(const std::string &amiibo_dir);
std::string GetName() override;
AmiiboUuidInfo GetUuidInfo() override;
AmiiboId GetAmiiboId() override;
std::string GetMiiCharInfoFileName() override;
Date GetFirstWriteDate() override;
Date GetLastWriteDate() override;
u16 GetWriteCounter() override;
u32 GetVersion() override;
void FullyRemove() override;
};
// Raw binary, 0.1 virtual amiibo format
struct RawAmiibo {
u8 uuid[10];
u8 unk1[6];
u8 unk2[1];
u16 unk_counter;
u8 unk3;
u8 unk_crypto[0x40];
u8 amiibo_id[8];
} PACKED;
class VirtualBinAmiibo : public IVirtualAmiiboBase {
private:
RawAmiibo raw_data;
Date base_date;
public:
VirtualBinAmiibo() : IVirtualAmiiboBase() {}
VirtualBinAmiibo(const std::string &path);
std::string GetName() override;
AmiiboUuidInfo GetUuidInfo() override;
AmiiboId GetAmiiboId() override;
std::string GetMiiCharInfoFileName() override;
Date GetFirstWriteDate() override;
Date GetLastWriteDate() override;
u16 GetWriteCounter() override;
u32 GetVersion() override;
void FullyRemove() override;
};
}

View File

@ -1,36 +0,0 @@
#pragma once
#include <sstream>
#include <fstream>
#include <mutex>
#include <stratosphere.hpp>
#include <stratosphere/sf.hpp>
#include <json.hpp>
#include <emu_Consts.hpp>
#include <logger/logger_Logger.hpp>
using i32 = s32;
static inline constexpr Result Success = 0;
using JSON = nlohmann::json;
struct Version {
u8 major;
u8 minor;
u8 micro;
bool dev_build;
};
static_assert(sizeof(Version) == sizeof(u32), "Invalid Version struct!");
static inline constexpr Version CurrentVersion = { EMUIIBO_MAJOR, EMUIIBO_MINOR, EMUIIBO_MICRO, EMUIIBO_DEV };
#define EMU_DEFINE_RESULT(name, desc) static constexpr Result Result##name = MAKERESULT(Module, desc);
using Lock = ams::os::Mutex;
#define EMU_LOCK_SCOPE_WITH(mtx_name) std::scoped_lock lk(mtx_name);
#define EMU_DO_UNLESS(cond, ...) ({ if(!(cond)) { __VA_ARGS__ } })

View File

@ -1,14 +0,0 @@
#pragma once
#include <string>
namespace consts {
static inline const std::string EmuDir = "sdmc:/emuiibo";
static inline const std::string SettingsPath = EmuDir + "/settings.json";
static inline const std::string LogFilePath = EmuDir + "/emuiibo.log";
static inline const std::string AmiiboDir = EmuDir + "/amiibo";
static inline const std::string DumpedMiisDir = EmuDir + "/miis";
static inline const std::string TempConversionAreasDir = EmuDir + "/temp_areas";
}

View File

@ -1,41 +0,0 @@
#pragma once
#include <emu_Types.hpp>
namespace result {
// Official NFC/NFP sysmodule's results
namespace nfp {
static constexpr u32 Module = 115;
EMU_DEFINE_RESULT(DeviceNotFound, 64)
EMU_DEFINE_RESULT(NeedRestart, 96)
EMU_DEFINE_RESULT(AreaNeedsToBeCreated, 128)
EMU_DEFINE_RESULT(AccessIdMismatch, 152)
EMU_DEFINE_RESULT(AreaAlreadyCreated, 168)
}
// Our results
namespace emu {
static constexpr u32 Module = 352; // Like emuiibo's program ID (0100000000000352)
EMU_DEFINE_RESULT(NoActiveVirtualAmiibo, 1)
EMU_DEFINE_RESULT(InvalidVirtualAmiibo, 2)
EMU_DEFINE_RESULT(IteratorEndReached, 3)
EMU_DEFINE_RESULT(UnableToReadMii, 4)
}
#define EMU_R_ASSERT(rc) { \
auto res = (rc); \
if(R_FAILED(res)) { \
fatalThrow(static_cast<ams::Result>(res).GetValue()); \
} \
}
}

View File

@ -1,109 +0,0 @@
#pragma once
#include <emu_Base.hpp>
// An amiibo ID
struct CharacterId {
u16 game_character_id;
u8 character_variant;
} PACKED;
static_assert(sizeof(CharacterId) == 3, "Invalid CharacterId type");
// Amiibo ID format in 3DS and in amiibo bin dumps (the one used in old emuiibo formats too)
struct OldAmiiboId {
CharacterId character_id;
u8 figure_type;
u16 model_number;
u8 series;
u8 unk_2;
};
static_assert(sizeof(OldAmiiboId) == 8, "Invalid OldAmiiboId type");
// Amiibo ID format used in the console
struct AmiiboId {
CharacterId character_id;
u8 series;
u16 model_number;
u8 figure_type;
static inline constexpr AmiiboId FromOldAmiiboId(OldAmiiboId old_id) {
AmiiboId id = {};
id.character_id = old_id.character_id;
id.series = old_id.series;
id.model_number = old_id.model_number;
id.figure_type = old_id.figure_type;
return id;
}
inline constexpr u64 Encode() {
union {
u64 v;
u8 u[8];
} converter = {0};
for(u32 i = 0; i < sizeof(AmiiboId); i++) {
converter.u[i] = ((u8*)this)[i];
}
return converter.v;
}
} PACKED;
static_assert(sizeof(AmiiboId) == 7, "Invalid AmiiboId type");
// IPC wrappers for NFP amiibo data structures
struct TagInfo : public ams::sf::LargeData {
NfpTagInfo info;
};
// More detailed amiibo info structs
struct Date {
u16 year;
u8 month;
u8 day;
};
static_assert(sizeof(Date) == 4, "Invalid Date type");
struct ModelInfoImpl {
AmiiboId id;
u8 reserved[0x39];
} PACKED;
static_assert(sizeof(ModelInfoImpl) == sizeof(NfpModelInfo), "Invalid ModelInfo type");
struct ModelInfo : public ams::sf::LargeData {
ModelInfoImpl info;
};
struct RegisterInfoImpl {
static inline constexpr size_t AmiiboNameLength = 40;
MiiCharInfo mii;
Date first_write_date;
char name[AmiiboNameLength + 1];
u8 font_region;
u8 reserved[0x7a];
} PACKED;
static_assert(sizeof(RegisterInfoImpl) == sizeof(NfpRegisterInfo), "Invalid RegisterInfo type");
struct RegisterInfo : public ams::sf::LargeData {
RegisterInfoImpl info;
};
struct CommonInfo : public ams::sf::LargeData {
NfpCommonInfo info;
};
// TODO: RE this struct
struct AdminInfo : public ams::sf::LargeData {
u8 raw[0x40];
};

View File

@ -1,166 +0,0 @@
#pragma once
#include <emu_Types.hpp>
#include <iomanip>
#include <sys/stat.h>
#include <dirent.h>
namespace fs {
inline void CreateDirectory(const std::string &path) {
mkdir(path.c_str(), 777);
}
template<mode_t Mode>
inline bool StatImpl(const std::string &path) {
struct stat st;
if(stat(path.c_str(), &st) == 0) {
if(st.st_mode & Mode) {
return true;
}
}
return false;
}
inline bool IsFile(const std::string &path) {
return StatImpl<S_IFREG>(path);
}
inline bool IsDirectory(const std::string &path) {
return StatImpl<S_IFDIR>(path);
}
inline bool MatchesExtension(const std::string &path, const std::string &ext) {
return path.substr(path.find_last_of(".") + 1) == ext;
}
inline std::string GetBaseName(const std::string &path) {
return path.substr(path.find_last_of("/") + 1);
}
inline std::string RemoveExtension(const std::string &path) {
return path.substr(0, path.find_last_of("."));
}
inline void DeleteFile(const std::string &path) {
remove(path.c_str());
}
inline void DeleteDirectory(const std::string &path) {
fsdevDeleteDirectoryRecursively(path.c_str());
}
inline void RecreateDirectory(const std::string &path) {
DeleteDirectory(path);
CreateDirectory(path);
}
inline void CreateEmptyFile(const std::string &path) {
std::ofstream ofs(path);
}
inline size_t GetFileSize(const std::string &path) {
struct stat st;
if(stat(path.c_str(), &st) == 0) {
return st.st_size;
}
return 0;
}
inline void ConcatImpl(std::string &base, const std::string &p) {
if(base.back() != '/') {
if(p.front() != '/') {
base += '/';
}
}
base += p;
}
template<typename ...Ps>
inline std::string Concat(const std::string &base, Ps &&...paths) {
// Generate a copy
auto ret = base;
(ConcatImpl(ret, paths), ...);
return ret;
}
inline JSON LoadJSONFile(const std::string &path) {
std::ifstream ifs(path);
if(ifs.good()) {
return JSON::parse(ifs);
}
return JSON::object();
}
inline void SaveJSONFile(const std::string &path, JSON &json) {
std::ofstream ofs(path);
if(ofs.good()) {
ofs << std::setw(4) << json;
}
}
template<typename T>
inline void Save(const std::string &path, T t) {
auto f = fopen(path.c_str(), "wb");
if(f) {
fwrite(&t, 1, sizeof(T), f);
fclose(f);
}
}
template<typename T>
inline T Read(const std::string &path) {
T t = T();
const size_t file_sz = GetFileSize(path);
if(file_sz >= sizeof(T)) {
auto f = fopen(path.c_str(), "rb");
if(f) {
fread(&t, 1, sizeof(T), f);
fclose(f);
}
}
return t;
}
inline void CopyFile(const std::string &in_path, const std::string &out_path) {
auto fsize = GetFileSize(in_path);
auto inf = fopen(in_path.c_str(), "rb");
if(inf) {
auto outf = fopen(out_path.c_str(), "wb");
if(outf) {
if(fsize > 0) {
auto buf = new u8[fsize]();
fread(buf, 1, fsize, inf);
fwrite(buf, 1, fsize, outf);
delete[] buf;
}
fclose(outf);
}
fclose(inf);
}
}
inline void EnsureEmuiiboDirectories() {
CreateDirectory(consts::EmuDir);
CreateDirectory(consts::AmiiboDir);
CreateDirectory(consts::DumpedMiisDir);
CreateDirectory(consts::TempConversionAreasDir);
}
#define FS_FOR(path, entry_v, path_v, ...) { \
auto dir = opendir((path).c_str()); \
if(dir) { \
while(true) { \
auto dt = readdir(dir); \
if(dt == nullptr) { \
break; \
} \
std::string entry_v = dt->d_name; \
auto path_v = fs::Concat((path), entry_v); \
__VA_ARGS__ \
} \
closedir(dir); \
} \
}
}

View File

@ -1,106 +0,0 @@
#pragma once
#include <ipc/ipc_Utils.hpp>
#include <sys/sys_Emulation.hpp>
namespace ipc::emu {
namespace impl {
using namespace ams;
#define I_EMULATION_SERVICE_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, void, GetVersion, (ams::sf::Out<Version> out_version)) \
AMS_SF_METHOD_INFO(C, H, 1, void, GetVirtualAmiiboDirectory, (const ams::sf::OutBuffer &out_path_buf)) \
AMS_SF_METHOD_INFO(C, H, 2, void, GetEmulationStatus, (ams::sf::Out<sys::EmulationStatus> out_status)) \
AMS_SF_METHOD_INFO(C, H, 3, void, SetEmulationStatus, (sys::EmulationStatus status)) \
AMS_SF_METHOD_INFO(C, H, 4, ams::Result, GetActiveVirtualAmiibo, (ams::sf::Out<amiibo::VirtualAmiiboData> out_data, const ams::sf::OutBuffer &out_path_buf)) \
AMS_SF_METHOD_INFO(C, H, 5, ams::Result, SetActiveVirtualAmiibo, (const ams::sf::InBuffer &path_buf)) \
AMS_SF_METHOD_INFO(C, H, 6, void, ResetActiveVirtualAmiibo, ()) \
AMS_SF_METHOD_INFO(C, H, 7, void, GetActiveVirtualAmiiboStatus, (ams::sf::Out<sys::VirtualAmiiboStatus> out_status)) \
AMS_SF_METHOD_INFO(C, H, 8, void, SetActiveVirtualAmiiboStatus, (sys::VirtualAmiiboStatus status)) \
AMS_SF_METHOD_INFO(C, H, 9, void, IsApplicationIdIntercepted, (ams::sf::Out<bool> out_intercepted, u64 app_id)) \
AMS_SF_METHOD_INFO(C, H, 10, void, IsCurrentApplicationIdIntercepted, (ams::sf::Out<bool> out_intercepted)) \
AMS_SF_METHOD_INFO(C, H, 11, ams::Result, TryParseVirtualAmiibo, (const ams::sf::InBuffer &path_buf, ams::sf::Out<amiibo::VirtualAmiiboData> out_data))
AMS_SF_DEFINE_INTERFACE(IEmulationService, I_EMULATION_SERVICE_INTERFACE_INFO)
}
constexpr ams::sm::ServiceName ServiceName = ams::sm::ServiceName::Encode("emuiibo");
class EmulationService final {
public:
void GetVersion(ams::sf::Out<Version> out_version) {
out_version.SetValue(CurrentVersion);
}
void GetVirtualAmiiboDirectory(const ams::sf::OutBuffer &out_path_buf) {
CopyStringToOutBuffer(consts::AmiiboDir, out_path_buf);
}
void GetEmulationStatus(ams::sf::Out<sys::EmulationStatus> out_status) {
auto status = sys::GetEmulationStatus();
EMU_LOG_FMT("Emulation status: " << static_cast<u32>(status))
out_status.SetValue(status);
}
void SetEmulationStatus(sys::EmulationStatus status) {
EMU_LOG_FMT("Emulation status: " << static_cast<u32>(status))
sys::SetEmulationStatus(status);
}
ams::Result GetActiveVirtualAmiibo(ams::sf::Out<amiibo::VirtualAmiiboData> out_data, const ams::sf::OutBuffer &out_path_buf) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
R_UNLESS(amiibo.IsValid(), result::emu::ResultNoActiveVirtualAmiibo);
out_data.SetValue(amiibo.ProduceData());
CopyStringToOutBuffer(amiibo.GetPath(), out_path_buf);
return ams::ResultSuccess();
}
ams::Result SetActiveVirtualAmiibo(const ams::sf::InBuffer &path_buf) {
auto path = CopyStringFromInBuffer(path_buf);
amiibo::VirtualAmiibo amiibo;
R_UNLESS(amiibo::VirtualAmiibo::GetValidVirtualAmiibo(path, amiibo), result::emu::ResultInvalidVirtualAmiibo);
sys::SetActiveVirtualAmiibo(amiibo);
return ams::ResultSuccess();
}
void ResetActiveVirtualAmiibo() {
amiibo::VirtualAmiibo empty_amiibo;
sys::SetActiveVirtualAmiibo(empty_amiibo);
}
void GetActiveVirtualAmiiboStatus(ams::sf::Out<sys::VirtualAmiiboStatus> out_status) {
auto status = sys::GetActiveVirtualAmiiboStatus();
out_status.SetValue(status);
}
void SetActiveVirtualAmiiboStatus(sys::VirtualAmiiboStatus status) {
sys::SetActiveVirtualAmiiboStatus(status);
}
void IsApplicationIdIntercepted(ams::sf::Out<bool> out_intercepted, u64 app_id) {
out_intercepted.SetValue(sys::IsApplicationIdIntercepted(app_id));
}
void IsCurrentApplicationIdIntercepted(ams::sf::Out<bool> out_intercepted) {
out_intercepted.SetValue(sys::IsCurrentApplicationIdIntercepted());
}
ams::Result TryParseVirtualAmiibo(const ams::sf::InBuffer &path_buf, ams::sf::Out<amiibo::VirtualAmiiboData> out_data) {
auto path = CopyStringFromInBuffer(path_buf);
amiibo::VirtualAmiibo amiibo;
R_UNLESS(amiibo::VirtualAmiibo::GetValidVirtualAmiibo(path, amiibo), result::emu::ResultInvalidVirtualAmiibo);
out_data.SetValue(amiibo.ProduceData());
return ams::ResultSuccess();
}
};
static_assert(impl::IsIEmulationService<EmulationService>);
}

View File

@ -1,19 +0,0 @@
#pragma once
#include <stratosphere.hpp>
namespace ipc {
inline void CopyStringToOutBuffer(const std::string &str, const ams::sf::OutBuffer &out_buf) {
const size_t str_len = std::min(str.length(), out_buf.GetSize());
strncpy(reinterpret_cast<char*>(out_buf.GetPointer()), str.c_str(), str_len);
}
inline std::string CopyStringFromInBuffer(const ams::sf::InBuffer &buf) {
const size_t str_len = buf.GetSize();
char str_buf[str_len + 1] = {0};
strncpy(str_buf, reinterpret_cast<const char*>(buf.GetPointer()), str_len);
return str_buf;
}
}

View File

@ -1,164 +0,0 @@
#pragma once
#include <ipc/nfp/nfp_Types.hpp>
#include <emu_Results.hpp>
#include <sys/sys_Emulation.hpp>
namespace ipc::nfp {
namespace impl {
using namespace ams;
#define I_COMMON_INTERFACE_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, void, Initialize, (const ams::sf::ClientAppletResourceUserId &client_aruid, const ams::sf::ClientProcessId &client_pid, const ams::sf::InBuffer &mcu_data)) \
AMS_SF_METHOD_INFO(C, H, 1, void, Finalize, ()) \
AMS_SF_METHOD_INFO(C, H, 2, ams::Result, ListDevices, (const ams::sf::OutPointerArray<DeviceHandle> &out_devices, ams::sf::Out<s32> out_count)) \
AMS_SF_METHOD_INFO(C, H, 3, ams::Result, StartDetection, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 4, ams::Result, StopDetection, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 5, ams::Result, Mount, (DeviceHandle handle, u32 type, u32 target)) \
AMS_SF_METHOD_INFO(C, H, 6, ams::Result, Unmount, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 10, ams::Result, Flush, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 11, ams::Result, Restore, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 13, ams::Result, GetTagInfo, (ams::sf::Out<TagInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 14, ams::Result, GetRegisterInfo, (ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 15, ams::Result, GetCommonInfo, (ams::sf::Out<CommonInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 16, ams::Result, GetModelInfo, (ams::sf::Out<ModelInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 17, ams::Result, AttachActivateEvent, (DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event)) \
AMS_SF_METHOD_INFO(C, H, 18, ams::Result, AttachDeactivateEvent, (DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event)) \
AMS_SF_METHOD_INFO(C, H, 19, void, GetState, (ams::sf::Out<u32> state)) \
AMS_SF_METHOD_INFO(C, H, 20, void, GetDeviceState, (DeviceHandle handle, ams::sf::Out<u32> state)) \
AMS_SF_METHOD_INFO(C, H, 21, ams::Result, GetNpadId, (DeviceHandle handle, ams::sf::Out<u32> npad_id)) \
AMS_SF_METHOD_INFO(C, H, 23, ams::Result, AttachAvailabilityChangeEvent, (ams::sf::Out<ams::sf::CopyHandle> event))
AMS_SF_DEFINE_INTERFACE(ICommonInterface, I_COMMON_INTERFACE_INTERFACE_INFO)
#define I_MANAGER_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, ams::Result, CreateInterface, (ams::sf::Out<std::shared_ptr<ICommonInterface>> out))
AMS_SF_DEFINE_MITM_INTERFACE(IManager, I_MANAGER_INTERFACE_INFO)
}
class CommonInterface {
public:
static constexpr u32 HandheldNpadId = 0x20;
static constexpr u32 Player1NpadId = 0;
protected:
NfpState state;
NfpDeviceState device_state;
ams::os::SystemEventType event_activate;
ams::os::SystemEventType event_deactivate;
ams::os::SystemEventType event_availability_change;
Service forward_service;
u64 client_app_id;
Lock amiibo_update_lock;
Thread amiibo_update_thread;
bool should_exit_thread;
protected:
NfpState GetStateValue();
void SetStateValue(NfpState val);
NfpDeviceState GetDeviceStateValue();
void SetDeviceStateValue(NfpDeviceState val);
inline void NotifyShouldExitThread() {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
this->should_exit_thread = true;
}
inline void NotifyThreadExitAndWait() {
this->NotifyShouldExitThread();
EMU_R_ASSERT(threadWaitForExit(&this->amiibo_update_thread));
}
template<typename T>
inline constexpr void IsInStateImpl(bool &out, T base, T state) {
if(base == state) {
out = true;
}
}
template<typename T, typename ...Ss>
inline constexpr bool IsStateAny(Ss &&...states) {
static_assert(std::is_same_v<T, NfpState> || std::is_same_v<T, NfpDeviceState>, "Invalid type");
bool ret = false;
if constexpr(std::is_same_v<T, NfpState>) {
auto state = this->GetStateValue();
(IsInStateImpl(ret, state, states), ...);
return ret;
}
else if constexpr(std::is_same_v<T, NfpDeviceState>) {
auto state = this->GetDeviceStateValue();
(IsInStateImpl(ret, state, states), ...);
return ret;
}
return false;
}
public:
CommonInterface(Service fwd, u64 app_id);
virtual ~CommonInterface();
void HandleVirtualAmiiboStatus(sys::VirtualAmiiboStatus status);
inline bool ShouldExitThread() {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
return this->should_exit_thread;
}
public:
void Initialize(const ams::sf::ClientAppletResourceUserId &client_aruid, const ams::sf::ClientProcessId &client_pid, const ams::sf::InBuffer &mcu_data);
void Finalize();
ams::Result ListDevices(const ams::sf::OutPointerArray<DeviceHandle> &out_devices, ams::sf::Out<s32> out_count);
ams::Result StartDetection(DeviceHandle handle);
ams::Result StopDetection(DeviceHandle handle);
ams::Result Mount(DeviceHandle handle, u32 type, u32 target);
ams::Result Unmount(DeviceHandle handle);
ams::Result Flush(DeviceHandle handle);
ams::Result Restore(DeviceHandle handle);
ams::Result GetTagInfo(ams::sf::Out<TagInfo> out_info, DeviceHandle handle);
ams::Result GetRegisterInfo(ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle);
ams::Result GetCommonInfo(ams::sf::Out<CommonInfo> out_info, DeviceHandle handle);
ams::Result GetModelInfo(ams::sf::Out<ModelInfo> out_info, DeviceHandle handle);
ams::Result AttachActivateEvent(DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event);
ams::Result AttachDeactivateEvent(DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event);
void GetState(ams::sf::Out<u32> state);
void GetDeviceState(DeviceHandle handle, ams::sf::Out<u32> state);
ams::Result GetNpadId(DeviceHandle handle, ams::sf::Out<u32> npad_id);
ams::Result AttachAvailabilityChangeEvent(ams::sf::Out<ams::sf::CopyHandle> event);
};
static_assert(impl::IsICommonInterface<CommonInterface>);
class ManagerBase : public ams::sf::MitmServiceImplBase {
protected:
static ams::Result CreateForwardInterface(Service *manager, Service *out);
template<typename I, typename T>
inline ams::Result CreateInterfaceImpl(ams::sf::Out<std::shared_ptr<I>> &out) {
EMU_LOG_FMT("Application ID 0x" << std::hex << std::setw(16) << std::setfill('0') << std::uppercase << this->client_info.program_id.value);
Service outsrv;
R_TRY(CreateForwardInterface(this->forward_service.get(), &outsrv));
const ams::sf::cmif::DomainObjectId object_id { serviceGetObjectId(&outsrv) };
EMU_LOG_FMT("MakeShared done");
out.SetValue(ams::sf::MakeShared<I, T>(outsrv, this->client_info.program_id.value), object_id);
EMU_LOG_FMT("Returning success...");
return ams::ResultSuccess();
}
public:
using MitmServiceImplBase::MitmServiceImplBase;
static bool ShouldMitm(const ams::sm::MitmProcessInfo &client_info) {
return sys::GetEmulationStatus() == sys::EmulationStatus::On;
}
};
}

View File

@ -1,14 +0,0 @@
#pragma once
#include <emu_Results.hpp>
namespace ipc::nfp {
struct DeviceHandle {
u32 npad_id;
u8 reserved[4];
};
static_assert(sizeof(DeviceHandle) == sizeof(u64), "Invalid DeviceHandle struct");
}

View File

@ -1,42 +0,0 @@
#pragma once
#include <ipc/nfp/nfp_Common.hpp>
namespace ipc::nfp::sys {
namespace impl {
using namespace ams;
#define I_SYSTEM_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 100, ams::Result, Format, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 101, ams::Result, GetAdminInfo, (ams::sf::Out<AdminInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 102, ams::Result, GetRegisterInfo2, (ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 103, ams::Result, SetRegisterInfo, (DeviceHandle handle, const RegisterInfo &info)) \
AMS_SF_METHOD_INFO(C, H, 104, ams::Result, DeleteRegisterInfo, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 105, ams::Result, DeleteApplicationArea, (DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 106, ams::Result, ExistsApplicationArea, (ams::sf::Out<u8> out_exists, DeviceHandle handle))
AMS_SF_DEFINE_INTERFACE(ISystem, I_SYSTEM_INTERFACE_INFO)
}
class System : public CommonInterface {
public:
using CommonInterface::CommonInterface;
public:
ams::Result Format(DeviceHandle handle);
ams::Result GetAdminInfo(ams::sf::Out<AdminInfo> out_info, DeviceHandle handle);
ams::Result GetRegisterInfo2(ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle);
ams::Result SetRegisterInfo(DeviceHandle handle, const RegisterInfo &info);
ams::Result DeleteRegisterInfo(DeviceHandle handle);
ams::Result DeleteApplicationArea(DeviceHandle handle);
ams::Result ExistsApplicationArea(ams::sf::Out<u8> out_exists, DeviceHandle handle);
};
static_assert(nfp::impl::IsICommonInterface<System>);
static_assert(impl::IsISystem<System>);
}

View File

@ -1,32 +0,0 @@
#pragma once
#include <ipc/nfp/sys/sys_System.hpp>
namespace ipc::nfp::sys {
namespace impl {
using namespace ams;
#define I_SYSTEM_MANAGER_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, ams::Result, CreateSystemInterface, (ams::sf::Out<std::shared_ptr<ISystem>> out))
AMS_SF_DEFINE_MITM_INTERFACE(ISystemManager, I_SYSTEM_MANAGER_INTERFACE_INFO)
}
constexpr ams::sm::ServiceName ServiceName = ams::sm::ServiceName::Encode("nfp:sys");
class SystemManager : public ManagerBase {
public:
using ManagerBase::ManagerBase;
public:
ams::Result CreateSystemInterface(ams::sf::Out<std::shared_ptr<impl::ISystem>> out) {
R_TRY((this->CreateInterfaceImpl<impl::ISystem, System>(out)));
return ams::ResultSuccess();
}
};
static_assert(impl::IsISystemManager<SystemManager>);
}

View File

@ -1,44 +0,0 @@
#pragma once
#include <ipc/nfp/nfp_Common.hpp>
namespace ipc::nfp::user {
namespace impl {
using namespace ams;
#define I_USER_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 7, ams::Result, OpenApplicationArea, (DeviceHandle handle, amiibo::AreaId id, ams::sf::Out<u32> out_npad_id)) \
AMS_SF_METHOD_INFO(C, H, 8, ams::Result, GetApplicationArea, (const ams::sf::OutBuffer &data, ams::sf::Out<u32> data_size, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 9, ams::Result, SetApplicationArea, (const ams::sf::InBuffer &data, DeviceHandle handle)) \
AMS_SF_METHOD_INFO(C, H, 12, ams::Result, CreateApplicationArea, (const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id)) \
AMS_SF_METHOD_INFO(C, H, 22, ams::Result, GetApplicationAreaSize, (DeviceHandle handle, ams::sf::Out<u32> size)) \
AMS_SF_METHOD_INFO(C, H, 24, ams::Result, RecreateApplicationArea, (const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id))
AMS_SF_DEFINE_INTERFACE(IUser, I_USER_INTERFACE_INFO)
}
class User final : public CommonInterface {
private:
amiibo::AreaId current_opened_area_id;
bool area_opened;
public:
User(Service fwd, u64 app_id) : CommonInterface(fwd, app_id), current_opened_area_id(0), area_opened(false) {}
public:
ams::Result OpenApplicationArea(DeviceHandle handle, amiibo::AreaId id, ams::sf::Out<u32> out_npad_id);
ams::Result GetApplicationArea(const ams::sf::OutBuffer &data, ams::sf::Out<u32> data_size, DeviceHandle handle);
ams::Result SetApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle);
ams::Result CreateApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id);
ams::Result GetApplicationAreaSize(DeviceHandle handle, ams::sf::Out<u32> size);
ams::Result RecreateApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id);
};
static_assert(nfp::impl::IsICommonInterface<User>);
static_assert(impl::IsIUser<User>);
}

View File

@ -1,62 +0,0 @@
#pragma once
#include <ipc/nfp/user/user_User.hpp>
namespace ipc::nfp::user {
namespace impl {
using namespace ams;
#define I_USER_MANAGER_INTERFACE_INFO(C, H) \
AMS_SF_METHOD_INFO(C, H, 0, ams::Result, CreateUserInterface, (ams::sf::Out<std::shared_ptr<IUser>> out))
AMS_SF_DEFINE_MITM_INTERFACE(IUserManager, I_USER_MANAGER_INTERFACE_INFO)
}
constexpr ams::sm::ServiceName ServiceName = ams::sm::ServiceName::Encode("nfp:user");
/*
class UserManager final : public ManagerBase {
public:
using ManagerBase::ManagerBase;
public:
ams::Result CreateUserInterface(ams::sf::Out<std::shared_ptr<impl::IUser>> out) {
R_TRY((this->CreateInterfaceImpl<impl::IUser, User>(out)));
EMU_LOG_FMT("Returning success...");
return ams::ResultSuccess();
}
};
*/
class UserManager final : public ams::sf::MitmServiceImplBase {
public:
using MitmServiceImplBase::MitmServiceImplBase;
static bool ShouldMitm(const ams::sm::MitmProcessInfo &client_info) {
return ::sys::GetEmulationStatus() == ::sys::EmulationStatus::On;
}
ams::Result CreateUserInterface(ams::sf::Out<std::shared_ptr<impl::IUser>> out) {
EMU_LOG_FMT("Application ID 0x" << std::hex << std::setw(16) << std::setfill('0') << std::uppercase << this->client_info.program_id.value);
Service outsrv;
R_UNLESS(::sys::GetEmulationStatus() == ::sys::EmulationStatus::On, ams::sm::mitm::ResultShouldForwardToSession());
R_TRY(serviceDispatch(this->forward_service.get(), 0,
.out_num_objects = 1,
.out_objects = &outsrv,
));
const ams::sf::cmif::DomainObjectId object_id { serviceGetObjectId(&outsrv) };
EMU_LOG_FMT("MakeShared done");
out.SetValue(ams::sf::MakeShared<impl::IUser, User>(outsrv, this->client_info.program_id.value), object_id);
EMU_LOG_FMT("Returning success...");
return ams::ResultSuccess();
}
};
static_assert(impl::IsIUserManager<UserManager>);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,17 +0,0 @@
#pragma once
#include <emu_Consts.hpp>
#include <sstream>
namespace logger {
void Log(const std::string &fn, const std::string &msg);
void ClearLogs();
}
#define EMU_LOG_FMT(...) { \
std::stringstream strm; \
strm << __VA_ARGS__; \
logger::Log(__PRETTY_FUNCTION__, strm.str()); \
}

View File

@ -1,9 +0,0 @@
#pragma once
namespace sys {
void ScanAmiiboDirectory();
void DumpConsoleMiis();
}

View File

@ -1,41 +0,0 @@
#pragma once
#include <amiibo/amiibo_Formats.hpp>
namespace sys {
enum class EmulationStatus : u32 {
On,
Off,
};
enum class VirtualAmiiboStatus : u32 {
Invalid,
Connected,
Disconnected
};
EmulationStatus GetEmulationStatus();
void SetEmulationStatus(EmulationStatus status);
amiibo::VirtualAmiibo &GetActiveVirtualAmiibo();
bool IsActiveVirtualAmiiboValid();
void SetActiveVirtualAmiibo(amiibo::VirtualAmiibo amiibo);
VirtualAmiiboStatus GetActiveVirtualAmiiboStatus();
void SetActiveVirtualAmiiboStatus(VirtualAmiiboStatus status);
Result GetCurrentApplicationId(u64 *out_app_id);
void RegisterInterceptedApplicationId(u64 app_id);
void UnregisterInterceptedApplicationId(u64 app_id);
bool IsApplicationIdIntercepted(u64 app_id);
inline bool IsCurrentApplicationIdIntercepted() {
u64 cur_app_id = 0;
if(R_SUCCEEDED(GetCurrentApplicationId(&cur_app_id))) {
return IsApplicationIdIntercepted(cur_app_id);
}
return false;
}
}

View File

@ -3,7 +3,7 @@
"title_id": "0x0100000000000352",
"title_id_range_min": "0x0100000000000352",
"title_id_range_max": "0x0100000000000352",
"main_thread_stack_size": "0x00004000",
"main_thread_stack_size": "0x00060000",
"main_thread_priority": 49,
"default_cpu_id": 3,
"process_category": 0,
@ -14,14 +14,14 @@
"filesystem_access": {
"permissions": "0xFFFFFFFFFFFFFFFF"
},
"service_host": [ "*" ],
"service_access": [ "*" ],
"service_host": [ "*" ],
"kernel_capabilities": [
{
"type": "kernel_flags",
"value": {
"highest_thread_priority": 63,
"lowest_thread_priority": 16,
"lowest_thread_priority": 24,
"lowest_cpu_id": 3,
"highest_cpu_id": 3
}
@ -78,54 +78,30 @@
"svcReplyAndReceive": "0x43",
"svcReplyAndReceiveWithUserBuffer": "0x44",
"svcCreateEvent": "0x45",
"svcCreateInterruptEvent": "0x53",
"svcReadWriteRegister": "0x4E",
"svcQueryIoMapping": "0x55",
"svcCreateDeviceAddressSpace": "0x56",
"svcAttachDeviceAddressSpace": "0x57",
"svcDetachDeviceAddressSpace": "0x58",
"svcMapDeviceAddressSpaceAligned": "0x5a",
"svcUnmapDeviceAddressSpace": "0x5c",
"svcGetSystemInfo": "0x6f",
"svcCallSecureMonitor": "0x7f",
"svcUnknown46": "0x46",
"svcUnknown47": "0x47",
"svcMapPhysicalMemoryUnsafe": "0x48",
"svcUnmapPhysicalMemoryUnsafe": "0x49",
"svcSetUnsafeLimit": "0x4A",
"svcCreateCodeMemory": "0x4B",
"svcControlCodeMemory": "0x4C",
"svcSleepSystem": "0x4D",
"svcSetProcessActivity": "0x4F",
"svcCreateSharedMemory": "0x50",
"svcMapTransferMemory": "0x51",
"svcUnmapTransferMemory": "0x52",
"svcDebugActiveProcess": "0x60",
"svcBreakDebugProcess": "0x61",
"svcTerminateDebugProcess": "0x62",
"svcGetDebugEvent": "0x63",
"svcContinueDebugEvent": "0x64",
"svcGetProcessList": "0x65",
"svcGetThreadList": "0x66",
"svcGetDebugThreadContext": "0x67",
"svcSetDebugThreadContext": "0x68",
"svcQueryDebugProcessMemory": "0x69",
"svcReadDebugProcessMemory": "0x6A",
"svcWriteDebugProcessMemory": "0x6B",
"svcSetHardwareBreakPoint": "0x6C",
"svcGetDebugThreadParam": "0x6D",
"svcConnectToPort": "0x72",
"svcSetProcessMemoryPermission": "0x73",
"svcMapProcessMemory": "0x74",
"svcUnmapProcessMemory": "0x75",
"svcQueryProcessMemory": "0x76",
"svcMapProcessCodeMemory": "0x77",
"svcUnmapProcessCodeMemory": "0x78"
"svcReadDebugProcessMemory": "0x6a",
"svcGetDebugThreadParam": "0x6d",
"svcCallSecureMonitor": "0x7F"
}
},
{
"type": "min_kernel_version",
"value": "0x0030"
},
{
"type": "handle_table_size",
"value": 64
},
{
"type": "debug_flags",
"value": {
"allow_debug": false,
"force_debug": true
}
}
]
}

View File

@ -1,106 +0,0 @@
#include <sys/sys_Common.hpp>
#include <ipc/nfp/sys/sys_SystemManager.hpp>
#include <ipc/nfp/user/user_UserManager.hpp>
#include <ipc/emu/emu_EmulationService.hpp>
#define INNER_HEAP_SIZE 0x40000
extern "C" {
u32 __nx_applet_type = AppletType_None;
u32 __nx_fs_num_sessions = 1;
u32 __nx_fsdev_direntry_cache_size = 1;
size_t nx_inner_heap_size = INNER_HEAP_SIZE;
char nx_inner_heap[INNER_HEAP_SIZE];
void __libnx_init_time(void);
void __libnx_initheap(void) {
void *addr = nx_inner_heap;
size_t size = nx_inner_heap_size;
extern char *fake_heap_start;
extern char *fake_heap_end;
fake_heap_start = (char*)addr;
fake_heap_end = (char*)addr + size;
}
void __appInit(void) {
ams::hos::InitializeForStratosphere();
ams::sm::DoWithSession([&] {
EMU_R_ASSERT(fsInitialize());
EMU_R_ASSERT(fsdevMountSdmc());
EMU_R_ASSERT(timeInitialize());
__libnx_init_time();
EMU_R_ASSERT(hidInitialize());
EMU_R_ASSERT(miiInitialize(MiiServiceType_System));
EMU_R_ASSERT(pmdmntInitialize());
EMU_R_ASSERT(pminfoInitialize());
});
}
void __appExit(void) {
pminfoExit();
pmdmntExit();
miiExit();
hidExit();
timeExit();
fsdevUnmountAll();
fsExit();
}
}
namespace ams {
ncm::ProgramId CurrentProgramId = { 0x0100000000000352ul };
namespace result {
bool CallFatalOnResultAssertion = true;
}
}
namespace {
struct ServerOptions {
static const size_t PointerBufferSize = 0x1000;
static const size_t MaxDomains = 9;
static const size_t MaxDomainObjects = 10;
};
constexpr size_t MaxServers = 4;
constexpr size_t MaxSessions = 40;
ams::sf::hipc::ServerManager<MaxServers, ServerOptions, MaxSessions> emuiibo_manager;
}
int main() {
// Clear previous logs, to avoid extremely big log files
logger::ClearLogs();
EMU_LOG_FMT("Starting emuiibo...")
sys::DumpConsoleMiis();
sys::ScanAmiiboDirectory();
// Register nfp:user
EMU_R_ASSERT((emuiibo_manager.RegisterMitmServer<ipc::nfp::user::impl::IUserManager, ipc::nfp::user::UserManager>(ipc::nfp::user::ServiceName)));
// Register nfp:sys - why is this still broken?
// EMU_R_ASSERT(emuiibo_manager.RegisterMitmServer<ipc::nfp::sys::ISystemManager>(ipc::nfp::sys::ServiceName));
// Register custom nfp:emu service
EMU_R_ASSERT((emuiibo_manager.RegisterServer<ipc::emu::impl::IEmulationService, ipc::emu::EmulationService>(ipc::emu::ServiceName, MaxSessions)));
emuiibo_manager.LoopProcess();
return 0;
}

View File

@ -1,43 +0,0 @@
#include <amiibo/amiibo_Areas.hpp>
namespace amiibo {
void AreaManager::CreateImpl(AreaId id, const void *data, size_t size, bool recreate) {
auto area_path = this->EncodeAreaFilePath(id);
if(recreate) {
fs::DeleteFile(area_path.c_str());
}
this->Write(id, data, size);
}
bool AreaManager::Exists(AreaId id) {
auto area_path = this->EncodeAreaFilePath(id);
return fs::IsFile(area_path);
}
void AreaManager::Read(AreaId id, void *data, size_t size) {
auto area_path = this->EncodeAreaFilePath(id);
auto area_size = this->GetSize(id);
auto read_sz = std::min(area_size, size);
auto f = fopen(area_path.c_str(), "rb");
if(f) {
fread(data, 1, read_sz, f);
fclose(f);
}
}
void AreaManager::Write(AreaId id, const void *data, size_t size) {
auto area_path = this->EncodeAreaFilePath(id);
auto f = fopen(area_path.c_str(), "wb");
if(f) {
fwrite(data, 1, size, f);
fclose(f);
}
}
size_t AreaManager::GetSize(AreaId id) {
auto area_path = this->EncodeAreaFilePath(id);
return fs::GetFileSize(area_path);
}
}

View File

@ -1,320 +0,0 @@
#include <amiibo/amiibo_Formats.hpp>
#include <algorithm>
namespace amiibo {
void VirtualAmiibo::Save() {
fs::CreateDirectory(this->path);
auto amiibo_flag = fs::Concat(this->path, "amiibo.flag");
fs::CreateEmptyFile(amiibo_flag);
auto json_file = fs::Concat(this->path, "amiibo.json");
fs::DeleteFile(json_file);
fs::SaveJSONFile(json_file, this->amiibo_data);
}
VirtualAmiibo::VirtualAmiibo(const std::string &amiibo_path) : IVirtualAmiiboBase(amiibo_path), area_manager(amiibo_path) {
this->amiibo_data = fs::LoadJSONFile(fs::Concat(amiibo_path, "amiibo.json"));
// Some checks to see if the amiibo is correct
EMU_DO_UNLESS(!this->amiibo_data.empty(), this->valid = false;);
EMU_DO_UNLESS(this->HasKey(this->amiibo_data, "name"), this->valid = false;);
EMU_DO_UNLESS(this->HasKey(this->amiibo_data, "id"), this->valid = false;);
}
std::string VirtualAmiibo::GetName() {
return this->ReadPlain<std::string>(this->amiibo_data, "name");
}
void VirtualAmiibo::SetName(const std::string &name) {
this->WritePlain(this->amiibo_data, "name", name);
}
AmiiboUuidInfo VirtualAmiibo::GetUuidInfo() {
AmiiboUuidInfo info = {};
const bool has_uuid = this->amiibo_data.find("uuid") != this->amiibo_data.end();
info.random_uuid = !has_uuid;
if(has_uuid) {
this->ReadByteArray(info.uuid, "uuid");
}
return info;
}
void VirtualAmiibo::SetUuidInfo(AmiiboUuidInfo info) {
this->amiibo_data.erase("uuid");
if(!info.random_uuid) {
this->WriteByteArray(info.uuid, 10, "uuid");
}
}
AmiiboId VirtualAmiibo::GetAmiiboId() {
AmiiboId id = {};
if(this->HasKey(this->amiibo_data, "id")) {
auto id_obj = this->amiibo_data["id"];
id.character_id.game_character_id = this->ReadPlain<u16>(id_obj, "game_character_id");
id.character_id.character_variant = this->ReadPlain<u8>(id_obj, "character_variant");
id.series = this->ReadPlain<u8>(id_obj, "series");
id.model_number = this->ReadPlain<u16>(id_obj, "model_number");
id.figure_type = this->ReadPlain<u8>(id_obj, "figure_type");
}
return id;
}
void VirtualAmiibo::SetAmiiboId(AmiiboId id) {
auto id_obj = JSON::object();
this->WritePlain(id_obj, "game_character_id", id.character_id.game_character_id);
this->WritePlain(id_obj, "character_variant", id.character_id.character_variant);
this->WritePlain(id_obj, "series", id.series);
this->WritePlain(id_obj, "model_number", id.model_number);
this->WritePlain(id_obj, "figure_type", id.figure_type);
this->amiibo_data["id"] = id_obj;
}
std::string VirtualAmiibo::GetMiiCharInfoFileName() {
return this->ReadPlain<std::string>(this->amiibo_data, "mii_charinfo_file");
}
void VirtualAmiibo::SetMiiCharInfoFileName(const std::string &char_info_path) {
this->WritePlain(this->amiibo_data, "mii_charinfo_file", char_info_path);
}
Date VirtualAmiibo::GetFirstWriteDate() {
return this->ReadDate("first_write_date");
}
void VirtualAmiibo::SetFirstWriteDate(Date date) {
this->WriteDate("first_write_date", date);
}
Date VirtualAmiibo::GetLastWriteDate() {
return this->ReadDate("last_write_date");
}
void VirtualAmiibo::SetLastWriteDate(Date date) {
this->WriteDate("last_write_date", date);
}
u16 VirtualAmiibo::GetWriteCounter() {
return this->ReadPlain<u16>(this->amiibo_data, "write_counter");
}
void VirtualAmiibo::SetWriteCounter(u16 counter) {
this->WritePlain(this->amiibo_data, "write_counter", counter);
}
void VirtualAmiibo::NotifyWritten() {
// Update counter, if 0xFFFF it won't be updated anymore (this is what N does)
auto counter = this->GetWriteCounter();
if(counter < UINT16_MAX) {
counter++;
}
this->SetWriteCounter(counter);
Date cur_date = this->MakeCurrentDate();
this->SetLastWriteDate(cur_date);
this->Save();
}
u32 VirtualAmiibo::GetVersion() {
return this->ReadPlain<u32>(this->amiibo_data, "version");
}
void VirtualAmiibo::SetVersion(u32 version) {
this->WritePlain(this->amiibo_data, "version", version);
}
void VirtualAmiibo::FullyRemove() {
fs::DeleteDirectory(this->path);
}
TagInfo VirtualAmiibo::ProduceTagInfo() {
TagInfo info = {};
// Normally amiibos have 7 here (3 trailing zeros), but this doesn't seem to be enforced/checked
info.info.uuid_length = 10;
auto uuid_info = this->GetUuidInfo();
if(uuid_info.random_uuid) {
// Random UUID can be helpful for amiibos used for daily bonus stuff - meaning infinite supply with some games like BOTW
// Follow most amiibos' pattern, and zero the last 3 bytes
randomGet(info.info.uuid, 7);
info.info.uuid[7] = 0;
info.info.uuid[8] = 0;
info.info.uuid[9] = 0;
}
else {
memcpy(info.info.uuid, uuid_info.uuid, 10);
}
info.info.tag_type = VirtualAmiibo::DefaultTagType;
info.info.protocol = VirtualAmiibo::DefaultProtocol;
return info;
}
RegisterInfo VirtualAmiibo::ProduceRegisterInfo() {
RegisterInfo info = {};
auto charinfo = this->ReadMiiCharInfo();
memcpy(&info.info.mii, &charinfo, sizeof(charinfo));
auto first_w_date = this->GetFirstWriteDate();
info.info.first_write_date = first_w_date;
auto name = this->GetName();
strncpy(info.info.name, name.c_str(), RegisterInfoImpl::AmiiboNameLength);
return info;
}
ModelInfo VirtualAmiibo::ProduceModelInfo() {
ModelInfo info = {};
info.info.id = this->GetAmiiboId();
EMU_LOG_FMT("Processed amiibo ID { Game & character ID: " << info.info.id.character_id.game_character_id << ", Character variant: " << (int)info.info.id.character_id.character_variant << ", Figure type: " << (int)info.info.id.figure_type << ", Model number: " << info.info.id.model_number << ", Series: " << (int)info.info.id.series << " }")
return info;
}
CommonInfo VirtualAmiibo::ProduceCommonInfo() {
CommonInfo info = {};
auto last_w_date = this->GetLastWriteDate();
info.info.last_write_year = last_w_date.year;
info.info.last_write_month = last_w_date.month;
info.info.last_write_day = last_w_date.day;
auto w_counter = this->GetWriteCounter();
info.info.write_counter = w_counter;
auto ver = this->GetVersion();
info.info.version = ver;
info.info.application_area_size = AreaManager::DefaultSize;
return info;
}
VirtualAmiiboData VirtualAmiibo::ProduceData() {
VirtualAmiiboData data = {};
data.uuid = this->GetUuidInfo();
auto name = this->GetName();
strncpy(data.name, name.c_str(), 40);
data.first_write_date = this->GetFirstWriteDate();
data.last_write_date = this->GetLastWriteDate();
data.mii_charinfo = this->ReadMiiCharInfo();
return data;
}
VirtualAmiiboV3::VirtualAmiiboV3(const std::string &amiibo_dir) : IVirtualAmiiboBase(amiibo_dir) {
this->tag_data = fs::LoadJSONFile(this->GetJSONFileName("tag"));
this->register_data = fs::LoadJSONFile(this->GetJSONFileName("register"));
this->common_data = fs::LoadJSONFile(this->GetJSONFileName("common"));
this->model_data = fs::LoadJSONFile(this->GetJSONFileName("model"));
// Some checks to see if the amiibo is correct
EMU_DO_UNLESS(!this->tag_data.empty(), this->valid = false;);
EMU_DO_UNLESS(!this->register_data.empty(), this->valid = false;);
EMU_DO_UNLESS(!this->common_data.empty(), this->valid = false;);
EMU_DO_UNLESS(!this->model_data.empty(), this->valid = false;);
EMU_DO_UNLESS(this->HasKey(this->register_data, "name"), this->valid = false;);
EMU_DO_UNLESS(this->HasKey(this->model_data, "amiiboId"), this->valid = false;);
EMU_DO_UNLESS(this->HasKey(this->tag_data, "uuid") || this->HasKey(this->tag_data, "randomUuid"), this->valid = false;);
}
std::string VirtualAmiiboV3::GetName() {
return this->ReadPlain<std::string>(this->register_data, "name");
}
AmiiboUuidInfo VirtualAmiiboV3::GetUuidInfo() {
AmiiboUuidInfo info = {};
info.random_uuid = this->tag_data.value("randomUuid", false);
if(!info.random_uuid) {
this->ReadStringByteArray(this->tag_data, info.uuid, "uuid");
}
return info;
}
AmiiboId VirtualAmiiboV3::GetAmiiboId() {
u8 array[8] = {0};
this->ReadStringByteArray(this->model_data, array, "amiiboId");
auto old_id = *(OldAmiiboId*)array;
// Reverse model number field (BE)
old_id.model_number = __builtin_bswap16(old_id.model_number);
auto id = AmiiboId::FromOldAmiiboId(old_id);
return id;
}
std::string VirtualAmiiboV3::GetMiiCharInfoFileName() {
return this->ReadPlain<std::string>(this->register_data, "miiCharInfo");
}
Date VirtualAmiiboV3::GetFirstWriteDate() {
return this->ReadStringDate(this->register_data, "firstWriteDate");
}
Date VirtualAmiiboV3::GetLastWriteDate() {
return this->ReadStringDate(this->common_data, "lastWriteDate");
}
u16 VirtualAmiiboV3::GetWriteCounter() {
return this->ReadPlain<u16>(this->common_data, "writeCounter");
}
u32 VirtualAmiiboV3::GetVersion() {
return this->ReadPlain<u32>(this->common_data, "version");
}
void VirtualAmiiboV3::FullyRemove() {
fs::DeleteDirectory(this->path);
}
VirtualBinAmiibo::VirtualBinAmiibo(const std::string &path) : IVirtualAmiiboBase(path) {
if(fs::GetFileSize(this->path) < sizeof(RawAmiibo)) {
this->valid = false;
return;
}
this->raw_data = fs::Read<RawAmiibo>(this->path);
this->base_date = this->MakeCurrentDate();
}
std::string VirtualBinAmiibo::GetName() {
return fs::RemoveExtension(fs::GetBaseName(this->path));
}
AmiiboUuidInfo VirtualBinAmiibo::GetUuidInfo() {
AmiiboUuidInfo info = {};
info.random_uuid = false;
memcpy(info.uuid, this->raw_data.uuid, 10);
return info;
}
AmiiboId VirtualBinAmiibo::GetAmiiboId() {
u8 id_array[8] = {0};
memcpy(id_array, this->raw_data.amiibo_id, 8);
auto old_id = *(OldAmiiboId*)id_array;
// Reverse model number field (BE)
old_id.model_number = __builtin_bswap16(old_id.model_number);
auto id = AmiiboId::FromOldAmiiboId(old_id);
return id;
}
std::string VirtualBinAmiibo::GetMiiCharInfoFileName() {
return "";
}
Date VirtualBinAmiibo::GetFirstWriteDate() {
return this->base_date;
}
Date VirtualBinAmiibo::GetLastWriteDate() {
return this->base_date;
}
u16 VirtualBinAmiibo::GetWriteCounter() {
return 0;
}
u32 VirtualBinAmiibo::GetVersion() {
return 0;
}
void VirtualBinAmiibo::FullyRemove() {
fs::DeleteFile(this->path);
}
}

View File

@ -1,265 +0,0 @@
#include <ipc/nfp/nfp_Common.hpp>
namespace ipc::nfp {
static void VirtualAmiiboStatusUpdateThread(void *iface_data) {
auto iface_ptr = reinterpret_cast<CommonInterface*>(iface_data);
while(true) {
if(iface_ptr->ShouldExitThread()) {
break;
}
auto status = sys::GetActiveVirtualAmiiboStatus();
iface_ptr->HandleVirtualAmiiboStatus(status);
svcSleepThread(100'000'000ul);
}
EMU_LOG_FMT("Exiting...")
}
CommonInterface::CommonInterface(Service fwd, u64 app_id) : state(NfpState_NonInitialized), device_state(NfpDeviceState_Unavailable), forward_service(fwd), client_app_id(app_id), amiibo_update_lock(true), should_exit_thread(false) {
EMU_LOG_FMT("Ctor started");
sys::RegisterInterceptedApplicationId(this->client_app_id);
EMU_R_ASSERT(ams::os::CreateSystemEvent(&this->event_activate, ams::os::EventClearMode_AutoClear, true));
EMU_R_ASSERT(ams::os::CreateSystemEvent(&this->event_deactivate, ams::os::EventClearMode_AutoClear, true));
EMU_R_ASSERT(ams::os::CreateSystemEvent(&this->event_availability_change, ams::os::EventClearMode_AutoClear, true));
EMU_R_ASSERT(threadCreate(&this->amiibo_update_thread, &VirtualAmiiboStatusUpdateThread, reinterpret_cast<void*>(this), nullptr, 0x1000, 0x2B, -2));
EMU_R_ASSERT(threadStart(&this->amiibo_update_thread));
EMU_LOG_FMT("Ctor ended");
}
CommonInterface::~CommonInterface() {
EMU_LOG_FMT("Dtor started");
serviceClose(&this->forward_service);
sys::UnregisterInterceptedApplicationId(this->client_app_id);
this->NotifyThreadExitAndWait();
EMU_LOG_FMT("Dtor ended");
}
void CommonInterface::HandleVirtualAmiiboStatus(sys::VirtualAmiiboStatus status) {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
auto state = this->GetDeviceStateValue();
switch(status) {
case sys::VirtualAmiiboStatus::Connected: {
switch(state) {
case NfpDeviceState_SearchingForTag: {
// The client was waiting for an amiibo, tell it that it's connected now
this->SetDeviceStateValue(NfpDeviceState_TagFound);
ams::os::SignalSystemEvent(&this->event_activate);
break;
}
default:
break;
}
break;
}
case sys::VirtualAmiiboStatus::Disconnected: {
switch(state) {
case NfpDeviceState_TagFound:
case NfpDeviceState_TagMounted: {
// The client thinks that the amiibo is connected, tell it that it was disconnected
this->SetDeviceStateValue(NfpDeviceState_SearchingForTag);
ams::os::SignalSystemEvent(&this->event_deactivate);
break;
}
default:
break;
}
break;
}
default:
break;
}
}
NfpState CommonInterface::GetStateValue() {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
return this->state;
}
void CommonInterface::SetStateValue(NfpState val) {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
this->state = val;
}
NfpDeviceState CommonInterface::GetDeviceStateValue() {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
return this->device_state;
}
void CommonInterface::SetDeviceStateValue(NfpDeviceState val) {
EMU_LOCK_SCOPE_WITH(this->amiibo_update_lock);
this->device_state = val;
}
void CommonInterface::Initialize(const ams::sf::ClientAppletResourceUserId &client_aruid, const ams::sf::ClientProcessId &client_pid, const ams::sf::InBuffer &mcu_data) {
EMU_LOG_FMT("Process ID: 0x" << std::hex << client_pid.GetValue().value << ", ARUID: 0x" << std:: hex << client_aruid.GetValue().value)
this->SetStateValue(NfpState_Initialized);
this->SetDeviceStateValue(NfpDeviceState_Initialized);
}
void CommonInterface::Finalize() {
EMU_LOG_FMT("Finalizing...")
this->SetStateValue(NfpState_NonInitialized);
this->SetDeviceStateValue(NfpDeviceState_Finalized);
}
ams::Result CommonInterface::ListDevices(const ams::sf::OutPointerArray<DeviceHandle> &out_devices, ams::sf::Out<s32> out_count) {
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
EMU_LOG_FMT("Device handle array length: " << out_devices.GetSize())
// Here, in emuiibo, we will only return one available device(handheld or player 1)
DeviceHandle handle = {};
handle.npad_id = HandheldNpadId;
// If player 1 is connected (aka joycons are detached), use that id, otherwise handheld will be connected
hidScanInput();
if(hidIsControllerConnected(CONTROLLER_PLAYER_1)) {
handle.npad_id = Player1NpadId;
}
out_devices[0] = handle;
out_count.SetValue(1);
return ams::ResultSuccess();
}
ams::Result CommonInterface::StartDetection(DeviceHandle handle) {
EMU_LOG_FMT("Started detection")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_Initialized, NfpDeviceState_TagRemoved), result::nfp::ResultDeviceNotFound);
this->SetDeviceStateValue(NfpDeviceState_SearchingForTag);
return ams::ResultSuccess();
}
ams::Result CommonInterface::StopDetection(DeviceHandle handle) {
EMU_LOG_FMT("Stopped detection")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
this->SetDeviceStateValue(NfpDeviceState_Initialized);
return ams::ResultSuccess();
}
ams::Result CommonInterface::Mount(DeviceHandle handle, u32 type, u32 target) {
EMU_LOG_FMT("Mounted")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
this->SetDeviceStateValue(NfpDeviceState_TagMounted);
return ams::ResultSuccess();
}
ams::Result CommonInterface::Unmount(DeviceHandle handle) {
EMU_LOG_FMT("Unmounted")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
this->device_state = NfpDeviceState_TagFound;
return ams::ResultSuccess();
}
ams::Result CommonInterface::Flush(DeviceHandle handle) {
EMU_LOG_FMT("Flushed")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
return ams::ResultSuccess();
}
ams::Result CommonInterface::Restore(DeviceHandle handle) {
EMU_LOG_FMT("Restored")
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
return ams::ResultSuccess();
}
ams::Result CommonInterface::GetTagInfo(ams::sf::Out<TagInfo> out_info, DeviceHandle handle) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Tag info - is amiibo valid? " << std::boolalpha << amiibo.IsValid() << ", amiibo name: " << amiibo.GetName())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagFound, NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto info = amiibo.ProduceTagInfo();
out_info.SetValue(info);
return ams::ResultSuccess();
}
ams::Result CommonInterface::GetRegisterInfo(ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Register info - is amiibo valid? " << std::boolalpha << amiibo.IsValid() << ", amiibo name: " << amiibo.GetName())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto info = amiibo.ProduceRegisterInfo();
out_info.SetValue(info);
return ams::ResultSuccess();
}
ams::Result CommonInterface::GetModelInfo(ams::sf::Out<ModelInfo> out_info, DeviceHandle handle) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Model info - is amiibo valid? " << std::boolalpha << amiibo.IsValid() << ", amiibo name: " << amiibo.GetName())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto info = amiibo.ProduceModelInfo();
out_info.SetValue(info);
return ams::ResultSuccess();
}
ams::Result CommonInterface::GetCommonInfo(ams::sf::Out<CommonInfo> out_info, DeviceHandle handle) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Common info - is amiibo valid? " << std::boolalpha << amiibo.IsValid() << ", amiibo name: " << amiibo.GetName())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto info = amiibo.ProduceCommonInfo();
out_info.SetValue(info);
return ams::ResultSuccess();
}
ams::Result CommonInterface::AttachActivateEvent(DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event) {
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
event.SetValue(ams::os::GetReadableHandleOfSystemEvent(&this->event_activate));
return ams::ResultSuccess();
}
ams::Result CommonInterface::AttachDeactivateEvent(DeviceHandle handle, ams::sf::Out<ams::sf::CopyHandle> event) {
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
event.SetValue(ams::os::GetReadableHandleOfSystemEvent(&this->event_deactivate));
return ams::ResultSuccess();
}
void CommonInterface::GetState(ams::sf::Out<u32> out_state) {
auto state = this->GetStateValue();
EMU_LOG_FMT("State: " << static_cast<u32>(state));
out_state.SetValue(static_cast<u32>(state));
}
void CommonInterface::GetDeviceState(DeviceHandle handle, ams::sf::Out<u32> out_state) {
auto state = this->GetDeviceStateValue();
EMU_LOG_FMT("Device state: " << static_cast<u32>(state));
out_state.SetValue(static_cast<u32>(state));
}
ams::Result CommonInterface::GetNpadId(DeviceHandle handle, ams::sf::Out<u32> out_npad_id) {
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
out_npad_id.SetValue(handle.npad_id);
return ams::ResultSuccess();
}
ams::Result CommonInterface::AttachAvailabilityChangeEvent(ams::sf::Out<ams::sf::CopyHandle> event) {
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
event.SetValue(ams::os::GetReadableHandleOfSystemEvent(&this->event_availability_change));
return ams::ResultSuccess();
}
ams::Result ManagerBase::CreateForwardInterface(Service *manager, Service *out) {
R_UNLESS(sys::GetEmulationStatus() == sys::EmulationStatus::On, ams::sm::mitm::ResultShouldForwardToSession());
R_TRY(serviceDispatch(manager, 0,
.out_num_objects = 1,
.out_objects = out,
));
EMU_LOG_FMT("Created custom NFP interface for emuiibo!")
return ams::ResultSuccess();
}
}

View File

@ -1,41 +0,0 @@
#include <ipc/nfp/sys/sys_System.hpp>
namespace ipc::nfp::sys {
ams::Result System::Format(DeviceHandle handle) {
EMU_LOG_FMT("System - Format!")
return ams::ResultSuccess();
}
ams::Result System::GetAdminInfo(ams::sf::Out<AdminInfo> out_info, DeviceHandle handle) {
EMU_LOG_FMT("System - Get AdminInfo!")
return ams::ResultSuccess();
}
ams::Result System::GetRegisterInfo2(ams::sf::Out<RegisterInfo> out_info, DeviceHandle handle) {
EMU_LOG_FMT("System - GetRegisterInfo2!")
return this->GetRegisterInfo(out_info, handle);
}
ams::Result System::SetRegisterInfo(DeviceHandle handle, const RegisterInfo &info) {
EMU_LOG_FMT("System - SetRegisterInfo!")
return ams::ResultSuccess();
}
ams::Result System::DeleteRegisterInfo(DeviceHandle handle) {
EMU_LOG_FMT("System - DeleteRegisterInfo!")
return ams::ResultSuccess();
}
ams::Result System::DeleteApplicationArea(DeviceHandle handle) {
EMU_LOG_FMT("System - Delete area!")
return ams::ResultSuccess();
}
ams::Result System::ExistsApplicationArea(ams::sf::Out<u8> out_exists, DeviceHandle handle) {
EMU_LOG_FMT("System - Exists area?")
out_exists.SetValue(0);
return ams::ResultSuccess();
}
}

View File

@ -1,117 +0,0 @@
#include <ipc/nfp/user/user_User.hpp>
namespace ipc::nfp::user {
ams::Result User::OpenApplicationArea(DeviceHandle handle, amiibo::AreaId id, ams::sf::Out<u32> out_npad_id) {
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Open area - area ID: 0x" << std::hex << id << std::dec << ", is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
out_npad_id.SetValue(handle.npad_id);
auto &area_manager = amiibo.GetAreaManager();
EMU_LOG_FMT("Open area - exists area? " << std::boolalpha << area_manager.Exists(id))
R_UNLESS(area_manager.Exists(id), result::nfp::ResultAreaNeedsToBeCreated);
// This area is opened now
this->current_opened_area_id = id;
this->area_opened = true;
return ams::ResultSuccess();
}
ams::Result User::GetApplicationArea(const ams::sf::OutBuffer &data, ams::sf::Out<u32> data_size, DeviceHandle handle) {
EMU_LOG_FMT("Get area - current area ID: " << std::hex << this->current_opened_area_id)
R_UNLESS(this->area_opened, result::nfp::ResultDeviceNotFound);
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Get area - is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto &area_manager = amiibo.GetAreaManager();
EMU_LOG_FMT("Get area - exists area? " << std::boolalpha << area_manager.Exists(this->current_opened_area_id))
R_UNLESS(area_manager.Exists(this->current_opened_area_id), result::nfp::ResultAreaNeedsToBeCreated);
auto size = area_manager.GetSize(this->current_opened_area_id);
R_UNLESS(size > 0, result::nfp::ResultAreaNeedsToBeCreated);
area_manager.Read(this->current_opened_area_id, data.GetPointer(), size);
data_size.SetValue(static_cast<u32>(size));
return ams::ResultSuccess();
}
ams::Result User::SetApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle) {
EMU_LOG_FMT("Set area - current area ID: " << std::hex << this->current_opened_area_id)
R_UNLESS(this->area_opened, result::nfp::ResultDeviceNotFound);
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Set area - is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto &area_manager = amiibo.GetAreaManager();
EMU_LOG_FMT("Set area - exists area? " << std::boolalpha << area_manager.Exists(this->current_opened_area_id))
R_UNLESS(area_manager.Exists(this->current_opened_area_id), result::nfp::ResultAreaNeedsToBeCreated);
auto size = area_manager.GetSize(this->current_opened_area_id);
R_UNLESS(size > 0, result::nfp::ResultAreaNeedsToBeCreated);
area_manager.Write(this->current_opened_area_id, data.GetPointer(), data.GetSize());
// Notify that the amiibo was written :P
amiibo.NotifyWritten();
return ams::ResultSuccess();
}
ams::Result User::CreateApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id) {
EMU_LOG_FMT("Create area - area ID: " << std::hex << id)
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Create area - is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto &area_manager = amiibo.GetAreaManager();
// If it already exists, this should not succeed
R_UNLESS(!area_manager.Exists(id), result::nfp::ResultAreaAlreadyCreated);
area_manager.Create(id, data.GetPointer(), data.GetSize());
return ams::ResultSuccess();
}
ams::Result User::GetApplicationAreaSize(DeviceHandle handle, ams::sf::Out<u32> size) {
EMU_LOG_FMT("Get area - current area ID: " << std::hex << this->current_opened_area_id)
R_UNLESS(this->area_opened, result::nfp::ResultDeviceNotFound);
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Get area - is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto &area_manager = amiibo.GetAreaManager();
EMU_LOG_FMT("Get area - exists area? " << std::boolalpha << area_manager.Exists(this->current_opened_area_id))
R_UNLESS(area_manager.Exists(this->current_opened_area_id), result::nfp::ResultAreaNeedsToBeCreated);
auto sz = area_manager.GetSize(this->current_opened_area_id);
size.SetValue(static_cast<u32>(sz));
return ams::ResultSuccess();
}
ams::Result User::RecreateApplicationArea(const ams::sf::InBuffer &data, DeviceHandle handle, amiibo::AreaId id) {
EMU_LOG_FMT("Recreate area - current area ID: " << std::hex << id)
R_UNLESS(this->area_opened, result::nfp::ResultDeviceNotFound);
auto &amiibo = sys::GetActiveVirtualAmiibo();
EMU_LOG_FMT("Recreate area - is amiibo valid? " << std::boolalpha << amiibo.IsValid())
R_UNLESS(this->IsStateAny<NfpState>(NfpState_Initialized), result::nfp::ResultDeviceNotFound);
R_UNLESS(this->IsStateAny<NfpDeviceState>(NfpDeviceState_TagMounted), result::nfp::ResultDeviceNotFound);
R_UNLESS(amiibo.IsValid(), result::nfp::ResultDeviceNotFound);
auto &area_manager = amiibo.GetAreaManager();
area_manager.Recreate(id, data.GetPointer(), data.GetSize());
return ams::ResultSuccess();
}
}

View File

@ -1,25 +0,0 @@
#include <logger/logger_Logger.hpp>
#include <cstdio>
#include <fs/fs_FileSystem.hpp>
namespace logger {
static Lock g_logging_lock(true);
// Lock logs to avoid race conditions from multiple threads
void Log(const std::string &fn, const std::string &msg) {
EMU_LOCK_SCOPE_WITH(g_logging_lock);
auto f = fopen(consts::LogFilePath.c_str(), "a+");
if(f) {
fprintf(f, "[ emuiibo v%s | %s ] %s\n", EMUIIBO_VERSION, fn.c_str(), msg.c_str());
fclose(f);
}
}
void ClearLogs() {
EMU_LOCK_SCOPE_WITH(g_logging_lock);
fs::DeleteFile(consts::LogFilePath);
}
}

View File

@ -1,73 +0,0 @@
#include <sys/sys_Common.hpp>
#include <fs/fs_FileSystem.hpp>
#include <amiibo/amiibo_Formats.hpp>
#include <dirent.h>
namespace sys {
static void ScanAmiiboDirectoryImpl(const std::string &base_path) {
FS_FOR(base_path, entry, path, {
// Process and convert outdated virtual amiibo formats
if(amiibo::VirtualAmiibo::IsValidVirtualAmiiboType<amiibo::VirtualBinAmiibo>(path)) {
EMU_LOG_FMT("Converting raw bin at '" << path << "'...")
auto ret = amiibo::VirtualAmiibo::ConvertVirtualAmiibo<amiibo::VirtualBinAmiibo>(path);
EMU_LOG_FMT("Conversion succeeded? " << std::boolalpha << ret << "...")
}
else if(amiibo::VirtualAmiibo::IsValidVirtualAmiiboType<amiibo::VirtualAmiiboV3>(path)) {
EMU_LOG_FMT("Converting V3 (0.3.x/0.4) virtual amiibo at '" << path << "'...")
auto ret = amiibo::VirtualAmiibo::ConvertVirtualAmiibo<amiibo::VirtualAmiiboV3>(path);
EMU_LOG_FMT("Conversion succeeded? " << std::boolalpha << ret << "...")
}
// Check that it isn't a valid amiibo (it would attempt to convert mii charinfo or area bins otherwise)
else if(!amiibo::VirtualAmiibo::IsValidVirtualAmiiboType<amiibo::VirtualAmiibo>(path)) {
// If it's a directory, scan amiibos there too
if(fs::IsDirectory(path)) {
ScanAmiiboDirectoryImpl(path);
}
}
});
}
void ScanAmiiboDirectory() {
ScanAmiiboDirectoryImpl(consts::AmiiboDir);
}
void DumpConsoleMiis() {
fs::EnsureEmuiiboDirectories();
// Recreate miis dir
fs::RecreateDirectory(consts::DumpedMiisDir);
MiiDatabase db;
auto rc = miiOpenDatabase(&db, MiiSpecialKeyCode_Normal);
if(R_SUCCEEDED(rc)) {
s32 count = 0;
auto flag = MiiSourceFlag_Database;
rc = miiDatabaseGetCount(&db, &count, flag);
if(R_SUCCEEDED(rc)) {
if(count > 0) {
auto buf = new MiiCharInfo[count]();
s32 total = 0;
rc = miiDatabaseGet1(&db, flag, buf, count, &total);
if(R_SUCCEEDED(rc)) {
for(s32 i = 0; i < total; i++) {
auto charinfo = buf[i];
const size_t mii_name_len = 10;
char mii_name[mii_name_len + 1] = {0};
// Use a copy to avoid warnings, since the charinfo struct is packed
u16 mii_name_16[mii_name_len + 1] = {0};
memcpy(mii_name_16, charinfo.mii_name, sizeof(mii_name_16));
utf16_to_utf8((u8*)mii_name, (const u16*)mii_name_16, mii_name_len);
auto charinfo_dir = std::to_string(i) + " - " + mii_name;
auto charinfo_dir_path = fs::Concat(consts::DumpedMiisDir, charinfo_dir);
fs::CreateDirectory(charinfo_dir_path);
auto charinfo_file_path = fs::Concat(charinfo_dir_path, "mii-charinfo.bin");
fs::Save(charinfo_file_path, charinfo);
}
}
delete[] buf;
}
}
miiDatabaseClose(&db);
}
}
}

View File

@ -1,85 +0,0 @@
#include <sys/sys_Emulation.hpp>
#include <algorithm>
namespace sys {
static Lock g_emulation_lock(true);
static EmulationStatus g_emulation_status = EmulationStatus::Off;
static amiibo::VirtualAmiibo g_virtual_amiibo;
static VirtualAmiiboStatus g_virtual_amiibo_status = VirtualAmiiboStatus::Invalid;
static std::vector<u64> g_intercepted_app_id_list;
EmulationStatus GetEmulationStatus() {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
return g_emulation_status;
}
void SetEmulationStatus(EmulationStatus status) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
g_emulation_status = status;
}
amiibo::VirtualAmiibo &GetActiveVirtualAmiibo() {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
return g_virtual_amiibo;
}
bool IsActiveVirtualAmiiboValid() {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
return g_virtual_amiibo.IsValid();
}
void SetActiveVirtualAmiibo(amiibo::VirtualAmiibo amiibo) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
g_virtual_amiibo = amiibo;
SetActiveVirtualAmiiboStatus(VirtualAmiiboStatus::Connected);
}
VirtualAmiiboStatus GetActiveVirtualAmiiboStatus() {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
if(!IsActiveVirtualAmiiboValid()) {
g_virtual_amiibo_status = VirtualAmiiboStatus::Invalid;
}
return g_virtual_amiibo_status;
}
void SetActiveVirtualAmiiboStatus(VirtualAmiiboStatus status) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
EMU_LOG_FMT("Setting new virtual amiibo status: " << static_cast<u32>(status))
if(IsActiveVirtualAmiiboValid()) {
g_virtual_amiibo_status = status;
}
else {
g_virtual_amiibo_status = VirtualAmiiboStatus::Invalid;
}
}
Result GetCurrentApplicationId(u64 *out_app_id) {
u64 tmp_pid = 0;
R_TRY(pmdmntGetApplicationProcessId(&tmp_pid));
R_TRY(pminfoGetProgramId(out_app_id, tmp_pid));
return 0;
}
void RegisterInterceptedApplicationId(u64 app_id) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
if(!IsApplicationIdIntercepted(app_id)) {
g_intercepted_app_id_list.push_back(app_id);
}
}
void UnregisterInterceptedApplicationId(u64 app_id) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
if(IsApplicationIdIntercepted(app_id)) {
g_intercepted_app_id_list.erase(std::remove(g_intercepted_app_id_list.begin(), g_intercepted_app_id_list.end(), app_id), g_intercepted_app_id_list.end());
}
}
bool IsApplicationIdIntercepted(u64 app_id) {
EMU_LOCK_SCOPE_WITH(g_emulation_lock);
return std::find(g_intercepted_app_id_list.begin(), g_intercepted_app_id_list.end(), app_id) != g_intercepted_app_id_list.end();
}
}

202
emuiibo/src/amiibo.rs Normal file
View File

@ -0,0 +1,202 @@
use nx::result::*;
use serde::{Serialize, Deserialize};
use alloc::string::String;
use alloc::vec::Vec;
use nx::service::fspsrv;
use nx::service::fspsrv::IFileSystem;
use nx::mem;
use nx::sync;
use nx::fs;
use nx::util;
use nx::ipc::sf::mii;
use nx::ipc::sf::nfp;
use crate::fsext;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct VirtualAmiiboUuidInfo {
use_random_uuid: bool,
uuid: [u8; 10]
}
#[derive(Copy, Clone)]
#[repr(C)]
pub struct VirtualAmiiboData {
uuid_info: VirtualAmiiboUuidInfo,
name: util::CString<41>,
first_write_date: nfp::Date,
last_write_date: nfp::Date,
mii_charinfo: mii::CharInfo
}
#[derive(Serialize, Deserialize, Debug)]
pub struct VirtualAmiiboId {
pub game_character_id: u16,
pub character_variant: u8,
pub figure_type: u8,
pub model_number: u16,
pub series: u8
}
impl VirtualAmiiboId {
pub const fn empty() -> Self {
Self { character_variant: 0, figure_type: 0, game_character_id: 0, model_number: 0, series: 0 }
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct VirtualAmiiboDate {
pub y: u16,
pub m: u8,
pub d: u8
}
impl VirtualAmiiboDate {
pub const fn empty() -> Self {
Self { y: 0, m: 0, d: 0 }
}
pub const fn to_date(&self) -> nfp::Date {
nfp::Date { year: self.y, month: self.m, day: self.d }
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct VirtualAmiiboInfo {
first_write_date: VirtualAmiiboDate,
id: VirtualAmiiboId,
last_write_date: VirtualAmiiboDate,
mii_charinfo_file: String,
name: String,
uuid: Option<Vec<u8>>,
version: u16,
write_counter: u16
}
impl VirtualAmiiboInfo {
pub const fn empty() -> Self {
Self {
first_write_date: VirtualAmiiboDate::empty(),
id: VirtualAmiiboId::empty(),
last_write_date: VirtualAmiiboDate::empty(),
mii_charinfo_file: String::new(),
name: String::new(),
uuid: None,
version: 0,
write_counter: 0
}
}
}
pub struct VirtualAmiibo {
pub info: VirtualAmiiboInfo,
pub mii_charinfo: mii::CharInfo,
pub path: String
}
impl VirtualAmiibo {
pub const fn empty() -> Self {
Self { info: VirtualAmiiboInfo::empty(), mii_charinfo: mii::CharInfo { data: [0; 0x58] }, path: String::new() }
}
pub fn new(info: VirtualAmiiboInfo, path: String) -> Result<Self> {
let mut amiibo = Self { info: info, mii_charinfo: mii::CharInfo { data: [0; 0x58] }, path: path };
amiibo.mii_charinfo = amiibo.load_mii_charinfo()?;
Ok(amiibo)
}
pub fn is_valid(&self) -> bool {
!self.path.is_empty()
}
pub fn load_mii_charinfo(&self) -> Result<mii::CharInfo> {
let mut mii_charinfo: mii::CharInfo = unsafe { core::mem::zeroed() };
let mut mii_charinfo_file = fs::open_file(format!("{}/{}", self.path, self.info.mii_charinfo_file), fs::FileOpenOption::Read())?;
let mii_charinfo_size = mii_charinfo_file.get_size()?;
result_return_unless!(mii_charinfo_size == core::mem::size_of::<mii::CharInfo>(), 0xBADD);
mii_charinfo_file.read(&mut mii_charinfo, mii_charinfo_size)?;
Ok(mii_charinfo)
}
pub fn produce_data(&self) -> Result<VirtualAmiiboData> {
let mut data: VirtualAmiiboData = unsafe { core::mem::zeroed() };
match self.info.uuid.as_ref() {
Some(uuid) => {
for i in 0..10 {
data.uuid_info.uuid[i] = uuid[i];
}
},
None => data.uuid_info.use_random_uuid = true
};
data.name.set_string(self.info.name.clone())?;
data.first_write_date = self.info.first_write_date.to_date();
data.last_write_date = self.info.last_write_date.to_date();
data.mii_charinfo = self.mii_charinfo;
Ok(data)
}
pub fn produce_tag_info(&self) -> nfp::TagInfo {
let mut tag_info: nfp::TagInfo = unsafe { core::mem::zeroed() };
tag_info.uuid_length = tag_info.uuid.len() as u8;
unsafe {
match self.info.uuid.as_ref() {
Some(uuid) => core::ptr::copy(uuid.as_ptr(), tag_info.uuid.as_mut_ptr(), tag_info.uuid.len()),
None => {
// TODO: random
core::ptr::write_bytes(tag_info.uuid.as_mut_ptr(), 0xBE, tag_info.uuid.len());
}
};
}
tag_info.tag_type = u32::max_value();
tag_info.protocol = u32::max_value();
tag_info
}
pub fn produce_register_info(&self) -> nfp::RegisterInfo {
let mut register_info: nfp::RegisterInfo = unsafe { core::mem::zeroed() };
register_info.mii_charinfo = self.mii_charinfo;
let _ = register_info.name.set_string(self.info.name.clone());
register_info.first_write_date = self.info.first_write_date.to_date();
register_info
}
pub fn produce_common_info(&self) -> nfp::CommonInfo {
let mut common_info: nfp::CommonInfo = unsafe { core::mem::zeroed() };
common_info.last_write_date = self.info.first_write_date.to_date();
common_info.application_area_size = 0xD8;
common_info.version = self.info.version;
common_info.write_counter = self.info.write_counter;
common_info
}
pub fn produce_model_info(&self) -> nfp::ModelInfo {
let mut model_info: nfp::ModelInfo = unsafe { core::mem::zeroed() };
model_info.game_character_id = self.info.id.game_character_id;
model_info.character_variant = self.info.id.character_variant;
model_info.figure_type = self.info.id.figure_type;
model_info.model_number = self.info.id.model_number;
model_info.series = self.info.id.series;
model_info
}
}
pub fn try_load_virtual_amiibo(path: String) -> Result<VirtualAmiibo> {
let amiibo_flag_file = format!("{}/amiibo.flag", path);
result_return_unless!(fsext::exists_file(amiibo_flag_file), 0xBEBE);
let amiibo_json_file = format!("{}/amiibo.json", path);
result_return_unless!(fsext::exists_file(amiibo_json_file.clone()), 0xBEBE);
let mut amiibo_json = fs::open_file(amiibo_json_file, fs::FileOpenOption::Read())?;
let mut amiibo_json_data: Vec<u8> = vec![0; amiibo_json.get_size()?];
amiibo_json.read(amiibo_json_data.as_mut_ptr(), amiibo_json_data.len())?;
if let Ok(amiibo_json_str) = core::str::from_utf8(amiibo_json_data.as_slice()) {
if let Ok(virtual_amiibo_info) = serde_json::from_str::<VirtualAmiiboInfo>(amiibo_json_str) {
return VirtualAmiibo::new(virtual_amiibo_info, path.clone());
}
}
Err(ResultCode::new(0xBEBE))
}

96
emuiibo/src/emu.rs Normal file
View File

@ -0,0 +1,96 @@
use nx::sync;
use alloc::vec::Vec;
use crate::amiibo;
#[derive(Copy, Clone)]
#[repr(C)]
pub struct Version {
pub major: u8,
pub minor: u8,
pub micro: u8,
pub is_dev_build: bool
}
impl Version {
pub const fn from(major: u8, minor: u8, micro: u8, is_dev_build: bool) -> Self {
Self { major: major, minor: minor, micro: micro, is_dev_build: is_dev_build }
}
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum EmulationStatus {
On,
Off
}
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
#[repr(u32)]
pub enum VirtualAmiiboStatus {
Invalid,
Connected,
Disconnected
}
pub const CURRENT_VERSION: Version = Version::from(0, 6, 0, true);
static mut G_EMULATION_STATUS: sync::Locked<EmulationStatus> = sync::Locked::new(false, EmulationStatus::Off);
static mut G_ACTIVE_VIRTUAL_AMIIBO_STATUS: sync::Locked<VirtualAmiiboStatus> = sync::Locked::new(false, VirtualAmiiboStatus::Invalid);
static mut G_INTERCEPTED_APPLICATION_IDS: sync::Locked<Vec<u64>> = sync::Locked::new(false, Vec::new());
static mut G_ACTIVE_VIRTUAL_AMIIBO: sync::Locked<amiibo::VirtualAmiibo> = sync::Locked::new(false, amiibo::VirtualAmiibo::empty());
pub fn get_emulation_status() -> EmulationStatus {
unsafe {
*G_EMULATION_STATUS.get()
}
}
pub fn set_emulation_status(status: EmulationStatus) {
unsafe {
G_EMULATION_STATUS.set(status);
}
}
pub fn get_active_virtual_amiibo_status() -> VirtualAmiiboStatus {
unsafe {
*G_ACTIVE_VIRTUAL_AMIIBO_STATUS.get()
}
}
pub fn set_active_virtual_amiibo_status(status: VirtualAmiiboStatus) {
unsafe {
G_ACTIVE_VIRTUAL_AMIIBO_STATUS.set(status);
}
}
pub fn register_intercepted_application_id(application_id: u64) {
unsafe {
G_INTERCEPTED_APPLICATION_IDS.get().push(application_id);
}
}
pub fn unregister_intercepted_application_id(application_id: u64) {
unsafe {
G_INTERCEPTED_APPLICATION_IDS.get().retain(|&id| id != application_id);
}
}
pub fn is_application_id_intercepted(application_id: u64) -> bool {
unsafe {
G_INTERCEPTED_APPLICATION_IDS.get().contains(&application_id)
}
}
pub fn get_active_virtual_amiibo() -> &'static amiibo::VirtualAmiibo {
unsafe {
G_ACTIVE_VIRTUAL_AMIIBO.get()
}
}
pub fn set_active_virtual_amiibo(virtual_amiibo: amiibo::VirtualAmiibo) {
unsafe {
G_ACTIVE_VIRTUAL_AMIIBO.set(virtual_amiibo);
set_active_virtual_amiibo_status(VirtualAmiiboStatus::Connected);
}
}

29
emuiibo/src/fsext.rs Normal file
View File

@ -0,0 +1,29 @@
use nx::result::*;
use nx::results;
use nx::fs;
use alloc::string::String;
pub fn exists_file(path: String) -> bool {
match fs::create_file(path.clone(), 0, fs::FileAttribute::None()) {
Ok(()) => {
let _ = fs::delete_file(path.clone());
false
},
Err(rc) => {
if results::fs::ResultPathAlreadyExists::matches(rc) {
true
}
else {
false
}
}
}
}
pub const BASE_DIR: &'static str = "sdmc:/emuiibo";
pub const VIRTUAL_AMIIBO_DIR: &'static str = "sdmc:/emuiibo/amiibo";
pub fn ensure_directories() {
let _ = fs::create_directory(String::from(BASE_DIR));
let _ = fs::create_directory(String::from(VIRTUAL_AMIIBO_DIR));
}

164
emuiibo/src/ipc/emu.rs Normal file
View File

@ -0,0 +1,164 @@
use nx::result::*;
use nx::mem;
use nx::ipc::sf;
use nx::ipc::server;
use nx::ipc::sf::applet;
use nx::ipc::sf::nfp;
use nx::ipc::sf::nfp::IUser;
use nx::ipc::sf::nfp::IUserManager;
use nx::ipc::sf::sm;
use nx::diag::log;
use nx::wait;
use nx::sync;
use nx::thread;
use alloc::string::String;
use crate::emu;
use crate::amiibo;
use crate::fsext;
pub trait IEmulationService {
ipc_interface_define_command!(get_version: () => (version: emu::Version));
ipc_interface_define_command!(get_virtual_amiibo_directory: (out_path: sf::OutMapAliasBuffer) => ());
ipc_interface_define_command!(get_emulation_status: () => (status: emu::EmulationStatus));
ipc_interface_define_command!(set_emulation_status: (status: emu::EmulationStatus) => ());
ipc_interface_define_command!(get_active_virtual_amiibo: (out_path: sf::OutMapAliasBuffer) => (virtual_amiibo: amiibo::VirtualAmiiboData));
ipc_interface_define_command!(set_active_virtual_amiibo: (path: sf::InMapAliasBuffer) => ());
ipc_interface_define_command!(reset_active_virtual_amiibo: () => ());
ipc_interface_define_command!(get_active_virtual_amiibo_status: () => (status: emu::VirtualAmiiboStatus));
ipc_interface_define_command!(set_active_virtual_amiibo_status: (status: emu::VirtualAmiiboStatus) => ());
ipc_interface_define_command!(is_application_id_intercepted: (application_id: u64) => (is_intercepted: bool));
ipc_interface_define_command!(is_current_application_id_intercepted: () => (is_intercepted: bool));
ipc_interface_define_command!(try_parse_virtual_amiibo: (path: sf::InMapAliasBuffer) => (virtual_amiibo: amiibo::VirtualAmiiboData));
}
pub struct EmulationService {
session: sf::Session
}
impl sf::IObject for EmulationService {
fn get_session(&mut self) -> &mut sf::Session {
&mut self.session
}
fn get_command_table(&self) -> sf::CommandMetadataTable {
ipc_server_make_command_table! {
get_version: 0,
get_virtual_amiibo_directory: 1,
get_emulation_status: 2,
set_emulation_status: 3,
get_active_virtual_amiibo: 4,
set_active_virtual_amiibo: 5,
reset_active_virtual_amiibo: 6,
get_active_virtual_amiibo_status: 7,
set_active_virtual_amiibo_status: 8,
is_application_id_intercepted: 9,
is_current_application_id_intercepted: 10,
try_parse_virtual_amiibo: 11
}
}
}
impl server::IServerObject for EmulationService {
fn new() -> Self {
Self { session: sf::Session::new() }
}
}
impl IEmulationService for EmulationService {
fn get_version(&mut self) -> Result<emu::Version> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_version... version: {}.{}.{} (dev: {})", emu::CURRENT_VERSION.major, emu::CURRENT_VERSION.minor, emu::CURRENT_VERSION.micro, emu::CURRENT_VERSION.is_dev_build);
Ok(emu::CURRENT_VERSION)
}
fn get_virtual_amiibo_directory(&mut self, mut out_path: sf::OutMapAliasBuffer) -> Result<()> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_virtual_amiibo_directory... dir: {}", fsext::VIRTUAL_AMIIBO_DIR);
out_path.set_string(String::from(fsext::VIRTUAL_AMIIBO_DIR));
Ok(())
}
fn get_emulation_status(&mut self) -> Result<emu::EmulationStatus> {
let status = emu::get_emulation_status();
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_emulation_status... status: {:?}", status);
Ok(status)
}
fn set_emulation_status(&mut self, status: emu::EmulationStatus) -> Result<()> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "set_emulation_status... new status: {:?}", status);
emu::set_emulation_status(status);
Ok(())
}
fn get_active_virtual_amiibo(&mut self, mut out_path: sf::OutMapAliasBuffer) -> Result<amiibo::VirtualAmiiboData> {
let amiibo = emu::get_active_virtual_amiibo();
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_active_virtual_amiibo... path: {}", amiibo.path);
result_return_unless!(amiibo.is_valid(), 0x360);
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => " - amiibo: {:?}", amiibo.info);
let data = amiibo.produce_data()?;
out_path.set_string(amiibo.path.clone());
Ok(data)
}
fn set_active_virtual_amiibo(&mut self, path: sf::InMapAliasBuffer) -> Result<()> {
let path_str = path.get_string();
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "set_active_virtual_amiibo... path: {}", path_str);
let amiibo = amiibo::try_load_virtual_amiibo(path_str)?;
result_return_unless!(amiibo.is_valid(), 0x360);
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => " - amiibo: {:?}", amiibo.info);
emu::set_active_virtual_amiibo(amiibo);
Ok(())
}
fn reset_active_virtual_amiibo(&mut self) -> Result<()> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "reset_active_virtual_amiibo...");
emu::set_active_virtual_amiibo(amiibo::VirtualAmiibo::empty());
Ok(())
}
fn get_active_virtual_amiibo_status(&mut self) -> Result<emu::VirtualAmiiboStatus> {
let status = emu::get_active_virtual_amiibo_status();
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_active_virtual_amiibo_status... status: {:?}", status);
Ok(status)
}
fn set_active_virtual_amiibo_status(&mut self, status: emu::VirtualAmiiboStatus) -> Result<()> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "set_active_virtual_amiibo_status... status: {:?}", status);
emu::set_active_virtual_amiibo_status(status);
Ok(())
}
fn is_application_id_intercepted(&mut self, application_id: u64) -> Result<bool> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "is_application_id_intercepted...");
Ok(emu::is_application_id_intercepted(application_id))
}
fn is_current_application_id_intercepted(&mut self) -> Result<bool> {
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "is_current_application_id_intercepted...");
// TODO
Ok(false)
}
fn try_parse_virtual_amiibo(&mut self, path: sf::InMapAliasBuffer) -> Result<amiibo::VirtualAmiiboData> {
let path_str = path.get_string();
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "try_parse_virtual_amiibo - path: {}", path_str);
let amiibo = amiibo::try_load_virtual_amiibo(path_str)?;
result_return_unless!(amiibo.is_valid(), 0x360);
// diag_log!(log::LmLogger { log::LogSeverity::Error, true } => " - amiibo: {:?}", amiibo.info);
let data = amiibo.produce_data()?;
Ok(data)
}
}
impl server::IService for EmulationService {
fn get_name() -> &'static str {
nul!("emuiibo")
}
fn get_max_sesssions() -> i32 {
40
}
}

2
emuiibo/src/ipc/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod nfp;
pub mod emu;

382
emuiibo/src/ipc/nfp.rs Normal file
View File

@ -0,0 +1,382 @@
use nx::result::*;
use nx::mem;
use nx::ipc::sf;
use nx::ipc::server;
use nx::ipc::sf::applet;
use nx::ipc::sf::nfp;
use nx::ipc::sf::nfp::IUser;
use nx::ipc::sf::nfp::IUserManager;
use nx::ipc::sf::sm;
use nx::diag::log;
use nx::wait;
use nx::sync;
use nx::thread;
use crate::emu;
pub struct User {
session: sf::Session,
activate_event: wait::SystemEvent,
deactivate_event: wait::SystemEvent,
availability_change_event: wait::SystemEvent,
state: sync::Locked<nfp::State>,
device_state: sync::Locked<nfp::DeviceState>,
should_end_thread: sync::Locked<bool>,
emu_handler_thread: thread::Thread
}
impl User {
pub fn new() -> Self {
Self { session: sf::Session::new(), activate_event: wait::SystemEvent::empty(), deactivate_event: wait::SystemEvent::empty(), availability_change_event: wait::SystemEvent::empty(), state: sync::Locked::new(false, nfp::State::NonInitialized), device_state: sync::Locked::new(false, nfp::DeviceState::Unavailable), emu_handler_thread: thread::Thread::empty(), should_end_thread: sync::Locked::new(false, false) }
}
pub fn is_state(&mut self, state: nfp::State) -> bool {
*self.state.get() == state
}
pub fn is_device_state(&mut self, device_state: nfp::DeviceState) -> bool {
*self.device_state.get() == device_state
}
pub fn handle_virtual_amiibo_status(&mut self, status: emu::VirtualAmiiboStatus) {
match status {
emu::VirtualAmiiboStatus::Connected => match *self.device_state.get() {
nfp::DeviceState::SearchingForTag => {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Connected: SearchingForTag => TagFound");
self.device_state.set(nfp::DeviceState::TagFound);
self.activate_event.signal().unwrap();
},
_ => {}
},
emu::VirtualAmiiboStatus::Disconnected => match *self.device_state.get() {
nfp::DeviceState::TagFound | nfp::DeviceState::TagMounted => {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Disconnected: TagFound/TagMounted => SearchingForTag");
self.device_state.set(nfp::DeviceState::SearchingForTag);
self.deactivate_event.signal().unwrap();
},
_ => {}
},
_ => {}
};
}
fn emu_handler_impl(user_v: *mut u8) {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Starting emu_handler thread...");
let user = user_v as *mut User;
unsafe {
loop {
if *(*user).should_end_thread.get() {
break;
}
let status = emu::get_active_virtual_amiibo_status();
(*user).handle_virtual_amiibo_status(status);
let _ = thread::sleep(100_000_000);
}
}
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Exiting emu_handler thread...");
}
}
impl Drop for User {
fn drop(&mut self) {
// TODO: emu::unregister_intercepted_application_id
self.should_end_thread.set(true);
self.emu_handler_thread.join().unwrap();
}
}
impl sf::IObject for User {
fn get_session(&mut self) -> &mut sf::Session {
&mut self.session
}
fn get_command_table(&self) -> sf::CommandMetadataTable {
ipc_server_make_command_table! {
initialize: 0,
finalize: 1,
list_devices: 2,
start_detection: 3,
stop_detection: 4,
mount: 5,
unmount: 6,
open_application_area: 7,
get_application_area: 8,
set_application_area: 9,
flush: 10,
restore: 11,
create_application_area: 12,
get_tag_info: 13,
get_register_info: 14,
get_common_info: 15,
get_model_info: 16,
attach_activate_event: 17,
attach_deactivate_event: 18,
get_state: 19,
get_device_state: 20,
get_npad_id: 21,
get_application_area_size: 22,
attach_availability_change_event: 23,
recreate_application_area: 24
}
}
}
impl IUser for User {
fn initialize(&mut self, aruid: applet::AppletResourceUserId, process_id: sf::ProcessId, mcu_data: sf::InMapAliasBuffer) -> Result<()> {
// TODO: make use of mcu data?
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "aruid: 0x{:X}, process_id: 0x{:X}, mcu data: {:p} - {}", aruid, process_id.process_id, mcu_data.buf, mcu_data.size);
result_return_unless!(self.is_state(nfp::State::NonInitialized), 0x8073);
self.state.set(nfp::State::Initialized);
self.device_state.set(nfp::DeviceState::Initialized);
// TODO: emu::register_intercepted_application_id
self.activate_event = wait::SystemEvent::new()?;
self.deactivate_event = wait::SystemEvent::new()?;
self.availability_change_event = wait::SystemEvent::new()?;
self.emu_handler_thread = thread::Thread::new(Self::emu_handler_impl, self as *mut Self as *mut u8, core::ptr::null_mut(), 0x1000, "ruiibo.AmiiboHandler")?;
self.emu_handler_thread.create_and_start(0x2B, -2)?;
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Everything initialized!");
Ok(())
}
fn finalize(&mut self) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Finalizing...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
self.state.set(nfp::State::NonInitialized);
self.device_state.set(nfp::DeviceState::Finalized);
Ok(())
}
fn list_devices(&mut self, out_devices: sf::OutPointerBuffer) -> Result<u32> {
let mut devices: &mut [nfp::DeviceHandle] = out_devices.get_mut_slice();
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Array length: {}", devices.len());
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
// Send a single fake device handle
// TODO: use hid to detect if the joycons are attached/detached
// Meanwhile, hardcode handheld
devices[0].npad_id = 0x20;
Ok(1)
}
fn start_detection(&mut self, device_handle: nfp::DeviceHandle) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Start detection...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
result_return_unless!(self.is_device_state(nfp::DeviceState::Initialized) || self.is_device_state(nfp::DeviceState::TagRemoved), 0x8073);
self.device_state.set(nfp::DeviceState::SearchingForTag);
Ok(())
}
fn stop_detection(&mut self, device_handle: nfp::DeviceHandle) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Stop detection...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
self.device_state.set(nfp::DeviceState::Initialized);
Ok(())
}
fn mount(&mut self, device_handle: nfp::DeviceHandle, device_type: nfp::DeviceType, mount_target: nfp::MountTarget) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "mount...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
self.device_state.set(nfp::DeviceState::TagMounted);
Ok(())
}
fn unmount(&mut self, device_handle: nfp::DeviceHandle) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "unmount...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
self.device_state.set(nfp::DeviceState::TagFound);
Ok(())
}
fn open_application_area(&mut self, device_handle: nfp::DeviceHandle, access_id: nfp::AccessId) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "open_application_area...");
// TODO
Err(ResultCode::new(0x10073))
}
fn get_application_area(&mut self, device_handle: nfp::DeviceHandle, out_data: sf::OutMapAliasBuffer) -> Result<u32> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_application_area...");
// TODO
Ok(out_data.size as u32)
}
fn set_application_area(&mut self, device_handle: nfp::DeviceHandle, data: sf::InMapAliasBuffer) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "set_application_area...");
// TODO
Ok(())
}
fn flush(&mut self, device_handle: nfp::DeviceHandle) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "flush...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(())
}
fn restore(&mut self, device_handle: nfp::DeviceHandle) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "restore...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(())
}
fn create_application_area(&mut self, device_handle: nfp::DeviceHandle, access_id: nfp::AccessId, data: sf::InMapAliasBuffer) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "create_application_area...");
// TODO
Ok(())
}
fn get_tag_info(&mut self, device_handle: nfp::DeviceHandle, mut out_tag_info: sf::OutFixedPointerBuffer<nfp::TagInfo>) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Tag info...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
result_return_unless!(self.is_device_state(nfp::DeviceState::TagFound) || self.is_device_state(nfp::DeviceState::TagMounted), 0x8073);
let amiibo = emu::get_active_virtual_amiibo();
result_return_unless!(amiibo.is_valid(), 0x8073);
out_tag_info.set_as(amiibo.produce_tag_info());
Ok(())
}
fn get_register_info(&mut self, device_handle: nfp::DeviceHandle, mut out_register_info: sf::OutFixedPointerBuffer<nfp::RegisterInfo>) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Register info...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
result_return_unless!(self.is_device_state(nfp::DeviceState::TagMounted), 0x8073);
let amiibo = emu::get_active_virtual_amiibo();
result_return_unless!(amiibo.is_valid(), 0x8073);
out_register_info.set_as(amiibo.produce_register_info());
Ok(())
}
fn get_common_info(&mut self, device_handle: nfp::DeviceHandle, mut out_common_info: sf::OutFixedPointerBuffer<nfp::CommonInfo>) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Common info...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
result_return_unless!(self.is_device_state(nfp::DeviceState::TagMounted), 0x8073);
let amiibo = emu::get_active_virtual_amiibo();
result_return_unless!(amiibo.is_valid(), 0x8073);
out_common_info.set_as(amiibo.produce_common_info());
Ok(())
}
fn get_model_info(&mut self, device_handle: nfp::DeviceHandle, mut out_model_info: sf::OutFixedPointerBuffer<nfp::ModelInfo>) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "Model info...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
result_return_unless!(self.is_device_state(nfp::DeviceState::TagMounted), 0x8073);
let amiibo = emu::get_active_virtual_amiibo();
result_return_unless!(amiibo.is_valid(), 0x8073);
out_model_info.set_as(amiibo.produce_model_info());
Ok(())
}
fn attach_activate_event(&mut self, device_handle: nfp::DeviceHandle) -> Result<sf::CopyHandle> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "attach_activate_event...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(sf::Handle::from(self.activate_event.client_handle))
}
fn attach_deactivate_event(&mut self, device_handle: nfp::DeviceHandle) -> Result<sf::CopyHandle> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "attach_deactivate_event...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(sf::Handle::from(self.deactivate_event.client_handle))
}
fn get_state(&mut self) -> Result<nfp::State> {
let state = *self.state.get();
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_state... - state: {:?}", state);
Ok(state)
}
fn get_device_state(&mut self, device_handle: nfp::DeviceHandle) -> Result<nfp::DeviceState> {
let device_state = *self.device_state.get();
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_device_state... - device state: {:?}", device_state);
Ok(device_state)
}
fn get_npad_id(&mut self, device_handle: nfp::DeviceHandle) -> Result<u32> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_npad_id...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(device_handle.npad_id)
}
fn get_application_area_size(&mut self, device_handle: nfp::DeviceHandle) -> Result<u32> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "get_application_area_size...");
// TODO
Ok(0)
}
fn attach_availability_change_event(&mut self) -> Result<sf::CopyHandle> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "attach_availability_change_event...");
result_return_unless!(self.is_state(nfp::State::Initialized), 0x8073);
Ok(sf::Handle::from(self.availability_change_event.client_handle))
}
fn recreate_application_area(&mut self, device_handle: nfp::DeviceHandle, access_id: nfp::AccessId, data: sf::InMapAliasBuffer) -> Result<()> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "recreate_application_area...");
// TODO
Ok(())
}
}
pub struct UserManager {
session: sf::Session
}
impl sf::IObject for UserManager {
fn get_session(&mut self) -> &mut sf::Session {
&mut self.session
}
fn get_command_table(&self) -> sf::CommandMetadataTable {
ipc_server_make_command_table! {
create_user_interface: 0
}
}
}
impl server::IServerObject for UserManager {
fn new() -> Self {
Self { session: sf::Session::new() }
}
}
impl IUserManager for UserManager {
fn create_user_interface(&mut self) -> Result<mem::Shared<dyn sf::IObject>> {
diag_log!(log::LmLogger { log::LogSeverity::Error, true } => "create_user_interface...");
Ok(mem::Shared::new(User::new()))
}
}
impl server::IMitmService for UserManager {
fn get_name() -> &'static str {
nul!("nfp:user")
}
fn should_mitm(_info: sm::MitmProcessInfo) -> bool {
emu::get_emulation_status() == emu::EmulationStatus::On
}
}

64
emuiibo/src/main.rs Normal file
View File

@ -0,0 +1,64 @@
#![no_std]
#![no_main]
#![feature(global_asm)]
#[macro_use]
extern crate nx;
#[macro_use]
extern crate alloc;
extern crate paste;
use nx::result::*;
use nx::results;
use nx::util;
use nx::wait;
use nx::thread;
use nx::diag::assert;
use nx::ipc::sf;
use nx::ipc::server;
use nx::service;
use nx::fs;
use nx::service::psc;
use nx::service::psc::IPmModule;
use nx::service::psc::IPmService;
use core::panic;
mod ipc;
mod emu;
mod fsext;
mod amiibo;
static mut STACK_HEAP: [u8; 0x60000] = [0; 0x60000];
#[no_mangle]
pub fn initialize_heap(_hbl_heap: util::PointerAndSize) -> util::PointerAndSize {
unsafe {
util::PointerAndSize::new(STACK_HEAP.as_mut_ptr(), STACK_HEAP.len())
}
}
#[no_mangle]
pub fn main() -> Result<()> {
thread::get_current_thread().name.set_str("ruiibo.Main")?;
fs::initialize()?;
fs::mount_sd_card("sdmc")?;
fsext::ensure_directories();
const POINTER_BUF_SIZE: usize = 0x400;
let mut manager: server::ServerManager<POINTER_BUF_SIZE> = server::ServerManager::new();
manager.register_mitm_service_server::<ipc::nfp::UserManager>()?;
manager.register_service_server::<ipc::emu::EmulationService>()?;
manager.loop_process()?;
fs::finalize();
Ok(())
}
#[panic_handler]
fn panic_handler(info: &panic::PanicInfo) -> ! {
util::on_panic_handler::<nx::diag::log::LmLogger>(info, assert::AssertMode::FatalThrow, results::lib::assert::ResultAssertionFailed::make())
}

@ -1 +1 @@
Subproject commit 66285245361a02e5480c7bb7dac9ef6449ae6181
Subproject commit a68af19eda8e2b1d51008a2e1a1b71649460f901

View File

@ -53,14 +53,12 @@ namespace emu {
}
Result GetActiveVirtualAmiibo(VirtualAmiiboData *out_amiibo_data, char *out_path, size_t out_path_size) {
return serviceDispatch(&g_emuiibo_srv, 4,
return serviceDispatchOut(&g_emuiibo_srv, 4, *out_amiibo_data,
.buffer_attrs = {
SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out,
SfBufferAttr_HipcMapAlias | SfBufferAttr_Out,
SfBufferAttr_HipcMapAlias | SfBufferAttr_Out
},
.buffers = {
{ out_amiibo_data, sizeof(VirtualAmiiboData) },
{ out_path, out_path_size },
{ out_path, out_path_size }
},
);
}
@ -96,14 +94,12 @@ namespace emu {
}
Result TryParseVirtualAmiibo(char *path, size_t path_size, VirtualAmiiboData *out_amiibo_data) {
return serviceDispatch(&g_emuiibo_srv, 11,
return serviceDispatchOut(&g_emuiibo_srv, 11, *out_amiibo_data,
.buffer_attrs = {
SfBufferAttr_HipcMapAlias | SfBufferAttr_In,
SfBufferAttr_FixedSize | SfBufferAttr_HipcPointer | SfBufferAttr_Out,
SfBufferAttr_HipcMapAlias | SfBufferAttr_In
},
.buffers = {
{ path, path_size },
{ out_amiibo_data, sizeof(VirtualAmiiboData) },
{ path, path_size }
},
);
}