Merge branch 'main' of https://github.com/boykopovar/AnyPS5 into feat/agc-4-4-4-4-partial-writes

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
JAB-devandClaude Sonnet 5.5 committed 2026-10-22 23:09:07 +02:00
commit 4287ca1cf1
123 files changed
+2459 -373

No files matched your search

+1 -1
View File
@@ -218,7 +218,7 @@ if(TARGET mimalloc)
target_compile_features(pe_import_first PRIVATE cxx_std_20)
if(MINGW)
target_compile_options(pe_import_first PRIVATE -fasynchronous-unwind-tables)
target_link_options(pe_import_first PRIVATE -static -static-libgcc -static-libstdc++)
target_link_options(pe_import_first PRIVATE -municode -static -static-libgcc -static-libstdc++)
endif()
add_custom_command(
OUTPUT ${PATCHED_DIR}/mimalloc.dll
+4 -3
View File
@@ -5,6 +5,7 @@
#include <cctype>
#include <cstdint>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <stdexcept>
@@ -78,16 +79,16 @@ void MoveImportFirst(std::vector<unsigned char>& bytes, const std::string& dll)
for (std::size_t index = 0; index < descriptors.size(); ++index) std::memcpy(bytes.data() + directory + index * DescriptorBytes, descriptors[index].data(), DescriptorBytes);
}
void MoveImportFirstInFile(const std::string& path, const std::string& dll) {
void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll) {
std::vector<unsigned char> bytes;
{
std::ifstream input(path, std::ios::binary);
if (!input) throw std::runtime_error("cannot open " + path);
if (!input) throw std::runtime_error("cannot open " + path.string());
bytes.assign(std::istreambuf_iterator<char>(input), std::istreambuf_iterator<char>());
}
MoveImportFirst(bytes, dll);
std::ofstream output(path, std::ios::binary | std::ios::trunc);
if (!output || !output.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) throw std::runtime_error("cannot write " + path);
if (!output || !output.write(reinterpret_cast<const char*>(bytes.data()), static_cast<std::streamsize>(bytes.size()))) throw std::runtime_error("cannot write " + path.string());
}
}
+2 -1
View File
@@ -1,13 +1,14 @@
#ifndef CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP
#define CORE_LIBS_ALLOCATOR_PEIMPORTFIRST_HPP
#include <filesystem>
#include <string>
#include <vector>
namespace Allocator {
void MoveImportFirst(std::vector<unsigned char>& image, const std::string& dll);
void MoveImportFirstInFile(const std::string& path, const std::string& dll);
void MoveImportFirstInFile(const std::filesystem::path& path, const std::string& dll);
}
+27 -7
View File
@@ -2,17 +2,37 @@
#include <cstdio>
#include <exception>
#include <filesystem>
#include <string>
int main(int argc, char** argv) {
if (argc != 3) {
std::fprintf(stderr, "usage: pe_import_first <file> <dll>\n");
return 2;
}
namespace {
int Usage() {
std::fprintf(stderr, "usage: pe_import_first <file> <dll>\n");
return 2;
}
int Run(const std::filesystem::path& file, const std::string& dll) {
try {
Allocator::MoveImportFirstInFile(argv[1], argv[2]);
Allocator::MoveImportFirstInFile(file, dll);
} catch (const std::exception& error) {
std::fprintf(stderr, "pe_import_first: %s: %s\n", argv[1], error.what());
std::fprintf(stderr, "pe_import_first: %s: %s\n", file.string().c_str(), error.what());
return 1;
}
return 0;
}
}
#ifdef _WIN32
int wmain(int argc, wchar_t** argv) {
if (argc != 3) return Usage();
const auto dll = std::filesystem::path(argv[2]).u8string();
return Run(argv[1], std::string(dll.begin(), dll.end()));
}
#else
int main(int argc, char** argv) {
if (argc != 3) return Usage();
return Run(argv[1], argv[2]);
}
#endif
@@ -113,7 +113,7 @@ void movesTheNamedImportFirst() {
void movesTheImportInAFile(const std::filesystem::path& directory) {
const auto path = directory / "moved.dll";
writeFile(path, image({"kernel32.dll", "mimalloc-redirect.dll"}));
Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll");
Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll");
const auto moved = readFile(path);
require(importName(moved, 0) == "mimalloc-redirect.dll" && importName(moved, 1) == "kernel32.dll", "the file's import did not move first");
}
@@ -122,7 +122,7 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s
const auto path = directory / (what + ".dll");
writeFile(path, bytes);
try {
Allocator::MoveImportFirstInFile(path.string(), "mimalloc-redirect.dll");
Allocator::MoveImportFirstInFile(path, "mimalloc-redirect.dll");
} catch (const std::exception& error) {
require(std::string(error.what()).find(expected) != std::string::npos, what + ": failed with another error: " + error.what());
require(readFile(path) == bytes, what + ": the file changed although the move failed");
@@ -134,7 +134,8 @@ void failsWithoutTouchingTheFile(const std::filesystem::path& directory, const s
}
int main() {
const auto directory = std::filesystem::temp_directory_path() / ("aps5-pe-import-first-" + std::to_string(std::random_device{}()));
auto directory = std::filesystem::temp_directory_path() / std::filesystem::path(u8"aps5-pe-import-first-a\u00e7\u00e3o-\u65e5\u672c-");
directory += std::to_string(std::random_device{}());
try {
std::filesystem::create_directories(directory);
movesTheNamedImportFirst();
+21 -2
View File
@@ -1653,9 +1653,17 @@ if(BUILD_TESTING)
ENVIRONMENT "APS5_PIN_WAIT_MS=2000" SKIP_RETURN_CODE 77 TIMEOUT 30)
set_tests_properties(agc_driver_unit_shadow_budget PROPERTIES
ENVIRONMENT "APS5_PIN_WAIT_MS=2000;APS5_UNIT_SHADOW_MIB=8" SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_completion_labels COMMAND agc_driver_recorder_tests --completion-labels-only)
set_tests_properties(agc_recorder_completion_labels PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_cmask COMMAND agc_driver_recorder_tests --cmask-only)
set_tests_properties(agc_recorder_cmask PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
add_test(NAME agc_recorder_cube COMMAND agc_driver_recorder_tests --cube-only)
set_tests_properties(agc_recorder_cube PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_host_import_lifetime agc_driver_host_import_mutation_race agc_driver_unit_shadow agc_driver_unit_shadow_budget PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_recorder_completion_labels agc_recorder_cmask agc_recorder_cube PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
set_property(TEST agc_driver_host_import_lifetime APPEND PROPERTY ENVIRONMENT_MODIFICATION
"APS5_RELEASE_UNDER_LOCK=unset:" "APS5_RELEASE_ON_UNLOCK=unset:")
@@ -2043,13 +2051,23 @@ if(BUILD_TESTING)
add_test(NAME agc_driver_store_pending_identity COMMAND agc_driver_store_over_untracked_tests --pending-identity)
set_tests_properties(agc_driver_store_pending_identity PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
add_test(NAME agc_driver_store_idle_results_wake COMMAND agc_driver_store_idle_results_tests --idle-epoch-wake)
set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 TIMEOUT 30 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_store_pending_identity PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_store_idle_results_wake PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
endif()
if(BUILD_TESTING)
add_test(NAME agc_driver_native_fmask COMMAND agc_driver_sample_multisample_target_tests --native-fmask)
set_tests_properties(agc_driver_native_fmask PROPERTIES TIMEOUT 30 ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
if(WIN32)
set_tests_properties(agc_driver_native_fmask PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_native_msaa_vertex_input COMMAND agc_driver_sample_multisample_target_tests --native-vertex-input)
set_tests_properties(agc_driver_native_msaa_vertex_input PROPERTIES
ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" TIMEOUT 30 SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
@@ -2833,15 +2851,16 @@ if(BUILD_TESTING)
endif()
endforeach()
add_test(NAME agc_driver_shutdown_threads_joined COMMAND agc_driver_shutdown_threads_tests --joined)
add_test(NAME agc_driver_shutdown_threads_queued_prebuilds COMMAND agc_driver_shutdown_threads_tests --queued-prebuilds)
add_test(NAME agc_driver_shutdown_threads_compare_helpers COMMAND agc_driver_shutdown_threads_tests --compare-helpers)
add_test(NAME agc_driver_shutdown_threads_draw_committer COMMAND agc_driver_shutdown_threads_tests --draw-committer)
add_test(NAME agc_driver_shutdown_threads_shader_cache_writer COMMAND agc_driver_shutdown_threads_tests --shader-cache-writer)
add_test(NAME agc_driver_shutdown_threads_late_shader_cache COMMAND agc_driver_shutdown_threads_tests --late-shader-cache)
add_test(NAME agc_driver_shutdown_threads_async_log_held COMMAND agc_driver_shutdown_threads_tests --async-log-held)
add_test(NAME agc_driver_shutdown_threads_exit_after_start COMMAND agc_driver_shutdown_threads_tests --exit-after-start)
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" TIMEOUT 30)
if(WIN32)
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
set_tests_properties(agc_driver_shutdown_threads_joined agc_driver_shutdown_threads_queued_prebuilds agc_driver_shutdown_threads_compare_helpers agc_driver_shutdown_threads_draw_committer agc_driver_shutdown_threads_shader_cache_writer agc_driver_shutdown_threads_late_shader_cache agc_driver_shutdown_threads_async_log_held agc_driver_shutdown_threads_exit_after_start PROPERTIES ENVIRONMENT_MODIFICATION "PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test(NAME agc_driver_compute_user_data_calls_registered COMMAND agc_driver_compute_user_data_calls_tests --registered)
set_tests_properties(agc_driver_compute_user_data_calls_registered PROPERTIES ENVIRONMENT "ANYPS5_NO_SHADER_CACHE=1" SKIP_RETURN_CODE 77 WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
@@ -1146,9 +1146,8 @@ public:
Closing() = true;
pools = {Existing(), ExistingPrebuild()};
}
for (auto* pool : pools) {
if (pool != nullptr) pool->Close();
}
if (pools[0] != nullptr) pools[0]->Close(false);
if (pools[1] != nullptr) pools[1]->Close(true);
}
private:
@@ -1204,11 +1203,17 @@ public:
idle.wait(lock, [&] { return unfinished.empty() || *unfinished.begin() > ticket; });
}
void Close() {
void Close(bool dropQueued) {
std::vector<Worker> joined;
std::deque<Task> dropped;
{
std::unique_lock lock(mutex);
closed = true;
if (dropQueued) {
dropped.swap(tasks);
for (const auto& task : dropped) unfinished.erase(task.sequence);
idle.notify_all();
}
idle.wait(lock, [&] { return tasks.empty() && running == 0; });
joined.swap(workers);
}
@@ -29,6 +29,7 @@ struct DepthPlaneStoreStatistics {
std::uint64_t bytes = 0;
};
DepthPlaneStoreStatistics DepthPlaneStoreCounts();
void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target);
bool SampleLocationsCompatibleDepth(const Context& context, VkFormat format, std::uint32_t samples);
std::uint64_t DepthSurfacesSerial();
std::uint64_t DepthSurfacesReleased();
@@ -90,6 +90,7 @@ struct GuestTextureResource {
std::uint32_t minLod = 0;
std::uint32_t allocatedMipCount = 0;
std::uint32_t samples = 1;
std::uint64_t fmaskCmaskAddress = 0;
};
float EffectiveMinLod(const GuestTextureResource& resource);
@@ -76,6 +76,7 @@ bool MultisampleTargetStorable(const Context& context, const ColorTarget& target
void ResolveMultisampleTarget(const Context& context, const ColorTarget& source, StorageTexture& destination, VkImageView destinationView);
bool MultisampledColorFillAbsorbs(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32_t pattern);
std::shared_ptr<Texture> MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components);
void ClearMultisampleTargets(VkDevice device);
void InstallResidentImageReadGuard_nid_no_patch();
void UpdateResidentImageUnmapObserver_nid_no_patch();
@@ -68,6 +68,8 @@ struct ColorTarget {
std::uint32_t pipeBankXor = 0;
std::uint32_t samples = 1;
std::uint32_t dccControl = 0;
std::uint64_t fmaskAddress = 0;
std::uint32_t fmaskPipeBankXor = 0;
std::uint16_t partialWriteMask = 0;
};
@@ -58,6 +58,7 @@ struct DepthGuard {
std::size_t bytes;
std::shared_ptr<std::atomic<bool>> active;
std::vector<GuestMemory::UnchangedQuery> live;
std::shared_ptr<std::atomic<bool>> hostWritten;
};
std::shared_mutex& depthGuardMutex() {
@@ -492,6 +493,7 @@ public:
void CopyAspectFrom(const DepthSurface& source, VkImageAspectFlagBits aspect) {
ForgetClear(aspect);
NoteHostWrite();
auto* recorder = RecorderFor(context);
std::unique_ptr<CommandBatch> batch;
if (recorder == nullptr) batch = std::make_unique<CommandBatch>(context);
@@ -541,7 +543,12 @@ public:
return std::any_of(textures.begin(), textures.end(), [](const auto& entry) { return entry.second.use_count() > 1; });
}
void NoteHostWrite() {
hostWritten->store(true, std::memory_order_release);
}
std::uint64_t written = ++depthWrites;
std::shared_ptr<std::atomic<bool>> hostWritten = std::make_shared<std::atomic<bool>>(false);
bool retired = false;
bool Overlaps(std::uint64_t address, std::uint64_t bytes) const {
@@ -691,6 +698,7 @@ public:
if (plane.storage.get() == &candidate) {
if (stencil || !StoreOverwrite(candidate)) copyPlane(plane, plane.storage, stencil, false);
written = ++depthWrites;
NoteHostWrite();
return true;
}
if (std::none_of(plane.replaced.begin(), plane.replaced.end(), [&](const auto& old) { return old.lock().get() == &candidate; })) continue;
@@ -708,6 +716,7 @@ public:
const auto source = std::const_pointer_cast<StorageTexture>(candidate.shared_from_this());
copyPlane(plane, source, stencil, false);
written = ++depthWrites;
NoteHostWrite();
return true;
}
return false;
@@ -1844,8 +1853,28 @@ std::uint64_t MultisampledDepthBytes_nid_no_patch(VkExtent2D extent, std::uint32
void RequireNoResidentDepthRead_nid_no_patch(std::uint64_t address, std::size_t bytes) {
if (bytes == 0u || !anyDepthGuard().load(std::memory_order_acquire)) return;
Require(address <= std::numeric_limits<std::uint64_t>::max() - bytes, "native depth read range overflows");
std::shared_lock lock(depthGuardMutex());
for (const auto& range : depthGuards()) Require(address >= range.address + range.bytes || range.address >= address + bytes, "sample-preserving guest publication of native multisample depth is not implemented");
const auto overlaps = [&](const DepthGuard& range) { return address < range.address + range.bytes && range.address < address + bytes; };
{
std::shared_lock lock(depthGuardMutex());
if (std::ranges::none_of(depthGuards(), overlaps)) return;
}
std::unique_lock lock(depthGuardMutex());
for (const auto& range : depthGuards()) {
if (overlaps(range)) Require(!range.hostWritten->load(std::memory_order_acquire), "sample-preserving guest publication of native multisample depth is not implemented");
}
for (const auto& range : depthGuards()) {
if (overlaps(range)) range.active->store(false, std::memory_order_release);
}
std::erase_if(depthGuards(), [](const auto& range) { return !range.active->load(std::memory_order_acquire); });
anyDepthGuard().store(!depthGuards().empty(), std::memory_order_release);
}
void NoteDepthSurfaceWrite(const Context& context, const DepthTarget& target) {
if (target.samples == 1u || !anyDepthGuard().load(std::memory_order_acquire)) return;
std::lock_guard lock(surfacesMutex());
for (const auto& surface : surfaces()) {
if (surface->active->load(std::memory_order_acquire) && surface->context.device == context.device && sameSurface(surface->target, target)) surface->NoteHostWrite();
}
}
bool HasResidentDepthGuards_nid_no_patch() {
@@ -1955,7 +1984,7 @@ VkImageView DepthSurfaceView(const Context& context, const DepthTarget& target,
auto& fresh = *surfaces().back();
if (target.samples != 1u && fresh.sampleable && fresh.sampling.refusal.empty()) {
std::unique_lock guardLock(depthGuardMutex());
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}});
for (const auto& range : fresh.sampling.initial) depthGuards().push_back({context.device, range.address, range.bytes, fresh.active, {{range.address, range.bytes, 0u}}, fresh.hostWritten});
anyDepthGuard().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
fresh.samplingActive = true;
@@ -2513,7 +2542,10 @@ bool DepthSurfaceStoreBack(const Context& context, const StorageTexture& storage
if (std::get<0>(key) != context.device || entry->storage.get() != &storage) continue;
const auto slices = layeredSlices(context, *entry);
entry->Copy(slices, false);
for (const auto& slice : slices) slice.surface->written = ++depthWrites;
for (const auto& slice : slices) {
slice.surface->written = ++depthWrites;
slice.surface->NoteHostWrite();
}
return true;
}
for (const auto& surface : surfaces()) {
@@ -84,6 +84,10 @@ GuestTextureResource ArraySurfaceForTarget(const ColorTarget& color) {
namespace {
bool writesDepthTarget(const State& state) {
return state.depthWrite || (state.stencilTest && (state.stencilFront.writeMask != 0u || state.stencilBack.writeMask != 0u));
}
std::size_t colorKeyCount(const ColorTarget& color, std::size_t bytes) {
if (color.mipCount != 1 || color.depth != 1 || !color.dccPipeAligned) return DccKeyBytes(bytes);
return DccKeyCount(ColorTextureTileMode(color.tileMode), color.elementBytes, color.extent.width, color.extent.height, bytes);
@@ -2366,7 +2370,10 @@ bool prepareDraw(const Context& context, const State& state, const Pm4::DrawPara
binding.target = std::make_unique<RenderTarget>(context, color, state.blends.at(color.exportIndex).blendEnable != 0);
targetViews.push_back(binding.target->View());
}
if (state.depth) targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite));
if (state.depth) {
targetViews.push_back(DepthSurfaceView(context, *state.depth, state.depthWrite));
if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth);
}
timer.phase(PhasePrepare);
const bool recordDraws = RecordDraws();
auto* recorder = prepared.recorder = Recorder::Active();
@@ -3013,6 +3020,7 @@ DrawRecipeOutcome DrawWithRecipe(const Context& context, const State& state, con
if (recipe.hasDepth) {
timer.phase(PhaseSetup);
const auto depthView = DepthSurfaceView(context, *state.depth, state.depthWrite);
if (writesDepthTarget(state)) NoteDepthSurfaceWrite(context, *state.depth);
timer.phase(PhaseReadTarget);
if (recipe.targetViews.back() != depthView) return miss(DrawRecipeMiss::TargetGone);
}
@@ -141,7 +141,8 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
const bool fmask = ShaderRecompiler::IsFmaskTextureFormat(static_cast<ShaderRecompiler::IrBufferFormat>(format));
if (fmask) {
Require(format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Fmask32_S8_F8), "guest FMASK format is not implemented");
Require(!metaCompress && !writeCompress, "FMASK CMASK compression is not implemented");
Require(!writeCompress && (!metaCompress || metaAddr != 0u), "FMASK CMASK compression is not implemented without readable metadata");
Require(!metaCompress || metaPipeAligned, "FMASK CMASK metadata must be pipe aligned");
Require((typeRaw == 9u || typeRaw == 13u) && tileModeRaw == 24u && baseLevel == 0u && lastLevel == 0u && maxMip == 0u, "FMASK reads require a single-mip 2D or 2D-array Z_64KB_X surface");
for (const auto selector : {dstSelX, dstSelY, dstSelZ, dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK destination channel selector is not implemented");
}
@@ -230,7 +231,8 @@ GuestTextureResource DecodeTextureResource(std::span<const std::uint32_t> words)
result.dstSelZ = static_cast<std::uint8_t>(dstSelZ);
result.dstSelW = static_cast<std::uint8_t>(dstSelW);
result.bcSwizzle = static_cast<std::uint8_t>(bcSwizzle);
result.dccAddress = metaCompress ? metaAddr << 8u : 0u;
result.dccAddress = metaCompress && !fmask ? metaAddr << 8u : 0u;
result.fmaskCmaskAddress = metaCompress && fmask ? metaAddr << 8u : 0u;
result.dccAlphaOnMsb = dccAlphaPos;
result.dccPipeAligned = metaPipeAligned;
result.iterate256 = iterate256;
@@ -9,6 +9,8 @@
#include "prx/libSceAgcDriver/Graphics/include/SampleOrder.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Texture.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureFormat.hpp"
#include "prx/libSceAgcDriver/Graphics/include/TextureDetiler.hpp"
#include "IntermediateRepresentation/IrMetadata/BufferFormat.hpp"
#include "prx/libSceAgcDriver/Execution/include/GuestMemory.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include <algorithm>
@@ -45,6 +47,43 @@ std::uint64_t collectBlocks(std::uint64_t address, std::size_t bytes) {
return generation != 0 ? generation : GuestMemory::CollectWritesUncached(address, bytes);
}
GuestTextureResource fmaskSnapshotResource(const GuestTextureResource& resource) {
Require(resource.format == static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt) && resource.fmaskCmaskAddress != 0u, "FMASK image format read semantics are not implemented");
Require(resource.dimension == TextureDimension::k2D || resource.dimension == TextureDimension::k2DArray, "FMASK image dimension is not implemented");
Require(resource.tileMode == TextureTileMode::kZ64KBX && (resource.pipeBankXor & ~0xcf00u) == 0u, "FMASK image swizzle or pipe/bank XOR is not implemented");
Require(resource.width != 0u && resource.width <= 16384u && resource.height != 0u && resource.height <= 16384u && resource.depthOrLastArray < 8192u && resource.baseArray <= resource.depthOrLastArray, "invalid FMASK image extent or layer range");
Require(resource.dimension == TextureDimension::k2DArray || (resource.depthOrLastArray == 0u && resource.baseArray == 0u), "FMASK 2D image has an array range");
Require(resource.mipCount == 1u && resource.allocatedMipCount == 1u && resource.baseLevel == 0u && resource.lastLevel == 0u && resource.minLod == 0u, "FMASK image mip fields are not implemented");
Require(resource.fmaskCmaskAddress == 0u || resource.dccPipeAligned, "FMASK image CMASK must be pipe aligned");
for (const auto selector : {resource.dstSelX, resource.dstSelY, resource.dstSelZ, resource.dstSelW}) Require(selector == 0u || selector == 1u || selector == 4u, "FMASK image selects an unverified channel");
auto snapshot = resource;
snapshot.format = static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt);
snapshot.tileMode = TextureTileMode::kLinear;
snapshot.pipeBankXor = 0u;
snapshot.dccAddress = 0u;
snapshot.fmaskCmaskAddress = 0u;
snapshot.dccAlphaOnMsb = false;
snapshot.dccPipeAligned = false;
return snapshot;
}
GuestTextureResource fmaskResource(const ColorTarget& target) {
GuestTextureResource resource{};
resource.baseAddress = target.fmaskAddress;
resource.width = target.extent.width;
resource.height = target.extent.height;
resource.mipCount = 1u;
resource.allocatedMipCount = 1u;
resource.tileMode = TextureTileMode::kZ64KBX;
resource.dimension = TextureDimension::k2D;
resource.format = static_cast<std::uint32_t>(ShaderRecompiler::IrBufferFormat::Format32UInt);
resource.dstSelX = 4u;
resource.pipeBankXor = target.fmaskPipeBankXor;
resource.fmaskCmaskAddress = target.cmaskAddress;
resource.dccPipeAligned = true;
return resource;
}
bool sameTarget(const ColorTarget& a, const ColorTarget& b) {
return a.surfaceAddress == b.surfaceAddress && a.format == b.format && a.extent.width == b.extent.width && a.extent.height == b.extent.height && a.samples == b.samples;
}
@@ -240,6 +279,28 @@ public:
recorder.Keep(shared_from_this(), static_cast<std::size_t>(storable.bytes));
}
std::shared_ptr<Texture> FmaskSampled(const GuestTextureResource& resource, VkComponentMapping components) {
const auto snapshot = fmaskSnapshotResource(resource);
Require(target.samples == 8u && target.dccAddress != 0u && fmaskRange.has_value(), "native FMASK sampling requires a tracked eight-fragment DCC target");
Require(target.tileMode == ColorTileMode::RenderTarget && target.depth == 1u && target.depthSlice == 0u && target.layers == 1u && target.arrayLastSlice == 0u && target.arraySlice == 0u && target.mip == 0u && target.mipCount == 1u, "native FMASK target layout is not implemented");
Require(target.fmaskAddress == resource.baseAddress && target.fmaskPipeBankXor == resource.pipeBankXor && target.cmaskAddress == resource.fmaskCmaskAddress, "native FMASK descriptor does not match its resident target");
Require(target.extent.width == resource.width && target.extent.height == resource.height && resource.baseArray == 0u && resource.depthOrLastArray == 0u, "native FMASK extent or subresource does not match its resident target");
auto* recorder = Recorder::Active();
Require(recorder != nullptr && recorder->Device() == context.device && !Recorder::InCompletion(), "native FMASK sampling requires its device's active recorder outside completion");
ApplyFastClear(target);
Keep();
const std::array<std::uint32_t, 5> key{static_cast<std::uint32_t>(components.r), static_cast<std::uint32_t>(components.g), static_cast<std::uint32_t>(components.b), static_cast<std::uint32_t>(components.a), static_cast<std::uint32_t>(resource.dimension)};
auto& texture = fmaskTextures[key];
if (texture == nullptr) {
const auto geometry = DescribeSurface(snapshot);
std::vector<std::byte> bytes(geometry.guestBytes);
constexpr std::uint32_t expanded = 0x76543210u;
for (std::size_t offset = 0; offset < bytes.size(); offset += sizeof(expanded)) std::memcpy(bytes.data() + offset, &expanded, sizeof(expanded));
texture = std::make_unique<Texture>(context, *context.detiler, snapshot, components, bytes);
}
return {shared_from_this(), texture.get()};
}
void Keep() {
if (auto* recorder = Recorder::Active(); recorder != nullptr && recorder->Device() == context.device) {
recorder->Keep(shared_from_this());
@@ -269,6 +330,7 @@ public:
colorFill = MultisampledColorFill(use);
fromTexture = false;
dccRanges.clear();
fmaskRange.reset();
metadataValid = std::make_shared<std::atomic<bool>>(true);
const auto initial = initialDccClear(use);
recordCommands(context, [&](VkCommandBuffer commands, Recorder* recorder) {
@@ -450,6 +512,8 @@ private:
VkImageView storageView = VK_NULL_HANDLE;
bool storageWritten = false;
VkDeviceSize allocationBytes = 0;
std::optional<GuestMemory::UnchangedQuery> fmaskRange;
std::map<std::array<std::uint32_t, 5>, std::unique_ptr<Texture>> fmaskTextures;
std::map<std::array<std::uint32_t, 5>, std::shared_ptr<Texture>> textures;
std::vector<std::shared_ptr<Texture>> retiredTextures;
std::vector<GuestMemory::UnchangedQuery> dccRanges;
@@ -505,18 +569,29 @@ private:
Require(a.address >= b.address + b.bytes || b.address >= a.address + a.bytes, "overlapping multisampled DCC color or metadata ranges are not implemented");
}
}
if (use.samples == 8u && use.fmaskAddress != 0u && use.cmaskAddress != 0u) {
const auto resource = fmaskResource(use);
static_cast<void>(fmaskSnapshotResource(resource));
fmaskRange = GuestMemory::UnchangedQuery{use.fmaskAddress, static_cast<std::size_t>(DescribeSurface(resource).guestBytes), 0};
auto& range = *fmaskRange;
GuestMemory::CheckRange(reinterpret_cast<const void*>(range.address), range.bytes, 65536u);
Require(GuestMemory::Watched(range.address, range.bytes), "native FMASK contents require watched guest memory");
range.generation = GuestMemory::CollectWritesUncached(range.address, range.bytes);
Require(range.generation != 0, "native FMASK contents have no write generation");
for (const auto& other : dccRanges) Require(range.address >= other.address + other.bytes || other.address >= range.address + range.bytes, "native FMASK overlaps its color or metadata planes");
}
std::vector<std::byte> dcc(dccRanges[1].bytes);
GuestMemory::Read(use.dccAddress, dcc);
std::vector<std::byte> cmask(use.cmaskBytes);
if (!cmask.empty()) GuestMemory::Read(use.cmaskAddress, cmask);
const auto clear = MultisampledDccClear(use, dcc, cmask);
Require(GuestMemory::UnchangedSinceAll(dccRanges), "initial multisampled DCC contents changed during import");
Require(GuestMemory::UnchangedSinceAll(dccRanges) && (!fmaskRange || GuestMemory::UnchangedSinceAll(std::span(&*fmaskRange, 1u))), "initial multisampled DCC contents changed during import");
return clear;
}
void requireDccRange(bool valid, const GuestMemory::UnchangedQuery& range, const char* reason) const {
if (valid) return;
const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : "CMASK";
const auto* plane = range.address == target.address ? "color" : range.address == target.dccAddress ? "DCC" : range.address == target.fmaskAddress ? "FMASK" : "CMASK";
char text[384];
std::snprintf(text, sizeof(text), "%s (%s range 0x%llx+0x%zx, %u samples)", reason, plane, static_cast<unsigned long long>(range.address), range.bytes, target.samples);
Require(false, text);
@@ -537,9 +612,18 @@ private:
requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident multisampled DCC target or its metadata are not implemented");
if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident multisampled DCC target or its metadata are not implemented");
}
if (fmaskRange) {
Require(use.fmaskAddress == target.fmaskAddress && use.fmaskPipeBankXor == target.fmaskPipeBankXor, "native FMASK target layout changed");
const auto& range = *fmaskRange;
requireDccRange(GuestMemory::Watched(range.address, range.bytes), range, "native FMASK sampling requires watched guest memory");
GuestMemory::CollectWritesUncached(range.address, range.bytes);
requireDccRange(GuestMemory::UnchangedSince(range.address, range.bytes, range.generation) || !GuestMemory::StoredOver(range.address, range.bytes, range.generation), range, "writes to a resident native FMASK are not implemented");
if (auto* recorder = Recorder::Active(); recorder != nullptr) requireDccRange(!recorder->PendingWriteOverlaps(range.address, range.bytes), range, "pending writes to a resident native FMASK are not implemented");
}
}
void release() noexcept {
fmaskTextures.clear();
textures.clear();
retiredTextures.clear();
for (const auto& [format, resolvePass] : resolvePasses) context.Function<PFN_vkDestroyRenderPass>("vkDestroyRenderPass")(context.device, resolvePass, nullptr);
@@ -647,6 +731,7 @@ void guard(const Context& context, const ColorTarget& target, const std::shared_
if (target.dccAddress != 0) {
guarded().push_back({context.device, target.dccAddress, MultisampledDccBytes(target), target.samples, active, metadataValid});
if (target.cmaskAddress != 0) guarded().push_back({context.device, target.cmaskAddress, target.cmaskBytes, target.samples, active, metadataValid});
if (target.samples == 8u && target.fmaskAddress != 0u && target.cmaskAddress != 0u) guarded().push_back({context.device, target.fmaskAddress, DescribeSurface(fmaskResource(target)).guestBytes, target.samples, active, metadataValid});
}
anyGuarded().store(true, std::memory_order_release);
InstallResidentImageReadGuard_nid_no_patch();
@@ -1166,6 +1251,20 @@ void NoteColorMetadataFill(std::uint64_t address, std::size_t bytes, std::uint32
}
}
std::shared_ptr<Texture> MultisampleTargetFmaskTexture_nid_no_patch(const Context& context, const GuestTextureResource& resource, VkComponentMapping components) {
std::lock_guard lock(targetsMutex());
discardRetiredTargets();
std::shared_ptr<MultisampleTarget> owner;
for (const auto& candidate : targets()) {
if (!candidate->active->load(std::memory_order_acquire) || candidate->context.device != context.device) continue;
const auto& target = candidate->target;
if (target.fmaskAddress != resource.baseAddress && (resource.fmaskCmaskAddress == 0u || target.cmaskAddress != resource.fmaskCmaskAddress)) continue;
Require(owner == nullptr, "FMASK descriptor refers to more than one resident target");
owner = candidate;
}
return owner != nullptr ? owner->FmaskSampled(resource, components) : nullptr;
}
void ClearMultisampleTargets(VkDevice device) {
std::lock_guard lock(targetsMutex());
std::erase_if(targets(), [&](const auto& target) { return target->context.device == device; });
@@ -35,7 +35,7 @@ static_assert(sizeof(VkStencilOpState) == 28, "VkStencilOpState changed: update
static_assert(sizeof(VkVertexInputBindingDescription) == 12, "VkVertexInputBindingDescription changed: update the recipe codec");
static_assert(sizeof(VkVertexInputAttributeDescription) == 16, "VkVertexInputAttributeDescription changed: update the recipe codec");
#if defined(__linux__) && defined(__x86_64__) && defined(__GLIBCXX__)
static_assert(sizeof(State) == 792, "State changed: update CanonicalPipelineState and the recipe codec");
static_assert(sizeof(State) == 800, "State changed: update CanonicalPipelineState and the recipe codec");
static_assert(sizeof(ShaderStages) == 112, "ShaderStages changed: update the recipe codec");
static_assert(sizeof(ShaderRecompiler::MeshConfiguration) == 40, "MeshConfiguration changed: update the recipe codec");
static_assert(sizeof(ShaderRecompiler::TessellationConfiguration) == 40, "TessellationConfiguration changed: update the recipe codec");
@@ -4,6 +4,7 @@
#include "ThreadOwned.hpp"
#include "prx/libc/include/HostThreadLocal.hpp"
#include "prx/libSceAgcDriver/Graphics/include/MultisampleTarget.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ColorTargetLayout.hpp"
#include "prx/libSceAgcDriver/Graphics/include/DepthSurface.hpp"
#include "prx/libSceAgcDriver/Graphics/include/PlaceholderImages.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
@@ -682,6 +683,14 @@ std::shared_ptr<Texture> cachedTexture(const Context& context, std::span<const s
logLookup({target.get(), resource, 0, DccKeys::Uncompressed, 0, nullptr, true});
return target;
}
if (resource.fmaskCmaskAddress != 0u) {
Require(!depthCompare, "FMASK depth comparison sampling is not implemented");
Require(resource.baseArray == 0u && resource.depthOrLastArray == 0u, "native FMASK array sampling is not implemented");
CompletePendingCpuWrites(context, resource.fmaskCmaskAddress, CmaskBytes(resource.width, resource.height));
auto native = MultisampleTargetFmaskTexture_nid_no_patch(context, resource, components);
Require(native != nullptr, "compressed FMASK sampling without a matching resident native target is not implemented");
return native;
}
CaptureTrace::Log("sampled-lookup address=%llx width=%u height=%u dcc=%llx", static_cast<unsigned long long>(resource.baseAddress), resource.width, resource.height, static_cast<unsigned long long>(resource.dccAddress));
RequireNoMultisampleTarget(resource.baseAddress, guestBytes != 0 ? static_cast<std::size_t>(guestBytes) : 1u, "a texture read");
if (auto* recorder = Recorder::Active(); recorder != nullptr) recorder->BoundKeptBytes();
@@ -949,8 +949,8 @@ DecodedColorFormat DecodeColorFormat(std::uint32_t format, std::uint32_t number,
if (number == unorm) return {VK_FORMAT_R32_UINT, 4, 0xe4u, ShaderRecompiler::ColorExportPacking::Unorm10_11_11};
return {VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4};
case 7:
if (swap != 2 || number != floating) return fail();
return {VK_FORMAT_R32_UINT, 4, 0xc6u, ShaderRecompiler::ColorExportPacking::Float11_11_10};
if ((swap != 0 && swap != 2) || number != floating) return fail();
return {VK_FORMAT_R32_UINT, 4, static_cast<std::uint8_t>(swap == 2 ? 0xc6u : 0xe4u), ShaderRecompiler::ColorExportPacking::Float11_11_10};
case 9:
// COLOR_2_10_10_10 keeps red in the low bits, the Vulkan A2B10G10R10 packing.
if (number != unorm) return fail();
@@ -1968,6 +1968,14 @@ ColorTarget DecodeColorBuffer(const Registers& cx, std::uint32_t slot, bool allo
if (color.samples == 1) GuestMemory::CheckRange(reinterpret_cast<const void*>(cmask), totalCmaskBytes, CmaskLayout::Alignment, true);
}
color.cmaskFastClear = (info & 0x2000u) != 0;
if (const auto fmask = find(cx, 0x321 + stride); color.samples > 1u && fmask != cx.end()) {
const auto high = find(cx, 0x3a0 + slot);
const auto fmaskHigh = high == cx.end() ? 0u : high->second;
Require((fmaskHigh & ~0xffu) == 0u, "invalid FMASK address extension");
const auto address = (static_cast<std::uint64_t>(fmaskHigh) << 40u) | (static_cast<std::uint64_t>(fmask->second) << 8u);
color.fmaskPipeBankXor = static_cast<std::uint32_t>(address & 0xffffu);
color.fmaskAddress = address - color.fmaskPipeBankXor;
}
}
if ((info & 0x10000000u) != 0) {
if (maxMip == 0) {
@@ -9916,7 +9916,12 @@ void float11_11_10TargetTests() {
auto changed = queue;
changed.context[0x31c] &= ~0x40000u;
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "rounding (ROUND_MODE 0) 11_11_10 float color targets");
for (const auto swap : {0u, 1u, 3u}) {
changed = queue;
changed.context[0x31c] &= ~0x1800u;
const auto standard = AgcDriver::Graphics::DecodeState(changed);
Require(standard.color.format == VK_FORMAT_R32_UINT && standard.color.componentMapping == 0xe4u && standard.color.packing == ColorExportPacking::Float11_11_10, "an 11_11_10 float target with the standard component order did not decode");
Require(AgcDriver::Graphics::ExportMappings(standard)[0] == 0xe4u, "export 0 reordered its components into the standard-order 11_11_10 float target");
for (const auto swap : {1u, 3u}) {
changed = queue;
changed.context[0x31c] = (changed.context[0x31c] & ~0x1800u) | (swap << 11u);
expectFailure([&] { AgcDriver::Graphics::DecodeState(changed); }, "unsupported color format 7 number type 7 component swap " + std::to_string(swap));
@@ -11102,6 +11107,39 @@ void pipelineJournalTests(bool longPath = false, bool extended = false) {
const PipelineSetLayouts decodedLayouts{layouts.layouts.main, layouts.layouts.fragment, decoded.splitSets, decoded.mainLayoutKey, decoded.fragmentLayoutKey};
BuildPipelineStoreKey(replayed, context, decoded.state, decoded.vertexInput, decodedLayouts, shaders, decoded.attachmentLayout);
Require(!original.empty() && original == canonical && original == replayed, "the canonical or the decoded state keys another pipeline");
auto fmaskState = state;
fmaskState.samples = 8;
fmaskState.color.samples = 8;
fmaskState.color.fmaskAddress = 0x30000;
fmaskState.color.fmaskPipeBankXor = 0x1200;
for (auto& target : fmaskState.colors) {
target.samples = 8;
target.fmaskAddress = 0x40000;
target.fmaskPipeBankXor = 0x2300;
}
const auto fmaskRecipe = MakeGraphicsPipelineRecipe(fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
Require(fmaskRecipe.state.color.fmaskAddress == 0 && fmaskRecipe.state.color.fmaskPipeBankXor == 0, "the canonical state kept the primary target's FMASK location");
for (const auto& canonicalColor : fmaskRecipe.state.colors) {
Require(canonicalColor.fmaskAddress == 0 && canonicalColor.fmaskPipeBankXor == 0, "the canonical state kept an attachment's FMASK location");
}
const auto fmaskBytes = EncodeGraphicsPipelineRecipe(fmaskRecipe);
GraphicsPipelineRecipe fmaskDecoded;
Require(DecodeGraphicsPipelineRecipe(fmaskBytes, fmaskDecoded) && EncodeGraphicsPipelineRecipe(fmaskDecoded) == fmaskBytes, "the FMASK pipeline recipe did not round-trip byte for byte");
std::vector<std::byte> fmaskKey, fmaskReplayedKey;
BuildPipelineStoreKey(fmaskKey, context, fmaskState, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
BuildPipelineStoreKey(fmaskReplayedKey, context, fmaskDecoded.state, fmaskDecoded.vertexInput, layouts.View(), shaders, fmaskDecoded.attachmentLayout);
Require(fmaskKey != original && fmaskKey == fmaskReplayedKey, "the decoded FMASK state keys another pipeline");
auto relocatedFmask = fmaskState;
relocatedFmask.color.fmaskAddress += 0x10000;
relocatedFmask.color.fmaskPipeBankXor ^= 0x100;
for (auto& target : relocatedFmask.colors) {
target.fmaskAddress += 0x20000;
target.fmaskPipeBankXor ^= 0x200;
}
std::vector<std::byte> relocatedFmaskKey;
BuildPipelineStoreKey(relocatedFmaskKey, context, relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL);
Require(relocatedFmaskKey == fmaskKey, "the FMASK resource location changed the pipeline key");
Require(EncodeGraphicsPipelineRecipe(MakeGraphicsPipelineRecipe(relocatedFmask, input, layouts.View(), shaders, VK_IMAGE_LAYOUT_GENERAL)) == fmaskBytes, "the FMASK resource location changed the journal recipe");
auto gridState = state;
gridState.samples = 2;
gridState.customSampleLocations = true;
@@ -123,6 +123,15 @@ void RunGuestTextureResourceTests() {
fmask.dstSelY = fmask.dstSelZ = fmask.dstSelW = 0u;
const auto fmaskResource = DecodeTextureResource(pack(fmask));
Require(fmaskResource.format == 20u && fmaskResource.dimension == TextureDimension::k2D && fmaskResource.tileMode == TextureTileMode::kZ64KBX, "uncompressed 32-bit FMASK did not use an integer Z surface");
auto compressedFmask = fmask;
compressedFmask.metaCompress = compressedFmask.metaPipeAligned = true;
compressedFmask.metaAddr = 0x123456789aull;
const auto compressedResource = DecodeTextureResource(pack(compressedFmask));
Require(compressedResource.fmaskCmaskAddress == compressedFmask.metaAddr << 8u && compressedResource.dccAddress == 0u && compressedResource.dccPipeAligned, "compressed FMASK CMASK was interpreted as color DCC");
compressedFmask.metaPipeAligned = false;
rejectFields(compressedFmask, "FMASK CMASK metadata must be pipe aligned");
compressedFmask.metaPipeAligned = compressedFmask.writeCompress = true;
rejectFields(compressedFmask, "FMASK CMASK compression is not implemented");
Fields fmaskArray = fmask;
fmaskArray.typeRaw = 13u;
fmaskArray.depth = 2u;
@@ -4804,11 +4804,14 @@ void idlePublishWaitTests(const Device& device, Recorder& recorder) {
recorder.Sync();
Require(words[2 * unit] == 0x44 && words[4 * unit - 1] == 0x45, "a CPU store made after the idle flush returned was overwritten by its publish");
Require(words[2 * unit + 1] == 0x22 && words[3 * unit - 1] == 0x22 && words[3 * unit] == 0x33 && words[1 * unit] == 0x11 && words[4 * unit] == 0x11, "the idle publish's bytes are wrong");
words[5 * unit] = 0x11;
CollectWritesUncached(address, bytes);
retile(address + 5 * unit, 0x55);
StorageTexture::FlushAllPending("label");
Require(recorder.PendingWriteOverlaps(address + 5 * unit, unit), "a publish of a flush other than the idle one was waited");
const bool pending = recorder.PendingWriteOverlaps(address + 5 * unit, unit);
recorder.Sync();
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land");
Require(words[5 * unit] == 0x55 && words[6 * unit - 1] == 0x55, "the label flush's publish did not land: the unit read as written after its retile, so the publish dropped it as stale");
Require(pending, "a publish of a flush other than the idle one was waited");
std::cout << "idle publish wait verified\n";
}
@@ -0,0 +1,19 @@
#ifndef CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
#define CORE_LIBS_PRX_LIBSCEAGCDRIVER_TESTS_SAVEDEXECMASKLOOP_HPP
#include <cstdint>
#include <vector>
namespace AgcTest {
inline std::vector<std::uint32_t> SavedExecMaskLoop(std::uint32_t rounds) {
std::vector<std::uint32_t> code{0xbea2047eu, 0xbea40380u};
for (std::uint32_t step = 0; step < rounds; ++step) code.insert(code.end(), {0xbefe0422u, 0x7c080080u, 0x8aa26a7eu});
const auto branch = 0xbf850000u | static_cast<std::uint16_t>(2 - static_cast<int>(code.size() + 3));
code.insert(code.end(), {0x80248124u, 0xbf0a8424u, branch, 0xbefe0422u});
return code;
}
}
#endif
@@ -20,6 +20,7 @@
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "SpirvBackend/SpirvAnalysis.hpp"
#include "SavedExecMaskLoop.hpp"
#if ANYPS5_ENABLE_SPIRV_TOOLS
#include "SpirvBackend/SpirvOptimizer.hpp"
#endif
@@ -4088,6 +4089,18 @@ void verifyHalfWaveReduction() {
{0u, 0x38000000u, 0x88000000u, spv::OpGroupNonUniformBitwiseOr, "half-wave reduction: the wave64 OR scan did not read lane 31 as a subgroup OR on 32 lanes"},
}};
for (const auto& reduction : scans) require(reductions(pixel(scan(reduction.identity, reduction.vector, reduction.scalar), 32u, true), reduction.reduce) == 1u, reduction.message);
auto loopScan = scan(0xffffffffu, 0x26000000u, 0x83800000u);
const auto savedMaskLoop = AgcTest::SavedExecMaskLoop(8u);
loopScan.insert(loopScan.begin(), savedMaskLoop.begin(), savedMaskLoop.end());
auto helperLoop = loopScan;
helperLoop[0] = 0xbea204c1u;
expectFailure([&] { static_cast<void>(pixel(helperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: a loop initialized with helper lanes was proven to exclude them");
auto upperHelperLoop = loopScan;
upperHelperLoop[0] = 0xbea2037eu;
upperHelperLoop.insert(upperHelperLoop.begin() + 1, 0xbea303c1u);
expectFailure([&] { static_cast<void>(pixel(upperHelperLoop, 32u, true)); }, "v_readlane_b32 of lane 63 is outside the 32-lane host subgroup", "half-wave reduction: the low-word loop proof was reused for helper-inclusive upper lanes");
require(reductions(pixel(loopScan, 32u, true), spv::OpGroupNonUniformUMin) == 1u, "half-wave reduction: a loop-carried saved EXEC mask exhausted the entry-lane proof on 32 lanes");
require(halves(pixel(loopScan, 64u, true), spv::OpGroupNonUniformUMin, 0xffffffffu), "half-wave reduction: a loop-carried saved EXEC mask did not preserve the two half-wave UMin reductions");
auto comparedScan = scan(0u, 0x38000000u, 0x88000000u);
*std::find(comparedScan.begin(), comparedScan.end(), 0xbe98047eu) = 0x8a986a7eu;
comparedScan.insert(comparedScan.begin(), 0x7c080080u);
@@ -1,11 +1,16 @@
#include "IntermediateRepresentation/IrBuilder.hpp"
#include "IntermediateRepresentation/IrProgram.hpp"
#include "Optimization/ShaderStageInputInfo.hpp"
#include "Optimization/TessellationLowering.hpp"
#include "SpirvBackend/SpirvEmitter.hpp"
#include <spirv/unified1/spirv.hpp>
#include <cstdint>
#include <cstdio>
#include <exception>
#include <functional>
#include <stdexcept>
#include <string>
#include <unordered_map>
using namespace ShaderRecompiler;
@@ -135,6 +140,52 @@ bool RefusedBecause(IrProgram& program, const char* reason) {
return !outcome.lowered && outcome.reason.find(reason) != std::string::npos;
}
std::uint32_t EmittedEvaluationControlPoint(std::uint32_t address) {
constexpr std::uint32_t ControlStride = 16u;
IrProgram program;
IrBuilder builder{program};
program.SetWaveSize(64);
program.Resources().stage = IrShaderStage::TessellationEvaluation;
program.Resources().srtPlanComplete = true;
program.Resources().resourceTrackingComplete = true;
program.Metadata().shaderInfoComplete = true;
program.Metadata().bindingLayoutComplete = true;
program.Metadata().tessellationControlStride = ControlStride;
auto& block = program.CreateBlock();
program.SetEntryBlock(block);
program.BlockOrder().push_back(&block);
BlockInfo info;
info.id = block.Id();
program.Metadata().blockInfo.push_back(info);
builder.SetInsertionPoint(block);
auto& read = builder.Emit(IrOpcode::GetTessellationAttribute, IrType::U32, {&builder.Constant(static_cast<std::uint32_t>(TessellationAttribute::EvaluationInput)), &builder.Constant(address), &builder.ConstantBool(true)});
static_cast<void>(builder.Emit(IrOpcode::ReferenceU32, IrType::Void, {&read}));
ShaderVertexInputInfo vertex;
vertex.tess.inputControlPoints = InputControlPoints;
vertex.tess.outputControlPoints = InputControlPoints;
vertex.tess.domain = 1u;
vertex.tess.partitioning = 2u;
vertex.tess.outputTopology = 2u;
ShaderStageInputInfo inputs;
inputs.vertex = &vertex;
CompiledBindingLayout bindings;
bindings.layout = program.Metadata().bindings;
SpirvTargetOptions target{};
target.vulkanVersion = 0x00401000;
target.spirvVersion = 0x00010300;
target.subgroupSize = 64;
const auto words = SpirvEmitter().Emit(program, inputs, bindings, target);
std::unordered_map<std::uint32_t, std::uint32_t> constants;
for (std::size_t at = 5; at < words.size(); at += words[at] >> 16u) {
const auto count = words[at] >> 16u;
if (count == 0u || at + count > words.size()) throw std::runtime_error("the domain program compiled to a malformed SPIR-V instruction");
const auto opcode = words[at] & 0xffffu;
if (opcode == spv::OpConstant && count == 4u) constants[words[at + 2u]] = words[at + 3u];
if (opcode == spv::OpAccessChain && count == 7u) return constants.at(words[at + 4u]);
}
throw std::runtime_error("the domain program reads no control point attribute");
}
bool run(const char* name, const std::function<bool()>& body) {
bool passed = false;
try {
@@ -206,5 +257,9 @@ int main() {
return patch->Opcode() == IrOpcode::IMul32 && patch->Argument(1)->Resolve()->ImmediateU32() == PatchStride;
});
passed &= run("a domain read at an immediate address past the last control point reads the last control point", [] {
return EmittedEvaluationControlPoint(InputControlPoints * 16u) == InputControlPoints - 1u;
});
return passed ? 0 : 1;
}
@@ -601,7 +601,8 @@ void CheckDrawFeedback() {
Require(device != nullptr, "native depth draw feedback requires Vulkan");
constexpr VkExtent2D extent{5u, 3u};
constexpr std::uint32_t samples = 4u;
TargetMemory memory(static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples))), metadata(65536u);
const auto depthBytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, 4u, samples));
TargetMemory memory(depthBytes), metadata(65536u);
std::memset(metadata.Data(), 0, 65536u);
DepthTarget depth{memory.Address(), 0u, extent, VK_FORMAT_D32_SFLOAT, 0.0f, 0u, metadata.Address(), false, samples};
depth.depthSwizzle = 24u;
@@ -634,6 +635,9 @@ void CheckDrawFeedback() {
const std::array<CompiledShader, 2> initial{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &producer, pixelOffset}}};
device->Draw(state, draw, initial);
device->WaitIdle();
std::array<std::byte, 1> probe{};
TargetRefuses([&] { AgcDriver::GuestMemory::Read(depth.address, probe); }, "sample-preserving guest publication");
Require(DepthSurfaceAt(depth.address), "a refused guest read retired a native depth surface a draw wrote");
reader.spirv = DepthFeedbackFragmentShader(binding, true);
reader.variantId = 3u;
const auto words = DepthSampleWords(depth);
@@ -650,7 +654,22 @@ void CheckDrawFeedback() {
const std::array<CompiledShader, 2> shaders{{{ShaderRecompiler::ShaderStage::Vertex, &vertex, 0u}, {ShaderRecompiler::ShaderStage::Fragment, &reader, pixelOffset}}};
TargetRefuses([&] { device->Draw(state, draw, shaders); }, "sampling a native multisampled depth target the draw attaches");
device->WaitIdle();
std::puts("Native depth feedback refused through the draw resource path");
TargetMemory testedMemory(depthBytes), testedMetadata(65536u);
std::memset(testedMetadata.Data(), 0, 65536u);
const auto guestByte = [](std::size_t offset) { return static_cast<std::byte>(offset * 7u + 3u); };
for (std::size_t offset = 0u; offset < depthBytes; ++offset) testedMemory.Data()[offset] = guestByte(offset);
auto tested = depth;
tested.address = testedMemory.Address();
tested.htileAddress = testedMetadata.Address();
state.depth = tested;
state.depthWrite = false;
device->Draw(state, draw, initial);
device->WaitIdle();
Require(DepthSurfaceAt(tested.address), "a depth-test-only draw left no resident native depth surface");
AgcDriver::GuestMemory::Read(tested.address + depthBytes - 1u, probe);
Require(probe[0] == guestByte(depthBytes - 1u), "a guest read after a depth-test-only draw did not return its guest bytes");
Require(!DepthSurfaceAt(tested.address), "a guest read left a native depth surface only depth-tested resident");
std::puts("Native depth feedback refused through the draw resource path; a written surface refused and a depth-tested one retired on guest read");
}
enum class ClearFault { None, PartialStore, CpuWrite, DepthWrite, BadMetadata, UnprovenDispatch, PendingWrite, Overlap, PossibleStore, CompletedPossibleStore, DriverDispatch, ImportWindowWrite };
@@ -732,6 +751,7 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
}
const auto view = DepthSurfaceView(context, target);
Require(DepthSurfaceView(context, target) == view, "native depth attachment did not reuse its image");
NoteDepthSurfaceWrite(context, target);
std::array<std::byte, 1> beforeSampling{};
TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address + bytes - 1u, beforeSampling); }, "sample-preserving guest publication");
TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy"); }, "sample-preserving guest publication");
@@ -944,6 +964,59 @@ void CheckDepthTarget(Context context, VkFormat format, std::uint32_t fragments,
std::printf("Native depth format=%u samples=%u clear=%u iterate256=%u: imported, rasterized twice, sampled and refused conflicts\n", static_cast<std::uint32_t>(format), fragments, one, iterate256);
}
void CheckUnwrittenGuestAccess(Context context, VkFormat format, std::uint32_t fragments) {
const VkExtent2D extent{5u, 3u};
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments));
const auto metadataBytes = static_cast<std::size_t>(HtileMetadataBytes(extent));
TargetMemory memory(bytes), metadata(65536u), unrelated(65536u);
const auto guestByte = [](std::size_t offset) { return static_cast<std::byte>(offset * 7u + 3u); };
for (std::size_t offset = 0u; offset < bytes; ++offset) memory.Data()[offset] = guestByte(offset);
std::memset(metadata.Data(), 0, metadataBytes);
DepthTarget target{memory.Address(), 0u, extent, format, 0.0f, 0u, metadata.Address(), false, fragments};
target.depthSwizzle = 24u;
target.iterate256 = target.htilePipeAligned = target.htileDepthOnly = true;
Recorder recorder(context);
recorder.Activate();
struct Cleanup {
Recorder& recorder;
Context context;
~Cleanup() { recorder.Sync(); ClearCachedTextures(context.device); ClearDepthSurfaces(context.device); }
} cleanup{recorder, context};
const auto resident = [&] { return HasResidentDepthGuards_nid_no_patch() && DepthSurfaceAt(target.address); };
auto view = DepthSurfaceView(context, target);
Require(view != VK_NULL_HANDLE && resident(), "unwritten native depth fixture has no resident owner");
auto otherDevice = context;
otherDevice.device = VK_NULL_HANDLE;
NoteDepthSurfaceWrite(otherDevice, target);
auto unattached = target;
unattached.address += 65536u;
NoteDepthSurfaceWrite(context, unattached);
std::array<std::byte, 1> probe{};
AgcDriver::GuestMemory::Read(unrelated.Address(), probe);
Require(resident(), "an unrelated guest read retired a native depth surface");
for (const bool readMetadata : {false, true}) {
if (readMetadata) RequireNoMultisampleTarget(target.htileAddress + metadataBytes - 1u, 1u, "a DMA copy");
else {
AgcDriver::GuestMemory::Read(target.address + bytes - 1u, probe);
Require(probe[0] == guestByte(bytes - 1u), "a guest read over an unwritten native depth surface did not return its guest bytes");
}
Require(!resident(), "a guest access left an unwritten native depth surface resident");
RequireNoResidentDepthRead_nid_no_patch(target.address, bytes);
RequireNoResidentDepthRead_nid_no_patch(target.htileAddress, metadataBytes);
recorder.Sync();
const auto rebound = DepthSurfaceView(context, target);
Require(rebound != VK_NULL_HANDLE && rebound != view && resident(), "rebinding after a guest access did not import a new native depth surface");
view = rebound;
}
NoteDepthSurfaceWrite(context, target);
TargetRefuses([&] { AgcDriver::GuestMemory::Read(target.address, probe); }, "sample-preserving guest publication");
TargetRefuses([&] { RequireNoMultisampleTarget(target.htileAddress, 1u, "a DMA copy"); }, "sample-preserving guest publication");
Require(resident(), "a refused guest access retired a written native depth surface");
for (std::size_t offset = 0u; offset < bytes; ++offset) Require(memory.Data()[offset] == guestByte(offset), "retiring an unwritten native depth surface changed its guest bytes");
Require(std::all_of(metadata.Data(), metadata.Data() + metadataBytes, [](std::byte value) { return value == std::byte{0}; }), "retiring an unwritten native depth surface changed its HTILE");
std::printf("Native depth format=%u samples=%u: guest access retired the unwritten surface and refused the written one\n", static_cast<std::uint32_t>(format), fragments);
}
void CheckHtileRouting(Context context, VkFormat format, std::uint32_t fragments) {
const VkExtent2D extent{5u, 3u};
const auto bytes = static_cast<std::size_t>(MultisampledDepthBytes_nid_no_patch(extent, format == VK_FORMAT_D16_UNORM ? 2u : 4u, fragments));
@@ -1132,6 +1205,7 @@ void CheckRetirement(Context context, VkFormat format, std::uint32_t samples, st
const auto words = DepthSampleWords(target);
const auto resource = DecodeTextureResource(words);
const auto firstView = DepthSurfaceView(context, target);
NoteDepthSurfaceWrite(context, target);
auto firstTexture = DepthSurfaceTexture(context, words, resource, VkComponentMapping{});
Require(firstTexture != nullptr, "retirement fixture has no native sampled view");
ColorTarget color{colorMemory.Address(), extent, VK_FORMAT_R8G8B8A8_UNORM, extent.width * extent.height * 4u, 0u};
@@ -1287,6 +1361,7 @@ int main(int argc, char** argv) {
for (const bool iterate256 : {true, false}) { CheckDepthTarget(context, format, samples, one, false, std::nullopt, ClearFault::None, lock, iterate256); ++checked; }
}
CheckHtileRouting(context, format, samples);
CheckUnwrittenGuestAccess(context, format, samples);
}
}
CheckEligibility(context);
@@ -45,6 +45,7 @@ constexpr std::uint32_t Height = 128;
constexpr std::uint32_t ScissorWidth = 128;
constexpr std::size_t BlockBytes = 65536;
constexpr std::uint32_t Float11_11_10Info = 0x4171cu;
constexpr std::uint32_t StandardOrderInfo = Float11_11_10Info & ~0x1800u;
constexpr std::uint32_t TiledAttrib3 = 0x4dc6c000u;
constexpr std::uint32_t LinearAttrib3 = 0x09000000u;
constexpr std::uint32_t Float32Export = 9;
@@ -58,7 +59,7 @@ constexpr std::array<std::array<float, 4>, 3> Triangle{{
{-1.0f, -1.0f, 0.5f, 1.0f}, {3.0f, -1.0f, 0.5f, 1.0f}, {-1.0f, 3.0f, 0.5f, 1.0f}
}};
alignas(256) std::array<std::array<std::uint32_t, 64>, 32> Programs{};
alignas(256) std::array<std::array<std::uint32_t, 64>, 64> Programs{};
std::size_t programCount = 0;
std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) {
@@ -78,7 +79,8 @@ std::uint32_t SmallFloat(float value, std::uint32_t mantissaBits) {
return (static_cast<std::uint32_t>(exponent - 1 + 15) << mantissaBits) | static_cast<std::uint32_t>(std::floor((fraction * 2.0 - 1.0) * scale));
}
std::uint32_t Packed(std::array<float, 3> rgb) {
std::uint32_t Packed(std::array<float, 3> rgb, bool standardOrder) {
if (standardOrder) return SmallFloat(rgb[0], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[2], 6) << 21u);
return SmallFloat(rgb[2], 5) | (SmallFloat(rgb[1], 6) << 10u) | (SmallFloat(rgb[0], 6) << 21u);
}
@@ -170,17 +172,17 @@ struct Block {
std::uint8_t* data = nullptr;
};
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3) {
AgcDriver::Graphics::ColorTarget DecodeTarget(const Block& block, std::uint32_t attrib3, bool standardOrder) {
AgcDriver::Registers cx;
cx[0x318] = static_cast<std::uint32_t>(block.Address() >> 8u);
cx[0x31b] = 0;
cx[0x31c] = Float11_11_10Info;
cx[0x31c] = standardOrder ? StandardOrderInfo : Float11_11_10Info;
cx[0x31d] = 0;
cx[0x3b0] = ((Width - 1u) << 14u) | (Height - 1u);
cx[0x3b8] = attrib3;
cx[0x390] = static_cast<std::uint32_t>(block.Address() >> 40u);
auto color = AgcDriver::Graphics::DecodeColorBuffer(cx, 0);
Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == 0xc6u && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface");
Require(color.format == VK_FORMAT_R32_UINT && color.elementBytes == 4 && color.componentMapping == (standardOrder ? 0xe4u : 0xc6u) && color.packing == ShaderRecompiler::ColorExportPacking::Float11_11_10 && color.bytes == block.bytes, "the 11_11_10 float target did not decode as a packed R32_UINT surface");
return color;
}
@@ -290,10 +292,10 @@ std::string Describe(std::array<float, 3> rgb) {
return text;
}
void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind) {
void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t attrib3, ColorTileMode mode, const char* kind, bool standardOrder) {
const ColorTargetLayout layout(Width, Height, mode, 4);
Require(layout.Bytes() == block.bytes, "the test surface size disagrees with its layout");
const auto color = DecodeTarget(block, attrib3);
const auto color = DecodeTarget(block, attrib3, standardOrder);
auto before = Fill(block);
const float nan = std::numeric_limits<float>::quiet_NaN();
const float infinity = std::numeric_limits<float>::infinity();
@@ -311,12 +313,12 @@ void TargetTests(AgcDriver::VulkanDevice& device, Block& block, std::uint32_t at
}};
for (const auto& rgb : cases) {
Draw(device, color, Float32Export, Float32Program(rgb));
ExpectSurface(device, block, layout, before, Packed(rgb), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target");
ExpectSurface(device, block, layout, before, Packed(rgb, standardOrder), std::string("a 32_ABGR export of ") + Describe(rgb) + " into a " + kind + " 11_11_10 float target");
before = ReadBack(device, block);
}
const std::array<float, 3> halves{0.5f, 0.75f, 0.125f};
Draw(device, color, Float16Export, Float16Program(halves));
ExpectSurface(device, block, layout, before, Packed(halves), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target");
ExpectSurface(device, block, layout, before, Packed(halves, standardOrder), std::string("an FP16_ABGR export of ") + Describe(halves) + " into a " + kind + " 11_11_10 float target");
}
}
@@ -327,8 +329,10 @@ int main() {
if (!device) return VulkanTestSkipped;
Block tiled(ColorTargetLayout(Width, Height, ColorTileMode::RenderTarget, 4).Bytes());
Block linear(ColorTargetLayout(Width, Height, ColorTileMode::Linear, 4).Bytes());
TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, "tiled");
TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, "linear");
for (const bool standardOrder : {false, true}) {
TargetTests(*device, tiled, TiledAttrib3, ColorTileMode::RenderTarget, standardOrder ? "tiled standard-order" : "tiled", standardOrder);
TargetTests(*device, linear, LinearAttrib3, ColorTileMode::Linear, standardOrder ? "linear standard-order" : "linear", standardOrder);
}
std::puts("11_11_10 float color target tests passed");
return 0;
} catch (const std::exception& error) {
@@ -11,6 +11,7 @@
#include <cstdlib>
#include <future>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
@@ -75,16 +76,29 @@ void AppendLabel(std::vector<std::uint32_t>& words, std::uint32_t* target, std::
}
struct Blocker {
std::atomic<bool> release{false};
struct State {
std::atomic<bool> release{false};
std::atomic<bool> done{false};
};
std::shared_ptr<State> state = std::make_shared<State>();
std::uint32_t* target = nullptr;
~Blocker() { release = true; }
~Blocker() { Release(); }
void Release() { state->release = true; }
void Enqueue(std::vector<DrawPipeline::Range> writes = {}) {
DrawPipeline::Queue0().Enqueue([this] {
while (!release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
DrawPipeline::Queue0().Enqueue([state = state, target = target] {
while (!state->release.load()) std::this_thread::sleep_for(std::chrono::milliseconds(1));
if (target != nullptr) std::atomic_ref<std::uint32_t>(*target).store(1);
std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex());
{ std::lock_guard gpuLock(AgcDriver::GuestMemory::GpuMutex()); }
state->done = true;
}, std::move(writes));
}
void Join() const {
const auto start = std::chrono::steady_clock::now();
while (!state->done.load()) {
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the blocker never ran");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
};
void SubmitBehind(Blocker& blocker, std::vector<std::uint32_t>& words) {
@@ -107,7 +121,7 @@ void ReleaseWhen(Blocker& blocker, std::size_t reached, std::size_t passed) {
Require(now - start < std::chrono::seconds(20), "the queue worker never reached the fill");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
blocker.release = true;
blocker.Release();
}
void Finish() {
@@ -118,6 +132,11 @@ void Finish() {
std::_Exit(1);
}
idle.get();
const auto start = std::chrono::steady_clock::now();
while (DrawPipeline::Queue0().Outstanding() != 0) {
Require(std::chrono::steady_clock::now() - start < std::chrono::seconds(20), "the draw pipeline kept an item after the queue went idle");
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Output.data()), sizeof(Output));
AgcDriver::GuestMemory::FlushGpuWrites(reinterpret_cast<std::uintptr_t>(Scratch.data()), sizeof(Scratch));
}
@@ -135,6 +154,7 @@ void InputsAfterQueuedLabels() {
SubmitBehind(blocker, words);
ReleaseWhen(blocker, 4, 5);
Finish();
blocker.Join();
for (std::size_t i = 0; i < Output.size(); ++i) {
const auto expected = i < 128 ? 0xababababu : 0xcafebabeu;
Require(Output[i] == expected, "a fill read its control or pattern before the queued labels that write them, at " + std::to_string(i));
@@ -156,6 +176,7 @@ void FillIntoFullPipeline() {
SubmitBehind(blocker, words);
ReleaseWhen(blocker, depth, depth + 1);
Finish();
blocker.Join();
for (std::size_t i = 0; i + 2 < depth; ++i) Require(Labels[i] == i + 1, "a queued label was not stored");
for (std::size_t i = 0; i < Output.size(); ++i) Require(Output[i] == (i < 256 ? 0x5a5a5a5au : 0xcafebabeu), "a fill behind a full draw pipeline did not land at " + std::to_string(i));
}
@@ -180,8 +201,9 @@ void SatisfiedWaitKeepsUnrelatedQueuedWork() {
while (DrawPipeline::Queue0().Outstanding() < 2 && std::chrono::steady_clock::now() < deadline)
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const bool keptQueued = DrawPipeline::Queue0().Outstanding() >= 2;
blocker.release = true;
blocker.Release();
Finish();
blocker.Join();
Require(keptQueued, "a satisfied WAIT_REG_MEM drained unrelated queued work");
Require(marker == 1, "a label after a satisfied WAIT_REG_MEM did not execute");
}
@@ -211,11 +233,12 @@ void SatisfiedWaitDrainsOverlappingQueuedWork() {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
const bool drained = DrawPipeline::DrainRequestsByOpcode(0x3c) > drainsBefore;
const bool labelQueuedEarly = DrawPipeline::Queue0().Outstanding() >= 2;
blocker.release = true;
blocker.Release();
Require(submitted.wait_for(std::chrono::seconds(20)) == std::future_status::ready,
"WAIT_REG_MEM submission did not finish after the overlapping write");
submitted.get();
Finish();
blocker.Join();
Require(drained && !labelQueuedEarly, "a satisfied WAIT_REG_MEM skipped an overlapping queued write");
Require(marker == 1, "a label after an overlapping WAIT_REG_MEM did not execute");
}
@@ -9,6 +9,9 @@
#include "prx/libSceAgcDriver/Graphics/include/ShaderInputState.hpp"
#include "prx/libSceAgcDriver/Graphics/include/State.hpp"
#include "prx/libc/include/GuestAllocations.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/GuestWriteWatch.hpp"
#include "prx/libSceAgcDriver/Graphics/include/ShaderResources.hpp"
#include "Recompiler.hpp"
#include "Optimization/ResourceProgram.hpp"
#include "VulkanTestDevice.hpp"
@@ -17,6 +20,8 @@
#define NOMINMAX
#endif
#include <windows.h>
#else
#include <sys/mman.h>
#endif
#include <algorithm>
#include <array>
@@ -120,14 +125,31 @@ constexpr std::array<std::array<float, 4>, 3> Triangle{{
alignas(256) std::array<std::uint32_t, Threads * Words> Buffer{};
struct Block {
explicit Block(std::size_t bytes) : bytes(bytes) {
explicit Block(std::size_t bytes, bool watched = false) : bytes(bytes), watched(watched) {
#ifdef _WIN32
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (watched) {
data = static_cast<std::uint8_t*>(GuestArena::GuestArenaAllocate_nid_postfix(bytes, BlockBytes));
GuestArena::GuestArenaCommit_nid_postfix(data, bytes, PAGE_READWRITE, bytes);
} else {
data = static_cast<std::uint8_t*>(VirtualAlloc(nullptr, bytes, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
}
#else
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
if (watched) {
void* raw = mmap(nullptr, bytes + BlockBytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
Require(raw != MAP_FAILED, "cannot map the color target");
const auto begin = reinterpret_cast<std::uintptr_t>(raw);
const auto aligned = (begin + BlockBytes - 1) & ~(static_cast<std::uintptr_t>(BlockBytes) - 1);
if (aligned != begin) munmap(raw, aligned - begin);
if (aligned + bytes != begin + bytes + BlockBytes) munmap(reinterpret_cast<void*>(aligned + bytes), begin + BlockBytes - aligned);
data = reinterpret_cast<std::uint8_t*>(aligned);
GuestWriteWatch::GuestWriteWatchRegister_nid_postfix(data, bytes);
} else {
data = static_cast<std::uint8_t*>(std::aligned_alloc(BlockBytes, bytes));
}
#endif
Require(data != nullptr, "cannot allocate the multisampled color target");
Require(data != nullptr, "cannot allocate the color target");
std::memset(data, 0, bytes);
Require(!watched || AgcDriver::GuestMemory::Watched(Address(), bytes), "the color target is not write-watched");
GuestAllocations::Mutation mutation;
mutation.Add(data, bytes, true, true, true);
}
@@ -137,15 +159,26 @@ struct Block {
mutation.Remove(data);
}
#ifdef _WIN32
VirtualFree(data, 0, MEM_RELEASE);
if (watched) {
GuestArena::GuestArenaReset_nid_postfix(data, bytes);
GuestArena::GuestArenaRelease_nid_postfix(data, bytes);
} else {
VirtualFree(data, 0, MEM_RELEASE);
}
#else
std::free(data);
if (watched) {
munmap(data, bytes);
GuestWriteWatch::GuestWriteWatchUnregister_nid_postfix(data, bytes);
} else {
std::free(data);
}
#endif
}
Block(const Block&) = delete;
Block& operator=(const Block&) = delete;
std::uint64_t Address() const { return reinterpret_cast<std::uintptr_t>(data); }
std::size_t bytes;
bool watched;
std::uint8_t* data = nullptr;
};
@@ -214,7 +247,7 @@ AgcDriver::Graphics::State TargetState(const AgcDriver::Graphics::ColorTarget& c
state.blends = {blend};
state.blend = blend;
state.blendConstants = {};
state.samples = Samples;
state.samples = color.samples;
return state;
}
@@ -349,19 +382,19 @@ void RequireStoreRefused(AgcDriver::VulkanDevice& device, const std::array<std::
Require(refusal.find(expected) != std::string::npos, what + " was not refused: " + refusal);
}
std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, bool arrayed) {
std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, bool arrayed, std::uint32_t fragments) {
std::vector<std::uint32_t> userData;
const auto program = CompileLoad(device, texture, userData, arrayed);
std::vector<std::array<float, 4>> loaded(Side * Side * Samples);
std::vector<std::array<float, 4>> loaded(Side * Side * fragments);
const auto codeAddress = reinterpret_cast<std::uintptr_t>((arrayed ? ArrayLoadCode : LoadCode).data());
for (std::uint32_t first = 0; first < loaded.size(); first += Threads) {
Buffer.fill(0xdeadbeefu);
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
const auto index = first + tid;
const auto pixel = index / Samples;
const auto pixel = index / fragments;
const std::array<std::uint32_t, 6> words = arrayed
? std::array<std::uint32_t, 6>{0u, 0u, pixel % Side, pixel / Side, 0u, index % Samples}
: std::array<std::uint32_t, 6>{0u, 0u, 0u, pixel % Side, pixel / Side, index % Samples};
? std::array<std::uint32_t, 6>{0u, 0u, pixel % Side, pixel / Side, 0u, index % fragments}
: std::array<std::uint32_t, 6>{0u, 0u, 0u, pixel % Side, pixel / Side, index % fragments};
std::copy(words.begin(), words.end(), &Buffer[tid * Words]);
}
device.Dispatch(program, 1, 1, 1, {}, codeAddress);
@@ -373,9 +406,9 @@ std::vector<std::array<float, 4>> RunLoad(AgcDriver::VulkanDevice& device, const
return loaded;
}
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture) {
auto loaded = RunLoad(device, texture, false);
const auto loadedArray = RunLoad(device, texture, true);
std::vector<std::array<float, 4>> LoadSamples(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& texture, std::uint32_t fragments = Samples) {
auto loaded = RunLoad(device, texture, false, fragments);
const auto loadedArray = RunLoad(device, texture, true, fragments);
Require(loadedArray == loaded, "Dim2DMsaaArray image_load disagreed with Dim2DMsaa image_load on the multisampled color target");
return loaded;
}
@@ -643,12 +676,210 @@ int NativeVertexInput(std::unique_ptr<AgcDriver::VulkanDevice>& device) {
return 0;
}
alignas(256) constexpr std::array<std::uint32_t, 10> NativeFmaskCode{0x34020084u, 0xe0381000u, 0x80000201u, 0xbf8c3f70u, 0xf0001f08u, 0x00020602u, 0xbf8c3f70u, 0xe0781000u, 0x80010601u, 0xbf810000u};
alignas(256) std::array<std::uint32_t, Threads * 4u> NativeFmaskInput{};
alignas(256) std::array<std::uint32_t, Threads * 4u + 4u> NativeFmaskOutput{};
std::array<std::uint32_t, 8> NativeFmaskDescriptor(const Block& fmask, const Block& cmask, std::uint32_t pipeXor) {
const auto address = fmask.Address() | pipeXor;
const auto metadata = cmask.Address();
return {static_cast<std::uint32_t>(address >> 8u), static_cast<std::uint32_t>(address >> 40u) | (166u << 20u) | (((Side - 1u) & 3u) << 30u), ((Side - 1u) >> 2u) | ((Side - 1u) << 14u), 4u | (1u << 3u) | (4u << 6u) | (24u << 20u) | (9u << 28u), 0u, 0u, (1u << 19u) | (1u << 21u) | (static_cast<std::uint32_t>(metadata >> 8u) << 24u), static_cast<std::uint32_t>(metadata >> 16u)};
}
ShaderRecompiler::RecompileResult CompileNativeFmask(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& image, bool registration) {
std::vector<std::uint32_t> userData(16u);
const auto buffer = [](const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return std::array<std::uint32_t, 4>{static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, bytes, 0x31016facu};
};
const auto input = buffer(NativeFmaskInput.data(), sizeof(NativeFmaskInput));
const auto output = buffer(NativeFmaskOutput.data(), Threads * 16u);
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4u);
std::copy(image.begin(), image.end(), userData.begin() + 8u);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data()), std::as_bytes(std::span(NativeFmaskCode))}}};
const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1u, 1u}, 0u, {false, false, false}, false, 1u};
auto target = device.Target();
target.registrationModules = registration;
ShaderRecompiler::RecompileRequest request{{ShaderStage::Compute, reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data()), NativeFmaskCode, 0u, {}}, {32u, 0u, userData, compute, std::nullopt, std::nullopt, memory}, target, {0u, 0u, 0u, 128u}};
request.useCache = false;
return ShaderRecompiler::Recompile(request);
}
void DispatchNativeFmask(AgcDriver::VulkanDevice& device, const std::array<std::uint32_t, 8>& image, bool registration) {
device.Dispatch(CompileNativeFmask(device, image, registration), 1u, 1u, 1u);
device.WaitIdle();
}
void NativeFmask(std::unique_ptr<AgcDriver::VulkanDevice>& device) {
using namespace AgcDriver::Graphics;
Block colorMemory(4u * BlockBytes, true);
Block dcc(BlockBytes, true);
Block cmask(BlockBytes, true);
Block fmask(BlockBytes, true);
Block unrelated(BlockBytes, true);
struct ReleaseDevice {
std::unique_ptr<AgcDriver::VulkanDevice>& value;
~ReleaseDevice() { value.reset(); }
} releaseDevice{device};
constexpr std::uint32_t sentinel = 0xdeadbeefu;
for (const auto pipeXor : {0u, 0xf00u}) {
std::memset(colorMemory.data, 0, colorMemory.bytes);
std::memset(dcc.data, 0, dcc.bytes);
std::memset(cmask.data, 0xcc, cmask.bytes);
std::memset(fmask.data, 0x11, fmask.bytes);
AgcDriver::Registers cx{{0x318, static_cast<std::uint32_t>(colorMemory.Address() >> 8u)}, {0x31b, 0u}, {0x31c, Float16x4Info | 0x10006000u}, {0x31d, (3u << 12u) | (3u << 15u)}, {0x31e, 0u}, {0x31f, static_cast<std::uint32_t>(cmask.Address() >> 8u)}, {0x321, static_cast<std::uint32_t>((fmask.Address() | pipeXor) >> 8u)}, {0x325, static_cast<std::uint32_t>(dcc.Address() >> 8u)}, {0x390, static_cast<std::uint32_t>(colorMemory.Address() >> 40u)}, {0x398, static_cast<std::uint32_t>(cmask.Address() >> 40u)}, {0x3a0, static_cast<std::uint32_t>(fmask.Address() >> 40u)}, {0x3a8, static_cast<std::uint32_t>(dcc.Address() >> 40u)}, {0x3b0, ((Side - 1u) << 14u) | (Side - 1u)}, {0x3b8, TiledAttrib3}};
const auto color = DecodeColorBuffer(cx, 0u);
Require(color.samples == 8u && color.cmaskAddress == cmask.Address(), "native FMASK fixture lost its decoded target association");
std::array<std::array<float, 4>, 8> expected{};
for (std::uint32_t sample = 0u; sample < 8u; ++sample) {
expected[sample] = {static_cast<float>(sample + 1u) / 8.0f, 0.25f, 0.5f, 1.0f};
auto pixel = PixelCode;
pixel[1] = std::bit_cast<std::uint32_t>(expected[sample][0]);
pixel[3] = std::bit_cast<std::uint32_t>(expected[sample][1]);
pixel[5] = std::bit_cast<std::uint32_t>(expected[sample][2]);
pixel[7] = std::bit_cast<std::uint32_t>(expected[sample][3]);
auto state = TargetState(color);
state.quadSampleMask.fill(static_cast<std::uint16_t>(1u << sample));
Draw(*device, state, 0u, 2u, pixel);
}
const auto native = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u);
std::vector<std::uint32_t> covered;
for (std::uint32_t pixel = 0; pixel < Side * Side; ++pixel) {
bool all = true;
for (std::size_t sample = 0; sample < 8u; ++sample) all = all && native[pixel * 8u + sample] == expected[sample];
if (all) covered.push_back(pixel);
}
Require(!covered.empty(), "native FMASK fixture did not render eight distinct sample colors");
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
const auto pixel = covered[lane % covered.size()];
NativeFmaskInput[lane * 4u] = pixel % Side;
NativeFmaskInput[lane * 4u + 1u] = pixel / Side;
NativeFmaskInput[lane * 4u + 2u] = 0u;
NativeFmaskInput[lane * 4u + 3u] = 0u;
}
const auto image = NativeFmaskDescriptor(fmask, cmask, pipeXor);
const auto check = [&] {
for (std::uint32_t lane = 0; lane < Threads; ++lane) {
const std::array<std::uint32_t, 4> mapping{0x76543210u, 1u, 0x76543210u, 0u};
for (std::size_t component = 0; component < 4u; ++component) Require(NativeFmaskOutput[lane * 4u + component] == mapping[component], "native FMASK load did not preserve its expanded sample indices and selectors");
for (std::uint32_t sample = 0; sample < 8u; ++sample) {
const auto fragment = NativeFmaskOutput[lane * 4u] >> (4u * sample) & 15u;
const auto pixel = NativeFmaskInput[lane * 4u + 1u] * Side + NativeFmaskInput[lane * 4u];
Require(native[pixel * 8u + fragment] == expected[sample], "native FMASK remapped a sampled color to another fragment");
}
}
Require(std::all_of(NativeFmaskOutput.begin() + Threads * 4u, NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "native FMASK wrote beyond its output");
};
for (const auto registration : {false, true}) {
for (std::uint32_t repeat = 0; repeat < 2u; ++repeat) {
NativeFmaskOutput.fill(sentinel);
DispatchNativeFmask(*device, image, registration);
check();
}
const auto program = CompileNativeFmask(*device, image, registration);
const auto codeAddress = reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data());
std::shared_ptr<const AgcDriver::Recipe> recipe;
NativeFmaskOutput.fill(sentinel);
device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, nullptr, &recipe);
device->WaitIdle();
check();
Require(recipe == nullptr, "native FMASK emitted a reusable recipe without an ownership proof");
const auto prepared = device->PrepareDispatch(program, {}, Threads, codeAddress);
Require(prepared != nullptr, "native FMASK did not prepare a deferred dispatch");
NativeFmaskOutput.fill(sentinel);
device->Dispatch(program, 1u, 1u, 1u, {}, codeAddress, prepared);
device->WaitIdle();
check();
}
Require(LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u) == native, "FMASK sampling changed the native samples");
Require(std::all_of(fmask.data, fmask.data + fmask.bytes, [](auto byte) { return byte == 0x11u; }) && std::all_of(cmask.data, cmask.data + cmask.bytes, [](auto byte) { return byte == 0xccu; }), "native FMASK sampling published or changed guest metadata");
const auto refused = [&](const auto& descriptor, std::string_view reason) {
NativeFmaskOutput.fill(sentinel);
std::string error;
try { DispatchNativeFmask(*device, descriptor, false); } catch (const std::runtime_error& e) { error = e.what(); }
Require(error.find(reason) != std::string::npos, "native FMASK missing refusal: " + error);
Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [sentinel](auto word) { return word == sentinel; }), "rejected native FMASK wrote output");
};
refused(NativeFmaskDescriptor(unrelated, cmask, pipeXor), "descriptor does not match");
refused(NativeFmaskDescriptor(fmask, unrelated, pipeXor), "descriptor does not match");
refused(NativeFmaskDescriptor(fmask, cmask, pipeXor ^ 0x100u), "descriptor does not match");
auto resized = image;
resized[2] += 1u;
refused(resized, "extent or subresource does not match");
const auto deferredProgram = CompileNativeFmask(*device, image, false);
const auto codeAddress = reinterpret_cast<std::uintptr_t>(NativeFmaskCode.data());
const auto preparedBeforeStore = device->PrepareDispatch(deferredProgram, {}, Threads, codeAddress);
Require(preparedBeforeStore != nullptr, "native FMASK did not prepare its metadata invalidation probe");
std::array<std::byte, 4> same{};
same.fill(std::byte{0x11});
AgcDriver::GuestMemory::Write(fmask.Address(), same);
refused(image, "writes to a resident native FMASK");
NativeFmaskOutput.fill(sentinel);
std::string deferredError;
try {
device->Dispatch(deferredProgram, 1u, 1u, 1u, {}, codeAddress, preparedBeforeStore);
device->WaitIdle();
} catch (const std::runtime_error& error) {
deferredError = error.what();
}
Require(deferredError.find("writes to a resident native FMASK") != std::string::npos, "deferred native FMASK missed its metadata store: " + deferredError);
Require(std::all_of(NativeFmaskOutput.begin(), NativeFmaskOutput.end(), [](auto word) { return word == sentinel; }), "rejected deferred native FMASK wrote output");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
cx[0x31c] &= ~0x2000u;
cx[0x31f] = 0u;
cx[0x398] = 0u;
const auto withoutCmask = DecodeColorBuffer(cx, 0u);
Require(withoutCmask.cmaskAddress == 0u, "the no-CMASK control retained metadata");
Draw(*device, TargetState(withoutCmask));
const auto uncompressed = LoadSamples(*device, TargetDescriptor(colorMemory, 3u), 8u);
Require(std::find(uncompressed.begin(), uncompressed.end(), Drawn) != uncompressed.end(), "tracking native FMASK broke ordinary DCC rendering without CMASK");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
Draw(*device, TargetState(color));
auto recreated = color;
recreated.pipeBankXor ^= 0x4000u;
recreated.fmaskAddress = 0u;
recreated.fmaskPipeBankXor = 0u;
Draw(*device, TargetState(recreated));
std::array<std::byte, 4> formerFmask{};
formerFmask.fill(std::byte{0x22});
AgcDriver::GuestMemory::Write(fmask.Address(), formerFmask);
Draw(*device, TargetState(recreated));
auto recreatedDescriptor = TargetDescriptor(colorMemory, 3u);
recreatedDescriptor[0] |= recreated.pipeBankXor >> 8u;
const auto recreatedSamples = LoadSamples(*device, recreatedDescriptor, 8u);
Require(std::find(recreatedSamples.begin(), recreatedSamples.end(), Drawn) != recreatedSamples.end(), "former FMASK metadata affected the recreated native target");
device->SubmitRecorded(true);
device->WaitIdle();
ClearMultisampleTargets(device->Device());
ClearCachedTextures(device->Device());
std::printf("native FMASK: XOR=0x%x, eight distinct samples, both registration modes, cached reuse, association/extent/write refusals and unchanged guest metadata passed\n", pipeXor);
}
}
}
int main(int argc, char** argv) {
try {
auto device = OpenVulkanTestDevice();
if (!device) return VulkanTestSkipped;
if (argc == 2 && std::string_view(argv[1]) == "--native-fmask") {
{
const auto context = AgcDriver::VulkanDeviceTestAccess::ContextOf(*device);
if (!context.storageImageMultisample || (context.limits.framebufferColorSampleCounts & VK_SAMPLE_COUNT_8_BIT) == 0) {
std::puts("native FMASK skipped, the device does not render or store 8-sample color");
return VulkanTestSkipped;
}
}
NativeFmask(device);
return 0;
}
if (argc == 2 && std::string_view(argv[1]) == "--native-vertex-input") return NativeVertexInput(device);
Block block(4u * BlockBytes);
Block other(BlockBytes);
@@ -76,6 +76,41 @@ void ClosingJoinsPreparation() {
Require(ranHere && RegistrationPreparationThreads() == 0, "a closed preparation pool restarted after being joined");
}
void ClosingDropsQueuedPrebuilds() {
using namespace AgcDriver::DriverDetail;
std::promise<void> started;
auto running = started.get_future();
std::promise<void> gate;
auto opened = gate.get_future().share();
bool queuedRan = false;
SubmitLibraryPrebuild([&] { started.set_value(); opened.wait(); });
for (int index = 0; index < 3; ++index) SubmitLibraryPrebuild([&] { queuedRan = true; });
running.wait();
auto entering = std::make_shared<std::promise<void>>();
auto entered = entering->get_future();
auto awaited = std::make_shared<std::promise<void>>();
auto woken = awaited->get_future();
std::thread([entering, awaited] {
entering->set_value();
AwaitLibraryPrebuilds();
awaited->set_value();
}).detach();
entered.wait();
const bool waiting = woken.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout;
auto closing = std::async(std::launch::async, [] { CloseRegistrationPreparation(); });
const bool waited = closing.wait_for(std::chrono::milliseconds(20)) == std::future_status::timeout;
gate.set_value();
const bool returned = closing.wait_for(std::chrono::seconds(10)) == std::future_status::ready;
const bool wokeUp = woken.wait_for(std::chrono::seconds(10)) == std::future_status::ready;
closing.get();
Require(waiting, "a waiter for the library prebuilds returned while one was still running");
Require(waited, "closing returned while a library prebuild was still running");
Require(returned, "closing did not return once the running library prebuild ended");
Require(wokeUp, "a waiter for the library prebuilds did not return after closing dropped the queued ones");
Require(!queuedRan, "closing ran the queued library prebuilds");
Require(RegistrationPreparationThreads() == 0, "closing the library prebuilds left their thread running");
}
void StoppingJoinsCompareHelpers() {
using namespace AgcDriver::Graphics;
const std::vector<std::byte> first(std::size_t{2} << 20u, std::byte{1});
@@ -168,6 +203,7 @@ int main(int argc, char** argv) {
try {
const std::string_view mode = argc > 1 ? argv[1] : "";
if (mode == "--joined") ClosingJoinsPreparation();
else if (mode == "--queued-prebuilds") ClosingDropsQueuedPrebuilds();
else if (mode == "--compare-helpers") StoppingJoinsCompareHelpers();
else if (mode == "--draw-committer") ShutdownJoinsDrawCommitter();
else if (mode == "--shader-cache-writer") ShutdownJoinsShaderCacheWriter();
@@ -22,6 +22,7 @@
#include <span>
#include <string>
#include <tuple>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -946,6 +947,28 @@ void RequireMergedHullWithoutSubgroups(const ShaderRecompiler::SpirvTarget& targ
}
}
void RequireMergedHullConstantIndices(const ShaderRecompiler::SpirvTarget& target) {
std::uint32_t cursor = 0;
const auto hull = Compile(target, ShaderStage::TessellationControl, MergedHullCode, cursor, MergedTriangles);
const auto words = hull.spirv.Words();
std::unordered_set<std::uint32_t> constants;
std::unordered_set<std::uint32_t> folded;
for (std::size_t at = 5; at + 1u < words.size(); at += words[at] >> 16u) {
const auto count = words[at] >> 16u;
Require(count != 0u && at + count <= words.size(), "the merged hull program compiled to a malformed SPIR-V instruction");
const auto opcode = words[at] & 0xffffu;
if (opcode == spv::OpConstant && count >= 3u) constants.insert(words[at + 2u]);
const bool integer = opcode == spv::OpIAdd || opcode == spv::OpIMul || opcode == spv::OpUDiv || opcode == spv::OpUMod || opcode == spv::OpShiftRightLogical || opcode == spv::OpBitwiseAnd;
const auto operand = [&](std::uint32_t id) { return constants.contains(id) || folded.contains(id); };
if (integer && count == 5u && operand(words[at + 3u]) && operand(words[at + 4u])) folded.insert(words[at + 2u]);
if (opcode == spv::OpAccessChain) {
for (std::uint32_t index = 4u; index < count; ++index) {
Require(!folded.contains(words[at + index]), "the merged hull program indexes a tessellation attribute with arithmetic on constants instead of a constant, which AMD's Windows driver reads wrongly");
}
}
}
}
void RequireRegisteredMergedStages() {
const auto low = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 8u); };
const auto high = [](const auto& code) { return static_cast<std::uint32_t>(reinterpret_cast<std::uintptr_t>(code.data()) >> 40u); };
@@ -1003,6 +1026,7 @@ int main() {
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullOutsidePatch, "reads LDS outside its patch's control point records", MergedTriangles);
ExpectRefusal(target, ShaderStage::TessellationControl, MergedHullReadLane, "another lane's register", MergedTriangles);
RequireMergedHullWithoutSubgroups(target);
RequireMergedHullConstantIndices(target);
RequirePatches(*device, target, MergedHullCode, "merged hull program", MergedTriangles, MergedHullCode);
RequirePatches(*device, target, MergedHullRegisters, "merged hull program storing its local part's registers", MergedTriangles, MergedHullRegisters);
RequireCounterClockwise(*device, target);
+42 -28
View File
@@ -851,14 +851,15 @@ std::uint32_t ReadBigEndian(const std::uint8_t* data, std::size_t bytes) {
int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int parseOfl, AjmDecMp3ParseFrame* frame);
void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame) {
void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint32_t header, AjmDecMp3ParseFrame* frame, int parseOfl) {
const std::size_t end = std::min<std::size_t>(streamSize, frame->frame_size);
if (end <= 4) return;
const std::size_t available = end - 4;
const std::size_t sideInfoStart = 4 + (((header >> 16u) & 1u) == 0 ? 2u : 0u);
if (end <= sideInfoStart) return;
const std::size_t available = end - sideInfoStart;
std::size_t bit = 0;
const auto readBits = [&](std::uint32_t count) {
std::uint32_t value = 0;
for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[4 + bit / 8] >> (7 - bit % 8)) & 1u : 0u);
for (std::uint32_t i = 0; i < count; ++i, ++bit) value = (value << 1u) | (bit / 8 < available ? (stream[sideInfoStart + bit / 8] >> (7 - bit % 8)) & 1u : 0u);
return value;
};
const bool mpeg1 = ((header >> 19u) & 3u) == 3u;
@@ -872,11 +873,20 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint
}
}
bit += mainDataBits;
std::size_t at = 4 + std::min<std::size_t>((bit + 7) / 8, available);
std::size_t at = sideInfoStart + std::min<std::size_t>((bit + 7) / 8, available);
if (at + 8 <= end && (std::memcmp(stream + at, "Xing", 4) == 0 || std::memcmp(stream + at, "Info", 4) == 0)) {
const std::uint8_t flags = stream[at + 7];
std::size_t field = at + 8;
std::size_t tagAt = at;
if (tagAt + 8 > end || (std::memcmp(stream + tagAt, "Xing", 4) != 0 && std::memcmp(stream + tagAt, "Info", 4) != 0)) {
const std::size_t fixedOffset = mpeg1 ? (mono ? 21u : 36u) : (mono ? 13u : 21u);
tagAt = 0;
if (fixedOffset + 8 <= end && (std::memcmp(stream + fixedOffset, "Xing", 4) == 0 || std::memcmp(stream + fixedOffset, "Info", 4) == 0)) {
tagAt = fixedOffset;
}
}
if (tagAt != 0) {
const std::uint8_t flags = stream[tagAt + 7];
std::size_t field = tagAt + 8;
std::uint32_t frames = 0;
if (flags & 0x01u) {
if (field + 4 > end) return;
@@ -903,30 +913,34 @@ void ParseMp3Ofl(const std::uint8_t* stream, std::uint32_t streamSize, std::uint
frame->encoder_delay = frame->samples_per_channel + delay + 529;
frame->ofl_type = MP3_OFL_LAME;
}
} else if (at != 36 && end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0) {
NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (VBRI header outside MPEG-1 stereo)");
} else if (at + 26 <= end && std::memcmp(stream + at, "VBRI", 4) == 0) {
frame->encoder_delay = ReadBigEndian(stream + at + 6, 2);
return;
}
const std::size_t vbriAt = end >= 40 && std::memcmp(stream + 36, "VBRI", 4) == 0 ? 36 : at;
if (vbriAt + 26 <= end && std::memcmp(stream + vbriAt, "VBRI", 4) == 0) {
frame->encoder_delay = ReadBigEndian(stream + vbriAt + 6, 2);
frame->ofl_type = MP3_OFL_VBRI;
if (frame->frame_size <= streamSize) {
if (parseOfl == 1 && frame->frame_size <= streamSize) {
AjmDecMp3ParseFrame next{};
if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast<std::uint32_t>(frame->frame_size), 1, &next) == 0 && next.ofl_type == MP3_OFL_FGH) {
if (ParseMp3Header(stream + frame->frame_size, streamSize - static_cast<std::uint32_t>(frame->frame_size), 2, &next) == 0 && next.ofl_type == MP3_OFL_FGH) {
frame->encoder_delay += next.encoder_delay;
frame->total_samples = next.total_samples;
frame->ofl_type = MP3_OFL_VBRI_AND_FGH;
}
}
} else if (at + 10 <= end) {
while (at + 9 < end && stream[at] != 0xB4u) ++at;
if (at + 9 >= end) return;
std::uint8_t crc = 0xFF;
for (std::size_t i = 0; i < 9; ++i) {
for (int shift = 7; shift >= 0; --shift) {
const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u);
crc = static_cast<std::uint8_t>(same ? (crc << 1u) ^ 0x45u : crc << 1u);
}
return;
}
while (at + 9 < end && stream[at] != 0xB4u) ++at;
if (at + 9 >= end) return;
std::uint8_t crc = 0xFF;
for (std::size_t i = 0; i < 9; ++i) {
for (int shift = 7; shift >= 0; --shift) {
const bool same = ((crc >> 7u) & 1u) == ((stream[at + i] >> shift) & 1u);
crc = static_cast<std::uint8_t>(same ? (crc << 1u) ^ 0x45u : crc << 1u);
}
if (stream[at + 9] != crc) return;
}
if (stream[at + 9] == crc) {
frame->encoder_delay = ReadBigEndian(stream + at + 1, 2);
frame->total_samples = ReadBigEndian(stream + at + 3, 4);
frame->ofl_type = MP3_OFL_FGH;
@@ -944,13 +958,13 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par
};
const std::uint32_t header = (std::uint32_t{stream[0]} << 24u) | (std::uint32_t{stream[1]} << 16u) | (std::uint32_t{stream[2]} << 8u) | stream[3];
const std::uint32_t version = (header >> 19u) & 3u;
const std::uint32_t layer = (header >> 17u) & 3u;
const std::uint32_t sampleRate = sampleRates[version][(header >> 10u) & 3u];
const std::uint32_t bitrate = kbps[version][(header >> 12u) & 15u] * 1000u;
const bool valid = (header >> 21u) == 0x7FFu && sampleRate != 0 && bitrate != 0;
const bool valid = (header >> 21u) == 0x7FFu && layer != 0 && sampleRate != 0 && bitrate != 0;
AJM_TRACE("[ajm] parse mp3 frame %02x %02x %02x %02x (%u bytes, parse ofl %d) -> %s, %u Hz, %u bps\n", stream[0], stream[1], stream[2], stream[3], streamSize, parseOfl, valid ? "ok" : "invalid", sampleRate, bitrate);
if (!valid) return SCE_AJM_ERROR_INVALID_PARAMETER;
if (parseOfl && ((header >> 17u) & 3u) != 1u) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup outside layer III)");
if (parseOfl && ((header >> 16u) & 1u) == 0) NotImplemented_nid_no_patch("sceAjmDecMp3ParseFrame (original file length lookup in a CRC-protected frame)");
if (parseOfl && layer != 1u) return SCE_AJM_ERROR_INVALID_PARAMETER;
const bool mpeg1 = version == 3;
frame->frame_size = (mpeg1 ? 144u : 72u) * bitrate / sampleRate + ((header >> 9u) & 1u);
frame->num_channels = ((header >> 6u) & 3u) == 3u ? 1u : 2u;
@@ -961,7 +975,7 @@ int ParseMp3Header(const std::uint8_t* stream, std::uint32_t streamSize, int par
frame->num_frames = 0;
frame->total_samples = 0;
frame->ofl_type = MP3_OFL_NONE;
if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame);
if (parseOfl) ParseMp3Ofl(stream, streamSize, header, frame, parseOfl);
return 0;
}
-15
View File
@@ -845,11 +845,6 @@ int APS5_VABI sceAmprAmmWaitCommandBufferCompletion(std::uint32_t id) {
return AmmSubmitted_nid_no_patch(id) ? 0 : SCE_KERNEL_ERROR_ESRCH;
}
int APS5_VABI _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
void APS5_VABI _Unlock_shared_ptr_spin_lock_nid_postfix(void) {
sharedPtrSpinLock.clear(std::memory_order_release);
}
@@ -858,21 +853,11 @@ int APS5_VABI _Cnd_init_with_name_nid_postfix(void** handle, const char*) {
return _Cnd_init_nid_postfix(handle);
}
int APS5_VABI _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNKSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE3getES3_S3_RSt8ios_baseRNSt5_IosbIiE8_IostateEP2tmPKcSE__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE7_GetcatEPPKNSt6locale5facetEPKS5__nid_postfix(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -22,6 +22,10 @@ int APS5_VABI sceAudioOut2LoInit(void) {
return sceAudioOut2Initialize();
}
int APS5_VABI sceAudioOut2LoTerminate(void) {
return 0;
}
int APS5_VABI sceAudioOut2LoContextQueryMemory(const AudioOut2ContextParam* params, size_t* memory_size) {
return sceAudioOut2ContextQueryMemory(params, memory_size);
}
@@ -15,6 +15,11 @@ int APS5_VABI sceAudioOut2Initialize(void) {
return 0;
}
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t param) {
(void)param;
return sceAudioOut2Initialize();
}
int APS5_VABI sceAudioOut2Set3DLatency(int userId, std::uint32_t output) {
if (output > MAX_SUPPORTED_3D_OUTPUT) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
if (userId != 0 && (userId != 0xFF || output == 0)) return SCE_AUDIO_OUT2_ERROR_INVALID_PARAM;
@@ -380,11 +380,6 @@ int APS5_VABI sceAudioOut2DebugStateCtrl(void) {
return 0;
}
int APS5_VABI sceAudioOut2LoTerminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2ContextBedWrite(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -395,11 +390,6 @@ int APS5_VABI sceAudioOut2PortCreateEx(void) {
return 0;
}
int APS5_VABI sceAudioOut2InitializeEx(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioOut2Terminate(void) {
NotImplemented_nid_no_patch(__func__);
return 0;
@@ -9,6 +9,7 @@
extern "C" {
int APS5_VABI sceAudioOut2Initialize();
int APS5_VABI sceAudioOut2InitializeEx(std::uint32_t);
int APS5_VABI sceAudioOut2LoInit();
int APS5_VABI sceAudioOut2LoTerminate();
int APS5_VABI sceAudioOut2Set3DLatency(int, std::uint32_t);
@@ -125,7 +126,9 @@ void TestEnableChat() {
void TestLowLatencyService() {
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service must be accepted");
Require(sceAudioOut2LoInit() == 0, "starting the low-latency service again must be accepted");
Require(ThrowsRuntimeError([] { sceAudioOut2LoTerminate(); }), "stopping the low-latency service must throw");
Require(sceAudioOut2LoTerminate() == 0, "stopping the low-latency service returns 0");
Require(sceAudioOut2LoTerminate() == 0, "stopping a stopped low-latency service returns 0");
Require(sceAudioOut2InitializeEx(0) == 0 && sceAudioOut2InitializeEx(1) == 0, "the extended initialization succeeds");
}
}
@@ -5,7 +5,7 @@ configure_windows_unwind(libSceAudioPropagation)
if(BUILD_TESTING)
add_test_executable(guest_audio_propagation_tests ${LIBS_INCLUDE_DIR}/tests/GuestAudioPropagation.cpp)
target_include_directories(guest_audio_propagation_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc)
target_link_libraries(guest_audio_propagation_tests PRIVATE libSceAudioPropagation libc ${CMAKE_DL_LIBS})
configure_windows_unwind(guest_audio_propagation_tests)
add_test(NAME guest_audio_propagation COMMAND guest_audio_propagation_tests)
if(WIN32)
@@ -380,56 +380,6 @@ int APS5_VABI sceAudioPropagationSystemQueryInfo(AudioPropagationHandle system,
return 0;
}
int APS5_VABI sceAudioPropagationAudioPathInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationAudioPathPointInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationMaterialInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationPortalSettingsInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationRayInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSourceRenderInfoInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSourceSetAudioPathsParamInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystem() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystemMemoryInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationSystemOptionInit() {
NotImplemented_nid_no_patch(__func__);
return 0;
}
int APS5_VABI sceAudioPropagationReportRegisterHandler(std::uint32_t category, ReportHandler handler, void* userData, ReportRegistration** registration) {
NotImplemented_nid_no_patch(__func__);
return 0;
+5 -1
View File
@@ -154,8 +154,12 @@ std::vector<Font::SystemFontFile> Font::SystemFontCandidates(std::uint32_t fontS
}
std::filesystem::path Font::SystemFontDirectory() {
#ifdef _WIN32
const wchar_t* configured = _wgetenv(L"ANYPS5_SYSTEM_FONTS");
#else
const char* configured = std::getenv("ANYPS5_SYSTEM_FONTS");
if (!configured || configured[0] == '\0') return ExecutableDirectory() / "anyps5-fonts";
#endif
if (!configured || configured[0] == 0) return ExecutableDirectory() / "anyps5-fonts";
const std::filesystem::path directory(configured);
std::error_code error;
if (!std::filesystem::is_directory(directory, error)) throw std::runtime_error("libSceFont: ANYPS5_SYSTEM_FONTS is not a directory: " + directory.string());
+40 -1
View File
@@ -12,7 +12,11 @@
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <limits>
#ifdef _WIN32
#include <windows.h>
#endif
#include <ft2build.h>
#include FT_FREETYPE_H
@@ -198,6 +202,41 @@ int APS5_VABI LibrarySupport(FontLibNative* library, std::uint32_t formats) {
return SCE_FONT_OK;
}
#ifdef _WIN32
void CloseMappedFile(FT_Stream stream) {
UnmapViewOfFile(stream->base);
delete stream;
}
FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) {
const HANDLE file = CreateFileW(std::filesystem::path(reinterpret_cast<const char8_t*>(path)).c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) return FT_Err_Cannot_Open_Resource;
LARGE_INTEGER size{};
if (!GetFileSizeEx(file, &size) || size.QuadPart <= 0 || size.QuadPart > std::numeric_limits<long>::max()) {
CloseHandle(file);
return FT_Err_Cannot_Open_Stream;
}
const HANDLE mapping = CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
CloseHandle(file);
if (!mapping) return FT_Err_Cannot_Open_Stream;
void* view = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
CloseHandle(mapping);
if (!view) return FT_Err_Cannot_Open_Stream;
auto* stream = new FT_StreamRec{};
stream->base = static_cast<unsigned char*>(view);
stream->size = static_cast<unsigned long>(size.QuadPart);
stream->close = &CloseMappedFile;
FT_Open_Args args{};
args.flags = FT_OPEN_STREAM;
args.stream = stream;
return FT_Open_Face(library, &args, subFontIndex, face);
}
#else
FT_Error OpenFileFace(FT_Library library, const char* path, FT_Long subFontIndex, FT_Face* face) {
return FT_New_Face(library, path, subFontIndex, face);
}
#endif
int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const void* fontAddress, std::uint32_t fontSize, std::uint32_t subFontIndex, std::uint32_t uniqueWord, FontObj** inoutFontObj) {
(void)uniqueWord;
if (!library || !fontAddress || !inoutFontObj) return SCE_FONT_ERROR_INVALID_PARAMETER;
@@ -222,7 +261,7 @@ int APS5_VABI LibraryOpenFont(FontLibNative* library, std::uint32_t mode, const
if (fromMemory) {
error = FT_New_Memory_Face(*ftLibrary, static_cast<const FT_Byte*>(fontAddress), static_cast<FT_Long>(fontSize), static_cast<FT_Long>(subFontIndex), &face);
} else {
error = FT_New_Face(*ftLibrary, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
error = OpenFileFace(*ftLibrary, static_cast<const char*>(fontAddress), static_cast<FT_Long>(subFontIndex), &face);
}
if (error != 0 || !face) {
if (fromMemory || error == FT_Err_Unknown_File_Format) return SCE_FONT_ERROR_NO_SUPPORT_FORMAT;
+20 -16
View File
@@ -43,6 +43,7 @@
#include "prx/libkernel/Socket/include/SocketPoll.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "src/ResolverTestHooks.hpp"
// Guest socket descriptors retain PS5 semantics while their transport is backed by host sockets.
@@ -478,6 +479,7 @@ int g_next_epoll = 0x4000;
int g_next_pool = 1;
int g_next_resolver = 0x100;
bool g_net_inited = false;
std::atomic<GuestSceNetTest::ResolverLookup> g_resolverLookupForTests{nullptr};
int* errno_slot() {
static thread_local int e = 0;
@@ -617,15 +619,6 @@ std::int64_t message_length(const NetMsghdr* message) {
return static_cast<std::int64_t>(total);
}
int receive_error(const Sock& socket) {
#ifdef _WIN32
if (WSAGetLastError() == WSAETIMEDOUT && socket.type != NET_SOCK_STREAM && socket.native->receiveTimeout > 0) return NET_EAGAIN;
#else
(void)socket;
#endif
return native_error();
}
bool message_flags_supported(int flags, int accepted) {
return (flags & ~accepted) == 0;
}
@@ -653,7 +646,7 @@ auto native_transfer(const NativeSocketHandle& socket, int flags, bool send, TOp
if (timeout > 0) {
const auto remaining = deadline - std::chrono::steady_clock::now();
if (remaining <= std::chrono::steady_clock::duration::zero()) {
WSASetLastError(completeLength > 0 ? WSAEWOULDBLOCK : WSAETIMEDOUT);
WSASetLastError(send && completeLength == 0 ? WSAETIMEDOUT : WSAEWOULDBLOCK);
return transferred > 0 ? static_cast<Result>(transferred) : static_cast<Result>(-1);
}
milliseconds = static_cast<int>(std::min<std::int64_t>(std::chrono::ceil<std::chrono::milliseconds>(remaining).count(), milliseconds));
@@ -788,6 +781,10 @@ bool parse_ipv4_parts(const char* text, std::uint32_t& address) {
} // namespace
void GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(ResolverLookup lookup) noexcept {
g_resolverLookupForTests.store(lookup);
}
extern "C" {
extern const std::uint32_t sce_net_in6addr_any[4] = {};
@@ -1262,7 +1259,7 @@ int64_t APS5_VABI sceNetRecv(int s, void* buf, size_t len, int flags) {
#ifdef _WIN32
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) return static_cast<int64_t>(len);
#endif
return result >= 0 ? result : fail(receive_error(socket));
return result >= 0 ? result : fail(native_error());
}
int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void* from, uint32_t* fromlen) {
@@ -1303,7 +1300,7 @@ int64_t APS5_VABI sceNetRecvfrom(int s, void* buf, size_t len, int flags, void*
#ifdef _WIN32
if (result < 0 && socket.type == NET_SOCK_DGRAM && WSAGetLastError() == WSAEMSGSIZE) result = static_cast<int>(len);
#endif
if (result < 0) return fail(receive_error(socket));
if (result < 0) return fail(native_error());
if (from && !native_to_guest_address(peer, from, fromlen)) return fail(NET_EAFNOSUPPORT);
return result;
}
@@ -1421,7 +1418,7 @@ int64_t APS5_VABI sceNetRecvmsg(int s, NetMsghdr* msg, int flags) {
});
if (received < 0) {
#ifdef _WIN32
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(receive_error(socket));
if (!datagram || WSAGetLastError() != WSAEMSGSIZE) return fail(native_error());
received = static_cast<std::int64_t>(buffer.size());
#else
return fail(native_error());
@@ -2052,6 +2049,14 @@ int lookup_host(int rid, const char* hostname, const char* func, int family, std
if (!initialize_sockets()) return fail(5);
std::uint64_t generation = 0;
if (const int error = begin_lookup(rid, &Resolver::abort_ntoa, generation)) return error;
if (const auto lookup = g_resolverLookupForTests.load()) {
if (!lookup(hostname, family, addresses) || addresses.empty()) {
set_errno(NET_ENOENT);
log_soft(func, "host DNS lookup failed");
return end_lookup(rid, generation, NET_ERROR_RESOLVER_ENODNS);
}
return end_lookup(rid, generation, 0);
}
addrinfo hints{};
hints.ai_family = family;
addrinfo* results = nullptr;
@@ -2468,12 +2473,11 @@ int APS5_VABI sceNetIoctl(int socketId, std::uint32_t command, void* argument) {
void* APS5_VABI sceNetMemoryAllocate(std::int32_t size, int flags) {
constexpr int zeroFill = 2;
constexpr std::size_t blockAlignment = 64;
if (size <= 0) return nullptr;
if ((flags & 4) != 0)
throw std::runtime_error("sceNetMemoryAllocate: pool reserve allocation is not implemented");
void* memory = nullptr;
try {
memory = GuestHeap::GuestHeapAllocate_nid_postfix(static_cast<std::size_t>(size));
memory = GuestHeap::GuestHeapAlign_nid_postfix(blockAlignment, static_cast<std::size_t>(size));
} catch (const std::bad_alloc&) {
return nullptr;
} catch (const std::length_error&) {
@@ -0,0 +1,10 @@
#pragma once
#include <array>
#include <cstdint>
#include <vector>
namespace GuestSceNetTest {
using ResolverLookup = bool (*)(const char*, int, std::vector<std::array<std::uint8_t, 16>>&);
void SetResolverLookupForTests_nid_no_patch(ResolverLookup lookup) noexcept;
}
@@ -17,6 +17,8 @@ static constexpr int SCE_NGS2_ERROR_INVALID_WAVEFORM_FORMAT = static_cast<int>(0
static constexpr int SCE_NGS2_ERROR_UNKNOWN_WAVEFORM_FORMAT = static_cast<int>(0x804A8432);
static constexpr int SCE_NGS2_ERROR_INVALID_VOICE_HANDLE = static_cast<int>(0x804A8203);
static constexpr int SCE_NGS2_ERROR_NULL_PARAM_LIST = static_cast<int>(0x804A8360);
static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_LISTENER_ADDRESS = static_cast<int>(0x804A8461);
static constexpr int SCE_NGS2_ERROR_INVALID_GEOM_FLAG = static_cast<int>(0x804A8463);
static constexpr std::uint32_t SCE_NGS2_RACK_ID_SAMPLER = 0x1000;
static constexpr std::uint32_t SCE_NGS2_RACK_ID_SUBMIXER = 0x2000;
+2 -2
View File
@@ -41,9 +41,9 @@ Vector PerpendicularHint(const Vector& front) {
}
int Ngs2GeomCalcListener(const Ngs2GeomListenerParam* param, Ngs2GeomListenerWork* outWork, std::uint32_t flags) {
if (param == nullptr) APS5_INVALID_ARG_EX;
if (param == nullptr) return SCE_NGS2_ERROR_INVALID_GEOM_LISTENER_ADDRESS;
if (outWork == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
if (flags > 1) throw std::runtime_error("NGS2: unknown listener coordinate flags");
if (flags > 1) return SCE_NGS2_ERROR_INVALID_GEOM_FLAG;
if (!std::isfinite(param->sound_speed) || param->sound_speed <= 0.0f) APS5_INVALID_ARG_EX;
const Vector position = ReadVector(param->position);
ReadVector(param->velocity);
@@ -212,6 +212,7 @@ static Ngs2SystemOption DefaultSystemOption() {
option.max_grain_samples = 512;
option.num_grain_samples = 256;
option.sample_rate = 48000;
option.max_voice_channels = 8;
return option;
}
@@ -422,7 +423,7 @@ int APS5_VABI sceNgs2SystemRender(uintptr_t system_handle, const Ngs2RenderBuffe
}
int APS5_VABI sceNgs2SystemResetOption(Ngs2SystemOption* option) {
if (option == nullptr) APS5_INVALID_ARG_EX;
if (option == nullptr) return SCE_NGS2_ERROR_INVALID_OUT_ADDRESS;
*option = DefaultSystemOption();
return SCE_NGS2_OK;
}
@@ -115,9 +115,18 @@ static void RejectListener(const Ngs2GeomListenerParam* param, std::uint32_t fla
static void TestInvalidListener() {
auto param = Listener();
Require(sceNgs2GeomCalcListener(&param, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
RejectListener(nullptr, 0);
RejectListener(&param, 2);
RejectListener(&param, 0xffffffffu);
Ngs2GeomListenerWork work;
std::memset(&work, 0x5a, sizeof(work));
unsigned char before[sizeof(work)];
std::memcpy(before, &work, sizeof(work));
Require(sceNgs2GeomCalcListener(nullptr, &work, 0) == static_cast<int>(0x804A8461));
Require(sceNgs2GeomCalcListener(nullptr, &work, 2) == static_cast<int>(0x804A8461));
Require(sceNgs2GeomCalcListener(nullptr, nullptr, 0) == static_cast<int>(0x804A8461));
Require(sceNgs2GeomCalcListener(&param, &work, 2) == static_cast<int>(0x804A8463));
Require(sceNgs2GeomCalcListener(&param, &work, 0x80000000u) == static_cast<int>(0x804A8463));
Require(sceNgs2GeomCalcListener(&param, &work, 0xffffffffu) == static_cast<int>(0x804A8463));
Require(sceNgs2GeomCalcListener(&param, nullptr, 2) == static_cast<int>(0x804A8010));
Require(std::memcmp(before, &work, sizeof(work)) == 0);
for (const float value : {0.0f, -1.0f, std::numeric_limits<float>::infinity(), std::numeric_limits<float>::quiet_NaN()}) {
param.sound_speed = value;
RejectListener(&param, 0);
@@ -1,5 +1,7 @@
#include "Ngs2Test.hpp"
#include <cstdint>
#include <cstring>
#include <iostream>
#include <stdexcept>
@@ -7,6 +9,28 @@ static void Status(int actual, int expected, const char* message) {
if (actual != expected) throw std::runtime_error(message);
}
static void CheckResetOption() {
Status(sceNgs2SystemResetOption(nullptr), static_cast<int>(0x804A8010u), "null system option did not return the invalid out address error");
constexpr std::uint64_t size = 0x90;
constexpr std::uint32_t maxGrainSamples = 512;
constexpr std::uint32_t numGrainSamples = 256;
constexpr std::uint32_t sampleRate = 48000;
constexpr std::uint32_t maxVoiceChannels = 8;
unsigned char expected[0x90] = {};
std::memcpy(expected + 0x00, &size, sizeof(size));
std::memcpy(expected + 0x6c, &maxGrainSamples, sizeof(maxGrainSamples));
std::memcpy(expected + 0x70, &numGrainSamples, sizeof(numGrainSamples));
std::memcpy(expected + 0x74, &sampleRate, sizeof(sampleRate));
std::memcpy(expected + 0x78, &maxVoiceChannels, sizeof(maxVoiceChannels));
static_assert(sizeof(Ngs2SystemOption) == sizeof(expected));
Ngs2SystemOption option;
std::memset(&option, 0xAB, sizeof(option));
Status(sceNgs2SystemResetOption(&option), SCE_NGS2_OK, "system option reset failed");
Require(std::memcmp(&option, expected, sizeof(expected)) == 0);
Ngs2ContextBufferInfo query{};
Status(sceNgs2SystemQueryBufferSize(&option, &query), SCE_NGS2_OK, "reset system option was rejected");
}
int main() {
try {
constexpr int systemError = static_cast<int>(0x804A8201u);
@@ -39,6 +63,7 @@ int main() {
Status(sceNgs2SystemDestroy(system, nullptr), SCE_NGS2_OK, "valid system destroy failed");
Status(sceNgs2SystemGetUserData(system, &value), systemError, "retired system returned the wrong error");
Require(value == sentinel);
CheckResetOption();
std::cout << "NGS2 PS5 handle errors and validation precedence checked\n";
return 0;
} catch (const std::exception& error) {
+2 -4
View File
@@ -84,7 +84,6 @@ int APS5_VABI sceNpAuthGetAuthorizationCodeV3(int req_id, const void* param, voi
if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
if (it->second.aborted) {
it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED;
return ORBIS_NP_AUTH_ERROR_ABORTED;
}
it->second.processed = true;
@@ -102,7 +101,6 @@ int APS5_VABI sceNpAuthGetIdTokenV3(int req_id, const void* param, void* id_toke
if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
if (it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ARGUMENT;
if (it->second.aborted) {
it->second.result = ORBIS_NP_AUTH_ERROR_ABORTED;
return ORBIS_NP_AUTH_ERROR_ABORTED;
}
it->second.processed = true;
@@ -125,7 +123,7 @@ int APS5_VABI sceNpAuthPollAsync(int req_id, int* result) {
std::lock_guard lock(g_requestMutex);
auto it = g_requests.find(req_id);
if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID;
if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID;
if (!it->second.polled) {
it->second.polled = true;
return NP_POLL_ASYNC_RUNNING;
@@ -154,7 +152,7 @@ int APS5_VABI sceNpAuthWaitAsync(int req_id, int* result) {
std::lock_guard lock(g_requestMutex);
auto it = g_requests.find(req_id);
if (it == g_requests.end()) return ORBIS_NP_AUTH_ERROR_REQUEST_NOT_FOUND;
if (!it->second.isAsync || (!it->second.processed && !it->second.aborted)) return ORBIS_NP_AUTH_ERROR_INVALID_ID;
if (!it->second.isAsync || !it->second.processed) return ORBIS_NP_AUTH_ERROR_INVALID_ID;
*result = it->second.result;
return NP_POLL_ASYNC_FINISHED;
}
@@ -4,6 +4,7 @@
#include <cstdint>
static constexpr int SCE_NP_TROPHY2_OK = 0;
static constexpr int SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID = -2141898486;
static constexpr int SCE_NP_TROPHY2_ERROR_ICON_FILE_NOT_FOUND = -2141898479;
static constexpr int NP_TROPHY2_CONTEXT_DEFAULT = 1;
@@ -82,9 +82,7 @@ int APS5_VABI sceNpTrophy2GetTrophyInfo(int context, int handle, int trophy_id,
return SCE_NP_TROPHY2_OK;
}
const auto trophy = package->trophies.find(trophy_id);
if (trophy == package->trophies.end()) {
throw std::invalid_argument(std::string(__func__) + ": trophy " + std::to_string(trophy_id) + " is not in " + NpTrophy2::PackagePath(owner->serviceLabel));
}
if (trophy == package->trophies.end()) return SCE_NP_TROPHY2_ERROR_INVALID_TROPHY_ID;
if (details != nullptr) fillDetails(trophy->second, details);
if (data != nullptr) fillData(*owner, trophy->second, data);
return SCE_NP_TROPHY2_OK;
+1 -1
View File
@@ -676,7 +676,7 @@ int APS5_VABI sceVideodec2Flush_nid_postfix(std::uint64_t handle, FrameBuffer* f
if (!found) throw std::runtime_error("Videodec2: invalid decoder handle");
auto& decoder = *found;
std::lock_guard guard(decoder.mutex);
if (!decoder.flushing) {
if (!decoder.flushing && decoder.ready.empty() && !decoder.inputs.empty()) {
Send(decoder, nullptr);
decoder.flushing = true;
}
+10
View File
@@ -62,6 +62,7 @@ target_sources(libc PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/HostEntropy.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Backtrace.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareAtomics.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/DinkumwareInternals.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/Time.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/TimeExtras.cpp
${CMAKE_CURRENT_SOURCE_DIR}/src/AtomicBuiltins.cpp
@@ -526,6 +527,15 @@ if(BUILD_TESTING)
set_tests_properties(guest_dinkumware_atomics PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_dinkumware_internals_tests ${LIBS_INCLUDE_DIR}/tests/GuestDinkumwareInternals.cpp)
target_include_directories(guest_dinkumware_internals_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_dinkumware_internals_tests PRIVATE libc)
configure_windows_unwind(guest_dinkumware_internals_tests)
add_test(NAME guest_dinkumware_internals COMMAND guest_dinkumware_internals_tests)
if(WIN32)
set_tests_properties(guest_dinkumware_internals PROPERTIES ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
endif()
add_test_executable(guest_hostname_tests ${LIBS_INCLUDE_DIR}/tests/GuestHostname.cpp)
target_include_directories(guest_hostname_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_hostname_tests PRIVATE libc)
+10 -8
View File
@@ -212,18 +212,21 @@ public:
_guest.descriptor = descriptor >= 0 && descriptor <= 32767 ? static_cast<std::int16_t>(descriptor) : -1;
return true;
}
void PublishStatus(bool endOfFile, bool error) {
__atomic_store_n(&_guest.readRemaining, 0, __ATOMIC_RELAXED);
__atomic_store_n(&_guest.writeRemaining, 0, __ATOMIC_RELAXED);
auto old = __atomic_load_n(&_guest.flags, __ATOMIC_RELAXED);
std::int16_t next;
do next = static_cast<std::int16_t>((old & ~0x60) | (endOfFile ? 0x20 : 0) | (error ? 0x40 : 0));
while (!__atomic_compare_exchange_n(&_guest.flags, &old, next, false, __ATOMIC_RELAXED, __ATOMIC_RELAXED));
}
void SyncStatus() {
if (!_handle) return;
_guest.readRemaining = 0;
_guest.writeRemaining = 0;
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
(std::feof(GetHandle()) ? 0x20 : 0) | (std::ferror(GetHandle()) || guestError ? 0x40 : 0));
PublishStatus(std::feof(GetHandle()) != 0, std::ferror(GetHandle()) != 0 || guestError);
}
void SyncUnlockedStatus() {
if (!_handle) return;
_guest.readRemaining = 0;
_guest.writeRemaining = 0;
#ifdef _WIN32
const bool endOfFile = std::feof(GetHandle()) != 0;
const bool error = std::ferror(GetHandle()) != 0;
@@ -231,8 +234,7 @@ public:
const bool endOfFile = ::feof_unlocked(GetHandle()) != 0;
const bool error = ::ferror_unlocked(GetHandle()) != 0;
#endif
_guest.flags = static_cast<std::int16_t>((_guest.flags & ~0x60) |
(endOfFile ? 0x20 : 0) | (error || guestError ? 0x40 : 0));
PublishStatus(endOfFile, error || guestError);
}
bool SwitchToReading() {
+8 -1
View File
@@ -5,6 +5,13 @@
#include <string>
#include "FilesystemError.hpp"
inline std::string TrimTrailingSeparators_nid_no_patch(std::string path) {
const auto separator = [](char character) { return character == '/' || character == '\\'; };
const std::size_t root = std::filesystem::path(path).root_path().native().size();
while (path.size() > root && path.size() > 1 && separator(path.back())) path.pop_back();
return path;
}
inline int RenameDotError_nid_no_patch(const char* path) {
std::size_t length = std::char_traits<char>::length(path);
const auto separator = [](char character) { return character == '/' || character == '\\'; };
@@ -15,7 +22,7 @@ inline int RenameDotError_nid_no_patch(const char* path) {
const bool dot = name == 1 && path[start] == '.';
const bool dotDot = name == 2 && path[start] == '.' && path[start + 1] == '.';
if (!dot && !dotDot) return 0;
const std::string parent = start == 0 ? std::string(".") : std::string(path, start);
const std::string parent = start == 0 ? std::string(".") : TrimTrailingSeparators_nid_no_patch(std::string(path, start));
std::filesystem::path resolved;
if (const int error = ResolvePathError_nid_no_patch(parent.c_str(), resolved)) return error;
std::error_code status;
@@ -0,0 +1,96 @@
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <string>
#include "prx/libc/include/General.hpp"
extern "C" {
int* APS5_VABI __error_nid_postfix();
double APS5_VABI strtod_nid_postfix(const char* str, char** endptr);
long long APS5_VABI strtoll_nid_postfix(const char* str, char** endptr, int base);
double APS5_VABI sin_nid_postfix(double x);
double APS5_VABI cos_nid_postfix(double x);
float APS5_VABI sinf_nid_postfix(float x);
float APS5_VABI cosf_nid_postfix(float x);
long double APS5_VABI sinl_nid_postfix(long double x);
long double APS5_VABI cosl_nid_postfix(long double x);
double APS5_VABI log_nid_postfix(double x);
double APS5_VABI log10_nid_postfix(double x);
double APS5_VABI log2_nid_postfix(double x);
float APS5_VABI logf_nid_postfix(float x);
float APS5_VABI log10f_nid_postfix(float x);
float APS5_VABI log2f_nid_postfix(float x);
}
namespace {
constexpr long long ExponentLimit = 1000000000;
constexpr int RangeError = 34;
bool Hexadecimal(const char* text) {
while (std::isspace(static_cast<unsigned char>(*text))) ++text;
if (*text == '+' || *text == '-') ++text;
return text[0] == '0' && (text[1] == 'x' || text[1] == 'X');
}
}
extern "C" {
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten) {
int& guestErrno = *__error_nid_postfix();
const int saved = guestErrno;
guestErrno = 0;
char* stop = nullptr;
const double value = strtod_nid_postfix(text, &stop);
if (end != nullptr) *end = stop;
const bool overflowed = std::isinf(value) && guestErrno == RangeError;
if (pten == 0 || stop == text || !(std::isfinite(value) || overflowed)) {
if (guestErrno == 0) guestErrno = saved;
return value;
}
if (Hexadecimal(text)) NotImplemented_nid_no_patch("_Stod (hexadecimal text with a power-of-ten scale)");
std::string number(text, static_cast<const char*>(stop));
long long exponent = 0;
const auto marker = number.find_first_of("eE");
if (marker != std::string::npos) {
exponent = std::strtoll(number.c_str() + marker + 1, nullptr, 10);
number.resize(marker);
}
exponent = std::clamp(exponent, -ExponentLimit, ExponentLimit) + std::clamp<std::int64_t>(pten, -ExponentLimit, ExponentLimit);
number += 'e';
number += std::to_string(exponent);
guestErrno = saved;
return strtod_nid_postfix(number.c_str(), nullptr);
}
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base) {
return strtoll_nid_postfix(text, end, base);
}
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant) {
return quadrant != 0 ? cos_nid_postfix(x) : sin_nid_postfix(x);
}
float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant) {
return quadrant != 0 ? cosf_nid_postfix(x) : sinf_nid_postfix(x);
}
long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant) {
return quadrant != 0 ? cosl_nid_postfix(x) : sinl_nid_postfix(x);
}
double APS5_VABI _Log_nid_postfix(double x, int base) {
if (base > 0) return log10_nid_postfix(x);
return base == 0 ? log_nid_postfix(x) : log2_nid_postfix(x);
}
float APS5_VABI _FLog_nid_postfix(float x, int base) {
if (base > 0) return log10f_nid_postfix(x);
return base == 0 ? logf_nid_postfix(x) : log2f_nid_postfix(x);
}
}
+10
View File
@@ -361,6 +361,13 @@ int APS5_VABI fprintf_nid_postfix(FileStream* stream, const char* format, ...) {
return result;
}
static bool SizeOverflowsInt(char* buffer, size_t size) {
if (size == 0 || size - 1 <= static_cast<size_t>(INT_MAX)) return false;
errno = 84;
if (buffer != nullptr) *buffer = '\0';
return true;
}
int APS5_VABI fscanf_nid_postfix(FileStream* stream, const char* format, ...) {
if (stream && !stream->IsOpen()) { errno = 9; return -1; }
auto* native = GetNativeStream(stream);
@@ -403,6 +410,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) {
}
int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) {
if (SizeOverflowsInt(buffer, size)) return -1;
__builtin_sysv_va_list args;
__builtin_sysv_va_start(args, format);
const int result = LibcDetail::FormatWindows(buffer, size, format, args);
@@ -439,6 +447,7 @@ int APS5_VABI libc_printf_nid_postfix(const char* format, ...) {
}
int APS5_VABI snprintf_nid_postfix(char* buffer, size_t size, const char* format, ...) {
if (SizeOverflowsInt(buffer, size)) return -1;
std::va_list args;
va_start(args, format);
const int result = RequiresGuestFormatting(format) ? LibcDetail::FormatWindows(buffer, size, format, args) : std::vsnprintf(buffer, size, format, args);
@@ -541,6 +550,7 @@ int APS5_VABI vsprintf_nid_postfix(char* str, const char* format, VaList* args)
}
int APS5_VABI vsnprintf_nid_postfix(char* str, size_t size, const char* format, VaList* c) {
if (SizeOverflowsInt(str, size)) return -1;
#ifdef _WIN32
return LibcDetail::FormatWindows(str, size, format, c);
#else
+3
View File
@@ -298,6 +298,9 @@ std::uint64_t _ZNSt7codecvtIcc9_MbstatetE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7codecvtIcc9_MbstatetE_nid_postfix[16] {};
std::uint64_t _ZNSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7num_putIcSt19ostreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[12] {};
std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[17] {};
std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
std::streamoff _ZSt7_BADOFF_nid_postfix = -1;
std::fpos_t _ZSt4_Fpz_nid_postfix {};
+20
View File
@@ -750,6 +750,26 @@ double APS5_VABI _Sinh_nid_postfix(double x, double y) {
return SonySinh(x, y);
}
double APS5_VABI _Cosh_nid_postfix(double x, double y) {
if (!std::isfinite(x) || !std::isfinite(y)) return y * std::cosh(x);
if (y == 0.0) return std::copysign(0.0, y);
constexpr double Ln2High = 0x1.62e42fefa0000p-1;
constexpr double Ln2Low = 0x1.cf79abc9e3b3ap-40;
constexpr double InverseLn2 = 0x1.71547652b82fep+0;
const double magnitude = std::abs(x);
if (magnitude <= (std::numeric_limits<double>::max_exponent - 1) * (Ln2High + Ln2Low))
return y * std::cosh(x);
int scaleExponent;
const double scale = std::frexp(y, &scaleExponent);
constexpr int MaximumExponentSpan = std::numeric_limits<double>::max_exponent -
std::numeric_limits<double>::min_exponent + std::numeric_limits<double>::digits + 4;
if (magnitude > MaximumExponentSpan * (Ln2High + Ln2Low))
return std::scalbn(scale, INT_MAX);
const int exponent = static_cast<int>(magnitude * InverseLn2);
const double reduced = std::fma(-exponent, Ln2Low, std::fma(-exponent, Ln2High, magnitude));
return std::scalbn(scale * (0.5 * std::exp(reduced)), scaleExponent + exponent);
}
int APS5_VABI _Fltrounds_nid_postfix(void) {
return SonyFltrounds();
}
+5 -3
View File
@@ -127,8 +127,9 @@ Fit FloatFit(std::string_view text) {
if (digits == 0) return Fit::Invalid;
if (Lower(text[index]) != (hex ? 'p' : 'e')) return Fit::Invalid;
++index;
if (index < text.size() && (text[index] == '+' || text[index] == '-')) ++index;
if (index == text.size()) return Fit::Prefix;
if (index == text.size()) return Fit::Valid;
if (text[index] == '+' || text[index] == '-') ++index;
if (index == text.size()) return Fit::Valid;
for (; index < text.size(); ++index) {
if (!IsDecimal(text[index])) return Fit::Invalid;
}
@@ -256,7 +257,7 @@ private:
}
Outcome Literal(char16_t expected) {
if (input.Peek() < 0) return Outcome::Input;
if (input.Peek() < 0) return Outcome::Matching;
if (input.Peek() != expected) return Outcome::Matching;
input.Advance();
return Outcome::Done;
@@ -303,6 +304,7 @@ private:
if (*format == u'%') {
++format;
SkipSpace();
if (input.Peek() < 0) return Outcome::Input;
return Literal(u'%');
}
const Directive directive = Parse();
+1 -1
View File
@@ -1843,7 +1843,7 @@ if(BUILD_TESTING)
target_include_directories(guest_file_lock_tests PRIVATE ${LIBS_INCLUDE_DIR})
target_link_libraries(guest_file_lock_tests PRIVATE libkernel libc)
configure_windows_unwind(guest_file_lock_tests)
foreach(fileLockCase IN ITEMS duplicate lifetime conversion threads streams process)
foreach(fileLockCase IN ITEMS duplicate lifetime conversion threads streams process directory)
add_test(NAME guest_file_lock_${fileLockCase} COMMAND guest_file_lock_tests ${fileLockCase})
set_tests_properties(guest_file_lock_${fileLockCase} PROPERTIES TIMEOUT 20 ENVIRONMENT_MODIFICATION
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libkernel>")
@@ -17,7 +17,10 @@ void ShareDescriptor(int from, int to);
void ForgetDescriptor(int fd);
bool Appends(int fd);
bool IsVnode(int fd);
bool IsCharacterDevice(int fd);
}
extern "C" bool DescriptorHasNoVnode_nid_no_patch(int fd);
#endif
@@ -3,6 +3,7 @@
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/RandomDevice.hpp"
#include "prx/libkernel/File/include/ArchivedFile.hpp"
#include "prx/libkernel/Socket/include/SocketRuntime.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libc/include/FileLock.hpp"
#include "prx/libc/include/general/VabiMacros.hpp"
@@ -583,6 +584,16 @@ bool Appends(int fd) {
return found != g_entries.end() && found->second.status && (*found->second.status & Append) != 0;
}
bool IsCharacterDevice(int fd) {
if (fd < 0) return false;
#ifdef _WIN32
return DescriptorIsOpen_nid_no_patch(fd) && !GuestSockets::IsOpen(fd) && !File::DirectoryDescriptorPath(fd) && ::GetFileType(WindowsDescriptorHandle_nid_no_patch(fd)) == FILE_TYPE_CHAR;
#else
struct stat status{};
return ::fstat(fd, &status) == 0 && S_ISCHR(status.st_mode);
#endif
}
bool IsVnode(int fd) {
if (fd < 0 || File::IsRandomDevice(fd)) return false;
if (File::IsArchivedFile(fd)) return true;
@@ -596,3 +607,7 @@ bool IsVnode(int fd) {
}
}
extern "C" bool DescriptorHasNoVnode_nid_no_patch(int fd) {
return !File::IsVnode(fd) && !File::IsCharacterDevice(fd) && !File::IsRandomDevice(fd);
}
+19 -1
View File
@@ -2,6 +2,7 @@
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libc/include/RenameDot.hpp"
#include "prx/libc/include/GuestArena.hpp"
#include "prx/libkernel/DirectMemory/DirectMemory.hpp"
#include "prx/libkernel/File/include/File.hpp"
@@ -352,6 +353,18 @@ std::int64_t APS5_VABI sceKernelWrite(int d, const void* buf, std::size_t nbytes
return static_cast<std::int64_t>(n);
}
static bool SeekOverflows(int d, std::int64_t offset, int whence) {
const std::int64_t current = NativeLseek(d, 0, 1);
if (current < 0) return false;
std::int64_t base = current;
if (whence == 2) {
base = NativeLseek(d, 0, 2);
NativeLseek(d, current, 0);
if (base < 0) return false;
}
return base > std::numeric_limits<std::int64_t>::max() - offset;
}
std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) {
if (GuestSockets::IsOpen(d)) return SCE_KERNEL_ERROR_ESPIPE;
if (whence < 0 || whence > 2) {
@@ -365,7 +378,11 @@ std::int64_t APS5_VABI sceKernelLseek(int d, std::int64_t offset, int whence) {
}
#endif
std::int64_t result = NativeLseek(d, offset, whence);
if (result < 0) return SceIoErrorFromErrno(errno);
if (result < 0) {
const int error = errno;
if (error == EINVAL && offset > 0 && (whence == 1 || whence == 2) && SeekOverflows(d, offset, whence)) return SceIoErrorFromErrno(EOVERFLOW);
return SceIoErrorFromErrno(error);
}
return result;
}
@@ -423,6 +440,7 @@ int StatInDirectory_nid_no_patch(int directory, const char* path, FileStat* sb,
int APS5_VABI sceKernelUnlink(const char* path) {
if (path == nullptr) return SceErrorFromErrno(GuestEfault);
if (*path == '\0') return SceErrorFromErrno(2);
if (const int dot = RenameDotError_nid_no_patch(path)) return SceKernelError(dot);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
constexpr int GuestEperm = 1;
+4 -1
View File
@@ -865,6 +865,9 @@ int APS5_VABI flock_nid_postfix(int d, int operation) {
#endif
const int type = operation & 8 ? 8 : operation & 2 ? 2 : operation & 1 ? 1 : 0;
if (type == 0) return PosixFailure(GUEST_EBADF);
#ifdef _WIN32
if (File::IsDirectoryDescriptor(d)) return 0;
#endif
if (NativeFlock(d, type | (operation & 4)) != 0) {
#ifdef _WIN32
const auto error = ::GetLastError();
@@ -1493,6 +1496,7 @@ int APS5_VABI sceKernelRename(const char* from, const char* to) {
int APS5_VABI sceKernelRmdir(const char* path) {
if (!path) return SceErrorFromErrno(GUEST_EFAULT);
if (*path == '\0') return SceErrorFromErrno(GUEST_ENOENT);
if (const int dot = RenameDotError_nid_no_patch(path)) return SceKernelError(dot);
std::filesystem::path native;
if (const int error = ResolvePathError_nid_no_patch(path, native)) return SceKernelError(error);
std::error_code error;
@@ -1502,7 +1506,6 @@ int APS5_VABI sceKernelRmdir(const char* path) {
const bool blockedByFile = HasNonDirectoryPrefix(native) || std::filesystem::exists(WithoutTrailingSeparator(native), error);
return SceErrorFromErrno(blockedByFile ? GUEST_ENOTDIR : GUEST_ENOENT);
}
if (FinalPathComponentIsDot_nid_no_patch(path)) return SceErrorFromErrno(GUEST_EINVAL);
if (!std::filesystem::is_empty(native, error)) return SceErrorFromErrno(GUEST_ENOTEMPTY);
if (!RemoveEmptyDirectory(native)) return SceErrorFromErrno(GUEST_EIO);
RecordWrittenPath_nid_no_patch(native);
+3 -1
View File
@@ -106,7 +106,9 @@ int ConnectError() {
}
int SendError(int type) {
#ifdef _WIN32
if (type == 2 && WSAGetLastError() == WSAENOTCONN) return 39;
const int error = WSAGetLastError();
if (type == 2 && error == WSAENOTCONN) return 39;
if (type == 1 && (error == WSAECONNABORTED || error == WSAECONNRESET)) return 32;
#else
static_cast<void>(type);
#endif
@@ -68,7 +68,7 @@ int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(std::uint32_t userValue, c
}
int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type) {
NotImplemented_nid_no_patch("Uxqkdta7wEg");
(void)type;
return 0;
}
+4 -1
View File
@@ -21,6 +21,7 @@
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/FilesystemError.hpp"
#include "prx/libkernel/File/include/DirectoryDescriptor.hpp"
#include "prx/libkernel/File/include/DescriptorControl.hpp"
#include "prx/libc/include/CreationMask.hpp"
#include "prx/libkernel/File/include/FileFlags.hpp"
#include "prx/libkernel/KernelErrors.hpp"
@@ -357,6 +358,7 @@ int APS5_VABI link_nid_postfix(const char* existing, const char* created) {
int APS5_VABI fchdir_nid_postfix(int fd) {
int error = 0;
const char* path = GetDirectoryDescriptorPath_nid_no_patch(fd, &error);
if (path == nullptr && error == GuestEnotdir && DescriptorHasNoVnode_nid_no_patch(fd)) return static_cast<int>(Fail(GuestEinval));
if (path == nullptr) return static_cast<int>(Fail(error));
#ifndef _WIN32
if (::faccessat(fd, ".", X_OK, AT_EACCESS) != 0) return static_cast<int>(Fail(errno));
@@ -385,6 +387,7 @@ int APS5_VABI chroot_nid_postfix(const char* path) {
int APS5_VABI symlink_nid_postfix(const char* target, const char* created) {
if (target == nullptr || created == nullptr) return static_cast<int>(Fail(GuestEfault));
if (std::strlen(target) >= GuestPathMax) return static_cast<int>(Fail(GuestEnametoolong));
if (*target == '\0' || *created == '\0') return static_cast<int>(Fail(GuestEnoent));
const std::filesystem::path contents(target);
if (contents.has_root_name() || contents.has_root_directory()) {
@@ -575,8 +578,8 @@ int APS5_VABI unlinkat_nid_postfix(int fd, const char* path, int flag) {
while (!native.has_filename() && native.has_relative_path()) native = native.parent_path();
std::error_code statusError;
const auto symlinkStatus = std::filesystem::symlink_status(native, statusError);
if (IsDotOrDotDot(path)) return static_cast<int>(Fail(GuestEinval));
if (flag & GuestAtRemovedir) {
if (IsDotOrDotDot(path)) return static_cast<int>(Fail(GuestEinval));
if (std::filesystem::is_symlink(symlinkStatus)) return static_cast<int>(Fail(GuestEnotdir));
if (!std::filesystem::is_directory(symlinkStatus)) {
if (HasNonDirectoryPrefix(native) || std::filesystem::exists(symlinkStatus))
+30 -1
View File
@@ -3,6 +3,7 @@
#include "prx/libc/include/GuestHeap.hpp"
#include <algorithm>
#include <array>
#include <climits>
#include <cmath>
#include <cstdlib>
#include <cstring>
@@ -84,6 +85,34 @@ static int APS5_VABI PrintList(const char* format, ...) {
return result;
}
static void CheckSizeLimits() {
using Formatter = int (APS5_VABI*)(char*, size_t, const char*, ...);
for (const Formatter format : {&snprintf_nid_postfix, &FormatList}) {
char output[8];
for (const std::size_t oversized : {static_cast<std::size_t>(INT_MAX) + 2, static_cast<std::size_t>(SIZE_MAX)}) {
for (const bool wide : {false, true}) {
std::memset(output, 'Z', sizeof(output));
int consumed = -1;
*__error_nid_postfix() = 0;
const int result = wide ? format(output, oversized, "%ls%n", u"x", &consumed)
: format(output, oversized, "x%n", &consumed);
Require(result == -1 && *__error_nid_postfix() == 84 && output[0] == 0 && consumed == -1);
Require(std::all_of(output + 1, output + sizeof(output), [](char value) { return value == 'Z'; }));
}
}
for (const std::size_t capacity : {std::size_t{0}, std::size_t{1}, static_cast<std::size_t>(INT_MAX), static_cast<std::size_t>(INT_MAX) + 1}) {
std::memset(output, 'Z', sizeof(output));
*__error_nid_postfix() = 35;
Require(format(output, capacity, "x") == 1 && *__error_nid_postfix() == 35);
Require(output[0] == (capacity == 0 ? 'Z' : capacity == 1 ? '\0' : 'x'));
const std::size_t written = capacity < 2 ? capacity : 2;
if (capacity > 1) Require(output[1] == 0);
Require(std::all_of(output + written, output + sizeof(output), [](char value) { return value == 'Z'; }));
}
Require(format(nullptr, 0, "x") == 1);
}
}
static bool CheckWidePrecision() {
const char16_t input[] = u"A\u00e9\u20ac\U0001f600Z";
const char* expected[] = {"", "A", "A", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9", "A\xc3\xa9\xe2\x82\xac",
@@ -368,4 +397,4 @@ static void CheckAllocatingFormat() {
free_nid_postfix(allocated);
}
int main() { RunChecks(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; }
int main() { CheckSizeLimits(); RunChecks(); CheckAllocatingFormat(); return CheckWidePrecision() ? 0 : 1; }
+87 -14
View File
@@ -391,6 +391,21 @@ void TestMp3ParseFrame() {
Require(sceAjmDecMp3ParseFrame(reservedSampleRate, 4, 0, &frame) == invalidParameter);
const std::uint8_t mpeg25Above64Kbps[4] = {0xFF, 0xE3, 0x98, 0xC0};
Require(sceAjmDecMp3ParseFrame(mpeg25Above64Kbps, 4, 0, &frame) == invalidParameter);
for (const std::uint8_t layer : {0u, 2u, 3u}) {
const std::uint8_t header[4] = {0xFF, static_cast<std::uint8_t>(0xF9u | (layer << 1u)), 0x90, 0x04};
if (layer == 0) {
std::memset(&frame, 0xA5, sizeof(frame));
const auto previous = frame;
Require(sceAjmDecMp3ParseFrame(header, sizeof(header), 0, &frame) == invalidParameter);
Require(std::memcmp(&frame, &previous, sizeof(frame)) == 0);
} else {
Require(ParsesTo(header, sizeof(header), 417, 2, 1152, 128000, 44100));
}
std::memset(&frame, 0xA5, sizeof(frame));
const auto previous = frame;
Require(sceAjmDecMp3ParseFrame(header, sizeof(header), 1, &frame) == invalidParameter);
Require(std::memcmp(&frame, &previous, sizeof(frame)) == 0);
}
}
const std::uint8_t LAME_MPEG1_STEREO_VBR[] = {
@@ -457,14 +472,17 @@ bool OflParsesTo(const std::vector<std::uint8_t>& stream, std::uint32_t streamSi
return frame.num_frames == frames && frame.encoder_delay == delay && frame.total_samples == total && frame.ofl_type == type;
}
bool OflThrows(const std::vector<std::uint8_t>& stream) {
AjmDecMp3ParseFrame frame{};
try {
sceAjmDecMp3ParseFrame(stream.data(), static_cast<std::uint32_t>(stream.size()), 1, &frame);
} catch (const std::runtime_error&) {
return true;
std::vector<std::uint8_t> ProtectedMp3Frame(std::vector<std::uint8_t> frame, bool tagAfterCrc) {
frame[1] &= 0xFE;
if (tagAfterCrc) {
const auto size = frame.size();
frame.insert(frame.begin() + 4, {0xA5, 0x5A});
frame.resize(size);
} else {
frame[4] = 0xA5;
frame[5] = 0x5A;
}
return false;
return frame;
}
void TestMp3ParseOfl() {
@@ -481,21 +499,75 @@ void TestMp3ParseOfl() {
const auto fgh = Mp3Frame(FGH_FRAME, sizeof(FGH_FRAME), 417);
Require(OflParsesTo(fgh, 417, 0, 1105, 441000, 3));
Require(OflParsesTo(Mp3Frame(FGH_BAD_CRC_FRAME, sizeof(FGH_BAD_CRC_FRAME), 417), 417, 0, 0, 0, 0));
const auto marker = std::find(fgh.begin() + 36, fgh.end(), 0xB4u);
Require(marker != fgh.end() && static_cast<std::size_t>(fgh.end() - marker) >= 10);
std::vector<std::uint8_t> misleadingFgh(417, 0);
std::copy_n(fgh.begin(), 4, misleadingFgh.begin());
const auto markerOffset = static_cast<std::size_t>(marker - fgh.begin());
std::copy_n(fgh.begin() + static_cast<std::ptrdiff_t>(markerOffset), 10, misleadingFgh.begin() + 36);
misleadingFgh[45] ^= 0x01u;
std::copy_n(fgh.begin() + static_cast<std::ptrdiff_t>(markerOffset), 10, misleadingFgh.begin() + 48);
Require(OflParsesTo(misleadingFgh, 417, 0, 0, 0, 0));
auto vbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417);
Require(OflParsesTo(vbri, 417, 0, 576, 0, 2));
vbri.insert(vbri.end(), fgh.begin(), fgh.end());
Require(OflParsesTo(vbri, 834, 0, 576 + 1105, 441000, 4));
Require(OflParsesTo(vbri, 480, 0, 576, 0, 2));
auto protectedFrame = stereo;
protectedFrame[1] = 0xFA;
Require(OflThrows(protectedFrame));
auto layer2 = stereo;
layer2[1] = 0xFD;
Require(OflThrows(layer2));
for (const bool tagAfterCrc : {false, true}) {
const auto protectedStereo = ProtectedMp3Frame(stereo, tagAfterCrc);
Require(OflParsesTo(protectedStereo, 417, 10, 1152 + 576 + 529, 10000, 1));
const auto protectedMono = ProtectedMp3Frame(mono, tagAfterCrc);
Require(OflParsesTo(protectedMono, 576, 10, 1152 + 576 + 529, 10000, 1));
const auto protectedMpeg2 = ProtectedMp3Frame(mpeg2, tagAfterCrc);
Require(OflParsesTo(protectedMpeg2, 208, 20, 576 + 576 + 529, 10000, 1));
}
auto protectedNonzeroSideInfo = ProtectedMp3Frame(stereo, true);
protectedNonzeroSideInfo[8] = 0x00;
protectedNonzeroSideInfo[9] = 0x10;
std::move_backward(protectedNonzeroSideInfo.begin() + 38, protectedNonzeroSideInfo.end() - 2, protectedNonzeroSideInfo.end());
protectedNonzeroSideInfo[38] = 0;
protectedNonzeroSideInfo[39] = 0;
Require(OflParsesTo(protectedNonzeroSideInfo, 417, 10, 1152 + 576 + 529, 10000, 1));
const auto protectedFgh = ProtectedMp3Frame(fgh, true);
Require(OflParsesTo(protectedFgh, 417, 0, 1105, 441000, 3));
auto monoVbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417);
monoVbri[3] = 0xC4;
Require(OflThrows(monoVbri));
Require(OflParsesTo(monoVbri, 417, 0, 576, 0, 2));
for (const std::uint8_t version : {0xF3u, 0xE3u}) {
for (const std::uint8_t mode : {0x04u, 0xC4u}) {
auto otherVbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), version == 0xF3 ? 208 : 417);
otherVbri[1] = version;
otherVbri[2] = 0x80;
otherVbri[3] = mode;
Require(OflParsesTo(otherVbri, static_cast<std::uint32_t>(otherVbri.size()), 0, 576, 0, 2));
}
}
const auto protectedVbri = ProtectedMp3Frame(monoVbri, false);
Require(OflParsesTo(protectedVbri, 417, 0, 576, 0, 2));
auto monoVbriAndFgh = monoVbri;
monoVbriAndFgh.insert(monoVbriAndFgh.end(), protectedFgh.begin(), protectedFgh.end());
Require(OflParsesTo(monoVbriAndFgh, 834, 0, 576 + 1105, 441000, 4));
}
void TestMp3MetadataBounds() {
const auto stereo = Mp3Frame(LAME_MPEG1_STEREO_VBR, sizeof(LAME_MPEG1_STEREO_VBR), 417);
const auto protectedStereo = ProtectedMp3Frame(stereo, true);
const auto protectedLame = ProtectedMp3Frame(stereo, false);
auto vbri = Mp3Frame(VBRI_FRAME, sizeof(VBRI_FRAME), 417);
vbri[3] = 0xC4;
for (const auto& complete : {stereo, protectedStereo, protectedLame, vbri}) {
for (std::size_t length = 0; length <= complete.size(); ++length) {
const std::vector<std::uint8_t> prefix(complete.begin(), complete.begin() + length);
AjmDecMp3ParseFrame frame{};
const int result = sceAjmDecMp3ParseFrame(prefix.data(), static_cast<std::uint32_t>(prefix.size()), 1, &frame);
Require(result == (length < 4 ? static_cast<int>(0x80930005) : 0));
if (length >= 4) Require(frame.frame_size == 417);
}
}
std::vector<std::uint8_t> chain;
for (std::size_t i = 0; i < 4096; ++i) chain.insert(chain.end(), vbri.begin(), vbri.end());
Require(OflParsesTo(chain, static_cast<std::uint32_t>(chain.size()), 0, 576, 0, 2));
}
struct GaplessDecode {
@@ -1933,6 +2005,7 @@ int main() {
Require(nullBuffersThrow && batchInfo.offset == 0);
TestMp3ParseFrame();
TestMp3ParseOfl();
TestMp3MetadataBounds();
TestMp3(context);
TestOpus(context);
TestOpus(context, 1);
+21 -2
View File
@@ -7,10 +7,15 @@
#include <cstring>
#include <stdexcept>
#ifdef _WIN32
#include <windows.h>
#else
#include <dlfcn.h>
#endif
extern "C" {
int APS5_VABI sceAudioPropagationPathGetNumPoints();
int APS5_VABI sceAudioPropagationSystemLock();
int APS5_VABI sceAudioPropagationMaterialInit();
}
namespace {
@@ -34,6 +39,16 @@ void RequireThrows(TCall call, const char* message) {
Require(false, message);
}
void* Resolve(const char* name) {
#ifdef _WIN32
const HMODULE module = GetModuleHandleA("libSceAudioPropagation.prx");
Require(module != nullptr, "library module");
return reinterpret_cast<void*>(GetProcAddress(module, name));
#else
return dlsym(RTLD_DEFAULT, name);
#endif
}
AudioPropagationSystemMemory Memory() {
AudioPropagationSystemMemory memory{};
memory.desc = {AudioPropagation::SystemMemoryId, AudioPropagation::SystemMemorySize};
@@ -294,7 +309,11 @@ int main() {
RequireThrows<std::runtime_error>([&] { sceAudioPropagationPathGetNumPoints(); }, "path get num points throw");
RequireThrows<std::runtime_error>([&] { sceAudioPropagationSystemLock(); }, "system lock throw");
RequireThrows<std::runtime_error>([&] { sceAudioPropagationMaterialInit(); }, "material init throw");
Require(Resolve("sceAudioPropagationSystemCreate") != nullptr && Resolve("sceAudioPropagationSourceSetAudioPath") != nullptr, "export lookup");
for (const char* name : {"sceAudioPropagationAudioPathInit", "sceAudioPropagationAudioPathPointInit", "sceAudioPropagationMaterialInit", "sceAudioPropagationPortalSettingsInit", "sceAudioPropagationRayInit", "sceAudioPropagationSourceRenderInfoInit", "sceAudioPropagationSourceSetAudioPathsParamInit", "sceAudioPropagationSystemMemoryInit", "sceAudioPropagationSystemOptionInit", "sceAudioPropagationSystem"}) {
Require(Resolve(name) == nullptr, name);
}
std::puts("AudioPropagation tests passed");
return 0;
@@ -15,6 +15,7 @@ int64_t APS5_VABI sceCoredumpWriteUserData(const void* data, size_t size);
void APS5_VABI sceCoredumpDebugTextOut(const char* str, int len);
int APS5_VABI sceCoredumpAttachUserFile(uint32_t userValue, const char* path);
int APS5_VABI sceCoredumpAttachMemoryRegionAsUserFile(uint32_t userValue, const void* mem, std::int32_t size, const char* name);
int APS5_VABI sceCoredumpSetUserDataType(std::int32_t type);
}
namespace {
@@ -77,6 +78,7 @@ int main() {
Require(sceCoredumpAttachUserFile(0x40000000u, "") == NotInCoredumpHandler);
Require(NotInCoredumpHandler < 0);
Require(sceCoredumpSetUserDataType(0) == 0);
RequireWritesRefused(region);
RequireAttachesRefused(region);
sceCoredumpDebugTextOut(text, static_cast<int>(sizeof(text) - 1));
@@ -0,0 +1,88 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
extern "C" {
int* APS5_VABI __error_nid_postfix();
double APS5_VABI _Stod_nid_postfix(const char* text, char** end, std::int64_t pten);
long long APS5_VABI _Stoll_nid_postfix(const char* text, char** end, int base);
double APS5_VABI _Sin_nid_postfix(double x, unsigned int quadrant);
float APS5_VABI _FSin_nid_postfix(float x, unsigned int quadrant);
long double APS5_VABI _LSin_nid_postfix(long double x, unsigned int quadrant);
double APS5_VABI _Log_nid_postfix(double x, int base);
float APS5_VABI _FLog_nid_postfix(float x, int base);
}
namespace {
constexpr int RangeError = 34;
void Require(bool condition, const char* message) {
if (!condition) {
std::fprintf(stderr, "Dinkumware internals: %s\n", message);
std::exit(1);
}
}
void CheckStod(const char* text, std::int64_t pten, double expected, long consumed, int error, const char* message) {
*__error_nid_postfix() = 0;
char* end = nullptr;
const double value = _Stod_nid_postfix(text, &end, pten);
const bool same = std::isnan(expected) ? std::isnan(value) : value == expected && std::signbit(value) == std::signbit(expected);
Require(same && end - text == consumed && *__error_nid_postfix() == error, message);
}
void TestStod() {
CheckStod("1.5", 0, 1.5, 3, 0, "unscaled decimal");
CheckStod("1.5", 1, 15.0, 3, 0, "decimal scaled up");
CheckStod("12", -2, 0.12, 2, 0, "decimal scaled down");
CheckStod(" -2.25e3xyz", 300, -0x1.a3f7f806a8135p+1007, 9, 0, "the scale is applied before rounding");
CheckStod("1e-400", 300, 1e-100, 6, 0, "an underflowing text scaled back into range");
CheckStod("1e-400", 0, 0.0, 6, RangeError, "an unscaled underflow");
CheckStod("1e300", 100, HUGE_VAL, 5, RangeError, "a scale that overflows");
CheckStod("1e300", 8, 1e308, 5, 0, "a scale up to the largest decades");
CheckStod("5e-324", -1, 0.0, 6, RangeError, "a scale that underflows to zero");
CheckStod("1", 2147483647, HUGE_VAL, 1, RangeError, "the largest scale");
CheckStod("5", std::int64_t{1} << 40, HUGE_VAL, 1, RangeError, "a scale above 32 bits");
CheckStod("1e400", -100, 1e300, 5, 0, "an overflowing text scaled back into range");
CheckStod("0.01e41", -2, 1e37, 7, 0, "a negative scale from leading fraction zeros");
CheckStod("1e-2147483648", -1, 0.0, 13, RangeError, "the smallest exponent and scale");
CheckStod("-0", 5, -0.0, 2, 0, "negative zero keeps its sign");
CheckStod("infinity", 3, HUGE_VAL, 8, 0, "infinity is not scaled");
CheckStod("nan", 3, NAN, 3, 0, "NaN is not scaled");
CheckStod("1e", 2, 100.0, 1, 0, "an exponent marker without digits");
CheckStod(".5", 2, 50.0, 2, 0, "a fraction without integer digits");
CheckStod("0.3", -1, 0.03, 3, 0, "0.3 scaled down rounds as 0.03");
CheckStod("123456789012345678901234567890", -20, 1234567890.1234567, 30, 0, "a long digit string");
CheckStod("abc", 4, 0.0, 0, 0, "no number");
CheckStod("0x1.8p0", 0, 1.5, 7, 0, "unscaled hexadecimal");
}
void TestStoll() {
char* end = nullptr;
*__error_nid_postfix() = 0;
Require(_Stoll_nid_postfix("-0x1F", &end, 0) == -31 && std::strcmp(end, "") == 0, "_Stoll base detection");
Require(_Stoll_nid_postfix("+42abc", &end, 10) == 42 && std::strcmp(end, "abc") == 0, "_Stoll stops at the first non-digit");
Require(_Stoll_nid_postfix(" z", &end, 36) == 35, "_Stoll base 36");
Require(*__error_nid_postfix() == 0, "_Stoll set errno without an overflow");
Require(_Stoll_nid_postfix("9223372036854775808", &end, 10) == 9223372036854775807ll && *__error_nid_postfix() == RangeError, "_Stoll saturates");
}
void TestDispatch() {
Require(_Sin_nid_postfix(0.5, 0) == std::sin(0.5) && _Sin_nid_postfix(0.5, 1) == std::cos(0.5) && _Sin_nid_postfix(0.5, 7) == std::cos(0.5), "_Sin");
Require(_FSin_nid_postfix(0.5f, 0) == std::sin(0.5f) && _FSin_nid_postfix(0.5f, 2) == std::cos(0.5f), "_FSin");
Require(_LSin_nid_postfix(0.5L, 0) == std::sin(0.5L) && _LSin_nid_postfix(0.5L, 3) == std::cos(0.5L), "_LSin");
Require(_Log_nid_postfix(8.0, 0) == std::log(8.0) && _Log_nid_postfix(1000.0, 1) == 3.0 && _Log_nid_postfix(1000.0, 5) == 3.0 && _Log_nid_postfix(8.0, -1) == 3.0, "_Log");
Require(_FLog_nid_postfix(8.0f, 0) == std::log(8.0f) && _FLog_nid_postfix(100.0f, 1) == 2.0f && _FLog_nid_postfix(8.0f, -3) == 3.0f, "_FLog");
}
}
int main() {
TestStod();
TestStoll();
TestDispatch();
}
+18
View File
@@ -296,6 +296,23 @@ static void Processes() {
Require(ChildTry(fixtures, 2) == 0);
}
static void Directories() {
const std::filesystem::path directory = std::filesystem::temp_directory_path() / ("anyps5-flock-dir-" + std::to_string(::GetCurrentProcessId()));
std::filesystem::create_directories(directory);
const std::string path = directory.string();
const int descriptor = open_nid_postfix(path.c_str(), 0x20000, 0);
Require(descriptor >= 0);
Require(flock_nid_postfix(descriptor, 1 | 4) == 0);
Require(flock_nid_postfix(descriptor, 2 | 4) == 0);
Require(flock_nid_postfix(descriptor, 2) == 0);
Require(flock_nid_postfix(descriptor, 8) == 0);
Require(flock_nid_postfix(descriptor, 0) == -1 && *__error_nid_postfix() == 9);
Require(flock_nid_postfix(descriptor, 4) == -1 && *__error_nid_postfix() == 9);
Require(close_nid_postfix(descriptor) == 0);
std::error_code ignored;
std::filesystem::remove_all(directory, ignored);
}
int main(int argc, char** argv) {
if (argc >= 4 && std::string(argv[1]) == "child") {
int busy = 0;
@@ -320,5 +337,6 @@ int main(int argc, char** argv) {
else if (mode == "threads") Threads();
else if (mode == "streams") Streams();
else if (mode == "process") Processes();
else if (mode == "directory") Directories();
else std::abort();
}
+32
View File
@@ -2,6 +2,7 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libc/include/General.hpp"
#include "prx/libc/include/FileStream.hpp"
#include "prx/libc/include/RenameDot.hpp"
#include "prx/libkernel/File/include/File.hpp"
#include "prx/libkernel/File/include/NativeStat.hpp"
#include "prx/libkernel/KernelErrors.hpp"
@@ -259,6 +260,29 @@ static void TestParentTraversal() {
Require(stat_nid_postfix("/parent-test-mount/missing/.", &status) == -1 && *__error_nid_postfix() == 2);
Require(stat_nid_postfix("/parent-test-mount/existing/.", &status) == 0 && (status.st_mode & 0170000) == 0040000);
Require(access_nid_postfix("/parent-test-mount/existing/./", 0) == 0);
Require(TrimTrailingSeparators_nid_no_patch("/") == "/");
Require(TrimTrailingSeparators_nid_no_patch("/mount/dir//") == "/mount/dir");
Require(TrimTrailingSeparators_nid_no_patch("dir/") == "dir");
#ifdef _WIN32
Require(TrimTrailingSeparators_nid_no_patch("C:\\") == "C:\\");
Require(TrimTrailingSeparators_nid_no_patch("C:/") == "C:/");
Require(TrimTrailingSeparators_nid_no_patch("C:\\dir\\") == "C:\\dir");
Require(TrimTrailingSeparators_nid_no_patch("C:\\\\") == "C:\\");
Require(TrimTrailingSeparators_nid_no_patch("C:\\folder\\\\") == "C:\\folder");
#endif
std::filesystem::create_directory(root / "dot-victim");
for (const char* name : {"/parent-test-mount/dot-victim/.", "/parent-test-mount/dot-victim/..", "/parent-test-mount/dot-victim/./", "/parent-test-mount/dot-victim/../"}) {
Require(unlink_nid_postfix(name) == -1 && *__error_nid_postfix() == 22);
Require(rmdir_nid_postfix(name) == -1 && *__error_nid_postfix() == 22);
Require(sceKernelUnlink(name) == static_cast<int>(0x80020016u));
Require(sceKernelRmdir(name) == static_cast<int>(0x80020016u));
}
Require(unlink_nid_postfix("/parent-test-mount/present.txt/.") == -1 && *__error_nid_postfix() == 20);
Require(rename_nid_postfix("/parent-test-mount/present.txt/.", "/parent-test-mount/dot-renamed") == -1 && *__error_nid_postfix() == 20);
Require(rename_nid_postfix("/parent-test-mount/present.txt/..", "/parent-test-mount/dot-renamed") == -1 && *__error_nid_postfix() == 20);
Require(rmdir_nid_postfix("/parent-test-mount/missing/..") == -1 && *__error_nid_postfix() == 2);
Require(std::filesystem::is_directory(root / "dot-victim") && std::filesystem::is_regular_file(root / "present.txt"));
std::filesystem::remove(root / "dot-victim");
#ifndef _WIN32
for (const char* name : {"slash-unlink", "slash-remove", "slash-rename", "slash-open", "slash-kernel"}) std::ofstream(root / name) << "kept";
Require(unlink_nid_postfix("/parent-test-mount/slash-unlink/") == -1 && *__error_nid_postfix() == 20);
@@ -1193,6 +1217,14 @@ int main() {
Require(pread_nid_postfix(locked, nullptr, 1, 0) == -1 && *__error_nid_postfix() == 14);
Require(close_nid_postfix(transferPipe[0]) == 0 && close_nid_postfix(transferPipe[1]) == 0);
}
Require(lseek_nid_postfix(locked, 4, 0) == 4);
Require(lseek_nid_postfix(locked, INT64_MAX, 1) == -1 && *__error_nid_postfix() == 84);
Require(lseek_nid_postfix(locked, 0, 1) == 4);
Require(lseek_nid_postfix(locked, -1, 0) == -1 && *__error_nid_postfix() == 22);
const std::int64_t lockedSize = lseek_nid_postfix(locked, 0, 2);
Require(lockedSize >= 0);
Require(lseek_nid_postfix(locked, INT64_MAX - lockedSize + 1, 2) == -1 && *__error_nid_postfix() == 84);
Require(lseek_nid_postfix(locked, 0, 1) == lockedSize);
Require(flock_nid_postfix(-1, 2 | 4) == -1 && *__error_nid_postfix() == 9);
Require(flock_nid_postfix(0x7fffffff, 1) == -1 && *__error_nid_postfix() == 9);
int flockPipe[2] = {-1, -1};
+18 -2
View File
@@ -1,4 +1,5 @@
#include "prx/libSceFont/include/FontDriver.hpp"
#include "prx/libc/include/General.hpp"
#include "tests/FontFixture.hpp"
#include <array>
#include <cstddef>
@@ -7,6 +8,7 @@
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <initializer_list>
#include <map>
#include <random>
@@ -89,9 +91,9 @@ static void Check(bool value, int line) {
static void SetFontDirectory(const std::filesystem::path& directory) {
#ifdef _WIN32
Require(_putenv_s("ANYPS5_SYSTEM_FONTS", directory.string().c_str()) == 0);
Require(_wputenv_s(L"ANYPS5_SYSTEM_FONTS", directory.wstring().c_str()) == 0);
#else
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.string().c_str(), 1) == 0);
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.c_str(), 1) == 0);
#endif
}
@@ -311,6 +313,20 @@ int main() {
Require(sceFontOpenFontMemory(library, notAFont, sizeof(notAFont), nullptr, &font) == SCE_FONT_ERROR_NO_SUPPORT_FORMAT && font == nullptr);
const std::vector<unsigned char> fontData = EmptyGlyphFont();
const auto fileFont = fontRoot / std::filesystem::path(u8"external-a\u00e7\u00e3o-\u65e5\u672c") / "font.ttf";
std::filesystem::create_directories(fileFont.parent_path());
{
std::ofstream file(fileFont, std::ios::binary);
file.write(reinterpret_cast<const char*>(fontData.data()), static_cast<std::streamsize>(fontData.size()));
Require(static_cast<bool>(file));
}
const std::u8string fontRootText = fontRoot.u8string();
AddPathAlias_nid_no_patch("/font-test", reinterpret_cast<const char*>(fontRootText.c_str()));
const std::u8string fileFontText = u8"/font-test/external-a\u00e7\u00e3o-\u65e5\u672c/font.ttf";
FontHandle fileHandle = nullptr;
Require(sceFontOpenFontFile(library, reinterpret_cast<const char*>(fileFontText.c_str()), 1, nullptr, &fileHandle) == SCE_FONT_OK && fileHandle != nullptr);
Require(sceFontCloseFont(fileHandle) == SCE_FONT_OK);
RemovePathAlias_nid_no_patch("/font-test");
Require(sceFontOpenFontMemory(library, fontData.data(), static_cast<std::uint32_t>(fontData.size()), nullptr, &font) == SCE_FONT_OK && font != nullptr);
int setting = -1;
Require(sceFontGetScriptLanguage(font, 3, &setting) == SCE_FONT_ERROR_INVALID_PARAMETER && setting == 0);
+30 -1
View File
@@ -32,6 +32,8 @@ int APS5_VABI fstatfs_nid_postfix(int, void*);
int APS5_VABI _fstatfs_nid_postfix(int, void*);
int APS5_VABI open_nid_postfix(const char*, int, int);
int APS5_VABI close_nid_postfix(int);
int APS5_VABI pipe_nid_postfix(int*);
int APS5_VABI socket_nid_postfix(int, int, int);
std::int64_t APS5_VABI readlink_nid_postfix(const char*, char*, std::size_t);
int APS5_VABI lstat_nid_postfix(const char*, FileStat*);
std::int64_t APS5_VABI pathconf_nid_postfix(const char*, int);
@@ -297,6 +299,19 @@ int main() {
Require(symlink_nid_postfix("file.txt", created.c_str()) == -1 && Error() == expected);
Require(std::filesystem::hard_link_count(file) == 1 && !std::filesystem::exists(root / "new.txt"));
}
for (const std::size_t length : {std::size_t{1024}, std::size_t{1100}}) {
const auto tooLong = std::string(length, 'a');
const auto created = (root / "target-too-long").string();
Require(symlink_nid_postfix(tooLong.c_str(), created.c_str()) == -1 && Error() == 63);
Require(!std::filesystem::exists(std::filesystem::symlink_status(root / "target-too-long")));
}
{
const auto longest = std::string(1023, 'a');
const auto created = (root / "target-longest").string();
const int made = symlink_nid_postfix(longest.c_str(), created.c_str());
Require(made == 0 || Error() != 63);
if (made == 0) Require(RemoveSymlink(created));
}
Require(readlink_nid_postfix(file.c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
Require(readlink_nid_postfix(root.string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 22);
Require(readlink_nid_postfix((root / "missing").string().c_str(), buffer, sizeof(buffer)) == -1 && Error() == 2);
@@ -417,6 +432,19 @@ int main() {
Require(fileDescriptor >= 0);
Require(fchdir_nid_postfix(-1) == -1 && Error() == 9);
Require(fchdir_nid_postfix(fileDescriptor) == -1 && Error() == 20);
int pipeEnds[2] = {-1, -1};
Require(pipe_nid_postfix(pipeEnds) == 0);
Require(fchdir_nid_postfix(pipeEnds[0]) == -1 && Error() == 22);
Require(fchdir_nid_postfix(pipeEnds[1]) == -1 && Error() == 22);
Require(close_nid_postfix(pipeEnds[0]) == 0 && close_nid_postfix(pipeEnds[1]) == 0);
const int characterDevice = open_nid_postfix("/dev/random", 0, 0);
Require(characterDevice >= 0);
Require(fchdir_nid_postfix(characterDevice) == -1 && Error() == 20);
Require(close_nid_postfix(characterDevice) == 0);
const int socketDescriptor = socket_nid_postfix(2, 2, 0);
Require(socketDescriptor >= 0);
Require(fchdir_nid_postfix(socketDescriptor) == -1 && Error() == 22);
Require(close_nid_postfix(socketDescriptor) == 0);
Require(fchdir_nid_postfix(directory) == 0);
char directoryPath[1024];
Require(getcwd_nid_postfix(directoryPath, sizeof(directoryPath)) == directoryPath);
@@ -605,7 +633,8 @@ int main() {
Require(unlinkat_nid_postfix(base, "file.txt", 0x800) == -1 && Error() == 20);
Require(unlinkat_nid_postfix(base, ".", 0x800) == -1 && Error() == 22);
Require(unlinkat_nid_postfix(base, "..", 0x800) == -1 && Error() == 22);
Require(unlinkat_nid_postfix(base, ".", 0) == -1 && Error() == 1);
Require(unlinkat_nid_postfix(base, ".", 0) == -1 && Error() == 22);
Require(unlinkat_nid_postfix(base, "..", 0) == -1 && Error() == 22);
std::filesystem::create_directory(root / "usub");
Require(unlinkat_nid_postfix(base, "usub", 0) == -1 && Error() == 1);
std::filesystem::create_directory(root / "nonempty");
+14
View File
@@ -34,6 +34,9 @@ void APS5_VABI _Locksyslock_nid_postfix();
void APS5_VABI _Unlocksyslock_nid_postfix();
std::uint32_t APS5_VABI _Towctrans_nid_postfix(std::uint32_t wc, std::uint64_t desc);
int* APS5_VABI __error_nid_postfix();
extern std::uint64_t _ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
extern std::uint64_t _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix;
extern std::uintptr_t _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix[17];
}
@@ -52,6 +55,16 @@ static void RequireException(TAction action) {
Require(caught);
}
static void CheckInputFacetObjects() {
Require(_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 0 && _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 0);
_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 7;
_ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 9;
Require(_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 7 && _ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix == 9);
_ZNSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
_ZNSt8time_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE2idE_nid_postfix = 0;
for (const auto slot : _ZTVSt7num_getIcSt19istreambuf_iteratorIcSt11char_traitsIcEEE_nid_postfix) Require(slot == 0);
}
static void CheckGuestCalls() {
auto* locale = _ZSt21_sceLibcClassicLocale_nid_postfix;
Require(locale != nullptr && locale == _ZNSt6locale16_GetgloballocaleEv_nid_postfix());
@@ -249,6 +262,7 @@ int main() {
CheckStreamWidth();
CheckStandardStreams();
CheckWideCaseTranslation();
CheckInputFacetObjects();
_Locksyslock_nid_postfix();
_Locksyslock_nid_postfix();
_Unlocksyslock_nid_postfix();
+9
View File
@@ -129,6 +129,7 @@ std::int64_t APS5_VABI lround_nid_postfix(double);
std::div_t APS5_VABI div_nid_postfix(int, int);
double APS5_VABI sinh_nid_postfix(double);
double APS5_VABI cosh_nid_postfix(double);
double APS5_VABI _Cosh_nid_postfix(double, double);
float APS5_VABI coshf_nid_postfix(float);
float APS5_VABI sinhf_nid_postfix(float);
float APS5_VABI _FSinh_nid_postfix(float, float);
@@ -839,6 +840,14 @@ static void CheckExtendedMath() {
static void CheckHyperbolic() {
const double infinity = std::numeric_limits<double>::infinity();
Require(_Cosh_nid_postfix(0.0, 2.0) == 2.0);
Require(Near(_Cosh_nid_postfix(1.0, 2.0), 3.0861612696304874, 1e-15));
Require(Near(_Cosh_nid_postfix(-1.0, -2.0), -3.0861612696304874, 1e-15));
Require(std::isinf(_Cosh_nid_postfix(infinity, -1.0)) && std::signbit(_Cosh_nid_postfix(infinity, -1.0)));
Require(std::isnan(_Cosh_nid_postfix(infinity, 0.0)));
Require(Near(_Cosh_nid_postfix(711.0, 1e-300), 0x1.2190c08e2f1a4p+28, 1e-15));
Require(_Cosh_nid_postfix(720.0, 0.0) == 0.0 && !std::signbit(_Cosh_nid_postfix(720.0, 0.0)));
Require(_Cosh_nid_postfix(720.0, -0.0) == 0.0 && std::signbit(_Cosh_nid_postfix(720.0, -0.0)));
Require(sinh_nid_postfix(0.) == 0. && std::signbit(sinh_nid_postfix(-0.)));
Require(cosh_nid_postfix(0.) == 1. && coshf_nid_postfix(0.f) == 1.f);
Require(asinh_nid_postfix(0.) == 0. && std::signbit(asinh_nid_postfix(-0.)) && std::signbit(asinhf_nid_postfix(-0.f)));
+39 -2
View File
@@ -2,6 +2,7 @@
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
extern "C" {
int APS5_VABI sceNpAuthAbortRequest(int req_id);
@@ -26,7 +27,43 @@ static void Require(bool condition) {
}
}
static void RequireUnstarted(int request) {
constexpr int invalidId = static_cast<int>(0x80550307);
constexpr int sentinel = 0x13579bdf;
for (int attempt = 0; attempt != 3; ++attempt) {
int result = sentinel;
Require(sceNpAuthPollAsync(request, &result) == invalidId);
Require(result == sentinel);
Require(sceNpAuthWaitAsync(request, &result) == invalidId);
Require(result == sentinel);
}
}
static void CheckUnstartedAborts() {
constexpr int aborted = static_cast<int>(0x80550304);
alignas(8) char parameter[16]{};
alignas(8) char output[16]{};
for (const bool async : {false, true}) {
const int request = async ? sceNpAuthCreateAsyncRequest(nullptr) : sceNpAuthCreateRequest();
Require(request > 0);
RequireUnstarted(request);
Require(sceNpAuthAbortRequest(request) == 0);
Require(sceNpAuthAbortRequest(request) == 0);
RequireUnstarted(request);
for (auto getter : {sceNpAuthGetIdToken, sceNpAuthGetIdTokenV3, sceNpAuthGetAuthorizationCode}) {
Require(getter(request, parameter, output) == aborted);
RequireUnstarted(request);
}
int issuer = 0x2468ace;
Require(sceNpAuthGetAuthorizationCodeV3(request, parameter, output, &issuer) == aborted);
Require(issuer == 0x2468ace);
RequireUnstarted(request);
Require(sceNpAuthDeleteRequest(request) == 0);
}
}
int main() {
CheckUnstartedAborts();
constexpr int invalidArgument = static_cast<int>(0x80550301);
constexpr int signedOut = static_cast<int>(0x80550006);
constexpr int aborted = static_cast<int>(0x80550304);
@@ -130,8 +167,8 @@ int main() {
Require(sceNpAuthGetIdTokenV3(abortedReq, param, token) == aborted);
Require(sceNpAuthGetAuthorizationCodeV3(abortedReq, param, authCode, &issuerId) == aborted);
int abortedWait = 0;
Require(sceNpAuthWaitAsync(abortedReq, &abortedWait) == 0);
Require(abortedWait == aborted);
Require(sceNpAuthWaitAsync(abortedReq, &abortedWait) == invalidId);
Require(abortedWait == 0);
const int syncReqA = sceNpAuthCreateRequest();
Require(sceNpAuthGetIdTokenA(syncReqA, nullptr, token) == invalidArgument);
+14 -2
View File
@@ -2,6 +2,7 @@
#include "prx/libSceNpTrophy2/include/NpTrophy2Types.hpp"
#include "prx/libSceNpTrophy2/include/TrophyStore.hpp"
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdio>
@@ -36,6 +37,7 @@ int APS5_VABI sceNpTrophy2GetTrophyInfoArray(int, int, uint32_t, uint32_t, NpTro
}
static constexpr int InitialUser = 0x10000000;
static constexpr int InvalidTrophyId = static_cast<int>(0x8055390Au);
static const char TropConf[] = R"({"defaultLanguage":"en-US","trophies":[
{"id":"0","grade":"P"},
@@ -168,8 +170,18 @@ static void ReportTrophyData(int context) {
Require(details.trophy_grade == 1 && std::strcmp(details.name, "All of them") == 0, "the platinum is not reported");
const auto steps = Trophy(context, 5, &details);
Require(details.target.type == 1 && details.target.value == 5 && !steps.unlocked && steps.progress.type == 1 && steps.progress.value == 0, "the progressive trophy does not report its target");
NpTrophy2Data unknown{};
Require(ThrowsInvalidArgument([context, &unknown] { sceNpTrophy2GetTrophyInfo(context, 1, 99, nullptr, &unknown); }), "a trophy that is not in the package was reported");
const auto untouched = [](const void* bytes, std::size_t size) {
const auto* first = static_cast<const unsigned char*>(bytes);
return std::all_of(first, first + size, [](unsigned char byte) { return byte == 0x5a; });
};
for (const int unknownId : {8, 99, -1}) {
NpTrophy2Details unknownDetails{};
NpTrophy2Data unknown{};
std::memset(&unknownDetails, 0x5a, sizeof(unknownDetails));
std::memset(&unknown, 0x5a, sizeof(unknown));
Require(sceNpTrophy2GetTrophyInfo(context, 1, unknownId, &unknownDetails, &unknown) == InvalidTrophyId, "a trophy that is not in the package did not return the invalid trophy id error");
Require(untouched(&unknownDetails, sizeof(unknownDetails)) && untouched(&unknown, sizeof(unknown)), "a trophy that is not in the package wrote its details or data");
}
std::vector<NpTrophy2Details> list(10);
std::vector<NpTrophy2Data> listed(10);
+4
View File
@@ -67,6 +67,8 @@ int APS5_VABI sceRtcSetCurrentTick(const RtcTick*);
int APS5_VABI sceRtcSetCurrentNetworkTick(const RtcTick*);
int APS5_VABI sceRtcSetCurrentAdNetworkTick(const RtcTick*);
int APS5_VABI sceRtcSetCurrentDebugNetworkTick(const RtcTick*);
int APS5_VABI sceRtcInit(void);
int APS5_VABI sceRtcEnd(void);
}
static void Require(bool value) { if (!value) std::abort(); }
@@ -907,4 +909,6 @@ int main() {
Require(sceRtcGetCurrentDebugNetworkTick(&current) == 0 && current.tick >= debugClock.tick && current.tick < debugClock.tick + 1000000);
Require(sceRtcGetCurrentRawNetworkTick(&current) == 0 && current.tick > unixEpochTick);
}
Require(sceRtcInit() == 0);
Require(sceRtcEnd() == 0);
}
+98 -24
View File
@@ -13,6 +13,7 @@
#include "prx/libc/include/general/VabiMacros.hpp"
#include "prx/libkernel/Socket/include/SocketSelectDeadline.hpp"
#include "prx/libkernel/Time/include/Time.hpp"
#include "prx/libSceNet/src/ResolverTestHooks.hpp"
#include "SceTypes.hpp"
#include <algorithm>
#include <array>
@@ -146,6 +147,20 @@ static void Require(bool condition) {
if (!condition) std::abort();
}
static bool FailResolverLookup(const char*, int, std::vector<std::array<std::uint8_t, 16>>&) {
return false;
}
class ScopedResolverLookupOverride {
public:
explicit ScopedResolverLookupOverride(GuestSceNetTest::ResolverLookup lookup) {
GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(lookup);
}
~ScopedResolverLookupOverride() {
GuestSceNetTest::SetResolverLookupForTests_nid_no_patch(nullptr);
}
};
static void APS5_VABI IgnoreNetCtlEvent(int, void*) {}
static bool Failed(std::int64_t result, int error) {
@@ -307,14 +322,20 @@ static void CheckRandom() {
Require(Failed(sceNetGetRandom(nullptr), 22));
std::array<std::uint32_t, 8> values{};
for (auto& value : values) Require(sceNetGetRandom(&value) == 0);
std::array<std::uint32_t, 2> guarded{0x5a5a5a5au, 0x5a5a5a5au};
Require(sceNetGetRandom(guarded.data()) == 0 && guarded[1] == 0x5a5a5a5au && *sceNetErrnoLoc() == 22);
Require(std::any_of(values.begin() + 1, values.end(), [&](std::uint32_t value) { return value != values[0]; }));
}
static void CheckNetMemory() {
Require(sceNetMemoryAllocate(0, 0) == nullptr);
Require(sceNetMemoryAllocate(-1, 2) == nullptr);
Require(sceNetMemoryAllocate(0, 4) == nullptr);
Require(sceNetMemoryAllocate(INT32_MIN, -1) == nullptr);
*sceNetErrnoLoc() = 33;
for (const std::int32_t size : {0, -1, INT32_MIN}) {
Require(sceNetMemoryAllocate(size, 0) == nullptr);
Require(sceNetMemoryAllocate(size, 2) == nullptr);
Require(sceNetMemoryAllocate(size, 4) == nullptr);
Require(sceNetMemoryAllocate(size, -1) == nullptr);
}
Require(*sceNetErrnoLoc() == 33);
auto* zeroed = static_cast<std::uint8_t*>(sceNetMemoryAllocate(4096, 2));
Require(zeroed != nullptr);
Require(std::all_of(zeroed, zeroed + 4096, [](std::uint8_t byte) { return byte == 0; }));
@@ -325,20 +346,20 @@ static void CheckNetMemory() {
Require(zeroed[4095] == 0xa5);
sceNetMemoryFree(plain);
sceNetMemoryFree(zeroed);
for (const int flags : {1, 3, 4, 6, 8, 0x10, 0x100, 0x40000001, -5, INT32_MIN}) {
auto* block = static_cast<std::uint8_t*>(sceNetMemoryAllocate(16, flags));
Require(block != nullptr && reinterpret_cast<std::uintptr_t>(block) % 64 == 0);
if ((flags & 2) != 0) Require(std::all_of(block, block + 16, [](std::uint8_t byte) { return byte == 0; }));
std::memset(block, 0x5a, 16);
sceNetMemoryFree(block);
}
for (const std::int32_t size : {1, 7, 256, 65536}) {
void* block = sceNetMemoryAllocate(size, 0);
Require(block != nullptr && reinterpret_cast<std::uintptr_t>(block) % 64 == 0);
sceNetMemoryFree(block);
}
sceNetMemoryFree(nullptr);
for (const int flags : {1, 0x40000001, -5}) {
auto* allocation = static_cast<std::uint8_t*>(sceNetMemoryAllocate(16, flags));
Require(allocation != nullptr);
if ((flags & 2) != 0) Require(std::all_of(allocation, allocation + 16, [](std::uint8_t byte) { return byte == 0; }));
sceNetMemoryFree(allocation);
}
bool threw = false;
try {
sceNetMemoryAllocate(16, 4);
} catch (const std::runtime_error&) {
threw = true;
}
Require(threw);
Require(*sceNetErrnoLoc() == 33);
}
static void CheckUnboundSocketName() {
@@ -598,6 +619,36 @@ static void CheckReceiveTimeout(int receiver, int sender, const std::array<std::
Require(sceNetSetsockopt(receiver, 0xffff, 0x1006, &disabled, sizeof(disabled)) == 0);
}
static void CheckStreamReceiveTimeout() {
const int listener = sceNetSocket("stream-timeout", 2, 1, 6);
Require(listener >= 0);
std::array<std::uint8_t, 16> address{16, 2, 0, 0, 127, 0, 0, 1};
Require(sceNetBind_nid_postfix(listener, address.data(), address.size()) == 0 && sceNetListen(listener, 1) == 0);
std::uint32_t size = address.size();
Require(sceNetGetsockname(listener, address.data(), &size) == 0);
const int client = sceNetSocket("stream-timeout-client", 2, 1, 6);
Require(client >= 0 && sceNetConnect(client, address.data(), address.size()) == 0);
const int accepted = sceNetAccept(listener, nullptr, nullptr);
Require(accepted >= 0);
const KernelTimeval timeout{0, 50000};
Require(sceNetSetsockopt(client, 0xffff, 0x1006, &timeout, sizeof(timeout)) == 0);
char buffer[8]{};
NetIovec in[] = {{buffer, sizeof(buffer)}};
NetMsghdr receive{nullptr, 0, in, 1, nullptr, 0, 0};
const auto start = std::chrono::steady_clock::now();
Require(Failed(sceNetRecv(client, buffer, sizeof(buffer), 0), 35));
Require(std::chrono::steady_clock::now() - start >= std::chrono::milliseconds(40));
Require(Failed(sceNetRecvfrom(client, buffer, sizeof(buffer), 0, nullptr, nullptr), 35));
Require(Failed(sceNetRecvmsg(client, &receive, 0), 35));
const char payload[] = "late";
Require(sceNetSend(accepted, payload, sizeof(payload), 0) == sizeof(payload));
Require(sceNetRecv(client, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0);
Require(sceNetSend(client, payload, sizeof(payload), 0) == sizeof(payload));
std::memset(buffer, 0, sizeof(buffer));
Require(sceNetRecv(accepted, buffer, sizeof(buffer), 0) == sizeof(payload) && std::strcmp(buffer, payload) == 0);
for (const int descriptor : {client, accepted, listener}) Require(sceNetSocketClose(descriptor) == 0);
}
static void CheckMicrosecondTimeouts(int receiver) {
const int receiveTimeout = 50000;
const int sendTimeout = 70000;
@@ -1004,6 +1055,8 @@ static void CheckUsleep() {
Require(std::chrono::steady_clock::now() - start >= std::chrono::microseconds(2000));
*sceNetErrnoLoc() = 0;
Require(Failed(sceNetUsleep(-1), 22));
*sceNetErrnoLoc() = 0;
Require(Failed(sceNetUsleep(INT32_MIN), 22));
}
static void CheckEpollIdent() {
@@ -1769,6 +1822,7 @@ int main() {
stdio_readable[0] = 1;
Require(select_nid_postfix(1, stdio_readable, nullptr, nullptr, poll_now) == 0 && stdio_readable[0] == 0);
CheckReceiveTimeout(udp_receiver, udp_sender, address);
CheckStreamReceiveTimeout();
CheckMicrosecondTimeouts(udp_receiver);
CheckMessages(udp_receiver, udp_sender, address);
Require(sceNetSocketClose(udp_sender) == 0);
@@ -1779,8 +1833,13 @@ int main() {
int resolver_error = -1;
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
std::array<std::uint8_t, 4> ipv4{};
Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.invalid.", ipv4.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1));
ipv4.fill(0xA5);
{
ScopedResolverLookupOverride lookupOverride(FailResolverLookup);
Require(sceNetResolverStartNtoa(resolver, "guest-sce-net.invalid.", ipv4.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1));
}
Require(std::all_of(ipv4.begin(), ipv4.end(), [](std::uint8_t byte) { return byte == 0xA5; }));
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 &&
resolver_error == static_cast<int>(0x804101E1));
Require(sceNetResolverStartNtoa(resolver, nullptr, ipv4.data(), 5000000, 1, 0) == static_cast<int>(0x80410116));
@@ -1794,7 +1853,12 @@ int main() {
Require(std::all_of(resolved6.begin(), resolved6.end() - 1, [](std::uint8_t byte) { return byte == 0; }) && resolved6[15] == 1);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
Require(sceNetResolverStartNtoa6(resolver, nullptr, resolved6.data(), 5000000, 1, 0) == static_cast<int>(0x80410116));
Require(sceNetResolverStartNtoa6(resolver, "127.0.0.1", resolved6.data(), 5000000, 1, 0) == static_cast<int>(0x804101E1));
const auto resolved6BeforeFailure = resolved6;
{
ScopedResolverLookupOverride lookupOverride(FailResolverLookup);
Require(sceNetResolverStartNtoa6(resolver, "127.0.0.1", resolved6.data(), 5000000, 1, 0) == static_cast<int>(0x804101E1));
}
Require(resolved6 == resolved6BeforeFailure);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x804101E1));
Require(sceNetResolverStartNtoa(resolver, "localhost", ipv4.data(), 5000000, 1, 0) == 0);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == 0);
@@ -1811,6 +1875,7 @@ int main() {
Require(std::all_of(records.begin() + 32 * record_count, records.begin() + 320, [](std::uint8_t byte) { return byte == 0; }));
Require(std::all_of(records.begin() + 328, records.begin() + 384, [](std::uint8_t byte) { return byte == 0; }));
Require(std::all_of(records.begin() + 384, records.end(), [](std::uint8_t byte) { return byte == 0xA5; }));
const auto resolvedRecords = records;
for (int first = 0; first < record_count; ++first) {
for (int second = first + 1; second < record_count; ++second) {
Require(std::memcmp(records.data() + 32 * first, records.data() + 32 * second, 4) != 0);
@@ -1821,8 +1886,12 @@ int main() {
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22 && records == resolved);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "localhost", nullptr, 5000000, 1, 0) ==
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", records.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1) && records == resolved);
{
ScopedResolverLookupOverride lookupOverride(FailResolverLookup);
Require(sceNetResolverStartNtoaMultipleRecordsEx(resolver, "guest-sce-net.invalid.", records.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1));
}
Require(records == resolvedRecords);
std::array<std::uint8_t, 512> plain{};
plain.fill(0xA5);
@@ -1833,8 +1902,13 @@ int main() {
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22 && plain == resolved);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "localhost", nullptr, 5000000, 1, 0) ==
static_cast<int>(0x80410116) && *sceNetErrnoLoc() == 22);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1) && plain == resolved);
const auto resolvedPlain = plain;
{
ScopedResolverLookupOverride lookupOverride(FailResolverLookup);
Require(sceNetResolverStartNtoaMultipleRecords(resolver, "guest-sce-net.invalid", plain.data(), 5000000, 1, 0) ==
static_cast<int>(0x804101E1));
}
Require(plain == resolvedPlain);
Require(sceNetResolverGetError(resolver, &resolver_error) == 0 && resolver_error == static_cast<int>(0x804101E1));
for (const int flags : {0x1, 0x10000}) {
bool threw = false;
+3 -3
View File
@@ -213,9 +213,9 @@ int main(int argc, char** argv) {
Require(Exchange(pair[1], pair[0], "still open"));
Require(close_nid_postfix(pair[0]) == 0);
Require(Readable(pair[1], In | Hup) && recv_nid_postfix(pair[1], received, sizeof(received), 0) == 0);
#ifndef _WIN32
Require(send_nid_postfix(pair[1], ping, sizeof(ping), NoSignal) == -1 && *__error_nid_postfix() == 32);
#endif
std::int64_t sent = 0;
for (int i = 0; i < 100 && sent >= 0; ++i) sent = send_nid_postfix(pair[1], ping, sizeof(ping), NoSignal);
Require(sent == -1 && *__error_nid_postfix() == 32);
Require(close_nid_postfix(pair[1]) == 0);
Require(close_nid_postfix(pair[1]) == -1 && *__error_nid_postfix() == 9);
-2
View File
@@ -148,11 +148,9 @@ int main() {
Require(close_nid_postfix(client) == 0);
Require(Readable(server, In | Hup) && read_nid_postfix(server, received, sizeof(received)) == 0);
Require(sceKernelRead(server, received, sizeof(received)) == 0);
#ifndef _WIN32
std::int64_t written = 0;
for (int i = 0; i < 100 && written >= 0; ++i) written = write_nid_postfix(server, request, sizeof(request));
Require(written == -1 && *__error_nid_postfix() == 32);
#endif
Require(close_nid_postfix(server) == 0);
*__error_nid_postfix() = 0;
Require(read_nid_postfix(server, received, sizeof(received)) == -1 && *__error_nid_postfix() == 9);
+3 -3
View File
@@ -127,9 +127,9 @@ static void WriteFile(const std::filesystem::path& path, const std::vector<unsig
static void SetFontDirectory(const std::filesystem::path& directory) {
#ifdef _WIN32
Require(_putenv_s("ANYPS5_SYSTEM_FONTS", directory.string().c_str()) == 0);
Require(_wputenv_s(L"ANYPS5_SYSTEM_FONTS", directory.wstring().c_str()) == 0);
#else
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.string().c_str(), 1) == 0);
Require(::setenv("ANYPS5_SYSTEM_FONTS", directory.c_str(), 1) == 0);
#endif
}
@@ -208,7 +208,7 @@ int main() {
const std::filesystem::path root = std::filesystem::temp_directory_path() / ("anyps5_guest_system_font-" + std::to_string(std::random_device{}()));
std::filesystem::remove_all(root);
const std::filesystem::path empty = root / "empty";
const std::filesystem::path fonts = root / "fonts";
const std::filesystem::path fonts = root / std::filesystem::path(u8"fonts-a\u00e7\u00e3o-\u65e5\u672c");
std::filesystem::create_directories(empty);
std::filesystem::create_directories(fonts);
WriteFile(fonts / "SST-Bold.otf", SquareGlyphFont('A', 700));
-2
View File
@@ -277,11 +277,9 @@ int main() {
Require(send_nid_postfix(client, request, sizeof(request), 0x20000) == -1 && *__error_nid_postfix() == 32);
Require(close_nid_postfix(accepted) == 0);
#ifndef _WIN32
std::int64_t sent = 0;
for (int i = 0; i < 100 && sent >= 0; ++i) sent = send_nid_postfix(client, request, sizeof(request), 0x20000);
Require(sent == -1 && *__error_nid_postfix() == 32);
#endif
Require(close_nid_postfix(client) == 0);
Require(close_nid_postfix(listener) == 0);
Require(close_nid_postfix(listener) == -1 && *__error_nid_postfix() == 9);
+87
View File
@@ -191,6 +191,50 @@ constexpr std::array<std::uint64_t, 6> HevcHashes{
0xe39bc891f8286267ull, 0x734caf1ba961b3c2ull, 0x7deb44bd1ed30531ull,
};
constexpr std::array<std::uint8_t, 658> AvcIpppStream{
0x00, 0x00, 0x00, 0x01, 0x09, 0x10, 0x00, 0x00, 0x00, 0x01, 0x67, 0x42, 0xc0, 0x0a, 0xda, 0x11,
0xec, 0x04, 0x40, 0x00, 0x00, 0x03, 0x00, 0x40, 0x00, 0x00, 0x0f, 0x03, 0xc4, 0x89, 0xa8, 0x00,
0x00, 0x00, 0x01, 0x68, 0xce, 0x0f, 0x2c, 0x80, 0x00, 0x00, 0x01, 0x65, 0x88, 0x84, 0x09, 0x7f,
0xe4, 0x5e, 0xd2, 0xf1, 0x40, 0x00, 0x40, 0x07, 0xc5, 0x00, 0x02, 0x50, 0x06, 0x46, 0x28, 0xcd,
0x0f, 0x80, 0x38, 0x07, 0xff, 0xc0, 0x07, 0xd8, 0x76, 0x33, 0x23, 0x81, 0xce, 0x7d, 0x73, 0x08,
0x2f, 0xc0, 0xff, 0x1f, 0xc1, 0x61, 0x01, 0x2f, 0xc1, 0x00, 0x01, 0xf0, 0x19, 0x8d, 0x80, 0x38,
0xd1, 0x0d, 0xcc, 0x7a, 0x99, 0xdf, 0xff, 0xda, 0x00, 0x3d, 0x82, 0x8e, 0xf2, 0x8f, 0x22, 0x18,
0x94, 0x07, 0x24, 0x94, 0x45, 0x22, 0x44, 0x5c, 0xb8, 0x7f, 0xe1, 0xa2, 0x99, 0xa3, 0xef, 0xe2,
0x80, 0x01, 0x9f, 0x03, 0x81, 0x0e, 0x01, 0x80, 0x70, 0x00, 0x10, 0x05, 0x00, 0x04, 0x00, 0x02,
0x00, 0x6e, 0xbf, 0x6f, 0x66, 0xdb, 0x0c, 0x3f, 0x7c, 0x5b, 0xcc, 0xf6, 0x6c, 0xcc, 0x30, 0xe1,
0x8c, 0x1c, 0x00, 0x04, 0x04, 0x80, 0x00, 0x42, 0x73, 0xfd, 0x1f, 0xfe, 0x20, 0x00, 0x13, 0x00,
0x01, 0x08, 0xf8, 0x80, 0x00, 0x20, 0x2c, 0x1f, 0x04, 0xbb, 0xf3, 0xc0, 0x23, 0xdd, 0x63, 0x6f,
0xf9, 0xe1, 0x04, 0x82, 0x53, 0x90, 0x01, 0x89, 0xe5, 0x89, 0x44, 0x5a, 0xf8, 0x39, 0xe0, 0x03,
0x20, 0x10, 0x50, 0x00, 0x25, 0xf9, 0x8d, 0xe0, 0x04, 0xf3, 0x62, 0xfd, 0x35, 0x7d, 0x77, 0x7e,
0x0b, 0xe1, 0x4d, 0xfe, 0x85, 0x2b, 0xef, 0xe8, 0xf0, 0x80, 0xa2, 0x80, 0x03, 0x04, 0x00, 0x06,
0xc0, 0x72, 0x80, 0x02, 0x8c, 0x14, 0xe0, 0x9d, 0xcd, 0xe8, 0x3f, 0xe9, 0xb5, 0x9b, 0x06, 0x4d,
0x5e, 0x80, 0xa2, 0x1f, 0x06, 0xda, 0xdf, 0x36, 0xfd, 0xa1, 0x0f, 0xfe, 0x08, 0xb0, 0x26, 0x48,
0xaf, 0x17, 0xe9, 0xc5, 0x9f, 0xbf, 0xba, 0x2e, 0x69, 0xa9, 0xe6, 0x7c, 0xbf, 0x4f, 0xfe, 0xc1,
0x64, 0x0a, 0xaa, 0x69, 0xda, 0x43, 0xff, 0xf9, 0xfd, 0xfc, 0xff, 0x77, 0xe9, 0x1e, 0x20, 0x18,
0x30, 0x1f, 0x80, 0x12, 0xe4, 0x51, 0x4d, 0x9b, 0xff, 0x9b, 0x83, 0xa0, 0x01, 0x72, 0xd1, 0x7f,
0x83, 0xa2, 0xe9, 0x82, 0x73, 0x13, 0xd3, 0x8d, 0x59, 0x95, 0x1f, 0xa6, 0x70, 0x4a, 0xeb, 0xb5,
0xe0, 0x42, 0x10, 0xc0, 0x02, 0x00, 0x1c, 0x20, 0x00, 0x28, 0x14, 0x68, 0xe0, 0x1a, 0x82, 0xd4,
0x87, 0x10, 0x00, 0x46, 0x54, 0x41, 0xc1, 0x81, 0x9a, 0x0c, 0x5d, 0xd0, 0x03, 0xd8, 0x0f, 0x52,
0x07, 0x2c, 0x00, 0x7a, 0x92, 0x12, 0x02, 0xdc, 0x01, 0x67, 0x3a, 0xc1, 0xa4, 0x5a, 0x83, 0x95,
0xc0, 0x30, 0x02, 0xf8, 0x49, 0xe2, 0x1e, 0x2b, 0xd5, 0x4a, 0x29, 0xa9, 0x6d, 0xf2, 0xc0, 0x7b,
0x27, 0x2c, 0xb3, 0xde, 0x18, 0x00, 0x11, 0x71, 0xe0, 0x70, 0x02, 0x0c, 0x34, 0x07, 0x06, 0x01,
0xa2, 0x90, 0x71, 0x40, 0x05, 0x12, 0x98, 0x01, 0x01, 0x63, 0xe2, 0xa8, 0xc0, 0xb8, 0xe0, 0xe0,
0xf0, 0x06, 0x8a, 0x41, 0xd0, 0x01, 0x59, 0x12, 0x08, 0x30, 0x7d, 0x39, 0x02, 0x1d, 0x5e, 0x01,
0x80, 0x26, 0x39, 0x0f, 0xc1, 0xef, 0x83, 0xc3, 0xf8, 0x3c, 0x7c, 0x4f, 0x86, 0xdd, 0x50, 0x6d,
0x69, 0xd0, 0xef, 0xe9, 0x70, 0x35, 0xc4, 0xb3, 0x84, 0x40, 0x00, 0x40, 0x48, 0x00, 0x54, 0x84,
0x00, 0x0d, 0x51, 0x81, 0xc3, 0x80, 0x3d, 0xd2, 0x1c, 0x20, 0x1c, 0xca, 0xa0, 0x10, 0x1a, 0x46,
0x38, 0x04, 0x06, 0xd0, 0x75, 0x00, 0x2f, 0x48, 0x1e, 0x80, 0x3f, 0x49, 0x14, 0xc0, 0x3f, 0xc4,
0x52, 0x01, 0x7e, 0x78, 0x36, 0x19, 0x08, 0x43, 0xf6, 0xc5, 0xc0, 0x01, 0xf8, 0x9f, 0x6e, 0x65,
0xa4, 0xff, 0xff, 0x86, 0x0e, 0xf8, 0x00, 0x8b, 0xfb, 0xe4, 0xf4, 0x00, 0x57, 0x5c, 0x07, 0x4b,
0x80, 0x66, 0xe3, 0xd2, 0xcd, 0xa7, 0x03, 0xa0, 0x41, 0x81, 0x20, 0x00, 0x20, 0x24, 0x80, 0x73,
0x80, 0x0e, 0x52, 0x0e, 0xe5, 0xc0, 0x27, 0xd9, 0x08, 0x9c, 0x6c, 0xa6, 0x58, 0x86, 0xc5, 0xe9,
0x0e, 0x40, 0x0c, 0xc8, 0x17, 0x5c, 0x50, 0x1a, 0xc1, 0x74, 0x47, 0x9f, 0xf0, 0x81, 0x0d, 0xb0,
0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x20, 0x25, 0xde, 0x49, 0xba,
0xbd, 0x70, 0x47, 0xbd, 0x5a, 0xa0, 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41,
0x9a, 0x40, 0x2d, 0xc4, 0xf0, 0x47, 0x55, 0xf7, 0xfa, 0xdf, 0xe0, 0x97, 0xbb, 0xee, 0xad, 0xf0,
0x43, 0xd5, 0x7b, 0x00, 0x00, 0x00, 0x01, 0x09, 0x30, 0x00, 0x00, 0x01, 0x41, 0x9a, 0x60, 0x35,
0xc6, 0xc0};
constexpr std::array<std::uint8_t, 234> HevcMain10Stream{
0x00, 0x00, 0x00, 0x01, 0x46, 0x01, 0x10, 0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff,
0xff, 0x02, 0x20, 0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x1e,
@@ -723,6 +767,48 @@ void testEmptyAccessUnit() {
check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed");
}
void testFlushAfterEveryUnit() {
std::vector<std::vector<std::uint8_t>> units;
for (std::size_t index = 0; index + 4 < AvcIpppStream.size(); ++index) {
if (AvcIpppStream[index] == 0 && AvcIpppStream[index + 1] == 0 && AvcIpppStream[index + 2] == 0 && AvcIpppStream[index + 3] == 1 && (AvcIpppStream[index + 4] & 0x1fu) == 9) units.emplace_back();
check(!units.empty(), "the IPPP stream does not start with an access unit delimiter");
units.back().push_back(AvcIpppStream[index]);
}
for (std::size_t index = AvcIpppStream.size() - 4; index < AvcIpppStream.size(); ++index) units.back().push_back(AvcIpppStream[index]);
check(units.size() == 4, "unexpected IPPP access unit count");
std::vector<std::uint8_t> buffer(frameBufferBytes());
const auto decode = [&](bool flushEachUnit) {
const auto handle = createDecoder(1, 66);
std::vector<std::uint64_t> hashes;
const auto take = [&](const OutputInfo& output, const FrameBuffer& frame) {
if (!output.isValid) return false;
check(frame.isAccepted && output.frameWidth == Width && output.frameHeight == CodedHeight, "unexpected IPPP picture");
hashes.push_back(hashNv12(buffer.data(), output.framePitch, output.frameHeight));
return true;
};
const auto flush = [&] {
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Flush_nid_postfix(handle, &frame, &output) == 0, "flush failed");
return take(output, frame);
};
for (std::size_t unit = 0; unit < units.size(); ++unit) {
const InputData input{sizeof(InputData), units[unit].data(), units[unit].size(), 3000 + unit, unit, 0};
FrameBuffer frame{sizeof(FrameBuffer), buffer.data(), buffer.size(), false};
OutputInfo output{sizeof(OutputInfo)};
check(sceVideodec2Decode_nid_postfix(handle, &input, &frame, &output) == 0, "IPPP decode failed");
take(output, frame);
if (flushEachUnit) flush();
}
while (flush()) {}
check(sceVideodec2DeleteDecoder_nid_postfix(handle) == 0, "delete failed");
return hashes;
};
const auto reference = decode(false);
check(reference.size() == units.size(), "an IPPP picture is missing without flushes");
check(decode(true) == reference, "a flush after each access unit lost or changed the pictures that follow it");
}
void testDecodeAfterFlush() {
std::vector<std::uint8_t> buffer(frameBufferBytes());
const auto handle = createDecoder(1, 100);
@@ -891,6 +977,7 @@ int main() {
testTenBit();
testDecodeAfterFlush();
testEmptyAccessUnit();
testFlushAfterEveryUnit();
testPictureOwnership();
testConcurrentOwnership();
std::puts("Videodec2 tests passed");
+6 -2
View File
@@ -139,13 +139,15 @@ static void CheckScan() {
double real = 0;
Require(swscanf_s_nid_postfix(u"1.5e3", u"%lf", &real) == 1 && real == 1500.0, "%lf");
Require(swscanf_s_nid_postfix(u"1e+x", u"%lf", &real) == 0, "%lf incomplete exponent is a matching failure");
Require(swscanf_s_nid_postfix(u"1e+", u"%lf", &real) == 1 && real == 1.0, "%lf converts the mantissa of an incomplete exponent");
long double extended = 0;
Require(swscanf_s_nid_postfix(u"2.25", u"%Lf", &extended) == 1 && extended == 2.25L, "%Lf");
float single = 0;
Require(swscanf_s_nid_postfix(u"-inf", u"%f", &single) == 1 && single == -std::numeric_limits<float>::infinity(), "%f infinity");
Require(swscanf_s_nid_postfix(u"a", u"b%d", &first) == 0, "literal mismatch is a matching failure");
Require(swscanf_s_nid_postfix(u"", u"x") == 0, "literal at end of input returns zero");
Require(swscanf_s_nid_postfix(u"", u"%%") == -1, "percent literal at end of input is an input failure");
Require(swscanf_s_nid_postfix(u"", u"%d", &first) == -1, "empty input is an input failure");
Require(swscanf_s_nid_postfix(u" ", u"%ls", text, std::size_t{6}) == -1, "white space only is an input failure");
Require(swscanf_s_nid_postfix(u"5", u"%d%d", &first, &second) == 1, "input failure after a conversion");
@@ -203,13 +205,15 @@ static void CheckUnsizedScan() {
double real = 0;
Require(swscanf_nid_postfix(u"1.5e3", u"%lf", &real) == 1 && real == 1500.0, "swscanf %lf");
Require(swscanf_nid_postfix(u"1e+x", u"%lf", &real) == 0, "swscanf %lf incomplete exponent is a matching failure");
Require(swscanf_nid_postfix(u"1e+", u"%lf", &real) == 1 && real == 1.0, "swscanf %lf converts the mantissa of an incomplete exponent");
long double extended = 0;
Require(swscanf_nid_postfix(u"2.25", u"%Lf", &extended) == 1 && extended == 2.25L, "swscanf %Lf");
float single = 0;
Require(swscanf_nid_postfix(u"-inf", u"%f", &single) == 1 && single == -std::numeric_limits<float>::infinity(), "swscanf %f infinity");
Require(swscanf_nid_postfix(u"a", u"b%d", &first) == 0, "swscanf literal mismatch is a matching failure");
Require(swscanf_nid_postfix(u"", u"x") == 0, "swscanf literal at end of input returns zero");
Require(swscanf_nid_postfix(u"", u"%%") == -1, "swscanf percent literal at end of input is an input failure");
Require(swscanf_nid_postfix(u"3 x", u"%d%d", &first, &second) == 1 && first == 3, "swscanf partial match");
Require(swscanf_nid_postfix(u"", u"%d", &first) == -1, "swscanf empty input is an input failure");
Require(swscanf_nid_postfix(u"", u"%ls", text) == -1, "swscanf empty input before %ls");
+12
View File
@@ -101,6 +101,14 @@ static void CheckConsumption() {
Require(fgetwc_nid_postfix(stream) == u'b', "the mismatching character is pushed back");
Close(stream);
stream = Open("");
Require(ScanStream(stream, u"x") == 0, "a literal at end of input returns zero");
Close(stream);
stream = Open("");
Require(ScanStream(stream, u"%%") == -1, "a percent literal at end of input is an input failure");
Close(stream);
stream = Open("x");
Require(ScanStream(stream, u"%d", &value) == 0, "a letter is not an integer");
Require(fgetwc_nid_postfix(stream) == u'x', "the rejected letter is pushed back");
@@ -120,6 +128,10 @@ static void CheckConsumption() {
Require(ScanStream(stream, u"%lf", &real) == 1 && real == 2.5, "a float");
Require(fgetwc_nid_postfix(stream) == u'x', "the character after the float");
Close(stream);
stream = Open("1e+");
Require(ScanStream(stream, u"%lf", &real) == 1 && real == 1.0, "an incomplete exponent converts its mantissa");
Close(stream);
}
static void CheckWideText() {
+18
View File
@@ -160,6 +160,16 @@ if(WIN32)
add_test(NAME windows_dependency_diagnostics COMMAND windows_dependency_diagnostics_tests)
endif()
if(NOT ANYPS5_RELINKER_ONLY)
add_test_executable(windows_import_identity_tests EXCLUDE_FROM_ALL ${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/WindowsImportIdentityTests.cpp)
target_link_libraries(windows_import_identity_tests PRIVATE relinker_core)
if(BUILD_TESTING)
set_target_properties(windows_import_identity_tests PROPERTIES EXCLUDE_FROM_ALL FALSE)
add_dependencies(windows_import_identity_tests libs)
add_test(NAME windows_import_identity COMMAND windows_import_identity_tests "${CMAKE_BINARY_DIR}/core/libs/libs")
endif()
endif()
add_test_executable(amd64_only_windows_tests EXCLUDE_FROM_ALL
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/tests/Amd64OnlyWindowsTests.cpp
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/windows/WindowsTrampolineBuilder.cpp
@@ -369,12 +379,15 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/codegen/src/x86/StubBodyBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/src/NidCompute.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/src/Sha1.cpp
)
target_include_directories(linux_load_pages_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include
${CMAKE_CURRENT_SOURCE_DIR}/io/include
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/include
)
add_test(NAME linux_load_pages COMMAND linux_load_pages_tests)
set_tests_properties(linux_load_pages PROPERTIES TIMEOUT 20)
@@ -409,12 +422,15 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/src/general/SectionHeaderTableBuilder.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/ByteWriter.cpp
${CMAKE_CURRENT_SOURCE_DIR}/io/src/BufferUtils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/src/NidCompute.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/src/Sha1.cpp
)
target_include_directories(amd64_only_converter_tests PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/domain/include
${CMAKE_CURRENT_SOURCE_DIR}/codegen/include
${CMAKE_CURRENT_SOURCE_DIR}/elfpatcher/include
${CMAKE_CURRENT_SOURCE_DIR}/io/include
${CMAKE_CURRENT_SOURCE_DIR}/../libs/nid/include
)
if(APPLE AND (CMAKE_OSX_ARCHITECTURES STREQUAL "x86_64" OR (NOT CMAKE_OSX_ARCHITECTURES AND CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")))
target_link_options(amd64_only_converter_tests PRIVATE "LINKER:-pagezero_size,0x10000")
@@ -598,6 +614,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_guest_host_libc.py $<TARGET_FILE:relinker>)
add_test(NAME windows_named_imports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_named_imports.py $<TARGET_FILE:relinker>)
add_test(NAME linux_named_imports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_named_imports.py $<TARGET_FILE:relinker>)
add_library(guest_module_args_host SHARED elfpatcher/tests/GuestModuleArgsHost.cpp)
target_include_directories(guest_module_args_host PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../libs)
set_target_properties(guest_module_args_host PROPERTIES
@@ -106,6 +106,7 @@ struct SysVDynamicSection {
std::vector<GuestRuntime> GuestModules;
std::map<VirtualAddress, std::string> ImportModules;
std::set<VirtualAddress> FunctionImports;
std::set<VirtualAddress> NamedImports;
std::set<std::string> UnmatchedImportModules;
std::vector<std::string> NeededLibraries;
};
@@ -4,10 +4,13 @@
#include <elfpatcher/general/SectionHeaderTableRequest.hpp>
#include <codegen/x86/Amd64OnlySubstitutionTable.hpp>
#include <codegen/x86/StubBodyBuilder.hpp>
#include <domain/ImportModule.hpp>
#include <io/BufferUtils.hpp>
#include <nid/NidCompute.hpp>
#include <algorithm>
#include <limits>
#include <map>
#include <set>
#include <span>
#include <sstream>
#include <string>
@@ -190,6 +193,46 @@ std::vector<TracedImport> TracedImports(const Domain::SysVDynamicSection& dynSec
return imports;
}
Domain::SysVDynamicSection ImportsByNid(Domain::SysVDynamicSection dynSection) {
RequireWholeEntries(dynSection.DynamicSegmentData, kDynEntrySize, "Executable dependency table");
for (std::size_t entry = 0; entry < dynSection.DynamicSegmentData.size(); entry += kDynEntrySize) {
if (static_cast<std::int64_t>(Io::ReadU64(dynSection.DynamicSegmentData, entry)) != DT_NEEDED)
continue;
const auto offset = Io::ReadU64(dynSection.DynamicSegmentData, entry + 8);
if (offset >= dynSection.DynStrData.size())
throw Domain::RelinkerException("DT_NEEDED string offset is out of bounds", offset);
const auto name = ReadDynamicString(dynSection.DynStrData, static_cast<std::uint32_t>(offset));
const auto library = Domain::NeededLibraryName(name);
if (library == name)
continue;
Io::WriteU64(dynSection.DynamicSegmentData, entry + 8, dynSection.DynStrData.size());
Io::AppendString(dynSection.DynStrData, library);
}
for (auto& library : dynSection.NeededLibraries)
library = Domain::NeededLibraryName(library);
std::set<std::uint64_t> symbols;
for (const auto* table : {&dynSection.RelaData, &dynSection.RelaPltData}) {
RequireWholeEntries(*table, kRelaEntrySize, "Relocation table");
for (std::size_t entry = 0; entry < table->size(); entry += kRelaEntrySize)
if (dynSection.NamedImports.contains(Io::ReadU64(*table, entry)))
symbols.insert(Io::ReadU64(*table, entry + 8) >> 32);
}
for (const auto symbol : symbols) {
if (symbol >= dynSection.DynSymData.size() / kSymEntrySize)
throw Domain::RelinkerException("Invalid import symbol index", symbol);
const auto symbolOffset = static_cast<std::size_t>(symbol * kSymEntrySize);
const auto name = ReadDynamicString(dynSection.DynStrData, Io::ReadU32(dynSection.DynSymData, symbolOffset));
if (name.empty() || name.find('#') != std::string::npos)
continue;
if (dynSection.DynStrData.size() > std::numeric_limits<std::uint32_t>::max())
throw Domain::RelinkerException("Dynamic string table too large");
Io::WriteU32(dynSection.DynSymData, symbolOffset, static_cast<std::uint32_t>(dynSection.DynStrData.size()));
Io::AppendString(dynSection.DynStrData, Nid::ComputeNid(name, ""));
}
return dynSection;
}
std::vector<std::uint8_t> TracePrologue(const std::uint64_t vaddr, const std::uint64_t table, const std::uint64_t tableEnd, const std::uint64_t function) {
std::vector<std::uint8_t> code = {0x52, 0x53, 0x41, 0x54, 0x48, 0x83, 0xec, 0x08};
const auto rip = [&](std::initializer_list<std::uint8_t> opcode, const std::uint64_t target) {
@@ -297,13 +340,14 @@ void LinuxElfPatcher::_appendTrampoline(
std::vector<std::uint8_t> LinuxElfPatcher::Patch(
std::vector<std::uint8_t> sourceElf,
const std::vector<Domain::ProgramHeader>& originalHeaders,
const Domain::SysVDynamicSection& dynSection,
const Domain::SysVDynamicSection& sourceDynSection,
const std::uint64_t originalPltGotVaddr,
const std::string& runPath,
const bool lazyBinding,
const bool dependencyDiagnostics,
const std::vector<Codegen::TrampolineSite>& trampolines)
{
const auto dynSection = ImportsByNid(sourceDynSection);
if (dependencyDiagnostics)
throw Domain::RelinkerException("Linux target does not support --windows-diagnostics");
if (_fixedBase && lazyBinding)
@@ -118,6 +118,23 @@ void windowsStubRejectsAnOverlongAllocatorPath() {
throw std::runtime_error("an allocator path past the startup buffer was accepted");
}
void windowsLibSceAmprObjectImportsFallBackToTheLibcProvider() {
using namespace Elfpatcher::Windows;
const auto nativeImports = WindowsImportBuilder().Build(0x1000);
const std::vector<std::string> libraries{"libSceAmpr.prx", "libc.prx"};
const std::vector<PeImport> amprImport{{.Name = "-mLzBSk-VGs", .TargetRva = 0x2000, .Addend = 0, .Library = "libSceAmpr.prx"}};
const auto amprStub = WindowsEntryStubBuilder().Build(0x40000, 0x1000, nativeImports, libraries, amprImport, "$ORIGIN/libs", false, false);
const std::vector<std::uint8_t> fallbackOrder{0, 0, 0, 0, 1, 0, 0, 0};
require(std::search(amprStub.Data.Data.begin(), amprStub.Data.Data.end(), fallbackOrder.begin(), fallbackOrder.end()) != amprStub.Data.Data.end(),
"a libSceAmpr-qualified locale object import does not fall back to libc");
const std::vector<PeImport> libcImport{{.Name = "-mLzBSk-VGs", .TargetRva = 0x2000, .Addend = 0, .Library = "libc.prx"}};
const auto libcStub = WindowsEntryStubBuilder().Build(0x40000, 0x1000, nativeImports, libraries, libcImport, "$ORIGIN/libs", false, false);
const std::vector<std::uint8_t> libcOrder{1, 0, 0, 0};
require(std::search(libcStub.Data.Data.begin(), libcStub.Data.Data.end(), libcOrder.begin(), libcOrder.end()) != libcStub.Data.Data.end(),
"a libc-qualified locale object import does not resolve through libc");
}
void windowsKernelVariantImportsSearchCanonicalKernelAfterTheRequestedVariant() {
using namespace Elfpatcher::Windows;
const auto nativeImports = WindowsImportBuilder().Build(0x1000);
@@ -153,6 +170,7 @@ int main() {
windowsStubLoadsTheAllocatorOnRequest("C:/Games/Title/libs", u"C:\\Games\\Title\\libs\\", {0x48, 0x8d, 0x0d});
windowsStubLoadsTheAllocatorOnRequest("$ORIGIN/libs", u"libs\\", {0x48, 0x8d, 0x35});
windowsStubRejectsAnOverlongAllocatorPath();
windowsLibSceAmprObjectImportsFallBackToTheLibcProvider();
windowsKernelVariantImportsSearchCanonicalKernelAfterTheRequestedVariant();
windowsKernelVariantDependenciesLoadCanonicalKernel();
} catch (const std::exception& error) {
@@ -0,0 +1,162 @@
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <windows.h>
#include <elfpatcher/windows/WindowsEntryStubBuilder.hpp>
#include <elfpatcher/windows/WindowsImportBuilder.hpp>
#include <elfpatcher/windows/WindowsPeWriter.hpp>
#include <array>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <random>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace {
namespace Fs = std::filesystem;
using namespace Elfpatcher::Windows;
constexpr std::uint32_t GotRva = LoadRva + 0x1000;
constexpr std::array<const char*, 3> LocaleObjects{"-mLzBSk-VGs", "a54t8+k7KpY", "KfcTPbeaOqg"};
void require(const bool condition, const std::string& message) {
if (!condition) throw std::runtime_error(message);
}
Fs::path createFixtureDirectory(const Fs::path& parent) {
std::random_device random;
for (int attempt = 0; attempt < 100; ++attempt) {
auto name = Fs::path("windows-import-identity-");
name += std::to_string(GetCurrentProcessId()) + "-" + std::to_string(random());
const auto directory = parent / name;
if (Fs::create_directory(directory)) return directory;
}
throw std::runtime_error("Cannot create a unique import identity fixture directory");
}
void appendDisplacement(std::vector<std::uint8_t>& code, const std::int32_t displacement) {
for (int i = 0; i < 4; ++i) code.push_back(static_cast<std::uint8_t>((static_cast<std::uint32_t>(displacement) >> (8 * i)) & 0xffu));
}
// For each pair of GOT slots: mov rax,[a]; cmp rax,[b]; jne fail. Then xor eax,eax; ret. fail: mov eax,1; ret.
std::vector<std::uint8_t> compareCode(const std::vector<std::pair<int, int>>& pairs) {
std::vector<std::uint8_t> code;
const auto failOffset = static_cast<std::int32_t>(pairs.size() * 16 + 3);
for (std::size_t i = 0; i < pairs.size(); ++i) {
const auto base = static_cast<std::uint32_t>(i * 16);
code.insert(code.end(), {0x48, 0x8b, 0x05});
appendDisplacement(code, static_cast<std::int32_t>(GotRva + 8u * static_cast<std::uint32_t>(pairs[i].first)) - static_cast<std::int32_t>(LoadRva + base + 7));
code.insert(code.end(), {0x48, 0x3b, 0x05});
appendDisplacement(code, static_cast<std::int32_t>(GotRva + 8u * static_cast<std::uint32_t>(pairs[i].second)) - static_cast<std::int32_t>(LoadRva + base + 14));
code.push_back(0x75);
code.push_back(static_cast<std::uint8_t>(failOffset - static_cast<std::int32_t>(base + 16)));
}
code.insert(code.end(), {0x31, 0xc0, 0xc3});
code.insert(code.end(), {0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3});
return code;
}
std::uint32_t slot(const int index) {
return GotRva + 8u * static_cast<std::uint32_t>(index);
}
void generateProbe(const Fs::path& file, const std::string& libraries, const std::vector<PeImport>& peImports, const std::vector<std::pair<int, int>>& pairs) {
const std::vector<std::string> loaded{"libSceAmpr.prx", "libkernel.prx", "libc.prx"};
PeSection entry{".guest", LoadRva, SectionRead | SectionExecute | 0x20u, compareCode(pairs)};
PeSection got{".got", GotRva, SectionRead | SectionWrite | 0x40u, std::vector<std::uint8_t>(8 * 8)};
const auto imports = WindowsImportBuilder().Build(LoadRva + 0x2000);
const auto startup = WindowsEntryStubBuilder().Build(LoadRva + 0x3000, entry.Rva, imports, loaded, peImports, libraries, false, false);
std::array<PeDirectory, 16> directories{};
directories[1] = imports.Directory;
directories[3] = startup.ExceptionDirectory;
directories[12] = imports.AddressTable;
const auto image = WindowsPeWriter().Write({entry, got, imports.Section, startup.Data, startup.Code}, startup.Code.Rva, directories, false, ImageBase, false);
std::ofstream stream(file, std::ios::binary);
stream.write(reinterpret_cast<const char*>(image.data()), static_cast<std::streamsize>(image.size()));
require(static_cast<bool>(stream), "Cannot write " + file.string());
}
DWORD runProbe(const Fs::path& probe, std::string& output) {
SECURITY_ATTRIBUTES security{sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE};
HANDLE input = nullptr;
HANDLE written = nullptr;
require(CreatePipe(&input, &written, &security, 0) && SetHandleInformation(input, HANDLE_FLAG_INHERIT, 0), "Cannot create the probe's pipe");
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
startup.dwFlags = STARTF_USESTDHANDLES;
startup.hStdOutput = written;
startup.hStdError = written;
startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
PROCESS_INFORMATION process{};
const auto filename = probe.wstring();
require(CreateProcessW(filename.c_str(), nullptr, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, probe.parent_path().wstring().c_str(), &startup, &process) != 0,
"Cannot start " + probe.filename().string() + ": " + std::to_string(GetLastError()));
CloseHandle(written);
if (WaitForSingleObject(process.hProcess, 120000) != WAIT_OBJECT_0) {
TerminateProcess(process.hProcess, 1);
throw std::runtime_error(probe.filename().string() + " timed out");
}
DWORD status = 0;
require(GetExitCodeProcess(process.hProcess, &status) != 0, "Cannot read the probe's exit code");
char buffer[4096];
DWORD size = 0;
while (ReadFile(input, buffer, sizeof(buffer), &size, nullptr) && size != 0) output.append(buffer, size);
CloseHandle(input);
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
return status;
}
// The generated probes load libc.prx next to the other libraries; its compiler runtime DLLs sit next to this test.
void copyRuntime(const Fs::path& directory) {
wchar_t self[MAX_PATH];
const auto length = GetModuleFileNameW(nullptr, self, MAX_PATH);
if (length == 0 || length >= MAX_PATH) return;
const auto own = Fs::path(self).parent_path();
for (const char* name : {"libgcc_s_seh-1.dll", "libstdc++-6.dll", "libwinpthread-1.dll"}) {
std::error_code error;
if (Fs::exists(own / name, error)) Fs::copy_file(own / name, directory / name, Fs::copy_options::overwrite_existing, error);
}
}
}
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "usage: windows_import_identity_tests <patched libraries directory>\n";
return 2;
}
try {
const auto libraries = Fs::absolute(argv[1]).string();
const auto directory = createFixtureDirectory(Fs::temp_directory_path());
copyRuntime(directory);
std::vector<PeImport> same;
for (int i = 0; i < 3; ++i) {
same.push_back({.Name = LocaleObjects[i], .TargetRva = slot(2 * i), .Addend = 0, .Library = "libSceAmpr.prx"});
same.push_back({.Name = LocaleObjects[i], .TargetRva = slot(2 * i + 1), .Addend = 0, .Library = "libc.prx"});
}
generateProbe(directory / "same.exe", libraries, same, {{0, 1}, {2, 3}, {4, 5}});
const std::vector<PeImport> different{
{.Name = LocaleObjects[0], .TargetRva = slot(0), .Addend = 0, .Library = "libSceAmpr.prx"},
{.Name = "k+AXqu2-eBc", .TargetRva = slot(6), .Addend = 0, .Library = "libkernel.prx"},
};
generateProbe(directory / "different.exe", libraries, different, {{0, 6}});
std::string output;
const auto sameStatus = runProbe(directory / "same.exe", output);
require(sameStatus == 0, "A libSceAmpr.prx-qualified import of the locale objects did not resolve to libc's object (exit " + std::to_string(sameStatus) + ")\n" + output);
output.clear();
const auto differentStatus = runProbe(directory / "different.exe", output);
require(differentStatus == 1, "The probe did not tell two different addresses apart (exit " + std::to_string(differentStatus) + ")\n" + output);
std::error_code error;
Fs::remove_all(directory, error);
std::cout << "Windows import identity tests passed\n";
} catch (const std::exception& error) {
std::cerr << error.what() << '\n';
return 1;
}
return 0;
}
@@ -1,5 +1,7 @@
#include <relinker/output/SysVDynamicSectionBuilder.hpp>
#include <elfpatcher/general/ElfConstants.hpp>
#include <domain/ImportModule.hpp>
#include <algorithm>
#include <cstring>
namespace Relinker {
@@ -77,6 +79,9 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection(
_appendElfSym(result.DynSymData, 0, 0, 0, 0, 0, 0);
const bool consoleFileNames = std::any_of(neededLibraries.begin(), neededLibraries.end(),
[](const std::string& name) { return Domain::NeededLibraryName(name) != name; });
auto stripHashSuffix = [](const std::string& value) -> std::string {
const auto hashPos = value.find('#');
if (hashPos == std::string::npos) return value;
@@ -98,6 +103,7 @@ SysVDynamicSection SysVDynamicSectionBuilder::BuildDynamicSection(
for (const auto& ref : nidReferences) {
if (!ref.Library.empty()) result.ImportModules.emplace(ref.RelocationAddress, ref.Library);
if (ref.Function) result.FunctionImports.insert(ref.RelocationAddress);
if (consoleFileNames && ref.Library.empty() && !ref.Nid.empty() && ref.Nid.find('#') == std::string::npos) result.NamedImports.insert(ref.RelocationAddress);
std::uint32_t relType = ref.RelocationTypeValue;
if (relType == 0) relType = R_X86_64_JUMP_SLOT;
@@ -98,7 +98,7 @@ def main():
assert not artifact.exists(), artifact
assert len(list((output.parent / 'app0').rglob('*.guest.prx'))) == 1
if not windows:
assert 'libGuest.suprx' in needed_libraries(output.read_bytes())
assert 'libGuest.prx' in needed_libraries(output.read_bytes())
result, output = convert(f'outside-ambiguous-{windows}', windows, 'libGuest.suprx', module_directory='.',
providers={'one.prx': declared_provider(22, ('libGuest',))},
@@ -187,7 +187,7 @@ def main():
if windows:
run(output, error='Failed to load module:')
else:
assert request in needed_libraries(output.read_bytes())
assert request.replace('.suprx', '.prx') in needed_libraries(output.read_bytes())
providers = {'wrong.prx': declared_provider(11, ('other',)),
'right.prx': declared_provider(22, ('libGuest',))}
@@ -186,7 +186,7 @@ def main():
assert output.exists(), output
assert not list((case / "app0").rglob("*.guest.prx"))
if not windows:
assert needed_libraries(output.read_bytes()) == ["needed.debug_prx" if "debug-name" in case.name else "needed.prx"]
assert needed_libraries(output.read_bytes()) == ["needed.prx"]
case = work / f"{windows}-ambiguous"
for name in ("first", "second"):
@@ -62,7 +62,7 @@ def main():
assert output.is_file()
assert not list((output.parent / 'app0').rglob('*.guest.prx'))
if not windows:
assert needed_libraries(output.read_bytes()) == ['libGuest.suprx']
assert needed_libraries(output.read_bytes()) == ['libGuest.prx']
result, output = convert(f'{label}-{identity}-excluded', windows, 'libGuest.suprx', files, (filename,))
assert result.returncode == 2 and 'Excluded guest module file not found' in result.stderr, result.stderr
assert not output.exists()
@@ -0,0 +1,84 @@
import os
from pathlib import Path
import platform
import subprocess
import sys
import tempfile
sys.path.insert(0, str(Path(__file__).resolve().parent / "macos"))
from guest_import_fixtures import executable
from nid import nid
from test_guest_module_directories import module_with_symbol, needed_libraries
from test_guest_symbol_names import imported_symbols
def library(work, name, symbol, value):
source = work / (name + ".c")
source.write_text("#include <unistd.h>\n"
f"int exported(void) __asm__(\"\\\"{symbol}\\\"\");\n"
f"int exported(void) {{ _exit({value}); }}\n")
output = work / (name + ".so")
subprocess.run([os.environ.get("CC", "cc"), "-shared", "-fPIC", str(source), "-o", str(output)], check=True)
return output.read_bytes()
def convert(relinker, case, image, module=None):
case.mkdir()
if module is not None:
(case / "sce_module").mkdir()
(case / "sce_module" / "bundled.prx").write_bytes(module)
source = case / "input.elf"
source.write_bytes(image)
output = case / "output.elf"
options = [] if module is not None else ["--skip-sce-module"]
result = subprocess.run([str(relinker), *options, str(source), str(output)], capture_output=True, text=True, timeout=30)
assert result.returncode == 0, (case.name, result.stdout, result.stderr)
return output
def run(output, library):
(output.parent / "libs").mkdir()
(output.parent / "libs" / "available.prx").write_bytes(library)
output.chmod(0o755)
return subprocess.run([str(output)], capture_output=True, text=True, timeout=30)
def main():
relinker = Path(sys.argv[1]).resolve()
runnable = sys.platform.startswith("linux") and platform.machine() in ("x86_64", "AMD64")
with tempfile.TemporaryDirectory(prefix="anyps5-linux-named-imports-") as directory:
work = Path(directory)
cases = {
"sprx": convert(relinker, work / "sprx", executable("available.sprx", "shared")),
"prx": convert(relinker, work / "prx", executable("available.prx", "shared")),
"qualified": convert(relinker, work / "qualified", executable("available.sprx", "shared#A#B")),
"name": convert(relinker, work / "name", executable("available.sprx", "shared")),
"guest": convert(relinker, work / "guest", executable("bundled.prx", "shared", extra_dependencies=("available.sprx",)),
module_with_symbol(True)),
}
image = cases["sprx"].read_bytes()
assert imported_symbols(image) == {nid("shared")}, imported_symbols(image)
assert needed_libraries(image) == ["available.prx"], needed_libraries(image)
assert imported_symbols(cases["prx"].read_bytes()) == {"shared"}
assert imported_symbols(cases["qualified"].read_bytes()) == {"shared"}
image = cases["guest"].read_bytes()
assert imported_symbols(image) == {"shared#guest"}, imported_symbols(image)
assert "available.prx" in needed_libraries(image), needed_libraries(image)
if not runnable:
return
by_nid = library(work, "by-nid", nid("shared"), 22)
by_name = library(work, "by-name", "shared", 44)
result = run(cases["sprx"], by_nid)
assert result.returncode == 22, (result.returncode, result.stdout, result.stderr)
result = run(cases["prx"], by_name)
assert result.returncode == 44, (result.returncode, result.stdout, result.stderr)
result = run(cases["qualified"], by_nid)
assert result.returncode != 0 and "undefined symbol: shared" in result.stderr, (result.returncode, result.stderr)
result = run(cases["name"], by_name)
assert result.returncode != 0 and f"undefined symbol: {nid('shared')}" in result.stderr, (result.returncode, result.stderr)
print("Linux named import tests passed")
if __name__ == "__main__":
main()
@@ -228,7 +228,7 @@ ImageDecode inspectImageDescriptor(const DescriptorValue& descriptor, const Imag
return rejectImage(PoisonReason::Fmask, "FMASK requires a direct sampled image load");
}
if (decoded.fmask && format != IrBufferFormat::Fmask32_S8_F8) return rejectImage(PoisonReason::Fmask, "FMASK format is not implemented");
if (decoded.fmask && (descriptor.dwords[6] & (3u << 20u)) != 0u) return rejectImage(PoisonReason::Fmask, "FMASK CMASK compression is not implemented");
if (decoded.fmask && ((descriptor.dwords[6] & (1u << 20u)) != 0u || ((descriptor.dwords[6] & (1u << 21u)) != 0u && (descriptor.dwords[6] >> 24u) == 0u && descriptor.dwords[7] == 0u))) return rejectImage(PoisonReason::Fmask, "FMASK CMASK compression is not implemented without readable metadata");
if (decoded.fmask && (base.packed || base.byElements != 0u)) return rejectImage(PoisonReason::Fmask, "packed FMASK image reads are not implemented");
if (base.packed) {
if (table || (!storage && descriptorImageSwizzle(descriptor) != ShaderImageIdentitySwizzle)) {
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more