Skip to content

Commit

Permalink
add BMP support for custom icons
Browse files Browse the repository at this point in the history
  • Loading branch information
MechDragon committed Aug 30, 2021
1 parent 3b44017 commit 765aaa0
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 28 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@
*.3ds
.vscode/
build/
!romfs
!romfs
core
*.bmp
*.srl1
30 changes: 30 additions & 0 deletions include/bmp.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once
#include <3ds.h>
#include "nds.h"

typedef struct __attribute__((__packed__)) {
u32 size;
u32 width;
u32 height;
u16 planes;
u16 bpp;
u32 compression;
u32 imageSizeCompressed;
u32 ppmX;
u32 ppmY;
u32 colors;
u32 importantColors;
} InfoHeader;
typedef struct __attribute__((__packed__)) {
u16 magic;
u32 filesize;
u32 reserved;
u32 dataOffset;
InfoHeader infoHeader;
} BMPHeader;


void toDSPixelOrder(u8* store, u8* source);
void convertImage(u8* store, u8* source);
void convertColors(u16 palette[16], u32 BGR888_palette[16]);
Result loadBmpAsIcon(std::string filename, tNDSBanner* banner);
23 changes: 2 additions & 21 deletions include/builder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,13 @@
#include <vector>
#include <map>
#include <sys/stat.h>
#include <sstream>
#include <fstream>
//#include <sstream>
//#include <fstream>

//#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
Expand Down Expand Up @@ -41,21 +40,3 @@ class Builder {
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;
}
4 changes: 4 additions & 0 deletions include/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ typedef struct
}__attribute__((__packed__))
sSignature;

std::string readEntireFile(const std::string& path);

bool fileExists (const std::string& name);
unsigned long fileSize (const std::string& name);
2 changes: 1 addition & 1 deletion include/nds.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/// 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

#pragma once
#include <3ds.h>
#include <citro2d.h>
typedef struct sNDSHeader {
Expand Down
5 changes: 4 additions & 1 deletion include/settings.hpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
#pragma once

#define VERSION "v1.1.0-beta"
#define VERSION "v1.2.0-beta"
//#define DEBUG 1

#define FORWARDER_DIR std::string("sdmc:/3ds/forwarder")
#define SDCARD_TEMPLATE_DIR FORWARDER_DIR+std::string("/templates/")
#define ROMFS_TEMPLATE_DIR std::string("romfs:/templates/")
#define SDCARD_BANNER_PATH FORWARDER_DIR+std::string("/banners/")
#define SDCARD_ICON_PATH FORWARDER_DIR+std::string("/icons/")

#define ENTRY_HEIGHT 48
#define FILELIST_HEIGHT (240-MENU_HEADING_HEIGHT-MENU_BORDER_HEIGHT)
Expand Down
84 changes: 84 additions & 0 deletions source/bmp.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#include <3ds.h>
#include <string>
#include <fstream>
#include "bmp.hpp"
#include "logger.hpp"

Logger bmplogger("BmpHandler");
Result loadBmpAsIcon(std::string filename, tNDSBanner* banner) {
BMPHeader bmpHeader={0};
std::ifstream f(filename);
if (f.fail()) {
bmplogger.error("Failed to open "+filename);
return -1;
}
f.read((char*)&bmpHeader, sizeof(bmpHeader));
if (bmpHeader.magic !=0x4D42) {
f.close();
bmplogger.error(filename + " is not a valid bitmap file");
bmplogger.error(std::to_string(bmpHeader.magic));
return -1;
}
if (bmpHeader.infoHeader.width != 32 || bmpHeader.infoHeader.height != 32) {
bmplogger.error(filename + " has invalid width or height. Must be 32x32");
bmplogger.error(filename + " is " + std::to_string(bmpHeader.infoHeader.width) + "x"+std::to_string(bmpHeader.infoHeader.height));
return -1;
}
if (bmpHeader.infoHeader.importantColors>16) {
bmplogger.error(filename + " has too many colors.");
return -1;
}
if (bmpHeader.infoHeader.bpp>4) {
bmplogger.error(filename + " is too deep. use 4bit.");
return -1;
}
if (bmpHeader.infoHeader.compression != 0) {
bmplogger.error(filename + " is compressed. Compression is not supported.");
}
u32 BGR888_palette[16]={0};
u16 palette[16]={0};
f.read((char*)BGR888_palette,sizeof(u32)*16);
f.seekg(bmpHeader.dataOffset);
u8 BGR888_data[0x200]={0};
f.read((char*)BGR888_data,0x200);
f.close();
convertColors(palette, BGR888_palette);
u8 image[0x200]={0};
convertImage(image,BGR888_data);
memcpy(banner->icon,image,0x200);
memcpy(banner->palette,palette,32);
return 0;
}
void convertColors(u16 palette[16], u32 BGR888_palette[16]) {
for (u8 i=0;i<16;i++) {
u32 color = BGR888_palette[i];
u16 r = (color >> 19) & 0b11111;
u16 g = (color >> 11) & 0b11111;
u16 b = (color >> 3) & 0b11111;
palette[i] = b<<10|g<<5|r;
}
}
void convertImage(u8* store, u8* source) {
toDSPixelOrder(store, source);
}
void toDSPixelOrder(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;
for (int i=0;i<0x200;i++) {
source[i]=(source[i]<<4)|(source[i]>>4);
}
// 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[y*4+cc*32+yy*128],source+offset,4);
offset+=4;
}
}
}
memcpy(store,tmp,0x200);
}
29 changes: 26 additions & 3 deletions source/builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "tmd.h"
#include "cia.h"
#include "ticket.h"
#include "bmp.hpp"
#include "settings.hpp"

Logger logger("Builder");
Expand Down Expand Up @@ -43,7 +44,7 @@ Result Builder::loadTemplate(std::string templateName) {
}
this->srl = readEntireFile(srlFileName);
memcpy(&this->srlBannerLocation,this->srl.c_str() + 0x68,4);

parseTemplate(srlTemplate);
if (this->launchPathLocation == 0 || this->launchPathLen==0) return -1;
return 0;
Expand Down Expand Up @@ -107,17 +108,31 @@ std::string Builder::buildSRL(std::string filename, bool randomTid, std::string
char extraTitles[2][0x100] = {0};
const u8 noAnimation[] = {0x01,0x00,0x00,0x01};
std::string customBannerFilename = filename.substr(0,filename.find_last_of('.'))+".bin";
std::string customIconFilename = filename.substr(0,filename.find_last_of('.'))+".bmp";

logger.info("looking for banner at "+customBannerFilename);
bool customBanner = fileExists(customBannerFilename) && fileSize(customBannerFilename) == 0x23C0;
bool customBMPIcon = fileExists(customIconFilename);
if (!customBanner) {
customBannerFilename=filename.substr(filename.find_last_of('/'),filename.find_last_of('.')-filename.find_last_of('/'))+".bin";
customBannerFilename=filename.substr(filename.find_last_of('/')+1,filename.find_last_of('.')-filename.find_last_of('/')-1)+".bin";
customBannerFilename=SDCARD_BANNER_PATH+customBannerFilename;
logger.info("looking for banner at "+customBannerFilename);
customBanner = fileExists(customBannerFilename) && fileSize(customBannerFilename) == 0x23C0;
}
if (!customBMPIcon) {
customIconFilename=filename.substr(filename.find_last_of('/')+1,filename.find_last_of('.')-filename.find_last_of('/')-1)+".bmp";
customIconFilename=SDCARD_ICON_PATH+customIconFilename;
logger.info("looking for custom bmp icon at "+customIconFilename);
customBMPIcon = fileExists(customIconFilename);
}
std::ifstream f(filename);
f.seekg(0);
f.read((char*)&header,sizeof(header));
bool extendedHeader = (header.headerSize == 0x4000);
if (header.bannerOffset == 0) {
logger.error("NDS file contains no banner. This file is not supported.");
return "";
}
f.seekg(header.bannerOffset);
f.read((char*)&banner,sizeof(banner));
if ((banner.version & 0xFF) > 1) {
Expand Down Expand Up @@ -156,6 +171,9 @@ std::string Builder::buildSRL(std::string filename, bool randomTid, std::string
f.read(animatedIconData,0x1180);
}
f.close();
}else if(customBMPIcon) {
if (R_SUCCEEDED(loadBmpAsIcon(customIconFilename,&banner)))
banner.version &= 3;
}
//TODO apply nds file to srl
std::string dsiware = this->srl;
Expand Down Expand Up @@ -186,7 +204,8 @@ std::string Builder::buildSRL(std::string filename, bool randomTid, std::string
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);
if (extendedHeader)
dsiware.replace(0x230,0x04,emagCode,0x04);
dsiware.replace(0x10,0x02,header.makercode,0x02);
// Set Banner

Expand Down Expand Up @@ -248,6 +267,10 @@ Result Builder::buildCIA(std::string filename, bool randomTid, std::string custo
PS_GenerateRandomBytes(contentID+1,3);

this->sections["content"] = buildSRL(filename, randomTid, customTitle);
if (this->sections["content"].size() == 0) {
logger.error("Failed to create forwarder.");
return -1;
}
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);
Expand Down
28 changes: 27 additions & 1 deletion source/helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <cstring>
#include <algorithm>
#include "helpers.hpp"
#include <sys/stat.h>

std::string sha256(u8* data, u32 size)
{
u8 buf[0x20]={0};
Expand Down Expand Up @@ -67,4 +69,28 @@ std::string toLowerCase(std::string s) {
std::string ret=s;
std::transform(ret.begin(), ret.end(), ret.begin(),[](unsigned char c){ return std::tolower(c); });
return ret;
}
}
std::string readEntireFile(const std::string& path) {
FILE * f = fopen(path.c_str(),"rb");
fseek(f,0,SEEK_END);
size_t s = ftell(f);
fseek(f,0,SEEK_SET);
char *buf=(char*)calloc(1,s);
fread(buf,1,s,f);
fclose(f);
std::string ret(buf,s);
free(buf);
return ret;
}

bool fileExists (const std::string& name) {
struct stat buffer;
return (stat (name.c_str(), &buffer) == 0);
}
unsigned long fileSize (const std::string& name) {
struct stat buffer;
if (stat (name.c_str(), &buffer)==0) {
return buffer.st_size;
}
return 0;
}
1 change: 1 addition & 0 deletions source/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ extern "C" {
#include "builder.hpp"
#include "settings.hpp"
#include "config.hpp"
#include "helpers.hpp"
namespace fs = std::filesystem;

#define SYSTEM_APP_COUNT 6
Expand Down

0 comments on commit 765aaa0

Please sign in to comment.