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 0000000..abd9ee6 Binary files /dev/null and b/romfs/sdcard.nds differ diff --git a/source/aes.c b/source/aes.c new file mode 100644 index 0000000..8e61c41 --- /dev/null +++ b/source/aes.c @@ -0,0 +1,571 @@ +/* + +This is an implementation of the AES algorithm, specifically ECB, CTR and CBC mode. +Block size can be chosen in aes.h - available choices are AES128, AES192, AES256. + +The implementation is verified against the test vectors in: + National Institute of Standards and Technology Special Publication 800-38A 2001 ED + +ECB-AES128 +---------- + + plain-text: + 6bc1bee22e409f96e93d7e117393172a + ae2d8a571e03ac9c9eb76fac45af8e51 + 30c81c46a35ce411e5fbc1191a0a52ef + f69f2445df4f9b17ad2b417be66c3710 + + key: + 2b7e151628aed2a6abf7158809cf4f3c + + resulting cipher + 3ad77bb40d7a3660a89ecaf32466ef97 + f5d3d58503b9699de785895a96fdbaaf + 43b1cd7f598ece23881b00e3ed030688 + 7b0c785e27e8ad3f8223207104725dd4 + + +NOTE: String length must be evenly divisible by 16byte (str_len % 16 == 0) + You should pad the end of the string with zeros if this is not the case. + For AES192/256 the key size is proportionally larger. + +*/ + + +/*****************************************************************************/ +/* Includes: */ +/*****************************************************************************/ +#include // 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