From 90d754f920e099d581d250145eeb00e83303dac0 Mon Sep 17 00:00:00 2001 From: HookedBehemoth Date: Mon, 20 Apr 2020 11:07:37 +0200 Subject: [PATCH] jpegdec reimplementation (#912) * add jpegdec reimplementation * reduce work memory * fix color space * jpegdec: cleanup results to use atmosphere style * fix outdated comments, correct do/while bug Co-authored-by: Michael Scire --- Makefile | 3 + .../impl/ams_system_thread_definitions.hpp | 3 + .../libvapours/include/vapours/results.hpp | 1 + .../vapours/results/capsrv_results.hpp | 91 +++++++++++ stratosphere/Makefile | 2 +- stratosphere/jpegdec/Makefile | 132 ++++++++++++++++ stratosphere/jpegdec/jpegdec.json | 97 ++++++++++++ .../jpegdec/source/impl/jpegdec_turbo.cpp | 148 ++++++++++++++++++ .../jpegdec/source/impl/jpegdec_turbo.hpp | 43 +++++ .../jpegdec/source/jpegdec_decode_service.cpp | 79 ++++++++++ .../jpegdec/source/jpegdec_decode_service.hpp | 34 ++++ stratosphere/jpegdec/source/jpegdec_main.cpp | 103 ++++++++++++ 12 files changed, 735 insertions(+), 1 deletion(-) create mode 100644 libraries/libvapours/include/vapours/results/capsrv_results.hpp create mode 100644 stratosphere/jpegdec/Makefile create mode 100644 stratosphere/jpegdec/jpegdec.json create mode 100644 stratosphere/jpegdec/source/impl/jpegdec_turbo.cpp create mode 100644 stratosphere/jpegdec/source/impl/jpegdec_turbo.hpp create mode 100644 stratosphere/jpegdec/source/jpegdec_decode_service.cpp create mode 100644 stratosphere/jpegdec/source/jpegdec_decode_service.hpp create mode 100644 stratosphere/jpegdec/source/jpegdec_main.cpp diff --git a/Makefile b/Makefile index b9bff6200..33a6670d9 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ dist-no-debug: all mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000034 mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000036 mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037 + mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/010000000000003C mkdir -p atmosphere-$(AMSVER)/atmosphere/fatal_errors mkdir -p atmosphere-$(AMSVER)/atmosphere/config_templates mkdir -p atmosphere-$(AMSVER)/atmosphere/config @@ -88,6 +89,7 @@ dist-no-debug: all cp stratosphere/fatal/fatal.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000034/exefs.nsp cp stratosphere/creport/creport.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000036/exefs.nsp cp stratosphere/ro/ro.nsp atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/exefs.nsp + cp stratosphere/jpedec/jpedec.nsp atmosphere-$(AMSVER)/atmosphere/contents/010000000000003C/exefs.nsp mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags touch atmosphere-$(AMSVER)/atmosphere/contents/0100000000000032/flags/boot2.flag mkdir -p atmosphere-$(AMSVER)/atmosphere/contents/0100000000000037/flags @@ -136,6 +138,7 @@ dist: dist-no-debug cp stratosphere/sm/sm.elf atmosphere-$(AMSVER)-debug/sm.elf cp stratosphere/spl/spl.elf atmosphere-$(AMSVER)-debug/spl.elf cp stratosphere/erpt/erpt.elf atmosphere-$(AMSVER)-debug/erpt.elf + cp stratosphere/jpegdec/jpegdec.elf atmosphere-$(AMSVER)-debug/jpegdec.elf cd atmosphere-$(AMSVER)-debug; zip -r ../atmosphere-$(AMSVER)-debug.zip ./*; cd ../; rm -r atmosphere-$(AMSVER)-debug mv atmosphere-$(AMSVER)-debug.zip out/atmosphere-$(AMSVER)-debug.zip diff --git a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp index c627462eb..82da25ee9 100644 --- a/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp +++ b/libraries/libstratosphere/include/stratosphere/ams/impl/ams_system_thread_definitions.hpp @@ -102,6 +102,9 @@ namespace ams::impl { AMS_DEFINE_SYSTEM_THREAD(21, erpt, Main); AMS_DEFINE_SYSTEM_THREAD(21, erpt, IpcServer); + /* jpegdec. */ + AMS_DEFINE_SYSTEM_THREAD(21, jpegdec, Main); + /* pgl. */ AMS_DEFINE_SYSTEM_THREAD(21, pgl, Main); AMS_DEFINE_SYSTEM_THREAD(21, pgl, ProcessControlTask); diff --git a/libraries/libvapours/include/vapours/results.hpp b/libraries/libvapours/include/vapours/results.hpp index aed86ea51..fbebb06b3 100644 --- a/libraries/libvapours/include/vapours/results.hpp +++ b/libraries/libvapours/include/vapours/results.hpp @@ -24,6 +24,7 @@ /* Official. */ #include +#include #include #include #include diff --git a/libraries/libvapours/include/vapours/results/capsrv_results.hpp b/libraries/libvapours/include/vapours/results/capsrv_results.hpp new file mode 100644 index 000000000..809b18215 --- /dev/null +++ b/libraries/libvapours/include/vapours/results/capsrv_results.hpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::capsrv { + + R_DEFINE_NAMESPACE_RESULT_MODULE(206); + + R_DEFINE_ERROR_RANGE(AlbumError, 2, 99); + R_DEFINE_ERROR_RESULT(AlbumWorkMemoryError, 3); + + R_DEFINE_ERROR_RESULT(AlbumAlreadyOpened, 7); + R_DEFINE_ERROR_RESULT(AlbumOutOfRange, 8); + + R_DEFINE_ERROR_RANGE(AlbumInvalidFileId, 10, 19); + R_DEFINE_ERROR_RESULT(AlbumInvalidApplicationId, 11); + R_DEFINE_ERROR_RESULT(AlbumInvalidTimestamp, 12); + R_DEFINE_ERROR_RESULT(AlbumInvalidStorage, 13); + R_DEFINE_ERROR_RESULT(AlbumInvalidFileContents, 14); + + R_DEFINE_ERROR_RESULT(AlbumIsNotMounted, 21); + R_DEFINE_ERROR_RESULT(AlbumIsFull, 22); + R_DEFINE_ERROR_RESULT(AlbumFileNotFound, 23); + R_DEFINE_ERROR_RESULT(AlbumInvalidFileData, 24); + R_DEFINE_ERROR_RESULT(AlbumFileCountLimit, 25); + R_DEFINE_ERROR_RESULT(AlbumFileNoThumbnail, 26); + + R_DEFINE_ERROR_RESULT(AlbumReadBufferShortage, 30); + + R_DEFINE_ERROR_RANGE(AlbumFileSystemError, 90, 99); + R_DEFINE_ERROR_RANGE(AlbumAccessCorrupted, 94, 96); + R_DEFINE_ERROR_RESULT(AlbumDestinationAccessCorrupted, 96); + + R_DEFINE_ERROR_RANGE(ControlError, 800, 899); + R_DEFINE_ERROR_RESULT(ControlResourceLimit, 820); + R_DEFINE_ERROR_RESULT(ControlNotOpened, 822); + + R_DEFINE_ERROR_RESULT(NotSupported, 1023); + + R_DEFINE_ERROR_RANGE(InternalError, 1024, 2047); + R_DEFINE_ERROR_RESULT(InternalJpegWorkMemoryShortage, 1212); + + R_DEFINE_ERROR_RANGE(InternalFileDataVerificationError, 1300, 1399); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationEmptyFileData, 1301); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationExifExtractionFailed, 1302); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationExifAnalyzationFailed, 1303); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationDateTimeExtractionFailed, 1304); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInvalidDateTimeLength, 1305); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentDateTime, 1306); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationMakerNoteExtractionFailed, 1307); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentApplicationId, 1308); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentSignature, 1309); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationUnsupportedOrientation, 1310); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInvalidDataDimension, 1311); + R_DEFINE_ERROR_RESULT(InternalFileDataVerificationInconsistentOrientation, 1312); + + R_DEFINE_ERROR_RANGE(InternalAlbumLimitationError, 1400, 1499); + R_DEFINE_ERROR_RESULT(InternalAlbumLimitationFileCountLimit, 1401); + + R_DEFINE_ERROR_RANGE(InternalSignatureError, 1500, 1599); + R_DEFINE_ERROR_RESULT(InternalSignatureExifExtractionFailed, 1501); + R_DEFINE_ERROR_RESULT(InternalSignatureMakerNoteExtractionFailed, 1502); + + R_DEFINE_ERROR_RANGE(InternalAlbumSessionError, 1700, 1799); + R_DEFINE_ERROR_RESULT(InternalAlbumLimitationSessionCountLimit, 1701); + + R_DEFINE_ERROR_RANGE(InternalAlbumTemporaryFileError, 1900, 1999); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCountLimit, 1901); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCreateError, 1902); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileCreateRetryCountLimit, 1903); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileOpenError, 1904); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileGetFileSizeError, 1905); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileSetFileSizeError, 1906); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileReadFileError, 1907); + R_DEFINE_ERROR_RESULT(InternalAlbumTemporaryFileWriteFileError, 1908); + +} diff --git a/stratosphere/Makefile b/stratosphere/Makefile index ecda499cf..314ccc50c 100644 --- a/stratosphere/Makefile +++ b/stratosphere/Makefile @@ -1,4 +1,4 @@ -MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt +MODULES := loader ncm pm sm boot ams_mitm spl eclct.stub ro creport fatal dmnt boot2 erpt jpegdec SUBFOLDERS := $(MODULES) diff --git a/stratosphere/jpegdec/Makefile b/stratosphere/jpegdec/Makefile new file mode 100644 index 000000000..9ddacd7e6 --- /dev/null +++ b/stratosphere/jpegdec/Makefile @@ -0,0 +1,132 @@ +#--------------------------------------------------------------------------------- +# pull in common stratosphere sysmodule configuration +#--------------------------------------------------------------------------------- +include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../../libraries/config/templates/stratosphere.mk + +#--------------------------------------------------------------------------------- +# jpegdec uses libjpeg. +#--------------------------------------------------------------------------------- +LIBS += -ljpeg + +#--------------------------------------------------------------------------------- +# 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)) + +export DEPSDIR := $(CURDIR)/$(BUILD) + +CFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.c)) $(notdir $(wildcard $(dir)/*.board.*.c)) $(notdir $(wildcard $(dir)/*.os.*.c)), \ + $(notdir $(wildcard $(dir)/*.c)))) +CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).c))) +CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).c))) +CFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).c))) + +CPPFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.cpp)) $(notdir $(wildcard $(dir)/*.board.*.cpp)) $(notdir $(wildcard $(dir)/*.os.*.cpp)), \ + $(notdir $(wildcard $(dir)/*.cpp)))) +CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).cpp))) +CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).cpp))) +CPPFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).cpp))) + +SFILES := $(foreach dir,$(SOURCES),$(filter-out $(notdir $(wildcard $(dir)/*.arch.*.s)) $(notdir $(wildcard $(dir)/*.board.*.s)) $(notdir $(wildcard $(dir)/*.os.*.s)), \ + $(notdir $(wildcard $(dir)/*.s)))) +SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.arch.$(ATMOSPHERE_ARCH_NAME).s))) +SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.board.$(ATMOSPHERE_BOARD_NAME).s))) +SFILES += $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.os.$(ATMOSPHERE_OS_NAME).s))) + +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(addsuffix .o,$(BINFILES)) \ + $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o) + +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) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(CONFIG_JSON)),) + jsons := $(wildcard *.json) + ifneq (,$(findstring $(TARGET).json,$(jsons))) + export APP_JSON := $(TOPDIR)/$(TARGET).json + else + ifneq (,$(findstring config.json,$(jsons))) + export APP_JSON := $(TOPDIR)/config.json + endif + endif +else + export APP_JSON := $(TOPDIR)/$(CONFIG_JSON) +endif + +.PHONY: $(BUILD) clean all + +#--------------------------------------------------------------------------------- +all: $(BUILD) + +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(TARGET).nsp $(TARGET).npdm $(TARGET).nso $(TARGET).elf + + +#--------------------------------------------------------------------------------- +else +.PHONY: all + +DEPENDS := $(OFILES:.o=.d) + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).nsp + +ifeq ($(strip $(APP_JSON)),) +$(OUTPUT).nsp : $(OUTPUT).nso +else +$(OUTPUT).nsp : $(OUTPUT).nso $(OUTPUT).npdm +endif + +$(OUTPUT).nso : $(OUTPUT).elf + +$(OUTPUT).elf : $(OFILES) + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/stratosphere/jpegdec/jpegdec.json b/stratosphere/jpegdec/jpegdec.json new file mode 100644 index 000000000..43b4fdc07 --- /dev/null +++ b/stratosphere/jpegdec/jpegdec.json @@ -0,0 +1,97 @@ +{ + "name": "jpegdec", + "title_id": "0x010000000000003c", + "title_id_range_min": "0x010000000000003c", + "title_id_range_max": "0x010000000000003c", + "main_thread_stack_size": "0x00004000", + "main_thread_priority": 49, + "default_cpu_id": 3, + "process_category": 0, + "is_retail": true, + "pool_partition": 2, + "is_64_bit": true, + "address_space_type": 3, + "filesystem_access": { + "permissions": "0x0" + }, + "service_access": ["fatal:u", "lm"], + "service_host": ["caps:dc"], + "kernel_capabilities": [{ + "type": "kernel_flags", + "value": { + "highest_thread_priority": 63, + "lowest_thread_priority": 24, + "lowest_cpu_id": 3, + "highest_cpu_id": 3 + } + }, { + "type": "syscalls", + "value": { + "svcSetHeapSize": "0x01", + "svcSetMemoryPermission": "0x02", + "svcSetMemoryAttribute": "0x03", + "svcMapMemory": "0x04", + "svcUnmapMemory": "0x05", + "svcQueryMemory": "0x06", + "svcExitProcess": "0x07", + "svcCreateThread": "0x08", + "svcStartThread": "0x09", + "svcExitThread": "0x0a", + "svcSleepThread": "0x0b", + "svcGetThreadPriority": "0x0c", + "svcSetThreadPriority": "0x0d", + "svcGetThreadCoreMask": "0x0e", + "svcSetThreadCoreMask": "0x0f", + "svcGetCurrentProcessorNumber": "0x10", + "svcSignalEvent": "0x11", + "svcClearEvent": "0x12", + "svcMapSharedMemory": "0x13", + "svcUnmapSharedMemory": "0x14", + "svcCreateTransferMemory": "0x15", + "svcCloseHandle": "0x16", + "svcResetSignal": "0x17", + "svcWaitSynchronization": "0x18", + "svcCancelSynchronization": "0x19", + "svcArbitrateLock": "0x1a", + "svcArbitrateUnlock": "0x1b", + "svcWaitProcessWideKeyAtomic": "0x1c", + "svcSignalProcessWideKey": "0x1d", + "svcGetSystemTick": "0x1e", + "svcConnectToNamedPort": "0x1f", + "svcSendSyncRequestLight": "0x20", + "svcSendSyncRequest": "0x21", + "svcSendSyncRequestWithUserBuffer": "0x22", + "svcSendAsyncRequestWithUserBuffer": "0x23", + "svcGetProcessId": "0x24", + "svcGetThreadId": "0x25", + "svcBreak": "0x26", + "svcOutputDebugString": "0x27", + "svcReturnFromException": "0x28", + "svcGetInfo": "0x29", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcCreateInterruptEvent": "0x53", + "svcReadWriteRegister": "0x4E", + "svcQueryIoMapping": "0x55", + "svcCreateDeviceAddressSpace": "0x56", + "svcAttachDeviceAddressSpace": "0x57", + "svcDetachDeviceAddressSpace": "0x58", + "svcMapDeviceAddressSpaceAligned": "0x5a", + "svcUnmapDeviceAddressSpace": "0x5c", + "svcGetSystemInfo": "0x6f", + "svcCallSecureMonitor": "0x7F" + } + }, { + "type": "min_kernel_version", + "value": "0x0030" + }, { + "type": "handle_table_size", + "value": 16 + }] +} \ No newline at end of file diff --git a/stratosphere/jpegdec/source/impl/jpegdec_turbo.cpp b/stratosphere/jpegdec/source/impl/jpegdec_turbo.cpp new file mode 100644 index 000000000..d36b6190e --- /dev/null +++ b/stratosphere/jpegdec/source/impl/jpegdec_turbo.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "jpegdec_turbo.hpp" + +#include + +namespace ams::jpegdec::impl { + + #define CAPSRV_ABORT_UNLESS(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + AMS_ABORT_UNLESS(__capsrv_assert_res); \ + } while (0) + + #define CAPSRV_ASSERT(expr) do { \ + const bool __capsrv_assert_res = (expr); \ + AMS_ASSERT(__capsrv_assert_res); \ + R_UNLESS(__capsrv_assert_res, capsrv::ResultAlbumError()); \ + } while (0) + + namespace { + + constexpr size_t LinebufferCount = 4; + constexpr size_t ColorComponents = 3; + + constexpr int ImageSizeHorizonalUnit = 0x10; + constexpr int ImageSizeVerticalUnit = 0x4; + + struct RGB { + u8 r, g, b; + }; + + struct RGBX { + u8 r, g, b, x; + }; + + void JpegErrorExit(j_common_ptr cinfo) { + /* ? */ + } + } + + Result DecodeJpeg(DecodeOutput &out, const DecodeInput &in, u8 *work, size_t work_size) { + CAPSRV_ABORT_UNLESS(util::IsAligned(in.width, ImageSizeHorizonalUnit)); + CAPSRV_ABORT_UNLESS(util::IsAligned(in.height, ImageSizeVerticalUnit)); + + CAPSRV_ABORT_UNLESS(out.bmp != nullptr); + CAPSRV_ABORT_UNLESS(out.bmp_size >= 4 * in.width * in.height); + + CAPSRV_ABORT_UNLESS(out.width != nullptr); + CAPSRV_ABORT_UNLESS(out.height != nullptr); + + const size_t linebuffer_size = ColorComponents * in.width; + const size_t total_linebuffer_size = LinebufferCount * linebuffer_size; + R_UNLESS(work_size >= total_linebuffer_size, capsrv::ResultInternalJpegWorkMemoryShortage()); + + jpeg_decompress_struct cinfo; + std::memset(&cinfo, 0, sizeof(cinfo)); + + jpeg_error_mgr jerr; + std::memset(&jerr, 0, sizeof(jerr)); + + cinfo.err = jpeg_std_error(&jerr); + jerr.error_exit = JpegErrorExit; + + /* TODO: Here Nintendo uses setjmp, on longjmp to error ResultAlbumInvalidFileData is returned. */ + + jpeg_create_decompress(&cinfo); + + ON_SCOPE_EXIT { + jpeg_destroy_decompress(&cinfo); + }; + + jpeg_mem_src(&cinfo, in.jpeg, in.jpeg_size); + + R_UNLESS(jpeg_read_header(&cinfo, true) == JPEG_HEADER_OK, capsrv::ResultAlbumInvalidFileData()); + + R_UNLESS(cinfo.image_width == in.width, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(cinfo.image_height == in.height, capsrv::ResultAlbumInvalidFileData()); + + cinfo.out_color_space = JCS_RGB; + cinfo.dct_method = JDCT_ISLOW; + cinfo.do_fancy_upsampling = in.fancy_upsampling; + cinfo.do_block_smoothing = in.block_smoothing; + + R_UNLESS(jpeg_start_decompress(&cinfo) == TRUE, capsrv::ResultAlbumInvalidFileData()); + + CAPSRV_ASSERT(cinfo.output_width == in.width); + CAPSRV_ASSERT(cinfo.output_height == in.height); + CAPSRV_ASSERT(cinfo.out_color_components == ColorComponents); + CAPSRV_ASSERT(cinfo.output_components == ColorComponents); + + /* Pointer to output. */ + RGBX *bmp = reinterpret_cast(out.bmp); + + /* Decode 4 lines at once. */ + u8 *linebuffer[4] = { + work + 0 * linebuffer_size, + work + 1 * linebuffer_size, + work + 2 * linebuffer_size, + work + 3 * linebuffer_size, + }; + + /* While we still have scanlines, parse! */ + while (cinfo.output_scanline < cinfo.output_height) { + /* Decode scanlines. */ + int parsed = jpeg_read_scanlines(&cinfo, linebuffer, 4); + CAPSRV_ASSERT(parsed <= ImageSizeVerticalUnit); + + /* Line by line */ + for (int index = 0; index < parsed; index++) { + u8 *buffer = linebuffer[index]; + const RGB* rgb = reinterpret_cast(buffer); + for (u32 i = 0; i < in.width; i++) { + /* Fill output. */ + bmp->r = rgb->r; + bmp->g = rgb->g; + bmp->b = rgb->b; + bmp->x = 0xFF; + + /* Traverse buffer. */ + bmp++; + rgb++; + } + } + } + + R_UNLESS(jpeg_finish_decompress(&cinfo) == TRUE, capsrv::ResultAlbumInvalidFileData()); + + *out.width = cinfo.output_width; + *out.height = cinfo.output_height; + + return ResultSuccess(); + } + +} \ No newline at end of file diff --git a/stratosphere/jpegdec/source/impl/jpegdec_turbo.hpp b/stratosphere/jpegdec/source/impl/jpegdec_turbo.hpp new file mode 100644 index 000000000..4f728b74b --- /dev/null +++ b/stratosphere/jpegdec/source/impl/jpegdec_turbo.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::jpegdec::impl { + + struct DecodeInput { + const u8 *jpeg; + size_t jpeg_size; + u32 width; + u32 height; + bool fancy_upsampling; + bool block_smoothing; + }; + + struct DecodeOutput { + u32 *width; + u32 *height; + u8 *bmp; + size_t bmp_size; + }; + + struct Dimensions { + u32 width, height; + }; + + Result DecodeJpeg(DecodeOutput &out, const DecodeInput &in, u8 *work, size_t work_size); + +} \ No newline at end of file diff --git a/stratosphere/jpegdec/source/jpegdec_decode_service.cpp b/stratosphere/jpegdec/source/jpegdec_decode_service.cpp new file mode 100644 index 000000000..18882faa8 --- /dev/null +++ b/stratosphere/jpegdec/source/jpegdec_decode_service.cpp @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "jpegdec_decode_service.hpp" +#include "impl/jpegdec_turbo.hpp" + +namespace ams::jpegdec { + + namespace { + + /* Enough for four linebuffers RGB. */ + u8 g_workmem[0x3C00]; + + } + + Result DecodeService::DecodeJpeg(const sf::OutNonSecureBuffer &out, const sf::InBuffer &in, u32 width, u32 height, const CapsScreenShotDecodeOption &opts) { + u8 *bmp = out.GetPointer(); + size_t bmp_size = out.GetSize(); + const u8 *jpeg = in.GetPointer(); + size_t jpeg_size = in.GetSize(); + + /* Clear the work memory and out buffer. */ + std::memset(g_workmem, 0, sizeof(g_workmem)); + std::memset(bmp, 0, bmp_size); + + /* Clear output memory on decode failure. */ + auto clear_guard = SCOPE_GUARD { std::memset(bmp, 0, bmp_size); }; + + R_UNLESS(util::IsAligned(width, 0x10), capsrv::ResultAlbumOutOfRange()); + R_UNLESS(util::IsAligned(height, 0x4), capsrv::ResultAlbumOutOfRange()); + + R_UNLESS(bmp != nullptr, capsrv::ResultAlbumReadBufferShortage()); + R_UNLESS(bmp_size >= 4 * width * height, capsrv::ResultAlbumReadBufferShortage()); + + R_UNLESS(jpeg != nullptr, capsrv::ResultAlbumInvalidFileData()); + R_UNLESS(jpeg_size != 0, capsrv::ResultAlbumInvalidFileData()); + + impl::DecodeInput decode_input = { + .jpeg = jpeg, + .jpeg_size = jpeg_size, + .width = width, + .height = height, + .fancy_upsampling = bool(opts.fancy_upsampling), + .block_smoothing = bool(opts.block_smoothing), + }; + + /* Official software ignores output written to this struct. */ + impl::Dimensions dims = {}; + + impl::DecodeOutput decode_output = { + .width = &dims.width, + .height = &dims.height, + .bmp = bmp, + .bmp_size = bmp_size, + }; + + + /* Decode the jpeg. */ + R_TRY(impl::DecodeJpeg(decode_output, decode_input, g_workmem, sizeof(g_workmem))); + clear_guard.Cancel(); + + /* Clear the work memory. */ + std::memset(g_workmem, 0, sizeof(g_workmem)); + + return ResultSuccess(); + } +} \ No newline at end of file diff --git a/stratosphere/jpegdec/source/jpegdec_decode_service.hpp b/stratosphere/jpegdec/source/jpegdec_decode_service.hpp new file mode 100644 index 000000000..32e802090 --- /dev/null +++ b/stratosphere/jpegdec/source/jpegdec_decode_service.hpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#pragma once +#include + +namespace ams::jpegdec { + + class DecodeService final : public sf::IServiceObject { + protected: + enum class CommandId { + DecodeJpeg = 3001, + }; + public: + /* Actual commands. */ + virtual Result DecodeJpeg(const sf::OutNonSecureBuffer &out, const sf::InBuffer &in, u32 width, u32 height, const CapsScreenShotDecodeOption &opts); + public: + DEFINE_SERVICE_DISPATCH_TABLE { + MAKE_SERVICE_COMMAND_META(DecodeJpeg) + }; + }; +} \ No newline at end of file diff --git a/stratosphere/jpegdec/source/jpegdec_main.cpp b/stratosphere/jpegdec/source/jpegdec_main.cpp new file mode 100644 index 000000000..ef649e85c --- /dev/null +++ b/stratosphere/jpegdec/source/jpegdec_main.cpp @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2018-2019 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include "jpegdec_decode_service.hpp" + +extern "C" { + extern u32 __start__; + + u32 __nx_applet_type = AppletType_None; + + #define INNER_HEAP_SIZE 0x18000 + size_t nx_inner_heap_size = INNER_HEAP_SIZE; + char nx_inner_heap[INNER_HEAP_SIZE]; + + void __libnx_initheap(void); + void __appInit(void); + void __appExit(void); + + /* Exception handling. */ + alignas(16) u8 __nx_exception_stack[ams::os::MemoryPageSize]; + u64 __nx_exception_stack_size = sizeof(__nx_exception_stack); + void __libnx_exception_handler(ThreadExceptionDump *ctx); +} + +namespace ams { + ncm::ProgramId CurrentProgramId = ncm::SystemProgramId::JpegDec; + + namespace result { + + bool CallFatalOnResultAssertion = true; + + } + +} + +using namespace ams; + +void __libnx_exception_handler(ThreadExceptionDump *ctx) { + ams::CrashHandler(ctx); +} + +void __libnx_initheap(void) { + void* addr = nx_inner_heap; + size_t size = nx_inner_heap_size; + + /* Newlib */ + extern char* fake_heap_start; + extern char* fake_heap_end; + + fake_heap_start = (char*)addr; + fake_heap_end = (char*)addr + size; +} + +void __appInit(void) { + hos::InitializeForStratosphere(); + ams::CheckApiVersion(); +} + +void __appExit(void) { + /* ... */ +} + +namespace { + constexpr size_t NumServers = 1; + sf::hipc::ServerManager g_server_manager; + + /* NOTE: Official code only allows for one session. */ + constexpr sm::ServiceName DecodeServiceName = sm::ServiceName::Encode("caps:dc"); + constexpr size_t DecodeMaxSessions = 2; + +} + +int main(int argc, char **argv) +{ + /* Set thread name. */ + os::SetThreadNamePointer(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_NAME(jpegdec, Main)); + + /* Official jpegdec changes its thread priority to 21 in main. */ + /* This is because older versions of the sysmodule had priority 20 in npdm. */ + os::ChangeThreadPriority(os::GetCurrentThread(), AMS_GET_SYSTEM_THREAD_PRIORITY(jpegdec, Main)); + AMS_ASSERT(os::GetThreadPriority(os::GetCurrentThread()) == AMS_GET_SYSTEM_THREAD_PRIORITY(jpegdec, Main)); + + /* Create service. */ + R_ASSERT(g_server_manager.RegisterServer(DecodeServiceName, DecodeMaxSessions)); + + /* Loop forever, servicing our services. */ + g_server_manager.LoopProcess(); + + /* Cleanup */ + return 0; +}