mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-10-12 02:09:17 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ba2f75bce | ||
|
|
26098fb42d | ||
|
|
d1d0cbccea | ||
|
|
7b4de2f13d | ||
|
|
dfc486fc6b | ||
|
|
6a900d00e6 | ||
|
|
31bd702e39 | ||
|
|
63ad856afa | ||
|
|
cb450d7e8f | ||
|
|
68e0d6021c | ||
|
|
10adf2ec39 | ||
|
|
987129bc91 | ||
|
|
b195c2ff1b | ||
|
|
f9b8c209a3 | ||
|
|
cd4d4e02ec | ||
|
|
d21c0d808d | ||
|
|
52343fa2f3 | ||
|
|
ff91404b7c | ||
|
|
e38d27157e | ||
|
|
0be621eb62 | ||
|
|
78f27eaad6 | ||
|
|
66ba732829 | ||
|
|
8f2fe1a99c | ||
|
|
0be6d12140 | ||
|
|
d6295528d7 | ||
|
|
510f4c243e | ||
|
|
ffa4c71088 | ||
|
|
5e89a0061e | ||
|
|
b26e0cfa91 | ||
|
|
33b2958da5 | ||
|
|
cfd03fc026 | ||
|
|
7c572eb4dd | ||
|
|
4d4c07c18a | ||
|
|
4c7d08582a | ||
|
|
304d544bc1 | ||
|
|
e4694a7a2c | ||
|
|
7f476dad89 | ||
|
|
222754bfd3 | ||
|
|
d064a83d11 | ||
|
|
8ffc512beb | ||
|
|
2bf8677d3c | ||
|
|
ab412d5ddb | ||
|
|
e97200a884 | ||
|
|
cb63cc82ca | ||
|
|
5f8dd1de61 | ||
|
|
479ca74cad | ||
|
|
925fbb2607 | ||
|
|
55a3aff337 | ||
|
|
12178de458 | ||
|
|
8e4659a6cb | ||
|
|
532b6520b4 | ||
|
|
1b90d1c149 | ||
|
|
c7642cb875 | ||
|
|
4dfdda9789 | ||
|
|
191932b854 | ||
|
|
b1e8e74619 | ||
|
|
183597deea | ||
|
|
2ff18d0fae | ||
|
|
3e7477107a | ||
|
|
7877ebe43d | ||
|
|
30ee6cc358 | ||
|
|
3db7c53c67 | ||
|
|
70c8683ae2 | ||
|
|
b240813958 | ||
|
|
c9fe397116 | ||
|
|
2cc650538d | ||
|
|
b444d6ad24 | ||
|
|
ba4a4b561e | ||
|
|
7c3d7c7a23 | ||
|
|
52de29af04 | ||
|
|
43a42d1a86 | ||
|
|
7b99edcfd6 | ||
|
|
7b7983ca1a | ||
|
|
a72c4c319b | ||
|
|
fcbed0bc6d | ||
|
|
32b54f1864 | ||
|
|
193975556d | ||
|
|
d9608bf0f0 | ||
|
|
46aee28f85 | ||
|
|
998298f9a8 | ||
|
|
c673da45f8 | ||
|
|
1ce71092c8 | ||
|
|
0d866154aa | ||
|
|
0374854c80 | ||
|
|
f5edadb2ef | ||
|
|
103d41944c | ||
|
|
a664f405bc | ||
|
|
5da9f4ab32 | ||
|
|
db7d845e09 | ||
|
|
e68ae2d051 | ||
|
|
1814fefb95 | ||
|
|
a1dc53fdf2 | ||
|
|
0ca1c58e75 | ||
|
|
8e245a6e15 | ||
|
|
df7964560b | ||
|
|
7d69855fbb | ||
|
|
2f5c31155a | ||
|
|
40da906c91 | ||
|
|
5298e3b7a1 | ||
|
|
e8b4646145 | ||
|
|
d5213de6dd | ||
|
|
b1f51cfaa6 | ||
|
|
b7f6f4e80f | ||
|
|
1c757181a0 | ||
|
|
5d8efbfeb9 | ||
|
|
dd12be585e | ||
|
|
fc746de37d | ||
|
|
f2b7edff27 | ||
|
|
18f18b305f | ||
|
|
d10658b818 | ||
|
|
b87a56979e | ||
|
|
5df3970a75 | ||
|
|
a3409242ce | ||
|
|
54ab16913e | ||
|
|
2ccc4983fa | ||
|
|
5741d891bb | ||
|
|
4dce4ecbe5 | ||
|
|
a7e274234f | ||
|
|
f1574d7060 | ||
|
|
08b226b8f8 | ||
|
|
373d0ea7f0 | ||
|
|
babeda0ed2 | ||
|
|
c3f3529b98 | ||
|
|
96ccd89cd6 | ||
|
|
0850e0ff5c | ||
|
|
a7cbd46749 | ||
|
|
0e0cdc7c29 | ||
|
|
acb249f057 | ||
|
|
a3dc7e6af4 | ||
|
|
982337435a | ||
|
|
cdc04e0d59 | ||
|
|
7aa2422a07 | ||
|
|
17320679e3 | ||
|
|
1dd70e036e | ||
|
|
53d9522073 | ||
|
|
3b3c7f4f07 | ||
|
|
a5515d2371 | ||
|
|
dfc0542a9f | ||
|
|
4d88114c92 | ||
|
|
81cbf7ba3d | ||
|
|
83b1e07299 | ||
|
|
8307c54cd4 | ||
|
|
805c931d49 | ||
|
|
be69c054ee | ||
|
|
d98d378704 | ||
|
|
d9a59820ba | ||
|
|
a1c6b12311 | ||
|
|
a3232fddee | ||
|
|
71d1a4527d | ||
|
|
5b350ff3e1 | ||
|
|
1c460fdd7f |
+6
-6
@@ -23,12 +23,12 @@ rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \
|
||||
../../.ci/optimize-mac.sh rpcs3.app
|
||||
|
||||
# Download translations
|
||||
mkdir -p "rpcs3.app/Contents/translations"
|
||||
mkdir -p "rpcs3.app/Contents/Resources/translations"
|
||||
ZIP_URL="https://github.com/RPCS3/rpcs3_translations/releases/latest/download/RPCS3-languages.zip"
|
||||
echo "Downloading translations from: $ZIP_URL"
|
||||
if curl -fsSL --retry 3 --retry-delay 60 "$ZIP_URL" -o "translations.zip"; then
|
||||
echo "Successfully downloaded translations."
|
||||
if unzip -o translations.zip -d "rpcs3.app/Contents/translations" >/dev/null 2>&1; then
|
||||
if unzip -o translations.zip -d "rpcs3.app/Contents/Resources/translations" >/dev/null 2>&1; then
|
||||
rm -f translations.zip
|
||||
else
|
||||
echo "Failed to extract translations.zip. Continuing without translations."
|
||||
@@ -40,10 +40,10 @@ fi
|
||||
|
||||
# Copy Qt translations manually
|
||||
QT_TRANS="$WORKDIR/qt-downloader/$QT_VER/clang_64/translations"
|
||||
cp $QT_TRANS/qt_*.qm rpcs3.app/Contents/translations
|
||||
cp $QT_TRANS/qtbase_*.qm rpcs3.app/Contents/translations
|
||||
cp $QT_TRANS/qtmultimedia_*.qm rpcs3.app/Contents/translations
|
||||
rm -f rpcs3.app/Contents/translations/qt_help_*.qm || true
|
||||
cp $QT_TRANS/qt_*.qm rpcs3.app/Contents/Resources/translations
|
||||
cp $QT_TRANS/qtbase_*.qm rpcs3.app/Contents/Resources/translations
|
||||
cp $QT_TRANS/qtmultimedia_*.qm rpcs3.app/Contents/Resources/translations
|
||||
rm -f rpcs3.app/Contents/Resources/translations/qt_help_*.qm || true
|
||||
|
||||
# Need to do this rename hack due to case insensitive filesystem
|
||||
mv rpcs3.app RPCS3_.app
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Label PRs
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
|
||||
jobs:
|
||||
Label_PRs:
|
||||
name: Label from title and linked issues
|
||||
runs-on: ubuntu-latest
|
||||
if: github.repository == 'RPCS3/rpcs3'
|
||||
permissions:
|
||||
issues: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Add labels from title and linked issues
|
||||
uses: actions/github-script@v9
|
||||
with:
|
||||
retries: 3
|
||||
script: |
|
||||
const AREAS = [
|
||||
'Build and CI', 'CLI', 'Filesystem', 'GUI', 'Input', 'ISO', 'Localization', 'Overlay', 'Render: Vulkan', 'RSX', 'Savestates',
|
||||
];
|
||||
|
||||
const {repository: {pullRequest: pull}} = await github.graphql(`
|
||||
query($owner: String!, $repo: String!, $number: Int!) {
|
||||
repository(owner: $owner, name: $repo) {
|
||||
pullRequest(number: $number) {
|
||||
labels(first: 100) { nodes { name } }
|
||||
closingIssuesReferences(first: 50) { nodes { labels(first: 100) { nodes { name } } } }
|
||||
timelineItems(itemTypes: [UNLABELED_EVENT], last: 100) { nodes { ... on UnlabeledEvent { label { name } } } }
|
||||
}
|
||||
}
|
||||
}`, {...context.repo, number: context.issue.number});
|
||||
|
||||
if (pull.labels.nodes.some(x => AREAS.includes(x.name)))
|
||||
return;
|
||||
|
||||
// Skip labels a maintainer removed
|
||||
const removed = pull.timelineItems.nodes.map(x => x.label.name);
|
||||
const fromTitle = /^rsx\b/i.test(context.payload.pull_request.title) ? ['RSX'] : [];
|
||||
const fromIssues = pull.closingIssuesReferences.nodes.flatMap(i => i.labels.nodes.map(x => x.name));
|
||||
const labels = [...new Set([...fromTitle, ...fromIssues])]
|
||||
.filter(name => AREAS.includes(name) && !removed.includes(name));
|
||||
|
||||
if (labels.length > 0) {
|
||||
console.log(`Adding labels: ${labels.join(', ')}`);
|
||||
await github.rest.issues.addLabels({...context.repo, issue_number: context.issue.number, labels});
|
||||
}
|
||||
+36
-10
@@ -29,8 +29,28 @@ jobs:
|
||||
actions: write
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
skip_ccache: ${{ steps.pr_labels.outputs.skip_ccache }}
|
||||
steps:
|
||||
- name: Look for the 'Shaders' label on the merged PR
|
||||
- name: "Look for the 'CI: Disable Build Caches' label on the PR"
|
||||
id: pr_labels
|
||||
if: github.event_name == 'pull_request'
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
# Query the live labels so adding the label and re-running all jobs works without a new push
|
||||
labels=$(gh api "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}" --jq '.labels[].name' 2>/dev/null) \
|
||||
|| labels=$(jq -r '.pull_request.labels[]?.name' "$GITHUB_EVENT_PATH")
|
||||
echo "Labels: ${labels:-<none>}"
|
||||
skip=false
|
||||
if grep -qix 'ci: disable build caches' <<< "$labels"; then
|
||||
skip=true
|
||||
echo "Builds will not restore ccache because of the 'CI: Disable Build Caches' label." >> "$GITHUB_STEP_SUMMARY"
|
||||
fi
|
||||
echo "skip_ccache=$skip" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: "Look for the 'Shaders' or 'CI: Disable Build Caches' label on the merged PR"
|
||||
id: labels
|
||||
if: github.event_name == 'push' && github.repository == 'RPCS3/rpcs3' && github.ref_name == 'master'
|
||||
continue-on-error: true
|
||||
@@ -41,14 +61,14 @@ jobs:
|
||||
for sha in $(jq -r '[.after, (.commits[]?.id)] | map(select(. != null)) | unique[]' "$GITHUB_EVENT_PATH"); do
|
||||
labels=$(gh api "repos/${{ github.repository }}/commits/$sha/pulls" --jq '.[].labels[].name' 2>/dev/null || true)
|
||||
echo "$sha: ${labels:-<no associated PR>}"
|
||||
if grep -qix 'shaders' <<< "$labels"; then
|
||||
if grep -qixE 'shaders|ci: disable build caches' <<< "$labels"; then
|
||||
found=true
|
||||
fi
|
||||
done
|
||||
echo "shaders=$found" >> "$GITHUB_OUTPUT"
|
||||
echo "clean=$found" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Remove all code caches
|
||||
if: steps.labels.outputs.shaders == 'true'
|
||||
if: steps.labels.outputs.clean == 'true'
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
@@ -63,7 +83,7 @@ jobs:
|
||||
echo "Removing $key (id $id)"
|
||||
gh api -X DELETE "repos/${{ github.repository }}/actions/caches/$id" --silent
|
||||
done < code_caches.tsv
|
||||
echo "Removed $(wc -l < code_caches.tsv) code cache(s) after a 'Shaders' PR merge." >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "Removed $(wc -l < code_caches.tsv) code cache(s) after merging a 'Shaders' or 'CI: Disable Build Caches' PR." >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
Linux_Build:
|
||||
# Only run push event on master branch of main repo, but run all PRs
|
||||
@@ -112,6 +132,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Restore build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
@@ -179,7 +200,7 @@ jobs:
|
||||
runs-on: ${{ matrix.runs-on }}
|
||||
env:
|
||||
CCACHE_DIR: /tmp/ccache_dir
|
||||
QT_VER: '6.11.2'
|
||||
QT_VER: '6.12.0'
|
||||
QT_VER_MAIN: '6'
|
||||
LLVM_COMPILER_VER: '22'
|
||||
RELEASE_MESSAGE: ../GitHubReleaseMessage.txt
|
||||
@@ -198,6 +219,7 @@ jobs:
|
||||
cache: 'disable'
|
||||
|
||||
- name: Restore Build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
@@ -263,9 +285,9 @@ jobs:
|
||||
env:
|
||||
COMPILER: msvc
|
||||
QT_VER_MAIN: '6'
|
||||
QT_VER: '6.11.2'
|
||||
QT_VER: '6.12.0'
|
||||
QT_VER_MSVC: 'msvc2022'
|
||||
QT_DATE: '202608131017'
|
||||
QT_DATE: '202609280346'
|
||||
LLVM_VER: '22.1.8'
|
||||
VULKAN_VER: '1.4.341.1'
|
||||
VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe'
|
||||
@@ -303,6 +325,7 @@ jobs:
|
||||
run: .ci/get_keys-windows.sh
|
||||
|
||||
- name: Restore Build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
@@ -387,9 +410,9 @@ jobs:
|
||||
env:
|
||||
COMPILER: msvc
|
||||
QT_VER_MAIN: '6'
|
||||
QT_VER: '6.11.2'
|
||||
QT_VER: '6.12.0'
|
||||
QT_VER_MSVC: 'msvc2022'
|
||||
QT_DATE: '202608131017'
|
||||
QT_DATE: '202609280346'
|
||||
LLVM_VER: '22.1.8'
|
||||
VULKAN_VER: '1.4.341.1'
|
||||
VULKAN_SDK_SHA: 'bcf2d75aa9556889ab974858666e20b3655b6055a0db704ccb47279ff33b5bfe'
|
||||
@@ -427,6 +450,7 @@ jobs:
|
||||
run: .ci/get_keys-windows.sh
|
||||
|
||||
- name: Restore Build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
@@ -529,6 +553,7 @@ jobs:
|
||||
p7zip
|
||||
|
||||
- name: Restore build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
@@ -599,6 +624,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Restore Build Ccache
|
||||
if: needs.Clean_Code_Caches.outputs.skip_ccache != 'true'
|
||||
uses: actions/cache/restore@main
|
||||
id: restore-build-ccache
|
||||
with:
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/7zip/7zip updated: 0766b733fe...9128b80e3a
Vendored
+1
-1
Submodule 3rdparty/FAudio updated: 667942a909...6839b88e30
+1
-1
Submodule 3rdparty/GPUOpen/VulkanMemoryAllocator updated: 1d8f600fd4...3aa921224c
Vendored
+1
@@ -56,6 +56,7 @@
|
||||
-DCURL_USE_OPENSSL=OFF ^
|
||||
-DCURL_USE_LIBPSL=OFF ^
|
||||
-DCURL_ZLIB=OFF ^
|
||||
-DCURL_ZSTD=OFF ^
|
||||
-DCURL_USE_LIBSSH2=OFF ^
|
||||
-DCMAKE_C_FLAGS="/DWOLFSSL_ALT_CERT_CHAINS /DHAVE_SNI /DNO_MD4 /DWOLFSSL_USER_SETTINGS /DUSE_IPV6 /I\"$(SolutionDir)3rdparty\curl\extra\" /I\"$(SolutionDir)3rdparty\wolfssl\wolfssl\" /I\"$(SolutionDir)3rdparty\wolfssl\wolfssl\wolfssl\" /I\"$(SolutionDir)3rdparty\wolfssl\extra\win32\"" ^
|
||||
-DCMAKE_C_FLAGS_RELEASE="/O2 /Ob1 /GF /Gy" ^
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/libsdl-org/SDL updated: fa2c02bb6e...829a65d769
Vendored
+64
-54
@@ -56,6 +56,63 @@ namespace pine
|
||||
|
||||
typedef unsigned long long SOCKET;
|
||||
|
||||
#ifndef _WIN32
|
||||
inline std::string get_socket_path(std::string_view file_name, int slot, int default_slot)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
const char* runtime_dir = std::getenv("TMPDIR");
|
||||
#else
|
||||
const char* runtime_dir = std::getenv("XDG_RUNTIME_DIR");
|
||||
#endif
|
||||
// fallback in case macOS or other OSes don't implement the XDG base
|
||||
// spec
|
||||
std::string path = runtime_dir ? std::string(runtime_dir) + '/' : std::string("/tmp/");
|
||||
path += file_name;
|
||||
|
||||
if (slot != default_slot)
|
||||
{
|
||||
fmt::append(path, ".%d", slot);
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
inline const char* bind_and_listen(SOCKET sock, u16 port)
|
||||
{
|
||||
const int exclusive = 1;
|
||||
if (setsockopt(sock, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast<const char*>(&exclusive), sizeof(exclusive)) != 0)
|
||||
return "setsockopt(SO_EXCLUSIVEADDRUSE) failed";
|
||||
|
||||
sockaddr_in server{};
|
||||
server.sin_family = AF_INET;
|
||||
server.sin_port = htons(port);
|
||||
|
||||
if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr.s_addr) != 1)
|
||||
return "inet_pton failed";
|
||||
#else
|
||||
inline const char* bind_and_listen(int sock, const std::string& socket_path)
|
||||
{
|
||||
sockaddr_un server{};
|
||||
|
||||
if (socket_path.size() >= sizeof(server.sun_path))
|
||||
return "socket path is too long";
|
||||
|
||||
server.sun_family = AF_UNIX;
|
||||
std::memcpy(server.sun_path, socket_path.c_str(), socket_path.size() + 1);
|
||||
|
||||
unlink(socket_path.c_str());
|
||||
#endif
|
||||
if (bind(sock, reinterpret_cast<sockaddr*>(&server), sizeof(server)) != 0)
|
||||
return "bind failed";
|
||||
|
||||
if (listen(sock, 4096) != 0)
|
||||
return "listen failed";
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template<typename Impl>
|
||||
class pine_server : public Impl
|
||||
{
|
||||
@@ -528,7 +585,6 @@ namespace pine
|
||||
{
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa {};
|
||||
struct sockaddr_in server {};
|
||||
m_sock = INVALID_SOCKET;
|
||||
m_msgsock = INVALID_SOCKET;
|
||||
|
||||
@@ -544,45 +600,9 @@ namespace pine
|
||||
return;
|
||||
}
|
||||
|
||||
// yes very good windows s/sun/sin/g sure is fine
|
||||
server.sin_family = AF_INET;
|
||||
// localhost only
|
||||
if (!inet_pton(server.sin_family, "127.0.0.1", &server.sin_addr.s_addr))
|
||||
{
|
||||
fmt::throw_exception("IPC: Failed to convert localhost");
|
||||
}
|
||||
server.sin_port = htons(Impl::get_port());
|
||||
|
||||
if (bind(m_sock, reinterpret_cast<struct sockaddr*>(&server), sizeof(server)) == SOCKET_ERROR)
|
||||
{
|
||||
Impl::error("IPC: Error while binding to socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
|
||||
const char* failure = bind_and_listen(m_sock, static_cast<u16>(Impl::get_port()));
|
||||
#else
|
||||
char* runtime_dir = nullptr;
|
||||
#ifdef __APPLE__
|
||||
runtime_dir = std::getenv("TMPDIR");
|
||||
#else
|
||||
runtime_dir = std::getenv("XDG_RUNTIME_DIR");
|
||||
#endif
|
||||
// fallback in case macOS or other OSes don't implement the XDG base
|
||||
// spec
|
||||
if (runtime_dir == nullptr)
|
||||
m_socket_name = "/tmp/rpcs3.sock";
|
||||
else
|
||||
{
|
||||
m_socket_name = runtime_dir;
|
||||
m_socket_name += "/rpcs3.sock";
|
||||
}
|
||||
|
||||
const int slot = Impl::get_port();
|
||||
if (slot != IPC_DEFAULT_SLOT)
|
||||
{
|
||||
fmt::append(m_socket_name, ".%d", slot);
|
||||
}
|
||||
|
||||
struct sockaddr_un server {};
|
||||
m_socket_name = get_socket_path("rpcs3.sock", Impl::get_port(), IPC_DEFAULT_SLOT);
|
||||
|
||||
m_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (m_sock < 0)
|
||||
@@ -590,23 +610,13 @@ namespace pine
|
||||
Impl::error("IPC: Cannot open socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
server.sun_family = AF_UNIX;
|
||||
strcpy(server.sun_path, m_socket_name.c_str());
|
||||
|
||||
// we unlink the socket so that when releasing this thread the socket gets
|
||||
// freed even if we didn't close correctly the loop
|
||||
unlink(m_socket_name.c_str());
|
||||
if (bind(m_sock, std::bit_cast<struct sockaddr*>(&server), sizeof(struct sockaddr_un)))
|
||||
{
|
||||
Impl::error("IPC: Error while binding to socket! Shutting down...");
|
||||
return;
|
||||
}
|
||||
const char* failure = bind_and_listen(m_sock, m_socket_name);
|
||||
#endif
|
||||
|
||||
// maximum queue of 4096 commands before refusing, approximated to the
|
||||
// nearest legal value. We do not use SOMAXCONN as windows have this idea
|
||||
// that a "reasonable" value is 5, which is not.
|
||||
listen(m_sock, 4096);
|
||||
if (failure)
|
||||
{
|
||||
Impl::error("IPC: Error while setting up socket (%s)! Shutting down...", failure);
|
||||
}
|
||||
}
|
||||
|
||||
pine_server(const pine_server&) = delete;
|
||||
|
||||
+5
-5
@@ -20,26 +20,26 @@ The following tools are required to build RPCS3 on Windows 10 or later:
|
||||
with standalone **CMake** tool.
|
||||
|
||||
- [Python 3.6+](https://www.python.org/downloads/) (add to PATH)
|
||||
- [Qt 6.11.2](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
|
||||
- [Qt 6.12.0](https://www.qt.io/download-qt-installer) In case you can't download from the official installer, you can use [Another Qt installer](https://github.com/miurahr/aqtinstall) (In that case you will need to manually add the "qtmultimedia" module when installing Qt)
|
||||
- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (see "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/windows/getting_started.html)). Note that future SDKs may not work.
|
||||
|
||||
The `sln` solution available only on **Visual Studio** is the preferred building solution. It easily allows to build the **RPCS3** application in `Release` and `Debug` mode.
|
||||
|
||||
In order to build **RPCS3** with the `sln` solution (with **Visual Studio**), **Qt** libs need to be detected. To detect the libs:
|
||||
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.11.2\msvc2022_64\`
|
||||
- add and set the `QTDIR` environment variable, e.g. `<QtInstallFolder>\6.12.0\msvc2022_64\`
|
||||
- or use the [Visual Studio Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.QtVisualStudioTools2022)
|
||||
|
||||
**NOTE:** If you have issues with the **Visual Studio Qt Plugin**, you may want to uninstall it and install the [Legacy Qt Plugin](https://marketplace.visualstudio.com/items?itemName=TheQtCompany.LEGACYQtVisualStudioTools2022) instead.
|
||||
|
||||
In order to build **RPCS3** with the `CMake` solution (with both **Visual Studio** and standalone **CMake** tool):
|
||||
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.11.2\msvc2022_64\`
|
||||
- add and set the `Qt6_ROOT` environment variable to the **Qt** libs path, e.g. `<QtInstallFolder>\6.12.0\msvc2022_64\`
|
||||
|
||||
### Linux
|
||||
|
||||
These are the essentials tools to build RPCS3 on Linux. Some of them can be installed through your favorite package manager:
|
||||
- Clang 17+ or GCC 13+
|
||||
- [CMake 3.28.0+](https://www.cmake.org/download/)
|
||||
- [Qt 6.11.2](https://www.qt.io/download-qt-installer)
|
||||
- [Qt 6.12.0](https://www.qt.io/download-qt-installer)
|
||||
- [Vulkan SDK 1.4.341.1](https://vulkan.lunarg.com/sdk/home) (See "Install the SDK" [here](https://vulkan.lunarg.com/doc/sdk/latest/linux/getting_started.html)). Note that future SDKs may not work.
|
||||
- [SDL3](https://github.com/libsdl-org/SDL/releases) (for the FAudio backend)
|
||||
|
||||
@@ -116,7 +116,7 @@ Start **Visual Studio**, click on `Open a project or solution` and select the `r
|
||||
##### Configuring the Qt Plugin (if used)
|
||||
|
||||
1) go to `Extensions->Qt VS Tools->Qt Versions`
|
||||
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.11.2\msvc2022_64`, version will fill in automatically
|
||||
2) add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\6.12.0\msvc2022_64`, version will fill in automatically
|
||||
3) go to `Extensions->Qt VS Tools->Options->Legacy Project Format`. (Only available in the **Legacy Qt Plugin**)
|
||||
4) set `Build: Run pre-build setup` to `true`. (Only available in the **Legacy Qt Plugin**)
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ option(USE_LTO "Use LTO for building" ON)
|
||||
option(BUILD_RPCS3_TESTS "Build RPCS3 unit tests." OFF)
|
||||
option(RUN_RPCS3_TESTS "Run RPCS3 unit tests. Requires BUILD_RPCS3_TESTS" OFF)
|
||||
option(USE_GAMEMODE "Choose whether to enable GameMode features or not." ON)
|
||||
option(BUNDLE_UDEV_RULES "Include udev rules on Linux/FreeBSD" OFF)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/buildfiles/cmake")
|
||||
|
||||
|
||||
+19
-1
@@ -1892,11 +1892,29 @@ bool fs::utime(const std::string& path, s64 atime, s64 mtime)
|
||||
#endif
|
||||
}
|
||||
|
||||
void fs::sync()
|
||||
void fs::sync(const std::string& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
static_cast<void>(path);
|
||||
fs::g_tls_error = fs::error::unknown;
|
||||
#elif defined(__linux__)
|
||||
// Only flush the filesystem containing the path: a global sync() also waits on every other mounted
|
||||
// filesystem and may block indefinitely on a slow or stalled one (e.g. network or FUSE mounts)
|
||||
const int fd = ::open(path.c_str(), O_RDONLY | O_CLOEXEC);
|
||||
|
||||
if (fd == -1)
|
||||
{
|
||||
fs::g_tls_error = to_error(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
const int res = ::syncfs(fd);
|
||||
const int err = errno;
|
||||
::close(fd);
|
||||
|
||||
fs::g_tls_error = res == 0 ? fs::error::ok : to_error(err);
|
||||
#else
|
||||
static_cast<void>(path);
|
||||
::sync();
|
||||
fs::g_tls_error = fs::error::ok;
|
||||
#endif
|
||||
|
||||
+2
-2
@@ -263,8 +263,8 @@ namespace fs
|
||||
// Set file access/modification time
|
||||
bool utime(const std::string& path, s64 atime, s64 mtime);
|
||||
|
||||
// Synchronize filesystems (TODO)
|
||||
void sync();
|
||||
// Synchronize the filesystem containing the given path (only that filesystem where supported)
|
||||
void sync(const std::string& path);
|
||||
|
||||
class file final
|
||||
{
|
||||
|
||||
@@ -612,7 +612,7 @@ void fmt_class_string<std::source_location>::format(std::string& out, u64 arg)
|
||||
#ifdef _WIN32
|
||||
if (DWORD error = GetLastError())
|
||||
{
|
||||
fmt::append(out, " (error=%s)", error, fmt::win_error_to_string(error));
|
||||
fmt::append(out, " (error=%s)", fmt::win_error_to_string(error));
|
||||
}
|
||||
#else
|
||||
if (int error = errno)
|
||||
|
||||
@@ -386,7 +386,7 @@ void decode_x64_reg_op(const u8* code, x64_op_t& out_op, x64_reg_t& out_reg, usz
|
||||
|
||||
case 0x67: // group 4
|
||||
{
|
||||
sig_log.error("decode_x64_reg_op(%016llxh): address-size override prefix found", code - out_length, prefix);
|
||||
sig_log.error("decode_x64_reg_op(%016llxh): address-size override prefix found", code - out_length);
|
||||
out_op = X64OP_NONE;
|
||||
out_reg = X64_NOT_SET;
|
||||
out_size = 0;
|
||||
|
||||
@@ -1190,7 +1190,7 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
|
||||
|
||||
if (!addr)
|
||||
{
|
||||
patch_log.error("Failed to allocate 0x%x bytes for code (entry=0x%x)", alloc_size, addr, out_branch);
|
||||
patch_log.error("Failed to allocate 0x%x bytes for code (entry=0x%x)", alloc_size, out_branch);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1436,7 +1436,7 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
|
||||
|
||||
if (dest_path.empty())
|
||||
{
|
||||
patch_log.error("Failed to patch file path at '%s': destination is not mounted", original_vfs_path, dest_vfs_path);
|
||||
patch_log.error("Failed to patch file path at '%s': destination is not mounted", original_vfs_path);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,11 @@ class shared_mutex final
|
||||
public:
|
||||
constexpr shared_mutex() = default;
|
||||
|
||||
const atomic_t<u32>& raw() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
bool try_lock_shared()
|
||||
{
|
||||
const u32 value = m_value.load();
|
||||
|
||||
@@ -55,7 +55,8 @@ namespace utils
|
||||
int compare_versions(const std::string& v1, const std::string& v2, bool& ok)
|
||||
{
|
||||
// Check if both version strings are valid
|
||||
ok = std::regex_match(v1, std::regex("[0-9.]*")) && std::regex_match(v2, std::regex("[0-9.]*"));
|
||||
static const std::regex comp = std::regex("[0-9.]*");
|
||||
ok = std::regex_match(v1, comp) && std::regex_match(v2, comp);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# DualShock 3
|
||||
notify 100 {
|
||||
match "system" "USB";
|
||||
match "subsystem" "INTERFACE";
|
||||
match "type" "ATTACH";
|
||||
match "vendor" "0x054c";
|
||||
match "product" "0x0268";
|
||||
action "chmod 0666 /dev/$cdev";
|
||||
};
|
||||
|
||||
# DualShock 4
|
||||
notify 100 {
|
||||
match "system" "USB";
|
||||
match "subsystem" "INTERFACE";
|
||||
match "type" "ATTACH";
|
||||
match "vendor" "0x054c";
|
||||
match "product" "0x05c4";
|
||||
action "chmod 0666 /dev/$cdev";
|
||||
};
|
||||
|
||||
# DualShock 4 Wireless Adapter
|
||||
notify 100 {
|
||||
match "system" "USB";
|
||||
match "subsystem" "INTERFACE";
|
||||
match "type" "ATTACH";
|
||||
match "vendor" "0x054c";
|
||||
match "product" "0x0ba0";
|
||||
action "chmod 0666 /dev/$cdev";
|
||||
};
|
||||
|
||||
# DualShock 4 Slim
|
||||
notify 100 {
|
||||
match "system" "USB";
|
||||
match "subsystem" "INTERFACE";
|
||||
match "type" "ATTACH";
|
||||
match "vendor" "0x054c";
|
||||
match "product" "0x09cc";
|
||||
action "chmod 0666 /dev/$cdev";
|
||||
};
|
||||
|
||||
# DualSense
|
||||
notify 100 {
|
||||
match "system" "USB";
|
||||
match "subsystem" "INTERFACE";
|
||||
match "type" "ATTACH";
|
||||
match "vendor" "0x054c";
|
||||
match "product" "0x0ce6";
|
||||
action "chmod 0666 /dev/$cdev";
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
# DualShock 3 over USB
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0268", MODE="0666"
|
||||
|
||||
# DualShock 3 over Bluetooth
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:0268*", MODE="0666"
|
||||
|
||||
# DualShock 4 over USB
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", MODE="0666"
|
||||
|
||||
# DualShock 4 Wireless Adapter over USB
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ba0", MODE="0666"
|
||||
|
||||
# DualShock 4 Slim over USB
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="09cc", MODE="0666"
|
||||
|
||||
# DualShock 4 over Bluetooth
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:05C4*", MODE="0666"
|
||||
|
||||
# DualShock 4 Slim over Bluetooth
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:09CC*", MODE="0666"
|
||||
|
||||
# PS5 DualSense controller over USB hidraw
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0ce6", MODE="0666", TAG+="uaccess"
|
||||
|
||||
# PS5 DualSense controller over bluetooth hidraw
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:0CE6*", MODE="0666", TAG+="uaccess"
|
||||
@@ -225,3 +225,14 @@ if(BUILD_RPCS3_TESTS)
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Udev rules, per platform
|
||||
if (BUNDLE_UDEV_RULES)
|
||||
if(CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
install(FILES ../config/udev/udev.rules
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/udev/rules.d/ RENAME 99-rpcs3.rules)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
|
||||
install(FILES ../config/udev/devd.conf
|
||||
DESTINATION ${CMAKE_INSTALL_SYSCONFDIR}/devd/ RENAME rpcs3.conf)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@@ -19,6 +19,7 @@ add_library(rpcs3_emu STATIC
|
||||
perf_monitor.cpp
|
||||
IPC_config.cpp
|
||||
IPC_socket.cpp
|
||||
Io/SkylanderPortalIPC_config.cpp
|
||||
)
|
||||
|
||||
if(USE_LTO)
|
||||
@@ -137,6 +138,8 @@ target_sources(rpcs3_emu PRIVATE
|
||||
../Loader/PSF.cpp
|
||||
../Loader/PUP.cpp
|
||||
../Loader/TAR.cpp
|
||||
../Loader/IRD.cpp
|
||||
../Loader/IRD_validation.cpp
|
||||
../Loader/ISO.cpp
|
||||
../Loader/iso_cache.cpp
|
||||
../Loader/content_validation.cpp
|
||||
@@ -426,6 +429,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Io/GHLtar.cpp
|
||||
Io/GunCon3.cpp
|
||||
Io/Infinity.cpp
|
||||
Io/wiimote_config.cpp
|
||||
Io/interception.cpp
|
||||
Io/KamenRider.cpp
|
||||
Io/KeyboardHandler.cpp
|
||||
@@ -443,6 +447,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Io/RB3MidiKeyboard.cpp
|
||||
Io/recording_config.cpp
|
||||
Io/Skylander.cpp
|
||||
Io/SkylanderPortalIPC.cpp
|
||||
Io/TopShotElite.cpp
|
||||
Io/TopShotFearmaster.cpp
|
||||
Io/Turntable.cpp
|
||||
@@ -694,6 +699,7 @@ target_link_libraries(rpcs3_emu
|
||||
3rdparty::vulkan
|
||||
3rdparty::glew
|
||||
3rdparty::libusb
|
||||
3rdparty::hidapi
|
||||
3rdparty::wolfssl
|
||||
3rdparty::openal
|
||||
3rdparty::cubeb
|
||||
|
||||
@@ -1270,8 +1270,8 @@ u32 cpu_thread::get_pc() const
|
||||
}
|
||||
case thread_class::rsx:
|
||||
{
|
||||
const auto ctrl = static_cast<const rsx::thread*>(this)->ctrl;
|
||||
return ctrl ? ctrl->get.load() : umax;
|
||||
const auto& ctrl = static_cast<const rsx::thread*>(this)->fifo_ctrl;
|
||||
return ctrl ? ctrl->get_pos() : umax;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
@@ -199,3 +199,154 @@ inline struct ppu_frsqrte_lut_t
|
||||
}
|
||||
}
|
||||
} ppu_frqrte_lut;
|
||||
|
||||
constexpr u32 ppu_vrefp_segments[32][2] =
|
||||
{
|
||||
{0x7ff800, 0x3e1}, {0x783800, 0x3a7}, {0x70ea00, 0x371}, {0x6a0800, 0x340},
|
||||
{0x638800, 0x313}, {0x5d6200, 0x2ea}, {0x579000, 0x2c4}, {0x520800, 0x2a0},
|
||||
{0x4cc800, 0x27f}, {0x47ca00, 0x261}, {0x430800, 0x245}, {0x3e8000, 0x22a},
|
||||
{0x3a2c00, 0x212}, {0x360800, 0x1fb}, {0x321400, 0x1e5}, {0x2e4a00, 0x1d1},
|
||||
{0x2aa800, 0x1be}, {0x272c00, 0x1ac}, {0x23d600, 0x19b}, {0x209e00, 0x18b},
|
||||
{0x1d8800, 0x17c}, {0x1a9000, 0x16e}, {0x17ae00, 0x15b}, {0x14f800, 0x15b},
|
||||
{0x124400, 0x143}, {0x0fbe00, 0x143}, {0x0d3800, 0x12d}, {0x0ade00, 0x12d},
|
||||
{0x088400, 0x11a}, {0x065000, 0x11a}, {0x041c00, 0x108}, {0x020c00, 0x106},
|
||||
};
|
||||
|
||||
constexpr u32 ppu_vrsqrtefp_segments[32][3] =
|
||||
{
|
||||
{0x34fd00, 0x568, 0x00}, {0x2f9700, 0x4f3, 0x26}, {0x2aa500, 0x48d, 0xc8}, {0x261800, 0x435, 0xc8},
|
||||
{0x21e400, 0x3e7, 0x26}, {0x1dfe00, 0x3a2, 0x88}, {0x1a5c00, 0x365, 0xc8}, {0x16f800, 0x32e, 0x22},
|
||||
{0x13ca00, 0x2fc, 0x00}, {0x10ce00, 0x2d0, 0x00}, {0x0dfe00, 0x2a8, 0x00}, {0x0b5700, 0x283, 0x26},
|
||||
{0x08d400, 0x261, 0xc8}, {0x067300, 0x243, 0x26}, {0x043100, 0x226, 0x22}, {0x020b00, 0x20b, 0x26},
|
||||
{0x7ff400, 0x7a4, 0x00}, {0x785200, 0x700, 0x00}, {0x715400, 0x670, 0x00}, {0x6ae400, 0x5f2, 0x88},
|
||||
{0x64f200, 0x584, 0x00}, {0x5f6e00, 0x524, 0x00}, {0x5a4c00, 0x4cc, 0x00}, {0x558000, 0x47e, 0x22},
|
||||
{0x510200, 0x43a, 0x88}, {0x4cca00, 0x3fa, 0x88}, {0x48d000, 0x3c2, 0x88}, {0x450e00, 0x38e, 0x22},
|
||||
{0x418200, 0x35e, 0x22}, {0x3e2400, 0x332, 0x88}, {0x3af200, 0x30a, 0x88}, {0x37e800, 0x2e6, 0x22},
|
||||
};
|
||||
|
||||
constexpr u16 ppu_vlogefp_segments[16][2] =
|
||||
{
|
||||
{0x0000, 12}, {0x0600, 10}, {0x0b00, 10}, {0x1000, 9},
|
||||
{0x1480, 9}, {0x1900, 9}, {0x1d80, 8}, {0x2180, 8},
|
||||
{0x2580, 8}, {0x297f, 7}, {0x2cff, 7}, {0x307f, 7},
|
||||
{0x33fe, 6}, {0x36fe, 6}, {0x39fe, 6}, {0x3cfe, 6},
|
||||
};
|
||||
|
||||
constexpr u16 ppu_vexptefp_segments[16][2] =
|
||||
{
|
||||
{0x0006, 6}, {0x0306, 6}, {0x0606, 6}, {0x0906, 6},
|
||||
{0x0c07, 7}, {0x0f87, 7}, {0x1307, 7}, {0x1680, 8},
|
||||
{0x1a80, 8}, {0x1e80, 8}, {0x2288, 9}, {0x2708, 9},
|
||||
{0x2b88, 9}, {0x3008, 10}, {0x3508, 10}, {0x3a08, 12},
|
||||
};
|
||||
|
||||
constexpr void ppu_vnormalize(s32& e, u32& m)
|
||||
{
|
||||
const u32 shift = std::countl_zero(m) - 8;
|
||||
m = (m << shift) & 0x7fffff;
|
||||
e = 1 - static_cast<s32>(shift);
|
||||
}
|
||||
|
||||
constexpr u32 ppu_vrefp(u32 x, bool nj)
|
||||
{
|
||||
const u32 s = x & 0x80000000;
|
||||
s32 e = (x >> 23) & 0xff;
|
||||
u32 m = x & 0x7fffff;
|
||||
|
||||
if (e == 0xff)
|
||||
return m ? x | 0x400000 : s;
|
||||
|
||||
if (e == 0 && (nj || m == 0))
|
||||
return s | 0x7f800000;
|
||||
|
||||
if (e == 0)
|
||||
ppu_vnormalize(e, m);
|
||||
|
||||
const auto& seg = ppu_vrefp_segments[m >> 18];
|
||||
const u32 mant = seg[0] - seg[1] * ((m >> 9) & 0x1ff);
|
||||
const s32 exp = 253 - e;
|
||||
|
||||
if (exp >= 0xff)
|
||||
return s | 0x7f800000;
|
||||
|
||||
if (exp > 0)
|
||||
return s | exp << 23 | mant;
|
||||
|
||||
if (nj)
|
||||
return s;
|
||||
|
||||
const u32 full = mant | 0x800000, shift = 1 - exp, half = 1u << (shift - 1);
|
||||
const u32 rest = full & ((half << 1) - 1);
|
||||
return s | ((full >> shift) + (rest > half || (rest == half && (full >> shift) & 1)));
|
||||
}
|
||||
|
||||
constexpr u32 ppu_vrsqrtefp(u32 x, bool nj)
|
||||
{
|
||||
const u32 s = x & 0x80000000;
|
||||
s32 e = (x >> 23) & 0xff;
|
||||
u32 m = x & 0x7fffff;
|
||||
|
||||
if (e == 0xff)
|
||||
return m ? x | 0x400000 : s ? 0x7fc00000 : 0;
|
||||
|
||||
if (e == 0 && (nj || m == 0))
|
||||
return s | 0x7f800000;
|
||||
|
||||
if (s)
|
||||
return 0x7fc00000;
|
||||
|
||||
if (e == 0)
|
||||
ppu_vnormalize(e, m);
|
||||
|
||||
const auto& seg = ppu_vrsqrtefp_segments[(e & 1) << 4 | m >> 19];
|
||||
const u32 key = (m >> 9) & 0x3ff;
|
||||
return (380 - e) >> 1 << 23 | (seg[0] - ((seg[1] * key) >> 2) - ((seg[2] >> (key & 7)) & 1));
|
||||
}
|
||||
|
||||
constexpr u32 ppu_vlogefp(u32 x, bool nj)
|
||||
{
|
||||
const u32 s = x & 0x80000000;
|
||||
s32 e = (x >> 23) & 0xff;
|
||||
u32 m = x & 0x7fffff;
|
||||
|
||||
if (e == 0xff)
|
||||
return m ? x | 0x400000 : s ? 0x7fc00000 : x;
|
||||
|
||||
if (e == 0 && (nj || m == 0))
|
||||
return 0xff800000;
|
||||
|
||||
if (s)
|
||||
return 0x7fc00000;
|
||||
|
||||
if (e == 0)
|
||||
ppu_vnormalize(e, m);
|
||||
|
||||
const u32 key = m >> 12;
|
||||
const auto& seg = ppu_vlogefp_segments[key >> 7];
|
||||
const s32 fixed = (e - 127) * 2048 + ((seg[0] + seg[1] * (key & 0x7f)) >> 3);
|
||||
return std::bit_cast<u32>(static_cast<f32>(fixed) / 2048);
|
||||
}
|
||||
|
||||
constexpr u32 ppu_vexptefp(u32 x, bool)
|
||||
{
|
||||
const u32 s = x & 0x80000000, e = (x >> 23) & 0xff, m = x & 0x7fffff;
|
||||
|
||||
if (e == 0xff)
|
||||
return m ? x | 0x400000 : s ? 0 : x;
|
||||
|
||||
if (e > 150)
|
||||
return s ? 0 : 0x7f800000;
|
||||
|
||||
const s64 mag = e >= 135 ? s64{m | 0x800000} << (e - 135) : e > 111 ? (m | 0x800000) >> (135 - e) : 0;
|
||||
const s64 fixed = (s ? -mag : mag) >> 4;
|
||||
const s64 exp = 127 + (fixed >> 11);
|
||||
|
||||
if (exp >= 0xff)
|
||||
return 0x7f800000;
|
||||
|
||||
if (exp <= 0)
|
||||
return 0;
|
||||
|
||||
const auto& seg = ppu_vexptefp_segments[(fixed >> 7) & 0xf];
|
||||
return static_cast<u32>(exp) << 23 | ((seg[0] + seg[1] * (fixed & 0x7f)) >> 3) << 12;
|
||||
}
|
||||
|
||||
@@ -274,7 +274,7 @@ error_code cellAudioInGetDeviceInfo(u32 deviceNumber, u32 deviceIndex, vm::ptr<C
|
||||
std::lock_guard lock(av_manager.mutex);
|
||||
|
||||
if (deviceNumber >= av_manager.devices.size())
|
||||
return CELL_AUDIO_OUT_ERROR_DEVICE_NOT_FOUND;
|
||||
return CELL_AUDIO_IN_ERROR_DEVICE_NOT_FOUND;
|
||||
|
||||
av_manager.copy_device_info(deviceNumber, info);
|
||||
|
||||
|
||||
@@ -1419,7 +1419,7 @@ error_code cellCameraReadEx(s32 dev_num, vm::ptr<CellCameraReadEx> read)
|
||||
u64 frame_number{};
|
||||
u64 bytes_read{};
|
||||
|
||||
if (!g_camera.get_camera_frame(g_camera.info.buffer.get_ptr(), width, height, frame_number, bytes_read))
|
||||
if (!g_camera.get_camera_frame(g_camera.info.buffer ? g_camera.info.buffer.get_ptr() : nullptr, width, height, frame_number, bytes_read))
|
||||
{
|
||||
return CELL_CAMERA_ERROR_DEVICE_NOT_FOUND;
|
||||
}
|
||||
@@ -1998,7 +1998,7 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state
|
||||
{
|
||||
if (!is_attached)
|
||||
{
|
||||
cellCamera.warning("Camera handler not attached. Sending attach event...", static_cast<int>(state));
|
||||
cellCamera.warning("Camera handler not attached (state=%d). Sending attach event...", static_cast<int>(state));
|
||||
is_attached = true;
|
||||
is_attached_dirty = true;
|
||||
}
|
||||
|
||||
@@ -188,6 +188,18 @@ struct content_permission final
|
||||
}
|
||||
};
|
||||
|
||||
// Content directory of the last cellGameDataCheckCreate2() call, measured by cellGameDataGetSizeKB()
|
||||
// A game may check several different directories: Rock Band 2 checks its own game data and then Rock Band's
|
||||
// in order to import its songs, so the size always refers to the dirName of the check which asked for it
|
||||
struct game_data_check final
|
||||
{
|
||||
shared_mutex mutex; // Checks may be issued from more than one thread of the game
|
||||
std::string dir; // "/dev_hdd0/game/" + dirName, empty until the first check
|
||||
|
||||
void set_dir(std::string_view path) { std::lock_guard lock(mutex); dir = path; }
|
||||
std::string get_dir() { reader_lock lock(mutex); return dir; }
|
||||
};
|
||||
|
||||
template<>
|
||||
void fmt_class_string<content_permission::check_mode>::format(std::string& out, u64 arg)
|
||||
{
|
||||
@@ -693,11 +705,14 @@ error_code cellGameDataGetSizeKB(ppu_thread& ppu, vm::ptr<u32> size)
|
||||
return CELL_GAMEDATA_ERROR_PARAM;
|
||||
}
|
||||
|
||||
const std::string content_dir = g_fxo->get<game_data_check>().get_dir();
|
||||
|
||||
lv2_obj::sleep(ppu);
|
||||
|
||||
const u64 start_sleep = ppu.start_time;
|
||||
|
||||
const std::string local_dir = vfs::get(Emu.GetDir());
|
||||
// Until the first check there is no dirName to go by: the booted directory is the game data itself for an HDD game
|
||||
const std::string local_dir = vfs::get(content_dir.empty() ? Emu.GetDir() : content_dir);
|
||||
|
||||
const auto dirsz = fs::get_dir_size(local_dir, 1024);
|
||||
|
||||
@@ -709,11 +724,15 @@ error_code cellGameDataGetSizeKB(ppu_thread& ppu, vm::ptr<u32> size)
|
||||
{
|
||||
const auto error = fs::g_tls_error;
|
||||
|
||||
if (fs::exists(local_dir))
|
||||
if (!fs::exists(local_dir))
|
||||
{
|
||||
cellGame.error("cellGameDataGetSizeKB(): Unknown failure on calculating directory '%s' size (%s)", local_dir, error);
|
||||
// The content directory is only created once the status callback has returned
|
||||
ppu.check_state();
|
||||
*size = 0;
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
cellGame.error("cellGameDataGetSizeKB(): Unknown failure on calculating directory '%s' size (%s)", local_dir, error);
|
||||
return CELL_GAMEDATA_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
@@ -1111,6 +1130,9 @@ error_code cellGameDataCheckCreate2(ppu_thread& ppu, u32 version, vm::cptr<char>
|
||||
strcpy_trunc(cbGet->getParam.titleLang[i], psf::get_string(sfo, fmt::format("TITLE_%02d", i)));
|
||||
}
|
||||
|
||||
// The status callback may ask for the size of this directory through cellGameDataGetSizeKB()
|
||||
g_fxo->get<game_data_check>().set_dir(dir);
|
||||
|
||||
lv2_sleep(5000, &ppu);
|
||||
|
||||
funcStat(ppu, cbResult, cbGet, cbSet);
|
||||
@@ -1327,7 +1349,9 @@ error_code cellGameDeleteGameData(vm::cptr<char> dirName)
|
||||
|
||||
auto remove_gd = [&]() -> error_code
|
||||
{
|
||||
if (Emu.GetCat() == "GD" && Emu.GetDir().substr(Emu.GetDir().find_last_of('/') + 1) == vfs::escape(name))
|
||||
const std::string_view boot_dir = fmt::trim_back_sv(Emu.GetDir(), fs::delim);
|
||||
|
||||
if (Emu.GetCat() == "GD" && boot_dir.substr(boot_dir.find_last_of(fs::delim) + 1) == vfs::escape(name))
|
||||
{
|
||||
// Boot patch cannot delete its own directory
|
||||
return CELL_GAME_ERROR_NOTSUPPORTED;
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/System.h"
|
||||
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
||||
#include "cellGame.h"
|
||||
|
||||
LOG_CHANNEL(cellGameExec);
|
||||
@@ -108,7 +110,8 @@ error_code cellGameGetBootGameInfo(vm::ptr<u32> type, vm::ptr<char> dirName, vm:
|
||||
|
||||
if (source_type == CELL_GAME_GAMETYPE_HDD)
|
||||
{
|
||||
const std::string dir_name = Emu.GetDir();
|
||||
const std::string_view boot_dir = fmt::trim_back_sv(Emu.GetDir(), fs::delim);
|
||||
const std::string dir_name{boot_dir.substr(boot_dir.find_last_of(fs::delim) + 1)};
|
||||
|
||||
if (dir_name.size() >= CELL_GAME_DIRNAME_SIZE)
|
||||
{
|
||||
|
||||
@@ -389,6 +389,8 @@ u32 g_defaultCommandBufferBegin, g_defaultCommandBufferFragmentCount;
|
||||
// Called by cellGcmInit
|
||||
error_code _cellGcmInitBody(ppu_thread& ppu, vm::pptr<CellGcmContextData> context, u32 cmdSize, u32 ioSize, u32 ioAddress)
|
||||
{
|
||||
ppu.state += cpu_flag::wait;
|
||||
|
||||
cellGcmSys.warning("_cellGcmInitBody(context=**0x%x, cmdSize=0x%x, ioSize=0x%x, ioAddress=0x%x)", context, cmdSize, ioSize, ioAddress);
|
||||
|
||||
auto& gcm_cfg = g_fxo->get<gcm_config>();
|
||||
|
||||
@@ -323,7 +323,7 @@ public:
|
||||
{
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(i));
|
||||
const bool connected = pad && pad->is_connected() && !pad->is_copilot() && i < attribute.max_connect;
|
||||
const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move;
|
||||
const bool is_real_move = g_cfg.io.move != move_handler::real || (pad && pad->m_pad_handler == pad_handler::move);
|
||||
|
||||
update_connection(i, connected && is_real_move);
|
||||
}
|
||||
@@ -1776,10 +1776,10 @@ static bool check_gem_num(u32 gem_num)
|
||||
return gem_num < CELL_GEM_MAX_NUM;
|
||||
}
|
||||
|
||||
static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_controller&, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
|
||||
static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_controller&, f32 x_pos, f32 y_pos)
|
||||
{
|
||||
const s16 x = static_cast<s16>(x_pos / (x_max / static_cast<f32>(rsx::overlays::overlay::virtual_width)));
|
||||
const s16 y = static_cast<s16>(y_pos / (y_max / static_cast<f32>(rsx::overlays::overlay::virtual_height)));
|
||||
const s16 x = static_cast<s16>(x_pos * rsx::overlays::overlay::virtual_width);
|
||||
const s16 y = static_cast<s16>(y_pos * rsx::overlays::overlay::virtual_height);
|
||||
|
||||
// Note: We shouldn't use sphere_rgb here. The game will set it to black in many cases.
|
||||
const gem_config_data::gem_color& rgb = gem_config_data::gem_color::get_default_color(gem_num);
|
||||
@@ -1788,30 +1788,24 @@ static inline void draw_overlay_cursor(u32 gem_num, const gem_config::gem_contro
|
||||
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + gem_num, x, y, color, 2'000'000, false);
|
||||
}
|
||||
|
||||
static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max)
|
||||
static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemImageState>& gem_image_state, f32 x_pos, f32 y_pos)
|
||||
{
|
||||
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
||||
|
||||
if (x_max <= 0) x_max = shared_data.width;
|
||||
if (y_max <= 0) y_max = shared_data.height;
|
||||
|
||||
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
|
||||
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
|
||||
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
|
||||
|
||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||
if (x_pos <= 0) x_pos -= 0.1f; else if (x_pos >= 1.0f) x_pos += 0.1f;
|
||||
if (y_pos <= 0) y_pos -= 0.1f; else if (y_pos >= 1.0f) y_pos += 0.1f;
|
||||
|
||||
// Image coordinates in pixels
|
||||
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
||||
const f32 image_y = static_cast<f32>(y_pos) / scaling_height;
|
||||
const f32 image_x = x_pos * shared_data.width;
|
||||
const f32 image_y = y_pos * shared_data.height;
|
||||
|
||||
// Centered image coordinates in pixels
|
||||
const f32 centered_x = image_x - (shared_data.width / 2.f);
|
||||
const f32 centered_y = (shared_data.height / 2.f) - image_y; // Image coordinates increase downwards, so we have to invert this
|
||||
|
||||
// Camera coordinates in mm (centered, so it's the same as world coordinates)
|
||||
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||
const f32 camera_x = centered_x * mmPerPixel;
|
||||
const f32 camera_y = centered_y * mmPerPixel;
|
||||
|
||||
@@ -1827,12 +1821,12 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
// Let's say the sphere is not visible if the position is at the edge of the screen
|
||||
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
|
||||
controller.radius_valid = x_pos > 0.0f && x_pos < 1.0f && y_pos > 0.0f && y_pos < 1.0f;
|
||||
}
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos);
|
||||
}
|
||||
|
||||
if (g_cfg.io.paint_move_spheres)
|
||||
@@ -1841,24 +1835,17 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
|
||||
}
|
||||
}
|
||||
|
||||
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data)
|
||||
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, f32 x_pos, f32 y_pos, ps_move_data& move_data)
|
||||
{
|
||||
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
||||
|
||||
if (x_max <= 0) x_max = shared_data.width;
|
||||
if (y_max <= 0) y_max = shared_data.height;
|
||||
|
||||
// Move the cursor out of the screen if we're at the screen border (Time Crisis 4 needs this)
|
||||
if (x_pos <= 0) x_pos -= x_max / 10; else if (x_pos >= x_max) x_pos += x_max / 10;
|
||||
if (y_pos <= 0) y_pos -= y_max / 10; else if (y_pos >= y_max) y_pos += y_max / 10;
|
||||
|
||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||
if (x_pos <= 0.0f) x_pos -= 0.1f; else if (x_pos >= 1.0f) x_pos += 0.1f;
|
||||
if (y_pos <= 0.0f) y_pos -= 0.1f; else if (y_pos >= 1.0f) y_pos += 0.1f;
|
||||
|
||||
// Image coordinates in pixels
|
||||
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
||||
const f32 image_y = static_cast<f32>(y_pos) / scaling_height;
|
||||
const f32 image_x = x_pos * shared_data.width;
|
||||
const f32 image_y = y_pos * shared_data.height;
|
||||
|
||||
// Half of the camera image
|
||||
const f32 half_width = shared_data.width / 2.f;
|
||||
@@ -1869,6 +1856,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
||||
const f32 centered_y = half_height - image_y; // Image coordinates increase downwards, so we have to invert this
|
||||
|
||||
// Camera coordinates in mm (centered, so it's the same as world coordinates)
|
||||
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||
const f32 camera_x = centered_x * mmPerPixel;
|
||||
const f32 camera_y = centered_y * mmPerPixel;
|
||||
|
||||
@@ -1907,7 +1895,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
||||
gem_state->quat[3] = quat.w();
|
||||
|
||||
// Calculate handle position based on our world coordinate and the current orientation
|
||||
constexpr ps_move_data::vect<3> offset_local_mm({0.f, 0.f, -45.f}); // handle is ~45 mm below sphere
|
||||
constexpr ps_move_data::vect<3> offset_local_mm({0.f, 0.f, 45.f}); // handle is ~45 mm below sphere
|
||||
const ps_move_data::vect<3> offset_world = ps_move_data::rotate_vector(quat, offset_local_mm);
|
||||
|
||||
gem_state->handle_pos[0] = gem_state->pos[0] - offset_world.x(); // Flip x offset
|
||||
@@ -1935,12 +1923,12 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
// Let's say the sphere is not visible if the position is at the edge of the screen
|
||||
controller.radius_valid = x_pos > 0 && x_pos < x_max && y_pos > 0 && y_pos < y_max;
|
||||
controller.radius_valid = x_pos > 0.0f && x_pos < 1.0f && y_pos > 0.0f && y_pos < 1.0f;
|
||||
}
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max);
|
||||
draw_overlay_cursor(gem_num, controller, x_pos, y_pos);
|
||||
}
|
||||
|
||||
if (g_cfg.io.paint_move_spheres)
|
||||
@@ -2026,13 +2014,10 @@ static void ds3_input_to_pad(const u32 gem_num, be_t<u16>& digital_buttons, be_t
|
||||
}
|
||||
}
|
||||
|
||||
constexpr u16 ds3_max_x = 255;
|
||||
constexpr u16 ds3_max_y = 255;
|
||||
|
||||
static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>& pad, s32& x_pos, s32& y_pos)
|
||||
static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>& pad, f32& x_pos, f32& y_pos)
|
||||
{
|
||||
x_pos = 0;
|
||||
y_pos = 0;
|
||||
x_pos = 0.0f;
|
||||
y_pos = 0.0f;
|
||||
|
||||
const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num);
|
||||
cfg->handle_input(pad, true, [&](const auto& value, bool& /*abort*/)
|
||||
@@ -2042,8 +2027,8 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr<Pad>&
|
||||
|
||||
switch (value.btn)
|
||||
{
|
||||
case gem_btn::x_axis: x_pos = value.value; break;
|
||||
case gem_btn::y_axis: y_pos = value.value; break;
|
||||
case gem_btn::x_axis: x_pos = value.value / 255.0f; break;
|
||||
case gem_btn::y_axis: y_pos = value.value / 255.0f; break;
|
||||
default: break;
|
||||
}
|
||||
});
|
||||
@@ -2067,16 +2052,16 @@ static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& contro
|
||||
return;
|
||||
}
|
||||
|
||||
s32 ds3_pos_x, ds3_pos_y;
|
||||
f32 ds3_pos_x, ds3_pos_y;
|
||||
ds3_get_stick_values(gem_num, pad, ds3_pos_x, ds3_pos_y);
|
||||
|
||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||
{
|
||||
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, pad->move_data);
|
||||
pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, pad->move_data);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
pos_to_gem_image_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y);
|
||||
pos_to_gem_image_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2116,11 +2101,11 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co
|
||||
gem_state->angaccel[i] = pad->move_data.angaccel_world[i];
|
||||
}
|
||||
|
||||
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data);
|
||||
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, pad->move_data);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
pos_to_gem_image_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max);
|
||||
pos_to_gem_image_state(gem_num, controller, gem_state, info.x_pos, info.y_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2166,12 +2151,12 @@ static const std::unordered_map<gem_btn, u16> ext_btn_indices =
|
||||
{ gem_btn::racing_wheel_l2, 1 },
|
||||
{ gem_btn::racing_wheel_r2, 2 },
|
||||
{ gem_btn::racing_wheel_paddle_l, 3 },
|
||||
{ gem_btn::racing_wheel_paddle_r, 4 },
|
||||
{ gem_btn::racing_wheel_paddle_r, 3 },
|
||||
{ gem_btn::combo_racing_wheel_throttle, 0 },
|
||||
{ gem_btn::combo_racing_wheel_l2, 1 },
|
||||
{ gem_btn::combo_racing_wheel_r2, 2 },
|
||||
{ gem_btn::combo_racing_wheel_paddle_l, 3 },
|
||||
{ gem_btn::combo_racing_wheel_paddle_r, 4 },
|
||||
{ gem_btn::combo_racing_wheel_paddle_r, 3 },
|
||||
};
|
||||
|
||||
static u32 ext_device_id(gem_ext_id id)
|
||||
@@ -2282,23 +2267,23 @@ static void input_to_ext(u32 external_device_id, CellGemExtPortData& ext, std::s
|
||||
{
|
||||
switch (value.btn)
|
||||
{
|
||||
case gem_btn::combo_racing_wheel_throttle:
|
||||
case gem_btn::combo_racing_wheel_l2:
|
||||
case gem_btn::combo_racing_wheel_r2:
|
||||
case gem_btn::racing_wheel_throttle:
|
||||
case gem_btn::racing_wheel_l2:
|
||||
case gem_btn::racing_wheel_r2:
|
||||
ext.custom[::at32(ext_btn_indices, value.btn)] = static_cast<u8>(value.value);
|
||||
break;
|
||||
case gem_btn::combo_racing_wheel_paddle_l:
|
||||
case gem_btn::combo_racing_wheel_paddle_r:
|
||||
case gem_btn::racing_wheel_paddle_l:
|
||||
case gem_btn::racing_wheel_paddle_r:
|
||||
ext.custom[::at32(ext_btn_indices, value.btn)] |= ::at32(ext_btn_map, value.btn);
|
||||
break;
|
||||
case gem_btn::combo_racing_wheel_d_pad_up:
|
||||
case gem_btn::combo_racing_wheel_d_pad_right:
|
||||
case gem_btn::combo_racing_wheel_d_pad_down:
|
||||
case gem_btn::combo_racing_wheel_d_pad_left:
|
||||
case gem_btn::racing_wheel_d_pad_up:
|
||||
case gem_btn::racing_wheel_d_pad_right:
|
||||
case gem_btn::racing_wheel_d_pad_down:
|
||||
case gem_btn::racing_wheel_d_pad_left:
|
||||
ext.digital1 |= ::at32(ext_btn_map, value.btn);
|
||||
break;
|
||||
case gem_btn::combo_racing_wheel_l1:
|
||||
case gem_btn::combo_racing_wheel_r1:
|
||||
case gem_btn::racing_wheel_l1:
|
||||
case gem_btn::racing_wheel_r1:
|
||||
ext.digital2 |= ::at32(ext_btn_map, value.btn);
|
||||
break;
|
||||
default:
|
||||
@@ -2652,11 +2637,11 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con
|
||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||
{
|
||||
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, mouse_no);
|
||||
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data);
|
||||
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, move_data);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
pos_to_gem_image_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max);
|
||||
pos_to_gem_image_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2707,25 +2692,23 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control
|
||||
if (!gem_state || !is_input_allowed())
|
||||
return;
|
||||
|
||||
int x_pos, y_pos, x_max, y_max;
|
||||
f32 x_pos, y_pos;
|
||||
{
|
||||
gun_thread& gun = g_fxo->get<gun_thread>();
|
||||
std::scoped_lock lock(gun.handler.mutex);
|
||||
|
||||
x_pos = gun.handler.get_axis_x(gem_no);
|
||||
y_pos = gun.handler.get_axis_y(gem_no);
|
||||
x_max = gun.handler.get_axis_x_max(gem_no);
|
||||
y_max = gun.handler.get_axis_y_max(gem_no);
|
||||
}
|
||||
|
||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||
{
|
||||
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, gem_no);
|
||||
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data);
|
||||
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, move_data);
|
||||
}
|
||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||
{
|
||||
pos_to_gem_image_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max);
|
||||
pos_to_gem_image_state(gem_no, controller, gem_state, x_pos, y_pos);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -3085,7 +3068,7 @@ error_code cellGemGetAccelerometerPositionInDevice(u32 gem_num, vm::ptr<f32> pos
|
||||
|
||||
error_code cellGemGetAllTrackableHues(vm::ptr<u8> hues)
|
||||
{
|
||||
cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)");
|
||||
cellGem.todo("cellGemGetAllTrackableHues(hues=*0x%x)", hues);
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
|
||||
@@ -3593,12 +3576,19 @@ error_code cellGemHSVtoRGB(f32 h, f32 s, f32 v, vm::ptr<f32> r, vm::ptr<f32> g,
|
||||
{
|
||||
cellGem.warning("cellGemHSVtoRGB(h=%f, s=%f, v=%f, r=*0x%x, g=*0x%x, b=*0x%x)", h, s, v, r, g, b);
|
||||
|
||||
if (s < 0.0f || s > 1.0f || v < 0.0f || v > 1.0f || !r || !g || !b)
|
||||
if (!r || !g || !b)
|
||||
{
|
||||
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
h = std::clamp(h, 0.0f, 360.0f);
|
||||
h = std::fmod(h, 360.0f);
|
||||
s = std::clamp(s, 0.0f, 1.0f);
|
||||
v = std::clamp(v, 0.0f, 1.0f);
|
||||
|
||||
if (h < 0.0)
|
||||
{
|
||||
h += 360.0f;
|
||||
}
|
||||
|
||||
const f32 c = v * s;
|
||||
const f32 x = c * (1.0f - fabs(fmod(h / 60.0f, 2.0f) - 1.0f));
|
||||
@@ -3639,9 +3629,9 @@ error_code cellGemHSVtoRGB(f32 h, f32 s, f32 v, vm::ptr<f32> r, vm::ptr<f32> g,
|
||||
b_tmp = x;
|
||||
}
|
||||
|
||||
*r = (r_tmp + m) * 255.0f;
|
||||
*g = (g_tmp + m) * 255.0f;
|
||||
*b = (b_tmp + m) * 255.0f;
|
||||
*r = std::clamp(r_tmp + m, 0.0f, 1.0f);
|
||||
*g = std::clamp(g_tmp + m, 0.0f, 1.0f);
|
||||
*b = std::clamp(b_tmp + m, 0.0f, 1.0f);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
@@ -4003,9 +3993,15 @@ error_code cellGemSetRumble(u32 gem_num, u8 rumble)
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
error_code cellGemSetYaw(u32 gem_num, vm::ptr<f32> z_direction)
|
||||
error_code cellGemSetYaw(u32 gem_num, v128 z_direction)
|
||||
{
|
||||
cellGem.todo("cellGemSetYaw(gem_num=%d, z_direction=*0x%x)", gem_num, z_direction);
|
||||
// Unpack vector argument
|
||||
const f32 z_direction_x = z_direction.fr[0];
|
||||
const f32 z_direction_y = z_direction.fr[1];
|
||||
const f32 z_direction_z = z_direction.fr[2];
|
||||
const f32 z_direction_w = z_direction.fr[3];
|
||||
|
||||
cellGem.warning("cellGemSetYaw(gem_num=%d, z_direction_x=%f, z_direction_y=%f, z_direction_z=%f, z_direction_w=%f)", gem_num, z_direction_x, z_direction_y, z_direction_z, z_direction_w);
|
||||
|
||||
auto& gem = g_fxo->get<gem_config>();
|
||||
|
||||
@@ -4016,12 +4012,31 @@ error_code cellGemSetYaw(u32 gem_num, vm::ptr<f32> z_direction)
|
||||
return CELL_GEM_ERROR_UNINITIALIZED;
|
||||
}
|
||||
|
||||
if (!z_direction || !check_gem_num(gem_num))
|
||||
if (!check_gem_num(gem_num))
|
||||
{
|
||||
return CELL_GEM_ERROR_INVALID_PARAMETER;
|
||||
}
|
||||
|
||||
// TODO
|
||||
if (g_cfg.io.move != move_handler::real)
|
||||
{
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
std::lock_guard pad_lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_pad_thread();
|
||||
const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num));
|
||||
|
||||
if (pad && pad->m_pad_handler == pad_handler::move && !pad->is_copilot())
|
||||
{
|
||||
// z_direction is the direction of the controller's z axis (sphere -> handle) in world coordinates.
|
||||
// This function is usually used when the game wants to re-orient the ps move towards the camera, e.g. during calibration or as a surrogate for it.
|
||||
// So far we've seen:
|
||||
// A: (0,0,1,0): The controller points straight at the camera along the camera axis. This is our default orientation.
|
||||
// B: The current sphere position (CellGemState.pos) while pointing at the camera.
|
||||
// This is equal to A if the controller is on the camera axis. Otherwise the correct yaw would be atan2(x, z).
|
||||
// For now we simply reset the orientation (including pitch and roll) instead of only adjusting the yaw.
|
||||
pad->move_data.orientation_reset_requested = true;
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
@@ -3702,7 +3702,7 @@ error_code cellHttpEndExternalCache()
|
||||
|
||||
error_code cellHttpInitExternalCache(vm::ptr<void> buf, u32 size)
|
||||
{
|
||||
cellHttp.todo("cellHttpInitExternalCache(buf=*0x%x, size=0x%x)", size);
|
||||
cellHttp.todo("cellHttpInitExternalCache(buf=*0x%x, size=0x%x)", buf, size);
|
||||
|
||||
auto& man = g_fxo->get<http_manager>();
|
||||
std::lock_guard lock(man.mtx);
|
||||
|
||||
@@ -188,76 +188,70 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
|
||||
|
||||
auto& dec = g_fxo->get<Music_Decode>();
|
||||
std::lock_guard lock(dec.mutex);
|
||||
std::scoped_lock slock(dec.decoder.m_mtx);
|
||||
|
||||
if (dec.decoder.has_error)
|
||||
if (dec.decoder.has_error())
|
||||
{
|
||||
return CELL_MUSIC_DECODE_ERROR_DECODE_FAILURE;
|
||||
}
|
||||
|
||||
if (dec.decoder.m_size == 0)
|
||||
{
|
||||
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "m_size == 0" };
|
||||
}
|
||||
std::scoped_lock data_lock(dec.decoder.data_mutex());
|
||||
const u64 size = dec.decoder.size();
|
||||
|
||||
ensure(dec.decoder.m_size >= dec.read_pos);
|
||||
const u64 size_left = dec.decoder.m_size - dec.read_pos;
|
||||
|
||||
if (dec.read_pos == 0)
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
|
||||
*position = CELL_MUSIC_DECODE_POSITION_START;
|
||||
}
|
||||
else if (!dec.decoder.track_fully_decoded || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine.
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
|
||||
*position = CELL_MUSIC_DECODE_POSITION_MID;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dec.decoder.set_next_index(true) == umax)
|
||||
if (size == 0)
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
|
||||
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
|
||||
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "size == 0" };
|
||||
}
|
||||
|
||||
ensure(size >= dec.read_pos);
|
||||
const u64 size_left = size - dec.read_pos;
|
||||
|
||||
if (dec.read_pos == 0)
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_START, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
|
||||
*position = CELL_MUSIC_DECODE_POSITION_START;
|
||||
}
|
||||
else if (!dec.decoder.track_fully_decoded() || size_left > reqSize) // track_fully_decoded is not guarded by a mutex, but since it is set to true after the decode, it should be fine.
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_MID, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
|
||||
*position = CELL_MUSIC_DECODE_POSITION_MID;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, m_size=%d", dec.read_pos, reqSize, dec.decoder.m_size.load());
|
||||
*position = CELL_MUSIC_DECODE_POSITION_END;
|
||||
if (dec.decoder.set_next_index(true) == umax)
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END_LIST_END, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
|
||||
*position = CELL_MUSIC_DECODE_POSITION_END_LIST_END;
|
||||
}
|
||||
else
|
||||
{
|
||||
cellMusicDecode.trace("cell_music_decode_read: position=CELL_MUSIC_DECODE_POSITION_END, read_pos=%d, reqSize=%d, size=%d", dec.read_pos, reqSize, size);
|
||||
*position = CELL_MUSIC_DECODE_POSITION_END;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const u64 size_to_read = std::min(reqSize, size_left);
|
||||
*readSize = size_to_read;
|
||||
const u64 read_size = dec.decoder.read(buf.get_ptr(), dec.read_pos, reqSize);
|
||||
*readSize = read_size;
|
||||
|
||||
if (size_to_read == 0)
|
||||
{
|
||||
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "size_to_read == 0" }; // TODO: speculative
|
||||
}
|
||||
|
||||
std::memcpy(buf.get_ptr(), &::at32(dec.decoder.data, dec.read_pos), size_to_read);
|
||||
|
||||
if (size_to_read < reqSize)
|
||||
{
|
||||
// Set the rest of the buffer to zero to prevent loud pops at the end of the stream if the game ignores the readSize.
|
||||
std::memset(vm::static_ptr_cast<u8>(buf).get_ptr() + size_to_read, 0, reqSize - size_to_read);
|
||||
}
|
||||
|
||||
dec.read_pos += size_to_read;
|
||||
|
||||
s64 start_time_ms = 0;
|
||||
|
||||
if (!dec.decoder.timestamps_ms.empty())
|
||||
{
|
||||
start_time_ms = dec.decoder.timestamps_ms.front().second;
|
||||
|
||||
while (dec.decoder.timestamps_ms.size() > 1 && dec.read_pos >= ::at32(dec.decoder.timestamps_ms, 1).first)
|
||||
if (read_size == 0)
|
||||
{
|
||||
dec.decoder.timestamps_ms.pop_front();
|
||||
return { CELL_MUSIC_DECODE_ERROR_NO_LPCM_DATA, "read_size == 0" }; // TODO: speculative
|
||||
}
|
||||
}
|
||||
|
||||
*startTime = static_cast<u32>(start_time_ms); // startTime is milliseconds
|
||||
if (read_size < reqSize)
|
||||
{
|
||||
// Set the rest of the buffer to zero to prevent loud pops at the end of the stream if the game ignores the readSize.
|
||||
std::memset(vm::static_ptr_cast<u8>(buf).get_ptr() + read_size, 0, reqSize - read_size);
|
||||
}
|
||||
|
||||
dec.read_pos += read_size;
|
||||
|
||||
const s64 start_time_ms = dec.decoder.get_start_time_ms(dec.read_pos);
|
||||
|
||||
*startTime = static_cast<u32>(start_time_ms); // startTime is milliseconds
|
||||
|
||||
cellMusicDecode.trace("cell_music_decode_read(read_size=%d, samples=%d, start_time_ms=%d)", read_size, read_size / sizeof(u64), start_time_ms);
|
||||
}
|
||||
|
||||
switch (*position)
|
||||
{
|
||||
@@ -272,8 +266,7 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
|
||||
{
|
||||
dec.read_pos = 0;
|
||||
dec.decoder.clear();
|
||||
dec.decoder.track_fully_consumed = 1;
|
||||
dec.decoder.track_fully_consumed.notify_one();
|
||||
dec.decoder.wake_up();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -282,8 +275,6 @@ error_code cell_music_decode_read(vm::ptr<void> buf, vm::ptr<u32> startTime, u64
|
||||
}
|
||||
}
|
||||
|
||||
cellMusicDecode.trace("cell_music_decode_read(size_to_read=%d, samples=%d, start_time_ms=%d)", size_to_read, size_to_read / sizeof(u64), start_time_ms);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
@@ -375,11 +366,7 @@ error_code cellMusicDecodeSetDecodeCommand(s32 command)
|
||||
if (!dec.func)
|
||||
return CELL_MUSIC_DECODE_ERROR_GENERIC;
|
||||
|
||||
error_code result = CELL_OK;
|
||||
{
|
||||
std::scoped_lock slock(dec.decoder.m_mtx);
|
||||
result = dec.set_decode_command(command);
|
||||
}
|
||||
const error_code result = dec.set_decode_command(command);
|
||||
|
||||
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
|
||||
{
|
||||
@@ -566,11 +553,7 @@ error_code cellMusicDecodeSetDecodeCommand2(s32 command)
|
||||
if (!dec.func)
|
||||
return CELL_MUSIC_DECODE_ERROR_GENERIC;
|
||||
|
||||
error_code result = CELL_OK;
|
||||
{
|
||||
std::scoped_lock slock(dec.decoder.m_mtx);
|
||||
result = dec.set_decode_command(command);
|
||||
}
|
||||
const error_code result = dec.set_decode_command(command);
|
||||
|
||||
sysutil_register_cb([&dec, result](ppu_thread& ppu) -> s32
|
||||
{
|
||||
|
||||
@@ -2189,7 +2189,14 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
|
||||
|
||||
// Remove old backup
|
||||
fs::remove_all(old_path);
|
||||
fs::sync();
|
||||
fs::sync(new_path);
|
||||
|
||||
#ifndef _WIN32
|
||||
if (fs::g_tls_error != fs::error::ok)
|
||||
{
|
||||
cellSaveData.warning("savedata_op(): Failed to sync filesystem of '%s' (%s)", new_path, fs::g_tls_error);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Backup old savedata
|
||||
if (!vfs::host::rename(dir_path, old_path, &g_mp_sys_dev_hdd0, false))
|
||||
|
||||
@@ -1313,7 +1313,7 @@ error_code cellSearchStartSceneSearch(CellSearchSceneSearchType searchType, vm::
|
||||
|
||||
for (u32 n = 0; n < tagNum; n++)
|
||||
{
|
||||
if (!tags[tagNum] || !memchr(&tags[tagNum], '\0', CELL_SEARCH_TAG_LEN_MAX))
|
||||
if (!tags[n] || !memchr(tags[n].get_ptr(), '\0', CELL_SEARCH_TAG_LEN_MAX))
|
||||
{
|
||||
return CELL_SEARCH_ERROR_TAG;
|
||||
}
|
||||
@@ -1403,7 +1403,7 @@ error_code cellSearchGetContentInfoByOffset(CellSearchId searchId, s32 offset, v
|
||||
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.photo, sizeof(content_info->data.photo));
|
||||
break;
|
||||
case CELL_SEARCH_CONTENTTYPE_VIDEO:
|
||||
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.video, sizeof(content_info->data.photo));
|
||||
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.video, sizeof(content_info->data.video));
|
||||
break;
|
||||
case CELL_SEARCH_CONTENTTYPE_MUSICLIST:
|
||||
if (infoBuffer) std::memcpy(infoBuffer.get_ptr(), &content_info->data.music_list, sizeof(content_info->data.music_list));
|
||||
@@ -1690,19 +1690,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
|
||||
const auto& first_content = first_content_id.second;
|
||||
ensure(first_content);
|
||||
|
||||
const auto get_random_content = [&searchObject, &first_content]() -> shared_ptr<search_content_t>
|
||||
{
|
||||
if (searchObject->content_ids.size() == 1)
|
||||
{
|
||||
return first_content;
|
||||
}
|
||||
|
||||
std::vector<content_id_type> result;
|
||||
std::sample(searchObject->content_ids.begin(), searchObject->content_ids.end(), std::back_inserter(result), 1, std::mt19937{std::random_device{}()});
|
||||
ensure(result.size() == 1);
|
||||
shared_ptr<search_content_t> content = ensure(result[0].second);
|
||||
return content;
|
||||
};
|
||||
// Index of the track to start with, if the search contains single tracks
|
||||
u32 first_track = umax;
|
||||
|
||||
if (contentId)
|
||||
{
|
||||
@@ -1741,8 +1730,8 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
|
||||
}
|
||||
else if (content->second->type == CELL_SEARCH_CONTENTTYPE_MUSIC)
|
||||
{
|
||||
context.playlist.push_back(content->second->infoPath.contentPath);
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning found track: Type=0x%x, Path='%s'", content_hash, +content->second->type, context.playlist.back());
|
||||
first_track = ::narrow<u32>(std::distance(searchObject->content_ids.begin(), content));
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Found track %d: Type=0x%x, Path='%s'", content_hash, first_track, +content->second->type, content->second->infoPath.contentPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1754,47 +1743,40 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
|
||||
// Abort if we can't find the playlist.
|
||||
return { CELL_SEARCH_ERROR_CONTENT_NOT_FOUND, "Type: CELL_SEARCH_CONTENTTYPE_MUSICLIST" };
|
||||
}
|
||||
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
|
||||
{
|
||||
// Select random track
|
||||
// TODO: whole playlist
|
||||
shared_ptr<search_content_t> content = get_random_content();
|
||||
context.playlist.push_back(content->infoPath.contentPath);
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning random track: Type=0x%x, Path='%s'", content_hash, +content->type, context.playlist.back());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select the first track by default
|
||||
// TODO: whole playlist
|
||||
context.playlist.push_back(first_content->infoPath.contentPath);
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Hash=%08X, Assigning first track: Type=0x%x, Path='%s'", content_hash, +first_content->type, context.playlist.back());
|
||||
}
|
||||
}
|
||||
else if (first_content->type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
|
||||
{
|
||||
// Abort if we don't have the necessary info to select a playlist.
|
||||
return { CELL_SEARCH_ERROR_NOT_SUPPORTED_CONTEXT, "Type: CELL_SEARCH_CONTENTTYPE_MUSICLIST" };
|
||||
}
|
||||
else if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
|
||||
|
||||
if (first_content->type != CELL_SEARCH_CONTENTTYPE_MUSICLIST)
|
||||
{
|
||||
// Select random track
|
||||
// TODO: whole playlist
|
||||
shared_ptr<search_content_t> content = get_random_content();
|
||||
context.playlist.push_back(content->infoPath.contentPath);
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning random track: Type=0x%x, Path='%s'", +content->type, context.playlist.back());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Select the first track by default
|
||||
// TODO: whole playlist
|
||||
context.playlist.push_back(first_content->infoPath.contentPath);
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning first track: Type=0x%x, Path='%s'", +first_content->type, context.playlist.back());
|
||||
if (first_track == umax)
|
||||
{
|
||||
// Start with a random track if shuffled, otherwise with the first track
|
||||
first_track = 0;
|
||||
|
||||
if (option == CELL_SEARCH_CONTEXTOPTION_SHUFFLE)
|
||||
{
|
||||
std::mt19937 engine{std::random_device{}()};
|
||||
first_track = ::narrow<u32>(std::uniform_int_distribution<usz>(0, searchObject->content_ids.size() - 1)(engine));
|
||||
}
|
||||
}
|
||||
|
||||
// The playlist contains all the tracks of the search
|
||||
for (const content_id_type& cid : searchObject->content_ids)
|
||||
{
|
||||
context.playlist.push_back(ensure(cid.second)->infoPath.contentPath);
|
||||
}
|
||||
|
||||
context.first_track = context.current_track = first_track;
|
||||
cellSearch.notice("cellSearchGetMusicSelectionContext(): Assigning %d tracks, starting with track %d: Path='%s'", context.playlist.size(), first_track, ::at32(context.playlist, first_track));
|
||||
}
|
||||
|
||||
context.content_type = first_content->type;
|
||||
context.repeat_mode = repeatMode;
|
||||
context.context_option = option;
|
||||
// TODO: context.first_track = ?;
|
||||
|
||||
// Resolve hashed paths
|
||||
for (std::string& track : context.playlist)
|
||||
@@ -1808,7 +1790,7 @@ error_code cellSearchGetMusicSelectionContext(CellSearchId searchId, vm::cptr<Ce
|
||||
context.create_playlist(music_selection_context::get_next_hash());
|
||||
*outContext = context.get();
|
||||
|
||||
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %d", context.to_string());
|
||||
cellSearch.success("cellSearchGetMusicSelectionContext: found selection context: %s", context.to_string());
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
@@ -2140,27 +2122,40 @@ error_code music_selection_context::find_content_id(vm::ptr<CellSearchContentId>
|
||||
if (!contents_id)
|
||||
return CELL_MUSIC_ERROR_PARAM;
|
||||
|
||||
// Search for the content that matches our current selection
|
||||
// Search for the content that matches our current selection, starting with the selected track
|
||||
auto& content_map = g_fxo->get<content_id_map>();
|
||||
shared_ptr<search_content_t> found_content;
|
||||
u64 hash = 0;
|
||||
|
||||
for (const std::string& track : playlist)
|
||||
const usz start = first_track < playlist.size() ? first_track : 0;
|
||||
u64 last_hash = 0;
|
||||
|
||||
for (usz i = 0; i < playlist.size(); i++)
|
||||
{
|
||||
if (content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST)
|
||||
const std::string_view track = ::at32(playlist, (start + i) % playlist.size());
|
||||
|
||||
// The tracks of a music list share the hash of their directory
|
||||
const u64 track_hash = std::hash<std::string_view>()(content_type == CELL_SEARCH_CONTENTTYPE_MUSICLIST ? fs::get_parent_dir_view(track) : track);
|
||||
|
||||
if (i > 0 && track_hash == last_hash)
|
||||
{
|
||||
hash = std::hash<std::string>()(fs::get_parent_dir(track));
|
||||
}
|
||||
else
|
||||
{
|
||||
hash = std::hash<std::string>()(track);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (auto found = content_map.map.find(hash); found != content_map.map.end())
|
||||
last_hash = track_hash;
|
||||
|
||||
if (auto found = content_map.map.find(track_hash); found != content_map.map.end())
|
||||
{
|
||||
hash = track_hash;
|
||||
found_content = found->second;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
// Look for the selected track below if no content is found
|
||||
hash = track_hash;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_content)
|
||||
|
||||
@@ -53,6 +53,31 @@ constexpr int averror_eof = AVERROR_EOF; // workaround for old-style-cast error
|
||||
|
||||
LOG_CHANNEL(cellVdec);
|
||||
|
||||
// Checks whether the AU holds a header that tells the decoder the picture size:
|
||||
// MPEG-2: sequence header; MPEG-4 Part 2: Video Object Layer header, or a short video header (H.263 picture start code) at the start of the AU
|
||||
static bool au_has_sequence_header(AVCodecID codec_id, const u8* data, u32 size)
|
||||
{
|
||||
if (codec_id == AV_CODEC_ID_MPEG4 && size >= 3 && data[0] == 0 && data[1] == 0 && (data[2] & 0xfc) == 0x80)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i + 3 < size; i++)
|
||||
{
|
||||
if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
|
||||
{
|
||||
const u8 code = data[i + 3];
|
||||
|
||||
if (codec_id == AV_CODEC_ID_MPEG2VIDEO ? code == 0xb3 : (code & 0xf0) == 0x20)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<CellVdecError>::format(std::string& out, u64 arg)
|
||||
{
|
||||
@@ -137,6 +162,10 @@ struct vdec_cmd
|
||||
: type(_type), seq_id(_seq_id), id(_id), mode(_mode), au(std::move(_au))
|
||||
{
|
||||
ensure(_type == vdec_cmd_type::au_decode);
|
||||
|
||||
// Copy the AU now: the game may refill its buffer before the decoder thread gets to this AU
|
||||
const u8* data = vm::_ptr<const u8>(au.startAddr);
|
||||
au_data.assign(data, data + au.size);
|
||||
}
|
||||
|
||||
explicit vdec_cmd(vdec_cmd_type _type, u64 _seq_id, u64 _id, s32 _framerate)
|
||||
@@ -151,6 +180,7 @@ struct vdec_cmd
|
||||
s32 mode{};
|
||||
s32 framerate{};
|
||||
CellVdecAuInfo au{};
|
||||
std::vector<u8> au_data;
|
||||
};
|
||||
|
||||
struct vdec_frame
|
||||
@@ -224,6 +254,8 @@ struct vdec_context final
|
||||
|
||||
AVRational log_time_base{}; // Used to reduce log spam
|
||||
AVRational log_framerate{}; // Used to reduce log spam
|
||||
u32 skipped_au_count{}; // Used to reduce log spam
|
||||
bool sequence_header_found = false;
|
||||
|
||||
vdec_context(s32 type, u32 /*profile*/, u32 addr, u32 size, vm::ptr<CellVdecCbMsg> func, u32 arg)
|
||||
: type(type)
|
||||
@@ -268,6 +300,9 @@ struct vdec_context final
|
||||
fmt::throw_exception("avcodec_descriptor_get() failed (type=0x%x)", type);
|
||||
}
|
||||
|
||||
// Only these decoders are checked for a sequence header before an AU is passed to them
|
||||
sequence_header_found = codec->id != AV_CODEC_ID_MPEG2VIDEO && codec->id != AV_CODEC_ID_MPEG4;
|
||||
|
||||
ctx = avcodec_alloc_context3(codec);
|
||||
|
||||
if (!ctx)
|
||||
@@ -413,13 +448,12 @@ struct vdec_context final
|
||||
const bool drain = cmd->type == vdec_cmd_type::drain;
|
||||
|
||||
const u32 au_mode = cmd->mode;
|
||||
const u32 au_addr = cmd->au.startAddr;
|
||||
const u32 au_size = cmd->au.size;
|
||||
const u64 au_pts = u64{cmd->au.pts.upper} << 32 | cmd->au.pts.lower;
|
||||
const u64 au_dts = u64{cmd->au.dts.upper} << 32 | cmd->au.dts.lower;
|
||||
au_usrd = cmd->au.userData;
|
||||
|
||||
packet.data = vm::_ptr<u8>(au_addr);
|
||||
packet.data = cmd->au_data.data();
|
||||
packet.size = au_size;
|
||||
packet.pts = au_pts != umax ? au_pts : s64{smin};
|
||||
packet.dts = au_dts != umax ? au_dts : s64{smin};
|
||||
@@ -447,10 +481,23 @@ struct vdec_context final
|
||||
{
|
||||
cellVdec.trace("AU decoding: handle=0x%x, seq_id=%d, cmd_id=%d, size=0x%x, pts=0x%llx, dts=0x%llx, userdata=0x%llx", handle, cmd->seq_id, cmd->id, au_size, au_pts, au_dts, au_usrd);
|
||||
|
||||
if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
|
||||
if (!drain && !sequence_header_found && !au_has_sequence_header(codec->id, packet.data, au_size))
|
||||
{
|
||||
// The decoder cannot use an AU before a sequence header (e.g. a stream continued on a new decoder,
|
||||
// or one that sends its sequence header after its first pictures): skip it
|
||||
if (skipped_au_count++ == 0)
|
||||
{
|
||||
cellVdec.warning("No sequence header yet, skipping AUs until one arrives (handle=0x%x, seq_id=%d, cmd_id=%d)", handle, cmd->seq_id, cmd->id);
|
||||
}
|
||||
}
|
||||
else if (int ret = avcodec_send_packet(ctx, drain ? nullptr : &packet); ret < 0)
|
||||
{
|
||||
fmt::throw_exception("AU queuing error (handle=0x%x, seq_id=%d, cmd_id=%d, error=0x%x): %s", handle, cmd->seq_id, cmd->id, ret, utils::av_error_to_string(ret));
|
||||
}
|
||||
else
|
||||
{
|
||||
sequence_header_found = true;
|
||||
}
|
||||
|
||||
while (!abort_decode && seq_id == cmd->seq_id)
|
||||
{
|
||||
@@ -577,6 +624,12 @@ struct vdec_context final
|
||||
|
||||
decoded_frames.push_back(std::move(frame));
|
||||
}
|
||||
|
||||
if (skipped_au_count && !decoded_frames.empty())
|
||||
{
|
||||
cellVdec.warning("Decoder output resumed after %d skipped AUs (handle=0x%x, seq_id=%d, cmd_id=%d)", skipped_au_count, handle, cmd->seq_id, cmd->id);
|
||||
skipped_au_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (thread_ctrl::state() != thread_state::aborting)
|
||||
@@ -610,10 +663,10 @@ struct vdec_context final
|
||||
|
||||
thread_ctrl::wait_for(10000);
|
||||
|
||||
if (elapsed++ >= 500) // 5 seconds
|
||||
// Report a wait once: a game may keep the pictures of a decoder it does not display for a long time
|
||||
if (++elapsed == 500) // 5 seconds
|
||||
{
|
||||
cellVdec.error("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
|
||||
elapsed = 0;
|
||||
cellVdec.warning("Video au decode has been waiting for a consumer for 5 seconds. (handle=0x%x, seq_id=%d, cmd_id=%d, queue_size=%d)", handle, cmd->seq_id, cmd->id, out_queue.size());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,6 +675,11 @@ struct vdec_context final
|
||||
break;
|
||||
}
|
||||
|
||||
if (elapsed >= 500)
|
||||
{
|
||||
cellVdec.notice("Video au decode resumed after waiting %d seconds for a consumer. (handle=0x%x, seq_id=%d, cmd_id=%d)", elapsed / 100, handle, cmd->seq_id, cmd->id);
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock{mutex};
|
||||
out_queue.push_back(std::move(decoded_frames.front()));
|
||||
@@ -1685,7 +1743,8 @@ error_code cellVdecDecodeAu(ppu_thread& ppu, u32 handle, CellVdecDecodeMode mode
|
||||
|
||||
if (!vdec->au_count.try_inc(4))
|
||||
{
|
||||
return CELL_VDEC_ERROR_BUSY;
|
||||
// The AU queue is full: BUSY asks the caller to retry, it is not an error
|
||||
return not_an_error(CELL_VDEC_ERROR_BUSY);
|
||||
}
|
||||
|
||||
const u64 seq_id = vdec->seq_id;
|
||||
@@ -1732,7 +1791,8 @@ error_code cellVdecDecodeAuEx2(ppu_thread& ppu, u32 handle, CellVdecDecodeMode m
|
||||
|
||||
if (!vdec->au_count.try_inc(4))
|
||||
{
|
||||
return CELL_VDEC_ERROR_BUSY;
|
||||
// The AU queue is full: BUSY asks the caller to retry, it is not an error
|
||||
return not_an_error(CELL_VDEC_ERROR_BUSY);
|
||||
}
|
||||
|
||||
CellVdecAuInfo au_info{};
|
||||
|
||||
@@ -167,7 +167,7 @@ error_code cellVoiceCreatePort(vm::ptr<u32> portId, vm::cptr<CellVoicePortParam>
|
||||
return CELL_VOICE_ERROR_ARGUMENT_INVALID;
|
||||
}
|
||||
|
||||
if (manager.ports.size() > CELLVOICE_MAX_PORT)
|
||||
if (manager.ports.size() >= CELLVOICE_MAX_PORT)
|
||||
return CELL_VOICE_ERROR_RESOURCE_INSUFFICIENT;
|
||||
|
||||
// Id: bits [8,15] seem to contain a "random" value
|
||||
@@ -427,6 +427,8 @@ error_code cellVoiceInitEx(vm::ptr<CellVoiceInitParam> pArg)
|
||||
|
||||
auto& manager = g_fxo->get<voice_manager>();
|
||||
|
||||
std::lock_guard lock(manager.mtx);
|
||||
|
||||
if (manager.is_init)
|
||||
return CELL_VOICE_ERROR_LIBVOICE_INITIALIZED;
|
||||
|
||||
|
||||
@@ -65,6 +65,7 @@ enum class ppu_exec_bit : u64
|
||||
fix_nj,
|
||||
set_vnan,
|
||||
fix_vnan,
|
||||
set_vest,
|
||||
set_fpcc,
|
||||
use_dfma,
|
||||
set_cr_stats,
|
||||
@@ -663,8 +664,8 @@ auto MFVSCR()
|
||||
u32 sat_bit = 0;
|
||||
if constexpr (((Flags == set_sat) || ...))
|
||||
sat_bit = !gv_testz(sat); //!!sat._u;
|
||||
d._u64[0] = 0;
|
||||
d._u64[1] = u64(sat_bit | (u32{nj} << 16)) << 32;
|
||||
d._u64[0] = sat_bit | (u32{nj} << 16);
|
||||
d._u64[1] = 0;
|
||||
};
|
||||
|
||||
RETURN_(ppu.vr[op.vd], ppu.sat, ppu.nj);
|
||||
@@ -678,7 +679,7 @@ auto MTVSCR()
|
||||
|
||||
static const auto exec = [](auto&& sat, auto&& nj, auto&& jm_mask, auto&& b)
|
||||
{
|
||||
const u32 vscr = b._u32[3];
|
||||
const u32 vscr = b._u32[0];
|
||||
if constexpr (((Flags == set_sat) || ...))
|
||||
sat._u = vscr & 1;
|
||||
if constexpr (((Flags == use_nj || Flags == fix_nj) || ...))
|
||||
@@ -1325,30 +1326,34 @@ template <u32 Build, ppu_exec_bit... Flags>
|
||||
auto VEXPTEFP()
|
||||
{
|
||||
if constexpr (Build == 0xf1a6)
|
||||
return ppu_exec_select<Flags...>::template select<fix_vnan>();
|
||||
return ppu_exec_select<Flags...>::template select<>();
|
||||
|
||||
static const auto exec = [](auto&& d, auto&& b)
|
||||
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
|
||||
{
|
||||
// for (u32 i = 0; i < 4; i++) d._f[i] = std::exp2f(b._f[i]);
|
||||
d = ppu_set_vnan<Flags...>(gv_exp2_approxfs(std::move(b)));
|
||||
for (u32 i = 0; i < 4; i++)
|
||||
{
|
||||
d._u32[i] = ppu_vexptefp(b._u32[i], nj);
|
||||
}
|
||||
};
|
||||
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
|
||||
}
|
||||
|
||||
template <u32 Build, ppu_exec_bit... Flags>
|
||||
auto VLOGEFP()
|
||||
{
|
||||
if constexpr (Build == 0xf1a6)
|
||||
return ppu_exec_select<Flags...>::template select<fix_vnan>();
|
||||
return ppu_exec_select<Flags...>::template select<>();
|
||||
|
||||
static const auto exec = [](auto&& d, auto&& b)
|
||||
static const auto exec = [](auto&& d, auto&& b, auto&& nj)
|
||||
{
|
||||
// for (u32 i = 0; i < 4; i++) d._f[i] = std::log2f(b._f[i]);
|
||||
d = ppu_set_vnan<Flags...>(gv_log2_approxfs(std::move(b)));
|
||||
for (u32 i = 0; i < 4; i++)
|
||||
{
|
||||
d._u32[i] = ppu_vlogefp(b._u32[i], nj);
|
||||
}
|
||||
};
|
||||
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb]);
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.nj);
|
||||
}
|
||||
|
||||
template <u32 Build, ppu_exec_bit... Flags>
|
||||
@@ -2173,16 +2178,26 @@ template <u32 Build, ppu_exec_bit... Flags>
|
||||
auto VREFP()
|
||||
{
|
||||
if constexpr (Build == 0xf1a6)
|
||||
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
|
||||
return ppu_exec_select<Flags...>::template select<set_vest, use_nj, fix_nj, set_vnan, fix_vnan>();
|
||||
|
||||
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
|
||||
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask, auto&& nj)
|
||||
{
|
||||
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
|
||||
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
|
||||
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), b), b));
|
||||
if constexpr (((Flags == set_vest) || ...))
|
||||
{
|
||||
for (u32 i = 0; i < 4; i++)
|
||||
{
|
||||
d._u32[i] = ppu_vrefp(b_._u32[i], nj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
|
||||
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
|
||||
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), b), b));
|
||||
}
|
||||
};
|
||||
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask, ppu.nj);
|
||||
}
|
||||
|
||||
template <u32 Build, ppu_exec_bit... Flags>
|
||||
@@ -2293,16 +2308,26 @@ template <u32 Build, ppu_exec_bit... Flags>
|
||||
auto VRSQRTEFP()
|
||||
{
|
||||
if constexpr (Build == 0xf1a6)
|
||||
return ppu_exec_select<Flags...>::template select<use_nj, fix_nj, set_vnan, fix_vnan>();
|
||||
return ppu_exec_select<Flags...>::template select<set_vest, use_nj, fix_nj, set_vnan, fix_vnan>();
|
||||
|
||||
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask)
|
||||
static const auto exec = [](auto&& d, auto&& b_, auto&& jm_mask, auto&& nj)
|
||||
{
|
||||
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
|
||||
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
|
||||
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), gv_sqrtfs(b)), b));
|
||||
if constexpr (((Flags == set_vest) || ...))
|
||||
{
|
||||
for (u32 i = 0; i < 4; i++)
|
||||
{
|
||||
d._u32[i] = ppu_vrsqrtefp(b_._u32[i], nj);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto m = gv_bcst32(jm_mask, &ppu_thread::jm_mask);
|
||||
auto b = ppu_flush_denormal<false, Flags...>(m, std::move(b_));
|
||||
d = ppu_flush_denormal<true, Flags...>(std::move(m), ppu_set_vnan<Flags...>(gv_divfs(gv_bcstfs(1.0f), gv_sqrtfs(b)), b));
|
||||
}
|
||||
};
|
||||
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask);
|
||||
RETURN_(ppu.vr[op.vd], ppu.vr[op.vb], ppu.jm_mask, ppu.nj);
|
||||
}
|
||||
|
||||
template <u32 Build, ppu_exec_bit... Flags>
|
||||
@@ -7362,6 +7387,8 @@ ppu_interpreter_rt_base::ppu_interpreter_rt_base() noexcept
|
||||
selected += fix_vnan;
|
||||
if (g_cfg.core.ppu_set_fpcc)
|
||||
selected += set_fpcc;
|
||||
if (g_cfg.core.ppu_set_vest)
|
||||
selected += set_vest;
|
||||
if (g_cfg.core.use_accurate_dfma)
|
||||
selected += use_dfma;
|
||||
if (g_cfg.core.ppu_debug)
|
||||
|
||||
@@ -2231,7 +2231,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, bool virtual_load, const std::str
|
||||
}
|
||||
}
|
||||
|
||||
if (ppc_seg != 0x0 && !ar)
|
||||
if (ppc_seg != 0x0 && !ar && !virtual_load)
|
||||
{
|
||||
if (ppc_seg != 0x1)
|
||||
{
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "PPUAnalyser.h"
|
||||
#include "PPUModule.h"
|
||||
#include "PPUDisAsm.h"
|
||||
#include "Common.h"
|
||||
#include "SPURecompiler.h"
|
||||
#include "timers.hpp"
|
||||
#include "lv2/sys_sync.h"
|
||||
@@ -2547,9 +2548,10 @@ void ppu_thread::cpu_task()
|
||||
{
|
||||
// The caller of sys_ppu_thread_start has higher or equal priority and waits for the exchange above:
|
||||
// let it leave the syscall and run its first instructions before this thread starts
|
||||
// Bounded, as it may enter another syscall right away (e.g. joining this thread), and ended on suspension, which cannot be acknowledged here
|
||||
if (const auto caller = idm::get_unlocked<named_thread<ppu_thread>>(caller_id))
|
||||
{
|
||||
while (!is_stopped())
|
||||
for (const u64 wait_start = get_system_time(); !is_stopped() && cpu_flag::suspend - state && get_system_time() - wait_start < 1000;)
|
||||
{
|
||||
const auto caller_state = +caller->state;
|
||||
|
||||
@@ -2564,7 +2566,7 @@ void ppu_thread::cpu_task()
|
||||
{
|
||||
constexpr u64 caller_head_start_us = 10;
|
||||
|
||||
for (const u64 start = get_system_time(); !is_stopped() && get_system_time() - start < caller_head_start_us;)
|
||||
for (const u64 start = get_system_time(); !is_stopped() && cpu_flag::suspend - state && get_system_time() - start < caller_head_start_us;)
|
||||
{
|
||||
std::this_thread::yield();
|
||||
}
|
||||
@@ -4886,6 +4888,15 @@ extern void ppu_initialize()
|
||||
}
|
||||
}
|
||||
|
||||
template <u32 (*Estimate)(u32, bool)>
|
||||
static void ppu_vec_estimate(v128& v, u32 nj)
|
||||
{
|
||||
for (u32 i = 0; i < 4; i++)
|
||||
{
|
||||
v._u32[i] = Estimate(v._u32[i], nj != 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_size)
|
||||
{
|
||||
ppu_log.notice("Entering ppu_initialize(const ppu_module&..)");
|
||||
@@ -4966,6 +4977,10 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
|
||||
{ "__escape", reinterpret_cast<u64>(+ppu_escape) },
|
||||
{ "__read_maybe_mmio32", reinterpret_cast<u64>(+ppu_read_mmio_aware_u32) },
|
||||
{ "__write_maybe_mmio32", reinterpret_cast<u64>(+ppu_write_mmio_aware_u32) },
|
||||
{ "__vrefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vrefp>) },
|
||||
{ "__vrsqrtefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vrsqrtefp>) },
|
||||
{ "__vexptefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vexptefp>) },
|
||||
{ "__vlogefp", reinterpret_cast<u64>(&ppu_vec_estimate<ppu_vlogefp>) },
|
||||
};
|
||||
|
||||
for (u64 index = 0; index < 1024; index++)
|
||||
@@ -5554,6 +5569,7 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
|
||||
accurate_nj_mode,
|
||||
contains_symbol_resolver,
|
||||
daz_and_ftz,
|
||||
accurate_vest,
|
||||
|
||||
__bitset_enum_max
|
||||
};
|
||||
@@ -5584,9 +5600,11 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
|
||||
settings += ppu_settings::contains_symbol_resolver; // Avoid invalidating all modules for this purpose
|
||||
if (g_cfg.core.set_daz_and_ftz)
|
||||
settings += ppu_settings::daz_and_ftz;
|
||||
if (g_cfg.core.ppu_set_vest)
|
||||
settings += ppu_settings::accurate_vest;
|
||||
|
||||
// Write version, hash, CPU, settings
|
||||
fmt::append(obj_name, "v8-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
|
||||
fmt::append(obj_name, "v9-kusa-%s-%s-%s.obj", fmt::base57(output, 16), fmt::base57(settings), jit_compiler::cpu(g_cfg.core.llvm_cpu.to_string()));
|
||||
}
|
||||
|
||||
if (cpu ? cpu->state.all_of(cpu_flag::exit) : Emu.IsStopped())
|
||||
|
||||
@@ -489,6 +489,16 @@ Value* PPUTranslator::VecHandleResult(Value* val, bool flush_denormals_manually)
|
||||
return val;
|
||||
}
|
||||
|
||||
Value* PPUTranslator::VecEstimate(StringRef name, u32 vr)
|
||||
{
|
||||
auto& entry = m_function->getEntryBlock();
|
||||
const auto data = IRBuilder<>(&entry, entry.getFirstInsertionPt()).CreateAlloca(GetType<u32[4]>());
|
||||
|
||||
m_ir->CreateStore(GetVr(vr, VrType::vi32), data);
|
||||
Call(GetType<void>(), name, data, ZExt(RegLoad(m_nj), GetType<u32>()));
|
||||
return m_ir->CreateLoad(GetType<u32[4]>(), data);
|
||||
}
|
||||
|
||||
Value* PPUTranslator::GetAddr(u64 _add)
|
||||
{
|
||||
if (m_reloc)
|
||||
@@ -1293,14 +1303,12 @@ void PPUTranslator::VCTUXS(ppu_opcode_t op)
|
||||
|
||||
void PPUTranslator::VEXPTEFP(ppu_opcode_t op)
|
||||
{
|
||||
const auto b = get_vr<f32[4]>(op.vb);
|
||||
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.exp2.v4f32", {b}}, true));
|
||||
SetVr(op.vd, VecEstimate("__vexptefp", op.vb));
|
||||
}
|
||||
|
||||
void PPUTranslator::VLOGEFP(ppu_opcode_t op)
|
||||
{
|
||||
const auto b = get_vr<f32[4]>(op.vb);
|
||||
set_vr(op.vd, vec_handle_result(llvm_calli<f32[4], decltype(b)>{"llvm.log2.v4f32", {b}}, true));
|
||||
SetVr(op.vd, VecEstimate("__vlogefp", op.vb));
|
||||
}
|
||||
|
||||
void PPUTranslator::VMADDFP(ppu_opcode_t op)
|
||||
@@ -1772,6 +1780,12 @@ void PPUTranslator::VPKUWUS(ppu_opcode_t op)
|
||||
|
||||
void PPUTranslator::VREFP(ppu_opcode_t op)
|
||||
{
|
||||
if (g_cfg.core.ppu_set_vest)
|
||||
{
|
||||
SetVr(op.vd, VecEstimate("__vrefp", op.vb));
|
||||
return;
|
||||
}
|
||||
|
||||
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / get_vr<f32[4]>(op.vb)));
|
||||
}
|
||||
|
||||
@@ -1815,6 +1829,12 @@ void PPUTranslator::VRLW(ppu_opcode_t op)
|
||||
|
||||
void PPUTranslator::VRSQRTEFP(ppu_opcode_t op)
|
||||
{
|
||||
if (g_cfg.core.ppu_set_vest)
|
||||
{
|
||||
SetVr(op.vd, VecEstimate("__vrsqrtefp", op.vb));
|
||||
return;
|
||||
}
|
||||
|
||||
set_vr(op.vd, vec_handle_result(fsplat<f32[4]>(1.0) / callf<f32[4]>(get_intrinsic<f32[4]>(Intrinsic::sqrt), get_vr<f32[4]>(op.vb))));
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ public:
|
||||
llvm::Value* VecHandleNan(llvm::Value* val);
|
||||
llvm::Value* VecHandleDenormal(llvm::Value* val);
|
||||
llvm::Value* VecHandleResult(llvm::Value* val, bool flush_denormals_manually = false);
|
||||
llvm::Value* VecEstimate(llvm::StringRef name, u32 vr);
|
||||
|
||||
template <typename T>
|
||||
auto vec_handle_result(T&& expr, bool flush_denormals_manually = false)
|
||||
|
||||
@@ -40,32 +40,6 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
|
||||
{
|
||||
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
|
||||
|
||||
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
|
||||
{
|
||||
if (old.mmio_offset == offset)
|
||||
{
|
||||
const u64 current = get_system_time();
|
||||
|
||||
if (current - old.mmio_time >= 1500)
|
||||
{
|
||||
old.mmio_time = current;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
old.mmio_offset = offset;
|
||||
return true;
|
||||
});
|
||||
|
||||
std::string log_message = fmt::format("RawSPU[%u]: read_reg(0x%x, offset=0x%x)", index, addr, offset);
|
||||
|
||||
if (is_changed)
|
||||
{
|
||||
spu_log.trace("%s", log_message);
|
||||
}
|
||||
|
||||
struct logger_end_t
|
||||
{
|
||||
u32 offset;
|
||||
@@ -75,6 +49,8 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
|
||||
|
||||
~logger_end_t() noexcept
|
||||
{
|
||||
if (log_message.empty()) return;
|
||||
|
||||
const auto [old, value_changed] = stats->fetch_op([&](raw_spu_log_stats_t& old)
|
||||
{
|
||||
if (old.mmio_offset == offset)
|
||||
@@ -96,7 +72,38 @@ bool spu_thread::read_reg(const u32 addr, u32& value)
|
||||
spu_log.trace("%s: value=0x%x", log_message, *value);
|
||||
}
|
||||
}
|
||||
} logger_end{offset, &value, &mmio_stats, std::move(log_message)};
|
||||
} logger_end{offset, &value, &mmio_stats, {}};
|
||||
|
||||
if (spu_log.trace)
|
||||
{
|
||||
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
|
||||
{
|
||||
if (old.mmio_offset == offset)
|
||||
{
|
||||
const u64 current = get_system_time();
|
||||
|
||||
if (current - old.mmio_time >= 1500)
|
||||
{
|
||||
old.mmio_time = current;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
old.mmio_offset = offset;
|
||||
return true;
|
||||
});
|
||||
|
||||
std::string log_message = fmt::format("RawSPU[%u]: read_reg(0x%x, offset=0x%x)", index, addr, offset);
|
||||
|
||||
if (is_changed)
|
||||
{
|
||||
spu_log.trace("%s", log_message);
|
||||
}
|
||||
|
||||
logger_end.log_message = std::move(log_message);
|
||||
}
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
@@ -267,29 +274,32 @@ bool spu_thread::write_reg(const u32 addr, const u32 value)
|
||||
{
|
||||
const u32 offset = addr - (RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index) - RAW_SPU_PROB_OFFSET;
|
||||
|
||||
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
|
||||
if (spu_log.trace)
|
||||
{
|
||||
if (old.mmio_offset == offset && old.mmio_value == value)
|
||||
const auto [old_stats, is_changed] = mmio_stats.fetch_op([&](raw_spu_log_stats_t& old)
|
||||
{
|
||||
const u64 current = get_system_time();
|
||||
|
||||
if (current - old.mmio_time >= 500)
|
||||
if (old.mmio_offset == offset && old.mmio_value == value)
|
||||
{
|
||||
old.mmio_time = current;
|
||||
return true;
|
||||
const u64 current = get_system_time();
|
||||
|
||||
if (current - old.mmio_time >= 500)
|
||||
{
|
||||
old.mmio_time = current;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
old.mmio_offset = offset;
|
||||
old.mmio_value = value;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (is_changed)
|
||||
{
|
||||
spu_log.trace("RawSPU[%u]: write_reg(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
|
||||
}
|
||||
|
||||
old.mmio_offset = offset;
|
||||
old.mmio_value = value;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (is_changed)
|
||||
{
|
||||
spu_log.trace("RawSPU[%u]: write_reg(0x%x, offset=0x%x, value=0x%x)", index, addr, offset, value);
|
||||
}
|
||||
|
||||
switch (offset)
|
||||
|
||||
@@ -7601,7 +7601,7 @@ public:
|
||||
const bool perm_or_zero_only = known_idx.Zero[6];
|
||||
const bool consts_only = known_idx.One[7];
|
||||
const bool consts_never_msb = known_idx.Zero[5];
|
||||
const bool consts_never_allones = known_idx.One[5];
|
||||
[[maybe_unused]] const bool consts_never_allones = known_idx.One[5];
|
||||
const bool idx_selects_single = known_idx.extractBits(1, 4).isConstant();
|
||||
|
||||
const auto a = get_vr<u8[16]>(op.ra);
|
||||
|
||||
@@ -54,6 +54,12 @@ struct hdd_read_state
|
||||
u64 last_end_time = 0;
|
||||
};
|
||||
|
||||
struct bdvd_read_state
|
||||
{
|
||||
shared_mutex mutex;
|
||||
u64 busy_until = 0;
|
||||
};
|
||||
|
||||
template<>
|
||||
void fmt_class_string<lv2_file_type>::format(std::string& out, u64 arg)
|
||||
{
|
||||
@@ -363,7 +369,10 @@ const lv2_fs_mount_info& lv2_fs_mount_info_map::lookup(std::string_view path, bo
|
||||
|
||||
if (ret->mp->flags & lv2_mp_flag::reflection)
|
||||
{
|
||||
ret = &lookup(Emu.GetDir(), false, nullptr);
|
||||
if (const std::string& dir = Emu.GetDir(); lv2_fs_object::get_path_root_and_trail(dir).first != dev_root)
|
||||
{
|
||||
ret = &lookup(dir, false, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
return *ret;
|
||||
@@ -607,6 +616,17 @@ u64 lv2_file::schedule_read(u64 size, u64 start, u64 offset) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mp == &g_mp_sys_dev_bdvd)
|
||||
{
|
||||
// Nominal 2x BD-ROM transfer rate (72 Mbit/s), not measured on a PS3
|
||||
constexpr u64 bdvd_transfer_rate = 9'000'000;
|
||||
auto& bdvd = g_fxo->get<bdvd_read_state>();
|
||||
std::lock_guard lock(bdvd.mutex);
|
||||
const u64 end = std::max(bdvd.busy_until, start) + size * 1'000'000 / bdvd_transfer_rate;
|
||||
bdvd.busy_until = end;
|
||||
return end;
|
||||
}
|
||||
|
||||
// Measured on a stock PS3 HDD
|
||||
constexpr u64 transfer_rate = 63'000 * 1024;
|
||||
constexpr u64 block_size = 0x10000;
|
||||
@@ -913,15 +933,13 @@ void lv2_file::save(utils::serial& ar)
|
||||
|
||||
ar(stats);
|
||||
|
||||
const usz old_end = ar.pad_from_end(stats.size);
|
||||
|
||||
if (usz read_size = file.read_at(0, &ar.data[old_end], stats.size); read_size != stats.size)
|
||||
if (usz read_size = file.read_at(0, ar.pad_from_end(stats.size), stats.size); read_size != stats.size)
|
||||
{
|
||||
ensure(read_size < stats.size);
|
||||
sys_fs.error("Read less than expected! (new-size=0x%x)", read_size);
|
||||
ar.trunc(stats.size - read_size);
|
||||
stats.size = read_size;
|
||||
ar.data.resize(old_end + stats.size);
|
||||
write_to_ptr<fs::stat_t>(ar.data, patch_stats_pos, stats);
|
||||
ar.patch_raw_data(patch_stats_pos, &stats, sizeof(stats));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1534,7 +1552,7 @@ error_code sys_fs_read(ppu_thread& ppu, u32 fd, vm::ptr<void> buf, u64 nbytes, v
|
||||
return CELL_EIO;
|
||||
}
|
||||
|
||||
const u64 read_start = (g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1))
|
||||
const u64 read_start = ((g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1)) || (g_cfg.vfs.emulate_bdvd_speed && file->mp == &g_mp_sys_dev_bdvd))
|
||||
? get_guest_system_time() : umax;
|
||||
const u64 read_offset = read_start != umax ? file->file.pos() : 0;
|
||||
const u64 read_bytes = file->op_read(buf, nbytes);
|
||||
@@ -2499,7 +2517,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
|
||||
|
||||
if (op == 0x8000000b && file->type != lv2_file_type::regular && arg->size)
|
||||
{
|
||||
sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, arg->size, file->name.data());
|
||||
sys_fs.error("%s type: Writing %u bytes to FD=%d (path=%s)", file->type, arg->size, fd, file->name.data());
|
||||
}
|
||||
|
||||
if (op == 0x8000000a && file->type != lv2_file_type::regular && arg->size >= 0x100000)
|
||||
@@ -2545,7 +2563,7 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
|
||||
file->file.seek(op_pos);
|
||||
}
|
||||
|
||||
const u64 op_start = (op == 0x8000000a && g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1))
|
||||
const u64 op_start = (op == 0x8000000a && ((g_cfg.vfs.emulate_hdd_speed && (file->mp == &g_mp_sys_dev_hdd0 || file->mp == &g_mp_sys_dev_hdd1)) || (g_cfg.vfs.emulate_bdvd_speed && file->mp == &g_mp_sys_dev_bdvd)))
|
||||
? get_guest_system_time() : umax;
|
||||
const u64 done_size = op == 0x8000000a
|
||||
? file->op_read(arg->buf, arg->size, op_pos)
|
||||
|
||||
@@ -790,7 +790,7 @@ error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr<void> buf, u32
|
||||
|
||||
if (flags & ~(SYS_NET_MSG_PEEK | SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
||||
{
|
||||
fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", flags);
|
||||
fmt::throw_exception("sys_net_bnet_recvfrom(s=%d): unknown flags (0x%x)", s, flags);
|
||||
}
|
||||
|
||||
s32 result = 0;
|
||||
@@ -919,12 +919,12 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
|
||||
|
||||
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
||||
{
|
||||
fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", flags);
|
||||
fmt::throw_exception("sys_net_bnet_sendmsg(s=%d): unknown flags (0x%x)", s, flags);
|
||||
}
|
||||
|
||||
s32 result{};
|
||||
|
||||
const auto sock = idm::check<lv2_socket>(s, [&](lv2_socket& sock)
|
||||
const auto sock = idm::check<lv2_socket>(s, [&, notify = lv2_obj::notify_all_t()](lv2_socket& sock)
|
||||
{
|
||||
auto netmsg = msg.get_ptr();
|
||||
const auto success = sock.sendmsg(flags, *netmsg);
|
||||
@@ -936,6 +936,8 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
|
||||
return true;
|
||||
}
|
||||
|
||||
auto lock = sock.lock();
|
||||
|
||||
sock.poll_queue(idm::get_unlocked<named_thread<ppu_thread>>(ppu.id), lv2_socket::poll_t::write, [&](bs_t<lv2_socket::poll_t> events) -> bool
|
||||
{
|
||||
if (events & lv2_socket::poll_t::write)
|
||||
@@ -950,10 +952,18 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
|
||||
}
|
||||
}
|
||||
|
||||
if (sock.so_sendtimeo && get_guest_system_time() - ppu.start_time > sock.so_sendtimeo)
|
||||
{
|
||||
result = -SYS_NET_EWOULDBLOCK;
|
||||
lv2_obj::awake(&ppu);
|
||||
return true;
|
||||
}
|
||||
|
||||
sock.set_poll_event(lv2_socket::poll_t::write);
|
||||
return false;
|
||||
});
|
||||
|
||||
lv2_obj::prepare_for_sleep(ppu);
|
||||
lv2_obj::sleep(ppu);
|
||||
return false;
|
||||
});
|
||||
@@ -972,7 +982,7 @@ error_code sys_net_bnet_sendmsg(ppu_thread& ppu, s32 s, vm::cptr<sys_net_msghdr>
|
||||
{
|
||||
break;
|
||||
}
|
||||
thread_ctrl::wait_on(ppu.state, state);
|
||||
ppu.state.wait(state);
|
||||
}
|
||||
|
||||
if (ppu.gpr[3] == static_cast<u64>(-SYS_NET_EINTR))
|
||||
@@ -998,7 +1008,7 @@ error_code sys_net_bnet_sendto(ppu_thread& ppu, s32 s, vm::cptr<void> buf, u32 l
|
||||
|
||||
if (flags & ~(SYS_NET_MSG_DONTWAIT | SYS_NET_MSG_WAITALL | SYS_NET_MSG_USECRYPTO | SYS_NET_MSG_USESIGNATURE))
|
||||
{
|
||||
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", flags);
|
||||
fmt::throw_exception("sys_net_bnet_sendto(s=%d): unknown flags (0x%x)", s, flags);
|
||||
}
|
||||
|
||||
if (addr && addrlen < sizeof(sys_net_sockaddr))
|
||||
|
||||
@@ -96,6 +96,7 @@ void lv2_socket_native::set_socket(socket_type native_socket, lv2_socket_family
|
||||
|
||||
set_default_buffers();
|
||||
set_non_blocking();
|
||||
set_dgram_disable_connreset();
|
||||
}
|
||||
|
||||
std::tuple<bool, s32, shared_ptr<lv2_socket>, sys_net_sockaddr> lv2_socket_native::accept(bool is_lock)
|
||||
@@ -1211,6 +1212,14 @@ void lv2_socket_native::set_non_blocking()
|
||||
np::set_socket_non_blocking(native_socket);
|
||||
}
|
||||
|
||||
void lv2_socket_native::set_dgram_disable_connreset()
|
||||
{
|
||||
if (type == SYS_NET_SOCK_DGRAM)
|
||||
{
|
||||
np::set_dgram_socket_disable_connreset(native_socket);
|
||||
}
|
||||
}
|
||||
|
||||
void lv2_socket_native::get_sockinfo(sys_net_sockinfo_t& info)
|
||||
{
|
||||
if (type != SYS_NET_SOCK_STREAM)
|
||||
|
||||
@@ -64,6 +64,7 @@ private:
|
||||
void set_socket(socket_type native_socket, lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol);
|
||||
void set_default_buffers();
|
||||
void set_non_blocking();
|
||||
void set_dgram_disable_connreset();
|
||||
|
||||
private:
|
||||
// Value keepers
|
||||
|
||||
@@ -53,6 +53,7 @@ nt_p2p_port::nt_p2p_port(u16 port)
|
||||
fmt::throw_exception("Failed to create DGRAM socket for P2P socket: %s!", get_last_error(true));
|
||||
|
||||
np::set_socket_non_blocking(p2p_socket);
|
||||
np::set_dgram_socket_disable_connreset(p2p_socket);
|
||||
|
||||
u32 optval = 131072; // value obtained from DECR for a SOCK_DGRAM_P2P socket(should maybe be bigger for actual socket?)
|
||||
if (setsockopt(p2p_socket, SOL_SOCKET, SO_RCVBUF, reinterpret_cast<const char*>(&optval), sizeof(optval)) != 0)
|
||||
|
||||
@@ -526,6 +526,7 @@ error_code _sys_ppu_thread_create(ppu_thread& ppu, vm::ptr<u64> thread_id, vm::p
|
||||
|
||||
if (!vm::read_string(threadname.addr(), max_size, ppu_name, true))
|
||||
{
|
||||
vm::dealloc(stack_base);
|
||||
dct.free(stack_size);
|
||||
return CELL_EFAULT;
|
||||
}
|
||||
|
||||
@@ -365,6 +365,8 @@ std::function<void(void*)> lv2_prx::load(utils::serial& ar)
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
file = make_file_view(std::move(file), offset, umax);
|
||||
prx = ppu_load_prx(ppu_prx_object{decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic))}, false, path, offset, &ar);
|
||||
ensure(prx);
|
||||
|
||||
prx->m_loaded_flags = std::move(loaded_flags);
|
||||
prx->m_external_loaded_flags = std::move(external_flags);
|
||||
|
||||
@@ -372,8 +374,6 @@ std::function<void(void*)> lv2_prx::load(utils::serial& ar)
|
||||
{
|
||||
prx->restore_exports();
|
||||
}
|
||||
|
||||
ensure(prx);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "Emu/Memory/vm_locking.h"
|
||||
#include "Emu/RSX/Core/RSXEngLock.hpp"
|
||||
#include "Emu/RSX/Core/RSXReservationLock.hpp"
|
||||
#include "Emu/RSX/Overlays/overlay_cursor.h"
|
||||
#include "Emu/RSX/Overlays/overlay_manager.h"
|
||||
#include "Emu/RSX/RSXThread.h"
|
||||
#include "util/asm.hpp"
|
||||
#include "sys_event.h"
|
||||
@@ -36,6 +38,68 @@ void fmt_class_string<sys_rsx_error>::format(std::string& out, u64 arg)
|
||||
});
|
||||
}
|
||||
|
||||
// ---- Hardware cursor ----
|
||||
// The hardware cursor is part of the display circuitry. It is not rendered directly by PGRAPH or any of the other 2D objects.
|
||||
// Emulation is implemented using the overlay system. It has a fixed 64x64 bitmap size.
|
||||
|
||||
rsx::overlays::bitmap_cursor* _sys_rsx_get_cursor()
|
||||
{
|
||||
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
|
||||
{
|
||||
return manager->get<rsx::overlays::bitmap_cursor>().get();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void _sys_rsx_init_cursor_overlay()
|
||||
{
|
||||
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
|
||||
{
|
||||
auto cursor = manager->create<rsx::overlays::bitmap_cursor>();
|
||||
if (const auto avconfig = g_fxo->try_get<rsx::avconf>())
|
||||
{
|
||||
cursor->set_screen_size(
|
||||
::narrow<u16>(avconfig->resolution_x),
|
||||
::narrow<u16>(avconfig->resolution_y));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _sys_rsx_enable_cursor_overlay()
|
||||
{
|
||||
if (auto cursor = _sys_rsx_get_cursor())
|
||||
{
|
||||
cursor->enable();
|
||||
}
|
||||
}
|
||||
|
||||
void _sys_rsx_disable_cursor_overlay()
|
||||
{
|
||||
if (auto cursor = _sys_rsx_get_cursor())
|
||||
{
|
||||
cursor->disable();
|
||||
}
|
||||
}
|
||||
|
||||
void _sys_rsx_move_cursor_overlay(s32 x, s32 y)
|
||||
{
|
||||
if (auto cursor = _sys_rsx_get_cursor())
|
||||
{
|
||||
cursor->set_pos(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
void _sys_rsx_update_cursor_image(u32 cursor_offset)
|
||||
{
|
||||
if (auto cursor = _sys_rsx_get_cursor())
|
||||
{
|
||||
cursor->set_bitmap(cursor_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Util ----
|
||||
|
||||
static u64 rsx_timeStamp()
|
||||
{
|
||||
return get_timebased_time();
|
||||
@@ -142,6 +206,8 @@ bool rsx::thread::send_event(u64 data1, u64 event_flags, u64 data3)
|
||||
return true;
|
||||
}
|
||||
|
||||
// ---- Event Queue ----
|
||||
|
||||
void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64 wait_timeout_ms = 1000ull)
|
||||
{
|
||||
const auto& driverInfo = *vm::_ptr<RsxDriverInfo>(rsxthr->driver_info);
|
||||
@@ -179,8 +245,7 @@ void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait
|
||||
thread_ctrl::wait_for(100);
|
||||
utils::spin_on_cacheline_once(queue->mutex.raw(), 0u, 100);
|
||||
|
||||
// Check for timeout
|
||||
if (wait_timeout_ms == umax)
|
||||
@@ -204,6 +269,8 @@ void _sys_rsx_drain_event_queue(rsx::thread* rsxthr, u64 event_flags = umax, u64
|
||||
}
|
||||
}
|
||||
|
||||
// ---- LV2 ----
|
||||
|
||||
error_code sys_rsx_device_open(cpu_thread& cpu)
|
||||
{
|
||||
cpu.state += cpu_flag::wait;
|
||||
@@ -777,7 +844,30 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 0x10D: // Called by cellGcmInitCursor
|
||||
case 0x10b:
|
||||
// when a4=3, cellGcmSetCursorPosition(a5=xpos, a6=ypos)
|
||||
// when a4=2, cellGcmSetCursorImageOffset(a5=offset)
|
||||
if (a4 == 3)
|
||||
_sys_rsx_move_cursor_overlay(static_cast<s32>(a5), static_cast<s32>(a6));
|
||||
else if (a4 == 2)
|
||||
_sys_rsx_update_cursor_image(static_cast<u32>(a5));
|
||||
else
|
||||
sys_rsx.error("Unknown subfunction 0x%llx (package=0x10b)", a4);
|
||||
break;
|
||||
case 0x10c:
|
||||
// when a4=1, cellGcmSetCursorEnable()
|
||||
// when a4=2, cellGcmSetCursorDisable()
|
||||
if (a4 == 1)
|
||||
_sys_rsx_enable_cursor_overlay();
|
||||
else if (a4 == 2)
|
||||
_sys_rsx_disable_cursor_overlay();
|
||||
else
|
||||
sys_rsx.error("Unknown subfunction 0x%llx (package=0x10c)", a4);
|
||||
break;
|
||||
case 0x10d:
|
||||
// cellGcmInitCursor(a3=1, a4=1, a5=0, a6=0)
|
||||
ensure(a4 == 1);
|
||||
_sys_rsx_init_cursor_overlay();
|
||||
break;
|
||||
|
||||
case 0x300: // Tiles
|
||||
|
||||
@@ -24,7 +24,7 @@ error_code sys_sm_get_params(vm::ptr<u8> a, vm::ptr<u8> b, vm::ptr<u32> c, vm::p
|
||||
|
||||
error_code sys_sm_get_ext_event2(vm::ptr<u64> a1, vm::ptr<u64> a2, vm::ptr<u64> a3, u64 a4)
|
||||
{
|
||||
sys_sm.trace("sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", a1, a2, a3, a4);
|
||||
sys_sm.trace("sys_sm_get_ext_event2(a1=*0x%x, a2=*0x%x, a3=*0x%x, a4=*0x%x, a4=0x%xll", a1, a2, a3, a4, a4);
|
||||
|
||||
if (a4 != 0 && a4 != 1)
|
||||
{
|
||||
|
||||
@@ -107,7 +107,7 @@ error_code sys_storage_write(u32 fd, u32 mode, u32 start_sector, u32 num_sectors
|
||||
|
||||
error_code sys_storage_send_device_command(u32 dev_handle, u64 cmd, vm::ptr<void> in, u64 inlen, vm::ptr<void> out, u64 outlen)
|
||||
{
|
||||
sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%, inlen=0x%x, out=*0x%x, outlen=0x%x)", dev_handle, cmd, in, inlen, out, outlen);
|
||||
sys_storage.todo("sys_storage_send_device_command(dev_handle=0x%x, cmd=0x%llx, in=*0x%x, inlen=0x%x, out=*0x%x, outlen=0x%x)", dev_handle, cmd, in, inlen, out, outlen);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
@@ -292,19 +292,14 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp
|
||||
}
|
||||
|
||||
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
|
||||
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
|
||||
{
|
||||
std::memcpy(buf, >, sizeof(GameTablet_data));
|
||||
return;
|
||||
}
|
||||
|
||||
static u8 noise_x = 0; // Toggle the LSB to simulate a noisy signal, Instant Artist dislikes a pen held perfectly still
|
||||
static u8 noise_y = 0;
|
||||
constexpr s32 tablet_max_x = 1920;
|
||||
constexpr s32 tablet_max_y = 1080;
|
||||
|
||||
const s32 tablet_x_pos = (mouse_data.x_pos * tablet_max_x / mouse_data.x_max) ^ noise_x;
|
||||
const s32 tablet_y_pos = (mouse_data.y_pos * tablet_max_y / mouse_data.y_max) ^ noise_y;
|
||||
const s32 tablet_x_pos = static_cast<s32>(mouse_data.x_pos * tablet_max_x) ^ noise_x;
|
||||
const s32 tablet_y_pos = static_cast<s32>(mouse_data.y_pos * tablet_max_y) ^ noise_y;
|
||||
noise_x ^= 0x1;
|
||||
noise_y ^= 0x1;
|
||||
|
||||
|
||||
+107
-9
@@ -3,9 +3,15 @@
|
||||
#include "MouseHandler.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/Io/guncon3_config.h"
|
||||
#include "Emu/Io/wiimote_config.h"
|
||||
#include "Emu/Cell/lv2/sys_usbd.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/RSX/Overlays/overlay_cursor.h"
|
||||
#include "Input/wiimote_handler.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include <cmath>
|
||||
#include <climits>
|
||||
#include <algorithm>
|
||||
|
||||
LOG_CHANNEL(guncon3_log);
|
||||
|
||||
@@ -127,6 +133,18 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
|
||||
: usb_device_emulated(location)
|
||||
, m_controller_index(controller_index)
|
||||
{
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
s_instances.push_back(this);
|
||||
// Sort instances by controller index (P1 < P2 < P3...)
|
||||
// This ensures that the first available GunCon (e.g. at P3) takes the first Wiimote,
|
||||
// and the second (e.g. at P4) takes the second Wiimote.
|
||||
std::sort(s_instances.begin(), s_instances.end(), [](auto* a, auto* b)
|
||||
{
|
||||
return a->m_controller_index < b->m_controller_index;
|
||||
});
|
||||
}
|
||||
|
||||
device = UsbDescriptorNode(USB_DESCRIPTOR_DEVICE,
|
||||
UsbDeviceDescriptor {
|
||||
.bcdUSB = 0x0110,
|
||||
@@ -174,6 +192,11 @@ usb_device_guncon3::usb_device_guncon3(u32 controller_index, const std::array<u8
|
||||
|
||||
usb_device_guncon3::~usb_device_guncon3()
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
if (auto it = std::find(s_instances.begin(), s_instances.end(), this); it != s_instances.end())
|
||||
{
|
||||
s_instances.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wValue, u16 wIndex, u16 wLength, u32 buf_size, u8* buf, UsbTransfer* transfer)
|
||||
@@ -188,6 +211,81 @@ void usb_device_guncon3::control_transfer(u8 bmRequestType, u8 bRequest, u16 wVa
|
||||
|
||||
extern bool is_input_allowed();
|
||||
|
||||
bool usb_device_guncon3::handle_wiimote(GunCon3_data& gc)
|
||||
{
|
||||
if (!get_wiimote_config().use_for_guncon.get())
|
||||
return false;
|
||||
|
||||
auto* wm = wiimote_handler::get_instance();
|
||||
auto states = wm->get_states();
|
||||
|
||||
// Determine which Wiimote to use based on our ordinal position among all GunCons
|
||||
s64 my_wiimote_index = -1;
|
||||
{
|
||||
std::lock_guard lock(s_instances_mutex);
|
||||
auto found = std::find(s_instances.begin(), s_instances.end(), this);
|
||||
if (found != s_instances.end())
|
||||
{
|
||||
my_wiimote_index = std::distance(s_instances.begin(), found);
|
||||
}
|
||||
}
|
||||
|
||||
if (my_wiimote_index < 0 || static_cast<usz>(my_wiimote_index) >= states.size())
|
||||
return false;
|
||||
|
||||
const auto& ws = states[my_wiimote_index];
|
||||
if (!ws.connected)
|
||||
return false;
|
||||
|
||||
const auto& map = get_wiimote_config().guncon_mapping;
|
||||
const auto is_pressed = [&](wiimote_button btn) { return (ws.buttons & static_cast<u16>(btn)) != 0; };
|
||||
|
||||
if (is_pressed(map.trigger.get())) gc.btn_trigger = 1;
|
||||
|
||||
// Wiimote to GunCon3 Button Mapping
|
||||
if (is_pressed(map.a1.get())) gc.btn_a1 = 1;
|
||||
if (is_pressed(map.a2.get())) gc.btn_a2 = 1;
|
||||
if (is_pressed(map.a3.get())) gc.btn_a3 = 1;
|
||||
if (is_pressed(map.b1.get())) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2.get())) gc.btn_b2 = 1;
|
||||
if (is_pressed(map.b3.get())) gc.btn_b3 = 1;
|
||||
if (is_pressed(map.c1.get())) gc.btn_c1 = 1;
|
||||
if (is_pressed(map.c2.get())) gc.btn_c2 = 1;
|
||||
|
||||
// Secondary / Hardcoded Alts
|
||||
if (is_pressed(map.b1_alt.get())) gc.btn_b1 = 1;
|
||||
if (is_pressed(map.b2_alt.get())) gc.btn_b2 = 1;
|
||||
|
||||
if (ws.ir[0].x < 1023)
|
||||
{
|
||||
// Map Wiimote IR (0..1023) to GunCon3 range (-32768..32767)
|
||||
const s32 raw_x = ws.ir[0].x;
|
||||
const s32 raw_y = ws.ir[0].y;
|
||||
|
||||
const s32 x_res = SHRT_MAX - (raw_x * USHRT_MAX / 1023);
|
||||
const s32 y_res = SHRT_MAX - (raw_y * USHRT_MAX / 767);
|
||||
|
||||
gc.gun_x = static_cast<s16>(std::clamp(x_res, SHRT_MIN, SHRT_MAX));
|
||||
gc.gun_y = static_cast<s16>(std::clamp(y_res, SHRT_MIN, SHRT_MAX));
|
||||
|
||||
if (g_cfg.io.show_move_cursor)
|
||||
{
|
||||
const s16 ax = static_cast<s16>((gc.gun_x + SHRT_MAX + 1) * rsx::overlays::overlay::virtual_width / USHRT_MAX);
|
||||
const s16 ay = static_cast<s16>((SHRT_MAX - gc.gun_y) * rsx::overlays::overlay::virtual_height / USHRT_MAX);
|
||||
rsx::overlays::set_cursor(rsx::overlays::cursor_offset::cell_gem + my_wiimote_index, ax, ay, { 1.0f, 1.0f, 1.0f, 1.0f }, 100'000, false);
|
||||
}
|
||||
|
||||
if (ws.ir[1].x < 1023)
|
||||
{
|
||||
const s32 dx = static_cast<s32>(ws.ir[0].x) - ws.ir[1].x;
|
||||
const s32 dy = static_cast<s32>(ws.ir[0].y) - ws.ir[1].y;
|
||||
gc.gun_z = static_cast<s16>(std::sqrt(dx * dx + dy * dy));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, UsbTransfer* transfer)
|
||||
{
|
||||
transfer->fake = true;
|
||||
@@ -213,6 +311,12 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle_wiimote(gc))
|
||||
{
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_cfg.io.mouse == mouse_handler::null)
|
||||
{
|
||||
guncon3_log.warning("GunCon3 requires a Mouse Handler enabled");
|
||||
@@ -270,7 +374,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
||||
|
||||
mouse_handler.Init(4);
|
||||
|
||||
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
if (mouse_index >= mouse_handler.GetMice().size())
|
||||
{
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
@@ -280,15 +384,9 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
||||
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
|
||||
cfg->handle_input(mouse_data, input_callback);
|
||||
|
||||
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
|
||||
{
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand 0..+wh to -32767..+32767
|
||||
gc.gun_x = (mouse_data.x_pos * USHRT_MAX / mouse_data.x_max) - SHRT_MAX;
|
||||
gc.gun_y = (mouse_data.y_pos * -USHRT_MAX / mouse_data.y_max) + SHRT_MAX;
|
||||
gc.gun_x = static_cast<s32>(mouse_data.x_pos * USHRT_MAX) - SHRT_MAX;
|
||||
gc.gun_y = static_cast<s32>(mouse_data.y_pos * -USHRT_MAX) + SHRT_MAX;
|
||||
}
|
||||
|
||||
guncon3_encode(&gc, buf, m_key.data());
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "Emu/Io/usb_device.h"
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
class usb_device_guncon3 : public usb_device_emulated
|
||||
{
|
||||
@@ -14,4 +16,9 @@ public:
|
||||
private:
|
||||
u32 m_controller_index;
|
||||
std::array<u8, 8> m_key{};
|
||||
|
||||
bool handle_wiimote(struct GunCon3_data& gc);
|
||||
|
||||
static inline std::vector<usb_device_guncon3*> s_instances;
|
||||
static inline std::mutex s_instances_mutex;
|
||||
};
|
||||
|
||||
@@ -1275,7 +1275,7 @@ void usb_device_logitech_g27::interrupt_transfer(u32 buf_size, u8* buf, u32 endp
|
||||
// Change device mode
|
||||
u8 cmd = buf[1];
|
||||
u8 arg = buf[2];
|
||||
if (buf[8] == 0xf8) // we have 2 commands back to back
|
||||
if (buf_size >= 11 && buf[8] == 0xf8) // we have 2 commands back to back
|
||||
{
|
||||
cmd = buf[9];
|
||||
arg = buf[10];
|
||||
|
||||
@@ -71,8 +71,8 @@ void MouseHandlerBase::Button(u32 index, u8 button, bool pressed)
|
||||
MouseData new_data{};
|
||||
new_data.update = CELL_MOUSE_DATA_UPDATE;
|
||||
new_data.buttons = mouse.buttons;
|
||||
new_data.pixel_x = mouse.x_pos;
|
||||
new_data.pixel_y = mouse.y_pos;
|
||||
new_data.pixel_x = mouse.x_pos_pixels;
|
||||
new_data.pixel_y = mouse.y_pos_pixels;
|
||||
|
||||
datalist.push_back(std::move(new_data));
|
||||
}
|
||||
@@ -102,8 +102,8 @@ void MouseHandlerBase::Scroll(u32 index, s8 x, s8 y)
|
||||
new_data.buttons = mouse.buttons;
|
||||
new_data.wheel = y;
|
||||
new_data.tilt = x;
|
||||
new_data.pixel_x = mouse.x_pos;
|
||||
new_data.pixel_y = mouse.y_pos;
|
||||
new_data.pixel_x = mouse.x_pos_pixels;
|
||||
new_data.pixel_y = mouse.y_pos_pixels;
|
||||
|
||||
datalist.push_back(std::move(new_data));
|
||||
}
|
||||
@@ -135,8 +135,8 @@ void MouseHandlerBase::Move(u32 index, s32 x_pos_new, s32 y_pos_new, s32 x_max,
|
||||
if (!is_relative)
|
||||
{
|
||||
// The PS3 expects relative mouse movement, so we have to calculate it with the last absolute position.
|
||||
x_delta = x_pos_new - mouse.x_pos;
|
||||
y_delta = y_pos_new - mouse.y_pos;
|
||||
x_delta = x_pos_new - mouse.x_pos_pixels;
|
||||
y_delta = y_pos_new - mouse.y_pos_pixels;
|
||||
}
|
||||
|
||||
new_data.x_axis = static_cast<s8>(std::clamp(x_delta, -127, 128));
|
||||
@@ -144,10 +144,10 @@ void MouseHandlerBase::Move(u32 index, s32 x_pos_new, s32 y_pos_new, s32 x_max,
|
||||
new_data.pixel_x = x_pos_new;
|
||||
new_data.pixel_y = y_pos_new;
|
||||
|
||||
mouse.x_max = x_max;
|
||||
mouse.y_max = y_max;
|
||||
mouse.x_pos = x_pos_new;
|
||||
mouse.y_pos = y_pos_new;
|
||||
mouse.x_pos = x_max == 0 ? 0.0f : std::clamp(x_pos_new / static_cast<f32>(x_max), 0.0f, 1.0f);
|
||||
mouse.y_pos = y_max == 0 ? 0.0f : std::clamp(y_pos_new / static_cast<f32>(y_max), 0.0f, 1.0f);
|
||||
mouse.x_pos_pixels = x_pos_new;
|
||||
mouse.y_pos_pixels = y_pos_new;
|
||||
|
||||
//CellMouseRawData& rawdata = GetRawData(p);
|
||||
//rawdata.data[rawdata.len % CELL_MOUSE_MAX_CODES] = 0; // (TODO)
|
||||
|
||||
@@ -112,10 +112,10 @@ using MouseDataList = std::deque<MouseData>;
|
||||
|
||||
struct Mouse
|
||||
{
|
||||
s32 x_pos = 0;
|
||||
s32 y_pos = 0;
|
||||
s32 x_max = 0;
|
||||
s32 y_max = 0;
|
||||
f32 x_pos = 0.0f;
|
||||
f32 y_pos = 0.0f;
|
||||
s32 x_pos_pixels = 0;
|
||||
s32 y_pos_pixels = 0;
|
||||
u8 buttons = 0; // actual mouse button positions
|
||||
|
||||
MouseTabletDataList m_tablet_datalist{};
|
||||
@@ -154,9 +154,9 @@ public:
|
||||
|
||||
MouseInfo& GetInfo() { return m_info; }
|
||||
std::vector<Mouse>& GetMice() { return m_mice; }
|
||||
MouseDataList& GetDataList(const u32 mouse) { return m_mice[mouse].m_datalist; }
|
||||
MouseTabletDataList& GetTabletDataList(const u32 mouse) { return m_mice[mouse].m_tablet_datalist; }
|
||||
MouseRawData& GetRawData(const u32 mouse) { return m_mice[mouse].m_rawdata; }
|
||||
MouseDataList& GetDataList(const usz mouse) { return ::at32(m_mice, mouse).m_datalist; }
|
||||
MouseTabletDataList& GetTabletDataList(const usz mouse) { return ::at32(m_mice, mouse).m_tablet_datalist; }
|
||||
MouseRawData& GetRawData(const usz mouse) { return ::at32(m_mice, mouse).m_rawdata; }
|
||||
|
||||
stx::init_mutex init;
|
||||
|
||||
|
||||
+190
-50
@@ -181,11 +181,22 @@ u16 PadHandlerBase::ConvertAxis(f32 value)
|
||||
return static_cast<u16>((value + 1.0) * (255.0 / 2.0));
|
||||
}
|
||||
|
||||
void PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor)
|
||||
u8 PadHandlerBase::ConvertAngleToU8(f32 angle, f32 distance_to_center)
|
||||
{
|
||||
if (!squircle_factor)
|
||||
return;
|
||||
if (distance_to_center < 0.5f)
|
||||
return 0;
|
||||
|
||||
constexpr f32 two_pi = std::numbers::pi_v<f32> * 2;
|
||||
angle = -angle;
|
||||
|
||||
if (angle < 0)
|
||||
angle += two_pi;
|
||||
|
||||
return static_cast<u8>(std::round(angle / two_pi * 0x100));
|
||||
}
|
||||
|
||||
std::tuple<f32, f32> PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor)
|
||||
{
|
||||
constexpr f32 radius = 127.5f;
|
||||
|
||||
// convert inX and Y to a (-1, 1) vector;
|
||||
@@ -196,13 +207,18 @@ void PadHandlerBase::ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_fac
|
||||
const f32 angle = std::atan2(y, x);
|
||||
const f32 distance_to_center = std::min(1.0f, std::sqrt(std::pow(x, 2.f) + std::pow(y, 2.f)));
|
||||
|
||||
// now find len/point on the given squircle from our current angle and radius in polar coords
|
||||
// https://thatsmaths.com/2016/07/14/squircles/
|
||||
const f32 new_len = (1 + std::pow(std::sin(2 * angle), 2.f) / (squircle_factor / 1000.f)) * distance_to_center;
|
||||
if (squircle_factor)
|
||||
{
|
||||
// now find len/point on the given squircle from our current angle and radius in polar coords
|
||||
// https://thatsmaths.com/2016/07/14/squircles/
|
||||
const f32 new_len = (1 + std::pow(std::sin(2 * angle), 2.f) / (squircle_factor / 1000.f)) * distance_to_center;
|
||||
|
||||
// we now have len and angle, convert to cartesian
|
||||
inX = Clamp0To255(std::round(((new_len * std::cos(angle)) + 1) * radius));
|
||||
inY = Clamp0To255(std::round(((new_len * std::sin(angle)) + 1) * radius));
|
||||
// we now have len and angle, convert to cartesian
|
||||
inX = Clamp0To255(std::round(((new_len * std::cos(angle)) + 1) * radius));
|
||||
inY = Clamp0To255(std::round(((new_len * std::sin(angle)) + 1) * radius));
|
||||
}
|
||||
|
||||
return std::tuple<f32, f32>(angle, distance_to_center);
|
||||
}
|
||||
|
||||
void PadHandlerBase::init_configs()
|
||||
@@ -422,16 +438,13 @@ void PadHandlerBase::get_motion_sensors(const std::string& pad_id, const motion_
|
||||
callback(pad_id, std::move(preview_values));
|
||||
}
|
||||
|
||||
void PadHandlerBase::convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const
|
||||
void PadHandlerBase::convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling, f32& angle, f32& distance_to_center) const
|
||||
{
|
||||
// Normalize our stick axis based on the deadzone
|
||||
std::tie(x_out, y_out) = NormalizeStickDeadzone(x_in, y_in, deadzone, anti_deadzone);
|
||||
|
||||
// Apply pad squircling if necessary
|
||||
if (padsquircling != 0)
|
||||
{
|
||||
ConvertToSquirclePoint(x_out, y_out, padsquircling);
|
||||
}
|
||||
std::tie(angle, distance_to_center) = ConvertToSquirclePoint(x_out, y_out, padsquircling);
|
||||
}
|
||||
|
||||
// Update the pad button values based on their type and thresholds. With this you can use axis or triggers as buttons or vice versa
|
||||
@@ -774,10 +787,11 @@ void PadHandlerBase::get_mapping(const pad_ensemble& binding)
|
||||
}
|
||||
|
||||
u16 lx, ly, rx, ry;
|
||||
f32 l_angle, l_distance_to_center, r_angle, r_distance_to_center;
|
||||
|
||||
// Normalize and apply pad squircling
|
||||
convert_stick_values(lx, ly, stick_val[0], stick_val[1], cfg->lstickdeadzone, cfg->lstick_anti_deadzone, cfg->lpadsquircling);
|
||||
convert_stick_values(rx, ry, stick_val[2], stick_val[3], cfg->rstickdeadzone, cfg->rstick_anti_deadzone, cfg->rpadsquircling);
|
||||
convert_stick_values(lx, ly, stick_val[0], stick_val[1], cfg->lstickdeadzone, cfg->lstick_anti_deadzone, cfg->lpadsquircling, l_angle, l_distance_to_center);
|
||||
convert_stick_values(rx, ry, stick_val[2], stick_val[3], cfg->rstickdeadzone, cfg->rstick_anti_deadzone, cfg->rpadsquircling, r_angle, r_distance_to_center);
|
||||
|
||||
if (m_type == pad_handler::ds4)
|
||||
{
|
||||
@@ -799,6 +813,9 @@ void PadHandlerBase::get_mapping(const pad_ensemble& binding)
|
||||
pad->m_sticks[2].m_value = rx;
|
||||
pad->m_sticks[3].m_value = 255 - ry;
|
||||
}
|
||||
|
||||
pad->m_angles[0] = ConvertAngleToU8(l_angle, l_distance_to_center);
|
||||
pad->m_angles[1] = ConvertAngleToU8(r_angle, r_distance_to_center);
|
||||
}
|
||||
|
||||
void PadHandlerBase::process()
|
||||
@@ -960,6 +977,14 @@ void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
|
||||
return;
|
||||
}
|
||||
|
||||
// The game told us that the controller currently points at the camera (see cellGemSetYaw)
|
||||
if (std::exchange(pad->move_data.orientation_reset_requested, false))
|
||||
{
|
||||
device->reset_orientation();
|
||||
pad->move_data.quaternion = ps_move_data::default_quaternion;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pad->move_data.orientation_enabled || pad->get_orientation_reset_button_active())
|
||||
{
|
||||
// This can be called extensively in quick succession, so let's just reset the pointer instead of creating a new object.
|
||||
@@ -971,16 +996,43 @@ void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
|
||||
device->update_orientation(pad->move_data);
|
||||
}
|
||||
|
||||
static void set_fusion_settings(FusionAhrs* ahrs, f32 sample_rate, bool drift_correction)
|
||||
{
|
||||
FusionAhrsSettings settings = fusionAhrsDefaultSettings;
|
||||
settings.sampleRate = sample_rate;
|
||||
settings.convention = FusionConvention::FusionConventionEnu;
|
||||
|
||||
if (drift_correction)
|
||||
{
|
||||
// Continuously pull the inclination towards the gravity measured by the accelerometer.
|
||||
// The accelerometer is ignored while the device is accelerated (e.g. swung), unless that lasts longer than the rejection timeout.
|
||||
settings.gain = 0.5f;
|
||||
settings.accelerationRejection = 10.0f; // degrees
|
||||
settings.magneticRejection = 10.0f; // degrees
|
||||
settings.rejectionTimeout = 5.0f; // seconds
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only use the accelerometer during the startup period. Afterwards we only integrate the gyro.
|
||||
settings.gain = 0.0f;
|
||||
}
|
||||
|
||||
FusionAhrsSetSettings(ahrs, &settings);
|
||||
}
|
||||
|
||||
void PadDevice::reset_orientation()
|
||||
{
|
||||
// Initialize Fusion
|
||||
ahrs = std::make_shared<FusionAhrs>();
|
||||
FusionAhrsInitialise(ahrs.get());
|
||||
|
||||
FusionAhrsSettings settings = fusionAhrsDefaultSettings;
|
||||
settings.convention = FusionConvention::FusionConventionEnu;
|
||||
settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
|
||||
FusionAhrsSetSettings(ahrs.get(), &settings);
|
||||
ahrs_sample_rate = fusionAhrsDefaultSettings.sampleRate;
|
||||
ahrs_measured_sample_rate = 0.0f;
|
||||
set_fusion_settings(ahrs.get(), ahrs_sample_rate, ahrs_drift_correction);
|
||||
|
||||
// Start measuring the sample period from scratch and discard samples from before the reset
|
||||
last_ahrs_update_time_us = 0;
|
||||
imu_sample_count = 0;
|
||||
}
|
||||
|
||||
void PadDevice::update_orientation(ps_move_data& move_data)
|
||||
@@ -990,53 +1042,141 @@ void PadDevice::update_orientation(ps_move_data& move_data)
|
||||
reset_orientation();
|
||||
}
|
||||
|
||||
// Get elapsed time since last update
|
||||
const u64 now_us = get_system_time();
|
||||
const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||
last_ahrs_update_time_us = now_us;
|
||||
if (!queues_imu_samples)
|
||||
{
|
||||
// Use the current sensor values and the elapsed time since the last update
|
||||
const u64 now_us = get_system_time();
|
||||
const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||
last_ahrs_update_time_us = now_us;
|
||||
|
||||
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
||||
// Don't ask how the axis work. It's basically been trial and error.
|
||||
// We need a valid sample period. Skip the first update after a reset.
|
||||
if (elapsed_sec > 0.0f)
|
||||
{
|
||||
imu_samples[0] = { move_data.accelerometer, move_data.gyro };
|
||||
imu_sample_count = 1;
|
||||
imu_sample_delta_time = elapsed_sec;
|
||||
}
|
||||
}
|
||||
|
||||
// Feed each queued sample to the AHRS
|
||||
const u32 sample_count = std::exchange(imu_sample_count, 0);
|
||||
f32 elapsed_sec = 0.0f;
|
||||
|
||||
for (u32 i = 0; i < sample_count; i++)
|
||||
{
|
||||
if (update_ahrs(move_data, ::at32(imu_samples, i), imu_sample_delta_time))
|
||||
{
|
||||
elapsed_sec += imu_sample_delta_time;
|
||||
}
|
||||
}
|
||||
|
||||
if (elapsed_sec > 0.0f)
|
||||
{
|
||||
move_data.update_orientation(elapsed_sec);
|
||||
}
|
||||
}
|
||||
|
||||
bool PadDevice::update_ahrs(ps_move_data& move_data, const imu_sample& sample, f32 elapsed_sec)
|
||||
{
|
||||
if (!ahrs || !(elapsed_sec > 0.0f))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const ps_move_data::vect<3>& accel = sample.accelerometer;
|
||||
const ps_move_data::vect<3>& gyro = sample.gyro;
|
||||
|
||||
// The sensor data in move_data uses the following device frame (see set_raw_orientation and the PS Move handler):
|
||||
// x: right, y: forward (towards the sphere), z: up (buttons)
|
||||
// The accelerometer and gyro share this frame.
|
||||
// Fusion uses East-North-Up, so we can feed it the device frame as is. The identity orientation is then "flat, pointing forward",
|
||||
// and the accelerometer measures +1g on the z axis at rest, which is what Fusion expects as gravity reference.
|
||||
ensure(ahrs->convention == FusionConvention::FusionConventionEnu); // East-North-Up
|
||||
|
||||
const FusionVector accelerometer{
|
||||
.axis {
|
||||
.x = -move_data.accelerometer.x(),
|
||||
.y = +move_data.accelerometer.y(),
|
||||
.z = +move_data.accelerometer.z()
|
||||
.x = accel.x(),
|
||||
.y = accel.y(),
|
||||
.z = accel.z()
|
||||
}
|
||||
};
|
||||
|
||||
const FusionVector gyroscope{
|
||||
FusionVector gyroscope{
|
||||
.axis {
|
||||
.x = +PadHandlerBase::rad_to_degree(move_data.gyro.x()),
|
||||
.y = +PadHandlerBase::rad_to_degree(move_data.gyro.z()),
|
||||
.z = -PadHandlerBase::rad_to_degree(move_data.gyro.y())
|
||||
.x = PadHandlerBase::rad_to_degree(gyro.x()),
|
||||
.y = PadHandlerBase::rad_to_degree(gyro.y()),
|
||||
.z = PadHandlerBase::rad_to_degree(gyro.z())
|
||||
}
|
||||
};
|
||||
|
||||
FusionVector magnetometer {};
|
||||
|
||||
if (move_data.magnetometer_enabled)
|
||||
if (!std::isfinite(gyroscope.axis.x) || !std::isfinite(gyroscope.axis.y) || !std::isfinite(gyroscope.axis.z))
|
||||
{
|
||||
magnetometer = FusionVector{
|
||||
.axis {
|
||||
.x = move_data.magnetometer.x(),
|
||||
.y = move_data.magnetometer.y(),
|
||||
.z = move_data.magnetometer.z()
|
||||
}
|
||||
};
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update Fusion
|
||||
if (ahrs_drift_correction && !gyro_bias_initialized)
|
||||
{
|
||||
FusionBiasInitialise(&gyro_bias);
|
||||
gyro_bias_initialized = true;
|
||||
}
|
||||
|
||||
// Keep a backup in case the update yields an invalid orientation (e.g. due to garbage sensor data)
|
||||
const FusionAhrs ahrs_backup = *ahrs;
|
||||
const FusionBias gyro_bias_backup = gyro_bias;
|
||||
|
||||
// The startup gain ramp and the rejection timeout are calculated per sample, so the settings have to match the actual sample rate.
|
||||
// Otherwise the startup period would depend on how often we get here.
|
||||
// Applying the settings also resets the internal rejection state, so we only do it if the sample rate changed noticeably.
|
||||
const f32 sample_rate = 1.0f / elapsed_sec;
|
||||
ahrs_measured_sample_rate = (ahrs_measured_sample_rate > 0.0f) ? (ahrs_measured_sample_rate * 0.9f + sample_rate * 0.1f) : sample_rate;
|
||||
|
||||
if (std::abs(ahrs_measured_sample_rate - ahrs_sample_rate) > ahrs_sample_rate * 0.25f)
|
||||
{
|
||||
ahrs_sample_rate = ahrs_measured_sample_rate;
|
||||
set_fusion_settings(ahrs.get(), ahrs_sample_rate, ahrs_drift_correction);
|
||||
}
|
||||
|
||||
// Remove the gyro offset. The offset is learned while the device is stationary.
|
||||
// The stationary period and the filter are also calculated per sample, so the settings have to match the sample rate as well.
|
||||
if (ahrs_drift_correction)
|
||||
{
|
||||
if (gyro_bias.settings.sampleRate != ahrs_sample_rate)
|
||||
{
|
||||
FusionBiasSettings bias_settings = fusionBiasDefaultSettings;
|
||||
bias_settings.sampleRate = ahrs_sample_rate;
|
||||
bias_settings.stationaryThreshold = 10.0f;
|
||||
FusionBiasSetSettings(&gyro_bias, &bias_settings);
|
||||
}
|
||||
|
||||
gyroscope = FusionBiasUpdate(&gyro_bias, gyroscope);
|
||||
}
|
||||
|
||||
// Update Fusion.
|
||||
// We don't use the magnetometer. It would need a proper calibration and would make the heading absolute instead of relative to the calibration pose.
|
||||
// Note: FusionAhrsUpdateNoMagnetometer would also lock the heading during the startup period, so we pass a zero vector instead.
|
||||
FusionAhrsSetSamplePeriod(ahrs.get(), elapsed_sec);
|
||||
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, magnetometer);
|
||||
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, FusionVector{});
|
||||
|
||||
// Get quaternion
|
||||
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(ahrs.get());
|
||||
move_data.quaternion[0] = quaternion.array[1];
|
||||
move_data.quaternion[1] = quaternion.array[2];
|
||||
move_data.quaternion[2] = quaternion.array[3];
|
||||
move_data.quaternion[3] = quaternion.array[0];
|
||||
move_data.update_orientation(elapsed_sec);
|
||||
|
||||
if (!std::isfinite(quaternion.array[0]) || !std::isfinite(quaternion.array[1]) ||
|
||||
!std::isfinite(quaternion.array[2]) || !std::isfinite(quaternion.array[3]))
|
||||
{
|
||||
// Discard this update and keep the last valid orientation
|
||||
*ahrs = ahrs_backup;
|
||||
gyro_bias = gyro_bias_backup;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Convert the quaternion from the device frame (x: right, y: forward, z: up) to the cellGem frame (x: right, y: up, z: backward).
|
||||
// The cellGem identity orientation is "facing the camera with buttons up", which is the same pose as our "flat, pointing forward".
|
||||
// The change of basis is a proper rotation C with (x, y, z) -> (x, z, -y), so q' = C * q * C^-1 = (w, C * v).
|
||||
// This is the same transform that ps move api uses for its OpenGL sensor basis.
|
||||
move_data.quaternion[0] = quaternion.array[1]; // x = x
|
||||
move_data.quaternion[1] = quaternion.array[3]; // y = z
|
||||
move_data.quaternion[2] = -quaternion.array[2]; // z = -y
|
||||
move_data.quaternion[3] = quaternion.array[0]; // w
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#endif
|
||||
extern "C" {
|
||||
#include "3rdparty/fusion/fusion/Fusion/FusionAhrs.h"
|
||||
#include "3rdparty/fusion/fusion/Fusion/FusionBias.h"
|
||||
}
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
@@ -50,11 +51,38 @@ public:
|
||||
bool enable_player_leds{};
|
||||
bool update_player_leds{true};
|
||||
|
||||
steady_clock::time_point last_reconnect_attempt{};
|
||||
|
||||
std::shared_ptr<FusionAhrs> ahrs; // Used to calculate quaternions from sensor data
|
||||
u64 last_ahrs_update_time_us = 0; // Last ahrs update
|
||||
bool ahrs_drift_correction = false; // Continuously correct the inclination using the accelerometer and estimate the gyro bias
|
||||
f32 ahrs_sample_rate = 0.0f; // Sample rate that the AHRS settings were applied with
|
||||
f32 ahrs_measured_sample_rate = 0.0f; // Smoothed measured sample rate
|
||||
|
||||
// Run-time estimation of the gyro offset (only used with drift correction).
|
||||
// This is a sensor property, so it is kept across orientation resets. Reset it if a different device is connected.
|
||||
FusionBias gyro_bias{};
|
||||
bool gyro_bias_initialized = false;
|
||||
|
||||
// Sensor samples (accelerometer in G, gyro in rad/s) for the next orientation update.
|
||||
// By default, update_orientation uses the current values in move_data and the time since the last update.
|
||||
// Handlers that set queues_imu_samples queue their own samples instead (e.g. once per input report).
|
||||
struct imu_sample
|
||||
{
|
||||
ps_move_data::vect<3> accelerometer{};
|
||||
ps_move_data::vect<3> gyro{};
|
||||
};
|
||||
bool queues_imu_samples = false;
|
||||
std::array<imu_sample, 2> imu_samples{};
|
||||
u32 imu_sample_count = 0;
|
||||
f32 imu_sample_delta_time = 0.0f; // Seconds per sample
|
||||
|
||||
void update_orientation(ps_move_data& move_data);
|
||||
void reset_orientation();
|
||||
|
||||
private:
|
||||
// Feeds one sensor sample to the AHRS and updates move_data.quaternion. Returns false if the sample was discarded.
|
||||
bool update_ahrs(ps_move_data& move_data, const imu_sample& sample, f32 elapsed_sec);
|
||||
};
|
||||
|
||||
struct pad_ensemble
|
||||
@@ -231,11 +259,14 @@ public:
|
||||
// input has to be [-1,1]. result will be [0,255]
|
||||
static u16 ConvertAxis(f32 value);
|
||||
|
||||
// Convert analog stick angle to a value ranging from 0 to 255
|
||||
static u8 ConvertAngleToU8(f32 angle, f32 distance_to_center);
|
||||
|
||||
// The DS3, (and i think xbox controllers) give a 'square-ish' type response, so that the corners will give (almost)max x/y instead of the ~30x30 from a perfect circle
|
||||
// using a simple scale/sensitivity increase would *work* although it eats a chunk of our usable range in exchange
|
||||
// this might be the best for now, in practice it seems to push the corners to max of 20x20, with a squircle_factor of ~4000
|
||||
// This function assumes inX and inY is already in 0-255
|
||||
static void ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor);
|
||||
static std::tuple<f32, f32> ConvertToSquirclePoint(u16& inX, u16& inY, u32 squircle_factor);
|
||||
|
||||
// u32 thumb_min = 0; // Unused. Make sure all handlers report 0+ values for sticks in get_button_values.
|
||||
u32 thumb_max = 255;
|
||||
@@ -273,7 +304,7 @@ public:
|
||||
virtual pad_capabilities get_capabilities(const std::string& /*pad_id*/);
|
||||
|
||||
u16 NormalizeStickInput(u16 raw_value, s32 threshold, s32 multiplier, bool ignore_threshold = false) const;
|
||||
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const;
|
||||
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling, f32& angle, f32& distance_to_center) const;
|
||||
void set_trigger_recognition_mode(trigger_recognition_mode mode) { m_trigger_recognition_mode = mode; }
|
||||
|
||||
virtual bool Init() { return true; }
|
||||
|
||||
+65
-12
@@ -6,6 +6,32 @@ LOG_CHANNEL(skylander_log, "skylander");
|
||||
|
||||
sky_portal g_skyportal;
|
||||
|
||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size)
|
||||
{
|
||||
constexpr unsigned short CRC_CCITT_TABLE[256] = {
|
||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273,
|
||||
0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, 0xA56A, 0xB54B, 0x8528,
|
||||
0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 0x6886,
|
||||
0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF,
|
||||
0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, 0x7E97, 0x6EB6, 0x5ED5,
|
||||
0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 0x30C2,
|
||||
0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8,
|
||||
0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, 0x26D3, 0x36F2, 0x0691,
|
||||
0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 0xEB3F,
|
||||
0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64,
|
||||
0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0};
|
||||
|
||||
u16 crc = init_value;
|
||||
|
||||
for (u32 i = 0; i < size; i++)
|
||||
{
|
||||
const u16 tmp = (crc >> 8) ^ buffer[i];
|
||||
crc = (crc << 8) ^ CRC_CCITT_TABLE[tmp];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
void skylander::save()
|
||||
{
|
||||
if (!sky_file)
|
||||
@@ -153,32 +179,41 @@ bool sky_portal::remove_skylander(u8 sky_num)
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file)
|
||||
u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot)
|
||||
{
|
||||
std::lock_guard lock(sky_mutex);
|
||||
|
||||
const u32 sky_serial = read_from_ptr<le_t<u32>>(data);
|
||||
u8 found_slot = 0xFF;
|
||||
|
||||
// mimics spot retaining on the portal
|
||||
for (u8 i = 0; i < 8; i++)
|
||||
if (requested_slot >= 0 && requested_slot <= 7)
|
||||
{
|
||||
if ((skylanders[i].status & 1) == 0)
|
||||
if ((skylanders[requested_slot].status & 1) == 0)
|
||||
found_slot = static_cast<u8>(requested_slot);
|
||||
}
|
||||
else
|
||||
{
|
||||
// mimics spot retaining on the portal
|
||||
for (u8 i = 0; i < 8; i++)
|
||||
{
|
||||
if (skylanders[i].last_id == sky_serial)
|
||||
if ((skylanders[i].status & 1) == 0)
|
||||
{
|
||||
found_slot = i;
|
||||
break;
|
||||
}
|
||||
if (skylanders[i].last_id == sky_serial)
|
||||
{
|
||||
found_slot = i;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i < found_slot)
|
||||
{
|
||||
found_slot = i;
|
||||
if (i < found_slot)
|
||||
{
|
||||
found_slot = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ensure(found_slot != 0xFF);
|
||||
if (found_slot == 0xFF)
|
||||
return 0xFF;
|
||||
|
||||
skylander& thesky = skylanders[found_slot];
|
||||
memcpy(thesky.data.data(), data.data(), thesky.data.size());
|
||||
@@ -191,6 +226,24 @@ u8 sky_portal::load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file
|
||||
return found_slot;
|
||||
}
|
||||
|
||||
void sky_portal::get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant)
|
||||
{
|
||||
ensure(sky_num < 8);
|
||||
std::lock_guard lock(sky_mutex);
|
||||
const auto& s = skylanders[sky_num];
|
||||
out_status = s.status;
|
||||
if (s.status & 1)
|
||||
{
|
||||
out_id = read_from_ptr<le_t<u16>>(s.data, 0x10);
|
||||
out_variant = read_from_ptr<le_t<u16>>(s.data, 0x1C);
|
||||
}
|
||||
else
|
||||
{
|
||||
out_id = 0;
|
||||
out_variant = 0;
|
||||
}
|
||||
}
|
||||
|
||||
usb_device_skylander::usb_device_skylander(const std::array<u8, 7>& location)
|
||||
: usb_device_emulated(location)
|
||||
{
|
||||
|
||||
@@ -27,7 +27,8 @@ public:
|
||||
void write_block(u8 sky_num, u8 block, const u8* to_write_buf, u8* reply_buf);
|
||||
|
||||
bool remove_skylander(u8 sky_num);
|
||||
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file);
|
||||
u8 load_skylander(const std::array<u8, 0x40 * 0x10>& data, fs::file in_file, int requested_slot = -1);
|
||||
void get_figure_info(u8 sky_num, u8& out_status, u16& out_id, u16& out_variant);
|
||||
|
||||
protected:
|
||||
shared_mutex sky_mutex;
|
||||
@@ -41,6 +42,8 @@ protected:
|
||||
|
||||
extern sky_portal g_skyportal;
|
||||
|
||||
u16 skylander_crc16(u16 init_value, const u8* buffer, u32 size);
|
||||
|
||||
class usb_device_skylander : public usb_device_emulated
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
#include "stdafx.h"
|
||||
#include "SkylanderPortalIPC.h"
|
||||
#include "SkylanderPortalIPC_config.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <WinSock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
using sky_socket_t = SOCKET;
|
||||
static constexpr sky_socket_t sky_invalid_socket = INVALID_SOCKET;
|
||||
static int sky_recv(sky_socket_t s, char* buf, int len) { return recv(s, buf, len, 0); }
|
||||
static int sky_send_some(sky_socket_t s, const char* buf, int len) { return send(s, buf, len, 0); }
|
||||
static void sky_close(sky_socket_t s) { closesocket(s); }
|
||||
static bool sky_interrupted() { return WSAGetLastError() == WSAEINTR; }
|
||||
|
||||
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
|
||||
{
|
||||
const DWORD timeout = timeout_ms;
|
||||
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) == 0;
|
||||
}
|
||||
#else
|
||||
#include <sys/socket.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/un.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
using sky_socket_t = int;
|
||||
static constexpr sky_socket_t sky_invalid_socket = -1;
|
||||
static int sky_recv(sky_socket_t s, char* buf, int len) { return static_cast<int>(read(s, buf, len)); }
|
||||
static int sky_send_some(sky_socket_t s, const char* buf, int len) { return static_cast<int>(write(s, buf, len)); }
|
||||
static void sky_close(sky_socket_t s) { close(s); }
|
||||
static bool sky_interrupted() { return errno == EINTR; }
|
||||
|
||||
static bool sky_set_recv_timeout(sky_socket_t s, u32 timeout_ms)
|
||||
{
|
||||
timeval timeout{};
|
||||
timeout.tv_sec = static_cast<time_t>(timeout_ms / 1000);
|
||||
timeout.tv_usec = static_cast<suseconds_t>(timeout_ms % 1000 * 1000);
|
||||
return setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) == 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "Skylander.h"
|
||||
#include "3rdparty/pine/pine_server.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC");
|
||||
|
||||
static constexpr usz max_command_size = 4096;
|
||||
static constexpr u32 client_timeout_ms = 1000;
|
||||
|
||||
static bool is_absolute_local_path(std::string_view path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Requiring a drive letter also rules out network (\\server\share) and device (\\.\) paths
|
||||
const bool has_drive_letter = path.size() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && path[1] == ':';
|
||||
return has_drive_letter && (path[2] == '\\' || path[2] == '/');
|
||||
#else
|
||||
return path.starts_with('/');
|
||||
#endif
|
||||
}
|
||||
|
||||
static std::string handle_load(int slot, const std::string& path)
|
||||
{
|
||||
if (slot < -1 || slot > 7)
|
||||
return "error invalid slot\n";
|
||||
|
||||
if (!is_absolute_local_path(path))
|
||||
return "error path must be absolute\n";
|
||||
|
||||
std::array<u8, 0x40 * 0x10> buf{};
|
||||
|
||||
// Checked before opening, so that devices and pipes are rejected without ever being opened
|
||||
fs::stat_t info{};
|
||||
if (!fs::get_stat(path, info) || info.is_directory || info.size != buf.size())
|
||||
return "error not a skylander file\n";
|
||||
|
||||
fs::file sky_file(path, fs::read + fs::write + fs::lock);
|
||||
if (!sky_file)
|
||||
return "error cannot open file\n";
|
||||
|
||||
if (sky_file.read(buf.data(), buf.size()) != buf.size())
|
||||
return "error file too small\n";
|
||||
|
||||
if (read_from_ptr<le_t<u16>>(buf, 0x1E) != skylander_crc16(0xFFFF, buf.data(), 0x1E))
|
||||
return "error not a skylander file\n";
|
||||
|
||||
const u8 result = g_skyportal.load_skylander(buf, std::move(sky_file), slot);
|
||||
if (result == 0xFF)
|
||||
return "error no free slot\n";
|
||||
|
||||
return "ok " + std::to_string(result) + "\n";
|
||||
}
|
||||
|
||||
static std::string handle_remove(int slot)
|
||||
{
|
||||
if (slot < 0 || slot > 7)
|
||||
return "error invalid slot\n";
|
||||
|
||||
if (!g_skyportal.remove_skylander(static_cast<u8>(slot)))
|
||||
return "error slot empty\n";
|
||||
|
||||
return "ok\n";
|
||||
}
|
||||
|
||||
static std::string handle_status()
|
||||
{
|
||||
std::string result;
|
||||
for (u8 i = 0; i < 8; i++)
|
||||
{
|
||||
u8 status;
|
||||
u16 id, variant;
|
||||
g_skyportal.get_figure_info(i, status, id, variant);
|
||||
if (status & 1)
|
||||
result += "slot" + std::to_string(i) + " loaded " + std::to_string(id) + " " + std::to_string(variant) + "\n";
|
||||
else
|
||||
result += "slot" + std::to_string(i) + " empty\n";
|
||||
}
|
||||
result += "ok\n";
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string handle_clear()
|
||||
{
|
||||
for (u8 i = 0; i < 8; i++)
|
||||
g_skyportal.remove_skylander(i);
|
||||
return "ok\n";
|
||||
}
|
||||
|
||||
static bool parse_int(const std::string& s, int& out)
|
||||
{
|
||||
if (s.empty())
|
||||
return false;
|
||||
char* end;
|
||||
errno = 0;
|
||||
const long val = std::strtol(s.c_str(), &end, 10);
|
||||
if (end == s.c_str() || *end != '\0' || errno != 0)
|
||||
return false;
|
||||
out = static_cast<int>(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::string process_command(const std::string& line)
|
||||
{
|
||||
if (line.empty())
|
||||
return "error empty command\n";
|
||||
|
||||
const usz first_space = line.find(' ');
|
||||
const std::string cmd = line.substr(0, first_space);
|
||||
|
||||
if (cmd == "status")
|
||||
return handle_status();
|
||||
|
||||
if (cmd == "clear")
|
||||
return handle_clear();
|
||||
|
||||
if (cmd == "remove")
|
||||
{
|
||||
if (first_space == std::string::npos)
|
||||
return "error missing slot\n";
|
||||
int slot;
|
||||
if (!parse_int(line.substr(first_space + 1), slot))
|
||||
return "error invalid slot\n";
|
||||
return handle_remove(slot);
|
||||
}
|
||||
|
||||
if (cmd == "load")
|
||||
{
|
||||
if (first_space == std::string::npos)
|
||||
return "error missing arguments\n";
|
||||
const std::string rest = line.substr(first_space + 1);
|
||||
const usz second_space = rest.find(' ');
|
||||
if (second_space == std::string::npos)
|
||||
return "error missing path\n";
|
||||
int slot;
|
||||
if (!parse_int(rest.substr(0, second_space), slot))
|
||||
return "error invalid arguments\n";
|
||||
const std::string path = rest.substr(second_space + 1);
|
||||
return handle_load(slot, path);
|
||||
}
|
||||
|
||||
return "error unknown command\n";
|
||||
}
|
||||
|
||||
static bool sky_send(sky_socket_t s, std::string_view data)
|
||||
{
|
||||
while (!data.empty())
|
||||
{
|
||||
const int sent = sky_send_some(s, data.data(), ::narrow<int>(data.size()));
|
||||
|
||||
if (sent > 0)
|
||||
data.remove_prefix(sent);
|
||||
else if (sent < 0 && sky_interrupted())
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool read_command(sky_socket_t client_sock, std::string& line)
|
||||
{
|
||||
if (!sky_set_recv_timeout(client_sock, client_timeout_ms))
|
||||
return false;
|
||||
|
||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(client_timeout_ms);
|
||||
|
||||
while (line.size() < max_command_size && std::chrono::steady_clock::now() < deadline)
|
||||
{
|
||||
char ch;
|
||||
const int received = sky_recv(client_sock, &ch, 1);
|
||||
|
||||
if (received == 0)
|
||||
return true;
|
||||
|
||||
if (received < 0)
|
||||
{
|
||||
if (sky_interrupted())
|
||||
continue;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ch == '\n' || ch == '\r')
|
||||
return true;
|
||||
|
||||
line += ch;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void SkylanderPortalIPCServer::operator()()
|
||||
{
|
||||
const int port = g_cfg_sky_ipc.get_port();
|
||||
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa{};
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
|
||||
{
|
||||
skylander_ipc_log.error("Cannot initialize winsock");
|
||||
return;
|
||||
}
|
||||
|
||||
const sky_socket_t listen_sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (listen_sock == sky_invalid_socket)
|
||||
{
|
||||
skylander_ipc_log.error("Cannot create socket");
|
||||
WSACleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* failure = pine::bind_and_listen(listen_sock, static_cast<u16>(port)))
|
||||
{
|
||||
skylander_ipc_log.error("Cannot start server on port %d: %s", port, failure);
|
||||
sky_close(listen_sock);
|
||||
WSACleanup();
|
||||
return;
|
||||
}
|
||||
|
||||
skylander_ipc_log.notice("Server started on port %d", port);
|
||||
#else
|
||||
const std::string socket_path = pine::get_socket_path("rpcs3.skylanders.sock", port, g_cfg_sky_ipc.sky_ipc_port.def);
|
||||
|
||||
const sky_socket_t listen_sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (listen_sock == sky_invalid_socket)
|
||||
{
|
||||
skylander_ipc_log.error("Cannot create socket");
|
||||
return;
|
||||
}
|
||||
|
||||
if (const char* failure = pine::bind_and_listen(listen_sock, socket_path))
|
||||
{
|
||||
skylander_ipc_log.error("Cannot start server at %s: %s", socket_path, failure);
|
||||
sky_close(listen_sock);
|
||||
return;
|
||||
}
|
||||
|
||||
skylander_ipc_log.notice("Server started at %s", socket_path);
|
||||
#endif
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
pollfd pfd{};
|
||||
pfd.fd = listen_sock;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
#ifdef _WIN32
|
||||
const int poll_result = WSAPoll(&pfd, 1, 10);
|
||||
#else
|
||||
const int poll_result = poll(&pfd, 1, 10);
|
||||
#endif
|
||||
if (poll_result < 0 && !sky_interrupted())
|
||||
{
|
||||
skylander_ipc_log.error("Waiting for connections failed, stopping server");
|
||||
break;
|
||||
}
|
||||
|
||||
if (poll_result <= 0)
|
||||
continue;
|
||||
|
||||
const sky_socket_t client_sock = accept(listen_sock, nullptr, nullptr);
|
||||
if (client_sock == sky_invalid_socket)
|
||||
continue;
|
||||
|
||||
std::string line;
|
||||
if (read_command(client_sock, line) && !line.empty())
|
||||
{
|
||||
if (!sky_send(client_sock, process_command(line)))
|
||||
skylander_ipc_log.warning("Failed to send the response");
|
||||
}
|
||||
|
||||
sky_close(client_sock);
|
||||
}
|
||||
|
||||
sky_close(listen_sock);
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#else
|
||||
unlink(socket_path.c_str());
|
||||
#endif
|
||||
|
||||
skylander_ipc_log.notice("Server stopped");
|
||||
}
|
||||
|
||||
SkylanderPortalIPCServer& SkylanderPortalIPCServer::operator=(thread_state)
|
||||
{
|
||||
return *this;
|
||||
}
|
||||
|
||||
SkylanderPortalIPCServerManager::SkylanderPortalIPCServerManager(bool enabled)
|
||||
{
|
||||
set_server_enabled(enabled);
|
||||
}
|
||||
|
||||
void SkylanderPortalIPCServerManager::set_server_enabled(bool enabled)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
const int port = g_cfg_sky_ipc.get_port();
|
||||
if (!m_server || port != m_old_port)
|
||||
{
|
||||
skylander_ipc_log.notice("Starting server with port %d", port);
|
||||
m_server = std::make_unique<server_thread>();
|
||||
m_old_port = port;
|
||||
}
|
||||
}
|
||||
else if (m_server)
|
||||
{
|
||||
skylander_ipc_log.notice("Stopping server");
|
||||
m_server.reset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include "Utilities/Thread.h"
|
||||
|
||||
struct SkylanderPortalIPCServer
|
||||
{
|
||||
static constexpr auto thread_name = "Skylanders Portal IPC"sv;
|
||||
void operator()();
|
||||
SkylanderPortalIPCServer& operator=(thread_state);
|
||||
};
|
||||
|
||||
class SkylanderPortalIPCServerManager
|
||||
{
|
||||
using server_thread = named_thread<SkylanderPortalIPCServer>;
|
||||
std::unique_ptr<server_thread> m_server;
|
||||
int m_old_port = 0;
|
||||
|
||||
public:
|
||||
explicit SkylanderPortalIPCServerManager(bool enabled);
|
||||
void set_server_enabled(bool enabled);
|
||||
};
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "stdafx.h"
|
||||
#include "SkylanderPortalIPC_config.h"
|
||||
|
||||
cfg_sky_ipc g_cfg_sky_ipc;
|
||||
|
||||
LOG_CHANNEL(skylander_ipc_log, "SkylanderIPC");
|
||||
|
||||
void cfg_sky_ipc::load()
|
||||
{
|
||||
const std::string path = cfg_sky_ipc::get_path();
|
||||
|
||||
fs::file cfg_file(path, fs::read);
|
||||
if (cfg_file)
|
||||
{
|
||||
skylander_ipc_log.notice("Loading Skylanders IPC config. Path: %s", path);
|
||||
from_string(cfg_file.to_string());
|
||||
}
|
||||
else
|
||||
{
|
||||
skylander_ipc_log.notice("Skylanders IPC config missing. Using default settings. Path: %s", path);
|
||||
from_default();
|
||||
}
|
||||
}
|
||||
|
||||
void cfg_sky_ipc::save() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const std::string path_to_cfg = fs::get_config_dir(true);
|
||||
if (!fs::create_path(path_to_cfg))
|
||||
{
|
||||
skylander_ipc_log.error("Could not create path: %s", path_to_cfg);
|
||||
}
|
||||
#endif
|
||||
|
||||
const std::string path = cfg_sky_ipc::get_path();
|
||||
|
||||
if (!cfg::node::save(path))
|
||||
{
|
||||
skylander_ipc_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error);
|
||||
}
|
||||
}
|
||||
|
||||
std::string cfg_sky_ipc::get_path()
|
||||
{
|
||||
return fs::get_config_dir(true) + "sky_ipc.yml";
|
||||
}
|
||||
|
||||
bool cfg_sky_ipc::get_server_enabled() const
|
||||
{
|
||||
return sky_ipc_server_enabled.get();
|
||||
}
|
||||
|
||||
int cfg_sky_ipc::get_port() const
|
||||
{
|
||||
return sky_ipc_port;
|
||||
}
|
||||
|
||||
void cfg_sky_ipc::set_server_enabled(const bool enabled)
|
||||
{
|
||||
this->sky_ipc_server_enabled.set(enabled);
|
||||
}
|
||||
|
||||
void cfg_sky_ipc::set_port(const int port)
|
||||
{
|
||||
this->sky_ipc_port.set(port);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
|
||||
struct cfg_sky_ipc : cfg::node
|
||||
{
|
||||
cfg::_bool sky_ipc_server_enabled{ this, "Skylanders IPC Server enabled", false };
|
||||
cfg::_int<1025, 65535> sky_ipc_port{ this, "Skylanders IPC Port", 28013 };
|
||||
|
||||
void load();
|
||||
void save() const;
|
||||
|
||||
bool get_server_enabled() const;
|
||||
int get_port() const;
|
||||
|
||||
void set_server_enabled(const bool enabled);
|
||||
void set_port(const int port);
|
||||
|
||||
private:
|
||||
static std::string get_path();
|
||||
};
|
||||
|
||||
extern cfg_sky_ipc g_cfg_sky_ipc;
|
||||
@@ -350,7 +350,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
|
||||
|
||||
mouse_handler.Init(4);
|
||||
|
||||
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
if (mouse_index >= mouse_handler.GetMice().size())
|
||||
{
|
||||
prepare_data(&ts, buf);
|
||||
@@ -361,17 +361,11 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
|
||||
cfg->handle_input(mouse_data, input_callback);
|
||||
ts.trigger = ts.btn_trigger ? 0xff : 0x00;
|
||||
|
||||
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
|
||||
{
|
||||
prepare_data(&ts, buf);
|
||||
return;
|
||||
}
|
||||
s32 led_lx = 0x3ff - (TSE_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT)) + TSE_CALIB_DIST);
|
||||
s32 led_rx = 0x3ff - (TSE_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT)) - TSE_CALIB_DIST);
|
||||
|
||||
s32 led_lx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) + TSE_CALIB_DIST);
|
||||
s32 led_rx = 0x3ff - (TSE_CALIB_RIGHT + (mouse_data.x_pos * (TSE_CALIB_LEFT - TSE_CALIB_RIGHT) / mouse_data.x_max) - TSE_CALIB_DIST);
|
||||
|
||||
s32 led_ly = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP) / mouse_data.y_max);
|
||||
s32 led_ry = TSE_CALIB_TOP + (mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP) / mouse_data.y_max);
|
||||
s32 led_ly = TSE_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP));
|
||||
s32 led_ry = TSE_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSE_CALIB_BOTTOM - TSE_CALIB_TOP));
|
||||
|
||||
u8 detect_l = 0x2, detect_r = 0x2; // 0x2 = led detected / 0xf = undetected
|
||||
|
||||
|
||||
@@ -374,7 +374,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
|
||||
|
||||
mouse_handler.Init(4);
|
||||
|
||||
const u32 mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : m_controller_index;
|
||||
if (mouse_index >= mouse_handler.GetMice().size())
|
||||
{
|
||||
prepare_data(&ts, buf);
|
||||
@@ -386,17 +386,11 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
|
||||
ts.trigger = ts.btn_trigger ? 0xff : 0x00;
|
||||
ts.heartrate = ts.btn_heartrate ? get_heartrate_sensor_value(60) : 0;
|
||||
|
||||
if (mouse_data.x_max <= 0 || mouse_data.y_max <= 0)
|
||||
{
|
||||
prepare_data(&ts, buf);
|
||||
return;
|
||||
}
|
||||
s32 led_lx = 0x3ff - (TSF_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT)) + TSF_CALIB_DIST);
|
||||
s32 led_rx = 0x3ff - (TSF_CALIB_RIGHT + static_cast<s32>(mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT)) - TSF_CALIB_DIST);
|
||||
|
||||
s32 led_lx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) + TSF_CALIB_DIST);
|
||||
s32 led_rx = 0x3ff - (TSF_CALIB_RIGHT + (mouse_data.x_pos * (TSF_CALIB_LEFT - TSF_CALIB_RIGHT) / mouse_data.x_max) - TSF_CALIB_DIST);
|
||||
|
||||
s32 led_ly = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max);
|
||||
s32 led_ry = TSF_CALIB_TOP + (mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP) / mouse_data.y_max);
|
||||
s32 led_ly = TSF_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP));
|
||||
s32 led_ry = TSF_CALIB_TOP + static_cast<s32>(mouse_data.y_pos * (TSF_CALIB_BOTTOM - TSF_CALIB_TOP));
|
||||
|
||||
u8 detect_l = 0x2, detect_r = 0x2; // 0x2 = led detected / 0xf = undetected
|
||||
|
||||
|
||||
@@ -76,7 +76,8 @@ public:
|
||||
|
||||
for (const auto& n : get_nodes())
|
||||
{
|
||||
init_button(static_cast<cfg_pad_btn<T>*>(n));
|
||||
if (auto* pbtn = dynamic_cast<cfg_pad_btn<T>*>(n))
|
||||
init_button(pbtn);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,9 +109,6 @@ public:
|
||||
if (max_value == 1023) return value & 0x03FF;
|
||||
return static_cast<u16>(std::clamp(1023.0f * (static_cast<f32>(std::min(value, max_value)) / ensure(max_value)), 0.0f, 1023.0f));
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(const input_value&, bool&)>& func) const
|
||||
|
||||
@@ -109,28 +109,21 @@ int evdev_gun_handler::get_button(u32 gunno, gun_button button) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
int evdev_gun_handler::get_axis_x(u32 gunno) const
|
||||
f32 evdev_gun_handler::get_axis(u32 gunno, int code) const
|
||||
{
|
||||
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_X);
|
||||
return axis.value - axis.min;
|
||||
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, code);
|
||||
const int range = axis.max - axis.min;
|
||||
return range == 0 ? 0.0f : std::clamp((axis.value - axis.min) / static_cast<f32>(range), 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
int evdev_gun_handler::get_axis_y(u32 gunno) const
|
||||
f32 evdev_gun_handler::get_axis_x(u32 gunno) const
|
||||
{
|
||||
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_Y);
|
||||
return axis.value - axis.min;
|
||||
return get_axis(gunno, ABS_X);
|
||||
}
|
||||
|
||||
int evdev_gun_handler::get_axis_x_max(u32 gunno) const
|
||||
f32 evdev_gun_handler::get_axis_y(u32 gunno) const
|
||||
{
|
||||
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_X);
|
||||
return axis.max - axis.min;
|
||||
}
|
||||
|
||||
int evdev_gun_handler::get_axis_y_max(u32 gunno) const
|
||||
{
|
||||
const evdev_axis& axis = ::at32(::at32(m_devices, gunno).axis, ABS_Y);
|
||||
return axis.max - axis.min;
|
||||
return get_axis(gunno, ABS_Y);
|
||||
}
|
||||
|
||||
void evdev_gun_handler::poll(u32 index)
|
||||
|
||||
@@ -30,16 +30,16 @@ public:
|
||||
bool is_init() const;
|
||||
u32 get_num_guns() const;
|
||||
int get_button(u32 gunno, gun_button button) const;
|
||||
int get_axis_x(u32 gunno) const;
|
||||
int get_axis_y(u32 gunno) const;
|
||||
int get_axis_x_max(u32 gunno) const;
|
||||
int get_axis_y_max(u32 gunno) const;
|
||||
f32 get_axis_x(u32 gunno) const;
|
||||
f32 get_axis_y(u32 gunno) const;
|
||||
|
||||
void poll(u32 index);
|
||||
|
||||
shared_mutex mutex;
|
||||
|
||||
private:
|
||||
f32 get_axis(u32 gunno, int code) const;
|
||||
|
||||
atomic_t<bool> m_is_init{false};
|
||||
struct udev* m_udev = nullptr;
|
||||
|
||||
|
||||
@@ -53,6 +53,9 @@ void fmt_class_string<pad_button>::format(std::string& out, u64 arg)
|
||||
case pad_button::mouse_button_6: return "Mouse Button 6";
|
||||
case pad_button::mouse_button_7: return "Mouse Button 7";
|
||||
case pad_button::mouse_button_8: return "Mouse Button 8";
|
||||
case pad_button::mouse: return "Mouse";
|
||||
case pad_button::left_stick: return "Left Stick";
|
||||
case pad_button::right_stick: return "Right Stick";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
@@ -106,6 +109,9 @@ u32 pad_button_offset(pad_button button)
|
||||
case pad_button::mouse_button_6:
|
||||
case pad_button::mouse_button_7:
|
||||
case pad_button::mouse_button_8:
|
||||
case pad_button::mouse:
|
||||
case pad_button::left_stick:
|
||||
case pad_button::right_stick:
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
@@ -158,6 +164,9 @@ u32 pad_button_keycode(pad_button button)
|
||||
case pad_button::mouse_button_6: return 6;
|
||||
case pad_button::mouse_button_7: return 7;
|
||||
case pad_button::mouse_button_8: return 8;
|
||||
case pad_button::mouse: return 0;
|
||||
case pad_button::left_stick: return 0;
|
||||
case pad_button::right_stick: return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,11 @@ enum class pad_button : u8
|
||||
mouse_button_6,
|
||||
mouse_button_7,
|
||||
mouse_button_8,
|
||||
|
||||
// Special "buttons" that map behaviour
|
||||
mouse,
|
||||
left_stick,
|
||||
right_stick
|
||||
};
|
||||
|
||||
u32 pad_button_offset(pad_button button);
|
||||
@@ -542,6 +547,7 @@ struct Pad
|
||||
std::array<AnalogStick, 4> m_sticks{};
|
||||
std::array<AnalogSensor, 4> m_sensors{};
|
||||
std::array<VibrateMotor, 2> m_vibrate_motors{};
|
||||
std::array<u8, 2> m_angles{};
|
||||
|
||||
std::vector<ButtonExternal> m_buttons_external;
|
||||
std::array<AnalogStickExternal, 4> m_sticks_external{};
|
||||
|
||||
@@ -62,6 +62,8 @@ struct ps_move_data
|
||||
bool calibration_requested = false;
|
||||
bool calibration_succeeded = false;
|
||||
|
||||
bool orientation_reset_requested = false; // Reset the orientation to the default (facing the camera). See cellGemSetYaw.
|
||||
|
||||
bool magnetometer_enabled = false;
|
||||
bool orientation_enabled = false;
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ void usb_device_passthrough::read_descriptors()
|
||||
for (u8 index = 0; index < device._device.bNumConfigurations; index++)
|
||||
{
|
||||
std::array<u8, 1000> buf{};
|
||||
const int ssize = libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_STANDARD | +LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_GET_DESCRIPTOR, 0x0200 | index, 0, buf.data(), buf.size(), 0);
|
||||
const int ssize = libusb_control_transfer(lusb_handle, +LIBUSB_ENDPOINT_IN | +LIBUSB_REQUEST_TYPE_STANDARD | +LIBUSB_RECIPIENT_DEVICE, LIBUSB_REQUEST_GET_DESCRIPTOR, 0x0200 | index, 0, buf.data(), static_cast<u16>(buf.size()), 0);
|
||||
if (ssize < 0)
|
||||
{
|
||||
sys_usbd.fatal("Couldn't get the config from the device: %d(%s)", ssize, libusb_error_name(ssize));
|
||||
|
||||
+97
-40
@@ -2,9 +2,11 @@
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "usio.h"
|
||||
#include "MouseHandler.h"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Emu/Io/usio_config.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/system_config.h"
|
||||
|
||||
LOG_CHANNEL(usio_log, "USIO");
|
||||
|
||||
@@ -33,6 +35,7 @@ void fmt_class_string<usio_btn>::format(std::string& out, u64 arg)
|
||||
case usio_btn::tekken_button4: return "Tekken Button 4";
|
||||
case usio_btn::tekken_button5: return "Tekken Button 5";
|
||||
case usio_btn::card_tapping: return "Card Tapping";
|
||||
case usio_btn::gun: return "Gun";
|
||||
case usio_btn::count: return "Count";
|
||||
}
|
||||
|
||||
@@ -213,7 +216,7 @@ void usb_device_usio::translate_input_taiko()
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_pad_thread();
|
||||
|
||||
std::vector<u8> input_buf(0x60);
|
||||
response.assign(0x60, 0);
|
||||
constexpr le_t<u16> c_hit = 0x1800;
|
||||
le_t<u16> digital_input = 0;
|
||||
|
||||
@@ -259,19 +262,19 @@ void usb_device_usio::translate_input_taiko()
|
||||
break;
|
||||
case usio_btn::taiko_hit_side_left:
|
||||
if (value.pressed)
|
||||
std::memcpy(input_buf.data() + 32 + offset, &c_hit, sizeof(u16));
|
||||
std::memcpy(response.data() + 32 + offset, &c_hit, sizeof(u16));
|
||||
break;
|
||||
case usio_btn::taiko_hit_center_right:
|
||||
if (value.pressed)
|
||||
std::memcpy(input_buf.data() + 36 + offset, &c_hit, sizeof(u16));
|
||||
std::memcpy(response.data() + 36 + offset, &c_hit, sizeof(u16));
|
||||
break;
|
||||
case usio_btn::taiko_hit_side_right:
|
||||
if (value.pressed)
|
||||
std::memcpy(input_buf.data() + 38 + offset, &c_hit, sizeof(u16));
|
||||
std::memcpy(response.data() + 38 + offset, &c_hit, sizeof(u16));
|
||||
break;
|
||||
case usio_btn::taiko_hit_center_left:
|
||||
if (value.pressed)
|
||||
std::memcpy(input_buf.data() + 34 + offset, &c_hit, sizeof(u16));
|
||||
std::memcpy(response.data() + 34 + offset, &c_hit, sizeof(u16));
|
||||
break;
|
||||
case usio_btn::card_tapping:
|
||||
if (value.pressed)
|
||||
@@ -292,10 +295,8 @@ void usb_device_usio::translate_input_taiko()
|
||||
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
|
||||
translate_from_pad(i, i);
|
||||
|
||||
std::memcpy(input_buf.data(), &digital_input, sizeof(u16));
|
||||
std::memcpy(input_buf.data() + 16, &m_io_status[0].coin_counter, sizeof(u16));
|
||||
|
||||
response = std::move(input_buf);
|
||||
std::memcpy(response.data(), &digital_input, sizeof(u16));
|
||||
std::memcpy(response.data() + 16, &m_io_status[0].coin_counter, sizeof(u16));
|
||||
}
|
||||
|
||||
void usb_device_usio::translate_input_tekken()
|
||||
@@ -303,45 +304,54 @@ void usb_device_usio::translate_input_tekken()
|
||||
std::lock_guard lock(pad::g_pad_mutex);
|
||||
const auto handler = pad::get_pad_thread();
|
||||
|
||||
std::vector<u8> input_buf(0x180);
|
||||
response.assign(0x180, 0);
|
||||
le_t<u64> digital_input[2]{};
|
||||
le_t<u16> digital_input_lm = 0;
|
||||
|
||||
const auto translate_from_pad = [&](usz pad_number, usz player)
|
||||
{
|
||||
const usz shift = (player % 2) * 24ULL;
|
||||
auto& status = m_io_status[player / 2];
|
||||
auto& input = digital_input[player / 2];
|
||||
auto& mouse_handler = g_fxo->get<MouseHandlerBase>();
|
||||
std::lock_guard mouse_lock(mouse_handler.mutex);
|
||||
mouse_handler.Init(static_cast<u32>(g_cfg_usio.players.size()));
|
||||
|
||||
if (const auto& pad = ::at32(handler->GetPads(), pad_number); pad->is_connected() && !pad->is_copilot() && is_input_allowed())
|
||||
const auto translate_input = [&](usz player)
|
||||
{
|
||||
if (!is_input_allowed())
|
||||
{
|
||||
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
|
||||
cfg->handle_input(pad, false, [&](const auto& value, bool& /*abort*/)
|
||||
return;
|
||||
}
|
||||
|
||||
const usz player_index = player % 2;
|
||||
const usz shift = (player_index) * 24ULL;
|
||||
const usz io_index = player / 2;
|
||||
auto& status = m_io_status[io_index];
|
||||
auto& input = digital_input[io_index];
|
||||
const auto& cfg = ::at32(g_cfg_usio.players, player);
|
||||
|
||||
const auto input_callback = [&](const auto& value, bool& /*abort*/)
|
||||
{
|
||||
switch (value.btn)
|
||||
{
|
||||
case usio_btn::test:
|
||||
if (player % 2 != 0)
|
||||
if (player_index != 0)
|
||||
break;
|
||||
if (value.pressed && !status.test_key_pressed) // Solve the need to hold the Test button
|
||||
status.test_on = !status.test_on;
|
||||
status.test_key_pressed = value.pressed;
|
||||
break;
|
||||
case usio_btn::coin:
|
||||
if (player % 2 != 0)
|
||||
if (player_index != 0)
|
||||
break;
|
||||
if (value.pressed && !status.coin_key_pressed) // Ensure only one coin is inserted each time the Coin button is pressed
|
||||
status.coin_counter++;
|
||||
status.coin_key_pressed = value.pressed;
|
||||
break;
|
||||
case usio_btn::service:
|
||||
if (player % 2 == 0 && value.pressed)
|
||||
if (player_index == 0 && value.pressed)
|
||||
input |= 0x4000;
|
||||
break;
|
||||
case usio_btn::enter:
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x800000ULL << shift;
|
||||
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x200ULL : 0x800000ULL) << shift;
|
||||
if (player == 0)
|
||||
digital_input_lm |= 0x800;
|
||||
}
|
||||
@@ -349,7 +359,7 @@ void usb_device_usio::translate_input_tekken()
|
||||
case usio_btn::up:
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x200000ULL << shift;
|
||||
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x2000ULL : 0x200000ULL) << shift;
|
||||
if (player == 0)
|
||||
digital_input_lm |= 0x200;
|
||||
}
|
||||
@@ -357,7 +367,7 @@ void usb_device_usio::translate_input_tekken()
|
||||
case usio_btn::down:
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x100000ULL << shift;
|
||||
input |= (g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? 0x1000ULL : 0x100000ULL) << shift;
|
||||
if (player == 0)
|
||||
digital_input_lm |= 0x400;
|
||||
}
|
||||
@@ -365,7 +375,7 @@ void usb_device_usio::translate_input_tekken()
|
||||
case usio_btn::left:
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x80000ULL << shift;
|
||||
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x800000ULL : 0x100000ULL) : (0x80000ULL << shift);
|
||||
if (player == 0)
|
||||
digital_input_lm |= 0x2000;
|
||||
}
|
||||
@@ -373,12 +383,12 @@ void usb_device_usio::translate_input_tekken()
|
||||
case usio_btn::right:
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x40000ULL << shift;
|
||||
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x400000ULL : 0x80000ULL) : (0x40000ULL << shift);
|
||||
if (player == 0)
|
||||
digital_input_lm |= 0x4000;
|
||||
}
|
||||
break;
|
||||
case usio_btn::tekken_button1:
|
||||
case usio_btn::tekken_button1: // or "2D / 3D Switch" button for shooter games
|
||||
if (value.pressed)
|
||||
{
|
||||
input |= 0x20000ULL << shift;
|
||||
@@ -386,9 +396,9 @@ void usb_device_usio::translate_input_tekken()
|
||||
digital_input_lm |= 0x100;
|
||||
}
|
||||
break;
|
||||
case usio_btn::tekken_button2:
|
||||
case usio_btn::tekken_button2: // or "Start" button for shooter games
|
||||
if (value.pressed)
|
||||
input |= 0x10000ULL << shift;
|
||||
input |= g_cfg.io.usio_mode == usio_handler_mode::shooter_games ? (player_index == 0 ? 0x200000ULL : 0x40000ULL) : (0x10000ULL << shift);
|
||||
break;
|
||||
case usio_btn::tekken_button3:
|
||||
if (value.pressed)
|
||||
@@ -409,10 +419,57 @@ void usb_device_usio::translate_input_tekken()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const auto write_gun_data = [&](u16 x, u16 y)
|
||||
{
|
||||
const le_t<u16> positions[2] = {x, y};
|
||||
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 32 + player_index * sizeof(u32), &positions, sizeof(u32));
|
||||
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 41 + player_index * sizeof(s16), &status.vital_sensors[player_index], sizeof(s8));
|
||||
std::memcpy(response.data() - io_index * 0x80 + 0x100 + 48, &status.wheel_rotation, sizeof(s8));
|
||||
};
|
||||
|
||||
if (const auto& pad = ::at32(handler->GetPads(), player); pad->is_connected() && !pad->is_copilot())
|
||||
{
|
||||
cfg->handle_input(pad, false, input_callback);
|
||||
|
||||
const bool is_ctrl_ls = cfg->gun == pad_button::left_stick;
|
||||
const bool is_ctrl_rs = cfg->gun == pad_button::right_stick;
|
||||
if (is_ctrl_ls || is_ctrl_rs)
|
||||
{
|
||||
const s8 angle = pad->m_angles[is_ctrl_ls ? 0 : 1];
|
||||
const s8 angle_delta = angle - status.vital_sensors[player_index];
|
||||
status.vital_sensors[player_index] = angle;
|
||||
status.wheel_rotation += angle_delta; // Multiple players can collaborate on turning the wheel
|
||||
|
||||
write_gun_data(static_cast<u16>(pad->m_sticks[is_ctrl_ls ? 0 : 2].m_value * USHRT_MAX / 0xff), static_cast<u16>(pad->m_sticks[is_ctrl_ls ? 1 : 3].m_value * USHRT_MAX / 0xff));
|
||||
}
|
||||
}
|
||||
|
||||
const usz mouse_index = g_cfg.io.mouse == mouse_handler::basic ? 0 : player;
|
||||
if (mouse_index < mouse_handler.GetMice().size())
|
||||
{
|
||||
const Mouse& mouse_data = ::at32(mouse_handler.GetMice(), mouse_index);
|
||||
cfg->handle_input(mouse_data, input_callback);
|
||||
|
||||
if (cfg->gun == pad_button::mouse)
|
||||
{
|
||||
s8 current_wheel = 0;
|
||||
MouseDataList& data_list = mouse_handler.GetDataList(mouse_index);
|
||||
if (!data_list.empty())
|
||||
{
|
||||
const MouseData& current_data = data_list.front();
|
||||
current_wheel = current_data.wheel * 0x10;
|
||||
data_list.pop_front();
|
||||
}
|
||||
status.vital_sensors[player_index] += current_wheel;
|
||||
status.wheel_rotation += current_wheel; // Multiple players can collaborate on turning the wheel
|
||||
|
||||
write_gun_data(static_cast<u16>(mouse_data.x_pos * USHRT_MAX), static_cast<u16>(mouse_data.y_pos * USHRT_MAX));
|
||||
}
|
||||
}
|
||||
|
||||
if (player % 2 == 0 && status.test_on)
|
||||
if (player_index == 0 && status.test_on)
|
||||
{
|
||||
input |= 0x80;
|
||||
if (player == 0)
|
||||
@@ -423,19 +480,19 @@ void usb_device_usio::translate_input_tekken()
|
||||
for (usz i = 0; i < m_io_status.size(); i++)
|
||||
m_io_status[i].card_tapped = false;
|
||||
for (usz i = 0; i < g_cfg_usio.players.size(); i++)
|
||||
translate_from_pad(i, i);
|
||||
{
|
||||
translate_input(i);
|
||||
}
|
||||
|
||||
for (usz i = 0; i < 2; i++)
|
||||
{
|
||||
std::memcpy(input_buf.data() - i * 0x80 + 0x100, &digital_input[i], sizeof(u64));
|
||||
std::memcpy(input_buf.data() - i * 0x80 + 0x100 + 0x10, &m_io_status[i].coin_counter, sizeof(u16));
|
||||
std::memcpy(response.data() - i * 0x80 + 0x100, &digital_input[i], sizeof(u64));
|
||||
std::memcpy(response.data() - i * 0x80 + 0x100 + 0x10, &m_io_status[i].coin_counter, sizeof(u16));
|
||||
}
|
||||
|
||||
std::memcpy(input_buf.data(), &digital_input_lm, sizeof(u16));
|
||||
std::memcpy(response.data(), &digital_input_lm, sizeof(u16));
|
||||
|
||||
input_buf[2] = 0b00010000; // DIP switches, 8 in total
|
||||
|
||||
response = std::move(input_buf);
|
||||
response[2] = 0b00010000; // DIP switches, 8 in total
|
||||
}
|
||||
|
||||
void usb_device_usio::emulate_card_reader(std::vector<u8>& buf, u16 reg)
|
||||
@@ -475,8 +532,8 @@ void usb_device_usio::emulate_card_reader(std::vector<u8>& buf, u16 reg)
|
||||
case 0x7800:
|
||||
{
|
||||
reader_index = reg == 0x7000 ? 0 : 1;
|
||||
buf = std::move(pending_response[reader_index]);
|
||||
pending_response[reader_index].clear(); // Ensure its empty state after being moved
|
||||
buf.assign(pending_response[reader_index].begin(), pending_response[reader_index].end());
|
||||
pending_response[reader_index].clear();
|
||||
break;
|
||||
}
|
||||
case 0x7400:
|
||||
|
||||
@@ -39,6 +39,8 @@ private:
|
||||
bool card_tapped = false;
|
||||
le_t<u16> coin_counter = 0;
|
||||
usz card_index = 0;
|
||||
s8 vital_sensors[2] = {0, 0};
|
||||
s8 wheel_rotation = 0;
|
||||
};
|
||||
|
||||
std::array<io_status, 2> m_io_status;
|
||||
|
||||
@@ -23,6 +23,8 @@ enum class usio_btn
|
||||
tekken_button5,
|
||||
card_tapping,
|
||||
|
||||
gun,
|
||||
|
||||
count
|
||||
};
|
||||
|
||||
@@ -31,8 +33,8 @@ struct cfg_usio final : public emulated_pad_config<usio_btn>
|
||||
cfg_usio(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
|
||||
cfg_pad_btn<usio_btn> test{ this, "Test", usio_btn::test, pad_button::select };
|
||||
cfg_pad_btn<usio_btn> coin{ this, "Coin", usio_btn::coin, pad_button::L3 };
|
||||
cfg_pad_btn<usio_btn> service{this, "Service", usio_btn::service, pad_button::R3};
|
||||
cfg_pad_btn<usio_btn> coin{ this, "Coin", usio_btn::coin, pad_button::L2 };
|
||||
cfg_pad_btn<usio_btn> service{ this, "Service", usio_btn::service, pad_button::R2 };
|
||||
cfg_pad_btn<usio_btn> enter{ this, "Enter/Start", usio_btn::enter, pad_button::start };
|
||||
cfg_pad_btn<usio_btn> up{ this, "Up", usio_btn::up, pad_button::dpad_up };
|
||||
cfg_pad_btn<usio_btn> down{ this, "Down", usio_btn::down, pad_button::dpad_down };
|
||||
@@ -42,12 +44,13 @@ struct cfg_usio final : public emulated_pad_config<usio_btn>
|
||||
cfg_pad_btn<usio_btn> taiko_hit_side_right{ this, "Taiko Hit Side Right", usio_btn::taiko_hit_side_right, pad_button::circle };
|
||||
cfg_pad_btn<usio_btn> taiko_hit_center_left{ this, "Taiko Hit Center Left", usio_btn::taiko_hit_center_left, pad_button::triangle };
|
||||
cfg_pad_btn<usio_btn> taiko_hit_center_right{ this, "Taiko Hit Center Right", usio_btn::taiko_hit_center_right, pad_button::cross };
|
||||
cfg_pad_btn<usio_btn> tekken_button1{this, "Tekken Button 1", usio_btn::tekken_button1, pad_button::square};
|
||||
cfg_pad_btn<usio_btn> tekken_button2{this, "Tekken Button 2", usio_btn::tekken_button2, pad_button::triangle};
|
||||
cfg_pad_btn<usio_btn> tekken_button3{this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross};
|
||||
cfg_pad_btn<usio_btn> tekken_button4{this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle};
|
||||
cfg_pad_btn<usio_btn> tekken_button5{this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1};
|
||||
cfg_pad_btn<usio_btn> card_tapping{this, "Card Tapping", usio_btn::card_tapping, pad_button::L1};
|
||||
cfg_pad_btn<usio_btn> tekken_button1{ this, "Tekken Button 1", usio_btn::tekken_button1, pad_button::square};
|
||||
cfg_pad_btn<usio_btn> tekken_button2{ this, "Tekken Button 2", usio_btn::tekken_button2, pad_button::triangle };
|
||||
cfg_pad_btn<usio_btn> tekken_button3{ this, "Tekken Button 3", usio_btn::tekken_button3, pad_button::cross };
|
||||
cfg_pad_btn<usio_btn> tekken_button4{ this, "Tekken Button 4", usio_btn::tekken_button4, pad_button::circle };
|
||||
cfg_pad_btn<usio_btn> tekken_button5{ this, "Tekken Button 5", usio_btn::tekken_button5, pad_button::R1 };
|
||||
cfg_pad_btn<usio_btn> card_tapping{ this, "Card Tapping", usio_btn::card_tapping, pad_button::L1 };
|
||||
cfg_pad_btn<usio_btn> gun{ this, "Gun", usio_btn::gun, pad_button::mouse };
|
||||
};
|
||||
|
||||
struct cfg_usios final : public emulated_pads_config<cfg_usio, 4>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "stdafx.h"
|
||||
#include "wiimote_config.h"
|
||||
#include "Utilities/File.h"
|
||||
|
||||
LOG_CHANNEL(wiimote_log, "Wiimote");
|
||||
|
||||
template <>
|
||||
void fmt_class_string<wiimote_button>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](wiimote_button value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case wiimote_button::None: return "None";
|
||||
case wiimote_button::Left: return "Left";
|
||||
case wiimote_button::Right: return "Right";
|
||||
case wiimote_button::Down: return "Down";
|
||||
case wiimote_button::Up: return "Up";
|
||||
case wiimote_button::Plus: return "Plus";
|
||||
case wiimote_button::Two: return "Two";
|
||||
case wiimote_button::One: return "One";
|
||||
case wiimote_button::B: return "B";
|
||||
case wiimote_button::A: return "A";
|
||||
case wiimote_button::Minus: return "Minus";
|
||||
case wiimote_button::Home: return "Home";
|
||||
}
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
cfg_wiimote& get_wiimote_config()
|
||||
{
|
||||
static cfg_wiimote instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
cfg_wiimote::cfg_wiimote()
|
||||
: cfg::node()
|
||||
, path(fs::get_config_dir(true) + "wiimote.yml")
|
||||
{
|
||||
}
|
||||
|
||||
bool cfg_wiimote::load()
|
||||
{
|
||||
if (fs::file f{path, fs::read})
|
||||
{
|
||||
return cfg::node::from_string(f.to_string());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cfg_wiimote::save() const
|
||||
{
|
||||
if (!cfg::node::save(path))
|
||||
{
|
||||
wiimote_log.error("Failed to save wiimote config to '%s'", path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/Config.h"
|
||||
#include "Input/wiimote_types.h"
|
||||
|
||||
struct cfg_wiimote : cfg::node
|
||||
{
|
||||
cfg_wiimote();
|
||||
bool load();
|
||||
void save() const;
|
||||
|
||||
cfg::_bool use_for_guncon{ this, "UseForGunCon", true };
|
||||
|
||||
struct node_guncon_mapping : cfg::node
|
||||
{
|
||||
node_guncon_mapping(cfg::node* _parent) : cfg::node(_parent, "GunCon Mapping") {}
|
||||
|
||||
cfg::_enum<wiimote_button> trigger{ this, "Trigger", wiimote_button::B };
|
||||
cfg::_enum<wiimote_button> a1{ this, "A1", wiimote_button::A };
|
||||
cfg::_enum<wiimote_button> a2{ this, "A2", wiimote_button::Minus };
|
||||
cfg::_enum<wiimote_button> a3{ this, "A3", wiimote_button::Left };
|
||||
cfg::_enum<wiimote_button> b1{ this, "B1", wiimote_button::One };
|
||||
cfg::_enum<wiimote_button> b2{ this, "B2", wiimote_button::Two };
|
||||
cfg::_enum<wiimote_button> b3{ this, "B3", wiimote_button::Home };
|
||||
cfg::_enum<wiimote_button> c1{ this, "C1", wiimote_button::Plus };
|
||||
cfg::_enum<wiimote_button> c2{ this, "C2", wiimote_button::Right };
|
||||
|
||||
cfg::_enum<wiimote_button> b1_alt{ this, "B1_Alt", wiimote_button::Up };
|
||||
cfg::_enum<wiimote_button> b2_alt{ this, "B2_Alt", wiimote_button::Down };
|
||||
} guncon_mapping{ this };
|
||||
|
||||
const std::string path;
|
||||
};
|
||||
|
||||
cfg_wiimote& get_wiimote_config();
|
||||
+52
-42
@@ -1185,6 +1185,39 @@ namespace vm
|
||||
|
||||
bool block_t::try_alloc(u32 addr, u64 bflags, u32 size, std::shared_ptr<utils::shm>&& shm) const
|
||||
{
|
||||
if (!size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this->flags & stack_guarded)
|
||||
{
|
||||
ensure(this->size >= 0x100000);
|
||||
|
||||
if (addr < this->addr + 4096 || addr + size - 1 >= this->addr + this->size - 4096 - 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Protect guard pages
|
||||
if (g_pages[addr / 4096 - 1] || g_pages[addr / 4096 + size / 4096])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Guard pages must be consecutive for non-edge allocations
|
||||
if (addr > this->addr + 4096 && g_pages[addr / 4096 - 2])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Same
|
||||
if (size + addr < this->addr + this->size - 4096 && g_pages[addr / 4096 + size / 4096 + 1])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if memory area is already mapped
|
||||
for (u32 i = addr / 4096; i <= (addr + size - 1) / 4096; i++)
|
||||
{
|
||||
@@ -1194,9 +1227,6 @@ namespace vm
|
||||
}
|
||||
}
|
||||
|
||||
const u32 page_addr = addr + (this->flags & stack_guarded ? 0x1000 : 0);
|
||||
const u32 page_size = size - (this->flags & stack_guarded ? 0x2000 : 0);
|
||||
|
||||
// No flags are default to readable/writable
|
||||
// Explicit (un...) flags are used to protect from such access
|
||||
u8 flags = 0;
|
||||
@@ -1225,15 +1255,8 @@ namespace vm
|
||||
flags |= page_1m_size;
|
||||
}
|
||||
|
||||
if (this->flags & stack_guarded)
|
||||
{
|
||||
// Mark overflow/underflow guard pages as allocated
|
||||
ensure(!g_pages[addr / 4096].exchange(page_allocated));
|
||||
ensure(!g_pages[addr / 4096 + size / 4096 - 1].exchange(page_allocated));
|
||||
}
|
||||
|
||||
// Map "real" memory pages; provide a function to search for mirrors with private member access
|
||||
_page_map(page_addr, flags, page_size, shm.get(), this->flags, [](vm::block_t* _this, utils::shm* shm)
|
||||
_page_map(addr, flags, size, shm.get(), this->flags, [](vm::block_t* _this, utils::shm* shm)
|
||||
{
|
||||
auto& map = (_this->m.*block_map)();
|
||||
|
||||
@@ -1260,23 +1283,15 @@ namespace vm
|
||||
// Fill stack guards with STACKGRD
|
||||
if (this->flags & stack_guarded)
|
||||
{
|
||||
auto fill64 = [](u8* ptr, u64 data, usz count)
|
||||
const auto fill64 = [](u8* ptr, u64 data, usz count)
|
||||
{
|
||||
#if defined(_M_X64) && defined(_MSC_VER)
|
||||
__stosq(reinterpret_cast<u64*>(ptr), data, count);
|
||||
#elif defined(ARCH_X64)
|
||||
__asm__ ("mov %0, %%rdi; mov %1, %%rax; mov %2, %%rcx; rep stosq;"
|
||||
:
|
||||
: "r" (ptr), "r" (data), "r" (count)
|
||||
: "rdi", "rax", "rcx", "memory");
|
||||
#else
|
||||
for (usz i = 0; i < count; i++)
|
||||
reinterpret_cast<u64*>(ptr)[i] = data;
|
||||
#endif
|
||||
u64* dst = reinterpret_cast<u64*>(ptr);
|
||||
std::fill(dst, dst + count, data);
|
||||
};
|
||||
|
||||
const u32 enda = addr + size - 4096;
|
||||
fill64(g_sudo_addr + addr, "STACKGRD"_u64, 4096 / sizeof(u64));
|
||||
const u32 begina = addr - 4096;
|
||||
const u32 enda = addr + size;
|
||||
fill64(g_sudo_addr + begina, "STACKGRD"_u64, 4096 / sizeof(u64));
|
||||
fill64(g_sudo_addr + enda, "UNDERFLO"_u64, 4096 / sizeof(u64));
|
||||
}
|
||||
|
||||
@@ -1455,7 +1470,7 @@ namespace vm
|
||||
const u32 min_page_size = flags & block_size_4k ? 0x1000 : 0x10000;
|
||||
|
||||
// Align to minimal page size
|
||||
const u32 size = utils::align(orig_size, min_page_size) + (flags & stack_guarded ? 0x2000 : 0);
|
||||
const u32 size = utils::align(orig_size, min_page_size);
|
||||
|
||||
// Check alignment (it's page allocation, so passing small values there is just silly)
|
||||
if (align < min_page_size || align != (0x80000000u >> std::countl_zero(align)))
|
||||
@@ -1503,7 +1518,7 @@ namespace vm
|
||||
{
|
||||
if (try_alloc(addr, flags, size, std::move(shm)))
|
||||
{
|
||||
return addr + (flags & stack_guarded ? 0x1000 : 0);
|
||||
return addr;
|
||||
}
|
||||
|
||||
if (addr == max)
|
||||
@@ -1598,7 +1613,7 @@ namespace vm
|
||||
|
||||
vm::writer_lock lock;
|
||||
|
||||
const auto found = m_map.find(addr - (flags & stack_guarded ? 0x1000 : 0));
|
||||
const auto found = m_map.find(addr);
|
||||
|
||||
if (found == m_map.end())
|
||||
{
|
||||
@@ -1611,14 +1626,7 @@ namespace vm
|
||||
}
|
||||
|
||||
// Get allocation size
|
||||
const auto size = found->second.first - (flags & stack_guarded ? 0x2000 : 0);
|
||||
|
||||
if (flags & stack_guarded)
|
||||
{
|
||||
// Clear guard pages
|
||||
ensure(g_pages[addr / 4096 - 1].exchange(0) == page_allocated);
|
||||
ensure(g_pages[addr / 4096 + size / 4096].exchange(0) == page_allocated);
|
||||
}
|
||||
const auto size = found->second.first;
|
||||
|
||||
// Unmap "real" memory pages
|
||||
ensure(size == _page_unmap(addr, size, this->flags, found->second.second.get(), unmap_notification.event_data));
|
||||
@@ -1684,7 +1692,7 @@ namespace vm
|
||||
|
||||
for (auto& entry : (m.*block_map)())
|
||||
{
|
||||
result += entry.second.first - (flags & stack_guarded ? 0x2000 : 0);
|
||||
result += entry.second.first;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1834,7 +1842,7 @@ namespace vm
|
||||
for (const auto& [addr, shm] : m_map)
|
||||
{
|
||||
// Assume first page flags represent all the map
|
||||
ar(g_pages[addr / 4096 + !!(flags & stack_guarded)]);
|
||||
ar(g_pages[addr / 4096]);
|
||||
|
||||
ar(addr);
|
||||
ar(shm.first);
|
||||
@@ -1851,8 +1859,7 @@ namespace vm
|
||||
}
|
||||
|
||||
// Save raw binary image
|
||||
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
|
||||
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr + guard_size), shm.first - guard_size * 2);
|
||||
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr), shm.first);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1918,6 +1925,10 @@ namespace vm
|
||||
{
|
||||
pflags |= block_size_1m;
|
||||
}
|
||||
else
|
||||
{
|
||||
pflags |= block_size_4k;
|
||||
}
|
||||
|
||||
// Map the memory through the same method as alloc() and falloc()
|
||||
// Copy the shared handle unconditionally
|
||||
@@ -1926,8 +1937,7 @@ namespace vm
|
||||
if (flags & preallocated)
|
||||
{
|
||||
// Load binary image
|
||||
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
|
||||
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr0 + guard_size), size0 - guard_size * 2);
|
||||
serialize_memory_bytes(ar, vm::get_super_ptr<u8>(addr0), size0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,12 @@
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#else
|
||||
#include <MSWSock.h>
|
||||
|
||||
#ifndef SIO_UDP_CONNRESET
|
||||
#define SIO_UDP_CONNRESET _WSAIOW(IOC_VENDOR, 12)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(IPv6_log, "IPv6_layer");
|
||||
@@ -294,6 +300,19 @@ namespace np
|
||||
::ioctlsocket(socket, FIONBIO, &_true);
|
||||
#else
|
||||
::fcntl(socket, F_SETFL, ::fcntl(socket, F_GETFL, 0) | O_NONBLOCK);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void set_dgram_socket_disable_connreset(socket_type socket)
|
||||
{
|
||||
if (socket)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
DWORD enable = FALSE;
|
||||
DWORD bytes_returned = 0;
|
||||
|
||||
WSAIoctl(socket, SIO_UDP_CONNRESET, &enable, sizeof(enable), nullptr, 0, &bytes_returned, nullptr, nullptr);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,4 +82,5 @@ namespace np
|
||||
|
||||
void close_socket(socket_type socket);
|
||||
void set_socket_non_blocking(socket_type socket);
|
||||
void set_dgram_socket_disable_connreset(socket_type socket);
|
||||
} // namespace np
|
||||
|
||||
@@ -229,6 +229,30 @@ namespace np
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 cast_type = message_info->casttype().value();
|
||||
const int dst_size = message_info->dst_size();
|
||||
bool valid_destination = false;
|
||||
|
||||
switch (cast_type)
|
||||
{
|
||||
case SCE_NP_MATCHING2_CASTTYPE_BROADCAST:
|
||||
valid_destination = dst_size == 0;
|
||||
break;
|
||||
case SCE_NP_MATCHING2_CASTTYPE_UNICAST:
|
||||
case SCE_NP_MATCHING2_CASTTYPE_MULTICAST_TEAM:
|
||||
valid_destination = dst_size == 1;
|
||||
break;
|
||||
case SCE_NP_MATCHING2_CASTTYPE_MULTICAST:
|
||||
valid_destination = dst_size > 0 && dst_size <= SCE_NP_MATCHING2_ROOM_MAX_SLOT;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!valid_destination || message_info->msg().size() > SCE_NP_MATCHING2_BIN_MSG_MAX_SIZE)
|
||||
{
|
||||
rpcn_log.error("Received malformed RoomMessageReceived notification");
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 event_key = get_event_key();
|
||||
auto [include_onlinename, include_avatarurl] = get_match2_context_options(room_event_cb_ctx);
|
||||
|
||||
|
||||
@@ -1635,6 +1635,7 @@ namespace np
|
||||
cur_status->hasData = 1;
|
||||
cur_status->lastChangedDate.tick = cur_pb_status.lastchangeddate();
|
||||
string_to_npid(cur_pb_status.lastchangedauthorid(), cur_status->lastChangedAuthorId);
|
||||
ensure(cur_pb_status.info().size() <= SCE_NP_TUS_DATA_INFO_MAX_SIZE);
|
||||
cur_status->info.infoSize = ::narrow<u32>(cur_pb_status.info().size());
|
||||
memcpy(cur_status->info.data, cur_pb_status.info().data(), cur_pb_status.info().size());
|
||||
}
|
||||
@@ -1872,8 +1873,9 @@ namespace np
|
||||
string_to_npid(pb_status.lastchangedauthorid(), data_status->lastChangedAuthorId);
|
||||
data_status->data = tdata->data;
|
||||
data_status->dataSize = ::narrow<u32>(pb_data->data().size());
|
||||
ensure(pb_status.info().size() <= SCE_NP_TUS_DATA_INFO_MAX_SIZE);
|
||||
data_status->info.infoSize = ::narrow<u32>(pb_status.info().size());
|
||||
memcpy(data_status->info.data, pb_data->status().info().data(), std::min(pb_data->status().info().size(), sizeof(data_status->info.data)));
|
||||
memcpy(data_status->info.data, pb_status.info().data(), pb_status.info().size());
|
||||
|
||||
const u32 to_copy = std::min<u32>(data_status->dataSize, tdata->recvSize);
|
||||
memcpy(data, pb_data->data().data(), to_copy);
|
||||
|
||||
@@ -3249,10 +3249,19 @@ namespace rpcn
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 main_type = pb_mdata->maintype().value();
|
||||
const u32 sub_type = pb_mdata->subtype().value();
|
||||
|
||||
if (main_type > std::numeric_limits<u16>::max() || sub_type > std::numeric_limits<u16>::max())
|
||||
{
|
||||
rpcn_log.warning("Discarded message with invalid type! (main_type=%u, sub_type=%u)", main_type, sub_type);
|
||||
return;
|
||||
}
|
||||
|
||||
message_data mdata = {
|
||||
.msgId = message_counter,
|
||||
.mainType = ::narrow<u16>(pb_mdata->maintype().value()),
|
||||
.subType = ::narrow<u16>(pb_mdata->subtype().value()),
|
||||
.mainType = static_cast<u16>(main_type),
|
||||
.subType = static_cast<u16>(sub_type),
|
||||
.msgFeatures = pb_mdata->msgfeatures(),
|
||||
.subject = pb_mdata->subject(),
|
||||
.body = pb_mdata->body()};
|
||||
|
||||
@@ -280,8 +280,6 @@ void signaling_handler::process_incoming_messages()
|
||||
}
|
||||
|
||||
const auto now = steady_clock::now();
|
||||
if (si)
|
||||
si->time_last_msg_recvd = now;
|
||||
|
||||
const auto setup_ping = [&]()
|
||||
{
|
||||
@@ -299,10 +297,16 @@ void signaling_handler::process_incoming_messages()
|
||||
queue_signaling_packet(sent_packet, si, now + REPEAT_PING_DELAY);
|
||||
};
|
||||
|
||||
const auto update_rtt = [&](u64 rtt_timestamp)
|
||||
const auto update_rtt = [&](u64 rtt_timestamp) -> bool
|
||||
{
|
||||
u64 timestamp_now = get_micro_timestamp(now);
|
||||
u64 rtt = timestamp_now - rtt_timestamp;
|
||||
const u64 timestamp_now = get_micro_timestamp(now);
|
||||
if (rtt_timestamp > timestamp_now || timestamp_now - rtt_timestamp > std::numeric_limits<u32>::max())
|
||||
{
|
||||
sign_log.error("Received a signaling packet with an invalid RTT timestamp");
|
||||
return false;
|
||||
}
|
||||
|
||||
const u64 rtt = timestamp_now - rtt_timestamp;
|
||||
si->last_rtts[(si->rtt_counters % 6)] = rtt;
|
||||
si->rtt_counters++;
|
||||
|
||||
@@ -313,7 +317,8 @@ void signaling_handler::process_incoming_messages()
|
||||
sum += si->last_rtts[index];
|
||||
}
|
||||
|
||||
si->rtt = ::narrow<u32>(sum / num_rtts);
|
||||
si->rtt = static_cast<u32>(sum / num_rtts);
|
||||
return true;
|
||||
};
|
||||
|
||||
switch (sp->command)
|
||||
@@ -325,7 +330,8 @@ void signaling_handler::process_incoming_messages()
|
||||
sent_packet.timestamp_sender = sp->timestamp_sender;
|
||||
break;
|
||||
case signal_pong:
|
||||
update_rtt(sp->timestamp_sender);
|
||||
if (!update_rtt(sp->timestamp_sender))
|
||||
continue;
|
||||
reply = false;
|
||||
schedule_repeat = false;
|
||||
reschedule_packet(si, signal_ping, now + 10s);
|
||||
@@ -344,7 +350,8 @@ void signaling_handler::process_incoming_messages()
|
||||
update_si_addr(si, op_addr, op_port);
|
||||
break;
|
||||
case signal_connect_ack:
|
||||
update_rtt(sp->timestamp_sender);
|
||||
if (!update_rtt(sp->timestamp_sender))
|
||||
continue;
|
||||
reply = true;
|
||||
schedule_repeat = false;
|
||||
setup_ping();
|
||||
@@ -356,7 +363,8 @@ void signaling_handler::process_incoming_messages()
|
||||
update_si_status(si, SCE_NP_SIGNALING_CONN_STATUS_ACTIVE, CELL_OK);
|
||||
break;
|
||||
case signal_confirm:
|
||||
update_rtt(sp->timestamp_receiver);
|
||||
if (!update_rtt(sp->timestamp_receiver))
|
||||
continue;
|
||||
reply = false;
|
||||
schedule_repeat = false;
|
||||
setup_ping();
|
||||
@@ -381,6 +389,9 @@ void signaling_handler::process_incoming_messages()
|
||||
default: sign_log.error("Invalid signaling command received"); continue;
|
||||
}
|
||||
|
||||
if (si)
|
||||
si->time_last_msg_recvd = now;
|
||||
|
||||
if (reply)
|
||||
{
|
||||
send_signaling_packet(sent_packet, op_addr, op_port);
|
||||
@@ -427,7 +438,7 @@ void signaling_handler::operator()()
|
||||
auto retire_info = sig.sig_info;
|
||||
sign_log.notice("Timeout disconnection");
|
||||
update_si_status(retire_info, SCE_NP_SIGNALING_CONN_STATUS_INACTIVE, SCE_NP_SIGNALING_ERROR_TIMEOUT);
|
||||
retire_packet(retire_info, signal_ping); // Retire ping packet if necessary
|
||||
retire_all_packets(retire_info); // A connect_ack and/or ping may still be in queue
|
||||
break; // qpackets has been emptied of all packets for this user so we're requeuing
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
#include "BufferUtils_avx512.h"
|
||||
#elif defined(ARCH_ARM64)
|
||||
#include "BufferUtils_neon.h"
|
||||
#endif
|
||||
|
||||
#if !defined(_MSC_VER)
|
||||
@@ -21,6 +23,9 @@
|
||||
#endif
|
||||
#undef FORCE_INLINE
|
||||
#include "Emu/CPU/sse2neon.h"
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#include "BufferUtils_sve.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER) || !defined(__SSE2__)
|
||||
@@ -68,6 +73,10 @@
|
||||
const v128 s_bswap_u32_mask = v128::from32(0x00010203, 0x04050607, 0x08090a0b, 0x0c0d0e0f);
|
||||
const v128 s_bswap_u16_mask = v128::from32(0x02030001, 0x06070405, 0x0a0b0809, 0x0e0f0c0d);
|
||||
|
||||
#if defined(ARCH_X64) || defined(ARCH_ARM64)
|
||||
#include "BufferUtils_compact.h"
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
{
|
||||
template <typename T, typename U>
|
||||
@@ -183,6 +192,9 @@ namespace
|
||||
#if defined(ARCH_X64)
|
||||
DECLARE(copy_data_swap_u32) = build_function_asm<void(*)(u32*, const u32*, u32), asmjit::simd_builder>("copy_data_swap_u32", &build_copy_data_swap_u32<false>);
|
||||
DECLARE(copy_data_swap_u32_cmp) = build_function_asm<bool(*)(u32*, const u32*, u32), asmjit::simd_builder>("copy_data_swap_u32_cmp", &build_copy_data_swap_u32<true>);
|
||||
#elif defined(ARCH_ARM64)
|
||||
DECLARE(copy_data_swap_u32) = copy_data_swap_u32_neon<false>;
|
||||
DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_neon<true>;
|
||||
#else
|
||||
DECLARE(copy_data_swap_u32) = copy_data_swap_u32_naive<false>;
|
||||
DECLARE(copy_data_swap_u32_cmp) = copy_data_swap_u32_naive<true>;
|
||||
@@ -299,6 +311,8 @@ namespace
|
||||
r = upload_xi16(src.data(), dst.data(), count);
|
||||
else
|
||||
r = upload_xi32(src.data(), dst.data(), count);
|
||||
#elif defined(ARCH_ARM64)
|
||||
r = upload_untouched_neon<T, false>(src.data(), dst.data(), count);
|
||||
#else
|
||||
r = upload_untouched_naive(src.data(), dst.data(), count);
|
||||
#endif
|
||||
@@ -405,6 +419,8 @@ namespace
|
||||
r = upload_xi16(src.data(), dst.data(), count, restart_index);
|
||||
else
|
||||
r = upload_xi32(src.data(), dst.data(), count, restart_index);
|
||||
#elif defined(ARCH_ARM64)
|
||||
r = upload_untouched_neon<T, true>(src.data(), dst.data(), count, restart_index);
|
||||
#else
|
||||
r = upload_untouched_naive(src.data(), dst.data(), count, restart_index);
|
||||
#endif
|
||||
@@ -445,7 +461,7 @@ namespace
|
||||
return std::make_tuple(min_index, max_index, written);
|
||||
}
|
||||
|
||||
const upload_untouched_skip_restart_dispatch s_generic_upload_untouched_skip_restart_dispatch =
|
||||
[[maybe_unused]] const upload_untouched_skip_restart_dispatch s_generic_upload_untouched_skip_restart_dispatch =
|
||||
{
|
||||
upload_untouched_skip_restart<u16>,
|
||||
upload_untouched_skip_restart<u32>,
|
||||
@@ -454,7 +470,7 @@ namespace
|
||||
#if defined(ARCH_X64)
|
||||
const upload_untouched_skip_restart_dispatch s_avx512_upload_untouched_skip_restart_dispatch =
|
||||
{
|
||||
upload_untouched_skip_restart<u16>,
|
||||
upload_swapped_avx2_skip_restart<u16>,
|
||||
upload_u32_swapped_avx3_skip_restart,
|
||||
};
|
||||
|
||||
@@ -477,8 +493,41 @@ namespace
|
||||
{
|
||||
return s_avx512_upload_untouched_skip_restart_dispatch;
|
||||
}
|
||||
|
||||
if (s_use_avx2)
|
||||
{
|
||||
static const upload_untouched_skip_restart_dispatch s_avx2 =
|
||||
{
|
||||
upload_swapped_avx2_skip_restart<u16>,
|
||||
upload_swapped_avx2_skip_restart<u32>,
|
||||
};
|
||||
return s_avx2;
|
||||
}
|
||||
#elif defined(ARCH_ARM64) && (defined(__GNUC__) || defined(__clang__))
|
||||
if (utils::has_sve())
|
||||
{
|
||||
static const upload_untouched_skip_restart_dispatch s_sve =
|
||||
{
|
||||
#ifdef BUFFERUTILS_HAS_SVE2P2
|
||||
utils::has_sve2p2() ? upload_swapped_sve2p2_skip_restart : upload_swapped_neon_skip_restart<u16>,
|
||||
#else
|
||||
upload_swapped_neon_skip_restart<u16>,
|
||||
#endif
|
||||
upload_swapped_sve_skip_restart,
|
||||
};
|
||||
return s_sve;
|
||||
}
|
||||
#endif
|
||||
#if defined(ARCH_ARM64)
|
||||
static const upload_untouched_skip_restart_dispatch s_neon =
|
||||
{
|
||||
upload_swapped_neon_skip_restart<u16>,
|
||||
upload_swapped_neon_skip_restart<u32>,
|
||||
};
|
||||
return s_neon;
|
||||
#else
|
||||
return s_generic_upload_untouched_skip_restart_dispatch;
|
||||
#endif
|
||||
}();
|
||||
|
||||
template<typename T, typename U = remove_be_t<T>>
|
||||
@@ -514,25 +563,6 @@ namespace
|
||||
return primitive_restart_impl::upload_untouched(src, dst, static_cast<U>(primitive_restart_index));
|
||||
}
|
||||
|
||||
void iota16(u16* dst, u32 count)
|
||||
{
|
||||
unsigned i = 0;
|
||||
#if defined(ARCH_X64) || defined(ARCH_ARM64)
|
||||
const unsigned step = 8; // We do 8 entries per step
|
||||
const __m128i vec_step = _mm_set1_epi16(8); // Constant to increment the raw values
|
||||
__m128i values = _mm_set_epi16(7, 6, 5, 4, 3, 2, 1, 0);
|
||||
__m128i* vec_ptr = utils::bless<__m128i>(dst);
|
||||
|
||||
for (; (i + step) <= count; i += step, vec_ptr++)
|
||||
{
|
||||
_mm_stream_si128(vec_ptr, values);
|
||||
values = _mm_add_epi16(values, vec_step);
|
||||
}
|
||||
#endif
|
||||
for (; i < count; ++i)
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
std::tuple<T, T, u32> expand_indexed_triangle_fan(std::span<to_be_t<const T>> src, std::span<T> dst, bool is_primitive_restart_enabled, u32 primitive_restart_index)
|
||||
{
|
||||
@@ -627,6 +657,161 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
namespace
|
||||
{
|
||||
AVX2_FUNC void iota16_avx2(u16* dst, u32 count)
|
||||
{
|
||||
u32 i = 0;
|
||||
|
||||
// Force 32-byte alignment
|
||||
const uptr mem_addr = reinterpret_cast<uptr>(dst);
|
||||
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 31) / sizeof(u16));
|
||||
for (; i < head; ++i)
|
||||
{
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
constexpr u32 step = 32; // We do 32 entries per step
|
||||
const __m256i vec_step = _mm256_set1_epi16(32); // Constant to increment the raw values
|
||||
__m256i values0 = _mm256_add_epi16(_mm256_set_epi16(15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0), _mm256_set1_epi16(static_cast<s16>(i)));
|
||||
__m256i values1 = _mm256_add_epi16(values0, _mm256_set1_epi16(16));
|
||||
__m256i* vec_ptr = utils::bless<__m256i>(dst + i);
|
||||
|
||||
for (; (i + step) <= count; i += step, vec_ptr += 2)
|
||||
{
|
||||
_mm256_store_si256(vec_ptr, values0);
|
||||
_mm256_store_si256(vec_ptr + 1, values1);
|
||||
values0 = _mm256_add_epi16(values0, vec_step);
|
||||
values1 = _mm256_add_epi16(values1, vec_step);
|
||||
}
|
||||
|
||||
// Half-line remainder
|
||||
if ((i + step / 2) <= count)
|
||||
{
|
||||
_mm256_store_si256(vec_ptr, values0);
|
||||
i += step / 2;
|
||||
}
|
||||
|
||||
_mm256_zeroupper();
|
||||
|
||||
for (; i < count; ++i)
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
AVX2_FUNC void iota32_avx2(u32* dst, u32 count)
|
||||
{
|
||||
u32 i = 0;
|
||||
|
||||
// Force 32-byte alignment
|
||||
const uptr mem_addr = reinterpret_cast<uptr>(dst);
|
||||
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 31) / sizeof(u32));
|
||||
for (; i < head; ++i)
|
||||
{
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
constexpr u32 step = 16; // We do 16 entries per step
|
||||
const __m256i vec_step = _mm256_set1_epi32(16); // Constant to increment the raw values
|
||||
__m256i values0 = _mm256_add_epi32(_mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0), _mm256_set1_epi32(i));
|
||||
__m256i values1 = _mm256_add_epi32(values0, _mm256_set1_epi32(8));
|
||||
__m256i* vec_ptr = utils::bless<__m256i>(dst + i);
|
||||
|
||||
for (; (i + step) <= count; i += step, vec_ptr += 2)
|
||||
{
|
||||
_mm256_store_si256(vec_ptr, values0);
|
||||
_mm256_store_si256(vec_ptr + 1, values1);
|
||||
values0 = _mm256_add_epi32(values0, vec_step);
|
||||
values1 = _mm256_add_epi32(values1, vec_step);
|
||||
}
|
||||
|
||||
// Half-line remainder
|
||||
if ((i + step / 2) <= count)
|
||||
{
|
||||
_mm256_store_si256(vec_ptr, values0);
|
||||
i += step / 2;
|
||||
}
|
||||
|
||||
_mm256_zeroupper();
|
||||
|
||||
for (; i < count; ++i)
|
||||
dst[i] = i;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void iota16(u16* dst, u32 count)
|
||||
{
|
||||
#if defined(ARCH_X64)
|
||||
if (s_use_avx2)
|
||||
{
|
||||
iota16_avx2(dst, count);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 i = 0;
|
||||
#if defined(ARCH_X64) || defined(ARCH_ARM64)
|
||||
// Force 16-byte alignment
|
||||
const uptr mem_addr = reinterpret_cast<uptr>(dst);
|
||||
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 15) / sizeof(u16));
|
||||
for (; i < head; ++i)
|
||||
{
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
constexpr u32 step = 8; // We do 8 entries per step
|
||||
const __m128i vec_step = _mm_set1_epi16(8); // Constant to increment the raw values
|
||||
__m128i values = _mm_set_epi16(7, 6, 5, 4, 3, 2, 1, 0);
|
||||
__m128i* vec_ptr = utils::bless<__m128i>(dst + i);
|
||||
|
||||
values = _mm_add_epi16(values, _mm_set1_epi16(static_cast<s16>(i)));
|
||||
for (; (i + step) <= count; i += step, vec_ptr++)
|
||||
{
|
||||
_mm_store_si128(vec_ptr, values);
|
||||
values = _mm_add_epi16(values, vec_step);
|
||||
}
|
||||
#endif
|
||||
for (; i < count; ++i)
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
void iota32(u32* dst, u32 count)
|
||||
{
|
||||
#if defined(ARCH_X64)
|
||||
if (s_use_avx2)
|
||||
{
|
||||
iota32_avx2(dst, count);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
u32 i = 0;
|
||||
#if defined(ARCH_X64) || defined(ARCH_ARM64)
|
||||
// Force 16-byte alignment
|
||||
const uptr mem_addr = reinterpret_cast<uptr>(dst);
|
||||
const u32 head = std::min<u32>(count, ((0 - mem_addr) & 15) / sizeof(u32));
|
||||
for (; i < head; ++i)
|
||||
{
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
constexpr u32 step = 4; // We do 4 entries per step
|
||||
const __m128i vec_step = _mm_set1_epi32(4); // Constant to increment the raw values
|
||||
__m128i values = _mm_set_epi32(3, 2, 1, 0);
|
||||
__m128i* vec_ptr = utils::bless<__m128i>(dst + i);
|
||||
|
||||
values = _mm_add_epi32(values, _mm_set1_epi32(i));
|
||||
for (; (i + step) <= count; i += step, vec_ptr++)
|
||||
{
|
||||
_mm_store_si128(vec_ptr, values);
|
||||
values = _mm_add_epi32(values, vec_step);
|
||||
}
|
||||
#endif
|
||||
for (; i < count; ++i)
|
||||
dst[i] = i;
|
||||
}
|
||||
|
||||
// Only handle quads and triangle fan now
|
||||
bool is_primitive_native(rsx::primitive_type draw_mode)
|
||||
{
|
||||
@@ -697,11 +882,11 @@ u32 get_index_type_size(rsx::index_array_type type)
|
||||
|
||||
void write_index_array_for_non_indexed_non_native_primitive_to_buffer(char* dst, rsx::primitive_type draw_mode, unsigned count)
|
||||
{
|
||||
auto typedDst = reinterpret_cast<u16*>(dst);
|
||||
auto typedDst = reinterpret_cast<u32*>(dst);
|
||||
switch (draw_mode)
|
||||
{
|
||||
case rsx::primitive_type::line_loop:
|
||||
iota16(typedDst, count);
|
||||
iota32(typedDst, count);
|
||||
typedDst[count] = 0;
|
||||
return;
|
||||
case rsx::primitive_type::triangle_fan:
|
||||
|
||||
@@ -43,3 +43,7 @@ extern void(*const copy_data_swap_u32)(u32* dst, const u32* src, u32 count);
|
||||
|
||||
// Copy and swap data in 32-bit units, return true if changed
|
||||
extern bool(*const copy_data_swap_u32_cmp)(u32* dst, const u32* src, u32 count);
|
||||
|
||||
void iota16(u16* dst, u32 count);
|
||||
|
||||
void iota32(u32* dst, u32 count);
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
#pragma once
|
||||
|
||||
namespace
|
||||
{
|
||||
template <usz Bytes, usz Lanes>
|
||||
constexpr auto make_compress_table()
|
||||
{
|
||||
std::array<std::array<u8, 16>, 1u << Lanes> table{};
|
||||
|
||||
for (usz mask = 0; mask < table.size(); ++mask)
|
||||
{
|
||||
table[mask].fill(0xff);
|
||||
usz out = 0;
|
||||
|
||||
for (usz lane = 0; lane < Lanes; ++lane)
|
||||
{
|
||||
if (mask & (1u << lane))
|
||||
{
|
||||
for (usz byte = 0; byte < Bytes; ++byte)
|
||||
{
|
||||
table[mask][out++] = static_cast<u8>(lane * Bytes + byte);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
alignas(64) constexpr auto s_compress_u16 = make_compress_table<2, 8>();
|
||||
alignas(64) constexpr auto s_compress_u32 = make_compress_table<4, 4>();
|
||||
|
||||
#if defined(ARCH_ARM64)
|
||||
template <typename T>
|
||||
std::tuple<T, T, u32> upload_swapped_neon_skip_restart(std::span<to_be_t<const T>> src, std::span<T> dst, T restart_index)
|
||||
{
|
||||
const u32 count = ::size32(src);
|
||||
u32 i = 0;
|
||||
u32 written = 0;
|
||||
T min_index = static_cast<T>(-1);
|
||||
T max_index = 0;
|
||||
|
||||
if constexpr (sizeof(T) == 2)
|
||||
{
|
||||
auto min = vdupq_n_u16(-1);
|
||||
auto max = vdupq_n_u16(0);
|
||||
const u16 weights[] = {1, 2, 4, 8, 16, 32, 64, 128};
|
||||
|
||||
for (; count - i >= 8; i += 8)
|
||||
{
|
||||
const auto value = vreinterpretq_u16_u8(vrev16q_u8(vld1q_u8(reinterpret_cast<const u8*>(src.data() + i))));
|
||||
const auto removed = vceqq_u16(value, vdupq_n_u16(restart_index));
|
||||
min = vminq_u16(min, vorrq_u16(value, removed));
|
||||
max = vmaxq_u16(max, vbicq_u16(value, removed));
|
||||
const u32 mask = vaddvq_u16(vbicq_u16(vld1q_u16(weights), removed));
|
||||
const auto packed = vqtbl1q_u8(vreinterpretq_u8_u16(value), vld1q_u8(s_compress_u16[mask].data()));
|
||||
// written <= i, so the full store stays inside the input-sized destination.
|
||||
vst1q_u8(reinterpret_cast<u8*>(dst.data() + written), packed);
|
||||
written += std::popcount(mask);
|
||||
}
|
||||
|
||||
min_index = vminvq_u16(min);
|
||||
max_index = vmaxvq_u16(max);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto min = vdupq_n_u32(-1);
|
||||
auto max = vdupq_n_u32(0);
|
||||
const u32 weights[] = {1, 2, 4, 8};
|
||||
|
||||
for (; count - i >= 4; i += 4)
|
||||
{
|
||||
const auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src.data() + i))));
|
||||
const auto removed = vceqq_u32(value, vdupq_n_u32(restart_index));
|
||||
min = vminq_u32(min, vorrq_u32(value, removed));
|
||||
max = vmaxq_u32(max, vbicq_u32(value, removed));
|
||||
const u32 mask = vaddvq_u32(vbicq_u32(vld1q_u32(weights), removed));
|
||||
const auto packed = vqtbl1q_u8(vreinterpretq_u8_u32(value), vld1q_u8(s_compress_u32[mask].data()));
|
||||
vst1q_u8(reinterpret_cast<u8*>(dst.data() + written), packed);
|
||||
written += std::popcount(mask);
|
||||
}
|
||||
|
||||
min_index = vminvq_u32(min);
|
||||
max_index = vmaxvq_u32(max);
|
||||
}
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
const T value = src[i];
|
||||
if (value != restart_index)
|
||||
{
|
||||
min_index = std::min(min_index, value);
|
||||
max_index = std::max(max_index, value);
|
||||
dst[written++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {min_index, max_index, written};
|
||||
}
|
||||
#elif defined(ARCH_X64)
|
||||
alignas(64) constexpr auto s_compress_u32_avx2 = []
|
||||
{
|
||||
std::array<std::array<u32, 8>, 256> table{};
|
||||
|
||||
for (u32 mask = 0; mask < 256; ++mask)
|
||||
{
|
||||
u32 out = 0;
|
||||
|
||||
for (u32 lane = 0; lane < 8; ++lane)
|
||||
{
|
||||
if (mask & (1u << lane))
|
||||
{
|
||||
table[mask][out++] = lane;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return table;
|
||||
}();
|
||||
|
||||
template <typename T>
|
||||
AVX2_FUNC std::tuple<T, T, u32> upload_swapped_avx2_skip_restart(std::span<to_be_t<const T>> src, std::span<T> dst, T restart_index)
|
||||
{
|
||||
const u32 count = ::size32(src);
|
||||
u32 i = 0;
|
||||
u32 written = 0;
|
||||
auto min = _mm256_set1_epi32(-1);
|
||||
auto max = _mm256_setzero_si256();
|
||||
const auto swap = _mm256_broadcastsi128_si256(sizeof(T) == 2 ? s_bswap_u16_mask : s_bswap_u32_mask);
|
||||
const auto restart = sizeof(T) == 2 ? _mm256_set1_epi16(restart_index) : _mm256_set1_epi32(restart_index);
|
||||
constexpr u32 lanes = 32 / sizeof(T);
|
||||
|
||||
for (; count - i >= lanes; i += lanes)
|
||||
{
|
||||
const auto raw = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(src.data() + i));
|
||||
const auto value = _mm256_shuffle_epi8(raw, swap);
|
||||
|
||||
if constexpr (sizeof(T) == 2)
|
||||
{
|
||||
const auto removed = _mm256_cmpeq_epi16(value, restart);
|
||||
min = _mm256_min_epu16(min, _mm256_or_si256(value, removed));
|
||||
max = _mm256_max_epu16(max, _mm256_andnot_si256(removed, value));
|
||||
const auto lo_mask = _mm256_castsi256_si128(removed);
|
||||
const auto hi_mask = _mm256_extracti128_si256(removed, 1);
|
||||
const u32 mask = static_cast<u32>(~_mm_movemask_epi8(_mm_packs_epi16(lo_mask, hi_mask))) & 0xffff;
|
||||
const u32 lo = mask & 255;
|
||||
const u32 hi = mask >> 8;
|
||||
const auto a = _mm_shuffle_epi8(_mm256_castsi256_si128(value), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s_compress_u16[lo].data())));
|
||||
const auto b = _mm_shuffle_epi8(_mm256_extracti128_si256(value, 1), _mm_loadu_si128(reinterpret_cast<const __m128i*>(s_compress_u16[hi].data())));
|
||||
_mm_storeu_si128(utils::bless<__m128i>(dst.data() + written), a);
|
||||
written += std::popcount(lo);
|
||||
_mm_storeu_si128(utils::bless<__m128i>(dst.data() + written), b);
|
||||
written += std::popcount(hi);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto removed = _mm256_cmpeq_epi32(value, restart);
|
||||
min = _mm256_min_epu32(min, _mm256_or_si256(value, removed));
|
||||
max = _mm256_max_epu32(max, _mm256_andnot_si256(removed, value));
|
||||
const u32 mask = static_cast<u32>(~_mm256_movemask_ps(_mm256_castsi256_ps(removed))) & 255;
|
||||
const auto indices = _mm256_loadu_si256(reinterpret_cast<const __m256i*>(s_compress_u32_avx2[mask].data()));
|
||||
const auto packed = _mm256_permutevar8x32_epi32(value, indices);
|
||||
_mm256_storeu_si256(reinterpret_cast<__m256i*>(dst.data() + written), packed);
|
||||
written += std::popcount(mask);
|
||||
}
|
||||
}
|
||||
|
||||
T min_index, max_index;
|
||||
if constexpr (sizeof(T) == 2)
|
||||
{
|
||||
const auto min128 = _mm_min_epu16(_mm256_castsi256_si128(min), _mm256_extracti128_si256(min, 1));
|
||||
const auto max128 = _mm_max_epu16(_mm256_castsi256_si128(max), _mm256_extracti128_si256(max, 1));
|
||||
min_index = static_cast<T>(_mm_cvtsi128_si32(_mm_minpos_epu16(min128)));
|
||||
max_index = static_cast<T>(~_mm_cvtsi128_si32(_mm_minpos_epu16(_mm_xor_si128(max128, _mm_set1_epi32(-1)))));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto min128 = _mm_min_epu32(_mm256_castsi256_si128(min), _mm256_extracti128_si256(min, 1));
|
||||
auto max128 = _mm_max_epu32(_mm256_castsi256_si128(max), _mm256_extracti128_si256(max, 1));
|
||||
min128 = _mm_min_epu32(min128, _mm_shuffle_epi32(min128, _MM_SHUFFLE(1, 0, 3, 2)));
|
||||
max128 = _mm_max_epu32(max128, _mm_shuffle_epi32(max128, _MM_SHUFFLE(1, 0, 3, 2)));
|
||||
min128 = _mm_min_epu32(min128, _mm_shuffle_epi32(min128, _MM_SHUFFLE(2, 3, 0, 1)));
|
||||
max128 = _mm_max_epu32(max128, _mm_shuffle_epi32(max128, _MM_SHUFFLE(2, 3, 0, 1)));
|
||||
min_index = static_cast<T>(_mm_cvtsi128_si32(min128));
|
||||
max_index = static_cast<T>(_mm_cvtsi128_si32(max128));
|
||||
}
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
const T value = src[i];
|
||||
if (value != restart_index)
|
||||
{
|
||||
min_index = std::min(min_index, value);
|
||||
max_index = std::max(max_index, value);
|
||||
dst[written++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {min_index, max_index, written};
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
|
||||
#include <arm_neon.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
template <bool Compare>
|
||||
auto copy_data_swap_u32_neon(u32* dst, const u32* src, u32 count)
|
||||
{
|
||||
uint32x4_t changed = vdupq_n_u32(0);
|
||||
u32 tail_changed = 0;
|
||||
u32 i = 0;
|
||||
|
||||
for (; count - i >= 4; i += 4)
|
||||
{
|
||||
const auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
|
||||
|
||||
if constexpr (Compare)
|
||||
{
|
||||
changed = vorrq_u32(changed, veorq_u32(value, vld1q_u32(dst + i)));
|
||||
}
|
||||
|
||||
vst1q_u32(dst + i, value);
|
||||
}
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
const u32 value = stx::se_storage<u32>::swap(src[i]);
|
||||
|
||||
if constexpr (Compare)
|
||||
{
|
||||
tail_changed |= value ^ dst[i];
|
||||
}
|
||||
|
||||
dst[i] = value;
|
||||
}
|
||||
|
||||
if constexpr (Compare)
|
||||
{
|
||||
return (vmaxvq_u32(changed) | tail_changed) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, bool Restart>
|
||||
u64 upload_untouched_neon(const be_t<T>* src, T* dst, u32 count, T restart_index = 0)
|
||||
{
|
||||
T min_index = static_cast<T>(-1);
|
||||
T max_index = 0;
|
||||
u32 i = 0;
|
||||
|
||||
if constexpr (sizeof(T) == 2)
|
||||
{
|
||||
auto min = vdupq_n_u16(-1);
|
||||
auto max = vdupq_n_u16(0);
|
||||
const auto restart = vdupq_n_u16(restart_index);
|
||||
|
||||
for (; count - i >= 8; i += 8)
|
||||
{
|
||||
auto value = vreinterpretq_u16_u8(vrev16q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
|
||||
|
||||
if constexpr (Restart)
|
||||
{
|
||||
const auto mask = vceqq_u16(value, restart);
|
||||
max = vmaxq_u16(max, vbicq_u16(value, mask));
|
||||
value = vorrq_u16(value, mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
max = vmaxq_u16(max, value);
|
||||
}
|
||||
|
||||
min = vminq_u16(min, value);
|
||||
vst1q_u16(dst + i, value);
|
||||
}
|
||||
|
||||
min_index = vminvq_u16(min);
|
||||
max_index = vmaxvq_u16(max);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto min = vdupq_n_u32(-1);
|
||||
auto max = vdupq_n_u32(0);
|
||||
const auto restart = vdupq_n_u32(restart_index);
|
||||
|
||||
for (; count - i >= 4; i += 4)
|
||||
{
|
||||
auto value = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8(reinterpret_cast<const u8*>(src + i))));
|
||||
|
||||
if constexpr (Restart)
|
||||
{
|
||||
const auto mask = vceqq_u32(value, restart);
|
||||
max = vmaxq_u32(max, vbicq_u32(value, mask));
|
||||
value = vorrq_u32(value, mask);
|
||||
}
|
||||
else
|
||||
{
|
||||
max = vmaxq_u32(max, value);
|
||||
}
|
||||
|
||||
min = vminq_u32(min, value);
|
||||
vst1q_u32(dst + i, value);
|
||||
}
|
||||
|
||||
min_index = vminvq_u32(min);
|
||||
max_index = vmaxvq_u32(max);
|
||||
}
|
||||
|
||||
for (; i < count; ++i)
|
||||
{
|
||||
const T value = src[i];
|
||||
if (Restart && value == restart_index)
|
||||
{
|
||||
dst[i] = static_cast<T>(-1);
|
||||
}
|
||||
else
|
||||
{
|
||||
min_index = std::min(min_index, value);
|
||||
max_index = std::max(max_index, value);
|
||||
dst[i] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return (u64{max_index} << 32) | min_index;
|
||||
}
|
||||
} // namespace
|
||||
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <arm_sve.h>
|
||||
|
||||
#if defined(__clang__)
|
||||
#define BUFFERUTILS_SVE __attribute__((target("sve")))
|
||||
#else
|
||||
#define BUFFERUTILS_SVE __attribute__((target("+sve")))
|
||||
#endif
|
||||
|
||||
// Older compilers do not provide the svcompact_u16 intrinsic.
|
||||
// Please remove this nonsense once we upgrade gcc in the CI - Whatcookie
|
||||
#if (defined(__clang__) && __clang_major__ >= 22) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ >= 17)
|
||||
#define BUFFERUTILS_HAS_SVE2P2
|
||||
#if defined(__clang__)
|
||||
#define BUFFERUTILS_SVE2P2 __attribute__((target("sve2p2")))
|
||||
#else
|
||||
#define BUFFERUTILS_SVE2P2 __attribute__((target("+sve2p2")))
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
BUFFERUTILS_SVE std::tuple<u32, u32, u32> upload_swapped_sve_skip_restart(std::span<to_be_t<const u32>> src, std::span<u32> dst, u32 restart_index)
|
||||
{
|
||||
const u32 count = ::size32(src);
|
||||
u32 written = 0;
|
||||
auto min = svdup_n_u32(u32{0xffffffff});
|
||||
auto max = svdup_n_u32(0);
|
||||
const auto all = svptrue_b32();
|
||||
|
||||
for (u64 i = 0; i < count; i += svcntw())
|
||||
{
|
||||
const auto active = svwhilelt_b32(i, u64{count});
|
||||
auto value = svld1_u32(active, reinterpret_cast<const u32*>(src.data()) + i);
|
||||
value = svrevb_u32_x(active, value);
|
||||
const auto keep = svcmpne_n_u32(active, value, restart_index);
|
||||
min = svmin_u32_m(keep, min, value);
|
||||
max = svmax_u32_m(keep, max, value);
|
||||
const auto packed = svcompact_u32(keep, value);
|
||||
const u32 processed = svcntp_b32(all, keep);
|
||||
const auto output = svwhilelt_b32(u64{0}, u64{processed});
|
||||
svst1_u32(output, dst.data() + written, packed);
|
||||
written += processed;
|
||||
}
|
||||
|
||||
return {svminv_u32(all, min), svmaxv_u32(all, max), written};
|
||||
}
|
||||
|
||||
#ifdef BUFFERUTILS_HAS_SVE2P2
|
||||
BUFFERUTILS_SVE2P2 std::tuple<u16, u16, u32> upload_swapped_sve2p2_skip_restart(std::span<to_be_t<const u16>> src, std::span<u16> dst, u16 restart_index)
|
||||
{
|
||||
const u32 count = ::size32(src);
|
||||
u32 written = 0;
|
||||
auto min = svdup_n_u16(u16{0xffff});
|
||||
auto max = svdup_n_u16(0);
|
||||
const auto all = svptrue_b16();
|
||||
|
||||
for (u64 i = 0; i < count; i += svcnth())
|
||||
{
|
||||
const auto active = svwhilelt_b16(i, u64{count});
|
||||
auto value = svld1_u16(active, reinterpret_cast<const u16*>(src.data()) + i);
|
||||
value = svrevb_u16_x(active, value);
|
||||
const auto keep = svcmpne_n_u16(active, value, restart_index);
|
||||
min = svmin_u16_m(keep, min, value);
|
||||
max = svmax_u16_m(keep, max, value);
|
||||
const auto packed = svcompact_u16(keep, value);
|
||||
const u32 processed = svcntp_b16(all, keep);
|
||||
const auto output = svwhilelt_b16(u64{0}, u64{processed});
|
||||
svst1_u16(output, dst.data() + written, packed);
|
||||
written += processed;
|
||||
}
|
||||
|
||||
return {svminv_u16(all, min), svmaxv_u16(all, max), written};
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
#undef BUFFERUTILS_SVE
|
||||
#ifdef BUFFERUTILS_HAS_SVE2P2
|
||||
#undef BUFFERUTILS_SVE2P2
|
||||
#endif
|
||||
@@ -1502,6 +1502,24 @@ namespace rsx
|
||||
}
|
||||
}
|
||||
|
||||
bool is_float_depth_format(rsx::surface_depth_format2 format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case rsx::surface_depth_format2::z16_float:
|
||||
case rsx::surface_depth_format2::z24s8_float:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
f32 encode_emulated_depth(f32 depth)
|
||||
{
|
||||
// Halving the IEEE bit pattern of a non-negative float is monotonic and maps every finite value below 1
|
||||
return std::bit_cast<f32>((std::bit_cast<u32>(std::max(depth, 0.f)) & 0x7fffffffu) >> 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns number of texel lines decoded in one pitch-length number of bytes
|
||||
*/
|
||||
@@ -1827,9 +1845,45 @@ namespace rsx
|
||||
}
|
||||
}
|
||||
|
||||
u32 get_max_depth_value(rsx::surface_depth_format2 format)
|
||||
f32 decode_e4m12(u32 value)
|
||||
{
|
||||
return get_format_block_size_in_bytes(format) == 2 ? 0xFFFF : 0xFFFFFF;
|
||||
// Rebias by 2^111: unsigned E4M12 with bias 16, see E4M12Conversion.glsl
|
||||
const uint bits = (value << 11) & 0x07FFF800u;
|
||||
return std::bit_cast<f32>(bits) * std::bit_cast<f32>(0x77000000u);
|
||||
}
|
||||
|
||||
f32 get_depth_clear_value(rsx::surface_depth_format2 format, u32 raw)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case rsx::surface_depth_format2::z16_uint:
|
||||
return float(raw) / 0xFFFF;
|
||||
case rsx::surface_depth_format2::z24s8_uint:
|
||||
return float(raw) / 0xFFFFFF;
|
||||
case rsx::surface_depth_format2::z16_float:
|
||||
return decode_e4m12(raw);
|
||||
case rsx::surface_depth_format2::z24s8_float:
|
||||
return std::bit_cast<f32>(raw << 7);
|
||||
default:
|
||||
fmt::throw_exception("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
// Hardware behaves as if each depth bound is converted into the surface's depth encoding, clamped to the format's range.
|
||||
f32 clamp_depth_bounds_value(rsx::surface_depth_format2 format, f32 value)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case rsx::surface_depth_format2::z16_uint:
|
||||
case rsx::surface_depth_format2::z24s8_uint:
|
||||
return std::clamp(value, 0.f, 1.f);
|
||||
case rsx::surface_depth_format2::z16_float:
|
||||
return std::clamp(value, 0.f, decode_e4m12(0xFFFF));
|
||||
case rsx::surface_depth_format2::z24s8_float:
|
||||
return std::max(value, 0.f);
|
||||
default:
|
||||
fmt::throw_exception("Unreachable");
|
||||
}
|
||||
}
|
||||
|
||||
bool is_texcoord_wrapping_mode(rsx::texture_wrap_mode mode)
|
||||
|
||||
@@ -364,8 +364,13 @@ namespace rsx
|
||||
|
||||
bool is_compressed_host_format(const texture_uploader_capabilities& caps, u32 format); // Returns true for host-compressed formats (DXT)
|
||||
u8 get_format_sample_count(rsx::surface_antialiasing antialias);
|
||||
u32 get_max_depth_value(rsx::surface_depth_format2 format);
|
||||
f32 get_depth_clear_value(rsx::surface_depth_format2 format, u32 raw);
|
||||
f32 clamp_depth_bounds_value(rsx::surface_depth_format2 format, f32 value);
|
||||
bool is_depth_stencil_format(rsx::surface_depth_format2 format);
|
||||
bool is_float_depth_format(rsx::surface_depth_format2 format);
|
||||
|
||||
// Storage encoding of float depth when the host cannot hold depth values above 1. Must match encode_emulated_depth in GLSL.
|
||||
f32 encode_emulated_depth(f32 depth);
|
||||
|
||||
/**
|
||||
* Format feature support. There is not simple format to determine what is supported here, results are from hw tests
|
||||
|
||||
@@ -133,7 +133,7 @@ namespace rsx
|
||||
m_data |= mask;
|
||||
}
|
||||
|
||||
bool test(T mask)
|
||||
bool test(T mask) const
|
||||
{
|
||||
return !!(m_data & static_cast<bitmask_type>(mask));
|
||||
}
|
||||
|
||||
@@ -1285,7 +1285,14 @@ namespace rsx
|
||||
}
|
||||
|
||||
template <typename commandbuffer_type>
|
||||
rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(commandbuffer_type& cmd, u32 texaddr, u32 required_width, u32 required_height, u32 required_pitch, u8 required_bpp, rsx::surface_access access)
|
||||
rsx::simple_array<surface_overlap_info> get_merged_texture_memory_region(
|
||||
commandbuffer_type& cmd,
|
||||
u32 texaddr,
|
||||
u32 required_width,
|
||||
u32 required_height,
|
||||
u32 required_pitch,
|
||||
u8 required_bpp,
|
||||
rsx::surface_access access)
|
||||
{
|
||||
rsx::simple_array<surface_overlap_info> result;
|
||||
rsx::simple_array<utils::pair<u32, bool>> dirty;
|
||||
@@ -1432,17 +1439,35 @@ namespace rsx
|
||||
|
||||
if (result.size() > 1)
|
||||
{
|
||||
result.sort([](const auto &a, const auto &b)
|
||||
result.sort([texaddr](const auto &a, const auto &b)
|
||||
{
|
||||
if (a.surface->last_use_tag == b.surface->last_use_tag)
|
||||
// Check for the common case first. 99.99% of lookups end here.
|
||||
if (a.surface->last_use_tag != b.surface->last_use_tag) [[ likely ]]
|
||||
{
|
||||
const auto area_a = a.dst_area.width * a.dst_area.height;
|
||||
const auto area_b = b.dst_area.width * b.dst_area.height;
|
||||
|
||||
return area_a < area_b;
|
||||
return a.surface->last_use_tag < b.surface->last_use_tag;
|
||||
}
|
||||
|
||||
return a.surface->last_use_tag < b.surface->last_use_tag;
|
||||
// Surfaces are aliasing. We need a tie-breaker
|
||||
if (a.is_depth != b.is_depth &&
|
||||
g_cfg.video.fb_aliasing_bias != framebuffer_aliasing_bias::_auto)
|
||||
{
|
||||
// User-defined preference for mismatched depth/color aliasing. This is the most common aliasing cause.
|
||||
// Preferred aspect goes last.
|
||||
const bool prefer_color = g_cfg.video.fb_aliasing_bias == framebuffer_aliasing_bias::prefer_color;
|
||||
return prefer_color ? a.is_depth : b.is_depth;
|
||||
}
|
||||
|
||||
// Check if we have a perfect match and make it go last
|
||||
if (a.base_address == texaddr && !a.is_clipped) return false;
|
||||
if (b.base_address == texaddr && !b.is_clipped) return true;
|
||||
|
||||
// Check if one of the two is able to serve the full request better than the other.
|
||||
if (a.is_clipped != b.is_clipped) return a.is_clipped;
|
||||
|
||||
// Compare available area if nothing else could break the tie
|
||||
const auto area_a = a.dst_area.width * a.dst_area.height;
|
||||
const auto area_b = b.dst_area.width * b.dst_area.height;
|
||||
return area_a < area_b;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -696,6 +696,15 @@ namespace rsx
|
||||
rop_control.set_blend_target_mask(blend_enable_mask);
|
||||
}
|
||||
|
||||
if (fragment_program.ctrl & RSX_SHADER_CONTROL_EMULATE_DEPTH_RANGE)
|
||||
{
|
||||
// Hardware clipping is disabled for these draws, see the fragment epilogue
|
||||
if (REGS(m_ctx)->depth_clamp_enabled() || !REGS(m_ctx)->depth_clip_enabled())
|
||||
{
|
||||
rop_control.enable_depth_clamp();
|
||||
}
|
||||
}
|
||||
|
||||
// Generate wpos coefficients
|
||||
// wpos equation is now as follows (ignoring pixel center offset):
|
||||
// wpos.y = (frag_coord / resolution_scale) * ((window_origin!=top)?-1.: 1.) + ((window_origin!=top)? window_height : 0)
|
||||
|
||||
@@ -36,14 +36,15 @@ namespace rsx
|
||||
rtt_config_dirty = (1 << 21), // Render target configuration changed
|
||||
rtt_config_contested = (1 << 22), // Render target configuration is indeterminate
|
||||
rtt_config_valid = (1 << 23), // Render target configuration is valid
|
||||
rtt_cache_state_dirty = (1 << 24), // Texture cache state is indeterminate
|
||||
rtt_config_no_attachments = (1 << 24), // Render target configuration has no attachments
|
||||
rtt_cache_state_dirty = (1 << 25), // Texture cache state is indeterminate
|
||||
|
||||
xform_instancing_state_dirty = (1 << 25), // Transform instancing state has changed
|
||||
xform_instancing_state_dirty = (1 << 26), // Transform instancing state has changed
|
||||
|
||||
zeta_address_is_cyclic = (1 << 26), // The currently bound Z buffer is active for R/W in a cyclic manner
|
||||
zeta_address_cyclic_barrier = (1 << 27), // A memory barrier is required to "end" the Z buffer cyclic state
|
||||
zeta_address_is_cyclic = (1 << 27), // The currently bound Z buffer is active for R/W in a cyclic manner
|
||||
zeta_address_cyclic_barrier = (1 << 28), // A memory barrier is required to "end" the Z buffer cyclic state
|
||||
|
||||
blend_config_dirty = (1 << 28), // Blending configuration has been updated. Only matters when programmable blending is in use.
|
||||
blend_config_dirty = (1 << 29), // Blending configuration has been updated. Only matters when programmable blending is in use.
|
||||
|
||||
// TODO - Should signal that we simply need to do a FP compare before the next draw call and invalidate the ucode if the content has changed.
|
||||
// Marking as dirty to invalidate hot cache also works, it's not like there's tons of barriers per frame anyway.
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace gl
|
||||
{"tc6", 12},
|
||||
{"tc7", 13},
|
||||
{"tc8", 14},
|
||||
{"tc9", 15}
|
||||
{"tc9", 15},
|
||||
{"depth_range", 16} // clip range for depth range emulation
|
||||
}};
|
||||
|
||||
int get_varying_register_location(std::string_view varying_register_name)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user