#include "stdafx.h"
#include "Emu/System.h"
#include "Emu/Cell/PPUModule.h"

#include <bitset>
#include "cellPamf.h"

LOG_CHANNEL(cellPamf);

template<>
void fmt_class_string<CellPamfError>::format(std::string& out, u64 arg)
{
	format_enum(out, arg, [](auto error)
	{
		switch (error)
		{
			STR_CASE(CELL_PAMF_ERROR_STREAM_NOT_FOUND);
			STR_CASE(CELL_PAMF_ERROR_INVALID_PAMF);
			STR_CASE(CELL_PAMF_ERROR_INVALID_ARG);
			STR_CASE(CELL_PAMF_ERROR_UNKNOWN_TYPE);
			STR_CASE(CELL_PAMF_ERROR_UNSUPPORTED_VERSION);
			STR_CASE(CELL_PAMF_ERROR_UNKNOWN_STREAM);
			STR_CASE(CELL_PAMF_ERROR_EP_NOT_FOUND);
			STR_CASE(CELL_PAMF_ERROR_NOT_AVAILABLE);
		}

		return unknown;
	});
}

error_code pamfVerifyMagicAndVersion(vm::cptr<PamfHeader> pAddr, vm::ptr<CellPamfReader> pSelf)
{
	ensure(!!pAddr); // Not checked on LLE

	if (pAddr->magic != std::bit_cast<be_t<u32>>("PAMF"_u32))
	{
		return CELL_PAMF_ERROR_UNKNOWN_TYPE;
	}

	if (pSelf)
	{
		pSelf->isPsmf = false;
	}

	be_t<u16> version;

	if (pAddr->version == std::bit_cast<be_t<u32>>("0040"_u32))
	{
		version = 40;
	}
	else if (pAddr->version == std::bit_cast<be_t<u32>>("0041"_u32))
	{
		version = 41;
	}
	else
	{
		return CELL_PAMF_ERROR_UNSUPPORTED_VERSION;
	}

	if (pSelf)
	{
		pSelf->version = version;
	}

	return CELL_OK;
}

error_code pamfGetHeaderAndDataSize(vm::cptr<PamfHeader> pAddr, u64 fileSize, vm::ptr<u64> headerSize, vm::ptr<u64> dataSize)
{
	ensure(!!pAddr); // Not checked on LLE

	if (error_code ret = pamfVerifyMagicAndVersion(pAddr, vm::null); ret != CELL_OK)
	{
		return ret;
	}

	const u64 header_size = pAddr->header_size * 0x800ull;
	const u64 data_size = pAddr->data_size * 0x800ull;

	if (header_size == 0 || (fileSize != 0 && header_size + data_size != fileSize))
	{
		return CELL_PAMF_ERROR_INVALID_PAMF;
	}

	if (headerSize)
	{
		*headerSize = header_size;
	}

	if (dataSize)
	{
		*dataSize = data_size;
	}

	return CELL_OK;
}

error_code pamfTypeChannelToStream(u8 type, u8 ch, u8* stream_coding_type, u8* stream_id, u8* private_stream_id)
{
	// This function breaks if ch is greater than 15, LLE doesn't check for this
	ensure(ch < 16);

	u8 _stream_coding_type;
	u8 _stream_id;
	u8 _private_stream_id;

	switch (type)
	{
	case CELL_PAMF_STREAM_TYPE_AVC:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_AVC;
		_stream_id = 0xe0 | ch;
		_private_stream_id = 0;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_M2V:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_M2V;
		_stream_id = 0xe0 | ch;
		_private_stream_id = 0;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_ATRAC3PLUS:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_ATRAC3PLUS;
		_stream_id = 0xbd;
		_private_stream_id = ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PAMF_LPCM:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_PAMF_LPCM;
		_stream_id = 0xbd;
		_private_stream_id = 0x40 | ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_AC3:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_AC3;
		_stream_id = 0xbd;
		_private_stream_id = 0x30 | ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_USER_DATA:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_USER_DATA;
		_stream_id = 0xbd;
		_private_stream_id = 0x20 | ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_AVC:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_PSMF;
		_stream_id = 0xe0 | ch;
		_private_stream_id = 0;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_ATRAC3PLUS:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_PSMF;
		_stream_id = 0xbd;
		_private_stream_id = ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_LPCM:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_PSMF;
		_stream_id = 0xbd;
		_private_stream_id = 0x10 | ch;
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_USER_DATA:
	{
		_stream_coding_type = PAMF_STREAM_CODING_TYPE_PSMF;
		_stream_id = 0xbd;
		_private_stream_id = 0x20 | ch;
		break;
	}

	default:
	{
		cellPamf.error("pamfTypeChannelToStream(): unknown type %d", type);
		return CELL_PAMF_ERROR_INVALID_ARG;
	}
	}

	if (stream_coding_type)
	{
		*stream_coding_type = _stream_coding_type;
	}

	if (stream_id)
	{
		*stream_id = _stream_id;
	}

	if (private_stream_id)
	{
		*private_stream_id = _private_stream_id;
	}

	return CELL_OK;
}

error_code pamfStreamToTypeChannel(u8 stream_coding_type, u8 stream_id, u8 private_stream_id, vm::ptr<u8> type, vm::ptr<u8> ch)
{
	u8 _type;
	u8 _ch;

	switch (stream_coding_type)
	{
	case PAMF_STREAM_CODING_TYPE_AVC:
		_type = CELL_PAMF_STREAM_TYPE_AVC;
		_ch = stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_M2V:
		_type = CELL_PAMF_STREAM_TYPE_M2V;
		_ch = stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_ATRAC3PLUS:
		_type = CELL_PAMF_STREAM_TYPE_ATRAC3PLUS;
		_ch = private_stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_PAMF_LPCM:
		_type = CELL_PAMF_STREAM_TYPE_PAMF_LPCM;
		_ch = private_stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_AC3:
		_type = CELL_PAMF_STREAM_TYPE_AC3;
		_ch = private_stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_USER_DATA:
		_type = CELL_PAMF_STREAM_TYPE_USER_DATA;
		_ch = private_stream_id & 0x0f;
		break;

	case PAMF_STREAM_CODING_TYPE_PSMF:
		if ((stream_id & 0xf0) == 0xe0)
		{
			_type = CELL_PAMF_STREAM_TYPE_PSMF_AVC;
			_ch = stream_id & 0x0f;

			if (private_stream_id != 0)
			{
				return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
			}
		}
		else if (stream_id == 0xbd)
		{
			_ch = private_stream_id & 0x0f;

			switch (private_stream_id & 0xf0)
			{
			case 0x00: _type = CELL_PAMF_STREAM_TYPE_PSMF_ATRAC3PLUS; break;
			case 0x10: _type = CELL_PAMF_STREAM_TYPE_PSMF_LPCM; break;
			case 0x20: _type = CELL_PAMF_STREAM_TYPE_PSMF_LPCM; break; // LLE doesn't use CELL_PAMF_STREAM_TYPE_PSMF_USER_DATA for some reason
			default: return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
			}
		}
		else
		{
			return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
		}
		break;

	default:
		cellPamf.error("pamfStreamToTypeChannel(): unknown stream_coding_type 0x%02x", stream_coding_type);
		return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
	}

	if (type)
	{
		*type = _type;
	}

	if (ch)
	{
		*ch = _ch;
	}

	return CELL_OK;
}

void pamfEpUnpack(vm::cptr<PamfEpHeader> ep_packed, vm::ptr<CellPamfEp> ep)
{
	ensure(!!ep_packed && !!ep); // Not checked on LLE

	ep->indexN = (ep_packed->value0 >> 14) + 1;
	ep->nThRefPictureOffset = ((ep_packed->value0 & 0x1fff) * 0x800) + 0x800;
	ep->pts.upper = ep_packed->pts_high;
	ep->pts.lower = ep_packed->pts_low;
	ep->rpnOffset = ep_packed->rpnOffset * 0x800ull;
}

void psmfEpUnpack(vm::cptr<PsmfEpHeader> ep_packed, vm::ptr<CellPamfEp> ep)
{
	ensure(!!ep_packed && !!ep); // Not checked on LLE

	ep->indexN = (ep_packed->value0 >> 14) + 1;
	ep->nThRefPictureOffset = ((ep_packed->value0 & 0xffe) * 0x400) + 0x800;
	ep->pts.upper = ep_packed->value0 & 1;
	ep->pts.lower = ep_packed->pts_low;
	ep->rpnOffset = ep_packed->rpnOffset * 0x800ull;
}

bool pamfIsSameStreamType(u8 type, u8 requested_type)
{
	switch (requested_type)
	{
	case CELL_PAMF_STREAM_TYPE_VIDEO: return type == CELL_PAMF_STREAM_TYPE_AVC || type == CELL_PAMF_STREAM_TYPE_M2V;
	case CELL_PAMF_STREAM_TYPE_AUDIO: return type == CELL_PAMF_STREAM_TYPE_ATRAC3PLUS || type == CELL_PAMF_STREAM_TYPE_AC3 || type == CELL_PAMF_STREAM_TYPE_PAMF_LPCM;
	case CELL_PAMF_STREAM_TYPE_UNK:   return type == CELL_PAMF_STREAM_TYPE_PAMF_LPCM || type == CELL_PAMF_STREAM_TYPE_PSMF_ATRAC3PLUS; // ??? no idea what this is for
	default:                          return requested_type == type;
	}
}

error_code pamfVerify(vm::cptr<PamfHeader> pAddr, u64 fileSize, vm::ptr<CellPamfReader> pSelf, u32 attribute)
{
	ensure(!!pAddr); // Not checked on LLE

	if (error_code ret = pamfVerifyMagicAndVersion(pAddr, pSelf); ret != CELL_OK)
	{
		return ret;
	}

	const u64 header_size = pAddr->header_size * 0x800ull;
	const u64 data_size = pAddr->data_size * 0x800ull;

	// Header size
	if (header_size == 0)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid header_size" };
	}

	if (pSelf)
	{
		pSelf->headerSize = header_size;
		pSelf->dataSize = data_size;
	}

	// Data size
	if (fileSize != 0 && header_size + data_size != fileSize)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: fileSize isn't equal header_size + data_size" };
	}

	const u32 psmf_marks_offset = pAddr->psmf_marks_offset;
	const u32 psmf_marks_size = pAddr->psmf_marks_size;
	const u32 unk_offset = pAddr->unk_offset;
	const u32 unk_size = pAddr->unk_size;

	// PsmfMarks
	if (psmf_marks_offset == 0)
	{
		if (psmf_marks_size != 0)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: psmf_marks_offset is zero but psmf_marks_size is not zero" };
		}
	}
	else
	{
		if (psmf_marks_size == 0)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: psmf_marks_offset is set but psmf_marks_size is zero" };
		}

		if (header_size < static_cast<u64>(psmf_marks_offset) + psmf_marks_size)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: header_size is less than psmf_marks_offset + psmf_marks_size" };
		}
	}

	if (unk_offset == 0)
	{
		if (unk_size != 0)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: unk_offset is zero but unk_size is not zero" };
		}
	}
	else
	{
		if (unk_size == 0)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: unk_offset is set but unk_size is zero" };
		}

		if (header_size < static_cast<u64>(unk_offset) + unk_size)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: header_size is less than unk_offset + unk_size" };
		}
	}

	if (unk_offset < static_cast<u64>(psmf_marks_offset) + psmf_marks_size)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: unk_offset is less than psmf_marks_offset + psmf_marks_size" };
	}


	// Sequence Info

	const u32 seq_info_size = pAddr->seq_info.size;

	// Sequence info size
	if (offsetof(PamfHeader, seq_info) + sizeof(u32) + seq_info_size > header_size)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid seq_info_size" };
	}

	const u64 start_pts = static_cast<u64>(pAddr->seq_info.start_pts_high) << 32 | pAddr->seq_info.start_pts_low;
	const u64 end_pts = static_cast<u64>(pAddr->seq_info.end_pts_high) << 32 | pAddr->seq_info.end_pts_low;

	// Start and end presentation time stamps
	if (end_pts > CODEC_TS_INVALID)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid end_pts" };
	}

	if (start_pts >= end_pts)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid start_pts" };
	}

	// Grouping period count
	if (pAddr->seq_info.grouping_period_num != 1)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid grouping_period_num" };
	}


	// Grouping Period

	const u32 grouping_period_size = pAddr->seq_info.grouping_periods.size;

	// Grouping period size
	if (offsetof(PamfHeader, seq_info.grouping_periods) + sizeof(u32) + grouping_period_size > header_size)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid grouping_period_size" };
	}

	const u64 grp_period_start_pts = static_cast<u64>(pAddr->seq_info.grouping_periods.start_pts_high) << 32 | pAddr->seq_info.grouping_periods.start_pts_low;
	const u64 grp_period_end_pts = static_cast<u64>(pAddr->seq_info.grouping_periods.start_pts_high) << 32 | pAddr->seq_info.grouping_periods.end_pts_low; // LLE uses start_pts_high due to a bug

	// Start and end presentation time stamps
	if (grp_period_end_pts > CODEC_TS_INVALID)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid grp_period_end_pts" };
	}

	if (grp_period_start_pts >= grp_period_end_pts)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid grp_period_start_pts" };
	}

	if (grp_period_start_pts != start_pts)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: grp_period_start_pts not equal start_pts" };
	}

	// Group count
	if (pAddr->seq_info.grouping_periods.group_num != 1)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid group_num" };
	}


	// Group

	const u32 group_size = pAddr->seq_info.grouping_periods.groups.size;

	// StreamGroup size
	if (offsetof(PamfHeader, seq_info.grouping_periods.groups) + sizeof(u32) + group_size > header_size)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid group_size" };
	}

	const u8 stream_num = pAddr->seq_info.grouping_periods.groups.stream_num;

	// Stream count
	if (stream_num == 0)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid stream_num" };
	}


	// Streams

	const auto streams = &pAddr->seq_info.grouping_periods.groups.streams;

	std::bitset<16> channels_used[6]{};

	u32 end_of_streams_addr = 0;
	u32 next_ep_table_addr = 0;

	for (u8 stream_idx = 0; stream_idx < stream_num; stream_idx++)
	{
		vm::var<u8> type;
		vm::var<u8> ch;

		// Stream coding type and IDs
		if (pamfStreamToTypeChannel(streams[stream_idx].stream_coding_type, streams[stream_idx].stream_id, streams[stream_idx].private_stream_id, type, ch) != CELL_OK)
		{
			return CELL_PAMF_ERROR_UNKNOWN_STREAM;
		}

		// Every channel may only be used once per type
		if (channels_used[*type].test(*ch))
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid channel" };
		}

		// Mark channel as used
		channels_used[*type].set(*ch);

		const u32 ep_offset = streams[stream_idx].ep_offset;
		const u32 ep_num = streams[stream_idx].ep_num;

		// Entry point offset and number
		if (ep_num == 0)
		{
			if (ep_offset != 0)
			{
				return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: ep_num is zero but ep_offset is not zero" };
			}
		}
		else
		{
			if (ep_offset == 0)
			{
				return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid ep_offset" };
			}

			if (ep_offset + ep_num * sizeof(PamfEpHeader) > header_size)
			{
				return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid ep_num" };
			}
		}


		// Entry points

		// Skip if there are no entry points or if the minimum header attribute is set
		if (ep_offset == 0 || attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
		{
			continue;
		}

		const auto eps = vm::cptr<PamfEpHeader>::make(pAddr.addr() + ep_offset);

		// Entry point tables must be sorted by the stream index to which they belong
		// and there mustn't be any gaps between them
		if (end_of_streams_addr == 0)
		{
			end_of_streams_addr = eps.addr();
			next_ep_table_addr = end_of_streams_addr;
		}
		else if (next_ep_table_addr != eps.addr())
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid ep table address" };
		}

		u64 previous_rpn_offset = 0;
		for (u32 ep_idx = 0; ep_idx < ep_num; ep_idx++)
		{
			const u64 pts = static_cast<u64>(eps[ep_idx].pts_high) << 32 | eps[ep_idx].pts_low;

			// Entry point time stamp
			if (pts > CODEC_TS_INVALID)
			{
				return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid ep pts" };
			}

			const u64 rpn_offset = eps[ep_idx].rpnOffset * 0x800ull;

			// Entry point rpnOffset
			if (rpn_offset > data_size || rpn_offset < previous_rpn_offset)
			{
				return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid rpn_offset" };
			}

			previous_rpn_offset = rpn_offset;
		}

		next_ep_table_addr += ep_num * sizeof(PamfEpHeader);
	}

	// This can overflow on LLE, the +4 is necessary on both sides and the left operand needs to be u32
	if (group_size + 4 > grouping_period_size - offsetof(PamfGroupingPeriod, groups) + sizeof(u32)
		|| grouping_period_size + 4 > seq_info_size - offsetof(PamfSequenceInfo, grouping_periods) + sizeof(u32))
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: size mismatch" };
	}

	// Since multiple grouping periods/groups was never implemented, number of streams in SequenceInfo must be equal stream_num in Group
	if (pAddr->seq_info.total_stream_num != stream_num)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: number of streams mismatch" };
	}


	// PsmfMarks
	// This is probably useless since the official PAMF tools don't support PsmfMarks

	if (end_of_streams_addr == 0)
	{
		if (psmf_marks_offset != 0)
		{
			end_of_streams_addr = pAddr.addr() + psmf_marks_offset;
		}
		else if (unk_offset != 0)
		{
			end_of_streams_addr = pAddr.addr() + unk_offset;
		}
	}

	if (end_of_streams_addr != 0 && pAddr.addr() + offsetof(PamfHeader, seq_info.grouping_periods) + sizeof(u32) + grouping_period_size != end_of_streams_addr)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid offset of ep tables or psmf marks" };
	}

	if (next_ep_table_addr != 0 && psmf_marks_offset == 0)
	{
		if (unk_offset != 0 && pAddr.addr() + unk_offset != next_ep_table_addr)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid unk_offset" };
		}
	}
	else if (next_ep_table_addr != 0 && pAddr.addr() + psmf_marks_offset != next_ep_table_addr)
	{
		return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid psmf_marks_offset" };
	}
	else if (psmf_marks_offset != 0 && !(attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER))
	{
		const u32 size = vm::read32(pAddr.addr() + psmf_marks_offset);

		if (size + sizeof(u32) != psmf_marks_size)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid psmf_marks_size" };
		}

		const u16 marks_num = vm::read16(pAddr.addr() + psmf_marks_offset + 6); // LLE uses the wrong offset (6 instead of 4)

		if (sizeof(u16) + marks_num * 0x28 /*sizeof PsmfMark*/ != size)
		{
			return { CELL_PAMF_ERROR_INVALID_PAMF, "pamfVerify() failed: invalid marks_num" };
		}

		// There are more checks in LLE but due to the bug above these would never be executed
	}

	return CELL_OK;
}

error_code cellPamfGetStreamOffsetAndSize(vm::ptr<PamfHeader> pAddr, u64 fileSize, vm::ptr<u64> pOffset, vm::ptr<u64> pSize);

error_code cellPamfGetHeaderSize(vm::ptr<PamfHeader> pAddr, u64 fileSize, vm::ptr<u64> pSize)
{
	cellPamf.notice("cellPamfGetHeaderSize(pAddr=*0x%x, fileSize=0x%llx, pSize=*0x%x)", pAddr, fileSize, pSize);

	return cellPamfGetStreamOffsetAndSize(pAddr, fileSize, pSize, vm::null);
}

error_code cellPamfGetHeaderSize2(vm::ptr<PamfHeader> pAddr, u64 fileSize, u32 attribute, vm::ptr<u64> pSize)
{
	cellPamf.notice("cellPamfGetHeaderSize2(pAddr=*0x%x, fileSize=0x%llx, attribute=0x%x, pSize=*0x%x)", pAddr, fileSize, attribute, pSize);

	const vm::var<u64> header_size;

	if (error_code ret = cellPamfGetStreamOffsetAndSize(pAddr, fileSize, header_size, vm::null); ret != CELL_OK)
	{
		return ret;
	}

	if (attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
	{
		if (pAddr->magic != std::bit_cast<be_t<u32>>("PAMF"_u32)) // Already checked in cellPamfGetStreamOffsetAndSize(), should always evaluate to false at this point
		{
			return CELL_PAMF_ERROR_UNKNOWN_TYPE;
		}

		const u64 min_header_size = offsetof(PamfHeader, seq_info) + sizeof(u32) + pAddr->seq_info.size; // Size without EP tables

		if (min_header_size > *header_size)
		{
			return CELL_PAMF_ERROR_INVALID_PAMF;
		}

		*header_size = min_header_size;
	}

	if (pSize)
	{
		*pSize = *header_size;
	}

	return CELL_OK;
}

error_code cellPamfGetStreamOffsetAndSize(vm::ptr<PamfHeader> pAddr, u64 fileSize, vm::ptr<u64> pOffset, vm::ptr<u64> pSize)
{
	cellPamf.notice("cellPamfGetStreamOffsetAndSize(pAddr=*0x%x, fileSize=0x%llx, pOffset=*0x%x, pSize=*0x%x)", pAddr, fileSize, pOffset, pSize);

	return pamfGetHeaderAndDataSize(pAddr, fileSize, pOffset, pSize);
}

error_code cellPamfVerify(vm::cptr<PamfHeader> pAddr, u64 fileSize)
{
	cellPamf.notice("cellPamfVerify(pAddr=*0x%x, fileSize=0x%llx)", pAddr, fileSize);

	return pamfVerify(pAddr, fileSize, vm::null, CELL_PAMF_ATTRIBUTE_VERIFY_ON);
}

error_code cellPamfReaderSetStreamWithIndex(vm::ptr<CellPamfReader> pSelf, u8 streamIndex);

error_code cellPamfReaderInitialize(vm::ptr<CellPamfReader> pSelf, vm::cptr<PamfHeader> pAddr, u64 fileSize, u32 attribute)
{
	cellPamf.notice("cellPamfReaderInitialize(pSelf=*0x%x, pAddr=*0x%x, fileSize=0x%llx, attribute=0x%x)", pSelf, pAddr, fileSize, attribute);

	ensure(!!pSelf); // Not checked on LLE

	std::memset(pSelf.get_ptr(), 0, sizeof(CellPamfReader));

	pSelf->attribute = attribute;

	if (attribute & CELL_PAMF_ATTRIBUTE_VERIFY_ON)
	{
		if (error_code ret = pamfVerify(pAddr, fileSize, pSelf, attribute); ret != CELL_OK)
		{
			return ret;
		}
	}

	pSelf->pamf.header = pAddr;
	pSelf->pamf.sequenceInfo = pAddr.ptr(&PamfHeader::seq_info);

	pSelf->currentGroupingPeriodIndex = -1;
	pSelf->currentGroupIndex = -1;
	pSelf->currentStreamIndex = -1;

	if (pAddr->seq_info.grouping_period_num == 0)
	{
		return CELL_PAMF_ERROR_INVALID_PAMF;
	}

	pSelf->currentGroupingPeriodIndex = 0;
	pSelf->pamf.currentGroupingPeriod = pSelf->pamf.sequenceInfo.ptr(&PamfSequenceInfo::grouping_periods);

	if (pAddr->seq_info.grouping_periods.group_num != 0)
	{
		pSelf->currentGroupIndex = 0;
		pSelf->pamf.currentGroup = pSelf->pamf.currentGroupingPeriod.ptr(&PamfGroupingPeriod::groups);

		cellPamfReaderSetStreamWithIndex(pSelf, 0);
	}

	return CELL_OK;
}

error_code cellPamfReaderGetPresentationStartTime(vm::ptr<CellPamfReader> pSelf, vm::ptr<CellCodecTimeStamp> pTimeStamp)
{
	cellPamf.notice("cellPamfReaderGetPresentationStartTime(pSelf=*0x%x, pTimeStamp=*0x%x)", pSelf, pTimeStamp);

	// always returns CELL_OK

	ensure(!!pSelf && !!pTimeStamp); // Not checked on LLE

	if (pSelf->isPsmf)
	{
		pTimeStamp->upper = pSelf->psmf.sequenceInfo->start_pts_high;
		pTimeStamp->lower = pSelf->psmf.sequenceInfo->start_pts_low;
	}
	else
	{
		pTimeStamp->upper = pSelf->pamf.sequenceInfo->start_pts_high;
		pTimeStamp->lower = pSelf->pamf.sequenceInfo->start_pts_low;
	}

	return CELL_OK;
}

error_code cellPamfReaderGetPresentationEndTime(vm::ptr<CellPamfReader> pSelf, vm::ptr<CellCodecTimeStamp> pTimeStamp)
{
	cellPamf.notice("cellPamfReaderGetPresentationEndTime(pSelf=*0x%x, pTimeStamp=*0x%x)", pSelf, pTimeStamp);

	// always returns CELL_OK

	ensure(!!pSelf && !!pTimeStamp); // Not checked on LLE

	if (pSelf->isPsmf)
	{
		pTimeStamp->upper = pSelf->psmf.sequenceInfo->end_pts_high;
		pTimeStamp->lower = pSelf->psmf.sequenceInfo->end_pts_low;
	}
	else
	{
		pTimeStamp->upper = pSelf->pamf.sequenceInfo->end_pts_high;
		pTimeStamp->lower = pSelf->pamf.sequenceInfo->end_pts_low;
	}

	return CELL_OK;
}

u32 cellPamfReaderGetMuxRateBound(vm::ptr<CellPamfReader> pSelf)
{
	cellPamf.notice("cellPamfReaderGetMuxRateBound(pSelf=*0x%x)", pSelf);

	ensure(!!pSelf); // Not checked on LLE

	if (pSelf->isPsmf)
	{
		return pSelf->psmf.sequenceInfo->mux_rate_bound & 0x003fffff;
	}

	return pSelf->pamf.sequenceInfo->mux_rate_bound & 0x003fffff;
}

u8 cellPamfReaderGetNumberOfStreams(vm::ptr<CellPamfReader> pSelf)
{
	cellPamf.notice("cellPamfReaderGetNumberOfStreams(pSelf=*0x%x)", pSelf);

	ensure(!!pSelf); // Not checked on LLE

	return pSelf->pamf.currentGroup->stream_num;
}

u8 cellPamfReaderGetNumberOfSpecificStreams(vm::ptr<CellPamfReader> pSelf, u8 streamType)
{
	cellPamf.notice("cellPamfReaderGetNumberOfSpecificStreams(pSelf=*0x%x, streamType=%d)", pSelf, streamType);

	ensure(!!pSelf); // Not checked on LLE

	const vm::var<u8> type;
	u8 found = 0;

	if (pSelf->isPsmf)
	{
		const auto streams = pSelf->psmf.currentGroup.ptr(&PsmfGroup::streams);

		for (u8 i = 0; i < pSelf->psmf.currentGroup->stream_num; i++)
		{
			if (pamfStreamToTypeChannel(PAMF_STREAM_CODING_TYPE_PSMF, streams[i].stream_id, streams[i].private_stream_id, type, vm::null) == CELL_OK)
			{
				found += pamfIsSameStreamType(*type, streamType);
			}
		}
	}
	else
	{
		const auto streams = pSelf->pamf.currentGroup.ptr(&PamfGroup::streams);

		for (u8 i = 0; i < pSelf->pamf.currentGroup->stream_num; i++)
		{
			if (pamfStreamToTypeChannel(streams[i].stream_coding_type, streams[i].stream_id, streams[i].private_stream_id, type, vm::null) == CELL_OK)
			{
				found += pamfIsSameStreamType(*type, streamType);
			}
		}
	}

	return found;
}

error_code cellPamfReaderSetStreamWithIndex(vm::ptr<CellPamfReader> pSelf, u8 streamIndex)
{
	cellPamf.notice("cellPamfReaderSetStreamWithIndex(pSelf=*0x%x, streamIndex=%d)", pSelf, streamIndex);

	ensure(!!pSelf); // Not checked on LLE

	if (streamIndex >= pSelf->pamf.currentGroup->stream_num)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	pSelf->currentStreamIndex = streamIndex;

	if (pSelf->isPsmf)
	{
		pSelf->psmf.currentStream = pSelf->psmf.currentGroup.ptr(&PsmfGroup::streams) + streamIndex;
	}
	else
	{
		pSelf->pamf.currentStream = pSelf->pamf.currentGroup.ptr(&PamfGroup::streams) + streamIndex;
	}

	return CELL_OK;
}

error_code cellPamfReaderSetStreamWithTypeAndChannel(vm::ptr<CellPamfReader> pSelf, u8 streamType, u8 ch)
{
	cellPamf.notice("cellPamfReaderSetStreamWithTypeAndChannel(pSelf=*0x%x, streamType=%d, ch=%d)", pSelf, streamType, ch);

	// This function is broken on LLE

	ensure(!!pSelf); // Not checked on LLE

	u8 stream_coding_type;
	u8 stream_id;
	u8 private_stream_id;

	if (pamfTypeChannelToStream(streamType, ch, &stream_coding_type, &stream_id, &private_stream_id) != CELL_OK)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	const u8 stream_num = pSelf->pamf.currentGroup->stream_num;
	u32 i = 0;

	if (pSelf->isPsmf)
	{
		const auto streams = pSelf->psmf.currentGroup.ptr(&PsmfGroup::streams);

		for (; i < stream_num; i++)
		{
			// LLE increments the index by 12 instead of 1
			if (stream_coding_type == PAMF_STREAM_CODING_TYPE_PSMF && streams[i * 12].stream_id == stream_id && streams[i * 12].private_stream_id == private_stream_id)
			{
				break;
			}
		}
	}
	else
	{
		const auto streams = pSelf->pamf.currentGroup.ptr(&PamfGroup::streams);

		for (; i < stream_num; i++)
		{
			// LLE increments the index by 0x10 instead of 1
			if (streams[i * 0x10].stream_coding_type == stream_coding_type && streams[i * 0x10].stream_id == stream_id && streams[i * 0x10].private_stream_id == private_stream_id)
			{
				break;
			}
		}
	}

	if (i == stream_num)
	{
		i = CELL_PAMF_ERROR_STREAM_NOT_FOUND; // LLE writes the error code to the index
	}

	if (pSelf->currentStreamIndex != i)
	{
		pSelf->pamf.currentStream = pSelf->pamf.currentGroup.ptr(&PamfGroup::streams); // LLE always sets this to the first stream
		pSelf->currentStreamIndex = i;
	}

	if (i == CELL_PAMF_ERROR_STREAM_NOT_FOUND)
	{
		return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
	}

	return not_an_error(i);
}

error_code cellPamfReaderSetStreamWithTypeAndIndex(vm::ptr<CellPamfReader> pSelf, u8 streamType, u8 streamIndex)
{
	cellPamf.notice("cellPamfReaderSetStreamWithTypeAndIndex(pSelf=*0x%x, streamType=%d, streamIndex=%d)", pSelf, streamType, streamIndex);

	ensure(!!pSelf); // Not checked on LLE

	const u8 stream_num = pSelf->pamf.currentGroup->stream_num;

	if (streamIndex >= stream_num)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	const vm::var<u8> type;
	u32 found = 0;

	if (pSelf->isPsmf)
	{
		const auto streams = pSelf->psmf.currentGroup.ptr(&PsmfGroup::streams);

		for (u8 i = 0; i < stream_num; i++)
		{
			if (pamfStreamToTypeChannel(PAMF_STREAM_CODING_TYPE_PSMF, streams[i].stream_id, streams[i].private_stream_id, type, vm::null) != CELL_OK)
			{
				continue;
			}

			found += *type == streamType;

			if (found > streamIndex)
			{
				pSelf->currentStreamIndex = streamIndex; // LLE sets this to the index counting only streams of the requested type instead of the overall index
				pSelf->psmf.currentStream = streams; // LLE always sets this to the first stream
				return not_an_error(i);
			}
		}
	}
	else
	{
		const auto streams = pSelf->pamf.currentGroup.ptr(&PamfGroup::streams);

		for (u8 i = 0; i < stream_num; i++)
		{
			if (pamfStreamToTypeChannel(streams[i].stream_coding_type, streams[i].stream_id, streams[i].private_stream_id, type, vm::null) != CELL_OK)
			{
				continue;
			}

			found += pamfIsSameStreamType(*type, streamType);

			if (found > streamIndex)
			{
				pSelf->currentStreamIndex = i;
				pSelf->pamf.currentStream = streams + i;
				return not_an_error(i);
			}
		}
	}

	return CELL_PAMF_ERROR_STREAM_NOT_FOUND;
}

error_code cellPamfStreamTypeToEsFilterId(u8 type, u8 ch, vm::ptr<CellCodecEsFilterId> pEsFilterId)
{
	cellPamf.notice("cellPamfStreamTypeToEsFilterId(type=%d, ch=%d, pEsFilterId=*0x%x)", type, ch, pEsFilterId);

	if (!pEsFilterId)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	u8 stream_id = 0;
	u8 private_stream_id = 0;

	if (pamfTypeChannelToStream(type, ch, nullptr, &stream_id, &private_stream_id) != CELL_OK)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	pEsFilterId->filterIdMajor = stream_id;
	pEsFilterId->filterIdMinor = private_stream_id;
	pEsFilterId->supplementalInfo1 = type == CELL_PAMF_STREAM_TYPE_AVC;
	pEsFilterId->supplementalInfo2 = 0;

	return CELL_OK;
}

s32 cellPamfReaderGetStreamIndex(vm::ptr<CellPamfReader> pSelf)
{
	cellPamf.notice("cellPamfReaderGetStreamIndex(pSelf=*0x%x)", pSelf);

	ensure(!!pSelf); // Not checked on LLE

	return pSelf->currentStreamIndex;
}

error_code cellPamfReaderGetStreamTypeAndChannel(vm::ptr<CellPamfReader> pSelf, vm::ptr<u8> pType, vm::ptr<u8> pCh)
{
	cellPamf.notice("cellPamfReaderGetStreamTypeAndChannel(pSelf=*0x%x, pType=*0x%x, pCh=*0x%x", pSelf, pType, pCh);

	ensure(!!pSelf); // Not checked on LLE

	if (pSelf->isPsmf)
	{
		const auto stream = pSelf->psmf.currentStream;
		return pamfStreamToTypeChannel(PAMF_STREAM_CODING_TYPE_PSMF, stream->stream_id, stream->private_stream_id, pType, pCh);
	}
	else
	{
		const auto stream = pSelf->pamf.currentStream;
		return pamfStreamToTypeChannel(stream->stream_coding_type, stream->stream_id, stream->private_stream_id, pType, pCh);
	}
}

error_code cellPamfReaderGetEsFilterId(vm::ptr<CellPamfReader> pSelf, vm::ptr<CellCodecEsFilterId> pEsFilterId)
{
	cellPamf.notice("cellPamfReaderGetEsFilterId(pSelf=*0x%x, pEsFilterId=*0x%x)", pSelf, pEsFilterId);

	// always returns CELL_OK

	ensure(!!pSelf && !!pEsFilterId); // Not checked on LLE

	if (pSelf->isPsmf)
	{
		pEsFilterId->filterIdMajor = pSelf->psmf.currentStream->stream_id;
		pEsFilterId->filterIdMinor = pSelf->psmf.currentStream->private_stream_id;
		pEsFilterId->supplementalInfo1 = 0;
	}
	else
	{
		pEsFilterId->filterIdMajor = pSelf->pamf.currentStream->stream_id;
		pEsFilterId->filterIdMinor = pSelf->pamf.currentStream->private_stream_id;
		pEsFilterId->supplementalInfo1 = pSelf->pamf.currentStream->stream_coding_type == PAMF_STREAM_CODING_TYPE_AVC;
	}

	pEsFilterId->supplementalInfo2 = 0;
	return CELL_OK;
}

error_code cellPamfReaderGetStreamInfo(vm::ptr<CellPamfReader> pSelf, vm::ptr<void> pInfo, u32 size)
{
	cellPamf.notice("cellPamfReaderGetStreamInfo(pSelf=*0x%x, pInfo=*0x%x, size=%d)", pSelf, pInfo, size);

	ensure(!!pSelf); // Not checked on LLE

	const auto& header = *pSelf->pamf.currentStream;
	const auto& psmf_header = *pSelf->psmf.currentStream;
	const vm::var<u8> type;
	error_code ret;

	if (pSelf->isPsmf)
	{
		ret = pamfStreamToTypeChannel(PAMF_STREAM_CODING_TYPE_PSMF, psmf_header.stream_id, psmf_header.private_stream_id, type, vm::null);
	}
	else
	{
		ret = pamfStreamToTypeChannel(header.stream_coding_type, header.stream_id, header.private_stream_id, type, vm::null);
	}

	if (ret != CELL_OK)
	{
		return CELL_PAMF_ERROR_INVALID_PAMF;
	}

	switch (*type)
	{
	case CELL_PAMF_STREAM_TYPE_AVC:
	{
		if (size < sizeof(CellPamfAvcInfo))
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		auto info = vm::static_ptr_cast<CellPamfAvcInfo>(pInfo);

		info->profileIdc = header.AVC.profileIdc;
		info->levelIdc = header.AVC.levelIdc;
		info->frameMbsOnlyFlag = (header.AVC.x2 & 0x80) >> 7;
		info->videoSignalInfoFlag = (header.AVC.x2 & 0x40) >> 6;
		info->frameRateInfo = (header.AVC.x2 & 0x0f) - 1;
		info->aspectRatioIdc = header.AVC.aspectRatioIdc;

		if (header.AVC.aspectRatioIdc == 0xff)
		{
			info->sarWidth = header.AVC.sarWidth;
			info->sarHeight = header.AVC.sarHeight;
		}
		else
		{
			info->sarWidth = 0;
			info->sarHeight = 0;
		}

		info->horizontalSize = (header.AVC.horizontalSize & u8{0xff}) * 16;
		info->verticalSize = (header.AVC.verticalSize & u8{0xff}) * 16;
		info->frameCropLeftOffset = header.AVC.frameCropLeftOffset;
		info->frameCropRightOffset = header.AVC.frameCropRightOffset;
		info->frameCropTopOffset = header.AVC.frameCropTopOffset;
		info->frameCropBottomOffset = header.AVC.frameCropBottomOffset;

		if (info->videoSignalInfoFlag)
		{
			info->videoFormat = header.AVC.x14 >> 5;
			info->videoFullRangeFlag = (header.AVC.x14 & 0x10) >> 4;
			info->colourPrimaries = header.AVC.colourPrimaries;
			info->transferCharacteristics = header.AVC.transferCharacteristics;
			info->matrixCoefficients = header.AVC.matrixCoefficients;
		}
		else
		{
			info->videoFormat = 0;
			info->videoFullRangeFlag = 0;
			info->colourPrimaries = 0;
			info->transferCharacteristics = 0;
			info->matrixCoefficients = 0;
		}

		info->entropyCodingModeFlag = (header.AVC.x18 & 0x80) >> 7;
		info->deblockingFilterFlag = (header.AVC.x18 & 0x40) >> 6;
		info->minNumSlicePerPictureIdc = (header.AVC.x18 & 0x30) >> 4;
		info->nfwIdc = header.AVC.x18 & 0x03;
		info->maxMeanBitrate = header.AVC.maxMeanBitrate;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_AVC");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_M2V:
	{
		if (size < sizeof(CellPamfM2vInfo))
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		auto info = vm::static_ptr_cast<CellPamfM2vInfo>(pInfo);

		switch (header.M2V.x0)
		{
		case 0x44: info->profileAndLevelIndication = 3; break;
		case 0x48: info->profileAndLevelIndication = 1; break;
		default: info->profileAndLevelIndication = CELL_PAMF_M2V_UNKNOWN;
		}

		info->progressiveSequence = (header.M2V.x2 & 0x80) >> 7;
		info->videoSignalInfoFlag = (header.M2V.x2 & 0x40) >> 6;
		info->frameRateInfo = header.M2V.x2 & 0xf;
		info->aspectRatioIdc = header.M2V.aspectRatioIdc;

		if (header.M2V.aspectRatioIdc == 0xff)
		{
			info->sarWidth = header.M2V.sarWidth;
			info->sarHeight = header.M2V.sarHeight;
		}
		else
		{
			info->sarWidth = 0;
			info->sarHeight = 0;
		}

		info->horizontalSize = (header.M2V.horizontalSize & u8{0xff}) * 16;
		info->verticalSize = (header.M2V.verticalSize & u8{0xff}) * 16;
		info->horizontalSizeValue = header.M2V.horizontalSizeValue;
		info->verticalSizeValue = header.M2V.verticalSizeValue;

		if (info->videoSignalInfoFlag)
		{
			info->videoFormat = header.M2V.x14 >> 5;
			info->videoFullRangeFlag = (header.M2V.x14 & 0x10) >> 4;
			info->colourPrimaries = header.M2V.colourPrimaries;
			info->transferCharacteristics = header.M2V.transferCharacteristics;
			info->matrixCoefficients = header.M2V.matrixCoefficients;
		}
		else
		{
			info->videoFormat = 0;
			info->videoFullRangeFlag = 0;
			info->colourPrimaries = 0;
			info->transferCharacteristics = 0;
			info->matrixCoefficients = 0;
		}

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_M2V");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_ATRAC3PLUS:
	{
		if (size < sizeof(CellPamfAtrac3plusInfo))
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		auto info = vm::static_ptr_cast<CellPamfAtrac3plusInfo>(pInfo);

		info->samplingFrequency = header.audio.freq & 0xf;
		info->numberOfChannels = header.audio.channels & 0xf;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_ATRAC3PLUS");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PAMF_LPCM:
	{
		if (size < sizeof(CellPamfLpcmInfo))
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		auto info = vm::static_ptr_cast<CellPamfLpcmInfo>(pInfo);

		info->samplingFrequency = header.audio.freq & 0xf;
		info->numberOfChannels = header.audio.channels & 0xf;
		info->bitsPerSample = header.audio.bps >> 6;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_PAMF_LPCM");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_AC3:
	{
		if (size < sizeof(CellPamfAc3Info))
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		auto info = vm::static_ptr_cast<CellPamfAc3Info>(pInfo);

		info->samplingFrequency = header.audio.freq & 0xf;
		info->numberOfChannels = header.audio.channels & 0xf;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_AC3");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_USER_DATA:
	case CELL_PAMF_STREAM_TYPE_PSMF_USER_DATA:
	{
		cellPamf.error("cellPamfReaderGetStreamInfo(): invalid type CELL_PAMF_STREAM_TYPE_USER_DATA");
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_AVC:
	{
		if (size < 4)
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		vm::static_ptr_cast<u16>(pInfo)[0] = psmf_header.video.horizontalSize * 0x10;
		vm::static_ptr_cast<u16>(pInfo)[1] = psmf_header.video.verticalSize * 0x10;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): CELL_PAMF_STREAM_TYPE_PSMF_AVC");
		break;
	}

	case CELL_PAMF_STREAM_TYPE_PSMF_ATRAC3PLUS:
	case CELL_PAMF_STREAM_TYPE_PSMF_LPCM:
	{
		if (size < 2)
		{
			return CELL_PAMF_ERROR_INVALID_ARG;
		}

		vm::static_ptr_cast<u8>(pInfo)[0] = psmf_header.audio.channelConfiguration;
		vm::static_ptr_cast<u8>(pInfo)[1] = psmf_header.audio.samplingFrequency & 0x0f;

		cellPamf.notice("cellPamfReaderGetStreamInfo(): PSMF audio");
		break;
	}

	default:
	{
		// invalid type or getting type/ch failed
		cellPamf.error("cellPamfReaderGetStreamInfo(): invalid type %d", *type);
		return CELL_PAMF_ERROR_INVALID_PAMF;
	}
	}

	return CELL_OK;
}

u32 cellPamfReaderGetNumberOfEp(vm::ptr<CellPamfReader> pSelf)
{
	cellPamf.notice("cellPamfReaderGetNumberOfEp(pSelf=*0x%x)", pSelf);

	ensure(!!pSelf); // Not checked on LLE

	return pSelf->isPsmf ? pSelf->psmf.currentStream->ep_num : pSelf->pamf.currentStream->ep_num;
}

error_code cellPamfReaderGetEpIteratorWithIndex(vm::ptr<CellPamfReader> pSelf, u32 epIndex, vm::ptr<CellPamfEpIterator> pIt)
{
	cellPamf.notice("cellPamfReaderGetEpIteratorWithIndex(pSelf=*0x%x, epIndex=%d, pIt=*0x%x)", pSelf, epIndex, pIt);

	ensure(!!pSelf && !!pIt); // Not checked on LLE

	const u32 ep_num = cellPamfReaderGetNumberOfEp(pSelf);

	if (epIndex >= ep_num)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	if (pSelf->attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
	{
		return CELL_PAMF_ERROR_NOT_AVAILABLE;
	}

	pIt->isPamf = !pSelf->isPsmf;
	pIt->index = epIndex;
	pIt->num = ep_num;
	pIt->pCur.set(pSelf->isPsmf ? pSelf->psmf.header.addr() + pSelf->psmf.currentStream->ep_offset + epIndex * sizeof(PsmfEpHeader)
		: pSelf->pamf.header.addr() + pSelf->pamf.currentStream->ep_offset + epIndex * sizeof(PamfEpHeader));

	return CELL_OK;
}

error_code cellPamfReaderGetEpIteratorWithTimeStamp(vm::ptr<CellPamfReader> pSelf, vm::ptr<CellCodecTimeStamp> pTimeStamp, vm::ptr<CellPamfEpIterator> pIt)
{
	cellPamf.notice("cellPamfReaderGetEpIteratorWithTimeStamp(pSelf=*0x%x, pTimeStamp=*0x%x, pIt=*0x%x)", pSelf, pTimeStamp, pIt);

	ensure(!!pSelf && !!pTimeStamp && !!pIt); // Not checked on LLE

	if (pSelf->attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
	{
		return CELL_PAMF_ERROR_NOT_AVAILABLE;
	}

	const u32 ep_num = cellPamfReaderGetNumberOfEp(pSelf);

	pIt->num = ep_num;
	pIt->isPamf = !pSelf->isPsmf;

	if (ep_num == 0)
	{
		return CELL_PAMF_ERROR_EP_NOT_FOUND;
	}

	u32 i = ep_num - 1;
	const u64 requested_time_stamp = std::bit_cast<be_t<u64>>(*pTimeStamp);

	if (pSelf->isPsmf)
	{
		const auto eps = vm::cptr<PsmfEpHeader>::make(pSelf->psmf.header.addr() + pSelf->psmf.currentStream->ep_offset);

		for (; i >= 1; i--) // always output eps[0] if no other suitable ep is found
		{
			const u64 time_stamp = (static_cast<u64>(eps[i].value0 & 1) << 32) | eps[i].pts_low;

			if (time_stamp <= requested_time_stamp)
			{
				break;
			}
		}

		pIt->pCur = eps + i;
	}
	else
	{
		const auto eps = vm::cptr<PamfEpHeader>::make(pSelf->pamf.header.addr() + pSelf->pamf.currentStream->ep_offset);

		for (; i >= 1; i--) // always output eps[0] if no other suitable ep is found
		{
			const u64 time_stamp = (static_cast<u64>(eps[i].pts_high) << 32) | eps[i].pts_low;

			if (time_stamp <= requested_time_stamp)
			{
				break;
			}
		}

		pIt->pCur = eps + i;
	}

	pIt->index = i;

	return CELL_OK;
}

error_code cellPamfEpIteratorGetEp(vm::ptr<CellPamfEpIterator> pIt, vm::ptr<CellPamfEp> pEp)
{
	cellPamf.notice("cellPamfEpIteratorGetEp(pIt=*0x%x, pEp=*0x%x)", pIt, pEp);

	// always returns CELL_OK

	ensure(!!pIt); // Not checked on LLE

	if (pIt->isPamf)
	{
		pamfEpUnpack(vm::static_ptr_cast<const PamfEpHeader>(pIt->pCur), pEp);
	}
	else
	{
		psmfEpUnpack(vm::static_ptr_cast<const PsmfEpHeader>(pIt->pCur), pEp);
	}

	return CELL_OK;
}

s32 cellPamfEpIteratorMove(vm::ptr<CellPamfEpIterator> pIt, s32 steps, vm::ptr<CellPamfEp> pEp)
{
	cellPamf.notice("cellPamfEpIteratorMove(pIt=*0x%x, steps=%d, pEp=*0x%x)", pIt, steps, pEp);

	ensure(!!pIt); // Not checked on LLE

	u32 new_index = pIt->index + steps;

	if (static_cast<s32>(new_index) < 0)
	{
		steps = -static_cast<s32>(pIt->index);
		new_index = 0;
	}
	else if (new_index >= pIt->num)
	{
		steps = (pIt->num - pIt->index) - 1;
		new_index = pIt->index + steps;
	}

	pIt->index = new_index;

	if (pIt->isPamf)
	{
		pIt->pCur = vm::static_ptr_cast<const PamfEpHeader>(pIt->pCur) + steps;

		if (pEp)
		{
			pamfEpUnpack(vm::static_ptr_cast<const PamfEpHeader>(pIt->pCur), pEp);
		}
	}
	else
	{
		pIt->pCur = vm::static_ptr_cast<const PsmfEpHeader>(pIt->pCur) + steps;

		if (pEp)
		{
			psmfEpUnpack(vm::static_ptr_cast<const PsmfEpHeader>(pIt->pCur), pEp);
		}
	}

	return steps;
}

error_code cellPamfReaderGetEpWithTimeStamp(vm::ptr<CellPamfReader> pSelf, vm::ptr<CellCodecTimeStamp> pTimeStamp, vm::ptr<CellPamfEpUnk> pEp, u32 unk)
{
	cellPamf.notice("cellPamfReaderGetEpWithTimeStamp(pSelf=*0x%x, pTimeStamp=*0x%x, pEp=*0x%x, unk=0x%x)", pSelf, pTimeStamp, pEp, unk);

	// This function is broken on LLE

	ensure(!!pSelf && !!pTimeStamp && !!pEp); // Not checked on LLE

	if (pSelf->attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
	{
		return CELL_PAMF_ERROR_NOT_AVAILABLE;
	}

	const u32 ep_num = cellPamfReaderGetNumberOfEp(pSelf);
	u64 next_rpn_offset = pSelf->dataSize;

	if (ep_num == 0)
	{
		return CELL_PAMF_ERROR_EP_NOT_FOUND;
	}

	u32 i = ep_num - 1;
	const u64 requested_time_stamp = std::bit_cast<be_t<u64>>(*pTimeStamp);

	if (pSelf->isPsmf)
	{
		const auto eps = vm::cptr<PsmfEpHeader>::make(pSelf->psmf.header.addr() + pSelf->psmf.currentStream->ep_offset);

		for (; i >= 1; i--) // always output eps[0] if no other suitable ep is found
		{
			const u64 time_stamp = (static_cast<u64>(eps[i].value0 & 1) << 32) | eps[i].pts_low;

			if (time_stamp <= requested_time_stamp)
			{
				break;
			}
		}

		// LLE doesn't write the result to pEp

		if (i < ep_num - 1)
		{
			next_rpn_offset = eps[i + 1].rpnOffset;
		}
	}
	else
	{
		const auto eps = vm::cptr<PamfEpHeader>::make(pSelf->pamf.header.addr() + pSelf->pamf.currentStream->ep_offset);

		for (; i >= 1; i--) // always output eps[0] if no other suitable ep is found
		{
			const u64 time_stamp = (static_cast<u64>(eps[i].pts_high) << 32) | eps[i].pts_low;

			if (time_stamp <= requested_time_stamp)
			{
				break;
			}
		}

		// LLE doesn't write the result to pEp

		if (i < ep_num - 1)
		{
			next_rpn_offset = eps[i + 1].rpnOffset;
		}
	}

	if (unk == sizeof(CellPamfEpUnk))
	{
		pEp->nextRpnOffset = next_rpn_offset;
	}

	return CELL_OK;
}

error_code cellPamfReaderGetEpWithIndex(vm::ptr<CellPamfReader> pSelf, u32 epIndex, vm::ptr<CellPamfEpUnk> pEp, u32 unk)
{
	cellPamf.notice("cellPamfReaderGetEpWithIndex(pSelf=*0x%x, epIndex=%d, pEp=*0x%x, unk=0x%x)", pSelf, epIndex, pEp, unk);

	ensure(!!pSelf && !!pEp); // Not checked on LLE

	const u32 ep_num = cellPamfReaderGetNumberOfEp(pSelf);
	u64 next_rpn_offset = pSelf->dataSize;

	if (epIndex >= ep_num)
	{
		return CELL_PAMF_ERROR_INVALID_ARG;
	}

	if (pSelf->attribute & CELL_PAMF_ATTRIBUTE_MINIMUM_HEADER)
	{
		return CELL_PAMF_ERROR_NOT_AVAILABLE;
	}

	if (pSelf->isPsmf)
	{
		const auto ep = vm::cptr<PsmfEpHeader>::make(pSelf->psmf.header.addr() + pSelf->psmf.currentStream->ep_offset + epIndex * sizeof(PsmfEpHeader));

		psmfEpUnpack(ep, pEp.ptr(&CellPamfEpUnk::ep));

		if (epIndex < ep_num - 1)
		{
			next_rpn_offset = ep[1].rpnOffset * 0x800ull;
		}
	}
	else
	{
		const auto ep = vm::cptr<PamfEpHeader>::make(pSelf->pamf.header.addr() + pSelf->pamf.currentStream->ep_offset + epIndex * sizeof(PamfEpHeader));

		pamfEpUnpack(ep, pEp.ptr(&CellPamfEpUnk::ep));

		if (epIndex < ep_num - 1)
		{
			next_rpn_offset = ep[1].rpnOffset * 0x800ull;
		}
	}

	if (unk == sizeof(CellPamfEpUnk))
	{
		pEp->nextRpnOffset = next_rpn_offset;
	}

	return CELL_OK;
}

DECLARE(ppu_module_manager::cellPamf)("cellPamf", []()
{
	REG_FUNC(cellPamf, cellPamfGetHeaderSize);
	REG_FUNC(cellPamf, cellPamfGetHeaderSize2);
	REG_FUNC(cellPamf, cellPamfGetStreamOffsetAndSize);
	REG_FUNC(cellPamf, cellPamfVerify);
	REG_FUNC(cellPamf, cellPamfReaderInitialize);
	REG_FUNC(cellPamf, cellPamfReaderGetPresentationStartTime);
	REG_FUNC(cellPamf, cellPamfReaderGetPresentationEndTime);
	REG_FUNC(cellPamf, cellPamfReaderGetMuxRateBound);
	REG_FUNC(cellPamf, cellPamfReaderGetNumberOfStreams);
	REG_FUNC(cellPamf, cellPamfReaderGetNumberOfSpecificStreams);
	REG_FUNC(cellPamf, cellPamfReaderSetStreamWithIndex);
	REG_FUNC(cellPamf, cellPamfReaderSetStreamWithTypeAndChannel);
	REG_FUNC(cellPamf, cellPamfReaderSetStreamWithTypeAndIndex);
	REG_FUNC(cellPamf, cellPamfStreamTypeToEsFilterId);
	REG_FUNC(cellPamf, cellPamfReaderGetStreamIndex);
	REG_FUNC(cellPamf, cellPamfReaderGetStreamTypeAndChannel);
	REG_FUNC(cellPamf, cellPamfReaderGetEsFilterId);
	REG_FUNC(cellPamf, cellPamfReaderGetStreamInfo);
	REG_FUNC(cellPamf, cellPamfReaderGetNumberOfEp);
	REG_FUNC(cellPamf, cellPamfReaderGetEpIteratorWithIndex);
	REG_FUNC(cellPamf, cellPamfReaderGetEpIteratorWithTimeStamp);
	REG_FUNC(cellPamf, cellPamfEpIteratorGetEp);
	REG_FUNC(cellPamf, cellPamfEpIteratorMove);
	REG_FUNC(cellPamf, cellPamfReaderGetEpWithTimeStamp);
	REG_FUNC(cellPamf, cellPamfReaderGetEpWithIndex);
});
