#include "stdafx.h" #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" #include "cellos/sys_fs.h" #include "png.h" #include "cellPng.h" #include "cellPngDec.h" #if PNG_LIBPNG_VER_MAJOR >= 1 && (PNG_LIBPNG_VER_MINOR < 5 || (PNG_LIBPNG_VER_MINOR == 5 && PNG_LIBPNG_VER_RELEASE < 7)) #define PNG_ERROR_ACTION_NONE 1 #define PNG_RGB_TO_GRAY_DEFAULT (-1) #endif #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 typedef png_bytep iCCP_profile_type; #else typedef png_charp iCCP_profile_type; #endif // Temporarily #ifndef _MSC_VER #pragma GCC diagnostic ignored "-Wunused-parameter" #endif LOG_CHANNEL(cellPngDec); template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](CellPngDecError value) { switch (value) { STR_CASE(CELL_PNGDEC_ERROR_HEADER); STR_CASE(CELL_PNGDEC_ERROR_STREAM_FORMAT); STR_CASE(CELL_PNGDEC_ERROR_ARG); STR_CASE(CELL_PNGDEC_ERROR_SEQ); STR_CASE(CELL_PNGDEC_ERROR_BUSY); STR_CASE(CELL_PNGDEC_ERROR_FATAL); STR_CASE(CELL_PNGDEC_ERROR_OPEN_FILE); STR_CASE(CELL_PNGDEC_ERROR_SPU_UNSUPPORT); STR_CASE(CELL_PNGDEC_ERROR_SPU_ERROR); STR_CASE(CELL_PNGDEC_ERROR_CB_PARAM); } return unknown; }); } // cellPngDec aliases to improve readability using PPHandle = vm::pptr; using PHandle = vm::ptr; using PThreadInParam = vm::cptr; using PThreadOutParam = vm::ptr; using PExtThreadInParam = vm::cptr; using PExtThreadOutParam = vm::ptr; using PPStream = vm::pptr; using PStream = vm::ptr; using PSrc = vm::cptr; using POpenInfo = vm::ptr; using POpenParam = vm::cptr; using PInfo = vm::ptr; using PExtInfo = vm::ptr; using PInParam = vm::cptr; using POutParam = vm::ptr; using PExtInParam = vm::cptr; using PExtOutParam = vm::ptr; using PDataControlParam = vm::cptr; using PDataOutInfo = vm::ptr; using PCbControlDisp = vm::cptr; using PCbControlStream = vm::cptr; using PDispParam = vm::ptr; // Custom read function for libpng, so we could decode images from a buffer void pngDecReadBuffer(png_structp png_ptr, png_bytep out, png_size_t length) { // Get the IO pointer png_voidp io_ptr = png_get_io_ptr(png_ptr); // Check if obtaining of the IO pointer failed if (!io_ptr) { cellPngDec.error("Failed to obtain the io_ptr failed."); return; } // Cast the IO pointer to our custom structure PngBuffer& buffer = *static_cast(io_ptr); // Read froma file or a buffer if (buffer.file) { // Get the file auto file = idm::get_unlocked(buffer.fd); // Read the data file->file.read(out, length); } else { // Get the current data pointer, including the current cursor position void* data = static_cast(buffer.data.get_ptr()) + buffer.cursor; // Copy the length of the current data pointer to the output memcpy(out, data, length); // Increment the cursor for the next time buffer.cursor += length; } } void pngDecRowCallback(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass) { PngStream* stream = static_cast(png_get_progressive_ptr(png_ptr)); if (!stream) { cellPngDec.error("Failed to obtain streamPtr in rowCallback."); return; } // we have to check this everytime as this func can be called multiple times per row, and/or only once per row if (stream->nextRow + stream->outputCounts == row_num) stream->nextRow = row_num; if (stream->ppuContext && (stream->nextRow == row_num || pass > 0)) { if (pass > 0) { stream->cbDispInfo->scanPassCount = pass; stream->cbDispInfo->nextOutputStartY = row_num; } else { stream->cbDispInfo->scanPassCount = 0; stream->cbDispInfo->nextOutputStartY = 0; } stream->cbDispInfo->outputImage = stream->cbDispParam->nextOutputImage; stream->cbCtrlDisp.cbCtrlDispFunc(*stream->ppuContext, stream->cbDispInfo, stream->cbDispParam, stream->cbCtrlDisp.cbCtrlDispArg); stream->cbDispInfo->outputStartY = row_num; } u8* data; if (pass > 0) data = static_cast(stream->cbDispParam->nextOutputImage.get_ptr()); else data = static_cast(stream->cbDispParam->nextOutputImage.get_ptr()) + ((row_num - stream->cbDispInfo->outputStartY) * stream->cbDispInfo->outputFrameWidthByte); png_progressive_combine_row(png_ptr, data, new_row); } void pngDecInfoCallback(png_structp png_ptr, png_infop info) { PngStream* stream = static_cast(png_get_progressive_ptr(png_ptr)); if (!stream) { cellPngDec.error("Failed to obtain streamPtr in rowCallback."); return; } const usz remaining = png_process_data_pause(png_ptr, false); stream->buffer->cursor += (stream->buffer->length - remaining); } void pngDecEndCallback(png_structp png_ptr, png_infop info) { PngStream* stream = static_cast(png_get_progressive_ptr(png_ptr)); if (!stream) { cellPngDec.error("Failed to obtain streamPtr in endCallback."); return; } stream->endOfFile = true; } // Custom error handler for libpng [[noreturn]] void pngDecError(png_structp png_ptr, png_const_charp error_message) { cellPngDec.error("%s", error_message); // we can't return here or libpng blows up fmt::throw_exception("Fatal Error in libpng: %s", error_message); } // Custom warning handler for libpng void pngDecWarning(png_structp png_ptr, png_const_charp error_message) { cellPngDec.warning("%s", error_message); } // Get the chunk information of the PNG file. IDAT is marked as existing, only after decoding or reading the header. // Bits (if set indicates existence of the chunk): // 0 - gAMA // 1 - sBIT // 2 - cHRM // 3 - PLTE // 4 - tRNS // 5 - bKGD // 6 - hIST // 7 - pHYs // 8 - oFFs // 9 - tIME // 10 - pCAL // 11 - sRGB // 12 - iCCP // 13 - sPLT // 14 - sCAL // 15 - IDAT // 16:30 - reserved be_t pngDecGetChunkInformation(PngStream* stream, bool IDAT = false) { // The end result of the chunk information (bigger-endian) be_t chunk_information = 0; // Needed pointers for getting the chunk information f64 gamma; f64 red_x; f64 red_y; f64 green_x; f64 green_y; f64 blue_x; f64 blue_y; f64 white_x; f64 white_y; f64 width; f64 height; s32 intent; s32 num_trans; s32 num_palette; s32 unit_type; s32 type; s32 nparams; s32 compression_type; s32 unit; u16* hist; png_uint_32 proflen; iCCP_profile_type profile; png_bytep trans_alpha; png_charp units; png_charp name; png_charp purpose; png_charpp params; png_int_32 X0; png_int_32 X1; png_int_32 offset_x; png_int_32 offset_y; png_uint_32 res_x; png_uint_32 res_y; png_colorp palette; png_color_8p sig_bit; png_color_16p background; png_color_16p trans_color; png_sPLT_tp entries; png_timep mod_time; // Get chunk information and set the appropriate bits if (png_get_gAMA(stream->png_ptr, stream->info_ptr, &gamma)) { chunk_information |= 1 << 0; // gAMA } if (png_get_sBIT(stream->png_ptr, stream->info_ptr, &sig_bit)) { chunk_information |= 1 << 1; // sBIT } if (png_get_cHRM(stream->png_ptr, stream->info_ptr, &white_x, &white_y, &red_x, &red_y, &green_x, &green_y, &blue_x, &blue_y)) { chunk_information |= 1 << 2; // cHRM } if (png_get_PLTE(stream->png_ptr, stream->info_ptr, &palette, &num_palette)) { chunk_information |= 1 << 3; // PLTE } if (png_get_tRNS(stream->png_ptr, stream->info_ptr, &trans_alpha, &num_trans, &trans_color)) { chunk_information |= 1 << 4; // tRNS } if (png_get_bKGD(stream->png_ptr, stream->info_ptr, &background)) { chunk_information |= 1 << 5; // bKGD } if (png_get_hIST(stream->png_ptr, stream->info_ptr, &hist)) { chunk_information |= 1 << 6; // hIST } if (png_get_pHYs(stream->png_ptr, stream->info_ptr, &res_x, &res_y, &unit_type)) { chunk_information |= 1 << 7; // pHYs } if (png_get_oFFs(stream->png_ptr, stream->info_ptr, &offset_x, &offset_y, &unit_type)) { chunk_information |= 1 << 8; // oFFs } if (png_get_tIME(stream->png_ptr, stream->info_ptr, &mod_time)) { chunk_information |= 1 << 9; // tIME } if (png_get_pCAL(stream->png_ptr, stream->info_ptr, &purpose, &X0, &X1, &type, &nparams, &units, ¶ms)) { chunk_information |= 1 << 10; // pCAL } if (png_get_sRGB(stream->png_ptr, stream->info_ptr, &intent)) { chunk_information |= 1 << 11; // sRGB } if (png_get_iCCP(stream->png_ptr, stream->info_ptr, &name, &compression_type, &profile, &proflen)) { chunk_information |= 1 << 12; // iCCP } if (png_get_sPLT(stream->png_ptr, stream->info_ptr, &entries)) { chunk_information |= 1 << 13; // sPLT } if (png_get_sCAL(stream->png_ptr, stream->info_ptr, &unit, &width, &height)) { chunk_information |= 1 << 14; // sCAL } if (IDAT) { chunk_information |= 1 << 15; // IDAT } return chunk_information; } error_code pngDecCreate(ppu_thread& ppu, PPHandle png_handle, PThreadInParam thread_in_param, PThreadOutParam thread_out_param, PExtThreadInParam extra_thread_in_param = vm::null, PExtThreadOutParam extra_thread_out_param = vm::null) { // Check if partial image decoding is used if (extra_thread_out_param) { fmt::throw_exception("Partial image decoding is not supported."); } // Allocate memory for the decoder handle auto handle = vm::ptr::make(thread_in_param->cbCtrlMallocFunc(ppu, sizeof(PngHandle), thread_in_param->cbCtrlMallocArg).addr()); // Check if the memory allocation for the handle failed if (!handle) { cellPngDec.error("PNG decoder creation failed."); return CELL_PNGDEC_ERROR_FATAL; } // Set the allocation functions in the handle handle->malloc_ = thread_in_param->cbCtrlMallocFunc; handle->malloc_arg = thread_in_param->cbCtrlMallocArg; handle->free_ = thread_in_param->cbCtrlFreeFunc; handle->free_arg = thread_in_param->cbCtrlFreeArg; // Set handle pointer *png_handle = handle; // Set the version information thread_out_param->pngCodecVersion = PNGDEC_CODEC_VERSION; return CELL_OK; } error_code pngDecDestroy(ppu_thread& ppu, PHandle handle) { // Deallocate the decoder handle memory if (handle->free_(ppu, handle, handle->free_arg) != 0) { cellPngDec.error("PNG decoder deallocation failed."); return CELL_PNGDEC_ERROR_FATAL; } return CELL_OK; } error_code pngDecOpen(ppu_thread& ppu, PHandle handle, PPStream png_stream, PSrc source, POpenInfo open_info, PCbControlStream control_stream = vm::null, POpenParam open_param = vm::null) { // partial decoding only supported with buffer type if (source->srcSelect != CELL_PNGDEC_BUFFER && control_stream) { cellPngDec.error("Attempted partial image decode with file."); return CELL_PNGDEC_ERROR_STREAM_FORMAT; } // Allocate memory for the stream structure auto stream = vm::ptr::make(handle->malloc_(ppu, sizeof(PngStream), handle->malloc_arg).addr()); // Check if the allocation of memory for the stream structure failed if (!stream) { cellPngDec.error("PNG stream creation failed."); return CELL_PNGDEC_ERROR_FATAL; } // Set memory info open_info->initSpaceAllocated = sizeof(PngStream); // Set the stream source to the source give by the game stream->source = *source; // Use virtual memory address as a handle *png_stream = stream; // Allocate memory for the PNG buffer for decoding auto buffer = vm::ptr::make(handle->malloc_(ppu, sizeof(PngBuffer), handle->malloc_arg).addr()); // Check for if the buffer structure allocation failed if (!buffer) { fmt::throw_exception("Memory allocation for the PNG buffer structure failed."); } // We might not be reading from a file stream buffer->file = false; // Set the buffer pointer in the stream structure, so we can later deallocate it stream->buffer = buffer; // Open the buffer/file and check the header u8 header[8]; // Need to test it somewhere if (stream->source.fileOffset != 0) { fmt::throw_exception("Non-0 file offset not supported."); } // Depending on the source type, get the first 8 bytes if (source->srcSelect == CELL_PNGDEC_FILE) { const auto real_path = vfs::get(stream->source.fileName.get_ptr()); // Open a file stream fs::file file_stream(real_path); // Check if opening of the PNG file failed if (!file_stream) { cellPngDec.error("Opening of PNG failed. (%s)", stream->source.fileName.get_ptr()); return CELL_PNGDEC_ERROR_OPEN_FILE; } // Read the header if (file_stream.read(header, 8) != 8) { cellPngDec.error("PNG header is too small."); return CELL_PNGDEC_ERROR_HEADER; } // Get the file descriptor buffer->fd = idm::make(stream->source.fileName.get_ptr(), std::move(file_stream), 0, 0, real_path); // Indicate that we need to read from a file stream buffer->file = true; } else { // We can simply copy the first 8 bytes memcpy(header, stream->source.streamPtr.get_ptr(), 8); } // Check if the header indicates a valid PNG file if (png_sig_cmp(header, 0, 8)) { cellPngDec.error("PNG signature is invalid."); return CELL_PNGDEC_ERROR_HEADER; } // Create a libpng structure, also pass our custom error/warning functions stream->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, pngDecError, pngDecWarning); // Check if the creation of the structure failed if (!stream->png_ptr) { cellPngDec.error("Creation of png_structp failed."); return CELL_PNGDEC_ERROR_FATAL; } // Create a libpng info structure stream->info_ptr = png_create_info_struct(stream->png_ptr); // Check if the creation of the structure failed if (!stream->info_ptr) { fmt::throw_exception("Creation of png_infop failed."); } // We must indicate, that we allocated more memory open_info->initSpaceAllocated += u32{sizeof(PngBuffer)}; if (source->srcSelect == CELL_PNGDEC_BUFFER) { buffer->length = stream->source.streamSize; buffer->data = stream->source.streamPtr; buffer->cursor = 8; } // Set the custom read function for decoding if (control_stream) { if (open_param && open_param->selectChunk != 0u) fmt::throw_exception("Partial Decoding with selectChunk not supported yet."); stream->cbCtrlStream.cbCtrlStrmArg = control_stream->cbCtrlStrmArg; stream->cbCtrlStream.cbCtrlStrmFunc = control_stream->cbCtrlStrmFunc; png_set_progressive_read_fn(stream->png_ptr, stream.get_ptr(), pngDecInfoCallback, pngDecRowCallback, pngDecEndCallback); // push header tag to libpng to keep us in sync png_process_data(stream->png_ptr, stream->info_ptr, header, 8); } else { png_set_read_fn(stream->png_ptr, buffer.get_ptr(), pngDecReadBuffer); // We need to tell libpng, that we already read 8 bytes png_set_sig_bytes(stream->png_ptr, 8); } return CELL_OK; } error_code pngDecClose(ppu_thread& ppu, PHandle handle, PStream stream) { // Remove the file descriptor, if a file descriptor was used for decoding if (stream->buffer->file) { idm::remove(stream->buffer->fd); } // Deallocate the PNG buffer structure used to decode from memory, if we decoded from memory if (stream->buffer) { if (handle->free_(ppu, stream->buffer, handle->free_arg) != 0) { cellPngDec.error("PNG buffer decoding structure deallocation failed."); return CELL_PNGDEC_ERROR_FATAL; } } // Free the memory allocated by libpng png_destroy_read_struct(&stream->png_ptr, &stream->info_ptr, nullptr); // Deallocate the stream memory if (handle->free_(ppu, stream, handle->free_arg) != 0) { cellPngDec.error("PNG stream deallocation failed."); return CELL_PNGDEC_ERROR_FATAL; } return CELL_OK; } void pngSetHeader(PngStream* stream) { stream->info.imageWidth = png_get_image_width(stream->png_ptr, stream->info_ptr); stream->info.imageHeight = png_get_image_height(stream->png_ptr, stream->info_ptr); stream->info.numComponents = png_get_channels(stream->png_ptr, stream->info_ptr); stream->info.colorSpace = getPngDecColourType(png_get_color_type(stream->png_ptr, stream->info_ptr)); stream->info.bitDepth = png_get_bit_depth(stream->png_ptr, stream->info_ptr); stream->info.interlaceMethod = png_get_interlace_type(stream->png_ptr, stream->info_ptr); stream->info.chunkInformation = pngDecGetChunkInformation(stream); } error_code pngDecSetParameter(PStream stream, PInParam in_param, POutParam out_param, PExtInParam extra_in_param = vm::null, PExtOutParam extra_out_param = vm::null) { if (in_param->outputPackFlag == CELL_PNGDEC_1BYTE_PER_NPIXEL) { fmt::throw_exception("Packing not supported! (%d)", in_param->outputPackFlag); } // flag to keep unknown chunks png_set_keep_unknown_chunks(stream->png_ptr, PNG_HANDLE_CHUNK_IF_SAFE, nullptr, 0); // Scale 16 bit depth down to 8 bit depth. if (stream->info.bitDepth == 16u && in_param->outputBitDepth == 8u) { // PS3 uses png_set_strip_16, since png_set_scale_16 wasn't available back then. png_set_strip_16(stream->png_ptr); } // This shouldnt ever happen, but not sure what to do if it does, just want it logged for now if (stream->info.bitDepth != 16u && in_param->outputBitDepth == 16u) cellPngDec.error("Output depth of 16 with non input depth of 16 specified!"); if (in_param->commandPtr) cellPngDec.warning("Ignoring CommandPtr."); if (stream->info.colorSpace != in_param->outputColorSpace) { // check if we need to set alpha const bool inputHasAlpha = cellPngColorSpaceHasAlpha(stream->info.colorSpace); const bool outputWantsAlpha = cellPngColorSpaceHasAlpha(in_param->outputColorSpace); if (outputWantsAlpha && !inputHasAlpha) { if (in_param->outputAlphaSelect == CELL_PNGDEC_FIX_ALPHA) png_set_add_alpha(stream->png_ptr, in_param->outputColorAlpha, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER); else { // Check if we can steal the alpha from a trns block if (png_get_valid(stream->png_ptr, stream->info_ptr, PNG_INFO_tRNS)) png_set_tRNS_to_alpha(stream->png_ptr); // if not, just set default of 0xff else png_set_add_alpha(stream->png_ptr, 0xff, in_param->outputColorSpace == CELL_PNGDEC_ARGB ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER); } } else if (inputHasAlpha && !outputWantsAlpha) png_set_strip_alpha(stream->png_ptr); else if (in_param->outputColorSpace == CELL_PNGDEC_ARGB && stream->info.colorSpace == CELL_PNGDEC_RGBA) png_set_swap_alpha(stream->png_ptr); // Handle gray<->rgb colorspace conversions // rgb output if (in_param->outputColorSpace == CELL_PNGDEC_ARGB || in_param->outputColorSpace == CELL_PNGDEC_RGBA || in_param->outputColorSpace == CELL_PNGDEC_RGB) { if (stream->info.colorSpace == CELL_PNGDEC_PALETTE) png_set_palette_to_rgb(stream->png_ptr); if ((stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE || stream->info.colorSpace == CELL_PNGDEC_GRAYSCALE_ALPHA) && stream->info.bitDepth < 8) png_set_expand_gray_1_2_4_to_8(stream->png_ptr); } // grayscale output else { if (stream->info.colorSpace == CELL_PNGDEC_ARGB || stream->info.colorSpace == CELL_PNGDEC_RGBA || stream->info.colorSpace == CELL_PNGDEC_RGB) { png_set_rgb_to_gray(stream->png_ptr, PNG_ERROR_ACTION_NONE, PNG_RGB_TO_GRAY_DEFAULT, PNG_RGB_TO_GRAY_DEFAULT); } else { // not sure what to do here cellPngDec.error("Grayscale / Palette to Grayscale / Palette conversion currently unsupported."); } } } stream->passes = png_set_interlace_handling(stream->png_ptr); // Update the info structure png_read_update_info(stream->png_ptr, stream->info_ptr); stream->out_param.outputWidth = stream->info.imageWidth; stream->out_param.outputHeight = stream->info.imageHeight; stream->out_param.outputBitDepth = in_param->outputBitDepth; stream->out_param.outputColorSpace = in_param->outputColorSpace; stream->out_param.outputMode = in_param->outputMode; stream->out_param.outputWidthByte = png_get_rowbytes(stream->png_ptr, stream->info_ptr); stream->out_param.outputComponents = png_get_channels(stream->png_ptr, stream->info_ptr); stream->packing = in_param->outputPackFlag; // Set the memory usage. We currently don't actually allocate memory for libpng through the callbacks, due to libpng needing a lot more memory compared to PS3 variant. stream->out_param.useMemorySpace = 0; if (extra_in_param) { if (extra_in_param->bufferMode != CELL_PNGDEC_LINE_MODE) { cellPngDec.error("Invalid Buffermode specified."); return CELL_PNGDEC_ERROR_ARG; } if (stream->passes > 1) { stream->outputCounts = 1; } else stream->outputCounts = extra_in_param->outputCounts; if (extra_out_param) { if (stream->outputCounts == 0) extra_out_param->outputHeight = stream->out_param.outputHeight; else extra_out_param->outputHeight = std::min(stream->outputCounts, stream->out_param.outputHeight.value()); extra_out_param->outputWidthByte = stream->out_param.outputWidthByte; } } *out_param = stream->out_param; return CELL_OK; } error_code pngDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr data, PDataControlParam data_control_param, PDataOutInfo data_out_info, PCbControlDisp cb_control_disp = vm::null, PDispParam disp_param = vm::null) { // Indicate, that the PNG decoding is stopped/failed. This is incase, we return an error code in the middle of decoding data_out_info->status = CELL_PNGDEC_DEC_STATUS_STOP; const u32 bytes_per_line = ::narrow(data_control_param->outputBytesPerLine); // Log this for now if (bytes_per_line < stream->out_param.outputWidthByte) { fmt::throw_exception("Bytes per line less than expected output! Got: %d, expected: %d", bytes_per_line, stream->out_param.outputWidthByte); } // partial decoding if (cb_control_disp && stream->outputCounts > 0) { // get data from cb auto streamInfo = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecStrmInfo), handle->malloc_arg).addr()); auto streamParam = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecStrmParam), handle->malloc_arg).addr()); stream->cbDispInfo = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecDispInfo), handle->malloc_arg).addr()); stream->cbDispParam = vm::ptr::make(handle->malloc_(ppu, sizeof(CellPngDecDispParam), handle->malloc_arg).addr()); auto freeMem = [&]() { handle->free_(ppu, streamInfo, handle->free_arg); handle->free_(ppu, streamParam, handle->free_arg); handle->free_(ppu, stream->cbDispInfo, handle->free_arg); handle->free_(ppu, stream->cbDispParam, handle->free_arg); }; // set things that won't change between callbacks stream->cbDispInfo->outputFrameWidthByte = bytes_per_line; stream->cbDispInfo->outputFrameHeight = stream->out_param.outputHeight; stream->cbDispInfo->outputWidthByte = stream->out_param.outputWidthByte; stream->cbDispInfo->outputBitDepth = stream->out_param.outputBitDepth; stream->cbDispInfo->outputComponents = stream->out_param.outputComponents; stream->cbDispInfo->outputHeight = stream->outputCounts; stream->cbDispInfo->outputStartXByte = 0; stream->cbDispInfo->outputStartY = 0; stream->cbDispInfo->scanPassCount = 0; stream->cbDispInfo->nextOutputStartY = 0; stream->ppuContext = &ppu; stream->nextRow = stream->cbDispInfo->outputHeight; stream->cbCtrlDisp.cbCtrlDispArg = cb_control_disp->cbCtrlDispArg; stream->cbCtrlDisp.cbCtrlDispFunc = cb_control_disp->cbCtrlDispFunc; stream->cbDispParam->nextOutputImage = disp_param->nextOutputImage; streamInfo->decodedStrmSize = ::narrow(stream->buffer->cursor); // push the rest of the buffer we have if (stream->buffer->length > stream->buffer->cursor) { u8* data = static_cast(stream->buffer->data.get_ptr()) + stream->buffer->cursor; png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length - stream->buffer->cursor); streamInfo->decodedStrmSize = ::narrow(stream->buffer->length); } // todo: commandPtr // then just loop until the end, the callbacks should take care of the rest while (stream->endOfFile != true) { stream->cbCtrlStream.cbCtrlStrmFunc(ppu, streamInfo, streamParam, stream->cbCtrlStream.cbCtrlStrmArg); streamInfo->decodedStrmSize += streamParam->strmSize; png_process_data(stream->png_ptr, stream->info_ptr, static_cast(streamParam->strmPtr.get_ptr()), streamParam->strmSize); } freeMem(); } else { // Check if the image needs to be flipped const bool flip = stream->out_param.outputMode == CELL_PNGDEC_BOTTOM_TO_TOP; // Decode the image // todo: commandptr { for (u32 j = 0; j < stream->passes; j++) { for (u32 i = 0; i < stream->out_param.outputHeight; ++i) { const u32 line = flip ? stream->out_param.outputHeight - i - 1 : i; png_read_row(stream->png_ptr, &data[line * bytes_per_line], nullptr); } } png_read_end(stream->png_ptr, stream->info_ptr); } } // Get the number of iTXt, tEXt and zTXt chunks const s32 text_chunks = png_get_text(stream->png_ptr, stream->info_ptr, nullptr, nullptr); // Set the chunk information and the previously obtained number of text chunks data_out_info->numText = static_cast(text_chunks); data_out_info->chunkInformation = pngDecGetChunkInformation(stream.get_ptr(), true); png_unknown_chunkp unknowns; const int num_unknowns = png_get_unknown_chunks(stream->png_ptr, stream->info_ptr, &unknowns); data_out_info->numUnknownChunk = num_unknowns; // Indicate that the decoding succeeded data_out_info->status = CELL_PNGDEC_DEC_STATUS_FINISH; return CELL_OK; } error_code cellPngDecCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam) { cellPngDec.warning("cellPngDecCreate(handle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x)", handle, threadInParam, threadOutParam); return pngDecCreate(ppu, handle, threadInParam, threadOutParam); } error_code cellPngDecExtCreate(ppu_thread& ppu, PPHandle handle, PThreadInParam threadInParam, PThreadOutParam threadOutParam, PExtThreadInParam extThreadInParam, PExtThreadOutParam extThreadOutParam) { cellPngDec.warning("cellPngDecExtCreate(mainHandle=**0x%x, threadInParam=*0x%x, threadOutParam=*0x%x, extThreadInParam=*0x%x, extThreadOutParam=*0x%x)", handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam); return pngDecCreate(ppu, handle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam); } error_code cellPngDecDestroy(ppu_thread& ppu, PHandle handle) { cellPngDec.warning("cellPngDecDestroy(mainHandle=*0x%x)", handle); return pngDecDestroy(ppu, handle); } error_code cellPngDecOpen(ppu_thread& ppu, PHandle handle, PPStream stream, PSrc src, POpenInfo openInfo) { cellPngDec.warning("cellPngDecOpen(handle=*0x%x, stream=**0x%x, src=*0x%x, openInfo=*0x%x)", handle, stream, src, openInfo); return pngDecOpen(ppu, handle, stream, src, openInfo); } error_code cellPngDecExtOpen(ppu_thread& ppu, PHandle handle, PPStream stream, PSrc src, POpenInfo openInfo, PCbControlStream cbCtrlStrm, POpenParam opnParam) { cellPngDec.warning("cellPngDecExtOpen(handle=*0x%x, stream=**0x%x, src=*0x%x, openInfo=*0x%x, cbCtrlStrm=*0x%x, opnParam=*0x%x)", handle, stream, src, openInfo, cbCtrlStrm, opnParam); return pngDecOpen(ppu, handle, stream, src, openInfo, cbCtrlStrm, opnParam); } error_code cellPngDecClose(ppu_thread& ppu, PHandle handle, PStream stream) { cellPngDec.warning("cellPngDecClose(handle=*0x%x, stream=*0x%x)", handle, stream); return pngDecClose(ppu, handle, stream); } error_code cellPngDecReadHeader(PHandle handle, PStream stream, PInfo info) { cellPngDec.warning("cellPngDecReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x)", handle, stream, info); // Read the header info png_read_info(stream->png_ptr, stream->info_ptr); pngSetHeader(stream.get_ptr()); // Set the pointer to stream info *info = stream->info; return CELL_OK; } error_code cellPngDecExtReadHeader(PHandle handle, PStream stream, PInfo info, PExtInfo extInfo) { cellPngDec.warning("cellPngDecExtReadHeader(handle=*0x%x, stream=*0x%x, info=*0x%x, extInfo=*0x%x)", handle, stream, info, extInfo); // Set the reserved value to 0, if passed to the function. (Should this be arg error if they dont pass?) if (extInfo) { extInfo->reserved = 0; } // lets push what we have so far u8* data = static_cast(stream->buffer->data.get_ptr()) + stream->buffer->cursor; png_process_data(stream->png_ptr, stream->info_ptr, data, stream->buffer->length); // lets hope we pushed enough for callback pngSetHeader(stream.get_ptr()); // png doesnt allow empty image, so quick check for 0 verifys if we got the header // not sure exactly what should happen if we dont have header, ask for more data with callback? if (stream->info.imageWidth == 0u) { fmt::throw_exception("Invalid or not enough data sent to get header"); return CELL_PNGDEC_ERROR_HEADER; } // Set the pointer to stream info *info = stream->info; return CELL_OK; } error_code cellPngDecSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam) { cellPngDec.warning("cellPngDecSetParameter(handle=*0x%x, stream=*0x%x, inParam=*0x%x, outParam=*0x%x)", handle, stream, inParam, outParam); return pngDecSetParameter(stream, inParam, outParam); } error_code cellPngDecExtSetParameter(PHandle handle, PStream stream, PInParam inParam, POutParam outParam, PExtInParam extInParam, PExtOutParam extOutParam) { cellPngDec.warning("cellPngDecExtSetParameter(handle=*0x%x, stream=*0x%x, inParam=*0x%x, outParam=*0x%x, extInParam=*0x%x, extOutParam=*0x%x", handle, stream, inParam, outParam, extInParam, extOutParam); return pngDecSetParameter(stream, inParam, outParam, extInParam, extOutParam); } error_code cellPngDecDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr data, PDataControlParam dataCtrlParam, PDataOutInfo dataOutInfo) { cellPngDec.warning("cellPngDecDecodeData(handle=*0x%x, stream=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x)", handle, stream, data, dataCtrlParam, dataOutInfo); return pngDecodeData(ppu, handle, stream, data, dataCtrlParam, dataOutInfo); } error_code cellPngDecExtDecodeData(ppu_thread& ppu, PHandle handle, PStream stream, vm::ptr data, PDataControlParam dataCtrlParam, PDataOutInfo dataOutInfo, PCbControlDisp cbCtrlDisp, PDispParam dispParam) { cellPngDec.warning("cellPngDecExtDecodeData(handle=*0x%x, stream=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x, cbCtrlDisp=*0x%x, dispParam=*0x%x)", handle, stream, data, dataCtrlParam, dataOutInfo, cbCtrlDisp, dispParam); return pngDecodeData(ppu, handle, stream, data, dataCtrlParam, dataOutInfo, cbCtrlDisp, dispParam); } error_code cellPngDecGetUnknownChunks(PHandle handle, PStream stream, vm::pptr unknownChunk, vm::ptr unknownChunkNumber) { cellPngDec.todo("cellPngDecGetUnknownChunks()"); return CELL_OK; } error_code cellPngDecGetpCAL(PHandle handle, PStream stream, vm::ptr pcal) { cellPngDec.todo("cellPngDecGetpCAL()"); return CELL_OK; } error_code cellPngDecGetcHRM(PHandle handle, PStream stream, vm::ptr chrm) { cellPngDec.todo("cellPngDecGetcHRM()"); return CELL_OK; } error_code cellPngDecGetsCAL(PHandle handle, PStream stream, vm::ptr scal) { cellPngDec.todo("cellPngDecGetsCAL()"); return CELL_OK; } error_code cellPngDecGetpHYs(PHandle handle, PStream stream, vm::ptr phys) { cellPngDec.todo("cellPngDecGetpHYs()"); return CELL_OK; } error_code cellPngDecGetoFFs(PHandle handle, PStream stream, vm::ptr offs) { cellPngDec.todo("cellPngDecGetoFFs()"); return CELL_OK; } error_code cellPngDecGetsPLT(PHandle handle, PStream stream, vm::ptr splt) { cellPngDec.todo("cellPngDecGetsPLT()"); return CELL_OK; } error_code cellPngDecGetbKGD(PHandle handle, PStream stream, vm::ptr bkgd) { cellPngDec.todo("cellPngDecGetbKGD()"); return CELL_OK; } error_code cellPngDecGettIME(PHandle handle, PStream stream, vm::ptr time) { cellPngDec.todo("cellPngDecGettIME()"); return CELL_OK; } error_code cellPngDecGethIST(PHandle handle, PStream stream, vm::ptr hist) { cellPngDec.todo("cellPngDecGethIST()"); return CELL_OK; } error_code cellPngDecGettRNS(PHandle handle, PStream stream, vm::ptr trns) { cellPngDec.todo("cellPngDecGettRNS()"); return CELL_OK; } error_code cellPngDecGetsBIT(PHandle handle, PStream stream, vm::ptr sbit) { cellPngDec.todo("cellPngDecGetsBIT()"); return CELL_OK; } error_code cellPngDecGetiCCP(PHandle handle, PStream stream, vm::ptr iccp) { cellPngDec.todo("cellPngDecGetiCCP()"); return CELL_OK; } error_code cellPngDecGetsRGB(PHandle handle, PStream stream, vm::ptr srgb) { cellPngDec.todo("cellPngDecGetsRGB()"); return CELL_OK; } error_code cellPngDecGetgAMA(PHandle handle, PStream stream, vm::ptr gama) { cellPngDec.todo("cellPngDecGetgAMA()"); return CELL_OK; } error_code cellPngDecGetPLTE(PHandle handle, PStream stream, vm::ptr plte) { cellPngDec.todo("cellPngDecGetPLTE()"); return CELL_OK; } error_code cellPngDecGetTextChunk(PHandle handle, PStream stream, vm::ptr textInfoNum, vm::pptr textInfo) { cellPngDec.todo("cellPngDecGetTextChunk()"); return CELL_OK; } DECLARE(ppu_module_manager::cellPngDec)("cellPngDec", []() { REG_FUNC(cellPngDec, cellPngDecGetUnknownChunks); REG_FUNC(cellPngDec, cellPngDecClose); REG_FUNC(cellPngDec, cellPngDecGetpCAL); REG_FUNC(cellPngDec, cellPngDecGetcHRM); REG_FUNC(cellPngDec, cellPngDecGetsCAL); REG_FUNC(cellPngDec, cellPngDecGetpHYs); REG_FUNC(cellPngDec, cellPngDecGetoFFs); REG_FUNC(cellPngDec, cellPngDecGetsPLT); REG_FUNC(cellPngDec, cellPngDecGetbKGD); REG_FUNC(cellPngDec, cellPngDecGettIME); REG_FUNC(cellPngDec, cellPngDecGethIST); REG_FUNC(cellPngDec, cellPngDecGettRNS); REG_FUNC(cellPngDec, cellPngDecGetsBIT); REG_FUNC(cellPngDec, cellPngDecGetiCCP); REG_FUNC(cellPngDec, cellPngDecGetsRGB); REG_FUNC(cellPngDec, cellPngDecGetgAMA); REG_FUNC(cellPngDec, cellPngDecGetPLTE); REG_FUNC(cellPngDec, cellPngDecGetTextChunk); REG_FUNC(cellPngDec, cellPngDecDestroy); REG_FUNC(cellPngDec, cellPngDecCreate); REG_FUNC(cellPngDec, cellPngDecExtCreate); REG_FUNC(cellPngDec, cellPngDecExtSetParameter); REG_FUNC(cellPngDec, cellPngDecSetParameter); REG_FUNC(cellPngDec, cellPngDecExtReadHeader); REG_FUNC(cellPngDec, cellPngDecReadHeader); REG_FUNC(cellPngDec, cellPngDecExtOpen); REG_FUNC(cellPngDec, cellPngDecOpen); REG_FUNC(cellPngDec, cellPngDecExtDecodeData); REG_FUNC(cellPngDec, cellPngDecDecodeData); });