mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-23 20:36:58 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9bc9c84312 | |||
| d3eaafab61 | |||
| e568555e8b | |||
| 9b599ca92c | |||
| 025b39c018 | |||
| 7c8583d204 | |||
| 9229c04ebf | |||
| 1b6af301b5 |
@@ -490,6 +490,9 @@ add_executable(save_data_memory_tests EXCLUDE_FROM_ALL
|
||||
target_link_libraries(save_data_memory_tests common fmt::fmt)
|
||||
target_include_directories(save_data_memory_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(http_uri_parse_tests
|
||||
"${KYTY_TESTS_DIR}/HttpUriParseTests.cpp")
|
||||
|
||||
add_executable(event_queue_lifetime_tests EXCLUDE_FROM_ALL
|
||||
"${KYTY_TESTS_DIR}/EventQueueLifetimeTests.cpp"
|
||||
"${KYTY_SOURCE_DIR}/kernel/eventQueue.cpp"
|
||||
@@ -578,6 +581,7 @@ if(BUILD_TESTING)
|
||||
add_test(NAME ngs2_sampler COMMAND $<TARGET_FILE:ngs2_sampler_tests>)
|
||||
add_test(NAME pad_haptics COMMAND $<TARGET_FILE:pad_haptics_tests>)
|
||||
add_test(NAME save_data_memory COMMAND $<TARGET_FILE:save_data_memory_tests>)
|
||||
add_test(NAME http_uri_parse COMMAND $<TARGET_FILE:http_uri_parse_tests>)
|
||||
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
|
||||
add_test(NAME shader_recompiler_alignbyte
|
||||
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
|
||||
@@ -646,6 +650,7 @@ if(BUILD_TESTING)
|
||||
ngs2_sampler_tests
|
||||
pad_haptics_tests
|
||||
save_data_memory_tests
|
||||
http_uri_parse_tests
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
[](#current-status)
|
||||
[](LICENSE)
|
||||
|
||||
**[Weekly updates](https://github.com/KytyPS5/KytyPS5/discussions/862)** — game progress, recent fixes and ongoing development.
|
||||
|
||||
**[Development on Discord](https://discord.gg/UNrkMqGaBg)** — KytyPS5 development.
|
||||
|
||||
KytyPS5 is a free and open-source PlayStation 5 emulator written in C++ for Windows and Linux,
|
||||
with experimental macOS support. It is based on a heavily modified version of
|
||||
[Kyty](https://github.com/InoriRus/Kyty). The project is in active development, and behavior
|
||||
|
||||
+3
-5
@@ -55,11 +55,9 @@ vk::Sampler SamplerCache::GetSampler(const ShaderSamplerResource& r) {
|
||||
case Prospero::SamplerAnisoRatio::kTwo: aniso_ratio = 2.0f; break;
|
||||
case Prospero::SamplerAnisoRatio::kFour: aniso_ratio = 4.0f; break;
|
||||
case Prospero::SamplerAnisoRatio::kEight: aniso_ratio = 8.0f; break;
|
||||
case Prospero::SamplerAnisoRatio::kSixteen: aniso_ratio = 16.0f; break;
|
||||
default:
|
||||
EXIT("unknown ratio: %d dwords=%08x,%08x,%08x,%08x\n",
|
||||
static_cast<int>(r.MaxAnisoRatio()), r.fields[0], r.fields[1], r.fields[2],
|
||||
r.fields[3]);
|
||||
case Prospero::SamplerAnisoRatio::kSixteen:
|
||||
// Reserved encodings 5-7 fall back to the maximum ratio.
|
||||
default: aniso_ratio = 16.0f; break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+2
-141
@@ -5,6 +5,7 @@
|
||||
#include "libs/ajm/atrac9_decoder.h"
|
||||
#include "libs/ajm/decoder.h"
|
||||
#include "libs/ajm/mp3_decoder.h"
|
||||
#include "libs/ajm/opus_decoder.h"
|
||||
#include "libs/audio.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -75,16 +76,6 @@ struct AjmGetStatisticsResult {
|
||||
uint32_t memory[6];
|
||||
};
|
||||
|
||||
struct AjmDecOpusInitializeParameters {
|
||||
uint32_t channel_num;
|
||||
uint32_t sample_rate;
|
||||
uint32_t mapping_family;
|
||||
};
|
||||
|
||||
struct AjmSidebandDecOpusCodecInfo {
|
||||
uint32_t frames_per_packet;
|
||||
};
|
||||
|
||||
enum class AjmCodec : uint32_t {
|
||||
DecMp3 = 0,
|
||||
DecAt9 = 1,
|
||||
@@ -120,8 +111,6 @@ constexpr uint64_t AJM_FLAG_SIDEBAND_STREAM = 1ull << 47u;
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_FORMAT_OFFSET = 7u;
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_FORMAT_MASK = 0x7u;
|
||||
constexpr uint64_t AJM_INSTANCE_FLAG_MAX_CHANNEL_MASK = 0x7fu;
|
||||
constexpr uint32_t AJM_DEC_OPUS_MAX_CHANNELS_FOR_10CH = 10;
|
||||
constexpr uint32_t AJM_DEC_OPUS_FRAME_SAMPLES = 960;
|
||||
|
||||
static std::atomic_uint32_t g_ajm_next_instance {1};
|
||||
static std::atomic_uint32_t g_ajm_next_batch {1};
|
||||
@@ -196,134 +185,6 @@ int KYTY_SYSV_ABI AjmDecAt9ParseConfigData(const void* config_data,
|
||||
return OK;
|
||||
}
|
||||
|
||||
class AjmOpusDecoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmOpusDecoder(uint32_t channels, uint32_t sample_rate, AjmSampleEncoding encoding,
|
||||
uint64_t flags)
|
||||
: AjmDecoder(channels, sample_rate, encoding) {
|
||||
(void)flags;
|
||||
}
|
||||
|
||||
AjmDecodeResult Initialize(const void* codec_parameters,
|
||||
size_t codec_parameters_size) override {
|
||||
auto result = MakeResult();
|
||||
|
||||
if (codec_parameters == nullptr ||
|
||||
codec_parameters_size < sizeof(AjmDecOpusInitializeParameters)) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* params = static_cast<const AjmDecOpusInitializeParameters*>(codec_parameters);
|
||||
if (params->channel_num == 0 || params->channel_num > AJM_DEC_OPUS_MAX_CHANNELS_FOR_10CH ||
|
||||
params->sample_rate == 0) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER;
|
||||
return result;
|
||||
}
|
||||
|
||||
SetFormat(params->channel_num, params->sample_rate, m_sample_encoding);
|
||||
m_mapping_family = params->mapping_family;
|
||||
m_total_decoded_samples = 0;
|
||||
m_is_initialized = true;
|
||||
m_frames_per_packet = 1;
|
||||
|
||||
LOGF("AJM Opus initialized: %" PRIu32 " Hz, %" PRIu32 " ch, mapping=%" PRIu32 "\n",
|
||||
params->sample_rate, params->channel_num, params->mapping_family);
|
||||
|
||||
return MakeResult();
|
||||
}
|
||||
|
||||
void Reset() override { m_total_decoded_samples = 0; }
|
||||
|
||||
AjmDecodeResult Decode(const void* input, size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) override {
|
||||
(void)multiple_frames;
|
||||
auto result = MakeResult();
|
||||
|
||||
if (input == nullptr || input_size == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
if (!m_is_initialized) {
|
||||
result.result = AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
|
||||
auto samples_to_write = AJM_DEC_OPUS_FRAME_SAMPLES;
|
||||
auto skip_samples = 0u;
|
||||
if (gapless != nullptr) {
|
||||
skip_samples = std::min<uint32_t>(samples_to_write, gapless->current.skip_samples);
|
||||
samples_to_write -= skip_samples;
|
||||
if (gapless->HasSampleLimit()) {
|
||||
samples_to_write =
|
||||
std::min<uint32_t>(samples_to_write, gapless->current.total_samples);
|
||||
}
|
||||
}
|
||||
|
||||
const auto output_bytes = static_cast<size_t>(samples_to_write) *
|
||||
static_cast<size_t>(m_channels) *
|
||||
AjmBytesPerSample(m_sample_encoding);
|
||||
if (output_bytes != 0 && output == nullptr) {
|
||||
result.result = AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
return result;
|
||||
}
|
||||
if (output_bytes > output_size) {
|
||||
result.result = AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (output_bytes != 0) {
|
||||
std::memset(output, 0, output_bytes);
|
||||
}
|
||||
|
||||
if (gapless != nullptr) {
|
||||
gapless->current.skip_samples =
|
||||
static_cast<uint16_t>(gapless->current.skip_samples - skip_samples);
|
||||
if (gapless->HasSampleLimit()) {
|
||||
gapless->current.total_samples =
|
||||
(gapless->current.total_samples > samples_to_write
|
||||
? gapless->current.total_samples - samples_to_write
|
||||
: 0);
|
||||
}
|
||||
|
||||
const auto skipped_samples = AJM_DEC_OPUS_FRAME_SAMPLES - samples_to_write;
|
||||
const auto skipped_total = std::min<uint32_t>(
|
||||
std::numeric_limits<uint16_t>::max(),
|
||||
static_cast<uint32_t>(gapless->current.skipped_samples) + skipped_samples);
|
||||
gapless->current.skipped_samples = static_cast<uint16_t>(skipped_total);
|
||||
}
|
||||
|
||||
result.input_consumed = input_size;
|
||||
result.output_written = output_bytes;
|
||||
result.frames = 1;
|
||||
result.frames_per_packet = m_frames_per_packet;
|
||||
m_total_decoded_samples += samples_to_write;
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
result.format = GetFormat();
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteCodecInfo(void* output, size_t output_size,
|
||||
const AjmDecodeResult& result) const override {
|
||||
(void)result;
|
||||
if (output == nullptr || output_size < sizeof(AjmSidebandDecOpusCodecInfo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* info = static_cast<AjmSidebandDecOpusCodecInfo*>(output);
|
||||
info->frames_per_packet = m_frames_per_packet;
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CodecInfoSize() const override {
|
||||
return sizeof(AjmSidebandDecOpusCodecInfo);
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_is_initialized = false;
|
||||
uint32_t m_mapping_family = 0;
|
||||
uint32_t m_frames_per_packet = 1;
|
||||
};
|
||||
|
||||
struct AjmInstanceState {
|
||||
uint32_t context = 0;
|
||||
uint32_t codec = 0;
|
||||
@@ -376,7 +237,7 @@ static std::unique_ptr<AjmDecoder> AjmCreateDecoder(uint32_t codec, uint64_t fla
|
||||
case static_cast<uint32_t>(AjmCodec::DecM4aac):
|
||||
return std::make_unique<AjmAacDecoder>(channels, 48000, encoding, flags);
|
||||
case static_cast<uint32_t>(AjmCodec::DecOpus):
|
||||
return std::make_unique<AjmOpusDecoder>(channels, 48000, encoding, flags);
|
||||
return std::make_unique<AjmOpusDecoder>(channels, encoding);
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
|
||||
#include "libs/ajm/decoder.h"
|
||||
#include "libs/ajm/ffmpeg_decoder_common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace Libs::Audio::Ajm {
|
||||
|
||||
struct AjmDecOpusInitializeParameters {
|
||||
uint32_t channel_num;
|
||||
uint32_t sample_rate;
|
||||
uint32_t mapping_family;
|
||||
};
|
||||
|
||||
struct AjmSidebandDecOpusCodecInfo {
|
||||
uint32_t frames_per_packet;
|
||||
};
|
||||
|
||||
static_assert(sizeof(AjmDecOpusInitializeParameters) == 12);
|
||||
static_assert(sizeof(AjmSidebandDecOpusCodecInfo) == 4);
|
||||
|
||||
class AjmOpusDecoder final: public AjmDecoder {
|
||||
public:
|
||||
AjmOpusDecoder(uint32_t max_channels, AjmSampleEncoding encoding)
|
||||
: AjmDecoder(0, 48000, encoding), m_max_channels(max_channels) {}
|
||||
|
||||
~AjmOpusDecoder() override { avcodec_free_context(&m_context); }
|
||||
|
||||
AjmDecodeResult Initialize(const void* parameters, size_t parameters_size) override {
|
||||
avcodec_free_context(&m_context);
|
||||
Reset();
|
||||
auto result = MakeResult();
|
||||
if (parameters == nullptr || parameters_size < sizeof(AjmDecOpusInitializeParameters)) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
const auto& params = *static_cast<const AjmDecOpusInitializeParameters*>(parameters);
|
||||
if (params.channel_num == 0 || params.channel_num > 2 || params.sample_rate != 48000 ||
|
||||
params.mapping_family != 0) {
|
||||
result.result = AJM_RESULT_INVALID_PARAMETER | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
if (params.channel_num > m_max_channels) {
|
||||
result.result = AJM_RESULT_TOO_MANY_CHANNELS | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto* codec = avcodec_find_decoder(AV_CODEC_ID_OPUS);
|
||||
m_context = codec != nullptr ? avcodec_alloc_context3(codec) : nullptr;
|
||||
if (m_context != nullptr) {
|
||||
av_channel_layout_default(&m_context->ch_layout, static_cast<int>(params.channel_num));
|
||||
m_context->sample_rate = static_cast<int>(params.sample_rate);
|
||||
if (avcodec_open2(m_context, codec, nullptr) < 0) {
|
||||
avcodec_free_context(&m_context);
|
||||
}
|
||||
}
|
||||
if (m_context == nullptr) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return result;
|
||||
}
|
||||
SetFormat(params.channel_num, params.sample_rate, m_sample_encoding);
|
||||
return MakeResult();
|
||||
}
|
||||
|
||||
void Reset() override {
|
||||
if (m_context != nullptr) {
|
||||
avcodec_flush_buffers(m_context);
|
||||
}
|
||||
m_total_decoded_samples = 0;
|
||||
m_frames_per_packet = 0;
|
||||
}
|
||||
|
||||
AjmDecodeResult Decode(const void* input, size_t input_size, void* output, size_t output_size,
|
||||
bool multiple_frames, AjmGaplessState* gapless) override {
|
||||
auto result = MakeResult();
|
||||
if (m_context == nullptr) {
|
||||
result.result = AJM_RESULT_NOT_INITIALIZED;
|
||||
return result;
|
||||
}
|
||||
if (input == nullptr || input_size == 0) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
return result;
|
||||
}
|
||||
|
||||
while (gapless == nullptr || !gapless->IsEnd()) {
|
||||
const auto* data = static_cast<const uint8_t*>(input) + result.input_consumed;
|
||||
const auto remaining = input_size - result.input_consumed;
|
||||
if (remaining < 2) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
break;
|
||||
}
|
||||
const auto packet_size = static_cast<uint32_t>(data[0]) | (uint32_t {data[1]} << 8u);
|
||||
if (packet_size > remaining - 2) {
|
||||
result.result = AJM_RESULT_PARTIAL_INPUT;
|
||||
break;
|
||||
}
|
||||
data += 2;
|
||||
if (packet_size == 0 || ((data[0] & 3u) == 3 && packet_size < 2)) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result.internal_result = 0x1043;
|
||||
break;
|
||||
}
|
||||
const uint32_t config = data[0] >> 3u;
|
||||
const uint32_t frames = (data[0] & 3u) == 0 ? 1
|
||||
: (data[0] & 3u) == 3 ? data[1] & 0x3fu
|
||||
: 2;
|
||||
const uint32_t frame_samples = config >= 16 ? 120u << (config & 3u)
|
||||
: config >= 12 ? 480u << (config & 1u)
|
||||
: (config & 3u) == 3 ? 2880u
|
||||
: 480u << (config & 3u);
|
||||
const uint32_t samples = frame_samples * frames;
|
||||
if (frames == 0 || frames > 6 || frame_samples > 960) {
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result.internal_result = static_cast<int32_t>(0xffff8800u);
|
||||
break;
|
||||
}
|
||||
const auto skip = gapless != nullptr
|
||||
? std::min(samples, uint32_t {gapless->current.skip_samples})
|
||||
: 0;
|
||||
const auto write = gapless != nullptr && gapless->HasSampleLimit()
|
||||
? std::min(samples - skip, gapless->current.total_samples)
|
||||
: samples - skip;
|
||||
const auto sample_bytes = m_channels * AjmBytesPerSample(m_sample_encoding);
|
||||
const auto output_bytes = write * sample_bytes;
|
||||
if (output_bytes > output_size - result.output_written ||
|
||||
(output_bytes != 0 && output == nullptr)) {
|
||||
result.result = AJM_RESULT_NOT_ENOUGH_ROOM;
|
||||
break;
|
||||
}
|
||||
|
||||
AVFrame* frame = DecodePacket(data, packet_size, &result);
|
||||
if (frame == nullptr) {
|
||||
break;
|
||||
}
|
||||
if (frame->nb_samples != static_cast<int>(samples) ||
|
||||
frame->ch_layout.nb_channels != static_cast<int>(m_channels)) {
|
||||
av_frame_free(&frame);
|
||||
result.result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result.internal_result = 0x1043;
|
||||
break;
|
||||
}
|
||||
if (output_bytes != 0) {
|
||||
std::memcpy(static_cast<uint8_t*>(output) + result.output_written,
|
||||
frame->data[0] + skip * sample_bytes, output_bytes);
|
||||
}
|
||||
av_frame_free(&frame);
|
||||
if (gapless != nullptr) {
|
||||
gapless->current.skip_samples -= static_cast<uint16_t>(skip);
|
||||
gapless->current.skipped_samples = static_cast<uint16_t>(
|
||||
std::min<uint32_t>(std::numeric_limits<uint16_t>::max(),
|
||||
gapless->current.skipped_samples + samples - write));
|
||||
if (gapless->HasSampleLimit()) {
|
||||
gapless->current.total_samples -= write;
|
||||
}
|
||||
}
|
||||
result.input_consumed += packet_size + 2;
|
||||
result.output_written += output_bytes;
|
||||
result.frames += frames;
|
||||
m_frames_per_packet = frames;
|
||||
m_total_decoded_samples += write;
|
||||
if (!multiple_frames) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
result.frames_per_packet = m_frames_per_packet;
|
||||
result.total_decoded_samples = m_total_decoded_samples;
|
||||
return result;
|
||||
}
|
||||
|
||||
void WriteCodecInfo(void* output, size_t size, const AjmDecodeResult&) const override {
|
||||
if (output != nullptr && size >= sizeof(AjmSidebandDecOpusCodecInfo)) {
|
||||
static_cast<AjmSidebandDecOpusCodecInfo*>(output)->frames_per_packet =
|
||||
m_frames_per_packet;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] size_t CodecInfoSize() const override {
|
||||
return sizeof(AjmSidebandDecOpusCodecInfo);
|
||||
}
|
||||
|
||||
private:
|
||||
AVFrame* DecodePacket(const uint8_t* data, uint32_t size, AjmDecodeResult* result) {
|
||||
AVPacket* packet = av_packet_alloc();
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (packet == nullptr || frame == nullptr ||
|
||||
av_new_packet(packet, static_cast<int>(size)) < 0) {
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_FATAL;
|
||||
return nullptr;
|
||||
}
|
||||
std::memcpy(packet->data, data, size);
|
||||
int rc = avcodec_send_packet(m_context, packet);
|
||||
av_packet_free(&packet);
|
||||
if (rc >= 0) {
|
||||
rc = avcodec_receive_frame(m_context, frame);
|
||||
}
|
||||
if (rc < 0) {
|
||||
av_frame_free(&frame);
|
||||
result->result = AJM_RESULT_CODEC_ERROR | AJM_RESULT_INVALID_DATA;
|
||||
result->internal_result = 0x1043;
|
||||
return nullptr;
|
||||
}
|
||||
AVFrame* converted = AjmConvertFfmpegFrame(frame, m_sample_encoding, result);
|
||||
av_frame_free(&frame);
|
||||
return converted;
|
||||
}
|
||||
|
||||
AVCodecContext* m_context = nullptr;
|
||||
const uint32_t m_max_channels;
|
||||
uint32_t m_frames_per_packet = 0;
|
||||
};
|
||||
|
||||
}
|
||||
+13
-3
@@ -291,7 +291,7 @@ static char* CopyUriPart(char*& dst, const UriPart& part) {
|
||||
}
|
||||
|
||||
static int ParseEmptyUri(SceHttpUriElement* out, void* pool, size_t* require, size_t prepare) {
|
||||
constexpr size_t needed = 3;
|
||||
constexpr size_t needed = 4;
|
||||
|
||||
if (require != nullptr) {
|
||||
*require = needed;
|
||||
@@ -310,10 +310,12 @@ static int ParseEmptyUri(SceHttpUriElement* out, void* pool, size_t* require, si
|
||||
auto* dst = static_cast<char*>(pool);
|
||||
out->scheme = dst++;
|
||||
out->hostname = dst++;
|
||||
out->path = dst;
|
||||
out->path = dst++;
|
||||
out->query = dst;
|
||||
out->scheme[0] = '\0';
|
||||
out->hostname[0] = '\0';
|
||||
out->path[0] = '\0';
|
||||
out->query[0] = '\0';
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -474,6 +476,9 @@ static int KYTY_SYSV_ABI HttpUriParse(SceHttpUriElement* out, const char* src_ur
|
||||
needed += part.len + 1;
|
||||
}
|
||||
}
|
||||
if (query.begin == nullptr) {
|
||||
needed += 1;
|
||||
}
|
||||
|
||||
if (require != nullptr) {
|
||||
*require = needed;
|
||||
@@ -496,7 +501,12 @@ static int KYTY_SYSV_ABI HttpUriParse(SceHttpUriElement* out, const char* src_ur
|
||||
out->password = CopyUriPart(dst, password);
|
||||
out->hostname = CopyUriPart(dst, hostname);
|
||||
out->path = CopyUriPart(dst, path);
|
||||
out->query = CopyUriPart(dst, query);
|
||||
if (query.begin == nullptr) {
|
||||
out->query = dst++;
|
||||
out->query[0] = '\0';
|
||||
} else {
|
||||
out->query = CopyUriPart(dst, query);
|
||||
}
|
||||
out->fragment = CopyUriPart(dst, fragment);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "common/abi.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
namespace Libs::LibHttp {
|
||||
void InitNet_1_Http(Loader::SymbolDatabase *symbols);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct SceHttpUriElement {
|
||||
int opaque = 0;
|
||||
char *scheme = nullptr;
|
||||
char *username = nullptr;
|
||||
char *password = nullptr;
|
||||
char *hostname = nullptr;
|
||||
char *path = nullptr;
|
||||
char *query = nullptr;
|
||||
char *fragment = nullptr;
|
||||
uint16_t port = 0;
|
||||
uint8_t reserved[10]{};
|
||||
};
|
||||
|
||||
int failures = 0;
|
||||
|
||||
#define CHECK(condition) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
std::fprintf(stderr, "HttpUriParseTests:%d: %s\n", __LINE__, \
|
||||
#condition); \
|
||||
failures++; \
|
||||
} \
|
||||
} while (false)
|
||||
|
||||
using HttpUriParse = int(KYTY_SYSV_ABI *)(SceHttpUriElement *, const char *,
|
||||
void *, size_t *, size_t);
|
||||
|
||||
HttpUriParse GetHttpUriParse() {
|
||||
Loader::SymbolDatabase symbols;
|
||||
Libs::LibHttp::InitNet_1_Http(&symbols);
|
||||
const auto *record =
|
||||
symbols.FindByNid("IWalAn-guFs", Loader::SymbolType::Func);
|
||||
CHECK(record != nullptr);
|
||||
return record != nullptr ? reinterpret_cast<HttpUriParse>(record->vaddr)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
bool PointsIntoPool(const char *ptr, const std::array<char, N> &pool,
|
||||
size_t used) {
|
||||
if (ptr == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const auto address = reinterpret_cast<uintptr_t>(ptr);
|
||||
const auto begin = reinterpret_cast<uintptr_t>(pool.data());
|
||||
return address >= begin && address < begin + used;
|
||||
}
|
||||
|
||||
void TestAbsentQuery(HttpUriParse parse) {
|
||||
constexpr char url[] = "http://example.com/path";
|
||||
size_t required = 0;
|
||||
CHECK(parse(nullptr, url, nullptr, &required, 0) == 0);
|
||||
|
||||
const size_t expected_required =
|
||||
sizeof("http") + sizeof("example.com") + sizeof("/path") + 1;
|
||||
CHECK(required == expected_required);
|
||||
|
||||
std::array<char, 64> pool{};
|
||||
SceHttpUriElement out{};
|
||||
size_t parsed_required = 0;
|
||||
CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0);
|
||||
CHECK(parsed_required == required);
|
||||
CHECK(out.query != nullptr);
|
||||
CHECK(PointsIntoPool(out.query, pool, required));
|
||||
if (out.query != nullptr) {
|
||||
CHECK(out.query[0] == '\0');
|
||||
}
|
||||
}
|
||||
|
||||
void TestEmptyUri(HttpUriParse parse) {
|
||||
constexpr char url[] = "";
|
||||
size_t required = 0;
|
||||
CHECK(parse(nullptr, url, nullptr, &required, 0) == 0);
|
||||
CHECK(required == 4);
|
||||
|
||||
std::array<char, 4> pool{};
|
||||
SceHttpUriElement out{};
|
||||
size_t parsed_required = 0;
|
||||
CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0);
|
||||
CHECK(parsed_required == required);
|
||||
CHECK(out.query != nullptr);
|
||||
CHECK(PointsIntoPool(out.query, pool, required));
|
||||
if (out.query != nullptr) {
|
||||
CHECK(out.query[0] == '\0');
|
||||
}
|
||||
}
|
||||
|
||||
void TestPresentQuery(HttpUriParse parse) {
|
||||
constexpr char url[] = "http://example.com/path?foo=bar";
|
||||
size_t required = 0;
|
||||
CHECK(parse(nullptr, url, nullptr, &required, 0) == 0);
|
||||
|
||||
std::array<char, 64> pool{};
|
||||
SceHttpUriElement out{};
|
||||
size_t parsed_required = 0;
|
||||
CHECK(parse(&out, url, pool.data(), &parsed_required, required) == 0);
|
||||
CHECK(parsed_required == required);
|
||||
CHECK(out.query != nullptr);
|
||||
CHECK(PointsIntoPool(out.query, pool, required));
|
||||
if (out.query != nullptr) {
|
||||
CHECK(std::strcmp(out.query, "?foo=bar") == 0);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
const auto parse = GetHttpUriParse();
|
||||
if (parse == nullptr) {
|
||||
return 1;
|
||||
}
|
||||
TestAbsentQuery(parse);
|
||||
TestEmptyUri(parse);
|
||||
TestPresentQuery(parse);
|
||||
return failures == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -8414,7 +8414,7 @@ public:
|
||||
if (width == 1 && height == 1) {
|
||||
Require(name, "single-texel tiled layout",
|
||||
color.desc.info.mip_layout[0] == ImageMipInfo{0, 0x10000, 128, 64},
|
||||
"the render target lost the SDK's padded block height");
|
||||
"the render target lost the expected padded block height");
|
||||
}
|
||||
|
||||
if (target.levels == 7) {
|
||||
@@ -8426,7 +8426,7 @@ public:
|
||||
Require(name, "linear mip chain", layout.offset == offsets[mip] &&
|
||||
layout.pitch == pitches[mip] &&
|
||||
layout.size == pitches[mip] * (height >> mip) * 8,
|
||||
"linear target mip offsets or padded pitches differ from the SDK");
|
||||
"linear target mip offsets or padded pitches differ from the expected layout");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2042,9 +2042,9 @@ void TestNewShaderRecompilerVop3LaneReadDestinationEncoding() {
|
||||
options.dump_ir = true;
|
||||
|
||||
auto result = RecompileForTest(shader, options);
|
||||
Check((result.decoded_dump.find("v_readfirstlane_b32 s25, v5") != std::string::npos),
|
||||
Check((result.decoded_dump.find("V_READFIRSTLANE_B32 s25, v5") != std::string::npos),
|
||||
"VOP3 V_READFIRSTLANE_B32 destination was not decoded from VDST");
|
||||
Check((result.decoded_dump.find("v_readlane_b32 s26, v5, 2") != std::string::npos),
|
||||
Check((result.decoded_dump.find("V_READLANE_B32 s26, v5, 2") != std::string::npos),
|
||||
"VOP3 V_READLANE_B32 destination was not decoded from VDST");
|
||||
CheckSpirvBinaryValidates(result.spirv);
|
||||
}
|
||||
@@ -13714,5 +13714,7 @@ int main() {
|
||||
TestGraphicsPushConstantPlacement();
|
||||
TestNewShaderRecompilerUnsupportedMemoryDecode();
|
||||
|
||||
TestNewShaderRecompilerVop3LaneReadDestinationEncoding();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user