mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-27 00:28:14 +00:00
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:
commit
4287ca1cf1
123 files changed
+2459
-373
No files matched your search
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(¶m, nullptr, 0) == SCE_NGS2_ERROR_INVALID_OUT_ADDRESS);
|
||||
RejectListener(nullptr, 0);
|
||||
RejectListener(¶m, 2);
|
||||
RejectListener(¶m, 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(¶m, &work, 2) == static_cast<int>(0x804A8463));
|
||||
Require(sceNgs2GeomCalcListener(¶m, &work, 0x80000000u) == static_cast<int>(0x804A8463));
|
||||
Require(sceNgs2GeomCalcListener(¶m, &work, 0xffffffffu) == static_cast<int>(0x804A8463));
|
||||
Require(sceNgs2GeomCalcListener(¶m, 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(¶m, 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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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; }
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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)));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(¤t) == 0 && current.tick >= debugClock.tick && current.tick < debugClock.tick + 1000000);
|
||||
Require(sceRtcGetCurrentRawNetworkTick(¤t) == 0 && current.tick > unixEpochTick);
|
||||
}
|
||||
Require(sceRtcInit() == 0);
|
||||
Require(sceRtcEnd() == 0);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
Reference in new issue
Block a user