Merge branch 'master' into windows-clang

This commit is contained in:
qurious-pixel 2026-01-23 00:58:49 -08:00 committed by GitHub
commit 93c7bc2565
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 1434 additions and 213 deletions

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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<gcm_config>().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<CellGcmReportData> cellGcmGetReportDataAddressLocation(u32 index, u32 location)
vm::ptr<CellGcmReportData> 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<CellGcmReportData> 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<CellGcmReportData> report = cellGcmGetReportDataAddressLocation(index, location);
vm::ptr<CellGcmReportData> 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<CellGcmReportData> report = cellGcmGetReportDataAddressLocation(ppu, index, location);
// Timestamp reports don't need host GPU access and are much faster to emulate
return vm::get_super_ptr<CellGcmReportData>(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);

View file

@ -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;

View file

@ -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,

View file

@ -3433,7 +3433,7 @@ error_code sceNpLookupUserProfile(s32 transId, vm::cptr<SceNpId> npId, vm::ptr<S
error_code sceNpLookupUserProfileAsync(s32 transId, vm::cptr<SceNpId> npId, vm::ptr<SceNpUserInfo> userInfo, vm::ptr<SceNpAboutMe> aboutMe, vm::ptr<SceNpMyLanguages> languages,
vm::ptr<SceNpCountryCode> countryCode, vm::ptr<SceNpAvatarImage> avatarImage, s32 prio, vm::ptr<void> 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<named_thread<np::np_handler>>();

View file

@ -1327,7 +1327,11 @@ void PPUTranslator::VMADDFP(ppu_opcode_t op)
void PPUTranslator::VMAXFP(ppu_opcode_t op)
{
const auto [a, b] = get_vrs<f32[4]>(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<f32[4]>(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)

View file

@ -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<usb_device_mic>(0, get_new_location(), MicType::Logitech));
break;
case microphone_handler::real_singstar:
case microphone_handler::singstar:
usb_devices.push_back(std::make_shared<usb_device_mic>(0, get_new_location(), MicType::SingStar));
break;
case microphone_handler::rocksmith:
usb_devices.push_back(std::make_shared<usb_device_mic>(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<ppu_thread>(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<u8>(+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<named_thread<usb_handler_thread>>();
@ -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<named_thread<usb_handler_thread>>();

View file

@ -56,7 +56,7 @@ struct UsbDeviceRequest
struct UsbDeviceIsoRequest
{
vm::ptr<void> buf;
vm::bptr<void> buf;
be_t<u32> start_frame;
be_t<u32> num_packets;
be_t<u16> packets[8];

View file

@ -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<u8>(wIndex)); break;
case LIBUSB_REQUEST_SET_INTERFACE: usb_device::set_interface(::narrow<u8>(wIndex), ::narrow<u8>(wValue)); break;
default: sys_usbd.error("Unhandled control transfer(0x%02x): 0x%02x", bmRequestType, bRequest); break;
}
break;

View file

@ -86,6 +86,54 @@ struct UsbDeviceHID
le_t<u16, 1> wDescriptorLength;
};
struct UsbAudioInputTerminal
{
u8 bDescriptorSubtype;
u8 bTerminalID;
le_t<u16, 1> wTerminalType;
u8 bAssocTerminal;
u8 bNrChannels;
le_t<u16, 1> wChannelConfig;
u8 iChannelNames;
u8 iTerminal;
};
struct UsbAudioOutputTerminal
{
u8 bDescriptorSubtype;
u8 bTerminalID;
le_t<u16, 1> wTerminalType;
u8 bAssocTerminal;
u8 bSourceID;
u8 iTerminal;
};
struct UsbAudioInterface
{
u8 bDescriptorSubtype;
u8 bTerminalLink;
u8 bDelay;
le_t<u16, 1> wFormatTag;
};
struct UsbAudioEndpoint
{
u8 bEndpointAddress;
u8 bmAttributes;
le_t<u16, 1> wMaxPacketSize;
u8 bInterval;
u8 bRefresh;
u8 bSynchAddress;
};
struct UsbAudioStreamingEndpoint
{
u8 bDescriptorSubtype;
u8 bmAttributes;
u8 bLockDelayUnits;
le_t<u16, 1> 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<u8, 7> 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;

View file

@ -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<u8, 7>& 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> usb_device_mic::make_singstar(u32 controller_index, const std::array<u8, 7>& location)
{
return std::make_shared<usb_device_mic>(controller_index, location, MicType::SingStar);
}
std::shared_ptr<usb_device> usb_device_mic::make_logitech(u32 controller_index, const std::array<u8, 7>& location)
{
return std::make_shared<usb_device_mic>(controller_index, location, MicType::Logitech);
}
std::shared_ptr<usb_device> usb_device_mic::make_rocksmith(u32 controller_index, const std::array<u8, 7>& location)
{
return std::make_shared<usb_device_mic>(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<mic_thread>();
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<u8*>(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<u8>(m_mic_type), transfer->iso_request.buf, transfer->iso_request.start_frame, transfer->iso_request.num_packets, index, inlen, outlen);
}
}

View file

@ -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<u8, 7>& location, MicType mic_type);
static std::shared_ptr<usb_device> make_singstar(u32 controller_index, const std::array<u8, 7>& location);
static std::shared_ptr<usb_device> make_logitech(u32 controller_index, const std::array<u8, 7>& location);
static std::shared_ptr<usb_device> make_rocksmith(u32 controller_index, const std::array<u8, 7>& 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];
};

View file

@ -101,6 +101,7 @@ void fmt_class_string<rpcn::CommandType>::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<u8> 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<u8> packet_data;
std::vector<u8> 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<u8> 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<u8> 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<u8> 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<u8> 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<u8> 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<u8> 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<ErrorType>(reply.get<u8>());
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<u8> 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<u8> 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<u8> packet_data;
if (!forge_send_reply(CommandType::RemoveFriend, req_id, data, packet_data))

View file

@ -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);

View file

@ -9,6 +9,7 @@ namespace rpcn
Login,
Terminate,
Create,
Delete,
SendToken,
SendResetToken,
ResetPassword,

View file

@ -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();

View file

@ -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<u32>(report_dma)));
}
void set_fragment_texture_dirty_bit(rsx::context* ctx, u32 arg, u32 index)

View file

@ -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<u32>(in_w, in_x + static_cast<u32>(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<u16>(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 };
}

View file

@ -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))
{

View file

@ -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;
}

View file

@ -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

View file

@ -501,7 +501,7 @@ void fmt_class_string<blit_engine::context_dma>::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";
}

View file

@ -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,
};

View file

@ -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)
{

View file

@ -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);
}
}

View file

@ -459,6 +459,7 @@
<ClCompile Include="Emu\Io\Skylander.cpp" />
<ClCompile Include="Emu\Io\KamenRider.cpp" />
<ClCompile Include="Emu\Io\usb_device.cpp" />
<ClCompile Include="Emu\Io\usb_microphone.cpp" />
<ClCompile Include="Emu\Io\usb_vfs.cpp" />
<ClCompile Include="Emu\RSX\Capture\rsx_capture.cpp" />
<ClCompile Include="Emu\RSX\Capture\rsx_replay.cpp" />
@ -831,6 +832,7 @@
<ClInclude Include="Emu\Io\Skylander.h" />
<ClInclude Include="Emu\Io\KamenRider.h" />
<ClInclude Include="Emu\Io\usb_device.h" />
<ClInclude Include="Emu\Io\usb_microphone.h" />
<ClInclude Include="Emu\Io\usb_vfs.h" />
<ClInclude Include="Emu\IPC.h" />
<ClInclude Include="Emu\Audio\AudioDumper.h" />

View file

@ -924,6 +924,9 @@
<ClCompile Include="Emu\Io\usb_device.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\usb_microphone.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
<ClCompile Include="Emu\Io\usb_vfs.cpp">
<Filter>Emu\Io</Filter>
</ClCompile>
@ -1402,6 +1405,9 @@
<ClCompile Include="Emu\NP\clans_client.cpp">
<Filter>Emu\NP</Filter>
</ClCompile>
<ClCompile Include="Loader\ISO.cpp">
<Filter>Loader</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -2121,6 +2127,9 @@
<ClInclude Include="Emu\Io\usb_device.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\usb_microphone.h">
<Filter>Emu\Io</Filter>
</ClInclude>
<ClInclude Include="Emu\Io\usb_vfs.h">
<Filter>Emu\Io</Filter>
</ClInclude>
@ -2818,6 +2827,9 @@
<ClInclude Include="Emu\NP\clans_config.h">
<Filter>Emu\NP</Filter>
</ClInclude>
<ClInclude Include="Loader\ISO.h">
<Filter>Loader</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">

View file

@ -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)

View file

@ -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);
}
}
}

View file

@ -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;
}

View file

@ -16,36 +16,85 @@
#include <QCompleter>
kamen_rider_dialog* kamen_rider_dialog::inst = nullptr;
std::array<std::optional<std::tuple<u8, u8, u8>>, UI_FIG_NUM> kamen_rider_dialog::figure_slots = {};
std::array<std::optional<std::tuple<u8, u8, u8, u8>>, UI_FIG_NUM> kamen_rider_dialog::figure_slots = {};
QString last_kamen_rider_path;
static const std::map<const std::pair<const u8, const u8>, 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::tuple<const u8, const u8, const u8>, 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<u8, 16>& 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<u8, 16> figure_data = {u8(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, 0x10, fig_type, 0x01, fig_id};
std::array<u8, 16> figure_data = {u8(dist(mt)), 0x03, 0x00, 0x00, 0x01, 0x0e, 0x0a, 0x0a, 0x10, figure_type, erc_type, character_id};
write_to_ptr<le_t<u32>>(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

View file

@ -42,7 +42,7 @@ protected:
protected:
std::array<QLineEdit*, UI_FIG_NUM> edit_kamen_riders{};
static std::array<std::optional<std::tuple<u8, u8, u8>>, UI_FIG_NUM> figure_slots;
static std::array<std::optional<std::tuple<u8, u8, u8, u8>>, UI_FIG_NUM> figure_slots;
private:
static kamen_rider_dialog* inst;

View file

@ -76,6 +76,20 @@ std::string derive_password(std::string_view user_password)
return derived_password;
}
std::shared_ptr<rpcn::rpcn_client> 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<std::string>& 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<rpcn_friends_dialog*>(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)));

View file

@ -3,6 +3,7 @@
#include <optional>
#include <QDialog>
#include <QComboBox>
#include <QLineEdit>
#include <QListWidget>
@ -83,6 +84,13 @@ private:
std::optional<std::string> 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;

View file

@ -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<shortcut_location> 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);
}
}

View file

@ -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);
}

View file

@ -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);
}