mirror of
https://github.com/XorTroll/emuiibo.git
synced 2024-11-26 19:10:30 +00:00
Push current Rust rewrite (working, with some bugs)
This commit is contained in:
parent
1e31f0d241
commit
cb57c056f7
4
.gitignore
vendored
4
.gitignore
vendored
@ -11,4 +11,6 @@ build/
|
||||
bin/
|
||||
obj/
|
||||
.vs/
|
||||
SdOut/
|
||||
SdOut/
|
||||
Cargo.lock
|
||||
target/
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -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
|
28
Makefile
28
Makefile
@ -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
22
emuiibo/Cargo.toml
Normal 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"
|
130
emuiibo/Makefile
130
emuiibo/Makefile
@ -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
10
emuiibo/Xargo.toml
Normal 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"]
|
35
emuiibo/aarch64-none-elf.json
Normal file
35
emuiibo/aarch64-none-elf.json
Normal 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
|
||||
}
|
83
emuiibo/aarch64-none-elf.ld
Normal file
83
emuiibo/aarch64-none-elf.ld
Normal 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
|
||||
}
|
@ -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);
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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__ } })
|
@ -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";
|
||||
|
||||
}
|
@ -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()); \
|
||||
} \
|
||||
}
|
||||
|
||||
}
|
@ -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];
|
||||
};
|
@ -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); \
|
||||
} \
|
||||
}
|
||||
|
||||
}
|
@ -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>);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
@ -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");
|
||||
|
||||
}
|
@ -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>);
|
||||
|
||||
}
|
@ -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>);
|
||||
|
||||
}
|
@ -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>);
|
||||
|
||||
}
|
@ -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>);
|
||||
|
||||
}
|
24665
emuiibo/include/json.hpp
24665
emuiibo/include/json.hpp
File diff suppressed because it is too large
Load Diff
@ -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()); \
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace sys {
|
||||
|
||||
void ScanAmiiboDirectory();
|
||||
void DumpConsoleMiis();
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -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;
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
202
emuiibo/src/amiibo.rs
Normal 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
96
emuiibo/src/emu.rs
Normal 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
29
emuiibo/src/fsext.rs
Normal 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
164
emuiibo/src/ipc/emu.rs
Normal 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
2
emuiibo/src/ipc/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod nfp;
|
||||
pub mod emu;
|
382
emuiibo/src/ipc/nfp.rs
Normal file
382
emuiibo/src/ipc/nfp.rs
Normal 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
64
emuiibo/src/main.rs
Normal 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())
|
||||
}
|
2
libtesla
2
libtesla
@ -1 +1 @@
|
||||
Subproject commit 66285245361a02e5480c7bb7dac9ef6449ae6181
|
||||
Subproject commit a68af19eda8e2b1d51008a2e1a1b71649460f901
|
@ -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 }
|
||||
},
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user