Merge branch 'main' into user_and_settings

This commit is contained in:
georgemoralis 2025-12-24 11:25:30 +02:00 committed by GitHub
commit b62c79675f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 265 additions and 162 deletions

View File

@ -160,7 +160,7 @@ jobs:
sudo add-apt-repository 'deb http://apt.llvm.org/noble/ llvm-toolchain-noble-19 main'
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang-19 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4
@ -216,7 +216,7 @@ jobs:
submodules: recursive
- name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 gcc-14 mold build-essential libasound2-dev libpulse-dev libopenal-dev libudev-dev libxcursor-dev libxi-dev libxss-dev libxtst-dev
- name: Cache CMake Configuration
uses: actions/cache@v4

View File

@ -202,8 +202,8 @@ execute_process(
# Set Version
set(EMULATOR_VERSION_MAJOR "0")
set(EMULATOR_VERSION_MINOR "12")
set(EMULATOR_VERSION_PATCH "6")
set(EMULATOR_VERSION_MINOR "13")
set(EMULATOR_VERSION_PATCH "1")
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")

View File

@ -37,6 +37,9 @@
<category translate="no">Game</category>
</categories>
<releases>
<release version="0.13.0" date="2025-12-24">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.13.0</url>
</release>
<release version="0.12.5" date="2025-11-07">
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.12.5</url>
</release>

@ -1 +1 @@
Subproject commit b0de1dcca26c0ebfb8011b8e59dd17fc399db0ff
Subproject commit 94dde08c8a9e4271a93a2a7e4159e9fb05d30c0a

2
externals/fmt vendored

@ -1 +1 @@
Subproject commit 64db979e38ec644b1798e41610b28c8d2c8a2739
Subproject commit ec73fb72477d80926c758894a3ab2cb3994fd051

2
externals/sdl3 vendored

@ -1 +1 @@
Subproject commit e9c2e9bfc3a6e1e70596f743fa9e1fc5fadabef7
Subproject commit bdb72bb3f051de32c91f5deb439a50bfd51499dc

View File

@ -790,11 +790,12 @@ static bool PatchesIllegalInstructionHandler(void* context) {
Common::Decoder::Instance()->decodeInstruction(instruction, operands, code_address);
if (ZYAN_SUCCESS(status) && instruction.mnemonic == ZydisMnemonic::ZYDIS_MNEMONIC_UD2)
[[unlikely]] {
UNREACHABLE_MSG("ud2 at code address {:#x}", (u64)code_address);
UNREACHABLE_MSG("ud2 at code address {:#x}", reinterpret_cast<u64>(code_address));
}
LOG_ERROR(Core, "Failed to patch address {:x} -- mnemonic: {}", (u64)code_address,
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
UNREACHABLE_MSG("Failed to patch address {:x} -- mnemonic: {}",
reinterpret_cast<u64>(code_address),
ZYAN_SUCCESS(status) ? ZydisMnemonicGetString(instruction.mnemonic)
: "Failed to decode");
}
}

View File

@ -52,6 +52,9 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
pos = corrected_path.find("//", pos + 1);
}
if (path.length() > 255)
return "";
const MntPair* mount = GetMount(corrected_path);
if (!mount) {
return "";

View File

@ -133,7 +133,7 @@ struct AjmSidebandGaplessDecode {
struct AjmSidebandResampleParameters {
float ratio;
uint32_t flags;
u32 flags;
};
struct AjmDecAt9InitializeParameters {

View File

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "ajm_result.h"
#include "common/assert.h"
#include "core/libraries/ajm/ajm_at9.h"
#include "error_codes.h"
@ -85,8 +86,8 @@ void AjmAt9Decoder::GetInfo(void* out_info) const {
auto* info = reinterpret_cast<AjmSidebandDecAt9CodecInfo*>(out_info);
info->super_frame_size = m_codec_info.superframeSize;
info->frames_in_super_frame = m_codec_info.framesInSuperframe;
info->next_frame_size = m_superframe_bytes_remain;
info->frame_samples = m_codec_info.frameSamples;
info->next_frame_size = static_cast<Atrac9Handle*>(m_handle)->Config.FrameBytes;
}
u8 g_at9_guid[] = {0xD2, 0x42, 0xE1, 0x47, 0xBA, 0x36, 0x8D, 0x4D,
@ -133,18 +134,22 @@ void AjmAt9Decoder::ParseRIFFHeader(std::span<u8>& in_buf, AjmInstanceGapless& g
}
}
std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
bool is_reset = false;
u32 AjmAt9Decoder::GetMinimumInputSize() const {
return m_superframe_bytes_remain;
}
DecoderResult AjmAt9Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
DecoderResult result{};
if (True(m_flags & AjmAt9CodecFlags::ParseRiffHeader) &&
*reinterpret_cast<u32*>(in_buf.data()) == 'FFIR') {
ParseRIFFHeader(in_buf, gapless);
is_reset = true;
result.is_reset = true;
}
if (!m_is_initialized) {
return {0, 0, is_reset};
result.result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
return result;
}
int ret = 0;
@ -166,7 +171,14 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
default:
UNREACHABLE();
}
ASSERT_MSG(ret == At9Status::ERR_SUCCESS, "Atrac9Decode failed ret = {:#x}", ret);
if (ret != At9Status::ERR_SUCCESS) {
LOG_ERROR(Lib_Ajm, "Atrac9Decode failed ret = {:#x}", ret);
result.result = ORBIS_AJM_RESULT_CODEC_ERROR | ORBIS_AJM_RESULT_FATAL;
result.internal_result = ret;
return result;
}
result.frames_decoded += 1;
in_buf = in_buf.subspan(bytes_used);
m_superframe_bytes_remain -= bytes_used;
@ -196,10 +208,10 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
UNREACHABLE();
}
const auto samples_written = pcm_written / m_codec_info.channels;
gapless.current.skipped_samples += m_codec_info.frameSamples - samples_written;
result.samples_written = pcm_written / m_codec_info.channels;
gapless.current.skipped_samples += m_codec_info.frameSamples - result.samples_written;
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples_written;
gapless.current.total_samples -= result.samples_written;
}
m_num_frames += 1;
@ -209,9 +221,23 @@ std::tuple<u32, u32, bool> AjmAt9Decoder::ProcessData(std::span<u8>& in_buf,
}
m_superframe_bytes_remain = m_codec_info.superframeSize;
m_num_frames = 0;
} else if (gapless.IsEnd()) {
// Drain the remaining superframe
std::vector<s16> buf(m_codec_info.frameSamples * m_codec_info.channels, 0);
while ((m_num_frames % m_codec_info.framesInSuperframe) != 0) {
ret = Atrac9Decode(m_handle, in_buf.data(), buf.data(), &bytes_used,
True(m_flags & AjmAt9CodecFlags::NonInterleavedOutput));
in_buf = in_buf.subspan(bytes_used);
m_superframe_bytes_remain -= bytes_used;
result.frames_decoded += 1;
m_num_frames += 1;
}
in_buf = in_buf.subspan(m_superframe_bytes_remain);
m_superframe_bytes_remain = m_codec_info.superframeSize;
m_num_frames = 0;
}
return {1, m_codec_info.frameSamples, is_reset};
return result;
}
AjmSidebandFormat AjmAt9Decoder::GetFormat() const {
@ -232,7 +258,7 @@ u32 AjmAt9Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto samples =
gapless.init.total_samples != 0
? std::min<u32>(gapless.current.total_samples, m_codec_info.frameSamples - skip_samples)
: m_codec_info.frameSamples;
: m_codec_info.frameSamples - skip_samples;
return samples * m_codec_info.channels * GetPCMSize(m_format);
}

View File

@ -36,9 +36,10 @@ struct AjmAt9Decoder final : AjmCodec {
void Initialize(const void* buffer, u32 buffer_size) override;
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
private:
template <class T>

View File

@ -52,6 +52,7 @@ struct AjmBatch {
u32 id{};
std::atomic_bool waiting{};
std::atomic_bool canceled{};
std::atomic_bool processed{};
std::binary_semaphore finished{0};
boost::container::small_vector<AjmJob, 16> jobs;

View File

@ -39,7 +39,12 @@ s32 AjmContext::BatchCancel(const u32 batch_id) {
batch = *p_batch;
}
batch->canceled = true;
if (batch->processed) {
return ORBIS_OK;
}
bool expected = false;
batch->canceled.compare_exchange_strong(expected, true);
return ORBIS_OK;
}
@ -58,7 +63,9 @@ void AjmContext::WorkerThread(std::stop_token stop) {
Common::SetCurrentThreadName("shadPS4:AjmWorker");
while (!stop.stop_requested()) {
auto batch = batch_queue.PopWait(stop);
if (batch != nullptr) {
if (batch != nullptr && !batch->canceled) {
bool expected = false;
batch->processed.compare_exchange_strong(expected, true);
ProcessBatch(batch->id, batch->jobs);
batch->finished.release();
}

View File

@ -1,27 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/libraries/ajm/ajm_at9.h"
#include "core/libraries/ajm/ajm_instance.h"
#include "core/libraries/ajm/ajm_mp3.h"
#include "ajm_at9.h"
#include "ajm_instance.h"
#include "ajm_mp3.h"
#include "ajm_result.h"
#include <magic_enum/magic_enum.hpp>
namespace Libraries::Ajm {
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;
u8 GetPCMSize(AjmFormatEncoding format) {
switch (format) {
case AjmFormatEncoding::S16:
@ -60,6 +48,7 @@ void AjmInstance::Reset() {
void AjmInstance::ExecuteJob(AjmJob& job) {
const auto control_flags = job.flags.control_flags;
job.output.p_result->result = 0;
if (True(control_flags & AjmJobControlFlags::Reset)) {
LOG_TRACE(Lib_Ajm, "Resetting instance {}", job.instance_id);
Reset();
@ -91,8 +80,7 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.total_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
return;
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
@ -106,61 +94,59 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
m_gapless.current.skip_samples -= sample_difference;
} else {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_INVALID_PARAMETER");
job.output.p_result->result = ORBIS_AJM_RESULT_INVALID_PARAMETER;
return;
job.output.p_result->result |= ORBIS_AJM_RESULT_INVALID_PARAMETER;
}
}
}
if (!job.input.buffer.empty() && !job.output.buffers.empty()) {
std::span<u8> in_buf(job.input.buffer);
SparseOutputBuffer out_buf(job.output.buffers);
std::span<u8> in_buf(job.input.buffer);
SparseOutputBuffer out_buf(job.output.buffers);
auto in_size = in_buf.size();
auto out_size = out_buf.Size();
u32 frames_decoded = 0;
u32 frames_decoded = 0;
auto in_size = in_buf.size();
auto out_size = out_buf.Size();
while (!in_buf.empty() && !out_buf.IsEmpty() && !m_gapless.IsEnd()) {
if (!HasEnoughSpace(out_buf)) {
if (job.output.p_mframe == nullptr || frames_decoded == 0) {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})",
out_buf.Size(), m_codec->GetNextFrameSize(m_gapless));
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
break;
}
}
const auto [nframes, nsamples, reset] =
m_codec->ProcessData(in_buf, out_buf, m_gapless);
if (reset) {
if (!job.input.buffer.empty()) {
for (;;) {
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
m_gapless.Reset();
m_total_samples = 0;
}
if (!nframes) {
LOG_WARNING(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_INITIALIZED");
job.output.p_result->result = ORBIS_AJM_RESULT_NOT_INITIALIZED;
if (!HasEnoughSpace(out_buf)) {
LOG_TRACE(Lib_Ajm, "ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM ({} < {})", out_buf.Size(),
m_codec->GetNextFrameSize(m_gapless));
job.output.p_result->result |= ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM;
}
if (in_buf.size() < m_codec->GetMinimumInputSize()) {
job.output.p_result->result |= ORBIS_AJM_RESULT_PARTIAL_INPUT;
}
if (job.output.p_result->result != 0) {
break;
}
const auto result = m_codec->ProcessData(in_buf, out_buf, m_gapless);
if (result.is_reset) {
m_total_samples = 0;
} else {
m_total_samples += result.samples_written;
}
frames_decoded += result.frames_decoded;
if (result.result != 0) {
job.output.p_result->result |= result.result;
job.output.p_result->internal_result = result.internal_result;
break;
}
frames_decoded += nframes;
m_total_samples += nsamples;
if (False(job.flags.run_flags & AjmJobRunFlags::MultipleFrames)) {
break;
}
}
}
const auto total_decoded_samples = m_total_samples;
if (m_flags.gapless_loop && m_gapless.IsEnd()) {
in_buf = in_buf.subspan(in_buf.size());
m_gapless.Reset();
m_codec->Reset();
}
if (job.output.p_mframe) {
job.output.p_mframe->num_frames = frames_decoded;
}
if (job.output.p_stream) {
job.output.p_stream->input_consumed = in_size - in_buf.size();
job.output.p_stream->output_written = out_size - out_buf.Size();
job.output.p_stream->total_decoded_samples = total_decoded_samples;
}
if (job.output.p_mframe) {
job.output.p_mframe->num_frames = frames_decoded;
}
if (job.output.p_stream) {
job.output.p_stream->input_consumed = in_size - in_buf.size();
job.output.p_stream->output_written = out_size - out_buf.Size();
job.output.p_stream->total_decoded_samples = m_total_samples;
}
if (job.output.p_format != nullptr) {
@ -175,6 +161,9 @@ void AjmInstance::ExecuteJob(AjmJob& job) {
}
bool AjmInstance::HasEnoughSpace(const SparseOutputBuffer& output) const {
if (m_gapless.IsEnd()) {
return true;
}
return output.Size() >= m_codec->GetNextFrameSize(m_gapless);
}

View File

@ -73,6 +73,14 @@ struct AjmInstanceGapless {
}
};
struct DecoderResult {
s32 result = 0;
s32 internal_result = 0;
u32 frames_decoded = 0;
u32 samples_written = 0;
bool is_reset = false;
};
class AjmCodec {
public:
virtual ~AjmCodec() = default;
@ -81,9 +89,10 @@ public:
virtual void Reset() = 0;
virtual void GetInfo(void* out_info) const = 0;
virtual AjmSidebandFormat GetFormat() const = 0;
virtual u32 GetMinimumInputSize() const = 0;
virtual u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const = 0;
virtual std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) = 0;
virtual DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) = 0;
};
class AjmInstance {
@ -94,7 +103,6 @@ public:
private:
bool HasEnoughSpace(const SparseOutputBuffer& output) const;
std::optional<u32> GetNumRemainingSamples() const;
void Reset();
AjmInstanceFlags m_flags{};

View File

@ -8,7 +8,7 @@ namespace Libraries::Ajm {
void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
if (job.output.p_engine) {
job.output.p_engine->usage_batch = 0.01;
job.output.p_engine->usage_batch = 0.05;
const auto ic = job.input.statistics_engine_parameters->interval_count;
for (u32 idx = 0; idx < ic; ++idx) {
job.output.p_engine->usage_interval[idx] = 0.01;
@ -25,10 +25,12 @@ void AjmInstanceStatistics::ExecuteJob(AjmJob& job) {
job.output.p_memory->batch_size = 0x4200;
job.output.p_memory->input_size = 0x2000;
job.output.p_memory->output_size = 0x2000;
job.output.p_memory->small_size = 0x200;
job.output.p_memory->small_size = 0x400;
}
}
void AjmInstanceStatistics::Reset() {}
AjmInstanceStatistics& AjmInstanceStatistics::Getinstance() {
static AjmInstanceStatistics instance;
return instance;

View File

@ -10,6 +10,7 @@ namespace Libraries::Ajm {
class AjmInstanceStatistics {
public:
void ExecuteJob(AjmJob& job);
void Reset();
static AjmInstanceStatistics& Getinstance();
};

View File

@ -122,6 +122,7 @@ void AjmMp3Decoder::Reset() {
avcodec_flush_buffers(m_codec_context);
m_header.reset();
m_frame_samples = 0;
m_frame_size = 0;
}
void AjmMp3Decoder::GetInfo(void* out_info) const {
@ -138,16 +139,28 @@ void AjmMp3Decoder::GetInfo(void* out_info) const {
}
}
std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
u32 AjmMp3Decoder::GetMinimumInputSize() const {
// 4 bytes is for mp3 header that contains frame_size
return std::max<u32>(m_frame_size, 4);
}
DecoderResult AjmMp3Decoder::ProcessData(std::span<u8>& in_buf, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) {
DecoderResult result{};
AVPacket* pkt = av_packet_alloc();
if ((!m_header.has_value() || m_frame_samples == 0) && in_buf.size() >= 4) {
m_header = std::byteswap(*reinterpret_cast<u32*>(in_buf.data()));
AjmDecMp3ParseFrame info{};
ParseMp3Header(in_buf.data(), in_buf.size(), false, &info);
ParseMp3Header(in_buf.data(), in_buf.size(), true, &info);
m_frame_samples = info.samples_per_channel;
m_frame_size = info.frame_size;
gapless.init = {
.total_samples = info.total_samples,
.skip_samples = static_cast<u16>(info.encoder_delay),
.skipped_samples = 0,
};
gapless.current = gapless.init;
}
int ret = av_parser_parse2(m_parser, m_codec_context, &pkt->data, &pkt->size, in_buf.data(),
@ -155,9 +168,6 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
ASSERT_MSG(ret >= 0, "Error while parsing {}", ret);
in_buf = in_buf.subspan(ret);
u32 frames_decoded = 0;
u32 samples_decoded = 0;
if (pkt->size) {
// Send the packet with the compressed data to the decoder
pkt->pts = m_parser->pts;
@ -177,9 +187,8 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
UNREACHABLE_MSG("Error during decoding");
}
frame = ConvertAudioFrame(frame);
samples_decoded += u32(frame->nb_samples);
frames_decoded += 1;
result.frames_decoded += 1;
u32 skip_samples = 0;
if (gapless.current.skip_samples > 0) {
skip_samples = std::min(u16(frame->nb_samples), gapless.current.skip_samples);
@ -211,6 +220,7 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
if (gapless.init.total_samples != 0) {
gapless.current.total_samples -= samples;
}
result.samples_written += samples;
av_frame_free(&frame);
}
@ -218,16 +228,16 @@ std::tuple<u32, u32, bool> AjmMp3Decoder::ProcessData(std::span<u8>& in_buf,
av_packet_free(&pkt);
return {frames_decoded, samples_decoded, false};
return result;
}
u32 AjmMp3Decoder::GetNextFrameSize(const AjmInstanceGapless& gapless) const {
const auto max_samples = gapless.init.total_samples != 0
? std::min(gapless.current.total_samples, m_frame_samples)
: m_frame_samples;
const auto skip_samples = std::min(u32(gapless.current.skip_samples), max_samples);
return (max_samples - skip_samples) * m_codec_context->ch_layout.nb_channels *
GetPCMSize(m_format);
const auto skip_samples = std::min<u32>(gapless.current.skip_samples, m_frame_samples);
const auto samples =
gapless.init.total_samples != 0
? std::min<u32>(gapless.current.total_samples, m_frame_samples - skip_samples)
: m_frame_samples - skip_samples;
return samples * m_codec_context->ch_layout.nb_channels * GetPCMSize(m_format);
}
class BitReader {
@ -264,7 +274,7 @@ private:
int AjmMp3Decoder::ParseMp3Header(const u8* p_begin, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame) {
LOG_INFO(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
LOG_TRACE(Lib_Ajm, "called stream_size = {} parse_ofl = {}", stream_size, parse_ofl);
if (p_begin == nullptr || stream_size < 4 || frame == nullptr) {
return ORBIS_AJM_ERROR_INVALID_PARAMETER;

View File

@ -70,9 +70,10 @@ public:
void Initialize(const void* buffer, u32 buffer_size) override {}
void GetInfo(void* out_info) const override;
AjmSidebandFormat GetFormat() const override;
u32 GetMinimumInputSize() const override;
u32 GetNextFrameSize(const AjmInstanceGapless& gapless) const override;
std::tuple<u32, u32, bool> ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
DecoderResult ProcessData(std::span<u8>& input, SparseOutputBuffer& output,
AjmInstanceGapless& gapless) override;
static int ParseMp3Header(const u8* buf, u32 stream_size, int parse_ofl,
AjmDecMp3ParseFrame* frame);
@ -97,6 +98,7 @@ private:
SwrContext* m_swr_context = nullptr;
std::optional<u32> m_header;
u32 m_frame_samples = 0;
u32 m_frame_size = 0;
};
} // namespace Libraries::Ajm

View File

@ -0,0 +1,17 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
constexpr int ORBIS_AJM_RESULT_NOT_INITIALIZED = 0x00000001;
constexpr int ORBIS_AJM_RESULT_INVALID_DATA = 0x00000002;
constexpr int ORBIS_AJM_RESULT_INVALID_PARAMETER = 0x00000004;
constexpr int ORBIS_AJM_RESULT_PARTIAL_INPUT = 0x00000008;
constexpr int ORBIS_AJM_RESULT_NOT_ENOUGH_ROOM = 0x00000010;
constexpr int ORBIS_AJM_RESULT_STREAM_CHANGE = 0x00000020;
constexpr int ORBIS_AJM_RESULT_TOO_MANY_CHANNELS = 0x00000040;
constexpr int ORBIS_AJM_RESULT_UNSUPPORTED_FLAG = 0x00000080;
constexpr int ORBIS_AJM_RESULT_SIDEBAND_TRUNCATED = 0x00000100;
constexpr int ORBIS_AJM_RESULT_PRIORITY_PASSED = 0x00000200;
constexpr int ORBIS_AJM_RESULT_CODEC_ERROR = 0x40000000;
constexpr int ORBIS_AJM_RESULT_FATAL = 0x80000000;

View File

@ -37,6 +37,7 @@
#endif
namespace D = Core::Devices;
namespace fs = std::filesystem;
using FactoryDevice = std::function<std::shared_ptr<D::BaseDevice>(u32, const char*, int, u16)>;
#define GET_DEVICE_FD(fd) \
@ -74,6 +75,7 @@ namespace Libraries::Kernel {
s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} flags = {:#x} mode = {:#o}", raw_path, flags, mode);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
@ -87,6 +89,11 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
if (strlen(raw_path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
bool nonblock = (flags & ORBIS_KERNEL_O_NONBLOCK) != 0;
bool append = (flags & ORBIS_KERNEL_O_APPEND) != 0;
// Flags fsync and sync behave the same
@ -121,7 +128,7 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
bool read_only = false;
file->m_guest_name = path;
file->m_host_name = mnt->GetHostPath(file->m_guest_name, &read_only);
bool exists = std::filesystem::exists(file->m_host_name);
bool exists = fs::exists(file->m_host_name);
s32 e = 0;
if (create) {
@ -149,14 +156,14 @@ s32 PS4_SYSV_ABI open(const char* raw_path, s32 flags, u16 mode) {
return -1;
}
if (std::filesystem::is_directory(file->m_host_name) || directory) {
if (fs::is_directory(file->m_host_name) || directory) {
// Directories can be opened even if the directory flag isn't set.
// In these cases, error behavior is identical to the directory code path.
directory = true;
}
if (directory) {
if (!std::filesystem::is_directory(file->m_host_name)) {
if (!fs::is_directory(file->m_host_name)) {
// If the opened file is not a directory, return ENOTDIR.
// This will trigger when create & directory is specified, this is expected.
h->DeleteHandle(handle);
@ -554,6 +561,10 @@ s64 PS4_SYSV_ABI sceKernelRead(s32 fd, void* buf, u64 nbytes) {
s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
LOG_INFO(Kernel_Fs, "path = {} mode = {:#o}", path, mode);
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (path == nullptr) {
*__Error() = POSIX_ENOTDIR;
return -1;
@ -563,7 +574,7 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
bool ro = false;
const auto dir_name = mnt->GetHostPath(path, &ro);
if (std::filesystem::exists(dir_name)) {
if (fs::exists(dir_name)) {
*__Error() = POSIX_EEXIST;
return -1;
}
@ -575,12 +586,12 @@ s32 PS4_SYSV_ABI posix_mkdir(const char* path, u16 mode) {
// CUSA02456: path = /aotl after sceSaveDataMount(mode = 1)
std::error_code ec;
if (dir_name.empty() || !std::filesystem::create_directory(dir_name, ec)) {
if (dir_name.empty() || !fs::create_directory(dir_name, ec)) {
*__Error() = POSIX_EIO;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@ -597,28 +608,32 @@ s32 PS4_SYSV_ABI sceKernelMkdir(const char* path, u16 mode) {
}
s32 PS4_SYSV_ABI posix_rmdir(const char* path) {
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const std::filesystem::path dir_name = mnt->GetHostPath(path, &ro);
const fs::path dir_name = mnt->GetHostPath(path, &ro);
if (ro) {
*__Error() = POSIX_EROFS;
return -1;
}
if (dir_name.empty() || !std::filesystem::is_directory(dir_name)) {
if (dir_name.empty() || !fs::is_directory(dir_name)) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!std::filesystem::exists(dir_name)) {
if (!fs::exists(dir_name)) {
*__Error() = POSIX_ENOENT;
return -1;
}
std::error_code ec;
s32 result = std::filesystem::remove_all(dir_name, ec);
s32 result = fs::remove_all(dir_name, ec);
if (ec) {
*__Error() = POSIX_EIO;
@ -638,11 +653,15 @@ s32 PS4_SYSV_ABI sceKernelRmdir(const char* path) {
s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
LOG_DEBUG(Kernel_Fs, "(PARTIAL) path = {}", path);
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
const auto path_name = mnt->GetHostPath(path);
std::memset(sb, 0, sizeof(OrbisKernelStat));
const bool is_dir = std::filesystem::is_directory(path_name);
const bool is_file = std::filesystem::is_regular_file(path_name);
const bool is_dir = fs::is_directory(path_name);
const bool is_file = fs::is_regular_file(path_name);
if (!is_dir && !is_file) {
*__Error() = POSIX_ENOENT;
return -1;
@ -650,12 +669,12 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
// get the difference between file clock and system clock
const auto now_sys = std::chrono::system_clock::now();
const auto now_file = std::filesystem::file_time_type::clock::now();
const auto now_file = fs::file_time_type::clock::now();
// calculate the file modified time
const auto mtime = std::filesystem::last_write_time(path_name);
const auto mtime = fs::last_write_time(path_name);
const auto mtimestamp = now_sys + (mtime - now_file);
if (std::filesystem::is_directory(path_name)) {
if (fs::is_directory(path_name)) {
sb->st_mode = 0000777u | 0040000u;
sb->st_size = 65536;
sb->st_blksize = 65536;
@ -665,7 +684,7 @@ s32 PS4_SYSV_ABI posix_stat(const char* path, OrbisKernelStat* sb) {
// TODO incomplete
} else {
sb->st_mode = 0000777u | 0100000u;
sb->st_size = static_cast<s64>(std::filesystem::file_size(path_name));
sb->st_size = static_cast<s64>(fs::file_size(path_name));
sb->st_blksize = 512;
sb->st_blocks = (sb->st_size + 511) / 512;
sb->st_mtim.tv_sec =
@ -686,6 +705,10 @@ s32 PS4_SYSV_ABI sceKernelStat(const char* path, OrbisKernelStat* sb) {
}
s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
if (strlen(path) > 255) {
return ORBIS_KERNEL_ERROR_ENAMETOOLONG;
}
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
std::string_view guest_path{path};
for (const auto& prefix : available_device | std::views::keys) {
@ -694,7 +717,7 @@ s32 PS4_SYSV_ABI sceKernelCheckReachability(const char* path) {
}
}
const auto path_name = mnt->GetHostPath(guest_path);
if (!std::filesystem::exists(path_name)) {
if (!fs::exists(path_name)) {
return ORBIS_KERNEL_ERROR_ENOENT;
}
return ORBIS_OK;
@ -807,7 +830,15 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
auto* mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
bool ro = false;
const auto src_path = mnt->GetHostPath(from, &ro);
if (!std::filesystem::exists(src_path)) {
if (strlen(from) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (strlen(to) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (!fs::exists(src_path)) {
*__Error() = POSIX_ENOENT;
return -1;
}
@ -820,32 +851,36 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
*__Error() = POSIX_EROFS;
return -1;
}
const bool src_is_dir = std::filesystem::is_directory(src_path);
const bool dst_is_dir = std::filesystem::is_directory(dst_path);
if (src_is_dir && !dst_is_dir) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!src_is_dir && dst_is_dir) {
*__Error() = POSIX_EISDIR;
return -1;
}
if (dst_is_dir && !std::filesystem::is_empty(dst_path)) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
const bool src_is_dir = fs::is_directory(src_path);
const bool dst_is_dir = fs::is_directory(dst_path);
if (fs::exists(dst_path)) {
if (src_is_dir && !dst_is_dir) {
*__Error() = POSIX_ENOTDIR;
return -1;
}
if (!src_is_dir && dst_is_dir) {
*__Error() = POSIX_EISDIR;
return -1;
}
if (dst_is_dir && !fs::is_empty(dst_path)) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
}
}
// On Windows, std::filesystem::rename will error if the file has been opened before.
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
// On Windows, fs::rename will error if the file has been opened before.
fs::copy(src_path, dst_path,
fs::copy_options::overwrite_existing | fs::copy_options::recursive);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(src_path);
if (file) {
auto access_mode = file->f.GetAccessMode();
file->f.Close();
std::filesystem::remove(src_path);
fs::remove(src_path);
file->f.Open(dst_path, access_mode);
} else {
std::filesystem::remove(src_path);
fs::remove_all(src_path);
}
return ORBIS_OK;
@ -1098,6 +1133,10 @@ s64 PS4_SYSV_ABI sceKernelPwritev(s32 fd, const OrbisKernelIovec* iov, s32 iovcn
}
s32 PS4_SYSV_ABI posix_unlink(const char* path) {
if (strlen(path) > 255) {
*__Error() = POSIX_ENAMETOOLONG;
return -1;
}
if (path == nullptr) {
*__Error() = POSIX_EINVAL;
return -1;
@ -1118,7 +1157,7 @@ s32 PS4_SYSV_ABI posix_unlink(const char* path) {
return -1;
}
if (std::filesystem::is_directory(host_path)) {
if (fs::is_directory(host_path)) {
*__Error() = POSIX_EPERM;
return -1;
}
@ -1491,6 +1530,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("FCcmRZhWtOk", "libkernel", 1, "libkernel", posix_pwritev);
LIB_FUNCTION("nKWi-N2HBV4", "libkernel", 1, "libkernel", sceKernelPwrite);
LIB_FUNCTION("mBd4AfLP+u8", "libkernel", 1, "libkernel", sceKernelPwritev);
LIB_FUNCTION("VAzswvTOCzI", "libkernel", 1, "libkernel", posix_unlink);
LIB_FUNCTION("AUXVxWeJU-A", "libkernel", 1, "libkernel", sceKernelUnlink);
LIB_FUNCTION("T8fER+tIGgk", "libScePosix", 1, "libkernel", posix_select);
LIB_FUNCTION("T8fER+tIGgk", "libkernel", 1, "libkernel", posix_select);

View File

@ -1357,7 +1357,8 @@ int PS4_SYSV_ABI sceNetResolverConnectDestroy() {
}
int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", name, poolid, flags);
const char* safe_name = name ? name : "";
LOG_INFO(Lib_Net, "name = {}, poolid = {}, flags = {}", safe_name, poolid, flags);
if (flags != 0) {
*sceNetErrnoLoc() = ORBIS_NET_EINVAL;
@ -1368,8 +1369,8 @@ int PS4_SYSV_ABI sceNetResolverCreate(const char* name, int poolid, int flags) {
auto* resolver = FDTable::Instance()->GetFile(fd);
resolver->is_opened = true;
resolver->type = Core::FileSys::FileType::Resolver;
resolver->resolver = std::make_shared<Resolver>(name, poolid, flags);
resolver->m_guest_name = name;
resolver->resolver = std::make_shared<Resolver>(safe_name, poolid, flags);
resolver->m_guest_name = safe_name;
return fd;
}

View File

@ -105,17 +105,6 @@ void Linker::Execute(const std::vector<std::string>& args) {
memory->SetupMemoryRegions(fmem_size, use_extended_mem1, use_extended_mem2);
// Simulate sceKernelInternalMemory mapping, a mapping usually performed during libkernel init.
// Due to the large size of this mapping, failing to emulate it causes issues in some titles.
// This mapping belongs in the system reserved area, which starts at address 0x880000000.
static constexpr VAddr KernelAllocBase = 0x880000000ULL;
static constexpr s64 InternalMemorySize = 0x1000000;
void* addr_out{reinterpret_cast<void*>(KernelAllocBase)};
s32 ret = Libraries::Kernel::sceKernelMapNamedFlexibleMemory(&addr_out, InternalMemorySize, 3,
0, "SceKernelInternalMemory");
ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
main_thread.Run([this, module, &args](std::stop_token) {
Common::SetCurrentThreadName("GAME_MainThread");
if (auto& ipc = IPC::Instance()) {

View File

@ -299,6 +299,7 @@ bool Instance::CreateDevice() {
amd_shader_trinary_minmax = add_extension(VK_AMD_SHADER_TRINARY_MINMAX_EXTENSION_NAME);
nv_framebuffer_mixed_samples = add_extension(VK_NV_FRAMEBUFFER_MIXED_SAMPLES_EXTENSION_NAME);
amd_mixed_attachment_samples = add_extension(VK_AMD_MIXED_ATTACHMENT_SAMPLES_EXTENSION_NAME);
shader_atomic_float = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_EXTENSION_NAME);
shader_atomic_float2 = add_extension(VK_EXT_SHADER_ATOMIC_FLOAT_2_EXTENSION_NAME);
if (shader_atomic_float2) {
shader_atomic_float2_features =

View File

@ -493,6 +493,7 @@ private:
bool amd_shader_trinary_minmax{};
bool nv_framebuffer_mixed_samples{};
bool amd_mixed_attachment_samples{};
bool shader_atomic_float{};
bool shader_atomic_float2{};
bool workgroup_memory_explicit_layout{};
bool portability_subset{};