diff --git a/Makefile b/Makefile index 759b94f..a869a59 100644 --- a/Makefile +++ b/Makefile @@ -49,13 +49,13 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 ASFLAGS := -g $(ARCH) LDFLAGS = -specs=3dsx.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map) -LIBS := -lctru -lm +LIBS := -lpng16 -lz -lm -lctru #--------------------------------------------------------------------------------- # list of directories containing libraries, this must be the top level containing # include and lib #--------------------------------------------------------------------------------- -LIBDIRS := $(CTRULIB) +LIBDIRS := $(CTRULIB) $(PORTLIBS) #--------------------------------------------------------------------------------- diff --git a/source/fs.c b/source/fs.c new file mode 100644 index 0000000..c6f03a1 --- /dev/null +++ b/source/fs.c @@ -0,0 +1,50 @@ +#include "fs.h" + +void openSdArchive() +{ + FSUSER_OpenArchive(&sdmcArchive, ARCHIVE_SDMC, fsMakePath(PATH_EMPTY, "")); +} + +void closeSdArchive() +{ + FSUSER_CloseArchive(sdmcArchive); +} + +int makeDir(const char *path) +{ + if (!path) + return -1; + + return mkdir(path, 0777); +} + +bool fileExists(char *path) +{ + FILE * temp = fopen(path, "r"); + if(temp == NULL) + return false; + + fclose(temp); + + return true; +} + +bool dirExists(const char *path) +{ + struct stat info; + + if(stat( path, &info ) != 0) + return false; + else if(info.st_mode & S_IFDIR) + return true; + else + return false; +} + +bool deleteFile(const char *path) +{ + openSdArchive(); + Result ret = FSUSER_DeleteFile(sdmcArchive, fsMakePath(PATH_ASCII, path)); + closeSdArchive(); + return ret == 0; +} \ No newline at end of file diff --git a/source/fs.h b/source/fs.h new file mode 100644 index 0000000..9c14390 --- /dev/null +++ b/source/fs.h @@ -0,0 +1,13 @@ +#include <3ds.h> +#include +#include +#include + +FS_Archive sdmcArchive; + +void openSdArchive(); +void closeSdArchive(); +int makeDir(const char * path); +bool fileExists(char * path); +bool dirExists(const char * path); +bool deleteFile(const char *path); \ No newline at end of file diff --git a/source/main.c b/source/main.c index eeadabe..e56c052 100644 --- a/source/main.c +++ b/source/main.c @@ -3,9 +3,12 @@ #include #include +#include "screenshot.h" + const char * getModel() { - const char *models[] = { + const char *models[] = + { "O3DS", "O3DS XL", "N3DS", @@ -25,7 +28,8 @@ const char * getModel() const char * getRegion() { - const char *regions[] = { + const char *regions[] = + { "JPN", "USA", "EUR", @@ -47,7 +51,8 @@ const char * getRegion() const char * getLang() { - const char *languages[] = { + const char *languages[] = + { "Japanese", "English", "French", @@ -157,8 +162,12 @@ int main(int argc, char *argv[]) { gspWaitForVBlank(); hidScanInput(); - if (hidKeysDown()) break; - gfxFlushBuffers(); + if (hidKeysDown()) + { + captureScreenshot(); + break; + } + gfxFlushBuffers(); gfxSwapBuffers(); } diff --git a/source/screenshot.c b/source/screenshot.c new file mode 100644 index 0000000..9d0f3f1 --- /dev/null +++ b/source/screenshot.c @@ -0,0 +1,284 @@ +#include "fs.h" +#include "screenshot.h" + +static const struct +{ + GSPGPU_FramebufferFormats format; + u32 bytes_per_pixel; + const char *str; +}formats[] = +{ + { GSP_RGBA8_OES, 4, "rgba8", }, + { GSP_BGR8_OES, 3, "bgr8", }, + { GSP_RGB565_OES, 2, "rgb565", }, + { GSP_RGB5_A1_OES, 2, "rgb5a1", }, + { GSP_RGBA4_OES, 2, "rgba4", }, +}; + +static const size_t num_formats = sizeof(formats)/sizeof(formats[0]); + +static inline void update_gfx(void) +{ + gfxFlushBuffers(); + gspWaitForVBlank(); + gfxSwapBuffers(); +} + +#define USE_CALLBACK + +static void png_file_write(png_structp png_ptr, png_bytep data, png_size_t length) +{ + ssize_t rc; + FILE *fp = (FILE*)png_get_io_ptr(png_ptr); + + if(length <= 0) + { + fprintf(stderr, "%s: length <= 0\n", __func__); + longjmp(png_jmpbuf(png_ptr), 1); + } + + rc = fwrite(data, 1, length, fp); + if(rc <= 0) + { + fprintf(stderr, "fwrite failed\n"); + longjmp(png_jmpbuf(png_ptr), 1); + } + else if(rc < length) + { + fprintf(stderr, "fwrite: wrote %zu/%zu bytes\n", rc, length); + longjmp(png_jmpbuf(png_ptr), 1); + } +} + +static void +png_file_flush(png_structp png_ptr) +{ + /* no-op */ +} + +static void +png_file_error(png_structp png_ptr, png_const_charp error_msg) +{ + fprintf(stderr, "%s\n", error_msg); +} + +static void png_file_warning(png_structp png_ptr, png_const_charp warning_msg) +{ + fprintf(stderr, "%s\n", warning_msg); +} + +#ifdef USE_CALLBACK + +static void png_row_callback(png_structp png_ptr, png_uint_32 row, int pass) +{ + fprintf(stderr, "\x1b[2;0H%3d%%\n", row*100 / 480); +} + +#endif + +static u8 png_buffer[400*480*4]; +static u8 *png_lines[480]; + +static u32 bytes_per_pixel(GSPGPU_FramebufferFormats format) +{ + switch(format) + { + case GSP_RGBA8_OES: + return 4; + case GSP_BGR8_OES: + return 3; + case GSP_RGB565_OES: + case GSP_RGB5_A1_OES: + case GSP_RGBA4_OES: + return 2; + } + + return 3; +} + +static void pixel_to_rgba(u8 *dst, const u8 *src, GSPGPU_FramebufferFormats format) +{ + u16 half; + + switch(format) + { + case GSP_RGBA8_OES: + dst[0] = src[3]; + dst[1] = src[2]; + dst[2] = src[1]; + dst[3] = src[0]; + break; + + case GSP_BGR8_OES: + dst[0] = src[2]; + dst[1] = src[1]; + dst[2] = src[0]; + dst[3] = 0xFF; + break; + + case GSP_RGB565_OES: + memcpy(&half, src, sizeof(half)); + dst[0] = (half >> 8) & 0xF8; + dst[1] = (half >> 3) & 0xFC; + dst[2] = (half << 3) & 0xF8; + dst[3] = 0xFF; + break; + + case GSP_RGB5_A1_OES: + memcpy(&half, src, sizeof(half)); + dst[0] = (half >> 8) & 0xF8; + dst[0] |= dst[0] >> 5; + dst[1] = (half >> 3) & 0xF8; + dst[0] |= dst[1] >> 5; + dst[2] = (half << 2) & 0xF8; + dst[0] |= dst[2] >> 5; + dst[3] = half & 1 ? 0xFF : 0x00; + break; + + case GSP_RGBA4_OES: + memcpy(&half, src, sizeof(half)); + dst[0] = (half >> 8) & 0xF0; + dst[0] |= dst[0] >> 4; + dst[1] = (half >> 4) & 0xF0; + dst[1] |= dst[1] >> 4; + dst[2] = (half >> 0) & 0xF0; + dst[2] |= dst[2] >> 4; + dst[3] = (half << 4) & 0xF0; + dst[3] |= dst[3] >> 4; + break; + } +} + +static inline u8* get_pixel(u8 *fb, u16 x, u16 y, u16 w, u16 h, u32 bpp) +{ + return &fb[(x*w + (w-y-1))*bpp]; +} + +static void fill_png_buffer(void) +{ + size_t x, y; + u8 *p = png_buffer; + u16 fbWidth, fbHeight; + u8 *fb = gfxGetFramebuffer(GFX_TOP, GFX_LEFT, &fbWidth, &fbHeight); + GSPGPU_FramebufferFormats topFmt = gfxGetScreenFormat(GFX_TOP); + GSPGPU_FramebufferFormats botFmt = gfxGetScreenFormat(GFX_BOTTOM); + u32 topBPP = bytes_per_pixel(topFmt); + u32 botBPP = bytes_per_pixel(botFmt); + + /* top screen */ + for(y = 0; y < 240; ++y) + { + for(x = 0; x < 400; ++x) + { + u8 *pixel = get_pixel(fb, x, y, fbWidth, fbHeight, topBPP); + pixel_to_rgba(p, pixel, topFmt); + p += 4; + } + } + + /* bottom screen */ + fb = gfxGetFramebuffer(GFX_BOTTOM, GFX_LEFT, &fbWidth, &fbHeight); + for(y = 0; y < 240; ++y) + { + for(x = 0; x < 400; ++x) + { + if(x < 40 || x > 360) + { + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x00; + *p++ = 0x00; + } + else + { + u8 *pixel = get_pixel(fb, x-40, y, fbWidth, fbHeight, botBPP); + pixel_to_rgba(p, pixel, botFmt); + p += 4; + } + } + } +} + +int screenshot_png(const char *path, int level) +{ + size_t i; + png_structp png_ptr; + png_infop info_ptr; + FILE *fp; + + if(level < Z_NO_COMPRESSION || level > Z_BEST_COMPRESSION) + { + fprintf(stderr, "invalid compression level %d\n", level); + return -1; + } + + for(i = 0; i < 480; ++i) + png_lines[i] = &png_buffer[400*4*i]; + + fp = fopen(path, "wb"); + if(fp == NULL) + { + fprintf(stderr, "failed to open '%s'\n", path); + return -1; + } + + setvbuf(fp, NULL, _IOFBF, 1024*8); + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, png_file_error, png_file_warning); + if(png_ptr == NULL) + { + fprintf(stderr, "png_create_write_struct failed\n"); + fclose(fp); + return -1; + } + + info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) + { + fprintf(stderr, "png_create_info_struct failed\n"); + png_destroy_write_struct(&png_ptr, NULL); + fclose(fp); + return -1; + } + + png_set_write_fn(png_ptr, fp, png_file_write, png_file_flush); + +#ifdef USE_CALLBACK + + png_set_write_status_fn(png_ptr, png_row_callback); + +#endif + + if(setjmp(png_jmpbuf(png_ptr))) + { + fprintf(stderr, "png failure\n"); + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + return -1; + } + + png_set_compression_level(png_ptr, level); + png_set_IHDR(png_ptr, info_ptr, 400, 480, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + png_write_info(png_ptr, info_ptr); + + fill_png_buffer(); + png_write_image(png_ptr, png_lines); + + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + + fprintf(stderr, "\x1b[2;0H \n"); + + return 0; +} + +void captureScreenshot() +{ + if (fileExists("/3ds/3DSident/screenshots/")) + deleteFile("/3ds/3DSident/screenshots/"); + + if (!dirExists("/3ds/3DSident/screenshots")) + makeDir("/3ds/3DSident/screenshots"); + + screenshot_png(screenshotPath, Z_NO_COMPRESSION); +} \ No newline at end of file diff --git a/source/screenshot.h b/source/screenshot.h new file mode 100644 index 0000000..4f75425 --- /dev/null +++ b/source/screenshot.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include +#include +#include <3ds.h> + +#define NUM_LEVELS (Z_BEST_COMPRESSION - Z_NO_COMPRESSION + 1) +#define screenshotPath "/3ds/3DSident/screenshots/3DSident.png" + +int level; +unsigned int format_choice; +GSPGPU_FramebufferFormats format; // = GSP_RGBA8_OES +int screenshot_png(const char *path, int level); +void captureScreenshot(); \ No newline at end of file