diff --git a/plugins/screenshot/Makefile b/plugins/screenshot/Makefile new file mode 100644 index 0000000..0bf2d6e --- /dev/null +++ b/plugins/screenshot/Makefile @@ -0,0 +1,291 @@ +DO_LOGGING := 1 + +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPRO") +endif +export PATH := $(DEVKITPPC)/bin:$(PORTLIBS)/bin:$(PATH) +export PORTLIBS := $(DEVKITPRO)/portlibs/ppc +export WUPSDIR := $(DEVKITPRO)/wups +export GCC_VER := $(shell $(DEVKITPPC)/bin/powerpc-eabi-gcc -dumpversion) + +PREFIX := powerpc-eabi- + +export AS := $(PREFIX)as +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export LD := $(PREFIX)ld +export AR := $(PREFIX)ar +export OBJCOPY := $(PREFIX)objcopy + +#--------------------------------------------------------------------------------- +# 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 +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)).mod +BUILD := build +SOURCES := src + +DATA := + +INCLUDES := src + +MAP ?= $(TARGET:.mod=.map) + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- + +# -Os: optimise size +# -Wall: generate lots of warnings +# -DGEKKO_U: define the symbol GEKKO (used in some headers) +# -D__wiiu__: define the symbol __wii__ (used in some headers) +# -mrvl: enable wii/gamecube compilation +# -mcpu=750: enable processor specific compilation +# -meabi: enable eabi specific compilation +# -mhard-float: enable hardware floating point instructions +# -fshort-wchar: use 16 bit whcar_t type in keeping with Wii executables +# -fno-common: stop common variables which the loader can't understand +# -msdata-none: do not use r2 or r13 as small data areas +# -memb: enable embedded application specific compilation +# -ffunction-sections: split up functions so linker can garbage collect +# -fdata-sections: split up data so linker can garbage collect +COMMON_CFLAGS += -Os -Wall -DGEKKO_U -D__wiiu__ -mrvl -mcpu=750 -meabi -mhard-float -fshort-wchar -fno-common -msdata=none -memb -ffunction-sections -fdata-sections + + +# -x c: compile as c code +# -std=c11: use the c11 standard +CFLAGS += $(COMMON_CFLAGS) -x c -std=c11 + +# -x c: compile as c++ code +# -std=gnu++11: use the c++11 standard +CXXFLAGS += $(COMMON_CFLAGS) -x c++ -std=gnu++11 + +ifeq ($(DO_LOGGING), 1) + CFLAGS += -D__LOGGING__ + CXXFLAGS += -D__LOGGING__ +endif + +ASFLAGS := -mregnames +# --relocatable: make sure ld doesn't remove relocations wups will need +# -s: strip local symbols to speed linking +# -u: keep certain sections +# -wrap: wrap function +# --gc-sections: remove unneeded symbols +# -T: use the linker script specified (to force certain wups sections together) +# -Map: generate a map file + +LDFLAG_COMMON += -u wups_load -u wups_meta -u wups_hooks -T $(WUPSDIR)/wups.ld \ + -Wl,-wrap,open,-wrap,close,-wrap,write,-wrap,read,-wrap,lseek,-wrap,stat,-wrap,fstat,-wrap,opendir,-wrap,closedir,-wrap,readdir,-wrap,mkdir \ + -Wl,-Map,$(notdir $@).map,-wrap,malloc,-wrap,free,-wrap,memalign,-wrap,calloc,-wrap,realloc,-wrap,malloc_usable_size,-wrap,_malloc_r,-wrap,_free_r,-wrap,_realloc_r,-wrap,_calloc_r,-wrap,_memalign_r,-wrap,_malloc_usable_size_r,--gc-sections + +LDFLAGS_MOD += $(LDFLAG_COMMON),--relocatable +LDFLAGS_ELF += --relocatable -s -T $(WUPSDIR)/wups_elf.ld + +#--------------------------------------------------------------------------------- +Q := @ +MAKEFLAGS += --no-print-directory +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lwups -lutils -ldynamiclibs -lgd -ljpeg +# + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(DEVKITPPC) + +#--------------------------------------------------------------------------------- +# 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 PROJECTDIR := $(CURDIR) +export OUTPUT := $(CURDIR)/$(TARGETDIR)/$(TARGET) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +TTFFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.ttf))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export LD_MOD := $(CC) +else + export LD_MOD := $(CXX) +endif + +export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) \ + $(PNGFILES:.png=.png.o) $(addsuffix .o,$(BINFILES)) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) -I$(PORTLIBS)/include \ + -I$(PORTLIBS)/include/libutils -I$(WUPSDIR)/include + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + -L$(PORTLIBS)/lib \ + -L$(WUPSDIR)/lib + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean install + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).elf $(OUTPUT).bin $(BUILD_DBG).elf $(OUTPUT) + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +THIS_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +############################################################################### +# Rule to make everything. +PHONY += all + +all : $(OUTPUT) +############################################################################### +# Special build rules + +# Rule to make the module file. +$(OUTPUT) : output.elf + @echo "checking for missing symbols ..." + @$(LD_MOD) ../$(BUILD)/output.elf $(LDFLAG_COMMON) $(LIBS) $(LIBPATHS) -o check_linking.elf + @echo "linking ..." $@ + @$(LD_MOD) ../$(BUILD)/output.elf $(LDFLAGS_MOD) $(LIBS) $(LIBPATHS) -o $@ + +# Rule to make the module file. +output.elf : $(OFILES) + @echo "linking ... output.elf" + + + @$(LD) $(OFILES) $(LDFLAGS_ELF) $(LIBS) $(LIBPATHS) -o $@ + +############################################################################### +# Standard build rules +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + @echo $(notdir $@) + @rm -f $@ + @$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + @echo $(notdir $<) + @$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(INCLUDE) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(INCLUDE) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.png.o : %.png + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.jpg.o : %.jpg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ttf.o : %.ttf + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.wav.o : %.wav + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.mp3.o : %.mp3 + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ogg.o : %.ogg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +############################################################################### +# Assembly listing rules + +# Rule to make assembly listing. +PHONY += list +list : $(LIST) + +# Rule to make the listing file. +%.list : $(TARGET) + $(LOG) + -$Qmkdir -p $(dir $@) + $Q$(OBJDUMP) -d $< > $@ + +############################################################################### +# Clean rule + +# Rule to clean files. +PHONY += clean +clean : + $Qrm -rf $(wildcard $(BUILD) $(BIN)) + +############################################################################### +# Phony targets + +.PHONY : $(PHONY) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/plugins/screenshot/README.md b/plugins/screenshot/README.md new file mode 100644 index 0000000..7549fd2 --- /dev/null +++ b/plugins/screenshot/README.md @@ -0,0 +1,20 @@ +# Screenshot tool + +This is just a simple plugin that takes screenshot of the TV and DRC screen. The screenshot will saved on the sd card. + +## Building + +For building you need: +- [wups](https://github.com/Maschell/WiiUPluginSystem) +- [dynamic_libs](https://github.com/Maschell/dynamic_libs/tree/lib) for access to the functions. +- [libutils](https://github.com/Maschell/libutils) for common functions. + +Install them (in this order) according to their README's. Don't forget the dependencies of the libs itself. + +You also need some other, external libraries. + +All needed dependencies are in the "libs" folder of this plugin. Extract the "portlibs.zip" archive into your devkitPro directory. +The archive includes: + +- gd +- libjpeg \ No newline at end of file diff --git a/plugins/screenshot/libs/portlibs.zip b/plugins/screenshot/libs/portlibs.zip new file mode 100644 index 0000000..0b155d8 Binary files /dev/null and b/plugins/screenshot/libs/portlibs.zip differ diff --git a/plugins/screenshot/src/common.h b/plugins/screenshot/src/common.h new file mode 100644 index 0000000..726f130 --- /dev/null +++ b/plugins/screenshot/src/common.h @@ -0,0 +1,6 @@ +#ifndef COMMON_H_ +#define COMMON_H_ + +#define WIIU_SCREENSHOT_PATH "sd:/wiiu/screenshots/" + +#endif // COMMON_H_ diff --git a/plugins/screenshot/src/function_patcher.cpp b/plugins/screenshot/src/function_patcher.cpp new file mode 100644 index 0000000..c6b2dcd --- /dev/null +++ b/plugins/screenshot/src/function_patcher.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "common.h" +#include "function_patcher.h" +#include "screenshot_utils.h" +#include "retain_vars.hpp" + +static bool takeScreenshotTV = false; +static bool takeScreenshotDRC = false; +static u8 screenshotCooldown = 0; +static u32 counter = 0; + +DECL_FUNCTION(int, VPADRead, int chan, VPADData *buffer, u32 buffer_size, s32 *error) { + int result = real_VPADRead(chan, buffer, buffer_size, error); + u32 btns = gButtonCombo; + if(result > 0 && (buffer[0].btns_h == btns) && screenshotCooldown == 0 && OSIsHomeButtonMenuEnabled()) { + counter++; + takeScreenshotTV = true; + takeScreenshotDRC = true; + + screenshotCooldown = 0x3C; + } + if(screenshotCooldown > 0) { + screenshotCooldown--; + } + + return result; +} + +DECL_FUNCTION(void, GX2CopyColorBufferToScanBuffer, const GX2ColorBuffer *colorBuffer, s32 scan_target) { + if(takeScreenshotTV || takeScreenshotDRC) { + OSCalendarTime output; + OSTicksToCalendarTime(OSGetTime(), &output); + char buffer[255] = {0}; + snprintf(buffer,254,"%s%04i-%02i-%02i/",WIIU_SCREENSHOT_PATH,output.year,output.mon,output.mday); + + FSUtils::CreateSubfolder(buffer); + + snprintf(buffer,254,"%s%04i-%02i-%02i/%04i-%02i.%02i_%02i.%02i.%02i_", + WIIU_SCREENSHOT_PATH,output.year,output.mon,output.mday,output.year,output.mon,output.mday,output.hour,output.min,output.sec); + + if(scan_target == 1 && colorBuffer != NULL && takeScreenshotTV && gAppStatus == WUPS_APP_STATUS_FOREGROUND) { + DEBUG_FUNCTION_LINE("Lets take a screenshot from TV. Source format: %d \n",colorBuffer->surface.format); + takeScreenshot((GX2ColorBuffer *)colorBuffer, StringTools::strfmt("%sTV.jpg",buffer).c_str()); + takeScreenshotTV = false; + } + if(scan_target == 4 && colorBuffer != NULL && takeScreenshotDRC && gAppStatus == WUPS_APP_STATUS_FOREGROUND) { + DEBUG_FUNCTION_LINE("Lets take a screenshot from DRC. Source format: %d \n",colorBuffer->surface.format); + takeScreenshot((GX2ColorBuffer *)colorBuffer, StringTools::strfmt("%sDRC.jpg",buffer).c_str()); + takeScreenshotDRC = false; + } + } + real_GX2CopyColorBufferToScanBuffer(colorBuffer,scan_target); +} + +WUPS_MUST_REPLACE(VPADRead, WUPS_LOADER_LIBRARY_VPAD, VPADRead); +WUPS_MUST_REPLACE(GX2CopyColorBufferToScanBuffer, WUPS_LOADER_LIBRARY_GX2, GX2CopyColorBufferToScanBuffer); diff --git a/plugins/screenshot/src/function_patcher.h b/plugins/screenshot/src/function_patcher.h new file mode 100644 index 0000000..e69de29 diff --git a/plugins/screenshot/src/main.cpp b/plugins/screenshot/src/main.cpp new file mode 100644 index 0000000..004d307 --- /dev/null +++ b/plugins/screenshot/src/main.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "retain_vars.hpp" + +// Mandatory plugin information. +WUPS_PLUGIN_NAME("Screenshot tool"); +WUPS_PLUGIN_DESCRIPTION("This plugin allows you to make screenshots that will be saved to the sd card"); +WUPS_PLUGIN_VERSION("v0.1"); +WUPS_PLUGIN_AUTHOR("Maschell"); +WUPS_PLUGIN_LICENSE("GPL"); + +// FS Access +WUPS_FS_ACCESS() + +u32 SplashScreen(s32 time,s32 combotime); + +// Gets called once the loader exists. +INITIALIZE_PLUGIN() { + InitOSFunctionPointers(); + InitSocketFunctionPointers(); + InitVPadFunctionPointers(); + InitGX2FunctionPointers(); + + log_init(); + + InitOSFunctionPointers(); + InitVPadFunctionPointers(); + u32 res = SplashScreen(10,2); + gButtonCombo = res; + +} + +// Called whenever an application was started. +ON_APPLICATION_START(my_args) { + InitOSFunctionPointers(); + InitSocketFunctionPointers(); + InitFSFunctionPointers(); + + log_init(); +} + +ON_APP_STATUS_CHANGED(status) { + gAppStatus = status; +} + + +#define FPS 60 +u32 SplashScreen(s32 time,s32 combotime) { + u32 result = VPAD_BUTTON_R | VPAD_BUTTON_L | VPAD_BUTTON_ZR | VPAD_BUTTON_ZL; + + // Init screen + OSScreenInit(); + + u32 screen_buf0_size = OSScreenGetBufferSizeEx(0); + u32 screen_buf1_size = OSScreenGetBufferSizeEx(1); + + u32 * screenbuffer0 = (u32*)memalign(0x100, screen_buf0_size); + u32 * screenbuffer1 = (u32*)memalign(0x100, screen_buf1_size); + + if(screenbuffer0 == NULL || screenbuffer1 == NULL) { + if(screenbuffer0 != NULL) { + free(screenbuffer0); + } + if(screenbuffer1 != NULL) { + free(screenbuffer1); + } + return result; + } + + OSScreenSetBufferEx(0, (void *)screenbuffer0); + OSScreenSetBufferEx(1, (void *)screenbuffer1); + + OSScreenEnableEx(0, 1); + OSScreenEnableEx(1, 1); + + // Clear screens + OSScreenClearBufferEx(0, 0); + OSScreenClearBufferEx(1, 0); + + // Flip buffers + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + + OSScreenClearBufferEx(0, 0); + OSScreenClearBufferEx(1, 0); + + std::vector strings; + strings.push_back("Screenshot tool 0.1 - by Maschell."); + strings.push_back(""); + strings.push_back(""); + strings.push_back("Press the combo you want to use for making screenshots now"); + strings.push_back("for 2 seconds."); + strings.push_back(" "); + strings.push_back("Otherwise the default combo (L+R+ZR+ZL button) will be used"); + strings.push_back("in 10 seconds."); + strings.push_back(" "); + strings.push_back("Press the TV buttons to exit with the default combo."); + + u8 pos = 0; + for (std::vector::iterator it = strings.begin() ; it != strings.end(); ++it) { + OSScreenPutFontEx(0, 0, pos, (*it).c_str()); + OSScreenPutFontEx(1, 0, pos, (*it).c_str()); + pos++; + } + + OSScreenFlipBuffersEx(0); + OSScreenFlipBuffersEx(1); + + s32 tickswait = time * FPS * 16; + + s32 sleepingtime = 16; + s32 times = tickswait/16; + s32 i=0; + + VPADData vpad_data; + s32 error; + u32 last = 0xFFFFFFFF; + s32 timer = 0; + while(i= combotime*FPS) { + result = vpad_data.btns_h; + break; + } + i++; + os_usleep(sleepingtime*1000); + } + + if(screenbuffer0 != NULL) { + free(screenbuffer0); + } + if(screenbuffer1 != NULL) { + free(screenbuffer1); + } + + return result; +} diff --git a/plugins/screenshot/src/retain_vars.cpp b/plugins/screenshot/src/retain_vars.cpp new file mode 100644 index 0000000..b36469d --- /dev/null +++ b/plugins/screenshot/src/retain_vars.cpp @@ -0,0 +1,3 @@ +#include "retain_vars.hpp" +wups_loader_app_status_t gAppStatus __attribute__((section(".data"))) = WUPS_APP_STATUS_UNKNOWN; +u32 gButtonCombo __attribute__((section(".data"))) = 0; diff --git a/plugins/screenshot/src/retain_vars.hpp b/plugins/screenshot/src/retain_vars.hpp new file mode 100644 index 0000000..8fad116 --- /dev/null +++ b/plugins/screenshot/src/retain_vars.hpp @@ -0,0 +1,10 @@ +#ifndef _RETAINS_VARS_H_ +#define _RETAINS_VARS_H_ + +#include +#include + +extern wups_loader_app_status_t gAppStatus; +extern u32 gButtonCombo; + +#endif // _RETAINS_VARS_H_ diff --git a/plugins/screenshot/src/screenshot_utils.cpp b/plugins/screenshot/src/screenshot_utils.cpp new file mode 100644 index 0000000..93a042a --- /dev/null +++ b/plugins/screenshot/src/screenshot_utils.cpp @@ -0,0 +1,222 @@ +#include "screenshot_utils.h" +#include +#include + +bool UnormR8G8B8A8TogdImage(gdImagePtr *gdImgTmp, void *image_data, u32 width, u32 rows, u32 pitch) { + *gdImgTmp = gdImageCreateTrueColor(width , rows ); + if(*gdImgTmp != NULL){ + R8G8B8A8_COLOR *buffer = (R8G8B8A8_COLOR *) image_data; + R8G8B8A8_COLOR val; + for (u32 row = 0; row < rows; ++row) { + for (u32 x = 0; x < width; ++x) { + val = buffer[row * pitch + x]; + gdImageSetPixel(*gdImgTmp, x , row , gdTrueColor(val.R, val.G, val.B)); + } + } + return true; + } + return false; +} + +bool saveAsJPEG(const char * path, u8 * sourceBuffer, u32 width, u32 height, u32 pitch, u32 format) { + if(path == NULL || sourceBuffer == NULL) { + DEBUG_FUNCTION_LINE("path or buffer NULL\n"); + return false; + } + + if(( format != GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_SRGB && + format != GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_UNORM)) { + DEBUG_FUNCTION_LINE("Format not supported\n"); + return false; + } + + gdImagePtr gdImagePtr = 0; + bool result = false; + + if(!UnormR8G8B8A8TogdImage(&gdImagePtr, sourceBuffer, width, height, pitch)){ + DEBUG_FUNCTION_LINE("Setting up the GD buffer failed\n"); + if(gdImagePtr != NULL){ + gdImageDestroy(gdImagePtr); + } + return false; + } + + int imd_size = 0; + void *data = gdImageJpegPtr(gdImagePtr, &imd_size, 95); + if (data) { + DEBUG_FUNCTION_LINE("Encoded file as JPEG. size = %d.\n",imd_size); + //FSUtils::CreateSubfolder("sd:/screenshots); + result = FSUtils::saveBufferToFile(path,data,imd_size); + if(!result){ + DEBUG_FUNCTION_LINE("Failed to save buffer to %s \n",path); + } + } + gdFree(data); + gdImageDestroy(gdImagePtr); + return result; +} + +bool copyBuffer(GX2ColorBuffer * sourceBuffer, GX2ColorBuffer * targetBuffer, u32 targetWidth, u32 targetHeight) { + // Making sure the buffers are not NULL + if (sourceBuffer != NULL && targetBuffer != NULL) { + GX2InitColorBuffer(targetBuffer, GX2_SURFACE_DIM_2D, targetWidth, targetHeight, 1, GX2_SURFACE_FORMAT_TCS_R8_G8_B8_A8_SRGB, GX2_AA_MODE_1X); + + // We need to override some values. + targetBuffer->surface.tile = GX2_TILE_MODE_LINEAR_ALIGNED; + targetBuffer->surface.use = GX2_SURFACE_USE_COLOR_BUFFER_TEXTURE; + targetBuffer->view_mip = 0; + targetBuffer->view_first_slice = 0; + targetBuffer->view_slices_count = 1; + GX2CalcSurfaceSizeAndAlignment(&targetBuffer->surface); + GX2InitColorBufferRegs(targetBuffer); + + // Let's allocate the memory. + targetBuffer->surface.image_data = memalign(targetBuffer->surface.align,targetBuffer->surface.image_size); + if(targetBuffer->surface.image_data == NULL) { + DEBUG_FUNCTION_LINE("failed to allocate memory.\n"); + return false; + } + DEBUG_FUNCTION_LINE("Allocated image data buffer. data %08X size %08X \n",targetBuffer->surface.image_data,targetBuffer->surface.image_size); + + GX2Invalidate(GX2_INVALIDATE_CPU, targetBuffer->surface.image_data, targetBuffer->surface.image_size); + if (sourceBuffer->surface.aa == GX2_AA_MODE_1X) { + // If AA is disabled, we can simply use GX2CopySurface. + GX2CopySurface(&sourceBuffer->surface, + sourceBuffer->view_mip, + sourceBuffer->view_first_slice, + &targetBuffer->surface, 0, 0); + } else { + // If AA is enabled, we need to resolve the AA buffer. + GX2Surface tempSurface; + tempSurface = sourceBuffer->surface; + tempSurface.aa = GX2_AA_MODE_1X; + GX2CalcSurfaceSizeAndAlignment(&tempSurface); + + tempSurface.image_data = memalign(tempSurface.align,tempSurface.image_size); + if(tempSurface.image_data == NULL) { + DEBUG_FUNCTION_LINE("failed to allocate data AA.\n"); + if(targetBuffer->surface.image_data != NULL) { + free(targetBuffer->surface.image_data); + targetBuffer->surface.image_data = NULL; + } + return false; + } + GX2ResolveAAColorBuffer(sourceBuffer,&tempSurface, 0, 0); + GX2CopySurface(&tempSurface, 0, 0,&targetBuffer->surface, 0, 0); + + // Sync CPU and GPU + GX2DrawDone(); + + if(tempSurface.image_data != NULL) { + free(tempSurface.image_data); + tempSurface.image_data = NULL; + } + } + return true; + } else { + DEBUG_FUNCTION_LINE("Couldn't copy buffer, pointer was NULL\n"); + return false; + } +} + + +bool takeScreenshot(GX2ColorBuffer *srcBuffer,const char * path) { + if(srcBuffer == NULL) { + return false; + } + DEBUG_FUNCTION_LINE("Taking screenshot. %s\n",path); + + GX2ColorBuffer colorBuffer; + GX2ColorBuffer * saveBuffer = NULL; + + // keep dimensions + u32 width = srcBuffer->surface.width; + u32 height = srcBuffer->surface.height; + + bool valid = false; + bool cancel = false; + bool low_memory = false; + do { + // At first we need to copy the buffer to fit our resolution. + if(saveBuffer == NULL) { + do { + valid = copyBuffer(srcBuffer,&colorBuffer,width,height); + // If the copying failed, we don't have enough memory. Let's decrease the resolution. + if(!valid) { + low_memory = true; + + if(height >= 1080) { + width = 1280; + height = 720; + DEBUG_FUNCTION_LINE("Switching to 720p.\n"); + } else if(height >= 720) { + width = 854; + height = 480; + DEBUG_FUNCTION_LINE("Switching to 480p.\n"); + } else if(height >= 480) { + width = 640; + height = 360; + DEBUG_FUNCTION_LINE("Switching to 360p.\n"); + } else { + // Cancel the screenshot if the resolution would be too low. + cancel = true; + break; + } + } else { + // On success save the pointer. + saveBuffer = &colorBuffer; + } + } while(!valid); + } + + // Check if we should proceed + if(cancel) { + // Free the memory on error. + if(colorBuffer.surface.image_data != NULL) { + free(colorBuffer.surface.image_data); + colorBuffer.surface.image_data = NULL; + } + return false; + } + + // Flush out destinations caches + GX2Invalidate(GX2_INVALIDATE_COLOR_BUFFER, colorBuffer.surface.image_data,colorBuffer.surface.image_size); + + // Wait for GPU to finish + GX2DrawDone(); + + DEBUG_FUNCTION_LINE("Trying to save.\n"); + + // Trying to save as JPEG. + valid = saveAsJPEG(path,(u8*) saveBuffer->surface.image_data,width,height,saveBuffer->surface.pitch,saveBuffer->surface.format); + + // Free the colorbuffer copy. + if(colorBuffer.surface.image_data != NULL) { + free(colorBuffer.surface.image_data); + colorBuffer.surface.image_data = NULL; + saveBuffer = NULL; + } + + // When taking the screenshot failed, decrease the resolution again ~. + if(!valid) { + low_memory = true; + if(height >= 1080) { + width = 1280; + height = 720; + DEBUG_FUNCTION_LINE("Switching to 720p.\n"); + } else if(height >= 720) { + width = 854; + height = 480; + DEBUG_FUNCTION_LINE("Switching to 480p.\n"); + } else if(height >= 480) { + width = 640; + height = 360; + DEBUG_FUNCTION_LINE("Switching to 360p.\n"); + } else { + return false; + } + } + } while(!valid); + + return true; +} diff --git a/plugins/screenshot/src/screenshot_utils.h b/plugins/screenshot/src/screenshot_utils.h new file mode 100644 index 0000000..abb857f --- /dev/null +++ b/plugins/screenshot/src/screenshot_utils.h @@ -0,0 +1,20 @@ +#ifndef _SCREENSHOT_UTILS_H_ +#define _SCREENSHOT_UTILS_H_ + +#include +#include +#include + +typedef struct _R8G8B8A8_COLOR { + u8 R, G, B, A; +} R8G8B8A8_COLOR; + +bool UnormR8G8B8A8TogdImage(gdImagePtr *gdImgTmp, void *image_data, u32 width, u32 rows, u32 pitch); + +bool saveAsJPEG(const char * path, u8 * sourceBuffer, u32 width, u32 height, u32 pitch, u32 format); + +bool copyBuffer(GX2ColorBuffer * sourceBuffer, GX2ColorBuffer * targetBuffer, u32 targetWidth, u32 targetHeight); + +bool takeScreenshot(GX2ColorBuffer *srcBuffer,const char * path); + +#endif