diff --git a/.ci/deploy-windows-clang.sh b/.ci/deploy-windows-clang.sh index 32c6f24808..07b4866fc4 100644 --- a/.ci/deploy-windows-clang.sh +++ b/.ci/deploy-windows-clang.sh @@ -23,7 +23,7 @@ cmake -DMSYS2_CLANG_BIN="$MSYS2_CLANG_BIN" -DMSYS2_USR_BIN="$MSYS2_USR_BIN" -Dex mkdir ./bin/config mkdir ./bin/config/input_configs curl -fsSL 'https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/master/gamecontrollerdb.txt' 1> ./bin/config/input_configs/gamecontrollerdb.txt -curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat +curl -fsSL 'https://rpcs3.net/compatibility?api=v1&export' | iconv -f ISO-8859-1 -t UTF-8 1> ./bin/GuiConfigs/compat_database.dat # Download translations mkdir -p ./bin/share/qt6/translations @@ -50,7 +50,7 @@ fi # Generate sha256 hashes # Write to file for GitHub releases sha256sum "$BUILD" | awk '{ print $1 }' | tee "$BUILD.sha256" -echo "$(cat "$BUILD.sha256");$(stat -c %s "$BUILD")B" > GitHubReleaseMessage.txt +echo "$(cat "$BUILD.sha256");$(stat -c %s "$BUILD")B" > "$RELEASE_MESSAGE" # Move files to publishing directory mkdir -p "$ARTIFACT_DIR" diff --git a/.ci/setup-windows-ci-vars.sh b/.ci/setup-windows-ci-vars.sh index 11373e0716..369450972c 100644 --- a/.ci/setup-windows-ci-vars.sh +++ b/.ci/setup-windows-ci-vars.sh @@ -13,14 +13,21 @@ COMM_TAG=$(awk '/version{.*}/ { printf("%d.%d.%d", $5, $6, $7) }' ./rpcs3/rpcs3_ COMM_COUNT=$(git rev-list --count HEAD) COMM_HASH=$(git rev-parse --short=8 HEAD) +# Differentiate Windows builds +if [ "$COMPILER" = 'clang' ];then + BUILD_SUFFIX="win64_${CPU_ARCH}_${COMPILER}" +else + BUILD_SUFFIX="${CPU_ARCH}_${COMPILER}" +fi + # Format the above into filenames if [ -n "$PR_NUMBER" ]; then AVVER="${COMM_TAG}-${COMM_HASH}" - BUILD_RAW="rpcs3-v${AVVER}_${CPU_ARCH}_${COMPILER}" + BUILD_RAW="rpcs3-v${AVVER}_${BUILD_SUFFIX}" BUILD="${BUILD_RAW}.7z" else AVVER="${COMM_TAG}-${COMM_COUNT}" - BUILD_RAW="rpcs3-v${AVVER}-${COMM_HASH}_${CPU_ARCH}_${COMPILER}" + BUILD_RAW="rpcs3-v${AVVER}-${COMM_HASH}_${BUILD_SUFFIX}" BUILD="${BUILD_RAW}.7z" fi diff --git a/.github/workflows/rpcs3.yml b/.github/workflows/rpcs3.yml index 4e078a3732..db2fd01389 100644 --- a/.github/workflows/rpcs3.yml +++ b/.github/workflows/rpcs3.yml @@ -349,6 +349,7 @@ jobs: name: ARM64 env: CCACHE_DIR: 'C:\ccache' + RELEASE_MESSAGE: ../GitHubReleaseMessage.txt name: RPCS3 Windows Clang ${{ matrix.arch }} runs-on: ${{ matrix.os }} steps: @@ -372,6 +373,7 @@ jobs: mingw-w64-clang-${{ matrix.arch }}-llvm mingw-w64-clang-${{ matrix.arch }}-ffmpeg mingw-w64-clang-${{ matrix.arch }}-opencv + mingw-w64-clang-${{ matrix.arch }}-iconv mingw-w64-clang-${{ matrix.arch }}-glew mingw-w64-clang-${{ matrix.arch }}-vulkan mingw-w64-clang-${{ matrix.arch }}-vulkan-headers @@ -404,6 +406,24 @@ jobs: .ci/setup-windows-ci-vars.sh ${{ matrix.arch }} ${{ matrix.compiler }} .ci/build-windows-clang.sh ${{ matrix.arch }} ${{ matrix.msys2 }} + - name: Deploy master build to GitHub Releases (only aarch64) + if: | + matrix.arch == 'aarch64' && + github.event_name != 'pull_request' && + github.repository == 'RPCS3/rpcs3' && + github.ref == 'refs/heads/master' + env: + RPCS3_TOKEN: ${{ secrets.RPCS3_TOKEN }} + # We specify it here since this upload is specific to arm64 + UPLOAD_COMMIT_HASH: ee05050fd1d8488148a771b526702656a10dacf0 + UPLOAD_REPO_FULL_NAME: "RPCS3/rpcs3-binaries-win-arm64" + run: | + COMM_TAG=$(awk '/version{.*}/ { printf("%d.%d.%d", $5, $6, $7) }' ./rpcs3/rpcs3_version.cpp) + COMM_COUNT=$(git rev-list --count HEAD) + COMM_HASH=$(git rev-parse --short=8 HEAD) + export AVVER="${COMM_TAG}-${COMM_COUNT}" + .ci/github-upload.sh + - name: Save build Ccache if: github.ref == 'refs/heads/master' uses: actions/cache/save@main diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 0c9b6d7f1e..d35dfd1ca8 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -424,6 +424,7 @@ target_sources(rpcs3_emu PRIVATE Io/TopShotFearmaster.cpp Io/Turntable.cpp Io/usb_device.cpp + Io/usb_microphone.cpp Io/usb_vfs.cpp Io/usio.cpp Io/LogitechG27.cpp diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index 7cb9186d4d..f799e4b6be 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -201,14 +201,6 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin m_use_vnni = true; m_use_gfni = true; } - - // Aarch64 CPUs - if (cpu == "cyclone" || cpu.contains("cortex")) - { - m_use_fma = true; - // AVX does not use intrinsics so far - m_use_avx = true; - } } llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index c709349080..99ddafde0a 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -3081,13 +3081,22 @@ protected: // Allow PSHUFB intrinsic bool m_use_ssse3 = true; +#ifdef ARCH_ARM64 + // all arm CPUS have FMA + bool m_use_fma = true; + // Should be nonsense to set this for ARM, + // but this flag is only used in SPU verification + // For now, setting this flag will speed up SPU verification + // but I will remove this later with explicit parralelism - Whatcookie + bool m_use_avx = true; +#else // Allow FMA bool m_use_fma = false; // Allow AVX bool m_use_avx = false; - +#endif // Allow skylake-x tier AVX-512 bool m_use_avx512 = false; diff --git a/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp b/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp index a96017b16b..4c219b71b0 100644 --- a/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp @@ -98,16 +98,26 @@ u32 gcmGetLocalMemorySize(u32 sdk_version) error_code gcmMapEaIoAddress(ppu_thread& ppu, u32 ea, u32 io, u32 size, bool is_strict); -u32 gcmIoOffsetToAddress(u32 ioOffset) +u32 gcmIoOffsetToAddress(u32 io_offs) { - const u32 upper12Bits = g_fxo->get().offsetTable.eaAddress[ioOffset >> 20]; + u32 upper_12bits = 0; - if (upper12Bits > 0xBFF) + if (io_offs < 0x20000000) { - return 0; + upper_12bits = rsx::get_current_renderer()->iomap_table.ea[io_offs >> 20]; + + if (upper_12bits >= rsx::constants::local_mem_base) + { + upper_12bits = 0; + } } - return (upper12Bits << 20) | (ioOffset & 0xFFFFF); + if (!upper_12bits) + { + cellGcmSys.error("Failed to convert io offset: 0x%x", io_offs); + } + + return upper_12bits | (io_offs & 0xFFFFF); } void InitOffsetTable() @@ -132,15 +142,15 @@ u32 cellGcmGetLabelAddress(u8 index) return rsx::get_current_renderer()->label_addr + 0x10 * index; } -vm::ptr cellGcmGetReportDataAddressLocation(u32 index, u32 location) +vm::ptr cellGcmGetReportDataAddressLocation(ppu_thread& ppu, u32 index, u32 location) { - cellGcmSys.warning("cellGcmGetReportDataAddressLocation(index=%d, location=%d)", index, location); + cellGcmSys.trace("cellGcmGetReportDataAddressLocation(index=%d, location=%d)", index, location); if (location == CELL_GCM_LOCATION_MAIN) { if (index >= 1024 * 1024) { - cellGcmSys.error("cellGcmGetReportDataAddressLocation: Wrong main index (%d)", index); + cellGcmSys.error("%s: Wrong main index (%d)", ppu.current_function, index); } return vm::cast(gcmIoOffsetToAddress(0x0e000000 + index * 0x10)); @@ -150,7 +160,7 @@ vm::ptr cellGcmGetReportDataAddressLocation(u32 index, u32 lo if (index >= 2048) { - cellGcmSys.error("cellGcmGetReportDataAddressLocation: Wrong local index (%d)", index); + cellGcmSys.error("%s: Wrong local index (%d)", ppu.current_function, index); } return vm::cast(rsx::get_current_renderer()->label_addr + ::offset32(&RsxReports::report) + index * 0x10); @@ -225,20 +235,22 @@ u32 cellGcmGetReportDataAddress(u32 index) return rsx::get_current_renderer()->label_addr + ::offset32(&RsxReports::report) + index * 0x10; } -u32 cellGcmGetReportDataLocation(u32 index, u32 location) +u32 cellGcmGetReportDataLocation(ppu_thread& ppu, u32 index, u32 location) { cellGcmSys.warning("cellGcmGetReportDataLocation(index=%d, location=%d)", index, location); - vm::ptr report = cellGcmGetReportDataAddressLocation(index, location); + vm::ptr report = cellGcmGetReportDataAddressLocation(ppu, index, location); return report->value; } -u64 cellGcmGetTimeStampLocation(u32 index, u32 location) +u64 cellGcmGetTimeStampLocation(ppu_thread& ppu, u32 index, u32 location) { - cellGcmSys.warning("cellGcmGetTimeStampLocation(index=%d, location=%d)", index, location); + cellGcmSys.trace("cellGcmGetTimeStampLocation(index=%d, location=%d)", index, location); - // NOTE: No error checkings - return cellGcmGetReportDataAddressLocation(index, location)->timer; + vm::ptr report = cellGcmGetReportDataAddressLocation(ppu, index, location); + + // Timestamp reports don't need host GPU access and are much faster to emulate + return vm::get_super_ptr(report.addr())->timer; } //---------------------------------------------------------------------------- @@ -1508,8 +1520,8 @@ DECLARE(ppu_module_manager::cellGcmSys)("cellGcmSys", []() REG_FUNC(cellGcmSys, cellGcmGetReportDataAddress); REG_FUNC(cellGcmSys, cellGcmGetReportDataAddressLocation); REG_FUNC(cellGcmSys, cellGcmGetReportDataLocation); - REG_FUNC(cellGcmSys, cellGcmGetTimeStamp).flag(MFF_FORCED_HLE); // HLE-ing this allows for optimizations around reports - REG_FUNC(cellGcmSys, cellGcmGetTimeStampLocation); + REG_FUNC(cellGcmSys, cellGcmGetTimeStamp).flag(MFF_FORCED_HLE); // HLE-ing this allows for optimizations around reports + REG_FUNC(cellGcmSys, cellGcmGetTimeStampLocation).flag(MFF_FORCED_HLE); // Ditto // Command Buffer Control REG_FUNC(cellGcmSys, cellGcmGetControlRegister); diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index df638670ba..49975b632c 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -822,7 +822,9 @@ static error_code vdecQueryAttr(s32 type, u32 profile, u32 spec_addr /* may be 0 { case CELL_VDEC_DIVX_QMOBILE : memSize = new_sdk ? 0x11B720 : 0x1DEF30; break; case CELL_VDEC_DIVX_MOBILE : memSize = new_sdk ? 0x19A740 : 0x26DED0; break; - case CELL_VDEC_MPEG4_SIMPLE_PROFILE: // just a guess based on the profile used by singstar before and after update + case CELL_VDEC_MPEG4_PROFILE_1: + case CELL_VDEC_MPEG4_PROFILE_3: + case CELL_VDEC_MPEG4_PROFILE_4: // just a guess based on the profile used by singstar before and after update case CELL_VDEC_DIVX_HOME_THEATER: memSize = new_sdk ? 0x386A60 : 0x498060; break; case CELL_VDEC_DIVX_HD_720 : memSize = new_sdk ? 0x692070 : 0x805690; break; case CELL_VDEC_DIVX_HD_1080 : memSize = new_sdk ? 0xD78100 : 0xFC9870; break; diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.h b/rpcs3/Emu/Cell/Modules/cellVdec.h index 8cf9a0b1de..c8d6df4ae4 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.h +++ b/rpcs3/Emu/Cell/Modules/cellVdec.h @@ -389,7 +389,9 @@ struct CellVdecAvcInfo // DIVX Profile enum DIVX_level : u8 { - CELL_VDEC_MPEG4_SIMPLE_PROFILE = 4, + CELL_VDEC_MPEG4_PROFILE_1 = 1, // SingStar Vol.2 / Vol.3 + CELL_VDEC_MPEG4_PROFILE_3 = 3, // Used for SingStar recordings + CELL_VDEC_MPEG4_PROFILE_4 = 4, // SingStar Pop CELL_VDEC_DIVX_QMOBILE = 10, CELL_VDEC_DIVX_MOBILE = 11, CELL_VDEC_DIVX_HOME_THEATER = 12, diff --git a/rpcs3/Emu/Cell/Modules/sceNp.cpp b/rpcs3/Emu/Cell/Modules/sceNp.cpp index 4b4c3cfd01..2b49309a9b 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp.cpp @@ -3433,7 +3433,7 @@ error_code sceNpLookupUserProfile(s32 transId, vm::cptr npId, vm::ptr npId, vm::ptr userInfo, vm::ptr aboutMe, vm::ptr languages, vm::ptr countryCode, vm::ptr avatarImage, s32 prio, vm::ptr option) { - sceNp.todo("sceNpLookupUserProfile(transId=%d, npId=*0x%x, userInfo=*0x%x, aboutMe=*0x%x, languages=*0x%x, countryCode=*0x%x, avatarImage=*0x%x, prio=%d, option=*0x%x)", transId, npId, userInfo, + sceNp.todo("sceNpLookupUserProfileAsync(transId=%d, npId=*0x%x, userInfo=*0x%x, aboutMe=*0x%x, languages=*0x%x, countryCode=*0x%x, avatarImage=*0x%x, prio=%d, option=*0x%x)", transId, npId, userInfo, aboutMe, languages, countryCode, avatarImage, prio, option); auto& nph = g_fxo->get>(); diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 6d4766a947..70d34aa775 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -1327,7 +1327,11 @@ void PPUTranslator::VMADDFP(ppu_opcode_t op) void PPUTranslator::VMAXFP(ppu_opcode_t op) { const auto [a, b] = get_vrs(op.va, op.vb); +#ifdef ARCH_ARM64 + set_vr(op.vd, vec_handle_result(fmax(a, b))); +#else set_vr(op.vd, vec_handle_result(select(fcmp_ord(a < b) | fcmp_uno(b != b), b, a))); +#endif } void PPUTranslator::VMAXSB(ppu_opcode_t op) @@ -1389,7 +1393,11 @@ void PPUTranslator::VMHRADDSHS(ppu_opcode_t op) void PPUTranslator::VMINFP(ppu_opcode_t op) { const auto [a, b] = get_vrs(op.va, op.vb); +#ifdef ARCH_ARM64 + set_vr(op.vd, vec_handle_result(fmin(a, b))); +#else set_vr(op.vd, vec_handle_result(select(fcmp_ord(a > b) | fcmp_uno(b != b), b, a))); +#endif } void PPUTranslator::VMINSB(ppu_opcode_t op) diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index 3afa2a9e06..dffd211aed 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -14,6 +14,7 @@ #include "Emu/Cell/timers.hpp" #include "Emu/Io/usb_device.h" +#include "Emu/Io/usb_microphone.h" #include "Emu/Io/usb_vfs.h" #include "Emu/Io/Skylander.h" #include "Emu/Io/Infinity.h" @@ -185,6 +186,9 @@ private: // Music devices {0x1415, 0x0000, 0x0000, "Singstar Microphone", nullptr, nullptr}, // {0x1415, 0x0020, 0x0020, "SingStar Microphone Wireless", nullptr, nullptr}, // TODO: verifiy + // {0x12ba, 0x00f0, 0x00f0, "Bandfuse USB Guitar Adapter", nullptr, nullptr}, + // {0x28aa, 0x0001, 0x0001, "Bandfuse USB Microphone", nullptr, nullptr}, + // {0x046d, 0x0a03, 0x0a03, "Logitech Microphone", nullptr, nullptr}, {0x12BA, 0x00FF, 0x00FF, "Rocksmith Guitar Adapter", nullptr, nullptr}, {0x12BA, 0x0100, 0x0100, "Guitar Hero Guitar", nullptr, nullptr}, @@ -549,6 +553,20 @@ usb_handler_thread::usb_handler_thread() } } + switch (g_cfg.audio.microphone_type) + { + case microphone_handler::standard: + usb_devices.push_back(std::make_shared(0, get_new_location(), MicType::Logitech)); + break; + case microphone_handler::real_singstar: + case microphone_handler::singstar: + usb_devices.push_back(std::make_shared(0, get_new_location(), MicType::SingStar)); + break; + case microphone_handler::rocksmith: + usb_devices.push_back(std::make_shared(0, get_new_location(), MicType::Rocksmith)); + break; + } + for (int i = 0; i < 8; i++) // Add VFS USB mass storage devices (/dev_usbXXX) to the USB device list { const auto usb_info = g_cfg_vfs.get_device(g_cfg_vfs.dev_usb, fmt::format("/dev_usb%03d", i)); @@ -1128,7 +1146,7 @@ error_code sys_usbd_finalize(ppu_thread& ppu, u32 handle) // Forcefully awake all waiters while (auto cpu = lv2_obj::schedule(usbh.sq, SYS_SYNC_FIFO)) { - // Special ternimation signal value + // Special termination signal value cpu->gpr[4] = 4; cpu->gpr[5] = 0; cpu->gpr[6] = 0; @@ -1480,7 +1498,7 @@ error_code sys_usbd_transfer_data(ppu_thread& ppu, u32 handle, u32 id_pipe, vm:: case LIBUSB_REQUEST_SET_CONFIGURATION: { pipe.device->set_configuration(static_cast(+request->wValue)); - pipe.device->set_interface(0); + pipe.device->set_interface(0, 0); break; } default: break; @@ -1523,7 +1541,7 @@ error_code sys_usbd_isochronous_transfer_data(ppu_thread& ppu, u32 handle, u32 i { ppu.state += cpu_flag::wait; - sys_usbd.todo("sys_usbd_isochronous_transfer_data(handle=0x%x, id_pipe=0x%x, iso_request=*0x%x)", handle, id_pipe, iso_request); + sys_usbd.trace("sys_usbd_isochronous_transfer_data(handle=0x%x, id_pipe=0x%x, iso_request=*0x%x)", handle, id_pipe, iso_request); auto& usbh = g_fxo->get>(); @@ -1537,8 +1555,21 @@ error_code sys_usbd_isochronous_transfer_data(ppu_thread& ppu, u32 handle, u32 i const auto& pipe = usbh.get_pipe(id_pipe); auto&& [transfer_id, transfer] = usbh.get_free_transfer(); + transfer.iso_request.buf = iso_request->buf; + transfer.iso_request.start_frame = iso_request->start_frame; + transfer.iso_request.num_packets = iso_request->num_packets; + for (u32 index = 0; index < iso_request->num_packets; index++) + { + transfer.iso_request.packets[index] = iso_request->packets[index]; + } + pipe.device->isochronous_transfer(&transfer); + if (transfer.fake) + { + usbh.push_fake_transfer(&transfer); + } + // returns an identifier specific to the transfer return not_an_error(transfer_id); } @@ -1567,7 +1598,7 @@ error_code sys_usbd_get_isochronous_transfer_status(ppu_thread& ppu, u32 handle, { ppu.state += cpu_flag::wait; - sys_usbd.todo("sys_usbd_get_isochronous_transfer_status(handle=0x%x, id_transfer=0x%x, unk1=0x%x, request=*0x%x, result=*0x%x)", handle, id_transfer, unk1, request, result); + sys_usbd.trace("sys_usbd_get_isochronous_transfer_status(handle=0x%x, id_transfer=0x%x, unk1=0x%x, request=*0x%x, result=*0x%x)", handle, id_transfer, unk1, request, result); auto& usbh = g_fxo->get>(); diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.h b/rpcs3/Emu/Cell/lv2/sys_usbd.h index 76f5f0b061..45e9214e82 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.h +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.h @@ -56,7 +56,7 @@ struct UsbDeviceRequest struct UsbDeviceIsoRequest { - vm::ptr buf; + vm::bptr buf; be_t start_frame; be_t num_packets; be_t packets[8]; diff --git a/rpcs3/Emu/Io/usb_device.cpp b/rpcs3/Emu/Io/usb_device.cpp index 9291805a8c..e78b836595 100644 --- a/rpcs3/Emu/Io/usb_device.cpp +++ b/rpcs3/Emu/Io/usb_device.cpp @@ -40,9 +40,10 @@ bool usb_device::set_configuration(u8 cfg_num) return true; } -bool usb_device::set_interface(u8 int_num) +bool usb_device::set_interface(u8 int_num, u8 alt_num) { current_interface = int_num; + current_altsetting = alt_num; return true; } @@ -141,9 +142,9 @@ bool usb_device_passthrough::set_configuration(u8 cfg_num) return (libusb_set_configuration(lusb_handle, cfg_num) == LIBUSB_SUCCESS); }; -bool usb_device_passthrough::set_interface(u8 int_num) +bool usb_device_passthrough::set_interface(u8 int_num, u8 alt_num) { - usb_device::set_interface(int_num); + usb_device::set_interface(int_num, alt_num); return (libusb_claim_interface(lusb_handle, int_num) == LIBUSB_SUCCESS); } @@ -290,7 +291,7 @@ void usb_device_emulated::control_transfer(u8 bmRequestType, u8 bRequest, u16 wV case 0U /*silences warning*/ | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_STANDARD | LIBUSB_RECIPIENT_INTERFACE: // 0x01 switch (bRequest) { - case LIBUSB_REQUEST_SET_INTERFACE: usb_device::set_interface(::narrow(wIndex)); break; + case LIBUSB_REQUEST_SET_INTERFACE: usb_device::set_interface(::narrow(wIndex), ::narrow(wValue)); break; default: sys_usbd.error("Unhandled control transfer(0x%02x): 0x%02x", bmRequestType, bRequest); break; } break; diff --git a/rpcs3/Emu/Io/usb_device.h b/rpcs3/Emu/Io/usb_device.h index 3d60bd36b0..8cd053efe2 100644 --- a/rpcs3/Emu/Io/usb_device.h +++ b/rpcs3/Emu/Io/usb_device.h @@ -86,6 +86,54 @@ struct UsbDeviceHID le_t wDescriptorLength; }; +struct UsbAudioInputTerminal +{ + u8 bDescriptorSubtype; + u8 bTerminalID; + le_t wTerminalType; + u8 bAssocTerminal; + u8 bNrChannels; + le_t wChannelConfig; + u8 iChannelNames; + u8 iTerminal; +}; + +struct UsbAudioOutputTerminal +{ + u8 bDescriptorSubtype; + u8 bTerminalID; + le_t wTerminalType; + u8 bAssocTerminal; + u8 bSourceID; + u8 iTerminal; +}; + +struct UsbAudioInterface +{ + u8 bDescriptorSubtype; + u8 bTerminalLink; + u8 bDelay; + le_t wFormatTag; +}; + +struct UsbAudioEndpoint +{ + u8 bEndpointAddress; + u8 bmAttributes; + le_t wMaxPacketSize; + u8 bInterval; + u8 bRefresh; + u8 bSynchAddress; +}; + +struct UsbAudioStreamingEndpoint +{ + u8 bDescriptorSubtype; + u8 bmAttributes; + u8 bLockDelayUnits; + le_t wLockDelay; +}; + struct UsbTransfer { u32 assigned_number = 0; @@ -183,7 +231,7 @@ public: virtual u32 get_configuration(u8* buf); virtual bool set_configuration(u8 cfg_num); - virtual bool set_interface(u8 int_num); + virtual bool set_interface(u8 int_num, u8 alt_num); virtual void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) = 0; virtual void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) = 0; @@ -198,6 +246,7 @@ public: protected: u8 current_config = 1; u8 current_interface = 0; + u8 current_altsetting = 0; std::array location{}; protected: @@ -214,7 +263,7 @@ public: void read_descriptors() override; u32 get_configuration(u8* buf) override; bool set_configuration(u8 cfg_num) override; - bool set_interface(u8 int_num) override; + bool set_interface(u8 int_num, u8 alt_num) override; void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; void interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer) override; void isochronous_transfer(UsbTransfer* transfer) override; diff --git a/rpcs3/Emu/Io/usb_microphone.cpp b/rpcs3/Emu/Io/usb_microphone.cpp new file mode 100644 index 0000000000..743fadb66a --- /dev/null +++ b/rpcs3/Emu/Io/usb_microphone.cpp @@ -0,0 +1,690 @@ +#include "stdafx.h" +#include "Emu/system_config.h" +#include "Emu/IdManager.h" +#include "Emu/Io/usb_microphone.h" +#include "Emu/Cell/lv2/sys_usbd.h" +#include "Emu/Cell/Modules/cellMic.h" +#include "Input/pad_thread.h" + +LOG_CHANNEL(usb_mic_log); + +usb_device_mic::usb_device_mic(u32 controller_index, const std::array& location, MicType mic_type) + : usb_device_emulated(location) + , m_controller_index(controller_index) + , m_mic_type(mic_type) + , m_sample_rate(8000) + , m_volume(0, 0) +{ + switch (mic_type) + { + case MicType::SingStar: + { + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, + UsbDeviceDescriptor { + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x08, + .idVendor = 0x1415, + .idProduct = 0x0000, + .bcdDevice = 0x0001, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x00, + .bNumConfigurations = 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, + UsbDeviceConfiguration { + .wTotalLength = 0x00b1, + .bNumInterfaces = 0x02, + .bConfigurationValue = 0x01, + .iConfiguration = 0x00, + .bmAttributes = 0x80, + .bMaxPower = 0x2d})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + + static constexpr u8 audio_if0_header[] = { + 0x09, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x01, // bDescriptorSubtype (CS_INTERFACE -> HEADER) + 0x00, 0x01, // bcdADC 1.00 + 0x28, 0x00, // wTotalLength 36 + 0x01, // binCollection 0x01 + 0x01, // baInterfaceNr 1 + }; + config0.add_node(UsbDescriptorNode(audio_if0_header[0], audio_if0_header[1], &audio_if0_header[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInputTerminal { + .bDescriptorSubtype = 0x02, // INPUT_TERMINAL + .bTerminalID = 0x01, + .wTerminalType = 0x0201, // Microphone + .bAssocTerminal = 0x02, + .bNrChannels = 0x02, + .wChannelConfig = 0x0003, + .iChannelNames = 0x00, + .iTerminal = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioOutputTerminal { + .bDescriptorSubtype = 0x03, // OUTPUT_TERMINAL + .bTerminalID = 0x02, + .wTerminalType = 0x0101, // USB Streaming + .bAssocTerminal = 0x01, + .bSourceID = 0x03, + .iTerminal = 0x00})); + + static constexpr u8 audio_if0_feature[] = { + 0x0A, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x06, // bDescriptorSubtype (CS_INTERFACE -> FEATURE_UNIT) + 0x03, // bUnitID + 0x01, // bSourceID + 0x01, // bControlSize 1 + 0x01, 0x02, // bmaControls[0] (Mute,Volume) + 0x02, 0x00, // bmaControls[1] (Volume,None) + }; + config0.add_node(UsbDescriptorNode(audio_if0_feature[0], audio_if0_feature[1], &audio_if0_feature[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x01, + .bNumEndpoints = 0x01, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInterface { + .bDescriptorSubtype = 0x01, // AS_GENERAL + .bTerminalLink = 0x02, + .bDelay = 0x01, + .wFormatTag = 0x0001})); + + static constexpr u8 audio_if1_alt1_type[] = { + 0x17, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x02, // bDescriptorSubtype (CS_INTERFACE -> FORMAT_TYPE) + 0x01, // bFormatType 1 + 0x01, // bNrChannels (Mono) + 0x02, // bSubFrameSize 2 + 0x10, // bBitResolution 16 + 0x05, // bSamFreqType 5 + 0x40, 0x1F, 0x00, // tSamFreq[1] 8000 Hz + 0x11, 0x2B, 0x00, // tSamFreq[2] 11025 Hz + 0x22, 0x56, 0x00, // tSamFreq[3] 22050 Hz + 0x44, 0xAC, 0x00, // tSamFreq[4] 44100 Hz + 0x80, 0xBB, 0x00, // tSamFreq[5] 48000 Hz + }; + config0.add_node(UsbDescriptorNode(audio_if1_alt1_type[0], audio_if1_alt1_type[1], &audio_if1_alt1_type[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbAudioEndpoint { + .bEndpointAddress = 0x81, + .bmAttributes = 0x05, + .wMaxPacketSize = 0x0064, + .bInterval = 0x01, + .bRefresh = 0x00, + .bSynchAddress = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT_ASI, + UsbAudioStreamingEndpoint { + .bDescriptorSubtype = 0x01, // EP_GENERAL + .bmAttributes = 0x01, // Sampling Freq Control + .bLockDelayUnits = 0x00, + .wLockDelay = 0x0000})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x02, + .bNumEndpoints = 0x01, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInterface { + .bDescriptorSubtype = 0x01, // AS_GENERAL + .bTerminalLink = 0x02, + .bDelay = 0x01, + .wFormatTag = 0x0001})); + + static constexpr u8 audio_if1_alt2_type[] = { + 0x17, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x02, // bDescriptorSubtype (CS_INTERFACE -> FORMAT_TYPE) + 0x01, // bFormatType 1 + 0x02, // bNrChannels (Stereo) + 0x02, // bSubFrameSize 2 + 0x10, // bBitResolution 16 + 0x05, // bSamFreqType 5 + 0x40, 0x1F, 0x00, // tSamFreq[1] 8000 Hz + 0x11, 0x2B, 0x00, // tSamFreq[2] 11025 Hz + 0x22, 0x56, 0x00, // tSamFreq[3] 22050 Hz + 0x44, 0xAC, 0x00, // tSamFreq[4] 44100 Hz + 0x80, 0xBB, 0x00, // tSamFreq[5] 48000 Hz + }; + config0.add_node(UsbDescriptorNode(audio_if1_alt2_type[0], audio_if1_alt2_type[1], &audio_if1_alt2_type[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbAudioEndpoint { + .bEndpointAddress = 0x81, + .bmAttributes = 0x05, + .wMaxPacketSize = 0x00c8, + .bInterval = 0x01, + .bRefresh = 0x00, + .bSynchAddress = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT_ASI, + UsbAudioStreamingEndpoint { + .bDescriptorSubtype = 0x01, // EP_GENERAL + .bmAttributes = 0x01, // Sampling Freq Control + .bLockDelayUnits = 0x00, + .wLockDelay = 0x0000})); + + add_string("Nam Tai E&E Products Ltd."); + add_string("USBMIC Serial# 012345678"); + break; + } + case MicType::Logitech: + { + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, + UsbDeviceDescriptor { + .bcdUSB = 0x0200, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x08, + .idVendor = 0x046d, + .idProduct = 0x0a03, + .bcdDevice = 0x0102, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x00, + .bNumConfigurations = 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, + UsbDeviceConfiguration { + .wTotalLength = 0x0079, + .bNumInterfaces = 0x02, + .bConfigurationValue = 0x01, + .iConfiguration = 0x03, + .bmAttributes = 0x80, + .bMaxPower = 0x1e})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + + static constexpr u8 audio_if0_header[] = { + 0x09, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x01, // bDescriptorSubtype (CS_INTERFACE -> HEADER) + 0x00, 0x01, // bcdADC 1.00 + 0x27, 0x00, // wTotalLength 39 + 0x01, // binCollection 0x01 + 0x01, // baInterfaceNr 1 + }; + config0.add_node(UsbDescriptorNode(audio_if0_header[0], audio_if0_header[1], &audio_if0_header[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInputTerminal { + .bDescriptorSubtype = 0x02, // INPUT_TERMINAL + .bTerminalID = 0x0d, + .wTerminalType = 0x0201, // Microphone + .bAssocTerminal = 0x00, + .bNrChannels = 0x01, + .wChannelConfig = 0x0000, + .iChannelNames = 0x00, + .iTerminal = 0x00})); + static constexpr u8 audio_if0_feature[] = { + 0x09, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x06, // bDescriptorSubtype (CS_INTERFACE -> FEATURE_UNIT) + 0x02, // bUnitID + 0x0d, // bSourceID + 0x01, // bControlSize 1 + 0x03, // bmaControls[0] (Mute,Volume) + 0x00, // bmaControls[1] (None) + 0x00, // iFeature + }; + config0.add_node(UsbDescriptorNode(audio_if0_feature[0], audio_if0_feature[1], &audio_if0_feature[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioOutputTerminal { + .bDescriptorSubtype = 0x03, // OUTPUT_TERMINAL + .bTerminalID = 0x0a, + .wTerminalType = 0x0101, // USB Streaming + .bAssocTerminal = 0x00, + .bSourceID = 0x02, + .iTerminal = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x01, + .bNumEndpoints = 0x01, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInterface { + .bDescriptorSubtype = 0x01, // AS_GENERAL + .bTerminalLink = 0x0a, + .bDelay = 0x00, + .wFormatTag = 0x0001})); + + static constexpr u8 audio_if1_alt1_type[] = { + 0x17, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x02, // bDescriptorSubtype (CS_INTERFACE -> FORMAT_TYPE) + 0x01, // bFormatType 1 + 0x01, // bNrChannels (Mono) + 0x02, // bSubFrameSize 2 + 0x10, // bBitResolution 16 + 0x05, // bSamFreqType 5 + 0x40, 0x1F, 0x00, // tSamFreq[1] 8000 Hz + 0x11, 0x2B, 0x00, // tSamFreq[2] 11025 Hz + 0x22, 0x56, 0x00, // tSamFreq[4] 22050 Hz + 0x44, 0xAC, 0x00, // tSamFreq[6] 44100 Hz + 0x80, 0xBB, 0x00, // tSamFreq[7] 48000 Hz + }; + config0.add_node(UsbDescriptorNode(audio_if1_alt1_type[0], audio_if1_alt1_type[1], &audio_if1_alt1_type[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbAudioEndpoint { + .bEndpointAddress = 0x84, + .bmAttributes = 0x0d, + .wMaxPacketSize = 0x0060, + .bInterval = 0x01, + .bRefresh = 0x00, + .bSynchAddress = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT_ASI, + UsbAudioStreamingEndpoint { + .bDescriptorSubtype = 0x01, // EP_GENERAL + .bmAttributes = 0x01, // Sampling Freq Control + .bLockDelayUnits = 0x02, + .wLockDelay = 0x0001})); + + add_string("Logitech"); + add_string("Logitech USB Microphone"); + break; + } + case MicType::Rocksmith: + { + device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE, + UsbDeviceDescriptor { + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x10, + .idVendor = 0x12ba, + .idProduct = 0x00ff, + .bcdDevice = 0x0100, + .iManufacturer = 0x01, + .iProduct = 0x02, + .iSerialNumber = 0x00, + .bNumConfigurations = 0x01}); + auto& config0 = device.add_node(UsbDescriptorNode(USB_DESCRIPTOR_CONFIG, + UsbDeviceConfiguration { + .wTotalLength = 0x0098, + .bNumInterfaces = 0x03, + .bConfigurationValue = 0x01, + .iConfiguration = 0x00, + .bmAttributes = 0x80, + .bMaxPower = 0x32})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x00, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x01, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + + static constexpr u8 audio_if0_header[] = { + 0x09, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x01, // bDescriptorSubtype (CS_INTERFACE -> HEADER) + 0x00, 0x01, // bcdADC 1.00 + 0x27, 0x00, // wTotalLength 39 + 0x01, // binCollection 0x01 + 0x01, // baInterfaceNr 1 + }; + config0.add_node(UsbDescriptorNode(audio_if0_header[0], audio_if0_header[1], &audio_if0_header[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInputTerminal { + .bDescriptorSubtype = 0x02, // INPUT_TERMINAL + .bTerminalID = 0x02, + .wTerminalType = 0x0201, // Microphone + .bAssocTerminal = 0x00, + .bNrChannels = 0x01, + .wChannelConfig = 0x0001, + .iChannelNames = 0x00, + .iTerminal = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioOutputTerminal { + .bDescriptorSubtype = 0x03, // OUTPUT_TERMINAL + .bTerminalID = 0x07, + .wTerminalType = 0x0101, // USB Streaming + .bAssocTerminal = 0x00, + .bSourceID = 0x0a, + .iTerminal = 0x00})); + + static constexpr u8 audio_if0_feature[] = { + 0x09, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x06, // bDescriptorSubtype (CS_INTERFACE -> FEATURE_UNIT) + 0x0a, // bUnitID + 0x02, // bSourceID + 0x01, // bControlSize 1 + 0x03, // bmaControls[0] (Mute,Volume) + 0x00, // bmaControls[1] (None) + 0x00, // iFeature + }; + config0.add_node(UsbDescriptorNode(audio_if0_feature[0], audio_if0_feature[1], &audio_if0_feature[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x00, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x01, + .bAlternateSetting = 0x01, + .bNumEndpoints = 0x01, + .bInterfaceClass = 0x01, + .bInterfaceSubClass = 0x02, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ACI, + UsbAudioInterface { + .bDescriptorSubtype = 0x01, // AS_GENERAL + .bTerminalLink = 0x07, + .bDelay = 0x01, + .wFormatTag = 0x0001})); + + static constexpr u8 audio_if1_alt1_type[] = { + 0x1d, // bLength + 0x24, // bDescriptorType (See Next Line) + 0x02, // bDescriptorSubtype (CS_INTERFACE -> FORMAT_TYPE) + 0x01, // bFormatType 1 + 0x01, // bNrChannels (Mono) + 0x02, // bSubFrameSize 2 + 0x10, // bBitResolution 16 + 0x07, // bSamFreqType 5 + 0x40, 0x1F, 0x00, // tSamFreq[1] 8000 Hz + 0x11, 0x2B, 0x00, // tSamFreq[2] 11025 Hz + 0x80, 0x3e, 0x00, // tSamFreq[3] 16000 Hz + 0x22, 0x56, 0x00, // tSamFreq[4] 22050 Hz + 0x00, 0x7d, 0x00, // tSamFreq[5] 32000 Hz + 0x44, 0xAC, 0x00, // tSamFreq[6] 44100 Hz + 0x80, 0xBB, 0x00, // tSamFreq[7] 48000 Hz + }; + config0.add_node(UsbDescriptorNode(audio_if1_alt1_type[0], audio_if1_alt1_type[1], &audio_if1_alt1_type[2])); + + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbAudioEndpoint { + .bEndpointAddress = 0x82, + .bmAttributes = 0x0d, + .wMaxPacketSize = 0x0064, + .bInterval = 0x01, + .bRefresh = 0x00, + .bSynchAddress = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT_ASI, + UsbAudioStreamingEndpoint { + .bDescriptorSubtype = 0x01, // EP_GENERAL + .bmAttributes = 0x01, // Sampling Freq Control + .bLockDelayUnits = 0x00, + .wLockDelay = 0x0000})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_INTERFACE, + UsbDeviceInterface { + .bInterfaceNumber = 0x02, + .bAlternateSetting = 0x00, + .bNumEndpoints = 0x01, + .bInterfaceClass = 0x03, + .bInterfaceSubClass = 0x00, + .bInterfaceProtocol = 0x00, + .iInterface = 0x00})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_HID, + UsbDeviceHID { + .bcdHID = 0x0111, + .bCountryCode = 0x00, + .bNumDescriptors = 0x01, + .bDescriptorType = 0x22, + .wDescriptorLength = 0x001a})); + config0.add_node(UsbDescriptorNode(USB_DESCRIPTOR_ENDPOINT, + UsbDeviceEndpoint { + .bEndpointAddress = 0x87, + .bmAttributes = 0x03, + .wMaxPacketSize = 0x0010, + .bInterval = 0x01})); + + add_string("Hercules."); + add_string("Rocksmith USB Guitar Adapter"); + break; + } + } +} + +std::shared_ptr usb_device_mic::make_singstar(u32 controller_index, const std::array& location) +{ + return std::make_shared(controller_index, location, MicType::SingStar); +} + +std::shared_ptr usb_device_mic::make_logitech(u32 controller_index, const std::array& location) +{ + return std::make_shared(controller_index, location, MicType::Logitech); +} + +std::shared_ptr usb_device_mic::make_rocksmith(u32 controller_index, const std::array& location) +{ + return std::make_shared(controller_index, location, MicType::Rocksmith); +} + +u16 usb_device_mic::get_num_emu_devices() +{ + return 1; +} + +void usb_device_mic::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) +{ + transfer->fake = true; + transfer->expected_count = buf_size; + transfer->expected_result = HC_CC_NOERR; + transfer->expected_time = get_timestamp() + 1000; + + switch (bmRequestType) + { + case 0U /*silences warning*/ | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE: // 0x21 + switch (bRequest) + { + case SET_CUR: + { + ensure(buf_size >= 2); + const u8 ch = wValue & 0xff; + if (ch == 0) + { + m_volume[0] = (buf[1] << 8) | buf[0]; + m_volume[1] = (buf[1] << 8) | buf[0]; + usb_mic_log.notice("Set Cur Volume[%d]: 0x%04x (%d dB)", ch, m_volume[0], m_volume[0] / 256); + } + else if (ch == 1) + { + m_volume[0] = (buf[1] << 8) | buf[0]; + usb_mic_log.notice("Set Cur Volume[%d]: 0x%04x (%d dB)", ch, m_volume[0], m_volume[0] / 256); + } + else if (ch == 2) + { + m_volume[1] = (buf[1] << 8) | buf[0]; + usb_mic_log.notice("Set Cur Volume[%d]: 0x%04x (%d dB)", ch, m_volume[1], m_volume[1] / 256); + } + break; + } + default: + usb_mic_log.error("Unhandled Request: 0x%02X/0x%02X", bmRequestType, bRequest); + break; + } + break; + case 0U /*silences warning*/ | LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT: // 0x22 + switch (bRequest) + { + case SET_CUR: + ensure(buf_size >= 3); + m_sample_rate = (buf[2] << 16) | (buf[1] << 8) | buf[0]; + usb_mic_log.notice("Set Sample Rate: %d", m_sample_rate); + break; + default: + usb_mic_log.error("Unhandled Request: 0x%02X/0x%02X", bmRequestType, bRequest); + break; + } + break; + case 0U /*silences warning*/ | LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_INTERFACE: // 0xa1 + switch (bRequest) + { + case GET_CUR: + { + ensure(buf_size >= 2); + const u8 ch = wValue & 0xff; + if (ch == 2) + { + buf[0] = (m_volume[1] ) & 0xff; + buf[1] = (m_volume[1] >> 8) & 0xff; + usb_mic_log.notice("Get Cur Volume[%d]: 0x%04x (%d dB)", ch, m_volume[1], m_volume[1] / 256); + } + else + { + buf[0] = (m_volume[0] ) & 0xff; + buf[1] = (m_volume[0] >> 8) & 0xff; + usb_mic_log.notice("Get Cur Volume[%d]: 0x%04x (%d dB)", ch, m_volume[0], m_volume[0] / 256); + } + break; + } + case GET_MIN: + { + ensure(buf_size >= 2); + constexpr s16 minVol = 0xff00; + buf[0] = (minVol ) & 0xff; + buf[1] = (minVol >> 8) & 0xff; + usb_mic_log.notice("Get Min Volume: 0x%04x (%d dB)", minVol, minVol / 256); + break; + } + case GET_MAX: + { + ensure(buf_size >= 2); + constexpr s16 maxVol = 0x0100; + buf[0] = (maxVol ) & 0xff; + buf[1] = (maxVol >> 8) & 0xff; + usb_mic_log.notice("Get Max Volume: 0x%04x (%d dB)", maxVol, maxVol / 256); + break; + } + default: + usb_mic_log.error("Unhandled Request: 0x%02X/0x%02X", bmRequestType, bRequest); + break; + } + break; + case 0U /*silences warning*/ | LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_CLASS | LIBUSB_RECIPIENT_ENDPOINT: // 0xa2 + switch (bRequest) + { + case GET_CUR: + ensure(buf_size >= 3); + buf[0] = (m_sample_rate ) & 0xff; + buf[1] = (m_sample_rate >> 8) & 0xff; + buf[2] = (m_sample_rate >> 16) & 0xff; + usb_mic_log.notice("Get Sample Rate: %d", m_sample_rate); + break; + default: + usb_mic_log.error("Unhandled Request: 0x%02X/0x%02X", bmRequestType, bRequest); + break; + } + break; + default: + usb_device_emulated::control_transfer(bmRequestType, bRequest, wValue, wIndex, wLength, buf_size, buf, transfer); + break; + } + + usb_mic_log.trace("control_transfer: req=0x%02X/0x%02X, val=0x%02x, idx=0x%02x, len=0x%02x, [%s]", + bmRequestType, bRequest, wValue, wIndex, wLength, fmt::buf_to_hexstring(buf, buf_size)); +} + +void usb_device_mic::isochronous_transfer(UsbTransfer* transfer) +{ + transfer->fake = true; + transfer->expected_count = 0; + transfer->expected_result = HC_CC_NOERR; + transfer->expected_time = get_timestamp() + 1000; + + const bool stereo = (m_mic_type == MicType::SingStar && current_altsetting == 2); + + auto& mic_thr = g_fxo->get(); + const std::lock_guard lock(mic_thr.mutex); + if (!mic_thr.init) + { + usb_mic_log.notice("mic init"); + mic_thr.load_config_and_init(); + mic_thr.init = 1; + } + if (!mic_thr.check_device(0)) + { + usb_mic_log.notice("mic check"); + } + microphone_device& device = ::at32(mic_thr.mic_list, 0); + if (!device.is_opened()) + { + usb_mic_log.notice("mic open"); + device.open_microphone(CELLMIC_SIGTYPE_RAW, m_sample_rate, m_sample_rate, stereo ? 2 : 1); + } + if (!device.is_started()) + { + usb_mic_log.notice("mic start"); + device.start_microphone(); + } + + u8* buf = static_cast(transfer->iso_request.buf.get_ptr()); + for (u32 index = 0; index < transfer->iso_request.num_packets; index++) + { + const u16 inlen = transfer->iso_request.packets[index] >> 4; + ensure(inlen >= (stereo ? 192 : 96)); + const u32 outlen = device.read_raw(buf, stereo ? 192 : 96); + buf += outlen; + transfer->iso_request.packets[index] = (outlen & 0xFFF) << 4; + usb_mic_log.trace(" isochronous_transfer: dev=%d, buf=0x%x, start=0x%x, pks=0x%x idx=0x%x, inlen=0x%x, outlen=0x%x", + static_cast(m_mic_type), transfer->iso_request.buf, transfer->iso_request.start_frame, transfer->iso_request.num_packets, index, inlen, outlen); + } +} diff --git a/rpcs3/Emu/Io/usb_microphone.h b/rpcs3/Emu/Io/usb_microphone.h new file mode 100644 index 0000000000..d8819a099b --- /dev/null +++ b/rpcs3/Emu/Io/usb_microphone.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Emu/Io/usb_device.h" + +enum class MicType +{ + SingStar, + Logitech, + Rocksmith, +}; + +enum +{ + SET_CUR = 0x01, + GET_CUR = 0x81, + SET_MIN = 0x02, + GET_MIN = 0x82, + SET_MAX = 0x03, + GET_MAX = 0x83, +}; + +class usb_device_mic : public usb_device_emulated +{ +public: + usb_device_mic(u32 controller_index, const std::array& location, MicType mic_type); + + static std::shared_ptr make_singstar(u32 controller_index, const std::array& location); + static std::shared_ptr make_logitech(u32 controller_index, const std::array& location); + static std::shared_ptr make_rocksmith(u32 controller_index, const std::array& location); + static u16 get_num_emu_devices(); + + void control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer) override; + void isochronous_transfer(UsbTransfer* transfer) override; + +private: + u32 m_controller_index; + MicType m_mic_type; + u32 m_sample_rate; + s16 m_volume[2]; +}; diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index 1ebbfdd14e..dc0af98f6c 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -101,6 +101,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case rpcn::CommandType::Login: return "Login"; case rpcn::CommandType::Terminate: return "Terminate"; case rpcn::CommandType::Create: return "Create"; + case rpcn::CommandType::Delete: return "Delete"; case rpcn::CommandType::SendToken: return "SendToken"; case rpcn::CommandType::SendResetToken: return "SendResetToken"; case rpcn::CommandType::ResetPassword: return "ResetPassword"; @@ -255,7 +256,7 @@ namespace rpcn rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); } - constexpr u32 RPCN_PROTOCOL_VERSION = 27; + constexpr u32 RPCN_PROTOCOL_VERSION = 28; constexpr usz RPCN_HEADER_SIZE = 15; const char* error_to_explanation(rpcn::ErrorType error) @@ -656,7 +657,7 @@ namespace rpcn } // Those commands are handled synchronously and won't be forwarded to NP Handler - if (command == CommandType::Login || command == CommandType::GetServerList || command == CommandType::Create || + if (command == CommandType::Login || command == CommandType::GetServerList || command == CommandType::Create || command == CommandType::Delete || command == CommandType::AddFriend || command == CommandType::RemoveFriend || command == CommandType::AddBlock || command == CommandType::RemoveBlock || command == CommandType::SendMessage || command == CommandType::SendToken || @@ -1192,7 +1193,7 @@ namespace rpcn std::copy(token.begin(), token.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; @@ -1278,7 +1279,7 @@ namespace rpcn bool rpcn_client::terminate_connection() { - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; std::vector data; @@ -1314,7 +1315,7 @@ namespace rpcn std::copy(email.begin(), email.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::Create, req_id, data, packet_data)) @@ -1348,7 +1349,7 @@ namespace rpcn std::copy(password.begin(), password.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::SendToken, req_id, data, packet_data)) @@ -1381,7 +1382,7 @@ namespace rpcn std::copy(email.begin(), email.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::SendResetToken, req_id, data, packet_data)) @@ -1416,7 +1417,7 @@ namespace rpcn std::copy(password.begin(), password.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::ResetPassword, req_id, data, packet_data)) @@ -1435,13 +1436,43 @@ namespace rpcn return error; } + ErrorType rpcn_client::delete_account() + { + const auto npid = g_cfg_rpcn.get_npid(); + const auto password = g_cfg_rpcn.get_password(); + + std::vector data; + std::copy(npid.begin(), npid.end(), std::back_inserter(data)); + data.push_back(0); + std::copy(password.begin(), password.end(), std::back_inserter(data)); + data.push_back(0); + + const u64 req_id = rpcn_request_counter.fetch_add(1); + + std::vector packet_data; + if (!forge_send_reply(CommandType::Delete, req_id, data, packet_data)) + { + return ErrorType::Malformed; + } + + vec_stream reply(packet_data); + auto error = static_cast(reply.get()); + + if (error == rpcn::ErrorType::NoError) + { + rpcn_log.success("Account was successfully deleted!"); + } + + return error; + } + bool rpcn_client::add_friend(const std::string& friend_username) { std::vector data; std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::AddFriend, req_id, data, packet_data)) @@ -1467,7 +1498,7 @@ namespace rpcn std::copy(friend_username.begin(), friend_username.end(), std::back_inserter(data)); data.push_back(0); - u64 req_id = rpcn_request_counter.fetch_add(1); + const u64 req_id = rpcn_request_counter.fetch_add(1); std::vector packet_data; if (!forge_send_reply(CommandType::RemoveFriend, req_id, data, packet_data)) diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 6d7126ffa6..3781711912 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -304,6 +304,7 @@ namespace rpcn ErrorType resend_token(const std::string& npid, const std::string& password); ErrorType send_reset_token(std::string_view npid, std::string_view email); ErrorType reset_password(std::string_view npid, std::string_view token, std::string_view password); + ErrorType delete_account(); bool add_friend(const std::string& friend_username); bool remove_friend(const std::string& friend_username); diff --git a/rpcs3/Emu/NP/rpcn_types.h b/rpcs3/Emu/NP/rpcn_types.h index 684f0e65e9..15fba827d5 100644 --- a/rpcs3/Emu/NP/rpcn_types.h +++ b/rpcs3/Emu/NP/rpcn_types.h @@ -9,6 +9,7 @@ namespace rpcn Login, Terminate, Create, + Delete, SendToken, SendResetToken, ResetPassword, diff --git a/rpcs3/Emu/RSX/Common/texture_cache_utils.h b/rpcs3/Emu/RSX/Common/texture_cache_utils.h index a180a1a8d1..82c52d8653 100644 --- a/rpcs3/Emu/RSX/Common/texture_cache_utils.h +++ b/rpcs3/Emu/RSX/Common/texture_cache_utils.h @@ -1373,7 +1373,7 @@ namespace rsx set_dirty(false); } - if (context == rsx::texture_upload_context::framebuffer_storage && !Emu.IsStopped()) + if (context == rsx::texture_upload_context::framebuffer_storage) { // Lock, unlock auto surface = derived()->get_render_target(); diff --git a/rpcs3/Emu/RSX/NV47/HW/common.cpp b/rpcs3/Emu/RSX/NV47/HW/common.cpp index 45e7b001a1..e66e1703e2 100644 --- a/rpcs3/Emu/RSX/NV47/HW/common.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/common.cpp @@ -58,19 +58,8 @@ namespace rsx u32 get_report_data_impl([[maybe_unused]] rsx::context* ctx, u32 offset) { - u32 location = 0; blit_engine::context_dma report_dma = REGS(ctx)->context_dma_report(); - - switch (report_dma) - { - case blit_engine::context_dma::to_memory_get_report: location = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_LOCAL; break; - case blit_engine::context_dma::report_location_main: location = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_MAIN; break; - case blit_engine::context_dma::memory_host_buffer: location = CELL_GCM_CONTEXT_DMA_MEMORY_HOST_BUFFER; break; - default: - return vm::addr_t(0); - } - - return vm::cast(get_address(offset, location)); + return vm::cast(get_address(offset, static_cast(report_dma))); } void set_fragment_texture_dirty_bit(rsx::context* ctx, u32 arg, u32 index) diff --git a/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp b/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp index 666b4ef53a..111611d887 100644 --- a/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp +++ b/rpcs3/Emu/RSX/NV47/HW/nv3089.cpp @@ -187,19 +187,26 @@ namespace rsx const u32 in_offset = in_x * in_bpp + in_pitch * in_y; const u32 out_offset = out_x * out_bpp + out_pitch * out_y; - const u32 src_line_length = (in_w * in_bpp); + const u32 src_line_length = (std::min(in_w, in_x + static_cast(std::ceil(clip_w / scale_x))) * in_bpp); u32 src_address = 0; const u32 dst_address = get_address(dst_offset, dst_dma, 1); // TODO: Add size + if (!dst_address) + { + rsx_log.error("NV3089_IMAGE_IN_SIZE: Unmapped dst_address (dst_offset=0x%x, dst_dma=0x%dx)", dst_offset, dst_dma); + RSX(ctx)->recover_fifo(); + return { false, src_info, dst_info }; + } + if (is_block_transfer && (clip_h == 1 || (in_pitch == out_pitch && src_line_length == in_pitch))) { const u32 nb_lines = std::min(clip_h, in_h); const u32 data_length = nb_lines * src_line_length; - if (src_address = get_address(src_offset, src_dma, data_length); - !src_address || !dst_address) + if (src_address = get_address(src_offset, src_dma, data_length); !src_address) { + rsx_log.error("NV3089_IMAGE_IN_SIZE: Unmapped src_address for in block transfer (src_offset=0x%x, src_dma=0x%x, data_length=0x%x)", src_offset, src_dma, data_length); RSX(ctx)->recover_fifo(); return { false, src_info, dst_info }; } @@ -221,9 +228,9 @@ namespace rsx const u16 read_h = std::min(static_cast(clip_h / scale_y), in_h); const u32 data_length = in_pitch * (read_h - 1) + src_line_length; - if (src_address = get_address(src_offset, src_dma, data_length); - !src_address || !dst_address) + if (src_address = get_address(src_offset, src_dma, data_length); !src_address) { + rsx_log.error("NV3089_IMAGE_IN_SIZE: Unmapped src_address (src_offset=0x%x, src_dma=0x%x, data_length=0x%x)", src_offset, src_dma, data_length); RSX(ctx)->recover_fifo(); return { false, src_info, dst_info }; } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index cc7b9fff17..44e8d8fb3f 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -150,11 +150,28 @@ namespace rsx case CELL_GCM_CONTEXT_DMA_MEMORY_HOST_BUFFER: case CELL_GCM_LOCATION_MAIN: { - if (const u32 ea = render->iomap_table.get_addr(offset); ea + 1) + if (const u32 ea = render->iomap_table.get_addr(offset); ea != umax) { - if (!size_to_check || vm::check_addr(ea, 0, size_to_check)) + if (size_to_check <= 1 || (offset < render->main_mem_size && render->main_mem_size - offset >= size_to_check)) { - return ea; + bool ok = true; + + for (u32 offs_index = 0x100000; offs_index < size_to_check + (offset & 0xfffff); offs_index += 0x100000) + { + // This check does not check continuity but rather that it's mapped at all + if (render->iomap_table.get_addr(offset + offs_index) == umax) + { + ok = false; + } + } + + if (ok) + { + if (!size_to_check || vm::check_addr(ea, 0, size_to_check)) + { + return ea; + } + } } } @@ -175,7 +192,7 @@ namespace rsx case CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_MAIN: { - if (const u32 ea = offset < 0x1000000 ? render->iomap_table.get_addr(0x0e000000 + offset) : -1; ea + 1) + if (const u32 ea = offset < 0x1000000 ? render->iomap_table.get_addr(0x0e000000 + offset) : -1; ea != umax) { if (!size_to_check || vm::check_addr(ea, 0, size_to_check)) { diff --git a/rpcs3/Emu/RSX/RSXZCULL.cpp b/rpcs3/Emu/RSX/RSXZCULL.cpp index 3dd9d1a776..63e39a8314 100644 --- a/rpcs3/Emu/RSX/RSXZCULL.cpp +++ b/rpcs3/Emu/RSX/RSXZCULL.cpp @@ -82,7 +82,7 @@ namespace rsx { // NOTE: Only enable host queries if pixel count is active to save on resources // Can optionally be enabled for either stats enabled or zpass enabled for accuracy - const bool data_stream_available = zpass_count_enabled; // write_enabled && (zpass_count_enabled || stats_enabled); + const bool data_stream_available = zpass_count_enabled; // surface_active && (zpass_count_enabled || stats_enabled); if (host_queries_active && !data_stream_available) { // Stop @@ -106,7 +106,7 @@ namespace rsx void ZCULL_control::set_status(class ::rsx::thread* ptimer, bool surface_active, bool zpass_active, bool zcull_stats_active, bool flush_queue) { - write_enabled = surface_active; + surface_active = surface_active; zpass_count_enabled = zpass_active; stats_enabled = zcull_stats_active; @@ -260,9 +260,20 @@ namespace rsx return; } + // Discard any running queries. The results will never be read anyway. + if (m_current_task && m_current_task->active) + { + discard_occlusion_query(m_current_task); + free_query(m_current_task); + m_current_task->active = false; + + allocate_new_query(ptimer); + begin_occlusion_query(m_current_task); + } + if (!m_pending_writes.empty()) { - //Remove any dangling/unclaimed queries as the information is lost anyway + // Remove any dangling/unclaimed queries as the information is lost anyway auto valid_size = m_pending_writes.size(); for (auto It = m_pending_writes.rbegin(); It != m_pending_writes.rend(); ++It) { @@ -340,14 +351,14 @@ namespace rsx } break; case CELL_GCM_ZCULL_STATS3: - value = (value || !write_enabled || !stats_enabled) ? 0 : u16{ umax }; + value = (value || !surface_active || !stats_enabled) ? 0 : u16{ umax }; break; case CELL_GCM_ZCULL_STATS2: case CELL_GCM_ZCULL_STATS1: case CELL_GCM_ZCULL_STATS: default: // Not implemented - value = (write_enabled && stats_enabled) ? -1 : 0; + value = (surface_active && stats_enabled) ? -1 : 0; break; } diff --git a/rpcs3/Emu/RSX/RSXZCULL.h b/rpcs3/Emu/RSX/RSXZCULL.h index 679357b457..d44cba5c95 100644 --- a/rpcs3/Emu/RSX/RSXZCULL.h +++ b/rpcs3/Emu/RSX/RSXZCULL.h @@ -109,7 +109,7 @@ namespace rsx protected: bool unit_enabled = false; // The ZCULL unit is on - bool write_enabled = false; // A surface in the ZCULL-monitored tile region has been loaded for rasterization + bool surface_active = false; // A surface in the ZCULL-monitored tile region has been loaded for rasterization bool stats_enabled = false; // Collecting of ZCULL statistics is enabled (not same as pixels passing Z test!) bool zpass_count_enabled = false; // Collecting of ZPASS statistics is enabled. If this is off, the counter does not increment bool host_queries_active = false; // The backend/host is gathering Z data for the ZCULL unit diff --git a/rpcs3/Emu/RSX/gcm_enums.cpp b/rpcs3/Emu/RSX/gcm_enums.cpp index 5c7d88c875..1357627fb2 100644 --- a/rpcs3/Emu/RSX/gcm_enums.cpp +++ b/rpcs3/Emu/RSX/gcm_enums.cpp @@ -501,7 +501,7 @@ void fmt_class_string::format(std::string& out, u64 ar switch (value) { case blit_engine::context_dma::report_location_main: return "report location main"; - case blit_engine::context_dma::to_memory_get_report: return "to memory get report"; + case blit_engine::context_dma::report_location_local: return "report location local"; case blit_engine::context_dma::memory_host_buffer: return "memory host buffer"; } diff --git a/rpcs3/Emu/RSX/gcm_enums.h b/rpcs3/Emu/RSX/gcm_enums.h index 539796a531..07901aa78c 100644 --- a/rpcs3/Emu/RSX/gcm_enums.h +++ b/rpcs3/Emu/RSX/gcm_enums.h @@ -1820,7 +1820,7 @@ namespace rsx enum class context_dma : u32 { - to_memory_get_report = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_LOCAL, + report_location_local = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_LOCAL, report_location_main = CELL_GCM_CONTEXT_DMA_REPORT_LOCATION_MAIN, memory_host_buffer = CELL_GCM_CONTEXT_DMA_MEMORY_HOST_BUFFER, }; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 7c0fa19c93..0fa9300cc2 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -844,6 +844,7 @@ bool Emulator::BootRsxCapture(const std::string& path) m_path.clear(); m_path_old.clear(); m_path_original.clear(); + m_path_real.clear(); m_title_id.clear(); m_title.clear(); m_localized_title.clear(); @@ -1460,7 +1461,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, path = path + "PS3_GAME/USRDIR/EBOOT.BIN"; } - m_path = path; + m_path_real = m_path; + m_path = std::move(path); } sys_log.notice("Load: is iso archive = %d (m_path='%s')", launching_from_disc_archive, m_path); @@ -3980,6 +3982,18 @@ game_boot_result Emulator::Restart(bool graceful) Emu.after_kill_callback = [this] { + // Reset boot path in case of ISO + if (m_path.starts_with(iso_device::virtual_device_name)) + { + sys_log.notice("Continuous boot: Resetting boot path from '%s' to '%s'", m_path, m_path_real); + ensure(!m_path_real.empty()); + ensure(!m_path_real.starts_with(iso_device::virtual_device_name)); + m_path = m_path_real; + } + + // Allow Boot (guarded by GracefulShutdown, which is the scope of this callback) + m_restrict_emu_state_change = 0; + // Reload with prior configs. if (const auto error = Load(m_title_id); error != game_boot_result::no_errors) { diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 5288d72a1e..3bf33bcd52 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -148,6 +148,7 @@ class Emulator final std::string m_path; std::string m_path_old; std::string m_path_original; + std::string m_path_real; std::string m_title_id; std::string m_title; std::string m_localized_title; @@ -393,7 +394,7 @@ public: { if (active) { - _this->m_restrict_emu_state_change--; + _this->m_restrict_emu_state_change.try_dec(0); } } diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 7f3be85a13..15c4a26d81 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -459,6 +459,7 @@ + @@ -831,6 +832,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 7e2c398090..09f4b3c2b6 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -924,6 +924,9 @@ Emu\Io + + Emu\Io + Emu\Io @@ -1402,6 +1405,9 @@ Emu\NP + + Loader + @@ -2121,6 +2127,9 @@ Emu\Io + + Emu\Io + Emu\Io @@ -2818,6 +2827,9 @@ Emu\NP + + Loader + diff --git a/rpcs3/rpcs3.cpp b/rpcs3/rpcs3.cpp index 007de26bdb..6c2130eca6 100644 --- a/rpcs3/rpcs3.cpp +++ b/rpcs3/rpcs3.cpp @@ -26,6 +26,7 @@ #include "Utilities/sema.h" #include "Utilities/date_time.h" #include "util/console.h" +#include "util/asm.hpp" #include "Crypto/decrypt_binaries.h" #ifdef _WIN32 #include "module_verifier.hpp" @@ -681,6 +682,10 @@ int run_rpcs3(int argc, char** argv) logs::set_init({std::move(ver), std::move(sys), std::move(os), std::move(qt), std::move(time)}); } +#ifdef ARCH_ARM64 + utils::init_arm_timer_scale(); +#endif + #ifdef _WIN32 sys_log.notice("Initialization times before main(): %fGc", intro_cycles / 1000000000.); #elif defined(RUSAGE_THREAD) diff --git a/rpcs3/rpcs3qt/game_list_actions.cpp b/rpcs3/rpcs3qt/game_list_actions.cpp index 8cc4f767ef..b37348f49d 100644 --- a/rpcs3/rpcs3qt/game_list_actions.cpp +++ b/rpcs3/rpcs3qt/game_list_actions.cpp @@ -811,7 +811,7 @@ bool game_list_actions::RemoveContentList(const std::string& serial, bool is_int // Add serial (title id) to the list of serials to be removed in "games.yml" file (if any) if (content_types & DISC) { - if (const auto it = m_content_info.disc_list.find(serial); it != m_content_info.disc_list.cend()) + if (m_content_info.disc_list.contains(serial)) m_content_info.removed_disc_list.insert(serial); } @@ -851,18 +851,10 @@ bool game_list_actions::RemoveContentList(const std::string& serial, bool is_int { if (const auto it = m_content_info.name_list.find(serial); it != m_content_info.name_list.cend()) { - const bool remove_rpcs3_links = ValidateRemoval(serial, rpcs3::utils::get_games_shortcuts_dir(), "link"); - - for (const auto& name : it->second) + for (const std::string& name : it->second) { - // Remove illegal characters from name to match the link name created by gui::utils::create_shortcut() - const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); - - // Remove rpcs3 shortcuts - if (remove_rpcs3_links) - RemoveContentBySerial(rpcs3::utils::get_games_shortcuts_dir(), simple_name + ".lnk", "link"); - - // TODO: Remove shortcuts from desktop/start menu + // Remove all shortcuts + gui::utils::remove_shortcuts(name, serial); } } } diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 483f5affdc..db66ce68a4 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -339,6 +339,14 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey default: break; // unreachable } + if (shortcut_key == gui::shortcuts::shortcut::gw_restart && !boot_current_game_savestate(true, index)) + { + // Normal restart if there is no savestate + Emu.Restart(); + break; + } + + // Reboot with savestate boot_current_game_savestate(false, index); break; } diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp index 8a4ef3e930..53fd2e07e0 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -16,36 +16,85 @@ #include kamen_rider_dialog* kamen_rider_dialog::inst = nullptr; -std::array>, UI_FIG_NUM> kamen_rider_dialog::figure_slots = {}; +std::array>, UI_FIG_NUM> kamen_rider_dialog::figure_slots = {}; QString last_kamen_rider_path; -static const std::map, const std::string> list_kamen_riders = { - {{0x10, 0x10}, "Kamen Rider Drive Wind"}, - {{0x10, 0x20}, "Kamen Rider Drive Water"}, - {{0x10, 0x30}, "Kamen Rider Drive Fire"}, - {{0x10, 0x40}, "Kamen Rider Drive Light"}, - {{0x10, 0x50}, "Kamen Rider Drive Dark"}, - {{0x11, 0x10}, "Kamen Rider Gaim Wind"}, - {{0x11, 0x20}, "Kamen Rider Gaim Water"}, - {{0x12, 0x20}, "Kamen Rider Wizard Water"}, - {{0x12, 0x30}, "Kamen Rider Wizard Fire"}, - {{0x13, 0x40}, "Kamen Rider Fourze Light"}, - {{0x14, 0x20}, "Kamen Rider 000 Water"}, - {{0x15, 0x10}, "Kamen Rider W (Double) Wind"}, - {{0x16, 0x50}, "Kamen Rider Decade Dark"}, - {{0x17, 0x50}, "Kamen Rider Kiva Dark"}, - {{0x18, 0x40}, "Kamen Rider Den-O Light"}, - {{0x19, 0x30}, "Kamen Rider Kabuto Fire"}, - {{0x1A, 0x30}, "Kamen Rider Hibiki Fire"}, - {{0x1B, 0x50}, "Kamen Rider Blade Dark"}, - {{0x1C, 0x50}, "Kamen Rider Faiz Dark"}, - {{0x1D, 0x10}, "Kamen Rider Ryuki Wind"}, - {{0x1E, 0x20}, "Kamen Rider Agito Water"}, - {{0x1F, 0x40}, "Kamen Rider Kuuga Light"}, - {{0x20, 0x00}, "Type Wild"}, - {{0x21, 0x00}, "Kamen Rider Zangetsu"}, - {{0x22, 0x00}, "All Dragon"}, - {{0x31, 0x00}, "Kachidoki Arms"}, +static const std::map, const std::string> list_kamen_riders = { + // Character ID [0x1b], ERC type [0x1a], Figure type [0x19] + {{0x10, 0x01, 0x10}, "Kamen Rider Drive (Wind)"}, + {{0x10, 0x01, 0x20}, "Kamen Rider Drive (Water)"}, + {{0x10, 0x01, 0x30}, "Kamen Rider Drive (Fire)"}, + {{0x10, 0x01, 0x40}, "Kamen Rider Drive (Light)"}, + {{0x10, 0x01, 0x50}, "Kamen Rider Drive (Dark)"}, + {{0x20, 0x01, 0x00}, "Kamen Rider Drive - Type Wild"}, + {{0x20, 0x02, 0x00}, "Kamen Rider Drive - Type Wild Gyasha Ver"}, + // {{ , , }, "Kamen Rider Drive - Type Speed Flare"}, + // {{ , , }, "Kamen Rider Drive - Type Technic"}, // 1.05 update + {{0x11, 0x01, 0x10}, "Kamen Rider Gaim (Wind)"}, + {{0x11, 0x01, 0x20}, "Kamen Rider Gaim (Water)"}, + {{0x21, 0x01, 0x00}, "Kamen Rider Gaim - Jimber Lemon Arms"}, + {{0x21, 0x02, 0x00}, "Kamen Rider Gaim - Kachidoki Arms"}, + {{0x21, 0x03, 0x00}, "Kamen Rider Gaim - Kiwami Arms"}, + {{0x12, 0x01, 0x20}, "Kamen Rider Wizard (Water)"}, + {{0x12, 0x01, 0x30}, "Kamen Rider Wizard (Fire)"}, + {{0x22, 0x01, 0x00}, "Kamen Rider Wizard - Infinity Style"}, + {{0x22, 0x02, 0x00}, "Kamen Rider Wizard - All Dragon"}, + {{0x22, 0x03, 0x00}, "Kamen Rider Wizard - Infinity Gold Dragon"}, + {{0x13, 0x01, 0x40}, "Kamen Rider Fourze (Light)"}, + {{0x23, 0x01, 0x00}, "Kamen Rider Fourze - Magnet States"}, + {{0x23, 0x02, 0x00}, "Kamen Rider Fourze - Cosmic States"}, + {{0x23, 0x03, 0x00}, "Kamen Rider Fourze - Meteor Nadeshiko Fusion States"}, + {{0x14, 0x01, 0x20}, "Kamen Rider OOO (Water)"}, + {{0x24, 0x01, 0x00}, "Kamen Rider OOO - Super Tatoba Combo"}, + {{0x24, 0x02, 0x00}, "Kamen Rider OOO - Putotyra Combo"}, + {{0x24, 0x04, 0x00}, "Kamen Rider OOO - Tajadol Combo"}, + {{0x15, 0x01, 0x10}, "Kamen Rider W (Double) (Wind)"}, + {{0x25, 0x01, 0x00}, "Kamen Rider W (Double) - Cyclone Joker Extreme"}, + {{0x25, 0x02, 0x00}, "Kamen Rider W (Double) - Cyclone Joker Gold Extreme"}, + {{0x25, 0x03, 0x00}, "Kamen Rider W (Double) - Fang Joker"}, + {{0x16, 0x01, 0x50}, "Kamen Rider Decade (Dark)"}, + {{0x26, 0x01, 0x00}, "Kamen Rider Decade - Complete Form"}, + {{0x26, 0x02, 0x00}, "Kamen Rider Decade - Strongest Complete Form"}, + {{0x26, 0x03, 0x00}, "Kamen Rider Decade - Final Form"}, + {{0x17, 0x01, 0x50}, "Kamen Rider Kiva (Dark)"}, + {{0x27, 0x01, 0x00}, "Kamen Rider Kiva - Dogabaki Form"}, + {{0x27, 0x02, 0x00}, "Kamen Rider Kiva - Emperor Form"}, + {{0x18, 0x01, 0x40}, "Kamen Rider Den-O (Light)"}, + {{0x28, 0x01, 0x00}, "Kamen Rider Den-O - Super Climax Form"}, + {{0x28, 0x02, 0x00}, "Kamen Rider Den-O - Liner Form"}, + {{0x28, 0x03, 0x00}, "Kamen Rider Den-O - Climax Form"}, + {{0x19, 0x01, 0x30}, "Kamen Rider Kabuto (Fire)"}, + {{0x29, 0x01, 0x00}, "Kamen Rider Kabuto - Hyper Form"}, + {{0x29, 0x02, 0x00}, "Kamen Rider Kabuto - Masked Form"}, + {{0x1a, 0x01, 0x30}, "Kamen Rider Hibiki (Fire)"}, + {{0x2a, 0x01, 0x00}, "Kamen Rider Hibiki - Kurenai"}, + {{0x2a, 0x02, 0x00}, "Kamen Rider Hibiki - Armed"}, + {{0x1b, 0x01, 0x50}, "Kamen Rider Blade (Dark)"}, + {{0x2b, 0x01, 0x00}, "Kamen Rider Blade - Joker Form"}, + {{0x2b, 0x02, 0x00}, "Kamen Rider Blade - King Form"}, + {{0x1c, 0x01, 0x50}, "Kamen Rider Faiz (Dark)"}, + {{0x2c, 0x01, 0x00}, "Kamen Rider Faiz - Axel Form"}, + {{0x2c, 0x02, 0x00}, "Kamen Rider Faiz - Blaster Form"}, + {{0x1d, 0x01, 0x10}, "Kamen Rider Ryuki (Wind)"}, + {{0x2d, 0x01, 0x00}, "Kamen Rider Ryuki - Dragreder"}, + {{0x2d, 0x02, 0x00}, "Kamen Rider Ryuki - Survive"}, + {{0x1e, 0x01, 0x20}, "Kamen Rider Agito (Water)"}, + {{0x2e, 0x01, 0x00}, "Kamen Rider Agito - Shining Form"}, + {{0x2e, 0x02, 0x00}, "Kamen Rider Agito - Burning Form"}, + {{0x1f, 0x01, 0x40}, "Kamen Rider Kuuga (Light)"}, + {{0x2f, 0x01, 0x00}, "Kamen Rider Kuuga - Ultimate Form"}, + {{0x2f, 0x02, 0x00}, "Kamen Rider Kuuga - Amazing Mighty"}, + + {{0x31, 0x01, 0x00}, "Kamen Rider Baron"}, + {{0x31, 0x02, 0x00}, "Kamen Rider Zangetsu Shin"}, + {{0x32, 0x01, 0x00}, "Kamen Rider Beast"}, + {{0x33, 0x01, 0x00}, "Kamen Rider Meteor"}, + {{0x34, 0x01, 0x00}, "Kamen Rider Birth"}, + {{0x35, 0x01, 0x00}, "Kamen Rider Accel"}, + {{0x36, 0x01, 0x00}, "Kamen Rider Diend"}, + {{0x36, 0x02, 0x00}, "Kamen Rider Shocker Combatman"}, + {{0x39, 0x01, 0x00}, "Kamen Rider Gatack"}, + // {{ , , }, "Kamen Rider Mach"}, // 01.05 update }; static u32 kamen_rider_crc32(const std::array& buffer) @@ -113,12 +162,13 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) QStringList filterlist; for (const auto& [entry, figure_name] : list_kamen_riders) { - const uint qvar = (entry.first << 8) | entry.second; + const auto& [character_id, erc_type, figure_type] = entry; + const uint qvar = (character_id << 16) | (erc_type << 8) | figure_type; QString name = QString::fromStdString(figure_name); combo_figlist->addItem(name, QVariant(qvar)); filterlist << std::move(name); } - combo_figlist->addItem(tr("--Unknown--"), QVariant(0xFFFF)); + combo_figlist->addItem(tr("--Unknown--"), QVariant(0xFFFFFFFF)); combo_figlist->setEditable(true); combo_figlist->setInsertPolicy(QComboBox::NoInsert); combo_figlist->model()->sort(0, Qt::AscendingOrder); @@ -137,17 +187,22 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) vbox_panel->addWidget(line); QHBoxLayout* hbox_idvar = new QHBoxLayout(); - QLabel* label_id = new QLabel(tr("ID:")); - QLabel* label_type = new QLabel(tr("Type:")); + QLabel* label_id = new QLabel(tr("Character:")); + QLabel* label_erc = new QLabel(tr("ERC:")); + QLabel* label_fig = new QLabel(tr("Figure:")); QLineEdit* edit_id = new QLineEdit("0"); - QLineEdit* edit_type = new QLineEdit("0"); + QLineEdit* edit_erc = new QLineEdit("0"); + QLineEdit* edit_fig = new QLineEdit("0"); QRegularExpressionValidator* rxv = new QRegularExpressionValidator(QRegularExpression("\\d*"), this); edit_id->setValidator(rxv); - edit_type->setValidator(rxv); + edit_erc->setValidator(rxv); + edit_fig->setValidator(rxv); hbox_idvar->addWidget(label_id); hbox_idvar->addWidget(edit_id); - hbox_idvar->addWidget(label_type); - hbox_idvar->addWidget(edit_type); + hbox_idvar->addWidget(label_erc); + hbox_idvar->addWidget(edit_erc); + hbox_idvar->addWidget(label_fig); + hbox_idvar->addWidget(edit_fig); vbox_panel->addLayout(hbox_idvar); QHBoxLayout* hbox_buttons = new QHBoxLayout(); @@ -162,42 +217,50 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { - const u16 fig_info = combo_figlist->itemData(index).toUInt(); - if (fig_info != 0xFFFF) + const u32 fig_info = combo_figlist->itemData(index).toUInt(); + if (fig_info != 0xFFFFFFFF) { - const u8 fig_id = fig_info >> 8; - const u8 fig_type = fig_info & 0xFF; + const u8 character_id = (fig_info >> 16) & 0xff; + const u8 erc_type = (fig_info >> 8) & 0xff; + const u8 figure_type = fig_info & 0xff; - edit_id->setText(QString::number(fig_id)); - edit_type->setText(QString::number(fig_type)); + edit_id->setText(QString::number(character_id)); + edit_erc->setText(QString::number(erc_type)); + edit_fig->setText(QString::number(figure_type)); } }); connect(btn_create, &QAbstractButton::clicked, this, [=, this]() { - bool ok_id = false, ok_var = false; - const u8 fig_id = edit_id->text().toUShort(&ok_id); - if (!ok_id) + bool ok_character = false, ok_erc = false, ok_fig = false; + const u8 character_id = edit_id->text().toUShort(&ok_character); + if (!ok_character) { QMessageBox::warning(this, tr("Error converting value"), tr("ID entered is invalid!"), QMessageBox::Ok); return; } - const u8 fig_type = edit_type->text().toUShort(&ok_var); - if (!ok_var) + const u8 erc_type = edit_erc->text().toUShort(&ok_erc); + if (!ok_erc) { - QMessageBox::warning(this, tr("Error converting value"), tr("Variant entered is invalid!"), QMessageBox::Ok); + QMessageBox::warning(this, tr("Error converting value"), tr("ERC entered is invalid!"), QMessageBox::Ok); + return; + } + const u8 figure_type = edit_fig->text().toUShort(&ok_fig); + if (!ok_fig) + { + QMessageBox::warning(this, tr("Error converting value"), tr("Figure entered is invalid!"), QMessageBox::Ok); return; } QString predef_name = last_kamen_rider_path; - const auto found_fig = list_kamen_riders.find(std::make_pair(fig_id, fig_type)); + const auto found_fig = list_kamen_riders.find(std::make_tuple(character_id, erc_type, figure_type)); if (found_fig != list_kamen_riders.cend()) { predef_name += QString::fromStdString(found_fig->second + ".bin"); } else { - predef_name += QString("Unknown(%1 %2).bin").arg(fig_id).arg(fig_type); + predef_name += QString("Unknown(%1 %2 %3).bin").arg(character_id).arg(erc_type).arg(figure_type); } file_path = QFileDialog::getSaveFileName(this, tr("Create Kamen Rider File"), predef_name, tr("Kamen Rider Object (*.bin);;All Files (*)")); @@ -231,7 +294,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) buf[7] = 0x89; buf[8] = 0x44; buf[10] = 0xc2; - std::array figure_data = {u8(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, 0x10, fig_type, 0x01, fig_id}; + std::array figure_data = {u8(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, 0x10, figure_type, erc_type, character_id}; write_to_ptr>(figure_data.data(), 0xC, kamen_rider_crc32(figure_data)); memcpy(&buf[16], figure_data.data(), figure_data.size()); fig_file.write(buf.data(), buf.size()); @@ -338,7 +401,7 @@ void kamen_rider_dialog::clear_kamen_rider(u8 slot) { if (const auto& slot_infos = ::at32(figure_slots, slot)) { - const auto& [cur_slot, id, var] = slot_infos.value(); + const auto& [cur_slot, character_id, erc_type, figure_type] = slot_infos.value(); g_ridergate.remove_figure(cur_slot); figure_slots[slot] = {}; update_edits(); @@ -385,11 +448,12 @@ void kamen_rider_dialog::load_kamen_rider_path(u8 slot, const QString& path) clear_kamen_rider(slot); - u8 fig_id = data[0x1B]; - u8 fig_type = data[0x19]; + u8 character_id = data[0x1b]; + u8 erc_type = data[0x1a]; + u8 figure_type = data[0x19]; u8 portal_slot = g_ridergate.load_figure(data, std::move(fig_file)); - figure_slots[slot] = std::tuple(portal_slot, fig_id, fig_type); + figure_slots[slot] = std::tuple(portal_slot, character_id, erc_type, figure_type); update_edits(); } @@ -401,15 +465,15 @@ void kamen_rider_dialog::update_edits() QString display_string; if (const auto& sd = figure_slots[i]) { - const auto& [portal_slot, fig_id, fig_type] = sd.value(); - const auto found_fig = list_kamen_riders.find(std::make_pair(fig_id, fig_type)); + const auto& [portal_slot, character_id, erc_type, figure_type] = sd.value(); + const auto found_fig = list_kamen_riders.find(std::make_tuple(character_id, erc_type, figure_type)); if (found_fig != list_kamen_riders.cend()) { display_string = QString::fromStdString(found_fig->second); } else { - display_string = QString(tr("Unknown (Id:%1 Type:%2)")).arg(fig_id).arg(fig_type); + display_string = QString(tr("Unknown (Character:%1 ERC:%2 Figure:%3)")).arg(character_id).arg(erc_type).arg(figure_type); } } else diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.h b/rpcs3/rpcs3qt/kamen_rider_dialog.h index 3eb040d2b6..3b6dc69e51 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.h +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.h @@ -42,7 +42,7 @@ protected: protected: std::array edit_kamen_riders{}; - static std::array>, UI_FIG_NUM> figure_slots; + static std::array>, UI_FIG_NUM> figure_slots; private: static kamen_rider_dialog* inst; diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp index 74d5b14e6a..24aa0767aa 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp @@ -76,6 +76,20 @@ std::string derive_password(std::string_view user_password) return derived_password; } +std::shared_ptr get_rpcn_connection(QWidget* parent) +{ + const auto rpcn = rpcn::rpcn_client::get_instance(0); + + if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) + { + const QString error_message = QObject::tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); + QMessageBox::critical(parent, QObject::tr("Error Connecting to RPCN!"), error_message, QMessageBox::Ok); + return nullptr; + } + + return rpcn; +} + rpcn_settings_dialog::rpcn_settings_dialog(QWidget* parent) : QDialog(parent) { @@ -279,15 +293,11 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); const auto avatar_url = "https://rpcs3.net/cdn/netplay/DefaultAvatar.png"; + const auto rpcn = get_rpcn_connection(this); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting"), error_message, QMessageBox::Ok); + if (!rpcn) return; - } if (auto error = rpcn->create_user(*username, *password, *username, avatar_url, *email); error != rpcn::ErrorType::NoError) { @@ -334,14 +344,11 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) connect(btn_test, &QAbstractButton::clicked, this, [this]() { - auto rpcn = rpcn::rpcn_client::get_instance(0); + const auto rpcn = get_rpcn_connection(this); - if (auto res = rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure) - { - const QString error_msg = tr("Failed to connect to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); - QMessageBox::warning(this, tr("Error connecting to RPCN!"), error_msg, QMessageBox::Ok); + if (!rpcn) return; - } + if (auto res = rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure) { const QString error_msg = tr("Failed to authentify to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); @@ -657,6 +664,68 @@ const std::optional& rpcn_ask_token_dialog::get_token() const return m_token; } +rpcn_confirm_delete_dialog::rpcn_confirm_delete_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Confirm Account Deletion")); + setObjectName("rpcn_confirm_delete_dialog"); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QLabel* lbl_description = new QLabel(tr("Are you sure you want to delete RPCN account \"%1\"?\n\n" + "Important:\n" + "Deleting your account will blacklist your username and email for 3 months.\n" + "To confirm, type your username below and click \"Yes\".\n") + .arg(QString::fromStdString(g_cfg_rpcn.get_npid()))); + + QLineEdit* edit_for_delete = new QLineEdit(); + edit_for_delete->setPlaceholderText(tr("Type your username to confirm")); + + QPushButton* btn_yes = new QPushButton(tr("Yes")); + btn_yes->setEnabled(false); + QPushButton* btn_no = new QPushButton(tr("No")); + QDialogButtonBox* btn_box = new QDialogButtonBox(Qt::Horizontal); + btn_box->addButton(btn_yes, QDialogButtonBox::AcceptRole); + btn_box->addButton(btn_no, QDialogButtonBox::RejectRole); + + vbox_global->addWidget(lbl_description); + vbox_global->addWidget(edit_for_delete); + vbox_global->addWidget(btn_box); + + setLayout(vbox_global); + + connect(edit_for_delete, &QLineEdit::textChanged, this, [btn_yes](const QString& text) + { + btn_yes->setEnabled(text == g_cfg_rpcn.get_npid()); + }); + + connect(btn_box, &QDialogButtonBox::accepted, this, [this]() + { + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) + return QDialog::reject(); + + if (auto error = rpcn->delete_account(); error != rpcn::ErrorType::NoError) + { + QString error_message; + switch (error) + { + case rpcn::ErrorType::LoginError: error_message = tr("Invalid login or password."); break; + case rpcn::ErrorType::LoginAlreadyLoggedIn: error_message = tr("Cannot delete a currently logged-in account."); break; + default: error_message = tr("An unknown error occurred."); break; + } + QMessageBox::critical(this, tr("Deletion Failed"), tr("Failed to delete the account:\n%1").arg(error_message), QMessageBox::Ok); + QDialog::reject(); + return; + } + + QMessageBox::information(this, tr("Account Deleted"), tr("Your account has been successfully deleted."), QMessageBox::Ok); + QDialog::accept(); + }); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) : QDialog(parent) { @@ -682,6 +751,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) QPushButton* btn_resendtoken = new QPushButton(tr("Resend Token"), this); QPushButton* btn_change_password = new QPushButton(tr("Change Password"), this); + QPushButton* btn_delete_account = new QPushButton(tr("Delete Account"), this); QPushButton* btn_save = new QPushButton(tr("Save"), this); vbox_labels->addWidget(lbl_username); @@ -694,6 +764,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) hbox_buttons->addWidget(btn_resendtoken); hbox_buttons->addWidget(btn_change_password); + hbox_buttons->addWidget(btn_delete_account); hbox_buttons->addStretch(); hbox_buttons->addWidget(btn_save); @@ -727,6 +798,7 @@ rpcn_account_edit_dialog::rpcn_account_edit_dialog(QWidget* parent) }); connect(btn_resendtoken, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::resend_token); connect(btn_change_password, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::change_password); + connect(btn_delete_account, &QAbstractButton::clicked, this, &rpcn_account_edit_dialog::delete_account); g_cfg_rpcn.load(); @@ -770,17 +842,13 @@ void rpcn_account_edit_dialog::resend_token() if (!save_config()) return; - const auto rpcn = rpcn::rpcn_client::get_instance(0); - const std::string npid = g_cfg_rpcn.get_npid(); const std::string password = g_cfg_rpcn.get_password(); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->resend_token(npid, password); error != rpcn::ErrorType::NoError) { @@ -823,13 +891,10 @@ void rpcn_account_edit_dialog::change_password() return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->send_reset_token(*username, *email); error != rpcn::ErrorType::NoError) { @@ -868,13 +933,10 @@ void rpcn_account_edit_dialog::change_password() return; { - const auto rpcn = rpcn::rpcn_client::get_instance(0); - if (auto result = rpcn->wait_for_connection(); result != rpcn::rpcn_state::failure_no_failure) - { - const QString error_message = tr("Failed to connect to RPCN server:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(result))); - QMessageBox::critical(this, tr("Error Connecting!"), error_message, QMessageBox::Ok); + const auto rpcn = get_rpcn_connection(this); + + if (!rpcn) return; - } if (auto error = rpcn->reset_password(*username, *token, *password); error != rpcn::ErrorType::NoError) { @@ -903,6 +965,17 @@ void rpcn_account_edit_dialog::change_password() } } +void rpcn_account_edit_dialog::delete_account() +{ + if (g_cfg_rpcn.get_npid().empty() || g_cfg_rpcn.get_password().empty()) + { + QMessageBox::warning(this, tr("Account Not Configured"), tr("Please configure your account in the settings before deleting it."), QMessageBox::Ok); + return; + } + rpcn_confirm_delete_dialog dlg_delete(this); + dlg_delete.exec(); +} + void friend_callback(void* param, rpcn::NotificationType ntype, const std::string& username, bool status) { auto* dlg = static_cast(param); @@ -1057,14 +1130,11 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent) setLayout(vbox_global); // Tries to connect to RPCN - m_rpcn = rpcn::rpcn_client::get_instance(0); + m_rpcn = get_rpcn_connection(this); - if (auto res = m_rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure) - { - const QString error_msg = tr("Failed to connect to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); - QMessageBox::warning(parent, tr("Error connecting to RPCN!"), error_msg, QMessageBox::Ok); + if (!m_rpcn) return; - } + if (auto res = m_rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure) { const QString error_msg = tr("Failed to authentify to RPCN:\n%0").arg(QString::fromStdString(rpcn::rpcn_state_to_string(res))); diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.h b/rpcs3/rpcs3qt/rpcn_settings_dialog.h index 3db92bbc7c..89d8253220 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.h +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.h @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -83,6 +84,13 @@ private: std::optional m_token; }; +class rpcn_confirm_delete_dialog : public QDialog +{ + Q_OBJECT +public: + rpcn_confirm_delete_dialog(QWidget* parent); +}; + class rpcn_account_edit_dialog : public QDialog { Q_OBJECT @@ -95,6 +103,7 @@ private: private Q_SLOTS: void resend_token(); void change_password(); + void delete_account(); protected: QLineEdit *m_edit_username, *m_edit_token; diff --git a/rpcs3/rpcs3qt/shortcut_utils.cpp b/rpcs3/rpcs3qt/shortcut_utils.cpp index 00bec65f13..2bc2bfc578 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.cpp +++ b/rpcs3/rpcs3qt/shortcut_utils.cpp @@ -32,9 +32,17 @@ LOG_CHANNEL(sys_log, "SYS"); namespace gui::utils { - bool create_square_shortcut_icon_file(const std::string& path, const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, const std::string& extension, int size) +#ifdef _WIN32 + static const std::string icon_extension = "ico"; +#elif defined(__APPLE__) + static const std::string icon_extension = "icns"; +#else + static const std::string icon_extension = "png"; +#endif + + bool create_square_shortcut_icon_file(const std::string& path, const std::string& src_icon_path, const std::string& target_icon_dir, std::string& target_icon_path, int size) { - if (src_icon_path.empty() || target_icon_dir.empty() || extension.empty()) + if (src_icon_path.empty() || target_icon_dir.empty()) { sys_log.error("Failed to create shortcut. Icon parameters empty."); return false; @@ -55,7 +63,7 @@ namespace gui::utils return false; } - target_icon_path = target_icon_dir + "shortcut." + fmt::to_lower(extension); + target_icon_path = target_icon_dir + "shortcut." + fmt::to_lower(icon_extension); QFile icon_file(QString::fromStdString(target_icon_path)); if (!icon_file.open(QFile::OpenModeFlag::ReadWrite | QFile::OpenModeFlag::Truncate)) @@ -65,13 +73,15 @@ namespace gui::utils } // Use QImageWriter instead of QPixmap::save in order to be able to log errors - if (QImageWriter writer(&icon_file, fmt::to_upper(extension).c_str()); !writer.write(icon.toImage())) + if (QImageWriter writer(&icon_file, fmt::to_upper(icon_extension).c_str()); !writer.write(icon.toImage())) { sys_log.error("Failed to write icon file '%s': %s", target_icon_path, writer.errorString()); return false; } icon_file.close(); + + sys_log.notice("Created shortcut icon file '%s'", target_icon_path); return true; } @@ -100,21 +110,21 @@ namespace gui::utils std::string link_path; - if (location == shortcut_location::desktop) + switch (location) { + case shortcut_location::desktop: link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DesktopLocation).toStdString(); - } - else if (location == shortcut_location::applications) - { + break; + case shortcut_location::applications: link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString(); - } + break; #ifdef _WIN32 - else if (location == shortcut_location::rpcs3_shortcuts) - { - link_path = rpcs3::utils::get_games_dir() + "/shortcuts/"; + case shortcut_location::rpcs3_shortcuts: + link_path = rpcs3::utils::get_games_shortcuts_dir(); fs::create_dir(link_path); - } + break; #endif + } if (!fs::is_dir(link_path) && !fs::create_dir(link_path)) { @@ -199,7 +209,7 @@ namespace gui::utils if (!src_icon_path.empty() && !target_icon_dir.empty()) { std::string target_icon_path; - if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "ico", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512)) return cleanup(false, ".ico creation failed"); const std::wstring w_icon_path = utf8_to_wchar(target_icon_path); @@ -233,6 +243,8 @@ namespace gui::utils #elif defined(__APPLE__) fmt::append(link_path, "/%s.app", simple_name); + sys_log.notice("Creating shortcut '%s' with arguments '%s'", link_path, target_cli_args); + const std::string contents_dir = link_path + "/Contents/"; const std::string macos_dir = contents_dir + "MacOS/"; const std::string resources_dir = contents_dir + "Resources/"; @@ -311,7 +323,7 @@ namespace gui::utils if (!src_icon_path.empty()) { std::string target_icon_path = resources_dir; - if (!create_square_shortcut_icon_file(path, src_icon_path, resources_dir, target_icon_path, "icns", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, resources_dir, target_icon_path, 512)) { // Error is logged in create_square_shortcut_icon_file return false; @@ -331,6 +343,8 @@ namespace gui::utils fmt::append(link_path, "/%s.desktop", simple_name); + sys_log.notice("Creating shortcut '%s' for '%s' with arguments '%s'", link_path, exe_path, target_cli_args); + std::string file_content; fmt::append(file_content, "[Desktop Entry]\n"); fmt::append(file_content, "Encoding=UTF-8\n"); @@ -349,7 +363,7 @@ namespace gui::utils if (!src_icon_path.empty() && !target_icon_dir.empty()) { std::string target_icon_path; - if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, "png", 512)) + if (!create_square_shortcut_icon_file(path, src_icon_path, target_icon_dir, target_icon_path, 512)) { // Error is logged in create_square_shortcut_icon_file return false; @@ -383,4 +397,86 @@ namespace gui::utils return true; #endif } + + void remove_shortcuts(const std::string& name, [[maybe_unused]] const std::string& serial) + { + const std::string simple_name = QString::fromStdString(vfs::escape(name, true)).simplified().toStdString(); + if (simple_name.empty() || simple_name == "." || simple_name == "..") + { + sys_log.error("Failed to remove shortcuts: Cleaned file name empty or not allowed"); + return; + } + + const auto remove_path = [](const std::string& path, bool is_file) + { + if (!path.empty()) + { + if (is_file && fs::is_file(path)) + { + if (fs::remove_file(path)) + { + sys_log.success("Removed shortcut file '%s'", path); + } + else + { + sys_log.error("Failed to remove shortcut file '%s': error='%s'", path, fs::g_tls_error); + } + } + else if (!is_file && fs::is_dir(path)) + { + if (fs::remove_all(path)) + { + sys_log.success("Removed shortcut directory '%s'", path); + } + else + { + sys_log.error("Failed to remove shortcut directory '%s': error='%s'", path, fs::g_tls_error); + } + } + } + }; + + std::vector locations = { + shortcut_location::desktop, + shortcut_location::applications + }; +#ifdef _WIN32 + locations.push_back(shortcut_location::rpcs3_shortcuts); +#endif + + for (shortcut_location location : locations) + { + std::string link_path; + + switch (location) + { + case shortcut_location::desktop: + link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::DesktopLocation).toStdString(); + break; + case shortcut_location::applications: + link_path = QStandardPaths::writableLocation(QStandardPaths::StandardLocation::ApplicationsLocation).toStdString(); + link_path += "/RPCS3"; + break; +#ifdef _WIN32 + case shortcut_location::rpcs3_shortcuts: + link_path = rpcs3::utils::get_games_shortcuts_dir(); + break; +#endif + } + +#ifdef _WIN32 + fmt::append(link_path, "/%s.lnk", simple_name); + remove_path(link_path, true); +#elif defined(__APPLE__) + fmt::append(link_path, "/%s.app", simple_name); + remove_path(link_path, false); +#else + fmt::append(link_path, "/%s.desktop", simple_name); + remove_path(link_path, true); +#endif + } + + const std::string icon_path = fmt::format("%sIcons/game_icons/%s/shortcut.%s", fs::get_config_dir(), serial, icon_extension); + remove_path(icon_path, true); + } } diff --git a/rpcs3/rpcs3qt/shortcut_utils.h b/rpcs3/rpcs3qt/shortcut_utils.h index 11208a2bf0..4e7fcdf0ce 100644 --- a/rpcs3/rpcs3qt/shortcut_utils.h +++ b/rpcs3/rpcs3qt/shortcut_utils.h @@ -2,7 +2,7 @@ namespace gui::utils { - enum shortcut_location + enum class shortcut_location { desktop, applications, @@ -19,4 +19,6 @@ namespace gui::utils const std::string& src_icon_path, const std::string& target_icon_dir, shortcut_location shortcut_location); + + void remove_shortcuts(const std::string& name, const std::string& serial); } diff --git a/rpcs3/util/asm.hpp b/rpcs3/util/asm.hpp index da3d7dac95..df773dafb3 100644 --- a/rpcs3/util/asm.hpp +++ b/rpcs3/util/asm.hpp @@ -193,10 +193,35 @@ namespace utils #endif } - // Synchronization helper (cache-friendly busy waiting) - inline void busy_wait(usz cycles = 3000) + // The hardware clock on many arm timers run south of 100mhz + // and the busy waits in RPCS3 were written assuming an x86 machine + // with hardware timers that run around 3GHz. + // For instance, on the snapdragon 8 gen 2, the hardware timer runs at 19.2mhz. + // This means that a busy wait that would have taken nanoseconds on x86 will run for + // many microseconds on many arm machines. +#ifdef ARCH_ARM64 + + inline u64 arm_timer_scale = 1; + + inline void init_arm_timer_scale() { + u64 freq = 0; + asm volatile("mrs %0, cntfrq_el0" : "=r"(freq)); + + // Try to scale hardware timer to match 3GHz + u64 timer_scale = freq / 30000000; + if (timer_scale) + arm_timer_scale = timer_scale; + } +#endif + + inline void busy_wait(u64 cycles = 3000) + { +#ifdef ARCH_ARM64 + const u64 stop = get_tsc() + ((cycles / 100) * arm_timer_scale); +#else const u64 stop = get_tsc() + cycles; +#endif do pause(); while (get_tsc() < stop); }