From e8ea6f4df5a9a48a319bd20c478668407e844d7e Mon Sep 17 00:00:00 2001 From: RandalHoffman Date: Mon, 12 Apr 2021 01:06:20 -0700 Subject: [PATCH] initial commit --- .gitignore | 10 + Makefile | 219 +++++++++++++++++ README.md | 18 ++ include/aes.h | 96 ++++++++ include/builder.hpp | 60 +++++ include/cia.h | 25 ++ include/config.hpp | 14 ++ include/dialog.hpp | 18 ++ include/graphics.h | 25 ++ include/helpers.hpp | 22 ++ include/image.hpp | 16 ++ include/logger.hpp | 27 ++ include/menu.hpp | 57 +++++ include/nds.h | 157 ++++++++++++ include/settings.hpp | 14 ++ include/ticket.h | 51 ++++ include/tmd.h | 43 ++++ romfs/sdcard.fwd | 4 + romfs/sdcard.nds | Bin 0 -> 206336 bytes source/aes.c | 571 +++++++++++++++++++++++++++++++++++++++++++ source/builder.cpp | 429 ++++++++++++++++++++++++++++++++ source/config.cpp | 28 +++ source/dialog.cpp | 80 ++++++ source/graphics.c | 87 +++++++ source/helpers.cpp | 73 ++++++ source/image.cpp | 26 ++ source/load.c | 58 +++++ source/logger.cpp | 4 + source/main.c.bak | 79 ++++++ source/main.cpp | 209 ++++++++++++++++ source/menu.cpp | 272 +++++++++++++++++++++ source/nds.c | 88 +++++++ source/stubs.c | 5 + 33 files changed, 2885 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 README.md create mode 100644 include/aes.h create mode 100644 include/builder.hpp create mode 100644 include/cia.h create mode 100644 include/config.hpp create mode 100644 include/dialog.hpp create mode 100644 include/graphics.h create mode 100644 include/helpers.hpp create mode 100644 include/image.hpp create mode 100644 include/logger.hpp create mode 100644 include/menu.hpp create mode 100644 include/nds.h create mode 100644 include/settings.hpp create mode 100644 include/ticket.h create mode 100644 include/tmd.h create mode 100644 romfs/sdcard.fwd create mode 100644 romfs/sdcard.nds create mode 100644 source/aes.c create mode 100644 source/builder.cpp create mode 100644 source/config.cpp create mode 100644 source/dialog.cpp create mode 100644 source/graphics.c create mode 100644 source/helpers.cpp create mode 100644 source/image.cpp create mode 100644 source/load.c create mode 100644 source/logger.cpp create mode 100644 source/main.c.bak create mode 100644 source/main.cpp create mode 100644 source/menu.cpp create mode 100644 source/nds.c create mode 100644 source/stubs.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cad5d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.nds +*.srl +*.cia +*.3dsx +*.smdh +*.elf +*.bin +*.3ds +.vscode/ +build/ \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24c6499 --- /dev/null +++ b/Makefile @@ -0,0 +1,219 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITARM)),) +$(error "Please set DEVKITARM in your environment. export DEVKITARM=devkitARM") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITARM)/3ds_rules + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# +# NO_SMDH: if set to anything, no SMDH file is generated. +# APP_TITLE is the name of the app stored in the SMDH file (Optional) +# APP_DESCRIPTION is the description of the app stored in the SMDH file (Optional) +# APP_AUTHOR is the author of the app stored in the SMDH file (Optional) +# ICON is the filename of the icon (.png), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .png +# - icon.png +# - /default_icon.png +#--------------------------------------------------------------------------------- + +APP_TITLE := NDS Forwarder Generator +APP_DESCRIPTION := Creates nds forwarders on the home menu +APP_AUTHOR := RandalHoffman + +TARGET := $(notdir $(CURDIR)) +BUILD := build +SOURCES := source +DATA := +INCLUDES := include +GRAPHICS := gfx +ROMFS := romfs + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv6k -mtune=mpcore -mfloat-abi=hard -mtp=soft + +CFLAGS := -g -Wall -O2 -mword-relocations \ + -ffunction-sections \ + $(ARCH) + +CFLAGS += $(INCLUDE) -DARM11 -D_3DS + +CXXFLAGS := $(CFLAGS) -fno-rtti -std=gnu++17 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) + +LIBS := -lcitro2d -lcitro3d -lctru -lm + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(CTRULIB) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(CURDIR)/$(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) \ + $(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +export OFILES_BIN := $(addsuffix .o,$(BINFILES)) \ + #$(PNGFILES:.png=.bgr.o) \ + +export OFILES := $(OFILES_BIN) $(OFILES_SOURCES) + +export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES))) #$(PNGFILES:.png=_bgr.h) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.png) + ifneq (,$(findstring $(TARGET).png,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).png + else + ifneq (,$(findstring icon.png,$(icons))) + export APP_ICON := $(TOPDIR)/icon.png + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +#IMAGEMAGICK := $(shell which convert) + +ifeq ($(strip $(NO_SMDH)),) + export _3DSXFLAGS += --smdh=$(CURDIR)/$(TARGET).smdh +endif + +ifneq ($(ROMFS),) + export _3DSXFLAGS += --romfs=$(CURDIR)/$(ROMFS) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +#ifneq ($(strip $(IMAGEMAGICK)),) +#ifeq ($(findstring System32,$(IMAGEMAGICK)),) + +#HAVE_CONVERT := yes + +#endif +#endif + +#ifeq ($(strip $(HAVE_CONVERT)),yes) + +all: $(BUILD) + +#else + +#all: +# @echo "Image Magick not found!" +# @echo +# @echo "Please install Image Magick from http://www.imagemagick.org/ to build this example" + +#endif + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +ifeq ($(strip $(NO_SMDH)),) +$(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).smdh +else +$(OUTPUT).3dsx : $(OUTPUT).elf +endif + +$(OFILES_SOURCES) : $(HFILES) + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +#%.bin.o : %.bin +#--------------------------------------------------------------------------------- +# @echo $(notdir $<) +# @$(bin2o) + + + +#--------------------------------------------------------------------------------- +#%_bgr.h %.bgr.o: %.bgr +#--------------------------------------------------------------------------------- +# @echo $(notdir $<) +# @$(bin2o) + +#--------------------------------------------------------------------------------- +#%.bgr: %.png +#--------------------------------------------------------------------------------- +# @echo $(notdir $<) +# @convert $< -rotate 90 $@ + +#-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/README.md b/README.md new file mode 100644 index 0000000..b77c5bb --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# NDSForwarder + +DSiWare Template downloaded from http://olmectron.github.io/forwarders/sdcard.(fwd|nds). Thanks to Olmectron for providing templates via their website. + +Thanks to Martin Korth for GBATek, which provided pretty much all the information needed about the nds and dsi formats. + +Thanks to 3DSGuy and everyone else who contributed to CTR Toolkit (of which, make_cia which is used in Olmectron's forwarder3ds app) + +Thanks to RocketRobz and the DS-Homebrew team for all the nds-bootstrap and TWiLightMenu++ stuff. + +Thanks to Oreo639, Piepie62, Fenrir, and everyone else who helped me in my development career. + +Thanks to lifehackerhansol for helping me test + +Finally, thanks to the DevKitPro team for the toolchain (wintermute et al). Without that, homebrew just wouldn't be a thing. + + +Anyone else that I forgot to list, it's not you, it's me. Thank you. My progress is a product of the community and all it gives back. Thank everyone for being a part of it and helping. diff --git a/include/aes.h b/include/aes.h new file mode 100644 index 0000000..ff152d5 --- /dev/null +++ b/include/aes.h @@ -0,0 +1,96 @@ +#ifndef _AES_H_ +#define _AES_H_ +#ifdef __cplusplus +extern "C" { +#endif +#include +#include + +// #define the macros below to 1/0 to enable/disable the mode of operation. +// +// CBC enables AES encryption in CBC-mode of operation. +// CTR enables encryption in counter-mode. +// ECB enables the basic ECB 16-byte block algorithm. All can be enabled simultaneously. + +// The #ifndef-guard allows it to be configured before #include'ing or at compile time. +#ifndef CBC + #define CBC 1 +#endif + +#ifndef ECB + #define ECB 0 +#endif + +#ifndef CTR + #define CTR 0 +#endif + + +#define AES128 1 +//#define AES192 1 +//#define AES256 1 + +#define AES_BLOCKLEN 16 // Block length in bytes - AES is 128b block only + +#if defined(AES256) && (AES256 == 1) + #define AES_KEYLEN 32 + #define AES_keyExpSize 240 +#elif defined(AES192) && (AES192 == 1) + #define AES_KEYLEN 24 + #define AES_keyExpSize 208 +#else + #define AES_KEYLEN 16 // Key length in bytes + #define AES_keyExpSize 176 +#endif + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) + uint8_t Iv[AES_BLOCKLEN]; +#endif +}; + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key); +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); +#endif + +#if defined(ECB) && (ECB == 1) +// buffer size is exactly AES_BLOCKLEN bytes; +// you need only AES_init_ctx as IV is not used in ECB +// NB: ECB is considered insecure for most uses +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); + +#endif // #if defined(ECB) && (ECB == !) + + +#if defined(CBC) && (CBC == 1) +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CBC) && (CBC == 1) + + +#if defined(CTR) && (CTR == 1) + +// Same function for encrypting as for decrypting. +// IV is incremented for every block, and used after encryption as XOR-compliment for output +// Suggesting https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx with AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + +#endif // #if defined(CTR) && (CTR == 1) + +#ifdef __cplusplus +} +#endif + +#endif // _AES_H_ \ No newline at end of file diff --git a/include/builder.hpp b/include/builder.hpp new file mode 100644 index 0000000..a83c299 --- /dev/null +++ b/include/builder.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include <3ds.h> +#include +#include +#include +#include +#include +#include + +#define ROMFS_SRL "romfs:/sdcard.nds" +#define ROMFS_TEMPLATE "romfs:/sdcard.fwd" +#define SDCARD_SRL "sdmc:/3ds/forwarder/sdcard.nds" +#define SDCARD_TEMPLATE "sdmc:/3ds/forwarder/sdcard.fwd" +#define SDCARD_BANNER_PATH "sdmc:/3ds/forwarder/banners" +#define NDSV1_HEADER_SIZE 0xA00 +#define NDSV2_HEADER_SIZE 0x1240 +#define NDSV3_HEADER_SIZE 0x2340 + +class Builder { + private: + std::string srl; + std::map sections; + u32 launchPathLocation=0x229BC; + u32 launchPathLen=252; + u32 srlBannerLocation=0x30200; + u8 rsaSignModulo[0x100]; + u8 rsaSignExponent[0x100]; + std::string ciaCertChain; + std::string ticketCertChain; + std::string buildSRL(std::string filename, bool randomTid=false, std::string customTitle=""); + std::string buildTMD(u8* contentId); + std::string getTWLTID(u8* srl); + std::string buildTicket(); + void readTWLTID(void* titleid, const void* srl); + std::string buildTicket(std::string filename); + void parseTemplate(std::string path); + public: + Result initialize(); + Result buildCIA(std::string filename, bool randomTid=false, std::string customTitle=""); + Result installCIA(); +}; +inline std::string readEntireFile(const std::string& path) { + std::ostringstream buf; + std::ifstream input (path.c_str()); + buf << input.rdbuf(); + return buf.str(); +} + +inline bool fileExists (const std::string& name) { + struct stat buffer; + return (stat (name.c_str(), &buffer) == 0); +} +inline unsigned long fileSize (const std::string& name) { + struct stat buffer; + if (stat (name.c_str(), &buffer)==0) { + return buffer.st_size; + } + return 0; +} diff --git a/include/cia.h b/include/cia.h new file mode 100644 index 0000000..1ebb34f --- /dev/null +++ b/include/cia.h @@ -0,0 +1,25 @@ +/* +0x00 0x04 Archive Header Size (Usually = 0x2020 bytes) +0x04 0x02 Type +0x06 0x02 Version +0x08 0x04 Certificate chain size +0x0C 0x04 Ticket size +0x10 0x04 TMD file size +0x14 0x04 Meta size (0 if no Meta data is present) +0x18 0x08 Content size +0x20 0x2000 Content Index +*/ +#include <3ds.h> +typedef struct +{ + u32 headerSize; + u16 type; + u16 version; + u32 certchainSize; + u32 ticketSize; + u32 tmdSize; + u32 metaSize; + u64 contentSize; + u8 contentIdx[0x2000]; +} __attribute__((__packed__)) +sCiaHeader; diff --git a/include/config.hpp b/include/config.hpp new file mode 100644 index 0000000..8039d7d --- /dev/null +++ b/include/config.hpp @@ -0,0 +1,14 @@ +#pragma once +#include +#include +#include <3ds.h> +#include + +class Config { + public: + bool randomTID; + bool customTitle; + void draw(bool interactive=false); + void interact(touchPosition *touch); + Config(); +}; \ No newline at end of file diff --git a/include/dialog.hpp b/include/dialog.hpp new file mode 100644 index 0000000..96760d9 --- /dev/null +++ b/include/dialog.hpp @@ -0,0 +1,18 @@ +#include +#include +#include + +class Dialog { + private: + std::vector options; + std::vector message; + C3D_RenderTarget* target; + float x,y,width,height; + int selected; + void draw(); + public: + int handle(); + Dialog(C3D_RenderTarget* target, float x, float y, float width, float height, std::string message, std::initializer_list options, int defaultChoice=0); + Dialog(C3D_RenderTarget* target, float x, float y, float width, float height, std::initializer_list message, std::initializer_list options, int defaultChoice=0); + +}; \ No newline at end of file diff --git a/include/graphics.h b/include/graphics.h new file mode 100644 index 0000000..4cc9877 --- /dev/null +++ b/include/graphics.h @@ -0,0 +1,25 @@ +#pragma once +#include <3ds.h> +#include +#ifdef __cplusplus +extern "C" { +#endif +#define HexColor(hex) C2D_Color32((hex>>16) & 0xFF,(hex>>8)&0xFF,(hex&0xFF),0xFF) + +// #define BGColor HexColor(0x17b3c1) +// #define BORDER_COLOR HexColor(0x2794eb) +// #define BORDER_FOREGROUND HexColor(0xbff8d4) +// #define HIGHLIGHT_BGCOLOR HexColor(0x47d6b6) +// #define HIGHLIGHT_FOREGROUND HexColor(0) +// #define FOREGROUND_COLOR HexColor(0) + +u8* getFrameBufferLocation(u8* buffer, u16 x, u16 y, u16 width, u16 height,u8 bps); +void displayImageAt(u8* framebuffer, u16* icon, u16 x, u16 y, u16 width, u16 height); +void drawPanelWithTitle(float x, float y, float z, float width, float height, float border, u32 bgColor, u32 color, const char* label, u32 fontColor); +void drawPanel(float x, float y, float z, float width, float height, float border, u32 bgColor, u32 color); +void drawText(float x, float y, float z, float scale, u32 bgColor, u32 fontColor, const char* text, u32 flags); +void drawCheckbox(float x, float y, float z, float width, float height, float scale, u32 bgColor, u32 borderColor, u32 color, const char* label, bool checked); +void C2DExtra_DrawRectHollow(float x, float y, float z, float width, float height, float thickness, u32 color); +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/include/helpers.hpp b/include/helpers.hpp new file mode 100644 index 0000000..ed53241 --- /dev/null +++ b/include/helpers.hpp @@ -0,0 +1,22 @@ +#include <3ds.h> +#include +#define REVERSE32 (val) ((val&0xFF)<<24)|((val&0xFF00)<<8)|((val&0xFF0000)>>8)|((val>>24)&0xFF) + +std::string sha256(u8* data, u32 size); +u16 crc16Modbus(const void* data, u32 size); +void memcpyrev(void* dest, void* source, u32 size); +Result sign(u8* hash, u8* mod, u32 modSize, u8* exp, u32 expSize, u8* signature); +void encryptAES128CBC(u8* out, u8* iv, u8* key, u8* data, u32 size) ; + +std::string aligned(const void* data, u64 size, u64 padTo); +std::string aligned(std::string data, u64 padTo); +std::string alignmentPadding(u64 size, u64 padTo); + +typedef struct +{ + u8 sigType[4]; + u8 signature[0x100]; + u8 padding[0x3c]; +}__attribute__((__packed__)) +sSignature; + diff --git a/include/image.hpp b/include/image.hpp new file mode 100644 index 0000000..090ace1 --- /dev/null +++ b/include/image.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include <3ds.h> + +class FrameBuffer { + u16 bps; + u8* framebuffer; + u16 height; + u16 width; + + public: + FrameBuffer(std::string screen, bool doubleBuffer, GSPGPU_FramebufferFormat format); + u8* getFrameBufferLocation(u16 x, u16 y) ; + void displayImageAt(u16* image, u16 x, u16 y, u16 iwidth, u16 iheight) ; +}; \ No newline at end of file diff --git a/include/logger.hpp b/include/logger.hpp new file mode 100644 index 0000000..4e3f2dc --- /dev/null +++ b/include/logger.hpp @@ -0,0 +1,27 @@ +#include +#include +#ifndef LOGPATH +#define LOGPATH "/3ds/forwarder" +#endif + +class Logger { + private: + std::string pluginName; + public: + static void log(std::string messageType, std::string msg){ + std::ofstream file(std::string(LOGPATH) + "/log.txt",std::ios_base::app); + file << messageType << ": " << msg << "\n"; + } + Logger(std::string plugin) { + this->pluginName=plugin; + } + void info(std::string s) { log("info","["+this->pluginName+"]"+s);} + void error(std::string s) { log("error","["+this->pluginName+"]"+s);} + void warn(std::string s) { log("warn","["+this->pluginName+"]"+s);} + void debug(std::string s) { + #ifdef DEBUG + log("debug","["+this->pluginName+"]"+s); + #endif + } + +}; \ No newline at end of file diff --git a/include/menu.hpp b/include/menu.hpp new file mode 100644 index 0000000..36a5a43 --- /dev/null +++ b/include/menu.hpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include "builder.hpp" +#include "config.hpp" +//#include "menu.h" +#define ATTRIB_DIR 1 +#define ATTRIB_HIDDEN 1<<1 + +enum MenuAction { + OpenFolder, + ReturnToMenu, + Install, + Install_All +}; + +class MenuSelection { + public: + std::filesystem::path path; + std::string display; + MenuAction action; + MenuSelection(std::string s="",std::filesystem::path p=std::filesystem::path("/")); + MenuSelection(MenuSelection* old); + MenuSelection* setPath(std::filesystem::path p); + MenuSelection* setDisplay(std::string s); + +}; +class Menu { + private: + std::vector::iterator selection; + std::vector::iterator top; + std::vector entries; + std::queue queue; + public: + std::filesystem::path currentDirectory; + Menu(std::vector entries); + Menu(); + ~Menu(); + Menu* addEntry(MenuSelection* s); + +// Menu* setSelections(std::vector s); +// std::vector getSelections(); + void drawMenu(); + void init(); + void down(); + void pageDown(); + void up(); + void pageUp(); + void action(); + Menu* back(); + Menu* handleQueue(Builder* builder, C3D_RenderTarget* target=nullptr, Config* config =nullptr); + bool hasQueue(); + +}; +Menu* generateMenu(std::filesystem::path path, Menu* prev); +bool sortMenuSelections(MenuSelection* a, MenuSelection* b); +std::string shorten(std::string s, u16 len); \ No newline at end of file diff --git a/include/nds.h b/include/nds.h new file mode 100644 index 0000000..6d435cd --- /dev/null +++ b/include/nds.h @@ -0,0 +1,157 @@ +/// data in this file was copied from libnds which is part of devkitpro +/// https://github.com/devkitPro/libnds +/// this header's structs are not my own and i make no claims to it + +#include <3ds.h> +#include +typedef struct sNDSHeader { + char gameTitle[12]; //!< 12 characters for the game title. + char gameCode[4]; //!< 4 characters for the game code. + char makercode[2]; //!< identifies the (commercial) developer. + u8 unitCode; //!< identifies the required hardware. + u8 deviceType; //!< type of device in the game card + u8 deviceSize; //!< capacity of the device (1 << n Mbit) + u8 reserved1[9]; + u8 romversion; //!< version of the ROM. + u8 flags; //!< bit 2: auto-boot flag. + + u32 arm9romOffset; //!< offset of the arm9 binary in the nds file. + void *arm9executeAddress; //!< adress that should be executed after the binary has been copied. + void *arm9destination; //!< destination address to where the arm9 binary should be copied. + u32 arm9binarySize; //!< size of the arm9 binary. + + u32 arm7romOffset; //!< offset of the arm7 binary in the nds file. + void *arm7executeAddress; //!< adress that should be executed after the binary has been copied. + void *arm7destination; //!< destination address to where the arm7 binary should be copied. + u32 arm7binarySize; //!< size of the arm7 binary. + + u32 filenameOffset; //!< File Name Table (FNT) offset. + u32 filenameSize; //!< File Name Table (FNT) size. + u32 fatOffset; //!< File Allocation Table (FAT) offset. + u32 fatSize; //!< File Allocation Table (FAT) size. + + u32 arm9overlaySource; //!< File arm9 overlay offset. + u32 arm9overlaySize; //!< File arm9 overlay size. + u32 arm7overlaySource; //!< File arm7 overlay offset. + u32 arm7overlaySize; //!< File arm7 overlay size. + + u32 cardControl13; //!< Port 40001A4h setting for normal commands (used in modes 1 and 3) + u32 cardControlBF; //!< Port 40001A4h setting for KEY1 commands (used in mode 2) + u32 bannerOffset; //!< offset to the banner with icon and titles etc. + + u16 secureCRC16; //!< Secure Area Checksum, CRC-16. + + u16 readTimeout; //!< Secure Area Loading Timeout. + + u32 unknownRAM1; //!< ARM9 Auto Load List RAM Address (?) + u32 unknownRAM2; //!< ARM7 Auto Load List RAM Address (?) + + u32 bfPrime1; //!< Secure Area Disable part 1. + u32 bfPrime2; //!< Secure Area Disable part 2. + u32 romSize; //!< total size of the ROM. + + u32 headerSize; //!< ROM header size. + u32 zeros88[14]; + u8 gbaLogo[156]; //!< Nintendo logo needed for booting the game. + u16 logoCRC16; //!< Nintendo Logo Checksum, CRC-16. + u16 headerCRC16; //!< header checksum, CRC-16. + +} tNDSHeader; + +typedef struct __DSiHeader { + tNDSHeader ndshdr; + u32 debugRomSource; //!< debug ROM offset. + u32 debugRomSize; //!< debug size. + u32 debugRomDestination; //!< debug RAM destination. + u32 offset_0x16C; //reserved? + + u8 zero[0x10]; + + u8 global_mbk_setting[5][4]; + u32 arm9_mbk_setting[3]; + u32 arm7_mbk_setting[3]; + u32 mbk9_wramcnt_setting; + + u32 region_flags; + u32 access_control; + u32 scfg_ext_mask; + u8 offset_0x1BC[3]; + u8 appflags; + + void *arm9iromOffset; + u32 offset_0x1C4; + void *arm9idestination; + u32 arm9ibinarySize; + void *arm7iromOffset; + u32 offset_0x1D4; + void *arm7idestination; + u32 arm7ibinarySize; + + u32 digest_ntr_start; + u32 digest_ntr_size; + u32 digest_twl_start; + u32 digest_twl_size; + u32 sector_hashtable_start; + u32 sector_hashtable_size; + u32 block_hashtable_start; + u32 block_hashtable_size; + u32 digest_sector_size; + u32 digest_block_sectorcount; + + u32 banner_size; + u32 offset_0x20C; + u32 total_rom_size; + u32 offset_0x214; + u32 offset_0x218; + u32 offset_0x21C; + + u32 modcrypt1_start; + u32 modcrypt1_size; + u32 modcrypt2_start; + u32 modcrypt2_size; + + u32 tid_low; + u32 tid_high; + u32 public_sav_size; + u32 private_sav_size; + u8 reserved3[176]; + u8 age_ratings[0x10]; + + u8 hmac_arm9[20]; + u8 hmac_arm7[20]; + u8 hmac_digest_master[20]; + u8 hmac_icon_title[20]; + u8 hmac_arm9i[20]; + u8 hmac_arm7i[20]; + u8 reserved4[40]; + u8 hmac_arm9_no_secure[20]; + u8 reserved5[2636]; + u8 debug_args[0x180]; + u8 rsa_signature[0x80]; + +} tDSiHeader; + + +#define __NDSHeader ((tNDSHeader *)0x02FFFE00) +#define __DSiHeader ((tDSiHeader *)0x02FFE000) + + +/*! + \brief the NDS banner format. + See gbatek for more information. +*/ +typedef struct sNDSBanner { + u16 version; //!< version of the banner. + u16 crc; //!< 16 bit crc/checksum of the banner. + u8 reserved[28]; + u8 icon[512]; //!< 32*32 icon of the game with 4 bit per pixel. + u16 palette[16]; //!< the pallete of the icon. + u16 titles[6][128]; //!< title of the game in 6 different languages. +} tNDSBanner; + + +/// this is my stuff vv +void _DStoBMPorder(u8* store, u8 *source); +void rotateInPlace90(u8 *source,u32 width,u32 height); +void convertIconToBmp(u16* bmp, tNDSBanner* banner ); +Result LoadIconFromNDS(const char* filename, u16* output); diff --git a/include/settings.hpp b/include/settings.hpp new file mode 100644 index 0000000..994c8fd --- /dev/null +++ b/include/settings.hpp @@ -0,0 +1,14 @@ +#pragma once + +#define ENTRY_HEIGHT 48 +#define FILELIST_HEIGHT (240-MENU_HEADING_HEIGHT-MENU_BORDER_HEIGHT) +#define MENU_BORDER_HEIGHT 8 +#define MENU_HEADING_HEIGHT 40 +#define MAX_ENTRY_COUNT ((FILELIST_HEIGHT-(FILELIST_HEIGHT%ENTRY_HEIGHT))/ENTRY_HEIGHT) + +#define BGColor HexColor(0xC87941) +#define BORDER_COLOR HexColor(0x290001) +#define BORDER_FOREGROUND HexColor(0xDBCBBD) +#define HIGHLIGHT_BGCOLOR HexColor(0x87431D) +#define HIGHLIGHT_FOREGROUND BGColor +#define FOREGROUND_COLOR BORDER_COLOR diff --git a/include/ticket.h b/include/ticket.h new file mode 100644 index 0000000..4302086 --- /dev/null +++ b/include/ticket.h @@ -0,0 +1,51 @@ +/* +0x0 0x40 Issuer +0x40 0x3C ECC PublicKey +0x7C 0x1 Version (For 3DS this is always 1) +0x7D 0x1 CaCrlVersion +0x7E 0x1 SignerCrlVersion +0x7F 0x10 TitleKey (normal-key encrypted using one of the common keyYs; see below) +0x8F 0x1 Reserved +0x90 0x8 TicketID +0x98 0x4 ConsoleID +0x9C 0x8 TitleID +0xA4 0x2 Reserved +0xA6 0x2 Ticket title version +0xA8 0x8 Reserved +0xB0 0x1 License Type +0xB1 0x1 Index to the common keyY used for this ticket, usually 0x1 for retail system titles; see below. +0xB2 0x2A Reserved +0xDC 0x4 eShop Account ID? +0xE0 0x1 Reserved +0xE1 0x1 Audit +0xE2 0x42 Reserved +0x124 0x40 Limits +0x164 X Content Index +*/ +#include <3ds.h> +typedef struct +{ + u8 signatureIssuer[0x40]; + u8 pubKey[0x3C]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 titleKey[0x10]; + u8 reserved1; + u8 ticketId[8]; + u8 consoleId[4]; + u8 titleId[8]; + u8 reserved2[2]; + u8 ticketTitleVersion[2]; + u8 reserved3[8]; + u8 licenseType; + u8 keyYIdx; + u8 reserved4[0x2A]; // Zero for CXI Content0 + u8 accountId[0x04]; + u8 reserved5; + u8 audit; + u8 reserved6[0x42]; + u8 limits[0x40]; + u8 contentIndex[0xAC]; +} __attribute__((__packed__)) +sTicketHeader; \ No newline at end of file diff --git a/include/tmd.h b/include/tmd.h new file mode 100644 index 0000000..d4eb71d --- /dev/null +++ b/include/tmd.h @@ -0,0 +1,43 @@ +#include <3ds.h> +typedef struct +{ + u8 signatureIssuer[0x40]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 reserved1; + u8 systemVersion[8]; + u8 titleId[8]; + u8 titleType[4]; + u8 groupId[2]; + u8 saveDataSizeLE[4]; + u8 privateSaveDataSizeLE[4]; // Zero for CXI Content0 + u8 reserved2[4]; + u8 srlFlag; // Zero for CXI Content0 + u8 reserved_3[0x31]; + u8 accessRights[4]; + u8 titleVersion[2]; + u8 contentCount[2]; + u8 bootContent[2]; + u8 padding[2]; + u8 contentInfoHash[0x20]; +} __attribute__((__packed__)) +sTMDHeader; + +typedef struct +{ + u8 contentIdxOffset[2]; + u8 contentCommandCount[2]; + u8 contentChunkHash[0x20]; +}__attribute__((__packed__)) +sContentInfoRecord; + +typedef struct +{ + u8 contentId[4]; + u8 contentIdx[2]; + u8 contentType[2]; + u8 contentSize[8]; + u8 hash[0x20]; +}__attribute__((__packed__)) +sContentChunkRecord; diff --git a/romfs/sdcard.fwd b/romfs/sdcard.fwd new file mode 100644 index 0000000..ac8a94d --- /dev/null +++ b/romfs/sdcard.fwd @@ -0,0 +1,4 @@ +name=3DS SD Card +gamepath_location=0x229BC +gamepath_length=252 +banner_location=0x30200 diff --git a/romfs/sdcard.nds b/romfs/sdcard.nds new file mode 100644 index 0000000000000000000000000000000000000000..abd9ee685a16af743cd8d609d079346e54049585 GIT binary patch literal 206336 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

!T$4kJR?aOP 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 // CBC mode, for memset +#include "aes.h" + +/*****************************************************************************/ +/* Defines: */ +/*****************************************************************************/ +// The number of columns comprising a state in AES. This is a constant in AES. Value=4 +#define Nb 4 + +#if defined(AES256) && (AES256 == 1) + #define Nk 8 + #define Nr 14 +#elif defined(AES192) && (AES192 == 1) + #define Nk 6 + #define Nr 12 +#else + #define Nk 4 // The number of 32 bit words in a key. + #define Nr 10 // The number of rounds in AES Cipher. +#endif + +// jcallan@github points out that declaring Multiply as a function +// reduces code size considerably with the Keil ARM compiler. +// See this link for more information: https://github.com/kokke/tiny-AES-C/pull/3 +#ifndef MULTIPLY_AS_A_FUNCTION + #define MULTIPLY_AS_A_FUNCTION 0 +#endif + + + + +/*****************************************************************************/ +/* Private variables: */ +/*****************************************************************************/ +// state - array holding the intermediate results during decryption. +typedef uint8_t state_t[4][4]; + + + +// The lookup-tables are marked const so they can be placed in read-only storage instead of RAM +// The numbers below can be computed dynamically trading ROM for RAM - +// This can be useful in (embedded) bootloader applications, where ROM is often limited. +static const uint8_t sbox[256] = { + //0 1 2 3 4 5 6 7 8 9 A B C D E F + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 }; + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d }; +#endif + +// The round constant word array, Rcon[i], contains the values given by +// x to the power (i-1) being powers of x (x is denoted as {02}) in the field GF(2^8) +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 }; + +/* + * Jordan Goulder points out in PR #12 (https://github.com/kokke/tiny-AES-C/pull/12), + * that you can remove most of the elements in the Rcon array, because they are unused. + * + * From Wikipedia's article on the Rijndael key schedule @ https://en.wikipedia.org/wiki/Rijndael_key_schedule#Rcon + * + * "Only the first some of these constants are actually used – up to rcon[10] for AES-128 (as 11 round keys are needed), + * up to rcon[8] for AES-192, up to rcon[7] for AES-256. rcon[0] is not used in AES algorithm." + */ + + +/*****************************************************************************/ +/* Private functions: */ +/*****************************************************************************/ +/* +static uint8_t getSBoxValue(uint8_t num) +{ + return sbox[num]; +} +*/ +#define getSBoxValue(num) (sbox[(num)]) + +// This function produces Nb(Nr+1) round keys. The round keys are used in each round to decrypt the states. +static void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; // Used for the column/row operations + + // The first round key is the key itself. + for (i = 0; i < Nk; ++i) + { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + // All other round keys are found from the previous round keys. + for (i = Nk; i < Nb * (Nr + 1); ++i) + { + { + k = (i - 1) * 4; + tempa[0]=RoundKey[k + 0]; + tempa[1]=RoundKey[k + 1]; + tempa[2]=RoundKey[k + 2]; + tempa[3]=RoundKey[k + 3]; + + } + + if (i % Nk == 0) + { + // This function shifts the 4 bytes in a word to the left once. + // [a0,a1,a2,a3] becomes [a1,a2,a3,a0] + + // Function RotWord() + { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + } + + // SubWord() is a function that takes a four-byte input word and + // applies the S-box to each of the four bytes to produce an output word. + + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + + tempa[0] = tempa[0] ^ Rcon[i/Nk]; + } +#if defined(AES256) && (AES256 == 1) + if (i % Nk == 4) + { + // Function Subword() + { + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + } + } +#endif + j = i * 4; k=(i - Nk) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +void AES_init_ctx(struct AES_ctx* ctx, const uint8_t* key) +{ + KeyExpansion(ctx->RoundKey, key); +} +#if (defined(CBC) && (CBC == 1)) || (defined(CTR) && (CTR == 1)) +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv) +{ + memcpy (ctx->Iv, iv, AES_BLOCKLEN); +} +#endif + +// This function adds the round key to state. +// The round key is added to the state by an XOR function. +static void AddRoundKey(uint8_t round, state_t* state, const uint8_t* RoundKey) +{ + uint8_t i,j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[i][j] ^= RoundKey[(round * Nb * 4) + (i * Nb) + j]; + } + } +} + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void SubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxValue((*state)[j][i]); + } + } +} + +// The ShiftRows() function shifts the rows in the state to the left. +// Each row is shifted with different offset. +// Offset = Row number. So the first row is not shifted. +static void ShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to left + temp = (*state)[0][1]; + (*state)[0][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[3][1]; + (*state)[3][1] = temp; + + // Rotate second row 2 columns to left + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to left + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[3][3]; + (*state)[3][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[1][3]; + (*state)[1][3] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x<<1) ^ (((x>>7) & 1) * 0x1b)); +} + +// MixColumns function mixes the columns of the state matrix +static void MixColumns(state_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) + { + t = (*state)[i][0]; + Tmp = (*state)[i][0] ^ (*state)[i][1] ^ (*state)[i][2] ^ (*state)[i][3] ; + Tm = (*state)[i][0] ^ (*state)[i][1] ; Tm = xtime(Tm); (*state)[i][0] ^= Tm ^ Tmp ; + Tm = (*state)[i][1] ^ (*state)[i][2] ; Tm = xtime(Tm); (*state)[i][1] ^= Tm ^ Tmp ; + Tm = (*state)[i][2] ^ (*state)[i][3] ; Tm = xtime(Tm); (*state)[i][2] ^= Tm ^ Tmp ; + Tm = (*state)[i][3] ^ t ; Tm = xtime(Tm); (*state)[i][3] ^= Tm ^ Tmp ; + } +} + +// Multiply is used to multiply numbers in the field GF(2^8) +// Note: The last call to xtime() is unneeded, but often ends up generating a smaller binary +// The compiler seems to be able to vectorize the operation better this way. +// See https://github.com/kokke/tiny-AES-c/pull/34 +#if MULTIPLY_AS_A_FUNCTION +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y>>1 & 1) * xtime(x)) ^ + ((y>>2 & 1) * xtime(xtime(x))) ^ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))); /* this last call to xtime() can be omitted */ + } +#else +#define Multiply(x, y) \ + ( ((y & 1) * x) ^ \ + ((y>>1 & 1) * xtime(x)) ^ \ + ((y>>2 & 1) * xtime(xtime(x))) ^ \ + ((y>>3 & 1) * xtime(xtime(xtime(x)))) ^ \ + ((y>>4 & 1) * xtime(xtime(xtime(xtime(x)))))) \ + +#endif + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +/* +static uint8_t getSBoxInvert(uint8_t num) +{ + return rsbox[num]; +} +*/ +#define getSBoxInvert(num) (rsbox[(num)]) + +// MixColumns function mixes the columns of the state matrix. +// The method used to multiply may be difficult to understand for the inexperienced. +// Please use the references to gain more information. +static void InvMixColumns(state_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) + { + a = (*state)[i][0]; + b = (*state)[i][1]; + c = (*state)[i][2]; + d = (*state)[i][3]; + + (*state)[i][0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + (*state)[i][1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + (*state)[i][2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + (*state)[i][3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + + +// The SubBytes Function Substitutes the values in the +// state matrix with values in an S-box. +static void InvSubBytes(state_t* state) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) + { + for (j = 0; j < 4; ++j) + { + (*state)[j][i] = getSBoxInvert((*state)[j][i]); + } + } +} + +static void InvShiftRows(state_t* state) +{ + uint8_t temp; + + // Rotate first row 1 columns to right + temp = (*state)[3][1]; + (*state)[3][1] = (*state)[2][1]; + (*state)[2][1] = (*state)[1][1]; + (*state)[1][1] = (*state)[0][1]; + (*state)[0][1] = temp; + + // Rotate second row 2 columns to right + temp = (*state)[0][2]; + (*state)[0][2] = (*state)[2][2]; + (*state)[2][2] = temp; + + temp = (*state)[1][2]; + (*state)[1][2] = (*state)[3][2]; + (*state)[3][2] = temp; + + // Rotate third row 3 columns to right + temp = (*state)[0][3]; + (*state)[0][3] = (*state)[1][3]; + (*state)[1][3] = (*state)[2][3]; + (*state)[2][3] = (*state)[3][3]; + (*state)[3][3] = temp; +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +// Cipher is the main function that encrypts the PlainText. +static void Cipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(0, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without MixColumns() + for (round = 1; ; ++round) + { + SubBytes(state); + ShiftRows(state); + if (round == Nr) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + // Add round key to last round + AddRoundKey(Nr, state, RoundKey); +} + +#if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) +static void InvCipher(state_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + // Add the First round key to the state before starting the rounds. + AddRoundKey(Nr, state, RoundKey); + + // There will be Nr rounds. + // The first Nr-1 rounds are identical. + // These Nr rounds are executed in the loop below. + // Last one without InvMixColumn() + for (round = (Nr - 1); ; --round) + { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } + +} +#endif // #if (defined(CBC) && CBC == 1) || (defined(ECB) && ECB == 1) + +/*****************************************************************************/ +/* Public functions: */ +/*****************************************************************************/ +#if defined(ECB) && (ECB == 1) + + +void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call encrypts the PlainText with the Key using AES algorithm. + Cipher((state_t*)buf, ctx->RoundKey); +} + +void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf) +{ + // The next function call decrypts the PlainText with the Key using AES algorithm. + InvCipher((state_t*)buf, ctx->RoundKey); +} + + +#endif // #if defined(ECB) && (ECB == 1) + + + + + +#if defined(CBC) && (CBC == 1) + + +static void XorWithIv(uint8_t* buf, const uint8_t* Iv) +{ + uint8_t i; + for (i = 0; i < AES_BLOCKLEN; ++i) // The block in AES is always 128bit no matter the key size + { + buf[i] ^= Iv[i]; + } +} + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} + +#endif // #if defined(CBC) && (CBC == 1) + + + +#if defined(CTR) && (CTR == 1) + +/* Symmetrical operation: same function for encrypting as for decrypting. Note any IV/nonce should never be reused with the same key */ +void AES_CTR_xcrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + uint8_t buffer[AES_BLOCKLEN]; + + size_t i; + int bi; + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + + memcpy(buffer, ctx->Iv, AES_BLOCKLEN); + Cipher((state_t*)buffer,ctx->RoundKey); + + /* Increment Iv and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will overflow */ + if (ctx->Iv[bi] == 255) + { + ctx->Iv[bi] = 0; + continue; + } + ctx->Iv[bi] += 1; + break; + } + bi = 0; + } + + buf[i] = (buf[i] ^ buffer[bi]); + } +} + +#endif // #if defined(CTR) && (CTR == 1) diff --git a/source/builder.cpp b/source/builder.cpp new file mode 100644 index 0000000..9c33d59 --- /dev/null +++ b/source/builder.cpp @@ -0,0 +1,429 @@ +#include <3ds.h> +#include "builder.hpp" +#include +#include +#include "logger.hpp" +#include +#include +#include +#include "helpers.hpp" +#include +#include "nds.h" +#include "tmd.h" +#include "cia.h" +#include "ticket.h" + +Logger logger("Builder"); + + + +Result Builder::initialize() { + + // READ SRL TEMPLATE FROM SD or ROMFS + std::string srlFileName = ROMFS_SRL; + std::string srlTemplate = ROMFS_TEMPLATE; + if (fileExists(SDCARD_SRL)) { + if (fileExists(SDCARD_TEMPLATE)) { + srlFileName=SDCARD_SRL; + srlTemplate=SDCARD_TEMPLATE; + logger.info("Loaded SDCard Templates from SDCARD."); + }else{ + logger.error("Missing '"+std::string(SDCARD_TEMPLATE)+"'. Unable to load "+std::string(SDCARD_SRL)+" due to missing file."); + } + }else{ + logger.info(std::string(SDCARD_SRL)+" not found. Using built in template."); + } + this->srl = readEntireFile(srlFileName); + memcpy(&this->srlBannerLocation,this->srl.c_str() + 0x68,4); + + parseTemplate(srlTemplate); + + // READ CERTCHAIN FROM CERTS.DB + { + std::vector offsetSizePairs{ + 0xC10,0x1F0, + 0x3A00,0x210, + 0x3F10,0x300, + 0x3C10,0x300 + }; + Handle hFile={}; + char buf[0x300]={0}; + u32 readCount=0; + std::string ciacert; + std::map certParts; + + Result res = FSUSER_OpenFileDirectly(&hFile, ARCHIVE_NAND_CTR_FS, fsMakePath(PATH_EMPTY,""), fsMakePath(PATH_ASCII,"/dbs/certs.db"), FS_OPEN_READ, 0); + if (R_SUCCEEDED(res)) { + + for (size_t i=0;iciaCertChain=certParts[0xc10]+certParts[0x3a00]+certParts[0x3f10]+certParts[0x3c10]; + this->ticketCertChain=certParts[0x3f10]+certParts[0xc10]+certParts[0x3a00]; + FSFILE_Read(hFile,&readCount,0x3c10,buf,0x300); + memcpy(this->rsaSignModulo,buf+0x1c8,100); + memcpy(this->rsaSignExponent,buf+0x2c8,4); + FSFILE_Close (hFile); + + }else{ + char buf[50]={0}; + sprintf(buf,"Failed to open certs.db file. res: %lx",res); + logger.error(std::string(buf)); + return res; + } + + } + + return 0; +} +std::string Builder::buildSRL(std::string filename, bool randomTid, std::string customTitle) { + if (filename.size() > this->launchPathLen) { + //return (MAKERESULT(RL_PERMANENT,RS_INVALIDARG,RM_SDMC,RD_TOO_LARGE)); + return ""; + } + //TODO Load nds file + sNDSHeader header={}; + sNDSBanner banner={}; + char animatedIconData[0x1180] = {0}; + char extraTitles[2][0x100] = {0}; + const u8 noAnimation[] = {0x01,0x00,0x00,0x01}; + std::string customBannerFilename = filename.substr(0,filename.find_last_of('.'))+".bin"; + logger.info("looking for banner at "+customBannerFilename); + bool customBanner = fileExists(customBannerFilename) && fileSize(customBannerFilename) == 0x23C0; + if (!customBanner) { + customBannerFilename=filename.substr(filename.find_last_of('/'),filename.find_last_of('.')-filename.find_last_of('/'))+".bin"; + customBannerFilename=SDCARD_BANNER_PATH+customBannerFilename; + logger.info("looking for banner at "+customBannerFilename); + customBanner = fileExists(customBannerFilename) && fileSize(customBannerFilename) == 0x23C0; + } + std::ifstream f(filename); + f.seekg(0); + f.read((char*)&header,sizeof(header)); + f.seekg(header.bannerOffset); + f.read((char*)&banner,sizeof(banner)); + if ((banner.version & 0xFF) > 1) { + //TODO: copy chinese title + f.read(extraTitles[0],0x100); + }else{ + memcpy(extraTitles[0],(u8*)&banner.titles[0],0x100); + } + if ((banner.version & 0xFF) > 2) { + //TODO: copy korean title + f.read(extraTitles[0],0x100); + }else{ + memcpy(extraTitles[1],(u8*)&banner.titles[0],0x100); + } + if ((banner.version & 0x100) > 0) { + //TODO: copy animated banner + f.seekg(header.bannerOffset+0x1240); + f.read(animatedIconData,0x1180); + } + + + f.close(); + + if (customBanner==true) { + std::ifstream f(customBannerFilename); + f.read((char*)&banner,sizeof(banner)); + if ((banner.version & 0xFF) > 1) { + //TODO: copy chinese title + f.read(extraTitles[0],0x100); + }else{ + memcpy(extraTitles[0],(u8*)&banner.titles[0],0x100); + } + if ((banner.version & 0xFF) > 2) { + //TODO: copy korean title + f.read(extraTitles[0],0x100); + }else{ + memcpy(extraTitles[1],(u8*)&banner.titles[0],0x100); + } + if ((banner.version & 0x100) > 0) { + //TODO: copy animated banner + f.seekg(0x1240); + f.read(animatedIconData,0x1180); + } + f.close(); + } + //TODO apply nds file to srl + std::string dsiware = this->srl; + if (randomTid) { + PS_GenerateRandomBytes(header.gameCode,4); + for (int i = 0;i<4;i++) { + unsigned char c = header.gameCode[i]; + if (c > 'Z' || c < 'A') c='A'+(c%26); + header.gameCode[i] = c; + } + } + if (!customTitle.empty()) { + char cTitle[0x100/2] = {0}; + strncpy(cTitle,customTitle.c_str(),0x100/2); + for(int i=0;i<6;i++) { + for (int x=0;x<0x100/2;x++) { + banner.titles[i][x] = cTitle[x]; + } + } + for(int i=0;i<2;i++) { + for (int x=0;x<0x100/2;x++) { + extraTitles[i][x] = cTitle[x]; + } + } + } + // Set header + // could be 1 command but this is easier to read + dsiware.replace(0,0x0C,header.gameTitle,0x0C); + dsiware.replace(0x0C,0x04,header.gameCode,0x04); + char emagCode[] = {header.gameCode[0x03],header.gameCode[0x02],header.gameCode[0x01],header.gameCode[0x00]}; + dsiware.replace(0x230,0x04,emagCode,0x04); + dsiware.replace(0x10,0x02,header.makercode,0x02); + // Set Banner + + // basic banner info + dsiware.replace(this->srlBannerLocation+0x20,0x200,(char*)banner.icon,0x200); // icon + dsiware.replace(this->srlBannerLocation+0x220,0x20,(char*)banner.palette,0x20); // palette + dsiware.replace(this->srlBannerLocation+0x240,0x600,(char*)banner.titles,0x600); // titles + dsiware.replace(this->srlBannerLocation+0x840,0x200,(char*)extraTitles,0x200); // titles +// dsiware.replace(this->srlBannerLocation,sizeof(banner),&banner,sizeof(banner)); + + // animated banner + if ((banner.version & 0x100) > 0) { + dsiware.replace(this->srlBannerLocation + 0x1240,0x1180,animatedIconData,0x1180); + }else{ + // fill all icon data with default + // then replace animation sequence with static image + for (u8 i = 0;i<8;i++) { + dsiware.replace(this->srlBannerLocation+0x1240+(0x200 * i),0x200,(char*)banner.icon,0x200); + dsiware.replace(this->srlBannerLocation+0x2240+(0x20 * i),0x20,(char*)banner.palette,0x20); + } + dsiware.replace(this->srlBannerLocation+0x2340,0x80,0x80,'\0'); + dsiware.replace(this->srlBannerLocation+0x2340,0x4,(char*)noAnimation,0x04); + } + + // SET PATH + std::string modifiedPath = filename.substr(filename.find_first_of('/')+1); + dsiware.replace(this->launchPathLocation,this->launchPathLen,this->launchPathLen,'\0'); + dsiware.replace(this->launchPathLocation,modifiedPath.size(),modifiedPath); + + u16 bannerCrcs[]={ + crc16Modbus(&dsiware[this->srlBannerLocation+0x20],0x820), + crc16Modbus(&dsiware[this->srlBannerLocation+0x20],0x920), + crc16Modbus(&dsiware[this->srlBannerLocation+0x20],0xA20), + crc16Modbus(&dsiware[this->srlBannerLocation+0x1240],0x1180) + }; + u16 headerCrc = crc16Modbus(dsiware.c_str(),0x15E); + dsiware.replace(this->srlBannerLocation+0x02,8,(char*)&bannerCrcs,8); + dsiware.replace(0x15E,2,(char*)&headerCrc,2); +#ifdef DEBUG + std::fstream of(filename.substr(0,filename.find_last_of("."))+".srl",std::ios_base::binary | std::ios_base::out); + of << dsiware; + of.close(); +#endif + //build twl + return dsiware; +} +void writeArray(const void* data, u32 size) { + for(u32 i=0; isections["content"] = buildSRL(filename, randomTid, customTitle); + std::string srlSha = sha256( (u8*)this->sections["content"].c_str(), this->sections["content"].size()); + this->sections["ticket"]=buildTicket(filename); + this->sections["tmd"]=this->buildTMD(contentID); + + sCiaHeader header={}; + header.certchainSize = this->ciaCertChain.size(); + header.contentSize = this->sections["content"].size(); + header.headerSize=sizeof(header); + header.metaSize=0; + header.ticketSize=this->sections["ticket"].size(); + header.tmdSize=this->sections["tmd"].size(); + memset(header.contentIdx,0,0x2000); + header.contentIdx[0x0] = 0x80; + std::string cia = aligned(std::string((char*)&header,sizeof(header)),0x40) + aligned(this->ciaCertChain,0x40) + aligned(this->sections["ticket"],0x40) + aligned(this->sections["tmd"],0x40) + aligned(this->sections["content"],0x40); + std::cout << "Created cia file data" << std::endl; + +#ifdef DEBUG + std::ofstream fo(filename.substr(0,filename.find_last_of('.')+1)+"cia"); + fo.write(cia.c_str(),cia.size()); + fo.close(); +#endif + Handle ciaInstallFileHandle={}; + u32 installSize = cia.size(), installOffset = 0; + u32 bytes_written = 0; + u32 size = cia.size(); + + Result ret =amInit(); + if (R_FAILED(ret)) { + std::cout << "Failed to Initialize AM\n" << std::endl; + return ret; + } + ret = AM_StartCiaInstall(MEDIATYPE_NAND, &ciaInstallFileHandle); + if (R_FAILED(ret)) { + std::cout << "Error in:\nAM_StartCiaInstall\nret: " << std::hex << ret << std::endl; + return ret; + } + std::cout << "Writing to file"; + do { + if (size > installSize) + size=installSize; + ret = FSFILE_Write(ciaInstallFileHandle, &bytes_written, installOffset, cia.c_str(), size, FS_WRITE_FLUSH); + if (R_FAILED(ret)) { + std::cout << "\nError in:\nwriting to file\nret: " << std::hex << ret << std::endl; + AM_CancelCIAInstall(ciaInstallFileHandle); + return ret; + } + std::cout << "."; + installOffset += bytes_written; + } while(installOffset < installSize); + std::cout << "done\n"; + ret = AM_FinishCiaInstall(ciaInstallFileHandle); + if (R_FAILED(ret)) { + printf("Error in:\nAM_FinishCiaInstall\n"); + return ret; + } + std::cout << "Installed Forwarder" << std::endl; + amExit(); + +return 0; +} + +std::string Builder::buildTicket(std::string filename) { + + std::string hash = sha256((u8*)filename.c_str(),filename.size()); + + sSignature sig = {.sigType={0x00,0x01,0x00,0x04}}; + sTicketHeader header={0}; + //memset(&header,0,sizeof(header)); + u8 issuer[]="Root-CA00000003-XS0000000c"; + + // https://github.com/Tiger21820/ctr_toolkit/blob/master/make_cia/ticket.h#L31 + u8 CONTENT_INDEX[] = + { 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, + 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00 + }; + + header.version=1; + memcpy(header.signatureIssuer,issuer,26); + memcpy(header.ticketId,hash.c_str(),8); + readTWLTID(header.titleId,this->sections["content"].c_str()); + // we dont actually need a valid encrypted title key, right? + // lets just use a title key of all 0 and normal key of all 0 and see where that takes us + u8 iv[0x10] = {0}; + memcpy(iv,header.titleId,8); + u8 key[0x10] = {0}; + encryptAES128CBC(header.titleKey,iv,key,header.titleKey,0x10); + memcpy(header.contentIndex,CONTENT_INDEX,0x30); + + //sign((u8*)sha256((u8*)&header,sizeof(header)).c_str(),this->rsaSignModulo, 0x100 ,this->rsaSignExponent, 0x100 ,sig.signature); + // who needs a real signature either + // sigpatches ftw + sig.signature[0x100-1]=1; + return (std::string((char*)&sig,sizeof(sig))+std::string((char*)&header,sizeof(header))); +} +std::string Builder::buildTMD(u8* contentId) { + + static const u8 sig_type[4] = { 0x00,0x01,0x00,0x04 }; + + //TMD HEADER + sTMDHeader tmd={}; + memset(&tmd,0,sizeof(tmd)); + u8 issuer[]="Root-CA00000003-CP0000000b"; + memcpy(tmd.signatureIssuer,issuer,26); + tmd.version=1; + readTWLTID(tmd.titleId,this->sections["content"].c_str()); +// memcpy(tmd.titleId,this->getTWLTID((u8*)this->sections["content"].c_str()).c_str(),8); // title id + tmd.titleType[0x3]=0x40; // title type: CTR + memcpy(tmd.saveDataSizeLE +0x5A,this->sections["content"].c_str()+0x238,4); // will always be 0 unless template changes + memcpy(tmd.privateSaveDataSizeLE,this->sections["content"].c_str()+0x23C,4); // will always be 0 unless template changes + tmd.srlFlag=this->sections["content"][0x1C]; + memcpy(tmd.titleVersion,this->sections["content"].c_str()+0x1D,2); // title version + tmd.contentCount[1] = 1; + + sContentInfoRecord infoRecords[64] = {0}; + infoRecords[0].contentCommandCount[1]=1; + sContentChunkRecord contentChunkRecord={0}; + memcpy(contentChunkRecord.contentId,contentId,4); + u64 contentSize = this->sections["content"].size(); + memcpyrev(&contentChunkRecord.contentSize,(u8*)&contentSize,8); + memcpy(contentChunkRecord.hash,sha256((u8*)this->sections["content"].c_str(),this->sections["content"].size()).c_str(),0x20); + + memcpy(infoRecords[0].contentChunkHash,sha256((u8*)&contentChunkRecord,sizeof(contentChunkRecord)).c_str(),0x20); + memcpy( tmd.contentInfoHash,sha256((u8*)&infoRecords,sizeof(sContentInfoRecord)*64).c_str(),0x20); + + std::string hash = sha256((u8*)&tmd,sizeof(sTMDHeader)); + u8 signature[0x100] = {0}; + // sign((u8*)hash.c_str(),this->rsaSignModulo, 0x100 ,this->rsaSignExponent, 0x100 ,signature); + // you have sig patches, right? cool... + signature[0x100-1]=1; + std::string tmdSection; + char reserved[0x3c]={0}; + tmdSection += std::string((char*)sig_type,4); + tmdSection += std::string((char*)signature,0x100); + tmdSection += std::string(reserved, 0x3C); + tmdSection += std::string((char*)&tmd,sizeof(tmd)); + tmdSection += std::string((char*)infoRecords,sizeof(sContentInfoRecord)*64); + tmdSection += std::string((char*)&contentChunkRecord,sizeof(contentChunkRecord)); + return tmdSection; +} +std::string Builder::getTWLTID(u8* srl) { + u8 twltitle[8]={0x00,0x04,0x80,0x00,0x00,0x00,0x00,0x00}; + for (int i=0;i<6;i++) + twltitle[i]=srl[0x233+i]; + return std::string((char*)twltitle,8); +} +void Builder::readTWLTID(void* titleid, const void* srl) { + u8 twltitle[8]={0x00,0x04,0x80,0x00,0x00,0x00,0x00,0x00}; + memcpyrev(twltitle+3,(u8*)srl + 0x230,5); + memcpy(titleid,twltitle,8); +} + +void Builder::parseTemplate(std::string path) { + std::ifstream f(path); + std::string line; + while (std::getline(f, line)) + { + + u32 value = 0; + u8 base = 16; + __try { + std::string sval = line.substr(line.find_first_of('=')+1); + if (strncmp(sval.c_str(), "0x", 2)!=0) { + base = 10; + } + value = stoul(sval,nullptr,base); + std::string key(line.substr(0,line.find_first_of('='))); + + if (key=="gamepath_location") { + this->launchPathLocation = value; + } + if (key=="gamepath_length") { + this->launchPathLen = value; + } + //std::cout << key << " ::: " << value << "\n"; + } + __catch(const std::invalid_argument & e) { } + + } + f.close(); + +} \ No newline at end of file diff --git a/source/config.cpp b/source/config.cpp new file mode 100644 index 0000000..8ee0de5 --- /dev/null +++ b/source/config.cpp @@ -0,0 +1,28 @@ +#include <3ds.h> +#include +#include "config.hpp" +#include "graphics.h" +#include "settings.hpp" + +Config::Config() { + this->customTitle=false; + this->randomTID=false; +} +void Config::draw(bool interactive) { + drawPanelWithTitle(0,0,0,320,240,MENU_BORDER_HEIGHT,BGColor,BORDER_COLOR,"Settings",BORDER_FOREGROUND); + drawCheckbox(MENU_BORDER_HEIGHT+10,MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+20,0,20,20,0.67,BGColor,BORDER_COLOR,FOREGROUND_COLOR,"Random TID",this->randomTID); + drawCheckbox(MENU_BORDER_HEIGHT+10,MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+60,0,20,20,0.67,BGColor,BORDER_COLOR,FOREGROUND_COLOR,"Custom Title",this->customTitle); + +} +void Config::interact(touchPosition *touch) { + if (touch->px > MENU_BORDER_HEIGHT+10 && touch->px < MENU_BORDER_HEIGHT+30) { + if (touch->py >= MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+20 && touch->py <= MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+50) { + this->randomTID=!this->randomTID; + } + if (touch->py >= MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+60 && touch->py <= MENU_BORDER_HEIGHT+MENU_HEADING_HEIGHT+80) { + this->customTitle=!this->customTitle; + } + } + +} + diff --git a/source/dialog.cpp b/source/dialog.cpp new file mode 100644 index 0000000..6bc2c93 --- /dev/null +++ b/source/dialog.cpp @@ -0,0 +1,80 @@ +#include <3ds.h> +#include +#include +#include +#include "dialog.hpp" +#include "graphics.h" +#include "settings.hpp" +void Dialog::draw() { + C2D_TextBuf buf = C2D_TextBufNew(4096); + C2D_Text ctext; + C2D_TextParse(&ctext,buf,"0"); + float textheight=0; + C2D_TextGetDimensions(&ctext,0.67,0.67,NULL,&textheight); + C2D_TextBufDelete(buf); + + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C2D_SceneBegin(this->target); + drawPanel(this->x,this->y,0,this->width, this->height,MENU_BORDER_HEIGHT,BGColor,BORDER_COLOR); + float drawx = this->x+MENU_BORDER_HEIGHT; + float drawxOffset = (this->width/this->options.size()); + float drawyOffset = (this->height*2/3); + float drawy = this->y+MENU_BORDER_HEIGHT+drawyOffset+(this->height/6); + for (size_t i=0;imessage.size();i++) + drawText(drawx+(this->width/2),this->y+(this->height*2/12)+((1+textheight)*i),0,0.67,0,FOREGROUND_COLOR,this->message[i].c_str(),C2D_AlignCenter); + for (size_t i=0;ioptions.size();i++) { + drawText(drawx+(drawxOffset*(i+0.5)),drawy,0,0.67,(this->selected==i)?HIGHLIGHT_BGCOLOR:BGColor,(this->selected==i)?HIGHLIGHT_FOREGROUND:FOREGROUND_COLOR, this->options[i].c_str(),C2D_AlignCenter); + } + C3D_FrameEnd(0); +} +int Dialog::handle() { + touchPosition pos; + touchPosition oldPos; + if (this->options.size() < 1) { + this->draw(); + return -1; + } + while (aptMainLoop()) + { + hidScanInput(); + oldPos = pos; + u32 kDown = hidKeysDown(); + hidTouchRead(&pos); + + if (pos.px != oldPos.px || pos.py != oldPos.py) { + // check tap position + } + if (kDown & KEY_LEFT) { + this->selected--; + if (this->selected < 0) this->selected=this->options.size()-1; + }else if(kDown & KEY_RIGHT) { + this->selected++; + if (this->selected > this->options.size()) this->selected=0; + }else if(kDown & KEY_A || kDown & KEY_START) { + return this->selected; + } + this->draw(); + } + return -1; +} +Dialog::Dialog(C3D_RenderTarget* target, float x, float y, float width, float height, std::string message, std::initializer_list options, int defaultChoice) { + this->options = std::vector(options.begin(),options.end()); + this->message.push_back(message); + this->target=target; + this->selected=defaultChoice; + this->x=x; + this->y=y; + this->width=width; + this->height=height; +} +Dialog::Dialog(C3D_RenderTarget* target, float x, float y, float width, float height, std::initializer_list message, std::initializer_list options, int defaultChoice) { + this->options = std::vector(options.begin(),options.end()); + this->message= std::vector(message.begin(),message.end()); + this->target=target; + this->selected=defaultChoice; + this->x=x; + this->y=y; + this->width=width; + this->height=height; +} + diff --git a/source/graphics.c b/source/graphics.c new file mode 100644 index 0000000..515bcbc --- /dev/null +++ b/source/graphics.c @@ -0,0 +1,87 @@ +#include <3ds.h> +#include +#include +#include "graphics.h" +u8* getFrameBufferLocation(u8* buffer, u16 x, u16 y, u16 width, u16 height,u8 bps) { + return buffer+(((x*height)+y)*bps); +} +void displayImageAt(u8* framebuffer, u16* icon, u16 x, u16 y, u16 width, u16 height) { + for (int c=0;c<32;c++) { + memcpy(getFrameBufferLocation(framebuffer,x+c,y,width,height,2),&icon[x*(32)],32*2); + } +} + +void drawPanelWithTitle(float x, float y, float z, float width, float height, float border, u32 bgColor, u32 color, const char* label, u32 fontColor) { + #define TITLE_HEIGHT 40 + if ((bgColor & 0xFF) > 0) + C2D_DrawRectSolid(x,y,z,width,height,bgColor); + C2D_DrawRectSolid(x,y,z,width,TITLE_HEIGHT,color); + C2DExtra_DrawRectHollow(x,y,z,width,TITLE_HEIGHT,1,color); + C2DExtra_DrawRectHollow(x,y+TITLE_HEIGHT-border,z,width,height-TITLE_HEIGHT+border,border,color); + C2D_TextBuf buf = C2D_TextBufNew(4096); + C2D_Text text; + C2D_TextParse(&text,buf,label); + float textheight=0; + C2D_TextGetDimensions(&text,0.67,0.67,NULL,&textheight); + C2D_TextOptimize(&text); + C2D_DrawText(&text, C2D_WithColor ,x+width/10,y+(TITLE_HEIGHT/2)-textheight/2,z,0.67,0.67,fontColor); + C2D_TextBufDelete(buf); + // C2DExtra_DrawRectHollow(x+border-1,y+border-1,2+width-border*2,2+50-border*2,3,DARKEN(color)); + // C2DExtra_DrawRectHollow(x,y,width,height,3,DARKEN(color) ); +} +void drawPanel(float x, float y, float z, float width, float height, float border, u32 bgColor, u32 color) { + //C2D_DrawRectSolid(x,y,0,width,50,color); + C2D_DrawRectSolid(x,y,z,width,height,bgColor); + C2DExtra_DrawRectHollow(x,y,z,width,height,border,color); + + // C2DExtra_DrawRectHollow(x+border-1,y+border-1,2+width-border*2,2+50-border*2,3,DARKEN(color)); + // C2DExtra_DrawRectHollow(x,y,width,height,3,DARKEN(color) ); +} + +void C2DExtra_DrawRectHollow(float x, float y, float z, float width, float height, float thickness, u32 color) { + //Left wall + C2D_DrawRectSolid(x,y,z,thickness,height,color); + //Right wall + C2D_DrawRectSolid(x+width-thickness,y,z,thickness,height,color); + //top wall + C2D_DrawRectSolid(x+thickness,y,z,width-thickness*2,thickness,color); + //bottom wall + C2D_DrawRectSolid(x+thickness,y+height-thickness,z,width-thickness*2,thickness,color); +} +void drawFilePanel(const char* text, float x, float y, float z, float width, float height, u32 bgcolor, u32 textcolor, C2D_Image* image) { + C2D_DrawRectSolid(x,y,0,width,height,bgcolor); + if (image != NULL) + C2D_DrawImageAt(*image,width/10*2,(y+height/2) - (image->tex->height/2),z,NULL,0.5f,0.5f); +} +void drawText(float x, float y, float z, float scale, u32 bgColor, u32 fontColor, const char* text, u32 flags) { + C2D_TextBuf buf = C2D_TextBufNew(4096); + C2D_Text ctext; + C2D_TextParse(&ctext,buf,text); + float textheight=0; + float textwidth=0; + C2D_TextGetDimensions(&ctext,scale,scale,&textwidth,&textheight); + C2D_TextOptimize(&ctext); + float left = x; + float top = y-(textheight/2); + if ((flags & C2D_AlignCenter) > 0) { + left = x-(textwidth/2); + }else if((flags & C2D_AlignRight) > 0) { + left = x-textwidth; + } + if ((flags & C2D_AtBaseline) > 0) { + top = y-textheight; + } + + if ((bgColor & 0xFF) > 0) { + C2D_DrawRectSolid(left-3,top-3,0,textwidth+6,textheight+6,bgColor); + } + C2D_DrawText(&ctext, C2D_WithColor | flags,x,((flags & C2D_AtBaseline) > 0)?y:y-(textheight/2),z,scale,scale,fontColor); + C2D_TextBufDelete(buf); +} +void drawCheckbox(float x, float y, float z, float width, float height, float scale, u32 bgColor, u32 borderColor, u32 color, const char* label, bool checked) { + C2DExtra_DrawRectHollow(x,y,z,width,height,2,borderColor); + if (checked) + C2D_DrawRectSolid(x+3,y+3,z,width-6,height-6,borderColor); + if (strlen(label) > 0) + drawText(x+width+3,y+(height/2),z,scale,bgColor,color,label,0); +} \ No newline at end of file diff --git a/source/helpers.cpp b/source/helpers.cpp new file mode 100644 index 0000000..140f73e --- /dev/null +++ b/source/helpers.cpp @@ -0,0 +1,73 @@ +#include <3ds.h> +#include +#include +#include "aes.h" +#include +#include "helpers.hpp" +std::string sha256(u8* data, u32 size) +{ + u8 buf[0x20]={0}; + Result res = FSUSER_UpdateSha256Context(data,size,buf); + if (R_SUCCEEDED(res)) { + return std::string((char*)buf,0x20); + } + return ""; +} +// taken from pseudocode in GBATEK +u16 crc16Modbus(const void* data, u32 size) { + u8* values = (u8*)data; + unsigned short crc = 0xFFFF; + for (unsigned int i=0; i < size; i++) { + crc=crc ^ values[i]; + for (unsigned char j=0;j<8;j++) { + bool carry = (crc & 0x0001) > 0; + crc=crc >> 1; + if (carry) + crc=crc^ 0xA001; + } + } + return crc; +} +void memcpyrev(void* dest, void* source, u32 size) { + for (u32 i=0; i 0 && count < padTo) { + char pad[count]={0}; + return std::string(pad,count); + } + return std::string(); +} diff --git a/source/image.cpp b/source/image.cpp new file mode 100644 index 0000000..be786d2 --- /dev/null +++ b/source/image.cpp @@ -0,0 +1,26 @@ +#include <3ds.h> +#include +#include +#include "image.hpp" +#include + FrameBuffer::FrameBuffer(std::string screen, bool doubleBuffer, GSPGPU_FramebufferFormat format) { + gfxSetDoubleBuffering(((screen=="top")?GFX_TOP:GFX_BOTTOM), doubleBuffer); + gfxSetScreenFormat (((screen=="top")?GFX_TOP:GFX_BOTTOM), format); + this->bps = gspGetBytesPerPixel(format); + framebuffer = gfxGetFramebuffer(((screen=="top")?GFX_TOP:GFX_BOTTOM), GFX_LEFT,&this->height,&this->width); + } + u8* FrameBuffer::getFrameBufferLocation(u16 x, u16 y) { + return this->framebuffer+(((x*this->height)+y)*this->bps); + } + void FrameBuffer::displayImageAt(u16* image, u16 x, u16 y, u16 iwidth, u16 iheight) { + std::cout << x << ", " << y << ", " << iwidth << ", " << iheight << "\n"; + std::cout << this->width << ", " << this->height << ", " << this->bps << "\n"; + int fromtop = (y); + for (int c=0;cbps); + } + // for (int x=0;x<32;x++) { + // memcpy(getFrameBufferLocation(fb,10+x,100,width,height,bytesPerPixel),&bmp[x*(32)],32*bytesPerPixel);//32*3); + // } + + } diff --git a/source/load.c b/source/load.c new file mode 100644 index 0000000..00e9ca1 --- /dev/null +++ b/source/load.c @@ -0,0 +1,58 @@ +// #include +// #include +// #include +// #include <3ds.h> + +// void printfile(const char* path) +// { +// FILE* f = fopen(path, "r"); +// if (f) +// { +// char mystring[100]; +// while (fgets(mystring, sizeof(mystring), f)) +// { +// int a = strlen(mystring); +// if (mystring[a-1] == '\n') +// { +// mystring[a-1] = 0; +// if (mystring[a-2] == '\r') +// mystring[a-2] = 0; +// } +// puts(mystring); +// } +// printf(">>EOF<<\n"); +// fclose(f); +// } +// } + +// int main() +// { +// gfxInitDefault(); +// consoleInit(GFX_TOP, NULL); + +// Result rc = romfsInit(); +// if (rc) +// printf("romfsInit: %08lX\n", rc); +// else +// { +// printf("romfs Init Successful!\n"); +// printfile("romfs:/folder/file.txt"); +// // Test reading a file with non-ASCII characters in the name +// printfile("romfs:/フォルダ/ファイル.txt"); +// } + +// // Main loop +// while (aptMainLoop()) +// { +// gspWaitForVBlank(); +// hidScanInput(); + +// u32 kDown = hidKeysDown(); +// if (kDown & KEY_START) +// break; // break in order to return to hbmenu +// } + +// romfsExit(); +// gfxExit(); +// return 0; +// } diff --git a/source/logger.cpp b/source/logger.cpp new file mode 100644 index 0000000..f6c3a97 --- /dev/null +++ b/source/logger.cpp @@ -0,0 +1,4 @@ +#include +#include +#include "logger.hpp" + diff --git a/source/main.c.bak b/source/main.c.bak new file mode 100644 index 0000000..6a900f2 --- /dev/null +++ b/source/main.c.bak @@ -0,0 +1,79 @@ +#include <3ds.h> +#include +#include +#include "nds.h" +#include "graphics.h" + +void denit() { + romfsExit(); + gfxExit(); +} +void failWait(char* message) { + printf("%s\n",message); + printf("\x1b[21;16HPress Start to exit."); + while (aptMainLoop()) { + hidScanInput(); + if (hidKeysDown() & KEY_START) break; // break in order to return to hbmenu + } + denit(); +} +Result init() { + gfxInitDefault(); + consoleInit(GFX_BOTTOM, NULL); + Result rc = romfsInit(); + if (R_FAILED(rc)) { + failWait("Failed to read init romfs\n"); + return rc; + } + return 0; +} +int main(int argc, char **argv) +{ + + if (R_FAILED(init())) + return -1; + + //We don't need double buffering in this example. In this way we can draw our image only once on screen. + gfxSetDoubleBuffering(GFX_TOP, false); + gfxSetScreenFormat (GFX_TOP, GSP_RGB5_A1_OES ); + u16 bytesPerPixel = gspGetBytesPerPixel(GSP_RGB5_A1_OES); + //Get the bottom screen's frame buffer + u16 width=0; + u16 height=0; + u8* fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT,&height,&width); +// printf("%dx%d\n",width,height); + + u16 bmp[0x400]={}; + LoadIconFromNDS("romfs:/Mario Kart DS.nds",bmp); +// memdump(stdout,"banner: ",&banner,sizeof(banner)); +// memdump(stdout,"bmp: ",&bmp,8*3); + //Copy our image in the bottom screen's frame buffer + //memcpy(fb, brew_bgr, brew_bgr_size); + + // for (int x=0;x<32;x++) { + // memcpy(getFrameBufferLocation(fb,10+x,100,width,height,bytesPerPixel),&bmp[x*(32)],32*bytesPerPixel);//32*3); + // } + // Main loop + printf("\x1b[21;16HPress Start to exit."); + while (aptMainLoop()) + { + //Scan all the inputs. This should be done once for each frame + hidScanInput(); + + //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) + u32 kDown = hidKeysDown(); + + if (kDown & KEY_START) break; // break in order to return to hbmenu + + // Flush and swap framebuffers + gfxFlushBuffers(); + gfxSwapBuffers(); + + //Wait for VBlank + gspWaitForVBlank(); + } + denit(); + return 0; + // Exit services + +} diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..25492bf --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,209 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +#include +#include +#include +extern "C" { +#include "nds.h" +#include "graphics.h" +} +#include "image.hpp" +#include "menu.hpp" +#include "builder.hpp" +#include "settings.hpp" +#include "config.hpp" +namespace fs = std::filesystem; + + +void denit() { + romfsExit(); + fsExit(); + psExit(); + C2D_Fini(); + C3D_Fini(); + gfxExit(); + +} +void failWait(std::string message) { + std::cout << message << '\n'; + std::cout << "\x1b[21;16HPress Start to exit.\n"; + while (aptMainLoop()) { + hidScanInput(); + if (hidKeysDown() & KEY_START) break; // break in order to return to hbmenu + } + denit(); +} +Result init() { + gfxInitDefault(); + C3D_Init(C3D_DEFAULT_CMDBUF_SIZE); + C2D_Init(C2D_DEFAULT_MAX_OBJECTS); + C2D_Prepare(); + //consoleInit(GFX_BOTTOM, NULL); + if (R_FAILED(fsInit())) { + failWait("Failed to read init romfs\n"); + } + if (R_FAILED(psInit())) { + failWait("Failed to read init romfs\n"); + } + if (R_FAILED(romfsInit())) { + failWait("Failed to read init romfs\n"); + } + return 0; +} + +int main() +{ + if (R_FAILED(init())) + return -1; + + if (!fileExists("sdmc:/3ds/forwarder")) { + std::filesystem::create_directories(std::filesystem::path("sdmc:/3ds/forwarder")); + } + C3D_RenderTarget* top = C2D_CreateScreenTarget(GFX_TOP, GFX_LEFT); + C3D_RenderTarget* bottom = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT); + Menu* menu = generateMenu(std::filesystem::path("/"),nullptr); + Config* config = new Config(); + int perPage = 2; + + + //FrameBuffer* screen = new FrameBuffer("top", false, GSP_RGB5_A1_OES); +// printf("%dx%d\n",width,height); + std::string filename = "romfs:/Mario Kart DS.nds"; + + u16 bmp[0x400]={}; + // LoadIconFromNDS(filename.c_str(),bmp); + // screen->displayImageAt(bmp,0,0,32,32); +// memdump(stdout,"banner: ",&banner,sizeof(banner)); +// memdump(stdout,"bmp: ",&bmp,8*3); + //Copy our image in the bottom screen's frame buffer + //memcpy(fb, brew_bgr, brew_bgr_size); + // for (int x=0;x<32;x++) { + // memcpy(getFrameBufferLocation(fb,10+x,100,width,height,bytesPerPixel),&bmp[x*(32)],32*bytesPerPixel);//32*3); + // } + // Main loop + //printf("\x1b[21;16HPress Start to exit."); + //drawList(topFile,selected,menu); + Builder b; + b.initialize(); + //b.buildCIA("romfs:/roms/mkds.nds"); + while (aptMainLoop()) + { + + //Scan all the inputs. This should be done once for each frame + hidScanInput(); + + //hidKeysDown returns information about which buttons have been just pressed (and they weren't in the previous frame) + u32 kDown = hidKeysDown(); + touchPosition touch; + hidTouchRead(&touch); + if (kDown & KEY_TOUCH) + config->interact(&touch); + if (kDown & KEY_START) { + break; // break in order to return to hbmenu + + }else if (kDown & KEY_DOWN) { + menu->down(); + // selected++; + // if (selected > perPage-1) { + // selected=perPage-1; + // if (menu.getSelections().size() > perPage+topFile) + // topFile++; + // }else if(selected+topFile >= menu.getSelections().size()) { + // selected = menu.getSelections().size()-1; +// } + + // // size_t pos = fileList.at(selected+topFile).find_last_of(".nds"); + // // if (pos != std::string::npos && pos == fileList.at(selected+topFile).length()-4) { + // // u16 tmp[0x400] = {0xFFFF}; + // // screen->displayImageAt(tmp,0,0,32,32); + // // free(tmp); + // // }else{ + // // screen->displayImageAt(bmp,0,0,32,32); + // // } + // drawList(topFile,selected,menu); + // // std::cout << menu.currentDirectory.generic_string() << "\n"; + // // std::cout << menu.currentDirectory.parent_path().generic_string() << "\n"; + }else if (kDown & KEY_UP) { + // selected--; + // if (selected < 0) { + // selected=0; + // if (topFile > 0) + // topFile--; + // } + // // size_t pos = fileList.at(selected+topFile).find_last_of(".nds"); + // // if (pos != std::string::npos && pos == fileList.at(selected+topFile).length()-4) { + // // u16 tmp[0x400] = {0xFFFF}; + // // screen->displayImageAt(tmp,0,0,32,32); + // // free(tmp); + // // }else{ + // // screen->displayImageAt(bmp,0,0,32,32); + // // } + // drawList(topFile,selected,menu); + // // std::cout << menu.currentDirectory.generic_string() << "\n"; + // // std::cout << menu.currentDirectory.parent_path().generic_string() << "\n"; + menu->up(); + }else if (kDown & KEY_RIGHT) menu->pageDown(); + // topFile += perPage; + // if (topFile+perPage > menu.getSelections().size()) + // topFile = menu.getSelections().size()-perPage; + // drawList(topFile,selected,menu); + else if (kDown & KEY_LEFT) menu->pageUp(); + // topFile -= perPage; + // if (topFile < 0) + // topFile = 0; + // drawList(topFile,selected,menu); + else if(kDown & KEY_A) menu->action(); + + // MenuSelection m = menu.getSelections().at(topFile+selected); + // if (m.isFolder) { + // menu.currentDirectory = m.path; + // menu.setSelections(onChangeDir(m.path)); + // selected = 0; + // topFile = 0; + // drawList(topFile,selected,menu); + // std::cout << menu.currentDirectory.generic_string() << "\n"; + // std::cout << menu.currentDirectory.parent_path().generic_string() << "\n"; + // }else { + // // u16 tmp[0x400] = {}; + // fs::path f = menu.getSelections().at(topFile+selected).path; + // b.buildCIA(f.generic_string()); + // // LoadIconFromNDS(f.c_str(),tmp); + // // screen->displayImageAt(tmp,0,0,32,32); + // } + else if(kDown & KEY_B) menu = menu->back(); + // if (menu.currentDirectory.has_parent_path()) { + // menu.currentDirectory = menu.currentDirectory.parent_path(); + // menu.setSelections(onChangeDir(menu.currentDirectory)); + // } + // selected = 0; + // topFile = 0; + // drawList(topFile,selected,menu); + // std::cout << menu.currentDirectory << "\n"; + // } + C3D_FrameBegin(C3D_FRAME_SYNCDRAW); + C2D_TargetClear(top, BGColor); + C2D_TargetClear(bottom, C2D_Color32f(0,0,0,1)); + C2D_SceneBegin(top); + //drawList(topFile,selected,menu); + menu->drawMenu(); + + C2D_SceneBegin(bottom); + config->draw(); + C3D_FrameEnd(0); + menu = menu->handleQueue(&b,bottom,config); + // Flush and swap framebuffers + //gfxFlushBuffers(); + //gfxSwapBuffers(); + + //Wait for VBlank + //gspWaitForVBlank(); + } + denit(); + return 0; + // Exit services + +} diff --git a/source/menu.cpp b/source/menu.cpp new file mode 100644 index 0000000..da55fbf --- /dev/null +++ b/source/menu.cpp @@ -0,0 +1,272 @@ +#include <3ds.h> +#include +#include +#include +#include +#include "menu.hpp" +extern "C" { +#include "graphics.h" +#include "nds.h" +} +#include "builder.hpp" +#include "dialog.hpp" +#include "settings.hpp" +#include "config.hpp" +//#include "menu.h" +//class MenuSelection { + + MenuSelection::MenuSelection(std::string s,std::filesystem::path p) { + this->display=s; + this->path=p; + } + MenuSelection::MenuSelection(MenuSelection* old) { + this->display=old->display; + this->path=old->path; + this->action=old->action; + } + MenuSelection* MenuSelection::setPath(std::filesystem::path p) { + this->path=p; + return this; + } + MenuSelection* MenuSelection::setDisplay(std::string s) { + this->display=s; + return this; + } + + +//class Menu { + Menu::~Menu() { + for ( auto item : this->entries ) delete item; + this->entries.clear(); + } + Menu::Menu() { + + } + Menu::Menu(std::vector entries) { + this->entries=entries; + } + Menu* Menu::addEntry(MenuSelection* s) { + this->entries.push_back(s); + return this; + } + void Menu::drawMenu() { + //draw + std::string title = shorten(this->currentDirectory.generic_string(),30); + drawPanelWithTitle(0,0,0.50, 400,240,MENU_BORDER_HEIGHT,0, BORDER_COLOR,title.c_str(),BORDER_FOREGROUND); + u16 offset = 0; + u8 counter=0; + for (std::vector::iterator entry=this->top;entry!=this->entries.end() && counter < MAX_ENTRY_COUNT;entry++) { + C2D_DrawRectSolid(MENU_BORDER_HEIGHT,MENU_HEADING_HEIGHT+offset,0,400-MENU_BORDER_HEIGHT,ENTRY_HEIGHT,(entry==this->selection)?HIGHLIGHT_BGCOLOR:BGColor); + C2DExtra_DrawRectHollow(0,MENU_HEADING_HEIGHT+offset,0,400,ENTRY_HEIGHT,1,BORDER_COLOR); + C2D_TextBuf buf = C2D_TextBufNew(4096); + C2D_Text text; + const char* test = "test"; + C2D_TextParse(&text,buf,&(*entry)->display.c_str()[0]); + float textheight=0; + C2D_TextGetDimensions(&text,0.67,0.67,NULL,&textheight); + C2D_TextOptimize(&text); + C2D_DrawText(&text, C2D_WithColor,40,MENU_HEADING_HEIGHT+offset+(ENTRY_HEIGHT/2)-(textheight/2),0,0.67,0.67,(entry==this->selection)?HIGHLIGHT_FOREGROUND:FOREGROUND_COLOR); + C2D_TextBufDelete(buf); + offset+=ENTRY_HEIGHT; + counter++; + } + + } + + Menu* generateMenu(std::filesystem::path path, Menu* prev) { + delete prev; + std::vector entries; + + bool ndsFilesVisible=false; + for (const auto & entry : std::filesystem::directory_iterator(path)) { + std::string filename = entry.path().filename(); + if (filename[0]=='.' || !(strcasecmp(entry.path().extension().c_str(),".nds")==0 || entry.is_directory())) + continue; + MenuSelection* menuEntry = new MenuSelection(); + menuEntry->path=entry.path(); + menuEntry->display=filename; + if (entry.is_directory()) { + menuEntry->action=OpenFolder; + + }else{ + menuEntry->action=Install; + ndsFilesVisible=true; + + } + entries.push_back(menuEntry); + } + std::sort(entries.begin(), entries.end(), sortMenuSelections); + + if (path.has_parent_path() && path.parent_path().compare(path)) { + MenuSelection* prevFolder = new MenuSelection(); + prevFolder->display=".."; + prevFolder->action=OpenFolder; + prevFolder->path=path.parent_path(); + entries.insert(entries.begin(),prevFolder); + } + if (ndsFilesVisible) { + MenuSelection* installAll = new MenuSelection(); + installAll->action=Install_All; + installAll->display="< Install all nds >"; + installAll->path=path; + entries.insert(entries.begin(),installAll); + } + Menu* menu = new Menu(entries); + menu->currentDirectory=path.generic_string(); + menu->init(); + return menu; + } + void Menu::init() { + this->top=entries.begin(); + this->selection=entries.begin(); + } + void Menu::down() { + if (this->selection+1 == this->entries.end()) { + this->selection=this->entries.begin(); + this->top=this->entries.begin(); + }else{ + if (this->selection+1==this->top+MAX_ENTRY_COUNT) { + top++; + } + this->selection++; + } + } + void Menu::up() { + if (this->selection == this->entries.begin()) { + this->selection=this->entries.end()-1; + if (this->entries.size() > MAX_ENTRY_COUNT) + this->top=this->entries.end()-MAX_ENTRY_COUNT; + }else{ + if (this->selection==this->top) { + this->top--; + } + this->selection--; + } + } + void Menu::action() { + this->queue.push(MenuSelection(*this->selection)); + } + void Menu::pageDown() { + if (this->top+MAX_ENTRY_COUNT == this->entries.end()) { + + this->selection=this->entries.end()-1; + + } else { + + for (int i=0;i < MAX_ENTRY_COUNT && this->top+MAX_ENTRY_COUNT != this->entries.end(); i++) { + this->top++; + this->selection++; + } + } + } + void Menu::pageUp() { + if (this->top==this->entries.begin()) { + this->selection=this->entries.begin(); + }else{ + for (int i=0;itop!=this->entries.begin();i++) { + this->top--; + this->selection--; + } + } + } + Menu* Menu::back() { + return generateMenu(this->currentDirectory.parent_path(),this); + } + bool Menu::hasQueue() { + return this->queue.size() > 0; + } + Menu* Menu::handleQueue(Builder* builder, C3D_RenderTarget* target, Config* config) { + if (!this->hasQueue()) + return this; + if (target==nullptr) + target = C2D_CreateScreenTarget(GFX_BOTTOM, GFX_LEFT); + while (queue.size() > 0) { + MenuSelection entry = this->queue.front(); + switch (entry.action) { + case Install: + if (Dialog(target,0,0,320,240,"Do you wish to install\n"+entry.path.filename().generic_string(),{"Yes","No"}).handle()==0) { + Result buildResult = 0; + if (config!=nullptr) { + std::string customTitle=""; + if (config->customTitle) { + char customTitleBuffer[0x51] = {0}; + SwkbdState kbstate; + swkbdInit(&kbstate,SWKBD_TYPE_NORMAL,2,0x50); + swkbdSetHintText(&kbstate,"Enter a Custom Title Name"); + swkbdSetFeatures(&kbstate,SWKBD_MULTILINE | SWKBD_DEFAULT_QWERTY); + swkbdInputText(&kbstate,customTitleBuffer,0x51); + customTitle=std::string(customTitleBuffer); + // if (customTitle.empty()) { + // Dialog(target,0,0,320,240,{"Not using a custom title"},{"OK"}).handle(); + // }else{ + // Dialog(target,0,0,320,240,{"Setting custom name to:",customTitle},{"OK"}).handle(); + // } + } + buildResult = builder->buildCIA(entry.path.generic_string(),config->randomTID,customTitle); + } else { + buildResult = builder->buildCIA(entry.path.generic_string()); + } + + if (R_SUCCEEDED(buildResult)) { + Dialog(target,0,0,320,240,"Install Complete",{"OK"}).handle(); + }else{ + Dialog(target,0,0,320,240,"Install Failed",{"OK"}).handle(); + } + } + break; + case Install_All: + if (Dialog(target,0,0,320,240,{"Do you wish to install","forwarders for all nds in:",entry.path.filename().generic_string()},{"Yes","No"}).handle()==0) { + for (const auto & dEntry : std::filesystem::directory_iterator(entry.path)) { + std::string filename = dEntry.path().filename(); + if (filename[0]=='.' || !(strcasecmp(dEntry.path().extension().c_str(),".nds")==0)) + continue; + std::string shortname = dEntry.path().filename().generic_string(); + Dialog(target,0,0,320,240,{"Installing",shorten(dEntry.path().filename().generic_string(),25)},{},0).handle(); + //Result res = builder->buildCIA(dEntry.path().generic_string(),(config!=nullptr)?config->randomTID:false); + Result buildResult = 0; + if (config!=nullptr) { + std::string customTitle=""; + if (config->customTitle) { + char customTitleBuffer[0x51] = {0}; + SwkbdState kbstate; + swkbdInit(&kbstate,SWKBD_TYPE_NORMAL,2,0x50); + swkbdSetHintText(&kbstate,shorten(dEntry.path().filename().generic_string(),30).c_str()); + swkbdSetFeatures(&kbstate,SWKBD_MULTILINE | SWKBD_DEFAULT_QWERTY); + swkbdInputText(&kbstate,customTitleBuffer,0x51); + customTitle=std::string(customTitleBuffer); + // if (customTitle.empty()) { + // Dialog(target,0,0,320,240,{"Not using a custom title"},{"OK"}).handle(); + // }else{ + // Dialog(target,0,0,320,240,{"Setting custom name to:",customTitle},{"OK"}).handle(); + // } + } + buildResult = builder->buildCIA(dEntry.path().generic_string(),config->randomTID,customTitle); + } else { + buildResult = builder->buildCIA(dEntry.path().generic_string()); + } + if (R_FAILED(buildResult)) { + Dialog(target,0,0,320,240,"Install Failed\n"+filename+"\n"+std::to_string((u32)buildResult),{"OK"}).handle(); + } + } + Dialog(target,0,0,320,240,"Install Complete",{"OK"}).handle(); + } + break; + case OpenFolder: + while (this->queue.size() > 0) this->queue.pop(); + return generateMenu(entry.path,this); + } + this->queue.pop(); + } + return this; + } + bool sortMenuSelections(MenuSelection* a, MenuSelection* b) { + if ((a->action==OpenFolder && b->action==OpenFolder) || (!a->action==OpenFolder && !b->action==OpenFolder)) + return a->displaydisplay; + return a->action==OpenFolder; + } +std::string shorten(std::string s, u16 len) { + if (len > 8 && s.length() > len) { + return s.substr(0,5)+"..."+s.substr(s.length()-(len-8)); + } + return s; +} \ No newline at end of file diff --git a/source/nds.c b/source/nds.c new file mode 100644 index 0000000..012eb54 --- /dev/null +++ b/source/nds.c @@ -0,0 +1,88 @@ +#include <3ds.h> +#include +#include +#include +#include +#include +//#define BGR5A1_RGB5A1(src) (((src >> 10) & 0b11111) | ((src & 0b11111) << 10) | (src & 0b1111100000)) + +u16 RGB5A1_BGR5A1(u16 color) { + u16 r = ((color >> 10) & 0b11111); + u16 g = ((color >> 5) & 0b11111); + u16 b = ((color) & 0b11111); + return b<<11|g<<6|r<<1|1; +} +void _DStoBMPorder(u8* store, u8 *source) { + + // ds icons are 8x8 pixel boxes, with 4bit width. 4x8 byte boxes + // they are arranged in a 4 wide by 4 tall pattern of boxes to 32x32 pixels + u8 tmp[0x200]; + int offset = 0; + // start at the bottom set of boxes + for (int yy=3;yy>=0;yy--) { + // start at the bottom row of the bottom set of boxes + for(int y=7;y>=0;y--) { + for (int cc=0;cc<4;cc++) { + memcpy(tmp+offset,&source[y*4+cc*32+yy*128],4); + offset+=4; + } + } + } + for(int x=0;x<0x200;x++) { + store[x*2]=tmp[x] & 0xF; + store[1+x*2]=(tmp[x] >> 4) & 0xF; + } +} +void convertIconToBmp(u16* bmp, tNDSBanner* banner ) { + u8 store[0x400]={0}; + _DStoBMPorder(store,banner->icon); + rotateInPlace90(store,32,32); + u16 colors[16]={0}; + for (u8 x=0;x<16;x++) { + colors[x] = RGB5A1_BGR5A1(banner->palette[x]); + //u16 color=banner->palette[x]; + // colors[x][2]=(color & 0b11111) << 3; + // colors[x][1]=((color >> 5) & 0b11111) << 3; + // colors[x][0]=((color >> 10) & 0b11111) << 3; + } + for (u32 x=0;x<0x400;x++) { + memcpy(bmp+(x),&colors[store[x]],2); +// printf("%d|",store[x]); + } + return; +} +void rotateInPlace90(u8 *source,u32 width,u32 height) { + u8 n[width][height]; + for (u32 x=0;xbannerOffset > 0) { + fseek(f,header->bannerOffset,SEEK_SET); + fread(banner,sizeof(tNDSBanner),1,f); + fclose(f); + free(header); + header=NULL; + + convertIconToBmp(output,banner); + + free(banner); + banner=NULL; + return 0; + } + return -1; +} diff --git a/source/stubs.c b/source/stubs.c new file mode 100644 index 0000000..c8d68be --- /dev/null +++ b/source/stubs.c @@ -0,0 +1,5 @@ +long int +pathconf (const char *filename, int parameter) +{ + return -1; +} \ No newline at end of file