// Copyright (c) 2012- PPSSPP Project. // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, version 2.0 or later versions. // This program is distributed in the hope that 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 2.0 for more details. // A copy of the GPL 2.0 should have been included with the program. // If not, see http://www.gnu.org/licenses/ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include #include "ext/jpge/jpgd.h" #include "Common/CommonTypes.h" #include "Common/Serialize/Serializer.h" #include "Common/Serialize/SerializeFuncs.h" #include "Core/Debugger/MemBlockInfo.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/sceJpeg.h" #include "Core/HLE/sceKernel.h" #include "GPU/GPUCommon.h" #include "Core/MemMap.h" #include "Core/Reporting.h" // Uncomment if you want to dump JPEGs loaded through sceJpeg to a file // #define JPEG_DEBUG #ifdef JPEG_DEBUG #include "ext/xxhash.h" #include "Common/File/FileUtil.h" #include "Common/StringUtils.h" #endif struct u24_be { unsigned char value[3]; operator unsigned int() { return 0x00000000 | (value[0] << 16) | (value[1] << 8) | (value[2] << 0); } }; static int mjpegInited = 0; static int mjpegWidth = 0; static int mjpegHeight = 0; void __JpegInit() { mjpegInited = 0; mjpegWidth = 0; mjpegHeight = 0; } enum : uint32_t { ERROR_JPEG_INVALID_DATA = 0x80650004, ERROR_JPEG_INVALID_COLORSPACE = 0x80650013, ERROR_JPEG_INVALID_SIZE = 0x80650020, ERROR_JPEG_NO_SOI = 0x80650023, ERROR_JPEG_INVALID_STATE = 0x80650039, ERROR_JPEG_OUT_OF_MEMORY = 0x80650041, ERROR_JPEG_ALREADY_INIT = 0x80650042, ERROR_JPEG_INVALID_VALUE = 0x80650051, }; void __JpegDoState(PointerWrap &p) { auto s = p.Section("sceJpeg", 1, 2); if (!s) return; Do(p, mjpegWidth); Do(p, mjpegHeight); if (s >= 2) { Do(p, mjpegInited); } else { mjpegInited = -1; } } static int getWidthHeight(int width, int height) { return (width << 16) | height; } // TODO: sceJpegCsc and sceJpegMJpegCsc use different factors. static u32 convertYCbCrToABGR(int y, int cb, int cr) { //see http://en.wikipedia.org/wiki/Yuv#Y.27UV444_to_RGB888_conversion for more information. cb = cb - 128; cr = cr - 128; int r = y + cr + (cr >> 2) + (cr >> 3) + (cr >> 5); int g = y - ((cb >> 2) + (cb >> 4) + (cb >> 5)) - ((cr >> 1) + (cr >> 3) + (cr >> 4) + (cr >> 5)); int b = y + cb + (cb >> 1) + (cb >> 2) + (cb >> 6); // check rgb value. if (r > 0xFF) r = 0xFF; if(r < 0) r = 0; if (g > 0xFF) g = 0xFF; if(g < 0) g = 0; if (b > 0xFF) b = 0xFF; if(b < 0) b = 0; return (b << 16) | (g << 8) | (r << 0); } static int JpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth, uint32_t chroma, int &usec) { if ((chroma & 0x000FFFFF) != 0x00020202 && (chroma & 0x000FFFFF) != 0x00020201 && (chroma & 0x000FFFFF) != 0x00020101) return hleLogError(ME, ERROR_JPEG_INVALID_COLORSPACE, "invalid colorspace"); if (bufferWidth < 0) bufferWidth = 0; int height = widthHeight & 0xFFFF; int width = (widthHeight >> 16) & 0xFFFF; if (height == 0) height = 1; uint8_t widthShift = ((chroma >> 8) & 0x03) - 1; uint8_t heightShift = (chroma & 0x03) - 1; int sizeY = width * height; int sizeCb = sizeY >> (widthShift + heightShift); uint64_t destSize = ((uint64_t)bufferWidth * (height - 1) + width) * 4; if (destSize > 0x3FFFFFFF || !Memory::IsValidRange(imageAddr, (uint32_t)destSize)) return hleLogError(ME, ERROR_JPEG_INVALID_VALUE, "invalid dest address or size"); if (sizeY > 0x3FFFFFFF || !Memory::IsValidRange(yCbCrAddr, sizeY + sizeCb + sizeCb)) return hleLogError(ME, ERROR_JPEG_INVALID_VALUE, "invalid src address or size"); u32_le *imageBuffer = (u32_le *)Memory::GetPointerWriteUnchecked(imageAddr); const u8 *Y = (const u8 *)Memory::GetPointerUnchecked(yCbCrAddr); const u8 *Cb = Y + sizeY; const u8 *Cr = Cb + sizeCb; // Very approximate estimate based on tests on a PSP. Usually under. usec += 60 + 6 * height + width / 2 + width / 4; if ((widthHeight & 0x00010001) == 0 && height > 1) { for (int y = 0; y < height; y += 2) { for (int x = 0; x < width; x += 2) { u8 y0 = Y[width * y + x]; u8 y1 = Y[width * y + x + 1]; u8 y2 = Y[width * (y + 1) + x]; u8 y3 = Y[width * (y + 1) + x + 1]; u8 cb = Cb[(width >> widthShift) * (y >> heightShift) + (x >> widthShift)]; u8 cr = Cr[(width >> widthShift) * (y >> heightShift) + (x >> widthShift)]; imageBuffer[bufferWidth * y + x] = convertYCbCrToABGR(y0, cb, cr); imageBuffer[bufferWidth * y + x + 1] = convertYCbCrToABGR(y1, cb, cr); imageBuffer[bufferWidth * (y + 1) + x] = convertYCbCrToABGR(y2, cb, cr); imageBuffer[bufferWidth * (y + 1) + x + 1] = convertYCbCrToABGR(y3, cb, cr); } } } else { for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { u8 yy = Y[width * y + x]; u8 cb = Cb[(width >> widthShift) * (y >> heightShift) + (x >> widthShift)]; u8 cr = Cr[(width >> widthShift) * (y >> heightShift) + (x >> widthShift)]; imageBuffer[bufferWidth * y + x] = convertYCbCrToABGR(yy, cb, cr); } } } NotifyMemInfo(MemBlockFlags::READ, yCbCrAddr, sizeY + sizeCb + sizeCb, "JpegCsc"); NotifyMemInfo(MemBlockFlags::WRITE, imageAddr, (uint32_t)destSize, "JpegCsc"); if ((widthHeight & 0xFFFF) == 0) return hleLogSuccessI(ME, -1); return hleLogSuccessI(ME, 0); } static int JpegMJpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth, int &usec) { int height = widthHeight & 0xFFFF; int width = (widthHeight >> 16) & 0xFFFF; if (bufferWidth < 0) bufferWidth = bufferWidth > -901 ? 901 + bufferWidth : 0; if (height == 0) height = 1; int sizeY = width * height; int sizeCb = sizeY >> 2; if (width > 720 || height > 480) return hleLogError(ME, ERROR_JPEG_INVALID_SIZE, "invalid size, max 720x480"); if (bufferWidth > 1024) return hleLogError(ME, ERROR_JPEG_INVALID_SIZE, "invalid stride, max 1024"); uint32_t destSize = (bufferWidth * (height - 1) + width) * 4; if (!Memory::IsValidRange(imageAddr, destSize)) return hleLogError(ME, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid dest address or size"); u32_le *imageBuffer = (u32_le *)Memory::GetPointerWriteUnchecked(imageAddr); const u8 *Y = (const u8 *)Memory::GetPointerUnchecked(yCbCrAddr); const u8 *Cb = Y + sizeY; const u8 *Cr = Cb + sizeCb; // Very approximate estimate based on tests on a PSP. Usually under. // The PSP behaves strangely for heights below 16, not rescheduling and writing fewer bytes. if (height >= 16) { usec += 9 * height; } if (!Memory::IsValidRange(yCbCrAddr, sizeY + sizeCb + sizeCb)) { // Seems to write based on zeros? Maybe reuses some other value? for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { imageBuffer[bufferWidth * y + x] = convertYCbCrToABGR(0, 0, 0); } } } else if ((widthHeight & 0x00010001) == 0 && height > 1) { for (int y = 0; y < height; y += 2) { for (int x = 0; x < width; x += 2) { u8 y0 = Y[width * y + x]; u8 y1 = Y[width * y + x + 1]; u8 y2 = Y[width * (y + 1) + x]; u8 y3 = Y[width * (y + 1) + x + 1]; u8 cb = Cb[(width >> 1) * (y >> 1) + (x >> 1)]; u8 cr = Cr[(width >> 1) * (y >> 1) + (x >> 1)]; imageBuffer[bufferWidth * y + x] = convertYCbCrToABGR(y0, cb, cr); imageBuffer[bufferWidth * y + x + 1] = convertYCbCrToABGR(y1, cb, cr); imageBuffer[bufferWidth * (y + 1) + x] = convertYCbCrToABGR(y2, cb, cr); imageBuffer[bufferWidth * (y + 1) + x + 1] = convertYCbCrToABGR(y3, cb, cr); } } NotifyMemInfo(MemBlockFlags::READ, yCbCrAddr, sizeY + sizeCb + sizeCb, "JpegMJpegCsc"); } else { for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { u8 yy = Y[width * y + x]; u8 cb = Cb[(width >> 1) * (y >> 1) + (x >> 1)]; u8 cr = Cr[(width >> 1) * (y >> 1) + (x >> 1)]; imageBuffer[bufferWidth * y + x] = convertYCbCrToABGR(yy, cb, cr); } } NotifyMemInfo(MemBlockFlags::READ, yCbCrAddr, sizeY + sizeCb + sizeCb, "JpegMJpegCsc"); } NotifyMemInfo(MemBlockFlags::WRITE, imageAddr, destSize, "JpegMJpegCsc"); return hleLogSuccessI(ME, 0); } static int sceJpegMJpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth) { if (mjpegInited == 0) return hleLogError(ME, 0x80000001, "not yet inited"); int usec = 0; int result = JpegMJpegCsc(imageAddr, yCbCrAddr, widthHeight, bufferWidth, usec); int width = (widthHeight >> 16) & 0xFFF; int height = widthHeight & 0xFFF; if (result >= 0) gpu->PerformWriteFormattedFromMemory(imageAddr, width * height * 4, width, GE_FORMAT_8888); if (usec != 0) return hleDelayResult(result, "jpeg csc", usec); return result; } static u32 convertARGBtoABGR(u32 argb) { return ((argb & 0xFF00FF00)) | ((argb & 0x000000FF) << 16) | ((argb & 0x00FF0000) >> 16); } static int DecodeJpeg(u32 jpegAddr, int jpegSize, u32 imageAddr, int &usec) { if (!Memory::IsValidRange(jpegAddr, jpegSize)) return hleLogError(ME, ERROR_JPEG_NO_SOI, "invalid jpeg address"); if (jpegSize == 0) return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "invalid jpeg data"); NotifyMemInfo(MemBlockFlags::READ, jpegAddr, jpegSize, "JpegDecodeMJpeg"); const u8 *buf = Memory::GetPointerUnchecked(jpegAddr); if (jpegSize < 2 || buf[0] != 0xFF || buf[1] != 0xD8) return hleLogError(ME, ERROR_JPEG_NO_SOI, "no SOI found, invalid data"); int width, height, actual_components; unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, 3); if (actual_components != 3 && actual_components != 1) { // The assumption that the image was RGB was wrong... // Try again. int components = actual_components; jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, components); } if (jpegBuf == nullptr) { return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "unable to decompress jpeg"); } usec += (width * height) / 14; if (!Memory::IsValidRange(imageAddr, mjpegWidth * mjpegHeight * 4)) { free(jpegBuf); return hleLogError(ME, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid output address"); } // Note: even if you Delete, the size is still allowed. if (width > mjpegWidth || height > mjpegHeight) { free(jpegBuf); return hleLogError(ME, ERROR_JPEG_INVALID_SIZE, "invalid output address"); } if (mjpegInited == 0) { // If you finish after setting the size, then call this - you get an interesting error. free(jpegBuf); return hleLogError(ME, 0x80000001, "mjpeg not inited"); } usec += (width * height) / 110; if (actual_components == 3 || actual_components == 1) { u24_be *imageBuffer = (u24_be*)jpegBuf; u32_le *abgr = (u32_le *)Memory::GetPointerUnchecked(imageAddr); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; x++) { abgr[x] = convertARGBtoABGR(imageBuffer[x]); } imageBuffer += width; abgr += mjpegWidth; } NotifyMemInfo(MemBlockFlags::WRITE, imageAddr, mjpegWidth * height, "JpegDecodeMJpeg"); } free(jpegBuf); return hleLogSuccessX(ME, getWidthHeight(width, height)); } static int sceJpegDecodeMJpeg(u32 jpegAddr, int jpegSize, u32 imageAddr, int dhtMode) { if ((jpegAddr | jpegSize | (jpegAddr + jpegSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid jpeg address"); if (imageAddr & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid output address"); int usec = 300; int result = DecodeJpeg(jpegAddr, jpegSize, imageAddr, usec); return hleDelayResult(result, "jpeg decode", usec); } static int sceJpegDecodeMJpegSuccessively(u32 jpegAddr, int jpegSize, u32 imageAddr, int dhtMode) { if ((jpegAddr | jpegSize | (jpegAddr + jpegSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid jpeg address"); if (imageAddr & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid output address"); int usec = 300; int result = DecodeJpeg(jpegAddr, jpegSize, imageAddr, usec); return hleDelayResult(result, "jpeg decode", usec); } static int sceJpegCsc(u32 imageAddr, u32 yCbCrAddr, int widthHeight, int bufferWidth, int colourInfo) { int usec = 0; int result = JpegCsc(imageAddr, yCbCrAddr, widthHeight, bufferWidth, colourInfo, usec); if (usec != 0) return hleDelayResult(result, "jpeg csc", usec); return result; } static int getYCbCrBufferSize(int w, int h) { // Return necessary buffer size for conversion: 12 bits per pixel return ((w * h) >> 1) * 3; } static int JpegGetOutputInfo(u32 jpegAddr, int jpegSize, u32 colourInfoAddr) { if (!Memory::IsValidRange(jpegAddr, jpegSize)) return hleLogError(ME, ERROR_JPEG_NO_SOI, "invalid jpeg address"); if (jpegSize == 0) return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "invalid jpeg data"); NotifyMemInfo(MemBlockFlags::READ, jpegAddr, jpegSize, "JpegGetOutputInfo"); const u8 *buf = Memory::GetPointerUnchecked(jpegAddr); if (jpegSize < 2 || buf[0] != 0xFF || buf[1] != 0xD8) return hleLogError(ME, ERROR_JPEG_NO_SOI, "no SOI found, invalid data"); int width, height, actual_components; unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, 3); if (actual_components != 3 && actual_components != 1) { // The assumption that the image was RGB was wrong... // Try again. int components = actual_components; jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, components); } if (jpegBuf == nullptr) { return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "unable to decompress jpeg"); } free(jpegBuf); // Buffer to store info about the color space in use. // - Bits 24 to 32 (Always empty): 0x00 // - Bits 16 to 24 (Color mode): 0x00 (Unknown), 0x01 (Greyscale) or 0x02 (YCbCr) // - Bits 8 to 16 (Vertical chroma subsampling value): 0x00, 0x01 or 0x02 // - Bits 0 to 8 (Horizontal chroma subsampling value): 0x00, 0x01 or 0x02 if (Memory::IsValidAddress(colourInfoAddr)) { // Note: can't actually seem to get any other subsampling values or color modes to work on a PSP. Memory::Write_U32(0x00020202, colourInfoAddr); NotifyMemInfo(MemBlockFlags::WRITE, colourInfoAddr, 4, "JpegGetOutputInfo"); } #ifdef JPEG_DEBUG const u8 *jpegDumpBuf = Memory::GetPointer(jpegAddr); u32 jpeg_xxhash = XXH32((const char *)jpegDumpBuf, jpegSize, 0xC0108888); Path jpegDir("Jpeg"); Path jpegFile = jpegDir / StringFromFormat("%X.jpg", jpeg_xxhash); FILE *wfp = File::OpenCFile(jpegFile, "wb"); if (!wfp) { File::CreateDir(jpegDir); wfp = File::OpenCFile(jpegFile, "wb"); } fwrite(jpegDumpBuf, 1, jpegSize, wfp); fclose(wfp); #endif //JPEG_DEBUG return hleLogSuccessX(ME, getYCbCrBufferSize(width, height)); } static int sceJpegGetOutputInfo(u32 jpegAddr, int jpegSize, u32 colourInfoAddr, int dhtMode) { if ((jpegAddr | jpegSize | (jpegAddr + jpegSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid jpeg address"); int result = JpegGetOutputInfo(jpegAddr, jpegSize, colourInfoAddr); // Time taken varies a bit, this is the low end. Depends on data. // Note that errors delay as well. return hleDelayResult(result, "jpeg get output info", 250); } static u32 convertRGBToYCbCr(u32 rgb) { //see http://en.wikipedia.org/wiki/Yuv#Y.27UV444_to_RGB888_conversion for more information. u8 r = (rgb >> 16) & 0xFF; u8 g = (rgb >> 8) & 0xFF; u8 b = (rgb >> 0) & 0xFF; int y = 0.299f * r + 0.587f * g + 0.114f * b + 0; int cb = -0.169f * r - 0.331f * g + 0.499f * b + 128.0f; int cr = 0.499f * r - 0.418f * g - 0.0813f * b + 128.0f; // check yCbCr value if ( y > 0xFF) y = 0xFF; if ( y < 0) y = 0; if (cb > 0xFF) cb = 0xFF; if (cb < 0) cb = 0; if (cr > 0xFF) cr = 0xFF; if (cr < 0) cr = 0; return (y << 16) | (cb << 8) | cr; } static int JpegConvertRGBToYCbCr(const void *data, u8 *output, int width, int height) { u24_be *imageBuffer = (u24_be *)data; int sizeY = width * height; int sizeCb = sizeY >> 2; u8 *Y = output; u8 *Cb = Y + sizeY; u8 *Cr = Cb + sizeCb; if ((width & 1) == 0 && (height & 1) == 0) { for (int y = 0; y < height; y += 2) { for (int x = 0; x < width; x += 2) { u32 rgb0 = imageBuffer[width * y + x]; u32 rgb1 = imageBuffer[width * y + x + 1]; u32 rgb2 = imageBuffer[width * (y + 1) + x]; u32 rgb3 = imageBuffer[width * (y + 1) + x + 1]; u32 yCbCr0 = convertRGBToYCbCr(rgb0); u32 yCbCr1 = convertRGBToYCbCr(rgb1); u32 yCbCr2 = convertRGBToYCbCr(rgb2); u32 yCbCr3 = convertRGBToYCbCr(rgb3); Y[width * y + x] = (yCbCr0 >> 16) & 0xFF; Y[width * y + x + 1] = (yCbCr1 >> 16) & 0xFF; Y[width * (y + 1) + x] = (yCbCr2 >> 16) & 0xFF; Y[width * (y + 1) + x + 1] = (yCbCr3 >> 16) & 0xFF; Cb[(width >> 1) * (y >> 1) + (x >> 1)] = (yCbCr0 >> 8) & 0xFF; Cr[(width >> 1) * (y >> 1) + (x >> 1)] = yCbCr0 & 0xFF; } } } else { for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { u32 rgb = imageBuffer[width * y + x]; u32 yCbCr = convertRGBToYCbCr(rgb); Y[width * y + x] = (yCbCr >> 16) & 0xFF; if ((y & 1) == 0 && (x & 1) == 0) { // Ideally, would average, but I suppose these just came from a JPEG, so they ought to match. Cb[(width >> 1) * (y >> 1) + (x >> 1)] = (yCbCr >> 8) & 0xFF; Cr[(width >> 1) * (y >> 1) + (x >> 1)] = yCbCr & 0xFF; } } } } return getWidthHeight(width, height); } static int JpegDecodeMJpegYCbCr(u32 jpegAddr, int jpegSize, u32 yCbCrAddr, int yCbCrSize, int &usec) { if (!Memory::IsValidRange(jpegAddr, jpegSize)) return hleLogError(ME, ERROR_JPEG_NO_SOI, "invalid jpeg address"); if (jpegSize == 0) return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "invalid jpeg data"); NotifyMemInfo(MemBlockFlags::READ, jpegAddr, jpegSize, "JpegDecodeMJpegYCbCr"); const u8 *buf = Memory::GetPointerUnchecked(jpegAddr); if (jpegSize < 2 || buf[0] != 0xFF || buf[1] != 0xD8) return hleLogError(ME, ERROR_JPEG_NO_SOI, "no SOI found, invalid data"); int width, height, actual_components; unsigned char *jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, 3); if (actual_components != 3 && actual_components != 1) { // The assumption that the image was RGB was wrong... // Try again. int components = actual_components; jpegBuf = jpgd::decompress_jpeg_image_from_memory(buf, jpegSize, &width, &height, &actual_components, components); } if (jpegBuf == nullptr) { return hleLogError(ME, ERROR_JPEG_INVALID_DATA, "unable to decompress jpeg"); } if (yCbCrSize < getYCbCrBufferSize(width, height)) { free(jpegBuf); return hleLogError(ME, ERROR_JPEG_OUT_OF_MEMORY, "buffer not large enough"); } // Technically, it seems like the PSP doesn't support grayscale, but we might as well. if (actual_components == 3 || actual_components == 1) { if (Memory::IsValidRange(yCbCrAddr, getYCbCrBufferSize(width, height))) { JpegConvertRGBToYCbCr(jpegBuf, Memory::GetPointerWriteUnchecked(yCbCrAddr), width, height); NotifyMemInfo(MemBlockFlags::WRITE, yCbCrAddr, getYCbCrBufferSize(width, height), "JpegDecodeMJpegYCbCr"); } else { // There's some weird behavior on the PSP where it writes data around the last passed address. WARN_LOG_REPORT(ME, "JpegDecodeMJpegYCbCr: Invalid output address (%08x / %08x) for %dx%d", yCbCrAddr, yCbCrSize, width, height); } } free(jpegBuf); // Rough estimate based on observed timing. usec += (width * height) / 14; return hleLogSuccessX(ME, getWidthHeight(width, height)); } static int sceJpegDecodeMJpegYCbCr(u32 jpegAddr, int jpegSize, u32 yCbCrAddr, int yCbCrSize, int dhtMode) { if ((jpegAddr | jpegSize | (jpegAddr + jpegSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid jpeg address"); if ((yCbCrAddr | yCbCrSize | (yCbCrAddr + yCbCrSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid output address"); if (!Memory::IsValidRange(jpegAddr, jpegSize)) return hleLogError(ME, ERROR_JPEG_INVALID_VALUE, "invalid jpeg address"); int usec = 300; int result = JpegDecodeMJpegYCbCr(jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, usec); return hleDelayResult(result, "jpeg decode", usec); } static int sceJpegDecodeMJpegYCbCrSuccessively(u32 jpegAddr, int jpegSize, u32 yCbCrAddr, int yCbCrSize, int dhtMode) { if ((jpegAddr | jpegSize | (jpegAddr + jpegSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid jpeg address"); if ((yCbCrAddr | yCbCrSize | (yCbCrAddr + yCbCrSize)) & 0x80000000) return hleLogError(ME, SCE_KERNEL_ERROR_PRIV_REQUIRED, "invalid output address"); // Do as same way as sceJpegDecodeMJpegYCbCr() but with smaller block size int usec = 300; int result = JpegDecodeMJpegYCbCr(jpegAddr, jpegSize, yCbCrAddr, yCbCrSize, usec); return hleDelayResult(result, "jpeg decode", usec); } static int sceJpeg_9B36444C() { ERROR_LOG_REPORT(ME, "UNIMPL sceJpeg_9B36444C()"); return 0; } static int sceJpegCreateMJpeg(int width, int height) { if (mjpegInited == 0) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "not yet inited"); if (mjpegInited == 2) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "already created"); if (width > 1024) return hleLogError(ME, ERROR_JPEG_INVALID_SIZE, "width outside bounds"); mjpegInited = 2; mjpegWidth = width; mjpegHeight = height; return hleLogSuccessInfoI(ME, 0); } static int sceJpegDeleteMJpeg() { if (mjpegInited == 0) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "not yet inited"); if (mjpegInited == 1) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "not yet created"); mjpegInited = 1; return hleLogSuccessInfoI(ME, 0); } static int sceJpegInitMJpeg() { if (mjpegInited == 1 || mjpegInited == 2) return hleLogError(ME, ERROR_JPEG_ALREADY_INIT, "already inited"); // If it was -1, it's from an old save state, avoid double init error but assume inited. if (mjpegInited == 0) mjpegInited = 1; return hleLogSuccessI(ME, hleDelayResult(0, "mjpeg init", 130)); } static int sceJpegFinishMJpeg() { if (mjpegInited == 0) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "already inited"); if (mjpegInited == 2) return hleLogError(ME, ERROR_JPEG_INVALID_STATE, "mjpeg not deleted"); // Even from an old save state, if we see this we leave compat mode. mjpegInited = 0; return hleLogSuccessI(ME, hleDelayResult(0, "mjpeg finish", 120)); } static int sceJpegMJpegCscWithColorOption() { ERROR_LOG_REPORT(ME, "UNIMPL sceJpegMJpegCscWithColorOption()"); return 0; } static int sceJpegDecompressAllImage() { ERROR_LOG_REPORT(ME, "UNIMPL sceJpegDecompressAllImage()"); return 0; } void JpegNotifyLoadStatus(int state) { if (state == -1) { // Reset our state on unload. __JpegInit(); } } const HLEFunction sceJpeg[] = { {0X0425B986, &WrapI_V, "sceJpegDecompressAllImage", 'i', "" }, {0X04B5AE02, &WrapI_UUII, "sceJpegMJpegCsc", 'i', "xxxi" }, {0X04B93CEF, &WrapI_UIUI, "sceJpegDecodeMJpeg", 'x', "xixi" }, {0X227662D7, &WrapI_UIUII, "sceJpegDecodeMJpegYCbCrSuccessively", 'x', "xixii"}, {0X48B602B7, &WrapI_V, "sceJpegDeleteMJpeg", 'i', "" }, {0X64B6F978, &WrapI_UIUI, "sceJpegDecodeMJpegSuccessively", 'x', "xixi" }, {0X67F0ED84, &WrapI_UUIII, "sceJpegCsc", 'i', "xxxix"}, {0X7D2F3D7F, &WrapI_V, "sceJpegFinishMJpeg", 'i', "" }, {0X8F2BB012, &WrapI_UIUI, "sceJpegGetOutputInfo", 'x', "xipi" }, {0X91EED83C, &WrapI_UIUII, "sceJpegDecodeMJpegYCbCr", 'x', "xixii"}, {0X9B36444C, &WrapI_V, "sceJpeg_9B36444C", 'i', "" }, {0X9D47469C, &WrapI_II, "sceJpegCreateMJpeg", 'i', "ii" }, {0XAC9E70E6, &WrapI_V, "sceJpegInitMJpeg", 'i', "" }, {0XA06A75C4, &WrapI_V, "sceJpegMJpegCscWithColorOption", 'i', "" }, }; void Register_sceJpeg() { RegisterModule("sceJpeg", ARRAY_SIZE(sceJpeg), sceJpeg); }