From 61cc6f21f64762de76ea7c77c4af9bd1b0809101 Mon Sep 17 00:00:00 2001 From: Steven Smith Date: Wed, 21 Jan 2015 21:56:25 -0800 Subject: [PATCH] Update for template changes. --- .gitignore | 10 +- Makefile | 74 ++--- resources/3ds.rsf | 13 +- resources/AppData.txt | 22 -- resources/cia.rsf | 2 +- resources/tools/banner/.gitignore | 2 - resources/tools/banner/banner/DSDecmp.exe | Bin 47616 -> 0 bytes resources/tools/banner/banner/banner.py | 98 ------- resources/tools/banner/create.py | 59 ---- resources/tools/banner/icon24/icon.py | 65 ----- resources/tools/banner/icon48/icon.py | 65 ----- source/common.c | 85 +++--- source/common.h | 2 +- source/main.cpp | 4 +- tools/banner/.gitignore | 1 + {resources/tools => tools}/banner/LICENSE.txt | 0 .../banner/audio => tools/banner}/audio.bcwav | Bin tools/banner/compress.py | 255 ++++++++++++++++++ tools/banner/create.py | 163 +++++++++++ .../tools/banner => tools}/banner/header.bin | Bin .../icon24 => tools/banner}/map24x24.bin | Bin .../banner => tools}/banner/map256x128.bin | Bin .../icon48 => tools/banner}/map48x48.bin | Bin {resources/tools => tools}/makerom | Bin 24 files changed, 513 insertions(+), 407 deletions(-) delete mode 100644 resources/AppData.txt delete mode 100644 resources/tools/banner/.gitignore delete mode 100644 resources/tools/banner/banner/DSDecmp.exe delete mode 100644 resources/tools/banner/banner/banner.py delete mode 100644 resources/tools/banner/create.py delete mode 100644 resources/tools/banner/icon24/icon.py delete mode 100644 resources/tools/banner/icon48/icon.py create mode 100644 tools/banner/.gitignore rename {resources/tools => tools}/banner/LICENSE.txt (100%) rename {resources/tools/banner/audio => tools/banner}/audio.bcwav (100%) create mode 100644 tools/banner/compress.py create mode 100644 tools/banner/create.py rename {resources/tools/banner => tools}/banner/header.bin (100%) rename {resources/tools/banner/icon24 => tools/banner}/map24x24.bin (100%) rename {resources/tools/banner => tools}/banner/map256x128.bin (100%) rename {resources/tools/banner/icon48 => tools/banner}/map48x48.bin (100%) rename {resources/tools => tools}/makerom (100%) diff --git a/.gitignore b/.gitignore index 1617389..e35f485 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,5 @@ .idea -build CMakeLists.txt -*.3dsx -*.elf -*.smdh -*.zip -*.cia -*.3ds \ No newline at end of file + +build +output \ No newline at end of file diff --git a/Makefile b/Makefile index a1ee950..b32946f 100644 --- a/Makefile +++ b/Makefile @@ -9,13 +9,11 @@ endif TOPDIR ?= $(CURDIR) include $(DEVKITARM)/3ds_rules -APP_ID = FBI APP_TITLE = FBI APP_DESCRIPTION = Open source CIA installer. APP_AUTHOR = Steveice10 #--------------------------------------------------------------------------------- -# 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 @@ -31,7 +29,6 @@ APP_AUTHOR = Steveice10 # - icon.png # - /default_icon.png #--------------------------------------------------------------------------------- -TARGET := $(APP_ID) BUILD := build SOURCES := source DATA := data @@ -65,12 +62,9 @@ LIBS := -lctru -lm #--------------------------------------------------------------------------------- LIBDIRS := $(CTRULIB) ./lib -MAKEROM = $(TOPDIR)/resources/tools/makerom -BANNER_TOOL = $(TOPDIR)/resources/tools/banner -PREPARE_BANNER = python2 banner.py -PREPARE_ICON24 = python2 icon.py -PREPARE_ICON48 = python2 icon.py -CREATE_BANNER = python2 create.py +MAKEROM = $(TOPDIR)/tools/makerom +BANNER_TOOL = $(TOPDIR)/tools/banner +CREATE_BANNER = python create.py #--------------------------------------------------------------------------------- @@ -80,7 +74,10 @@ CREATE_BANNER = python2 create.py ifneq ($(BUILD),$(notdir $(CURDIR))) #--------------------------------------------------------------------------------- -export OUTPUT := $(CURDIR)/$(TARGET) +null := +SPACE := $(null) $(null) +export OUTPUT_D := $(CURDIR)/output +export OUTPUT := $(OUTPUT_D)/$(subst $(SPACE),,$(APP_TITLE)) export TOPDIR := $(CURDIR) export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ @@ -116,18 +113,7 @@ export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ 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 +export APP_ICON := $(TOPDIR)/$(ICON) .PHONY: $(BUILD) clean all @@ -141,7 +127,7 @@ $(BUILD): #--------------------------------------------------------------------------------- clean: @echo clean ... - @rm -fr $(BUILD) $(TARGET).3dsx $(OUTPUT).smdh $(TARGET).elf $(TARGET).3ds $(TARGET).cia $(BANNER_TOOL)/banner.bnr $(BANNER_TOOL)/icon.icn + @rm -fr $(BUILD) $(OUTPUT_D) #--------------------------------------------------------------------------------- @@ -154,40 +140,30 @@ DEPENDS := $(OFILES:.o=.d) #--------------------------------------------------------------------------------- ifeq ($(strip $(NO_SMDH)),) .PHONY: all -all : $(OUTPUT).3dsx $(OUTPUT).smdh $(OUTPUT).cia $(OUTPUT).3ds +all : $(OUTPUT_D) $(OUTPUT).3dsx $(OUTPUT).smdh $(OUTPUT).cia $(OUTPUT).3ds endif +$(OUTPUT_D) : + @[ -d $@ ] || mkdir -p $@ + $(OUTPUT).3dsx : $(OUTPUT).elf $(OUTPUT).elf : $(OFILES) -$(BANNER_TOOL)/banner.bnr: $(TOPDIR)/resources/banner.png $(TOPDIR)/resources/icon24.png $(TOPDIR)/resources/icon48.png - cp $(TOPDIR)/resources/banner.png $(BANNER_TOOL)/banner/banner.png - cp $(TOPDIR)/resources/icon24.png $(BANNER_TOOL)/icon24/icon24.png - cp $(TOPDIR)/resources/icon48.png $(BANNER_TOOL)/icon48/icon48.png - cd $(BANNER_TOOL)/banner; $(PREPARE_BANNER) - cd $(BANNER_TOOL)/icon24; $(PREPARE_ICON24) - cd $(BANNER_TOOL)/icon48; $(PREPARE_ICON48) - cd $(BANNER_TOOL); $(CREATE_BANNER) - rm $(BANNER_TOOL)/banner/banner.png - rm $(BANNER_TOOL)/banner/banner.cgfx - rm $(BANNER_TOOL)/banner/compressed.cgfx - rm $(BANNER_TOOL)/banner/banner.cbmd - rm $(BANNER_TOOL)/icon24/icon24.png - rm $(BANNER_TOOL)/icon24/icon24.ctpk - rm $(BANNER_TOOL)/icon48/icon48.png - rm $(BANNER_TOOL)/icon48/icon48.ctpk +banner.bnr: $(TOPDIR)/resources/icon24.png $(TOPDIR)/resources/icon48.png $(TOPDIR)/resources/banner.png + cd $(BANNER_TOOL); $(CREATE_BANNER) "$(APP_TITLE)" "$(APP_TITLE)" "$(APP_AUTHOR)" $(TOPDIR)/resources/icon24.png $(TOPDIR)/resources/icon48.png $(TOPDIR)/resources/banner.png $(TOPDIR)/$(BUILD)/icon.icn $(TOPDIR)/$(BUILD)/banner.bnr + @echo "built ... banner files" -$(BANNER_TOOL)/icon.icn: $(BANNER_TOOL)/banner.bnr +icon.icn: banner.bnr -$(OUTPUT).cia: $(OUTPUT).elf $(TOPDIR)/resources/cia.rsf $(BANNER_TOOL)/banner.bnr $(BANNER_TOOL)/icon.icn - @cp $(OUTPUT).elf $(TARGET)_stripped.elf - @$(PREFIX)strip $(TARGET)_stripped.elf - $(MAKEROM) -f cia -o $(OUTPUT).cia -rsf $(TOPDIR)/resources/cia.rsf -target t -exefslogo -elf $(TARGET)_stripped.elf -icon $(BANNER_TOOL)/icon.icn -banner $(BANNER_TOOL)/banner.bnr +stripped.elf: $(OUTPUT).elf + @cp $(OUTPUT).elf stripped.elf + @$(PREFIX)strip stripped.elf + +$(OUTPUT).cia: stripped.elf $(TOPDIR)/resources/cia.rsf banner.bnr icon.icn + $(MAKEROM) -f cia -o $(OUTPUT).cia -rsf $(TOPDIR)/resources/cia.rsf -target t -exefslogo -elf stripped.elf -icon icon.icn -banner banner.bnr @echo "built ... $(notdir $@)" -$(OUTPUT).3ds: $(OUTPUT).elf $(TOPDIR)/resources/3ds.rsf $(BANNER_TOOL)/banner.bnr $(BANNER_TOOL)/icon.icn - @cp $(OUTPUT).elf $(TARGET)_stripped.elf - @$(PREFIX)strip $(TARGET)_stripped.elf - $(MAKEROM) -f cci -o $(OUTPUT).3ds -rsf $(TOPDIR)/resources/3ds.rsf -target d -exefslogo -elf $(TARGET)_stripped.elf -icon $(BANNER_TOOL)/icon.icn -banner $(BANNER_TOOL)/banner.bnr +$(OUTPUT).3ds: stripped.elf $(TOPDIR)/resources/3ds.rsf banner.bnr icon.icn + $(MAKEROM) -f cci -o $(OUTPUT).3ds -rsf $(TOPDIR)/resources/3ds.rsf -target d -exefslogo -elf stripped.elf -icon icon.icn -banner banner.bnr @echo "built ... $(notdir $@)" #--------------------------------------------------------------------------------- diff --git a/resources/3ds.rsf b/resources/3ds.rsf index 996f115..19cf3bd 100644 --- a/resources/3ds.rsf +++ b/resources/3ds.rsf @@ -15,7 +15,7 @@ TitleInfo: Category : Application CardInfo: - MediaSize : 128MB # 128MB / 256MB / 512MB / 1GB / 2GB / 4GB + MediaSize : 128MB # 128MB / 256MB / 512MB / 1GB / 2GB / 4GB / 8GB / 16GB / 32GB MediaType : Card1 # Card1 / Card2 CardDevice : None # NorFlash(Pick this if you use savedata) / None @@ -26,6 +26,7 @@ Option: EnableCrypt : true # Enables encryption for NCCH and CIA EnableCompress : true # Compresses exefs code + ExeFs: # these are the program segments from the ELF, check your elf for the appropriate segment names ReadOnly: - .rodata @@ -42,10 +43,10 @@ PlainRegion: # only used with SDK ELFs - .module_id AccessControlInfo: - #UseExtSaveData : true - #ExtSaveDataId: 0xff3ff - #UseExtendedSaveDataAccessControl: true - #AccessibleSaveDataIds: [0x101, 0x202, 0x303, 0x404, 0x505, 0x606] + # UseExtSaveData : true + # ExtSaveDataId: 0xff3ff + # UseExtendedSaveDataAccessControl: true + # AccessibleSaveDataIds: [0x101, 0x202, 0x303, 0x404, 0x505, 0x606] SystemControlInfo: SaveDataSize: 128KB @@ -138,7 +139,6 @@ AccessControlInfo: - $hostio0 - $hostio1 - ac:u - - am:u - boss:U - cam:u - cecd:u @@ -162,6 +162,7 @@ AccessControlInfo: - y2r:u - ldr:ro - ir:USER + - am:u SystemControlInfo: diff --git a/resources/AppData.txt b/resources/AppData.txt deleted file mode 100644 index c096621..0000000 --- a/resources/AppData.txt +++ /dev/null @@ -1,22 +0,0 @@ -#-display text: enter your info between the quotes -#-don't use unicode (i.e. weird characters) or program will barf -#-not certain about string length limits so be conservative - -longtitle="FBI CIA Installer" -shortitle="FBI" -publisher="Steveice10" - -#-setting flags: don't change unless you know what you're doing -#-1 is on, 0 is off -#-for more info, visit http://3dbrew.org/wiki/SMDH#Flags - -visibility =1 -autoBoot =0 -use3D =1 -requireEULA =0 -autoSaveOnExit=0 -extendedBanner=0 -gameRatings =0 -useSaveData =1 -recordAppUsage=0 -disableSaveBU =0 \ No newline at end of file diff --git a/resources/cia.rsf b/resources/cia.rsf index 3f95fc6..dc8be5b 100644 --- a/resources/cia.rsf +++ b/resources/cia.rsf @@ -173,7 +173,6 @@ AccessControlInfo: - $hostio0 - $hostio1 - ac:u - - am:u - boss:U - cam:u - cecd:u @@ -199,6 +198,7 @@ AccessControlInfo: - ir:USER - ir:u - csnd:SND + - am:u SystemControlInfo: diff --git a/resources/tools/banner/.gitignore b/resources/tools/banner/.gitignore deleted file mode 100644 index 84362f8..0000000 --- a/resources/tools/banner/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.bnr -*.icn diff --git a/resources/tools/banner/banner/DSDecmp.exe b/resources/tools/banner/banner/DSDecmp.exe deleted file mode 100644 index 84529d271eddbf7694023286f4beeafd51d6a035..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 47616 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 diff --git a/resources/tools/banner/banner/banner.py b/resources/tools/banner/banner/banner.py deleted file mode 100644 index 38abc8c..0000000 --- a/resources/tools/banner/banner/banner.py +++ /dev/null @@ -1,98 +0,0 @@ -from PIL import Image, ImageDraw -import sys,os,platform - -f=open('banner.png','rb') -image=Image.open(f) -if image.size[0] != 256 or image.size[1] != 128: - f.close() - print "ERROR: Image must be exactly 256 x 128. Abort." - sys.exit(4) - -icon=open('banner.cgfx','wb') -hdr=open("header.bin","rb") - -posit=open('map256x128.bin','rb') -''' -chx=[0,1,4,5,16,17,20,21] -chy=[0,2,8,10,32,34,40,42] -w=image.size[0] -h=image.size[1] -''' -n=256*128 -i=[0]*(n*2) -cbmdhdr="\x43\x42\x4D\x44\x00\x00\x00\x00\x88"+("\x00"*0x7B) - -dump=list(image.getdata()) -pos=0 - -for x in range(n): - #xx=x%w - #yy=x/w - #print xx,yy - #pos=(chx[x%8]+chy[(x>>3)%8])+((x>>6)<<6) fail (i'm not a math major :p) - #print pos - - p1=ord(posit.read(1)) - p2=ord(posit.read(1)) - pos=p1+(p2<<8) - #print p1,p2,pos - r=dump[x][0]>>4 - g=dump[x][1]>>4 - b=dump[x][2]>>4 - a=dump[x][3]>>4 - - i[pos<<1]= (b<<4) | a - i[(pos<<1)+1]= (r<<4) | g - - -buf=hdr.read() -hdr.close() - -for byte in buf: - icon.write((byte)) -for byte in i: - icon.write(chr(byte)) - -icon.close() - -if platform.system() == "Windows": - os.system("DSDecmp.exe -c lz11 banner.cgfx compressed.cgfx") -else: - os.system("wine DSDecmp.exe -c lz11 banner.cgfx compressed.cgfx") - -ccgfx=open("compressed.cgfx",'rb') -l=ccgfx.read() -len=len(l)+136 - -pad=16-(len%16) -l+=("\x00"*pad) -len+=pad - -cbmdlen=['']*4 -for c in range(4): - cbmdlen[c]=chr(len&255) - len=len>>8 - -for i in cbmdlen: - cbmdhdr+=i - -cbmd=open("banner.cbmd","wb") -cbmdfinal=l -cbmdhdr+=cbmdfinal -cbmd.write(cbmdhdr) -cbmd.close() - -print "Done." -f.close() -posit.close() -cbmd.close() -ccgfx.close() -sys.exit(0) - - - - - - - - diff --git a/resources/tools/banner/create.py b/resources/tools/banner/create.py deleted file mode 100644 index 3451400..0000000 --- a/resources/tools/banner/create.py +++ /dev/null @@ -1,59 +0,0 @@ -import os,sys -execfile("../../AppData.txt") - -icn=open("icon.icn","wb") -bnr=open("banner.bnr","wb") - -cptk1=open("icon24/icon24.ctpk","rb") -cptk2=open("icon48/icon48.ctpk","rb") -bcwav1=open("audio/audio.bcwav","rb") -cbmd1=open("banner/banner.cbmd","rb") - -ctp1=cptk1.read() -ctp2=cptk2.read() -bcwav=bcwav1.read() -cbmd=cbmd1.read() - -header=list("\x53\x4D\x44\x48\x00\x00\x00\x00") -header+=("\x00"*0x1FF8) -header+="\x00\x00\x00\x00\x00\x00\x00\x00\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00\x00\xFF\xFF\xFF\x7F\x31\x48\x62\x64\x99\x99\x99\x19\x91\x18\x62\x64\xA5\x01\x00\x00\x00\x01\x00\x00\x00\x00\x80\x3F\x32\x41\x79\x24\x00\x00\x00\x00\x00\x00\x00\x00" - -header[0x2028]=chr(visibility | autoBoot<<1 | use3D<<2 | requireEULA<<3 | autoSaveOnExit<<4 | extendedBanner<<5 | gameRatings<<6 | useSaveData<<7) -header[0x2029]=chr(recordAppUsage | disableSaveBU<<2) - -offset=8 -pos=0 - -for x in range(11): - for c in longtitle: - header[offset+pos*2]=longtitle[pos] - pos+=1 - pos=0 - offset+=0x80 - for c in shortitle: - header[offset+pos*2]=shortitle[pos] - pos+=1 - pos=0 - offset+=0x100 - - for c in publisher: - header[offset+pos*2]=publisher[pos] - pos+=1 - pos=0 - offset+=0x80 - -header=''.join(header) -header+=(ctp1+ctp2) - -icn.write(header) -bnr.write(cbmd+bcwav) - -print "banner.bnr built." -print "icon.icn built." -print "Done." -bnr.close() -icn.close() -cptk1.close() -cptk2.close() -bcwav1.close() -cbmd1.close() \ No newline at end of file diff --git a/resources/tools/banner/icon24/icon.py b/resources/tools/banner/icon24/icon.py deleted file mode 100644 index 569412f..0000000 --- a/resources/tools/banner/icon24/icon.py +++ /dev/null @@ -1,65 +0,0 @@ -from PIL import Image, ImageDraw -import sys,os - -icon=open('icon24.ctpk','wb') - -f=open('icon24.png','rb') -image=Image.open(f) -posit=open('map24x24.bin','rb') - -chx=[0,1,4,5,16,17,20,21] -chy=[0,2,8,10,32,34,40,42] -w=image.size[0] -h=image.size[1] -n=w*w -i=[0]*(n*2) - -if w != h: - print "width, height unequal" - f.close() - icon.close() - sys.exit(1) -if w != 24: - print "sides need to be 24 pixels" - f.close() - icon.close() - sys.exit(1) - -dump=list(image.getdata()) - - -pos=0 - -for x in xrange(n): - #xx=x%w - #yy=x/w - #print xx,yy - #pos=(chx[x%8]+chy[(x>>3)%8])+((x>>6)<<6) - #print pos - - p1=ord(posit.read(1)) - p2=ord(posit.read(1)) - pos=p1+(p2<<8) - #print p1,p2,pos - r=dump[x][0]>>3 - g=dump[x][1]>>2 - b=dump[x][2]>>3 - - i[pos<<1]=(g&7)<<5| b - i[(pos<<1)+1]=(r)<<3 | g>>3 - -for byte in i: - icon.write(chr(byte)) -print "Done." -icon.close() -f.close() -posit.close() - -exit() - - - - - - - diff --git a/resources/tools/banner/icon48/icon.py b/resources/tools/banner/icon48/icon.py deleted file mode 100644 index 0a6fdc9..0000000 --- a/resources/tools/banner/icon48/icon.py +++ /dev/null @@ -1,65 +0,0 @@ -from PIL import Image, ImageDraw -import sys,os - -icon=open('icon48.ctpk','wb') - -f=open('icon48.png','rb') -image=Image.open(f) -posit=open('map48x48.bin','rb') - -chx=[0,1,4,5,16,17,20,21] -chy=[0,2,8,10,32,34,40,42] -w=image.size[0] -h=image.size[1] -n=w*w -i=[0]*(n*2) - -if w != h: - print "width, height unequal" - f.close() - icon.close() - sys.exit(1) -if w != 48: - print "sides need to be 48 pixels" - f.close() - icon.close() - sys.exit(1) - -dump=list(image.getdata()) - - -pos=0 - -for x in xrange(n): - #xx=x%w - #yy=x/w - #print xx,yy - #pos=(chx[x%8]+chy[(x>>3)%8])+((x>>6)<<6) - #print pos - - p1=ord(posit.read(1)) - p2=ord(posit.read(1)) - pos=p1+(p2<<8) - #print p1,p2,pos - r=dump[x][0]>>3 - g=dump[x][1]>>2 - b=dump[x][2]>>3 - - i[pos<<1]=(g&7)<<5| b - i[(pos<<1)+1]=(r)<<3 | g>>3 - -for byte in i: - icon.write(chr(byte)) -print "Done." -icon.close() -f.close() -posit.close() - -exit() - - - - - - - diff --git a/source/common.c b/source/common.c index a222eb5..f4d1b98 100644 --- a/source/common.c +++ b/source/common.c @@ -547,13 +547,26 @@ App* app_list(MediaType mediaType, u32* count) { } u32 titleCount; - AM_GetTitleCount(app_mediatype_to_byte(mediaType), &titleCount); + if(AM_GetTitleCount(app_mediatype_to_byte(mediaType), &titleCount) != 0) { + if(count != NULL) { + *count = 0; + } + + return (App*) malloc(0); + } + if(count != NULL) { *count = titleCount; } u64 titleIds[titleCount]; - AM_GetTitleList(app_mediatype_to_byte(mediaType), titleCount, titleIds); + if(AM_GetTitleList(app_mediatype_to_byte(mediaType), titleCount, titleIds) != 0) { + if(count != NULL) { + *count = 0; + } + + return (App*) malloc(0); + } App* titles = (App*) malloc(titleCount * sizeof(App)); for(int i = 0; i < titleCount; i++) { @@ -581,23 +594,24 @@ bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int p return false; } + FILE* fd = fopen(path, "r"); + if(!fd) { + return false; + } + + fseek(fd, 0, SEEK_END); + u64 size = (u64) ftell(fd); + fseek(fd, 0, SEEK_SET); + if(onProgress != NULL) { onProgress(0); } - FS_archive sdmcArchive = (FS_archive) {ARCH_SDMC, (FS_path) {PATH_EMPTY, 1, (u8*) ""}}; - FSUSER_OpenArchive(NULL, &sdmcArchive); - Handle fileHandle; - u64 size; - - if(FSUSER_OpenFile(NULL, &fileHandle, sdmcArchive, FS_makePath(PATH_CHAR, path + 5), FS_OPEN_READ, FS_ATTRIBUTE_NONE) != 0) { + Handle ciaHandle; + if(AM_StartCiaInstall(app_mediatype_to_byte(mediaType), &ciaHandle) != 0) { return false; } - FSFILE_GetSize(fileHandle, &size); - - Handle ciaHandle; - AM_StartCiaInstall(app_mediatype_to_byte(mediaType), &ciaHandle); FSFILE_SetSize(ciaHandle, size); u32 bufSize = 1024 * 256; // 256KB @@ -610,23 +624,27 @@ bool app_install(MediaType mediaType, const char* path, bool (*onProgress)(int p break; } - u32 bytesRead; - FSFILE_Read(fileHandle, &bytesRead, pos, buf, bufSize); + u32 bytesRead = fread(buf, 1, bufSize, fd); FSFILE_Write(ciaHandle, NULL, pos, buf, bytesRead, FS_WRITE_NOFLUSH); } - if(!cancelled) { - if(onProgress != NULL) { - onProgress(100); - } + free(buf); + fclose(fd); - AM_FinishCiaInstall(app_mediatype_to_byte(mediaType), &ciaHandle); + if(cancelled) { + return false; } - free(buf); - FSFILE_Close(fileHandle); - FSUSER_CloseArchive(NULL, &sdmcArchive); - return !cancelled; + if(onProgress != NULL) { + onProgress(100); + } + + Result res = AM_FinishCiaInstall(app_mediatype_to_byte(mediaType), &ciaHandle); + if(res != 0 && res != 0xC8A044DC) { // Happens when already installed, but seems to have succeeded anyway... + return false; + } + + return true; } bool app_delete(MediaType mediaType, App app) { @@ -648,22 +666,27 @@ bool app_launch(MediaType mediaType, App app) { u64 fs_get_free_space(MediaType mediaType) { u32 clusterSize; u32 freeClusters; + Result res = 0; if(mediaType == NAND) { - FSUSER_GetNandArchiveResource(NULL, NULL, &clusterSize, NULL, &freeClusters); + res = FSUSER_GetNandArchiveResource(NULL, NULL, &clusterSize, NULL, &freeClusters); } else { - FSUSER_GetSdmcArchiveResource(NULL, NULL, &clusterSize, NULL, &freeClusters); + res = FSUSER_GetSdmcArchiveResource(NULL, NULL, &clusterSize, NULL, &freeClusters); + } + + if(res != 0) { + return 0; } return clusterSize * freeClusters; } -void platform_init() { - srvInit(); - aptInit(); - hidInit(NULL); +bool platform_init() { + if(srvInit() != 0 || aptInit() != 0 || hidInit(NULL) != 0 || fsInit() != 0 || sdmcInit() != 0) { + return false; + } + gfxInitDefault(); - fsInit(); - sdmcInit(); + return true; } void platform_cleanup() { diff --git a/source/common.h b/source/common.h index e25c0df..72ad727 100644 --- a/source/common.h +++ b/source/common.h @@ -113,7 +113,7 @@ bool app_launch(MediaType mediaType, App app); u64 fs_get_free_space(MediaType mediaType); -void platform_init(); +bool platform_init(); void platform_cleanup(); bool platform_is_running(); u64 platform_get_time(); diff --git a/source/main.cpp b/source/main.cpp index 4d4f556..a4a343d 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -4,7 +4,9 @@ #include "ui.h" int main(int argc, char **argv) { - platform_init(); + if(!platform_init()) { + return 0; + } MediaType destination = SD; Mode mode = INSTALL; diff --git a/tools/banner/.gitignore b/tools/banner/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/tools/banner/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file diff --git a/resources/tools/banner/LICENSE.txt b/tools/banner/LICENSE.txt similarity index 100% rename from resources/tools/banner/LICENSE.txt rename to tools/banner/LICENSE.txt diff --git a/resources/tools/banner/audio/audio.bcwav b/tools/banner/audio.bcwav similarity index 100% rename from resources/tools/banner/audio/audio.bcwav rename to tools/banner/audio.bcwav diff --git a/tools/banner/compress.py b/tools/banner/compress.py new file mode 100644 index 0000000..46f1895 --- /dev/null +++ b/tools/banner/compress.py @@ -0,0 +1,255 @@ +# used http://code.google.com/p/u-lzss/source/browse/trunk/js/lib/ulzss.js as +# a guide +from sys import stderr + +from collections import defaultdict +from operator import itemgetter +from struct import pack, unpack + +class SlidingWindow: + # The size of the sliding window + size = 4096 + + # The minimum displacement. + disp_min = 2 + + # The hard minimum — a disp less than this can't be represented in the + # compressed stream. + disp_start = 1 + + # The minimum length for a successful match in the window + match_min = 1 + + # The maximum length of a successful match, inclusive. + match_max = None + + def __init__(self, buf): + self.data = buf + self.hash = defaultdict(list) + self.full = False + + self.start = 0 + self.stop = 0 + #self.index = self.disp_min - 1 + self.index = 0 + + assert self.match_max is not None + + def next(self): + if self.index < self.disp_start - 1: + self.index += 1 + return + + if self.full: + olditem = self.data[self.start] + assert self.hash[olditem][0] == self.start + self.hash[olditem].pop(0) + + item = self.data[self.stop] + self.hash[item].append(self.stop) + self.stop += 1 + self.index += 1 + + if self.full: + self.start += 1 + else: + if self.size <= self.stop: + self.full = True + + def advance(self, n=1): + """Advance the window by n bytes""" + for _ in range(n): + self.next() + + def search(self): + match_max = self.match_max + match_min = self.match_min + + counts = [] + indices = self.hash[self.data[self.index]] + for i in indices: + matchlen = self.match(i, self.index) + if matchlen >= match_min: + disp = self.index - i + #assert self.index - disp >= 0 + #assert self.disp_min <= disp < self.size + self.disp_min + if self.disp_min <= disp: + counts.append((matchlen, -disp)) + if matchlen >= match_max: + #assert matchlen == match_max + return counts[-1] + + if counts: + match = max(counts, key=itemgetter(0)) + return match + + return None + + def match(self, start, bufstart): + size = self.index - start + + if size == 0: + return 0 + + matchlen = 0 + it = range(min(len(self.data) - bufstart, self.match_max)) + for i in it: + if self.data[start + (i % size)] == self.data[bufstart + i]: + matchlen += 1 + else: + break + return matchlen + +class NLZ10Window(SlidingWindow): + size = 4096 + + match_min = 3 + match_max = 3 + 0xf + +class NLZ11Window(SlidingWindow): + size = 4096 + + match_min = 3 + match_max = 0x111 + 0xFFFF + +class NOverlayWindow(NLZ10Window): + disp_min = 3 + +def _compress(input, windowclass=NLZ10Window): + """Generates a stream of tokens. Either a byte (int) or a tuple of (count, + displacement).""" + + window = windowclass(input) + + i = 0 + while True: + if len(input) <= i: + break + match = window.search() + if match: + yield match + #if match[1] == -283: + # raise Exception(match, i) + window.advance(match[0]) + i += match[0] + else: + yield input[i] + window.next() + i += 1 + +def packflags(flags): + n = 0 + for i in range(8): + n <<= 1 + try: + if flags[i]: + n |= 1 + except IndexError: + pass + return n + +def chunkit(it, n): + buf = [] + for x in it: + buf.append(x) + if n <= len(buf): + yield buf + buf = [] + if buf: + yield buf + +def compress(input, out): + # header + out.write(pack("B", packflags(flags))) + + for t in tokens: + if type(t) == tuple: + count, disp = t + count -= 3 + disp = (-disp) - 1 + assert 0 <= disp < 4096 + sh = (count << 12) | disp + out.write(pack(">H", sh)) + else: + out.write(pack(">B", t)) + + length += 1 + length += sum(2 if f else 1 for f in flags) + + # padding + padding = 4 - (length % 4 or 4) + if padding: + out.write(b'\xff' * padding) + +def compress_nlz11(input, out): + # header + out.write(pack("B", packflags(flags))) + length += 1 + + for t in tokens: + if type(t) == tuple: + count, disp = t + disp = (-disp) - 1 + #if disp == 282: + # raise Exception + assert 0 <= disp <= 0xFFF + if count <= 1 + 0xF: + count -= 1 + assert 2 <= count <= 0xF + sh = (count << 12) | disp + out.write(pack(">H", sh)) + length += 2 + elif count <= 0x11 + 0xFF: + count -= 0x11 + assert 0 <= count <= 0xFF + b = count >> 4 + sh = ((count & 0xF) << 12) | disp + out.write(pack(">BH", b, sh)) + length += 3 + elif count <= 0x111 + 0xFFFF: + count -= 0x111 + assert 0 <= count <= 0xFFFF + l = (1 << 28) | (count << 12) | disp + out.write(pack(">L", l)) + length += 4 + else: + raise ValueError(count) + else: + out.write(pack(">B", t)) + length += 1 + + # padding + padding = 4 - (length % 4 or 4) + if padding: + out.write(b'\xff' * padding) + +def dump_compress_nlz11(input, out): + # body + length = 0 + def dump(): + for t in _compress(input, windowclass=NLZ11Window): + if type(t) == tuple: + yield t + from pprint import pprint + pprint(list(dump())) + +if __name__ == '__main__': + from sys import stdout, argv + data = open(argv[1], "rb").read() + stdout = stdout.detach() + #compress(data, stdout) + compress_nlz11(data, stdout) + + #dump_compress_nlz11(data, stdout) \ No newline at end of file diff --git a/tools/banner/create.py b/tools/banner/create.py new file mode 100644 index 0000000..ea39d22 --- /dev/null +++ b/tools/banner/create.py @@ -0,0 +1,163 @@ +from PIL import Image, ImageDraw +from io import BytesIO +from compress import compress_nlz11 +from struct import pack +import sys + +longtitle=sys.argv[1] +shortitle=sys.argv[2] +publisher=sys.argv[3] + +# fixed variables; not likely these will be used in this context, but here just in case. +visibility =1 +autoBoot =0 +use3D =1 +requireEULA =0 +autoSaveOnExit=0 +extendedBanner=0 +gameRatings =0 +useSaveData =1 +recordAppUsage=0 +disableSaveBU =0 + +def make_icon(file, size): + f=open(file,'rb') + image=Image.open(f) + posit=open('map' + str(size) + 'x' + str(size) + '.bin','rb') + + w=image.size[0] + h=image.size[1] + n=w*w + i=[0]*(n*2) + + if w != h: + print("width, height unequal") + f.close() + sys.exit(1) + if w != size: + print("sides need to be " + str(size) + " pixels") + f.close() + sys.exit(1) + + dump=list(image.getdata()) + + + pos=0 + + for x in range(n): + p1=ord(posit.read(1)) + p2=ord(posit.read(1)) + pos=p1+(p2<<8) + + r=dump[x][0]>>3 + g=dump[x][1]>>2 + b=dump[x][2]>>3 + + i[pos<<1]=(g&7)<<5| b + i[(pos<<1)+1]=(r)<<3 | g>>3 + + f.close() + posit.close() + return bytearray(i) + +def make_banner(file): + f=open(file,'rb') + image=Image.open(f) + if image.size[0] != 256 or image.size[1] != 128: + f.close() + print("ERROR: Image must be exactly 256 x 128. Abort.") + sys.exit(4) + + hdr=open("header.bin","rb") + + posit=open('map256x128.bin','rb') + n=256*128 + i=[0]*(n*2) + cbmdhdr=bytearray(b"\x43\x42\x4D\x44\x00\x00\x00\x00\x88"+(b"\x00"*0x7B)) + + dump=list(image.getdata()) + pos=0 + + for x in range(n): + p1=ord(posit.read(1)) + p2=ord(posit.read(1)) + pos=p1+(p2<<8) + + r=dump[x][0]>>4 + g=dump[x][1]>>4 + b=dump[x][2]>>4 + a=dump[x][3]>>4 + + i[pos<<1]= (b<<4) | a + i[(pos<<1)+1]= (r<<4) | g + + + buf=hdr.read() + hdr.close() + + out = BytesIO() + compress_nlz11(buf+bytearray(i), out) + l=bytearray(out.getvalue()) + length=len(l)+136 + + pad=16-(length%16) + l+=bytearray([0]*pad) + length+=pad + + for c in range(4): + cbmdhdr+=pack("B", length & 255) + length=length>>8 + + cbmdhdr+=l + + f.close() + posit.close() + return bytearray(cbmdhdr) + +def make_audio(): + # TODO: Convert WAV file. + bcwav1=open("audio.bcwav","rb") + bcwav=bcwav1.read() + bcwav1.close() + return bytearray(bcwav) + +ctp1=make_icon(sys.argv[4], 24) +ctp2=make_icon(sys.argv[5], 48) +bcwav=make_audio() +cbmd=make_banner(sys.argv[6]) + +icn=open(sys.argv[7],"wb") +bnr=open(sys.argv[8],"wb") + +header=bytearray(b"\x53\x4D\x44\x48\x00\x00\x00\x00")+bytearray(b"\x00"*0x1FF8)+bytearray(b"\x00\x00\x00\x00\x00\x00\x00\x00\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x00\x00\x00\x00\xFF\xFF\xFF\x7F\x31\x48\x62\x64\x99\x99\x99\x19\x91\x18\x62\x64\xA5\x01\x00\x00\x00\x01\x00\x00\x00\x00\x80\x3F\x32\x41\x79\x24\x00\x00\x00\x00\x00\x00\x00\x00") + +header[0x2028]=(visibility | autoBoot<<1 | use3D<<2 | requireEULA<<3 | autoSaveOnExit<<4 | extendedBanner<<5 | gameRatings<<6 | useSaveData<<7) +header[0x2029]=(recordAppUsage | disableSaveBU<<2) + +offset=8 +pos=0 + +for x in range(11): + for c in longtitle: + header[offset+pos*2]=ord(longtitle[pos]) + pos+=1 + pos=0 + offset+=0x80 + for c in shortitle: + header[offset+pos*2]=ord(shortitle[pos]) + pos+=1 + pos=0 + offset+=0x100 + for c in publisher: + header[offset+pos*2]=ord(publisher[pos]) + pos+=1 + pos=0 + offset+=0x80 + +header+=(ctp1+ctp2) + +icn.write(header) +bnr.write(cbmd+bcwav) + +bnr.close() +icn.close() \ No newline at end of file diff --git a/resources/tools/banner/banner/header.bin b/tools/banner/header.bin similarity index 100% rename from resources/tools/banner/banner/header.bin rename to tools/banner/header.bin diff --git a/resources/tools/banner/icon24/map24x24.bin b/tools/banner/map24x24.bin similarity index 100% rename from resources/tools/banner/icon24/map24x24.bin rename to tools/banner/map24x24.bin diff --git a/resources/tools/banner/banner/map256x128.bin b/tools/banner/map256x128.bin similarity index 100% rename from resources/tools/banner/banner/map256x128.bin rename to tools/banner/map256x128.bin diff --git a/resources/tools/banner/icon48/map48x48.bin b/tools/banner/map48x48.bin similarity index 100% rename from resources/tools/banner/icon48/map48x48.bin rename to tools/banner/map48x48.bin diff --git a/resources/tools/makerom b/tools/makerom similarity index 100% rename from resources/tools/makerom rename to tools/makerom