├── .clang-format ├── .gitattributes ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── boot.ld ├── boot.mk ├── boot ├── 7zTypes.h ├── LzmaDec.c ├── LzmaDec.h ├── compress.py ├── load.c ├── main.S └── sections.h ├── channel.mk ├── channel ├── Main │ ├── Arch.cpp │ ├── Arch.hpp │ ├── GlobalsConfig.cpp │ ├── GlobalsConfig.hpp │ ├── IOSBoot.cpp │ ├── IOSBoot.hpp │ ├── Launch.S │ ├── LaunchState.hpp │ ├── Saoirse.cpp │ ├── Saoirse.hpp │ └── TaskThread.hpp └── UI │ ├── BasicUI.cpp │ ├── BasicUI.hpp │ ├── Input.cpp │ ├── Input.hpp │ ├── debugPrint.c │ └── debugPrint.h ├── common ├── DVD │ ├── DI.cpp │ ├── DI.hpp │ └── EmuDI.hpp ├── Debug │ ├── Log.cpp │ └── Log.hpp └── System │ ├── AES.cpp │ ├── AES.hpp │ ├── ES.cpp │ ├── ES.hpp │ ├── Hollywood.hpp │ ├── ISFS.hpp │ ├── LaunchError.hpp │ ├── OS.cpp │ ├── OS.hpp │ ├── SHA.cpp │ ├── SHA.hpp │ ├── Types.h │ └── Util.h ├── data.mk ├── ios.ld ├── ios.mk ├── ios ├── CTGP │ ├── Blob.cpp │ ├── Blob.hpp │ ├── EmuHID.cpp │ └── EmuHID.hpp ├── Disk │ ├── DeviceMgr.cpp │ ├── DeviceMgr.hpp │ ├── FatFS.cpp │ ├── SDCard.cpp │ ├── SDCard.hpp │ ├── USB.cpp │ ├── USB.hpp │ ├── USBStorage.cpp │ └── USBStorage.hpp ├── EmuSDIO │ ├── EmuSDIO.cpp │ └── EmuSDIO.hpp ├── FAT │ ├── diskio.h │ ├── ff.c │ ├── ff.h │ ├── ffconf.h │ └── ffunicode.c ├── IOS │ ├── EmuES.cpp │ ├── EmuES.hpp │ ├── IPCLog.cpp │ ├── IPCLog.hpp │ ├── Patch.cpp │ ├── Patch.hpp │ ├── RMConfig.s │ ├── Syscalls.h │ ├── Syscalls.s │ ├── System.cpp │ └── System.hpp ├── Loader │ └── Loader.cpp └── System │ ├── Config.cpp │ └── Config.hpp └── ios_loader.ld /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | # BasedOnStyle: LLVM 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: Align 5 | AlignConsecutiveAssignments: false 6 | AlignConsecutiveDeclarations: false 7 | AlignEscapedNewlines: Right 8 | AlignOperands: true 9 | AlignTrailingComments: false 10 | AllowAllParametersOfDeclarationOnNextLine: true 11 | AllowShortBlocksOnASingleLine: false 12 | AllowShortCaseLabelsOnASingleLine: false 13 | AllowShortEnumsOnASingleLine: false 14 | AllowShortFunctionsOnASingleLine: None 15 | AllowShortIfStatementsOnASingleLine: false 16 | AllowShortLoopsOnASingleLine: false 17 | AlwaysBreakAfterDefinitionReturnType: None 18 | AlwaysBreakAfterReturnType: None 19 | AlwaysBreakBeforeMultilineStrings: false 20 | AlwaysBreakTemplateDeclarations: Yes 21 | BinPackArguments: true 22 | BinPackParameters: true 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | AfterExternBlock: false 33 | BeforeCatch: false 34 | BeforeElse: false 35 | IndentBraces: false 36 | SplitEmptyFunction: true 37 | SplitEmptyRecord: true 38 | SplitEmptyNamespace: true 39 | BreakBeforeBinaryOperators: None 40 | BreakBeforeBraces: Custom 41 | BreakBeforeInheritanceComma: false 42 | BreakInheritanceList: BeforeColon 43 | BreakBeforeTernaryOperators: true 44 | BreakConstructorInitializersBeforeComma: false 45 | BreakConstructorInitializers: BeforeColon 46 | BreakAfterJavaFieldAnnotations: false 47 | BreakStringLiterals: true 48 | ColumnLimit: 80 49 | CommentPragmas: '^ IWYU pragma:' 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | ConstructorInitializerIndentWidth: 4 53 | ContinuationIndentWidth: 4 54 | Cpp11BracedListStyle: true 55 | DerivePointerAlignment: false 56 | DisableFormat: false 57 | ExperimentalAutoDetectBinPacking: false 58 | FixNamespaceComments: true 59 | ForEachMacros: 60 | - foreach 61 | - Q_FOREACH 62 | - BOOST_FOREACH 63 | IncludeBlocks: Preserve 64 | IncludeCategories: 65 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 66 | Priority: 2 67 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 68 | Priority: 3 69 | - Regex: '.*' 70 | Priority: 1 71 | IncludeIsMainRegex: '(Test)?$' 72 | IndentCaseLabels: false 73 | IndentPPDirectives: None 74 | IndentWidth: 4 75 | IndentWrappedFunctionNames: false 76 | JavaScriptQuotes: Leave 77 | JavaScriptWrapImports: true 78 | KeepEmptyLinesAtTheStartOfBlocks: true 79 | MacroBlockBegin: '' 80 | MacroBlockEnd: '' 81 | MaxEmptyLinesToKeep: 1 82 | NamespaceIndentation: None 83 | ObjCBinPackProtocolList: Auto 84 | ObjCBlockIndentWidth: 2 85 | ObjCSpaceAfterProperty: false 86 | ObjCSpaceBeforeProtocolList: true 87 | PenaltyBreakAssignment: 2 88 | PenaltyBreakBeforeFirstCallParameter: 19 89 | PenaltyBreakComment: 300 90 | PenaltyBreakFirstLessLess: 120 91 | PenaltyBreakString: 1000 92 | PenaltyBreakTemplateDeclaration: 10 93 | PenaltyExcessCharacter: 1000000 94 | PenaltyReturnTypeOnItsOwnLine: 60 95 | PointerAlignment: Left 96 | ReflowComments: true 97 | SortIncludes: true 98 | SortUsingDeclarations: true 99 | SpaceAfterCStyleCast: false 100 | SpaceAfterTemplateKeyword: true 101 | SpaceBeforeAssignmentOperators: true 102 | SpaceBeforeCpp11BracedList: false 103 | SpaceBeforeCtorInitializerColon: true 104 | SpaceBeforeInheritanceColon: true 105 | SpaceBeforeParens: ControlStatements 106 | SpaceBeforeRangeBasedForLoopColon: true 107 | SpaceInEmptyParentheses: false 108 | SpacesBeforeTrailingComments: 1 109 | SpacesInAngles: false 110 | SpacesInContainerLiterals: true 111 | SpacesInCStyleCastParentheses: false 112 | SpacesInParentheses: false 113 | SpacesInSquareBrackets: false 114 | Standard: Cpp11 115 | StatementMacros: 116 | - Q_UNUSED 117 | - QT_REQUIRE_VERSION 118 | TabWidth: 8 119 | UseTab: Never 120 | ... 121 | 122 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | build_ios 3 | build_channel 4 | build_boot 5 | .vscode 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Palapeli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean 2 | 3 | BIN2S := bin2s 4 | 5 | all: 6 | @$(MAKE) --no-print-directory -f ios.mk 7 | @$(MAKE) --no-print-directory -f data.mk 8 | @$(MAKE) --no-print-directory -f channel.mk 9 | @$(MAKE) --no-print-directory -f boot.mk 10 | 11 | clean: 12 | @rm -fr build_ios build_channel build_boot bin -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CTGP-R MSC 2 | 3 | CTGP-R MSC (Mass Storage Class) is a custom launcher for CTGP-R that adds support for loading CTGP-R's files off a USB storage device. It works by patching IOS to catch and redirect commands directed to the SD Card. This makes it possible to use CTGP-R on a Wii Mini, or a Wii/Wii U with a broken SD Card slot. 4 | 5 | Note that this does NOT mean that you can use an ISO, you still need a Mario Kart Wii game disc. This project is unrelated to and not compatible with USB loaders. 6 | 7 | This project is based on [Saoirse](https://github.com/TheLordScruffy/saoirse). 8 | 9 | ## Installing 10 | 11 | Make sure your USB device is formatted to FAT32, then follow the [guide on the ChadSoft website](https://chadsoft.co.uk/install-guide/) for regular CTGP-R, using a USB device in place of an SD Card. Download the latest release of CTGP-R MSC from here and then launch it using the Homebrew Channel. It can be installed to the Wii Menu as well just by using the regular CTGP-R channel installer. If you have already installed the regular CTGP-R channel, simply remove it and then add it again when launching through CTGP-R MSC. 12 | 13 | ## License 14 | 15 | All code in this repository is licensed under the MIT license unless stated otherwise in an individual file header. The full text of the license can be found in the 'LICENSE' file. 16 | -------------------------------------------------------------------------------- /boot.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-powerpc", "elf32-powerpc", "elf32-powerpc"); 2 | OUTPUT_ARCH(powerpc:common); 3 | EXTERN(_start); 4 | ENTRY(_start); 5 | 6 | PHDRS 7 | { 8 | stub PT_LOAD FLAGS(5); 9 | text PT_LOAD FLAGS(6); 10 | data PT_LOAD FLAGS(6); 11 | bss PT_LOAD; 12 | } 13 | 14 | SECTIONS 15 | { 16 | . = 0x80003400; 17 | 18 | .stub : { 19 | STUB_START = .; 20 | *(.stub) 21 | . = ALIGN(32); 22 | STUB_END = .; 23 | } : stub = 0 24 | 25 | . = 0x80004000; 26 | 27 | .text : { 28 | TEXT_START = .; 29 | *(.text*) 30 | . = ALIGN(32); 31 | TEXT_END = .; 32 | } : text = 0 33 | 34 | .data : { 35 | RODATA_START = .; 36 | KEEP(*(.bdkey)) 37 | *(.rodata*) 38 | RODATA_END = .; 39 | . = ALIGN(4); 40 | DATA_START = .; 41 | *(.data*) 42 | . = ALIGN(32); 43 | DATA_END = .; 44 | } : data = 0 45 | 46 | .bss : { 47 | PROVIDE(__bss_start = .); 48 | *(.dynsbss) 49 | *(.sbss) 50 | *(.sbss.*) 51 | *(.gnu.linkonce.sb.*) 52 | *(.scommon) 53 | *(.sbss2) 54 | *(.sbss2.*) 55 | *(.gnu.linkonce.sb2.*) 56 | *(.bss) 57 | *(.bss.*) 58 | *(.dynbss) 59 | *(.gnu.linkonce.b.*) 60 | *(COMMON) 61 | . = ALIGN(32); 62 | PROVIDE (__bss_end = .); 63 | } : bss = 0 64 | } 65 | -------------------------------------------------------------------------------- /boot.mk: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | # Clear the implicit built in rules 3 | #--------------------------------------------------------------------------------- 4 | .SUFFIXES: 5 | #--------------------------------------------------------------------------------- 6 | ifeq ($(strip $(DEVKITPPC)),) 7 | $(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") 8 | endif 9 | 10 | include $(DEVKITPPC)/wii_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # INCLUDES is a list of directories containing extra header files 17 | #--------------------------------------------------------------------------------- 18 | TARGET := boot 19 | BUILD := build_boot 20 | BIN := bin 21 | SOURCES := boot 22 | INCLUDES := 23 | 24 | #--------------------------------------------------------------------------------- 25 | # options for code generation 26 | #--------------------------------------------------------------------------------- 27 | 28 | CFLAGS = -g -O4 -Wall $(MACHDEP) $(INCLUDE) -ffreestanding 29 | CXXFLAGS = $(CFLAGS) 30 | 31 | LDFLAGS = -g -T../boot.ld -Wl,--gc-sections -Wl,-Map,$(notdir $@).map -ffreestanding -nodefaultlibs -nostdlib 32 | 33 | #--------------------------------------------------------------------------------- 34 | # any extra libraries we wish to link with the project 35 | #--------------------------------------------------------------------------------- 36 | LIBS := -lgcc -logc -lm 37 | 38 | #--------------------------------------------------------------------------------- 39 | # list of directories containing libraries, this must be the top level containing 40 | # include and lib 41 | #--------------------------------------------------------------------------------- 42 | LIBDIRS := 43 | 44 | #--------------------------------------------------------------------------------- 45 | # no real need to edit anything past this point unless you need to add additional 46 | # rules for different file extensions 47 | #--------------------------------------------------------------------------------- 48 | ifneq ($(BUILD),$(notdir $(CURDIR))) 49 | #--------------------------------------------------------------------------------- 50 | 51 | export OUTPUT := $(CURDIR)/$(TARGET) 52 | 53 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 54 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 55 | 56 | export DEPSDIR := $(CURDIR)/$(BUILD) 57 | 58 | #--------------------------------------------------------------------------------- 59 | # automatically build a list of object files for our project 60 | #--------------------------------------------------------------------------------- 61 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 62 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 63 | sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 64 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) 65 | BINFILES := $(BIN)/channel.dol.lzma 66 | 67 | #--------------------------------------------------------------------------------- 68 | # use CXX for linking C++ projects, CC for standard C 69 | #--------------------------------------------------------------------------------- 70 | ifeq ($(strip $(CPPFILES)),) 71 | export LD := $(CC) 72 | else 73 | export LD := $(CXX) 74 | endif 75 | 76 | export OFILES := $(addsuffix .o,$(notdir $(BINFILES))) \ 77 | $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ 78 | $(sFILES:.s=.o) $(SFILES:.S=.o) 79 | 80 | #--------------------------------------------------------------------------------- 81 | # build a list of include paths 82 | #--------------------------------------------------------------------------------- 83 | export INCLUDE := $(foreach dir,$(INCLUDES), -I$(CURDIR)/$(dir)) \ 84 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 85 | -I$(LIBOGC_INC) 86 | 87 | #--------------------------------------------------------------------------------- 88 | # build a list of library paths 89 | #--------------------------------------------------------------------------------- 90 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ 91 | -L$(LIBOGC_LIB) 92 | 93 | export OUTPUT := $(CURDIR)/$(BIN)/$(TARGET) 94 | .PHONY: $(BUILD) clean 95 | 96 | #--------------------------------------------------------------------------------- 97 | $(BUILD): 98 | @[ -d $@ ] || mkdir -p $@ 99 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/boot.mk 100 | 101 | #--------------------------------------------------------------------------------- 102 | clean: 103 | @echo clean ... 104 | @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol 105 | 106 | #--------------------------------------------------------------------------------- 107 | run: 108 | wiiload $(BIN)/$(TARGET).dol 109 | 110 | #--------------------------------------------------------------------------------- 111 | else 112 | 113 | DEPENDS := $(OFILES:.o=.d) 114 | 115 | #--------------------------------------------------------------------------------- 116 | # main targets 117 | #--------------------------------------------------------------------------------- 118 | $(OUTPUT).dol: $(OUTPUT).elf 119 | $(OUTPUT).elf: $(OFILES) 120 | 121 | -include $(DEPENDS) 122 | 123 | #--------------------------------------------------------------------------------- 124 | # This rule links in the lzma compressed dol 125 | #--------------------------------------------------------------------------------- 126 | channel.dol.lzma.o : $(CURDIR)/../$(BIN)/channel.dol.lzma 127 | #--------------------------------------------------------------------------------- 128 | @$(bin2o) 129 | 130 | $(CURDIR)/../$(BIN)/channel.dol.lzma: $(CURDIR)/../$(BIN)/channel.dol 131 | @echo compressing ... $(notdir $@) 132 | @python $(CURDIR)/../boot/compress.py $< $@ 133 | 134 | #--------------------------------------------------------------------------------- 135 | endif 136 | #--------------------------------------------------------------------------------- 137 | -------------------------------------------------------------------------------- /boot/LzmaDec.h: -------------------------------------------------------------------------------- 1 | /* LzmaDec.h -- LZMA Decoder 2 | 2020-03-19 : Igor Pavlov : Public domain */ 3 | 4 | #ifndef __LZMA_DEC_H 5 | #define __LZMA_DEC_H 6 | 7 | #include "7zTypes.h" 8 | 9 | EXTERN_C_BEGIN 10 | 11 | /* #define _LZMA_PROB32 */ 12 | /* _LZMA_PROB32 can increase the speed on some CPUs, 13 | but memory usage for CLzmaDec::probs will be doubled in that case */ 14 | 15 | typedef 16 | #ifdef _LZMA_PROB32 17 | UInt32 18 | #else 19 | UInt16 20 | #endif 21 | CLzmaProb; 22 | 23 | 24 | /* ---------- LZMA Properties ---------- */ 25 | 26 | #define LZMA_PROPS_SIZE 5 27 | 28 | typedef struct _CLzmaProps 29 | { 30 | Byte lc; 31 | Byte lp; 32 | Byte pb; 33 | Byte _pad_; 34 | UInt32 dicSize; 35 | } CLzmaProps; 36 | 37 | /* LzmaProps_Decode - decodes properties 38 | Returns: 39 | SZ_OK 40 | SZ_ERROR_UNSUPPORTED - Unsupported properties 41 | */ 42 | 43 | SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); 44 | 45 | 46 | /* ---------- LZMA Decoder state ---------- */ 47 | 48 | /* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. 49 | Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ 50 | 51 | #define LZMA_REQUIRED_INPUT_MAX 20 52 | 53 | typedef struct 54 | { 55 | /* Don't change this structure. ASM code can use it. */ 56 | CLzmaProps prop; 57 | CLzmaProb *probs; 58 | CLzmaProb *probs_1664; 59 | Byte *dic; 60 | SizeT dicBufSize; 61 | SizeT dicPos; 62 | const Byte *buf; 63 | UInt32 range; 64 | UInt32 code; 65 | UInt32 processedPos; 66 | UInt32 checkDicSize; 67 | UInt32 reps[4]; 68 | UInt32 state; 69 | UInt32 remainLen; 70 | 71 | UInt32 numProbs; 72 | unsigned tempBufSize; 73 | Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; 74 | } CLzmaDec; 75 | 76 | #define LzmaDec_Construct(p) { (p)->dic = NULL; (p)->probs = NULL; } 77 | 78 | void LzmaDec_Init(CLzmaDec *p); 79 | 80 | /* There are two types of LZMA streams: 81 | - Stream with end mark. That end mark adds about 6 bytes to compressed size. 82 | - Stream without end mark. You must know exact uncompressed size to decompress such stream. */ 83 | 84 | typedef enum 85 | { 86 | LZMA_FINISH_ANY, /* finish at any point */ 87 | LZMA_FINISH_END /* block must be finished at the end */ 88 | } ELzmaFinishMode; 89 | 90 | /* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! 91 | 92 | You must use LZMA_FINISH_END, when you know that current output buffer 93 | covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. 94 | 95 | If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, 96 | and output value of destLen will be less than output buffer size limit. 97 | You can check status result also. 98 | 99 | You can use multiple checks to test data integrity after full decompression: 100 | 1) Check Result and "status" variable. 101 | 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. 102 | 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. 103 | You must use correct finish mode in that case. */ 104 | 105 | typedef enum 106 | { 107 | LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ 108 | LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ 109 | LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ 110 | LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ 111 | LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ 112 | } ELzmaStatus; 113 | 114 | /* ELzmaStatus is used only as output value for function call */ 115 | 116 | 117 | /* ---------- Interfaces ---------- */ 118 | 119 | /* There are 3 levels of interfaces: 120 | 1) Dictionary Interface 121 | 2) Buffer Interface 122 | 3) One Call Interface 123 | You can select any of these interfaces, but don't mix functions from different 124 | groups for same object. */ 125 | 126 | 127 | /* There are two variants to allocate state for Dictionary Interface: 128 | 1) LzmaDec_Allocate / LzmaDec_Free 129 | 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs 130 | You can use variant 2, if you set dictionary buffer manually. 131 | For Buffer Interface you must always use variant 1. 132 | 133 | LzmaDec_Allocate* can return: 134 | SZ_OK 135 | SZ_ERROR_MEM - Memory allocation error 136 | SZ_ERROR_UNSUPPORTED - Unsupported properties 137 | */ 138 | 139 | SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); 140 | void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc); 141 | 142 | SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); 143 | void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr alloc); 144 | 145 | /* ---------- Dictionary Interface ---------- */ 146 | 147 | /* You can use it, if you want to eliminate the overhead for data copying from 148 | dictionary to some other external buffer. 149 | You must work with CLzmaDec variables directly in this interface. 150 | 151 | STEPS: 152 | LzmaDec_Construct() 153 | LzmaDec_Allocate() 154 | for (each new stream) 155 | { 156 | LzmaDec_Init() 157 | while (it needs more decompression) 158 | { 159 | LzmaDec_DecodeToDic() 160 | use data from CLzmaDec::dic and update CLzmaDec::dicPos 161 | } 162 | } 163 | LzmaDec_Free() 164 | */ 165 | 166 | /* LzmaDec_DecodeToDic 167 | 168 | The decoding to internal dictionary buffer (CLzmaDec::dic). 169 | You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! 170 | 171 | finishMode: 172 | It has meaning only if the decoding reaches output limit (dicLimit). 173 | LZMA_FINISH_ANY - Decode just dicLimit bytes. 174 | LZMA_FINISH_END - Stream must be finished after dicLimit. 175 | 176 | Returns: 177 | SZ_OK 178 | status: 179 | LZMA_STATUS_FINISHED_WITH_MARK 180 | LZMA_STATUS_NOT_FINISHED 181 | LZMA_STATUS_NEEDS_MORE_INPUT 182 | LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK 183 | SZ_ERROR_DATA - Data error 184 | SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure 185 | */ 186 | 187 | SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, 188 | const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); 189 | 190 | 191 | /* ---------- Buffer Interface ---------- */ 192 | 193 | /* It's zlib-like interface. 194 | See LzmaDec_DecodeToDic description for information about STEPS and return results, 195 | but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need 196 | to work with CLzmaDec variables manually. 197 | 198 | finishMode: 199 | It has meaning only if the decoding reaches output limit (*destLen). 200 | LZMA_FINISH_ANY - Decode just destLen bytes. 201 | LZMA_FINISH_END - Stream must be finished after (*destLen). 202 | */ 203 | 204 | SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, 205 | const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); 206 | 207 | 208 | /* ---------- One Call Interface ---------- */ 209 | 210 | /* LzmaDecode 211 | 212 | finishMode: 213 | It has meaning only if the decoding reaches output limit (*destLen). 214 | LZMA_FINISH_ANY - Decode just destLen bytes. 215 | LZMA_FINISH_END - Stream must be finished after (*destLen). 216 | 217 | Returns: 218 | SZ_OK 219 | status: 220 | LZMA_STATUS_FINISHED_WITH_MARK 221 | LZMA_STATUS_NOT_FINISHED 222 | LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK 223 | SZ_ERROR_DATA - Data error 224 | SZ_ERROR_MEM - Memory allocation error 225 | SZ_ERROR_UNSUPPORTED - Unsupported properties 226 | SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). 227 | SZ_ERROR_FAIL - Some unexpected error: internal error of code, memory corruption or hardware failure 228 | */ 229 | 230 | SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, 231 | const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, 232 | ELzmaStatus *status, ISzAllocPtr alloc); 233 | 234 | EXTERN_C_END 235 | 236 | #endif 237 | -------------------------------------------------------------------------------- /boot/compress.py: -------------------------------------------------------------------------------- 1 | import sys, lzma 2 | 3 | dol = open(sys.argv[1], "rb") 4 | lzmadol = lzma.open(sys.argv[2], "wb", format=lzma.FORMAT_ALONE) 5 | 6 | dol.seek(0, 2) 7 | insize = dol.tell() 8 | dol.seek(0, 0) 9 | 10 | inbytes = dol.read(insize) 11 | lzmadol.write(inbytes) 12 | -------------------------------------------------------------------------------- /boot/load.c: -------------------------------------------------------------------------------- 1 | // load.c - LZMA loader stub 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "LzmaDec.h" 7 | #include "sections.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | typedef unsigned char u8; 14 | typedef unsigned int u32; 15 | 16 | extern const u8 channel_dol_lzma[]; 17 | extern u32 channel_dol_lzma_end; 18 | 19 | void LoaderAbort() 20 | { 21 | // Do something 22 | int* i = (int*)0x90000000; 23 | int* j = (int*)0x90000000; 24 | while (1) { 25 | *i = *j + 1; 26 | } 27 | } 28 | 29 | #define DECODE_ADDR ((u8*)(0x81200000)) 30 | 31 | typedef struct { 32 | union { 33 | struct { 34 | u32 dol_text[7]; 35 | u32 dol_data[11]; 36 | }; 37 | u32 dol_sect[7 + 11]; 38 | }; 39 | union { 40 | struct { 41 | u32 dol_text_addr[7]; 42 | u32 dol_data_addr[11]; 43 | }; 44 | u32 dol_sect_addr[7 + 11]; 45 | }; 46 | union { 47 | struct { 48 | u32 dol_text_size[7]; 49 | u32 dol_data_size[11]; 50 | }; 51 | u32 dol_sect_size[7 + 11]; 52 | }; 53 | u32 dol_bss_addr; 54 | u32 dol_bss_size; 55 | u32 dol_entry_point; 56 | u32 dol_pad[0x1C / 4]; 57 | } DOL; 58 | 59 | static inline void clearWords(u32* data, u32 count) 60 | { 61 | while (count--) { 62 | asm volatile("dcbz 0, %0\n" 63 | //"sync\n" 64 | "dcbf 0, %0\n" ::"r"(data)); 65 | data += 8; 66 | } 67 | } 68 | 69 | static inline void copyWords(u32* dest, u32* src, u32 count) 70 | { 71 | register u32 value = 0; 72 | while (count--) { 73 | asm volatile("dcbz 0, %1\n" 74 | //"sync\n" 75 | "lwz %0, 0(%2)\n" 76 | "stw %0, 0(%1)\n" 77 | "lwz %0, 4(%2)\n" 78 | "stw %0, 4(%1)\n" 79 | "lwz %0, 8(%2)\n" 80 | "stw %0, 8(%1)\n" 81 | "lwz %0, 12(%2)\n" 82 | "stw %0, 12(%1)\n" 83 | "lwz %0, 16(%2)\n" 84 | "stw %0, 16(%1)\n" 85 | "lwz %0, 20(%2)\n" 86 | "stw %0, 20(%1)\n" 87 | "lwz %0, 24(%2)\n" 88 | "stw %0, 24(%1)\n" 89 | "lwz %0, 28(%2)\n" 90 | "stw %0, 28(%1)\n" 91 | "dcbf 0, %1\n" ::"r"(value), 92 | "r"(dest), "r"(src)); 93 | dest += 8; 94 | src += 8; 95 | } 96 | } 97 | 98 | #define INLINE_MEMCPY(__dst, __src, __len) \ 99 | do { \ 100 | for (int __i = 0; __i < (__len); __i++) { \ 101 | ((unsigned char*)(__dst))[__i] = ((unsigned char*)(__src))[__i]; \ 102 | } \ 103 | } while (0) 104 | 105 | void InitExceptionHandlers() 106 | { 107 | register u32 branch = 0x4C000064; 108 | register u32 addr = 0x80000000; 109 | 110 | #define EXCEPTION_HANDLER_ASM(offset) \ 111 | asm volatile("stwu %1, " #offset "(%0)\n" \ 112 | "dcbf 0, %0\n" \ 113 | "sync\n" \ 114 | "icbi 0, %0\n" \ 115 | "isync\n" \ 116 | : \ 117 | : "r"(addr), "r"(branch)); 118 | 119 | EXCEPTION_HANDLER_ASM(0x100); // 0100 120 | EXCEPTION_HANDLER_ASM(0x100); // 0200 121 | EXCEPTION_HANDLER_ASM(0x100); // 0300 122 | EXCEPTION_HANDLER_ASM(0x100); // 0400 123 | EXCEPTION_HANDLER_ASM(0x100); // 0500 124 | EXCEPTION_HANDLER_ASM(0x100); // 0600 125 | EXCEPTION_HANDLER_ASM(0x100); // 0700 126 | EXCEPTION_HANDLER_ASM(0x100); // 0800 127 | EXCEPTION_HANDLER_ASM(0x100); // 0900 128 | EXCEPTION_HANDLER_ASM(0x400); // 0D00 129 | EXCEPTION_HANDLER_ASM(0x200); // 0F00 130 | EXCEPTION_HANDLER_ASM(0x400); // 1300 131 | EXCEPTION_HANDLER_ASM(0x100); // 1400 132 | EXCEPTION_HANDLER_ASM(0x300); // 1700 133 | } 134 | 135 | __attribute__((noreturn)) void load() 136 | { 137 | InitExceptionHandlers(); 138 | 139 | clearWords(&__bss_start, ((u32)&__bss_end - (u32)&__bss_start) / 32); 140 | 141 | // Copy our sections to somewhere else in memory for the channel installer 142 | SectionSaveStruct* sections = (SectionSaveStruct*)SECTION_SAVE_ADDR; 143 | sections->sectionsMagic = 0; 144 | sections->stubStart = (u32)&STUB_START; 145 | sections->stubEnd = (u32)&STUB_END; 146 | sections->textStart = (u32)&TEXT_START; 147 | sections->textEnd = (u32)&TEXT_END; 148 | sections->rodataStart = (u32)&RODATA_START; 149 | sections->rodataEnd = (u32)&RODATA_END; 150 | sections->bssStart = (u32)&__bss_start; 151 | sections->bssEnd = (u32)&__bss_end; 152 | sections->rwDataSize = (u32)&DATA_END - (u32)&DATA_START; 153 | 154 | void* rwData = (void*)(sections + 1); 155 | INLINE_MEMCPY(rwData, &DATA_START, sections->rwDataSize); 156 | 157 | sections->sectionsMagic = SECTION_SAVE_MAGIC; 158 | 159 | ELzmaStatus status; 160 | 161 | u32 channel_dol_lzma_size = 162 | (const u8*)&channel_dol_lzma_end - channel_dol_lzma; 163 | size_t destLen = 0x700000; 164 | size_t inLen = channel_dol_lzma_size - 5; 165 | SRes ret = LzmaDecode(DECODE_ADDR, &destLen, channel_dol_lzma + 0xD, &inLen, 166 | channel_dol_lzma, LZMA_PROPS_SIZE, LZMA_FINISH_END, 167 | &status, 0); 168 | 169 | if (ret != SZ_OK) 170 | LoaderAbort(); 171 | 172 | DOL* dol = (DOL*)DECODE_ADDR; 173 | clearWords((u32*)dol->dol_bss_addr, dol->dol_bss_size / 4); 174 | 175 | for (int i = 0; i < 7 + 11; i++) { 176 | if (dol->dol_sect_size[i] != 0) { 177 | copyWords((u32*)dol->dol_sect_addr[i], 178 | (u32*)(DECODE_ADDR + dol->dol_sect[i]), 179 | (dol->dol_sect_size[i] / 4) / 8); 180 | } 181 | } 182 | 183 | (*(void (*)(void))dol->dol_entry_point)(); 184 | while (1) { 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /boot/main.S: -------------------------------------------------------------------------------- 1 | // main.S - LZMA loader init 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #define r0 0 7 | #define r1 1 8 | #define sp 1 9 | #define r2 2 10 | #define toc 2 11 | #define r3 3 12 | #define r4 4 13 | #define r5 5 14 | #define r6 6 15 | #define r7 7 16 | #define r8 8 17 | #define r9 9 18 | #define r10 10 19 | #define r11 11 20 | #define r12 12 21 | #define r13 13 22 | #define r14 14 23 | #define r15 15 24 | #define r16 16 25 | #define r17 17 26 | #define r18 18 27 | #define r19 19 28 | #define r20 20 29 | #define r21 21 30 | #define r22 22 31 | #define r23 23 32 | #define r24 24 33 | #define r25 25 34 | #define r26 26 35 | #define r27 27 36 | #define r28 28 37 | #define r29 29 38 | #define r30 30 39 | #define r31 31 40 | 41 | #define SRR0 26 42 | #define SRR1 27 43 | 44 | .text 45 | .section .stub 46 | .global _start 47 | _start: 48 | lis r3, rvlStartup@h 49 | andis. r3, r3, 0x7FFF 50 | ori r3, r3, rvlStartup@l 51 | mtspr SRR0, r3 52 | li r4, 0 53 | mtspr SRR1, r4 54 | rfi 55 | 56 | rvlStartup: 57 | lis sp, _stack_end@h 58 | ori sp, sp, _stack_end@l 59 | bl __InitBATS 60 | bl __InitCache 61 | bl load 62 | b 0 63 | 64 | .size _start, . - _start 65 | 66 | .section .bss 67 | .global _stack 68 | .global _stack_end 69 | _stack: 70 | .space 0x1000 71 | _stack_end: 72 | -------------------------------------------------------------------------------- /boot/sections.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | extern u32 STUB_START; 6 | extern u32 STUB_END; 7 | 8 | extern u32 TEXT_START; 9 | extern u32 TEXT_END; 10 | 11 | extern u32 RODATA_START; 12 | extern u32 RODATA_END; 13 | 14 | extern u32 DATA_START; 15 | extern u32 DATA_END; 16 | 17 | extern u32 __bss_start; 18 | extern u32 __bss_end; 19 | 20 | #define SECTION_SAVE_ADDR 0x92000000 21 | 22 | #define SECTION_SAVE_MAGIC 0x53454354 23 | 24 | typedef struct { 25 | u32 sectionsMagic; 26 | u32 stubStart; 27 | u32 stubEnd; 28 | u32 textStart; 29 | u32 textEnd; 30 | u32 rodataStart; 31 | u32 rodataEnd; 32 | u32 bssStart; 33 | u32 bssEnd; 34 | u32 rwDataSize; 35 | // RW data is copied here 36 | } SectionSaveStruct; 37 | -------------------------------------------------------------------------------- /channel.mk: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | # Clear the implicit built in rules 3 | #--------------------------------------------------------------------------------- 4 | .SUFFIXES: 5 | #--------------------------------------------------------------------------------- 6 | ifeq ($(strip $(DEVKITPPC)),) 7 | $(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") 8 | endif 9 | 10 | include $(DEVKITPPC)/wii_rules 11 | 12 | #--------------------------------------------------------------------------------- 13 | # TARGET is the name of the output 14 | # BUILD is the directory where object files & intermediate files will be placed 15 | # SOURCES is a list of directories containing source code 16 | # INCLUDES is a list of directories containing extra header files 17 | #--------------------------------------------------------------------------------- 18 | TARGET := channel 19 | BUILD := build_channel 20 | BIN := bin 21 | SOURCES := channel $(wildcard common/*) $(wildcard channel/*) 22 | DATA := data 23 | INCLUDES := $(SOURCES) $(BUILD) common 24 | 25 | #--------------------------------------------------------------------------------- 26 | # options for code generation 27 | #--------------------------------------------------------------------------------- 28 | 29 | CFLAGS = -g -Os -DNDEBUG -Wall -Wextra -Wpedantic -Wnull-dereference -Wshadow -Werror -Wno-format-truncation -fno-exceptions \ 30 | -fno-asynchronous-unwind-tables -fno-unwind-tables -fno-builtin-memcpy -fno-builtin-memset -Wno-pointer-arith \ 31 | -Wno-unused-variable \ 32 | $(MACHDEP) $(INCLUDE) 33 | CXXFLAGS = $(CFLAGS) -std=c++20 -fno-rtti -Wno-register -Wno-narrowing 34 | 35 | LDFLAGS = -g $(MACHDEP) -Wl,-Map,$(OUTPUT).map -Wl,--section-start,.init=0x80800000 36 | 37 | #--------------------------------------------------------------------------------- 38 | # any extra libraries we wish to link with the project 39 | #--------------------------------------------------------------------------------- 40 | LIBS := -lfat -lwiiuse -lbte -logc -lm 41 | 42 | #--------------------------------------------------------------------------------- 43 | # list of directories containing libraries, this must be the top level containing 44 | # include and lib 45 | #--------------------------------------------------------------------------------- 46 | LIBDIRS := 47 | 48 | #--------------------------------------------------------------------------------- 49 | # no real need to edit anything past this point unless you need to add additional 50 | # rules for different file extensions 51 | #--------------------------------------------------------------------------------- 52 | ifneq ($(BUILD),$(notdir $(CURDIR))) 53 | #--------------------------------------------------------------------------------- 54 | 55 | export OUTPUT := $(CURDIR)/$(TARGET) 56 | 57 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 58 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 59 | 60 | export DEPSDIR := $(CURDIR)/$(BUILD) 61 | 62 | #--------------------------------------------------------------------------------- 63 | # automatically build a list of object files for our project 64 | #--------------------------------------------------------------------------------- 65 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 66 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 67 | sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 68 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) 69 | BINFILES := $(BIN)/data.ar 70 | 71 | #--------------------------------------------------------------------------------- 72 | # use CXX for linking C++ projects, CC for standard C 73 | #--------------------------------------------------------------------------------- 74 | ifeq ($(strip $(CPPFILES)),) 75 | export LD := $(CC) 76 | else 77 | export LD := $(CXX) 78 | endif 79 | 80 | export OFILES := $(addsuffix .o,$(notdir $(BINFILES))) \ 81 | $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ 82 | $(sFILES:.s=.o) $(SFILES:.S=.o) 83 | 84 | #--------------------------------------------------------------------------------- 85 | # build a list of include paths 86 | #--------------------------------------------------------------------------------- 87 | export INCLUDE := $(foreach dir,$(INCLUDES), -I$(CURDIR)/$(dir)) \ 88 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 89 | -I$(LIBOGC_INC) 90 | 91 | #--------------------------------------------------------------------------------- 92 | # build a list of library paths 93 | #--------------------------------------------------------------------------------- 94 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ 95 | -L$(LIBOGC_LIB) 96 | 97 | export OUTPUT := $(CURDIR)/$(BIN)/$(TARGET) 98 | .PHONY: $(BUILD) clean 99 | 100 | #--------------------------------------------------------------------------------- 101 | $(BUILD): 102 | @[ -d $@ ] || mkdir -p $@ 103 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/channel.mk 104 | 105 | #--------------------------------------------------------------------------------- 106 | clean: 107 | @echo clean ... 108 | @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).dol 109 | 110 | #--------------------------------------------------------------------------------- 111 | run: 112 | wiiload $(BIN)/$(TARGET).dol 113 | 114 | #--------------------------------------------------------------------------------- 115 | else 116 | 117 | DEPENDS := $(OFILES:.o=.d) 118 | 119 | #--------------------------------------------------------------------------------- 120 | # main targets 121 | #--------------------------------------------------------------------------------- 122 | $(OUTPUT).dol: $(OUTPUT).elf 123 | $(OUTPUT).elf: $(OFILES) 124 | 125 | -include $(DEPENDS) 126 | 127 | #--------------------------------------------------------------------------------- 128 | # This rule links in the data archive 129 | #--------------------------------------------------------------------------------- 130 | data.ar.o : $(CURDIR)/../$(BIN)/data.ar 131 | #--------------------------------------------------------------------------------- 132 | @echo $(notdir $<) 133 | @$(bin2o) 134 | 135 | #--------------------------------------------------------------------------------- 136 | endif 137 | #--------------------------------------------------------------------------------- 138 | -------------------------------------------------------------------------------- /channel/Main/Arch.cpp: -------------------------------------------------------------------------------- 1 | // Arch.cpp - Archive data reader 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Arch.hpp" 7 | #include 8 | #include 9 | 10 | Arch* Arch::sInstance; 11 | 12 | bool Arch::getShortName(const char* name, char* out) 13 | { 14 | if (m_lfnFile == nullptr) 15 | return false; 16 | 17 | const char* start = m_lfnFile; 18 | const char* end = m_lfnFile + m_lfnFileSize; 19 | 20 | while (start < end) { 21 | if (*start == '\n' || *start == ' ') { 22 | start++; 23 | continue; 24 | } 25 | 26 | const char* fnend = strchr(start, '/'); 27 | u32 len = fnend - start; 28 | if (strlen(name) == len && !strncmp(start, name, len)) { 29 | snprintf(out, 8, "/%d", start - m_lfnFile); 30 | return true; 31 | } 32 | start = fnend + 1; 33 | } 34 | 35 | return false; 36 | } 37 | 38 | const void* Arch::getFile(const char* name, u32* size) 39 | { 40 | char shortName[8]; 41 | const char* rname = name; 42 | 43 | if (strlen(name) > 15) { 44 | if (!getShortName(name, shortName)) 45 | return nullptr; 46 | rname = shortName; 47 | } 48 | 49 | for (auto it : m_subfiles) { 50 | if (strlen(rname) == it.nameLen && 51 | !strncmp(rname, it.name, it.nameLen)) { 52 | if (size != nullptr) { 53 | *size = it.size; 54 | } 55 | return it.data; 56 | } 57 | } 58 | return nullptr; 59 | } 60 | 61 | const void* Arch::getFileStatic(const char* name, u32* size) 62 | { 63 | return Arch::sInstance->getFile(name, size); 64 | } 65 | 66 | Arch::Arch(const char* file, u32 size) 67 | { 68 | m_file = file; 69 | m_lfnFile = nullptr; 70 | 71 | if (strncmp(m_file, "!\n", sizeof("!\n") - 1)) { 72 | m_valid = false; 73 | return; 74 | } 75 | 76 | std::vector m_longFileNames; 77 | const char* start = m_file + sizeof("!\n") - 1; 78 | const char* end = start + size; 79 | 80 | while (start + 0x3C <= end) { 81 | if (*start == '\n') { 82 | start++; 83 | continue; 84 | } 85 | 86 | const char* fnend; 87 | if (start[0] != '/') { 88 | fnend = strchr(start + 1, '/'); 89 | } else { 90 | fnend = strchr(start + 1, ' '); 91 | } 92 | 93 | Subfile subfile; 94 | subfile.name = start; 95 | subfile.nameLen = fnend - start; 96 | subfile.size = atoi(start + 0x30); 97 | subfile.data = start + 0x3C; 98 | m_subfiles.push_back(subfile); 99 | 100 | if (subfile.nameLen == 2 && !strncmp(subfile.name, "//", 2)) { 101 | m_lfnFile = subfile.data; 102 | m_lfnFileSize = subfile.size; 103 | } 104 | 105 | start += 0x3C + subfile.size; 106 | } 107 | 108 | m_valid = true; 109 | return; 110 | } 111 | -------------------------------------------------------------------------------- /channel/Main/Arch.hpp: -------------------------------------------------------------------------------- 1 | // Arch.hpp - Archive data reader 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | 10 | class Arch 11 | { 12 | public: 13 | static Arch* sInstance; 14 | 15 | Arch(const char* file, u32 size); 16 | bool getShortName(const char* name, char* out); 17 | const void* getFile(const char* name, u32* size = nullptr); 18 | static const void* getFileStatic(const char* name, u32* size = nullptr); 19 | 20 | private: 21 | const char* m_file; 22 | bool m_valid; 23 | struct Subfile { 24 | const char* name; 25 | u32 nameLen; 26 | u32 size; 27 | const char* data; 28 | }; 29 | std::vector m_subfiles; 30 | const char* m_lfnFile; 31 | u32 m_lfnFileSize; 32 | }; 33 | -------------------------------------------------------------------------------- /channel/Main/GlobalsConfig.cpp: -------------------------------------------------------------------------------- 1 | // GlobalsConfig.cpp 2 | // Written by riidefi 3 | // Based on BrainSlug, by Chadderz 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | #include "GlobalsConfig.hpp" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | typedef struct { 15 | char gamename[4]; 16 | char company[2]; 17 | uint8_t disknum; 18 | uint8_t gamever; 19 | uint8_t streaming; 20 | uint8_t streambufsize; 21 | uint8_t pad[14]; 22 | uint32_t wii_magic; 23 | uint32_t gc_magic; 24 | } os_disc_id_t; 25 | 26 | typedef enum { 27 | OS_BOOT_NORMAL = 0x0d15ea5e 28 | } os_boot_type_t; 29 | 30 | typedef struct { 31 | uint32_t boot_type; 32 | uint32_t version; 33 | uint32_t mem1_size; 34 | uint32_t console_type; 35 | uint32_t arena_low; 36 | uint32_t arena_high; 37 | void* fst; 38 | uint32_t fst_size; 39 | } os_system_info_t; 40 | 41 | typedef struct { 42 | uint32_t enabled; 43 | uint32_t exception_mask; 44 | void* destination; 45 | uint8_t temp[0x14]; 46 | uint8_t hook[0x24]; 47 | uint8_t padding[0x3c]; 48 | } os_debugger_t; 49 | 50 | typedef enum { 51 | OS_TV_MODE_NTSC, 52 | OS_TV_MODE_PAL, 53 | OS_TV_MODE_MPAL, 54 | OS_TV_MODE_DEBUG, 55 | OS_TV_MODE_DEBUG_PAL, 56 | OS_TV_MODE_PAL60, 57 | } os_tv_mode_t; 58 | 59 | typedef struct { 60 | void* current_context_phy; 61 | uint32_t previous_interrupt_mask; 62 | uint32_t current_interrupt_mask; 63 | uint32_t tv_mode; 64 | uint32_t aram_size; 65 | void* current_context; 66 | void* default_thread; 67 | void* thread_queue_head; 68 | void* thread_queue_tail; 69 | void* current_thread; 70 | uint32_t debug_monitor_size; 71 | void* debug_monitor_location; 72 | uint32_t simulated_memory_size; 73 | void* bi2; 74 | uint32_t bus_speed; 75 | uint32_t cpu_speed; 76 | } os_thread_info_t; 77 | 78 | typedef struct { 79 | os_disc_id_t disc; /* 0x0 */ 80 | os_system_info_t info; /* 0x20 */ 81 | os_debugger_t debugger; /* 0x40 */ 82 | os_thread_info_t threads; /* 0xc0 */ 83 | } os_early_globals_t; 84 | 85 | static os_early_globals_t* const os0 = (os_early_globals_t*)0x80000000; 86 | 87 | typedef struct { 88 | void* exception_handlers[0x10]; /* 0x0 */ 89 | void* irq_handlers[0x20]; /* 0x40 */ 90 | uint8_t paddingc0[0x100 - 0xc0]; /* 0xc0 */ 91 | uint32_t mem1_size; /* 0x100 */ 92 | uint32_t mem1_simulated_size; /* 0x104 */ 93 | void* mem1_end; /* 0x108 */ 94 | uint8_t padding10c[0x110 - 0x10c]; /* 0x10c */ 95 | void* fst; /* 0x110 */ 96 | uint8_t padding114[0x118 - 0x114]; /* 0x114 */ 97 | uint32_t mem2_size; /* 0x118 */ 98 | uint32_t mem2_simulated_size; /* 0x11c */ 99 | uint8_t padding120[0x130 - 0x120]; /* 0x120 */ 100 | uint32_t ios_heap_start; /* 0x130 */ 101 | uint32_t ios_heap_end; /* 0x134 */ 102 | uint32_t hollywood_version; /* 0x138 */ 103 | uint8_t padding13c[0x140 - 0x13c]; /* 0x13c */ 104 | uint16_t ios_number; /* 0x140 */ 105 | uint16_t ios_revision; /* 0x142 */ 106 | uint32_t ios_build_date; /* 0x144 */ 107 | uint8_t padding148[0x158 - 0x148]; /* 0x148 */ 108 | uint32_t gddr_vendor_id; /* 0x158 */ 109 | uint32_t legacy_di; /* 0x15c */ 110 | uint32_t init_semaphore; /* 0x160 */ 111 | uint32_t mios_flag; /* 0x164 */ 112 | uint8_t padding168[0x180 - 0x168]; /* 0x168 */ 113 | char application_name[4]; /* 0x180 */ 114 | os_disc_id_t* id; /* 0x184 */ 115 | uint16_t expected_ios_number; /* 0x188 */ 116 | uint16_t expected_ios_revision; /* 0x18a */ 117 | uint32_t launch_code; /* 0x18c */ 118 | uint32_t return_code; /* 0x190 */ 119 | } os_late_globals_t; 120 | 121 | static os_late_globals_t* const os1 = (os_late_globals_t*)0x80003000; 122 | 123 | uint32_t GetArenaLow() 124 | { 125 | return os0->info.arena_low; 126 | } 127 | void SetArenaLow(uint32_t low) 128 | { 129 | os0->info.arena_low = low; 130 | } 131 | uint32_t GetArenaHigh() 132 | { 133 | return os0->info.arena_high; 134 | } 135 | void SetArenaHigh(uint32_t high) 136 | { 137 | os0->info.arena_high = high; 138 | } 139 | 140 | void SetupGlobals([[maybe_unused]] int fst_expand) 141 | { 142 | // GXRModeObj* mode = VIDEO_GetPreferredMode(nullptr); 143 | 144 | #if 0 145 | switch (os0->disc.gamename[3]) { 146 | case 'E': 147 | case 'J': 148 | case 'K': 149 | os0->threads.tv_mode = OS_TV_MODE_NTSC; 150 | break; 151 | case 'P': 152 | case 'D': 153 | case 'F': 154 | case 'X': 155 | case 'Y': 156 | if (mode->viTVMode == OS_TV_MODE_PAL) 157 | os0->threads.tv_mode = OS_TV_MODE_PAL; 158 | else 159 | os0->threads.tv_mode = OS_TV_MODE_PAL60; 160 | break; 161 | } 162 | #endif 163 | 164 | // os0->info.boot_type = OS_BOOT_NORMAL; 165 | os0->info.version = 1; 166 | os0->info.mem1_size = 0x01800000; 167 | 168 | // [Heap---------------][Mods][FST][END OF MEMORY] 169 | os0->info.console_type = 0x23; 170 | os0->info.arena_low = 0x80000000; 171 | os0->info.arena_high = 0x81800000; 172 | os0->info.fst = (char*)0x81800000; 173 | os0->info.fst_size = 0; 174 | 175 | os0->threads.debug_monitor_location = (void*)0x81800000; 176 | os0->threads.simulated_memory_size = 0x01800000; 177 | os0->threads.bus_speed = 0x0E7BE2C0; 178 | os0->threads.cpu_speed = 0x2B73A840; 179 | 180 | *(u32*)0x80000100 = 0x4C000064; 181 | *(u32*)0x80000200 = 0x4C000064; 182 | *(u32*)0x80000300 = 0x4C000064; 183 | *(u32*)0x80000400 = 0x4C000064; 184 | *(u32*)0x80000500 = 0x4C000064; 185 | *(u32*)0x80000600 = 0x4C000064; 186 | *(u32*)0x80000700 = 0x4C000064; 187 | *(u32*)0x80000800 = 0x4C000064; 188 | *(u32*)0x80000900 = 0x4C000064; 189 | *(u32*)0x80000D00 = 0x4C000064; 190 | *(u32*)0x80000F00 = 0x4C000064; 191 | *(u32*)0x80001300 = 0x4C000064; 192 | *(u32*)0x80001400 = 0x4C000064; 193 | *(u32*)0x80001700 = 0x4C000064; 194 | 195 | DCFlushRange(os0, 0x3f00); 196 | 197 | *(u32*)0x80000C00 = 0x4C000064; 198 | *(u32*)0xC0000C00 = 0x4C000064; 199 | } 200 | -------------------------------------------------------------------------------- /channel/Main/GlobalsConfig.hpp: -------------------------------------------------------------------------------- 1 | // GlobalsConfig.hpp 2 | // Written by riidefi 3 | // Based on BrainSlug, by Chadderz 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | #pragma once 8 | #include 9 | 10 | void SetupGlobals(int fst_expand); 11 | 12 | uint32_t GetArenaLow(); 13 | void SetArenaLow(uint32_t low); 14 | uint32_t GetArenaHigh(); 15 | void SetArenaHigh(uint32_t high); 16 | 17 | namespace mem 18 | { 19 | inline void* AllocFromArenaHigh(uint32_t size) 20 | { 21 | const uint32_t current_High = GetArenaHigh(); 22 | SetArenaHigh(current_High + size); 23 | 24 | return reinterpret_cast(current_High); 25 | } 26 | 27 | struct arena_high { 28 | }; 29 | } // namespace mem 30 | 31 | inline void* operator new(uint32_t size, mem::arena_high) 32 | { 33 | return mem::AllocFromArenaHigh(size); 34 | } 35 | inline void* operator new[](uint32_t size, mem::arena_high) 36 | { 37 | return mem::AllocFromArenaHigh(size); 38 | } 39 | -------------------------------------------------------------------------------- /channel/Main/IOSBoot.hpp: -------------------------------------------------------------------------------- 1 | // IOSBoot.hpp - IOS startup code 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace IOSBoot 13 | { 14 | 15 | s32 Entry(u32 entrypoint); 16 | s32 Launch(const void* data, u32 len); 17 | void SafeFlushRange(const void* data, u32 len); 18 | void LaunchSaoirseIOS(); 19 | void DebugLaunchReport(); 20 | 21 | class IPCLog 22 | { 23 | public: 24 | static IPCLog* sInstance; 25 | 26 | IPCLog(); 27 | 28 | int getEventCount() const 29 | { 30 | return m_eventCount; 31 | } 32 | 33 | void startGameIOS(void* dolData, u32 dolSize); 34 | 35 | void setEventWaitingQueue(Queue* queue, int count) 36 | { 37 | m_eventQueue = queue; 38 | m_triggerEventCount = count; 39 | } 40 | 41 | protected: 42 | bool handleEvent(s32 result); 43 | static s32 threadEntry(void* userdata); 44 | 45 | bool reset = false; 46 | IOS::ResourceCtrl logRM{"/dev/saoirse"}; 47 | char logBuffer[256] ATTRIBUTE_ALIGN(32); 48 | 49 | int m_eventCount = 0; 50 | Queue* m_eventQueue; 51 | int m_triggerEventCount = -1; 52 | 53 | Thread m_thread; 54 | }; 55 | 56 | void SetupPrintHook(); 57 | void ReadPrintHook(); 58 | 59 | } // namespace IOSBoot 60 | -------------------------------------------------------------------------------- /channel/Main/Launch.S: -------------------------------------------------------------------------------- 1 | .text 2 | .global LaunchTrampoline 3 | LaunchTrampoline: 4 | mtctr 3 5 | 6 | li 0, 0 7 | lis 1, 0x8088 8 | ori 1, 1, 0x2390 9 | lis 2, 0x807C 10 | ori 2, 2, 0x9ECC 11 | li 3, 0 12 | li 4, 0 13 | li 5, 0x3988 14 | li 6, 0 15 | lis 7, 0x8098 16 | ori 7, 7, 0x747C 17 | lis 8, 0x8098 18 | ori 8, 8, 0x73A4 19 | li 9, -4 20 | li 10, 0 21 | li 11, 0 22 | lis 12, 0xCD00 23 | ori 12, 12, 0x6438 24 | lis 13, 0x807C 25 | ori 13, 13, 0xC100 26 | li 14, 0 27 | li 15, 0 28 | li 16, 0 29 | li 17, 0 30 | li 18, 0 31 | li 19, 0 32 | li 20, 0 33 | li 21, 0 34 | li 22, 0 35 | li 23, 0 36 | li 24, 2 37 | li 25, 4 38 | lis 26, 0x807C 39 | ori 26, 26, 0x2CC4 40 | li 27, 3 41 | li 28, 0xB4 42 | lis 29, 0x8098 43 | ori 29, 29, 0x73A8 44 | lis 30, 0x80AF 45 | ori 30, 30, 0x6488 46 | lis 31, 0x8130 47 | 48 | bctrl 49 | 50 | b 0 51 | -------------------------------------------------------------------------------- /channel/Main/LaunchState.hpp: -------------------------------------------------------------------------------- 1 | // LaunchState.hpp - Game launch progress 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | template 11 | struct LaunchValue { 12 | LaunchValue() 13 | { 14 | available = false; 15 | } 16 | 17 | bool available; 18 | T state; 19 | }; 20 | 21 | class LaunchState 22 | { 23 | public: 24 | static LaunchState* Get() 25 | { 26 | static LaunchState* instance = nullptr; 27 | 28 | if (instance == nullptr) { 29 | instance = new LaunchState(); 30 | } 31 | 32 | return instance; 33 | } 34 | 35 | LaunchValue DiscInserted; 36 | LaunchValue ReadDiscID; 37 | LaunchValue SDCardInserted; 38 | 39 | // Unavailable if currently trying to launch. False if failed. 40 | LaunchValue LaunchReady; 41 | 42 | LaunchValue Error; 43 | }; 44 | -------------------------------------------------------------------------------- /channel/Main/Saoirse.cpp: -------------------------------------------------------------------------------- 1 | // Saoirse.cpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Saoirse.hpp" 7 | #include "Arch.hpp" 8 | #include "GlobalsConfig.hpp" 9 | #include "IOSBoot.hpp" 10 | #include 11 | #include 12 | #include
13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | LIBOGC_SUCKS_BEGIN 21 | #include 22 | #include 23 | #include 24 | #include 25 | LIBOGC_SUCKS_END 26 | 27 | bool g_calledFromHBC = false; 28 | 29 | #define HBC_TITLE_0 0x000100014C554C5A 30 | #define HBC_TITLE_1 0x000100014F484243 31 | 32 | void ReturnToLoader() 33 | { 34 | if (g_calledFromHBC) { 35 | WII_LaunchTitle(HBC_TITLE_0); 36 | WII_LaunchTitle(HBC_TITLE_1); 37 | } 38 | 39 | WII_ReturnToMenu(); 40 | } 41 | 42 | bool RTCRead(u32 offset, u32* value) 43 | { 44 | if (EXI_Lock(EXI_CHANNEL_0, EXI_DEVICE_1, NULL) == 0) 45 | return false; 46 | if (EXI_Select(EXI_CHANNEL_0, EXI_DEVICE_1, EXI_SPEED8MHZ) == 0) { 47 | EXI_Unlock(EXI_CHANNEL_0); 48 | return false; 49 | } 50 | 51 | bool ret = true; 52 | if (EXI_Imm(EXI_CHANNEL_0, &offset, 4, EXI_WRITE, NULL) == 0) 53 | ret = false; 54 | if (EXI_Sync(EXI_CHANNEL_0) == 0) 55 | ret = false; 56 | if (EXI_Imm(EXI_CHANNEL_0, value, 4, EXI_READ, NULL) == 0) 57 | ret = false; 58 | if (EXI_Sync(EXI_CHANNEL_0) == 0) 59 | ret = false; 60 | if (EXI_Deselect(EXI_CHANNEL_0) == 0) 61 | ret = false; 62 | EXI_Unlock(EXI_CHANNEL_0); 63 | 64 | return ret; 65 | } 66 | 67 | bool RTCWrite(u32 offset, u32 value) 68 | { 69 | if (EXI_Lock(EXI_CHANNEL_0, EXI_DEVICE_1, NULL) == 0) 70 | return false; 71 | if (EXI_Select(EXI_CHANNEL_0, EXI_DEVICE_1, EXI_SPEED8MHZ) == 0) { 72 | EXI_Unlock(EXI_CHANNEL_0); 73 | return false; 74 | } 75 | 76 | // Enable write mode 77 | offset |= 0x80000000; 78 | 79 | bool ret = true; 80 | if (EXI_Imm(EXI_CHANNEL_0, &offset, 4, EXI_WRITE, NULL) == 0) 81 | ret = false; 82 | if (EXI_Sync(EXI_CHANNEL_0) == 0) 83 | ret = false; 84 | if (EXI_Imm(EXI_CHANNEL_0, &value, 4, EXI_WRITE, NULL) == 0) 85 | ret = false; 86 | if (EXI_Sync(EXI_CHANNEL_0) == 0) 87 | ret = false; 88 | if (EXI_Deselect(EXI_CHANNEL_0) == 0) 89 | ret = false; 90 | EXI_Unlock(EXI_CHANNEL_0); 91 | 92 | return ret; 93 | } 94 | 95 | // Re-enables holding the power button to turn off the console on vWii 96 | bool WiiUEnableHoldPower() 97 | { 98 | // RTC_CONTROL1 |= 4COUNT_EN 99 | 100 | u32 flags = 0; 101 | bool ret = RTCRead(0x21000D00, &flags); 102 | if (!ret) 103 | return false; 104 | 105 | ret = RTCWrite(0x21000D00, flags | 1); 106 | if (!ret) 107 | return false; 108 | 109 | return true; 110 | } 111 | 112 | static void PIErrorHandler([[maybe_unused]] u32 nIrq, 113 | [[maybe_unused]] void* pCtx) 114 | { 115 | // u32 cause = read32(0x0C003000); // INTSR 116 | write32(0x0C003000, 1); // Reset 117 | } 118 | 119 | void abort() 120 | { 121 | LIBOGC_SUCKS_BEGIN 122 | u32 lr = mfspr(8); 123 | LIBOGC_SUCKS_END 124 | 125 | PRINT(Core, ERROR, "Abort called. LR = 0x%08X\n", lr); 126 | 127 | sleep(2); 128 | exit(0); 129 | 130 | // If this somehow returns then halt. 131 | IRQ_Disable(); 132 | while (1) { 133 | } 134 | } 135 | 136 | [[maybe_unused]] static s32 UIThreadEntry([[maybe_unused]] void* arg) 137 | { 138 | BasicUI::sInstance->Loop(); 139 | return 0; 140 | } 141 | 142 | typedef struct { 143 | union { 144 | u32 dol_sect[7 + 11]; 145 | }; 146 | union { 147 | u32 dol_sect_addr[7 + 11]; 148 | }; 149 | union { 150 | u32 dol_sect_size[7 + 11]; 151 | }; 152 | u32 dol_bss_addr; 153 | u32 dol_bss_size; 154 | u32 dol_entry_point; 155 | u32 dol_pad[0x1C / 4]; 156 | } DOL; 157 | 158 | extern "C" { 159 | void LaunchTrampoline(u32 entry); 160 | } 161 | 162 | void LaunchGame() 163 | { 164 | #ifndef DISABLE_UI 165 | VIDEO_SetBlack(true); 166 | VIDEO_Flush(); 167 | VIDEO_WaitVSync(); 168 | #endif 169 | 170 | u32 entryPoint = *(u32*)0xC0003400; 171 | 172 | delete IOSBoot::IPCLog::sInstance; 173 | 174 | // TODO: Proper shutdown 175 | SYS_ResetSystem(SYS_SHUTDOWN, 0, 0); 176 | IRQ_Disable(); 177 | 178 | memset((void*)0x80001800, 0, 0x1800); 179 | DCFlushRange((void*)0x80001800, 0x1800); 180 | memset((void*)0x80003400, 0, 0xB00); 181 | DCFlushRange((void*)0x80003400, 0xB00); 182 | 183 | SetupGlobals(0); 184 | 185 | LaunchTrampoline(entryPoint); 186 | 187 | /* Unreachable! */ 188 | abort(); 189 | } 190 | 191 | void TestISFS() 192 | { 193 | } 194 | 195 | #include 196 | 197 | void TestISFSReadDir() 198 | { 199 | } 200 | 201 | void TestDirectOpen() 202 | { 203 | } 204 | 205 | #include "../../boot/sections.h" 206 | 207 | u32 totalDolSize = 0; 208 | void* dolData = nullptr; 209 | 210 | void MakeDOLForInstaller(SectionSaveStruct* sections) 211 | { 212 | u32 stubSize = sections->stubEnd - sections->stubStart; 213 | u32 textSize = sections->textEnd - sections->textStart; 214 | u32 rodataSize = sections->rodataEnd - sections->rodataStart; 215 | u32 bssSize = sections->bssEnd - sections->bssStart; 216 | 217 | totalDolSize = sizeof(DOL) + round_up(stubSize, 0x40) + 218 | round_up(textSize, 0x40) + round_up(rodataSize, 4) + 219 | sections->rwDataSize; 220 | PRINT(Core, INFO, "total DOL size: %08X", totalDolSize); 221 | 222 | dolData = new u8[totalDolSize]; 223 | assert(dolData != nullptr); 224 | PRINT(Core, INFO, "Alloc: %08X", dolData); 225 | 226 | memset(dolData, 0, totalDolSize); 227 | DOL* dol = (DOL*)dolData; 228 | 229 | dol->dol_sect[0] = sizeof(DOL); 230 | dol->dol_sect_addr[0] = sections->stubStart; 231 | dol->dol_sect_size[0] = stubSize; 232 | dol->dol_sect[7] = dol->dol_sect[0] + round_up(stubSize, 0x40); 233 | dol->dol_sect_addr[7] = sections->textStart; 234 | dol->dol_sect_size[7] = textSize; 235 | dol->dol_sect[8] = dol->dol_sect[7] + round_up(textSize, 0x40); 236 | dol->dol_sect_addr[8] = sections->rodataStart; 237 | dol->dol_sect_size[8] = round_up(rodataSize, 4) + sections->rwDataSize; 238 | 239 | memcpy(dolData + dol->dol_sect[0], (void*)sections->stubStart, stubSize); 240 | memcpy(dolData + dol->dol_sect[7], (void*)sections->textStart, textSize); 241 | memcpy(dolData + dol->dol_sect[8], (void*)sections->rodataStart, 242 | rodataSize); 243 | memcpy(dolData + dol->dol_sect[8] + round_up(rodataSize, 4), 244 | (void*)(sections + 1), sections->rwDataSize); 245 | 246 | dol->dol_bss_addr = sections->bssStart; 247 | dol->dol_bss_size = bssSize; 248 | dol->dol_entry_point = 0x80003400; 249 | } 250 | 251 | s32 main([[maybe_unused]] s32 argc, [[maybe_unused]] char** argv) 252 | { 253 | // Properly handle PI errors 254 | IRQ_Request(IRQ_PI_ERROR, PIErrorHandler, nullptr); 255 | __UnmaskIrq(IM_PI_ERROR); 256 | 257 | if (*(u32*)(0x80001804) == 0x53545542) { 258 | g_calledFromHBC = true; 259 | *(u32*)(0x80001804) = 0; 260 | DCFlushRange((void*)0x80001804, 4); 261 | } 262 | 263 | // This is a nice thing to enable for development, but we should probably 264 | // leave it disabled for the end user, unless we can figure out why it was 265 | // disabled in the first place. 266 | // WiiUEnableHoldPower(); 267 | 268 | LaunchState::Get()->Error.available = true; 269 | if (IOS_ReloadIOS(58) < 0) { 270 | abort(); 271 | } 272 | 273 | #ifndef DISABLE_UI 274 | Input::sInstance = new Input(); 275 | BasicUI::sInstance = new BasicUI(); 276 | BasicUI::sInstance->InitVideo(); 277 | new Thread(UIThreadEntry, nullptr, nullptr, 0x1000, 80); 278 | 279 | PRINT(Core, WARN, "Debug console initialized"); 280 | VIDEO_WaitVSync(); 281 | #endif 282 | 283 | // Let's make the DOL! 284 | SectionSaveStruct* sections = (SectionSaveStruct*)SECTION_SAVE_ADDR; 285 | if (sections->sectionsMagic == SECTION_SAVE_MAGIC) { 286 | MakeDOLForInstaller(sections); 287 | } 288 | 289 | // Setup main data archive 290 | extern const char data_ar[]; 291 | extern const char data_ar_end[]; 292 | Arch::sInstance = new Arch(data_ar, data_ar_end - data_ar); 293 | 294 | // Launch Saoirse IOS 295 | IOSBoot::LaunchSaoirseIOS(); 296 | 297 | PRINT(Core, INFO, "Send start game IOS request!"); 298 | IOSBoot::IPCLog::sInstance->startGameIOS(dolData, totalDolSize); 299 | 300 | LaunchState::Get()->DiscInserted.state = true; 301 | LaunchState::Get()->DiscInserted.available = true; 302 | LaunchState::Get()->ReadDiscID.state = true; 303 | LaunchState::Get()->ReadDiscID.available = true; 304 | LaunchState::Get()->LaunchReady.state = true; 305 | LaunchState::Get()->LaunchReady.available = true; 306 | 307 | // usleep(32000); 308 | LaunchGame(); 309 | 310 | LWP_SuspendThread(LWP_GetSelf()); 311 | } 312 | -------------------------------------------------------------------------------- /channel/Main/Saoirse.hpp: -------------------------------------------------------------------------------- 1 | // Saoirse.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | void LaunchGame(); 9 | void TestISFS(); 10 | void TestISFSReadDir(); 11 | void TestDirectOpen(); 12 | -------------------------------------------------------------------------------- /channel/Main/TaskThread.hpp: -------------------------------------------------------------------------------- 1 | // TaskThread.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | 10 | class TaskThread 11 | { 12 | public: 13 | static s32 __threadProc([[maybe_unused]] void* arg) 14 | { 15 | TaskThread* that = reinterpret_cast(arg); 16 | that->taskEntry(); 17 | that->taskSuccess(); 18 | return 0; 19 | } 20 | 21 | void start(Queue* onDestroyQueue = nullptr) 22 | { 23 | if (m_running) 24 | return; 25 | 26 | m_running = true; 27 | m_cancelTask = false; 28 | m_onDestroyQueue = onDestroyQueue; 29 | m_thread.create(&__threadProc, reinterpret_cast(this), nullptr, 30 | 0x1000, 80); 31 | } 32 | 33 | void stop() 34 | { 35 | m_cancelTask = true; 36 | } 37 | 38 | bool isRunning() const 39 | { 40 | return m_running; 41 | } 42 | 43 | void taskBreak() 44 | { 45 | if (m_cancelTask) { 46 | _destroyThread(0); 47 | } 48 | } 49 | 50 | void taskAbort() 51 | { 52 | _destroyThread(-1); 53 | } 54 | 55 | void taskSuccess() 56 | { 57 | _destroyThread(0); 58 | } 59 | 60 | virtual void taskEntry() = 0; 61 | 62 | private: 63 | void _destroyThread(int result) 64 | { 65 | m_running = false; 66 | m_thread.~Thread(); 67 | if (m_onDestroyQueue != nullptr) { 68 | m_onDestroyQueue->send(result); 69 | } 70 | } 71 | 72 | Thread m_thread; 73 | bool m_cancelTask = false; 74 | bool m_running = false; 75 | Queue* m_onDestroyQueue = nullptr; 76 | }; 77 | -------------------------------------------------------------------------------- /channel/UI/BasicUI.cpp: -------------------------------------------------------------------------------- 1 | // BasicUI.cpp - Simple text-based UI 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "BasicUI.hpp" 7 | #include
8 | #include
9 | #include 10 | #include 11 | #include 12 | #include 13 | LIBOGC_SUCKS_BEGIN 14 | #include 15 | #include 16 | #include 17 | #include 18 | LIBOGC_SUCKS_END 19 | 20 | BasicUI* BasicUI::sInstance; 21 | 22 | struct OptionDisplay { 23 | const char* title; 24 | BasicUI::OptionType type; 25 | }; 26 | 27 | OptionDisplay options[] = { 28 | { 29 | "Exit", 30 | BasicUI::OptionType::Exit, 31 | }, 32 | }; 33 | 34 | BasicUI::BasicUI() 35 | { 36 | m_rmode = nullptr; 37 | 38 | #ifndef NDEBUG 39 | m_xfbConsole = nullptr; 40 | #endif 41 | 42 | m_xfbUI = nullptr; 43 | } 44 | 45 | void BasicUI::InitVideo() 46 | { 47 | // Initialize video. 48 | VIDEO_Init(); 49 | 50 | // Get preferred video mode. 51 | m_rmode = VIDEO_GetPreferredMode(NULL); 52 | 53 | // Allocate framebuffers. 54 | #ifndef NDEBUG 55 | m_xfbConsole = MEM_K0_TO_K1(SYS_AllocateFramebuffer(m_rmode)); 56 | 57 | // Initialize debug console. 58 | console_init(m_xfbConsole, 20, 20, m_rmode->fbWidth, m_rmode->xfbHeight, 59 | m_rmode->fbWidth * VI_DISPLAY_PIX_SZ); 60 | #endif 61 | m_xfbUI = MEM_K0_TO_K1(SYS_AllocateFramebuffer(m_rmode)); 62 | 63 | // Configure render mode. 64 | VIDEO_Configure(m_rmode); 65 | 66 | // Set framebuffer to UI. 67 | VIDEO_SetNextFramebuffer(m_xfbUI); 68 | 69 | // Clear UI framebuffer 70 | ClearScreen(); 71 | 72 | // Disable VI black. 73 | VIDEO_SetBlack(0); 74 | 75 | VIDEO_Flush(); 76 | VIDEO_WaitVSync(); 77 | 78 | if (m_rmode->viTVMode & VI_NON_INTERLACE) 79 | VIDEO_WaitVSync(); 80 | 81 | printf("\x1b[2;0H"); 82 | 83 | // Draw text to the screen. 84 | DebugPrint_Init(m_xfbUI, m_rmode->fbWidth, m_rmode->xfbHeight); 85 | } 86 | 87 | void BasicUI::Loop() 88 | { 89 | m_cursorEnabled = true; 90 | m_optionSelected = false; 91 | m_selectedOption = 0; 92 | 93 | while (true) { 94 | UpdateOptions(); 95 | DrawTitle(); 96 | // DrawOptions(); 97 | 98 | if (m_optionSelected) { 99 | OnSelect(options[m_selectedOption].type); 100 | } 101 | 102 | Input::sInstance->ScanButton(); 103 | 104 | #ifndef NDEBUG 105 | u32 buttonDown = Input::sInstance->GetButtonDown(); 106 | u32 buttonUp = Input::sInstance->GetButtonUp(); 107 | 108 | if (buttonDown & Input::BTN_DEBUG) { 109 | // Switch XFB to console. 110 | VIDEO_SetNextFramebuffer(m_xfbConsole); 111 | VIDEO_Flush(); 112 | } 113 | 114 | if (buttonUp & Input::BTN_DEBUG) { 115 | // Switch XFB to UI. 116 | VIDEO_SetNextFramebuffer(m_xfbUI); 117 | VIDEO_Flush(); 118 | } 119 | #endif 120 | 121 | VIDEO_Flush(); 122 | VIDEO_WaitVSync(); 123 | } 124 | } 125 | 126 | void BasicUI::ClearScreen() 127 | { 128 | for (int i = 0; i < m_rmode->fbWidth * m_rmode->xfbHeight * 2; i += 4) { 129 | write32(reinterpret_cast(m_xfbUI) + i, BACKGROUND_COLOUR); 130 | } 131 | } 132 | 133 | void BasicUI::DrawTitle() 134 | { 135 | if (LaunchState::Get()->Error.state == LaunchError::OK) 136 | return; 137 | 138 | // Sometimes the first USB device change will return 0 devices for some 139 | // reason, so let's wait to display something 140 | static int waitTick = 30; 141 | if (waitTick != 0) { 142 | waitTick--; 143 | return; 144 | } 145 | 146 | Input::sInstance->Init(); 147 | 148 | DebugPrint_Printf(2, 1, "CTGP-R MSC v1.0"); 149 | 150 | switch (LaunchState::Get()->Error.state) { 151 | case LaunchError::NoSDCard: 152 | DebugPrint_Printf(7, 4, "Please insert an SD card or USB."); 153 | DebugPrint_Printf(8, 4, " "); 154 | break; 155 | 156 | case LaunchError::NoCTGPR: 157 | DebugPrint_Printf(7, 4, "The inserted SD card or USB does"); 158 | DebugPrint_Printf(8, 4, " not contain CTGP-R. "); 159 | break; 160 | 161 | case LaunchError::CTGPCorrupt: 162 | DebugPrint_Printf(7, 4, " Could not load CTGP-R. "); 163 | DebugPrint_Printf(8, 4, " Your pack may be corrupted. "); 164 | break; 165 | 166 | default: 167 | DebugPrint_Printf(7, 4, "Error not implemented!"); 168 | break; 169 | } 170 | } 171 | 172 | BasicUI::OptionStatus BasicUI::GetOptionStatus(OptionType opt) 173 | { 174 | if (m_optionSelected && options[m_selectedOption].type == opt) 175 | return OptionStatus::Selected; 176 | 177 | switch (opt) { 178 | case OptionType::Exit: 179 | return OptionStatus::Enabled; 180 | 181 | default: 182 | return OptionStatus::Hidden; 183 | } 184 | } 185 | 186 | void BasicUI::DrawOptions() 187 | { 188 | } 189 | 190 | void BasicUI::UpdateOptions() 191 | { 192 | if (!m_cursorEnabled) 193 | return; 194 | 195 | u32 btn = Input::sInstance->GetButtonDown(); 196 | 197 | if (btn & Input::BTN_HOME) { 198 | if (GetOptionStatus(options[m_selectedOption].type) == 199 | OptionStatus::Enabled) { 200 | m_cursorEnabled = false; 201 | m_optionSelected = true; 202 | } 203 | } 204 | } 205 | 206 | extern void ReturnToLoader(); 207 | 208 | void BasicUI::OnSelect([[maybe_unused]] OptionType opt) 209 | { 210 | Input::sInstance->Shutdown(); 211 | ReturnToLoader(); 212 | } 213 | -------------------------------------------------------------------------------- /channel/UI/BasicUI.hpp: -------------------------------------------------------------------------------- 1 | // BasicUI.hpp - Simple text-based UI 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | LIBOGC_SUCKS_BEGIN 10 | #include 11 | LIBOGC_SUCKS_END 12 | 13 | class BasicUI 14 | { 15 | public: 16 | static BasicUI* sInstance; 17 | 18 | BasicUI(); 19 | void InitVideo(); 20 | void Loop(); 21 | 22 | enum class OptionType { 23 | StartGame, 24 | TestFS, 25 | Exit, 26 | }; 27 | 28 | private: 29 | enum class OptionStatus { 30 | Disabled, 31 | Hidden, 32 | Enabled, 33 | Waiting, 34 | Selected, 35 | }; 36 | 37 | void ClearScreen(); 38 | void DrawTitle(); 39 | OptionStatus GetOptionStatus(OptionType opt); 40 | void DrawOptions(); 41 | void UpdateOptions(); 42 | void OnSelect(OptionType opt); 43 | 44 | private: 45 | GXRModeObj* m_rmode; 46 | #ifndef NDEBUG 47 | void* m_xfbConsole; 48 | #endif 49 | void* m_xfbUI; 50 | 51 | int m_selectedOption; 52 | bool m_cursorEnabled; 53 | bool m_optionSelected; 54 | }; 55 | -------------------------------------------------------------------------------- /channel/UI/Input.cpp: -------------------------------------------------------------------------------- 1 | // Input.cpp - User input manager 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Input.hpp" 7 | #include 8 | LIBOGC_SUCKS_BEGIN 9 | #include 10 | #include 11 | LIBOGC_SUCKS_END 12 | 13 | Input* Input::sInstance; 14 | 15 | Input::Input() 16 | { 17 | m_inputInit = false; 18 | 19 | m_lastState = 0; 20 | m_state = 0; 21 | m_scanned = false; 22 | } 23 | 24 | void Input::ScanButton() 25 | { 26 | m_lastState = m_state; 27 | m_state = GetButtonRaw(); 28 | 29 | if (!m_scanned) { 30 | // On the first scan, automatically assume that all buttons pressed are 31 | // being held, 32 | m_lastState = m_state; 33 | // ... except for the debug button. 34 | m_lastState &= ~BTN_DEBUG; 35 | } 36 | 37 | m_scanned = true; 38 | } 39 | 40 | u32 Input::GetButtonDown() 41 | { 42 | // Clear buttons that were pressed in the last state. 43 | return m_state & ~m_lastState; 44 | } 45 | 46 | u32 Input::GetButtonUp() 47 | { 48 | // Clear buttons that weren't pressed in the last state. 49 | return ~m_state & m_lastState; 50 | } 51 | 52 | u32 Input::GetButtonHeld() 53 | { 54 | return GetButtonRaw(); 55 | } 56 | 57 | void Input::Init() 58 | { 59 | if (!m_inputInit) { 60 | PAD_Init(); 61 | WPAD_Init(); 62 | 63 | m_inputInit = true; 64 | } 65 | } 66 | 67 | void Input::Shutdown() 68 | { 69 | if (m_inputInit) { 70 | WPAD_Shutdown(); 71 | 72 | m_inputInit = false; 73 | } 74 | } 75 | 76 | u32 Input::GetButtonRaw() 77 | { 78 | if (!m_inputInit) 79 | return 0; 80 | 81 | PAD_ScanPads(); 82 | WPAD_ScanPads(); 83 | u32 down = PAD_ButtonsHeld(0); 84 | u32 wiimotedown = WPAD_ButtonsHeld(0); 85 | 86 | u32 result = 0; 87 | 88 | if (down & PAD_BUTTON_UP || wiimotedown & WPAD_BUTTON_UP) 89 | result |= BTN_UP; 90 | 91 | if (down & PAD_BUTTON_DOWN || wiimotedown & WPAD_BUTTON_DOWN) 92 | result |= BTN_DOWN; 93 | 94 | if (down & PAD_BUTTON_LEFT || wiimotedown & WPAD_BUTTON_LEFT) 95 | result |= BTN_LEFT; 96 | 97 | if (down & PAD_BUTTON_RIGHT || wiimotedown & WPAD_BUTTON_RIGHT) 98 | result |= BTN_RIGHT; 99 | 100 | if (down & PAD_BUTTON_A || wiimotedown & WPAD_BUTTON_A) 101 | result |= BTN_SELECT; 102 | 103 | if (down & PAD_BUTTON_B || wiimotedown & WPAD_BUTTON_B) 104 | result |= BTN_BACK; 105 | 106 | if (down & PAD_BUTTON_MENU || wiimotedown & WPAD_BUTTON_HOME) 107 | result |= BTN_HOME; 108 | 109 | if (down & PAD_TRIGGER_Z || wiimotedown & WPAD_BUTTON_1) 110 | result |= BTN_DEBUG; 111 | 112 | return result; 113 | } 114 | -------------------------------------------------------------------------------- /channel/UI/Input.hpp: -------------------------------------------------------------------------------- 1 | // Input.hpp - User input manager 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | class Input 11 | { 12 | public: 13 | static Input* sInstance; 14 | 15 | Input(); 16 | 17 | enum Button { 18 | BTN_UP = 1 << 0, 19 | BTN_DOWN = 1 << 1, 20 | BTN_LEFT = 1 << 2, 21 | BTN_RIGHT = 1 << 3, 22 | BTN_SELECT = 1 << 4, 23 | BTN_BACK = 1 << 5, 24 | BTN_HOME = 1 << 6, 25 | 26 | // Z on GameCube controller. 27 | BTN_DEBUG = 1 << 7, 28 | }; 29 | 30 | // Updates the state. 31 | void ScanButton(); 32 | 33 | // Only set on initial press. 34 | u32 GetButtonDown(); 35 | 36 | // Only set when lifted. 37 | u32 GetButtonUp(); 38 | 39 | // Always set if pressed. 40 | u32 GetButtonHeld(); 41 | 42 | void Init(); 43 | void Shutdown(); 44 | 45 | private: 46 | // Gets the raw button data. 47 | u32 GetButtonRaw(); 48 | 49 | bool m_inputInit; 50 | 51 | // Previous state. 52 | u32 m_lastState; 53 | // Current state. 54 | u32 m_state; 55 | 56 | // If the buttons have been scanned at least once. 57 | bool m_scanned; 58 | }; 59 | -------------------------------------------------------------------------------- /channel/UI/debugPrint.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------* 2 | * Author : Star 3 | * Date : 18 Jul 2021 4 | * File : debugPrint.h 5 | * Version : 1.0.0.0 6 | *---------------------------------------------------------------------------*/ 7 | 8 | #ifndef __DEBUGPRINT_H__ 9 | #define __DEBUGPRINT_H__ 10 | 11 | #ifdef __cplusplus 12 | extern "C" 13 | { 14 | #endif 15 | 16 | #include 17 | 18 | //! Definitions 19 | #define FOREGROUND_COLOUR 0xEB7FEB7F // White 20 | #define BACKGROUND_COLOUR 0x10801080 // Black 21 | 22 | //! Function Prototype Declarations 23 | 24 | //! Public Functions 25 | void DebugPrint_Init(void* xfb, unsigned short xfbWidth, unsigned short xfbHeight); 26 | int DebugPrint_Printf(int iCurrentRow, int iCurrentColumn, const char* pFormatString, ...); 27 | 28 | void DebugPrint_SetForegroundColour(GXColor gxColor); 29 | void DebugPrint_SetBackgroundColour(GXColor gxColor); 30 | 31 | #ifdef __cplusplus 32 | } 33 | #endif 34 | 35 | #endif // __DEBUGPRINT_H__ -------------------------------------------------------------------------------- /common/DVD/DI.cpp: -------------------------------------------------------------------------------- 1 | // DI.cpp - DI types and I/O 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "DI.hpp" 7 | 8 | DI* DI::sInstance; 9 | 10 | const char* DI::PrintError(DIError error) 11 | { 12 | #define DI_ERROR_STR(val) \ 13 | case DIError::val: \ 14 | return #val 15 | switch (error) { 16 | DI_ERROR_STR(Unknown); 17 | DI_ERROR_STR(OK); 18 | DI_ERROR_STR(Drive); 19 | DI_ERROR_STR(CoverClosed); 20 | DI_ERROR_STR(Timeout); 21 | DI_ERROR_STR(Security); 22 | DI_ERROR_STR(Verify); 23 | DI_ERROR_STR(Invalid); 24 | } 25 | #undef DI_ERR_STR 26 | return "Unknown"; 27 | } 28 | 29 | // DVDLowInquiry; Retrieves information about the drive version. 30 | DI::DIError DI::Inquiry(DriveInfo* info) 31 | { 32 | DICommand block = { 33 | .cmd = DIIoctl::Inquiry, 34 | .args = {0}, 35 | }; 36 | DriveInfo drvInfo ATTRIBUTE_ALIGN(32); 37 | 38 | DIError res = 39 | CallIoctl(block, DIIoctl::Inquiry, reinterpret_cast(&drvInfo), 40 | sizeof(DriveInfo)); 41 | if (res == DIError::OK) 42 | *info = drvInfo; 43 | return res; 44 | } 45 | 46 | // DVDLowReadDiskID; Reads the current disc ID and initializes the drive. 47 | DI::DIError DI::ReadDiskID(DiskID* diskid) 48 | { 49 | DICommand block = { 50 | .cmd = DIIoctl::ReadDiskID, 51 | .args = {0}, 52 | }; 53 | DiskID drvDiskid ATTRIBUTE_ALIGN(32); 54 | 55 | DIError res = 56 | CallIoctl(block, DIIoctl::ReadDiskID, 57 | reinterpret_cast(&drvDiskid), sizeof(DiskID)); 58 | if (res == DIError::OK) 59 | *diskid = drvDiskid; 60 | return res; 61 | } 62 | 63 | // DVDLowRead; Reads and decrypts disc data. This command can only be used 64 | // if hashing and encryption are enabled for the disc. DVDLowOpenPartition 65 | // needs to have been called before for the keys to be read. 66 | DI::DIError DI::Read(void* data, u32 lenBytes, u32 wordOffset) 67 | { 68 | DICommand block = { 69 | .cmd = DIIoctl::Read, 70 | .args = {lenBytes, wordOffset}, 71 | }; 72 | return CallIoctl(block, DIIoctl::Read, data, lenBytes); 73 | } 74 | 75 | // DVDLowWaitForCoverClose; Waits for a disc to be inserted; if there is 76 | // already a disc inserted, it must be removed first. This command does not 77 | // time out; if no disc is inserted, it will wait forever. 78 | // Returns DIError::CoverClosed on success. 79 | DI::DIError DI::WaitForCoverClose() 80 | { 81 | DICommand block = { 82 | .cmd = DIIoctl::WaitForCoverClose, 83 | .args = {0}, 84 | }; 85 | return CallIoctl(block, DIIoctl::WaitForCoverClose); 86 | } 87 | 88 | // DVDLowGetLength; Returns the length of the last transfer. 89 | DI::DIError DI::GetLength(u32* length) 90 | { 91 | DICommand block = { 92 | .cmd = DIIoctl::GetLength, 93 | .args = {0}, 94 | }; 95 | u32 drvLength; 96 | DIError res = CallIoctl(block, DIIoctl::GetLength, 97 | reinterpret_cast(&drvLength), sizeof(u32)); 98 | *length = drvLength; 99 | return res; 100 | } 101 | 102 | // DVDLowReset; Resets the drive. spinup(true) is used to spinup the drive 103 | // on start or once a disc is inserted. spinup(false) is used to turn it off 104 | // when launching a new title. 105 | DI::DIError DI::Reset(bool spinup) 106 | { 107 | DICommand block = { 108 | .cmd = DIIoctl::Reset, 109 | .args = {static_cast(spinup)}, 110 | }; 111 | return CallIoctl(block, DIIoctl::Reset); 112 | } 113 | 114 | // DVDLowOpenPartition; Opens a partition, including verifying it through 115 | // ES. ReadDiskID needs to have been called beforehand. 116 | // PARAMETERS: 117 | // tmd - output (required, must be 32 byte aligned) 118 | // ticket - input (optional, must be 32 byte aligned) 119 | // certs - input (optional, must be 32 byte aligned) 120 | DI::DIError DI::OpenPartition(u32 wordOffset, ES::TMDFixed<512>* tmd, 121 | ES::ESError* esError, const ES::Ticket* ticket, 122 | const void* certs, u32 certsLen) 123 | { 124 | if (!aligned(tmd, 32) || !aligned(ticket, 32) || !aligned(certs, 32)) 125 | return DIError::Invalid; 126 | DICommand block = { 127 | .cmd = DIIoctl::OpenPartition, 128 | .args = {wordOffset}, 129 | }; 130 | u32 output[8] ATTRIBUTE_ALIGN(32); 131 | 132 | IOS::IOVector<3, 2> vec; 133 | // input - Command block 134 | vec.in[0].data = █ 135 | vec.in[0].len = sizeof(DICommand); 136 | // input - Ticket (optional) 137 | vec.in[1].data = ticket; 138 | vec.in[1].len = ticket ? sizeof(ES::Ticket) : 0; 139 | // input - Shared certs (optional) 140 | vec.in[2].data = certs; 141 | vec.in[2].len = certs ? certsLen : 0; 142 | // output - TMD 143 | vec.out[0].data = tmd; 144 | vec.out[0].len = sizeof(ES::TMDFixed<512>); 145 | // output - ES Error 146 | vec.out[1].data = output; 147 | vec.out[1].len = sizeof(output); 148 | 149 | DIError res = static_cast(di.ioctlv(DIIoctl::OpenPartition, vec)); 150 | if (esError != nullptr) 151 | *esError = static_cast(output[0]); 152 | 153 | return res; 154 | } 155 | 156 | // DVDLowClosePartition; Closes the currently-open partition, removing 157 | // information about its keys and such. 158 | DI::DIError DI::ClosePartition() 159 | { 160 | DICommand block = { 161 | .cmd = DIIoctl::ClosePartition, 162 | .args = {0}, 163 | }; 164 | return CallIoctl(block, DIIoctl::ClosePartition); 165 | } 166 | 167 | // DVDLowUnencryptedRead; Reads raw data from the disc. Only usable in the 168 | // "System Area" of the disc. 169 | DI::DIError DI::UnencryptedRead(void* data, u32 lenBytes, u32 wordOffset) 170 | { 171 | DICommand block = {.cmd = DIIoctl::UnencryptedRead, 172 | .args = {lenBytes, wordOffset}}; 173 | return CallIoctl(block, DIIoctl::UnencryptedRead, data, lenBytes); 174 | } 175 | 176 | // DVDLowOpenPartitionWithTmdAndTicket; Opens a partition, including 177 | // verifying it through ES. ReadDiskID needs to have been called beforehand. 178 | // This function takes an already-read TMD and can take an already-read 179 | // ticket, which means it can be faster since the ticket does not need to be 180 | // read from the disc. 181 | // PARAMETERS: 182 | // tmd - input (required, must be 32 byte aligned) 183 | // ticket - input (optional, must be 32 byte aligned) 184 | // certs - input (optional, must be 32 byte aligned) 185 | DI::DIError DI::OpenPartitionWithTmdAndTicket(u32 wordOffset, ES::TMD* tmd, 186 | ES::ESError* esError, 187 | const ES::Ticket* ticket, 188 | const void* certs, u32 certsLen) 189 | { 190 | if (!aligned(tmd, 32) || !aligned(ticket, 32) || !aligned(certs, 32)) 191 | return DIError::Invalid; 192 | DICommand block = { 193 | .cmd = DIIoctl::OpenPartitionWithTmdAndTicket, 194 | .args = {wordOffset}, 195 | }; 196 | u32 output[8] ATTRIBUTE_ALIGN(32); 197 | 198 | IOS::IOVector<4, 1> vec; 199 | // input - Command block 200 | vec.in[0].data = █ 201 | vec.in[0].len = sizeof(DICommand); 202 | // input - Ticket (optional) 203 | vec.in[1].data = ticket; 204 | vec.in[1].len = ticket ? sizeof(ES::Ticket) : 0; 205 | // input - TMD 206 | vec.in[2].data = tmd; 207 | vec.in[2].len = tmd->size(); 208 | // input - Shared certs (optional) 209 | vec.in[3].data = certs; 210 | vec.in[3].len = certs ? certsLen : 0; 211 | // output - ES Error 212 | vec.out[0].data = output; 213 | vec.out[0].len = sizeof(output); 214 | 215 | DIError res = static_cast( 216 | di.ioctlv(DIIoctl::OpenPartitionWithTmdAndTicket, vec)); 217 | if (esError != nullptr) 218 | *esError = static_cast(output[0]); 219 | 220 | return res; 221 | } 222 | 223 | // DVDLowOpenPartitionWithTmdAndTicketView; Opens a partition, including 224 | // verifying it through ES. ReadDiskID needs to have been called beforehand. 225 | // This function takes an already-read TMD and can take an already-read 226 | // ticket view, which means it can be faster since the ticket does not need 227 | // to be read from the disc. 228 | // PARAMETERS: 229 | // tmd - input (required, must be 32 byte aligned) 230 | // ticketView - input (optional, must be 32 byte aligned) 231 | // certs - input (optional, must be 32 byte aligned) 232 | DI::DIError DI::OpenPartitionWithTmdAndTicketView( 233 | u32 wordOffset, ES::TMD* tmd, ES::ESError* esError, 234 | const ES::TicketView* ticketView, const void* certs, u32 certsLen) 235 | { 236 | if (!aligned(tmd, 32) || !aligned(ticketView, 32) || !aligned(certs, 32)) 237 | return DIError::Invalid; 238 | DICommand block = { 239 | .cmd = DIIoctl::OpenPartitionWithTmdAndTicketView, 240 | .args = {wordOffset}, 241 | }; 242 | u32 output[8] ATTRIBUTE_ALIGN(32); 243 | 244 | IOS::IOVector<4, 1> vec; 245 | // input - Command block 246 | vec.in[0].data = █ 247 | vec.in[0].len = sizeof(DICommand); 248 | // input - Ticket View (optional) 249 | vec.in[1].data = ticketView; 250 | vec.in[1].len = ticketView ? sizeof(ES::TicketView) : 0; 251 | // input - TMD 252 | vec.in[2].data = tmd; 253 | vec.in[2].len = tmd->size(); 254 | // input - Shared certs (optional) 255 | vec.in[3].data = certs; 256 | vec.in[3].len = certs ? certsLen : 0; 257 | // output - ES Error 258 | vec.out[0].data = output; 259 | vec.out[0].len = sizeof(output); 260 | 261 | DIError res = static_cast( 262 | di.ioctlv(DIIoctl::OpenPartitionWithTmdAndTicketView, vec)); 263 | if (esError != nullptr) 264 | *esError = static_cast(output[0]); 265 | 266 | return res; 267 | } 268 | 269 | // DVDLowSeek; Seeks to the sector containing a specific position on the 270 | // disc. 271 | DI::DIError DI::Seek(u32 wordOffset) 272 | { 273 | DICommand block = { 274 | .cmd = DIIoctl::Seek, 275 | .args = {wordOffset}, 276 | }; 277 | return CallIoctl(block, DIIoctl::Seek); 278 | } 279 | 280 | // DVDLowReadDiskBca; Reads the last 64 bytes of the burst cutting area. 281 | DI::DIError DI::ReadDiskBca(u8* out) 282 | { 283 | if (!aligned(out, 32)) 284 | return DIError::Invalid; 285 | DICommand block = { 286 | .cmd = DIIoctl::ReadDiskBca, 287 | .args = {0}, 288 | }; 289 | return CallIoctl(block, DIIoctl::ReadDiskBca, out, 64); 290 | } 291 | 292 | DI::DIError DI::CallIoctl(DICommand& block, DIIoctl cmd, void* out, u32 outLen) 293 | { 294 | return static_cast(di.ioctl(cmd, reinterpret_cast(&block), 295 | sizeof(DICommand), out, outLen)); 296 | } 297 | -------------------------------------------------------------------------------- /common/DVD/DI.hpp: -------------------------------------------------------------------------------- 1 | // DI.hpp - DI types and I/O 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Documentation of the drive used here is referenced from 14 | // - https://wiibrew.org/wiki//dev/di 15 | // - Yet Another GameCube Documentation 16 | 17 | class DI 18 | { 19 | public: 20 | static DI* sInstance; 21 | 22 | enum class DIError : s32 { 23 | Unknown = 0x0, 24 | OK = 0x1, 25 | Drive = 0x2, 26 | CoverClosed = 0x4, 27 | Timeout = 0x10, 28 | Security = 0x20, 29 | Verify = 0x40, 30 | Invalid = 0x80, 31 | }; 32 | 33 | static const char* PrintError(DIError error); 34 | 35 | enum class DIIoctl : u8 { 36 | Inquiry = 0x12, 37 | ReadDiskID = 0x70, 38 | Read = 0x71, 39 | WaitForCoverClose = 0x79, 40 | GetCoverRegister = 0x7A, 41 | NotifyReset = 0x7E, 42 | SetSpinupFlag = 0x7F, 43 | ReadDvdPhysical = 0x80, 44 | ReadDvdCopyright = 0x81, 45 | ReadDvdDiscKey = 0x82, 46 | GetLength = 0x83, 47 | GetDIMMBUF = 0x84, 48 | MaskCoverInterrupt = 0x85, 49 | ClearCoverInterrupt = 0x86, 50 | UnmaskStatusInterrupts = 0x87, 51 | GetCoverStatus = 0x88, 52 | UnmaskCoverInterrupt = 0x89, 53 | Reset = 0x8A, 54 | OpenPartition = 0x8B, 55 | ClosePartition = 0x8C, 56 | UnencryptedRead = 0x8D, 57 | EnableDvdVideo = 0x8E, 58 | GetNoDiscOpenPartitionParams = 0x90, 59 | NoDiscOpenPartition = 0x91, 60 | GetNoDiscBufferSizes = 0x92, 61 | OpenPartitionWithTmdAndTicket = 0x93, 62 | OpenPartitionWithTmdAndTicketView = 0x94, 63 | GetStatusRegister = 0x95, 64 | GetControlRegister = 0x96, 65 | ReportKey = 0xA4, 66 | Seek = 0xAB, 67 | ReadDvd = 0xD0, 68 | ReadDvdConfig = 0xD1, 69 | StopLaser = 0xD2, 70 | Offset = 0xD9, 71 | ReadDiskBca = 0xDA, 72 | RequestDiscStatus = 0xDB, 73 | RequestRetryNumber = 0xDC, 74 | SetMaximumRotation = 0xDD, 75 | SerMeasControl = 0xDF, 76 | RequestError = 0xE0, 77 | AudioStream = 0xE1, 78 | RequestAudioStatus = 0xE2, 79 | StopMotor = 0xE3, 80 | AudioBufferConfig = 0xE4, 81 | }; 82 | 83 | struct DriveInfo { 84 | u16 revisionLevel; 85 | u16 deviceCode; 86 | u32 releaseDate; 87 | u8 version; // ? 88 | u8 pad[0x17]; 89 | }; 90 | 91 | struct DiskID { 92 | char gameID[4]; 93 | u16 groupID; 94 | u8 discNum; 95 | u8 discVer; 96 | u8 discStreamFlag; 97 | u8 discStreamSize; 98 | u8 pad[0xE]; 99 | u32 discMagic; 100 | u32 discMagicGC; 101 | }; 102 | 103 | struct DICommand { 104 | DIIoctl cmd; 105 | // implicit pad 106 | u32 args[7]; 107 | }; 108 | static_assert(sizeof(DICommand) == 0x20); 109 | 110 | struct Partition { 111 | ES::Ticket ticket; 112 | u32 tmdByteLength; 113 | u32 tmdWordOffset; 114 | u32 certChainByteLength; 115 | u32 certChainWordOffset; 116 | u32 h3TableWordOffset; 117 | u32 dataWordOffset; 118 | u32 dataWordLength; 119 | }; 120 | static_assert(sizeof(DICommand) == 0x20); 121 | 122 | // DVDLowInquiry; Retrieves information about the drive version. 123 | DIError Inquiry(DriveInfo* info); 124 | 125 | // DVDLowReadDiskID; Reads the current disc ID and initializes the drive. 126 | DIError ReadDiskID(DiskID* diskid); 127 | 128 | // DVDLowRead; Reads and decrypts disc data. This command can only be used 129 | // if hashing and encryption are enabled for the disc. DVDLowOpenPartition 130 | // needs to have been called before for the keys to be read. 131 | DIError Read(void* data, u32 lenBytes, u32 wordOffset); 132 | 133 | // DVDLowWaitForCoverClose; Waits for a disc to be inserted; if there is 134 | // already a disc inserted, it must be removed first. This command does not 135 | // time out; if no disc is inserted, it will wait forever. 136 | // Returns DIError::CoverClosed on success. 137 | DIError WaitForCoverClose(); 138 | 139 | // DVDLowGetLength; Returns the length of the last transfer. 140 | DIError GetLength(u32* length); 141 | 142 | // DVDLowReset; Resets the drive. spinup(true) is used to spinup the drive 143 | // on start or once a disc is inserted. spinup(false) is used to turn it off 144 | // when launching a new title. 145 | DIError Reset(bool spinup); 146 | 147 | // DVDLowOpenPartition; Opens a partition, including verifying it through 148 | // ES. ReadDiskID needs to have been called beforehand. 149 | // PARAMETERS: 150 | // tmd - output (required, must be 32 byte aligned) 151 | // ticket - input (optional, must be 32 byte aligned) 152 | // certs - input (optional, must be 32 byte aligned) 153 | DIError OpenPartition(u32 wordOffset, ES::TMDFixed<512>* tmd, 154 | ES::ESError* esError = nullptr, 155 | const ES::Ticket* ticket = nullptr, 156 | const void* certs = nullptr, u32 certsLen = 0); 157 | 158 | // DVDLowClosePartition; Closes the currently-open partition, removing 159 | // information about its keys and such. 160 | DIError ClosePartition(); 161 | 162 | // DVDLowUnencryptedRead; Reads raw data from the disc. Only usable in the 163 | // "System Area" of the disc. 164 | DIError UnencryptedRead(void* data, u32 lenBytes, u32 wordOffset); 165 | 166 | // DVDLowOpenPartitionWithTmdAndTicket; Opens a partition, including 167 | // verifying it through ES. ReadDiskID needs to have been called beforehand. 168 | // This function takes an already-read TMD and can take an already-read 169 | // ticket, which means it can be faster since the ticket does not need to be 170 | // read from the disc. 171 | // PARAMETERS: 172 | // tmd - input (required, must be 32 byte aligned) 173 | // ticket - input (optional, must be 32 byte aligned) 174 | // certs - input (optional, must be 32 byte aligned) 175 | DIError OpenPartitionWithTmdAndTicket(u32 wordOffset, ES::TMD* tmd, 176 | ES::ESError* esError = nullptr, 177 | const ES::Ticket* ticket = nullptr, 178 | const void* certs = nullptr, 179 | u32 certsLen = 0); 180 | 181 | // DVDLowOpenPartitionWithTmdAndTicketView; Opens a partition, including 182 | // verifying it through ES. ReadDiskID needs to have been called beforehand. 183 | // This function takes an already-read TMD and can take an already-read 184 | // ticket view, which means it can be faster since the ticket does not need 185 | // to be read from the disc. 186 | // PARAMETERS: 187 | // tmd - input (required, must be 32 byte aligned) 188 | // ticketView - input (optional, must be 32 byte aligned) 189 | // certs - input (optional, must be 32 byte aligned) 190 | DIError OpenPartitionWithTmdAndTicketView( 191 | u32 wordOffset, ES::TMD* tmd, ES::ESError* esError = nullptr, 192 | const ES::TicketView* ticketView = nullptr, const void* certs = nullptr, 193 | u32 certsLen = 0); 194 | 195 | // DVDLowSeek; Seeks to the sector containing a specific position on the 196 | // disc. 197 | DIError Seek(u32 wordOffset); 198 | 199 | // DVDLowReadDiskBca; Reads the last 64 bytes of the burst cutting area. 200 | DIError ReadDiskBca(u8* out); 201 | 202 | s32 GetFd() const 203 | { 204 | return di.fd(); 205 | } 206 | 207 | private: 208 | DIError CallIoctl(DICommand& block, DIIoctl cmd, void* out = nullptr, 209 | u32 outLen = 0); 210 | 211 | IOS::ResourceCtrl di{"/dev/di"}; 212 | }; 213 | -------------------------------------------------------------------------------- /common/DVD/EmuDI.hpp: -------------------------------------------------------------------------------- 1 | // EmuDI.hpp - Shared types for the emulated DI RM 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace EmuDI 11 | { 12 | 13 | struct DVDPatch { 14 | u32 disc_offset; 15 | u32 disc_length; 16 | 17 | /* file info */ 18 | u64 start_cluster; 19 | u64 cur_cluster; 20 | u32 file_offset; 21 | u32 drv; 22 | }; 23 | 24 | } // namespace EmuDI 25 | -------------------------------------------------------------------------------- /common/Debug/Log.cpp: -------------------------------------------------------------------------------- 1 | // Log.cpp - Debug log 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Log.hpp" 7 | #include 8 | #include 9 | #ifdef TARGET_IOS 10 | #include 11 | #include 12 | #include 13 | #endif 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef TARGET_IOS 20 | bool Log::ipcLogEnabled = false; 21 | #endif 22 | 23 | static constexpr std::array logColors = { 24 | "\x1b[37;1m", 25 | "\x1b[33;1m", 26 | "\x1b[31;1m", 27 | }; 28 | static constexpr std::array logChars = { 29 | 'I', 30 | 'W', 31 | 'E', 32 | }; 33 | static Mutex* logMutex; 34 | constexpr u32 logMask = 0xFFFFFFFF; 35 | constexpr u32 logLevel = 0; 36 | 37 | bool Log::IsEnabled() 38 | { 39 | #ifdef TARGET_IOS 40 | return ipcLogEnabled || DeviceMgr::sInstance->IsLogEnabled(); 41 | #else 42 | return true; 43 | #endif 44 | } 45 | 46 | void Log::VPrint(LogSource src, const char* srcStr, const char* funcStr, 47 | LogLevel level, const char* format, va_list args) 48 | { 49 | if (!IsEnabled()) 50 | return; 51 | 52 | if (logMutex == nullptr) { 53 | logMutex = new Mutex; 54 | } 55 | 56 | if (src == LogSource::IOS_USB) 57 | return; 58 | 59 | u32 slvl = static_cast(level); 60 | u32 schan = static_cast(src); 61 | ASSERT(slvl < logColors.size()); 62 | 63 | if (level != LogLevel::ERROR) { 64 | if (!(logMask & (1 << schan))) 65 | return; 66 | if (slvl < logLevel) 67 | return; 68 | } 69 | { 70 | logMutex->lock(); 71 | 72 | static std::array logBuffer; 73 | u32 len = vsnprintf(&logBuffer[0], logBuffer.size(), format, args); 74 | if (len >= logBuffer.size()) { 75 | len = logBuffer.size() - 1; 76 | logBuffer[len] = 0; 77 | } 78 | 79 | // Remove newline at the end of log 80 | if (logBuffer[len - 1] == '\n') 81 | logBuffer[len - 1] = 0; 82 | 83 | #ifdef TARGET_IOS 84 | static std::array printBuffer; 85 | 86 | if (ipcLogEnabled) { 87 | len = snprintf(&printBuffer[0], printBuffer.size(), 88 | "%s[%s %s] %s\x1b[37;1m", logColors[slvl], srcStr, 89 | funcStr, logBuffer.data()); 90 | IPCLog::sInstance->Print(&printBuffer[0]); 91 | } 92 | 93 | if (DeviceMgr::sInstance->IsLogEnabled()) { 94 | len = snprintf(&printBuffer[0], printBuffer.size(), 95 | "<%llu> %c[%s %s] %s", System::GetTime(), 96 | logChars[slvl], srcStr, funcStr, logBuffer.data()); 97 | DeviceMgr::sInstance->WriteToLog(&printBuffer[0], len); 98 | } 99 | 100 | #else 101 | printf("%s[%s %s] %s\n\x1b[37;1m", logColors[slvl], srcStr, funcStr, 102 | logBuffer.data()); 103 | #endif 104 | logMutex->unlock(); 105 | } 106 | } 107 | 108 | void Log::Print(LogSource src, const char* srcStr, const char* funcStr, 109 | LogLevel level, const char* format, ...) 110 | { 111 | va_list args; 112 | va_start(args, format); 113 | VPrint(src, srcStr, funcStr, level, format, args); 114 | va_end(args); 115 | } 116 | -------------------------------------------------------------------------------- /common/Debug/Log.hpp: -------------------------------------------------------------------------------- 1 | // Log.hpp - Debug log 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #ifdef TARGET_IOS 9 | #include 10 | #endif 11 | 12 | namespace Log 13 | { 14 | 15 | enum class LogSource { 16 | Core, 17 | DVD, 18 | Loader, 19 | Payload, 20 | FST, 21 | PatchList, 22 | IOS, 23 | IOS_Loader, 24 | IOS_DevMgr, 25 | IOS_USB, 26 | IOS_EmuFS, 27 | IOS_EmuDI, 28 | IOS_EmuES, 29 | IOS_EmuSDIO, 30 | IOS_EmuHID, 31 | }; 32 | 33 | enum class LogLevel { 34 | INFO, 35 | WARN, 36 | ERROR 37 | }; 38 | 39 | enum class IPCLogIoctl { 40 | RegisterPrintHook, 41 | StartGameEvent, 42 | SetTime, 43 | }; 44 | 45 | enum class IPCLogReply { 46 | Print, 47 | Notice, 48 | Close, 49 | SetLaunchState, 50 | }; 51 | 52 | #ifdef TARGET_IOS 53 | extern bool ipcLogEnabled; 54 | #endif 55 | 56 | bool IsEnabled(); 57 | 58 | void VPrint(LogSource src, const char* srcStr, const char* funcStr, 59 | LogLevel level, const char* format, va_list args); 60 | void Print(LogSource src, const char* srcStr, const char* funcStr, 61 | LogLevel level, const char* format, ...); 62 | 63 | #ifdef NDEBUG 64 | 65 | #define PRINT(...) 66 | 67 | #else 68 | 69 | #define PRINT(CHANNEL, LEVEL, ...) \ 70 | Log::Print(Log::LogSource::CHANNEL, #CHANNEL, __FUNCTION__, \ 71 | Log::LogLevel::LEVEL, __VA_ARGS__) 72 | 73 | #endif 74 | 75 | } // namespace Log 76 | -------------------------------------------------------------------------------- /common/System/AES.cpp: -------------------------------------------------------------------------------- 1 | // AES.cpp - AES engine interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "AES.hpp" 7 | 8 | AES* AES::sInstance = nullptr; 9 | -------------------------------------------------------------------------------- /common/System/AES.hpp: -------------------------------------------------------------------------------- 1 | // AES.hpp - AES engine interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | 11 | class AES 12 | { 13 | public: 14 | static AES* sInstance; 15 | 16 | private: 17 | enum class AESIoctl { 18 | Encrypt = 2, 19 | Decrypt = 3, 20 | }; 21 | 22 | static constexpr u32 MaxInputSize = 0x10000; 23 | 24 | public: 25 | /* 26 | * AES-128 CBC encrypt a block using the AES hardware engine. 27 | */ 28 | s32 Encrypt(const u8* key, u8* iv, const void* input, u32 size, 29 | void* output) 30 | { 31 | IOS::IOVector<2, 2> vec; 32 | vec.in[0].data = input; 33 | vec.in[0].len = size; 34 | vec.in[1].data = key; 35 | vec.in[1].len = 16; 36 | vec.out[0].data = output; 37 | vec.out[0].len = size; 38 | vec.out[1].data = iv; 39 | vec.out[1].len = 16; 40 | return m_rm.ioctlv(AESIoctl::Encrypt, vec); 41 | } 42 | 43 | /* 44 | * AES-128 CBC decrypt a block using the AES hardware engine. 45 | */ 46 | s32 Decrypt(const u8* key, u8* iv, const void* input, u32 size, 47 | void* output) 48 | { 49 | if (size < MaxInputSize) { 50 | IOS::IOVector<2, 2> vec; 51 | vec.in[0].data = input; 52 | vec.in[0].len = size; 53 | vec.in[1].data = key; 54 | vec.in[1].len = 16; 55 | vec.out[0].data = output; 56 | vec.out[0].len = size; 57 | vec.out[1].data = iv; 58 | vec.out[1].len = 16; 59 | return m_rm.ioctlv(AESIoctl::Decrypt, vec); 60 | } 61 | 62 | s32 ret = IOSError::OK; 63 | while (size > 0 || ret != IOSError::OK) { 64 | IOS::IOVector<2, 2> vec; 65 | 66 | vec.in[1].data = key; 67 | vec.in[1].len = 16; 68 | vec.out[1].data = iv; 69 | vec.out[1].len = 16; 70 | 71 | if (size > MaxInputSize) { 72 | vec.in[0].data = input; 73 | vec.in[0].len = MaxInputSize; 74 | vec.out[0].data = output; 75 | vec.out[0].len = MaxInputSize; 76 | 77 | size -= MaxInputSize; 78 | input += MaxInputSize; 79 | output += MaxInputSize; 80 | } else { 81 | vec.in[0].data = input; 82 | vec.in[0].len = size; 83 | vec.out[0].data = output; 84 | vec.out[0].len = size; 85 | 86 | size = 0; 87 | } 88 | 89 | ret = m_rm.ioctlv(AESIoctl::Decrypt, vec); 90 | } 91 | 92 | return ret; 93 | } 94 | 95 | private: 96 | IOS::ResourceCtrl m_rm{"/dev/aes"}; 97 | }; 98 | -------------------------------------------------------------------------------- /common/System/ES.cpp: -------------------------------------------------------------------------------- 1 | // ES.cpp - ES interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "ES.hpp" 7 | 8 | ES* ES::sInstance = nullptr; 9 | 10 | ES::ESError ES::AddContentData(s32 cfd, u8* data, u32 dataSize) 11 | { 12 | IOS::IVector<2> vec; 13 | vec.in[0].data = reinterpret_cast(&cfd); 14 | vec.in[0].len = sizeof(u32); 15 | vec.in[1].data = data; 16 | vec.in[1].len = dataSize; 17 | 18 | s32 ret = m_rm.ioctlv(ESIoctl::AddContentData, vec); 19 | return static_cast(ret); 20 | } 21 | 22 | ES::ESError ES::GetDeviceID(u32* out) 23 | { 24 | IOS::OVector<1> vec; 25 | vec.out[0].data = reinterpret_cast(out); 26 | vec.out[0].len = sizeof(u32); 27 | 28 | s32 ret = m_rm.ioctlv(ESIoctl::GetDeviceID, vec); 29 | return static_cast(ret); 30 | } 31 | 32 | ES::ESError ES::LaunchTitle(u64 titleID, const TicketView* view) 33 | { 34 | IOS::IVector<2> vec; 35 | vec.in[0].data = reinterpret_cast(&titleID); 36 | vec.in[0].len = sizeof(u64); 37 | vec.in[1].data = reinterpret_cast(view); 38 | vec.in[1].len = sizeof(TicketView); 39 | 40 | s32 ret = m_rm.ioctlv(ESIoctl::LaunchTitle, vec); 41 | return static_cast(ret); 42 | } 43 | 44 | ES::ESError ES::GetOwnedTitlesCount(u32* outCount) 45 | { 46 | IOS::OVector<1> vec; 47 | vec.out[0].data = reinterpret_cast(outCount); 48 | vec.out[0].len = sizeof(u32); 49 | 50 | s32 ret = m_rm.ioctlv(ESIoctl::GetOwnedTitlesCount, vec); 51 | return static_cast(ret); 52 | } 53 | 54 | ES::ESError ES::GetTitlesCount(u32* outCount) 55 | { 56 | IOS::OVector<1> vec; 57 | vec.out[0].data = reinterpret_cast(outCount); 58 | vec.out[0].len = sizeof(u32); 59 | 60 | s32 ret = m_rm.ioctlv(ESIoctl::GetTitlesCount, vec); 61 | return static_cast(ret); 62 | } 63 | 64 | ES::ESError ES::GetTitles(u32 count, u64* outTitles) 65 | { 66 | IOS::IOVector<1, 1> vec; 67 | vec.in[0].data = reinterpret_cast(&count); 68 | vec.in[0].len = sizeof(u32); 69 | vec.out[0].data = reinterpret_cast(outTitles); 70 | vec.out[0].len = sizeof(u64) * count; 71 | 72 | s32 ret = m_rm.ioctlv(ESIoctl::GetTitles, vec); 73 | return static_cast(ret); 74 | } 75 | 76 | ES::ESError ES::GetTitleContentsCount(u64 titleID, u32* outCount) 77 | { 78 | IOS::IOVector<1, 1> vec; 79 | vec.in[0].data = reinterpret_cast(&titleID); 80 | vec.in[0].len = sizeof(u64); 81 | vec.out[0].data = reinterpret_cast(outCount); 82 | vec.out[0].len = sizeof(u32); 83 | 84 | s32 ret = m_rm.ioctlv(ESIoctl::GetTitleContentsCount, vec); 85 | return static_cast(ret); 86 | } 87 | 88 | ES::ESError ES::GetTitleContents(u64 titleID, u32 count, u32* outContents) 89 | { 90 | IOS::IOVector<2, 1> vec; 91 | vec.in[0].data = reinterpret_cast(&titleID); 92 | vec.in[0].len = sizeof(u64); 93 | vec.in[1].data = reinterpret_cast(&count); 94 | vec.in[1].len = sizeof(u32); 95 | vec.out[0].data = reinterpret_cast(outContents); 96 | vec.out[0].len = sizeof(u32) * count; 97 | 98 | s32 ret = m_rm.ioctlv(ESIoctl::GetTitleContents, vec); 99 | return static_cast(ret); 100 | } 101 | 102 | ES::ESError ES::GetNumTicketViews(u64 titleID, u32* outCount) 103 | { 104 | IOS::IOVector<1, 1> vec; 105 | vec.in[0].data = reinterpret_cast(&titleID); 106 | vec.in[0].len = sizeof(u64); 107 | vec.out[0].data = reinterpret_cast(outCount); 108 | vec.out[0].len = sizeof(u32); 109 | 110 | s32 ret = m_rm.ioctlv(ESIoctl::GetNumTicketViews, vec); 111 | return static_cast(ret); 112 | } 113 | 114 | ES::ESError ES::GetTicketViews(u64 titleID, u32 count, TicketView* outViews) 115 | { 116 | IOS::IOVector<2, 1> vec; 117 | vec.in[0].data = reinterpret_cast(&titleID); 118 | vec.in[0].len = sizeof(u64); 119 | vec.in[1].data = reinterpret_cast(&count); 120 | vec.in[1].len = sizeof(u32); 121 | vec.out[0].data = reinterpret_cast(outViews); 122 | vec.out[0].len = sizeof(TicketView) * count; 123 | 124 | s32 ret = m_rm.ioctlv(ESIoctl::GetTicketViews, vec); 125 | return static_cast(ret); 126 | } 127 | 128 | ES::ESError ES::GetTMDViewSize(u64 titleID, u32* outSize) 129 | { 130 | IOS::IOVector<1, 1> vec; 131 | vec.in[0].data = reinterpret_cast(&titleID); 132 | vec.in[0].len = sizeof(u64); 133 | vec.out[0].data = reinterpret_cast(outSize); 134 | vec.out[0].len = sizeof(u32); 135 | 136 | s32 ret = m_rm.ioctlv(ESIoctl::GetTMDViewSize, vec); 137 | return static_cast(ret); 138 | } 139 | 140 | ES::ESError ES::GetTMDView(u64 titleID, void* out, u32 outLen) 141 | { 142 | IOS::IOVector<1, 1> vec; 143 | vec.in[0].data = reinterpret_cast(&titleID); 144 | vec.in[0].len = sizeof(u64); 145 | vec.out[0].data = out; 146 | vec.out[0].len = outLen; 147 | 148 | s32 ret = m_rm.ioctlv(ESIoctl::GetTMDView, vec); 149 | return static_cast(ret); 150 | } 151 | 152 | ES::ESError ES::DIGetTicketView(const Ticket* inTicket, TicketView* outView) 153 | { 154 | IOS::IOVector<1, 1> vec; 155 | vec.in[0].data = reinterpret_cast(inTicket); 156 | vec.in[0].len = sizeof(Ticket); 157 | vec.out[0].data = reinterpret_cast(outView); 158 | vec.out[0].len = sizeof(TicketView); 159 | 160 | s32 ret = m_rm.ioctlv(ESIoctl::DIGetTicketView, vec); 161 | return static_cast(ret); 162 | } 163 | 164 | ES::ESError ES::DIGetTicketView(TicketView* outView) 165 | { 166 | return DIGetTicketView(nullptr, outView); 167 | } 168 | 169 | ES::ESError ES::GetDataDir(u64 titleID, char* outPath) 170 | { 171 | IOS::IOVector<1, 1> vec; 172 | vec.in[0].data = reinterpret_cast(&titleID); 173 | vec.in[0].len = sizeof(u64); 174 | vec.out[0].data = outPath; 175 | vec.out[0].len = 30; 176 | 177 | s32 ret = m_rm.ioctlv(ESIoctl::GetDataDir, vec); 178 | return static_cast(ret); 179 | } 180 | 181 | ES::ESError ES::GetDeviceCert(void* out) 182 | { 183 | IOS::OVector<1> vec; 184 | vec.out[0].data = out; 185 | vec.out[0].len = 0x180; 186 | 187 | s32 ret = m_rm.ioctlv(ESIoctl::GetDeviceCert, vec); 188 | return static_cast(ret); 189 | } 190 | 191 | ES::ESError ES::GetTitleID(u64* outTitleID) 192 | { 193 | IOS::OVector<1> vec; 194 | vec.out[0].data = reinterpret_cast(outTitleID); 195 | vec.out[0].len = sizeof(u64); 196 | 197 | s32 ret = m_rm.ioctlv(ESIoctl::GetTitleID, vec); 198 | return static_cast(ret); 199 | } 200 | -------------------------------------------------------------------------------- /common/System/ES.hpp: -------------------------------------------------------------------------------- 1 | // ES.hpp - ES interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class ES 13 | { 14 | public: 15 | static ES* sInstance; 16 | 17 | enum class ESError : s32 { 18 | OK = 0, 19 | InvalidPubKeyType = -1005, 20 | ReadError = -1009, 21 | WriteError = -1010, 22 | InvalidSigType = -1012, 23 | MaxOpen = -1016, 24 | Invalid = -1017, 25 | DeviceIDMatch = -1020, 26 | HashMatch = -1022, 27 | NoMemory = -1024, 28 | NoAccess = -1026, 29 | IssuerNotFound = -1027, 30 | TicketNotFound = -1028, 31 | InvalidTicket = -1029, 32 | OutdatedBoot2 = -1031, 33 | TicketLimit = -1033, 34 | OutdatedTitle = -1035, 35 | RequiredIOSNotInstalled = -1036, 36 | WrongTMDContentCount = -1037, 37 | NoTMD = -1039, 38 | }; 39 | 40 | enum class ESIoctl { 41 | AddTicket = 0x01, 42 | AddTitleStart = 0x02, 43 | AddContentStart = 0x03, 44 | AddContentData = 0x04, 45 | AddContentFinish = 0x05, 46 | AddTitleFinish = 0x06, 47 | GetDeviceID = 0x07, 48 | LaunchTitle = 0x08, 49 | OpenContent = 0x09, 50 | ReadContent = 0x0A, 51 | CloseContent = 0x0B, 52 | GetOwnedTitlesCount = 0x0C, 53 | GetOwnedTitles = 0x0D, 54 | GetTitlesCount = 0x0E, 55 | GetTitles = 0x0F, 56 | GetTitleContentsCount = 0x10, 57 | GetTitleContents = 0x11, 58 | GetNumTicketViews = 0x12, 59 | GetTicketViews = 0x13, 60 | GetTMDViewSize = 0x14, 61 | GetTMDView = 0x15, 62 | GetConsumption = 0x16, 63 | DeleteTitle = 0x17, 64 | DeleteTicket = 0x18, 65 | DIGetTMDViewSize = 0x19, 66 | DIGetTMDView = 0x1A, 67 | DIGetTicketView = 0x1B, 68 | DIVerify = 0x1C, 69 | GetDataDir = 0x1D, 70 | GetDeviceCert = 0x1E, 71 | ImportBoot = 0x1F, 72 | GetTitleID = 0x20, 73 | SetUID = 0x21, 74 | DeleteTitleContent = 0x22, 75 | SeekContent = 0x23, 76 | OpenTitleContent = 0x24, 77 | LaunchBC = 0x25, 78 | ExportTitleInit = 0x26, 79 | ExportContentBegin = 0x27, 80 | ExportContentData = 0x28, 81 | ExportContentEnd = 0x29, 82 | ExportTitleDone = 0x2A, 83 | AddTmd = 0x2B, 84 | Encrypt = 0x2C, 85 | Decrypt = 0x2D, 86 | GetBoot2Version = 0x2E, 87 | AddTitleCancel = 0x2F, 88 | Sign = 0x30, 89 | VerifySign = 0x31, 90 | GetStoredContentCount = 0x32, 91 | GetStoredContent = 0x33, 92 | GetStoredTmdSize = 0x34, 93 | GetStoredTmd = 0x35, 94 | GetSharedContentCount = 0x36, 95 | GetSharedContents = 0x37, 96 | DeleteSharedContent = 0x38, 97 | GetDiTmdSize = 0x39, 98 | GetDiTmd = 0x3A, 99 | DiVerifyWithTicketView = 0x3B, 100 | SetupStreamKey = 0x3C, 101 | DeleteStreamKey = 0x3D, 102 | }; 103 | 104 | enum class SigType : u32 { 105 | RSA_2048 = 0x00010001, 106 | RSA_4096 = 0x00010000, 107 | }; 108 | 109 | enum class Region : u16 { 110 | Japan = 0, 111 | USA = 1, 112 | Europe = 2, 113 | None = 3, 114 | Korea = 4, 115 | }; 116 | 117 | enum AccessFlag { 118 | Flag_Hardware = 0x1, 119 | Flag_DVDVideo = 0x2, 120 | }; 121 | 122 | struct TMDContent { 123 | enum Flags { 124 | Flag_Default = 0x1, 125 | Flag_Normal = 0x4000, 126 | Flag_DLC = 0x8000, 127 | }; 128 | u32 cid; 129 | u16 index; 130 | u16 flags; 131 | u64 size; 132 | u8 hash[0x14]; 133 | } ATTRIBUTE_PACKED; 134 | 135 | struct TMDHeader { 136 | SigType sigType; 137 | u8 sigBlock[256]; 138 | u8 fill1[60]; 139 | char issuer[64]; 140 | u8 version; 141 | u8 caCRLVersion; 142 | u8 signerCRLVersion; 143 | u8 vWiiTitle; 144 | u64 iosTitleID; 145 | u64 titleID; 146 | u32 titleType; 147 | u16 groupID; 148 | u16 zero; 149 | Region region; 150 | u8 ratings[16]; 151 | u8 reserved[12]; 152 | u8 ipcMask[12]; 153 | u8 reserved2[18]; 154 | u32 accessRights; 155 | u16 titleVersion; 156 | u16 numContents; 157 | u16 bootIndex; 158 | u16 fill2; 159 | } ATTRIBUTE_PACKED; 160 | static_assert(sizeof(TMDHeader) == 0x1E4); 161 | 162 | struct TMD { 163 | TMDHeader header; 164 | TMDContent* getContents() 165 | { 166 | return reinterpret_cast(this + 1); 167 | } 168 | 169 | u32 size() const 170 | { 171 | return sizeof(TMDHeader) + sizeof(TMDContent) * header.numContents; 172 | } 173 | } ATTRIBUTE_PACKED; 174 | 175 | template 176 | struct TMDFixed : TMD { 177 | TMDContent contents[TNumContents]; 178 | 179 | u32 size() const 180 | { 181 | return sizeof(TMDHeader) + sizeof(TMDContent) * TNumContents; 182 | } 183 | } ATTRIBUTE_PACKED; 184 | 185 | struct TicketLimit { 186 | u32 tag; 187 | u32 value; 188 | } ATTRIBUTE_PACKED; 189 | static_assert(sizeof(TicketLimit) == 0x8); 190 | 191 | struct TicketInfo { 192 | u64 ticketID; 193 | u32 consoleID; 194 | u64 titleID; 195 | u16 unknown_0x1E4; 196 | u16 ticketTitleVersion; 197 | u16 permittedTitlesMask; 198 | u32 permitMask; 199 | bool allowTitleExport; 200 | u8 commonKeyIndex; 201 | u8 reserved[0x30]; 202 | u8 cidxMask[0x40]; 203 | u16 fill4; 204 | TicketLimit limits[8]; 205 | u16 fill8; 206 | } ATTRIBUTE_PACKED; 207 | static_assert(sizeof(TicketInfo) == 0xD4); 208 | 209 | struct Ticket { 210 | SigType sigType; 211 | u8 sigBlock[0x100]; 212 | u8 fill1[0x3C]; 213 | char issuer[64]; 214 | u8 fill2[0x3F]; 215 | u8 titleKey[16]; 216 | u8 fill3; 217 | TicketInfo info; 218 | } ATTRIBUTE_PACKED; 219 | static_assert(sizeof(Ticket) == 0x2A4); 220 | 221 | struct TicketView { 222 | u32 view; 223 | TicketInfo info; 224 | } ATTRIBUTE_PACKED; 225 | static_assert(sizeof(TicketView) == 0xD8); 226 | 227 | public: 228 | ESError AddContentData(s32 cfd, u8* data, u32 dataSize); 229 | ESError GetDeviceID(u32* out); 230 | ESError LaunchTitle(u64 titleID, const TicketView* view); 231 | ESError GetOwnedTitlesCount(u32* outCount); 232 | ESError GetTitlesCount(u32* outCount); 233 | ESError GetTitles(u32 count, u64* outTitles); 234 | ESError GetTitleContentsCount(u64 titleID, u32* outCount); 235 | ESError GetTitleContents(u64 titleID, u32 count, u32* outContents); 236 | ESError GetNumTicketViews(u64 titleID, u32* outCount); 237 | ESError GetTicketViews(u64 titleID, u32 count, TicketView* outViews); 238 | ESError GetTMDViewSize(u64 titleID, u32* outSize); 239 | ESError GetTMDView(u64 titleID, void* out, u32 outLen); 240 | ESError DIGetTicketView(const Ticket* inTicket, TicketView* outView); 241 | ESError DIGetTicketView(TicketView* outView); 242 | ESError GetDataDir(u64 titleID, char* outPath); 243 | ESError GetDeviceCert(void* out); 244 | ESError GetTitleID(u64* outTitleID); 245 | 246 | public: 247 | IOS::ResourceCtrl m_rm{"/dev/es"}; 248 | }; 249 | -------------------------------------------------------------------------------- /common/System/ISFS.hpp: -------------------------------------------------------------------------------- 1 | // ISFS.hpp - ISFS types 2 | // Written by Star 3 | // Written by Palapeli 4 | // 5 | // SPDX-License-Identifier: MIT 6 | 7 | #pragma once 8 | 9 | // Definitions 10 | #define NAND_DIRECTORY_SEPARATOR_CHAR '/' 11 | 12 | #define NAND_MAX_FILEPATH_LENGTH 64 // Including the NULL terminator 13 | #define NAND_MAX_FILE_DESCRIPTOR_AMOUNT 15 14 | 15 | #define NAND_SEEK_SET 0 16 | #define NAND_SEEK_CUR 1 17 | #define NAND_SEEK_END 2 18 | 19 | constexpr s32 ISFSMaxPath = NAND_MAX_FILEPATH_LENGTH; 20 | 21 | enum class ISFSIoctl { 22 | Format = 0x1, 23 | GetStats = 0x2, 24 | CreateDir = 0x3, 25 | ReadDir = 0x4, 26 | SetAttr = 0x5, 27 | GetAttr = 0x6, 28 | Delete = 0x7, 29 | Rename = 0x8, 30 | CreateFile = 0x9, 31 | GetFileStats = 0xB, 32 | GetUsage = 0xC, 33 | Shutdown = 0xD, 34 | 35 | OpenDirect = 0x1000, 36 | }; 37 | 38 | struct ISFSRenameBlock { 39 | char pathOld[ISFSMaxPath]; 40 | char pathNew[ISFSMaxPath]; 41 | }; 42 | 43 | struct ISFSAttrBlock { 44 | u32 ownerId; // UID, title specific 45 | u16 groupId; // GID, the "maker", for example 01 in RMCE01. 46 | char path[ISFSMaxPath]; 47 | // Access flags (like IOS::Mode) 48 | // If the caller's identifiers match UID or GID, use those 49 | // permissions. Otherwise use otherPerm. 50 | u8 ownerPerm; // Permissions for UID 51 | u8 groupPerm; // Permissions for GID 52 | u8 otherPerm; // Permissions for any other process 53 | u8 attributes; 54 | u8 pad[2]; 55 | }; 56 | -------------------------------------------------------------------------------- /common/System/LaunchError.hpp: -------------------------------------------------------------------------------- 1 | // LaunchError.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | enum class LaunchError { 9 | OK, // Display nothing 10 | NoSDCard, // Please insert an SD card 11 | NoCTGPR, // The inserted SD card does not contain CTGP-R 12 | CTGPCorrupt, // Could not load CTGP-R, the pack may be corrupted 13 | SDCardErr, // The inserted SD card could not be read 14 | }; 15 | -------------------------------------------------------------------------------- /common/System/OS.cpp: -------------------------------------------------------------------------------- 1 | // OS.cpp - libogc-IOS compatible types and functions 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "OS.hpp" 7 | #include 8 | 9 | #ifdef TARGET_IOS 10 | 11 | s32 IOS::Resource::ipcToCallbackThread(void* arg) 12 | { 13 | Queue* queue = reinterpret_cast*>(arg); 14 | 15 | while (1) { 16 | Request* req = queue->receive(); 17 | ASSERT(req != nullptr); 18 | if (req->cb != nullptr) { 19 | req->cb(req->result, req->userdata); 20 | } 21 | delete req; 22 | } 23 | } 24 | 25 | void IOS::Resource::MakeIPCToCallbackThread() 26 | { 27 | Queue* queue = new Queue(16); 28 | s_toCbQueue = queue->id(); 29 | new Thread(&ipcToCallbackThread, reinterpret_cast(queue), nullptr, 30 | 0x1000, 80); 31 | } 32 | 33 | s32 IOS::Resource::s_toCbQueue = -1; 34 | 35 | #else 36 | 37 | s32 ipcHeap = iosCreateHeap(0x20000); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /common/System/SHA.cpp: -------------------------------------------------------------------------------- 1 | // SHA.cpp - SHA engine interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "SHA.hpp" 7 | 8 | SHA* SHA::sInstance = nullptr; 9 | -------------------------------------------------------------------------------- /common/System/SHA.hpp: -------------------------------------------------------------------------------- 1 | // SHA.hpp - SHA engine interface 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | 11 | class SHA 12 | { 13 | public: 14 | static SHA* sInstance; 15 | 16 | struct Context { 17 | u32 state[5]; 18 | u32 count[2]; 19 | }; 20 | 21 | private: 22 | enum class SHAIoctl { 23 | Init = 0, 24 | Update = 1, 25 | Final = 2, 26 | }; 27 | 28 | static constexpr u32 MaxInputSize = 0x10000; 29 | 30 | s32 Command(SHAIoctl cmd, Context* ctx, const void* data, u32 len, 31 | u8* hashOut) 32 | { 33 | IOS::IOVector<1, 2> vec; 34 | vec.in[0].data = data; 35 | vec.in[0].len = len; 36 | vec.out[0].data = reinterpret_cast(ctx); 37 | vec.out[0].len = sizeof(Context); 38 | vec.out[1].data = hashOut; 39 | vec.out[1].len = hashOut ? 0x14 : 0; 40 | 41 | return m_rm.ioctlv(cmd, vec); 42 | } 43 | 44 | public: 45 | s32 Init(Context* ctx) 46 | { 47 | return Command(SHAIoctl::Init, ctx, nullptr, 0, nullptr); 48 | } 49 | 50 | /* 51 | * Update hash in the SHA-1 context. 52 | */ 53 | s32 Update(Context* ctx, const void* data, u32 len) 54 | { 55 | return Command(SHAIoctl::Update, ctx, data, len, nullptr); 56 | } 57 | 58 | /* 59 | * Finalize the SHA-1 context and get the result hash. 60 | */ 61 | s32 Final(Context* ctx, u8* hashOut) 62 | { 63 | return Command(SHAIoctl::Final, ctx, nullptr, 0, hashOut); 64 | } 65 | 66 | /* 67 | * Finalize the SHA-1 context and get the result hash. 68 | */ 69 | s32 Final(Context* ctx, const void* data, u32 len, u8* hashOut) 70 | { 71 | if (!len) 72 | return Final(ctx, hashOut); 73 | 74 | return Command(SHAIoctl::Final, ctx, data, len, hashOut); 75 | } 76 | 77 | /* 78 | * Quick full hash calculate. 79 | */ 80 | static s32 Calculate(const void* data, u32 len, u8* hashOut) 81 | { 82 | ASSERT(sInstance != nullptr); 83 | 84 | Context ctx PPC_ALIGN; 85 | 86 | s32 ret = sInstance->Init(&ctx); 87 | if (ret != IOSError::OK) 88 | return ret; 89 | 90 | while (len > 0) { 91 | if (len > 0x10000) { 92 | ret = sInstance->Update(&ctx, data, 0x10000); 93 | if (ret != 0) 94 | return ret; 95 | 96 | len -= 0x10000; 97 | data += 0x10000; 98 | } else { 99 | return sInstance->Final(&ctx, data, len, hashOut); 100 | } 101 | } 102 | 103 | return -4; 104 | } 105 | 106 | private: 107 | IOS::ResourceCtrl m_rm{"/dev/sha"}; 108 | }; 109 | -------------------------------------------------------------------------------- /common/System/Types.h: -------------------------------------------------------------------------------- 1 | // From libogc 2 | 3 | #pragma once 4 | #ifndef TARGET_IOS 5 | # include 6 | #else 7 | 8 | /* for IOS */ 9 | /*! \file types.h 10 | \brief Data type definitions 11 | */ 12 | 13 | #include 14 | #include 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif /* __cplusplus */ 19 | 20 | /*+----------------------------------------------------------------------------------------------+*/ 21 | typedef uint8_t u8; ///< 8bit unsigned integer 22 | typedef uint16_t u16; ///< 16bit unsigned integer 23 | typedef uint32_t u32; ///< 32bit unsigned integer 24 | typedef uint64_t u64; ///< 64bit unsigned integer 25 | /*+----------------------------------------------------------------------------------------------+*/ 26 | typedef int8_t s8; ///< 8bit signed integer 27 | typedef int16_t s16; ///< 16bit signed integer 28 | typedef int32_t s32; ///< 32bit signed integer 29 | typedef int64_t s64; ///< 64bit signed integer 30 | /*+----------------------------------------------------------------------------------------------+*/ 31 | typedef volatile u8 vu8; ///< 8bit unsigned volatile integer 32 | typedef volatile u16 vu16; ///< 16bit unsigned volatile integer 33 | typedef volatile u32 vu32; ///< 32bit unsigned volatile integer 34 | typedef volatile u64 vu64; ///< 64bit unsigned volatile integer 35 | /*+----------------------------------------------------------------------------------------------+*/ 36 | typedef volatile s8 vs8; ///< 8bit signed volatile integer 37 | typedef volatile s16 vs16; ///< 16bit signed volatile integer 38 | typedef volatile s32 vs32; ///< 32bit signed volatile integer 39 | typedef volatile s64 vs64; ///< 64bit signed volatile integer 40 | /*+----------------------------------------------------------------------------------------------+*/ 41 | // fixed point math typedefs 42 | typedef s16 sfp16; ///< signed 8:8 fixed point 43 | typedef s32 sfp32; ///< signed 20:8 fixed point 44 | typedef u16 ufp16; ///< unsigned 8:8 fixed point 45 | typedef u32 ufp32; ///< unsigned 24:8 fixed point 46 | /*+----------------------------------------------------------------------------------------------+*/ 47 | typedef float f32; 48 | typedef double f64; 49 | /*+----------------------------------------------------------------------------------------------+*/ 50 | typedef volatile float vf32; 51 | typedef volatile double vf64; 52 | /*+----------------------------------------------------------------------------------------------+*/ 53 | 54 | 55 | typedef unsigned int BOOL; 56 | /*+----------------------------------------------------------------------------------------------+*/ 57 | // alias type typedefs 58 | #define FIXED s32 ///< Alias type for sfp32 59 | /*+----------------------------------------------------------------------------------------------+*/ 60 | #ifndef TRUE 61 | #define TRUE 1 ///< True 62 | #endif 63 | /*+----------------------------------------------------------------------------------------------+*/ 64 | #ifndef FALSE 65 | #define FALSE 0 ///< False 66 | #endif 67 | /*+----------------------------------------------------------------------------------------------+*/ 68 | #ifndef NULL 69 | #define NULL 0 ///< Pointer to 0 70 | #endif 71 | /*+----------------------------------------------------------------------------------------------+*/ 72 | #ifndef LITTLE_ENDIAN 73 | #define LITTLE_ENDIAN 3412 74 | #endif /* LITTLE_ENDIAN */ 75 | /*+----------------------------------------------------------------------------------------------+*/ 76 | #ifndef BIG_ENDIAN 77 | #define BIG_ENDIAN 1234 78 | #endif /* BIG_ENDIAN */ 79 | /*+----------------------------------------------------------------------------------------------+*/ 80 | #ifndef BYTE_ORDER 81 | #define BYTE_ORDER BIG_ENDIAN 82 | #endif /* BYTE_ORDER */ 83 | /*+----------------------------------------------------------------------------------------------+*/ 84 | 85 | 86 | #ifdef __cplusplus 87 | } 88 | #endif /* __cplusplus */ 89 | 90 | #endif 91 | 92 | /* END OF FILE */ 93 | -------------------------------------------------------------------------------- /common/System/Util.h: -------------------------------------------------------------------------------- 1 | // Util.h - Common Saoirse utils 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #include "Types.h" 7 | 8 | #ifndef ATTRIBUTE_ALIGN 9 | #define ATTRIBUTE_ALIGN(v) __attribute__((aligned(v))) 10 | #endif 11 | #ifndef ATTRIBUTE_PACKED 12 | #define ATTRIBUTE_PACKED __attribute__((packed)) 13 | #endif 14 | #ifndef ATTRIBUTE_TARGET 15 | #define ATTRIBUTE_TARGET(t) __attribute__((target(#t))) 16 | #endif 17 | #ifndef ATTRIBUTE_SECTION 18 | #define ATTRIBUTE_SECTION(t) __attribute__((section(#t))) 19 | #endif 20 | #ifndef ATTRIBUTE_NOINLINE 21 | #define ATTRIBUTE_NOINLINE __attribute__((noinline)) 22 | #endif 23 | 24 | #ifndef TARGET_IOS 25 | #define PPC_ALIGN ATTRIBUTE_ALIGN(32) 26 | #else 27 | #define PPC_ALIGN 28 | #endif 29 | 30 | #ifdef __cplusplus 31 | #define EXTERN_C_START extern "C" { 32 | #define EXTERN_C_END } 33 | #else 34 | #define EXTERN_C_START 35 | #define EXTERN_C_END 36 | #endif 37 | 38 | #define LIBOGC_SUCKS_BEGIN \ 39 | _Pragma("GCC diagnostic push") \ 40 | _Pragma("GCC diagnostic ignored \"-Wpedantic\"") 41 | 42 | #define LIBOGC_SUCKS_END _Pragma("GCC diagnostic pop") 43 | 44 | #define ASM(...) asm volatile(#__VA_ARGS__) 45 | 46 | #define ASM_FUNCTION(_PROTOTYPE, ...) \ 47 | __attribute__((naked)) _PROTOTYPE \ 48 | { \ 49 | ASM(__VA_ARGS__); \ 50 | } 51 | 52 | #ifdef __cplusplus 53 | template 54 | constexpr T round_up(T num, unsigned int align) 55 | { 56 | u32 raw = (u32)num; 57 | return (T)((raw + align - 1) & -align); 58 | } 59 | 60 | template 61 | constexpr T round_down(T num, unsigned int align) 62 | { 63 | u32 raw = (u32)num; 64 | return (T)(raw & -align); 65 | } 66 | 67 | template 68 | constexpr bool aligned(T addr, unsigned int align) 69 | { 70 | return !((u32)addr & (align - 1)); 71 | } 72 | 73 | #include 74 | 75 | template 76 | constexpr bool check_bounds(T1 bounds, size_t bound_len, T2 buffer, size_t len) 77 | { 78 | size_t low = reinterpret_cast(bounds); 79 | size_t high = low + bound_len; 80 | size_t inside = reinterpret_cast(buffer); 81 | size_t insidehi = inside + len; 82 | 83 | return (high >= low) && (insidehi >= inside) && (inside >= low) && 84 | (insidehi <= high); 85 | } 86 | 87 | template 88 | constexpr bool in_mem1(T addr) 89 | { 90 | const u32 value = reinterpret_cast(addr); 91 | return value < 0x01800000; 92 | } 93 | 94 | template 95 | constexpr bool in_mem2(T addr) 96 | { 97 | const u32 value = reinterpret_cast(addr); 98 | return (value >= 0x10000000) && (value < 0x14000000); 99 | } 100 | 101 | template 102 | constexpr bool in_mem1_effective(T addr) 103 | { 104 | const u32 value = reinterpret_cast(addr); 105 | return (value >= 0x80000000) && (value < 0x81800000); 106 | } 107 | 108 | template 109 | constexpr bool in_mem2_effective(T addr) 110 | { 111 | const u32 value = reinterpret_cast(addr); 112 | return (value >= 0x90000000) && (value < 0x94000000); 113 | } 114 | 115 | #endif 116 | 117 | static inline u32 u64Hi(u64 value) 118 | { 119 | return value >> 32; 120 | } 121 | 122 | static inline u32 u64Lo(u64 value) 123 | { 124 | return value & 0xFFFFFFFF; 125 | } 126 | 127 | #ifndef TARGET_IOS 128 | LIBOGC_SUCKS_BEGIN 129 | #include 130 | LIBOGC_SUCKS_END 131 | #else 132 | 133 | static inline u32 bswap32(u32 val) 134 | { 135 | return ((val & 0xFF) << 24) | ((val & 0xFF00) << 8) | 136 | ((val & 0xFF0000) >> 8) | ((val & 0xFF000000) >> 24); 137 | } 138 | 139 | static inline u16 bswap16(u16 val) 140 | { 141 | return ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8); 142 | } 143 | 144 | static inline u32 _read8(u32 address) 145 | { 146 | return *(vu8*)address; 147 | } 148 | 149 | static inline u32 _read16(u32 address) 150 | { 151 | return *(vu16*)address; 152 | } 153 | 154 | static inline u32 _read32(u32 address) 155 | { 156 | return *(vu32*)address; 157 | } 158 | 159 | static inline void _write8(u32 address, u8 value) 160 | { 161 | *(vu8*)address = value; 162 | } 163 | 164 | static inline void _write16(u32 address, u16 value) 165 | { 166 | *(vu16*)address = value; 167 | } 168 | 169 | static inline void _write32(u32 address, u32 value) 170 | { 171 | *(vu32*)address = value; 172 | } 173 | 174 | static inline void _mask32(u32 address, u32 clear, u32 set) 175 | { 176 | *(vu32*)address = ((*(vu32*)address) & ~clear) | set; 177 | } 178 | 179 | #define write8(_ADDRESS, _VALUE) _write8((u32)(_ADDRESS), (u8)(_VALUE)) 180 | #define write16(_ADDRESS, _VALUE) _write16((u32)(_ADDRESS), (u16)(_VALUE)) 181 | #define write32(_ADDRESS, _VALUE) _write32((u32)(_ADDRESS), (u32)(_VALUE)) 182 | #define read8(_ADDRESS) _read8((u32)(_ADDRESS)) 183 | #define read16(_ADDRESS) _read16((u32)(_ADDRESS)) 184 | #define read32(_ADDRESS) _read32((u32)(_ADDRESS)) 185 | 186 | #define mask32(_ADDRESS, _CLEAR, _SET) \ 187 | _mask32((u32)(_ADDRESS), (u32)(_CLEAR), (u32)(_SET)) 188 | 189 | #endif 190 | 191 | #define read16_le(_ADDRESS) bswap16(read16((u32)(_ADDRESS))) 192 | #define read32_le(_ADDRESS) bswap32(read32((u32)(_ADDRESS))) 193 | #define write16_le(_ADDRESS, _VALUE) \ 194 | write16((u32)(_ADDRESS), bswap16((u16)(_VALUE))) 195 | #define write32_le(_ADDRESS, _VALUE) \ 196 | write32((u32)(_ADDRESS), bswap32((u32)(_VALUE))) 197 | 198 | // libogc doesn't have this for some reason? 199 | static inline void mask16(u32 address, u16 clear, u16 set) 200 | { 201 | *(vu16*)address = ((*(vu16*)address) & ~clear) | set; 202 | } 203 | -------------------------------------------------------------------------------- /data.mk: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | # Clear the implicit built in rules 3 | #--------------------------------------------------------------------------------- 4 | .SUFFIXES: 5 | 6 | #--------------------------------------------------------------------------------- 7 | # TARGET is the name of the output 8 | # BUILD is the directory where object files & intermediate files will be placed 9 | #--------------------------------------------------------------------------------- 10 | BIN := bin 11 | TARGET := data.ar 12 | DATA := data 13 | 14 | #--------------------------------------------------------------------------------- 15 | # the prefix on the compiler executables 16 | #--------------------------------------------------------------------------------- 17 | PREFIX := $(DEVKITPPC)/bin/powerpc-eabi- 18 | AR := $(PREFIX)ar 19 | 20 | #--------------------------------------------------------------------------------- 21 | # automatically build a list of object files for our project 22 | #--------------------------------------------------------------------------------- 23 | DUMMY != mkdir -p $(BIN) 24 | 25 | OUTPUT := $(BIN)/$(TARGET) 26 | DATAFILES := $(foreach dir,$(DATA),$(wildcard $(dir)/*)) 27 | 28 | default: $(OUTPUT) 29 | 30 | $(OUTPUT): $(DATAFILES) $(BIN)/saoirse_ios.elf $(BIN)/ios_loader.bin 31 | @rm -rf $@ 32 | @echo packing ... $(notdir $@) 33 | @$(AR) -r $@ $(DATAFILES) $(BIN)/saoirse_ios.elf $(BIN)/ios_loader.bin 34 | 35 | 36 | -------------------------------------------------------------------------------- /ios.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(arm) 2 | __entry = 0; 3 | ENTRY(__entry) 4 | 5 | PHDRS { 6 | ios_note_hdr PT_NOTE AT(0) FLAGS(0); 7 | text PT_LOAD FLAGS(0x00F00000 | 5); 8 | data PT_LOAD FLAGS(0x00F00000 | 6); 9 | } 10 | 11 | MEMORY { 12 | phys_ram(rwx) : ORIGIN = 0x13670000, LENGTH = 0x80000 13 | virt_ram(rwx) : ORIGIN = 0x7FE80000, LENGTH = 0x80000 14 | } 15 | 16 | SECTIONS 17 | { 18 | .ios_note : { 19 | KEEP(*(.ios_note)) 20 | } : ios_note_hdr = 0 21 | 22 | .text : { 23 | *(.text*) 24 | *(.syscall*) 25 | } > virt_ram AT > phys_ram : text = 0 26 | 27 | .data : ALIGN(0x1000) { 28 | __data_start = .; 29 | *(.data*) 30 | } > virt_ram AT > phys_ram : data = 0 31 | 32 | .rodata : ALIGN(32) { 33 | *(.rodata*) 34 | /* C++ init */ 35 | . = ALIGN(4); 36 | _init_array_start = .; 37 | KEEP(*(.init_array)) 38 | _init_array_end = .; 39 | } > virt_ram AT > phys_ram : data = 0 40 | 41 | .bss : ALIGN(32) { 42 | __bss_start = .; 43 | *(.bss*) 44 | *(.sbss*) 45 | __bss_end = .; 46 | __data_end = .; 47 | } > virt_ram AT > phys_ram : data = 0 48 | 49 | /DISCARD/ : 50 | { 51 | *(.ARM*) 52 | *(.comment) 53 | } 54 | } 55 | PROVIDE(__end__ = .); 56 | PROVIDE(__exidx_start = .); 57 | PROVIDE(__exidx_end = .); 58 | -------------------------------------------------------------------------------- /ios.mk: -------------------------------------------------------------------------------- 1 | #--------------------------------------------------------------------------------- 2 | # Clear the implicit built in rules 3 | #--------------------------------------------------------------------------------- 4 | .SUFFIXES: 5 | 6 | #--------------------------------------------------------------------------------- 7 | # TARGET is the name of the output 8 | # BUILD is the directory where object files & intermediate files will be placed 9 | # SOURCES is a list of directories containing source code 10 | # INCLUDES is a list of directories containing extra header files 11 | #--------------------------------------------------------------------------------- 12 | TARGET := saoirse_ios 13 | BUILD := build_ios 14 | SOURCES := $(wildcard ios/*) $(wildcard common/*) 15 | DATA := data 16 | INCLUDES := ios common 17 | BIN := bin 18 | 19 | LIBS := 20 | LIBDIRS := 21 | 22 | 23 | #--------------------------------------------------------------------------------- 24 | # the prefix on the compiler executables 25 | #--------------------------------------------------------------------------------- 26 | ifeq ($(COMPILER),clang) 27 | 28 | PREFIX := $(DEVKITARM)/bin/arm-none-eabi- 29 | CC := clang 30 | CXX := clang++ 31 | AR := $(PREFIX)ar 32 | OBJCOPY := $(PREFIX)objcopy 33 | LD := $(PREFIX)g++ 34 | AS := $(PREFIX)g++ 35 | 36 | else 37 | 38 | PREFIX := $(DEVKITARM)/bin/arm-none-eabi- 39 | CC := $(PREFIX)gcc 40 | CXX := $(PREFIX)g++ 41 | AR := $(PREFIX)ar 42 | OBJCOPY := $(PREFIX)objcopy 43 | LD := $(PREFIX)g++ 44 | AS := $(PREFIX)g++ 45 | 46 | endif 47 | 48 | #--------------------------------------------------------------------------------- 49 | # linker script 50 | #--------------------------------------------------------------------------------- 51 | LINKSCRIPT := ios.ld 52 | LINKSCRIPT_LOADER := ios_loader.ld 53 | 54 | TARGET := saoirse_ios 55 | LOADER_TARGET := ios_loader 56 | 57 | #--------------------------------------------------------------------------------- 58 | # automatically build a list of object files for our project 59 | #--------------------------------------------------------------------------------- 60 | DUMMY != mkdir -p $(BIN) $(BUILD) $(foreach dir,$(SOURCES),$(BUILD)/$(dir)) 61 | 62 | OUTPUT := $(BIN)/$(TARGET) 63 | LOADER_OUTPUT := $(BIN)/$(LOADER_TARGET) 64 | CFILES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.c)) 65 | CPPFILES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.cpp)) 66 | sFILES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.s)) 67 | SFILES := $(foreach dir,$(SOURCES),$(wildcard $(dir)/*.S)) 68 | 69 | OFILES := $(CPPFILES:.cpp=_cpp.o) $(CFILES:.c=_c.o) \ 70 | $(sFILES:.s=_s.o) $(SFILES:.S=_S.o) 71 | OFILES := $(addprefix $(BUILD)/, $(OFILES)) 72 | 73 | DEPENDS := $(addsuffix .d, $(basename $(OFILES))) 74 | 75 | VPATH = $(foreach dir,$(SOURCES),$(ROOT)/$(dir)) 76 | 77 | 78 | #--------------------------------------------------------------------------------- 79 | # options for code generation 80 | #--------------------------------------------------------------------------------- 81 | INCLUDE := $(foreach dir,$(INCLUDES),-I$(dir)) 82 | ifeq ($(COMPILER),clang) 83 | INCLUDE += -I$(DEVKITARM)/arm-none-eabi/include/c++/11.2.0 -I$(DEVKITARM)/arm-none-eabi/include -I$(DEVKITARM)/arm-none-eabi/include/c++/11.2.0/arm-none-eabi/be 84 | endif 85 | 86 | #--------------------------------------------------------------------------------- 87 | # build a list of library paths 88 | #--------------------------------------------------------------------------------- 89 | LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 90 | LIBS := -lgcc 91 | 92 | CLANG_ARCH := --target=arm-none-eabi -mcpu=arm926ej-s -mtune=arm9tdmi -mbig-endian 93 | GCC_ARCH := -march=armv5te -mtune=arm9tdmi -mthumb-interwork -mbig-endian 94 | 95 | ifeq ($(COMPILER),clang) 96 | CFLAGS := $(CLANG_ARCH) 97 | else 98 | CFLAGS := $(GCC_ARCH) 99 | endif 100 | 101 | CFLAGS += $(INCLUDE) -Os -DTARGET_IOS -DNDEBUG -Wall -Wextra -Wpedantic -Werror -Wno-unused-parameter -Wno-keyword-macro \ 102 | -Wno-nested-anon-types -Wno-unused-const-variable -Wno-unused-function -Wno-unused-variable -Wno-unused-but-set-variable -fshort-enums -fomit-frame-pointer \ 103 | -fverbose-asm -ffunction-sections -fdata-sections -fno-exceptions -Wno-pointer-arith -Wno-format-truncation 104 | CXXFLAGS = $(CFLAGS) -std=c++20 -fno-rtti -fno-builtin-memcpy -fno-builtin-memset -Wno-narrowing 105 | 106 | ifeq ($(COMPILER),clang) 107 | AFLAGS = $(CLANG_ARCH) -x assembler-with-cpp 108 | else 109 | AFLAGS = $(GCC_ARCH) -x assembler-with-cpp 110 | endif 111 | 112 | LDFLAGS = $(GCC_ARCH) $(LIBPATHS) $(LIBS) -n -Wl,--gc-sections -Wl,-static 113 | 114 | 115 | default: $(OUTPUT).elf $(LOADER_OUTPUT).bin 116 | 117 | clean: 118 | @echo cleaning... 119 | @rm -rf $(BIN) $(BUILD) 120 | 121 | $(OUTPUT).elf: $(OUTPUT)_dbg.elf 122 | @echo output ... $(notdir $@) 123 | @$(LD) -s -o $@ $(OFILES) -T$(LINKSCRIPT) $(LDFLAGS) -Wl,-Map,$(BIN)/$(TARGET).map 124 | 125 | $(OUTPUT)_dbg.elf: $(OFILES) $(LINKSCRIPT) 126 | @echo linking ... $(notdir $@) 127 | @$(LD) -g -o $@ $(OFILES) -T$(LINKSCRIPT) $(LDFLAGS) 128 | 129 | $(LOADER_OUTPUT).bin: $(LOADER_OUTPUT).elf 130 | @echo output ... $(notdir $@) 131 | @$(OBJCOPY) $< $@ -O binary 132 | 133 | $(LOADER_OUTPUT).elf: $(OFILES) $(LINKSCRIPT_LOADER) 134 | @echo linking ... $(notdir $@) 135 | @$(LD) -g -o $@ $(OFILES) -T$(LINKSCRIPT_LOADER) $(LDFLAGS) 136 | 137 | $(BUILD)/%_cpp.o : %.cpp 138 | @echo $(notdir $<) 139 | @$(CC) -g -MMD -MF $(BUILD)/$*_cpp.d $(CXXFLAGS) -c $< -o$@ 140 | 141 | $(BUILD)/%_c.o : %.c 142 | @echo $(notdir $<) 143 | @$(CC) -g -MMD -MF $(BUILD)/$*_c.d $(CFLAGS) -c $< -o$@ 144 | 145 | $(BUILD)/%_s.o : %.s 146 | @echo $(notdir $<) 147 | @$(CC) -g -MMD -MF $(BUILD)/$*_s.d $(AFLAGS) -c $< -o$@ 148 | 149 | $(BUILD)/%_bin.o : %.bin 150 | @echo $(notdir $<) 151 | @$(bin2o) 152 | 153 | -include $(DEPENDS) 154 | 155 | define bin2o 156 | @echo -e "\t.section .rodata\n\t.align 4\n\t.global $(*)\n\t.global $(*)_end\n$(*):\n\t.incbin \"$(subst /,\\\\\\\\,$(shell echo $< | sed 's=/==;s=/=:/='))\"\n$(*)_end:\n" > $@.s 157 | @$(CC) $(ASFLAGS) $(AFLAGS) -c $@.s -o $@ 158 | @rm -rf $@.s 159 | endef 160 | -------------------------------------------------------------------------------- /ios/CTGP/Blob.hpp: -------------------------------------------------------------------------------- 1 | // Blob.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class Blob 13 | { 14 | public: 15 | enum class MountError { 16 | OK, 17 | FileNotFound, 18 | DiskError, 19 | }; 20 | 21 | MountError Mount(u32 devId); 22 | void Reset(); 23 | FRESULT ReadSectors(u32 sector, u32 count, void* data); 24 | 25 | bool LaunchDOL(FIL* dolFile); 26 | bool LaunchMainDOL(u32 devId); 27 | 28 | public: 29 | bool m_mounted; 30 | 31 | FIL m_fil ATTRIBUTE_ALIGN(32); 32 | int m_devId = -1; 33 | 34 | bool m_opened; 35 | }; 36 | -------------------------------------------------------------------------------- /ios/CTGP/EmuHID.cpp: -------------------------------------------------------------------------------- 1 | // EmuHID.cpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "EmuHID.hpp" 7 | 8 | // This is meant to be a fix for the issue where USB HID breaks after our fake 9 | // IOS reload 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | static IOS::ResourceCtrl s_hidRM("/dev/usb/hid", 11); 19 | 20 | static Queue hidQueue(8); 21 | 22 | static IOS::Request* s_deviceChangeReq; 23 | 24 | static bool s_deviceChangeAvailable; 25 | static USB::DeviceEntry* s_devices = nullptr; 26 | static u32 s_deviceCount = 0; 27 | 28 | static IOS::Request s_cbReq; 29 | bool s_waitingForCb = false; 30 | 31 | void IPCRequest(IOS::Request* req) 32 | { 33 | switch (req->cmd) { 34 | case IOS::Command::Open: 35 | case IOS::Command::Close: 36 | req->reply(0); 37 | break; 38 | 39 | case IOS::Command::Ioctl: 40 | switch (USB::USBv5Ioctl(req->ioctl.cmd)) { 41 | case USB::USBv5Ioctl::GetDeviceChange: { 42 | PRINT(IOS_EmuHID, INFO, "Get device change!"); 43 | 44 | if (s_deviceChangeAvailable) { 45 | // Just return immediately 46 | PRINT(IOS_EmuHID, INFO, "Get first device change! Reply = %d", 47 | s_deviceCount); 48 | s_deviceChangeAvailable = false; 49 | memcpy(req->ioctl.io, s_devices, 50 | std::min(req->ioctl.io_len, 51 | sizeof(USB::DeviceEntry) * USB::MaxDevices)); 52 | req->reply(s_deviceCount); 53 | s_deviceChangeReq = nullptr; 54 | break; 55 | } 56 | 57 | // Else enqueue 58 | PRINT(IOS_EmuHID, INFO, "Enqueuing dev change!"); 59 | s_deviceChangeReq = req; 60 | break; 61 | } 62 | 63 | case USB::USBv5Ioctl::Shutdown: { 64 | // The best command 65 | PRINT(IOS_EmuHID, INFO, "Shutdown"); 66 | if (s_deviceChangeReq != nullptr) { 67 | s_deviceChangeReq->reply(0); 68 | s_deviceChangeReq = nullptr; 69 | } 70 | req->reply(0); 71 | break; 72 | } 73 | 74 | case USB::USBv5Ioctl::AttachFinish: { 75 | // Once the caller calls AttachFinish we can start doing this again 76 | PRINT(IOS_EmuHID, INFO, "Attach Finish"); 77 | // How does this fix anything..? 78 | // OK this literally doesn't make any sense why this fixes it but it 79 | // does? Why??? 80 | usleep(30000); 81 | // I guess sleep can be like that sometimes 82 | req->reply(0); 83 | break; 84 | } 85 | 86 | default: { 87 | req->reply(s_hidRM.ioctl(USB::USBv5Ioctl(req->ioctl.cmd), 88 | req->ioctl.in, req->ioctl.in_len, 89 | req->ioctl.io, req->ioctl.io_len)); 90 | break; 91 | } 92 | } 93 | break; 94 | 95 | case IOS::Command::Ioctlv: { 96 | req->reply(s_hidRM.ioctlv(USB::USBv5Ioctl(req->ioctlv.cmd), 97 | req->ioctlv.in_count, req->ioctlv.io_count, 98 | req->ioctlv.vec)); 99 | break; 100 | } 101 | 102 | case IOS::Command::Reply: { 103 | if (req == &s_cbReq) { 104 | PRINT(IOS_EmuHID, INFO, "Got device change reply! %d", req->result); 105 | 106 | if (req->result >= 0) { 107 | s_deviceCount = req->result; 108 | u8* input = (u8*)IOS::Alloc(32); 109 | for (u32 i = 0; i < s_deviceCount; i++) { 110 | memset(input, 0, 32); 111 | write32(input, s_devices[i].devId); 112 | // Ignore the result, it will return IOSError::Invalid if 113 | // it's not attached, but that doesn't matter 114 | s_hidRM.ioctl(USB::USBv5Ioctl::Attach, input, 32, nullptr, 115 | 0); 116 | } 117 | IOS::Free(input); 118 | 119 | s_hidRM.ioctl(USB::USBv5Ioctl::AttachFinish, nullptr, 0, 120 | nullptr, 0); 121 | s_hidRM.ioctlAsync(USB::USBv5Ioctl::GetDeviceChange, nullptr, 0, 122 | s_devices, 123 | sizeof(USB::DeviceEntry) * USB::MaxDevices, 124 | &hidQueue, &s_cbReq); 125 | } else { 126 | s_deviceCount = 0; 127 | } 128 | 129 | if (s_deviceChangeReq != nullptr) { 130 | PRINT(IOS_EmuHID, INFO, "Replying to enqueued device change"); 131 | memcpy(s_deviceChangeReq->ioctl.io, s_devices, 132 | std::min(s_deviceChangeReq->ioctl.io_len, 133 | sizeof(USB::DeviceEntry) * USB::MaxDevices)); 134 | s_deviceChangeReq->reply(req->result); 135 | s_deviceChangeAvailable = false; 136 | s_deviceChangeReq = nullptr; 137 | break; 138 | } 139 | 140 | PRINT(IOS_EmuHID, INFO, "No enqueued device change"); 141 | s_deviceChangeAvailable = true; 142 | break; 143 | } 144 | 145 | PRINT(IOS_EmuHID, INFO, "Got weird unknown reply huh"); 146 | break; 147 | } 148 | 149 | default: 150 | PRINT(IOS_EmuHID, ERROR, "Unknown command we just got! %d", req->cmd); 151 | req->reply(-4); 152 | break; 153 | } 154 | } 155 | 156 | // Automatically release everything on reload 157 | void EmuHID::Reload() 158 | { 159 | PRINT(IOS_EmuHID, INFO, "Doing reload!"); 160 | 161 | if (s_deviceChangeReq != nullptr) { 162 | s_deviceChangeReq->reply(0); 163 | s_deviceChangeReq = nullptr; 164 | } 165 | 166 | u8* input = (u8*)IOS::Alloc(32); 167 | 168 | // Do suspend because CTGP really cares if the resume doesnt work 169 | for (u32 i = 0; i < s_deviceCount; i++) { 170 | memset(input, 0, 32); 171 | write32(input, s_devices[i].devId); 172 | write8(input + 0xB, 0); 173 | s_hidRM.ioctl(USB::USBv5Ioctl::SuspendResume, input, 32, nullptr, 0); 174 | } 175 | 176 | IOS::Free(input); 177 | 178 | // Force the device change to be available 179 | s_deviceChangeAvailable = true; 180 | } 181 | 182 | s32 EmuHID::ThreadEntry([[maybe_unused]] void* arg) 183 | { 184 | PRINT(IOS_EmuHID, INFO, "Starting HID..."); 185 | PRINT(IOS_EmuHID, INFO, "EmuHID thread ID: %d", IOS_GetThreadId()); 186 | 187 | s32 ret = IOS_RegisterResourceManager("~dev/usb/hid", hidQueue.id()); 188 | assert(ret == IOSError::OK); 189 | 190 | s_devices = (USB::DeviceEntry*)IOS::Alloc(sizeof(USB::DeviceEntry) * 191 | USB::MaxDevices); 192 | 193 | // Let's get the initial device change 194 | ret = s_hidRM.ioctlAsync( 195 | USB::USBv5Ioctl::GetDeviceChange, nullptr, 0, s_devices, 196 | sizeof(USB::DeviceEntry) * USB::MaxDevices, &hidQueue, &s_cbReq); 197 | 198 | IPCLog::sInstance->Notify(1); 199 | while (true) { 200 | IOS::Request* req = hidQueue.receive(); 201 | IPCRequest(req); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /ios/CTGP/EmuHID.hpp: -------------------------------------------------------------------------------- 1 | // EmuHID.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace EmuHID 11 | { 12 | 13 | void Reload(); 14 | s32 ThreadEntry(void* arg); 15 | 16 | } // namespace EmuHID 17 | -------------------------------------------------------------------------------- /ios/Disk/DeviceMgr.hpp: -------------------------------------------------------------------------------- 1 | // DeviceMgr.hpp - I/O storage device manager 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | class DeviceMgr 19 | { 20 | public: 21 | static DeviceMgr* sInstance; 22 | 23 | DeviceMgr(); 24 | ~DeviceMgr(); 25 | 26 | static constexpr u32 DeviceCount = 9; 27 | 28 | bool IsInserted(u32 devId); 29 | bool IsMounted(u32 devId); 30 | void SetError(u32 devId); 31 | FATFS* GetFilesystem(u32 devId); 32 | void ForceUpdate(); 33 | 34 | u32 DRVToDevID(u32 drv) const 35 | { 36 | return drv; 37 | } 38 | 39 | private: 40 | void USBFatal(); 41 | void USBChange(USB::DeviceEntry* devices, u32 count); 42 | 43 | public: 44 | bool IsLogEnabled(); 45 | void WriteToLog(const char* str, u32 len); 46 | 47 | bool DeviceInit(u32 devId); 48 | bool DeviceRead(u32 devId, void* data, u32 sector, u32 count); 49 | bool DeviceWrite(u32 devId, const void* data, u32 sector, u32 count); 50 | bool DeviceSync(u32 devId); 51 | 52 | private: 53 | void Run(); 54 | static s32 ThreadEntry(void* arg); 55 | 56 | struct NullDevice { 57 | u8 pad; 58 | }; 59 | 60 | struct DeviceHandle { 61 | std::variant disk; 62 | FATFS fs; 63 | 64 | bool enabled; 65 | bool inserted; 66 | bool error; 67 | bool mounted; 68 | }; 69 | 70 | void InitHandle(u32 devId); 71 | void UpdateHandle(u32 devId); 72 | bool OpenLogFile(); 73 | 74 | private: 75 | struct USBDeviceHandle { 76 | bool inUse; 77 | u32 usbId; 78 | u32 intId; 79 | }; 80 | 81 | USBDeviceHandle m_usbDevices[USB::MaxDevices]; 82 | 83 | u32 m_usbDeviceCount = 0; 84 | bool m_usbError = false; 85 | 86 | Thread m_thread; 87 | 88 | Queue m_timerQueue; 89 | s32 m_timer; 90 | 91 | bool m_logEnabled; 92 | u32 m_logDevice; 93 | FIL m_logFile; 94 | 95 | DeviceHandle m_devices[DeviceCount]; 96 | LaunchError m_launchError; 97 | bool m_somethingInserted; 98 | }; 99 | -------------------------------------------------------------------------------- /ios/Disk/FatFS.cpp: -------------------------------------------------------------------------------- 1 | // FatFS.cpp - FatFS Disk I/O Layer 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | DSTATUS disk_status(BYTE pdrv) 15 | { 16 | auto devId = DeviceMgr::sInstance->DRVToDevID(pdrv); 17 | 18 | if (DeviceMgr::sInstance->IsMounted(devId)) 19 | return 0; 20 | 21 | return STA_NODISK; 22 | } 23 | 24 | DSTATUS disk_initialize(BYTE pdrv) 25 | { 26 | auto devId = DeviceMgr::sInstance->DRVToDevID(pdrv); 27 | 28 | if (DeviceMgr::sInstance->DeviceInit(devId)) 29 | return 0; 30 | 31 | return STA_NOINIT; 32 | } 33 | 34 | DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count) 35 | { 36 | auto devId = DeviceMgr::sInstance->DRVToDevID(pdrv); 37 | 38 | if (DeviceMgr::sInstance->DeviceRead(devId, reinterpret_cast(buff), 39 | static_cast(sector), 40 | static_cast(count))) 41 | return RES_OK; 42 | 43 | return RES_ERROR; 44 | } 45 | 46 | DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count) 47 | { 48 | auto devId = DeviceMgr::sInstance->DRVToDevID(pdrv); 49 | 50 | if (DeviceMgr::sInstance->DeviceWrite( 51 | devId, reinterpret_cast(buff), 52 | static_cast(sector), static_cast(count))) 53 | return RES_OK; 54 | 55 | return RES_ERROR; 56 | } 57 | 58 | DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff) 59 | { 60 | auto devId = DeviceMgr::sInstance->DRVToDevID(pdrv); 61 | 62 | switch (cmd) { 63 | case CTRL_SYNC: 64 | return DeviceMgr::sInstance->DeviceSync(devId) ? RES_OK : RES_ERROR; 65 | 66 | case GET_SECTOR_SIZE: 67 | // Sectors are always 512 bytes 68 | *reinterpret_cast(buff) = 512; 69 | return RES_OK; 70 | 71 | default: 72 | PRINT(IOS_DevMgr, ERROR, "Unknown command: %d", cmd); 73 | return RES_PARERR; 74 | } 75 | } 76 | 77 | // From https://howardhinnant.github.io/date_algorithms.html#civil_from_days 78 | template 79 | constexpr std::tuple civil_from_days(Int z) noexcept 80 | { 81 | static_assert( 82 | std::numeric_limits::digits >= 18, 83 | "This algorithm has not been ported to a 16 bit unsigned integer"); 84 | static_assert( 85 | std::numeric_limits::digits >= 20, 86 | "This algorithm has not been ported to a 16 bit signed integer"); 87 | z += 719468; 88 | const Int era = (z >= 0 ? z : z - 146096) / 146097; 89 | const unsigned doe = static_cast(z - era * 146097); // [0, 146096] 90 | const unsigned yoe = 91 | (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // [0, 399] 92 | const Int y = static_cast(yoe) + era * 400; 93 | const unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100); // [0, 365] 94 | const unsigned mp = (5 * doy + 2) / 153; // [0, 11] 95 | const unsigned d = doy - (153 * mp + 2) / 5 + 1; // [1, 31] 96 | const unsigned m = mp < 10 ? mp + 3 : mp - 9; // [1, 12] 97 | return std::tuple(y + (m <= 2), m, d); 98 | } 99 | 100 | DWORD get_fattime() 101 | { 102 | struct { 103 | u32 year : 7; 104 | u32 month : 4; 105 | u32 day : 5; 106 | u32 hour : 5; 107 | u32 minute : 6; 108 | u32 second : 5; 109 | } fattime; 110 | 111 | u64 time64 = System::GetTime(); 112 | u32 time32 = time64; 113 | 114 | s32 days = time64 / 86400; 115 | auto date = civil_from_days(days); 116 | 117 | fattime = { 118 | .year = std::get<0>(date) - 1980, 119 | .month = std::get<1>(date), 120 | .day = std::get<2>(date), 121 | .hour = (time32 / 60 / 60) % 24, 122 | .minute = (time32 / 60) % 60, 123 | .second = time32 % 60, 124 | }; 125 | 126 | DWORD* retval = (DWORD*)(&fattime); 127 | return *retval; 128 | } 129 | 130 | void* ff_memalloc(UINT msize) 131 | { 132 | return new u8[msize]; 133 | } 134 | 135 | void ff_memfree(void* mblock) 136 | { 137 | u8* data = reinterpret_cast(mblock); 138 | delete[] data; 139 | } 140 | 141 | int ff_cre_syncobj([[maybe_unused]] BYTE vol, FF_SYNC_t* sobj) 142 | { 143 | Mutex* mutex = new Mutex; 144 | *sobj = reinterpret_cast(mutex); 145 | return 1; 146 | } 147 | 148 | // Lock sync object 149 | int ff_req_grant(FF_SYNC_t sobj) 150 | { 151 | Mutex* mutex = reinterpret_cast(sobj); 152 | mutex->lock(); 153 | return 1; 154 | } 155 | 156 | void ff_rel_grant(FF_SYNC_t sobj) 157 | { 158 | Mutex* mutex = reinterpret_cast(sobj); 159 | mutex->unlock(); 160 | } 161 | 162 | // Delete a sync object 163 | int ff_del_syncobj(FF_SYNC_t sobj) 164 | { 165 | Mutex* mutex = reinterpret_cast(sobj); 166 | delete mutex; 167 | return 1; 168 | } 169 | -------------------------------------------------------------------------------- /ios/Disk/SDCard.hpp: -------------------------------------------------------------------------------- 1 | // SDCard.hpp 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #include 7 | 8 | typedef u32 sec_t; 9 | 10 | class SDCard 11 | { 12 | public: 13 | static bool Open(); 14 | static bool Startup(); 15 | static bool Shutdown(); 16 | static s32 ReadSectors(sec_t sector, sec_t numSectors, void* buffer); 17 | static s32 WriteSectors(sec_t sector, sec_t numSectors, const void* buffer); 18 | static bool ClearStatus(); 19 | static bool IsInserted(); 20 | static bool IsInitialized(); 21 | }; 22 | -------------------------------------------------------------------------------- /ios/Disk/USB.cpp: -------------------------------------------------------------------------------- 1 | // USB.cpp - USB2 Device I/O 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "USB.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | USB* USB::sInstance = nullptr; 14 | 15 | USB::USB(s32 id) 16 | { 17 | if (id >= 0) 18 | new (&ven) IOS::ResourceCtrl("/dev/usb/ven", id); 19 | } 20 | 21 | bool USB::Init() 22 | { 23 | if (ven.fd() < 0) { 24 | PRINT(IOS_USB, ERROR, "Failed to open /dev/usb/ven: %d", ven.fd()); 25 | return false; 26 | } 27 | 28 | // Check USB RM version. 29 | u32* verBuffer = (u32*)IOS::Alloc(32); 30 | s32 ret = ven.ioctl(USBv5Ioctl::GetVersion, nullptr, 0, verBuffer, 32); 31 | u32 ver = verBuffer[0]; 32 | IOS::Free(verBuffer); 33 | 34 | if (ret != IOSError::OK) { 35 | PRINT(IOS_USB, ERROR, "GetVersion error: %d", ret); 36 | return false; 37 | } 38 | 39 | if (ver != 0x00050001) { 40 | PRINT(IOS_USB, ERROR, "Unrecognized USB RM version: 0x%X", ver); 41 | return false; 42 | } 43 | 44 | return true; 45 | } 46 | 47 | /* 48 | * Aynchronous call to get the next device change. Sends 'req' to 'queue' 49 | * when GetDeviceChange responds. 50 | * devices - Output device entries, must have USB::MaxDevices entries, 51 | * 32-bit aligned, MEM2 virtual = physical address. 52 | */ 53 | bool USB::EnqueueDeviceChange(DeviceEntry* devices, Queue* queue, 54 | IOS::Request* req) 55 | { 56 | if (m_reqSent) { 57 | s32 ret = ven.ioctl(USBv5Ioctl::AttachFinish, nullptr, 0, nullptr, 0); 58 | if (ret != IOSError::OK) { 59 | PRINT(IOS_USB, ERROR, "AttachFinish error: %d", ret); 60 | return false; 61 | } 62 | } 63 | 64 | m_reqSent = false; 65 | s32 ret = ven.ioctlAsync(USBv5Ioctl::GetDeviceChange, nullptr, 0, devices, 66 | sizeof(DeviceEntry) * MaxDevices, queue, req); 67 | if (ret != IOSError::OK) { 68 | PRINT(IOS_USB, ERROR, "GetDeviceChange async error: %d", ret); 69 | return false; 70 | } 71 | 72 | m_reqSent = true; 73 | return true; 74 | } 75 | 76 | /* 77 | * Get USB descriptors for a device. 78 | */ 79 | USB::USBError USB::GetDeviceInfo(u32 devId, DeviceInfo* outInfo, u8 alt) 80 | { 81 | u8* input = (u8*)IOS::Alloc(32); 82 | write32(input, devId); 83 | write8(input + 0x8, alt); 84 | 85 | void* tempInfo = IOS::Alloc(sizeof(DeviceInfo)); 86 | 87 | s32 ret = ven.ioctl(USBv5Ioctl::GetDeviceInfo, input, 32, tempInfo, 88 | sizeof(DeviceInfo)); 89 | memcpy(outInfo, tempInfo, sizeof(DeviceInfo)); 90 | 91 | IOS::Free(input); 92 | IOS::Free(tempInfo); 93 | return static_cast(ret); 94 | } 95 | 96 | /* 97 | * Attaches the provided device to the current handle. 98 | */ 99 | USB::USBError USB::Attach(u32 devId) 100 | { 101 | u8* input = (u8*)IOS::Alloc(32); 102 | write32(input, devId); 103 | 104 | s32 ret = ven.ioctl(USBv5Ioctl::Attach, input, 32, nullptr, 0); 105 | 106 | IOS::Free(input); 107 | return static_cast(ret); 108 | } 109 | 110 | /* 111 | * Releases the provided device from the current handle. 112 | */ 113 | USB::USBError USB::Release(u32 devId) 114 | { 115 | u8* input = (u8*)IOS::Alloc(32); 116 | write32(input, devId); 117 | 118 | s32 ret = ven.ioctl(USBv5Ioctl::Release, input, 32, nullptr, 0); 119 | 120 | IOS::Free(input); 121 | return static_cast(ret); 122 | } 123 | 124 | USB::USBError USB::AttachFinish() 125 | { 126 | return static_cast( 127 | ven.ioctl(USBv5Ioctl::AttachFinish, nullptr, 0, nullptr, 0)); 128 | } 129 | 130 | /* 131 | * Suspend or resume a device. Returns Invalid if the new state is the same 132 | * as the current one. 133 | */ 134 | USB::USBError USB::SuspendResume(u32 devId, State state) 135 | { 136 | u8* input = (u8*)IOS::Alloc(32); 137 | write32(input, devId); 138 | write8(input + 0xB, state == State::Resume ? 1 : 0); 139 | 140 | s32 ret = ven.ioctl(USBv5Ioctl::SuspendResume, input, 32, nullptr, 0); 141 | 142 | IOS::Free(input); 143 | return static_cast(ret); 144 | } 145 | 146 | /* 147 | * Cancel ongoing transfer on an endpoint. 148 | */ 149 | USB::USBError USB::CancelEndpoint(u32 devId, u8 endpoint) 150 | { 151 | u8* input = (u8*)IOS::Alloc(32); 152 | 153 | // Cancel all control messages 154 | write32(input, devId); 155 | write8(input + 0x8, endpoint); 156 | s32 ret = ven.ioctl(USBv5Ioctl::CancelEndpoint, input, 32, nullptr, 0); 157 | 158 | IOS::Free(input); 159 | return static_cast(ret); 160 | } 161 | 162 | USB::USBError USB::CtrlMsg(u32 devId, u8 requestType, u8 request, u16 value, 163 | u16 index, u16 length, void* data) 164 | { 165 | // Must be in a physical = virtual region. 166 | assert((u32)data >= 0x10000000 && (u32)data < 0x14000000); 167 | 168 | if (!aligned(data, 32)) 169 | return USBError::Invalid; 170 | if (length && !data) 171 | return USBError::Invalid; 172 | if (!length && data) 173 | return USBError::Invalid; 174 | 175 | Input* msg = (Input*)IOS::Alloc(sizeof(Input)); 176 | msg->fd = devId; 177 | msg->ctrl = { 178 | .requestType = requestType, 179 | .request = request, 180 | .value = value, 181 | .index = index, 182 | .length = length, 183 | .data = data, 184 | }; 185 | 186 | s32 ret; 187 | if ((requestType & CtrlType::Dir_Mask) == CtrlType::Dir_Host2Device) { 188 | IOS::IVector<2> vec; 189 | IOS_FlushDCache(data, length); 190 | vec.in[0].data = msg; 191 | vec.in[0].len = sizeof(Input); 192 | vec.in[1].data = data; 193 | vec.in[1].len = length; 194 | ret = ven.ioctlv(USBv5Ioctl::CtrlTransfer, vec); 195 | } else { 196 | IOS::IOVector<1, 1> vec; 197 | IOS_FlushDCache(data, length); 198 | vec.in[0].data = msg; 199 | vec.in[0].len = sizeof(Input); 200 | vec.out[0].data = data; 201 | vec.out[0].len = length; 202 | ret = ven.ioctlv(USBv5Ioctl::CtrlTransfer, vec); 203 | } 204 | 205 | IOS::Free(msg); 206 | if ((ret - 8) == length) 207 | return USBError::OK; 208 | 209 | if (ret >= 0) 210 | return USBError::ShortTransfer; 211 | 212 | return static_cast(ret); 213 | } 214 | 215 | USB::USBError USB::IntrBulkMsg(u32 devId, USBv5Ioctl ioctl, u8 endpoint, 216 | u16 length, void* data) 217 | { 218 | // Must be in a physical = virtual region. 219 | assert((u32)data >= 0x10000000 && (u32)data < 0x14000000); 220 | 221 | if (!aligned(data, 32)) 222 | return USBError::Invalid; 223 | if (length && !data) 224 | return USBError::Invalid; 225 | if (!length && data) 226 | return USBError::Invalid; 227 | 228 | Input* msg = (Input*)IOS::Alloc(sizeof(Input)); 229 | msg->fd = devId; 230 | 231 | if (ioctl == USBv5Ioctl::IntrTransfer) { 232 | msg->intr = { 233 | .data = data, 234 | .length = length, 235 | .endpoint = endpoint, 236 | }; 237 | } else if (ioctl == USBv5Ioctl::BulkTransfer) { 238 | msg->bulk = { 239 | .data = data, 240 | .length = length, 241 | .pad = {0}, 242 | .endpoint = endpoint, 243 | }; 244 | } else { 245 | IOS::Free(msg); 246 | return USBError::Invalid; 247 | } 248 | 249 | s32 ret; 250 | if (endpoint & DirEndpointIn) { 251 | IOS::IVector<2> vec; 252 | IOS_FlushDCache(data, length); 253 | vec.in[0].data = msg; 254 | vec.in[0].len = sizeof(Input); 255 | vec.in[1].data = data; 256 | vec.in[1].len = length; 257 | ret = ven.ioctlv(ioctl, vec); 258 | } else { 259 | IOS::IOVector<1, 1> vec; 260 | IOS_FlushDCache(data, length); 261 | vec.in[0].data = msg; 262 | vec.in[0].len = sizeof(Input); 263 | vec.out[0].data = data; 264 | vec.out[0].len = length; 265 | ret = ven.ioctlv(ioctl, vec); 266 | } 267 | 268 | IOS::Free(msg); 269 | if (ret == length) 270 | return USBError::OK; 271 | 272 | if (ret >= 0) 273 | return USBError::ShortTransfer; 274 | 275 | return static_cast(ret); 276 | } 277 | -------------------------------------------------------------------------------- /ios/Disk/USB.hpp: -------------------------------------------------------------------------------- 1 | // USB.hpp - USB2 Device I/O 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | class USB 12 | { 13 | public: 14 | static USB* sInstance; 15 | 16 | enum class USBv5Ioctl { 17 | GetVersion = 0, 18 | GetDeviceChange = 1, 19 | Shutdown = 2, 20 | GetDeviceInfo = 3, 21 | Attach = 4, 22 | Release = 5, 23 | AttachFinish = 6, 24 | SetAlternateSetting = 7, 25 | 26 | SuspendResume = 16, 27 | CancelEndpoint = 17, 28 | CtrlTransfer = 18, 29 | IntrTransfer = 19, 30 | IsoTransfer = 20, 31 | BulkTransfer = 21, 32 | }; 33 | 34 | enum class USBError { 35 | /* Results from IOS or the resource manager */ 36 | OK = IOSError::OK, 37 | NoAccess = IOSError::NoAccess, 38 | Invalid = IOSError::Invalid, 39 | 40 | /* Results from this interface */ 41 | ShortTransfer = -2000, 42 | 43 | /* Results from the host controller */ 44 | Halted = -7102, 45 | }; 46 | 47 | enum class ClassCode : u8 { 48 | HID = 0x03, 49 | MassStorage = 0x8, 50 | }; 51 | 52 | enum class SubClass : u8 { 53 | MassStorage_SCSI = 0x06, 54 | }; 55 | 56 | enum class Protocol : u8 { 57 | MassStorage_BulkOnly = 0x50, 58 | }; 59 | 60 | /* Common bitmasks */ 61 | struct CtrlType { 62 | static constexpr u32 Dir_Mask = (1 << 7); 63 | static constexpr u32 Dir_Host2Device = (0 << 7); 64 | static constexpr u32 Dir_Device2Host = (1 << 7); 65 | 66 | static constexpr u32 TransferType_Mask = 3; 67 | static constexpr u32 TransferType_Control = 0; 68 | static constexpr u32 TransferType_Isochronous = 1; 69 | static constexpr u32 TransferType_Bulk = 2; 70 | static constexpr u32 TransferType_Interrupt = 3; 71 | 72 | static constexpr u32 Rec_Mask = 31; 73 | static constexpr u32 Rec_Device = 0; 74 | static constexpr u32 Rec_Interface = 1; 75 | static constexpr u32 Rec_Endpoint = 2; 76 | static constexpr u32 Rec_Other = 3; 77 | 78 | static constexpr u32 ReqType_Mask = (3 << 5); 79 | static constexpr u32 ReqType_Standard = (0 << 5); 80 | static constexpr u32 ReqType_Class = (1 << 5); 81 | static constexpr u32 ReqType_Vendor = (2 << 5); 82 | static constexpr u32 ReqType_Reserved = (3 << 5); 83 | CtrlType() = delete; 84 | }; 85 | 86 | static constexpr u32 DirEndpointIn = 0x80; 87 | static constexpr u32 DirEndpointOut = 0x00; 88 | 89 | static constexpr u32 MaxDevices = 32; 90 | 91 | struct DeviceEntry { 92 | u32 devId; 93 | u16 vid; 94 | u16 pid; 95 | u16 devNum2; 96 | u8 ifNum; 97 | u8 altSetCount; 98 | }; 99 | static_assert(sizeof(DeviceEntry) == 0xC); 100 | 101 | struct Input { 102 | u32 fd; 103 | u32 heapBuffers; 104 | union { 105 | struct { 106 | u8 requestType; 107 | u8 request; 108 | u16 value; 109 | u16 index; 110 | u16 length; 111 | void* data; 112 | } ctrl; 113 | 114 | struct { 115 | void* data; 116 | u16 length; 117 | u8 pad[4]; 118 | u8 endpoint; 119 | } bulk; 120 | 121 | struct { 122 | void* data; 123 | u16 length; 124 | u8 endpoint; 125 | } intr; 126 | 127 | struct { 128 | void* data; 129 | void* packetSizes; 130 | u8 packets; 131 | u8 endpoint; 132 | } iso; 133 | 134 | struct { 135 | u16 pid; 136 | u16 vid; 137 | } notify; 138 | 139 | u32 args[14]; 140 | }; 141 | }; 142 | 143 | struct DeviceDescriptor { 144 | u8 length; 145 | u8 descType; 146 | u16 usbVer; 147 | u8 devClass; 148 | u8 devSubClass; 149 | u8 devProtocol; 150 | u8 maxPacketSize0; 151 | u16 vid; 152 | u16 pid; 153 | u16 devVer; 154 | u8 manufacturer; 155 | u8 product; 156 | u8 serialNum; 157 | u8 numConfigs; 158 | u8 _12[0x14 - 0x12]; 159 | }; 160 | static_assert(sizeof(DeviceDescriptor) == 0x14); 161 | 162 | struct ConfigDescriptor { 163 | u8 length; 164 | u8 descType; 165 | u16 totalLength; 166 | u8 numInterfaces; 167 | u8 configValue; 168 | u8 config; 169 | u8 attributes; 170 | u8 maxPower; 171 | u8 _9[0xC - 0x9]; 172 | }; 173 | static_assert(sizeof(ConfigDescriptor) == 0xC); 174 | 175 | struct InterfaceDescriptor { 176 | u8 length; 177 | u8 descType; 178 | u8 ifNum; 179 | u8 altSetting; 180 | u8 numEndpoints; 181 | ClassCode ifClass; 182 | SubClass ifSubClass; 183 | Protocol ifProtocol; 184 | u8 interface; 185 | u8 _9[0xC - 0x9]; 186 | }; 187 | static_assert(sizeof(InterfaceDescriptor) == 0xC); 188 | 189 | struct EndpointDescriptor { 190 | u8 length; 191 | u8 descType; 192 | u8 endpointAddr; 193 | u8 attributes; 194 | u16 maxPacketSize; 195 | u8 interval; 196 | u8 _7[0x8 - 0x7]; 197 | }; 198 | static_assert(sizeof(EndpointDescriptor) == 0x8); 199 | 200 | struct DeviceInfo { 201 | u32 devId; 202 | u8 _4[0x14 - 0x04]; 203 | DeviceDescriptor device; 204 | ConfigDescriptor config; 205 | InterfaceDescriptor interface; 206 | EndpointDescriptor endpoint[16]; 207 | }; 208 | static_assert(sizeof(DeviceInfo) == 0xC0); 209 | 210 | USB(s32 id); 211 | 212 | /* 213 | * Initialize the interface. 214 | */ 215 | bool Init(); 216 | 217 | bool IsOpen() const 218 | { 219 | return ven.fd() >= 0; 220 | } 221 | 222 | /* 223 | * Aynchronous call to get the next device change. Sends 'req' to 'queue' 224 | * when GetDeviceChange responds. 225 | * devices - Output device entries, must have USB::MaxDevices entries, 226 | * 32-bit aligned, MEM2 virtual = physical address. 227 | */ 228 | bool EnqueueDeviceChange(DeviceEntry* devices, Queue* queue, 229 | IOS::Request* req); 230 | 231 | /* 232 | * Get USB descriptors for a device. 233 | */ 234 | USBError GetDeviceInfo(u32 devId, DeviceInfo* outInfo, u8 alt = 0); 235 | 236 | /* 237 | * Attaches the provided device to the current handle. 238 | */ 239 | USBError Attach(u32 devId); 240 | 241 | /* 242 | * Releases the provided device from the current handle. 243 | */ 244 | USBError Release(u32 devId); 245 | 246 | /* 247 | * Unlocks the manager from the current handle, triggers change callbacks 248 | * for the other active handles. 249 | */ 250 | USBError AttachFinish(); 251 | 252 | enum class State { 253 | Suspend = 0, 254 | Resume = 1, 255 | }; 256 | 257 | /* 258 | * Suspend or resume a device. Returns Invalid if the new state is the same 259 | * as the current one. 260 | */ 261 | USBError SuspendResume(u32 devId, State state); 262 | 263 | /* 264 | * Cancel ongoing transfer on an endpoint. 265 | */ 266 | USBError CancelEndpoint(u32 devId, u8 endpoint); 267 | 268 | /* 269 | * Read interrupt transfer on the device. 270 | */ 271 | USBError ReadIntrMsg(u32 devId, u8 endpoint, u16 length, void* data) 272 | { 273 | return IntrBulkMsg(devId, USBv5Ioctl::IntrTransfer, endpoint, length, 274 | data); 275 | } 276 | 277 | /* 278 | * Read bulk transfer on the device. 279 | */ 280 | USBError ReadBulkMsg(u32 devId, u8 endpoint, u16 length, void* data) 281 | { 282 | return IntrBulkMsg(devId, USBv5Ioctl::BulkTransfer, endpoint, length, 283 | data); 284 | } 285 | 286 | /* 287 | * Read control message on the device. 288 | */ 289 | USBError ReadCtrlMsg(u32 devId, u8 requestType, u8 request, u16 value, 290 | u16 index, u16 length, void* data) 291 | { 292 | return CtrlMsg(devId, requestType, request, value, index, length, data); 293 | } 294 | 295 | /* 296 | * Write interrupt transfer on the device. 297 | */ 298 | USBError WriteIntrMsg(u32 devId, u8 endpoint, u16 length, void* data) 299 | { 300 | return IntrBulkMsg(devId, USBv5Ioctl::IntrTransfer, endpoint, length, 301 | data); 302 | } 303 | 304 | /* 305 | * Write bulk transfer on the device. 306 | */ 307 | USBError WriteBulkMsg(u32 devId, u8 endpoint, u16 length, void* data) 308 | { 309 | return IntrBulkMsg(devId, USBv5Ioctl::BulkTransfer, endpoint, length, 310 | data); 311 | } 312 | 313 | /* 314 | * Read control message on the device. 315 | */ 316 | USBError WriteCtrlMsg(u32 devId, u8 requestType, u8 request, u16 value, 317 | u16 index, u16 length, void* data) 318 | { 319 | return CtrlMsg(devId, requestType, request, value, index, length, data); 320 | } 321 | 322 | private: 323 | USBError CtrlMsg(u32 devId, u8 requestType, u8 request, u16 value, 324 | u16 index, u16 length, void* data); 325 | 326 | USBError IntrBulkMsg(u32 devId, USBv5Ioctl ioctl, u8 endpoint, u16 length, 327 | void* data); 328 | 329 | IOS::ResourceCtrl ven{-1}; 330 | Thread m_thread; 331 | bool m_reqSent = false; 332 | }; 333 | -------------------------------------------------------------------------------- /ios/Disk/USBStorage.cpp: -------------------------------------------------------------------------------- 1 | // USBStorage.cpp - USB Mass Storage I/O 2 | // 3 | // This file is from MKW-SP 4 | // Copyright (C) 2021-2022 Pablo Stebler 5 | // SPDX-License-Identifier: MIT 6 | // 7 | // Modified by Palapeli for Saoirse 8 | 9 | // Resources: 10 | // - https://www.usb.org/sites/default/files/usbmassbulk_10.pdf 11 | // - 12 | // https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf 13 | // - 14 | // https://www.downtowndougbrown.com/2018/12/usb-mass-storage-with-embedded-devices-tips-and-quirks/ 15 | // - https://github.com/devkitPro/libogc/blob/master/libogc/usbstorage.c 16 | 17 | #include "USBStorage.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | enum { 25 | MSC_GET_MAX_LUN = 0xfe, 26 | }; 27 | 28 | enum { 29 | CSW_SIZE = 0x1f, 30 | CBW_SIZE = 0xd, 31 | }; 32 | 33 | enum { 34 | SCSI_TEST_UNIT_READY = 0x0, 35 | SCSI_REQUEST_SENSE = 0x3, 36 | SCSI_INQUIRY = 0x12, 37 | SCSI_READ_CAPACITY_10 = 0x25, 38 | SCSI_READ_10 = 0x28, 39 | SCSI_WRITE_10 = 0x2a, 40 | SCSI_SYNCHRONIZE_CACHE_10 = 0x35, 41 | }; 42 | 43 | enum { 44 | SCSI_TYPE_DIRECT_ACCESS = 0x0, 45 | }; 46 | 47 | USBStorage::USBStorage(USB* usb, USB::DeviceInfo info) 48 | { 49 | m_buffer = (u8*)IOS::Alloc(0x4000); 50 | m_usb = usb; 51 | m_info = info; 52 | } 53 | 54 | bool USBStorage::GetLunCount(u8* lunCount) 55 | { 56 | u8 requestType = USB::CtrlType::Rec_Interface; 57 | requestType |= USB::CtrlType::ReqType_Class; 58 | requestType |= USB::CtrlType::Dir_Device2Host; 59 | if (m_usb->WriteCtrlMsg(m_id, requestType, MSC_GET_MAX_LUN, 0, m_interface, 60 | 0x1, m_buffer) != USB::USBError::OK) { 61 | PRINT(IOS_USB, ERROR, "WriteCtrlMsg failed"); 62 | return false; 63 | } 64 | *lunCount = read8(m_buffer) + 1; 65 | return *lunCount >= 1 && *lunCount <= 16; 66 | } 67 | 68 | bool USBStorage::SCSITransfer(bool isWrite, u32 size, void* data, u8 lun, 69 | u8 cbSize, void* cb) 70 | { 71 | assert(!!size == !!data); 72 | assert(lun <= 16); 73 | assert(cbSize >= 1 && cbSize <= 16); 74 | assert(cb); 75 | 76 | memset(m_buffer, 0, CSW_SIZE); 77 | 78 | m_tag++; 79 | 80 | write32_le(m_buffer + 0x0, 0x43425355); 81 | write32_le(m_buffer + 0x4, m_tag); 82 | write32_le(m_buffer + 0x8, size); 83 | write8(m_buffer + 0xC, !isWrite << 7); 84 | write8(m_buffer + 0xD, lun); 85 | write8(m_buffer + 0xE, cbSize); 86 | memcpy(m_buffer + 0xF, cb, cbSize); 87 | 88 | if (m_usb->WriteBulkMsg(m_id, m_outEndpoint, CSW_SIZE, m_buffer) != 89 | USB::USBError::OK) { 90 | PRINT(IOS_USB, ERROR, "WriteBulkMsg failed"); 91 | return false; 92 | } 93 | 94 | u32 remainingSize = size; 95 | while (remainingSize > 0) { 96 | u32 chunkSize = std::min(remainingSize, 0x4000); 97 | if (isWrite) { 98 | memcpy(m_buffer, data, chunkSize); 99 | } 100 | if (m_usb->WriteBulkMsg(m_id, isWrite ? m_outEndpoint : m_inEndpoint, 101 | chunkSize, m_buffer) != USB::USBError::OK) { 102 | PRINT(IOS_USB, ERROR, "WriteBulkMsg (2) failed"); 103 | return false; 104 | } 105 | if (!isWrite) { 106 | memcpy(data, m_buffer, chunkSize); 107 | } 108 | remainingSize -= chunkSize; 109 | data += chunkSize; 110 | } 111 | 112 | memset(m_buffer, 0, CBW_SIZE); 113 | 114 | if (m_usb->WriteBulkMsg(m_id, m_inEndpoint, CBW_SIZE, m_buffer) != 115 | USB::USBError::OK) { 116 | PRINT(IOS_USB, ERROR, "WriteBulkMsg (3) failed"); 117 | return false; 118 | } 119 | 120 | if (read32_le(m_buffer + 0x0) != 0x53425355) { 121 | return false; 122 | } 123 | if (read32_le(m_buffer + 0x4) != m_tag) { 124 | return false; 125 | } 126 | if (read32_le(m_buffer + 0x8) != 0) { 127 | return false; 128 | } 129 | if (read8(m_buffer + 0xC) != 0) { 130 | return false; 131 | } 132 | 133 | return true; 134 | } 135 | 136 | bool USBStorage::TestUnitReady(u8 lun) 137 | { 138 | u8 cmd[6] = {0}; 139 | write8(cmd + 0x0, SCSI_TEST_UNIT_READY); 140 | 141 | return SCSITransfer(false, 0, NULL, lun, sizeof(cmd), cmd); 142 | } 143 | 144 | bool USBStorage::Inquiry(u8 lun, u8* type) 145 | { 146 | u8 response[36] = {0}; 147 | u8 cmd[6] = {0}; 148 | write8(cmd + 0x0, SCSI_INQUIRY); 149 | write8(cmd + 0x1, lun << 5); 150 | write8(cmd + 0x4, sizeof(response)); 151 | 152 | if (!SCSITransfer(false, sizeof(response), response, lun, sizeof(cmd), 153 | cmd)) { 154 | return false; 155 | } 156 | 157 | *type = response[0] & 0x1f; 158 | return true; 159 | } 160 | 161 | bool USBStorage::InitLun(u8 lun) 162 | { 163 | if (!TestUnitReady(lun)) { 164 | return false; 165 | } 166 | 167 | u8 type; 168 | if (!Inquiry(lun, &type)) { 169 | return false; 170 | } 171 | return type == SCSI_TYPE_DIRECT_ACCESS; 172 | } 173 | 174 | bool USBStorage::RequestSense(u8 lun) 175 | { 176 | u8 response[18] = {0}; 177 | u8 cmd[6] = {0}; 178 | write8(cmd + 0x0, SCSI_REQUEST_SENSE); 179 | write8(cmd + 0x4, sizeof(response)); 180 | 181 | if (!SCSITransfer(false, sizeof(response), response, lun, sizeof(cmd), 182 | cmd)) { 183 | return false; 184 | } 185 | 186 | PRINT(IOS_USB, INFO, "USBStorage: Sense key: %x", response[0x2] & 0xf); 187 | return true; 188 | } 189 | 190 | bool USBStorage::FindLun(u8 lunCount, u8* lun) 191 | { 192 | for (*lun = 0; *lun < lunCount; (*lun)++) { 193 | for (u32 i = 0; i < 5; i++) { 194 | if (InitLun(*lun)) { 195 | return true; 196 | } 197 | 198 | // This can clear a UNIT ATTENTION condition 199 | RequestSense(*lun); 200 | 201 | usleep(i * 10); 202 | } 203 | } 204 | 205 | return false; 206 | } 207 | 208 | bool USBStorage::ReadCapacity(u8 lun, u32* blockSize) 209 | { 210 | u8 response[8] = {0}; 211 | u8 cmd[10] = {0}; 212 | write8(cmd, SCSI_READ_CAPACITY_10); 213 | 214 | if (!SCSITransfer(false, sizeof(response), response, lun, sizeof(cmd), 215 | cmd)) { 216 | return false; 217 | } 218 | 219 | *blockSize = read32(response + 0x4); 220 | return true; 221 | } 222 | 223 | bool USBStorage::Init() 224 | { 225 | u8 numEndpoints = m_info.interface.numEndpoints; 226 | assert(numEndpoints <= 16); 227 | bool outFound = false; 228 | bool inFound = false; 229 | for (u32 i = 0; i < numEndpoints; i++) { 230 | const USB::EndpointDescriptor* endpointDescriptor = &m_info.endpoint[i]; 231 | 232 | u8 transferType = 233 | endpointDescriptor->attributes & USB::CtrlType::TransferType_Mask; 234 | if (transferType != USB::CtrlType::TransferType_Bulk) { 235 | continue; 236 | } 237 | 238 | u8 direction = 239 | endpointDescriptor->endpointAddr & USB::CtrlType::Dir_Mask; 240 | if (!outFound && direction == USB::CtrlType::Dir_Host2Device) { 241 | m_outEndpoint = endpointDescriptor->endpointAddr; 242 | outFound = true; 243 | m_maxPacketSize = endpointDescriptor->maxPacketSize; 244 | } 245 | if (!inFound && direction == USB::CtrlType::Dir_Device2Host) { 246 | m_inEndpoint = endpointDescriptor->endpointAddr; 247 | inFound = true; 248 | } 249 | } 250 | if (!outFound || !inFound) { 251 | return false; 252 | } 253 | 254 | u16 vendorId = m_info.device.vid; 255 | u16 productId = m_info.device.pid; 256 | PRINT(IOS_USB, INFO, "USBStorage: Found device %x:%x", vendorId, productId); 257 | m_id = m_info.devId; 258 | m_interface = m_info.interface.ifNum; 259 | 260 | PRINT(IOS_USB, INFO, "USBStorage: Max packet size: %d", m_maxPacketSize); 261 | 262 | u8 lunCount; 263 | if (!GetLunCount(&lunCount)) { 264 | return false; 265 | } 266 | PRINT(IOS_USB, INFO, "USBStorage: Device has %d logical unit(s)", lunCount); 267 | 268 | if (!FindLun(lunCount, &m_lun)) { 269 | return false; 270 | } 271 | PRINT(IOS_USB, INFO, "USBStorage: Using logical unit %d", m_lun); 272 | 273 | if (!ReadCapacity(m_lun, &m_blockSize)) { 274 | return false; 275 | } 276 | PRINT(IOS_USB, INFO, "USBStorage: Block size: %d bytes", m_blockSize); 277 | 278 | if (!TestUnitReady(m_lun)) { 279 | return false; 280 | } 281 | 282 | m_valid = true; 283 | return true; 284 | } 285 | 286 | u32 USBStorage::SectorSize() 287 | { 288 | return m_blockSize; 289 | } 290 | 291 | bool USBStorage::ReadSectors(u32 firstSector, u32 sectorCount, void* buffer) 292 | { 293 | assert(sectorCount <= UINT16_MAX); 294 | 295 | for (u32 i = 0; i < 1; i++) { 296 | u8 cmd[10] = {0}; 297 | write8(cmd + 0x0, SCSI_READ_10); 298 | write16(cmd + 0x2, firstSector >> 16); 299 | write16(cmd + 0x4, firstSector & 0xFFFF); 300 | write8(cmd + 0x7, sectorCount >> 8); 301 | write8(cmd + 0x8, sectorCount & 0xFF); 302 | 303 | u32 size = sectorCount * m_blockSize; 304 | if (SCSITransfer(false, size, buffer, m_lun, sizeof(cmd), cmd)) { 305 | return true; 306 | } 307 | 308 | usleep(i * 10); 309 | } 310 | 311 | return false; 312 | } 313 | 314 | bool USBStorage::WriteSectors(u32 firstSector, u32 sectorCount, 315 | const void* buffer) 316 | { 317 | assert(sectorCount <= UINT16_MAX); 318 | 319 | for (u32 i = 0; i < 1; i++) { 320 | u8 cmd[10] = {0}; 321 | write8(cmd + 0x0, SCSI_WRITE_10); 322 | write16(cmd + 0x2, firstSector >> 16); 323 | write16(cmd + 0x4, firstSector & 0xFFFF); 324 | write8(cmd + 0x7, sectorCount >> 8); 325 | write8(cmd + 0x8, sectorCount & 0xFF); 326 | 327 | u32 size = sectorCount * m_blockSize; 328 | if (SCSITransfer(true, size, const_cast(buffer), m_lun, 329 | sizeof(cmd), cmd)) { 330 | return true; 331 | } 332 | 333 | usleep(i * 10); 334 | } 335 | 336 | return false; 337 | } 338 | 339 | bool USBStorage::Sync() 340 | { 341 | u8 cmd[10] = {0}; 342 | write8(cmd, SCSI_SYNCHRONIZE_CACHE_10); 343 | 344 | return SCSITransfer(false, 0, NULL, m_lun, sizeof(cmd), cmd); 345 | } 346 | -------------------------------------------------------------------------------- /ios/Disk/USBStorage.hpp: -------------------------------------------------------------------------------- 1 | // USBStorage.hpp 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | #pragma once 6 | #include 7 | #include 8 | 9 | class USBStorage 10 | { 11 | public: 12 | USBStorage(USB* usb, USB::DeviceInfo info); 13 | 14 | private: 15 | enum class USBStorageError { 16 | OK = 0, 17 | USBHalted = int(USB::USBError::Halted), 18 | }; 19 | 20 | bool GetLunCount(u8* lunCount); 21 | bool SCSITransfer(bool isWrite, u32 size, void* data, u8 lun, u8 cbSize, 22 | void* cb); 23 | bool TestUnitReady(u8 lun); 24 | bool Inquiry(u8 lun, u8* type); 25 | bool InitLun(u8 lun); 26 | bool RequestSense(u8 lun); 27 | bool FindLun(u8 lunCount, u8* lun); 28 | bool ReadCapacity(u8 lun, u32* blockSize); 29 | 30 | public: 31 | bool Init(); 32 | 33 | u32 SectorSize(); 34 | bool ReadSectors(u32 firstSector, u32 sectorCount, void* buffer); 35 | bool WriteSectors(u32 firstSector, u32 sectorCount, const void* buffer); 36 | bool Sync(); 37 | 38 | u32 GetDevID() const 39 | { 40 | return m_info.devId; 41 | } 42 | 43 | private: 44 | USB* m_usb; 45 | USB::DeviceInfo m_info; 46 | bool m_valid = false; 47 | 48 | u32 m_id; 49 | u8 m_interface; 50 | u8 m_outEndpoint; 51 | u8 m_inEndpoint; 52 | 53 | u32 m_maxPacketSize; 54 | u32 m_tag = 0; 55 | u8 m_lun; 56 | u32 m_blockSize; 57 | 58 | u8* m_buffer; 59 | }; 60 | -------------------------------------------------------------------------------- /ios/EmuSDIO/EmuSDIO.hpp: -------------------------------------------------------------------------------- 1 | // EmuSDIO.hpp 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | namespace EmuSDIO 11 | { 12 | 13 | extern int g_emuDevId; 14 | s32 ThreadEntry(void* arg); 15 | 16 | } // namespace EmuSDIO 17 | -------------------------------------------------------------------------------- /ios/FAT/diskio.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------/ 2 | / Low level disk interface modlue include file (C)ChaN, 2019 / 3 | /-----------------------------------------------------------------------*/ 4 | 5 | #ifndef _DISKIO_DEFINED 6 | #define _DISKIO_DEFINED 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #include "ff.h" 13 | 14 | /* Status of Disk Functions */ 15 | typedef BYTE DSTATUS; 16 | 17 | /* Results of Disk Functions */ 18 | typedef enum { 19 | RES_OK = 0, /* 0: Successful */ 20 | RES_ERROR, /* 1: R/W Error */ 21 | RES_WRPRT, /* 2: Write Protected */ 22 | RES_NOTRDY, /* 3: Not Ready */ 23 | RES_PARERR /* 4: Invalid Parameter */ 24 | } DRESULT; 25 | 26 | 27 | /*---------------------------------------*/ 28 | /* Prototypes for disk control functions */ 29 | 30 | 31 | DSTATUS disk_initialize (BYTE pdrv); 32 | DSTATUS disk_status (BYTE pdrv); 33 | DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); 34 | DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); 35 | DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); 36 | 37 | 38 | /* Disk Status Bits (DSTATUS) */ 39 | 40 | #define STA_NOINIT 0x01 /* Drive not initialized */ 41 | #define STA_NODISK 0x02 /* No medium in the drive */ 42 | #define STA_PROTECT 0x04 /* Write protected */ 43 | 44 | 45 | /* Command code for disk_ioctrl fucntion */ 46 | 47 | /* Generic command (Used by FatFs) */ 48 | #define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ 49 | #define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ 50 | #define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ 51 | #define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ 52 | #define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ 53 | 54 | /* Generic command (Not used by FatFs) */ 55 | #define CTRL_POWER 5 /* Get/Set power status */ 56 | #define CTRL_LOCK 6 /* Lock/Unlock media removal */ 57 | #define CTRL_EJECT 7 /* Eject media */ 58 | #define CTRL_FORMAT 8 /* Create physical format on the media */ 59 | 60 | /* MMC/SDC specific ioctl command */ 61 | #define MMC_GET_TYPE 10 /* Get card type */ 62 | #define MMC_GET_CSD 11 /* Get CSD */ 63 | #define MMC_GET_CID 12 /* Get CID */ 64 | #define MMC_GET_OCR 13 /* Get OCR */ 65 | #define MMC_GET_SDSTAT 14 /* Get SD status */ 66 | #define ISDIO_READ 55 /* Read data form SD iSDIO register */ 67 | #define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ 68 | #define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ 69 | 70 | /* ATA/CF specific ioctl command */ 71 | #define ATA_GET_REV 20 /* Get F/W revision */ 72 | #define ATA_GET_MODEL 21 /* Get model name */ 73 | #define ATA_GET_SN 22 /* Get serial number */ 74 | 75 | #ifdef __cplusplus 76 | } 77 | #endif 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /ios/IOS/EmuES.hpp: -------------------------------------------------------------------------------- 1 | // EmuFS.hpp - Proxy ES RM 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | namespace EmuES 12 | { 13 | 14 | ES::ESError DIVerify(u64 titleID, const ES::Ticket* ticket); 15 | s32 ThreadEntry(void* arg); 16 | 17 | } // namespace EmuES 18 | -------------------------------------------------------------------------------- /ios/IOS/IPCLog.cpp: -------------------------------------------------------------------------------- 1 | // IPCLog.cpp - IOS to PowerPC logging through IPC 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "IPCLog.hpp" 7 | #include 8 | #include 9 | #include 10 | 11 | IPCLog* IPCLog::sInstance; 12 | 13 | IPCLog::IPCLog() : m_ipcQueue(8), m_responseQueue(1), m_startRequestQueue(1) 14 | { 15 | s32 ret = IOS_RegisterResourceManager("/dev/saoirse", m_ipcQueue.id()); 16 | if (ret < 0) 17 | AbortColor(YUV_WHITE); 18 | } 19 | 20 | void IPCLog::Print(const char* buffer) 21 | { 22 | IOS::Request* req = m_responseQueue.receive(); 23 | memcpy(req->ioctl.io, buffer, printSize); 24 | req->reply(0); 25 | } 26 | 27 | void IPCLog::Notify(u32 id) 28 | { 29 | IOS::Request* req = m_responseQueue.receive(); 30 | u32 id32 = u32(id); 31 | memcpy(req->ioctl.io, &id32, sizeof(u32)); 32 | req->reply(1); 33 | } 34 | 35 | void IPCLog::SetLaunchState(LaunchError state) 36 | { 37 | IOS::Request* req = m_responseQueue.receive(); 38 | u32 state32 = u32(state); 39 | memcpy(req->ioctl.io, &state32, sizeof(u32)); 40 | req->reply(3); 41 | } 42 | 43 | static void* s_dolAddr = nullptr; 44 | static u32 s_dolSize = 0; 45 | 46 | void IPCLog::HandleRequest(IOS::Request* req) 47 | { 48 | switch (req->cmd) { 49 | case IOS::Command::Open: 50 | if (strcmp(req->open.path, "/dev/saoirse") != 0) { 51 | req->reply(IOSError::NotFound); 52 | break; 53 | } 54 | 55 | if (!Log::ipcLogEnabled) { 56 | req->reply(IOSError::NotFound); 57 | break; 58 | } 59 | 60 | req->reply(IOSError::OK); 61 | break; 62 | 63 | case IOS::Command::Close: 64 | Log::ipcLogEnabled = false; 65 | // Wait for any ongoing requests to finish. TODO: This could be done 66 | // better with a mutex maybe? 67 | usleep(10000); 68 | m_responseQueue.receive()->reply(2); 69 | req->reply(IOSError::OK); 70 | break; 71 | 72 | case IOS::Command::Ioctl: 73 | switch (static_cast(req->ioctl.cmd)) { 74 | case Log::IPCLogIoctl::RegisterPrintHook: 75 | // Read from console 76 | if (req->ioctl.io_len != printSize || !aligned(req->ioctl.in, 32)) { 77 | req->reply(IOSError::Invalid); 78 | break; 79 | } 80 | 81 | // Will reply on next print 82 | m_responseQueue.send(req); 83 | break; 84 | 85 | case Log::IPCLogIoctl::StartGameEvent: 86 | // Start game IOS command 87 | s_dolAddr = req->ioctl.in; 88 | s_dolSize = req->ioctl.in_len; 89 | 90 | m_startRequestQueue.send(0); 91 | req->reply(IOSError::OK); 92 | break; 93 | 94 | case Log::IPCLogIoctl::SetTime: 95 | if (req->ioctl.in_len != sizeof(u32) + sizeof(u64) || 96 | !aligned(req->ioctl.in, 4)) { 97 | req->reply(IOSError::Invalid); 98 | break; 99 | } 100 | 101 | System::SetTime(*reinterpret_cast(req->ioctl.in), 102 | *reinterpret_cast(req->ioctl.in + 4)); 103 | req->reply(IOSError::OK); 104 | break; 105 | 106 | default: 107 | req->reply(IOSError::Invalid); 108 | break; 109 | } 110 | break; 111 | 112 | default: 113 | req->reply(IOSError::Invalid); 114 | break; 115 | } 116 | } 117 | 118 | void IPCLog::Run() 119 | { 120 | while (true) { 121 | IOS::Request* req = m_ipcQueue.receive(); 122 | HandleRequest(req); 123 | } 124 | } 125 | 126 | void IPCLog::WaitForStartRequest(void** dolAddr, u32* dolSize) 127 | { 128 | m_startRequestQueue.receive(); 129 | 130 | *dolAddr = s_dolAddr; 131 | *dolSize = s_dolSize; 132 | } 133 | -------------------------------------------------------------------------------- /ios/IOS/IPCLog.hpp: -------------------------------------------------------------------------------- 1 | // IPCLog.hpp - IOS to PowerPC logging through IPC 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | #include 8 | #include 9 | #include 10 | 11 | class IPCLog 12 | { 13 | public: 14 | static IPCLog* sInstance; 15 | 16 | static constexpr int printSize = 256; 17 | 18 | IPCLog(); 19 | void Run(); 20 | void Print(const char* buffer); 21 | void Notify(u32 id); 22 | void SetLaunchState(LaunchError state); 23 | 24 | void WaitForStartRequest(void** dolAddr, u32* dolSize); 25 | 26 | protected: 27 | void HandleRequest(IOS::Request* req); 28 | 29 | Queue m_ipcQueue; 30 | Queue m_responseQueue; 31 | Queue m_startRequestQueue; 32 | }; 33 | -------------------------------------------------------------------------------- /ios/IOS/Patch.cpp: -------------------------------------------------------------------------------- 1 | // Patch.cpp - IOS kernel patching 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Patch.hpp" 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | u32 ipcThreadPtr = 0; 17 | u32 verifyPubKeyFuncPtr = 0; 18 | bool skipSignCheck = false; 19 | 20 | // clang-format off 21 | ATTRIBUTE_TARGET(arm) 22 | ATTRIBUTE_NOINLINE 23 | ASM_FUNCTION(void InvalidateICacheLine(u32 addr), 24 | // r0 = addr 25 | mcr p15, 0, r0, c7, c5, 1; 26 | bx lr 27 | ) 28 | 29 | ATTRIBUTE_TARGET(thumb) 30 | ASM_FUNCTION(static void IOSOpenStrncpyTrampoline(), 31 | // Overwrite first parameter 32 | str r0, [sp, #0x14]; 33 | ldr r3, =IOSOpenStrncpy; 34 | mov r12, r3; 35 | mov r3, r10; // pid 36 | bx r12; 37 | ) 38 | // clang-format on 39 | 40 | char g_iosOpenStr[64]; 41 | 42 | extern "C" char* IOSOpenStrncpy(char* dest, const char* src, u32 num, s32 pid) 43 | { 44 | strncpy(dest, src, num); 45 | 46 | if (pid != 15) { 47 | // Not PPCBOOT pid 48 | return dest; 49 | } 50 | 51 | strncpy(g_iosOpenStr, src, num); 52 | 53 | if (src[0] != '/') { 54 | if (src[0] == '$' || src[0] == '~') { 55 | // This is our proxy character! 56 | dest[0] = 0; 57 | } 58 | return dest; 59 | } 60 | 61 | if (!strncmp(src, "/dev/", 5)) { 62 | if (!strcmp(src, "/dev/sao_loader")) { 63 | // Disallow opening the loader file RM 64 | dest[0] = 0; 65 | return dest; 66 | } 67 | 68 | if (!strcmp(src, "/dev/flash") || !strcmp(src, "/dev/boot2")) { 69 | // No 70 | dest[0] = 0; 71 | return dest; 72 | } 73 | #if 0 74 | if (!strcmp(src, "/dev/fs")) { 75 | dest[0] = '$'; 76 | return dest; 77 | } 78 | if (!strncmp(src, "/dev/di", 7)) { 79 | dest[0] = '~'; 80 | } 81 | #endif 82 | if (!strcmp(src, "/dev/es")) { 83 | dest[0] = '~'; 84 | return dest; 85 | } 86 | 87 | if (!strcmp(src, "/dev/sdio/slot0")) { 88 | dest[0] = '~'; 89 | return dest; 90 | } 91 | 92 | if (!strcmp(src, "/dev/usb/hid")) { 93 | dest[0] = '~'; 94 | return dest; 95 | } 96 | 97 | return dest; 98 | } 99 | 100 | #if 0 101 | // ISFS path 102 | if (Config::sInstance->IsISFSPathReplaced(src)) 103 | dest[0] = '$'; 104 | #endif 105 | 106 | return dest; 107 | } 108 | 109 | constexpr bool ValidJumptablePtr(u32 address) 110 | { 111 | return address >= 0xFFFF0040 && !(address & 3); 112 | } 113 | 114 | constexpr bool ValidKernelCodePtr(u32 address) 115 | { 116 | return address >= 0xFFFF0040 && (address & 2) != 2; 117 | } 118 | 119 | template 120 | static inline T* ToUncached(T* address) 121 | { 122 | return reinterpret_cast(reinterpret_cast(address) | 0x80000000); 123 | } 124 | 125 | constexpr u16 ThumbBLHi(u32 src, u32 dest) 126 | { 127 | s32 diff = dest - (src + 4); 128 | return ((diff >> 12) & 0x7FF) | 0xF000; 129 | } 130 | 131 | constexpr u16 ThumbBLLo(u32 src, u32 dest) 132 | { 133 | s32 diff = dest - (src + 4); 134 | return ((diff >> 1) & 0x7FF) | 0xF800; 135 | } 136 | 137 | static inline bool IsPPCRegion(const void* ptr) 138 | { 139 | const u32 address = reinterpret_cast(ptr); 140 | return (address < 0x01800000) || 141 | (address >= 0x10000000 && address < 0x13400000); 142 | } 143 | 144 | static u32 FindSyscallTable() 145 | { 146 | u32 undefinedHandler = read32(0xFFFF0024); 147 | if (read32(0xFFFF0004) != 0xE59FF018 || undefinedHandler < 0xFFFF0040 || 148 | undefinedHandler >= 0xFFFFF000 || (undefinedHandler & 3) || 149 | read32(undefinedHandler) != 0xE9CD7FFF) { 150 | PRINT(IOS, ERROR, "FindSyscallTable: Invalid undefined handler"); 151 | abort(); 152 | } 153 | 154 | for (s32 i = 0x300; i < 0x700; i += 4) { 155 | if (read32(undefinedHandler + i) == 0xE6000010 && 156 | ValidJumptablePtr(read32(undefinedHandler + i + 4)) && 157 | ValidJumptablePtr(read32(undefinedHandler + i + 8))) 158 | return read32(undefinedHandler + i + 8); 159 | } 160 | 161 | return 0; 162 | } 163 | 164 | s32 IOSCVerifySignHook(u8* inputData, u32 inputSize, int publicHandle, 165 | u8* signData) 166 | { 167 | if (!skipSignCheck) { 168 | return (*(s32(*)(u8*, u32, int, u8*))verifyPubKeyFuncPtr)( 169 | inputData, inputSize, publicHandle, signData); 170 | } 171 | 172 | return 0; 173 | } 174 | 175 | void PatchIOSOpen() 176 | { 177 | PRINT(IOS, WARN, "The search for IOS_Open syscall"); 178 | 179 | u32 jumptable = FindSyscallTable(); 180 | if (jumptable == 0) { 181 | PRINT(IOS, ERROR, "Could not find syscall table"); 182 | abort(); 183 | } 184 | 185 | // Get the pointer to the IOSC_VerifyPublicKeySign syscall 186 | u32 addr = jumptable + 0x6C * 4; 187 | assert(ValidJumptablePtr(addr)); 188 | verifyPubKeyFuncPtr = read32(addr); 189 | write32(addr, (u32)&IOSCVerifySignHook); 190 | PRINT(IOS, INFO, "Replaced IOSC_VerifyPublicKeySign"); 191 | 192 | addr = jumptable + 0x1C * 4; 193 | assert(ValidJumptablePtr(addr)); 194 | addr = read32(addr); 195 | assert(ValidKernelCodePtr(addr)); 196 | addr &= ~1; // Remove thumb bit 197 | 198 | ipcThreadPtr = read32(addr - 0x1C); 199 | 200 | // Search backwards for the bytes to patch 201 | for (int i = 0; i < 0x180; i += 2) { 202 | if (read16(addr - i) == 0x1C6A && read16(addr - i - 2) == 0x58D0) { 203 | write16(addr - i + 2, 204 | ThumbBLHi(addr - i + 2, 205 | (u32)ToUncached(&IOSOpenStrncpyTrampoline))); 206 | write16(addr - i + 4, 207 | ThumbBLLo(addr - i + 2, 208 | (u32)ToUncached(&IOSOpenStrncpyTrampoline))); 209 | 210 | PRINT(IOS, WARN, "Patched %08X = %04X%04X", addr - i + 2, 211 | read16(addr - i + 2), read16(addr - i + 4)); 212 | 213 | // IOS automatically aligns flush 214 | IOS_FlushDCache((void*)(addr - i + 2), 4); 215 | InvalidateICacheLine(round_down(addr - i + 2, 32)); 216 | InvalidateICacheLine(round_down(addr - i + 2, 32) + 32); 217 | return; 218 | } 219 | } 220 | 221 | PRINT(IOS, ERROR, "Could not find IOS_Open instruction to patch"); 222 | } 223 | 224 | static bool CheckImportKeyFunction(u32 addr) 225 | { 226 | if (read16(addr) == 0xB5F0 && read16(addr + 0x12) == 0x2600 && 227 | read16(addr + 0x14) == 0x281F && read16(addr + 0x16) == 0xD806) { 228 | return true; 229 | } 230 | return false; 231 | } 232 | 233 | static u32 FindImportKeyFunction() 234 | { 235 | // Check known addresses 236 | 237 | if (CheckImportKeyFunction(0x13A79C58)) { 238 | return 0x13A79C58 + 1; 239 | } 240 | 241 | if (CheckImportKeyFunction(0x13A79918)) { 242 | return 0x13A79918 + 1; 243 | } 244 | 245 | for (int i = 0; i < 0x1000; i += 2) { 246 | u32 addr = 0x13A79500 + i; 247 | if (CheckImportKeyFunction(addr)) { 248 | return addr + 1; 249 | } 250 | } 251 | 252 | return 0; 253 | } 254 | 255 | const u8 KoreanCommonKey[] = { 256 | 0x63, 0xb8, 0x2b, 0xb4, 0xf4, 0x61, 0x4e, 0x2e, 257 | 0x13, 0xf2, 0xfe, 0xfb, 0xba, 0x4c, 0x9b, 0x7e, 258 | }; 259 | 260 | void ImportKoreanCommonKey() 261 | { 262 | u32 func = FindImportKeyFunction(); 263 | 264 | if (func == 0) { 265 | PRINT(IOS, ERROR, "Could not find import key function"); 266 | return; 267 | } 268 | 269 | PRINT(IOS, WARN, "Found import key function at 0x%08X", func); 270 | 271 | // Call function by address 272 | (*(void (*)(int keyIndex, const u8* key, u32 keySize))func)( 273 | 11, KoreanCommonKey, sizeof(KoreanCommonKey)); 274 | } 275 | 276 | constexpr u32 MakePPCBranch(u32 src, u32 dest) 277 | { 278 | return ((dest - src) & 0x03FFFFFC) | 0x48000000; 279 | } 280 | 281 | bool IsWiiU() 282 | { 283 | // Read LT_CHIPREVID; this will read zero on a normal Wii. 284 | // Note: this _does_ work without system mode, since the hardware registers 285 | // are mapped as read-only. 286 | return (read32(0x0D8005A0) >> 16) == 0xCAFE; 287 | } 288 | 289 | // Credit: marcan 290 | // https://fail0verflow.com/blog/2013/espresso/ 291 | bool ResetEspresso(u32 entry) 292 | { 293 | PRINT(IOS, WARN, "Resetting Espresso..."); 294 | 295 | DASSERT(IsWiiU()); 296 | if (!IsWiiU()) { 297 | PRINT(IOS, ERROR, "This reset can only be used on Wii U!"); 298 | return false; 299 | } 300 | 301 | DASSERT(in_mem1(entry)); 302 | if (!in_mem1(entry)) { 303 | PRINT(IOS, ERROR, "Invalid entry point: 0x%08X! Must be in MEM1!", 304 | entry); 305 | return false; 306 | } 307 | 308 | // Disable this until the PPC has started up again. 309 | bool ipcLogEnabledSave = Log::ipcLogEnabled; 310 | Log::ipcLogEnabled = false; 311 | 312 | // Reading the TMD for the boot index would be the proper way to get this 313 | // path, but that's more work! I don't think this path has ever changed and 314 | // I really doubt that it ever will. 315 | // Regardless, this should be fixed at some point. 316 | s32 ret = IOS_LaunchElf("/title/00000001/00000200/content/00000003.app"); 317 | if (ret != IOSError::OK) { 318 | PRINT(IOS, ERROR, "IOS_LaunchElf fail: %d", ret); 319 | return false; 320 | } 321 | 322 | PRINT(IOS, INFO, "Now watching for decryption..."); 323 | constexpr u32 FirstAddr = 0x01330418; 324 | constexpr u32 FirstAddrValue = 0x48000129; 325 | 326 | while (true) { 327 | IOS_InvalidateDCache((void*)FirstAddr, sizeof(u32)); 328 | 329 | // Check if the first instruction has been decrypted. The block has 330 | // already been hashed by this point. 331 | if (read32(FirstAddr) == FirstAddrValue) { 332 | PRINT(IOS, INFO, "Decrypted!"); 333 | 334 | write32(FirstAddr, MakePPCBranch(FirstAddr, entry)); 335 | IOS_FlushDCache((void*)FirstAddr, sizeof(u32)); 336 | 337 | PRINT(IOS, WARN, "Patched %08X = %08X", FirstAddr, 338 | MakePPCBranch(FirstAddr, entry)); 339 | break; 340 | } 341 | } 342 | 343 | // PPC has started up again so reenable the IPC log. 344 | Log::ipcLogEnabled = ipcLogEnabledSave; 345 | return true; 346 | } 347 | -------------------------------------------------------------------------------- /ios/IOS/Patch.hpp: -------------------------------------------------------------------------------- 1 | // Patch.hpp - IOS kernel patching 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | void PatchIOSOpen(); 11 | void ImportKoreanCommonKey(); 12 | bool IsWiiU(); 13 | bool ResetEspresso(u32 entry); 14 | 15 | extern u32 ipcThreadPtr; 16 | extern bool skipSignCheck; 17 | 18 | extern char g_iosOpenStr[64]; 19 | -------------------------------------------------------------------------------- /ios/IOS/RMConfig.s: -------------------------------------------------------------------------------- 1 | // RMConfig.s - IOS module configuration 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #define IOS_NOTE_START() \ 7 | .text; \ 8 | .arm; \ 9 | .balign 4; \ 10 | .section .ios_note; \ 11 | elf_note:; \ 12 | .long 0; /* Name size */ \ 13 | .long ios_note_end - ios_note; \ 14 | .long 0; /* Note type */ \ 15 | ios_note: 16 | 17 | #define IOS_NOTE(pid, entry_point, prio, stack_size, stack_top) \ 18 | .long 0xB; \ 19 | .long pid; \ 20 | .long 9; \ 21 | .long entry_point; \ 22 | .long 0x7D; \ 23 | .long prio; \ 24 | .long 0x7E; \ 25 | .long stack_size; \ 26 | .long 0x7F; \ 27 | .long stack_top 28 | 29 | #define IOS_NOTE_END() \ 30 | .size elf_note, . - elf_note; \ 31 | ios_note_end: 32 | 33 | #define IOS_STACK(name, sSize) \ 34 | .bss; \ 35 | .balign 32; \ 36 | .global name; \ 37 | name:; \ 38 | .space sSize; \ 39 | .size name, sSize 40 | 41 | IOS_STACK(EntryStack, 0x400); 42 | 43 | IOS_NOTE_START(); 44 | IOS_NOTE( 45 | 0, 46 | Entry, 47 | 127, 48 | 0x400, 49 | EntryStack + 0x400 50 | ); 51 | IOS_NOTE_END(); 52 | -------------------------------------------------------------------------------- /ios/IOS/Syscalls.h: -------------------------------------------------------------------------------- 1 | // Syscalls.h - IOS system call definitions 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | 11 | #define IOS_SUCCESS 0 12 | #define IOS_EACCES -1 13 | #define IOS_EEXIST -2 14 | #define IOS_EINVAL -4 15 | #define IOS_EMAX -5 16 | #define IOS_ENOENT -6 17 | #define IOS_EQUEUEFULL -8 18 | #define IOS_EIO -12 19 | #define IOS_ENOMEM -22 20 | 21 | EXTERN_C_START 22 | 23 | /* <---------- 24 | * IOS Thread 25 | * ----------> */ 26 | typedef s32 (*IOSThreadProc)(void* arg); 27 | 28 | s32 IOS_CreateThread(IOSThreadProc proc, void* arg, u32* stack_top, 29 | u32 stacksize, s32 priority, bool detached); 30 | s32 IOS_JoinThread(s32 threadid, void** value); 31 | s32 IOS_CancelThread(s32 threadid, void* value); 32 | s32 IOS_GetThreadId(void); 33 | s32 IOS_GetProcessId(void); 34 | s32 IOS_StartThread(s32 threadid); 35 | s32 IOS_SuspendThread(s32 threadid); 36 | void IOS_YieldThread(void); 37 | u32 IOS_GetThreadPriority(s32 threadid); 38 | s32 IOS_SetThreadPriority(s32 threadid, u32 priority); 39 | 40 | /* <---------- 41 | * IOS Message 42 | * ----------> */ 43 | s32 IOS_CreateMessageQueue(u32* buf, u32 msg_count); 44 | s32 IOS_DestroyMessageQueue(s32 queue_id); 45 | s32 IOS_SendMessage(s32 queue_id, u32 message, u32 flags); 46 | s32 IOS_JamMessage(s32 queue_id, u32 message, u32 flags); 47 | s32 IOS_ReceiveMessage(s32 queue_id, u32* message, u32 flags); 48 | 49 | /* <---------- 50 | * IOS Timer 51 | * ----------> */ 52 | s32 IOS_CreateTimer(s32 usec, s32 repeat_usec, s32 queue, u32 msg); 53 | s32 IOS_RestartTimer(s32 timer, s32 usec, s32 repeat_usec); 54 | s32 IOS_StopTimer(s32 timer); 55 | s32 IOS_DestroyTimer(s32 timer); 56 | u32 IOS_GetTime(); 57 | 58 | /* <---------- 59 | * IOS Memory 60 | * ----------> */ 61 | s32 IOS_CreateHeap(void* ptr, s32 length); 62 | s32 IOS_DestroyHeap(s32 heap); 63 | void* IOS_Alloc(s32 heap, u32 length); 64 | void* IOS_AllocAligned(s32 heap, u32 length, u32 align); 65 | s32 IOS_Free(s32 heap, void* ptr); 66 | 67 | /* <---------- 68 | * IPC (Inter-process communication) 69 | * ----------> */ 70 | #define IOS_OPEN 1 71 | #define IOS_CLOSE 2 72 | #define IOS_READ 3 73 | #define IOS_WRITE 4 74 | #define IOS_SEEK 5 75 | #define IOS_IOCTL 6 76 | #define IOS_IOCTLV 7 77 | #define IOS_IPC_REPLY 8 78 | 79 | #define IOS_OPEN_NONE 0 80 | #define IOS_OPEN_READ 1 81 | #define IOS_OPEN_WRITE 2 82 | #define IOS_OPEN_RW (IOS_OPEN_READ | IOS_OPEN_WRITE) 83 | 84 | #define IOS_SEEK_SET 0 85 | #define IOS_SEEK_CUR 1 86 | #define IOS_SEEK_END 2 87 | 88 | typedef struct { 89 | void* data; 90 | u32 len; 91 | } IOVector; 92 | 93 | typedef struct { 94 | u32 cmd; 95 | s32 result; 96 | union { 97 | s32 fd; 98 | s32 handle; 99 | }; 100 | union { 101 | struct { 102 | char* path; 103 | u32 mode; 104 | u32 uid; 105 | u16 gid; 106 | } open; 107 | 108 | struct { 109 | void* data; 110 | u32 len; 111 | } read, write; 112 | 113 | struct { 114 | s32 where; 115 | s32 whence; 116 | } seek; 117 | 118 | struct { 119 | u32 cmd; 120 | void* in; 121 | u32 in_len; 122 | void* io; 123 | u32 io_len; 124 | } ioctl; 125 | 126 | struct { 127 | u32 cmd; 128 | u32 in_count; 129 | u32 io_count; 130 | IOVector* vec; 131 | } ioctlv; 132 | 133 | u32 args[5]; 134 | }; 135 | } IOSRequest; 136 | 137 | s32 IOS_Open(const char* path, u32 mode); 138 | s32 IOS_OpenAsync(const char* path, u32 mode, s32 queue_id, IOSRequest* msg); 139 | s32 IOS_Close(s32 fd); 140 | s32 IOS_CloseAsync(s32 fd, s32 queue_id, IOSRequest* msg); 141 | 142 | s32 IOS_Seek(s32 fd, s32 where, s32 whence); 143 | s32 IOS_SeekAsync(s32 fd, s32 where, s32 whence, s32 queue_id, IOSRequest* msg); 144 | s32 IOS_Read(s32 fd, void* buf, s32 len); 145 | s32 IOS_ReadAsync(s32 fd, void* buf, s32 len, s32 queue_id, IOSRequest* msg); 146 | s32 IOS_Write(s32 fd, const void* buf, s32 len); 147 | s32 IOS_WriteAsync(s32 fd, const void* buf, s32 len, s32 queue_id, 148 | IOSRequest* msg); 149 | 150 | s32 IOS_Ioctl(s32 fd, u32 command, void* in, u32 in_len, void* io, u32 io_len); 151 | s32 IOS_IoctlAsync(s32 fd, u32 command, void* in, u32 in_len, void* io, 152 | u32 io_len, s32 queue_id, IOSRequest* msg); 153 | 154 | s32 IOS_Ioctlv(s32 fd, u32 command, u32 in_cnt, u32 out_cnt, IOVector* vec); 155 | s32 IOS_IoctlvAsync(s32 fd, u32 command, u32 in_cnt, u32 out_cnt, IOVector* vec, 156 | s32 queue_id, IOSRequest* msg); 157 | 158 | s32 IOS_RegisterResourceManager(const char* device, s32 queue_id); 159 | s32 IOS_ResourceReply(const IOSRequest* request, s32 reply); 160 | 161 | /* <---------- 162 | * ARM Memory and Cache 163 | * ----------> */ 164 | void IOS_InvalidateDCache(void* address, u32 size); 165 | void IOS_FlushDCache(const void* address, u32 size); 166 | void* IOS_VirtualToPhysical(void* virt); 167 | 168 | /* <---------- 169 | * Misc IOS 170 | * ----------> */ 171 | s32 IOS_SetPPCACRPerms(u8 enable); 172 | s32 IOS_SetIpcAccessRights(u8* rights); 173 | s32 IOS_SetUid(u32 pid, u32 uid); 174 | u32 IOS_GetUid(); 175 | s32 IOS_SetGid(u32 pid, u16 gid); 176 | u16 IOS_GetGid(); 177 | s32 IOS_LaunchElf(const char* path); 178 | s32 IOS_LaunchRM(const char* path); 179 | 180 | EXTERN_C_END 181 | -------------------------------------------------------------------------------- /ios/IOS/Syscalls.s: -------------------------------------------------------------------------------- 1 | // Syscalls.s - IOS system calls 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | .section ".text" 7 | .arm 8 | .align 2 9 | 10 | #define SECT(ARG) .syscall ## ARG 11 | 12 | #define DEF_SYSCALL(name, id) \ 13 | .global name; \ 14 | .section SECT(.name), "ax", %progbits; \ 15 | .type name, function; \ 16 | .align 2; \ 17 | name:; \ 18 | .word 0xE6000010 | id << 5; \ 19 | bx lr; \ 20 | .size name, .- name 21 | 22 | /* System calls start */ 23 | DEF_SYSCALL( IOS_CreateThread, 0x00 ) 24 | DEF_SYSCALL( IOS_JoinThread, 0x01 ) 25 | DEF_SYSCALL( IOS_CancelThread, 0x02 ) 26 | DEF_SYSCALL( IOS_GetThreadId, 0x03 ) 27 | DEF_SYSCALL( IOS_GetProcessId, 0x04 ) 28 | DEF_SYSCALL( IOS_StartThread, 0x05 ) 29 | DEF_SYSCALL( IOS_SuspendThread, 0x06 ) 30 | DEF_SYSCALL( IOS_YieldThread, 0x07 ) 31 | DEF_SYSCALL( IOS_GetThreadPriority, 0x08 ) 32 | DEF_SYSCALL( IOS_SetThreadPriority, 0x09 ) 33 | 34 | DEF_SYSCALL( IOS_CreateMessageQueue, 0x0A ) 35 | DEF_SYSCALL( IOS_DestroyMessageQueue, 0x0B ) 36 | DEF_SYSCALL( IOS_SendMessage, 0x0C ) 37 | DEF_SYSCALL( IOS_JamMessage, 0x0D ) 38 | DEF_SYSCALL( IOS_ReceiveMessage, 0x0E ) 39 | 40 | DEF_SYSCALL( IOS_HandleEvent, 0x0F ) 41 | DEF_SYSCALL( IOS_UnregisterEventHandler, 0x10 ) 42 | DEF_SYSCALL( IOS_CreateTimer, 0x11 ) 43 | DEF_SYSCALL( IOS_RestartTimer, 0x12 ) 44 | DEF_SYSCALL( IOS_StopTimer, 0x13 ) 45 | DEF_SYSCALL( IOS_DestroyTimer, 0x14 ) 46 | DEF_SYSCALL( IOS_GetTime, 0x15 ) 47 | 48 | DEF_SYSCALL( IOS_CreateHeap, 0x16 ) 49 | DEF_SYSCALL( IOS_DestroyHeap, 0x17 ) 50 | DEF_SYSCALL( IOS_Alloc, 0x18 ) 51 | DEF_SYSCALL( IOS_AllocAligned, 0x19 ) 52 | DEF_SYSCALL( IOS_Free, 0x1A ) 53 | 54 | DEF_SYSCALL( IOS_RegisterResourceManager, 0x1B ) 55 | DEF_SYSCALL( IOS_Open, 0x1C ) 56 | DEF_SYSCALL( IOS_Close, 0x1D ) 57 | DEF_SYSCALL( IOS_Read, 0x1E ) 58 | DEF_SYSCALL( IOS_Write, 0x1F ) 59 | DEF_SYSCALL( IOS_Seek, 0x20 ) 60 | DEF_SYSCALL( IOS_Ioctl, 0x21 ) 61 | DEF_SYSCALL( IOS_Ioctlv, 0x22 ) 62 | DEF_SYSCALL( IOS_OpenAsync, 0x23 ) 63 | DEF_SYSCALL( IOS_CloseAsync, 0x24 ) 64 | DEF_SYSCALL( IOS_ReadAsync, 0x25 ) 65 | DEF_SYSCALL( IOS_WriteAsync, 0x26 ) 66 | DEF_SYSCALL( IOS_SeekAsync, 0x27 ) 67 | DEF_SYSCALL( IOS_IoctlAsync, 0x28 ) 68 | DEF_SYSCALL( IOS_IoctlvAsync, 0x29 ) 69 | DEF_SYSCALL( IOS_ResourceReply, 0x2A ) 70 | 71 | DEF_SYSCALL( IOS_SetUid, 0x2B ) 72 | DEF_SYSCALL( IOS_GetUid, 0x2C ) 73 | DEF_SYSCALL( IOS_SetGid, 0x2D ) 74 | DEF_SYSCALL( IOS_GetGid, 0x2E ) 75 | 76 | DEF_SYSCALL( IOS_InvalidateDCache, 0x3F ) 77 | DEF_SYSCALL( IOS_FlushDCache, 0x40 ) 78 | DEF_SYSCALL( IOS_LaunchElf, 0x41 ) 79 | DEF_SYSCALL( IOS_LaunchOS, 0x42 ) 80 | DEF_SYSCALL( IOS_LaunchOSFromMemory, 0x43 ) 81 | DEF_SYSCALL( IOS_SetVersion, 0x4C ) 82 | DEF_SYSCALL( IOS_GetVersion, 0x4D ) 83 | DEF_SYSCALL( IOS_VirtualToPhysical, 0x4F ) 84 | DEF_SYSCALL( IOS_SetPPCACRPerms, 0x54 ) 85 | DEF_SYSCALL( IOS_SetIpcAccessRights, 0x59 ) 86 | DEF_SYSCALL( IOS_LaunchRM, 0x5A ) 87 | -------------------------------------------------------------------------------- /ios/IOS/System.hpp: -------------------------------------------------------------------------------- 1 | // System.hpp - Saoirse IOS system 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | class System 13 | { 14 | public: 15 | static void SetHeap(s32 hid) 16 | { 17 | s_heapId = hid; 18 | } 19 | 20 | static s32 GetHeap() 21 | { 22 | return s_heapId; 23 | } 24 | 25 | static void SetTime(u32 hwTimerVal, u64 epoch); 26 | static u64 GetTime(); 27 | 28 | static void* UnalignedMemcpy(void* dest, const void* src, size_t len); 29 | 30 | public: 31 | static s32 s_heapId; 32 | static void* s_dolData; 33 | static u32 s_dolSize; 34 | static u8 s_dolHash[0x14]; 35 | }; 36 | 37 | #define YUV_RED ((84 << 24) | (255 << 16) | (76 << 8)) 38 | #define YUV_DARK_RED ((106 << 24) | (192 << 16) | (38 << 8)) 39 | #define YUV_GREEN ((43 << 24) | (21 << 16) | (149 << 8)) 40 | #define YUV_DARK_GREEN ((85 << 24) | (74 << 16) | (75 << 8)) 41 | #define YUV_BLUE ((255 << 24) | (107 << 16) | (29 << 8)) 42 | #define YUV_DARK_BLUE ((192 << 24) | (117 << 16) | (14 << 8)) 43 | #define YUV_PINK ((170 << 24) | (181 << 16) | (180 << 8)) 44 | #define YUV_PURPLE ((170 << 24) | (181 << 16) | (52 << 8)) 45 | #define YUV_CYAN ((149 << 24) | (64 << 16) | (89 << 8)) 46 | #define YUV_YELLOW ((0 << 24) | (148 << 16) | (225 << 8)) 47 | #define YUV_DARK_YELLOW ((64 << 24) | (138 << 16) | (113 << 8)) 48 | #define YUV_WHITE ((128 << 24) | (128 << 16) | (255 << 8)) 49 | #define YUV_GRAY ((128 << 24) | (128 << 16) | (128 << 8)) 50 | 51 | #define YUV_0 YUV_RED 52 | #define YUV_1 YUV_DARK_RED 53 | #define YUV_2 YUV_GREEN 54 | #define YUV_3 YUV_DARK_GREEN 55 | #define YUV_4 YUV_BLUE 56 | #define YUV_5 YUV_DARK_BLUE 57 | #define YUV_6 YUV_PINK 58 | #define YUV_7 YUV_PURPLE 59 | #define YUV_8 YUV_CYAN 60 | #define YUV_9 YUV_YELLOW 61 | #define YUV_10 YUV_DARK_YELLOW 62 | #define YUV_11 YUV_WHITE 63 | #define YUV_12 YUV_GRAY 64 | 65 | void AbortColor(u32 color); 66 | void KernelWrite(u32 address, u32 value); 67 | 68 | EXTERN_C_START 69 | void abort(); 70 | void usleep(u32 usec); 71 | EXTERN_C_END 72 | 73 | #ifndef NDEBUG 74 | 75 | #define ASSERT assert 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /ios/System/Config.cpp: -------------------------------------------------------------------------------- 1 | // Config.cpp - Saoirse config 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #include "Config.hpp" 7 | #include 8 | 9 | Config* Config::sInstance; 10 | 11 | bool Config::IsISFSPathReplaced(const char* path) 12 | { 13 | // A list of paths to be replaced will be provided by the channel in the 14 | // future. 15 | if (strncmp(path, "/title/00010000/", sizeof("/title/00010000/") - 1) == 0) 16 | return true; 17 | if (strncmp(path, "/title/00010004/", sizeof("/title/00010004/") - 1) == 0) 18 | return true; 19 | 20 | return false; 21 | } 22 | 23 | bool Config::IsFileLogEnabled() 24 | { 25 | return true; 26 | } 27 | 28 | bool Config::BlockIOSReload() 29 | { 30 | return m_blockIOSReload; 31 | // return false; 32 | } 33 | -------------------------------------------------------------------------------- /ios/System/Config.hpp: -------------------------------------------------------------------------------- 1 | // Config.hpp - Saoirse config 2 | // Written by Palapeli 3 | // 4 | // SPDX-License-Identifier: MIT 5 | 6 | #pragma once 7 | 8 | // Config is currently hardcoded 9 | 10 | class Config 11 | { 12 | public: 13 | static Config* sInstance; 14 | 15 | bool IsISFSPathReplaced(const char* path); 16 | bool IsFileLogEnabled(); 17 | bool BlockIOSReload(); 18 | 19 | bool m_blockIOSReload = false; 20 | }; 21 | -------------------------------------------------------------------------------- /ios_loader.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_ARCH(arm) 2 | ENTRY(LoaderEntry) 3 | 4 | MEMORY { 5 | ram(rwx) : ORIGIN = 0x10100000, LENGTH = 0x40000 6 | } 7 | 8 | SECTIONS 9 | { 10 | .text : { 11 | *(.start*) 12 | *(.text*) 13 | *(.syscall*) 14 | __data_start = .; 15 | *(.data*) 16 | *(.rodata*) 17 | __bss_start = .; 18 | *(.bss*) 19 | *(.sbss*) 20 | __bss_end = .; 21 | __data_end = .; 22 | } > ram = 0 23 | 24 | /DISCARD/ : 25 | { 26 | *(.ARM*) 27 | *(.comment) 28 | } 29 | } 30 | 31 | PROVIDE(__end__ = .); 32 | PROVIDE(__exidx_start = .); 33 | PROVIDE(__exidx_end = .); --------------------------------------------------------------------------------