├── .gitattributes ├── .github └── workflows │ └── ccpp.yml ├── LICENSE ├── Makefile ├── README.md ├── icon.jpg ├── include ├── arcStructs.h ├── console.h └── ftp.h ├── libs ├── include │ ├── cover.h │ ├── zbuff.h │ ├── zdict.h │ ├── zstd.h │ └── zstd_errors.h └── lib │ └── libzstd.a └── source ├── arcReader.h ├── console.c ├── dumper.h ├── ftp.c ├── ftp_main.h ├── main.cpp ├── menu.h ├── mod_installer.h └── utils.h /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h eol=lf 2 | *.c eol=lf 3 | *.cpp eol=lf 4 | -------------------------------------------------------------------------------- /.github/workflows/ccpp.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | container: devkitpro/devkita64:latest 9 | steps: 10 | - uses: actions/checkout@master 11 | - name: make 12 | run: make LATESTTAG=Beta 13 | - name: Upload build artifact 14 | uses: actions/upload-artifact@v1 15 | with: 16 | name: binary 17 | path: Ultimate_Mod_Manager.nro 18 | upload: 19 | runs-on: ubuntu-latest 20 | needs: build 21 | steps: 22 | - uses: actions/download-artifact@v1 23 | with: 24 | name: binary 25 | - name: Upload Release 26 | uses: Genwald/create-release@eba889c 27 | with: 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | name: beta 30 | code: beta 31 | body: Latest commit, automatically updated. May not be fully tested or fully working. 32 | prerelease: true 33 | recreate: true 34 | assets: binary/Ultimate_Mod_Manager.nro:Ultimate_Mod_Manager.nro:application/octet-stream 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Genwald 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 | #--------------------------------------------------------------------------------- 2 | .SUFFIXES: 3 | #--------------------------------------------------------------------------------- 4 | 5 | ifeq ($(strip $(DEVKITPRO)),) 6 | $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") 7 | endif 8 | 9 | TOPDIR ?= $(CURDIR) 10 | include $(DEVKITPRO)/libnx/switch_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 | # DATA is a list of directories containing data files 17 | # INCLUDES is a list of directories containing header files 18 | # EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". 19 | # ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) 20 | # 21 | # NO_ICON: if set to anything, do not use icon. 22 | # NO_NACP: if set to anything, no .nacp file is generated. 23 | # APP_TITLE is the name of the app stored in the .nacp file (Optional) 24 | # APP_AUTHOR is the author of the app stored in the .nacp file (Optional) 25 | # APP_VERSION is the version of the app stored in the .nacp file (Optional) 26 | # APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) 27 | # ICON is the filename of the icon (.jpg), relative to the project folder. 28 | # If not set, it attempts to use one of the following (in this order): 29 | # - .jpg 30 | # - icon.jpg 31 | # - /default_icon.jpg 32 | #--------------------------------------------------------------------------------- 33 | 34 | GITREV := $(shell git rev-parse HEAD 2>/dev/null) 35 | GITREV_SHORT := $(shell git rev-parse HEAD 2>/dev/null | cut -c1-8) 36 | LATESTTAG := $(shell git describe --tags $(shell git rev-list --tags --max-count=1 2>/dev/null) 2>/dev/null) 37 | 38 | APP_TITLE := Ultimate Mod Manager 39 | APP_AUTHOR := Genwald, jugeeya, jam1garner 40 | ICON := icon.jpg 41 | APP_VERSION := $(LATESTTAG) 42 | 43 | ifneq ($(strip $(GITREV)),) 44 | GITTAG := $(shell git describe --tags $(GITREV) 2>/dev/null) 45 | ifneq ($(strip $(GITTAG)),$(strip $(LATESTTAG))) 46 | APP_VERSION := $(APP_VERSION)-$(GITREV_SHORT) 47 | endif 48 | endif 49 | 50 | TARGET := $(subst $e ,_,$(notdir $(APP_TITLE))) 51 | OUTDIR := out 52 | BUILD := build 53 | SOURCES := source 54 | DATA := data 55 | INCLUDES := include 56 | EXEFS_SRC := exefs_src 57 | #ROMFS := romfs 58 | 59 | #--------------------------------------------------------------------------------- 60 | # options for code generation 61 | #--------------------------------------------------------------------------------- 62 | ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE 63 | 64 | CFLAGS := -g -Wall -O3 -ffunction-sections \ 65 | $(ARCH) $(DEFINES) 66 | 67 | CFLAGS += $(INCLUDE) -D__SWITCH__ -DVERSION_STRING="\"$(APP_VERSION)\"" 68 | 69 | CXXFLAGS := $(CFLAGS) -fno-rtti -std=c++17 70 | 71 | ASFLAGS := -g $(ARCH) 72 | LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-no-as-needed,-Map,$(notdir $*.map) 73 | 74 | LIBS := -lnx -lstdc++fs -lmbedcrypto -lzstd 75 | 76 | #--------------------------------------------------------------------------------- 77 | # list of directories containing libraries, this must be the top level containing 78 | # include and lib 79 | #--------------------------------------------------------------------------------- 80 | LIBDIRS := $(PORTLIBS) $(LIBNX) $(CURDIR)/libs 81 | 82 | 83 | #--------------------------------------------------------------------------------- 84 | # no real need to edit anything past this point unless you need to add additional 85 | # rules for different file extensions 86 | #--------------------------------------------------------------------------------- 87 | ifneq ($(BUILD),$(notdir $(CURDIR))) 88 | #--------------------------------------------------------------------------------- 89 | 90 | export OUTPUT := $(CURDIR)/$(TARGET) 91 | export TOPDIR := $(CURDIR) 92 | 93 | export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ 94 | $(foreach dir,$(DATA),$(CURDIR)/$(dir)) 95 | 96 | export DEPSDIR := $(CURDIR)/$(BUILD) 97 | 98 | CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) 99 | CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) 100 | SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) 101 | BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) 102 | 103 | #--------------------------------------------------------------------------------- 104 | # use CXX for linking C++ projects, CC for standard C 105 | #--------------------------------------------------------------------------------- 106 | ifeq ($(strip $(CPPFILES)),) 107 | #--------------------------------------------------------------------------------- 108 | export LD := $(CC) 109 | #--------------------------------------------------------------------------------- 110 | else 111 | #--------------------------------------------------------------------------------- 112 | export LD := $(CXX) 113 | #--------------------------------------------------------------------------------- 114 | endif 115 | #--------------------------------------------------------------------------------- 116 | 117 | export OFILES_BIN := $(addsuffix .o,$(BINFILES)) 118 | export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) 119 | export OFILES := $(OFILES_BIN) $(OFILES_SRC) 120 | export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) 121 | 122 | export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 123 | $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ 124 | -I$(CURDIR)/$(BUILD) 125 | 126 | export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) 127 | 128 | export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) 129 | 130 | ifeq ($(strip $(ICON)),) 131 | icons := $(wildcard *.jpg) 132 | ifneq (,$(findstring $(TARGET).jpg,$(icons))) 133 | export APP_ICON := $(TOPDIR)/$(TARGET).jpg 134 | else 135 | ifneq (,$(findstring icon.jpg,$(icons))) 136 | export APP_ICON := $(TOPDIR)/icon.jpg 137 | endif 138 | endif 139 | else 140 | export APP_ICON := $(TOPDIR)/$(ICON) 141 | endif 142 | 143 | ifeq ($(strip $(NO_ICON)),) 144 | export NROFLAGS += --icon=$(APP_ICON) 145 | endif 146 | 147 | ifeq ($(strip $(NO_NACP)),) 148 | export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp 149 | endif 150 | 151 | ifneq ($(APP_TITLEID),) 152 | export NACPFLAGS += --titleid=$(APP_TITLEID) 153 | endif 154 | 155 | ifneq ($(ROMFS),) 156 | export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) 157 | endif 158 | 159 | .PHONY: $(BUILD) clean all 160 | 161 | #--------------------------------------------------------------------------------- 162 | all: $(BUILD) 163 | 164 | $(BUILD): 165 | @[ -d $@ ] || mkdir -p $@ $(BUILD) $(CURDIR) 166 | @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile 167 | 168 | #--------------------------------------------------------------------------------- 169 | clean: 170 | @echo clean ... 171 | @rm -fr $(BUILD) $(OUTDIR) $(OUTPUT).pfs0 $(OUTPUT).nacp $(OUTPUT).elf $(OUTPUT).nso $(OUTPUT).nro 172 | 173 | 174 | #--------------------------------------------------------------------------------- 175 | else 176 | .PHONY: all 177 | 178 | DEPENDS := $(OFILES:.o=.d) 179 | 180 | #--------------------------------------------------------------------------------- 181 | # main targets 182 | #--------------------------------------------------------------------------------- 183 | all : $(OUTPUT).pfs0 $(OUTPUT).nro 184 | 185 | $(OUTPUT).pfs0 : $(OUTPUT).nso 186 | 187 | $(OUTPUT).nso : $(OUTPUT).elf 188 | 189 | ifeq ($(strip $(NO_NACP)),) 190 | $(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp 191 | else 192 | $(OUTPUT).nro : $(OUTPUT).elf 193 | endif 194 | 195 | $(OUTPUT).elf : $(OFILES) 196 | 197 | $(OFILES_SRC) : $(HFILES_BIN) 198 | 199 | #--------------------------------------------------------------------------------- 200 | # you need a rule like this for each extension you use as binary data 201 | #--------------------------------------------------------------------------------- 202 | %.bin.o : %.bin 203 | #--------------------------------------------------------------------------------- 204 | @echo $(notdir $<) 205 | @$(bin2o) 206 | 207 | #--------------------------------------------------------------------------------- 208 | %.nxfnt.o : %.nxfnt 209 | #--------------------------------------------------------------------------------- 210 | @echo $(notdir $<) 211 | @$(bin2o) 212 | 213 | -include $(DEPENDS) 214 | 215 | #--------------------------------------------------------------------------------------- 216 | endif 217 | #--------------------------------------------------------------------------------------- 218 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ultimate Mod Manager 2 | 3 | ## Download 4 | 5 | Download the latest from [the releases page](https://github.com/ultimate-research/UltimateModManager/releases). 6 | 7 | ## Installation 8 | - Place the NRO in `sdmc:/switch/` 9 | - Put mods in `sdmc:/UltimateModManager/mods`. Folders at the top-level here will be the selectable modpacks. Compressed mod files may start with their offset in hexadecimal, such as `0x35AA26F08_model.numatb` or be named by their path within the data.arc. All uncompressed mod files must be in their data.arc folder layout and have the correct original naming. An example path of an arc path named mod file would be `UltimateModManager/mods/organized_css/ui/param/database/ui_chara_db.prc`, and it would show up in the application as `organized_css`. 10 | 11 | ## Usage 12 | - Use the DataArcDumper to dump your `data.arc` first if you have not. If you want to use this functionality, please start the HBL by overriding Smash itself, which is done by starting Smash while holding the R button. 13 | - Use the FTP server to transfer over mods to your Switch over your Wi-Fi connection. Open this server, use its IP address in conjunction with either a file transfer client like WinSCP or the ArcInstaller. Completely optional, as mods can be transferred over via the SD card as well. 14 | - Use the Mod Installer to install mods. Any time a mod is installed, the corresponding vanilla files will be populated as backups. If at any time you want to uninstall all mods in the data.arc, press the option to restore backups or uninstall individual mods with the Y button. 15 | 16 | 17 | ## Build from source 18 | 19 | ### Dependencies 20 | - [A DEVKITPRO environment](https://devkitpro.org/wiki/Getting_Started) 21 | - Install the mbedtls library: `pacman -S switch-mbedtls` 22 | 23 | At this point, you can build with a simple `make`. 24 | -------------------------------------------------------------------------------- /icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-research/UltimateModManager/08999dbc5b1858207a636909a78f51933ad83cb9/icon.jpg -------------------------------------------------------------------------------- /include/arcStructs.h: -------------------------------------------------------------------------------- 1 | #include "switch/types.h" 2 | #pragma pack(push, 1) 3 | 4 | struct _sArcHeader 5 | { 6 | u64 Magic; 7 | s64 MusicDataOffset; 8 | s64 FileDataOffset; 9 | s64 FileDataOffset2; 10 | s64 FileSystemOffset; 11 | s64 FileSystemSearchOffset; 12 | s64 Padding; 13 | }; 14 | 15 | struct _sCompressedTableHeader 16 | { 17 | u32 DataOffset; 18 | s32 DecompressedSize; 19 | s32 CompressedSize; 20 | s32 SectionSize; 21 | }; 22 | 23 | struct _sDirectoryList 24 | { 25 | u32 FullPathHash; 26 | u32 FullPathHashLengthAndIndex; // index to 0x922D 27 | 28 | u32 NameHash; 29 | u32 NameHashLength; 30 | 31 | u32 ParentFolderHash; 32 | u32 ParentFolderHashLength; 33 | 34 | u32 ExtraDisRe; // disposible and resident only 4 entries have this 35 | u32 ExtraDisReLength; 36 | 37 | s32 FileInformationStartIndex; 38 | s32 FileInformationCount; 39 | 40 | s32 ChildDirectoryStartIndex; 41 | s32 ChildDirectoryCount; 42 | 43 | u32 Flags; // TODO 44 | }; 45 | struct _sDirectoryOffset 46 | { 47 | s64 Offset; 48 | u32 UnknownSomeSize; // TODO: decompsize? 49 | u32 Size; 50 | u32 SubDataStartIndex; 51 | u32 SubDataCount; 52 | u32 RedirectIndex; 53 | }; 54 | 55 | struct _sFileInformationPath 56 | { 57 | u32 Path; 58 | u32 DirectoryIndex; 59 | u32 Extension; 60 | u32 FileTableFlag; 61 | 62 | u32 Parent; 63 | u32 Unk5; 64 | u32 FileName; 65 | u32 Unk6; 66 | }; 67 | struct _sFileInformationUnknownTable 68 | { 69 | u32 SomeIndex; 70 | u32 SomeIndex2; 71 | }; 72 | struct _sFileInformationIndex 73 | { 74 | u32 DirectoryOffsetIndex; 75 | u32 FileInformationIndex; 76 | }; 77 | struct _sFileInformationSubIndex 78 | { 79 | u32 DirectoryOffsetIndex; 80 | u32 SubFileIndex; 81 | u32 FileInformationIndexAndFlag; // TODO figure out flag 82 | }; 83 | 84 | struct _sRegionalInfo 85 | { 86 | char unk[0xE]; 87 | }; 88 | 89 | struct _sFileInformationV1 90 | { 91 | u32 Path; 92 | u32 DirectoryIndex; 93 | u32 Extension; 94 | u32 FileTableFlag; 95 | 96 | u32 Parent; 97 | u32 Unk5; 98 | u32 Hash2; 99 | u32 Unk6; 100 | 101 | u32 SubFile_Index; 102 | u32 Flags; 103 | }; 104 | 105 | struct _sFileInformationV2 106 | { 107 | u32 PathIndex; 108 | u32 IndexIndex; 109 | 110 | u32 SubIndexIndex; 111 | u32 Flags; 112 | }; 113 | 114 | struct _sFileSystemHeaderV1 115 | { 116 | u32 FileSize; 117 | u32 FolderCount; 118 | u32 FileCount1; 119 | u32 FileNameCount; 120 | 121 | u32 SubFileCount; 122 | u32 LastTableCount; 123 | u32 HashFolderCount; 124 | u32 FileInformationCount; 125 | 126 | u32 FileCount2; 127 | u32 SubFileCount2; 128 | u32 unk1_10; 129 | u32 unk2_10; 130 | 131 | u8 AnotherHashTableSize; 132 | u8 unk11; 133 | u16 unk12; 134 | 135 | u32 MovieCount; 136 | u32 Part1Count; 137 | u32 Part2Count; 138 | u32 MusicFileCount; 139 | }; 140 | 141 | struct _sFileSystemHeader 142 | { 143 | u32 TableFileSize; 144 | u32 FileInformationPathCount; 145 | u32 FileInformationIndexCount; 146 | u32 DirectoryCount; 147 | 148 | u32 DirectoryOffsetCount1; 149 | 150 | u32 DirectoryHashSearchCount; 151 | u32 FileInformationCount; 152 | u32 FileInformationSubIndexCount; 153 | u32 SubFileCount; 154 | 155 | u32 DirectoryOffsetCount2; 156 | u32 SubFileCount2; 157 | u32 padding; // padding 158 | 159 | u32 unk1_10; // both always 0x10 160 | u32 unk2_10; 161 | unsigned char RegionalCount1; // 0xE 162 | unsigned char RegionalCount2; // 0x5 163 | u16 padding2; 164 | }; 165 | struct _sStreamHeader 166 | { 167 | u32 UnkCount; 168 | u32 StreamHashCount; 169 | u32 StreamIndexToOffsetCount; 170 | u32 StreamOffsetCount; 171 | }; 172 | struct _sStreamUnk 173 | { 174 | u32 Hash; 175 | u32 LengthAndSize; 176 | u32 Index; 177 | }; 178 | 179 | struct _sSearchHashHeader 180 | { 181 | u64 SectionSize; 182 | u32 FolderLengthHashCount; 183 | u32 SomeCount3; 184 | u32 SomeCount4; 185 | }; 186 | struct _sHashIndexGroup 187 | { 188 | u32 Hash; 189 | s32 index; 190 | }; 191 | struct _sHashGroup 192 | { 193 | u32 FilePathHash; 194 | s32 FilePathLengthAndIndex; 195 | u32 FolderHash; 196 | s16 FolderHashLengthAndIndex; 197 | u32 FileNameHash; 198 | s32 FileNameLength; 199 | u32 ExtensionHash; 200 | u32 ExtensionLength; 201 | }; 202 | 203 | struct _sSubFileInfo 204 | { 205 | u32 Offset; 206 | u32 CompSize; 207 | u32 DecompSize; 208 | u32 Flags; // 0x03 if compressed 209 | }; 210 | 211 | #pragma pack(pop) 212 | 213 | struct _sStreamHashToName 214 | { 215 | u32 Hash; 216 | u32 NameIndex; 217 | }; 218 | struct _sStreamNameToHash 219 | { 220 | u32 Hash; 221 | u32 NameIndex; 222 | u32 Flags; 223 | }; 224 | struct _sStreamIndexToOffset 225 | { 226 | s32 FileIndex; 227 | }; 228 | struct _sStreamOffset 229 | { 230 | s64 Size; 231 | s64 Offset; 232 | }; -------------------------------------------------------------------------------- /include/console.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _3DS 4 | #include <3ds.h> 5 | #elif defined(__SWITCH__) 6 | #include 7 | #endif 8 | 9 | #if defined(_3DS) || defined(__SWITCH__) 10 | #define ESC(x) "\x1b[" #x 11 | #define RESET ESC(0m) 12 | #define BLACK ESC(30m) 13 | #define RED ESC(31;1m) 14 | #define GREEN ESC(32;1m) 15 | #define YELLOW ESC(33;1m) 16 | #define BLUE ESC(34;1m) 17 | #define MAGENTA ESC(35;1m) 18 | #define CYAN ESC(36;1m) 19 | #define WHITE ESC(37;1m) 20 | #else 21 | #define ESC(x) 22 | #define RESET 23 | #define BLACK 24 | #define RED 25 | #define GREEN 26 | #define YELLOW 27 | #define BLUE 28 | #define MAGENTA 29 | #define CYAN 30 | #define WHITE 31 | #endif 32 | 33 | void console_init(void); 34 | void console_exit(void); 35 | 36 | __attribute__((format(printf,1,2))) 37 | void console_set_status(const char *fmt, ...); 38 | 39 | __attribute__((format(printf,1,2))) 40 | void console_print(const char *fmt, ...); 41 | 42 | __attribute__((format(printf,1,2))) 43 | void debug_print(const char *fmt, ...); 44 | 45 | void console_render(void); 46 | -------------------------------------------------------------------------------- /include/ftp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define STATUS_STRING "ftpd v2.3" 4 | /*! Loop status */ 5 | typedef enum 6 | { 7 | LOOP_CONTINUE, /*!< Continue looping */ 8 | LOOP_RESTART, /*!< Reinitialize */ 9 | LOOP_EXIT, /*!< Terminate looping */ 10 | } loop_status_t; 11 | 12 | int ftp_init(void); 13 | loop_status_t ftp_loop(void); 14 | void ftp_exit(void); 15 | -------------------------------------------------------------------------------- /libs/include/cover.h: -------------------------------------------------------------------------------- 1 | #include /* fprintf */ 2 | #include /* malloc, free, qsort */ 3 | #include /* memset */ 4 | #include /* clock */ 5 | #include "mem.h" /* read */ 6 | #include "pool.h" 7 | #include "threading.h" 8 | #include "zstd_internal.h" /* includes zstd.h */ 9 | #ifndef ZDICT_STATIC_LINKING_ONLY 10 | #define ZDICT_STATIC_LINKING_ONLY 11 | #endif 12 | #include "zdict.h" 13 | 14 | /** 15 | * COVER_best_t is used for two purposes: 16 | * 1. Synchronizing threads. 17 | * 2. Saving the best parameters and dictionary. 18 | * 19 | * All of the methods except COVER_best_init() are thread safe if zstd is 20 | * compiled with multithreaded support. 21 | */ 22 | typedef struct COVER_best_s { 23 | ZSTD_pthread_mutex_t mutex; 24 | ZSTD_pthread_cond_t cond; 25 | size_t liveJobs; 26 | void *dict; 27 | size_t dictSize; 28 | ZDICT_cover_params_t parameters; 29 | size_t compressedSize; 30 | } COVER_best_t; 31 | 32 | /** 33 | * A segment is a range in the source as well as the score of the segment. 34 | */ 35 | typedef struct { 36 | U32 begin; 37 | U32 end; 38 | U32 score; 39 | } COVER_segment_t; 40 | 41 | /** 42 | *Number of epochs and size of each epoch. 43 | */ 44 | typedef struct { 45 | U32 num; 46 | U32 size; 47 | } COVER_epoch_info_t; 48 | 49 | /** 50 | * Struct used for the dictionary selection function. 51 | */ 52 | typedef struct COVER_dictSelection { 53 | BYTE* dictContent; 54 | size_t dictSize; 55 | size_t totalCompressedSize; 56 | } COVER_dictSelection_t; 57 | 58 | /** 59 | * Computes the number of epochs and the size of each epoch. 60 | * We will make sure that each epoch gets at least 10 * k bytes. 61 | * 62 | * The COVER algorithms divide the data up into epochs of equal size and 63 | * select one segment from each epoch. 64 | * 65 | * @param maxDictSize The maximum allowed dictionary size. 66 | * @param nbDmers The number of dmers we are training on. 67 | * @param k The parameter k (segment size). 68 | * @param passes The target number of passes over the dmer corpus. 69 | * More passes means a better dictionary. 70 | */ 71 | COVER_epoch_info_t COVER_computeEpochs(U32 maxDictSize, U32 nbDmers, 72 | U32 k, U32 passes); 73 | 74 | /** 75 | * Warns the user when their corpus is too small. 76 | */ 77 | void COVER_warnOnSmallCorpus(size_t maxDictSize, size_t nbDmers, int displayLevel); 78 | 79 | /** 80 | * Checks total compressed size of a dictionary 81 | */ 82 | size_t COVER_checkTotalCompressedSize(const ZDICT_cover_params_t parameters, 83 | const size_t *samplesSizes, const BYTE *samples, 84 | size_t *offsets, 85 | size_t nbTrainSamples, size_t nbSamples, 86 | BYTE *const dict, size_t dictBufferCapacity); 87 | 88 | /** 89 | * Returns the sum of the sample sizes. 90 | */ 91 | size_t COVER_sum(const size_t *samplesSizes, unsigned nbSamples) ; 92 | 93 | /** 94 | * Initialize the `COVER_best_t`. 95 | */ 96 | void COVER_best_init(COVER_best_t *best); 97 | 98 | /** 99 | * Wait until liveJobs == 0. 100 | */ 101 | void COVER_best_wait(COVER_best_t *best); 102 | 103 | /** 104 | * Call COVER_best_wait() and then destroy the COVER_best_t. 105 | */ 106 | void COVER_best_destroy(COVER_best_t *best); 107 | 108 | /** 109 | * Called when a thread is about to be launched. 110 | * Increments liveJobs. 111 | */ 112 | void COVER_best_start(COVER_best_t *best); 113 | 114 | /** 115 | * Called when a thread finishes executing, both on error or success. 116 | * Decrements liveJobs and signals any waiting threads if liveJobs == 0. 117 | * If this dictionary is the best so far save it and its parameters. 118 | */ 119 | void COVER_best_finish(COVER_best_t *best, ZDICT_cover_params_t parameters, 120 | COVER_dictSelection_t selection); 121 | /** 122 | * Error function for COVER_selectDict function. Checks if the return 123 | * value is an error. 124 | */ 125 | unsigned COVER_dictSelectionIsError(COVER_dictSelection_t selection); 126 | 127 | /** 128 | * Error function for COVER_selectDict function. Returns a struct where 129 | * return.totalCompressedSize is a ZSTD error. 130 | */ 131 | COVER_dictSelection_t COVER_dictSelectionError(size_t error); 132 | 133 | /** 134 | * Always call after selectDict is called to free up used memory from 135 | * newly created dictionary. 136 | */ 137 | void COVER_dictSelectionFree(COVER_dictSelection_t selection); 138 | 139 | /** 140 | * Called to finalize the dictionary and select one based on whether or not 141 | * the shrink-dict flag was enabled. If enabled the dictionary used is the 142 | * smallest dictionary within a specified regression of the compressed size 143 | * from the largest dictionary. 144 | */ 145 | COVER_dictSelection_t COVER_selectDict(BYTE* customDictContent, 146 | size_t dictContentSize, const BYTE* samplesBuffer, const size_t* samplesSizes, unsigned nbFinalizeSamples, 147 | size_t nbCheckSamples, size_t nbSamples, ZDICT_cover_params_t params, size_t* offsets, size_t totalCompressedSize); 148 | -------------------------------------------------------------------------------- /libs/include/zbuff.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under both the BSD-style license (found in the 6 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7 | * in the COPYING file in the root directory of this source tree). 8 | * You may select, at your option, one of the above-listed licenses. 9 | */ 10 | 11 | /* *************************************************************** 12 | * NOTES/WARNINGS 13 | ******************************************************************/ 14 | /* The streaming API defined here is deprecated. 15 | * Consider migrating towards ZSTD_compressStream() API in `zstd.h` 16 | * See 'lib/README.md'. 17 | *****************************************************************/ 18 | 19 | 20 | #if defined (__cplusplus) 21 | extern "C" { 22 | #endif 23 | 24 | #ifndef ZSTD_BUFFERED_H_23987 25 | #define ZSTD_BUFFERED_H_23987 26 | 27 | /* ************************************* 28 | * Dependencies 29 | ***************************************/ 30 | #include /* size_t */ 31 | #include "zstd.h" /* ZSTD_CStream, ZSTD_DStream, ZSTDLIB_API */ 32 | 33 | 34 | /* *************************************************************** 35 | * Compiler specifics 36 | *****************************************************************/ 37 | /* Deprecation warnings */ 38 | /* Should these warnings be a problem, 39 | * it is generally possible to disable them, 40 | * typically with -Wno-deprecated-declarations for gcc 41 | * or _CRT_SECURE_NO_WARNINGS in Visual. 42 | * Otherwise, it's also possible to define ZBUFF_DISABLE_DEPRECATE_WARNINGS 43 | */ 44 | #ifdef ZBUFF_DISABLE_DEPRECATE_WARNINGS 45 | # define ZBUFF_DEPRECATED(message) ZSTDLIB_API /* disable deprecation warnings */ 46 | #else 47 | # if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ 48 | # define ZBUFF_DEPRECATED(message) [[deprecated(message)]] ZSTDLIB_API 49 | # elif (defined(GNUC) && (GNUC > 4 || (GNUC == 4 && GNUC_MINOR >= 5))) || defined(__clang__) 50 | # define ZBUFF_DEPRECATED(message) ZSTDLIB_API __attribute__((deprecated(message))) 51 | # elif defined(__GNUC__) && (__GNUC__ >= 3) 52 | # define ZBUFF_DEPRECATED(message) ZSTDLIB_API __attribute__((deprecated)) 53 | # elif defined(_MSC_VER) 54 | # define ZBUFF_DEPRECATED(message) ZSTDLIB_API __declspec(deprecated(message)) 55 | # else 56 | # pragma message("WARNING: You need to implement ZBUFF_DEPRECATED for this compiler") 57 | # define ZBUFF_DEPRECATED(message) ZSTDLIB_API 58 | # endif 59 | #endif /* ZBUFF_DISABLE_DEPRECATE_WARNINGS */ 60 | 61 | 62 | /* ************************************* 63 | * Streaming functions 64 | ***************************************/ 65 | /* This is the easier "buffered" streaming API, 66 | * using an internal buffer to lift all restrictions on user-provided buffers 67 | * which can be any size, any place, for both input and output. 68 | * ZBUFF and ZSTD are 100% interoperable, 69 | * frames created by one can be decoded by the other one */ 70 | 71 | typedef ZSTD_CStream ZBUFF_CCtx; 72 | ZBUFF_DEPRECATED("use ZSTD_createCStream") ZBUFF_CCtx* ZBUFF_createCCtx(void); 73 | ZBUFF_DEPRECATED("use ZSTD_freeCStream") size_t ZBUFF_freeCCtx(ZBUFF_CCtx* cctx); 74 | 75 | ZBUFF_DEPRECATED("use ZSTD_initCStream") size_t ZBUFF_compressInit(ZBUFF_CCtx* cctx, int compressionLevel); 76 | ZBUFF_DEPRECATED("use ZSTD_initCStream_usingDict") size_t ZBUFF_compressInitDictionary(ZBUFF_CCtx* cctx, const void* dict, size_t dictSize, int compressionLevel); 77 | 78 | ZBUFF_DEPRECATED("use ZSTD_compressStream") size_t ZBUFF_compressContinue(ZBUFF_CCtx* cctx, void* dst, size_t* dstCapacityPtr, const void* src, size_t* srcSizePtr); 79 | ZBUFF_DEPRECATED("use ZSTD_flushStream") size_t ZBUFF_compressFlush(ZBUFF_CCtx* cctx, void* dst, size_t* dstCapacityPtr); 80 | ZBUFF_DEPRECATED("use ZSTD_endStream") size_t ZBUFF_compressEnd(ZBUFF_CCtx* cctx, void* dst, size_t* dstCapacityPtr); 81 | 82 | /*-************************************************* 83 | * Streaming compression - howto 84 | * 85 | * A ZBUFF_CCtx object is required to track streaming operation. 86 | * Use ZBUFF_createCCtx() and ZBUFF_freeCCtx() to create/release resources. 87 | * ZBUFF_CCtx objects can be reused multiple times. 88 | * 89 | * Start by initializing ZBUF_CCtx. 90 | * Use ZBUFF_compressInit() to start a new compression operation. 91 | * Use ZBUFF_compressInitDictionary() for a compression which requires a dictionary. 92 | * 93 | * Use ZBUFF_compressContinue() repetitively to consume input stream. 94 | * *srcSizePtr and *dstCapacityPtr can be any size. 95 | * The function will report how many bytes were read or written within *srcSizePtr and *dstCapacityPtr. 96 | * Note that it may not consume the entire input, in which case it's up to the caller to present again remaining data. 97 | * The content of `dst` will be overwritten (up to *dstCapacityPtr) at each call, so save its content if it matters or change @dst . 98 | * @return : a hint to preferred nb of bytes to use as input for next function call (it's just a hint, to improve latency) 99 | * or an error code, which can be tested using ZBUFF_isError(). 100 | * 101 | * At any moment, it's possible to flush whatever data remains within buffer, using ZBUFF_compressFlush(). 102 | * The nb of bytes written into `dst` will be reported into *dstCapacityPtr. 103 | * Note that the function cannot output more than *dstCapacityPtr, 104 | * therefore, some content might still be left into internal buffer if *dstCapacityPtr is too small. 105 | * @return : nb of bytes still present into internal buffer (0 if it's empty) 106 | * or an error code, which can be tested using ZBUFF_isError(). 107 | * 108 | * ZBUFF_compressEnd() instructs to finish a frame. 109 | * It will perform a flush and write frame epilogue. 110 | * The epilogue is required for decoders to consider a frame completed. 111 | * Similar to ZBUFF_compressFlush(), it may not be able to output the entire internal buffer content if *dstCapacityPtr is too small. 112 | * In which case, call again ZBUFF_compressFlush() to complete the flush. 113 | * @return : nb of bytes still present into internal buffer (0 if it's empty) 114 | * or an error code, which can be tested using ZBUFF_isError(). 115 | * 116 | * Hint : _recommended buffer_ sizes (not compulsory) : ZBUFF_recommendedCInSize() / ZBUFF_recommendedCOutSize() 117 | * input : ZBUFF_recommendedCInSize==128 KB block size is the internal unit, use this value to reduce intermediate stages (better latency) 118 | * output : ZBUFF_recommendedCOutSize==ZSTD_compressBound(128 KB) + 3 + 3 : ensures it's always possible to write/flush/end a full block. Skip some buffering. 119 | * By using both, it ensures that input will be entirely consumed, and output will always contain the result, reducing intermediate buffering. 120 | * **************************************************/ 121 | 122 | 123 | typedef ZSTD_DStream ZBUFF_DCtx; 124 | ZBUFF_DEPRECATED("use ZSTD_createDStream") ZBUFF_DCtx* ZBUFF_createDCtx(void); 125 | ZBUFF_DEPRECATED("use ZSTD_freeDStream") size_t ZBUFF_freeDCtx(ZBUFF_DCtx* dctx); 126 | 127 | ZBUFF_DEPRECATED("use ZSTD_initDStream") size_t ZBUFF_decompressInit(ZBUFF_DCtx* dctx); 128 | ZBUFF_DEPRECATED("use ZSTD_initDStream_usingDict") size_t ZBUFF_decompressInitDictionary(ZBUFF_DCtx* dctx, const void* dict, size_t dictSize); 129 | 130 | ZBUFF_DEPRECATED("use ZSTD_decompressStream") size_t ZBUFF_decompressContinue(ZBUFF_DCtx* dctx, 131 | void* dst, size_t* dstCapacityPtr, 132 | const void* src, size_t* srcSizePtr); 133 | 134 | /*-*************************************************************************** 135 | * Streaming decompression howto 136 | * 137 | * A ZBUFF_DCtx object is required to track streaming operations. 138 | * Use ZBUFF_createDCtx() and ZBUFF_freeDCtx() to create/release resources. 139 | * Use ZBUFF_decompressInit() to start a new decompression operation, 140 | * or ZBUFF_decompressInitDictionary() if decompression requires a dictionary. 141 | * Note that ZBUFF_DCtx objects can be re-init multiple times. 142 | * 143 | * Use ZBUFF_decompressContinue() repetitively to consume your input. 144 | * *srcSizePtr and *dstCapacityPtr can be any size. 145 | * The function will report how many bytes were read or written by modifying *srcSizePtr and *dstCapacityPtr. 146 | * Note that it may not consume the entire input, in which case it's up to the caller to present remaining input again. 147 | * The content of `dst` will be overwritten (up to *dstCapacityPtr) at each function call, so save its content if it matters, or change `dst`. 148 | * @return : 0 when a frame is completely decoded and fully flushed, 149 | * 1 when there is still some data left within internal buffer to flush, 150 | * >1 when more data is expected, with value being a suggested next input size (it's just a hint, which helps latency), 151 | * or an error code, which can be tested using ZBUFF_isError(). 152 | * 153 | * Hint : recommended buffer sizes (not compulsory) : ZBUFF_recommendedDInSize() and ZBUFF_recommendedDOutSize() 154 | * output : ZBUFF_recommendedDOutSize== 128 KB block size is the internal unit, it ensures it's always possible to write a full block when decoded. 155 | * input : ZBUFF_recommendedDInSize == 128KB + 3; 156 | * just follow indications from ZBUFF_decompressContinue() to minimize latency. It should always be <= 128 KB + 3 . 157 | * *******************************************************************************/ 158 | 159 | 160 | /* ************************************* 161 | * Tool functions 162 | ***************************************/ 163 | ZBUFF_DEPRECATED("use ZSTD_isError") unsigned ZBUFF_isError(size_t errorCode); 164 | ZBUFF_DEPRECATED("use ZSTD_getErrorName") const char* ZBUFF_getErrorName(size_t errorCode); 165 | 166 | /** Functions below provide recommended buffer sizes for Compression or Decompression operations. 167 | * These sizes are just hints, they tend to offer better latency */ 168 | ZBUFF_DEPRECATED("use ZSTD_CStreamInSize") size_t ZBUFF_recommendedCInSize(void); 169 | ZBUFF_DEPRECATED("use ZSTD_CStreamOutSize") size_t ZBUFF_recommendedCOutSize(void); 170 | ZBUFF_DEPRECATED("use ZSTD_DStreamInSize") size_t ZBUFF_recommendedDInSize(void); 171 | ZBUFF_DEPRECATED("use ZSTD_DStreamOutSize") size_t ZBUFF_recommendedDOutSize(void); 172 | 173 | #endif /* ZSTD_BUFFERED_H_23987 */ 174 | 175 | 176 | #ifdef ZBUFF_STATIC_LINKING_ONLY 177 | #ifndef ZBUFF_STATIC_H_30298098432 178 | #define ZBUFF_STATIC_H_30298098432 179 | 180 | /* ==================================================================================== 181 | * The definitions in this section are considered experimental. 182 | * They should never be used in association with a dynamic library, as they may change in the future. 183 | * They are provided for advanced usages. 184 | * Use them only in association with static linking. 185 | * ==================================================================================== */ 186 | 187 | /*--- Dependency ---*/ 188 | #define ZSTD_STATIC_LINKING_ONLY /* ZSTD_parameters, ZSTD_customMem */ 189 | #include "zstd.h" 190 | 191 | 192 | /*--- Custom memory allocator ---*/ 193 | /*! ZBUFF_createCCtx_advanced() : 194 | * Create a ZBUFF compression context using external alloc and free functions */ 195 | ZBUFF_DEPRECATED("use ZSTD_createCStream_advanced") ZBUFF_CCtx* ZBUFF_createCCtx_advanced(ZSTD_customMem customMem); 196 | 197 | /*! ZBUFF_createDCtx_advanced() : 198 | * Create a ZBUFF decompression context using external alloc and free functions */ 199 | ZBUFF_DEPRECATED("use ZSTD_createDStream_advanced") ZBUFF_DCtx* ZBUFF_createDCtx_advanced(ZSTD_customMem customMem); 200 | 201 | 202 | /*--- Advanced Streaming Initialization ---*/ 203 | ZBUFF_DEPRECATED("use ZSTD_initDStream_usingDict") size_t ZBUFF_compressInit_advanced(ZBUFF_CCtx* zbc, 204 | const void* dict, size_t dictSize, 205 | ZSTD_parameters params, unsigned long long pledgedSrcSize); 206 | 207 | 208 | #endif /* ZBUFF_STATIC_H_30298098432 */ 209 | #endif /* ZBUFF_STATIC_LINKING_ONLY */ 210 | 211 | 212 | #if defined (__cplusplus) 213 | } 214 | #endif 215 | -------------------------------------------------------------------------------- /libs/include/zdict.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under both the BSD-style license (found in the 6 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7 | * in the COPYING file in the root directory of this source tree). 8 | * You may select, at your option, one of the above-listed licenses. 9 | */ 10 | 11 | #ifndef DICTBUILDER_H_001 12 | #define DICTBUILDER_H_001 13 | 14 | #if defined (__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | 19 | /*====== Dependencies ======*/ 20 | #include /* size_t */ 21 | 22 | 23 | /* ===== ZDICTLIB_API : control library symbols visibility ===== */ 24 | #ifndef ZDICTLIB_VISIBILITY 25 | # if defined(__GNUC__) && (__GNUC__ >= 4) 26 | # define ZDICTLIB_VISIBILITY __attribute__ ((visibility ("default"))) 27 | # else 28 | # define ZDICTLIB_VISIBILITY 29 | # endif 30 | #endif 31 | #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) 32 | # define ZDICTLIB_API __declspec(dllexport) ZDICTLIB_VISIBILITY 33 | #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) 34 | # define ZDICTLIB_API __declspec(dllimport) ZDICTLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ 35 | #else 36 | # define ZDICTLIB_API ZDICTLIB_VISIBILITY 37 | #endif 38 | 39 | 40 | /*! ZDICT_trainFromBuffer(): 41 | * Train a dictionary from an array of samples. 42 | * Redirect towards ZDICT_optimizeTrainFromBuffer_fastCover() single-threaded, with d=8, steps=4, 43 | * f=20, and accel=1. 44 | * Samples must be stored concatenated in a single flat buffer `samplesBuffer`, 45 | * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order. 46 | * The resulting dictionary will be saved into `dictBuffer`. 47 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 48 | * or an error code, which can be tested with ZDICT_isError(). 49 | * Note: Dictionary training will fail if there are not enough samples to construct a 50 | * dictionary, or if most of the samples are too small (< 8 bytes being the lower limit). 51 | * If dictionary training fails, you should use zstd without a dictionary, as the dictionary 52 | * would've been ineffective anyways. If you believe your samples would benefit from a dictionary 53 | * please open an issue with details, and we can look into it. 54 | * Note: ZDICT_trainFromBuffer()'s memory usage is about 6 MB. 55 | * Tips: In general, a reasonable dictionary has a size of ~ 100 KB. 56 | * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`. 57 | * In general, it's recommended to provide a few thousands samples, though this can vary a lot. 58 | * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. 59 | */ 60 | ZDICTLIB_API size_t ZDICT_trainFromBuffer(void* dictBuffer, size_t dictBufferCapacity, 61 | const void* samplesBuffer, 62 | const size_t* samplesSizes, unsigned nbSamples); 63 | 64 | 65 | /*====== Helper functions ======*/ 66 | ZDICTLIB_API unsigned ZDICT_getDictID(const void* dictBuffer, size_t dictSize); /**< extracts dictID; @return zero if error (not a valid dictionary) */ 67 | ZDICTLIB_API unsigned ZDICT_isError(size_t errorCode); 68 | ZDICTLIB_API const char* ZDICT_getErrorName(size_t errorCode); 69 | 70 | 71 | 72 | #ifdef ZDICT_STATIC_LINKING_ONLY 73 | 74 | /* ==================================================================================== 75 | * The definitions in this section are considered experimental. 76 | * They should never be used with a dynamic library, as they may change in the future. 77 | * They are provided for advanced usages. 78 | * Use them only in association with static linking. 79 | * ==================================================================================== */ 80 | 81 | typedef struct { 82 | int compressionLevel; /* optimize for a specific zstd compression level; 0 means default */ 83 | unsigned notificationLevel; /* Write log to stderr; 0 = none (default); 1 = errors; 2 = progression; 3 = details; 4 = debug; */ 84 | unsigned dictID; /* force dictID value; 0 means auto mode (32-bits random value) */ 85 | } ZDICT_params_t; 86 | 87 | /*! ZDICT_cover_params_t: 88 | * k and d are the only required parameters. 89 | * For others, value 0 means default. 90 | */ 91 | typedef struct { 92 | unsigned k; /* Segment size : constraint: 0 < k : Reasonable range [16, 2048+] */ 93 | unsigned d; /* dmer size : constraint: 0 < d <= k : Reasonable range [6, 16] */ 94 | unsigned steps; /* Number of steps : Only used for optimization : 0 means default (40) : Higher means more parameters checked */ 95 | unsigned nbThreads; /* Number of threads : constraint: 0 < nbThreads : 1 means single-threaded : Only used for optimization : Ignored if ZSTD_MULTITHREAD is not defined */ 96 | double splitPoint; /* Percentage of samples used for training: Only used for optimization : the first nbSamples * splitPoint samples will be used to training, the last nbSamples * (1 - splitPoint) samples will be used for testing, 0 means default (1.0), 1.0 when all samples are used for both training and testing */ 97 | unsigned shrinkDict; /* Train dictionaries to shrink in size starting from the minimum size and selects the smallest dictionary that is shrinkDictMaxRegression% worse than the largest dictionary. 0 means no shrinking and 1 means shrinking */ 98 | unsigned shrinkDictMaxRegression; /* Sets shrinkDictMaxRegression so that a smaller dictionary can be at worse shrinkDictMaxRegression% worse than the max dict size dictionary. */ 99 | ZDICT_params_t zParams; 100 | } ZDICT_cover_params_t; 101 | 102 | typedef struct { 103 | unsigned k; /* Segment size : constraint: 0 < k : Reasonable range [16, 2048+] */ 104 | unsigned d; /* dmer size : constraint: 0 < d <= k : Reasonable range [6, 16] */ 105 | unsigned f; /* log of size of frequency array : constraint: 0 < f <= 31 : 1 means default(20)*/ 106 | unsigned steps; /* Number of steps : Only used for optimization : 0 means default (40) : Higher means more parameters checked */ 107 | unsigned nbThreads; /* Number of threads : constraint: 0 < nbThreads : 1 means single-threaded : Only used for optimization : Ignored if ZSTD_MULTITHREAD is not defined */ 108 | double splitPoint; /* Percentage of samples used for training: Only used for optimization : the first nbSamples * splitPoint samples will be used to training, the last nbSamples * (1 - splitPoint) samples will be used for testing, 0 means default (0.75), 1.0 when all samples are used for both training and testing */ 109 | unsigned accel; /* Acceleration level: constraint: 0 < accel <= 10, higher means faster and less accurate, 0 means default(1) */ 110 | unsigned shrinkDict; /* Train dictionaries to shrink in size starting from the minimum size and selects the smallest dictionary that is shrinkDictMaxRegression% worse than the largest dictionary. 0 means no shrinking and 1 means shrinking */ 111 | unsigned shrinkDictMaxRegression; /* Sets shrinkDictMaxRegression so that a smaller dictionary can be at worse shrinkDictMaxRegression% worse than the max dict size dictionary. */ 112 | 113 | ZDICT_params_t zParams; 114 | } ZDICT_fastCover_params_t; 115 | 116 | /*! ZDICT_trainFromBuffer_cover(): 117 | * Train a dictionary from an array of samples using the COVER algorithm. 118 | * Samples must be stored concatenated in a single flat buffer `samplesBuffer`, 119 | * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order. 120 | * The resulting dictionary will be saved into `dictBuffer`. 121 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 122 | * or an error code, which can be tested with ZDICT_isError(). 123 | * See ZDICT_trainFromBuffer() for details on failure modes. 124 | * Note: ZDICT_trainFromBuffer_cover() requires about 9 bytes of memory for each input byte. 125 | * Tips: In general, a reasonable dictionary has a size of ~ 100 KB. 126 | * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`. 127 | * In general, it's recommended to provide a few thousands samples, though this can vary a lot. 128 | * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. 129 | */ 130 | ZDICTLIB_API size_t ZDICT_trainFromBuffer_cover( 131 | void *dictBuffer, size_t dictBufferCapacity, 132 | const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, 133 | ZDICT_cover_params_t parameters); 134 | 135 | /*! ZDICT_optimizeTrainFromBuffer_cover(): 136 | * The same requirements as above hold for all the parameters except `parameters`. 137 | * This function tries many parameter combinations and picks the best parameters. 138 | * `*parameters` is filled with the best parameters found, 139 | * dictionary constructed with those parameters is stored in `dictBuffer`. 140 | * 141 | * All of the parameters d, k, steps are optional. 142 | * If d is non-zero then we don't check multiple values of d, otherwise we check d = {6, 8}. 143 | * if steps is zero it defaults to its default value. 144 | * If k is non-zero then we don't check multiple values of k, otherwise we check steps values in [50, 2000]. 145 | * 146 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 147 | * or an error code, which can be tested with ZDICT_isError(). 148 | * On success `*parameters` contains the parameters selected. 149 | * See ZDICT_trainFromBuffer() for details on failure modes. 150 | * Note: ZDICT_optimizeTrainFromBuffer_cover() requires about 8 bytes of memory for each input byte and additionally another 5 bytes of memory for each byte of memory for each thread. 151 | */ 152 | ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_cover( 153 | void* dictBuffer, size_t dictBufferCapacity, 154 | const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, 155 | ZDICT_cover_params_t* parameters); 156 | 157 | /*! ZDICT_trainFromBuffer_fastCover(): 158 | * Train a dictionary from an array of samples using a modified version of COVER algorithm. 159 | * Samples must be stored concatenated in a single flat buffer `samplesBuffer`, 160 | * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order. 161 | * d and k are required. 162 | * All other parameters are optional, will use default values if not provided 163 | * The resulting dictionary will be saved into `dictBuffer`. 164 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 165 | * or an error code, which can be tested with ZDICT_isError(). 166 | * See ZDICT_trainFromBuffer() for details on failure modes. 167 | * Note: ZDICT_trainFromBuffer_fastCover() requires 6 * 2^f bytes of memory. 168 | * Tips: In general, a reasonable dictionary has a size of ~ 100 KB. 169 | * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`. 170 | * In general, it's recommended to provide a few thousands samples, though this can vary a lot. 171 | * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. 172 | */ 173 | ZDICTLIB_API size_t ZDICT_trainFromBuffer_fastCover(void *dictBuffer, 174 | size_t dictBufferCapacity, const void *samplesBuffer, 175 | const size_t *samplesSizes, unsigned nbSamples, 176 | ZDICT_fastCover_params_t parameters); 177 | 178 | /*! ZDICT_optimizeTrainFromBuffer_fastCover(): 179 | * The same requirements as above hold for all the parameters except `parameters`. 180 | * This function tries many parameter combinations (specifically, k and d combinations) 181 | * and picks the best parameters. `*parameters` is filled with the best parameters found, 182 | * dictionary constructed with those parameters is stored in `dictBuffer`. 183 | * All of the parameters d, k, steps, f, and accel are optional. 184 | * If d is non-zero then we don't check multiple values of d, otherwise we check d = {6, 8}. 185 | * if steps is zero it defaults to its default value. 186 | * If k is non-zero then we don't check multiple values of k, otherwise we check steps values in [50, 2000]. 187 | * If f is zero, default value of 20 is used. 188 | * If accel is zero, default value of 1 is used. 189 | * 190 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 191 | * or an error code, which can be tested with ZDICT_isError(). 192 | * On success `*parameters` contains the parameters selected. 193 | * See ZDICT_trainFromBuffer() for details on failure modes. 194 | * Note: ZDICT_optimizeTrainFromBuffer_fastCover() requires about 6 * 2^f bytes of memory for each thread. 195 | */ 196 | ZDICTLIB_API size_t ZDICT_optimizeTrainFromBuffer_fastCover(void* dictBuffer, 197 | size_t dictBufferCapacity, const void* samplesBuffer, 198 | const size_t* samplesSizes, unsigned nbSamples, 199 | ZDICT_fastCover_params_t* parameters); 200 | 201 | /*! ZDICT_finalizeDictionary(): 202 | * Given a custom content as a basis for dictionary, and a set of samples, 203 | * finalize dictionary by adding headers and statistics. 204 | * 205 | * Samples must be stored concatenated in a flat buffer `samplesBuffer`, 206 | * supplied with an array of sizes `samplesSizes`, providing the size of each sample in order. 207 | * 208 | * dictContentSize must be >= ZDICT_CONTENTSIZE_MIN bytes. 209 | * maxDictSize must be >= dictContentSize, and must be >= ZDICT_DICTSIZE_MIN bytes. 210 | * 211 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`), 212 | * or an error code, which can be tested by ZDICT_isError(). 213 | * Note: ZDICT_finalizeDictionary() will push notifications into stderr if instructed to, using notificationLevel>0. 214 | * Note 2: dictBuffer and dictContent can overlap 215 | */ 216 | #define ZDICT_CONTENTSIZE_MIN 128 217 | #define ZDICT_DICTSIZE_MIN 256 218 | ZDICTLIB_API size_t ZDICT_finalizeDictionary(void* dictBuffer, size_t dictBufferCapacity, 219 | const void* dictContent, size_t dictContentSize, 220 | const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples, 221 | ZDICT_params_t parameters); 222 | 223 | typedef struct { 224 | unsigned selectivityLevel; /* 0 means default; larger => select more => larger dictionary */ 225 | ZDICT_params_t zParams; 226 | } ZDICT_legacy_params_t; 227 | 228 | /*! ZDICT_trainFromBuffer_legacy(): 229 | * Train a dictionary from an array of samples. 230 | * Samples must be stored concatenated in a single flat buffer `samplesBuffer`, 231 | * supplied with an array of sizes `samplesSizes`, providing the size of each sample, in order. 232 | * The resulting dictionary will be saved into `dictBuffer`. 233 | * `parameters` is optional and can be provided with values set to 0 to mean "default". 234 | * @return: size of dictionary stored into `dictBuffer` (<= `dictBufferCapacity`) 235 | * or an error code, which can be tested with ZDICT_isError(). 236 | * See ZDICT_trainFromBuffer() for details on failure modes. 237 | * Tips: In general, a reasonable dictionary has a size of ~ 100 KB. 238 | * It's possible to select smaller or larger size, just by specifying `dictBufferCapacity`. 239 | * In general, it's recommended to provide a few thousands samples, though this can vary a lot. 240 | * It's recommended that total size of all samples be about ~x100 times the target size of dictionary. 241 | * Note: ZDICT_trainFromBuffer_legacy() will send notifications into stderr if instructed to, using notificationLevel>0. 242 | */ 243 | ZDICTLIB_API size_t ZDICT_trainFromBuffer_legacy( 244 | void *dictBuffer, size_t dictBufferCapacity, 245 | const void *samplesBuffer, const size_t *samplesSizes, unsigned nbSamples, 246 | ZDICT_legacy_params_t parameters); 247 | 248 | /* Deprecation warnings */ 249 | /* It is generally possible to disable deprecation warnings from compiler, 250 | for example with -Wno-deprecated-declarations for gcc 251 | or _CRT_SECURE_NO_WARNINGS in Visual. 252 | Otherwise, it's also possible to manually define ZDICT_DISABLE_DEPRECATE_WARNINGS */ 253 | #ifdef ZDICT_DISABLE_DEPRECATE_WARNINGS 254 | # define ZDICT_DEPRECATED(message) ZDICTLIB_API /* disable deprecation warnings */ 255 | #else 256 | # define ZDICT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) 257 | # if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */ 258 | # define ZDICT_DEPRECATED(message) [[deprecated(message)]] ZDICTLIB_API 259 | # elif (ZDICT_GCC_VERSION >= 405) || defined(__clang__) 260 | # define ZDICT_DEPRECATED(message) ZDICTLIB_API __attribute__((deprecated(message))) 261 | # elif (ZDICT_GCC_VERSION >= 301) 262 | # define ZDICT_DEPRECATED(message) ZDICTLIB_API __attribute__((deprecated)) 263 | # elif defined(_MSC_VER) 264 | # define ZDICT_DEPRECATED(message) ZDICTLIB_API __declspec(deprecated(message)) 265 | # else 266 | # pragma message("WARNING: You need to implement ZDICT_DEPRECATED for this compiler") 267 | # define ZDICT_DEPRECATED(message) ZDICTLIB_API 268 | # endif 269 | #endif /* ZDICT_DISABLE_DEPRECATE_WARNINGS */ 270 | 271 | ZDICT_DEPRECATED("use ZDICT_finalizeDictionary() instead") 272 | size_t ZDICT_addEntropyTablesFromBuffer(void* dictBuffer, size_t dictContentSize, size_t dictBufferCapacity, 273 | const void* samplesBuffer, const size_t* samplesSizes, unsigned nbSamples); 274 | 275 | 276 | #endif /* ZDICT_STATIC_LINKING_ONLY */ 277 | 278 | #if defined (__cplusplus) 279 | } 280 | #endif 281 | 282 | #endif /* DICTBUILDER_H_001 */ 283 | -------------------------------------------------------------------------------- /libs/include/zstd_errors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016-present, Yann Collet, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under both the BSD-style license (found in the 6 | * LICENSE file in the root directory of this source tree) and the GPLv2 (found 7 | * in the COPYING file in the root directory of this source tree). 8 | * You may select, at your option, one of the above-listed licenses. 9 | */ 10 | 11 | #ifndef ZSTD_ERRORS_H_398273423 12 | #define ZSTD_ERRORS_H_398273423 13 | 14 | #if defined (__cplusplus) 15 | extern "C" { 16 | #endif 17 | 18 | /*===== dependency =====*/ 19 | #include /* size_t */ 20 | 21 | 22 | /* ===== ZSTDERRORLIB_API : control library symbols visibility ===== */ 23 | #ifndef ZSTDERRORLIB_VISIBILITY 24 | # if defined(__GNUC__) && (__GNUC__ >= 4) 25 | # define ZSTDERRORLIB_VISIBILITY __attribute__ ((visibility ("default"))) 26 | # else 27 | # define ZSTDERRORLIB_VISIBILITY 28 | # endif 29 | #endif 30 | #if defined(ZSTD_DLL_EXPORT) && (ZSTD_DLL_EXPORT==1) 31 | # define ZSTDERRORLIB_API __declspec(dllexport) ZSTDERRORLIB_VISIBILITY 32 | #elif defined(ZSTD_DLL_IMPORT) && (ZSTD_DLL_IMPORT==1) 33 | # define ZSTDERRORLIB_API __declspec(dllimport) ZSTDERRORLIB_VISIBILITY /* It isn't required but allows to generate better code, saving a function pointer load from the IAT and an indirect jump.*/ 34 | #else 35 | # define ZSTDERRORLIB_API ZSTDERRORLIB_VISIBILITY 36 | #endif 37 | 38 | /*-********************************************* 39 | * Error codes list 40 | *-********************************************* 41 | * Error codes _values_ are pinned down since v1.3.1 only. 42 | * Therefore, don't rely on values if you may link to any version < v1.3.1. 43 | * 44 | * Only values < 100 are considered stable. 45 | * 46 | * note 1 : this API shall be used with static linking only. 47 | * dynamic linking is not yet officially supported. 48 | * note 2 : Prefer relying on the enum than on its value whenever possible 49 | * This is the only supported way to use the error list < v1.3.1 50 | * note 3 : ZSTD_isError() is always correct, whatever the library version. 51 | **********************************************/ 52 | typedef enum { 53 | ZSTD_error_no_error = 0, 54 | ZSTD_error_GENERIC = 1, 55 | ZSTD_error_prefix_unknown = 10, 56 | ZSTD_error_version_unsupported = 12, 57 | ZSTD_error_frameParameter_unsupported = 14, 58 | ZSTD_error_frameParameter_windowTooLarge = 16, 59 | ZSTD_error_corruption_detected = 20, 60 | ZSTD_error_checksum_wrong = 22, 61 | ZSTD_error_dictionary_corrupted = 30, 62 | ZSTD_error_dictionary_wrong = 32, 63 | ZSTD_error_dictionaryCreation_failed = 34, 64 | ZSTD_error_parameter_unsupported = 40, 65 | ZSTD_error_parameter_outOfBound = 42, 66 | ZSTD_error_tableLog_tooLarge = 44, 67 | ZSTD_error_maxSymbolValue_tooLarge = 46, 68 | ZSTD_error_maxSymbolValue_tooSmall = 48, 69 | ZSTD_error_stage_wrong = 60, 70 | ZSTD_error_init_missing = 62, 71 | ZSTD_error_memory_allocation = 64, 72 | ZSTD_error_workSpace_tooSmall= 66, 73 | ZSTD_error_dstSize_tooSmall = 70, 74 | ZSTD_error_srcSize_wrong = 72, 75 | ZSTD_error_dstBuffer_null = 74, 76 | /* following error codes are __NOT STABLE__, they can be removed or changed in future versions */ 77 | ZSTD_error_frameIndex_tooLarge = 100, 78 | ZSTD_error_seekableIO = 102, 79 | ZSTD_error_maxCode = 120 /* never EVER use this value directly, it can change in future versions! Use ZSTD_isError() instead */ 80 | } ZSTD_ErrorCode; 81 | 82 | /*! ZSTD_getErrorCode() : 83 | convert a `size_t` function result into a `ZSTD_ErrorCode` enum type, 84 | which can be used to compare with enum list published above */ 85 | ZSTDERRORLIB_API ZSTD_ErrorCode ZSTD_getErrorCode(size_t functionResult); 86 | ZSTDERRORLIB_API const char* ZSTD_getErrorString(ZSTD_ErrorCode code); /**< Same as ZSTD_getErrorName, but using a `ZSTD_ErrorCode` enum argument */ 87 | 88 | 89 | #if defined (__cplusplus) 90 | } 91 | #endif 92 | 93 | #endif /* ZSTD_ERRORS_H_398273423 */ 94 | -------------------------------------------------------------------------------- /libs/lib/libzstd.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ultimate-research/UltimateModManager/08999dbc5b1858207a636909a78f51933ad83cb9/libs/lib/libzstd.a -------------------------------------------------------------------------------- /source/arcReader.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "arcStructs.h" 9 | #include "utils.h" 10 | #include 11 | 12 | #define ZSTD_STATIC_LINKING_ONLY 13 | #include 14 | 15 | #define SUBFILE_DECOMPRESSED 0x00000000 16 | #define SUBFILE_COMPRESSED_ZSTD 0x00000003 17 | 18 | #define LOADARRAY(arr, type, count) \ 19 | arr = (type*)malloc(sizeof(type) * (count)); \ 20 | for (size_t i = 0; i < count; i++) reader.load(arr[i]) 21 | 22 | struct membuf : std::streambuf { 23 | membuf(char* begin, char* end) { 24 | this->setg(begin, begin, end); 25 | } 26 | }; 27 | 28 | class ObjectStream { 29 | public: 30 | std::istream& is; 31 | u64 pos; 32 | ObjectStream(std::istream& _is) : is(_is) { pos = 0; }; 33 | template 34 | void load(T& v) { 35 | is.read((char*)&v, sizeof(v)); 36 | pos += sizeof(v); 37 | } 38 | }; 39 | 40 | class ArcReader { 41 | private: 42 | std::map pathToFileInfo; 43 | std::map pathToFileInfoV1; 44 | std::map pathCrc32ToStreamInfo; 45 | std::vector FilePaths; 46 | u64 numFiles; 47 | ZSTD_DStream* dstream; 48 | std::string arcPath; 49 | 50 | const u64 Magic = 0xABCDEF9876543210; 51 | 52 | _sArcHeader header; 53 | 54 | // stream 55 | _sStreamUnk* streamUnk; 56 | _sStreamHashToName* streamHashToName; 57 | _sStreamNameToHash* streamNameToHash; 58 | _sStreamIndexToOffset* streamIndexToFile; 59 | _sStreamOffset* streamOffsets; 60 | 61 | // file system 62 | _sFileSystemHeader fsHeader; 63 | _sRegionalInfo* regionalInfo; 64 | _sStreamHeader streamHeader; 65 | 66 | _sFileInformationPath* fileInfoPath; 67 | _sFileInformationIndex* fileInfoIndex; 68 | _sFileInformationSubIndex* fileInfoSubIndex; 69 | _sFileInformationV2* fileInfoV2; 70 | 71 | _sSubFileInfo* subFiles; 72 | 73 | _sFileInformationUnknownTable* fileInfoUnknownTable; 74 | _sHashIndexGroup* filePathToIndexHashGroup; 75 | 76 | // Directory information 77 | _sHashIndexGroup* directoryHashGroup; 78 | _sDirectoryList* directoryList; 79 | _sDirectoryOffset* directoryOffsets; 80 | _sHashIndexGroup* directoryChildHashGroup; 81 | 82 | // V1 arc only 83 | _sFileInformationV1* fileInfoV1; 84 | 85 | // extra saved data 86 | u64 subFilesOffset = 0; 87 | u32 subFilesCount = 0; 88 | _sCompressedTableHeader compHeader; 89 | char* table; 90 | 91 | bool initialized = false; 92 | 93 | bool zstd_decompress(void* comp, void* decomp, u32 comp_size, u32 decomp_size) { 94 | ZSTD_resetDStream(dstream); 95 | 96 | ZSTD_inBuffer input = {comp, comp_size, 0}; 97 | ZSTD_outBuffer output = {decomp, decomp_size, 0}; 98 | 99 | size_t ret = ZSTD_decompressStream(dstream, &output, &input); 100 | if (ZSTD_isError(ret)) { 101 | log("err %s\n", ZSTD_getErrorName(ret)); 102 | return false; 103 | } 104 | 105 | return true; 106 | } 107 | 108 | size_t zstd_compress(void* comp, size_t comp_size, void* decomp, size_t decomp_size, int cLevel) { 109 | /*ZSTD_CCtx* const cctx = ZSTD_createCCtx(); 110 | ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, cLevel); 111 | 112 | ZSTD_outBuffer output = {comp, comp_size, 0}; 113 | ZSTD_inBuffer input = {decomp, decomp_size, 0}; 114 | 115 | size_t ret = ZSTD_compressStream(cctx, &output, &input); 116 | if (ZSTD_isError(ret)) { 117 | printf("err %s\n", ZSTD_getErrorName(ret)); 118 | return 0; 119 | } 120 | size_t ret2 = ZSTD_endStream(cctx, &output); 121 | if (ZSTD_isError(ret2)) { 122 | printf("err %s\n", ZSTD_getErrorName(ret2)); 123 | return 0; 124 | } 125 | ZSTD_freeCCtx(cctx); 126 | return output.pos;*/ 127 | 128 | ZSTD_CCtx* cctx = ZSTD_createCCtx(); 129 | ZSTD_parameters params; 130 | params.fParams = {0,0,1}; 131 | params.cParams = ZSTD_getCParams(cLevel, decomp_size, 0); 132 | size_t dataSize = ZSTD_compress_advanced(cctx, comp, comp_size, decomp, decomp_size, nullptr, 0, params); 133 | if(ZSTD_isError(dataSize)) { 134 | log("zstd fail: %s\n", ZSTD_getErrorName(dataSize)); 135 | return 0; 136 | } 137 | ZSTD_freeCCtx(cctx); 138 | return dataSize; 139 | } 140 | 141 | bool Init() { 142 | dstream = ZSTD_createDStream(); 143 | ZSTD_initDStream(dstream); 144 | std::fstream reader(this->arcPath); 145 | reader.read((char*)&header, sizeof(header)); 146 | if (header.Magic != Magic) { 147 | log("ARC magic does not match\n"); // add proper errors 148 | return false; 149 | } 150 | reader.seekg(header.FileSystemOffset); 151 | 152 | s32 tableSize; 153 | //char* table; 154 | table = ReadCompressedTable(reader, &tableSize); 155 | if (header.FileDataOffset < 0x8824AF68) { 156 | Version = 0x00010000; 157 | if (table) { 158 | ReadFileSystemV1(table, tableSize); 159 | //free(table); 160 | } 161 | } else { 162 | if (table) { 163 | ReadFileSystem(table, tableSize); 164 | //free(table); 165 | } 166 | } 167 | reader.close(); 168 | ZSTD_freeDStream(dstream); 169 | return true; 170 | } 171 | 172 | void ReadFileSystem(char* table, s32 tableSize) { 173 | membuf sbuf(table, table + tableSize); 174 | std::istream iTableReader(&sbuf); 175 | ObjectStream reader(iTableReader); 176 | 177 | _sFileSystemHeader fsHeader; 178 | reader.load(fsHeader); 179 | 180 | u32 extraFolder = 0; 181 | u32 extraCount = 0; 182 | u32 extraCount2 = 0; 183 | u32 extraSubCount = 0; 184 | 185 | if (tableSize >= 0x2992DD4) { 186 | // Version 3+ 187 | reader.load(Version); 188 | 189 | reader.load(extraFolder); 190 | reader.load(extraCount); 191 | 192 | u64 junk; 193 | reader.load(junk); // some extra thing :thinking 194 | reader.load(extraCount2); 195 | reader.load(extraSubCount); 196 | } else { 197 | Version = 0x00020000; 198 | reader.is.seekg(0x3C, reader.is.beg); 199 | } 200 | 201 | // skip this for now 202 | LOADARRAY(regionalInfo, _sRegionalInfo, 12); 203 | 204 | // Streams 205 | reader.load(streamHeader); 206 | 207 | LOADARRAY(streamUnk, _sStreamUnk, streamHeader.UnkCount); 208 | LOADARRAY(streamHashToName, _sStreamHashToName, streamHeader.StreamHashCount); 209 | LOADARRAY(streamNameToHash, _sStreamNameToHash, streamHeader.StreamHashCount); 210 | LOADARRAY(streamIndexToFile, _sStreamIndexToOffset, streamHeader.StreamIndexToOffsetCount); 211 | LOADARRAY(streamOffsets, _sStreamOffset, streamHeader.StreamOffsetCount); 212 | 213 | // Unknown 214 | u32 unkCount1, unkCount2; 215 | reader.load(unkCount1); 216 | reader.load(unkCount2); 217 | 218 | LOADARRAY(fileInfoUnknownTable, _sFileInformationUnknownTable, unkCount2); 219 | LOADARRAY(filePathToIndexHashGroup, _sHashIndexGroup, unkCount1); 220 | 221 | // FileTables 222 | 223 | LOADARRAY(fileInfoPath, _sFileInformationPath, fsHeader.FileInformationPathCount); 224 | LOADARRAY(fileInfoIndex, _sFileInformationIndex, fsHeader.FileInformationIndexCount); 225 | 226 | // directory tables 227 | 228 | // directory hashes by length and index to directory probably 0x6000 something 229 | LOADARRAY(directoryHashGroup, _sHashIndexGroup, fsHeader.DirectoryCount); 230 | LOADARRAY(directoryList, _sDirectoryList, fsHeader.DirectoryCount); 231 | LOADARRAY(directoryOffsets, _sDirectoryOffset, fsHeader.DirectoryOffsetCount1 + fsHeader.DirectoryOffsetCount2 + extraFolder); 232 | LOADARRAY(directoryChildHashGroup, _sHashIndexGroup, fsHeader.DirectoryHashSearchCount); 233 | 234 | // file information tables 235 | numFiles = fsHeader.FileInformationCount + fsHeader.SubFileCount2 + extraCount; 236 | LOADARRAY(fileInfoV2, _sFileInformationV2, numFiles); 237 | LOADARRAY(fileInfoSubIndex, _sFileInformationSubIndex, fsHeader.FileInformationSubIndexCount + fsHeader.SubFileCount2 + extraCount2); 238 | subFilesOffset = reader.pos; 239 | subFilesCount = fsHeader.SubFileCount + fsHeader.SubFileCount2 + extraSubCount; 240 | LOADARRAY(subFiles, _sSubFileInfo, fsHeader.SubFileCount + fsHeader.SubFileCount2 + extraSubCount); 241 | } 242 | 243 | void ReadFileSystemV1(char* table, s32 tableSize) { 244 | membuf sbuf(table, table + tableSize); 245 | std::istream iTableReader(&sbuf); 246 | ObjectStream reader(iTableReader); 247 | 248 | _sFileSystemHeaderV1 nodeHeader; 249 | reader.load(nodeHeader); 250 | 251 | reader.is.seekg(0x68, reader.is.beg); 252 | 253 | // Hash Table 254 | reader.is.seekg(0x8 * nodeHeader.Part1Count); 255 | 256 | // Hash Table 2 257 | LOADARRAY(streamNameToHash, _sStreamNameToHash, nodeHeader.Part1Count); 258 | 259 | // Hash Table 3 260 | LOADARRAY(streamIndexToFile, _sStreamIndexToOffset, nodeHeader.Part2Count); 261 | 262 | // stream offsets 263 | LOADARRAY(streamOffsets, _sStreamOffset, nodeHeader.MusicFileCount); 264 | 265 | // Another Hash Table 266 | reader.is.seekg(0xC * 0xE); 267 | 268 | //folders 269 | LOADARRAY(directoryList, _sDirectoryList, nodeHeader.FolderCount); 270 | 271 | //file offsets 272 | 273 | LOADARRAY(directoryOffsets, _sDirectoryOffset, nodeHeader.FileCount1 + nodeHeader.FileCount2); 274 | //DirectoryOffsets_2 = reader.ReadType<_sDirectoryOffsets>(R, NodeHeader.FileCount2); 275 | LOADARRAY(directoryChildHashGroup, _sHashIndexGroup, nodeHeader.HashFolderCount); 276 | LOADARRAY(fileInfoV1, _sFileInformationV1, nodeHeader.FileInformationCount); 277 | LOADARRAY(subFiles, _sSubFileInfo, nodeHeader.SubFileCount + nodeHeader.SubFileCount2); 278 | } 279 | 280 | char* ReadCompressedTable(std::fstream& reader, s32* tableSize) { 281 | //_sCompressedTableHeader compHeader; 282 | reader.read((char*)&compHeader, sizeof(compHeader)); 283 | 284 | char* tableBytes; 285 | s32 size; 286 | 287 | if (compHeader.DataOffset > 0x10) { 288 | u64 currPos = reader.tellg(); 289 | u64 tableStart = currPos - 0x10; 290 | reader.seekg(tableStart, reader.beg); 291 | reader.read((char*)&size, sizeof(s32)); 292 | reader.seekg(tableStart, reader.beg); 293 | tableBytes = (char*)malloc(size); 294 | reader.read(tableBytes, size); 295 | } else if (compHeader.DataOffset == 0x10) { 296 | char* compressedTableBytes = (char*)malloc(compHeader.CompressedSize); 297 | reader.read(compressedTableBytes, compHeader.CompressedSize); 298 | backupTable((char*)&compHeader, sizeof(compHeader), compressedTableBytes, compHeader.CompressedSize); 299 | size = compHeader.DecompressedSize; 300 | tableBytes = (char*)malloc(size); 301 | zstd_decompress(compressedTableBytes, tableBytes, compHeader.CompressedSize, compHeader.DecompressedSize); 302 | free(compressedTableBytes); 303 | } else { 304 | size = compHeader.CompressedSize; 305 | tableBytes = (char*)malloc(size); 306 | reader.read(tableBytes, size); 307 | } 308 | 309 | *tableSize = size; 310 | return tableBytes; 311 | } 312 | 313 | void Deinit() { 314 | free(regionalInfo); 315 | 316 | // Streams 317 | free(streamUnk); 318 | free(streamHashToName); 319 | free(streamNameToHash); 320 | free(streamIndexToFile); 321 | free(streamOffsets); 322 | 323 | // Unknown 324 | free(fileInfoUnknownTable); 325 | free(filePathToIndexHashGroup); 326 | 327 | // FileTables 328 | free(fileInfoPath); 329 | free(fileInfoIndex); 330 | 331 | // dir info 332 | free(directoryHashGroup); 333 | free(directoryList); 334 | free(directoryOffsets); 335 | free(directoryChildHashGroup); 336 | 337 | // file information tables 338 | free(fileInfoV2); 339 | free(fileInfoSubIndex); 340 | free(subFiles); 341 | 342 | // extra 343 | free(table); 344 | } 345 | 346 | void writeTableData (void* data, u64 totalSize, u64 offset, FILE * writer) { 347 | errno = 0; 348 | char* charData = (char*)data; 349 | for(u64 i = 0; i < totalSize; i++) table[offset+i] = charData[i]; 350 | if (compHeader.DataOffset > 0x10) printf("\nOOF\n"); // todo 351 | else if (compHeader.DataOffset == 0x10) { 352 | size_t origTableSize; 353 | if(std::filesystem::exists(tablePath)) 354 | origTableSize = std::filesystem::file_size(tablePath); 355 | else 356 | origTableSize = compHeader.CompressedSize; 357 | char* compTable = (char*)malloc(origTableSize); 358 | size_t compTableSize = zstd_compress(compTable, origTableSize, table, compHeader.DecompressedSize, 22); 359 | if(compTableSize == 0) { 360 | return; 361 | } 362 | compHeader.CompressedSize = compTableSize; 363 | if(fseek(writer, header.FileSystemOffset, SEEK_SET) != 0) printf("seek: %s\n", strerror(errno)); 364 | size_t ret = fwrite((char*)&compHeader, sizeof(char), sizeof(_sCompressedTableHeader), writer); 365 | if(ret != sizeof(_sCompressedTableHeader)) { 366 | printf("write header: %s\n", strerror(errno)); 367 | return; 368 | } 369 | ret = fwrite(compTable, sizeof(char), compTableSize, writer); 370 | if(ret != compTableSize) printf("write: %s\n", strerror(errno)); 371 | fclose(writer); 372 | free(compTable); 373 | } 374 | else printf("\nOOF2\n"); // todo 375 | } 376 | void backupTable(char* header, size_t headerSize, char* table, size_t tableSize) { 377 | if(!std::filesystem::exists(tablePath)) { 378 | FILE* writer = fopen(tablePath, "wb"); 379 | fwrite(header, sizeof(char), headerSize, writer); 380 | fwrite(table, sizeof(char), tableSize, writer); 381 | fclose(writer); 382 | } 383 | } 384 | 385 | void checkRegionalSuffix(std::string& path, int& regionIndex) { 386 | size_t semicolonIndex; 387 | if ((semicolonIndex = path.find(";")) != std::string::npos) 388 | path[semicolonIndex] = ':'; 389 | 390 | for (size_t i = 0; i < NUM_REGIONS; i++) { 391 | std::string regionTag = RegionTags[i]; 392 | size_t pos; 393 | if ((pos = path.find(regionTag)) != std::string::npos) { 394 | path.erase(pos, regionTag.size()); 395 | regionIndex = i; 396 | break; 397 | } 398 | } 399 | } 400 | 401 | public: 402 | int Version; 403 | void writeFileInfo(FILE * arc) { 404 | writeTableData(subFiles, sizeof(_sSubFileInfo) * subFilesCount, subFilesOffset, arc); 405 | } 406 | bool restoreTable() { 407 | if(std::filesystem::exists(tablePath)) { 408 | FILE* reader = fopen(tablePath, "rb"); 409 | size_t fileSize = std::filesystem::file_size(tablePath); 410 | char* tableBuf = new char[fileSize]; 411 | fread(tableBuf, sizeof(char), fileSize, reader); 412 | fclose(reader); 413 | FILE* writer = fopen(this->arcPath.c_str(), "r+b"); 414 | fseek(writer, header.FileSystemOffset, SEEK_SET); 415 | fwrite(tableBuf, sizeof(char), fileSize, writer); 416 | fclose(writer); 417 | Deinit(); 418 | Init(); 419 | return true; 420 | } 421 | return false; 422 | } 423 | int updateFileInfo(std::string path, u32 Offset = 0, u32 CompSize = 0, u32 DecompSize = 0, u32 Flags = 0) { 424 | int regionIndex = getRegion(); 425 | checkRegionalSuffix(path, regionIndex); 426 | 427 | u32 path_hash = crc32Calculate(strTolower(path).c_str(), path.size()); 428 | if ((Version != 0x00010000 && pathToFileInfo.count(path_hash) == 0) || 429 | (Version == 0x00010000 && pathToFileInfoV1.count(path_hash) == 0)) 430 | return -1; 431 | if (Version == 0x00010000) 432 | return -1; 433 | _sFileInformationV2 fileinfo = pathToFileInfo[path_hash]; 434 | auto subIndex = fileInfoSubIndex[fileinfo.SubIndexIndex]; 435 | //regional 436 | if ((fileinfo.Flags & 0x00008000) == 0x8000) { 437 | subIndex = fileInfoSubIndex[fileinfo.SubIndexIndex + 1 + regionIndex]; 438 | } 439 | if(Offset != 0) subFiles[subIndex.SubFileIndex].Offset = Offset; 440 | if(CompSize != 0) subFiles[subIndex.SubFileIndex].CompSize = CompSize; 441 | if(DecompSize != 0) subFiles[subIndex.SubFileIndex].DecompSize = DecompSize; 442 | if(Flags != 0) subFiles[subIndex.SubFileIndex].Flags = Flags; 443 | return 0; 444 | } 445 | static const size_t NUM_REGIONS = 14; 446 | std::string RegionTags[NUM_REGIONS] = 447 | { 448 | "+jp_ja", 449 | "+us_en", 450 | "+us_fr", 451 | "+us_es", 452 | "+eu_en", 453 | "+eu_fr", 454 | "+eu_es", 455 | "+eu_de", 456 | "+eu_nl", 457 | "+eu_it", 458 | "+eu_ru", 459 | "+kr_ko", 460 | "+zh_cn", 461 | "+zh_tw" 462 | }; 463 | 464 | void GetFileInformation(std::string arcFileName, s64& offset, u32& compSize, u32& decompSize, bool& regional, int regionIndex = 1) { 465 | checkRegionalSuffix(arcFileName, regionIndex); 466 | 467 | u32 path_hash = crc32Calculate(strTolower(arcFileName).c_str(), arcFileName.size()); 468 | 469 | offset = 0; 470 | compSize = 0; 471 | decompSize = 0; 472 | regional = false; 473 | 474 | if ((Version != 0x00010000 && pathToFileInfo.count(path_hash) == 0) || 475 | (Version == 0x00010000 && pathToFileInfoV1.count(path_hash) == 0)) { // 476 | // check for stream file 477 | if (pathCrc32ToStreamInfo.count(path_hash) != 0) { 478 | auto fileinfo = pathCrc32ToStreamInfo[path_hash]; 479 | 480 | if (fileinfo.Flags == 1 || fileinfo.Flags == 2) { 481 | if (fileinfo.Flags == 2 && regionIndex > 5) 482 | regionIndex = 0; 483 | 484 | auto streamindex = streamIndexToFile[(fileinfo.NameIndex >> 8) + regionIndex].FileIndex; 485 | auto offsetinfo = streamOffsets[streamindex]; 486 | offset = offsetinfo.Offset; 487 | compSize = (u32)offsetinfo.Size; 488 | decompSize = (u32)offsetinfo.Size; 489 | regional = true; 490 | } else { 491 | auto streamindex = streamIndexToFile[fileinfo.NameIndex >> 8].FileIndex; 492 | auto offsetinfo = streamOffsets[streamindex]; 493 | offset = offsetinfo.Offset; 494 | compSize = (u32)offsetinfo.Size; 495 | decompSize = (u32)offsetinfo.Size; 496 | } 497 | return; 498 | } 499 | 500 | return; 501 | } 502 | if (IsRegional(path_hash)) 503 | regional = true; 504 | 505 | if (Version == 0x00010000) 506 | GetFileInformation(pathToFileInfoV1[path_hash], offset, compSize, decompSize, regionIndex); 507 | else 508 | GetFileInformation(pathToFileInfo[path_hash], offset, compSize, decompSize, regionIndex); 509 | } 510 | 511 | void GetFileInformation(_sFileInformationV2 fileinfo, s64& offset, u32& compSize, u32& decompSize, int regionIndex) { 512 | auto fileIndex = fileInfoIndex[fileinfo.IndexIndex]; 513 | 514 | //redirect 515 | if ((fileinfo.Flags & 0x00000010) == 0x10) { 516 | fileinfo = fileInfoV2[fileIndex.FileInformationIndex]; 517 | } 518 | 519 | auto subIndex = fileInfoSubIndex[fileinfo.SubIndexIndex]; 520 | 521 | auto subFile = subFiles[subIndex.SubFileIndex]; 522 | auto directoryOffset = directoryOffsets[subIndex.DirectoryOffsetIndex]; 523 | 524 | //regional 525 | if ((fileinfo.Flags & 0x00008000) == 0x8000) { 526 | subIndex = fileInfoSubIndex[fileinfo.SubIndexIndex + 1 + regionIndex]; 527 | subFile = subFiles[subIndex.SubFileIndex]; 528 | directoryOffset = directoryOffsets[subIndex.DirectoryOffsetIndex]; 529 | } 530 | 531 | offset = (header.FileDataOffset + directoryOffset.Offset + (subFile.Offset << 2)); 532 | compSize = subFile.CompSize; 533 | decompSize = subFile.DecompSize; 534 | } 535 | 536 | void GetFileInformation(_sFileInformationV1 fileInfo, long& offset, u32& compSize, u32& decompSize, int regionIndex) { 537 | auto subFile = subFiles[fileInfo.SubFile_Index]; 538 | auto dirIndex = directoryList[fileInfo.DirectoryIndex >> 8].FullPathHashLengthAndIndex >> 8; 539 | auto directoryOffset = directoryOffsets[dirIndex]; 540 | 541 | //redirect 542 | if ((fileInfo.Flags & 0x00300000) == 0x00300000) 543 | { 544 | GetFileInformation(fileInfoV1[subFile.Flags&0xFFFFFF], offset, compSize, decompSize, regionIndex); 545 | return; 546 | } 547 | 548 | //regional 549 | if ((fileInfo.FileTableFlag >> 8) > 0) 550 | { 551 | subFile = subFiles[(fileInfo.FileTableFlag >> 8) + regionIndex]; 552 | directoryOffset = directoryOffsets[dirIndex + 1 + regionIndex]; 553 | } 554 | 555 | offset = (header.FileDataOffset + directoryOffset.Offset + (subFile.Offset << 2)); 556 | compSize = subFile.CompSize; 557 | decompSize = subFile.DecompSize; 558 | } 559 | 560 | bool IsRedirected(u32 path_hash) { 561 | if (pathToFileInfoV1.count(path_hash) != 0) 562 | return ((pathToFileInfoV1[path_hash].Flags & 0x00300000) == 0x00300000); 563 | if (pathToFileInfo.count(path_hash) != 0) 564 | return (pathToFileInfo[path_hash].Flags & 0x00000010) == 0x10; 565 | return false; 566 | } 567 | 568 | bool IsRegional(u32 path_hash) { 569 | if (pathToFileInfoV1.count(path_hash) != 0) 570 | return ((pathToFileInfoV1[path_hash].FileTableFlag >> 8) > 0); 571 | if (pathToFileInfo.count(path_hash) != 0) 572 | return ((pathToFileInfo[path_hash].Flags & 0x00008000) == 0x8000); 573 | return false; 574 | } 575 | 576 | void InitializePathToFileInfo() { 577 | for (size_t i = 0; i < streamHeader.StreamHashCount; i++) { 578 | auto v = streamNameToHash[i]; 579 | pathCrc32ToStreamInfo.try_emplace(v.Hash, v); 580 | } 581 | 582 | if (Version == 0x00010000) { 583 | for (size_t i = 0; i < FilePaths.size(); i++) 584 | { 585 | if (!pathToFileInfoV1.count(FilePaths[i]) == 0) 586 | pathToFileInfoV1.try_emplace(FilePaths[i], fileInfoV1[i]); 587 | } 588 | } else { 589 | for (size_t i = 0; i < FilePaths.size(); i++) { 590 | if (pathToFileInfo.count(FilePaths[i]) == 0) 591 | pathToFileInfo.try_emplace(FilePaths[i], fileInfoV2[i]); 592 | } 593 | } 594 | } 595 | 596 | std::vector GetFileList() { 597 | if (Version == 0x00010000) 598 | return GetFileListV1(); 599 | else 600 | return GetFileListV2(); 601 | } 602 | 603 | std::vector GetFileListV1() { 604 | std::vector filePaths; 605 | 606 | for (size_t i = 0; i < numFiles; i++) { 607 | auto fileInfo = fileInfoV1[i]; 608 | filePaths.push_back(fileInfo.Path); 609 | } 610 | 611 | return filePaths; 612 | } 613 | 614 | std::vector GetFileListV2() { 615 | std::vector filePaths; 616 | 617 | for (size_t i = 0; i < numFiles; i++) { 618 | auto fileInfo = fileInfoV2[i]; 619 | auto path = fileInfoPath[fileInfo.PathIndex]; 620 | filePaths.push_back(path.Path); 621 | } 622 | 623 | return filePaths; 624 | } 625 | 626 | ArcReader(std::string arcPath) { 627 | this->arcPath = arcPath; 628 | 629 | bool ret = Init(); 630 | if (!ret) return; 631 | FilePaths = GetFileList(); 632 | InitializePathToFileInfo(); 633 | initialized = ret; 634 | } 635 | 636 | bool isInitialized() { 637 | return initialized; 638 | } 639 | 640 | ~ArcReader() { 641 | Deinit(); 642 | } 643 | }; -------------------------------------------------------------------------------- /source/console.c: -------------------------------------------------------------------------------- 1 | #include "console.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #ifdef _3DS 10 | #include <3ds.h> 11 | #define CONSOLE_WIDTH 50 12 | #define CONSOLE_HEIGHT 30 13 | #elif defined(__SWITCH__) 14 | #include 15 | #define CONSOLE_WIDTH 80 16 | #define CONSOLE_HEIGHT 45 17 | #endif 18 | 19 | static PrintConsole status_console; 20 | static PrintConsole main_console; 21 | #if ENABLE_LOGGING 22 | static bool disable_logging = false; 23 | #endif 24 | 25 | 26 | #if defined(_3DS) 27 | static PrintConsole tcp_console; 28 | 29 | /*! initialize console subsystem */ 30 | void 31 | console_init(void) 32 | { 33 | consoleInit(GFX_TOP, &status_console); 34 | consoleSetWindow(&status_console, 0, 0, 50, 1); 35 | 36 | consoleInit(GFX_TOP, &main_console); 37 | consoleSetWindow(&main_console, 0, 1, 50, 29); 38 | 39 | consoleInit(GFX_BOTTOM, &tcp_console); 40 | 41 | consoleSelect(&main_console); 42 | } 43 | 44 | 45 | /*! print tcp tables */ 46 | static void 47 | print_tcp_table(void) 48 | { 49 | static SOCU_TCPTableEntry tcp_entries[32]; 50 | socklen_t optlen; 51 | size_t i; 52 | int rc, lines = 0; 53 | 54 | #ifdef ENABLE_LOGGING 55 | disable_logging = true; 56 | #endif 57 | 58 | consoleSelect(&tcp_console); 59 | console_print("\x1b[0;0H\x1b[K"); 60 | optlen = sizeof(tcp_entries); 61 | rc = SOCU_GetNetworkOpt(SOL_CONFIG, NETOPT_TCP_TABLE, tcp_entries, &optlen); 62 | if(rc != 0 && errno != ENODEV) 63 | console_print(RED "tcp table: %d %s\n\x1b[J\n" RESET, errno, strerror(errno)); 64 | else if(rc == 0) 65 | { 66 | for(i = 0; lines < 30 && i < optlen / sizeof(SOCU_TCPTableEntry); ++i) 67 | { 68 | SOCU_TCPTableEntry *entry = &tcp_entries[i]; 69 | struct sockaddr_in *local = (struct sockaddr_in*)&entry->local; 70 | struct sockaddr_in *remote = (struct sockaddr_in*)&entry->remote; 71 | 72 | console_print(GREEN "%stcp[%zu]: ", i == 0 ? "" : "\n", i); 73 | switch(entry->state) 74 | { 75 | case TCP_STATE_CLOSED: 76 | console_print("CLOSED\x1b[K"); 77 | local = remote = NULL; 78 | break; 79 | 80 | case TCP_STATE_LISTEN: 81 | console_print("LISTEN\x1b[K"); 82 | remote = NULL; 83 | break; 84 | 85 | case TCP_STATE_ESTABLISHED: 86 | console_print("ESTABLISHED\x1b[K"); 87 | break; 88 | 89 | case TCP_STATE_FINWAIT1: 90 | console_print("FINWAIT1\x1b[K"); 91 | break; 92 | 93 | case TCP_STATE_FINWAIT2: 94 | console_print("FINWAIT2\x1b[K"); 95 | break; 96 | 97 | case TCP_STATE_CLOSE_WAIT: 98 | console_print("CLOSE_WAIT\x1b[K"); 99 | break; 100 | 101 | case TCP_STATE_LAST_ACK: 102 | console_print("LAST_ACK\x1b[K"); 103 | break; 104 | 105 | case TCP_STATE_TIME_WAIT: 106 | console_print("TIME_WAIT\x1b[K"); 107 | break; 108 | 109 | default: 110 | console_print("State %lu\x1b[K", entry->state); 111 | break; 112 | } 113 | 114 | ++lines; 115 | 116 | if(local && (lines++ < 30)) 117 | console_print("\n Local %s:%u\x1b[K", inet_ntoa(local->sin_addr), 118 | ntohs(local->sin_port)); 119 | 120 | if(remote && (lines++ < 30)) 121 | console_print("\n Peer %s:%u\x1b[K", inet_ntoa(remote->sin_addr), 122 | ntohs(remote->sin_port)); 123 | } 124 | 125 | console_print(RESET "\x1b[J"); 126 | } 127 | else 128 | console_print("\x1b[2J"); 129 | 130 | consoleSelect(&main_console); 131 | 132 | #ifdef ENABLE_LOGGING 133 | disable_logging = false; 134 | #endif 135 | } 136 | 137 | #elif defined(__SWITCH__) 138 | /*! initialize console subsystem */ 139 | void 140 | console_init(void) 141 | { 142 | consoleInit(&status_console); 143 | consoleSetWindow(&status_console, 0, 0, CONSOLE_WIDTH, 1); 144 | 145 | consoleInit(&main_console); 146 | consoleSetWindow(&main_console, 0, 1, CONSOLE_WIDTH, CONSOLE_HEIGHT-1); 147 | 148 | consoleSelect(&main_console); 149 | } 150 | void 151 | console_exit(void) 152 | { 153 | consoleExit(&status_console); 154 | consoleExit(&main_console); 155 | } 156 | #endif 157 | 158 | #if defined(_3DS) || defined(__SWITCH__) 159 | 160 | 161 | /*! set status bar contents 162 | * 163 | * @param[in] fmt format string 164 | * @param[in] ... format arguments 165 | */ 166 | void 167 | console_set_status(const char *fmt, ...) 168 | { 169 | va_list ap; 170 | 171 | consoleSelect(&status_console); 172 | va_start(ap, fmt); 173 | vprintf(fmt, ap); 174 | #ifdef ENABLE_LOGGING 175 | vfprintf(stderr, fmt, ap); 176 | #endif 177 | va_end(ap); 178 | consoleSelect(&main_console); 179 | } 180 | 181 | /*! add text to the console 182 | * 183 | * @param[in] fmt format string 184 | * @param[in] ... format arguments 185 | */ 186 | void 187 | console_print(const char *fmt, ...) 188 | { 189 | va_list ap; 190 | 191 | va_start(ap, fmt); 192 | vprintf(fmt, ap); 193 | #ifdef ENABLE_LOGGING 194 | if(!disable_logging) 195 | vfprintf(stderr, fmt, ap); 196 | #endif 197 | va_end(ap); 198 | } 199 | 200 | /*! print debug message 201 | * 202 | * @param[in] fmt format string 203 | * @param[in] ... format arguments 204 | */ 205 | void 206 | debug_print(const char *fmt, ...) 207 | { 208 | #ifdef ENABLE_LOGGING 209 | va_list ap; 210 | 211 | va_start(ap, fmt); 212 | vfprintf(stderr, fmt, ap); 213 | va_end(ap); 214 | #endif 215 | } 216 | 217 | /*! draw console to screen */ 218 | void 219 | console_render(void) 220 | { 221 | /* print tcp table */ 222 | #ifdef _3DS 223 | print_tcp_table(); 224 | #endif 225 | /* flush framebuffer */ 226 | #ifdef _3DS 227 | gfxFlushBuffers(); 228 | gspWaitForVBlank(); 229 | gfxSwapBuffers(); 230 | #endif 231 | #ifdef __SWITCH__ 232 | consoleUpdate(NULL); 233 | #endif 234 | } 235 | 236 | 237 | #else 238 | 239 | /* this is a lot easier when you have a real console */ 240 | 241 | void 242 | console_init(void) 243 | { 244 | } 245 | 246 | void 247 | console_set_status(const char *fmt, ...) 248 | { 249 | va_list ap; 250 | va_start(ap, fmt); 251 | vprintf(fmt, ap); 252 | va_end(ap); 253 | fputc('\n', stdout); 254 | } 255 | 256 | void 257 | console_print(const char *fmt, ...) 258 | { 259 | va_list ap; 260 | va_start(ap, fmt); 261 | vprintf(fmt, ap); 262 | va_end(ap); 263 | } 264 | 265 | void 266 | debug_print(const char *fmt, ...) 267 | { 268 | #ifdef ENABLE_LOGGING 269 | va_list ap; 270 | va_start(ap, fmt); 271 | vfprintf(stderr, fmt, ap); 272 | va_end(ap); 273 | #endif 274 | } 275 | 276 | void console_render(void) 277 | { 278 | } 279 | #endif 280 | 281 | -------------------------------------------------------------------------------- /source/dumper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include "utils.h" 10 | #include 11 | 12 | bool dump_done = false; 13 | bool exfat = false; 14 | bool verifyDump = false; 15 | std::string outPath = dataArcPath(getCFW()); 16 | const int MD5_DIGEST_LENGTH = 16; 17 | 18 | void md5HashFromFile(std::string filename, unsigned char* out) 19 | { 20 | FILE *inFile = fopen (filename.c_str(), "rb"); 21 | mbedtls_md5_context md5Context; 22 | int bytes; 23 | const u64 bufSize = 512000; 24 | unsigned char data[bufSize]; 25 | 26 | if (inFile == NULL) 27 | { 28 | printf (CONSOLE_RED "\nThe data.arc file can not be opened." CONSOLE_RESET); 29 | return; 30 | } 31 | mbedtls_md5_init (&md5Context); 32 | mbedtls_md5_starts_ret(&md5Context); 33 | 34 | fseek(inFile, 0, SEEK_END); 35 | u64 size = ftell(inFile); 36 | fseek(inFile, 0, SEEK_SET); 37 | u64 sizeRead = 0; 38 | int percent = 0; 39 | while ((bytes = fread (data, 1, bufSize, inFile)) != 0) 40 | { 41 | mbedtls_md5_update_ret (&md5Context, data, bytes); 42 | sizeRead += bytes; 43 | percent = sizeRead * 100 / size; 44 | print_progress(percent, 100); 45 | consoleUpdate(NULL); 46 | } 47 | mbedtls_md5_finish_ret (&md5Context, out); 48 | fclose(inFile); 49 | return; 50 | } 51 | 52 | 53 | void copy(const char* from, const char* to, bool exfat = false) 54 | { 55 | Result rc=0; 56 | //const u64 fat32Max = 0xFFFFFFFF; 57 | //const u64 splitSize = 0xFFFF0000; 58 | const u64 smashTID = 0x01006A800016E000; 59 | const u64 bufSize = 0x0F116C00; 60 | 61 | if(runningTID() != smashTID) 62 | { 63 | printf(CONSOLE_RED "\nYou must override Smash for this application to work properly.\nHold 'R' while launching Smash to do so." CONSOLE_RESET); 64 | return; 65 | } 66 | if (!applicationMode) 67 | { 68 | printf(CONSOLE_RED "\nNo applet mode.\nYou must override Smash for this application to work properly.\nHold 'R' while launching Smash to do so." CONSOLE_RESET); 69 | return; 70 | } 71 | std::string backups = "/UltimateModManager/backups"; 72 | removeRecursive(backups); 73 | mkdir(backups.c_str(), 0777); 74 | remove(tablePath); 75 | rc = fsFsDeleteFile(fsdevGetDeviceFileSystem("sdmc"), outPath.c_str()); 76 | if(R_FAILED(rc)) 77 | { 78 | // 0x202 = Path does not exist. 79 | if(rc == 0x202) 80 | { 81 | rc = fsdevDeleteDirectoryRecursively(outPath.c_str()); 82 | if(R_FAILED(rc) && rc != 0x202) 83 | { 84 | printf(CONSOLE_RED "\nFailed to remove the data.arc folder. error: 0x%x" CONSOLE_RESET, rc); 85 | return; 86 | } 87 | } 88 | else 89 | { 90 | printf(CONSOLE_RED "\nFailed to remove the data.arc. error: 0x%x" CONSOLE_RESET, rc); 91 | return; 92 | } 93 | } 94 | romfsMountFromCurrentProcess("romfs"); 95 | FILE* source = fopen(from, "rb"); 96 | if(source == nullptr) 97 | { 98 | printf (CONSOLE_RED "\nThe romfs could not be read." CONSOLE_RESET); 99 | fclose(source); 100 | romfsUnmount("romfs"); 101 | return; 102 | } 103 | fseek(source, 0, SEEK_END); 104 | u64 size = ftell(source); 105 | fseek(source, 0, SEEK_SET); 106 | 107 | std::uintmax_t spaceAvailable = std::filesystem::space(to).available; 108 | if(spaceAvailable < size) 109 | { 110 | double mbAvailable = spaceAvailable/1024.0/1024.0; 111 | double mbNeeded = size/1024.0/1024.0; 112 | printf(CONSOLE_RED "\nNot enough storage space on the SD card.\nYou need %.2f MB, you have %.2f MB available" CONSOLE_RESET, mbNeeded, mbAvailable); 113 | fclose(source); 114 | romfsUnmount("romfs"); 115 | return; 116 | } 117 | std::string folder(to); 118 | folder = folder.substr(0, folder.find_last_of("/")); 119 | if(!std::filesystem::exists(folder)) 120 | { 121 | mkdirs(folder, 0744); 122 | } 123 | if(!exfat) { 124 | rc = fsdevCreateFile(to, 0, FsCreateOption_BigFile); 125 | if (R_FAILED(rc)) { 126 | printf("\nfsdevCreateFile() failed: 0x%x", rc); 127 | fclose(source); 128 | romfsUnmount("romfs"); 129 | return; 130 | } 131 | } 132 | 133 | FILE* dest = fopen(to, "wb+"); 134 | if(dest == nullptr) 135 | { 136 | printf(CONSOLE_RED "\nCould not open the destination file. error: %s" CONSOLE_RESET, strerror(errno)); 137 | fclose(dest); 138 | fclose(source); 139 | romfsUnmount("romfs"); 140 | return; 141 | } 142 | 143 | char* buf = new char[bufSize]; 144 | u64 sizeWritten = 0; 145 | size_t ret; 146 | int percent = 0; 147 | u32 srcCRC; 148 | u32 destCRC; 149 | u32 failCount = 0; 150 | u64 dataSize = bufSize; 151 | if(size == 0) 152 | printf(CONSOLE_RED "\nThere was a problem opening the data.arc" CONSOLE_RESET); 153 | while(sizeWritten < size) 154 | { 155 | if(sizeWritten + dataSize > size) 156 | { 157 | dataSize = size-sizeWritten; 158 | } 159 | fread(buf, sizeof(char), dataSize, source); 160 | ret = fwrite(buf, sizeof(char), dataSize, dest); 161 | if(ret != dataSize) 162 | { 163 | printf(CONSOLE_RED "\nSomething went wrong!" CONSOLE_RESET); 164 | if(sizeWritten > 0 && exfat) 165 | printf(CONSOLE_YELLOW "\nYou sure your sd card is exfat?" CONSOLE_RESET); 166 | fclose(dest); 167 | fclose(source); 168 | romfsUnmount("romfs"); 169 | return; 170 | } 171 | if(verifyDump) 172 | { 173 | srcCRC = crc32Calculate(buf, dataSize); 174 | fseek(dest, -dataSize, SEEK_CUR); 175 | fread(buf, sizeof(char), dataSize, dest); 176 | destCRC = crc32Calculate(buf, dataSize); 177 | if(srcCRC != destCRC) 178 | { 179 | if(failCount < 3) 180 | { 181 | ++failCount; 182 | printf(CONSOLE_RED "\nVerification failed %u time(s). Retrying." CONSOLE_RESET, failCount); 183 | consoleUpdate(NULL); 184 | fseek(dest, -dataSize, SEEK_CUR); 185 | fseek(source, -dataSize, SEEK_CUR); 186 | continue; 187 | } 188 | else 189 | { 190 | printf(CONSOLE_RED "\nVerification failed. An error has occured in writing the file. Halting dump." CONSOLE_RESET); 191 | fclose(dest); 192 | fclose(source); 193 | delete[] buf; 194 | romfsUnmount("romfs"); 195 | fsFsDeleteFile(fsdevGetDeviceFileSystem("sdmc"), outPath.c_str()); 196 | return; 197 | } 198 | } 199 | else if (failCount != 0) 200 | { 201 | // clearing "failed" lines 202 | printf("\x1b[%uA\x1b[0J",failCount); 203 | failCount = 0; 204 | } 205 | } 206 | sizeWritten += dataSize; 207 | percent = sizeWritten * 100 / size; 208 | print_progress(percent, 100); 209 | //printf("\x1b[20;2Hdest pos: %lld, source pos: %lld", (long long int)dest.tellp(), (long long int)source.tellg()); // Debug log 210 | //printf("\x1b[22;2H%lu/%lu", sizeWritten, size); // Debug log 211 | consoleUpdate(NULL); 212 | } 213 | fclose(source); 214 | fclose(dest); 215 | delete[] buf; 216 | romfsUnmount("romfs"); 217 | //printf("\n"); 218 | } 219 | 220 | void dumperMainLoop(int kDown) { 221 | u64 kHeld = hidKeysHeld(CONTROLLER_P1_AUTO); 222 | if (kDown & KEY_X) 223 | { 224 | if(kHeld & KEY_R) { 225 | if(std::filesystem::exists(outPath)) 226 | { 227 | printf("\nBeginning hash generation...\n" CONSOLE_ESC(s)); 228 | consoleUpdate(NULL); 229 | unsigned char out[MD5_DIGEST_LENGTH]; 230 | u64 startTime = std::time(0); 231 | // Should I block home button here too? 232 | appletSetMediaPlaybackState(true); 233 | md5HashFromFile(outPath, out); 234 | appletSetMediaPlaybackState(false); 235 | u64 endTime = std::time(0); 236 | printf("\nMD5:"); 237 | for(int i = 0; i < MD5_DIGEST_LENGTH; i++) printf("%02x", out[i]); 238 | printf("\nHashing took %.2f minutes", (float)(endTime - startTime)/60); 239 | consoleUpdate(NULL); 240 | shortVibratePattern(); 241 | } 242 | else 243 | { 244 | printf(CONSOLE_RED "\nNo data.arc file found on the SD card." CONSOLE_RESET); 245 | } 246 | } 247 | else { 248 | appletRequestLaunchApplication(smashTID, NULL); 249 | } 250 | } 251 | if (kDown & KEY_Y) exfat = true; 252 | if (kDown & KEY_A) exfat = false; 253 | if ((kDown & KEY_A || kDown & KEY_Y) && !dump_done) 254 | { 255 | if(kHeld & KEY_R) 256 | { 257 | verifyDump = true; 258 | printf("\nBeginning the dumping process with verification..."); 259 | } 260 | else 261 | { 262 | verifyDump = false; 263 | printf("\nBeginning the dumping process..."); 264 | } 265 | printf("\nThe dump will be saved to: %s\n" CONSOLE_ESC(s), outPath.c_str()); 266 | consoleUpdate(NULL); 267 | u64 startTime = std::time(0); 268 | appletBeginBlockingHomeButton(0); 269 | appletSetMediaPlaybackState(true); 270 | copy("romfs:/data.arc", outPath.c_str(), exfat); 271 | appletEndBlockingHomeButton(); 272 | appletSetMediaPlaybackState(false); 273 | u64 endTime = std::time(0); 274 | 275 | dump_done = true; // So you don't accidentally dump twice 276 | printf("\nCompleted in %.2f minutes.", (float)(endTime - startTime)/60); 277 | printf("\nPress 'X' to verify the dump by launching smash"); 278 | printf("\nPress 'B' to return to the main menu\n"); 279 | consoleUpdate(NULL); 280 | shortVibratePattern(); 281 | } 282 | 283 | if (kDown & KEY_B) { 284 | menu = mainMenu; 285 | printMainMenu(); 286 | } 287 | } 288 | -------------------------------------------------------------------------------- /source/ftp_main.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #ifdef _3DS 7 | #include <3ds.h> 8 | #elif defined(__SWITCH__) 9 | #include 10 | #endif 11 | #include "console.h" 12 | #include "ftp.h" 13 | #include "menu.h" 14 | 15 | /*! looping mechanism 16 | * 17 | * @param[in] callback function to call during each iteration 18 | * 19 | * @returns loop status 20 | */ 21 | static loop_status_t 22 | loop(loop_status_t (*callback)(void)) 23 | { 24 | loop_status_t status = LOOP_CONTINUE; 25 | 26 | #ifdef _3DS 27 | while(aptMainLoop()) 28 | { 29 | status = callback(); 30 | console_render(); 31 | if(status != LOOP_CONTINUE) 32 | return status; 33 | } 34 | return LOOP_EXIT; 35 | #elif defined(__SWITCH__) 36 | appletSetMediaPlaybackState(true); 37 | while(appletMainLoop()) 38 | { 39 | console_render(); 40 | status = callback(); 41 | if(status != LOOP_CONTINUE) 42 | return status; 43 | } 44 | appletSetMediaPlaybackState(false); 45 | menu = mainMenu; 46 | printMainMenu(); 47 | return LOOP_EXIT; 48 | #else 49 | while(status == LOOP_CONTINUE) 50 | status = callback(); 51 | return status; 52 | #endif 53 | } 54 | 55 | #ifdef _3DS 56 | /*! wait until the B button is pressed 57 | * 58 | * @returns loop status 59 | */ 60 | static loop_status_t 61 | wait_for_b(void) 62 | { 63 | /* update button state */ 64 | hidScanInput(); 65 | 66 | /* check if B was pressed */ 67 | if(hidKeysDown() & KEY_B) { 68 | menu = mainMenu; 69 | printMainMenu(); 70 | return LOOP_EXIT; 71 | } 72 | 73 | /* B was not pressed */ 74 | return LOOP_CONTINUE; 75 | } 76 | #endif 77 | 78 | /*! entry point 79 | * 80 | * @param[in] argc unused 81 | * @param[in] argv unused 82 | * 83 | * returns exit status 84 | */ 85 | int 86 | ftp_main() 87 | { 88 | loop_status_t status = LOOP_RESTART; 89 | 90 | #ifdef _3DS 91 | /* initialize needed 3DS services */ 92 | acInit(); 93 | gfxInitDefault(); 94 | gfxSet3D(false); 95 | sdmcWriteSafe(false); 96 | /* initialize needed Switch services */ 97 | #elif defined(__SWITCH__) 98 | nifmInitialize(NifmServiceType_User); 99 | #endif 100 | 101 | /* initialize console subsystem */ 102 | console_init(); 103 | 104 | #ifdef ENABLE_LOGGING 105 | /* open log file */ 106 | #ifdef _3DS 107 | FILE *fp = freopen("/ftpd.log", "wb", stderr); 108 | #else 109 | FILE *fp = freopen("ftpd.log", "wb", stderr); 110 | #endif 111 | if(fp == NULL) 112 | { 113 | console_print(RED "freopen: 0x%08X\n" RESET, errno); 114 | goto log_fail; 115 | } 116 | 117 | /* truncate log file */ 118 | if(ftruncate(fileno(fp), 0) != 0) 119 | { 120 | console_print(RED "ftruncate: 0x%08X\n" RESET, errno); 121 | goto log_fail; 122 | } 123 | #endif 124 | 125 | console_set_status("\n" GREEN STATUS_STRING 126 | #ifdef ENABLE_LOGGING 127 | " DEBUG" 128 | #endif 129 | RESET); 130 | 131 | while(status == LOOP_RESTART) 132 | { 133 | /* initialize ftp subsystem */ 134 | if(ftp_init() == 0) 135 | { 136 | /* ftp loop */ 137 | status = loop(ftp_loop); 138 | 139 | /* done with ftp */ 140 | ftp_exit(); 141 | menu = mainMenu; 142 | printMainMenu(); 143 | status = LOOP_EXIT; 144 | } 145 | } 146 | 147 | #if defined(_3DS) 148 | console_print("Press B to exit\n"); 149 | #endif 150 | 151 | #ifdef ENABLE_LOGGING 152 | log_fail: 153 | if(fclose(stderr) != 0) 154 | console_print(RED "fclose(%d): 0x%08X\n" RESET, fileno(stderr), errno); 155 | #endif 156 | 157 | #ifdef _3DS 158 | loop(wait_for_b); 159 | 160 | /* deinitialize 3DS services */ 161 | gfxExit(); 162 | acExit(); 163 | #elif defined(__SWITCH__) 164 | /* deinitialize Switch services */ 165 | nifmExit(); 166 | #endif 167 | return 0; 168 | } -------------------------------------------------------------------------------- /source/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include "dumper.h" 9 | #include "mod_installer.h" 10 | #include "menu.h" 11 | #include "utils.h" 12 | extern "C" { 13 | #include "ftp_main.h" 14 | #include "console.h" 15 | } 16 | 17 | void mainMenuLoop(int kDown) { 18 | if (kDown & KEY_A) { 19 | installation_finish = false; 20 | updateInstalledList(); 21 | menu = modInstallerMenu; 22 | consoleClear(); 23 | } 24 | 25 | else if (kDown & KEY_X) { 26 | dump_done = false; 27 | menu = arcDumperMenu; 28 | printDumperMenu(); 29 | } 30 | 31 | else if (kDown & KEY_Y) { 32 | nifmInitialize(NifmServiceType_User); 33 | NifmInternetConnectionStatus connectionStatus; 34 | if(R_SUCCEEDED(nifmGetInternetConnectionStatus(nullptr, nullptr, &connectionStatus))) { 35 | if(connectionStatus == NifmInternetConnectionStatus_Connected) 36 | menu = ftpMenu; 37 | } 38 | nifmExit(); 39 | } 40 | } 41 | 42 | int main(int argc, char **argv) 43 | { 44 | console_init(); 45 | console_set_status("\n" GREEN "Mod Installer" RESET); 46 | if(!std::filesystem::exists(mods_root)) 47 | mkdirs(mods_root, 0777); 48 | mkdir(backups_root, 0777); 49 | if(std::filesystem::is_directory(std::filesystem::status(arc_path)) && !std::filesystem::is_empty(arc_path)) 50 | fsdevSetConcatenationFileAttribute(arc_path.c_str()); 51 | if(!std::filesystem::exists(outPath) || std::filesystem::is_empty(arc_path)) 52 | { 53 | menu = arcDumperMenu; 54 | printDumperMenu(); 55 | } 56 | remove(log_file); 57 | applicationMode = isApplicationMode(); 58 | updateInstalledList(); 59 | 60 | while(appletMainLoop()) 61 | { 62 | hidScanInput(); 63 | 64 | u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); 65 | 66 | if(kDown & KEY_PLUS) break; // break in order to return to hbmenu 67 | 68 | switch(menu) { 69 | case mainMenu: 70 | mainMenuLoop(kDown); 71 | break; 72 | case modInstallerMenu: 73 | modInstallerMainLoop(kDown); 74 | break; 75 | case arcDumperMenu: 76 | dumperMainLoop(kDown); 77 | break; 78 | case ftpMenu: 79 | ftp_main(); 80 | break; 81 | } 82 | 83 | consoleUpdate(NULL); 84 | } 85 | console_exit(); 86 | return 0; 87 | } -------------------------------------------------------------------------------- /source/menu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | extern "C" { 4 | #include "console.h" 5 | } 6 | 7 | enum menus { 8 | mainMenu, 9 | modInstallerMenu, 10 | arcDumperMenu, 11 | ftpMenu 12 | }; 13 | 14 | menus menu = modInstallerMenu; 15 | 16 | void printMainMenu() { 17 | consoleClear(); 18 | console_set_status("\n" GREEN "Ultimate Mod Manager " VERSION_STRING " " RESET); 19 | printf("\n\nPress 'A' to go to the Mod Installer"); 20 | printf("\nPress 'X' to go to the Data Arc Dumper"); 21 | printf("\nPress 'Y' to go to the FTP Server"); 22 | } 23 | void printDumperMenu() { 24 | consoleClear(); 25 | console_set_status("\n" GREEN "Data Arc Dumper" RESET); 26 | printf("\n\nPress 'A' to dump as a split file"); 27 | printf("\nPress 'Y' to dump as a single file (exFAT only)"); 28 | printf("\nHold 'R' when starting a dump to verify that it dumps correctly (takes longer)"); 29 | printf("\nPress 'X' to launch smash"); 30 | printf("\nPress 'B' to return to the main menu"); 31 | printf("\nPress 'R'+'X' to generate an MD5 hash of the file\n"); 32 | } -------------------------------------------------------------------------------- /source/mod_installer.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #define ZSTD_STATIC_LINKING_ONLY 10 | #include 11 | #include 12 | #include "utils.h" 13 | #include "arcReader.h" 14 | #include 15 | #include 16 | 17 | #define FILE_READ_SIZE 0x20000 18 | 19 | ArcReader* arcReader = nullptr; 20 | 21 | bool uninstall = false; 22 | bool deleteMod = false; 23 | std::queue TableWriteFiles; 24 | typedef struct ModFile { 25 | std::string mod_path; 26 | s64 offset; 27 | u32 compSize; 28 | u32 decompSize; 29 | bool regional; 30 | std::string dupeBackupPath = ""; 31 | 32 | ModFile(std::string mod_path, s64 offset, u32 compSize, u32 decompSize, bool regional) 33 | : mod_path(std::move(mod_path)), offset(offset), compSize(compSize), decompSize(decompSize), regional(regional) {} 34 | } ModFile; 35 | std::vector mod_files; 36 | 37 | bool installation_finish = false; 38 | s64 mod_folder_index = 0; 39 | ZSTD_CCtx* compContext = nullptr; 40 | std::list installIDXs; 41 | std::list InstalledMods; 42 | std::queue modDirList; 43 | 44 | const char* mods_root = "sdmc:/UltimateModManager/mods/"; 45 | const char* backups_root = "sdmc:/UltimateModManager/backups/"; 46 | std::string arc_path = dataArcPath(getCFW()); 47 | 48 | int regionIndex = getRegion(); 49 | 50 | void updateInstalledList() { 51 | InstalledMods.clear(); 52 | for(auto& dirEntry: std::filesystem::directory_iterator(backups_root)) { 53 | if(dirEntry.is_directory()) 54 | InstalledMods.push_back(dirEntry.path().filename()); 55 | } 56 | } 57 | 58 | bool ZSTDFileIsFrame(const char* filePath) { 59 | const size_t magicSize = 4; 60 | unsigned char buf[magicSize]; 61 | FILE* file = fopen(filePath, "rb"); 62 | fread(buf, magicSize, sizeof(unsigned char), file); 63 | fclose(file); 64 | return ZSTD_isFrame(buf, magicSize); 65 | } 66 | 67 | bool paddable(u64 padSize) { 68 | return !(padSize == 1 || padSize == 2 || padSize == 5); 69 | } 70 | 71 | bool compressFile(const char* path, u64 compSize, u64 &dataSize, char* outBuff, u64 bufSize) 72 | { 73 | FILE* inFile = fopen(path, "rb"); 74 | fseek(inFile, 0, SEEK_END); 75 | u64 inSize = ftell(inFile); 76 | fseek(inFile, 0, SEEK_SET); 77 | char* inBuff = new(std::nothrow) char[inSize]; 78 | if(inBuff == nullptr) { 79 | log(CONSOLE_RED "Failed to allocate for mod file. Not enough memory.\n" CONSOLE_RESET); 80 | return false; 81 | } 82 | fread(inBuff, sizeof(char), inSize, inFile); 83 | fclose(inFile); 84 | int compLvl = 3; 85 | s64 bytesAway = LONG_MAX; 86 | if(compContext == nullptr) compContext = ZSTD_createCCtx(); 87 | // Minimize header size 88 | ZSTD_CCtx_setParameter(compContext, ZSTD_c_contentSizeFlag, 1); 89 | ZSTD_CCtx_setParameter(compContext, ZSTD_c_checksumFlag, 0); 90 | ZSTD_CCtx_setParameter(compContext, ZSTD_c_dictIDFlag, 1); 91 | do { 92 | ZSTD_CCtx_reset(compContext, ZSTD_reset_session_only); 93 | ZSTD_CCtx_setParameter(compContext, ZSTD_c_compressionLevel, compLvl); 94 | dataSize = ZSTD_compress2(compContext, outBuff, bufSize, inBuff, inSize); 95 | if(ZSTD_isError(dataSize)) 96 | log("%s Error at lvl %d: %lx %s\n", path, compLvl, dataSize, ZSTD_getErrorName(dataSize)); 97 | if(!ZSTD_isError(dataSize) && dataSize > compSize) { 98 | debug_log("Compressed \"%s\" to %lu bytes, %lu bytes away from goal, at level %d.\n", path, dataSize, dataSize - compSize, compLvl); 99 | if((s64)(dataSize - compSize) < bytesAway) 100 | bytesAway = dataSize - compSize; 101 | } 102 | compLvl++; 103 | // Caused issues too often 104 | //if(compLvl==10) compLvl = 17; // skip arbitrary amount of levels for speed. 105 | } 106 | while((dataSize > compSize || ZSTD_isError(dataSize) || !paddable(compSize - dataSize)) && compLvl <= ZSTD_maxCLevel()); 107 | 108 | if(compLvl > ZSTD_maxCLevel()) { 109 | if(bytesAway != LONG_MAX) 110 | log("%lx bytes too large " CONSOLE_RED "compression failed on %s\n" CONSOLE_RESET, bytesAway, path); 111 | else 112 | log(CONSOLE_RED "Can not compress %s to %lx bytes\n" CONSOLE_RESET, path, compSize); 113 | return false; 114 | } 115 | delete[] inBuff; 116 | return true; 117 | } 118 | 119 | void checkForBackups(std::vector &mod_files) { 120 | for(auto& dirEntry: std::filesystem::recursive_directory_iterator(backups_root)) { 121 | if(dirEntry.is_regular_file()) { 122 | for(ModFile& mod : mod_files) { 123 | if(dirEntry.path().filename() == strsprintf("0x%lx.backup", mod.offset)) { 124 | mod.dupeBackupPath = dirEntry.path(); 125 | } 126 | } 127 | } 128 | } 129 | } 130 | 131 | void backup(char* modName, u64 modSize, u64 offset, FILE* arc, std::string dupeBackupPath) { 132 | int fileNameSize = snprintf(nullptr, 0, "%s%s/0x%lx.backup", backups_root, modName, offset) + 1; 133 | char* backup_path = new char[fileNameSize]; 134 | snprintf(backup_path, fileNameSize, "%s%s/0x%lx.backup", backups_root, modName, offset); 135 | std::string parentPath = std::filesystem::path(backup_path).parent_path(); 136 | if(!std::filesystem::exists(parentPath)) 137 | mkdir(parentPath.c_str(), 0777); 138 | if(dupeBackupPath != "") { 139 | rename(dupeBackupPath.c_str(), backup_path); 140 | rmdir(std::filesystem::path(dupeBackupPath).parent_path().c_str()); 141 | delete[] backup_path; 142 | return; 143 | } 144 | fseek(arc, offset, SEEK_SET); 145 | char* buf = new char[modSize]; 146 | fread(buf, sizeof(char), modSize, arc); 147 | 148 | FILE* backup = fopen(backup_path, "wb"); 149 | if(backup) 150 | fwrite(buf, sizeof(char), modSize, backup); 151 | else 152 | log(CONSOLE_RED "Attempted to create backup file '%s', failed to get file handle\n" CONSOLE_RESET, backup_path); 153 | fclose(backup); 154 | delete[] buf; 155 | delete[] backup_path; 156 | return; 157 | } 158 | 159 | bool writeFileToOffset(FILE* inFile, FILE* outFile, u64 offset) { 160 | int ret = fseek(inFile, 0, SEEK_SET); 161 | if(ret != 0) { 162 | log(CONSOLE_RED "Failed to seek file with errno %d\n" CONSOLE_RESET, ret); 163 | fclose(inFile); 164 | return false; 165 | } 166 | ret = fseek(outFile, offset, SEEK_SET); 167 | if(ret != 0) { 168 | log(CONSOLE_RED "Failed to seek offset %lx from start of data.arc with errno %d\n" CONSOLE_RESET, offset, ret); 169 | fclose(outFile); 170 | return false; 171 | } 172 | char* copy_buffer = new char[FILE_READ_SIZE]; 173 | // Copy in up to FILE_READ_SIZE byte chunks 174 | size_t size; 175 | do { 176 | size = fread(copy_buffer, sizeof(char), FILE_READ_SIZE, inFile); 177 | fwrite(copy_buffer, sizeof(char), size, outFile); 178 | } while(size == FILE_READ_SIZE); 179 | delete[] copy_buffer; 180 | return true; 181 | } 182 | 183 | int load_mod(ModFile &mod, FILE* arc) { 184 | std::string pathStr = mod.mod_path; 185 | const char* path = mod.mod_path.c_str(); 186 | u64 modSize = std::filesystem::file_size(path); 187 | char* compBuf = nullptr; 188 | u64 bufSize = ZSTD_compressBound(modSize); //mod.compSize+0x100; 189 | u64 compSize = 0; 190 | 191 | if(mod.compSize != 0) { 192 | std::string arcFileName = pathStr.substr(pathStr.find('/',pathStr.find("mods/")+5)+1); 193 | bool infoUpdated = false; 194 | if(mod.compSize == mod.decompSize && modSize > mod.decompSize) 195 | if(arcReader->updateFileInfo(arcFileName, 0, 0, mod.decompSize+1, SUBFILE_COMPRESSED_ZSTD) != -1) { 196 | infoUpdated = true; 197 | mod.decompSize++; 198 | } 199 | if(modSize > mod.decompSize) { 200 | if(arcReader->updateFileInfo(arcFileName, 0, 0, modSize) != -1) 201 | infoUpdated = true; 202 | else { 203 | log(CONSOLE_RED "%s can not be larger than expected uncompressed size\n" CONSOLE_RESET, path); 204 | return -1; 205 | } 206 | } 207 | if(mod.compSize != mod.decompSize && !ZSTDFileIsFrame(path)) { 208 | compBuf = new(std::nothrow) char[bufSize]; 209 | if(compBuf == nullptr) { 210 | log(CONSOLE_RED "%s Failed to allocate for compression. Not enough memory.\n" CONSOLE_RESET, path); 211 | return -1; 212 | } 213 | bool succeeded = compressFile(path, mod.compSize, compSize, compBuf, bufSize); 214 | if(!succeeded) 215 | { 216 | delete[] compBuf; 217 | return -1; 218 | } 219 | } 220 | if(infoUpdated) { 221 | TableWriteFiles.push(arcFileName); 222 | } 223 | } 224 | const char* modNameStart = path+strlen(mods_root); 225 | u32 modNameSize = (u32)(strchr(modNameStart, '/') - modNameStart); 226 | char* modName = new char[modNameSize+1]; 227 | strncpy(modName, modNameStart, modNameSize); 228 | modName[modNameSize] = 0; 229 | if(mod.compSize > 0) 230 | backup(modName, mod.compSize, mod.offset, arc, mod.dupeBackupPath); 231 | else 232 | backup(modName, modSize, mod.offset, arc, mod.dupeBackupPath); 233 | delete[] modName; 234 | 235 | if(compBuf != nullptr) { 236 | u64 headerSize = ZSTD_frameHeaderSize(compBuf, mod.compSize); 237 | u64 paddingSize = mod.compSize - compSize; 238 | fseek(arc, mod.offset, SEEK_SET); 239 | fwrite(compBuf, sizeof(char), headerSize, arc); 240 | char* zBuff = new char[paddingSize]; 241 | memset(zBuff, 0, paddingSize); 242 | if(paddingSize % 3 != 0) { 243 | if(paddingSize >= 4 && paddingSize % 3 == 1) zBuff[paddingSize-4] = 2; 244 | else if(paddingSize >= 8 && paddingSize % 3 == 2) { 245 | zBuff[paddingSize-4] = 2; 246 | zBuff[paddingSize-8] = 2; 247 | } 248 | } 249 | fwrite(zBuff, sizeof(char), paddingSize, arc); 250 | delete[] zBuff; 251 | fwrite(compBuf+headerSize, sizeof(char), (compSize - headerSize), arc); 252 | delete[] compBuf; 253 | } 254 | else { 255 | FILE* f = fopen(path, "rb"); 256 | if(f) { 257 | bool succeed = writeFileToOffset(f, arc, mod.offset); 258 | fclose(f); 259 | if(!succeed) 260 | return -1; 261 | } 262 | else { 263 | log(CONSOLE_RED "Found file '%s', failed to get file handle\n" CONSOLE_RESET, path); 264 | return -1; 265 | } 266 | } 267 | return 0; 268 | } 269 | 270 | #define UC(c) ((unsigned char)c) 271 | 272 | char _isxdigit (unsigned char c) { 273 | if(( c >= UC('0') && c <= UC('9') ) || 274 | ( c >= UC('a') && c <= UC('f') ) || 275 | ( c >= UC('A') && c <= UC('F') )) 276 | return 1; 277 | return 0; 278 | } 279 | 280 | unsigned char xtoc(char x) { 281 | if(x >= UC('0') && x <= UC('9')) 282 | return x - UC('0'); 283 | else if(x >= UC('a') && x <= UC('f')) 284 | return (x - UC('a')) + 0xa; 285 | else if(x >= UC('A') && x <= UC('F')) 286 | return (x - UC('A')) + 0xA; 287 | return -1; 288 | } 289 | 290 | uint64_t hex_to_u64(const char* str) { 291 | uint64_t value = 0; 292 | int idx = 0; 293 | if(str[0] == '0' && str[1] == 'x') { 294 | idx += 2; 295 | while(_isxdigit(str[idx])) { 296 | value *= 0x10; 297 | value += xtoc(str[idx]); 298 | idx++; 299 | } 300 | } 301 | return value; 302 | } 303 | 304 | void load_mods(FILE* f_arc) { 305 | size_t num_mod_files = mod_files.size(); 306 | printf(CONSOLE_ESC(s)); 307 | checkForBackups(mod_files); 308 | for(size_t i = 0; i < num_mod_files; i++) { 309 | print_progress(i, num_mod_files); 310 | consoleUpdate(NULL); 311 | std::string mod_path = mod_files[i].mod_path; 312 | std::string rel_mod_dir; 313 | bool backupsFolder = mod_path.find(backups_root) != std::string::npos; 314 | if(backupsFolder) 315 | rel_mod_dir = mod_path.substr(strlen(backups_root)); 316 | else 317 | rel_mod_dir = mod_path.substr(strlen(mods_root)); 318 | std::string arcFileName = rel_mod_dir.substr(rel_mod_dir.find('/')+1); 319 | s64 offset = mod_files[i].offset; 320 | 321 | if(offset == 0) { 322 | log(CONSOLE_RED "\"%s\" does not exist, check its path\n" CONSOLE_RESET, arcFileName.c_str()); 323 | continue; 324 | } 325 | const char* mod_path_c_str = mod_path.c_str(); 326 | if(backupsFolder) { 327 | FILE* f_backup = fopen(mod_path_c_str,"rb"); 328 | if(f_backup != nullptr) { 329 | writeFileToOffset(f_backup, f_arc, offset); 330 | fclose(f_backup); 331 | remove(mod_path_c_str); 332 | std::string parentPath = std::filesystem::path(mod_path).parent_path(); 333 | rmdir(parentPath.c_str()); 334 | } 335 | } 336 | else { 337 | if(!uninstall) { 338 | appletSetCpuBoostMode(ApmCpuBoostMode_Type1); 339 | load_mod(mod_files[i], f_arc); 340 | appletSetCpuBoostMode(ApmCpuBoostMode_Disabled); 341 | debug_log("installed \"%s\"\n", mod_path_c_str); 342 | } 343 | else { 344 | const char* modNameStart = mod_path_c_str+strlen(mods_root); 345 | u32 modNameSize = (u32)(strchr(modNameStart, '/') - modNameStart); 346 | char* modName = new char[modNameSize+1]; 347 | strncpy(modName, modNameStart, modNameSize); 348 | modName[modNameSize] = 0; 349 | int fileNameSize = snprintf(nullptr, 0, "%s%s/0x%lx.backup", backups_root, modName, offset) + 1; 350 | char* backup_path = new char[fileNameSize]; 351 | snprintf(backup_path, fileNameSize, "%s%s/0x%lx.backup", backups_root, modName, offset); 352 | FILE* f_backup = fopen(backup_path,"rb"); 353 | if(f_backup != nullptr) { 354 | writeFileToOffset(f_backup, f_arc, offset); 355 | fclose(f_backup); 356 | remove(backup_path); 357 | debug_log("restored \"%s\"\n", backup_path); 358 | } 359 | else { 360 | log(CONSOLE_RED "backup of '%s', '0x%lx' does not exist. The file may have been overwitten by another mod.\n" CONSOLE_RESET, arcFileName.c_str(), offset); 361 | } 362 | std::string parentPath = std::filesystem::path(backup_path).parent_path(); 363 | delete[] backup_path; 364 | std::error_code ec; 365 | if(std::filesystem::is_empty(parentPath, ec)) { 366 | rmdir(parentPath.c_str()); 367 | } 368 | } 369 | } 370 | } 371 | print_progress(num_mod_files, num_mod_files); 372 | if(num_mod_files == 0) 373 | printf("Note: " CONSOLE_YELLOW "Nothing installed\n" CONSOLE_RESET); 374 | consoleUpdate(NULL); 375 | mod_files.clear(); 376 | } 377 | 378 | int enumerate_mod_files(std::string mod_dir) { 379 | std::error_code ec; 380 | s64 offset = 0; 381 | u32 compSize = 0, decompSize = 0; 382 | bool regional = false; 383 | std::filesystem::path modpath; 384 | auto fsit = std::filesystem::recursive_directory_iterator(std::filesystem::path(mod_dir), ec); 385 | if(!ec) { 386 | for(; fsit != std::filesystem::recursive_directory_iterator(); ++fsit) { 387 | modpath = fsit->path(); 388 | if(fsit->is_regular_file()) { 389 | if(modpath.filename().string()[0] != '.') { 390 | offset = hex_to_u64(modpath.filename().c_str()); 391 | if(offset == 0) { 392 | if(arcReader == nullptr) { 393 | arcReader = new ArcReader(arc_path.c_str()); 394 | if(!arcReader->isInitialized()) 395 | arcReader = nullptr; 396 | } 397 | if(arcReader != nullptr) { 398 | std::string pathStr = modpath.string(); 399 | std::string arcFileName = pathStr.substr(pathStr.find('/',pathStr.find("mods/")+5)+1); 400 | arcReader->GetFileInformation(arcFileName, offset, compSize, decompSize, regional); 401 | } 402 | } 403 | mod_files.emplace_back(modpath, offset, compSize, decompSize, regional); 404 | } 405 | } 406 | else if(modpath.filename().string()[0] == '.') { 407 | fsit.disable_recursion_pending(); 408 | } 409 | } 410 | } 411 | else { 412 | log(CONSOLE_RED "Failed to open mod directory '%s'\n" CONSOLE_RESET, mod_dir.c_str()); 413 | } 414 | return 0; 415 | } 416 | 417 | void perform_installation() { 418 | Result res; 419 | int smashVersion; 420 | std::string rootModDir = modDirList.front(); 421 | FILE* f_arc; 422 | if(!std::filesystem::exists(arc_path) || std::filesystem::is_directory(std::filesystem::status(arc_path))) { 423 | log(CONSOLE_RED "\nNo data.arc found!\n" CONSOLE_RESET 424 | " Please use the " CONSOLE_GREEN "Data Arc Dumper" CONSOLE_RESET " first.\n"); 425 | goto end; 426 | } 427 | if(!uninstall) 428 | printf("\nInstalling mods...\n\n"); 429 | else 430 | printf("\nUninstalling mods...\n\n"); 431 | consoleUpdate(NULL); 432 | while(!modDirList.empty()) { 433 | std::string mod_dir = modDirList.front(); 434 | modDirList.pop(); 435 | enumerate_mod_files(mod_dir); 436 | } 437 | f_arc = fopen(arc_path.c_str(), "r+b"); 438 | if(!f_arc) { 439 | log(CONSOLE_RED "Failed to get file handle to data.arc\n" CONSOLE_RESET); 440 | goto end; 441 | } 442 | load_mods(f_arc); 443 | if(!TableWriteFiles.empty()) { 444 | printf("Writing file table\n(this may take a minute)\n"); 445 | debug_log("Writing file table for:\n"); 446 | while(!TableWriteFiles.empty()) { 447 | debug_log("%s\n",TableWriteFiles.front().c_str()); 448 | TableWriteFiles.pop(); 449 | } 450 | consoleUpdate(NULL); 451 | arcReader->writeFileInfo(f_arc); 452 | } 453 | fclose(f_arc); 454 | res = getSmashVersion(&smashVersion); 455 | if(arcReader != nullptr && R_SUCCEEDED(res) && arcReader->Version != smashVersion) { 456 | printf(CONSOLE_RED "Warning: Your data.arc does not match your game version\n" CONSOLE_RESET); 457 | } 458 | if(deleteMod) { 459 | printf("Deleting mod files\n"); 460 | fsdevDeleteDirectoryRecursively(rootModDir.c_str()); 461 | } 462 | 463 | end: 464 | if(errorLogs.empty()) 465 | printf(CONSOLE_GREEN "Successful\n" CONSOLE_RESET); 466 | else { 467 | printf("Error Logs:\n"); 468 | while (!errorLogs.empty()) { 469 | printf(errorLogs.top().c_str()); 470 | errorLogs.pop(); 471 | } 472 | } 473 | printf("Press B to return to the Mod Installer.\n"); 474 | printf("Press X to launch Smash\n\n"); 475 | } 476 | 477 | void modInstallerMainLoop(int kDown) 478 | { 479 | u64 kHeld = hidKeysHeld(CONTROLLER_P1_AUTO); 480 | if(!installation_finish) { 481 | consoleClear(); 482 | if(kHeld & KEY_RSTICK_DOWN) { 483 | if(kHeld & KEY_RSTICK) 484 | svcSleepThread(7e+4); 485 | else 486 | svcSleepThread(7e+7); 487 | mod_folder_index++; 488 | } 489 | if(kHeld & KEY_RSTICK_UP) { 490 | if(kHeld & KEY_RSTICK) 491 | svcSleepThread(7e+4); 492 | else 493 | svcSleepThread(7e+7); 494 | mod_folder_index--; 495 | } 496 | if(kDown & KEY_ZR) { 497 | std::list::iterator it = std::find(installIDXs.begin(), installIDXs.end(), mod_folder_index); 498 | if(it == installIDXs.end()) 499 | installIDXs.push_back(mod_folder_index); 500 | else 501 | installIDXs.erase(it); 502 | } 503 | if(kDown & KEY_DDOWN || kDown & KEY_LSTICK_DOWN) 504 | mod_folder_index++; 505 | else if(kDown & KEY_DUP || kDown & KEY_LSTICK_UP) 506 | mod_folder_index--; 507 | 508 | bool start_install = false; 509 | deleteMod = false; 510 | if(kDown & KEY_Y) { 511 | if(kHeld & KEY_L && kHeld & KEY_R) 512 | deleteMod = true; 513 | start_install = true; 514 | uninstall = true; 515 | } 516 | else if(kDown & KEY_A) { 517 | start_install = true; 518 | uninstall = false; 519 | } 520 | printf("\n"); 521 | std::error_code ec; 522 | auto fsit = std::filesystem::directory_iterator(std::filesystem::path(mods_root), ec); 523 | if(!ec) { 524 | s64 curr_folder_index = 0; 525 | std::filesystem::path path; 526 | for(auto& dirEntry: fsit) { 527 | if(dirEntry.is_directory()) { 528 | path = dirEntry.path(); 529 | if(std::find(InstalledMods.begin(), InstalledMods.end(), path.filename()) != InstalledMods.end()) 530 | printf(CONSOLE_BLUE); 531 | if(curr_folder_index == mod_folder_index) { 532 | printf("%s> ", CONSOLE_GREEN); 533 | if(start_install) { 534 | modDirList.emplace(dirEntry.path()); 535 | } 536 | } 537 | else if(std::find(installIDXs.begin(), installIDXs.end(), curr_folder_index) != installIDXs.end()) { 538 | printf(CONSOLE_CYAN); 539 | if(start_install) { 540 | modDirList.emplace(dirEntry.path()); 541 | } 542 | } 543 | if(curr_folder_index < 42 || curr_folder_index <= mod_folder_index) 544 | printf("%s\n", path.filename().c_str()); 545 | printf(CONSOLE_RESET); 546 | curr_folder_index++; 547 | } 548 | } 549 | 550 | if(mod_folder_index < 0) 551 | mod_folder_index = curr_folder_index; 552 | 553 | if(mod_folder_index > curr_folder_index) 554 | mod_folder_index = 0; 555 | 556 | if(mod_folder_index == curr_folder_index) { 557 | printf(CONSOLE_GREEN "> "); 558 | if(start_install) { 559 | modDirList.push(backups_root); 560 | if(arcReader == nullptr) { 561 | arcReader = new ArcReader(arc_path.c_str()); 562 | if(!arcReader->isInitialized()) { 563 | arcReader = nullptr; 564 | } 565 | } 566 | arcReader->restoreTable(); 567 | } 568 | } 569 | 570 | if(curr_folder_index < 42 || curr_folder_index <= mod_folder_index) 571 | printf(CONSOLE_YELLOW "Restore backups and file table\n" CONSOLE_RESET); 572 | 573 | if(mod_folder_index == curr_folder_index) 574 | printf(CONSOLE_RESET); 575 | 576 | if(applicationMode) 577 | console_set_status(GREEN "\nMod Installer " VERSION_STRING " " RESET); 578 | else 579 | console_set_status(GREEN "\nMod Installer " CONSOLE_RED " APPLET MODE NOT RECOMMENDED" RESET); 580 | printf(CONSOLE_ESC(s) CONSOLE_ESC(46;1H) GREEN "A" RESET "=Install " 581 | GREEN "Y" RESET "=Uninstall " GREEN "L+R+Y" RESET "=Delete " 582 | GREEN "R-Stick" RESET "=Scroll " GREEN "ZR" RESET "=Multi-select " 583 | GREEN "B" RESET "=Main Menu" CONSOLE_ESC(u)); 584 | } 585 | else { 586 | log(CONSOLE_RED "%s folder not found\n\n" CONSOLE_RESET, mods_root); 587 | } 588 | 589 | consoleUpdate(NULL); 590 | if(start_install) { 591 | consoleClear(); 592 | perform_installation(); 593 | installation_finish = true; 594 | installIDXs.clear(); 595 | updateInstalledList(); 596 | } 597 | } 598 | 599 | if(kDown & KEY_B) { 600 | if(installation_finish) { 601 | installation_finish = false; 602 | consoleClear(); 603 | } 604 | else { 605 | if(arcReader != nullptr) { 606 | delete arcReader; 607 | arcReader = nullptr; 608 | } 609 | if(compContext != nullptr) { 610 | ZSTD_freeCCtx(compContext); 611 | compContext = nullptr; 612 | } 613 | menu = mainMenu; 614 | printMainMenu(); 615 | } 616 | } 617 | if(kDown & KEY_X) { 618 | appletRequestLaunchApplication(smashTID, NULL); 619 | } 620 | if(kHeld & KEY_L && kHeld & KEY_R && kDown & KEY_MINUS) { 621 | if(arcReader == nullptr) { 622 | arcReader = new ArcReader(arc_path.c_str()); 623 | if(!arcReader->isInitialized()) { 624 | arcReader = nullptr; 625 | } 626 | } 627 | arcReader->restoreTable(); 628 | } 629 | } 630 | -------------------------------------------------------------------------------- /source/utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "menu.h" 7 | #include "switch.h" 8 | 9 | #define NUM_PROGRESS_CHARS 50 10 | const u64 smashTID = 0x01006A800016E000; 11 | const char* manager_root = "sdmc:/UltimateModManager/"; 12 | const char* tablePath = "sdmc:/UltimateModManager/compTable.backup"; 13 | bool applicationMode = false; 14 | enum smashRegions { 15 | jp_ja, 16 | us_en, 17 | us_fr, 18 | us_es, 19 | eu_en, 20 | eu_fr, 21 | eu_es, 22 | eu_de, 23 | eu_nl, 24 | eu_it, 25 | eu_ru, 26 | kr_ko, 27 | zh_cn, 28 | zh_tw 29 | }; 30 | const std::map regionMap { 31 | {"ja", jp_ja}, 32 | {"en-US", us_en}, 33 | {"fr", eu_fr}, 34 | {"de", eu_de}, 35 | {"it", eu_it}, 36 | {"es", eu_es}, 37 | {"zh-CN", zh_cn}, 38 | {"ko", kr_ko}, 39 | {"nl", eu_nl}, 40 | {"pt", eu_en}, 41 | {"ru", eu_ru}, 42 | {"zh-TW", zh_tw}, 43 | {"en-GB", eu_en}, 44 | {"fr-CA", us_fr}, 45 | {"es-419", us_es}, 46 | {"zh-Hans", zh_cn}, 47 | {"zh-Hant", zh_tw}, 48 | }; 49 | 50 | const char* log_file = "sdmc:/UltimateModManager/log.txt"; 51 | const bool debug = std::filesystem::exists("sdmc:/UltimateModManager/debug.flag"); 52 | void debug_log(const char*, ...) __attribute__((format(printf, 1, 2))); 53 | void debug_log(const char* format, ...) { 54 | if(debug) { 55 | char buf[10]; 56 | std::time_t now = std::time(0); 57 | std::strftime(buf, sizeof(buf), "%T", std::localtime(&now)); 58 | va_list args; 59 | va_start(args, format); 60 | FILE* log = fopen(log_file, "ab"); 61 | fprintf(log, "[%s] ", buf); 62 | vfprintf(log, format, args); 63 | fclose(log); 64 | va_end(args); 65 | } 66 | } 67 | 68 | u64 getAtmosVersion() { 69 | splInitialize(); 70 | u64 ver = 0; 71 | SplConfigItem SplConfigItem_ExosphereVersion = (SplConfigItem)65000; 72 | if(R_SUCCEEDED(splGetConfig(SplConfigItem_ExosphereVersion, &ver))) { 73 | u32 major = (ver >> 32) & 0xFF; 74 | u32 minor = (ver >> 24) & 0xFF; 75 | u32 micro = (ver >> 16) & 0xFF; 76 | ver = (major*10000) + (minor*100) + micro; 77 | } 78 | splExit(); 79 | return ver; 80 | } 81 | 82 | std::string strTolower(std::string string) { 83 | for(int i = 0; string[i] != 0; i++) { 84 | string[i] = tolower(string[i]); 85 | } 86 | return string; 87 | } 88 | 89 | bool isApplicationMode() { 90 | AppletType currAppType = appletGetAppletType(); 91 | return (currAppType == AppletType_Application || currAppType == AppletType_SystemApplication); 92 | } 93 | 94 | int getRegion() { 95 | u64 languageCode; 96 | if(R_FAILED(appletGetDesiredLanguage(&languageCode))) { 97 | if (R_SUCCEEDED(setInitialize())) { 98 | setGetSystemLanguage(&languageCode); 99 | setExit(); 100 | } 101 | } 102 | return regionMap.find((char*)&languageCode)->second; 103 | } 104 | 105 | Result getVersion(u64 tid, char version[0x10]) { 106 | Result rc; 107 | nsInitialize(); 108 | NsApplicationControlData contolData; 109 | rc = nsGetApplicationControlData(NsApplicationControlSource_Storage, tid, &contolData, sizeof(NsApplicationControlData), NULL); 110 | nsExit(); 111 | strcpy(version, contolData.nacp.display_version); 112 | return rc; 113 | } 114 | 115 | Result getSmashVersion(int* out) { 116 | Result rc; 117 | char version[0x10]; 118 | *out = 0; 119 | rc = getVersion(smashTID, version); 120 | if(R_SUCCEEDED(rc)) { 121 | char* value = strtok(version, "."); 122 | *out += strtol(value, NULL, 10) * 0x10000; 123 | value = strtok(NULL, "."); 124 | *out += strtol(value, NULL, 10) * 0x100; 125 | value = strtok(NULL, "."); 126 | *out += strtol(value, NULL, 10); 127 | } 128 | return rc; 129 | } 130 | 131 | void print_progress(size_t progress, size_t max) { 132 | size_t prog_chars; 133 | if (max == 0) prog_chars = NUM_PROGRESS_CHARS; 134 | else prog_chars = ((float) progress / max) * NUM_PROGRESS_CHARS; 135 | 136 | printf(CONSOLE_ESC(u)); 137 | printf(CONSOLE_ESC(s)); 138 | if (prog_chars < NUM_PROGRESS_CHARS) printf(YELLOW); 139 | else printf(GREEN); 140 | 141 | printf("["); 142 | for (size_t i = 0; i < prog_chars; i++) 143 | printf("="); 144 | 145 | if (prog_chars < NUM_PROGRESS_CHARS) printf(">"); 146 | else printf("="); 147 | 148 | for (size_t i = 0; i < NUM_PROGRESS_CHARS - prog_chars; i++) 149 | printf(" "); 150 | 151 | printf("]\t%lu/%lu\n" RESET, progress, max); 152 | } 153 | 154 | std::string strsprintf(const char*, ...) __attribute__((format(printf, 1, 2))); 155 | std::string strsprintf(const char* format, ...) { 156 | va_list args; 157 | va_start(args, format); 158 | int size = vsnprintf(nullptr, 0, format, args) + 1; 159 | char* cstr = new char[size]; 160 | vsnprintf(cstr, size, format, args); 161 | std::string str(cstr); 162 | delete[] cstr; 163 | va_end(args); 164 | return str; 165 | } 166 | 167 | std::stack errorLogs; 168 | void log(const char*, ...) __attribute__((format(printf, 1, 2))); 169 | void log(const char* format, ...) { 170 | va_list args; 171 | va_start(args, format); 172 | int len = vsnprintf(nullptr, 0, format, args) + 1; 173 | char* buffer = new char[len]; 174 | vsnprintf(buffer, len, format, args); 175 | errorLogs.emplace(buffer); 176 | delete[] buffer; 177 | va_end(args); 178 | } 179 | 180 | bool isServiceRunning(const char* serviceName) { 181 | Handle handle; 182 | SmServiceName encodedName = smEncodeName(serviceName); 183 | bool running = R_FAILED(smRegisterService(&handle, encodedName, false, 1)); 184 | 185 | svcCloseHandle(handle); 186 | 187 | if (!running) 188 | smUnregisterService(encodedName); 189 | 190 | return running; 191 | } 192 | 193 | enum cfwName { 194 | atmosphere, 195 | sxos, 196 | ReiNX, 197 | yuzu, 198 | }; 199 | cfwName getCFW() 200 | { 201 | if (isServiceRunning("rnx")) 202 | return ReiNX; 203 | if (isServiceRunning("tx")) 204 | return sxos; 205 | SplConfigItem SplConfigItem_ExosphereVersion = (SplConfigItem)65000; 206 | u64 output; 207 | splInitialize(); 208 | Result res = splGetConfig(SplConfigItem_ExosphereVersion, &output); 209 | splExit(); 210 | if (R_SUCCEEDED(res)) 211 | return atmosphere; 212 | return yuzu; 213 | } 214 | 215 | std::string dataArcPath(cfwName cfw) { 216 | std::string path; 217 | switch(cfw) { 218 | case atmosphere: 219 | if(getAtmosVersion() >= 1000) 220 | path = "/atmosphere/contents/01006A800016E000/romfs/data.arc"; 221 | else 222 | path = "/atmosphere/titles/01006A800016E000/romfs/data.arc"; 223 | break; 224 | case sxos: 225 | path = "/sxos/titles/01006A800016E000/romfs/data.arc"; 226 | break; 227 | case ReiNX: 228 | path = "/ReiNX/contents/01006A800016E000/romfs/data.arc"; 229 | break; 230 | case yuzu: 231 | path = "/yuzu/load/01006A800016E000/UMM/romfs/data.arc"; 232 | break; 233 | } 234 | return path; 235 | } 236 | 237 | u64 runningTID() 238 | { 239 | u64 pid = 0; 240 | u64 tid = 0; 241 | pmdmntInitialize(); 242 | pminfoInitialize(); 243 | pmdmntGetApplicationProcessId(&pid); 244 | pminfoGetProgramId(&tid, pid); 245 | pminfoExit(); 246 | pmdmntExit(); 247 | return tid; 248 | } 249 | 250 | bool fileExists (const std::string& name) { 251 | return (access(name.c_str(), F_OK) != -1); 252 | } 253 | 254 | int mkdirs (const std::string path, int mode = 0777) { 255 | int slashIDX = path.find_last_of("/"); 256 | if(mkdir(path.c_str(), mode) == -1 && slashIDX != -1) { 257 | mkdirs(path.substr(0, slashIDX), mode); 258 | return mkdir(path.c_str(), mode); 259 | } 260 | return 0; 261 | } 262 | 263 | void vibrateFor(HidVibrationValue VibrationValue, u32 VibrationDeviceHandle[2], s64 time) 264 | { 265 | // Default values 266 | HidVibrationValue VibrationValue_stop; 267 | VibrationValue_stop.amp_low = 0; 268 | VibrationValue_stop.freq_low = 160.0f; 269 | VibrationValue_stop.amp_high = 0; 270 | VibrationValue_stop.freq_high = 320.0f; 271 | 272 | HidVibrationValue VibrationValues[2]; 273 | // ON 274 | memcpy(&VibrationValues[0], &VibrationValue, sizeof(HidVibrationValue)); 275 | memcpy(&VibrationValues[1], &VibrationValue, sizeof(HidVibrationValue)); 276 | hidSendVibrationValues(VibrationDeviceHandle, VibrationValues, 2); 277 | 278 | svcSleepThread(time); 279 | // OFF 280 | memcpy(&VibrationValues[0], &VibrationValue_stop, sizeof(HidVibrationValue)); 281 | memcpy(&VibrationValues[1], &VibrationValue_stop, sizeof(HidVibrationValue)); 282 | hidSendVibrationValues(VibrationDeviceHandle, VibrationValues, 2); 283 | } 284 | 285 | void shortVibratePattern() 286 | { 287 | u32 VibrationDeviceHandle[2]; 288 | HidVibrationValue VibrationValue; 289 | 290 | VibrationValue.amp_low = 0.35f; 291 | VibrationValue.freq_low = 160.0f; 292 | VibrationValue.amp_high = 0.2f; 293 | VibrationValue.freq_high = 320.0f; 294 | 295 | if(hidIsControllerConnected(CONTROLLER_PLAYER_1)) { 296 | hidInitializeVibrationDevices(VibrationDeviceHandle, 2, CONTROLLER_PLAYER_1, hidGetControllerType(CONTROLLER_PLAYER_1)); 297 | } 298 | else { 299 | hidInitializeVibrationDevices(VibrationDeviceHandle, 2, CONTROLLER_HANDHELD, TYPE_HANDHELD); 300 | } 301 | 302 | vibrateFor(VibrationValue, VibrationDeviceHandle, 1e+9); 303 | svcSleepThread(3.5e+7); 304 | vibrateFor(VibrationValue, VibrationDeviceHandle, 3.5e+8); 305 | svcSleepThread(5e+7); 306 | vibrateFor(VibrationValue, VibrationDeviceHandle, 3.5e+8); 307 | } 308 | 309 | void removeRecursive(std::filesystem::path path) 310 | { 311 | if (std::filesystem::is_directory(path)) 312 | { 313 | for (auto & child : std::filesystem::directory_iterator(path)) 314 | removeRecursive(child.path()); 315 | rmdir(path.c_str()); 316 | } 317 | 318 | std::filesystem::remove(path); 319 | } 320 | --------------------------------------------------------------------------------