Compare commits
14 Commits
sampleshad
...
jdlo-eden-
| Author | SHA1 | Date | |
|---|---|---|---|
| b3eba6a275 | |||
| c486334c78 | |||
| 7dc30a5e2d | |||
| b16cece0a6 | |||
| 19bbd9894e | |||
|
3bb0d29aa1
|
|||
| cf4f813145 | |||
|
3290ed80d8
|
|||
| a71a82d3b7 | |||
| bd1d270e97 | |||
| 664ff7cc85 | |||
| b5c86787ab | |||
|
|
cfae726289 | ||
|
|
bb94cff886 |
@@ -158,7 +158,7 @@ set(YUZU_QT_MIRROR "" CACHE STRING "What mirror to use for downloading the bundl
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
set(EXT_DEFAULT OFF)
|
||||
if (MSVC OR ANDROID)
|
||||
if (MSVC OR ANDROID OR APPLE)
|
||||
set(EXT_DEFAULT ON)
|
||||
endif()
|
||||
option(YUZU_USE_CPM "Use CPM to fetch system dependencies (fmt, boost, etc) if needed. Externals will still be fetched." ${EXT_DEFAULT})
|
||||
@@ -425,7 +425,7 @@ if (YUZU_USE_CPM)
|
||||
endif()
|
||||
|
||||
# fmt
|
||||
AddJsonPackage(fmt)
|
||||
AddJsonPackage(NAME fmt BUNDLED_PACKAGE ON)
|
||||
|
||||
# lz4
|
||||
AddJsonPackage(lz4)
|
||||
@@ -530,7 +530,9 @@ if (APPLE)
|
||||
# Umbrella framework for everything GUI-related
|
||||
find_library(COCOA_LIBRARY Cocoa REQUIRED)
|
||||
find_library(IOKIT_LIBRARY IOKit REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
|
||||
find_library(COREVIDEO_LIBRARY CoreVideo REQUIRED)
|
||||
find_library(VIDEOTOOLBOX_LIBRARY VideoToolbox REQUIRED)
|
||||
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY} ${VIDEOTOOLBOX_LIBRARY})
|
||||
elseif (WIN32)
|
||||
# Target Windows 10
|
||||
add_compile_definitions(_WIN32_WINNT=0x0A00 WINVER=0x0A00)
|
||||
@@ -563,7 +565,7 @@ find_package(VulkanUtilityLibraries)
|
||||
find_package(SimpleIni)
|
||||
find_package(SPIRV-Tools)
|
||||
find_package(sirit)
|
||||
find_package(gamemode)
|
||||
find_package(gamemode)
|
||||
|
||||
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
||||
find_package(xbyak)
|
||||
|
||||
10
externals/ffmpeg/cpmfile.json
vendored
10
externals/ffmpeg/cpmfile.json
vendored
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"ffmpeg": {
|
||||
"repo": "FFmpeg/FFmpeg",
|
||||
"sha": "5e56937b74",
|
||||
"hash": "9ab0457dcd6ce6359b5053c1662f57910d332f68ca0cca9d4134d858464840917027374de3d97e0863c3a7daaea2fe4f4cd17d1c6d8e7f740f4ad91e71c2932b",
|
||||
"tag": "n6.1",
|
||||
"bundled": true
|
||||
},
|
||||
"ffmpeg-ci": {
|
||||
"ci": true,
|
||||
"package": "FFmpeg",
|
||||
"name": "ffmpeg",
|
||||
"repo": "crueter-ci/FFmpeg",
|
||||
"version": "8.0.1-5e56937b74",
|
||||
"repo": "FFmpeg/FFmpeg",
|
||||
"tag": "n6.1",
|
||||
"version": "6.1",
|
||||
"min_version": "4.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,6 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
|
||||
@@ -31,6 +31,7 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||
RENDERER_PROVOKING_VERTEX("provoking_vertex"),
|
||||
RENDERER_DESCRIPTOR_INDEXING("descriptor_indexing"),
|
||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||
BLACK_BACKGROUNDS("black_backgrounds"),
|
||||
|
||||
@@ -160,6 +160,13 @@ abstract class SettingsItem(
|
||||
descriptionId = R.string.descriptor_indexing_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SwitchSetting(
|
||||
BooleanSetting.RENDERER_SAMPLE_SHADING,
|
||||
titleId = R.string.sample_shading,
|
||||
descriptionId = R.string.sample_shading_description
|
||||
)
|
||||
)
|
||||
put(
|
||||
SliderSetting(
|
||||
IntSetting.RENDERER_SAMPLE_SHADING_FRACTION,
|
||||
|
||||
@@ -453,11 +453,12 @@ class SettingsFragmentPresenter(
|
||||
private fun addEdenVeilSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.veil_extensions))
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING_FRACTION.key)
|
||||
add(ByteSetting.RENDERER_DYNA_STATE.key)
|
||||
add(BooleanSetting.RENDERER_VERTEX_INPUT_DYNAMIC_STATE.key)
|
||||
add(BooleanSetting.RENDERER_PROVOKING_VERTEX.key)
|
||||
add(BooleanSetting.RENDERER_DESCRIPTOR_INDEXING.key)
|
||||
add(BooleanSetting.RENDERER_SAMPLE_SHADING.key)
|
||||
add(IntSetting.RENDERER_SAMPLE_SHADING_FRACTION.key)
|
||||
|
||||
add(HeaderSetting(R.string.veil_renderer))
|
||||
add(IntSetting.DMA_ACCURACY.key)
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">فهرسة الوصف</string>
|
||||
<string name="descriptor_indexing_description">يحسن معالجة النسيج والمخزن المؤقت، بالإضافة إلى طبقة الترجمة Maxwell. مدعوم من بعض وحدات معالجة الرسومات Vulkan 1.1 وجميع وحدات معالجة الرسومات Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">تظليل العينة</string>
|
||||
<string name="sample_shading_description">يسمح لتظلل الأجزاء بتنفيذ كل عينة في جزء متعدد العينات بدلاً من مرة واحدة لكل جزء. يحسن جودة الرسومات على حساب بعض الأداء. لا تدعم هذه الإضافة سوى أجهزة Vulkan 1.1+ فقط.</string>
|
||||
<string name="sample_shading_fraction">نسبة التظليل النموذجية</string>
|
||||
<string name="sample_shading_fraction_description">كثافة تمرير تظليل العينة. تؤدي القيم الأعلى إلى تحسين الجودة بشكل أكبر، ولكنها تقلل أيضًا من الأداء إلى حد كبير.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">يحسن الإضاءة ومعالجة الرؤوس في بعض الألعاب. مدعوم فقط على وحدات معالجة الرسومات التي تدعم فولكان 1.0+.</string>
|
||||
<string name="descriptor_indexing">فهرسة الواصفات</string>
|
||||
<string name="descriptor_indexing_description">يحسن معالجة القوام والمخازن المؤقتة، بالإضافة إلى طبقة ترجمة ماكسويل. مدعوم من قبل بعض وحدات معالجة الرسومات التي تدعم فولكان 1.1 وجميع وحدات معالجة الرسومات التي تدعم فولكان 1.2+.</string>
|
||||
<string name="sample_shading">سێبەندی نمونە</string>
|
||||
<string name="sample_shading_description">ڕێگە بە شێدەری پارچە دەدات کە بۆ هەر نمونەیەک لە پارچەی فرە نمونەیدا جێبەجێ بکات لە جیاتی جێبەجێکردنی بۆ هەر پارچەیەک. جۆرایی گرافیک باشتر دەکات بە بەهای هەندێک لە کارایی. تەنها ئامێرەکانی Vulkan 1.1+ پشتگیری ئەم درێژکراوە دەکەن.</string>
|
||||
<string name="sample_shading_fraction">پێکهاتەی سێبەرکردنی نموونە</string>
|
||||
<string name="sample_shading_fraction_description">چڕی تێپەڕاندنی سێبەرکردنی نموونە. بەهای زیاتر کوالێتی باشتر دەکات بەڵام کارایی زیاتر کەم دەکاتەوە.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">Zlepšuje osvětlení a zpracování vertexů v některých hrách. Podporováno pouze na GPU s Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexování deskriptorů</string>
|
||||
<string name="descriptor_indexing_description">Zlepšuje zpracování textur a bufferů, stejně jako Maxwell překladovou vrstvu. Podporováno některými GPU s Vulkan 1.1 a všemi GPU s Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Vzorkovací stínování</string>
|
||||
<string name="sample_shading_description">Umožňuje fragment shaderu provádět výpočty pro každý vzorek ve fragmentu s více vzorky namísto jednou pro fragment. Zlepšuje kvalitu grafiky na úkor výkonu. Tuto funkci podporují pouze zařízení s Vulkan 1.1+.</string>
|
||||
<string name="sample_shading_fraction">Podíl stínování vzorku</string>
|
||||
<string name="sample_shading_fraction_description">Intenzita průchodu stínování vzorku. Vyšší hodnoty zlepšují kvalitu, ale také výrazněji snižují výkon.</string>
|
||||
|
||||
|
||||
@@ -78,6 +78,8 @@
|
||||
<string name="provoking_vertex_description">Verbessert die Beleuchtung und die Vertex-Verarbeitung in einigen Spielen. Wird nur von GPUs mit Vulkan 1.0+ unterstützt.</string>
|
||||
<string name="descriptor_indexing">Deskriptor-Indizierung</string>
|
||||
<string name="descriptor_indexing_description">Verbessert die Textur- und Puffer-Verarbeitung sowie die Maxwell-Übersetzungsschicht. Wird von einigen Vulkan 1.1-GPUs und allen Vulkan 1.2+-GPUs unterstützt.</string>
|
||||
<string name="sample_shading">Sample Shading</string>
|
||||
<string name="sample_shading_description">Ermöglicht dem Fragment-Shader, pro Sample in einem mehrfach gesampleten Fragment ausgeführt zu werden, anstatt einmal pro Fragment. Verbessert die Grafikqualität auf Kosten der Leistung. Nur Vulkan 1.1+-Geräte unterstützen diese Erweiterung.</string>
|
||||
<string name="sample_shading_fraction">Sample-Shading-Anteil</string>
|
||||
<string name="sample_shading_fraction_description">Die Intensität des Sample-Shading-Durchgangs. Höhere Werte verbessern die Qualität stärker, beeinträchtigen aber auch die Leistung stärker.</string>
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<string name="provoking_vertex_description">Mejora la iluminación y el manejo de vértices en ciertos juegos. Solo es compatible con las GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexación del descriptor</string>
|
||||
<string name="descriptor_indexing_description">Mejora la textura y el manejo del búfer, así como la capa de traducción Maxwell. Compatible con algunas GPU Vulkan 1.1 y todas las GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Sombreado de muestra</string>
|
||||
<string name="sample_shading_description">Permite que el sombreador de fragmentos se ejecute por muestra en un fragmento de múltiples muestras en lugar de una vez por fragmento. Mejora la calidad gráfica a costa de algo de rendimiento. Solo los dispositivos Vulkan 1.1+ admiten esta extensión.</string>
|
||||
<string name="sample_shading_fraction">Fracción de sombreado de muestra</string>
|
||||
<string name="sample_shading_fraction_description">La intensidad del paso de sombreado de la muestra. Los valores más altos mejoran más la calidad, pero también reducen el rendimiento en mayor medida.</string>
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@
|
||||
<string name="fast_cpu_time_description">از Boost (1700MHz) برای کار با بالاترین سرعت کلاک بومی سوئیچ یا Fast (2000MHz) برای کار با دو برابر سرعت استفاده کنید.</string>
|
||||
<string name="memory_layout">چیدمان حافظه</string>
|
||||
<string name="memory_layout_description">(آزمایشی) چیدمان حافظه شبیهسازی شده را تغییر میدهد. این تنظیم عملکرد را افزایش نمیدهد، اما ممکن است به بازیهایی که از رزولوشن بالا با استفاده از مادها استفاده میکنند کمک کند. در تلفنهای با 8 گیگابایت رم یا کمتر استفاده نشود.</string>
|
||||
<string name="sample_shading">سایهزنی نمونه</string>
|
||||
<string name="sample_shading_description">اجازه میدهد شیدر قطعه در هر نمونه از یک قطعه چندنمونهای اجرا شود به جای یک بار برای هر قطعه. کیفیت گرافیک را به بهای کاهش عملکرد بهبود میبخشد. فقط دستگاههای Vulkan 1.1+ از این افزونه پشتیبانی میکنند.</string>
|
||||
<string name="sample_shading_fraction">کسر سایهزنی نمونه</string>
|
||||
<string name="sample_shading_fraction_description">شدت مرحله سایهزنی نمونه. مقادیر بالاتر کیفیت را بیشتر بهبود میبخشد اما عملکرد را نیز به میزان بیشتری کاهش میدهد.</string>
|
||||
<string name="custom_cpu_ticks">تیکهای CPU سفارشی</string>
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
<string name="provoking_vertex_description">Améliore l`éclairage et la gestion des vertex dans certains jeux. Pris en charge uniquement par les GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexation des descripteurs</string>
|
||||
<string name="descriptor_indexing_description">Améliore la gestion des textures et des tampons, ainsi que la couche de traduction Maxwell. Pris en charge par certains GPU Vulkan 1.1 et tous les GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Échantillonnage de shading</string>
|
||||
<string name="sample_shading_description">Permet au fragment shader de s\'exécuter par échantillon dans un fragment multi-échantillonné au lieu d\'une fois par fragment. Améliore la qualité graphique au détriment des performances. Seuls les appareils Vulkan 1.1+ prennent en charge cette extension.</string>
|
||||
<string name="sample_shading_fraction">Fraction d\'ombrage d\'échantillon</string>
|
||||
<string name="sample_shading_fraction_description">L\'intensité de la passe d\'ombrage d\'échantillon. Des valeurs plus élevées améliorent davantage la qualité mais réduisent aussi plus fortement les performances.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">משפר תאורה וטיפול בקודקודים במשחקים מסוימים. נתמך רק בכרטיסי מסך עם Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">אינדוקס תיאורים</string>
|
||||
<string name="descriptor_indexing_description">משפר טיפול במרקמים ומאגרים, כמו גם בשכבת התרגום של Maxwell. נתמך בחלק מכרטיסי ה-Vulkan 1.1 ובכל כרטיסי ה-Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">דגימת צל</string>
|
||||
<string name="sample_shading_description">מאפשר לשברי הצללה לרוץ לכל דגימה בקטע רב-דגימות במקום פעם אחת לקטע. משפר את איכות הגרפיקה במחיר של ביצועים. רק מכשירי Vulkan 1.1+ תומכים בהרחבה זו.</string>
|
||||
<string name="sample_shading_fraction">שבר הצללה לדוגמה</string>
|
||||
<string name="sample_shading_fraction_description">עוצמת מעבר ההצללה לדוגמה. ערכים גבוהים יותר משפרים את האיכות יותר אך גם מפחיתים את הביצועים במידה רבה יותר.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">Javítja a világítást és a csúcskezelést bizonyos játékokban. Csak Vulkan 1.0+ GPU-kon támogatott.</string>
|
||||
<string name="descriptor_indexing">Deskriptor Indexelés</string>
|
||||
<string name="descriptor_indexing_description">Javítja a textúrák és pufferek kezelését, valamint a Maxwell fordítási réteget. Néhány Vulkan 1.1 GPU és minden Vulkan 1.2+ GPU támogatja.</string>
|
||||
<string name="sample_shading">Mintavételezés árnyékolás</string>
|
||||
<string name="sample_shading_description">Lehetővé teszi, hogy a fragment shader mintánként fusson egy többmintás fragmentben a fragmentenkénti futtatás helyett. Javítja a grafikai minőséget a teljesítmény rovására. Csak Vulkan 1.1+ eszközök támogatják ezt a kiterjesztést.</string>
|
||||
<string name="sample_shading_fraction">Mintavételezés árnyékolási hányad</string>
|
||||
<string name="sample_shading_fraction_description">A mintavételezés árnyékolási lépés intenzitása. A magasabb értékek jobb minőséget eredményeznek, de nagyobb mértékben csökkentik a teljesítményt.</string>
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<string name="provoking_vertex_description">Meningkatkan pencahayaan dan penanganan vertex di beberapa game. Hanya didukung di GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Pengindeks Deskriptor</string>
|
||||
<string name="descriptor_indexing_description">Meningkatkan penanganan tekstur dan buffer, serta lapisan terjemahan Maxwell. Didukung oleh beberapa GPU Vulkan 1.1 dan semua GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Pencahayaan Sampel</string>
|
||||
<string name="sample_shading_description">Memungkinkan fragment shader dieksekusi per sampel dalam fragmen multisampel alih-alih sekali per fragmen. Meningkatkan kualitas grafis dengan mengorbankan kinerja. Hanya perangkat Vulkan 1.1+ yang mendukung ekstensi ini.</string>
|
||||
<string name="sample_shading_fraction">Fraksi Pencahayaan Sampel</string>
|
||||
<string name="sample_shading_fraction_description">Intensitas proses pencahayaan sampel. Nilai lebih tinggi meningkatkan kualitas lebih baik tetapi juga mengurangi performa lebih besar.</string>
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<string name="provoking_vertex_description">Migliora illuminazione e gestione dei vertici in alcuni giochi. Supportato solo su GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indicizzazione descrittori</string>
|
||||
<string name="descriptor_indexing_description">Migliora la gestione di texture e buffer, nonché il livello di traduzione Maxwell. Supportato da alcune GPU Vulkan 1.1 e tutte le GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Shading campione</string>
|
||||
<string name="sample_shading_description">Permette al fragment shader di eseguire per campione in un frammento multi-campione invece che una volta per frammento. Migliora la qualità grafica a scapito delle prestazioni. Solo i dispositivi Vulkan 1.1+ supportano questa estensione.</string>
|
||||
<string name="sample_shading_fraction">Frazione di ombreggiatura campione</string>
|
||||
<string name="sample_shading_fraction_description">L\'intensità della passata di ombreggiatura campione. Valori più alti migliorano la qualità ma riducono maggiormente le prestazioni.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">特定のゲームで照明と頂点処理を改善します。Vulkan 1.0+ GPUでのみサポートされています。</string>
|
||||
<string name="descriptor_indexing">ディスクリプタインデキシング</string>
|
||||
<string name="descriptor_indexing_description">テクスチャとバッファの処理、およびMaxwell翻訳レイヤーを改善します。一部のVulkan 1.1 GPUとすべてのVulkan 1.2+ GPUでサポートされています。</string>
|
||||
<string name="sample_shading">サンプルシェーディング</string>
|
||||
<string name="sample_shading_description">マルチサンプルフラグメントでフラグメントシェーダーをフラグメントごとではなくサンプルごとに実行できるようにします。パフォーマンスを犠牲にしてグラフィック品質を向上させます。Vulkan 1.1+デバイスのみがこの拡張機能をサポートしています。</string>
|
||||
<string name="sample_shading_fraction">サンプルシェーディング率</string>
|
||||
<string name="sample_shading_fraction_description">サンプルシェーディング処理の強度。高い値ほど品質は向上しますが、パフォーマンスも大きく低下します。</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">일부 게임에서 조명과 버텍스 처리를 개선합니다. Vulkan 1.0+ GPU에서만 지원됩니다.</string>
|
||||
<string name="descriptor_indexing">디스크립터 인덱싱</string>
|
||||
<string name="descriptor_indexing_description">텍스처 및 버퍼 처리와 Maxwell 변환 레이어를 개선합니다. 일부 Vulkan 1.1 GPU 및 모든 Vulkan 1.2+ GPU에서 지원됩니다.</string>
|
||||
<string name="sample_shading">샘플 쉐이딩</string>
|
||||
<string name="sample_shading_description">멀티샘플 프래그먼트에서 프래그먼트 쉐이더가 프래그먼트당 한 번이 아니라 샘플당 실행되도록 합니다. 성능을 희생하여 그래픽 품질을 향상시킵니다. Vulkan 1.1+ 장치만 이 확장을 지원합니다.</string>
|
||||
<string name="sample_shading_fraction">샘플 쉐이딩 비율</string>
|
||||
<string name="sample_shading_fraction_description">샘플 쉐이딩 패스의 강도. 값이 높을수록 품질이 더 향상되지만 성능도 더 크게 저하됩니다.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">Forbedrer belysning og vertexhåndtering i enkelte spill. Støttes kun på Vulkan 1.0+ GPU-er.</string>
|
||||
<string name="descriptor_indexing">Beskrivelsesindeksering</string>
|
||||
<string name="descriptor_indexing_description">Forbedrer tekstur- og bufferhåndtering, samt Maxwell-oversettelseslaget. Støttes av noen Vulkan 1.1 GPU-er og alle Vulkan 1.2+ GPU-er.</string>
|
||||
<string name="sample_shading">Prøvegjengivelse</string>
|
||||
<string name="sample_shading_description">Lar fragment-shaderen kjøres per prøve i et flerprøvefragment i stedet for en gang per fragment. Forbedrer grafikkvaliteten på bekostning av ytelse. Bare Vulkan 1.1+-enheter støtter denne utvidelsen.</string>
|
||||
<string name="sample_shading_fraction">Prøveskyggebrøk</string>
|
||||
<string name="sample_shading_fraction_description">Intensiteten til prøveskyggepasseringen. Høyere verdier forbedrer kvaliteten mer, men reduserer også ytelsen i større grad.</string>
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
<string name="provoking_vertex_description">Poprawia oświetlenie i obsługę wierzchołków w niektórych grach. Obsługiwane tylko przez GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indeksowanie deskryptorów</string>
|
||||
<string name="descriptor_indexing_description">Poprawia obsługę tekstur i buforów oraz warstwę tłumaczenia Maxwell. Obsługiwane przez niektóre GPU Vulkan 1.1 i wszystkie GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Cieniowanie próbek</string>
|
||||
<string name="sample_shading_description">Pozwala shaderowi fragmentów wykonywać się na próbkę w fragmencie wielopróbkowym zamiast raz na fragment. Poprawia jakość grafiki kosztem wydajności. Tylko urządzenia Vulkan 1.1+ obsługują to rozszerzenie.</string>
|
||||
<string name="sample_shading_fraction">Ułamek cieniowania próbki</string>
|
||||
<string name="sample_shading_fraction_description">Intensywność przebiegu cieniowania próbki. Wyższe wartości poprawiają jakość, ale także w większym stopniu zmniejszają wydajność.</string>
|
||||
|
||||
|
||||
@@ -91,6 +91,8 @@
|
||||
<string name="provoking_vertex_description">Vértice Provocante: Melhora a iluminação e o processamento de vértices em certos jogos. Suportado apenas em GPUs com Vulkan 1.0 ou superior.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
<string name="descriptor_indexing_description">Indexação de Descritores: Melhora o processamento de texturas e buffers, assim como a camada de tradução Maxwell. Suportado por algumas GPUs Vulkan 1.1 e todas as GPUs Vulkan 1.2 ou superiores.</string>
|
||||
<string name="sample_shading">Sample Shading</string>
|
||||
<string name="sample_shading_description">Amostragem de Sombreamento: Permite que o shader de fragmento seja processado por cada amostra em fragmentos multiamostrados, em vez de executar uma vez por fragmento, melhorando a qualidade gráfica, porém impactando levemente o desempenho. Funciona apenas em dispositivos Vulkan 1.1 ou superiores.</string>
|
||||
<string name="sample_shading_fraction">Sample Shading Fraction</string>
|
||||
<string name="sample_shading_fraction_description">Fração de Sombreamento de Amostra: Define a intensidade do sample shading. Quanto maior, melhor a qualidade, mas maior o impacto no desempenho.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">Melhora a iluminação e o tratamento de vértices em certos jogos. Suportado apenas em GPUs Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Indexação de descritores</string>
|
||||
<string name="descriptor_indexing_description">Melhora o tratamento de texturas e buffers, assim como a camada de tradução Maxwell. Suportado por algumas GPUs Vulkan 1.1 e todas Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Amostragem de sombreamento</string>
|
||||
<string name="sample_shading_description">Permite que o fragment shader seja executado por amostra num fragmento multi-amostrado em vez de uma vez por fragmento. Melhora a qualidade gráfica à custa de desempenho. Apenas dispositivos Vulkan 1.1+ suportam esta extensão.</string>
|
||||
<string name="sample_shading_fraction">Fração de Sombreamento de Amostra</string>
|
||||
<string name="sample_shading_fraction_description">A intensidade da passagem de sombreamento de amostra. Valores mais elevados melhoram a qualidade, mas também reduzem o desempenho numa maior medida.</string>
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
<string name="provoking_vertex_description">Улучшает освещение и обработку вершин в некоторых играх. Поддерживается только ГПУ с Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Индексирование дескрипторов</string>
|
||||
<string name="descriptor_indexing_description">Улучшает обработку текстур и буферов, а также слой перевода Maxwell. Поддерживается некоторыми ГПУ Vulkan 1.1 и всеми ГПУ Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Сэмпловый шейдинг</string>
|
||||
<string name="sample_shading_description">Позволяет шейдеру фрагментов выполняться на каждый сэмпл в мультисэмпловом фрагменте вместо одного раза на фрагмент. Улучшает качество графики ценой производительности. Только устройства с Vulkan 1.1+ поддерживают это расширение.</string>
|
||||
<string name="sample_shading_fraction">Доля сэмплового затенения</string>
|
||||
<string name="sample_shading_fraction_description">Интенсивность прохода сэмплового затенения. Более высокие значения улучшают качество, но и сильнее снижают производительность.</string>
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@
|
||||
<string name="provoking_vertex_description">Побољшава осветљење и вертификат руковања у одређеним играма. Подржан само на Вулкану 1.0+ ГПУ-у.</string>
|
||||
<string name="descriptor_indexing">Индексирање дескриптора</string>
|
||||
<string name="descriptor_indexing_description">Побољшава текстуру и руковање међуспремника, као и преводилачки слој Маквелл. Подржани од стране неких Вулкана 1.1 ГПУ-а и сви Вулкан 1.2+ ГПУ.</string>
|
||||
<string name="sample_shading">Семпловање сенчења</string>
|
||||
<string name="sample_shading_description">Омогућава фрагмент шејдеру да се извршава по узорку у вишеузорачном фрагменту уместо једном по фрагменту. Побољшава квалитет графике на рачун перформанси. Само Vulkan 1.1+ уређаји подржавају ову екстензију.</string>
|
||||
<string name="sample_shading_fraction">Удео сенчења узорка</string>
|
||||
<string name="sample_shading_fraction_description">Интензитет проласка сенчења узорка. Веће вредности побољшавају квалитет више, али такође више смањују перформансе.</string>
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
<string name="provoking_vertex_description">Покращує освітлення та взаємодію з вершинами у деяких іграх. Лише для ГП з підтримкою Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Індексація дескрипторів</string>
|
||||
<string name="descriptor_indexing_description">Покращує обробку текстур та буферів, а також шар перекладу Maxwell. Підтримується деякими GPU Vulkan 1.1 та всіма GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Шейдинг зразків</string>
|
||||
<string name="sample_shading_description">Дозволяє шейдеру фрагментів виконуватися на кожен семпл у багатосемпловому фрагменті замість одного разу на фрагмент. Покращує якість графіки за рахунок продуктивності. Лише пристрої з Vulkan 1.1+ підтримують це розширення.</string>
|
||||
<string name="sample_shading_fraction">Частка затінення зразка</string>
|
||||
<string name="sample_shading_fraction_description">Інтенсивність проходу затінення зразка. Вищі значення покращують якість, але й сильніше знижують продуктивність.</string>
|
||||
|
||||
|
||||
@@ -70,6 +70,8 @@
|
||||
<string name="provoking_vertex_description">Cải thiện ánh sáng và xử lý đỉnh trong một số trò chơi. Chỉ được hỗ trợ trên GPU Vulkan 1.0+.</string>
|
||||
<string name="descriptor_indexing">Lập chỉ mục bộ mô tả</string>
|
||||
<string name="descriptor_indexing_description">Cải thiện xử lý kết cấu và bộ đệm, cũng như lớp dịch Maxwell. Được hỗ trợ bởi một số GPU Vulkan 1.1 và tất cả GPU Vulkan 1.2+.</string>
|
||||
<string name="sample_shading">Tô bóng mẫu</string>
|
||||
<string name="sample_shading_description">Cho phép fragment shader thực thi trên mỗi mẫu trong một fragment đa mẫu thay vì một lần mỗi fragment. Cải thiện chất lượng đồ họa với chi phí hiệu suất. Chỉ thiết bị Vulkan 1.1+ hỗ trợ tiện ích mở rộng này.</string>
|
||||
<string name="sample_shading_fraction">Phần trăm tô bóng mẫu</string>
|
||||
<string name="sample_shading_fraction_description">Cường độ của bước tô bóng mẫu. Giá trị cao hơn cải thiện chất lượng tốt hơn nhưng cũng giảm hiệu suất nhiều hơn.</string>
|
||||
|
||||
|
||||
@@ -93,6 +93,8 @@
|
||||
<string name="provoking_vertex_description">改善某些游戏中的光照和顶点处理。仅支持Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
<string name="descriptor_indexing_description">改进纹理和缓冲区处理以及Maxwell转换层。部分Vulkan 1.1 GPU和所有Vulkan 1.2+ GPU支持。</string>
|
||||
<string name="sample_shading">采样着色</string>
|
||||
<string name="sample_shading_description">允许片段着色器在多采样片段中每个样本执行一次,而不是每个片段执行一次。以提高性能为代价改善图形质量。仅Vulkan 1.1+设备支持此扩展。</string>
|
||||
<string name="sample_shading_fraction">采样着色比例</string>
|
||||
<string name="sample_shading_fraction_description">采样着色处理的强度。值越高,质量改善越多,但性能降低也越明显。</string>
|
||||
|
||||
|
||||
@@ -96,6 +96,8 @@
|
||||
<string name="provoking_vertex_description">改善某些遊戲中的光照和頂點處理。僅支援Vulkan 1.0+ GPU。</string>
|
||||
<string name="descriptor_indexing">描述符索引</string>
|
||||
<string name="descriptor_indexing_description">改進紋理和緩衝區處理以及Maxwell轉換層。部分Vulkan 1.1 GPU和所有Vulkan 1.2+ GPU支援。</string>
|
||||
<string name="sample_shading">取樣著色</string>
|
||||
<string name="sample_shading_description">允許片段著色器在多取樣片段中每個樣本執行一次,而不是每個片段執行一次。以提高效能為代價改善圖形品質。僅Vulkan 1.1+裝置支援此擴充功能。</string>
|
||||
<string name="sample_shading_fraction">採樣著色比例</string>
|
||||
<string name="sample_shading_fraction_description">採樣著色處理的強度。數值越高,品質改善越多,但效能降低也越明顯。</string>
|
||||
|
||||
|
||||
@@ -101,8 +101,10 @@
|
||||
<string name="provoking_vertex_description">Improves lighting and vertex handling in certain games. Only supported on Vulkan 1.0+ GPUs.</string>
|
||||
<string name="descriptor_indexing">Descriptor Indexing</string>
|
||||
<string name="descriptor_indexing_description">Improves texture and buffer handling, as well as the Maxwell translation layer. Supported by some Vulkan 1.1 GPUs and all Vulkan 1.2+ GPUs.</string>
|
||||
<string name="sample_shading">Sample Shading</string>
|
||||
<string name="sample_shading_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead once per fragment. Improves graphics quality at the cost of some performance. Only Vulkan 1.1+ devices support this extension.</string>
|
||||
<string name="sample_shading_fraction">Sample Shading Fraction</string>
|
||||
<string name="sample_shading_fraction_description">Allows the fragment shader to execute per sample in a multi-sampled fragment instead of once per fragment. Improves graphics quality at the cost of performance. Higher values improve quality but degrade performance.</string>
|
||||
<string name="sample_shading_fraction_description">The intensity of the sample shading pass. Higher values improve quality more but also reduce performance to a greater extent.</string>
|
||||
|
||||
<string name="veil_renderer">Renderer</string>
|
||||
<string name="sync_memory_operations">Sync Memory Operations</string>
|
||||
|
||||
@@ -14,6 +14,8 @@ android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
kotlin.parallel.tasks.in.project=true
|
||||
android.defaults.buildfeatures.buildconfig=true
|
||||
|
||||
# Android Gradle plugin 8.0.2
|
||||
android.suppressUnsupportedCompileSdk=34
|
||||
android.native.buildOutput=verbose
|
||||
@@ -34,8 +34,8 @@ struct Member {
|
||||
struct RoomInformation {
|
||||
std::string name; ///< Name of the server
|
||||
std::string description; ///< Server description
|
||||
u32 member_slots; ///< Maximum number of members in this room
|
||||
u16 port; ///< The port of this room
|
||||
u32 member_slots{}; ///< Maximum number of members in this room
|
||||
u16 port{}; ///< The port of this room
|
||||
GameInfo preferred_game; ///< Game to advertise that you want to play
|
||||
std::string host_username; ///< Forum username of the host
|
||||
};
|
||||
@@ -46,8 +46,8 @@ struct Room {
|
||||
std::string id;
|
||||
std::string verify_uid; ///< UID used for verification
|
||||
std::string ip;
|
||||
u32 net_version;
|
||||
bool has_password;
|
||||
u32 net_version{};
|
||||
bool has_password = false;
|
||||
|
||||
std::vector<Member> members;
|
||||
};
|
||||
|
||||
@@ -514,7 +514,6 @@ struct Values {
|
||||
SwitchableSetting<bool> barrier_feedback_loops{linkage, true, "barrier_feedback_loops",
|
||||
Category::RendererAdvanced};
|
||||
|
||||
SwitchableSetting<u32, true> sample_shading_fraction{linkage, 0, 0, 100, "sample_shading_fraction", Category::RendererExtensions, Specialization::Scalar, true};
|
||||
SwitchableSetting<u8, true> dyna_state{linkage,
|
||||
#if defined (_WIN32)
|
||||
3,
|
||||
@@ -536,6 +535,17 @@ struct Values {
|
||||
SwitchableSetting<bool> vertex_input_dynamic_state{linkage, true, "vertex_input_dynamic_state", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> provoking_vertex{linkage, false, "provoking_vertex", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> descriptor_indexing{linkage, false, "descriptor_indexing", Category::RendererExtensions};
|
||||
SwitchableSetting<bool> sample_shading{linkage, false, "sample_shading", Category::RendererExtensions, Specialization::Paired};
|
||||
SwitchableSetting<u32, true> sample_shading_fraction{linkage,
|
||||
50,
|
||||
0,
|
||||
100,
|
||||
"sample_shading_fraction",
|
||||
Category::RendererExtensions,
|
||||
Specialization::Scalar,
|
||||
true,
|
||||
false,
|
||||
&sample_shading};
|
||||
|
||||
Setting<bool> renderer_debug{linkage, false, "debug", Category::RendererDebug};
|
||||
Setting<bool> renderer_shader_feedback{linkage, false, "shader_feedback",
|
||||
|
||||
@@ -17,8 +17,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
game_settings.cpp
|
||||
game_settings.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
cpu_manager.cpp
|
||||
@@ -45,7 +43,11 @@ add_library(core STATIC
|
||||
device_memory.cpp
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory.h
|
||||
device_memory_manager.h
|
||||
device_memory_manager.inc
|
||||
internal_network/legacy_online.cpp
|
||||
internal_network/legacy_online.h
|
||||
file_sys/bis_factory.cpp
|
||||
file_sys/bis_factory.h
|
||||
file_sys/card_image.cpp
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <array>
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "core/hle/service/services.h"
|
||||
#include "core/hle/service/set/system_settings_server.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
#include "core/internal_network/network.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory.h"
|
||||
@@ -137,6 +138,14 @@ struct System::Impl {
|
||||
kernel.SetMulticore(is_multicore);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
|
||||
// Start Legacy Online Service (UDP port 6000 + HTTP port 8080 for mobile app)
|
||||
if (!legacy_online) {
|
||||
legacy_online = std::make_unique<Network::LegacyOnlineService>();
|
||||
legacy_online->Start();
|
||||
}
|
||||
}
|
||||
|
||||
void ReinitializeIfNecessary(System& system) {
|
||||
@@ -293,6 +302,48 @@ struct System::Impl {
|
||||
return SystemResultStatus::Success;
|
||||
}
|
||||
|
||||
|
||||
void LoadOverrides(u64 programId) const {
|
||||
std::string vendor = gpu_core->Renderer().GetDeviceVendor();
|
||||
LOG_INFO(Core, "GPU Vendor: {}", vendor);
|
||||
|
||||
// Reset all per-game flags
|
||||
Settings::values.use_squashed_iterated_blend = false;
|
||||
|
||||
// Insert PC overrides here
|
||||
|
||||
#ifdef ANDROID
|
||||
// Example on how to set a setting based on the program ID and vendor
|
||||
if (programId == 0x010028600EBDA000 && vendor == "Mali") { // Mario 3d World
|
||||
// Settings::values.example = true;
|
||||
}
|
||||
|
||||
// Example array of program IDs
|
||||
const std::array<u64, 10> example_array = {
|
||||
//0xprogramId
|
||||
0x0004000000033400, // Game 1
|
||||
0x0004000000033500 // Game 2
|
||||
// And so on
|
||||
};
|
||||
|
||||
for (auto id : example_array) {
|
||||
if (programId == id) {
|
||||
// Settings::values.example = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Ninja Gaiden Ragebound
|
||||
constexpr u64 ngr = 0x0100781020710000ULL;
|
||||
|
||||
if (programId == ngr) {
|
||||
LOG_INFO(Core, "Enabling game specifc override: use_squashed_iterated_blend");
|
||||
Settings::values.use_squashed_iterated_blend = true;
|
||||
}
|
||||
}
|
||||
|
||||
SystemResultStatus Load(System& system, Frontend::EmuWindow& emu_window,
|
||||
const std::string& filepath,
|
||||
Service::AM::FrontendAppletParameters& params) {
|
||||
@@ -378,8 +429,7 @@ struct System::Impl {
|
||||
LOG_ERROR(Core, "Failed to find program id for ROM");
|
||||
}
|
||||
|
||||
|
||||
GameSettings::LoadOverrides(program_id, gpu_core->Renderer());
|
||||
LoadOverrides(program_id);
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info;
|
||||
game_info.name = name;
|
||||
@@ -428,6 +478,12 @@ struct System::Impl {
|
||||
stop_event = {};
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (legacy_online) {
|
||||
// Keep legacy_online running for the emulator's lifetime
|
||||
// legacy_online->Stop();
|
||||
// legacy_online.reset();
|
||||
}
|
||||
|
||||
if (auto room_member = Network::GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
room_member->SendGameInfo(game_info);
|
||||
@@ -517,6 +573,9 @@ struct System::Impl {
|
||||
/// Network instance
|
||||
Network::NetworkInstance network_instance;
|
||||
|
||||
/// Legacy Online Service
|
||||
std::unique_ptr<Network::LegacyOnlineService> legacy_online;
|
||||
|
||||
/// Debugger
|
||||
std::unique_ptr<Core::Debugger> debugger;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/savedata_factory.h"
|
||||
#include "core/file_sys/vfs/vfs.h"
|
||||
#include "core/file_sys/sdmc_factory.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -55,39 +57,114 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
return dir->CreateDirectoryRelative(save_directory);
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
u64 target_program_id = meta.program_id;
|
||||
// CRITICAL FIX: If the game requests Cache/Temp with ProgramID 0 (generic),
|
||||
// we MUST redirect it to the actual running TitleID, otherwise it looks in '.../0000000000000000'.
|
||||
if ((meta.type == SaveDataType::Cache || meta.type == SaveDataType::Temporary) && target_program_id == 0) {
|
||||
target_program_id = system.GetApplicationProcessProgramID();
|
||||
LOG_INFO(Service_FS, "Redirecting generic Cache request (ID 0) to active TitleID: {:016X}", target_program_id);
|
||||
}
|
||||
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, target_program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto out = dir->GetDirectoryRelative(save_directory);
|
||||
|
||||
if (out == nullptr) {
|
||||
LOG_WARNING(Service_FS, "Cache/Save path NOT FOUND: '{}'. Auto-create={}", save_directory, auto_create);
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "Cache/Save path FOUND: '{}'", save_directory);
|
||||
}
|
||||
|
||||
if (out == nullptr && (ShouldSaveDataBeAutomaticallyCreated(space, meta) && auto_create)) {
|
||||
LOG_INFO(Service_FS, "Auto-creating save directory...");
|
||||
return Create(space, meta);
|
||||
}
|
||||
|
||||
if (out != nullptr) {
|
||||
// Some emulators (Ryujinx) or even different firmware versions may rely on the commit
|
||||
// directories /0 or /1 being present for cache or save data.
|
||||
// We prioritizing /1 as it usually implies a newer commit if both exist,
|
||||
// but /0 is what's commonly used by Ryujinx for cache.
|
||||
|
||||
// Ryujinx behavior: If 0 exists and 1 does not, copy 0 to 1.
|
||||
auto dir_0 = out->GetSubdirectory("0");
|
||||
auto dir_1 = out->GetSubdirectory("1");
|
||||
|
||||
if (dir_0) LOG_INFO(Service_FS, "Found subdirectory '0' in save path.");
|
||||
if (dir_1) LOG_INFO(Service_FS, "Found subdirectory '1' in save path.");
|
||||
|
||||
if (dir_0 != nullptr && dir_1 == nullptr) {
|
||||
// User requested removal of auto-copy 0->1 logic.
|
||||
// We simply don't create/copy '1' if it's missing. We rely on fallback to '0'.
|
||||
LOG_INFO(Service_FS, "Ryujinx structure detected: '0' exists, '1' missing. Skipping copy (User Request).");
|
||||
// VfsRawCopyD(dir_0, dir_1); // REMOVED
|
||||
}
|
||||
|
||||
// Check for 'Addressables' and 'Addressables2' and delete 'json.cache' if present.
|
||||
// This is a specific workaround for games (e.g. Just Dance) that freeze if they find old cache metadata.
|
||||
// We force them to regenerate it.
|
||||
const auto CleanCache = [](VirtualDir root) {
|
||||
if (root == nullptr) return;
|
||||
const char* subdirs[] = {"Addressables", "Addressables2"};
|
||||
for (const char* subdir_name : subdirs) {
|
||||
auto subdir = root->GetSubdirectory(subdir_name);
|
||||
if (subdir != nullptr) {
|
||||
if (subdir->DeleteFile("json.cache")) {
|
||||
LOG_INFO(Service_FS, "Deleted stale 'json.cache' in '{}'", subdir_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
VirtualDir commit_root = nullptr;
|
||||
if (dir_1 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '1' as Commit Root.");
|
||||
commit_root = dir_1;
|
||||
} else if (dir_0 != nullptr) {
|
||||
LOG_INFO(Service_FS, "Using subdirectory '0' as Commit Root.");
|
||||
commit_root = dir_0;
|
||||
} else {
|
||||
LOG_INFO(Service_FS, "No '0' or '1' subdirectories found. Using parent folder as Commit Root.");
|
||||
commit_root = out;
|
||||
}
|
||||
|
||||
CleanCache(commit_root);
|
||||
|
||||
// Implement SD_Cache.XXXX logic for Cache Storage
|
||||
if (meta.type == SaveDataType::Cache) {
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
|
||||
auto sd_cache_dir = commit_root->GetSubdirectory(sd_cache_name);
|
||||
if (sd_cache_dir != nullptr) {
|
||||
LOG_INFO(Service_FS, "Found SD_Cache directory: '{}'", sd_cache_name);
|
||||
return sd_cache_dir;
|
||||
} else if (auto_create) {
|
||||
LOG_INFO(Service_FS, "Auto-creating SD_Cache directory: '{}'", sd_cache_name);
|
||||
return commit_root->CreateSubdirectory(sd_cache_name);
|
||||
} else {
|
||||
LOG_WARNING(Service_FS, "SD_Cache directory '{}' not found in commit root.", sd_cache_name);
|
||||
// Fallback? Or return nullptr?
|
||||
// If the user strictly wants SD_Cache, we should probably return nullptr if not found/created.
|
||||
// But for now, returning the commit_root might be safer for legacy compatibility IF SD_Cache isn't strictly enforced for existing saves?
|
||||
// User said "SD_Cache now represents which CacheStorage it will be". This implies correct behavior is to use SD_Cache.
|
||||
// If we return commit_root, it's CacheStorage_0 (conceptually) or just "everything".
|
||||
// Let's assume we return nullptr if not found, to trigger creation logic or error.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return commit_root;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
VirtualDir SaveDataFactory::GetSaveDataSpaceDirectory(SaveDataSpaceId space) const {
|
||||
return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
const auto path = GetSaveDataSpaceIdPath(space);
|
||||
// Ensure the directory exists, otherwise FindAllSaves fails.
|
||||
return GetOrCreateDirectoryRelative(dir, path);
|
||||
// return dir->GetDirectoryRelative(GetSaveDataSpaceIdPath(space));
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
@@ -96,12 +173,12 @@ std::string SaveDataFactory::GetSaveDataSpaceIdPath(SaveDataSpaceId space) {
|
||||
return "/system/";
|
||||
case SaveDataSpaceId::User:
|
||||
case SaveDataSpaceId::SdUser:
|
||||
case SaveDataSpaceId::Temporary: // Map into User so we can find the save/ folder
|
||||
return "/user/";
|
||||
case SaveDataSpaceId::Temporary:
|
||||
return "/temp/";
|
||||
default:
|
||||
ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
return "/unrecognized/"; ///< To prevent corruption when ignoring asserts.
|
||||
// ASSERT_MSG(false, "Unrecognized SaveDataSpaceId: {:02X}", static_cast<u8>(space));
|
||||
LOG_WARNING(Service_FS, "Unrecognized SaveDataSpaceId: {:02X}, defaulting to /user/", static_cast<u8>(space));
|
||||
return "/user/";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,8 +214,9 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
return fmt::format("{}save/{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
case SaveDataType::Temporary:
|
||||
return fmt::format("{}{:016X}/{:016X}{:016X}/{:016X}", out, 0, user_id[1], user_id[0],
|
||||
title_id);
|
||||
// Unified Cache/Temporary Path: Always use save/cache/{TitleID}
|
||||
// This simplifies user instructions and avoids UUID/Permission issues.
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
case SaveDataType::Cache:
|
||||
return fmt::format("{}save/cache/{:016X}", out, title_id);
|
||||
default:
|
||||
@@ -147,6 +225,40 @@ std::string SaveDataFactory::GetFullPath(ProgramId program_id, VirtualDir dir,
|
||||
}
|
||||
}
|
||||
|
||||
SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_,
|
||||
VirtualDir save_directory_)
|
||||
: system{system_}, program_id{program_id_}, dir{std::move(save_directory_)} {
|
||||
// Delete all temporary storages
|
||||
// On hardware, it is expected that temporary storage be empty at first use.
|
||||
dir->DeleteSubdirectoryRecursive("temp");
|
||||
}
|
||||
|
||||
SaveDataFactory::~SaveDataFactory() = default;
|
||||
|
||||
VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const {
|
||||
const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id,
|
||||
meta.user_id, meta.system_save_data_id);
|
||||
|
||||
auto created_dir = dir->CreateDirectoryRelative(save_directory);
|
||||
|
||||
// For Cache storage, enforce the new hierarchy: .../1/SD_Cache.XXXX
|
||||
if (meta.type == SaveDataType::Cache && created_dir != nullptr) {
|
||||
// Ensure commit directory '1' exists
|
||||
auto commit_dir = created_dir->GetSubdirectory("1");
|
||||
if (commit_dir == nullptr) {
|
||||
commit_dir = created_dir->CreateSubdirectory("1");
|
||||
}
|
||||
|
||||
if (commit_dir != nullptr) {
|
||||
// Create SD_Cache.XXXX
|
||||
const std::string sd_cache_name = fmt::format("SD_Cache.{:04X}", meta.index);
|
||||
return commit_dir->CreateSubdirectory(sd_cache_name);
|
||||
}
|
||||
}
|
||||
|
||||
return created_dir;
|
||||
}
|
||||
|
||||
std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) {
|
||||
if (future) {
|
||||
Common::UUID uuid;
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace Core::Frontend {
|
||||
struct CabinetParameters {
|
||||
Service::NFP::TagInfo tag_info;
|
||||
Service::NFP::RegisterInfo register_info;
|
||||
Service::NFP::CabinetMode mode;
|
||||
Service::NFP::CabinetMode mode{};
|
||||
};
|
||||
|
||||
using CabinetCallback = std::function<void(bool, const std::string&)>;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -20,9 +23,9 @@ struct KeyboardInitializeParameters {
|
||||
std::u16string initial_text;
|
||||
char16_t left_optional_symbol_key;
|
||||
char16_t right_optional_symbol_key;
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
s32 initial_cursor_position;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
s32 initial_cursor_position{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdPasswordMode password_mode;
|
||||
Service::AM::Frontend::SwkbdTextDrawType text_draw_type;
|
||||
@@ -34,12 +37,12 @@ struct KeyboardInitializeParameters {
|
||||
};
|
||||
|
||||
struct InlineAppearParameters {
|
||||
u32 max_text_length;
|
||||
u32 min_text_length;
|
||||
f32 key_top_scale_x;
|
||||
f32 key_top_scale_y;
|
||||
f32 key_top_translate_x;
|
||||
f32 key_top_translate_y;
|
||||
u32 max_text_length{};
|
||||
u32 min_text_length{};
|
||||
f32 key_top_scale_x{};
|
||||
f32 key_top_scale_y{};
|
||||
f32 key_top_translate_x{};
|
||||
f32 key_top_translate_y{};
|
||||
Service::AM::Frontend::SwkbdType type;
|
||||
Service::AM::Frontend::SwkbdKeyDisableFlags key_disable_flags;
|
||||
bool key_top_as_floating;
|
||||
@@ -50,7 +53,7 @@ struct InlineAppearParameters {
|
||||
|
||||
struct InlineTextParameters {
|
||||
std::u16string input_text;
|
||||
s32 cursor_position;
|
||||
s32 cursor_position{};
|
||||
};
|
||||
|
||||
class SoftwareKeyboardApplet : public Applet {
|
||||
|
||||
@@ -686,6 +686,16 @@ void FileSystemController::CreateFactories(FileSys::VfsFilesystem& vfs, bool ove
|
||||
using EdenPath = Common::FS::EdenPath;
|
||||
const auto sdmc_dir_path = Common::FS::GetEdenPath(EdenPath::SDMCDir);
|
||||
const auto sdmc_load_dir_path = sdmc_dir_path / "atmosphere/contents";
|
||||
|
||||
// If the NAND user save location doesn't exist but the SDMC contains
|
||||
// Nintendo/save (common portable save structure), create a host-side
|
||||
// symlink so the emulator will see those saves under the expected NAND path.
|
||||
// This helps users who placed saves under `<eden>/user/sdmc/Nintendo/save/...`.
|
||||
// SDMC to NAND sync logic REMOVED as per user request.
|
||||
// The emulator will no longer attempt to symlink or copy "Nintendo/save" or "SD_Cache.0000"
|
||||
// from SDMC to the NAND user save directory.
|
||||
// Users must ensure their save/cache structure is valid within the NAND directory itself
|
||||
// if that is what they intend to use, or rely on the game creating it.
|
||||
const auto rw_mode = FileSys::OpenMode::ReadWrite;
|
||||
|
||||
auto nand_directory =
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -14,7 +14,7 @@ namespace Service::FileSystem {
|
||||
|
||||
ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space)
|
||||
FileSys::SaveDataSpaceId space, bool cache_only)
|
||||
: ServiceFramework{system_, "ISaveDataInfoReader"}, save_data_controller{
|
||||
save_data_controller_} {
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -22,7 +22,7 @@ ISaveDataInfoReader::ISaveDataInfoReader(Core::System& system_,
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
FindAllSaves(space);
|
||||
FindAllSaves(space, cache_only);
|
||||
}
|
||||
|
||||
ISaveDataInfoReader::~ISaveDataInfoReader() = default;
|
||||
@@ -63,7 +63,7 @@ Result ISaveDataInfoReader::ReadSaveDataInfo(
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only) {
|
||||
FileSys::VirtualDir save_root{};
|
||||
const auto result = save_data_controller->OpenSaveDataSpace(&save_root, space);
|
||||
|
||||
@@ -74,8 +74,12 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
|
||||
for (const auto& type : save_root->GetSubdirectories()) {
|
||||
if (type->GetName() == "save") {
|
||||
FindNormalSaves(space, type);
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary) {
|
||||
if (cache_only) {
|
||||
FindCacheSaves(space, type);
|
||||
} else {
|
||||
FindNormalSaves(space, type);
|
||||
}
|
||||
} else if (space == FileSys::SaveDataSpaceId::Temporary && !cache_only) {
|
||||
FindTemporaryStorageSaves(space, type);
|
||||
}
|
||||
}
|
||||
@@ -84,6 +88,11 @@ void ISaveDataInfoReader::FindAllSaves(FileSys::SaveDataSpaceId space) {
|
||||
void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& save_id : type->GetSubdirectories()) {
|
||||
// Skip cache directory in normal scans
|
||||
if (save_id->GetName() == "cache") {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const auto& user_id : save_id->GetSubdirectories()) {
|
||||
// Skip non user id subdirectories
|
||||
if (user_id->GetName().size() != 0x20) {
|
||||
@@ -132,6 +141,96 @@ void ISaveDataInfoReader::FindNormalSaves(FileSys::SaveDataSpaceId space,
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindCacheSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
const auto cache_dir = type->GetSubdirectory("cache");
|
||||
if (cache_dir == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& title_id_dir : cache_dir->GetSubdirectories()) {
|
||||
const auto title_id = stoull_be(title_id_dir->GetName());
|
||||
// Simple validation: TitleID should be non-zero
|
||||
if (title_id == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine commit root (priority to "1", then "0", then self)
|
||||
auto commit_root = title_id_dir->GetSubdirectory("1");
|
||||
if (commit_root == nullptr) {
|
||||
commit_root = title_id_dir->GetSubdirectory("0");
|
||||
}
|
||||
// If neither exists, we might fall back to title_id_dir itself if users put SD_Cache directly there,
|
||||
// but based on SaveDataFactory we expect it inside 0 or 1.
|
||||
if (commit_root == nullptr) {
|
||||
// Check if SD_Cache exists directly in title_dir (legacy/fallback)
|
||||
bool has_sd_cache_root = false;
|
||||
for (const auto& sub : title_id_dir->GetSubdirectories()) {
|
||||
if (sub->GetName().find("SD_Cache.") == 0) {
|
||||
has_sd_cache_root = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (has_sd_cache_root) {
|
||||
commit_root = title_id_dir;
|
||||
} else {
|
||||
continue; // No valid storage found
|
||||
}
|
||||
}
|
||||
|
||||
bool found_any = false;
|
||||
for (const auto& sd_cache_dir : commit_root->GetSubdirectories()) {
|
||||
const std::string& name = sd_cache_dir->GetName();
|
||||
if (name.find("SD_Cache.") == 0) {
|
||||
// Parse index from "SD_Cache.XXXX" (hexadecimal)
|
||||
u64 index = 0;
|
||||
try {
|
||||
if (name.size() > 9) {
|
||||
index = std::stoull(name.substr(9), nullptr, 16); // Base 16 for hex
|
||||
}
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
sd_cache_dir->GetSize(),
|
||||
static_cast<u16>(index), // Correct index with cast
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
found_any = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for legacy "flat" cache if no SD_Cache folders found?
|
||||
// If the user specific structure IS enforced, maybe we don't fallback.
|
||||
// But if they have existing cache without the folder, it is effectively index 0.
|
||||
if (!found_any) {
|
||||
// Treat the entire commit_root as index 0 (Legacy behavior)
|
||||
info.emplace_back(SaveDataInfo{
|
||||
0,
|
||||
space,
|
||||
FileSys::SaveDataType::Cache,
|
||||
{}, // padding 0x6
|
||||
{}, // user_id (empty array match)
|
||||
0, // save_id
|
||||
title_id,
|
||||
commit_root->GetSize(),
|
||||
0, // index 0
|
||||
FileSys::SaveDataRank::Primary,
|
||||
{}, // padding 0x25
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ISaveDataInfoReader::FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space,
|
||||
const FileSys::VirtualDir& type) {
|
||||
for (const auto& user_id : type->GetSubdirectories()) {
|
||||
|
||||
@@ -16,7 +16,7 @@ class ISaveDataInfoReader final : public ServiceFramework<ISaveDataInfoReader> {
|
||||
public:
|
||||
explicit ISaveDataInfoReader(Core::System& system_,
|
||||
std::shared_ptr<SaveDataController> save_data_controller_,
|
||||
FileSys::SaveDataSpaceId space);
|
||||
FileSys::SaveDataSpaceId space, bool cache_only = false);
|
||||
~ISaveDataInfoReader() override;
|
||||
|
||||
struct SaveDataInfo {
|
||||
@@ -38,8 +38,9 @@ public:
|
||||
OutArray<SaveDataInfo, BufferAttr_HipcMapAlias> out_entries);
|
||||
|
||||
private:
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space);
|
||||
void FindAllSaves(FileSys::SaveDataSpaceId space, bool cache_only);
|
||||
void FindNormalSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindCacheSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
void FindTemporaryStorageSaves(FileSys::SaveDataSpaceId space, const FileSys::VirtualDir& type);
|
||||
|
||||
std::shared_ptr<SaveDataController> save_data_controller;
|
||||
|
||||
@@ -353,10 +353,10 @@ Result FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(
|
||||
|
||||
Result FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(
|
||||
OutInterface<ISaveDataInfoReader> out_interface) {
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
*out_interface = std::make_shared<ISaveDataInfoReader>(system, save_data_controller,
|
||||
FileSys::SaveDataSpaceId::Temporary);
|
||||
FileSys::SaveDataSpaceId::User, true);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -42,8 +42,16 @@ bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& co
|
||||
const auto& message_header = context.GetDomainMessageHeader();
|
||||
const auto object_id = message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field, which indicates
|
||||
// this is not actually a proper domain request
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, treat it as a regular session request
|
||||
return session_handler != nullptr;
|
||||
}
|
||||
|
||||
if (object_id > DomainHandlerCount()) {
|
||||
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
|
||||
return false;
|
||||
}
|
||||
return !DomainHandler(object_id - 1).expired();
|
||||
@@ -91,7 +99,18 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
|
||||
// If there is a DomainMessageHeader, then this is CommandType "Request"
|
||||
const auto& domain_message_header = context.GetDomainMessageHeader();
|
||||
const u32 object_id{domain_message_header.object_id};
|
||||
const u32 object_id = domain_message_header.object_id;
|
||||
|
||||
// Some games send magic numbers in the object_id field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
if (object_id == sfci_magic || object_id == sfco_magic) {
|
||||
// This is not a domain request, handle as regular session request
|
||||
LOG_DEBUG(IPC, "Detected magic number 0x{:08X} in object_id, treating as regular session request. Command={}",
|
||||
object_id, context.GetCommand());
|
||||
return session_handler->HandleSyncRequest(*server_session, context);
|
||||
}
|
||||
|
||||
switch (domain_message_header.command) {
|
||||
case IPC::DomainMessageHeader::CommandType::SendMessage:
|
||||
if (object_id > this->DomainHandlerCount()) {
|
||||
@@ -200,7 +219,17 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
// If this is an incoming message, only CommandType "Request" has a domain header
|
||||
// All outgoing domain messages have the domain header, if only incoming has it
|
||||
if (incoming || domain_message_header) {
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
// Check if the next value is actually a magic number (SFCI/SFCO)
|
||||
// Some games send these in the object_id field, indicating this is not a domain request
|
||||
const u32 possible_object_id = src_cmdbuf[rp.GetCurrentOffset() + 1]; // object_id is second field
|
||||
const u32 sfci_magic = Common::MakeMagic('S', 'F', 'C', 'I');
|
||||
const u32 sfco_magic = Common::MakeMagic('S', 'F', 'C', 'O');
|
||||
|
||||
if (possible_object_id != sfci_magic && possible_object_id != sfco_magic) {
|
||||
// This is a proper domain request
|
||||
domain_message_header = rp.PopRaw<IPC::DomainMessageHeader>();
|
||||
}
|
||||
// If it's a magic number, skip reading the domain header entirely
|
||||
} else {
|
||||
if (GetManager()->IsDomain()) {
|
||||
LOG_WARNING(IPC, "Domain request has no DomainMessageHeader!");
|
||||
@@ -220,9 +249,15 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
|
||||
if (incoming) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
// Only check magic if we have a valid data payload header
|
||||
// When domain header is skipped (SFCI in object_id), the structure is different
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'I'));
|
||||
}
|
||||
} else {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
if (domain_message_header) {
|
||||
ASSERT(data_payload_header->magic == Common::MakeMagic('S', 'F', 'C', 'O'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -79,7 +82,7 @@ using DeviceHandle = u64;
|
||||
|
||||
// This is nn::nfc::TagInfo
|
||||
struct TagInfo {
|
||||
UniqueSerialNumber uuid;
|
||||
UniqueSerialNumber uuid{};
|
||||
u8 uuid_length;
|
||||
INSERT_PADDING_BYTES(0x15);
|
||||
NfcProtocol protocol;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -315,7 +318,7 @@ static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
|
||||
// This is nn::nfp::RegisterInfo
|
||||
struct RegisterInfo {
|
||||
Service::Mii::CharInfo mii_char_info;
|
||||
WriteDate creation_date;
|
||||
WriteDate creation_date{};
|
||||
AmiiboName amiibo_name;
|
||||
u8 font_region;
|
||||
INSERT_PADDING_BYTES(0x7A);
|
||||
|
||||
@@ -164,7 +164,7 @@ IPlatformServiceManager::IPlatformServiceManager(Core::System& system_, const ch
|
||||
// Rebuild shared fonts from data ncas or synthesize
|
||||
|
||||
impl->shared_font = std::make_shared<Kernel::PhysicalMemory>(SHARED_FONT_MEM_SIZE);
|
||||
for (auto font : SHARED_FONTS) {
|
||||
for (auto& font : SHARED_FONTS) {
|
||||
FileSys::VirtualFile romfs;
|
||||
const auto nca =
|
||||
nand->GetEntry(static_cast<u64>(font.first), FileSys::ContentRecordType::Data);
|
||||
@@ -261,7 +261,7 @@ Result IPlatformServiceManager::GetSharedFontInOrderOfPriority(
|
||||
out_font_sizes.size(), impl->shared_font_regions.size()});
|
||||
|
||||
for (size_t i = 0; i < max_size; i++) {
|
||||
auto region = impl->GetSharedFontRegion(i);
|
||||
auto& region = impl->GetSharedFontRegion(i);
|
||||
|
||||
out_font_codes[i] = static_cast<u32>(i);
|
||||
out_font_offsets[i] = region.offset;
|
||||
|
||||
@@ -66,7 +66,9 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
|
||||
|
||||
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
|
||||
system.SetNVDECActive(true);
|
||||
if (!system.GetNVDECActive()) {
|
||||
system.SetNVDECActive(true);
|
||||
}
|
||||
sessions[fd] = session_id;
|
||||
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
@@ -22,6 +22,9 @@
|
||||
#include "core/internal_network/sockets.h"
|
||||
#include "network/network.h"
|
||||
#include <common/settings.h>
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
using Common::Expected;
|
||||
using Common::Unexpected;
|
||||
@@ -315,7 +318,15 @@ void BSD::Fcntl(HLERequestContext& ctx) {
|
||||
const u32 cmd = rp.Pop<u32>();
|
||||
const s32 arg = rp.Pop<s32>();
|
||||
|
||||
LOG_DEBUG(Service, "called. fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
// Log with more detail to understand non-blocking configuration
|
||||
if (cmd == 4) { // SETFL
|
||||
bool is_nonblock = (arg & 0x800) != 0; // O_NONBLOCK
|
||||
LOG_INFO(Service, "Fcntl SETFL fd={} arg={} (non-blocking={})", fd, arg, is_nonblock);
|
||||
} else if (cmd == 3) { // GETFL
|
||||
LOG_INFO(Service, "Fcntl GETFL fd={}", fd);
|
||||
} else {
|
||||
LOG_INFO(Service, "Fcntl fd={} cmd={} arg={}", fd, cmd, arg);
|
||||
}
|
||||
|
||||
const auto [ret, bsd_errno] = FcntlImpl(fd, static_cast<FcntlCmd>(cmd), arg);
|
||||
|
||||
@@ -802,33 +813,40 @@ Errno BSD::SetSockOptImpl(s32 fd, u32 level, OptName optname, std::span<const u8
|
||||
|
||||
switch (optname) {
|
||||
case OptName::REUSEADDR:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} REUSEADDR={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid REUSEADDR value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetReuseAddr(value != 0));
|
||||
case OptName::KEEPALIVE:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} KEEPALIVE={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid KEEPALIVE value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetKeepAlive(value != 0));
|
||||
case OptName::BROADCAST:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} BROADCAST={}", fd, value);
|
||||
if (value != 0 && value != 1) {
|
||||
LOG_WARNING(Service, "Invalid BROADCAST value: {}", value);
|
||||
return Errno::INVAL;
|
||||
}
|
||||
return Translate(socket->SetBroadcast(value != 0));
|
||||
case OptName::SNDBUF:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} SNDBUF={}", fd, value);
|
||||
return Translate(socket->SetSndBuf(value));
|
||||
case OptName::RCVBUF:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} RCVBUF={}", fd, value);
|
||||
return Translate(socket->SetRcvBuf(value));
|
||||
case OptName::SNDTIMEO:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} SNDTIMEO={}", fd, value);
|
||||
return Translate(socket->SetSndTimeo(value));
|
||||
case OptName::RCVTIMEO:
|
||||
LOG_INFO(Service, "SetSockOpt fd={} RCVTIMEO={}", fd, value);
|
||||
return Translate(socket->SetRcvTimeo(value));
|
||||
case OptName::NOSIGPIPE:
|
||||
LOG_WARNING(Service, "(STUBBED) setting NOSIGPIPE to {}", value);
|
||||
LOG_INFO(Service, "SetSockOpt fd={} NOSIGPIPE={}", fd, value);
|
||||
return Errno::SUCCESS;
|
||||
default:
|
||||
LOG_WARNING(Service, "(STUBBED) Unimplemented optname={} (0x{:x}), returning INVAL",
|
||||
@@ -919,12 +937,134 @@ std::pair<s32, Errno> BSD::RecvFromImpl(s32 fd, u32 flags, std::vector<u8>& mess
|
||||
return {ret, bsd_errno};
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
std::pair<s32, Errno> BSD::SendImpl(s32 fd, u32 flags, std::span<const u8> message) {
|
||||
if (!IsFileDescriptorValid(fd)) {
|
||||
return {-1, Errno::BADF};
|
||||
}
|
||||
|
||||
const size_t original_size = message.size();
|
||||
|
||||
// Inspect for Authorization header to inject custom token (HTTP/Localhost support)
|
||||
const std::string_view data_view(reinterpret_cast<const char*>(message.data()),
|
||||
message.size());
|
||||
|
||||
// Optimized check: only look if it looks like an HTTP request with auth
|
||||
// We do a case-insensitive search for the specific header pattern
|
||||
std::string request_str(data_view);
|
||||
std::string request_lower = Common::ToLower(request_str);
|
||||
|
||||
size_t auth_pos = request_lower.find("authorization: switch t=");
|
||||
|
||||
if (auth_pos != std::string::npos) {
|
||||
LOG_INFO(Service,
|
||||
"BSDJ: Found 'Authorization: switch t=' in SendImpl. Injecting custom auth.");
|
||||
|
||||
const auto auth_file_path =
|
||||
Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
|
||||
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read,
|
||||
Common::FS::FileType::TextFile);
|
||||
|
||||
if (auth_file.IsOpen()) {
|
||||
std::vector<u8> file_content_vec(auth_file.GetSize());
|
||||
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
|
||||
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
|
||||
|
||||
// Simple Base64 Decoder
|
||||
auto DecodeBase64 = [](std::string_view input) -> std::string {
|
||||
static const int T[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
|
||||
|
||||
std::string out;
|
||||
int val = 0, valb = -8;
|
||||
for (unsigned char c : input) {
|
||||
if (T[c] == -1)
|
||||
break;
|
||||
val = (val << 6) + T[c];
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(char((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Decode the INI content
|
||||
std::string file_content = DecodeBase64(encoded_content);
|
||||
|
||||
// Find TickedId
|
||||
std::string auth_token;
|
||||
static constexpr std::string_view KeyName = "TickedId=";
|
||||
size_t key_pos = file_content.find(KeyName);
|
||||
|
||||
if (key_pos != std::string::npos) {
|
||||
size_t value_start = key_pos + KeyName.size();
|
||||
size_t value_end = file_content.find_first_of("\r\n", value_start);
|
||||
if (value_end == std::string::npos) {
|
||||
value_end = file_content.size();
|
||||
}
|
||||
auth_token = file_content.substr(value_start, value_end - value_start);
|
||||
}
|
||||
|
||||
if (!auth_token.empty()) {
|
||||
// Ensure the token has the correct prefix "uplaypc_v1 t="
|
||||
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
|
||||
auth_token = "uplaypc_v1 t=" + auth_token;
|
||||
}
|
||||
|
||||
// Find end of the line
|
||||
size_t end_pos = request_str.find("\r\n", auth_pos);
|
||||
if (end_pos != std::string::npos) {
|
||||
bool is_header_start = (auth_pos == 0) || (request_str[auth_pos - 1] == '\n');
|
||||
|
||||
if (is_header_start) {
|
||||
LOG_INFO(Service, "BSDJ: Injecting token (TickedId): {}...",
|
||||
auth_token.substr(0, 20));
|
||||
|
||||
std::string new_header = "Authorization: " + auth_token;
|
||||
request_str.replace(auth_pos, end_pos - auth_pos, new_header);
|
||||
|
||||
// Send the MODIFIED message
|
||||
std::span<const u8> new_message(
|
||||
reinterpret_cast<const u8*>(request_str.data()),
|
||||
request_str.size());
|
||||
auto result = Translate(
|
||||
file_descriptors[fd]->socket->Send(new_message, flags));
|
||||
LOG_CRITICAL(
|
||||
Service,
|
||||
"SendImpl (Modified) real_sent={} original_size={} errno={}",
|
||||
result.first, original_size, static_cast<int>(result.second));
|
||||
|
||||
// Mask the return size: If we successfully sent the larger buffer,
|
||||
// tell the guest we sent exactly what they asked for.
|
||||
if (result.first > 0 && result.second == Errno::SUCCESS) {
|
||||
// Return original size to prevent guest confusion
|
||||
return {static_cast<s32>(original_size), Errno::SUCCESS};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// LOG_WARNING(Service, "jdlo_auth.ini not found");
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Service, "SendImpl called: fd={}, size={} bytes, flags={}", fd, message.size(),
|
||||
flags);
|
||||
auto result = Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
LOG_CRITICAL(Service, "SendImpl result: sent={} bytes, errno={}", result.first,
|
||||
static_cast<int>(result.second));
|
||||
return result;
|
||||
}
|
||||
return Translate(file_descriptors[fd]->socket->Send(message, flags));
|
||||
}
|
||||
|
||||
std::pair<s32, Errno> BSD::SendToImpl(s32 fd, u32 flags, std::span<const u8> message,
|
||||
std::span<const u8> addr) {
|
||||
|
||||
@@ -60,12 +60,29 @@ NSD::NSD(Core::System& system_, const char* name) : ServiceFramework{system_, na
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting NSD host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static std::string ResolveImpl(const std::string& fqdn_in) {
|
||||
// The real implementation makes various substitutions.
|
||||
// For now we just return the string as-is, which is good enough when not
|
||||
// connecting to real Nintendo servers.
|
||||
LOG_WARNING(Service, "(STUBBED) called, fqdn_in={}", fqdn_in);
|
||||
return fqdn_in;
|
||||
|
||||
return GetRedirectedHost(fqdn_in);
|
||||
}
|
||||
|
||||
static Result ResolveCommon(const std::string& fqdn_in, std::array<char, 0x100>& fqdn_out) {
|
||||
|
||||
@@ -29,13 +29,13 @@ SFDNSRES::SFDNSRES(Core::System& system_) : ServiceFramework{system_, "sfdnsres"
|
||||
{4, nullptr, "GetHostStringErrorRequest"},
|
||||
{5, &SFDNSRES::GetGaiStringErrorRequest, "GetGaiStringErrorRequest"},
|
||||
{6, &SFDNSRES::GetAddrInfoRequest, "GetAddrInfoRequest"},
|
||||
{7, nullptr, "GetNameInfoRequest"},
|
||||
{8, nullptr, "RequestCancelHandleRequest"},
|
||||
{7, &SFDNSRES::GetNameInfoRequest, "GetNameInfoRequest"},
|
||||
{8, &SFDNSRES::RequestCancelHandleRequest, "RequestCancelHandleRequest"},
|
||||
{9, nullptr, "CancelRequest"},
|
||||
{10, &SFDNSRES::GetHostByNameRequestWithOptions, "GetHostByNameRequestWithOptions"},
|
||||
{11, nullptr, "GetHostByAddrRequestWithOptions"},
|
||||
{12, &SFDNSRES::GetAddrInfoRequestWithOptions, "GetAddrInfoRequestWithOptions"},
|
||||
{13, nullptr, "GetNameInfoRequestWithOptions"},
|
||||
{13, &SFDNSRES::GetNameInfoRequestWithOptions, "GetNameInfoRequestWithOptions"},
|
||||
{14, &SFDNSRES::ResolverSetOptionRequest, "ResolverSetOptionRequest"},
|
||||
{15, nullptr, "ResolverGetOptionRequest"},
|
||||
};
|
||||
@@ -66,6 +66,21 @@ static bool IsBlockedHost(const std::string& host) {
|
||||
[&host](const std::string& domain) { return host.find(domain) != std::string::npos; });
|
||||
}
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service, "Redirecting host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static NetDbError GetAddrInfoErrorToNetDbError(GetAddrInfoError result) {
|
||||
// These combinations have been verified on console (but are not
|
||||
// exhaustive).
|
||||
@@ -163,7 +178,8 @@ static std::pair<u32, GetAddrInfoError> GetHostByNameRequestImpl(HLERequestConte
|
||||
parameters.use_nsd_resolve, parameters.cancel_handle, parameters.process_id);
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
// For now, ignore options, which are in input buffer 1 for GetHostByNameRequestWithOptions.
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
@@ -281,7 +297,8 @@ static std::pair<u32, GetAddrInfoError> GetAddrInfoRequestImpl(HLERequestContext
|
||||
// before looking up.
|
||||
|
||||
const auto host_buffer = ctx.ReadBuffer(0);
|
||||
const std::string host = Common::StringFromBuffer(host_buffer);
|
||||
std::string host = Common::StringFromBuffer(host_buffer);
|
||||
host = GetRedirectedHost(host);
|
||||
|
||||
// Prevent resolution of Nintendo servers
|
||||
if (IsBlockedHost(host)) {
|
||||
@@ -364,6 +381,120 @@ void SFDNSRES::GetAddrInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void SFDNSRES::GetNameInfoRequest(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 cancel_handle = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service, "called. flags={}, cancel_handle={}, process_id={}", flags, cancel_handle,
|
||||
process_id);
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::GetNameInfoRequestWithOptions(HLERequestContext& ctx) {
|
||||
struct InputParameters {
|
||||
u32 flags;
|
||||
u32 interface_index;
|
||||
u64 process_id;
|
||||
u32 padding; // 0x14 + 4 = 0x18? No. 0x14 aligned to 8 bytes?
|
||||
// Wait, sizeof(InputParameters) == 0x14.
|
||||
};
|
||||
// Derived from partial snippets:
|
||||
// u32 flags, u32 interface_index, u64 process_id.
|
||||
// 4 + 4 + 8 = 16 bytes (0x10).
|
||||
// The previous prompt had static_assert size 0x14.
|
||||
// Maybe a u32 padding?
|
||||
|
||||
// Let's rely on standard layout.
|
||||
// I will use manual popping for safety if struct definition is unknown.
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto addr_in = rp.PopRaw<SockAddrIn>();
|
||||
const u32 flags = rp.Pop<u32>();
|
||||
const u32 interface_index = rp.Pop<u32>();
|
||||
const u64 process_id = rp.Pop<u64>();
|
||||
(void)flags;
|
||||
(void)interface_index;
|
||||
(void)process_id;
|
||||
// If there was padding, it might be implicitly popped or ignored.
|
||||
|
||||
struct OutputParameters {
|
||||
u32 data_size;
|
||||
GetAddrInfoError gai_error;
|
||||
NetDbError netdb_error;
|
||||
Errno bsd_errno;
|
||||
};
|
||||
static_assert(sizeof(OutputParameters) == 0x10);
|
||||
|
||||
const auto res = Network::GetNameInfo(Translate(addr_in));
|
||||
if (res.second != 0) {
|
||||
const auto network_error = Network::TranslateGetAddrInfoErrorFromNative(res.second);
|
||||
const auto service_error = Translate(network_error);
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = 0,
|
||||
.gai_error = service_error,
|
||||
.netdb_error = GetAddrInfoErrorToNetDbError(service_error),
|
||||
.bsd_errno = GetAddrInfoErrorToErrno(service_error),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string& host = res.first;
|
||||
const u32 data_size = static_cast<u32>(host.size() + 1);
|
||||
|
||||
ctx.WriteBuffer(host.data(), data_size, 0);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw(OutputParameters{
|
||||
.data_size = data_size,
|
||||
.gai_error = GetAddrInfoError::SUCCESS,
|
||||
.netdb_error = NetDbError::Success,
|
||||
.bsd_errno = Errno::SUCCESS,
|
||||
});
|
||||
}
|
||||
|
||||
void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
@@ -372,4 +503,24 @@ void SFDNSRES::ResolverSetOptionRequest(HLERequestContext& ctx) {
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<s32>(0); // bsd errno
|
||||
}
|
||||
|
||||
void SFDNSRES::RequestCancelHandleRequest(HLERequestContext& ctx) {
|
||||
// This is just a stub for now.
|
||||
// In a real implementation this would likely cancel a pending request represented by the handle.
|
||||
LOG_WARNING(Service, "(STUBBED) called");
|
||||
|
||||
struct InputParameters {
|
||||
u32 cancel_handle;
|
||||
};
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto input = rp.PopRaw<InputParameters>();
|
||||
|
||||
LOG_DEBUG(Service, "cancel_handle={}", input.cancel_handle);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(0); // cancel_handle (response seems to echo it or return a new one? usually just an error code or unrelated)
|
||||
// Actually based on typical patterns, it probably returns an error code (bsd_errno).
|
||||
rb.Push<s32>(0); // bsd_errno
|
||||
}
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -22,7 +22,10 @@ private:
|
||||
void GetHostByNameRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequest(HLERequestContext& ctx);
|
||||
void GetAddrInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void GetNameInfoRequest(HLERequestContext& ctx);
|
||||
void GetNameInfoRequestWithOptions(HLERequestContext& ctx);
|
||||
void ResolverSetOptionRequest(HLERequestContext& ctx);
|
||||
void RequestCancelHandleRequest(HLERequestContext& ctx);
|
||||
};
|
||||
|
||||
} // namespace Service::Sockets
|
||||
|
||||
@@ -16,10 +16,12 @@ enum class Errno : u32 {
|
||||
SUCCESS = 0,
|
||||
BADF = 9,
|
||||
AGAIN = 11,
|
||||
ACCES = 13,
|
||||
INVAL = 22,
|
||||
MFILE = 24,
|
||||
PIPE = 32,
|
||||
MSGSIZE = 90,
|
||||
ADDRINUSE = 98,
|
||||
CONNABORTED = 103,
|
||||
CONNRESET = 104,
|
||||
NOTCONN = 107,
|
||||
|
||||
@@ -37,6 +37,10 @@ Errno Translate(Network::Errno value) {
|
||||
return Errno::CONNRESET;
|
||||
case Network::Errno::INPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case Network::Errno::ACCES:
|
||||
return Errno::ACCES;
|
||||
case Network::Errno::ADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", value);
|
||||
return Errno::SUCCESS;
|
||||
@@ -261,7 +265,9 @@ PollEvents Translate(Network::PollEvents flags) {
|
||||
Network::SockAddrIn Translate(SockAddrIn value) {
|
||||
// Note: 6 is incorrect, but can be passed by homebrew (because libnx sets
|
||||
// sin_len to 6 when deserializing getaddrinfo results).
|
||||
ASSERT(value.len == 0 || value.len == sizeof(value) || value.len == 6);
|
||||
if (value.len != 0 && value.len != sizeof(value) && value.len != 6) {
|
||||
LOG_WARNING(Service, "Unexpected SockAddrIn length={}", value.len);
|
||||
}
|
||||
|
||||
return {
|
||||
.family = Translate(static_cast<Domain>(value.family)),
|
||||
|
||||
@@ -117,15 +117,20 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
|
||||
shared_data->connection_count++;
|
||||
LOG_CRITICAL(Service_SSL, "ISslConnection created! Total connections: {}", shared_data->connection_count);
|
||||
}
|
||||
|
||||
~ISslConnection() {
|
||||
shared_data->connection_count--;
|
||||
if (fd_to_close.has_value()) {
|
||||
const s32 fd = *fd_to_close;
|
||||
if (!do_not_close_socket) {
|
||||
LOG_ERROR(Service_SSL,
|
||||
"do_not_close_socket was changed after setting socket; is this right?");
|
||||
if (do_not_close_socket) {
|
||||
// If we aren't supposed to close the socket, but we have an fd_to_close,
|
||||
// that means the configuration changed after we took ownership.
|
||||
// This is weird but we should probably honor the flag.
|
||||
// However, the original valid logic seemed to imply we duped the socket
|
||||
// and should close our dup... but let's stick to what the flag says.
|
||||
LOG_INFO(Service_SSL, "do_not_close_socket is true, skipping close of fd {}", fd);
|
||||
} else {
|
||||
auto bsd = system.ServiceManager().GetService<Service::Sockets::BSD>("bsd:u");
|
||||
if (bsd) {
|
||||
@@ -269,16 +274,17 @@ private:
|
||||
}
|
||||
|
||||
Result PendingImpl(s32* out_pending) {
|
||||
LOG_WARNING(Service_SSL, "(STUBBED) called.");
|
||||
*out_pending = 0;
|
||||
return ResultSuccess;
|
||||
ASSERT_OR_EXECUTE(did_handshake, { return ResultInternalError; });
|
||||
return backend->Pending(out_pending);
|
||||
}
|
||||
|
||||
void SetSocketDescriptor(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const s32 in_fd = rp.Pop<s32>();
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor called with fd={}", in_fd);
|
||||
s32 out_fd{-1};
|
||||
const Result res = SetSocketDescriptorImpl(&out_fd, in_fd);
|
||||
LOG_CRITICAL(Service_SSL, "SetSocketDescriptor result: res={}, out_fd={}", res.raw, out_fd);
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(res);
|
||||
rb.Push<s32>(out_fd);
|
||||
@@ -308,7 +314,9 @@ private:
|
||||
}
|
||||
|
||||
void DoHandshake(HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_SSL, "DoHandshake called, socket={}", socket != nullptr);
|
||||
const Result res = DoHandshakeImpl();
|
||||
LOG_INFO(Service_SSL, "DoHandshake result: {}, did_handshake={}", res.raw, did_handshake);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(res);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
virtual Result Read(size_t* out_size, std::span<u8> data) = 0;
|
||||
virtual Result Write(size_t* out_size, std::span<const u8> data) = 0;
|
||||
virtual Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) = 0;
|
||||
virtual Result Pending(s32* out_pending) = 0;
|
||||
};
|
||||
|
||||
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend);
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
@@ -48,28 +49,32 @@ bool OneTimeInitBIO();
|
||||
#ifdef YUZU_BUNDLED_OPENSSL
|
||||
// This is ported from httplib
|
||||
struct scope_exit {
|
||||
explicit scope_exit(std::function<void(void)> &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
explicit scope_exit(std::function<void(void)> &&f)
|
||||
: exit_function(std::move(f)), execute_on_destruction{true} {}
|
||||
|
||||
scope_exit(scope_exit &&rhs) noexcept
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
scope_exit(scope_exit &&rhs) noexcept
|
||||
: exit_function(std::move(rhs.exit_function)),
|
||||
execute_on_destruction{rhs.execute_on_destruction} {
|
||||
rhs.release();
|
||||
}
|
||||
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) { this->exit_function(); }
|
||||
}
|
||||
~scope_exit() {
|
||||
if (execute_on_destruction) {
|
||||
this->exit_function();
|
||||
}
|
||||
}
|
||||
|
||||
void release() { this->execute_on_destruction = false; }
|
||||
void release() {
|
||||
this->execute_on_destruction = false;
|
||||
}
|
||||
|
||||
private:
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
scope_exit(const scope_exit &) = delete;
|
||||
void operator=(const scope_exit &) = delete;
|
||||
scope_exit &operator=(scope_exit &&) = delete;
|
||||
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
std::function<void(void)> exit_function;
|
||||
bool execute_on_destruction;
|
||||
};
|
||||
|
||||
inline X509_STORE *CreateCaCertStore(const char *ca_cert,
|
||||
@@ -115,6 +120,22 @@ inline void LoadCaCertStore(SSL_CTX* ctx, const char* ca_cert, std::size_t size)
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
static const std::vector<std::pair<std::string, std::string>> redirectionRules = {
|
||||
{"public-ubiservices.com", "jdlo.ovosimpatico.com"},
|
||||
{"public-ubiservices.ubi.com", "jdlo.ovosimpatico.com"},
|
||||
};
|
||||
|
||||
static std::string GetRedirectedHost(const std::string& host) {
|
||||
for (const auto& rule : redirectionRules) {
|
||||
if (host.find(rule.first) != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Redirecting SSL host '{}' to '{}'", host, rule.second);
|
||||
return rule.second;
|
||||
}
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SSLConnectionBackendOpenSSL final : public SSLConnectionBackend {
|
||||
@@ -157,7 +178,8 @@ public:
|
||||
socket = std::move(socket_in);
|
||||
}
|
||||
|
||||
Result SetHostName(const std::string& hostname) override {
|
||||
Result SetHostName(const std::string& hostname_in) override {
|
||||
const std::string hostname = GetRedirectedHost(hostname_in);
|
||||
if (!SSL_set1_host(ssl, hostname.c_str())) { // hostname for verification
|
||||
LOG_ERROR(Service_SSL, "SSL_set1_host({}) failed", hostname);
|
||||
return CheckOpenSSLErrors();
|
||||
@@ -195,6 +217,123 @@ public:
|
||||
}
|
||||
|
||||
Result Write(size_t* out_size, std::span<const u8> data) override {
|
||||
const size_t original_size = data.size();
|
||||
const std::string_view data_view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||
|
||||
// Log all POST requests for debugging
|
||||
if (data_view.size() > 5 && data_view.substr(0, 5) == "POST ") {
|
||||
LOG_INFO(Service_SSL, "Intercepted POST request. Length: {}. Preview: {}", data.size(), data_view.substr(0, std::min(data_view.size(), size_t(200))));
|
||||
}
|
||||
|
||||
std::string request_str(data_view);
|
||||
std::string request_lower = Common::ToLower(request_str);
|
||||
|
||||
// Look for the specific authorization header value we want to replace: "switch t="
|
||||
// We match "authorization: switch t=" case-insensitively
|
||||
size_t auth_pos = request_lower.find("authorization: switch t=");
|
||||
|
||||
if (auth_pos != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Found 'Authorization: switch t=' header. Injecting custom auth.");
|
||||
|
||||
const auto auth_file_path = Common::FS::GetEdenPath(Common::FS::EdenPath::EdenDir) / "jdlo_auth.ini";
|
||||
Common::FS::IOFile auth_file(auth_file_path, Common::FS::FileAccessMode::Read, Common::FS::FileType::TextFile);
|
||||
if (auth_file.IsOpen()) {
|
||||
std::vector<u8> file_content_vec(auth_file.GetSize());
|
||||
if (auth_file.Read(file_content_vec) == file_content_vec.size()) {
|
||||
std::string encoded_content(file_content_vec.begin(), file_content_vec.end());
|
||||
|
||||
// Simple Base64 Decoder
|
||||
auto DecodeBase64 = [](std::string_view input) -> std::string {
|
||||
static const int T[256] = {
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1};
|
||||
|
||||
std::string out;
|
||||
int val = 0, valb = -8;
|
||||
for (unsigned char c : input) {
|
||||
if (T[c] == -1)
|
||||
break;
|
||||
val = (val << 6) + T[c];
|
||||
valb += 6;
|
||||
if (valb >= 0) {
|
||||
out.push_back(char((val >> valb) & 0xFF));
|
||||
valb -= 8;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
// Decode the INI content
|
||||
std::string file_content = DecodeBase64(encoded_content);
|
||||
|
||||
// Find TickedId
|
||||
std::string auth_token;
|
||||
static constexpr std::string_view KeyName = "TickedId=";
|
||||
size_t key_pos = file_content.find(KeyName);
|
||||
|
||||
if (key_pos != std::string::npos) {
|
||||
size_t value_start = key_pos + KeyName.size();
|
||||
size_t value_end = file_content.find_first_of("\r\n", value_start);
|
||||
if (value_end == std::string::npos) {
|
||||
value_end = file_content.size();
|
||||
}
|
||||
auth_token = file_content.substr(value_start, value_end - value_start);
|
||||
} else {
|
||||
// Fallback: If not found, maybe it wasn't base64 or format is different?
|
||||
// Try using raw content if decode failed to produce readable key?
|
||||
// Actually, let's just stick to the decoded content.
|
||||
}
|
||||
|
||||
if (!auth_token.empty()) {
|
||||
// Ensure the token has the correct prefix "uplaypc_v1 t="
|
||||
if (auth_token.find("uplaypc_v1 t=") == std::string::npos) {
|
||||
auth_token = "uplaypc_v1 t=" + auth_token;
|
||||
}
|
||||
|
||||
LOG_INFO(Service_SSL,
|
||||
"Injecting custom Authorization from jdlo_auth.ini (decoded "
|
||||
"TickedId): {}...",
|
||||
auth_token.substr(0, 20));
|
||||
|
||||
// Find existing Authorization header position case-insensitively
|
||||
size_t header_pos = request_lower.find("\r\nauthorization: ");
|
||||
|
||||
if (header_pos != std::string::npos) {
|
||||
size_t end_pos = request_str.find("\r\n", header_pos + 2);
|
||||
if (end_pos != std::string::npos) {
|
||||
LOG_INFO(Service_SSL, "Replacing existing Authorization header.");
|
||||
request_str.replace(header_pos, end_pos - header_pos,
|
||||
"\r\nAuthorization: " + auth_token);
|
||||
}
|
||||
} else {
|
||||
LOG_INFO(Service_SSL, "Appending new Authorization header.");
|
||||
size_t body_pos = request_str.find("\r\n\r\n");
|
||||
if (body_pos != std::string::npos) {
|
||||
request_str.insert(body_pos, "\r\nAuthorization: " + auth_token);
|
||||
}
|
||||
}
|
||||
|
||||
const int ret = SSL_write_ex(ssl, request_str.data(), request_str.size(), out_size);
|
||||
if (ret == 1) { // Success
|
||||
*out_size = original_size;
|
||||
}
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Service_SSL, "Failed to read jdlo_auth.ini content");
|
||||
}
|
||||
} else {
|
||||
LOG_WARNING(Service_SSL, "jdlo_auth.ini not found at {}",
|
||||
Common::FS::PathToUTF8String(auth_file_path));
|
||||
}
|
||||
}
|
||||
|
||||
const int ret = SSL_write_ex(ssl, data.data(), data.size(), out_size);
|
||||
return HandleReturn("SSL_write_ex", out_size, ret);
|
||||
}
|
||||
@@ -247,6 +386,28 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
if (!ssl) {
|
||||
return ResultInternalError;
|
||||
}
|
||||
int pending = SSL_pending(ssl);
|
||||
if (pending > 0) {
|
||||
*out_pending = pending;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendOpenSSL() {
|
||||
// this is null-tolerant:
|
||||
SSL_free(ssl);
|
||||
|
||||
@@ -489,6 +489,27 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
*out_pending = static_cast<s32>(cleartext_read_buf.size());
|
||||
if (*out_pending > 0) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (!ciphertext_read_buf.empty()) {
|
||||
*out_pending = 1;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
~SSLConnectionBackendSchannel() {
|
||||
if (handshake_state != HandshakeState::Initial) {
|
||||
DeleteSecurityContext(&ctxt);
|
||||
|
||||
@@ -149,6 +149,26 @@ public:
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result Pending(s32* out_pending) override {
|
||||
size_t bufferSize = 0;
|
||||
OSStatus status = SSLGetBufferedReadSize(context, &bufferSize);
|
||||
if (status == 0 && bufferSize > 0) {
|
||||
*out_pending = static_cast<s32>(bufferSize);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Network::PollFD poll_fd{socket.get(), Network::PollEvents::In, Network::PollEvents::In};
|
||||
std::vector<Network::PollFD> poll_fds{poll_fd};
|
||||
auto [count, err] = Network::Poll(poll_fds, 0);
|
||||
if (count > 0 && (poll_fds[0].revents & Network::PollEvents::In) != Network::PollEvents{}) {
|
||||
*out_pending = 1;
|
||||
} else {
|
||||
*out_pending = 0;
|
||||
}
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
|
||||
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
|
||||
return ReadOrWriteCallback(connection, data, dataLength, true);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "core/internal_network/network_interface.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define NOMINMAX
|
||||
|
||||
#include <windows.h>
|
||||
#include <wlanapi.h>
|
||||
#ifdef _MSC_VER
|
||||
|
||||
701
src/core/internal_network/legacy_online.cpp
Normal file
701
src/core/internal_network/legacy_online.cpp
Normal file
@@ -0,0 +1,701 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "core/internal_network/legacy_online.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <regex>
|
||||
#include <iomanip>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// For SHA1 and Base64
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace Network {
|
||||
|
||||
// Simple SHA1 implementation for WebSocket handshake
|
||||
namespace {
|
||||
|
||||
class SHA1 {
|
||||
public:
|
||||
SHA1() { reset(); }
|
||||
|
||||
void update(const uint8_t* data, size_t len) {
|
||||
while (len--) {
|
||||
buffer[buffer_size++] = *data++;
|
||||
if (buffer_size == 64) {
|
||||
process_block();
|
||||
buffer_size = 0;
|
||||
}
|
||||
total_bits += 8;
|
||||
}
|
||||
}
|
||||
|
||||
void update(const std::string& str) {
|
||||
update(reinterpret_cast<const uint8_t*>(str.data()), str.size());
|
||||
}
|
||||
|
||||
std::array<uint8_t, 20> finalize() {
|
||||
// Padding
|
||||
buffer[buffer_size++] = 0x80;
|
||||
while (buffer_size != 56) {
|
||||
if (buffer_size == 64) {
|
||||
process_block();
|
||||
buffer_size = 0;
|
||||
}
|
||||
buffer[buffer_size++] = 0;
|
||||
}
|
||||
|
||||
// Append length in bits
|
||||
for (int i = 7; i >= 0; --i) {
|
||||
buffer[buffer_size++] = static_cast<uint8_t>(total_bits >> (i * 8));
|
||||
}
|
||||
process_block();
|
||||
|
||||
std::array<uint8_t, 20> result;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
result[i*4+0] = static_cast<uint8_t>(h[i] >> 24);
|
||||
result[i*4+1] = static_cast<uint8_t>(h[i] >> 16);
|
||||
result[i*4+2] = static_cast<uint8_t>(h[i] >> 8);
|
||||
result[i*4+3] = static_cast<uint8_t>(h[i]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
void reset() {
|
||||
h[0] = 0x67452301;
|
||||
h[1] = 0xEFCDAB89;
|
||||
h[2] = 0x98BADCFE;
|
||||
h[3] = 0x10325476;
|
||||
h[4] = 0xC3D2E1F0;
|
||||
buffer_size = 0;
|
||||
total_bits = 0;
|
||||
}
|
||||
|
||||
void process_block() {
|
||||
uint32_t w[80];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
w[i] = (buffer[i*4+0] << 24) | (buffer[i*4+1] << 16) |
|
||||
(buffer[i*4+2] << 8) | buffer[i*4+3];
|
||||
}
|
||||
for (int i = 16; i < 80; ++i) {
|
||||
uint32_t t = w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16];
|
||||
w[i] = (t << 1) | (t >> 31);
|
||||
}
|
||||
|
||||
uint32_t a = h[0], b = h[1], c = h[2], d = h[3], e = h[4];
|
||||
|
||||
for (int i = 0; i < 80; ++i) {
|
||||
uint32_t f, k;
|
||||
if (i < 20) {
|
||||
f = (b & c) | ((~b) & d);
|
||||
k = 0x5A827999;
|
||||
} else if (i < 40) {
|
||||
f = b ^ c ^ d;
|
||||
k = 0x6ED9EBA1;
|
||||
} else if (i < 60) {
|
||||
f = (b & c) | (b & d) | (c & d);
|
||||
k = 0x8F1BBCDC;
|
||||
} else {
|
||||
f = b ^ c ^ d;
|
||||
k = 0xCA62C1D6;
|
||||
}
|
||||
|
||||
uint32_t temp = ((a << 5) | (a >> 27)) + f + e + k + w[i];
|
||||
e = d;
|
||||
d = c;
|
||||
c = (b << 30) | (b >> 2);
|
||||
b = a;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
h[0] += a;
|
||||
h[1] += b;
|
||||
h[2] += c;
|
||||
h[3] += d;
|
||||
h[4] += e;
|
||||
}
|
||||
|
||||
uint32_t h[5];
|
||||
uint8_t buffer[64];
|
||||
size_t buffer_size;
|
||||
uint64_t total_bits;
|
||||
};
|
||||
|
||||
std::string base64_encode(const uint8_t* data, size_t len) {
|
||||
static const char* chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
std::string result;
|
||||
result.reserve((len + 2) / 3 * 4);
|
||||
|
||||
for (size_t i = 0; i < len; i += 3) {
|
||||
uint32_t n = static_cast<uint32_t>(data[i]) << 16;
|
||||
if (i + 1 < len) n |= static_cast<uint32_t>(data[i + 1]) << 8;
|
||||
if (i + 2 < len) n |= static_cast<uint32_t>(data[i + 2]);
|
||||
|
||||
result += chars[(n >> 18) & 0x3F];
|
||||
result += chars[(n >> 12) & 0x3F];
|
||||
result += (i + 1 < len) ? chars[(n >> 6) & 0x3F] : '=';
|
||||
result += (i + 2 < len) ? chars[n & 0x3F] : '=';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string compute_websocket_accept(const std::string& key) {
|
||||
// WebSocket magic GUID
|
||||
const std::string magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
std::string combined = key + magic;
|
||||
|
||||
SHA1 sha1;
|
||||
sha1.update(combined);
|
||||
auto hash = sha1.finalize();
|
||||
|
||||
return base64_encode(hash.data(), hash.size());
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
LegacyOnlineService::LegacyOnlineService() {
|
||||
#ifdef _WIN32
|
||||
WSADATA wsa_data;
|
||||
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) {
|
||||
LOG_ERROR(Network, "WSAStartup failed with error: {}", WSAGetLastError());
|
||||
winsock_initialized = false;
|
||||
} else {
|
||||
winsock_initialized = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LegacyOnlineService::~LegacyOnlineService() {
|
||||
Stop();
|
||||
#ifdef _WIN32
|
||||
if (winsock_initialized) {
|
||||
WSACleanup();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Start() {
|
||||
if (is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!winsock_initialized) {
|
||||
LOG_ERROR(Network, "Cannot start Legacy Online Service: Winsock not initialized");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
is_running = true;
|
||||
udp_worker_thread = std::thread(&LegacyOnlineService::UdpServerLoop, this);
|
||||
http_worker_thread = std::thread(&LegacyOnlineService::HttpServerLoop, this);
|
||||
}
|
||||
|
||||
void LegacyOnlineService::Stop() {
|
||||
if (!is_running) {
|
||||
return;
|
||||
}
|
||||
|
||||
is_running = false;
|
||||
|
||||
if (udp_socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (http_socket_fd != ~0ULL) {
|
||||
#ifdef _WIN32
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
}
|
||||
|
||||
if (udp_worker_thread.joinable()) {
|
||||
udp_worker_thread.join();
|
||||
}
|
||||
if (http_worker_thread.joinable()) {
|
||||
http_worker_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
void LegacyOnlineService::UdpServerLoop() {
|
||||
LOG_INFO(Network, "Starting Legacy Online UDP Server on port {}", UDP_PORT);
|
||||
|
||||
auto s = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create UDP socket");
|
||||
return;
|
||||
}
|
||||
udp_socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(udp_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(UDP_PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(udp_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind UDP to port {}: {}", UDP_PORT, strerror(errno));
|
||||
close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Legacy Online UDP Server waiting for messages...");
|
||||
|
||||
char buffer[2048];
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
int len = recvfrom(static_cast<SOCKET>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
ssize_t len = recvfrom(static_cast<int>(udp_socket_fd), buffer, sizeof(buffer), 0, (sockaddr*)&client_addr, &client_len);
|
||||
#endif
|
||||
|
||||
if (!is_running) break;
|
||||
|
||||
if (len > 0) {
|
||||
const char* ack_msg = "ACK";
|
||||
#ifdef _WIN32
|
||||
sendto(static_cast<SOCKET>(udp_socket_fd), ack_msg, static_cast<int>(strlen(ack_msg)), 0, (sockaddr*)&client_addr, client_len);
|
||||
#else
|
||||
sendto(static_cast<int>(udp_socket_fd), ack_msg, strlen(ack_msg), 0, (sockaddr*)&client_addr, client_len);
|
||||
#endif
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (udp_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(udp_socket_fd));
|
||||
#else
|
||||
if (udp_socket_fd != ~0ULL) close(static_cast<int>(udp_socket_fd));
|
||||
#endif
|
||||
udp_socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Legacy Online UDP Server stopped");
|
||||
}
|
||||
|
||||
void LegacyOnlineService::HttpServerLoop() {
|
||||
LOG_INFO(Network, "Starting Mobile App HTTP/WebSocket Server on port {}", HTTP_PORT);
|
||||
|
||||
auto s = socket(AF_INET, SOCK_STREAM, 0);
|
||||
#ifdef _WIN32
|
||||
if (s == INVALID_SOCKET) {
|
||||
#else
|
||||
if (s == -1) {
|
||||
#endif
|
||||
LOG_ERROR(Network, "Failed to create HTTP socket");
|
||||
return;
|
||||
}
|
||||
http_socket_fd = static_cast<uintptr_t>(s);
|
||||
|
||||
int opt = 1;
|
||||
#ifdef _WIN32
|
||||
setsockopt(static_cast<SOCKET>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
|
||||
#else
|
||||
setsockopt(static_cast<int>(http_socket_fd), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||
#endif
|
||||
|
||||
sockaddr_in server_addr{};
|
||||
server_addr.sin_family = AF_INET;
|
||||
server_addr.sin_addr.s_addr = INADDR_ANY;
|
||||
server_addr.sin_port = htons(HTTP_PORT);
|
||||
|
||||
int res = -1;
|
||||
#ifdef _WIN32
|
||||
res = bind(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#else
|
||||
res = bind(static_cast<int>(http_socket_fd), (sockaddr*)&server_addr, sizeof(server_addr));
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to bind HTTP to port {}: {}", HTTP_PORT, strerror(errno));
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
res = listen(static_cast<SOCKET>(http_socket_fd), 10);
|
||||
#else
|
||||
res = listen(static_cast<int>(http_socket_fd), 10);
|
||||
#endif
|
||||
|
||||
if (res < 0) {
|
||||
#ifdef _WIN32
|
||||
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, WSAGetLastError());
|
||||
closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
LOG_ERROR(Network, "Failed to listen on HTTP port {}: {}", HTTP_PORT, strerror(errno));
|
||||
close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server listening on port {}...", HTTP_PORT);
|
||||
|
||||
while (is_running) {
|
||||
sockaddr_in client_addr{};
|
||||
#ifdef _WIN32
|
||||
int client_len = sizeof(client_addr);
|
||||
SOCKET client_fd = accept(static_cast<SOCKET>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
|
||||
if (client_fd == INVALID_SOCKET) {
|
||||
#else
|
||||
socklen_t client_len = sizeof(client_addr);
|
||||
int client_fd = accept(static_cast<int>(http_socket_fd), (sockaddr*)&client_addr, &client_len);
|
||||
if (client_fd == -1) {
|
||||
#endif
|
||||
if (!is_running) break;
|
||||
continue;
|
||||
}
|
||||
|
||||
char client_ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
|
||||
LOG_INFO(Network, "HTTP/WebSocket connection from {}:{}", client_ip, ntohs(client_addr.sin_port));
|
||||
|
||||
// Read HTTP request
|
||||
char buffer[4096];
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
#ifdef _WIN32
|
||||
int bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
#else
|
||||
ssize_t bytes_read = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
|
||||
#endif
|
||||
|
||||
if (bytes_read > 0) {
|
||||
std::string request(buffer, bytes_read);
|
||||
LOG_INFO(Network, "Request:\n{}", request);
|
||||
|
||||
// Check if this is a WebSocket upgrade request
|
||||
bool is_websocket = request.find("Upgrade: websocket") != std::string::npos;
|
||||
|
||||
if (is_websocket) {
|
||||
// Extract Sec-WebSocket-Key
|
||||
std::string ws_key;
|
||||
std::regex key_regex("Sec-WebSocket-Key: ([^\r\n]+)");
|
||||
std::smatch match;
|
||||
if (std::regex_search(request, match, key_regex)) {
|
||||
ws_key = match[1].str();
|
||||
}
|
||||
|
||||
// Extract Sec-WebSocket-Protocol
|
||||
std::string ws_protocol;
|
||||
std::regex protocol_regex("Sec-WebSocket-Protocol: ([^\r\n]+)");
|
||||
if (std::regex_search(request, match, protocol_regex)) {
|
||||
ws_protocol = match[1].str();
|
||||
}
|
||||
|
||||
LOG_INFO(Network, "WebSocket upgrade request - Key: {}, Protocol: {}", ws_key, ws_protocol);
|
||||
|
||||
// Compute accept key
|
||||
std::string accept_key = compute_websocket_accept(ws_key);
|
||||
LOG_INFO(Network, "WebSocket Accept Key: {}", accept_key);
|
||||
|
||||
// Build WebSocket handshake response
|
||||
std::ostringstream ws_response;
|
||||
ws_response << "HTTP/1.1 101 Switching Protocols\r\n";
|
||||
ws_response << "Upgrade: websocket\r\n";
|
||||
ws_response << "Connection: Upgrade\r\n";
|
||||
ws_response << "Sec-WebSocket-Accept: " << accept_key << "\r\n";
|
||||
if (!ws_protocol.empty()) {
|
||||
ws_response << "Sec-WebSocket-Protocol: " << ws_protocol << "\r\n";
|
||||
}
|
||||
ws_response << "\r\n";
|
||||
|
||||
std::string response_str = ws_response.str();
|
||||
#ifdef _WIN32
|
||||
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
|
||||
#else
|
||||
send(client_fd, response_str.c_str(), response_str.size(), 0);
|
||||
#endif
|
||||
LOG_INFO(Network, "WebSocket handshake completed! Response:\n{}", response_str);
|
||||
|
||||
// Now handle WebSocket messages
|
||||
LOG_INFO(Network, "Mobile app WebSocket connected! Entering message loop...");
|
||||
|
||||
while (is_running) {
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
#ifdef _WIN32
|
||||
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
|
||||
#else
|
||||
bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
|
||||
#endif
|
||||
if (bytes_read <= 0) {
|
||||
LOG_INFO(Network, "WebSocket connection closed");
|
||||
break;
|
||||
}
|
||||
|
||||
// Parse WebSocket frame
|
||||
uint8_t* frame = reinterpret_cast<uint8_t*>(buffer);
|
||||
uint8_t opcode = frame[0] & 0x0F;
|
||||
bool masked = (frame[1] & 0x80) != 0;
|
||||
uint64_t payload_len = frame[1] & 0x7F;
|
||||
|
||||
size_t header_len = 2;
|
||||
if (payload_len == 126) {
|
||||
payload_len = (frame[2] << 8) | frame[3];
|
||||
header_len = 4;
|
||||
} else if (payload_len == 127) {
|
||||
header_len = 10;
|
||||
payload_len = 0;
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
payload_len = (payload_len << 8) | frame[2 + i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t mask_key[4] = {0};
|
||||
if (masked) {
|
||||
memcpy(mask_key, frame + header_len, 4);
|
||||
header_len += 4;
|
||||
}
|
||||
|
||||
// Unmask payload
|
||||
std::string payload;
|
||||
for (size_t i = 0; i < payload_len && (header_len + i) < static_cast<size_t>(bytes_read); ++i) {
|
||||
char c = frame[header_len + i];
|
||||
if (masked) {
|
||||
c ^= mask_key[i % 4];
|
||||
}
|
||||
payload += c;
|
||||
}
|
||||
|
||||
// LOG_INFO(Network, "WebSocket message (opcode={}): {}", opcode, payload);
|
||||
|
||||
// Handle different opcodes
|
||||
if (opcode == 0x08) {
|
||||
// Close frame
|
||||
// LOG_INFO(Network, "WebSocket close frame received");
|
||||
break;
|
||||
} else if (opcode == 0x09) {
|
||||
// Ping - send pong. Theoretically should echo payload, but empty pong is usually fine.
|
||||
// LOG_INFO(Network, "WebSocket PING received. Responding with PONG.");
|
||||
uint8_t pong[2] = {0x8A, 0x00};
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(pong), 2, 0);
|
||||
#else
|
||||
send(client_fd, pong, 2, 0);
|
||||
#endif
|
||||
} else if (opcode == 0x0A) {
|
||||
// Pong (keep-alive response from client)
|
||||
// LOG_INFO(Network, "WebSocket PONG received from client.");
|
||||
} else if (opcode == 0x01 || opcode == 0x02) {
|
||||
// Text or Binary frame - process Just Dance protocol
|
||||
std::string response;
|
||||
|
||||
// Check message type and respond appropriately
|
||||
// Protocol flow based on JoyDance:
|
||||
// 1. Phone -> Console: JD_PhoneDataCmdHandshakeHello
|
||||
// 2. Console -> Phone: JD_PhoneDataCmdHandshakeContinue (with phoneID)
|
||||
// 3. Phone -> Console: JD_PhoneDataCmdSync (with phoneID)
|
||||
// 4. Console -> Phone: JD_PhoneDataCmdSyncEnd (with phoneID)
|
||||
// 5. Connected!
|
||||
|
||||
if (payload.find("JD_PhoneDataCmdHandshakeHello") != std::string::npos) {
|
||||
// Step 2: Respond with HandshakeContinue
|
||||
// PROTOCOL FIX: No "root" wrapper. Authenticated ID=1 (Int).
|
||||
// CRITICAL: App sent Freq=0. Providing configuration values.
|
||||
// CLEANUP: Removed extra status fields to prevent parsing errors.
|
||||
response = R"({"__class":"JD_PhoneDataCmdHandshakeContinue","phoneID":1,"accelAcquisitionFreqHz":50,"accelAcquisitionLatency":40,"accelMaxRange":8})";
|
||||
// LOG_INFO(Network, "Sending HandshakeContinue (id=1, cfg=50Hz)");
|
||||
|
||||
// Send HandshakeContinue
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81);
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
// LOG_INFO(Network, "WebSocket response sent: {}", response);
|
||||
|
||||
// Removed proactive commands to verify if app accepts HandshakeContinue and sends Sync
|
||||
response.clear();
|
||||
|
||||
} else if (payload.find("JD_PhoneDataCmdSync") != std::string::npos) {
|
||||
// Step 4: Respond with SyncEnd (NO ROOT)
|
||||
// Using phoneID 1 (Int)
|
||||
response = R"({"__class":"JD_PhoneDataCmdSyncEnd","phoneID":1,"status":"ok"})";
|
||||
// LOG_INFO(Network, "Sending SyncEnd (id=1, no root) - Connection complete!");
|
||||
|
||||
// Send SyncEnd
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81);
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
|
||||
// Step 5: Send Activation Commands immediately to authorize input and accel
|
||||
// 5a. Enable Input
|
||||
std::string cmd1 = R"({"__class":"InputSetup_ConsoleCommandData","isEnabled":1,"inputSetup":{"isEnabled":1}})";
|
||||
std::vector<uint8_t> f1; f1.push_back(0x81); f1.push_back(static_cast<uint8_t>(cmd1.size())); for(char c:cmd1) f1.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f1.data()), static_cast<int>(f1.size()), 0);
|
||||
#else
|
||||
send(client_fd, f1.data(), f1.size(), 0);
|
||||
#endif
|
||||
|
||||
// 5b. Enable Accelerometer
|
||||
std::string cmd2 = R"({"__class":"JD_EnableAccelValuesSending_ConsoleCommandData","isEnabled":1})";
|
||||
std::vector<uint8_t> f2; f2.push_back(0x81); f2.push_back(static_cast<uint8_t>(cmd2.size())); for(char c:cmd2) f2.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f2.data()), static_cast<int>(f2.size()), 0);
|
||||
#else
|
||||
send(client_fd, f2.data(), f2.size(), 0);
|
||||
#endif
|
||||
|
||||
// 5c. UI Setup (optional but good for safety)
|
||||
std::string cmd3 = R"({"__class":"JD_PhoneUiSetupData","isPopup":0,"inputSetup":{"isEnabled":1}})";
|
||||
std::vector<uint8_t> f3; f3.push_back(0x81); f3.push_back(static_cast<uint8_t>(cmd3.size())); for(char c:cmd3) f3.push_back(static_cast<uint8_t>(c));
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(f3.data()), static_cast<int>(f3.size()), 0);
|
||||
#else
|
||||
send(client_fd, f3.data(), f3.size(), 0);
|
||||
#endif
|
||||
|
||||
// LOG_INFO(Network, "Sent Activation Commands (Input, Accel, UI)");
|
||||
response.clear();
|
||||
} else if (payload.find("JD_PhoneScoringData") != std::string::npos) {
|
||||
// Accelerometer/scoring data - no response needed
|
||||
// LOG_DEBUG(Network, "Received phone scoring data");
|
||||
continue;
|
||||
} else if (payload.find("JD_Input_PhoneCommandData") != std::string::npos) {
|
||||
// Button input from phone - no response needed
|
||||
// LOG_INFO(Network, "Received phone input command");
|
||||
continue;
|
||||
} else if (payload.find("JD_Pause_PhoneCommandData") != std::string::npos) {
|
||||
// Pause command from phone
|
||||
// LOG_INFO(Network, "Received pause command from phone");
|
||||
continue;
|
||||
} else if (payload.find("JD_Custom_PhoneCommandData") != std::string::npos) {
|
||||
// Custom shortcut command
|
||||
// LOG_INFO(Network, "Received custom command from phone");
|
||||
continue;
|
||||
} else if (payload.find("JD_CancelKeyboard_PhoneCommandData") != std::string::npos) {
|
||||
// Keyboard cancelled
|
||||
// LOG_INFO(Network, "Phone cancelled keyboard");
|
||||
continue;
|
||||
} else {
|
||||
// Unknown message - log but don't respond
|
||||
// LOG_INFO(Network, "Unknown phone message type");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.empty()) {
|
||||
// Build WebSocket frame
|
||||
std::vector<uint8_t> ws_frame;
|
||||
ws_frame.push_back(0x81); // Text frame, FIN
|
||||
if (response.size() < 126) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size()));
|
||||
} else if (response.size() < 65536) {
|
||||
ws_frame.push_back(126);
|
||||
ws_frame.push_back(static_cast<uint8_t>((response.size() >> 8) & 0xFF));
|
||||
ws_frame.push_back(static_cast<uint8_t>(response.size() & 0xFF));
|
||||
}
|
||||
for (char c : response) {
|
||||
ws_frame.push_back(static_cast<uint8_t>(c));
|
||||
}
|
||||
#ifdef _WIN32
|
||||
send(client_fd, reinterpret_cast<char*>(ws_frame.data()), static_cast<int>(ws_frame.size()), 0);
|
||||
#else
|
||||
send(client_fd, ws_frame.data(), ws_frame.size(), 0);
|
||||
#endif
|
||||
LOG_INFO(Network, "WebSocket response sent: {}", response);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular HTTP request
|
||||
std::string response_body = R"({"status":"ok","message":"Eden Mobile Bridge"})";
|
||||
std::ostringstream http_response;
|
||||
http_response << "HTTP/1.1 200 OK\r\n";
|
||||
http_response << "Content-Type: application/json\r\n";
|
||||
http_response << "Content-Length: " << response_body.size() << "\r\n";
|
||||
http_response << "Connection: close\r\n";
|
||||
http_response << "\r\n";
|
||||
http_response << response_body;
|
||||
|
||||
std::string response_str = http_response.str();
|
||||
#ifdef _WIN32
|
||||
send(client_fd, response_str.c_str(), static_cast<int>(response_str.size()), 0);
|
||||
#else
|
||||
send(client_fd, response_str.c_str(), response_str.size(), 0);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
closesocket(client_fd);
|
||||
#else
|
||||
close(client_fd);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (http_socket_fd != ~0ULL) closesocket(static_cast<SOCKET>(http_socket_fd));
|
||||
#else
|
||||
if (http_socket_fd != ~0ULL) close(static_cast<int>(http_socket_fd));
|
||||
#endif
|
||||
http_socket_fd = ~0ULL;
|
||||
LOG_INFO(Network, "Mobile App HTTP/WebSocket Server stopped");
|
||||
}
|
||||
|
||||
} // namespace Network
|
||||
36
src/core/internal_network/legacy_online.h
Normal file
36
src/core/internal_network/legacy_online.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
namespace Network {
|
||||
|
||||
class LegacyOnlineService {
|
||||
public:
|
||||
LegacyOnlineService();
|
||||
~LegacyOnlineService();
|
||||
|
||||
void Start();
|
||||
void Stop();
|
||||
|
||||
private:
|
||||
void UdpServerLoop();
|
||||
void HttpServerLoop();
|
||||
|
||||
std::atomic_bool is_running{false};
|
||||
std::thread udp_worker_thread;
|
||||
std::thread http_worker_thread;
|
||||
uintptr_t udp_socket_fd{~0ULL};
|
||||
uintptr_t http_socket_fd{~0ULL};
|
||||
bool winsock_initialized{false};
|
||||
|
||||
static constexpr int UDP_PORT = 6000;
|
||||
static constexpr int HTTP_PORT = 8080;
|
||||
};
|
||||
|
||||
} // namespace Network
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -75,6 +75,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_socket;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -83,6 +85,7 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
#endif
|
||||
|
||||
switch (static_cast<Domain>(input.family)) {
|
||||
case Domain::Unspecified:
|
||||
case Domain::INET:
|
||||
result.sin_family = AF_INET;
|
||||
break;
|
||||
@@ -105,6 +108,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
LINGER MakeLinger(bool enable, u32 linger_value) {
|
||||
ASSERT(linger_value <= (std::numeric_limits<u_short>::max)());
|
||||
|
||||
@@ -124,6 +129,7 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
case 0:
|
||||
return Errno::SUCCESS;
|
||||
case WSAEBADF:
|
||||
case WSAENOTSOCK:
|
||||
return Errno::BADF;
|
||||
case WSAEINVAL:
|
||||
return Errno::INVAL;
|
||||
@@ -157,6 +163,10 @@ Errno TranslateNativeError(int e, CallType call_type = CallType::Other) {
|
||||
return Errno::TIMEDOUT;
|
||||
case WSAEINPROGRESS:
|
||||
return Errno::INPROGRESS;
|
||||
case WSAEACCES:
|
||||
return Errno::ACCES;
|
||||
case WSAEADDRINUSE:
|
||||
return Errno::ADDRINUSE;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented errno={}", e);
|
||||
return Errno::OTHER;
|
||||
@@ -212,6 +222,8 @@ SOCKET GetInterruptSocket() {
|
||||
return interrupt_pipe_fd[0];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
sockaddr_in result;
|
||||
|
||||
@@ -234,6 +246,8 @@ sockaddr TranslateFromSockAddrIn(SockAddrIn input) {
|
||||
return addr;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
int WSAPoll(WSAPOLLFD* fds, ULONG nfds, int timeout) {
|
||||
return poll(fds, static_cast<nfds_t>(nfds), timeout);
|
||||
}
|
||||
@@ -320,6 +334,8 @@ Errno GetAndLogLastError(CallType call_type = CallType::Other) {
|
||||
return err;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
switch (gai_err) {
|
||||
case 0:
|
||||
@@ -373,6 +389,8 @@ GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int gai_err) {
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
Domain TranslateDomainFromNative(int domain) {
|
||||
switch (domain) {
|
||||
case 0:
|
||||
@@ -607,6 +625,8 @@ Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) {
|
||||
const size_t num = pollfds.size();
|
||||
|
||||
@@ -684,6 +704,10 @@ Errno Socket::Initialize(Domain domain, Type type, Protocol protocol) {
|
||||
fd = socket(TranslateDomainToNative(domain), TranslateTypeToNative(type),
|
||||
TranslateProtocolToNative(protocol));
|
||||
if (fd != INVALID_SOCKET) {
|
||||
// Enable SO_REUSEADDR to allow port reuse after socket close
|
||||
// This prevents "Address already in use" errors when rebinding
|
||||
int reuse = 1;
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char*>(&reuse), sizeof(reuse));
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
|
||||
@@ -801,6 +825,35 @@ std::pair<s32, Errno> Socket::Recv(int flags, std::span<u8> message) {
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
|
||||
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
|
||||
if (!is_non_blocking) {
|
||||
std::vector<WSAPOLLFD> host_pollfds{
|
||||
WSAPOLLFD{fd, POLLIN, 0},
|
||||
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
|
||||
};
|
||||
|
||||
// Poll with a longer timeout (30 seconds) to wait for data
|
||||
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 30000);
|
||||
|
||||
if (host_pollfds[1].revents != 0) {
|
||||
// Interrupt signaled, return EAGAIN
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres == 0) {
|
||||
// Timeout - return AGAIN so the game can retry
|
||||
LOG_DEBUG(Network, "Recv poll timeout after 30 seconds, returning EAGAIN");
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres < 0) {
|
||||
return {-1, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
// Data is available, proceed with recv
|
||||
LOG_INFO(Network, "Recv poll detected data available!");
|
||||
}
|
||||
|
||||
const auto result =
|
||||
recv(fd, reinterpret_cast<char*>(message.data()), static_cast<int>(message.size()), 0);
|
||||
if (result != SOCKET_ERROR) {
|
||||
@@ -814,6 +867,35 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
|
||||
ASSERT(flags == 0);
|
||||
ASSERT(message.size() < static_cast<size_t>((std::numeric_limits<int>::max)()));
|
||||
|
||||
// If socket is blocking, use poll with interrupt socket to avoid infinite blocking
|
||||
if (!is_non_blocking) {
|
||||
std::vector<WSAPOLLFD> host_pollfds{
|
||||
WSAPOLLFD{fd, POLLIN, 0},
|
||||
WSAPOLLFD{GetInterruptSocket(), POLLIN, 0},
|
||||
};
|
||||
|
||||
// Poll with a short timeout (5ms) to emulate Switch non-blocking/interruptible behavior
|
||||
// This prevents the game from freezing when waiting for UDP packets on the main thread
|
||||
const int pollres = WSAPoll(host_pollfds.data(), static_cast<ULONG>(host_pollfds.size()), 5);
|
||||
|
||||
if (host_pollfds[1].revents != 0) {
|
||||
// Interrupt signaled, return EAGAIN
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres == 0) {
|
||||
// Timeout - return AGAIN so the game can continue its loop (rendering, input, etc.)
|
||||
return {-1, Errno::AGAIN};
|
||||
}
|
||||
|
||||
if (pollres < 0) {
|
||||
return {-1, GetAndLogLastError()};
|
||||
}
|
||||
|
||||
// Data is available, proceed with recvfrom
|
||||
LOG_INFO(Network, "RecvFrom poll detected data available!");
|
||||
}
|
||||
|
||||
sockaddr_in addr_in{};
|
||||
socklen_t addrlen = sizeof(addr_in);
|
||||
socklen_t* const p_addrlen = addr ? &addrlen : nullptr;
|
||||
@@ -824,6 +906,10 @@ std::pair<s32, Errno> Socket::RecvFrom(int flags, std::span<u8> message, SockAdd
|
||||
if (result != SOCKET_ERROR) {
|
||||
if (addr) {
|
||||
*addr = TranslateToSockAddrIn(addr_in, addrlen);
|
||||
LOG_INFO(Network, "RecvFrom received {} bytes from {}:{}",
|
||||
result, IPv4AddressToString(addr->ip), addr->portno);
|
||||
} else {
|
||||
LOG_INFO(Network, "RecvFrom received {} bytes", result);
|
||||
}
|
||||
return {static_cast<s32>(result), Errno::SUCCESS};
|
||||
}
|
||||
@@ -859,20 +945,66 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, std::span<const u8> message,
|
||||
if (addr) {
|
||||
host_addr_in = TranslateFromSockAddrIn(*addr);
|
||||
to = &host_addr_in;
|
||||
LOG_INFO(Network, "SendTo sending {} bytes to {}:{}",
|
||||
message.size(), IPv4AddressToString(addr->ip), addr->portno);
|
||||
} else {
|
||||
LOG_INFO(Network, "SendTo sending {} bytes (no addr)", message.size());
|
||||
}
|
||||
|
||||
// Log packet content for debugging mobile app connection
|
||||
if (message.size() > 0 && message.size() <= 200) {
|
||||
std::string hex_dump;
|
||||
std::string ascii_dump;
|
||||
for (size_t i = 0; i < message.size(); ++i) {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%02X ", message[i]);
|
||||
hex_dump += hex;
|
||||
// Build ASCII representation
|
||||
if (message[i] >= 32 && message[i] < 127) {
|
||||
ascii_dump += static_cast<char>(message[i]);
|
||||
} else {
|
||||
ascii_dump += '.';
|
||||
}
|
||||
}
|
||||
LOG_INFO(Network, "SendTo packet HEX: {}", hex_dump);
|
||||
LOG_INFO(Network, "SendTo packet ASCII: {}", ascii_dump);
|
||||
} else if (message.size() > 200) {
|
||||
// Log first 200 bytes for large packets
|
||||
std::string hex_dump;
|
||||
std::string ascii_dump;
|
||||
for (size_t i = 0; i < 200; ++i) {
|
||||
char hex[4];
|
||||
snprintf(hex, sizeof(hex), "%02X ", message[i]);
|
||||
hex_dump += hex;
|
||||
if (message[i] >= 32 && message[i] < 127) {
|
||||
ascii_dump += static_cast<char>(message[i]);
|
||||
} else {
|
||||
ascii_dump += '.';
|
||||
}
|
||||
}
|
||||
LOG_INFO(Network, "SendTo packet HEX (first 200): {}", hex_dump);
|
||||
LOG_INFO(Network, "SendTo packet ASCII (first 200): {}", ascii_dump);
|
||||
}
|
||||
|
||||
const auto result = sendto(fd, reinterpret_cast<const char*>(message.data()),
|
||||
static_cast<int>(message.size()), 0, to, to_len);
|
||||
if (result != SOCKET_ERROR) {
|
||||
LOG_INFO(Network, "SendTo success: sent {} bytes", result);
|
||||
return {static_cast<s32>(result), Errno::SUCCESS};
|
||||
}
|
||||
|
||||
LOG_ERROR(Network, "SendTo failed!");
|
||||
return {-1, GetAndLogLastError(CallType::Send)};
|
||||
}
|
||||
|
||||
Errno Socket::Close() {
|
||||
if (fd == INVALID_SOCKET) {
|
||||
return Errno::SUCCESS;
|
||||
}
|
||||
[[maybe_unused]] const int result = closesocket(fd);
|
||||
ASSERT(result == 0);
|
||||
if (result != 0) {
|
||||
GetAndLogLastError();
|
||||
}
|
||||
fd = INVALID_SOCKET;
|
||||
|
||||
return Errno::SUCCESS;
|
||||
|
||||
@@ -48,6 +48,8 @@ enum class Errno {
|
||||
TIMEDOUT,
|
||||
MSGSIZE,
|
||||
INPROGRESS,
|
||||
ACCES,
|
||||
ADDRINUSE,
|
||||
OTHER,
|
||||
};
|
||||
|
||||
@@ -122,8 +124,33 @@ std::optional<IPv4Address> GetHostIPv4Address();
|
||||
std::string IPv4AddressToString(IPv4Address ip_addr);
|
||||
u32 IPv4AddressToInteger(IPv4Address ip_addr);
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#endif
|
||||
|
||||
// named to avoid name collision with Windows macro
|
||||
Common::Expected<std::vector<AddrInfo>, GetAddrInfoError> GetAddressInfo(
|
||||
const std::string& host, const std::optional<std::string>& service);
|
||||
|
||||
sockaddr TranslateFromSockAddrIn(SockAddrIn input);
|
||||
|
||||
inline std::pair<std::string, int> GetNameInfo(const SockAddrIn& addr) {
|
||||
sockaddr sa = TranslateFromSockAddrIn(addr);
|
||||
sockaddr_in addr_in{};
|
||||
std::memcpy(&addr_in, &sa, sizeof(sockaddr_in));
|
||||
|
||||
char host[1025]; // NI_MAXHOST
|
||||
int err = getnameinfo(reinterpret_cast<const sockaddr*>(&addr_in), sizeof(addr_in), host, sizeof(host), nullptr, 0, 0);
|
||||
|
||||
if (err != 0) {
|
||||
return {std::string{}, err};
|
||||
}
|
||||
|
||||
return {std::string(host), 0};
|
||||
}
|
||||
|
||||
GetAddrInfoError TranslateGetAddrInfoErrorFromNative(int err);
|
||||
|
||||
} // namespace Network
|
||||
|
||||
@@ -107,7 +107,7 @@ else()
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.57 REQUIRED)
|
||||
find_package(fmt 8 CONFIG)
|
||||
# find_package(fmt 8 CONFIG)
|
||||
|
||||
# Pull in externals CMakeLists for libs where available
|
||||
add_subdirectory(externals)
|
||||
|
||||
@@ -97,15 +97,15 @@ public:
|
||||
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
void InterpreterFallback(u32 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u32 /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:08x} && {}) code = {:08x}", pc, num_instructions, *MemoryReadCode(pc));
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override {
|
||||
void ExceptionRaised(u32 /*pc*/, Dynarmic::A32::Exception /*exception*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:08x}) code = {:08x}", pc, *MemoryReadCode(pc));
|
||||
}
|
||||
|
||||
@@ -190,15 +190,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(std::uint32_t pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(std::uint32_t /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "InterpreterFallback({:016x} && {})", pc, num_instructions);
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(std::uint32_t pc, Dynarmic::A32::Exception) override {
|
||||
void ExceptionRaised(std::uint32_t /*pc*/, Dynarmic::A32::Exception) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
|
||||
@@ -105,15 +105,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
|
||||
UNREACHABLE(); // ASSERT(false&& "InterpreterFallback({:016x} && {})", pc, num_instructions);
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override {
|
||||
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception /*exception*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
@@ -208,15 +208,15 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, size_t num_instructions) override {
|
||||
void InterpreterFallback(u64 /*pc*/, size_t /*num_instructions*/) override {
|
||||
ASSERT(ignore_invalid_insn && "InterpreterFallback");
|
||||
}
|
||||
|
||||
void CallSVC(std::uint32_t swi) override {
|
||||
void CallSVC(std::uint32_t /*swi*/) override {
|
||||
UNREACHABLE(); //ASSERT(false && "CallSVC({})", swi);
|
||||
}
|
||||
|
||||
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception) override {
|
||||
void ExceptionRaised(u64 /*pc*/, Dynarmic::A64::Exception) override {
|
||||
UNREACHABLE(); //ASSERT(false && "ExceptionRaised({:016x})", pc);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ private:
|
||||
void Save();
|
||||
|
||||
PlayTimeDatabase database;
|
||||
u64 running_program_id;
|
||||
u64 running_program_id{};
|
||||
std::jthread play_time_thread;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -53,7 +56,7 @@ struct ChatEntry {
|
||||
|
||||
/// Represents a system status message.
|
||||
struct StatusMessageEntry {
|
||||
StatusMessageTypes type; ///< Type of the message
|
||||
StatusMessageTypes type{}; ///< Type of the message
|
||||
/// Subject of the message. i.e. the user who is joining/leaving/being banned, etc.
|
||||
std::string nickname;
|
||||
std::string username;
|
||||
|
||||
@@ -324,13 +324,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Improves rendering of transparency effects in specific games."));
|
||||
|
||||
// Renderer (Extensions)
|
||||
INSERT(Settings,
|
||||
sample_shading_fraction,
|
||||
tr("Sample Shading"),
|
||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||
"instead of once per fragment. Improves graphics quality at the cost of performance.\n"
|
||||
"Higher values improve quality but degrade performance."));
|
||||
|
||||
INSERT(Settings,
|
||||
dyna_state,
|
||||
tr("Extended Dynamic State"),
|
||||
@@ -353,6 +346,15 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent)
|
||||
tr("Improves texture & buffer handling and the Maxwell translation layer.\n"
|
||||
"Some Vulkan 1.1+ and all 1.2+ devices support this extension."));
|
||||
|
||||
INSERT(Settings, sample_shading, QString(), QString());
|
||||
|
||||
INSERT(Settings,
|
||||
sample_shading_fraction,
|
||||
tr("Sample Shading"),
|
||||
tr("Allows the fragment shader to execute per sample in a multi-sampled fragment "
|
||||
"instead of once per fragment. Improves graphics quality at the cost of performance.\n"
|
||||
"Higher values improve quality but degrade performance."));
|
||||
|
||||
// Renderer (Debug)
|
||||
|
||||
// System
|
||||
|
||||
@@ -446,10 +446,12 @@ void EmitIsHelperInvocation(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
void EmitSR_WScaleFactorXY(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3c003c00u;", inst);
|
||||
}
|
||||
|
||||
void EmitSR_WScaleFactorZ(EmitContext& ctx, IR::Inst& inst) {
|
||||
LOG_WARNING(Shader, "(STUBBED) called");
|
||||
ctx.AddU32("{}=0x3f800000u;", inst);
|
||||
}
|
||||
|
||||
void EmitYDirection(EmitContext& ctx, IR::Inst& inst) {
|
||||
|
||||
@@ -58,6 +58,9 @@ std::string FormatFloat(std::string_view value, IR::Type type) {
|
||||
if (value == "nan") {
|
||||
return "utof(0x7fc00000)";
|
||||
}
|
||||
if (value == "-nan") {
|
||||
return "utof(0xffc00000)";
|
||||
}
|
||||
if (value == "inf") {
|
||||
return "utof(0x7f800000)";
|
||||
}
|
||||
|
||||
@@ -1705,21 +1705,26 @@ Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
|
||||
return NULL_BINDING;
|
||||
}
|
||||
|
||||
// xbzk: New size logic. Fixes MCI.
|
||||
// If ever the * comment below prove wrong, the 'if' block may be removed.
|
||||
const auto size = [&]() {
|
||||
const bool is_nvn_cbuf = cbuf_index == 0;
|
||||
// The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
if (is_nvn_cbuf) {
|
||||
const u32 ssbo_size = gpu_memory->Read<u32>(ssbo_addr + 8);
|
||||
if (ssbo_size != 0) {
|
||||
return ssbo_size;
|
||||
// * The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
|
||||
const u64 next_qword = gpu_memory->Read<u64>(ssbo_addr + 8);
|
||||
const u32 upper_32 = static_cast<u32>(next_qword >> 32);
|
||||
// Hardware-based detection: GPU addresses have non-zero upper bits
|
||||
if (upper_32 == 0) {
|
||||
// This is a size field, not a GPU address
|
||||
return static_cast<u32>(next_qword); // Return lower_32
|
||||
}
|
||||
}
|
||||
// Other titles (notably Doom Eternal) may use STG/LDG on buffer addresses in custom defined
|
||||
// cbufs, which do not store the sizes adjacent to the addresses, so use the fully
|
||||
// mapped buffer size for now.
|
||||
// Fall through: either not NVN cbuf (Doom Eternal & +), or NVN but ssbo_addr+8 is a GPU address (MCI)
|
||||
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
|
||||
// Cap at 8MB to prevent allocator overflow from misinterpreted addresses
|
||||
return (std::min)(memory_layout_size, static_cast<u32>(8_MiB));
|
||||
}();
|
||||
|
||||
// Alignment only applies to the offset of the buffer
|
||||
const u32 alignment = runtime.GetStorageBufferAlignment();
|
||||
const GPUVAddr aligned_gpu_addr = Common::AlignDown(gpu_addr, alignment);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
@@ -17,7 +20,7 @@ void Scheduler::Push(s32 channel, CommandList&& entries) {
|
||||
std::unique_lock lk(scheduling_guard);
|
||||
auto it = channels.find(channel);
|
||||
ASSERT(it != channels.end());
|
||||
auto channel_state = it->second;
|
||||
auto& channel_state = it->second;
|
||||
gpu.BindChannel(channel_state->bind_id);
|
||||
channel_state->dma_pusher->Push(std::move(entries));
|
||||
channel_state->dma_pusher->DispatchCalls();
|
||||
|
||||
@@ -81,7 +81,8 @@ public:
|
||||
if constexpr (can_async_check) {
|
||||
guard.lock();
|
||||
}
|
||||
if (Settings::IsGPULevelLow() || (Settings::IsGPULevelMedium() && !should_flush)) {
|
||||
// if ((Settings::IsGPULevelLow() || Settings::IsGPULevelMedium()) && !should_flush) {
|
||||
if (false) {
|
||||
func();
|
||||
} else {
|
||||
uncommitted_operations.emplace_back(std::move(func));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include <thread>
|
||||
#include "core/memory.h"
|
||||
#include "video_core/host1x/ffmpeg/ffmpeg.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
@@ -7,6 +7,9 @@
|
||||
#include <array>
|
||||
#include <tuple>
|
||||
#include <stdint.h>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
|
||||
extern "C" {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
@@ -107,9 +110,19 @@ void Vic::Execute() {
|
||||
auto output_height{config.output_surface_config.out_surface_height + 1};
|
||||
output_surface.resize_destructive(output_width * output_height);
|
||||
|
||||
// Initialize the surface with the appropriate black pixel
|
||||
Pixel black_pixel{};
|
||||
if (config.output_surface_config.out_pixel_format == VideoPixelFormat::Y8__V8U8_N420) {
|
||||
// Y=0, U=512, V=512 (10-bit), A=0
|
||||
black_pixel = {0, 512, 512, 0};
|
||||
} else {
|
||||
// R=0, G=0, B=0, A=0
|
||||
black_pixel = {0, 0, 0, 0};
|
||||
}
|
||||
std::fill(output_surface.begin(), output_surface.end(), black_pixel);
|
||||
|
||||
if (Settings::values.nvdec_emulation.GetValue() == Settings::NvdecEmulation::Off) [[unlikely]] {
|
||||
// Fill the frame with black, as otherwise they can have random data and be very glitchy.
|
||||
std::fill(output_surface.begin(), output_surface.end(), Pixel{});
|
||||
|
||||
} else {
|
||||
for (size_t i = 0; i < config.slot_structs.size(); i++) {
|
||||
auto& slot_config{config.slot_structs[i]};
|
||||
@@ -122,7 +135,18 @@ void Vic::Execute() {
|
||||
nvdec_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
}
|
||||
|
||||
if (auto frame = frame_queue.GetFrame(nvdec_id, luma_offset); frame) {
|
||||
auto frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
if (!frame) {
|
||||
// We might've failed to find the frame, or the nvdec id is stale/wrong.
|
||||
// Try to find the nvdec id again.
|
||||
const s32 new_id = frame_queue.VicFindNvdecFdFromOffset(luma_offset);
|
||||
if (new_id != -1) {
|
||||
nvdec_id = new_id;
|
||||
frame = frame_queue.GetFrame(nvdec_id, luma_offset);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame) {
|
||||
if (frame.get()) {
|
||||
switch (frame->GetPixelFormat()) {
|
||||
case AV_PIX_FMT_YUV420P:
|
||||
@@ -191,20 +215,44 @@ void Vic::ReadProgressiveY8__V8U8_N420(const SlotStruct& slot, std::span<const P
|
||||
|
||||
const auto alpha{u16(slot.config.planar_alpha.Value())};
|
||||
for (s32 y = 0; y < in_luma_height; y++) {
|
||||
const auto src_luma{y * in_luma_stride};
|
||||
const auto src_chroma{(y / 2) * in_chroma_stride};
|
||||
const auto dst{y * out_luma_stride};
|
||||
for (s32 x = 0; x < in_luma_width; x++) {
|
||||
slot_surface[dst + x].r = u16(luma_buffer[src_luma + x] << 2);
|
||||
// Chroma samples are duplicated horizontally and vertically.
|
||||
if(planar) {
|
||||
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + x / 2] << 2);
|
||||
slot_surface[dst + x].b = u16(chroma_v_buffer[src_chroma + x / 2] << 2);
|
||||
const u8* luma_ptr = luma_buffer + y * in_luma_stride;
|
||||
const u8* chroma_u_ptr = chroma_u_buffer + (y / 2) * in_chroma_stride;
|
||||
// For planar, V buffer is separate. For NV12, it is not used directly in the same way.
|
||||
const u8* chroma_v_ptr = planar ? (chroma_v_buffer + (y / 2) * in_chroma_stride) : nullptr;
|
||||
|
||||
Pixel* dst_ptr = &slot_surface[y * out_luma_stride];
|
||||
|
||||
for (s32 x = 0; x < in_luma_width; x += 2) {
|
||||
u16 u_val, v_val;
|
||||
|
||||
if (planar) {
|
||||
// YUV420P: U and V are in separate planes.
|
||||
// 1 UV pair for 2 horizontal pixels.
|
||||
u_val = u16(chroma_u_ptr[x / 2] << 2);
|
||||
v_val = u16(chroma_v_ptr[x / 2] << 2);
|
||||
} else {
|
||||
slot_surface[dst + x].g = u16(chroma_u_buffer[src_chroma + (x & ~1) + 0] << 2);
|
||||
slot_surface[dst + x].b = u16(chroma_u_buffer[src_chroma + (x & ~1) + 1] << 2);
|
||||
// NV12: UV are interleaved in the second plane.
|
||||
// U is at even byte, V is at odd byte.
|
||||
// x is even (0, 2, 4...), so x corresponds to the byte offset in the interleaved buffer.
|
||||
u_val = u16(chroma_u_ptr[x] << 2);
|
||||
v_val = u16(chroma_u_ptr[x + 1] << 2);
|
||||
}
|
||||
slot_surface[dst + x].a = alpha;
|
||||
|
||||
// Pixel 1 (Even x)
|
||||
dst_ptr[0].r = u16(luma_ptr[x] << 2);
|
||||
dst_ptr[0].g = u_val;
|
||||
dst_ptr[0].b = v_val;
|
||||
dst_ptr[0].a = alpha;
|
||||
|
||||
// Pixel 2 (Odd x), check boundary
|
||||
if (x + 1 < in_luma_width) {
|
||||
dst_ptr[1].r = u16(luma_ptr[x + 1] << 2);
|
||||
dst_ptr[1].g = u_val;
|
||||
dst_ptr[1].b = v_val;
|
||||
dst_ptr[1].a = alpha;
|
||||
}
|
||||
|
||||
dst_ptr += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ static OGLProgram LinkSeparableProgram(GLuint shader) {
|
||||
glGetProgramInfoLog(program.handle, log_length, nullptr, log.data());
|
||||
if (link_status == GL_FALSE) {
|
||||
LOG_ERROR(Render_OpenGL, "{}", log);
|
||||
glDeleteProgram(program.handle);
|
||||
program.handle = 0;
|
||||
} else {
|
||||
LOG_WARNING(Render_OpenGL, "{}", log);
|
||||
}
|
||||
|
||||
@@ -116,9 +116,14 @@ void WindowAdaptPass::DrawToFramebuffer(ProgramManager& program_manager, std::li
|
||||
glBindTextureUnit(0, textures[i]);
|
||||
glProgramUniformMatrix3x2fv(vert.handle, ModelViewMatrixLocation, 1, GL_FALSE,
|
||||
matrices[i].data());
|
||||
glProgramUniform2ui(frag.handle, ScreenSizeLocation,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
if (frag.handle != 0) {
|
||||
const GLint screen_size_loc = glGetUniformLocation(frag.handle, "screen_size");
|
||||
if (screen_size_loc != -1) {
|
||||
glProgramUniform2ui(frag.handle, screen_size_loc,
|
||||
static_cast<GLuint>(layout.screen.GetWidth()),
|
||||
static_cast<GLuint>(layout.screen.GetHeight()));
|
||||
}
|
||||
}
|
||||
glNamedBufferSubData(vertex_buffer.handle, 0, sizeof(vertices[i]), std::data(vertices[i]));
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
}
|
||||
|
||||
@@ -744,8 +744,8 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
|
||||
.pNext = nullptr,
|
||||
.flags = 0,
|
||||
.rasterizationSamples = MaxwellToVK::MsaaMode(key.state.msaa_mode),
|
||||
.sampleShadingEnable = Settings::values.sample_shading_fraction.GetValue() > 0 ? VK_TRUE : VK_FALSE,
|
||||
.minSampleShading = float(Settings::values.sample_shading_fraction.GetValue()) / 100.0f,
|
||||
.sampleShadingEnable = Settings::values.sample_shading.GetValue() ? VK_TRUE : VK_FALSE,
|
||||
.minSampleShading = static_cast<float>(Settings::values.sample_shading_fraction.GetValue()) / 100.0f,
|
||||
.pSampleMask = nullptr,
|
||||
.alphaToCoverageEnable = key.state.alpha_to_coverage_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
.alphaToOneEnable = key.state.alpha_to_one_enabled != 0 ? VK_TRUE : VK_FALSE,
|
||||
|
||||
@@ -108,7 +108,7 @@ VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t in
|
||||
|
||||
VkRect2D GetScissorState(const Maxwell& regs, size_t index, u32 up_scale = 1, u32 down_shift = 0) {
|
||||
const auto& src = regs.scissor_test[index];
|
||||
VkRect2D scissor;
|
||||
VkRect2D scissor{};
|
||||
const auto scale_up = [&](s32 value) -> s32 {
|
||||
if (value == 0) {
|
||||
return 0U;
|
||||
@@ -374,7 +374,7 @@ void RasterizerVulkan::Clear(u32 layer_count) {
|
||||
}
|
||||
UpdateViewportsState(regs);
|
||||
|
||||
VkRect2D default_scissor;
|
||||
VkRect2D default_scissor{};
|
||||
default_scissor.offset.x = 0;
|
||||
default_scissor.offset.y = 0;
|
||||
default_scissor.extent.width = (std::numeric_limits<s32>::max)();
|
||||
|
||||
@@ -277,7 +277,10 @@ std::optional<u64> GenericEnvironment::TryFindSize() {
|
||||
Tegra::Texture::TICEntry GenericEnvironment::ReadTextureInfo(GPUVAddr tic_addr, u32 tic_limit,
|
||||
bool via_header_index, u32 raw) {
|
||||
const auto handle{Tegra::Texture::TexturePair(raw, via_header_index)};
|
||||
ASSERT(handle.first <= tic_limit);
|
||||
if (handle.first > tic_limit) {
|
||||
LOG_WARNING(Shader, "Texture ID {} is out of bounds (limit {})", handle.first, tic_limit);
|
||||
return {};
|
||||
}
|
||||
const GPUVAddr descriptor_addr{tic_addr + handle.first * sizeof(Tegra::Texture::TICEntry)};
|
||||
Tegra::Texture::TICEntry entry;
|
||||
gpu_memory->ReadBlock(descriptor_addr, &entry, sizeof(entry));
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -71,11 +74,11 @@ private:
|
||||
|
||||
std::unique_ptr<InputProfiles> profiles;
|
||||
|
||||
std::array<ConfigureInputPlayer*, 8> player_controllers;
|
||||
std::array<QWidget*, 8> player_tabs;
|
||||
std::array<ConfigureInputPlayer*, 8> player_controllers{};
|
||||
std::array<QWidget*, 8> player_tabs{};
|
||||
// Checkboxes representing the "Connected Controllers".
|
||||
std::array<QCheckBox*, 8> connected_controller_checkboxes;
|
||||
ConfigureInputAdvanced* advanced;
|
||||
std::array<QCheckBox*, 8> connected_controller_checkboxes{};
|
||||
ConfigureInputAdvanced* advanced = nullptr;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -689,10 +692,10 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
|
||||
{
|
||||
// Draw joysticks
|
||||
using namespace Settings::NativeAnalog;
|
||||
const auto l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
|
||||
const auto l_button = button_values[Settings::NativeButton::LStick];
|
||||
const auto r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
|
||||
const auto r_button = button_values[Settings::NativeButton::RStick];
|
||||
const auto& l_stick = QPointF(stick_values[LStick].x.value, stick_values[LStick].y.value);
|
||||
const auto& l_button = button_values[Settings::NativeButton::LStick];
|
||||
const auto& r_stick = QPointF(stick_values[RStick].x.value, stick_values[RStick].y.value);
|
||||
const auto& r_button = button_values[Settings::NativeButton::RStick];
|
||||
|
||||
DrawJoystick(p, center + QPointF(-171, -41) + (l_stick * 4), 1.0f, l_button);
|
||||
DrawJoystick(p, center + QPointF(171, 8) + (r_stick * 4), 1.0f, r_button);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -214,7 +217,7 @@ private:
|
||||
|
||||
bool mapping_active{};
|
||||
int blink_counter{};
|
||||
int callback_key;
|
||||
int callback_key{};
|
||||
QColor button_color{};
|
||||
ColorMapping colors{};
|
||||
Core::HID::LedPattern led_pattern{0, 0, 0, 0};
|
||||
|
||||
@@ -1495,7 +1495,7 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
||||
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
|
||||
auto_paused = true;
|
||||
OnPauseGame();
|
||||
} else if (!emu_thread->IsRunning() && auto_paused && state == Qt::ApplicationActive) {
|
||||
} else if (!emu_thread->IsRunning() && auto_paused && (state & Qt::ApplicationActive)) {
|
||||
auto_paused = false;
|
||||
OnStartGame();
|
||||
}
|
||||
@@ -1505,7 +1505,7 @@ void MainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) {
|
||||
(state & (Qt::ApplicationHidden | Qt::ApplicationInactive))) {
|
||||
Settings::values.audio_muted = true;
|
||||
auto_muted = true;
|
||||
} else if (auto_muted && state == Qt::ApplicationActive) {
|
||||
} else if (auto_muted && (state & Qt::ApplicationActive)) {
|
||||
Settings::values.audio_muted = false;
|
||||
auto_muted = false;
|
||||
}
|
||||
|
||||
@@ -482,13 +482,13 @@ private:
|
||||
|
||||
MultiplayerState* multiplayer_state = nullptr;
|
||||
|
||||
GRenderWindow* render_window;
|
||||
GameList* game_list;
|
||||
LoadingScreen* loading_screen;
|
||||
GRenderWindow* render_window = nullptr;
|
||||
GameList* game_list = nullptr;
|
||||
LoadingScreen* loading_screen = nullptr;
|
||||
QTimer shutdown_timer;
|
||||
OverlayDialog* shutdown_dialog{};
|
||||
|
||||
GameListPlaceholder* game_list_placeholder;
|
||||
GameListPlaceholder* game_list_placeholder = nullptr;
|
||||
|
||||
std::vector<VkDeviceInfo::Record> vk_device_records;
|
||||
|
||||
@@ -531,7 +531,7 @@ private:
|
||||
QString startup_icon_theme;
|
||||
|
||||
// Debugger panes
|
||||
ControllerDialog* controller_dialog;
|
||||
ControllerDialog* controller_dialog = nullptr;
|
||||
|
||||
QAction* actions_recent_files[max_recent_files_item];
|
||||
|
||||
@@ -543,7 +543,7 @@ private:
|
||||
QTranslator translator;
|
||||
|
||||
// Install progress dialog
|
||||
QProgressDialog* install_progress;
|
||||
QProgressDialog* install_progress = nullptr;
|
||||
|
||||
// Last game booted, used for multi-process apps
|
||||
QString last_filename_booted;
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -175,7 +178,7 @@ public:
|
||||
private:
|
||||
QString username;
|
||||
QString nickname;
|
||||
u64 title_id;
|
||||
u64 title_id{};
|
||||
QString game_name;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user