ppsspp/Core/HLE/scePsmf.cpp
Unknown W. Brackets 2450724be2 Make Memory::Memcpy() execute memchecks directly.
This makes it easier to handle breakpoints in HLE.
2015-04-05 18:09:20 -07:00

2063 lines
74 KiB
C++

// 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 "Common/ChunkFile.h"
#include "Core/MemMapHelpers.h"
#include "Core/Reporting.h"
#include "Core/System.h"
#include "Core/FileSystems/MetaFileSystem.h"
#include "Core/HLE/HLE.h"
#include "Core/HLE/HLEHelperThread.h"
#include "Core/HLE/FunctionWrappers.h"
#include "Core/HLE/scePsmf.h"
#include "Core/HLE/sceMpeg.h"
#include "Core/HW/MediaEngine.h"
#include "GPU/GPUInterface.h"
#include "GPU/GPUState.h"
#include <map>
#include <algorithm>
// "Go Sudoku" is a good way to test this code...
const int PSMF_VIDEO_STREAM_ID = 0xE0;
const int PSMF_AUDIO_STREAM_ID = 0xBD;
const int PSMF_AVC_STREAM = 0;
const int PSMF_ATRAC_STREAM = 1;
const int PSMF_PCM_STREAM = 2;
const int PSMF_DATA_STREAM = 3;
const int PSMF_AUDIO_STREAM = 15;
const int PSMF_PLAYER_VERSION_FULL = 0;
const int PSMF_PLAYER_VERSION_BASIC = 1;
const int PSMF_PLAYER_VERSION_NET = 2;
const int PSMF_PLAYER_CONFIG_LOOP = 0;
const int PSMF_PLAYER_CONFIG_NO_LOOP = 1;
const int PSMF_PLAYER_CONFIG_MODE_LOOP = 0;
const int PSMF_PLAYER_CONFIG_MODE_PIXEL_TYPE = 1;
const int PSMF_PLAYER_WARMUP_FRAMES = 3;
static const int VIDEO_FRAME_DURATION_TS = 3003;
int psmfMaxAheadTimestamp = 40000;
int audioSamples = 2048;
int audioSamplesBytes = audioSamples * 4;
int videoPixelMode = GE_CMODE_32BIT_ABGR8888;
int videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP;
enum PsmfPlayerError {
ERROR_PSMF_NOT_INITIALIZED = 0x80615001,
ERROR_PSMF_BAD_VERSION = 0x80615002,
ERROR_PSMF_NOT_FOUND = 0x80615025,
ERROR_PSMF_INVALID_ID = 0x80615100,
ERROR_PSMF_INVALID_VALUE = 0x806151fe,
ERROR_PSMF_INVALID_TIMESTAMP = 0x80615500,
ERROR_PSMF_INVALID_PSMF = 0x80615501,
ERROR_PSMFPLAYER_INVALID_STATUS = 0x80616001,
ERROR_PSMFPLAYER_INVALID_STREAM = 0x80616003,
ERROR_PSMFPLAYER_BUFFER_SIZE = 0x80616005,
ERROR_PSMFPLAYER_INVALID_CONFIG = 0x80616006,
ERROR_PSMFPLAYER_INVALID_PARAM = 0x80616008,
ERROR_PSMFPLAYER_NO_MORE_DATA = 0x8061600c,
};
enum PsmfPlayerStatus {
PSMF_PLAYER_STATUS_NONE = 0x0,
PSMF_PLAYER_STATUS_INIT = 0x1,
PSMF_PLAYER_STATUS_STANDBY = 0x2,
PSMF_PLAYER_STATUS_PLAYING = 0x4,
PSMF_PLAYER_STATUS_ERROR = 0x100,
PSMF_PLAYER_STATUS_PLAYING_FINISHED = 0x200,
};
enum PsmfPlayerMode {
PSMF_PLAYER_MODE_PLAY = 0,
PSMF_PLAYER_MODE_SLOWMOTION = 1,
PSMF_PLAYER_MODE_STEPFRAME = 2,
PSMF_PLAYER_MODE_PAUSE = 3,
PSMF_PLAYER_MODE_FORWARD = 4,
PSMF_PLAYER_MODE_REWIND = 5,
};
struct PsmfData {
u32_le version;
u32_le headerSize;
u32_le headerOffset;
u32_le streamSize;
u32_le streamOffset;
u32_le streamNum;
u32_le unk1;
u32_le unk2;
};
struct PsmfPlayerCreateData {
PSPPointer<u8> buffer;
u32 bufferSize;
int threadPriority;
};
struct PsmfPlayerData {
s32_le videoCodec;
s32_le videoStreamNum;
s32_le audioCodec;
s32_le audioStreamNum;
s32_le playMode;
s32_le playSpeed;
};
struct PsmfInfo {
u32_le lastFrameTS;
s32_le numVideoStreams;
s32_le numAudioStreams;
s32_le numPCMStreams;
s32_le playerVersion;
};
struct PsmfVideoData {
s32_le frameWidth;
u32_le displaybuf;
u32_le displaypts;
};
struct PsmfEntry {
int EPPts;
int EPOffset;
int EPIndex;
int EPPicOffset;
};
static int getMaxAheadTimestamp(int packets) {
return std::max(40000, packets * 700);
}
// Some of our platforms don't play too nice with direct unaligned access.
static u32 ReadUnalignedU32BE(const u8 *p) {
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | (p[3] << 0);
}
class PsmfStream;
// This does NOT match the raw structure. Due to endianness etc,
// we read it manually.
// TODO: Change to work directly with the data in RAM instead of this
// JPSCP-esque class.
typedef std::map<int, PsmfStream *> PsmfStreamMap;
class Psmf {
public:
// For savestates only.
Psmf() {}
Psmf(const u8 *ptr, u32 data);
~Psmf();
void DoState(PointerWrap &p);
bool isValidCurrentStreamNumber() const {
return currentStreamNum >= 0 && currentStreamNum < (int)streamMap.size(); // urgh, checking size isn't really right here.
}
void setStreamNum(int num);
bool setStreamWithType(int type, int channel);
int FindEPWithTimestamp(int pts) const;
u32 magic;
u32 version;
u32 streamOffset;
u32 streamSize;
u32 headerSize;
u32 headerOffset;
u32 streamType;
u32 streamChannel;
// 0x50
u32 streamDataTotalSize;
u32 presentationStartTime;
u32 presentationEndTime;
u32 streamDataNextBlockSize;
u32 streamDataNextInnerBlockSize;
int numStreams;
int currentStreamNum;
int currentAudioStreamNum;
int currentVideoStreamNum;
// parameters gotten from streams
// I guess this is the seek information?
u32 EPMapOffset;
u32 EPMapEntriesNum;
int videoWidth;
int videoHeight;
int audioChannels;
int audioFrequency;
std::vector<PsmfEntry> EPMap;
PsmfStreamMap streamMap;
};
class PsmfPlayer {
public:
// For savestates only.
PsmfPlayer() : filehandle(0), finishThread(nullptr) {
mediaengine = new MediaEngine();
}
PsmfPlayer(const PsmfPlayerCreateData *data);
~PsmfPlayer() {
if (mediaengine)
delete mediaengine;
pspFileSystem.CloseFile(filehandle);
}
void DoState(PointerWrap &p);
void ScheduleFinish(u32 handle) {
if (!finishThread) {
finishThread = new HLEHelperThread("scePsmfPlayer", "scePsmfPlayer", "__PsmfPlayerFinish", playbackThreadPriority, 0x100);
finishThread->Start(handle, 0);
}
}
void AbortFinish() {
if (finishThread) {
delete finishThread;
finishThread = nullptr;
}
}
bool HasReachedEnd() {
bool videoPtsEnd = (s64)psmfPlayerAvcAu.pts >= (s64)totalDurationTimestamp - VIDEO_FRAME_DURATION_TS;
// If we're out of video data and have no audio, it's over even if the pts isn't there yet.
return videoPtsEnd || (mediaengine->IsVideoEnd() && mediaengine->IsNoAudioData());
}
u32 filehandle;
u32 fileoffset;
int readSize;
int streamSize;
u8 tempbuf[0x10000];
int videoCodec;
int videoStreamNum;
int audioCodec;
int audioStreamNum;
int playMode;
int playSpeed;
u64 totalDurationTimestamp;
int displayBuffer;
int displayBufferSize;
int playbackThreadPriority;
int psmfMaxAheadTimestamp;
int totalVideoStreams;
int totalAudioStreams;
int playerVersion;
int videoStep;
int warmUp;
s64 seekDestTimeStamp;
SceMpegAu psmfPlayerAtracAu;
SceMpegAu psmfPlayerAvcAu;
PsmfPlayerStatus status;
MediaEngine *mediaengine;
HLEHelperThread *finishThread;
};
class PsmfStream {
public:
// Used for save states.
PsmfStream() {
}
PsmfStream(int type, int channel) {
this->type = type;
this->channel = channel;
}
void readMPEGVideoStreamParams(const u8 *addr, const u8 *data, Psmf *psmf) {
int streamId = addr[0];
int privateStreamId = addr[1];
// two unknowns here
psmf->EPMapOffset = ReadUnalignedU32BE(&addr[4]);
psmf->EPMapEntriesNum = ReadUnalignedU32BE(&addr[8]);
psmf->videoWidth = addr[12] * 16;
psmf->videoHeight = addr[13] * 16;
const u32 EP_MAP_STRIDE = 1 + 1 + 4 + 4;
psmf->EPMap.clear();
for (u32 i = 0; i < psmf->EPMapEntriesNum; i++) {
const u8 *const entryAddr = data + psmf->EPMapOffset + EP_MAP_STRIDE * i;
PsmfEntry entry;
entry.EPIndex = entryAddr[0];
entry.EPPicOffset = entryAddr[1];
entry.EPPts = ReadUnalignedU32BE(&entryAddr[2]);
entry.EPOffset = ReadUnalignedU32BE(&entryAddr[6]);
psmf->EPMap.push_back(entry);
}
INFO_LOG(ME, "PSMF MPEG data found: id=%02x, privid=%02x, epmoff=%08x, epmnum=%08x, width=%i, height=%i", streamId, privateStreamId, psmf->EPMapOffset, psmf->EPMapEntriesNum, psmf->videoWidth, psmf->videoHeight);
}
void readPrivateAudioStreamParams(const u8 *addr, Psmf *psmf) {
int streamId = addr[0];
int privateStreamId = addr[1];
psmf->audioChannels = addr[14];
psmf->audioFrequency = addr[15];
// two unknowns here
INFO_LOG(ME, "PSMF private audio found: id=%02x, privid=%02x, channels=%i, freq=%i", streamId, privateStreamId, psmf->audioChannels, psmf->audioFrequency);
}
void DoState(PointerWrap &p) {
auto s = p.Section("PsmfStream", 1);
if (!s)
return;
p.Do(type);
p.Do(channel);
}
int type;
int channel;
};
Psmf::Psmf(const u8 *ptr, u32 data) {
headerOffset = data;
magic = *(u32_le *)&ptr[0];
version = *(u32_le *)&ptr[4];
streamOffset = ReadUnalignedU32BE(&ptr[8]);
streamSize = ReadUnalignedU32BE(&ptr[12]);
streamDataTotalSize = ReadUnalignedU32BE(&ptr[0x50]);
presentationStartTime = getMpegTimeStamp(ptr + PSMF_FIRST_TIMESTAMP_OFFSET);
presentationEndTime = getMpegTimeStamp(ptr + PSMF_LAST_TIMESTAMP_OFFSET);
streamDataNextBlockSize = ReadUnalignedU32BE(&ptr[0x6A]);
streamDataNextInnerBlockSize = ReadUnalignedU32BE(&ptr[0x7C]);
numStreams = *(u16_be *)&ptr[0x80];
// TODO: Always?
headerSize = 0x800;
currentStreamNum = -1;
currentAudioStreamNum = -1;
currentVideoStreamNum = -1;
for (int i = 0; i < numStreams; i++) {
PsmfStream *stream = 0;
const u8 *const currentStreamAddr = ptr + 0x82 + i * 16;
int streamId = currentStreamAddr[0];
if ((streamId & PSMF_VIDEO_STREAM_ID) == PSMF_VIDEO_STREAM_ID) {
stream = new PsmfStream(PSMF_AVC_STREAM, ++currentVideoStreamNum);
stream->readMPEGVideoStreamParams(currentStreamAddr, ptr, this);
} else if ((streamId & PSMF_AUDIO_STREAM_ID) == PSMF_AUDIO_STREAM_ID) {
stream = new PsmfStream(PSMF_ATRAC_STREAM, ++currentAudioStreamNum);
stream->readPrivateAudioStreamParams(currentStreamAddr, this);
}
if (stream) {
currentStreamNum++;
streamMap[currentStreamNum] = stream;
}
}
}
Psmf::~Psmf() {
for (auto it = streamMap.begin(), end = streamMap.end(); it != end; ++it) {
delete it->second;
}
streamMap.clear();
}
PsmfPlayer::PsmfPlayer(const PsmfPlayerCreateData *data) {
videoCodec = -1;
videoStreamNum = -1;
audioCodec = -1;
audioStreamNum = -1;
playMode = 0;
playSpeed = 1;
totalDurationTimestamp = 0;
status = PSMF_PLAYER_STATUS_INIT;
mediaengine = new MediaEngine;
finishThread = nullptr;
filehandle = 0;
fileoffset = 0;
readSize = 0;
streamSize = 0;
videoStep = 0;
warmUp = 0;
seekDestTimeStamp = 0;
psmfPlayerAtracAu.dts =-1;
psmfPlayerAtracAu.pts = -1;
psmfPlayerAvcAu.dts = -1;
psmfPlayerAvcAu.pts = -1;
displayBuffer = data->buffer.ptr;
displayBufferSize = data->bufferSize;
playbackThreadPriority = data->threadPriority;
}
void Psmf::DoState(PointerWrap &p) {
auto s = p.Section("Psmf", 1, 2);
if (!s)
return;
p.Do(magic);
p.Do(version);
p.Do(streamOffset);
p.Do(streamSize);
p.Do(headerOffset);
p.Do(streamDataTotalSize);
p.Do(presentationStartTime);
p.Do(presentationEndTime);
p.Do(streamDataNextBlockSize);
p.Do(streamDataNextInnerBlockSize);
p.Do(numStreams);
p.Do(currentStreamNum);
p.Do(currentAudioStreamNum);
p.Do(currentVideoStreamNum);
p.Do(EPMapOffset);
p.Do(EPMapEntriesNum);
p.Do(videoWidth);
p.Do(videoHeight);
p.Do(audioChannels);
p.Do(audioFrequency);
if (s >= 2) {
p.Do(EPMap);
}
p.Do(streamMap);
}
void PsmfPlayer::DoState(PointerWrap &p) {
auto s = p.Section("PsmfPlayer", 1, 7);
if (!s)
return;
p.Do(videoCodec);
p.Do(videoStreamNum);
p.Do(audioCodec);
p.Do(audioStreamNum);
p.Do(playMode);
p.Do(playSpeed);
p.Do(displayBuffer);
p.Do(displayBufferSize);
p.Do(playbackThreadPriority);
p.Do(psmfMaxAheadTimestamp);
if (s >= 4) {
p.Do(totalDurationTimestamp);
} else {
long oldTimestamp;
p.Do(oldTimestamp);
totalDurationTimestamp = oldTimestamp;
}
if (s >= 2) {
p.Do(totalVideoStreams);
p.Do(totalAudioStreams);
p.Do(playerVersion);
} else {
totalVideoStreams = 1;
totalAudioStreams = 1;
playerVersion = PSMF_PLAYER_VERSION_FULL;
}
if (s >= 3) {
p.Do(videoStep);
} else {
videoStep = 0;
}
if (s >= 4) {
p.Do(warmUp);
} else {
warmUp = 10000;
}
if (s >= 5) {
p.Do(seekDestTimeStamp);
} else {
seekDestTimeStamp = 0;
}
p.DoClass(mediaengine);
p.Do(filehandle);
p.Do(fileoffset);
p.Do(readSize);
p.Do(streamSize);
p.Do(status);
if (s >= 4) {
p.Do(psmfPlayerAtracAu);
}
p.Do(psmfPlayerAvcAu);
if (s >= 7) {
bool hasFinishThread = finishThread != NULL;
p.Do(hasFinishThread);
if (hasFinishThread) {
p.Do(finishThread);
}
} else if (s >= 6) {
p.Do(finishThread);
} else {
finishThread = NULL;
}
}
void Psmf::setStreamNum(int num) {
currentStreamNum = num;
if (!isValidCurrentStreamNumber())
return;
PsmfStreamMap::iterator iter = streamMap.find(currentStreamNum);
if (iter == streamMap.end())
return;
int type = iter->second->type;
int channel = iter->second->channel;
switch (type) {
case PSMF_AVC_STREAM:
if (currentVideoStreamNum != num) {
// TODO: Tell video mediaengine or something about channel.
currentVideoStreamNum = num;
}
break;
case PSMF_ATRAC_STREAM:
case PSMF_PCM_STREAM:
if (currentAudioStreamNum != num) {
// TODO: Tell audio mediaengine or something about channel.
currentAudioStreamNum = num;
}
break;
}
}
bool Psmf::setStreamWithType(int type, int channel) {
for (PsmfStreamMap::iterator iter = streamMap.begin(); iter != streamMap.end(); ++iter) {
if (iter->second->type == type && iter->second->channel == channel) {
setStreamNum(iter->first);
return true;
}
}
return false;
}
int Psmf::FindEPWithTimestamp(int pts) const {
int best = -1;
int bestPts = 0;
for (int i = 0; i < (int)EPMap.size(); ++i) {
const int matchPts = EPMap[i].EPPts;
if (matchPts == pts) {
// Exact match, take it.
return i;
}
// TODO: Does it actually do fuzzy matching?
if (matchPts < pts && matchPts >= bestPts) {
best = i;
bestPts = matchPts;
}
}
return best;
}
static std::map<u32, Psmf *> psmfMap;
static std::map<u32, PsmfPlayer *> psmfPlayerMap;
static Psmf *getPsmf(u32 psmf)
{
auto psmfstruct = PSPPointer<PsmfData>::Create(psmf);
if (!psmfstruct.IsValid())
return 0;
auto iter = psmfMap.find(psmfstruct->headerOffset);
if (iter != psmfMap.end())
return iter->second;
else
return 0;
}
static PsmfPlayer *getPsmfPlayer(u32 psmfplayer)
{
auto iter = psmfPlayerMap.find(Memory::Read_U32(psmfplayer));
if (iter != psmfPlayerMap.end())
return iter->second;
else
return 0;
}
void __PsmfInit()
{
videoPixelMode = GE_CMODE_32BIT_ABGR8888;
videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP;
}
void __PsmfDoState(PointerWrap &p)
{
auto s = p.Section("scePsmf", 1);
if (!s)
return;
p.Do(psmfMap);
}
void __PsmfPlayerDoState(PointerWrap &p)
{
auto s = p.Section("scePsmfPlayer", 1);
if (!s)
return;
p.Do(psmfPlayerMap);
p.Do(videoPixelMode);
p.Do(videoLoopStatus);
}
void __PsmfShutdown()
{
for (auto it = psmfMap.begin(), end = psmfMap.end(); it != end; ++it)
delete it->second;
for (auto it = psmfPlayerMap.begin(), end = psmfPlayerMap.end(); it != end; ++it)
delete it->second;
psmfMap.clear();
psmfPlayerMap.clear();
}
static u32 scePsmfSetPsmf(u32 psmfStruct, u32 psmfData)
{
if (!Memory::IsValidAddress(psmfData)) {
// TODO: Check error code.
ERROR_LOG_REPORT(ME, "scePsmfSetPsmf(%08x, %08x): bad address", psmfStruct, psmfData);
return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
}
INFO_LOG(ME, "scePsmfSetPsmf(%08x, %08x)", psmfStruct, psmfData);
Psmf *psmf = new Psmf(Memory::GetPointer(psmfData), psmfData);
PsmfData data = {0};
data.version = psmf->version;
data.headerSize = 0x800;
data.streamSize = psmf->streamSize;
data.streamNum = psmf->numStreams;
data.headerOffset = psmf->headerOffset;
auto iter = psmfMap.find(data.headerOffset);
if (iter != psmfMap.end())
delete iter->second;
psmfMap[data.headerOffset] = psmf;
Memory::WriteStruct(psmfStruct, &data);
return 0;
}
static u32 scePsmfGetNumberOfStreams(u32 psmfStruct)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetNumberOfStreams(%08x): invalid psmf", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetNumberOfStreams(%08x)", psmfStruct);
return psmf->numStreams;
}
static u32 scePsmfGetNumberOfSpecificStreams(u32 psmfStruct, int streamType)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetNumberOfSpecificStreams(%08x, %08x): invalid psmf", psmfStruct, streamType);
return ERROR_PSMF_NOT_FOUND;
}
WARN_LOG(ME, "scePsmfGetNumberOfSpecificStreams(%08x, %08x)", psmfStruct, streamType);
int streamNum = 0;
int type = (streamType == PSMF_AUDIO_STREAM ? PSMF_ATRAC_STREAM : streamType);
for (int i = (int)psmf->streamMap.size() - 1; i >= 0; i--) {
if (psmf->streamMap[i]->type == type)
streamNum++;
}
return streamNum;
}
static u32 scePsmfSpecifyStreamWithStreamType(u32 psmfStruct, u32 streamType, u32 channel)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfSpecifyStreamWithStreamType(%08x, %08x, %i): invalid psmf", psmfStruct, streamType, channel);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG(ME, "scePsmfSpecifyStreamWithStreamType(%08x, %08x, %i)", psmfStruct, streamType, channel);
if (!psmf->setStreamWithType(streamType, channel)) {
psmf->setStreamNum(-1);
}
return 0;
}
static u32 scePsmfSpecifyStreamWithStreamTypeNumber(u32 psmfStruct, u32 streamType, u32 typeNum)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfSpecifyStreamWithStreamTypeNumber(%08x, %08x, %08x): invalid psmf", psmfStruct, streamType, typeNum);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG_REPORT(ME, "scePsmfSpecifyStreamWithStreamTypeNumber(%08x, %08x, %08x)", psmfStruct, streamType, typeNum);
// right now typeNum and channel are the same...
if (!psmf->setStreamWithType(streamType, typeNum)) {
psmf->setStreamNum(-1);
}
return 0;
}
static u32 scePsmfSpecifyStream(u32 psmfStruct, int streamNum) {
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfSpecifyStream(%08x, %i): invalid psmf", psmfStruct, streamNum);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG(ME, "scePsmfSpecifyStream(%08x, %i)", psmfStruct, streamNum);
psmf->setStreamNum(streamNum);
return 0;
}
static u32 scePsmfGetVideoInfo(u32 psmfStruct, u32 videoInfoAddr) {
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetVideoInfo(%08x, %08x): invalid psmf", psmfStruct, videoInfoAddr);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG(ME, "scePsmfGetVideoInfo(%08x, %08x)", psmfStruct, videoInfoAddr);
if (Memory::IsValidAddress(videoInfoAddr)) {
Memory::Write_U32(psmf->videoWidth, videoInfoAddr);
Memory::Write_U32(psmf->videoHeight, videoInfoAddr + 4);
}
return 0;
}
static u32 scePsmfGetAudioInfo(u32 psmfStruct, u32 audioInfoAddr) {
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetAudioInfo(%08x, %08x): invalid psmf", psmfStruct, audioInfoAddr);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG(ME, "scePsmfGetAudioInfo(%08x, %08x)", psmfStruct, audioInfoAddr);
if (Memory::IsValidAddress(audioInfoAddr)) {
Memory::Write_U32(psmf->audioChannels, audioInfoAddr);
Memory::Write_U32(psmf->audioFrequency, audioInfoAddr + 4);
}
return 0;
}
static u32 scePsmfGetCurrentStreamType(u32 psmfStruct, u32 typeAddr, u32 channelAddr) {
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetCurrentStreamType(%08x, %08x, %08x): invalid psmf", psmfStruct, typeAddr, channelAddr);
return ERROR_PSMF_NOT_FOUND;
}
INFO_LOG(ME, "scePsmfGetCurrentStreamType(%08x, %08x, %08x)", psmfStruct, typeAddr, channelAddr);
if (Memory::IsValidAddress(typeAddr)) {
u32 type = 0, channel = 0;
if (psmf->streamMap.find(psmf->currentStreamNum) != psmf->streamMap.end())
type = psmf->streamMap[psmf->currentStreamNum]->type;
if (psmf->streamMap.find(psmf->currentStreamNum) != psmf->streamMap.end())
channel = psmf->streamMap[psmf->currentStreamNum]->channel;
Memory::Write_U32(type, typeAddr);
Memory::Write_U32(channel, channelAddr);
}
return 0;
}
static u32 scePsmfGetStreamSize(u32 psmfStruct, u32 sizeAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetStreamSize(%08x, %08x): invalid psmf", psmfStruct, sizeAddr);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetStreamSize(%08x, %08x)", psmfStruct, sizeAddr);
if (Memory::IsValidAddress(sizeAddr)) {
Memory::Write_U32(psmf->streamSize, sizeAddr);
}
return 0;
}
static u32 scePsmfQueryStreamOffset(u32 bufferAddr, u32 offsetAddr)
{
WARN_LOG(ME, "scePsmfQueryStreamOffset(%08x, %08x)", bufferAddr, offsetAddr);
if (Memory::IsValidAddress(offsetAddr)) {
Memory::Write_U32(bswap32(Memory::Read_U32(bufferAddr + PSMF_STREAM_OFFSET_OFFSET)), offsetAddr);
}
return 0;
}
static u32 scePsmfQueryStreamSize(u32 bufferAddr, u32 sizeAddr)
{
WARN_LOG(ME, "scePsmfQueryStreamSize(%08x, %08x)", bufferAddr, sizeAddr);
if (Memory::IsValidAddress(sizeAddr)) {
Memory::Write_U32(bswap32(Memory::Read_U32(bufferAddr + PSMF_STREAM_SIZE_OFFSET)), sizeAddr);
}
return 0;
}
static u32 scePsmfGetHeaderSize(u32 psmfStruct, u32 sizeAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetHeaderSize(%08x, %08x): invalid psmf", psmfStruct, sizeAddr);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetHeaderSize(%08x, %08x)", psmfStruct, sizeAddr);
if (Memory::IsValidAddress(sizeAddr)) {
Memory::Write_U32(psmf->headerSize, sizeAddr);
}
return 0;
}
static u32 scePsmfGetPsmfVersion(u32 psmfStruct)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetPsmfVersion(%08x): invalid psmf", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetPsmfVersion(%08x)", psmfStruct);
return psmf->version;
}
static u32 scePsmfVerifyPsmf(u32 psmfAddr)
{
u32 magic = Memory::Read_U32(psmfAddr);
if (magic != PSMF_MAGIC) {
ERROR_LOG(ME, "scePsmfVerifyPsmf(%08x): bad magic %08x", psmfAddr, magic);
return ERROR_PSMF_NOT_FOUND;
}
int version = Memory::Read_U32(psmfAddr + PSMF_STREAM_VERSION_OFFSET);
if (version < 0) {
ERROR_LOG(ME, "scePsmfVerifyPsmf(%08x): bad version %08x", psmfAddr, version);
return ERROR_PSMF_NOT_FOUND;
}
// Kurohyou 2 (at least the demo) uses an uninitialized value that happens to be zero on the PSP.
// It appears to be written by scePsmfVerifyPsmf(), so we write some bytes into the stack here.
Memory::Memset(currentMIPS->r[MIPS_REG_SP] - 0x20, 0, 0x20);
DEBUG_LOG(ME, "scePsmfVerifyPsmf(%08x)", psmfAddr);
return 0;
}
static u32 scePsmfGetNumberOfEPentries(u32 psmfStruct)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetNumberOfEPentries(%08x): invalid psmf", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetNumberOfEPentries(%08x)", psmfStruct);
return psmf->EPMapEntriesNum;
}
static u32 scePsmfGetPresentationStartTime(u32 psmfStruct, u32 startTimeAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetPresentationStartTime(%08x, %08x): invalid psmf", psmfStruct, startTimeAddr);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetPresentationStartTime(%08x, %08x)", psmfStruct, startTimeAddr);
if (Memory::IsValidAddress(startTimeAddr)) {
Memory::Write_U32(psmf->presentationStartTime, startTimeAddr);
}
return 0;
}
static u32 scePsmfGetPresentationEndTime(u32 psmfStruct, u32 endTimeAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetPresentationEndTime(%08x, %08x): invalid psmf", psmfStruct, endTimeAddr);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetPresentationEndTime(%08x, %08x)", psmfStruct, endTimeAddr);
if (Memory::IsValidAddress(endTimeAddr)) {
Memory::Write_U32(psmf->presentationEndTime, endTimeAddr);
}
return 0;
}
static u32 scePsmfGetCurrentStreamNumber(u32 psmfStruct)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetCurrentStreamNumber(%08x): invalid psmf", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetCurrentStreamNumber(%08x)", psmfStruct);
return psmf->currentStreamNum;
}
static u32 scePsmfCheckEPMap(u32 psmfStruct)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfCheckEPMap(%08x): invalid psmf", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfCheckEPMap(%08x)", psmfStruct);
return psmf->EPMap.empty() ? ERROR_PSMF_NOT_FOUND : 0;
}
static u32 scePsmfGetEPWithId(u32 psmfStruct, int epid, u32 entryAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetEPWithId(%08x, %i, %08x): invalid psmf", psmfStruct, epid, entryAddr);
return ERROR_PSMF_NOT_INITIALIZED;
}
DEBUG_LOG(ME, "scePsmfGetEPWithId(%08x, %i, %08x)", psmfStruct, epid, entryAddr);
if (epid < 0 || epid >= (int)psmf->EPMap.size()) {
ERROR_LOG(ME, "scePsmfGetEPWithId(%08x, %i): invalid id", psmfStruct, epid);
return ERROR_PSMF_NOT_FOUND;
}
if (Memory::IsValidAddress(entryAddr)) {
Memory::WriteStruct(entryAddr, &psmf->EPMap[epid]);
}
return 0;
}
static u32 scePsmfGetEPWithTimestamp(u32 psmfStruct, u32 ts, u32 entryAddr)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetEPWithTimestamp(%08x, %i, %08x): invalid psmf", psmfStruct, ts, entryAddr);
return ERROR_PSMF_NOT_INITIALIZED;
}
DEBUG_LOG(ME, "scePsmfGetEPWithTimestamp(%08x, %i, %08x)", psmfStruct, ts, entryAddr);
if (ts < psmf->presentationStartTime) {
ERROR_LOG(ME, "scePsmfGetEPWithTimestamp(%08x, %i): invalid timestamp", psmfStruct, ts);
return ERROR_PSMF_NOT_FOUND;
}
int epid = psmf->FindEPWithTimestamp(ts);
if (epid < 0 || epid >= (int)psmf->EPMap.size()) {
ERROR_LOG(ME, "scePsmfGetEPWithTimestamp(%08x, %i): invalid id", psmfStruct, epid);
return ERROR_PSMF_NOT_FOUND;
}
if (Memory::IsValidAddress(entryAddr)) {
Memory::WriteStruct(entryAddr, &psmf->EPMap[epid]);
}
return 0;
}
static u32 scePsmfGetEPidWithTimestamp(u32 psmfStruct, u32 ts)
{
Psmf *psmf = getPsmf(psmfStruct);
if (!psmf) {
ERROR_LOG(ME, "scePsmfGetEPidWithTimestamp(%08x, %i): invalid psmf", psmfStruct, ts);
return ERROR_PSMF_NOT_FOUND;
}
DEBUG_LOG(ME, "scePsmfGetEPidWithTimestamp(%08x, %i)", psmfStruct, ts);
if (psmf->EPMap.empty()) {
ERROR_LOG(ME, "scePsmfGetEPidWithTimestamp(%08x): EPMap is empty", psmfStruct);
return ERROR_PSMF_NOT_FOUND;
}
if (ts < psmf->presentationStartTime) {
ERROR_LOG(ME, "scePsmfGetEPidWithTimestamp(%08x, %i): invalid timestamp", psmfStruct, ts);
return ERROR_PSMF_INVALID_TIMESTAMP;
}
int epid = psmf->FindEPWithTimestamp(ts);
if (epid < 0 || epid >= (int)psmf->EPMap.size()) {
ERROR_LOG(ME, "scePsmfGetEPidWithTimestamp(%08x, %i): invalid id", psmfStruct, epid);
return ERROR_PSMF_INVALID_ID;
}
return epid;
}
static int scePsmfPlayerCreate(u32 psmfPlayer, u32 dataPtr)
{
auto player = PSPPointer<u32>::Create(psmfPlayer);
const auto data = PSPPointer<const PsmfPlayerCreateData>::Create(dataPtr);
if (!player.IsValid() || !data.IsValid()) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): bad pointers", psmfPlayer, dataPtr);
// Crashes on a PSP.
return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
}
if (!data->buffer.IsValid()) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): invalid buffer address %08x", psmfPlayer, dataPtr, data->buffer.ptr);
// Also crashes on a PSP.
*player = 0;
return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
}
if (data->bufferSize < 0x00285800) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): buffer too small %08x", psmfPlayer, dataPtr, data->bufferSize);
*player = 0;
return ERROR_PSMFPLAYER_BUFFER_SIZE;
}
if (data->threadPriority < 0x10 || data->threadPriority >= 0x6E) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): bad thread priority %02x", psmfPlayer, dataPtr, data->threadPriority);
*player = 0;
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
if (!psmfPlayerMap.empty()) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerCreate(%08x, %08x): already have an active player", psmfPlayer, dataPtr);
// TODO: Tests show this is what we should do. Leaving it off for now to see if safe.
//*player = 0;
//return ERROR_MPEG_ALREADY_INIT;
}
INFO_LOG(ME, "scePsmfPlayerCreate(%08x, %08x)", psmfPlayer, dataPtr);
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
psmfplayer = new PsmfPlayer(data);
if (psmfPlayerMap.find(psmfPlayer) != psmfPlayerMap.end())
delete psmfPlayerMap[psmfPlayer];
psmfPlayerMap[psmfPlayer] = psmfplayer;
// Write something there to identify it with.
*player = psmfPlayer;
}
// These really shouldn't be globals. But, you can only have one psmfplayer anyway.
videoPixelMode = GE_CMODE_32BIT_ABGR8888;
videoLoopStatus = PSMF_PLAYER_CONFIG_NO_LOOP;
psmfplayer->psmfMaxAheadTimestamp = getMaxAheadTimestamp(581);
psmfplayer->status = PSMF_PLAYER_STATUS_INIT;
return hleDelayResult(0, "player create", 20000);
}
static int scePsmfPlayerStop(u32 psmfPlayer) {
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerStop(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerStop(%08x): not yet playing", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
psmfplayer->AbortFinish();
INFO_LOG(ME, "scePsmfPlayerStop(%08x)", psmfPlayer);
psmfplayer->status = PSMF_PLAYER_STATUS_STANDBY;
return hleDelayResult(0, "psmfplayer stop", 3000);
}
static int scePsmfPlayerBreak(u32 psmfPlayer)
{
WARN_LOG(ME, "scePsmfPlayerBreak(%08x)", psmfPlayer);
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerBreak(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
psmfplayer->AbortFinish();
return 0;
}
static int _PsmfPlayerFillRingbuffer(PsmfPlayer *psmfplayer) {
if (!psmfplayer->filehandle)
return -1;
u8* buf = psmfplayer->tempbuf;
int tempbufSize = (int)sizeof(psmfplayer->tempbuf);
int size;
// Let's not burn a bunch of time adding data all at once.
int addMax = std::max(2048 * 100, tempbufSize);
do {
size = std::min(psmfplayer->mediaengine->getRemainSize(), tempbufSize);
size = std::min(psmfplayer->mediaengine->getAudioRemainSize(), size);
size = std::min(psmfplayer->streamSize - psmfplayer->readSize, size);
if (size <= 0)
break;
size = (int)pspFileSystem.ReadFile(psmfplayer->filehandle, buf, size);
psmfplayer->readSize += size;
psmfplayer->mediaengine->addStreamData(buf, size);
addMax -= size;
if (addMax <= 0)
break;
} while (size > 0);
if (psmfplayer->readSize >= psmfplayer->streamSize && videoLoopStatus == PSMF_PLAYER_CONFIG_LOOP) {
// Start looping, but only if we've finished.
if (psmfplayer->HasReachedEnd()) {
psmfplayer->readSize = 0;
pspFileSystem.SeekFile(psmfplayer->filehandle, psmfplayer->fileoffset, FILEMOVE_BEGIN);
psmfplayer->mediaengine->reloadStream();
}
}
return 0;
}
static int _PsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int offset, bool docallback) {
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer || psmfplayer->status != PSMF_PLAYER_STATUS_INIT) {
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (!filename) {
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
int delayUs = 1100;
psmfplayer->filehandle = pspFileSystem.OpenFile(filename, (FileAccess) FILEACCESS_READ);
if (!psmfplayer->filehandle) {
return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs);
}
if (offset != 0)
pspFileSystem.SeekFile(psmfplayer->filehandle, offset, FILEMOVE_BEGIN);
u8 *buf = psmfplayer->tempbuf;
int tempbufSize = (int)sizeof(psmfplayer->tempbuf);
int size = (int)pspFileSystem.ReadFile(psmfplayer->filehandle, buf, 2048);
delayUs += 2000;
const u32 magic = *(u32_le *)buf;
if (magic != PSMF_MAGIC) {
// TODO: Let's keep trying as we were before.
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: incorrect PSMF magic, bad data");
//return SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT;
}
// TODO: Merge better with Psmf.
u16 numStreams = *(u16_be *)(buf + 0x80);
if (numStreams > 128) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: too many streams in PSMF video, bogus data");
return hleDelayResult(SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT, "psmfplayer set", delayUs);
}
psmfplayer->totalVideoStreams = 0;
psmfplayer->totalAudioStreams = 0;
psmfplayer->playerVersion = PSMF_PLAYER_VERSION_FULL;
for (u16 i = 0; i < numStreams; i++) {
const u8 *currentStreamAddr = buf + 0x82 + i * 16;
const int streamId = *currentStreamAddr;
if ((streamId & PSMF_VIDEO_STREAM_ID) == PSMF_VIDEO_STREAM_ID) {
++psmfplayer->totalVideoStreams;
// If we don't have EP info for /any/ video stream, revert to BASIC.
const u32 epOffset = ReadUnalignedU32BE(currentStreamAddr + 4);
const u32 epEntries = ReadUnalignedU32BE(currentStreamAddr + 8);
// TODO: Actually, if these don't match, it seems to be an invalid PSMF.
if (epOffset == 0 || epEntries == 0) {
psmfplayer->playerVersion = PSMF_PLAYER_VERSION_BASIC;
}
} else if ((streamId & PSMF_AUDIO_STREAM_ID) == PSMF_AUDIO_STREAM_ID) {
++psmfplayer->totalAudioStreams;
} else {
WARN_LOG_REPORT(ME, "scePsmfPlayerSetPsmf*: unexpected streamID %x", streamId);
}
}
// TODO: It seems like it's invalid if there's not at least 1 video stream.
int mpegoffset = *(s32_be *)(buf + PSMF_STREAM_OFFSET_OFFSET);
psmfplayer->readSize = size - mpegoffset;
psmfplayer->streamSize = *(s32_be *)(buf + PSMF_STREAM_SIZE_OFFSET);
psmfplayer->fileoffset = offset + mpegoffset;
psmfplayer->mediaengine->loadStream(buf, 2048, std::max(2048 * 500, tempbufSize));
_PsmfPlayerFillRingbuffer(psmfplayer);
psmfplayer->totalDurationTimestamp = psmfplayer->mediaengine->getLastTimeStamp();
psmfplayer->status = PSMF_PLAYER_STATUS_STANDBY;
return hleDelayResult(0, "psmfplayer set", delayUs);
}
static int scePsmfPlayerSetPsmf(u32 psmfPlayer, const char *filename)
{
u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, false);
if (result == ERROR_PSMFPLAYER_INVALID_STATUS) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid psmf player or status", psmfPlayer, filename);
} else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid filename", psmfPlayer, filename);
} else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s): invalid file data or does not exist", psmfPlayer, filename);
} else {
INFO_LOG(ME, "scePsmfPlayerSetPsmf(%08x, %s)", psmfPlayer, filename);
}
return result;
}
static int scePsmfPlayerSetPsmfCB(u32 psmfPlayer, const char *filename)
{
// TODO: hleCheckCurrentCallbacks?
u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, 0, true);
if (result == ERROR_PSMFPLAYER_INVALID_STATUS) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid psmf player or status", psmfPlayer, filename);
} else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid filename", psmfPlayer, filename);
} else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s): invalid file data or does not exist", psmfPlayer, filename);
} else {
INFO_LOG(ME, "scePsmfPlayerSetPsmfCB(%08x, %s)", psmfPlayer, filename);
}
return result;
}
static int scePsmfPlayerSetPsmfOffset(u32 psmfPlayer, const char *filename, int offset)
{
u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, false);
if (result == ERROR_PSMFPLAYER_INVALID_STATUS) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid psmf player or status", psmfPlayer, filename);
} else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid filename", psmfPlayer, filename);
} else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s): invalid file data or does not exist", psmfPlayer, filename);
} else {
INFO_LOG(ME, "scePsmfPlayerSetPsmfOffset(%08x, %s)", psmfPlayer, filename);
}
return result;
}
static int scePsmfPlayerSetPsmfOffsetCB(u32 psmfPlayer, const char *filename, int offset)
{
// TODO: hleCheckCurrentCallbacks?
u32 result = _PsmfPlayerSetPsmfOffset(psmfPlayer, filename, offset, true);
if (result == ERROR_PSMFPLAYER_INVALID_STATUS) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid psmf player or status", psmfPlayer, filename);
} else if (result == ERROR_PSMFPLAYER_INVALID_PARAM) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid filename", psmfPlayer, filename);
} else if (result == SCE_KERNEL_ERROR_ILLEGAL_ARGUMENT) {
ERROR_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s): invalid file data or does not exist", psmfPlayer, filename);
} else {
INFO_LOG(ME, "scePsmfPlayerSetPsmfOffsetCB(%08x, %s)", psmfPlayer, filename);
}
return result;
}
static int scePsmfPlayerGetAudioOutSize(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetAudioOutSize(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
WARN_LOG(ME, "%i = scePsmfPlayerGetAudioOutSize(%08x)", audioSamplesBytes, psmfPlayer);
return audioSamplesBytes;
}
static bool __PsmfPlayerContinueSeek(PsmfPlayer *psmfplayer, int tries = 50) {
if (psmfplayer->seekDestTimeStamp <= 0) {
return true;
}
while (!psmfplayer->mediaengine->seekTo(psmfplayer->seekDestTimeStamp, videoPixelMode)) {
if (--tries <= 0) {
return false;
}
_PsmfPlayerFillRingbuffer(psmfplayer);
if (psmfplayer->mediaengine->IsVideoEnd()) {
break;
}
}
// Seek is done, so forget about it.
psmfplayer->seekDestTimeStamp = 0;
return true;
}
static int scePsmfPlayerStart(u32 psmfPlayer, u32 psmfPlayerData, int initPts)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d): invalid psmf player", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status == PSMF_PLAYER_STATUS_INIT) {
ERROR_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d): psmf not yet set", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
auto playerData = PSPPointer<PsmfPlayerData>::Create(psmfPlayerData);
if (!playerData.IsValid()) {
// Crashes on a PSP.
ERROR_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d): bad data address", psmfPlayer, psmfPlayerData, initPts);
return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
}
if (playerData->playMode < 0 || playerData->playMode > (int)PSMF_PLAYER_MODE_REWIND) {
ERROR_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d): invalid mode", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
if (initPts >= psmfplayer->mediaengine->getLastTimeStamp()) {
ERROR_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d): pts is outside video", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
if (psmfplayer->totalAudioStreams > 0) {
if (playerData->audioCodec != 0x0F && playerData->audioCodec != 0x01) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): invalid audio codec %02x", psmfPlayer, psmfPlayerData, initPts, playerData->audioCodec);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (playerData->audioStreamNum >= psmfplayer->totalAudioStreams) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): unable to change audio stream to %d", psmfPlayer, psmfPlayerData, initPts, playerData->audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
}
if (playerData->videoCodec != 0x0E && playerData->videoCodec != 0x00) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): invalid video codec %02x", psmfPlayer, psmfPlayerData, initPts, playerData->videoCodec);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (playerData->videoStreamNum < 0 || playerData->videoStreamNum >= psmfplayer->totalVideoStreams) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): unable to change video stream to %d", psmfPlayer, psmfPlayerData, initPts, playerData->videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
switch ((PsmfPlayerMode)(s32)playerData->playMode) {
case PSMF_PLAYER_MODE_FORWARD:
case PSMF_PLAYER_MODE_REWIND:
if (psmfplayer->playerVersion == PSMF_PLAYER_VERSION_BASIC) {
WARN_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): no EP data for FORWARD/REWIND", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
WARN_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): unsupported playMode", psmfPlayer, psmfPlayerData, initPts);
break;
case PSMF_PLAYER_MODE_PLAY:
case PSMF_PLAYER_MODE_PAUSE:
break;
default:
WARN_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): unsupported playMode", psmfPlayer, psmfPlayerData, initPts);
break;
}
if (psmfplayer->playerVersion == PSMF_PLAYER_VERSION_BASIC && initPts != 0) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerStart(%08x, %08x, %d): unable to seek without EPmap", psmfPlayer, psmfPlayerData, initPts);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
WARN_LOG(ME, "scePsmfPlayerStart(%08x, %08x, %d)", psmfPlayer, psmfPlayerData, initPts);
psmfplayer->AbortFinish();
psmfplayer->mediaengine->setVideoStream(playerData->videoStreamNum);
psmfplayer->videoCodec = playerData->videoCodec;
psmfplayer->videoStreamNum = playerData->videoStreamNum;
if (psmfplayer->totalAudioStreams > 0) {
psmfplayer->mediaengine->setAudioStream(playerData->audioStreamNum);
psmfplayer->audioCodec = playerData->audioCodec;
psmfplayer->audioStreamNum = playerData->audioStreamNum;
}
psmfplayer->playMode = playerData->playMode;
psmfplayer->playSpeed = playerData->playSpeed;
// Does not alter current pts, it just catches up when Update()/etc. get there.
int delayUs = psmfplayer->status == PSMF_PLAYER_STATUS_PLAYING ? 3000 : 0;
psmfplayer->status = PSMF_PLAYER_STATUS_PLAYING;
psmfplayer->warmUp = 0;
psmfplayer->mediaengine->openContext();
s64 dist = initPts - psmfplayer->mediaengine->getVideoTimeStamp();
if (dist < 0 || dist > VIDEO_FRAME_DURATION_TS * 60) {
// When seeking backwards, we just start populating the stream from the start.
pspFileSystem.SeekFile(psmfplayer->filehandle, 0, FILEMOVE_BEGIN);
u8 *buf = psmfplayer->tempbuf;
int tempbufSize = (int)sizeof(psmfplayer->tempbuf);
int size = (int)pspFileSystem.ReadFile(psmfplayer->filehandle, buf, tempbufSize);
psmfplayer->mediaengine->loadStream(buf, size, std::max(2048 * 500, tempbufSize));
int mpegoffset = *(s32_be *)(buf + PSMF_STREAM_OFFSET_OFFSET);
psmfplayer->readSize = size - mpegoffset;
Psmf psmf(psmfplayer->tempbuf, 0);
int lastOffset = 0;
for (auto it = psmf.EPMap.begin(), end = psmf.EPMap.end(); it != end; ++it) {
if (initPts <= it->EPPts - (int)psmf.presentationStartTime) {
break;
}
lastOffset = it->EPOffset;
}
psmfplayer->readSize = lastOffset * 2048;
pspFileSystem.SeekFile(psmfplayer->filehandle, psmfplayer->fileoffset + psmfplayer->readSize, FILEMOVE_BEGIN);
_PsmfPlayerFillRingbuffer(psmfplayer);
}
psmfplayer->seekDestTimeStamp = initPts;
__PsmfPlayerContinueSeek(psmfplayer);
return delayUs == 0 ? 0 : hleDelayResult(0, "psmfplayer start", delayUs);
}
static int scePsmfPlayerDelete(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerDelete(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
INFO_LOG(ME, "scePsmfPlayerDelete(%08x)", psmfPlayer);
delete psmfplayer;
psmfPlayerMap.erase(Memory::Read_U32(psmfPlayer));
Memory::Write_U32(0, psmfPlayer);
return hleDelayResult(0, "psmfplayer deleted", 20000);
}
static int scePsmfPlayerUpdate(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerUpdate(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerUpdate(%08x): not playing yet", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "scePsmfPlayerUpdate(%08x)", psmfPlayer);
if (psmfplayer->HasReachedEnd()) {
if (videoLoopStatus == PSMF_PLAYER_CONFIG_NO_LOOP && psmfplayer->videoStep >= 1) {
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING_FINISHED) {
psmfplayer->ScheduleFinish(psmfPlayer);
INFO_LOG(ME, "scePsmfPlayerUpdate(%08x): video end scheduled", psmfPlayer);
}
}
}
psmfplayer->videoStep++;
return 0;
}
static int scePsmfPlayerReleasePsmf(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerReleasePsmf(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_STANDBY) {
ERROR_LOG(ME, "scePsmfPlayerReleasePsmf(%08x): not set yet", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
WARN_LOG(ME, "scePsmfPlayerReleasePsmf(%08x)", psmfPlayer);
psmfplayer->status = PSMF_PLAYER_STATUS_INIT;
return 0;
}
static void __PsmfUpdatePts(PsmfPlayer *psmfplayer, PsmfVideoData *videoData) {
// getVideoTimestamp() includes the frame duration, remove it for this frame's pts.
psmfplayer->psmfPlayerAvcAu.pts = psmfplayer->mediaengine->getVideoTimeStamp() - VIDEO_FRAME_DURATION_TS;
if (videoData) {
videoData->displaypts = (u32)psmfplayer->psmfPlayerAvcAu.pts;
}
}
static int scePsmfPlayerGetVideoData(u32 psmfPlayer, u32 videoDataAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): invalid psmf player", psmfPlayer, videoDataAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): psmf not playing", psmfPlayer, videoDataAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
auto videoData = PSPPointer<PsmfVideoData>::Create(videoDataAddr);
if (!videoData.IsValid()) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): invalid data pointer", psmfPlayer, videoDataAddr);
// Technically just crashes if videoData is not valid.
return SCE_KERNEL_ERROR_INVALID_POINTER;
}
if (videoData->frameWidth < 0) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): illegal bufw %d", psmfPlayer, videoDataAddr, videoData->frameWidth);
return SCE_KERNEL_ERROR_PRIV_REQUIRED;
}
if (videoData->frameWidth != 0 && videoData->frameWidth < psmfplayer->mediaengine->VideoWidth()) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): bufw %d smaller than width %d", psmfPlayer, videoDataAddr, videoData->frameWidth, psmfplayer->mediaengine->VideoWidth());
return SCE_KERNEL_ERROR_INVALID_VALUE;
}
hleEatCycles(20000);
if (!__PsmfPlayerContinueSeek(psmfplayer)) {
DEBUG_LOG(HLE, "scePsmfPlayerGetVideoData(%08x, %08x): still seeking", psmfPlayer, videoDataAddr);
return ERROR_PSMFPLAYER_NO_MORE_DATA;
}
// On a real PSP, this takes a potentially variable amount of time.
// Normally a minimum of 3 without audio, 5 with. But if you don't delay sufficiently between, hundreds.
// It should be okay if we start videos quicker, but some games expect the first couple to fail.
if (psmfplayer->warmUp < PSMF_PLAYER_WARMUP_FRAMES) {
DEBUG_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): warming up", psmfPlayer, videoDataAddr);
++psmfplayer->warmUp;
return ERROR_PSMFPLAYER_NO_MORE_DATA;
}
// In case we change warm up later, save a high value in savestates - video started.
psmfplayer->warmUp = 10000;
// It's fine to pass an invalid value here if it's still warming up, but after that it's not okay.
if (!Memory::IsValidAddress(videoData->displaybuf)) {
ERROR_LOG(ME, "scePsmfPlayerGetVideoData(%08x, %08x): invalid buffer pointer %08x", psmfPlayer, videoDataAddr, videoData->displaybuf);
return SCE_KERNEL_ERROR_INVALID_POINTER;
}
bool doVideoStep = true;
if (psmfplayer->playMode == PSMF_PLAYER_MODE_PAUSE) {
doVideoStep = false;
} else if (!psmfplayer->mediaengine->IsNoAudioData()) {
s64 deltapts = psmfplayer->mediaengine->getVideoTimeStamp() - psmfplayer->mediaengine->getAudioTimeStamp();
// Don't skip the very first frame, sometimes audio starts with an early timestamp.
if (deltapts > 0 && psmfplayer->mediaengine->getVideoTimeStamp() > 0) {
// Don't advance, just return the same frame again.
// TODO: This also seems somewhat based on Update() calls, but audio is involved too...
doVideoStep = false;
} else {
// This is an approximation, it should allow a certain amount ahead before skipping frames.
while (deltapts <= -(VIDEO_FRAME_DURATION_TS * 5)) {
psmfplayer->mediaengine->stepVideo(videoPixelMode, true);
deltapts = psmfplayer->mediaengine->getVideoTimeStamp() - psmfplayer->mediaengine->getAudioTimeStamp();
}
}
} else {
// No audio, based on Update() calls. playSpeed doesn't seem to matter?
if (psmfplayer->videoStep <= 1 && psmfplayer->mediaengine->getVideoTimeStamp() > 0) {
doVideoStep = false;
} else {
psmfplayer->videoStep = 0;
}
}
if (doVideoStep) {
psmfplayer->mediaengine->stepVideo(videoPixelMode);
}
// It seems the frameWidth is rounded down to even values, and defaults to 512.
int bufw = videoData->frameWidth == 0 ? 512 : videoData->frameWidth & ~1;
// Always write the video frame, even after the video has ended.
int displaybufSize = psmfplayer->mediaengine->writeVideoImage(videoData->displaybuf, bufw, videoPixelMode);
gpu->InvalidateCache(videoData->displaybuf, displaybufSize, GPU_INVALIDATE_SAFE);
__PsmfUpdatePts(psmfplayer, videoData);
_PsmfPlayerFillRingbuffer(psmfplayer);
DEBUG_LOG(ME, "%08x=scePsmfPlayerGetVideoData(%08x, %08x)", 0, psmfPlayer, videoDataAddr);
return hleDelayResult(0, "psmfPlayer video decode", 3000);
}
static int scePsmfPlayerGetAudioData(u32 psmfPlayer, u32 audioDataAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetAudioData(%08x, %08x): invalid psmf player", psmfPlayer, audioDataAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerGetAudioData(%08x, %08x): not yet playing", psmfPlayer, audioDataAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (!Memory::IsValidAddress(audioDataAddr)) {
ERROR_LOG(ME, "scePsmfPlayerGetAudioData(%08x, %08x): invalid audio pointer", psmfPlayer, audioDataAddr);
return SCE_KERNEL_ERROR_INVALID_POINTER;
}
// Don't return audio frames before we would return video frames.
if (psmfplayer->warmUp < PSMF_PLAYER_WARMUP_FRAMES) {
DEBUG_LOG(ME, "scePsmfPlayerGetAudioData(%08x, %08x): warming up", psmfPlayer, audioDataAddr);
return ERROR_PSMFPLAYER_NO_MORE_DATA;
}
if (psmfplayer->playMode == PSMF_PLAYER_MODE_PAUSE) {
INFO_LOG(HLE, "scePsmfPlayerGetAudioData(%08x): paused mode", psmfPlayer);
return ERROR_PSMFPLAYER_NO_MORE_DATA;
}
int ret = 0;
if (psmfplayer->mediaengine->getAudioSamples(audioDataAddr) == 0) {
if (psmfplayer->totalAudioStreams > 0 && (s64)psmfplayer->psmfPlayerAvcAu.pts < (s64)psmfplayer->totalDurationTimestamp - VIDEO_FRAME_DURATION_TS) {
// Write zeros for any missing trailing frames so it syncs with the video.
Memory::Memset(audioDataAddr, 0, audioSamplesBytes);
} else {
ret = (int)ERROR_PSMFPLAYER_NO_MORE_DATA;
}
}
DEBUG_LOG(ME, "%08x=scePsmfPlayerGetAudioData(%08x, %08x)", ret, psmfPlayer, audioDataAddr);
if (ret != 0) {
hleEatCycles(10000);
} else {
hleEatCycles(30000);
}
hleReSchedule("psmfplayer audio decode");
return ret;
}
static int scePsmfPlayerGetCurrentStatus(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
// Mana Khemia and other games call this even when not necessary.
// It's annoying so the logging is verbose'd out.
VERBOSE_LOG(ME, "scePsmfPlayerGetCurrentStatus(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status == PSMF_PLAYER_STATUS_NONE) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentStatus(%08x): not initialized", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "%d=scePsmfPlayerGetCurrentStatus(%08x)", psmfplayer->status, psmfPlayer);
return psmfplayer->status;
}
static u32 scePsmfPlayerGetCurrentPts(u32 psmfPlayer, u32 currentPtsAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentPts(%08x, %08x): invalid psmf player", psmfPlayer, currentPtsAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_STANDBY) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentPts(%08x, %08x): not initialized", psmfPlayer, currentPtsAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->psmfPlayerAvcAu.pts < 0) {
WARN_LOG(ME, "scePsmfPlayerGetCurrentPts(%08x, %08x): no frame yet", psmfPlayer, currentPtsAddr);
return ERROR_PSMFPLAYER_NO_MORE_DATA;
}
DEBUG_LOG(ME, "scePsmfPlayerGetCurrentPts(%08x, %08x)", psmfPlayer, currentPtsAddr);
if (Memory::IsValidAddress(currentPtsAddr)) {
Memory::Write_U32(psmfplayer->psmfPlayerAvcAu.pts, currentPtsAddr);
}
return 0;
}
static u32 scePsmfPlayerGetPsmfInfo(u32 psmfPlayer, u32 psmfInfoAddr)
{
auto info = PSPPointer<PsmfInfo>::Create(psmfInfoAddr);
if (!Memory::IsValidAddress(psmfPlayer) || !info.IsValid()) {
ERROR_LOG(ME, "scePsmfPlayerGetPsmfInfo(%08x, %08x): invalid addresses", psmfPlayer, psmfInfoAddr);
// PSP would crash.
return SCE_KERNEL_ERROR_ILLEGAL_ADDRESS;
}
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetPsmfInfo(%08x, %08x): invalid psmf player", psmfPlayer, psmfInfoAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_STANDBY) {
ERROR_LOG(ME, "scePsmfPlayerGetPsmfInfo(%08x): psmf not set yet", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "scePsmfPlayerGetPsmfInfo(%08x, %08x)", psmfPlayer, psmfInfoAddr);
// The first frame is at 0, so subtract one frame's duration to get the last frame's timestamp.
info->lastFrameTS = psmfplayer->totalDurationTimestamp - VIDEO_FRAME_DURATION_TS;
info->numVideoStreams = psmfplayer->totalVideoStreams;
info->numAudioStreams = psmfplayer->totalAudioStreams;
// pcm stream num?
info->numPCMStreams = 0;
info->playerVersion = psmfplayer->playerVersion;
return 0;
}
static u32 scePsmfPlayerGetCurrentPlayMode(u32 psmfPlayer, u32 playModeAddr, u32 playSpeedAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentPlayMode(%08x, %08x, %08x): invalid psmf player", psmfPlayer, playModeAddr, playSpeedAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "scePsmfPlayerGetCurrentPlayMode(%08x, %08x, %08x)", psmfPlayer, playModeAddr, playSpeedAddr);
if (Memory::IsValidAddress(playModeAddr)) {
Memory::Write_U32(psmfplayer->playMode, playModeAddr);
}
if (Memory::IsValidAddress(playSpeedAddr)) {
Memory::Write_U32(psmfplayer->playSpeed, playSpeedAddr);
}
return 0;
}
static u32 scePsmfPlayerGetCurrentVideoStream(u32 psmfPlayer, u32 videoCodecAddr, u32 videoStreamNumAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentVideoStream(%08x, %08x, %08x): invalid psmf player", psmfPlayer, videoCodecAddr, videoStreamNumAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status == PSMF_PLAYER_STATUS_INIT) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentVideoStream(%08x): psmf not yet set", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "scePsmfPlayerGetCurrentVideoStream(%08x, %08x, %08x)", psmfPlayer, videoCodecAddr, videoStreamNumAddr);
if (Memory::IsValidAddress(videoCodecAddr)) {
Memory::Write_U32(psmfplayer->videoCodec == 0x0E ? 0 : psmfplayer->videoCodec, videoCodecAddr);
}
if (Memory::IsValidAddress(videoStreamNumAddr)) {
Memory::Write_U32(psmfplayer->videoStreamNum, videoStreamNumAddr);
}
return 0;
}
static u32 scePsmfPlayerGetCurrentAudioStream(u32 psmfPlayer, u32 audioCodecAddr, u32 audioStreamNumAddr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentAudioStream(%08x, %08x, %08x): invalid psmf player", psmfPlayer, audioCodecAddr, audioStreamNumAddr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status == PSMF_PLAYER_STATUS_INIT) {
ERROR_LOG(ME, "scePsmfPlayerGetCurrentVideoStream(%08x): psmf not yet set", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
DEBUG_LOG(ME, "scePsmfPlayerGetCurrentAudioStream(%08x, %08x, %08x)", psmfPlayer, audioCodecAddr, audioStreamNumAddr);
if (Memory::IsValidAddress(audioCodecAddr)) {
Memory::Write_U32(psmfplayer->audioCodec == 0x0F ? 1 : psmfplayer->audioCodec, audioCodecAddr);
}
if (Memory::IsValidAddress(audioStreamNumAddr)) {
Memory::Write_U32(psmfplayer->audioStreamNum, audioStreamNumAddr);
}
return 0;
}
static int scePsmfPlayerSetTempBuf(u32 psmfPlayer, u32 tempBufAddr, u32 tempBufSize)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerSetTempBuf(%08x, %08x, %08x): invalid psmf player", psmfPlayer, tempBufAddr, tempBufSize);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_INIT) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetTempBuf(%08x, %08x, %08x): invalid status %x", psmfPlayer, tempBufAddr, tempBufSize, psmfplayer->status);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (tempBufSize < 0x00010000) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSetTempBuf(%08x, %08x, %08x): buffer too small", psmfPlayer, tempBufAddr, tempBufSize);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
INFO_LOG(ME, "scePsmfPlayerSetTempBuf(%08x, %08x, %08x)", psmfPlayer, tempBufAddr, tempBufSize);
// fake it right now, use tempbuf from memory directly
//psmfplayer->tempbuf = tempBufAddr;
//psmfplayer->tempbufSize = tempBufSize;
return 0;
}
static u32 scePsmfPlayerChangePlayMode(u32 psmfPlayer, int playMode, int playSpeed)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): invalid psmf player", psmfPlayer, playMode, playSpeed);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status < PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): not playing yet", psmfPlayer, playMode, playSpeed);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (playMode < 0 || playMode > (int)PSMF_PLAYER_MODE_REWIND) {
ERROR_LOG(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): invalid mode", psmfPlayer, playMode, playSpeed);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
switch (playMode) {
case PSMF_PLAYER_MODE_FORWARD:
case PSMF_PLAYER_MODE_REWIND:
if (psmfplayer->playerVersion == PSMF_PLAYER_VERSION_BASIC) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): no EP data for FORWARD/REWIND", psmfPlayer, playMode, playSpeed);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
psmfplayer->playSpeed = playSpeed;
WARN_LOG_REPORT(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): unsupported playMode", psmfPlayer, playMode, playSpeed);
break;
case PSMF_PLAYER_MODE_PLAY:
case PSMF_PLAYER_MODE_PAUSE:
if (psmfplayer->playSpeed != playSpeed) {
WARN_LOG_REPORT(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): play speed not changed", psmfPlayer, playMode, playSpeed);
} else {
DEBUG_LOG(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i)", psmfPlayer, playMode, playSpeed);
}
break;
default:
if (psmfplayer->playSpeed != playSpeed) {
WARN_LOG_REPORT(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): play speed not changed", psmfPlayer, playMode, playSpeed);
}
WARN_LOG_REPORT(ME, "scePsmfPlayerChangePlayMode(%08x, %i, %i): unsupported playMode", psmfPlayer, playMode, playSpeed);
break;
}
psmfplayer->playMode = playMode;
return 0;
}
static u32 scePsmfPlayerSelectAudio(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerSelectAudio(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerSelectAudio(%08x): not playing", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
int next = psmfplayer->audioStreamNum + 1;
if (next >= psmfplayer->totalAudioStreams)
next = 0;
if (next == psmfplayer->audioStreamNum || !psmfplayer->mediaengine->setAudioStream(next)) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectAudio(%08x): no stream to switch to", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
WARN_LOG_REPORT(ME, "scePsmfPlayerSelectAudio(%08x)", psmfPlayer);
psmfplayer->audioStreamNum = next;
return 0;
}
static u32 scePsmfPlayerSelectVideo(u32 psmfPlayer)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerSelectVideo(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerSelectVideo(%08x): not playing", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
int next = psmfplayer->videoStreamNum + 1;
if (next >= psmfplayer->totalVideoStreams)
next = 0;
if (next == psmfplayer->videoStreamNum || !psmfplayer->mediaengine->setVideoStream(next)) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectVideo(%08x): no stream to switch to", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
WARN_LOG_REPORT(ME, "scePsmfPlayerSelectVideo(%08x)", psmfPlayer);
psmfplayer->videoStreamNum = next;
return 0;
}
static u32 scePsmfPlayerSelectSpecificVideo(u32 psmfPlayer, int videoCodec, int videoStreamNum)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): invalid psmf player", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): not playing", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->totalVideoStreams < 2) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): unable to change stream", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (videoStreamNum < 0 || videoStreamNum >= psmfplayer->totalVideoStreams) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): bad stream num param", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
if (videoCodec != 0x0E && videoCodec != 0x00) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): invalid codec", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (psmfplayer->totalVideoStreams < 2 || !psmfplayer->mediaengine->setVideoStream(videoStreamNum)) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i): unable to change stream", psmfPlayer, videoCodec, videoStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
WARN_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificVideo(%08x, %i, %i)", psmfPlayer, videoCodec, videoStreamNum);
if (psmfplayer->videoStreamNum != videoStreamNum) {
hleDelayResult(0, "psmf select video", 100);
}
psmfplayer->videoCodec = videoCodec;
psmfplayer->videoStreamNum = videoStreamNum;
return 0;
}
// WARNING: This function appears to be buggy in most libraries.
static u32 scePsmfPlayerSelectSpecificAudio(u32 psmfPlayer, int audioCodec, int audioStreamNum)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): invalid psmf player", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): not playing", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->totalAudioStreams < 2) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): unable to change stream", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (audioStreamNum < 0 || audioStreamNum >= psmfplayer->totalAudioStreams) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): bad stream num param", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
if (audioCodec != 0x0F && audioCodec != 0x01) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): invalid codec", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
if (psmfplayer->totalAudioStreams < 2 || !psmfplayer->mediaengine->setAudioStream(audioStreamNum)) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i): unable to change stream", psmfPlayer, audioCodec, audioStreamNum);
return ERROR_PSMFPLAYER_INVALID_STREAM;
}
WARN_LOG_REPORT(ME, "scePsmfPlayerSelectSpecificAudio(%08x, %i, %i)", psmfPlayer, audioCodec, audioStreamNum);
if (psmfplayer->audioStreamNum != audioStreamNum) {
hleDelayResult(0, "psmf select audio", 100);
}
psmfplayer->audioCodec = audioCodec;
psmfplayer->audioStreamNum = audioStreamNum;
return 0;
}
static u32 scePsmfPlayerConfigPlayer(u32 psmfPlayer, int configMode, int configAttr)
{
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG(ME, "scePsmfPlayerConfigPlayer(%08x, %i, %i): invalid psmf player", psmfPlayer, configMode, configAttr);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
// This one works in any status as long as it's created.
switch (configMode) {
case PSMF_PLAYER_CONFIG_MODE_LOOP:
if (configAttr != 0 && configAttr != 1) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerConfigPlayer(%08x, loop, %i): invalid value", psmfPlayer, configAttr);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
INFO_LOG(ME, "scePsmfPlayerConfigPlayer(%08x, loop, %i)", psmfPlayer, configAttr);
videoLoopStatus = configAttr;
break;
case PSMF_PLAYER_CONFIG_MODE_PIXEL_TYPE:
if (configAttr < -1 || configAttr > 3) {
ERROR_LOG_REPORT(ME, "scePsmfPlayerConfigPlayer(%08x, pixelType, %i): invalid value", psmfPlayer, configAttr);
return ERROR_PSMFPLAYER_INVALID_PARAM;
}
INFO_LOG(ME, "scePsmfPlayerConfigPlayer(%08x, pixelType, %i)", psmfPlayer, configAttr);
// Does -1 mean default or something?
if (configAttr != -1) {
videoPixelMode = configAttr;
} else {
// TODO: At least for one video, this was the same as 8888.
videoPixelMode = GE_CMODE_32BIT_ABGR8888;
}
break;
default:
ERROR_LOG_REPORT(ME, "scePsmfPlayerConfigPlayer(%08x, %i, %i): unknown parameter", psmfPlayer, configMode, configAttr);
return ERROR_PSMFPLAYER_INVALID_CONFIG;
}
return 0;
}
static int __PsmfPlayerFinish(u32 psmfPlayer) {
PsmfPlayer *psmfplayer = getPsmfPlayer(psmfPlayer);
if (!psmfplayer) {
ERROR_LOG_REPORT(ME, "__PsmfPlayerFinish(%08x): invalid psmf player", psmfPlayer);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
if (psmfplayer->status != PSMF_PLAYER_STATUS_PLAYING) {
ERROR_LOG_REPORT(ME, "__PsmfPlayerFinish(%08x): unexpected status %d", psmfPlayer, psmfplayer->status);
return ERROR_PSMFPLAYER_INVALID_STATUS;
}
INFO_LOG(ME, "__PsmfPlayerFinish(%08x): video end reached", psmfPlayer);
psmfplayer->status = PSMF_PLAYER_STATUS_PLAYING_FINISHED;
return 0;
}
const HLEFunction scePsmf[] = {
{0XC22C8327, &WrapU_UU<scePsmfSetPsmf>, "scePsmfSetPsmf", 'x', "xx" },
{0XC7DB3A5B, &WrapU_UUU<scePsmfGetCurrentStreamType>, "scePsmfGetCurrentStreamType", 'x', "xxx"},
{0X28240568, &WrapU_U<scePsmfGetCurrentStreamNumber>, "scePsmfGetCurrentStreamNumber", 'x', "x" },
{0X1E6D9013, &WrapU_UUU<scePsmfSpecifyStreamWithStreamType>, "scePsmfSpecifyStreamWithStreamType", 'x', "xxx"},
{0X0C120E1D, &WrapU_UUU<scePsmfSpecifyStreamWithStreamTypeNumber>, "scePsmfSpecifyStreamWithStreamTypeNumber", 'x', "xxx"},
{0X4BC9BDE0, &WrapU_UI<scePsmfSpecifyStream>, "scePsmfSpecifyStream", 'x', "xi" },
{0X76D3AEBA, &WrapU_UU<scePsmfGetPresentationStartTime>, "scePsmfGetPresentationStartTime", 'x', "xx" },
{0XBD8AE0D8, &WrapU_UU<scePsmfGetPresentationEndTime>, "scePsmfGetPresentationEndTime", 'x', "xx" },
{0XEAED89CD, &WrapU_U<scePsmfGetNumberOfStreams>, "scePsmfGetNumberOfStreams", 'x', "x" },
{0X7491C438, &WrapU_U<scePsmfGetNumberOfEPentries>, "scePsmfGetNumberOfEPentries", 'x', "x" },
{0X0BA514E5, &WrapU_UU<scePsmfGetVideoInfo>, "scePsmfGetVideoInfo", 'x', "xx" },
{0XA83F7113, &WrapU_UU<scePsmfGetAudioInfo>, "scePsmfGetAudioInfo", 'x', "xx" },
{0X971A3A90, &WrapU_U<scePsmfCheckEPMap>, "scePsmfCheckEPmap", 'x', "x" },
{0X68D42328, &WrapU_UI<scePsmfGetNumberOfSpecificStreams>, "scePsmfGetNumberOfSpecificStreams", 'x', "xi" },
{0X5B70FCC1, &WrapU_UU<scePsmfQueryStreamOffset>, "scePsmfQueryStreamOffset", 'x', "xx" },
{0X9553CC91, &WrapU_UU<scePsmfQueryStreamSize>, "scePsmfQueryStreamSize", 'x', "xx" },
{0XB78EB9E9, &WrapU_UU<scePsmfGetHeaderSize>, "scePsmfGetHeaderSize", 'x', "xx" },
{0XA5EBFE81, &WrapU_UU<scePsmfGetStreamSize>, "scePsmfGetStreamSize", 'x', "xx" },
{0XE1283895, &WrapU_U<scePsmfGetPsmfVersion>, "scePsmfGetPsmfVersion", 'x', "x" },
{0X2673646B, &WrapU_U<scePsmfVerifyPsmf>, "scePsmfVerifyPsmf", 'x', "x" },
{0X4E624A34, &WrapU_UIU<scePsmfGetEPWithId>, "scePsmfGetEPWithId", 'x', "xix"},
{0X7C0E7AC3, &WrapU_UUU<scePsmfGetEPWithTimestamp>, "scePsmfGetEPWithTimestamp", 'x', "xxx"},
{0X5F457515, &WrapU_UU<scePsmfGetEPidWithTimestamp>, "scePsmfGetEPidWithTimestamp", 'x', "xx" },
{0X43AC7DBB, nullptr, "scePsmfGetPsmfMark", '?', "" },
{0XDE78E9FC, nullptr, "scePsmfGetNumberOfPsmfMarks", '?', "" },
};
const HLEFunction scePsmfPlayer[] =
{
{0X235D8787, &WrapI_UU<scePsmfPlayerCreate>, "scePsmfPlayerCreate", 'i', "xx" },
{0X1078C008, &WrapI_U<scePsmfPlayerStop>, "scePsmfPlayerStop", 'i', "x" },
{0X1E57A8E7, &WrapU_UII<scePsmfPlayerConfigPlayer>, "scePsmfPlayerConfigPlayer", 'x', "xii"},
{0X2BEB1569, &WrapI_U<scePsmfPlayerBreak>, "scePsmfPlayerBreak", 'i', "x" },
{0X3D6D25A9, &WrapI_UC<scePsmfPlayerSetPsmf>, "scePsmfPlayerSetPsmf", 'i', "xs" },
{0X58B83577, &WrapI_UC<scePsmfPlayerSetPsmfCB>, "scePsmfPlayerSetPsmfCB", 'i', "xs" },
{0X3EA82A4B, &WrapI_U<scePsmfPlayerGetAudioOutSize>, "scePsmfPlayerGetAudioOutSize", 'i', "x" },
{0X3ED62233, &WrapU_UU<scePsmfPlayerGetCurrentPts>, "scePsmfPlayerGetCurrentPts", 'x', "xx" },
{0X46F61F8B, &WrapI_UU<scePsmfPlayerGetVideoData>, "scePsmfPlayerGetVideoData", 'i', "xx" },
{0X68F07175, &WrapU_UUU<scePsmfPlayerGetCurrentAudioStream>, "scePsmfPlayerGetCurrentAudioStream", 'x', "xxx"},
{0X75F03FA2, &WrapU_UII<scePsmfPlayerSelectSpecificVideo>, "scePsmfPlayerSelectSpecificVideo", 'x', "xii"},
{0X85461EFF, &WrapU_UII<scePsmfPlayerSelectSpecificAudio>, "scePsmfPlayerSelectSpecificAudio", 'x', "xii"},
{0X8A9EBDCD, &WrapU_U<scePsmfPlayerSelectVideo>, "scePsmfPlayerSelectVideo", 'x', "x" },
{0X95A84EE5, &WrapI_UUI<scePsmfPlayerStart>, "scePsmfPlayerStart", 'i', "xxi"},
{0X9B71A274, &WrapI_U<scePsmfPlayerDelete>, "scePsmfPlayerDelete", 'i', "x" },
{0X9FF2B2E7, &WrapU_UUU<scePsmfPlayerGetCurrentVideoStream>, "scePsmfPlayerGetCurrentVideoStream", 'x', "xxx"},
{0XA0B8CA55, &WrapI_U<scePsmfPlayerUpdate>, "scePsmfPlayerUpdate", 'i', "x" },
{0XA3D81169, &WrapU_UII<scePsmfPlayerChangePlayMode>, "scePsmfPlayerChangePlayMode", 'x', "xii"},
{0XB8D10C56, &WrapU_U<scePsmfPlayerSelectAudio>, "scePsmfPlayerSelectAudio", 'x', "x" },
{0XB9848A74, &WrapI_UU<scePsmfPlayerGetAudioData>, "scePsmfPlayerGetAudioData", 'i', "xx" },
{0XDF089680, &WrapU_UU<scePsmfPlayerGetPsmfInfo>, "scePsmfPlayerGetPsmfInfo", 'x', "xx" },
{0XE792CD94, &WrapI_U<scePsmfPlayerReleasePsmf>, "scePsmfPlayerReleasePsmf", 'i', "x" },
{0XF3EFAA91, &WrapU_UUU<scePsmfPlayerGetCurrentPlayMode>, "scePsmfPlayerGetCurrentPlayMode", 'x', "xxx"},
{0XF8EF08A6, &WrapI_U<scePsmfPlayerGetCurrentStatus>, "scePsmfPlayerGetCurrentStatus", 'i', "x" },
{0X2D0E4E0A, &WrapI_UUU<scePsmfPlayerSetTempBuf>, "scePsmfPlayerSetTempBuf", 'i', "xxx"},
{0X76C0F4AE, &WrapI_UCI<scePsmfPlayerSetPsmfOffset>, "scePsmfPlayerSetPsmfOffset", 'i', "xsi"},
{0XA72DB4F9, &WrapI_UCI<scePsmfPlayerSetPsmfOffsetCB>, "scePsmfPlayerSetPsmfOffsetCB", 'i', "xsi"},
{0X340C12CB, nullptr, "scePsmfPlayer_340C12CB", '?', "" },
// Fake function for PPSSPP's use.
{0X05B193B7, &WrapI_U<__PsmfPlayerFinish>, "__PsmfPlayerFinish", 'i', "x" },
};
void Register_scePsmf() {
RegisterModule("scePsmf",ARRAY_SIZE(scePsmf),scePsmf);
}
void Register_scePsmfPlayer() {
RegisterModule("scePsmfPlayer",ARRAY_SIZE(scePsmfPlayer),scePsmfPlayer);
}