#include "stdafx.h" #include "Emu/VFS.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" // STB_IMAGE_IMPLEMENTATION is already defined in stb_image.cpp #include #include "cellos/sys_fs.h" #include "cellGifDec.h" LOG_CHANNEL(cellGifDec); // Temporarily #ifndef _MSC_VER #pragma GCC diagnostic ignored "-Wunused-parameter" #endif template <> void fmt_class_string::format(std::string& out, u64 arg) { format_enum(out, arg, [](auto error) { switch (error) { STR_CASE(CELL_GIFDEC_ERROR_OPEN_FILE); STR_CASE(CELL_GIFDEC_ERROR_STREAM_FORMAT); STR_CASE(CELL_GIFDEC_ERROR_SEQ); STR_CASE(CELL_GIFDEC_ERROR_ARG); STR_CASE(CELL_GIFDEC_ERROR_FATAL); STR_CASE(CELL_GIFDEC_ERROR_SPU_UNSUPPORT); STR_CASE(CELL_GIFDEC_ERROR_SPU_ERROR); STR_CASE(CELL_GIFDEC_ERROR_CB_PARAM); } return unknown; }); } error_code cellGifDecCreate(vm::ptr mainHandle, vm::cptr threadInParam, vm::ptr threadOutParam) { cellGifDec.todo("cellGifDecCreate(mainHandle=*0x%x, threadInParam=*0x%x, threadOutParam=*0x%x)", mainHandle, threadInParam, threadOutParam); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } *mainHandle = {}; if (!threadOutParam || !threadInParam || !threadInParam->cbCtrlMallocFunc || !threadInParam->cbCtrlFreeFunc || (threadInParam->spuThreadEnable != CELL_GIFDEC_SPU_THREAD_DISABLE && (threadInParam->spuThreadEnable != CELL_GIFDEC_SPU_THREAD_ENABLE || threadInParam->ppuThreadPriority > 3071 || threadInParam->spuThreadPriority > 255))) { return CELL_GIFDEC_ERROR_ARG; } threadOutParam->gifCodecVersion = 0x240000; return CELL_OK; } error_code cellGifDecExtCreate(vm::ptr mainHandle, vm::cptr threadInParam, vm::ptr threadOutParam, vm::cptr extThreadInParam, vm::ptr extThreadOutParam) { cellGifDec.todo("cellGifDecExtCreate(mainHandle=*0x%x, threadInParam=*0x%x, threadOutParam=*0x%x, extThreadInParam=*0x%x, extThreadOutParam=*0x%x)", mainHandle, threadInParam, threadOutParam, extThreadInParam, extThreadOutParam); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } *mainHandle = {}; if (!threadOutParam || !extThreadOutParam || !extThreadInParam || !threadInParam || !threadInParam->cbCtrlMallocFunc || !threadInParam->cbCtrlFreeFunc || (threadInParam->spuThreadEnable != CELL_GIFDEC_SPU_THREAD_DISABLE && threadInParam->spuThreadEnable != CELL_GIFDEC_SPU_THREAD_ENABLE)) { return CELL_GIFDEC_ERROR_ARG; } if (threadInParam->spuThreadEnable == CELL_GIFDEC_SPU_THREAD_ENABLE && !extThreadInParam->spurs) { return CELL_GIFDEC_ERROR_ARG; } if (extThreadInParam->maxContention == 0u || extThreadInParam->maxContention >= 8u) { return CELL_GIFDEC_ERROR_ARG; } for (u32 i = 0; i < 8; i++) { if (extThreadInParam->priority[i] > 15) { return CELL_GIFDEC_ERROR_ARG; } } threadOutParam->gifCodecVersion = 0x240000; return CELL_OK; } error_code cellGifDecOpen(vm::ptr mainHandle, vm::pptr subHandle, vm::cptr src, vm::ptr openInfo) { cellGifDec.warning("cellGifDecOpen(mainHandle=*0x%x, subHandle=**0x%x, src=*0x%x, openInfo=*0x%x)", mainHandle, subHandle, src, openInfo); if (!mainHandle || !subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO { return CELL_GIFDEC_ERROR_SEQ; } if (!openInfo || !src) { return CELL_GIFDEC_ERROR_ARG; } GifStream current_subHandle{}; current_subHandle.fd = 0; current_subHandle.src = *src; switch (src->srcSelect) { case CELL_GIFDEC_BUFFER: { if (!src->streamPtr || !src->streamSize) { return CELL_GIFDEC_ERROR_ARG; } current_subHandle.fileSize = src->streamSize; break; } case CELL_GIFDEC_FILE: { if (!src->fileName) { return CELL_GIFDEC_ERROR_OPEN_FILE; } // Get file descriptor and size const std::string real_path = vfs::get(src->fileName.get_ptr()); fs::file file_s(real_path); if (!file_s) { return CELL_GIFDEC_ERROR_OPEN_FILE; } if (src->fileOffset < 0) { return CELL_GIFDEC_ERROR_ARG; } current_subHandle.fileSize = file_s.size(); current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path); break; } default: { return CELL_GIFDEC_ERROR_ARG; } } subHandle->set(vm::alloc(sizeof(GifStream), vm::main)); **subHandle = current_subHandle; return CELL_OK; } error_code cellGifDecExtOpen(vm::ptr mainHandle, vm::pptr subHandle, vm::cptr src, vm::ptr openInfo, vm::cptr cbCtrlStrm) { cellGifDec.todo("cellGifDecExtOpen(mainHandle=*0x%x, subHandle=*0x%x, src=*0x%x, openInfo=*0x%x, cbCtrlStrm=*0x%x)", mainHandle, subHandle, src, openInfo, cbCtrlStrm); if (!mainHandle || !subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO { return CELL_GIFDEC_ERROR_SEQ; } if (!openInfo || !src) { return CELL_GIFDEC_ERROR_ARG; } GifStream current_subHandle{}; current_subHandle.fd = 0; current_subHandle.src = *src; switch (src->srcSelect) { case CELL_GIFDEC_BUFFER: { if (!src->streamPtr || !src->streamSize) { return CELL_GIFDEC_ERROR_ARG; } current_subHandle.fileSize = src->streamSize; break; } case CELL_GIFDEC_FILE: { if (!src->fileName) { return CELL_GIFDEC_ERROR_OPEN_FILE; } // Get file descriptor and size const std::string real_path = vfs::get(src->fileName.get_ptr()); fs::file file_s(real_path); if (!file_s) { return CELL_GIFDEC_ERROR_OPEN_FILE; } if (src->fileOffset < 0) { return CELL_GIFDEC_ERROR_ARG; } current_subHandle.fileSize = file_s.size(); current_subHandle.fd = idm::make(src->fileName.get_ptr(), std::move(file_s), 0, 0, real_path); break; } default: { return CELL_GIFDEC_ERROR_ARG; } } return CELL_OK; } error_code cellGifDecReadHeader(vm::ptr mainHandle, vm::ptr subHandle, vm::ptr info) { cellGifDec.warning("cellGifDecReadHeader(mainHandle=*0x%x, subHandle=*0x%x, info=*0x%x)", mainHandle, subHandle, info); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!info) { return CELL_GIFDEC_ERROR_ARG; } const u32 fd = subHandle->fd; CellGifDecInfo& current_info = subHandle->info; // Write the header to buffer u8 buffer[13]; switch (subHandle->src.srcSelect) { case CELL_GIFDEC_BUFFER: { std::memcpy(buffer, subHandle->src.streamPtr.get_ptr(), sizeof(buffer)); break; } case CELL_GIFDEC_FILE: { auto file = idm::get_unlocked(fd); file->file.seek(0); file->file.read(buffer, sizeof(buffer)); break; } default: break; // TODO } if (read_from_ptr>(buffer + 0) != 0x47494638u || (read_from_ptr>(buffer + 4) != 0x6139u && read_from_ptr>(buffer + 4) != 0x6137u)) // Error: The first 6 bytes are not a valid GIF signature { return CELL_GIFDEC_ERROR_STREAM_FORMAT; // Surprisingly there is no error code related with headerss } const u8 packedField = buffer[10]; current_info.SWidth = buffer[6] + buffer[7] * 0x100; current_info.SHeight = buffer[8] + buffer[9] * 0x100; current_info.SGlobalColorTableFlag = packedField >> 7; current_info.SColorResolution = ((packedField >> 4) & 7) + 1; current_info.SSortFlag = (packedField >> 3) & 1; current_info.SSizeOfGlobalColorTable = (packedField & 7) + 1; current_info.SBackGroundColor = buffer[11]; current_info.SPixelAspectRatio = buffer[12]; *info = current_info; return CELL_OK; } error_code cellGifDecExtReadHeader(vm::ptr mainHandle, vm::cptr subHandle, vm::ptr info, vm::ptr extInfo) { cellGifDec.todo("cellGifDecExtReadHeader(mainHandle=*0x%x, subHandle=*0x%x, info=*0x%x, extInfo=*0x%x)", mainHandle, subHandle, info, extInfo); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!info || !extInfo) { return CELL_GIFDEC_ERROR_ARG; } return CELL_OK; } error_code cellGifDecSetParameter(vm::ptr mainHandle, vm::ptr subHandle, vm::cptr inParam, vm::ptr outParam) { cellGifDec.warning("cellGifDecSetParameter(mainHandle=*0x%x, subHandle=*0x%x, inParam=*0x%x, outParam=*0x%x)", mainHandle, subHandle, inParam, outParam); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!inParam || !outParam) { return CELL_GIFDEC_ERROR_ARG; } CellGifDecInfo& current_info = subHandle->info; CellGifDecOutParam& current_outParam = subHandle->outParam; current_outParam.outputWidthByte = (current_info.SWidth * current_info.SColorResolution * 3) / 8; current_outParam.outputWidth = current_info.SWidth; current_outParam.outputHeight = current_info.SHeight; current_outParam.outputColorSpace = inParam->colorSpace; switch (current_outParam.outputColorSpace) { case CELL_GIFDEC_RGBA: case CELL_GIFDEC_ARGB: current_outParam.outputComponents = 4; break; default: return CELL_GIFDEC_ERROR_ARG; // Not supported color space } current_outParam.outputBitDepth = 0; // Unimplemented current_outParam.useMemorySpace = 0; // Unimplemented *outParam = current_outParam; return CELL_OK; } error_code cellGifDecExtSetParameter(vm::ptr mainHandle, vm::ptr subHandle, vm::cptr inParam, vm::ptr outParam, vm::cptr extInParam, vm::ptr extOutParam) { cellGifDec.todo("cellGifDecExtSetParameter(mainHandle=*0x%x, subHandle=*0x%x, inParam=*0x%x, outParam=*0x%x, extInParam=*0x%x, extOutParam=*0x%x)", mainHandle, subHandle, inParam, outParam, extInParam, extOutParam); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!inParam || !outParam) { return CELL_GIFDEC_ERROR_ARG; } CellGifDecInfo& current_info = subHandle->info; CellGifDecOutParam& current_outParam = subHandle->outParam; current_outParam.outputWidthByte = (current_info.SWidth * current_info.SColorResolution * 3) / 8; current_outParam.outputWidth = current_info.SWidth; current_outParam.outputHeight = current_info.SHeight; current_outParam.outputColorSpace = inParam->colorSpace; switch (current_outParam.outputColorSpace) { case CELL_GIFDEC_RGBA: case CELL_GIFDEC_ARGB: current_outParam.outputComponents = 4; break; default: return CELL_GIFDEC_ERROR_ARG; // Not supported color space } current_outParam.outputBitDepth = 0; // Unimplemented current_outParam.useMemorySpace = 0; // Unimplemented *outParam = current_outParam; if (!extInParam || extInParam->bufferMode != CELL_GIFDEC_LINE_MODE || !extOutParam) { return CELL_GIFDEC_ERROR_ARG; } return CELL_OK; } error_code cellGifDecDecodeData(vm::ptr mainHandle, vm::cptr subHandle, vm::ptr data, vm::cptr dataCtrlParam, vm::ptr dataOutInfo) { cellGifDec.warning("cellGifDecDecodeData(mainHandle=*0x%x, subHandle=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x)", mainHandle, subHandle, data, dataCtrlParam, dataOutInfo); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!dataOutInfo || !dataCtrlParam) { return CELL_GIFDEC_ERROR_ARG; } dataOutInfo->status = CELL_GIFDEC_DEC_STATUS_STOP; const u32 fd = subHandle->fd; const u64 fileSize = subHandle->fileSize; const CellGifDecOutParam& current_outParam = subHandle->outParam; // Copy the GIF file to a buffer std::unique_ptr gif(new u8[fileSize]); switch (subHandle->src.srcSelect) { case CELL_GIFDEC_BUFFER: std::memcpy(gif.get(), subHandle->src.streamPtr.get_ptr(), fileSize); break; case CELL_GIFDEC_FILE: { auto file = idm::get_unlocked(fd); file->file.seek(0); file->file.read(gif.get(), fileSize); break; } default: break; // TODO } // Decode GIF file. (TODO: Is there any faster alternative? Can we do it without external libraries?) int width, height, actual_components; auto image = std::unique_ptr( stbi_load_from_memory(gif.get(), ::narrow(fileSize), &width, &height, &actual_components, 4), &::free); if (!image) return CELL_GIFDEC_ERROR_STREAM_FORMAT; const int bytesPerLine = static_cast(dataCtrlParam->outputBytesPerLine); constexpr char nComponents = 4; const u32 image_size = width * height * nComponents; switch (current_outParam.outputColorSpace) { case CELL_GIFDEC_RGBA: { if (bytesPerLine > width * nComponents) // Check if we need padding { const int linesize = std::min(bytesPerLine, width * nComponents); for (int i = 0; i < height; i++) { const int dstOffset = i * bytesPerLine; const int srcOffset = width * nComponents * i; memcpy(&data[dstOffset], &image.get()[srcOffset], linesize); } } else { memcpy(data.get_ptr(), image.get(), image_size); } break; } case CELL_GIFDEC_ARGB: { if (bytesPerLine > width * nComponents) // Check if we need padding { // TODO: find out if we can't do padding without an extra copy const int linesize = std::min(bytesPerLine, width * nComponents); const auto output = std::make_unique(linesize); for (int i = 0; i < height; i++) { const int dstOffset = i * bytesPerLine; const int srcOffset = width * nComponents * i; for (int j = 0; j < linesize; j += nComponents) { output[j + 0] = image.get()[srcOffset + j + 3]; output[j + 1] = image.get()[srcOffset + j + 0]; output[j + 2] = image.get()[srcOffset + j + 1]; output[j + 3] = image.get()[srcOffset + j + 2]; } std::memcpy(&data[dstOffset], output.get(), linesize); } } else { const auto img = std::make_unique(image_size); uint* source_current = reinterpret_cast(image.get()); uint* dest_current = img.get(); for (uint i = 0; i < image_size / nComponents; i++) { uint val = *source_current; *dest_current = (val >> 24) | (val << 8); // set alpha (A8) as leftmost byte source_current++; dest_current++; } std::memcpy(data.get_ptr(), img.get(), image_size); } break; } default: return CELL_GIFDEC_ERROR_ARG; } dataOutInfo->status = CELL_GIFDEC_DEC_STATUS_FINISH; dataOutInfo->recordType = CELL_GIFDEC_RECORD_TYPE_IMAGE_DESC; return CELL_OK; } error_code cellGifDecExtDecodeData(vm::ptr mainHandle, vm::cptr subHandle, vm::ptr data, vm::cptr dataCtrlParam, vm::ptr dataOutInfo, vm::cptr cbCtrlDisp, vm::ptr dispParam) { cellGifDec.todo("cellGifDecExtDecodeData(mainHandle=*0x%x, subHandle=*0x%x, data=*0x%x, dataCtrlParam=*0x%x, dataOutInfo=*0x%x, cbCtrlDisp=*0x%x, dispParam=*0x%x)", mainHandle, subHandle, data, dataCtrlParam, dataOutInfo, cbCtrlDisp, dispParam); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check sub handle { return CELL_GIFDEC_ERROR_SEQ; } if (!dataOutInfo || !dataCtrlParam) { return CELL_GIFDEC_ERROR_ARG; } return CELL_OK; } error_code cellGifDecClose(vm::ptr mainHandle, vm::cptr subHandle) { cellGifDec.warning("cellGifDecClose(mainHandle=*0x%x, subHandle=*0x%x)", mainHandle, subHandle); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } if (!subHandle) { return CELL_GIFDEC_ERROR_ARG; } idm::remove(subHandle->fd); vm::dealloc(subHandle.addr()); return CELL_OK; } error_code cellGifDecDestroy(vm::ptr mainHandle) { cellGifDec.todo("cellGifDecDestroy(mainHandle=*0x%x)", mainHandle); if (!mainHandle) { return CELL_GIFDEC_ERROR_ARG; } if (false) // TODO: check main handle { return CELL_GIFDEC_ERROR_SEQ; } return CELL_OK; } DECLARE(ppu_module_manager::cellGifDec)("cellGifDec", []() { REG_FUNC(cellGifDec, cellGifDecCreate); REG_FUNC(cellGifDec, cellGifDecExtCreate); REG_FUNC(cellGifDec, cellGifDecOpen); REG_FUNC(cellGifDec, cellGifDecReadHeader); REG_FUNC(cellGifDec, cellGifDecSetParameter); REG_FUNC(cellGifDec, cellGifDecDecodeData); REG_FUNC(cellGifDec, cellGifDecClose); REG_FUNC(cellGifDec, cellGifDecDestroy); REG_FUNC(cellGifDec, cellGifDecExtOpen); REG_FUNC(cellGifDec, cellGifDecExtReadHeader); REG_FUNC(cellGifDec, cellGifDecExtSetParameter); REG_FUNC(cellGifDec, cellGifDecExtDecodeData); });