Merge 6574763143
into 1ae0b23265
This commit is contained in:
commit
e8c9fa7003
|
@ -154,6 +154,11 @@ dependencies {
|
|||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||
|
||||
implementation("com.nononsenseapps:filepicker:4.2.1")
|
||||
|
||||
// Motion Camera emulation
|
||||
implementation("androidx.camera:camera-core:1.4.1")
|
||||
implementation("androidx.camera:camera-camera2:1.4.1")
|
||||
implementation("androidx.camera:camera-lifecycle:1.4.1")
|
||||
}
|
||||
|
||||
fun getGitVersion(): String {
|
||||
|
|
|
@ -13,6 +13,9 @@
|
|||
<uses-feature
|
||||
android:name="android.hardware.gamepad"
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.software.leanback"
|
||||
android:required="false"/>
|
||||
|
@ -28,6 +31,7 @@
|
|||
<uses-permission
|
||||
android:name="android.permission.VIBRATE"
|
||||
android:required="false"/>
|
||||
<uses-permission-sdk-23 android:name="android.permission.CAMERA"/>
|
||||
|
||||
<application
|
||||
android:name=".DolphinApplication"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.hardware.usb.UsbManager;
|
||||
|
@ -15,13 +16,15 @@ import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
|||
public class DolphinApplication extends Application
|
||||
{
|
||||
private static DolphinApplication application;
|
||||
private static ActivityTracker sActivityTracker;
|
||||
|
||||
@Override
|
||||
public void onCreate()
|
||||
{
|
||||
super.onCreate();
|
||||
application = this;
|
||||
registerActivityLifecycleCallbacks(new ActivityTracker());
|
||||
sActivityTracker = new ActivityTracker();
|
||||
registerActivityLifecycleCallbacks(sActivityTracker);
|
||||
VolleyUtil.init(getApplicationContext());
|
||||
System.loadLibrary("main");
|
||||
|
||||
|
@ -36,4 +39,9 @@ public class DolphinApplication extends Application
|
|||
{
|
||||
return application.getApplicationContext();
|
||||
}
|
||||
|
||||
public static Activity getAppActivity()
|
||||
{
|
||||
return sActivityTracker.getCurrentActivity();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -459,6 +459,8 @@ public final class NativeLibrary
|
|||
|
||||
private static native String GetCurrentTitleDescriptionUnchecked();
|
||||
|
||||
public static native void CameraSetData(byte[] image);
|
||||
|
||||
@Keep
|
||||
public static void displayToastMsg(final String text, final boolean long_length)
|
||||
{
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
|
@ -60,6 +59,7 @@ import org.dolphinemu.dolphinemu.ui.main.ThemeProvider
|
|||
import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
@ -994,6 +994,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
|||
this.themeId = themeId
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
PermissionsHandler.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BACKSTACK_NAME_MENU = "menu"
|
||||
private const val BACKSTACK_NAME_SUBMENU = "submenu"
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.features.camera
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ImageFormat
|
||||
import android.hardware.camera2.CameraMetadata
|
||||
import android.util.Log
|
||||
import android.util.Size
|
||||
import androidx.camera.core.ImageAnalysis
|
||||
import androidx.camera.core.ImageCapture
|
||||
import androidx.camera.core.ImageProxy
|
||||
import androidx.camera.core.Preview
|
||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import org.dolphinemu.dolphinemu.DolphinApplication
|
||||
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
|
||||
import org.dolphinemu.dolphinemu.utils.PermissionsHandler
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class Camera {
|
||||
companion object {
|
||||
val TAG = "Camera"
|
||||
private var instance: Camera? = null
|
||||
|
||||
private var cameraEntries: Array<String> = arrayOf()
|
||||
private var cameraValues: Array<String> = arrayOf()
|
||||
|
||||
private var width = 0
|
||||
private var height = 0
|
||||
private var isRunning: Boolean = false
|
||||
private lateinit var imageCapture: ImageCapture
|
||||
private lateinit var cameraExecutor: ExecutorService
|
||||
|
||||
fun getInstance(context: Context) = instance ?: synchronized(this) {
|
||||
instance ?: Camera().also {
|
||||
instance = it
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
val cameraInfos = cameraProvider.getAvailableCameraInfos()
|
||||
var index = 0;
|
||||
fun getCameraDescription(type: Int) : String {
|
||||
return when (type) {
|
||||
CameraMetadata.LENS_FACING_BACK -> "Back"
|
||||
CameraMetadata.LENS_FACING_FRONT -> "Front"
|
||||
CameraMetadata.LENS_FACING_EXTERNAL -> "External"
|
||||
else -> "Unknown"
|
||||
}
|
||||
}
|
||||
for (camera in cameraInfos) {
|
||||
cameraEntries += "${index}: ${getCameraDescription(camera.lensFacing)}"
|
||||
cameraValues += index++.toString()
|
||||
}
|
||||
}, ContextCompat.getMainExecutor(context))
|
||||
cameraExecutor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
}
|
||||
|
||||
fun getCameraEntries(): Array<String> {
|
||||
return cameraEntries
|
||||
}
|
||||
|
||||
fun getCameraValues(): Array<String> {
|
||||
return cameraValues
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun resumeCamera() {
|
||||
if (width == 0 || height == 0)
|
||||
return
|
||||
Log.i(TAG, "resumeCamera")
|
||||
startCamera(width, height)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun startCamera(width: Int, height: Int) {
|
||||
this.width = width
|
||||
this.height = height
|
||||
if (!PermissionsHandler.hasCameraAccess(DolphinApplication.getAppContext())) {
|
||||
PermissionsHandler.requestCameraPermission(DolphinApplication.getAppActivity())
|
||||
return
|
||||
}
|
||||
|
||||
if (isRunning)
|
||||
return
|
||||
isRunning = true
|
||||
Log.i(TAG, "startCamera: " + width + "x" + height)
|
||||
Log.i(TAG, "Selected Camera: " + StringSetting.MAIN_SELECTED_CAMERA.string)
|
||||
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(DolphinApplication.getAppContext())
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
|
||||
val cameraSelector = cameraProvider.getAvailableCameraInfos()
|
||||
.get(Integer.parseInt(StringSetting.MAIN_SELECTED_CAMERA.string))
|
||||
.cameraSelector
|
||||
val preview = Preview.Builder().build()
|
||||
imageCapture = ImageCapture.Builder()
|
||||
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
|
||||
.build()
|
||||
val imageAnalyzer = ImageAnalysis.Builder()
|
||||
.setTargetResolution(Size(width, height))
|
||||
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_YUV_420_888)
|
||||
.build()
|
||||
.also {
|
||||
it.setAnalyzer(cameraExecutor, ImageProcessor())
|
||||
}
|
||||
|
||||
cameraProvider.bindToLifecycle(DolphinApplication.getAppActivity() as LifecycleOwner, cameraSelector, preview, imageCapture, imageAnalyzer)
|
||||
}, ContextCompat.getMainExecutor(DolphinApplication.getAppContext()))
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun stopCamera() {
|
||||
if (!isRunning)
|
||||
return
|
||||
isRunning = false
|
||||
width = 0
|
||||
height = 0
|
||||
Log.i(TAG, "stopCamera")
|
||||
|
||||
val cameraProviderFuture = ProcessCameraProvider.getInstance(DolphinApplication.getAppContext())
|
||||
cameraProviderFuture.addListener({
|
||||
val cameraProvider = cameraProviderFuture.get()
|
||||
cameraProvider.unbindAll()
|
||||
}, ContextCompat.getMainExecutor(DolphinApplication.getAppContext()))
|
||||
}
|
||||
|
||||
private class ImageProcessor : ImageAnalysis.Analyzer {
|
||||
override fun analyze(image: ImageProxy) {
|
||||
if (image.format != ImageFormat.YUV_420_888) {
|
||||
Log.e(TAG, "Error: Unhandled image format: ${image.format}")
|
||||
image.close()
|
||||
stopCamera()
|
||||
return
|
||||
}
|
||||
|
||||
Log.i(TAG, "analyze sz=${image.width}x${image.height} / fmt=${image.format} / "
|
||||
+ "rot=${image.imageInfo.rotationDegrees} / "
|
||||
// + "px0=${image.planes[0].pixelStride} / px1=${image.planes[1].pixelStride} / px2=${image.planes[2].pixelStride} / "
|
||||
+ "row0=${image.planes[0].rowStride} / row1=${image.planes[1].rowStride} / row2=${image.planes[2].rowStride}"
|
||||
)
|
||||
|
||||
// Convert YUV_420_888 to YUY2
|
||||
val yuy2Image = ByteArray(2 * width * height)
|
||||
for (line in 0 until height) {
|
||||
for (col in 0 until width) {
|
||||
val yuy2Pos = 2 * (width * line + col)
|
||||
val yPos = image.planes[0].rowStride * line + image.planes[0].pixelStride * col
|
||||
var uPos = image.planes[1].rowStride * (line / 2) + image.planes[1].pixelStride * (col / 2)
|
||||
var vPos = image.planes[2].rowStride * (line / 2) + image.planes[2].pixelStride * (col / 2)
|
||||
yuy2Image.set(yuy2Pos, image.planes[0].buffer.get(yPos))
|
||||
yuy2Image.set(yuy2Pos + 1, if (col % 2 == 0) image.planes[1].buffer.get(uPos)
|
||||
else image.planes[2].buffer.get(vPos))
|
||||
}
|
||||
}
|
||||
image.close()
|
||||
NativeLibrary.CameraSetData(yuy2Image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ import org.dolphinemu.dolphinemu.R
|
|||
import org.dolphinemu.dolphinemu.databinding.DialogInputStringBinding
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView
|
||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
||||
import java.io.File
|
||||
import java.util.Locale
|
||||
|
||||
|
|
|
@ -82,6 +82,12 @@ enum class IntSetting(
|
|||
"DoubleTapButton",
|
||||
NativeLibrary.ButtonType.WIIMOTE_BUTTON_A
|
||||
),
|
||||
MAIN_EMULATED_CAMERA(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_EMULATED_USB_DEVICES,
|
||||
"EmulatedCamera",
|
||||
0
|
||||
),
|
||||
SYSCONF_LANGUAGE(Settings.FILE_SYSCONF, "IPL", "LNG", 0x01),
|
||||
SYSCONF_SOUND_MODE(Settings.FILE_SYSCONF, "IPL", "SND", 0x01),
|
||||
SYSCONF_SENSOR_BAR_POSITION(Settings.FILE_SYSCONF, "BT", "BAR", 0x01),
|
||||
|
|
|
@ -55,6 +55,12 @@ enum class StringSetting(
|
|||
"ResourcePackPath",
|
||||
""
|
||||
),
|
||||
MAIN_SELECTED_CAMERA(
|
||||
Settings.FILE_DOLPHIN,
|
||||
Settings.SECTION_EMULATED_USB_DEVICES,
|
||||
"SelectedCamera",
|
||||
""
|
||||
),
|
||||
MAIN_FS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "NANDRootPath", ""),
|
||||
MAIN_WII_SD_CARD_IMAGE_PATH(
|
||||
Settings.FILE_DOLPHIN,
|
||||
|
|
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.withContext
|
|||
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.activities.UserDataActivity
|
||||
import org.dolphinemu.dolphinemu.features.camera.Camera
|
||||
import org.dolphinemu.dolphinemu.features.input.model.ControlGroupEnabledSetting
|
||||
import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting
|
||||
import org.dolphinemu.dolphinemu.features.input.model.InputMappingDoubleSetting
|
||||
|
@ -186,6 +187,28 @@ class SettingsFragmentPresenter(
|
|||
}
|
||||
|
||||
private fun addTopLevelSettings(sl: ArrayList<SettingsItem>) {
|
||||
sl.add(
|
||||
SingleChoiceSetting(
|
||||
context,
|
||||
IntSetting.MAIN_EMULATED_CAMERA,
|
||||
R.string.emulated_camera,
|
||||
0,
|
||||
R.array.emulatedCameraEntries,
|
||||
R.array.emulatedCameraValues
|
||||
)
|
||||
)
|
||||
var camerasEntries = Camera.getCameraEntries()
|
||||
var cameraValues = Camera.getCameraValues()
|
||||
sl.add(
|
||||
StringSingleChoiceSetting(
|
||||
context,
|
||||
StringSetting.MAIN_SELECTED_CAMERA,
|
||||
R.string.selected_camera,
|
||||
0,
|
||||
camerasEntries,
|
||||
cameraValues
|
||||
)
|
||||
)
|
||||
sl.add(SubmenuSetting(context, R.string.config, MenuTag.CONFIG))
|
||||
sl.add(SubmenuSetting(context, R.string.graphics_settings, MenuTag.GRAPHICS))
|
||||
|
||||
|
@ -876,6 +899,18 @@ class SettingsFragmentPresenter(
|
|||
)
|
||||
|
||||
sl.add(HeaderSetting(context, R.string.emulated_usb_devices, 0))
|
||||
// var camerasEntries = Camera.getCameraEntries()
|
||||
// var cameraValues = Camera.getCameraValues()
|
||||
// sl.add(
|
||||
// StringSingleChoiceSetting(
|
||||
// context,
|
||||
// StringSetting.MAIN_EMULATE_CAMERA,
|
||||
// R.string.emulate_camera,
|
||||
// 0,
|
||||
// camerasEntries,
|
||||
// cameraValues
|
||||
// )
|
||||
// )
|
||||
sl.add(
|
||||
SwitchSetting(
|
||||
context,
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.R
|
|||
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
||||
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter
|
||||
import org.dolphinemu.dolphinemu.databinding.ActivityMainBinding
|
||||
import org.dolphinemu.dolphinemu.features.camera.Camera
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
|
||||
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||
|
@ -76,6 +77,7 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
|||
}
|
||||
|
||||
presenter.onCreate()
|
||||
Camera.getInstance(applicationContext)
|
||||
|
||||
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
|
||||
if (savedInstanceState == null) {
|
||||
|
@ -216,6 +218,7 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
|||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
PermissionsHandler.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
||||
PermissionsHandler.setWritePermissionDenied()
|
||||
|
|
|
@ -220,6 +220,7 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
|
|||
grantResults: IntArray
|
||||
) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
PermissionsHandler.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
||||
PermissionsHandler.setWritePermissionDenied()
|
||||
|
|
|
@ -7,12 +7,14 @@ import android.os.Bundle
|
|||
class ActivityTracker : ActivityLifecycleCallbacks {
|
||||
val resumedActivities = HashSet<Activity>()
|
||||
var backgroundExecutionAllowed = false
|
||||
var currentActivity : Activity? = null
|
||||
|
||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||
|
||||
override fun onActivityStarted(activity: Activity) {}
|
||||
|
||||
override fun onActivityResumed(activity: Activity) {
|
||||
currentActivity = activity
|
||||
resumedActivities.add(activity)
|
||||
if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) {
|
||||
backgroundExecutionAllowed = true
|
||||
|
@ -21,6 +23,9 @@ class ActivityTracker : ActivityLifecycleCallbacks {
|
|||
}
|
||||
|
||||
override fun onActivityPaused(activity: Activity) {
|
||||
if (currentActivity === activity) {
|
||||
currentActivity = null
|
||||
}
|
||||
resumedActivities.remove(activity)
|
||||
if (backgroundExecutionAllowed && resumedActivities.isEmpty()) {
|
||||
backgroundExecutionAllowed = false
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
package org.dolphinemu.dolphinemu.utils;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
|
@ -10,16 +11,21 @@ import android.os.Environment;
|
|||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import org.dolphinemu.dolphinemu.features.camera.Camera;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
|
||||
import static android.Manifest.permission.CAMERA;
|
||||
|
||||
public class PermissionsHandler
|
||||
{
|
||||
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
|
||||
public static final int REQUEST_CODE_CAMERA_PERMISSION = 502;
|
||||
private static boolean sWritePermissionDenied = false;
|
||||
|
||||
public static void requestWritePermission(final FragmentActivity activity)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return;
|
||||
|
||||
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
|
||||
|
@ -28,7 +34,7 @@ public class PermissionsHandler
|
|||
|
||||
public static boolean hasWriteAccess(Context context)
|
||||
{
|
||||
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return true;
|
||||
|
||||
if (!isExternalStorageLegacy())
|
||||
|
@ -52,4 +58,27 @@ public class PermissionsHandler
|
|||
{
|
||||
return sWritePermissionDenied;
|
||||
}
|
||||
|
||||
public static boolean hasCameraAccess(Context context) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return true;
|
||||
|
||||
int hasCameraPermission = ContextCompat.checkSelfPermission(context, CAMERA);
|
||||
return hasCameraPermission == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
public static void requestCameraPermission(Activity activity) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
|
||||
return;
|
||||
|
||||
activity.requestPermissions(new String[]{CAMERA}, REQUEST_CODE_CAMERA_PERMISSION);
|
||||
}
|
||||
|
||||
public static void onRequestPermissionsResult(int requestCode, @NotNull String[] permissions, @NotNull int[] grantResults) {
|
||||
if (requestCode == PermissionsHandler.REQUEST_CODE_CAMERA_PERMISSION) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Camera.Companion.resumeCamera();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,6 +223,18 @@
|
|||
<item>5</item>
|
||||
</integer-array>
|
||||
|
||||
<!-- Emulated camera -->
|
||||
<string-array name="emulatedCameraEntries">
|
||||
<item>@string/disabled</item>
|
||||
<item>Duel Scanner</item>
|
||||
<item>Motion Tracking Camera</item>
|
||||
</string-array>
|
||||
<integer-array name="emulatedCameraValues">
|
||||
<item>0</item>
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</integer-array>
|
||||
|
||||
<!-- Texture Cache Accuracy Preference -->
|
||||
<string-array name="textureCacheAccuracyEntries">
|
||||
<item>@string/accuracy_fast</item>
|
||||
|
|
|
@ -911,6 +911,8 @@ It can efficiently compress both junk data and encrypted Wii data.
|
|||
|
||||
<!-- Emulated USB Devices -->
|
||||
<string name="emulated_usb_devices">Emulated USB Devices</string>
|
||||
<string name="emulated_camera">Emulated Camera</string>
|
||||
<string name="selected_camera">Selected Camera</string>
|
||||
<string name="emulate_skylander_portal">Skylanders Portal</string>
|
||||
<string name="skylanders_manager">Skylanders Manager</string>
|
||||
<string name="create_skylander_title">Create Skylander</string>
|
||||
|
|
|
@ -113,6 +113,11 @@ static jclass s_core_device_control_class;
|
|||
static jfieldID s_core_device_control_pointer;
|
||||
static jmethodID s_core_device_control_constructor;
|
||||
|
||||
static jclass s_camera_class;
|
||||
static jmethodID s_camera_start;
|
||||
static jmethodID s_camera_resume;
|
||||
static jmethodID s_camera_stop;
|
||||
|
||||
static jclass s_input_detector_class;
|
||||
static jfieldID s_input_detector_pointer;
|
||||
|
||||
|
@ -528,6 +533,26 @@ jmethodID GetCoreDeviceControlConstructor()
|
|||
return s_core_device_control_constructor;
|
||||
}
|
||||
|
||||
jclass GetCameraClass()
|
||||
{
|
||||
return s_camera_class;
|
||||
}
|
||||
|
||||
jmethodID GetCameraStart()
|
||||
{
|
||||
return s_camera_start;
|
||||
}
|
||||
|
||||
jmethodID GetCameraResume()
|
||||
{
|
||||
return s_camera_resume;
|
||||
}
|
||||
|
||||
jmethodID GetCameraStop()
|
||||
{
|
||||
return s_camera_stop;
|
||||
}
|
||||
|
||||
jclass GetInputDetectorClass()
|
||||
{
|
||||
return s_input_detector_class;
|
||||
|
@ -759,6 +784,13 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
|||
"(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V");
|
||||
env->DeleteLocalRef(core_device_control_class);
|
||||
|
||||
const jclass camera_class = env->FindClass("org/dolphinemu/dolphinemu/features/camera/Camera");
|
||||
s_camera_class = reinterpret_cast<jclass>(env->NewGlobalRef(camera_class));
|
||||
s_camera_start = env->GetStaticMethodID(camera_class, "startCamera", "(II)V");
|
||||
s_camera_resume = env->GetStaticMethodID(camera_class, "resumeCamera", "()V");
|
||||
s_camera_stop = env->GetStaticMethodID(camera_class, "stopCamera", "()V");
|
||||
env->DeleteLocalRef(camera_class);
|
||||
|
||||
const jclass input_detector_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/InputDetector");
|
||||
s_input_detector_class = reinterpret_cast<jclass>(env->NewGlobalRef(input_detector_class));
|
||||
|
@ -803,6 +835,7 @@ JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved)
|
|||
env->DeleteGlobalRef(s_numeric_setting_class);
|
||||
env->DeleteGlobalRef(s_core_device_class);
|
||||
env->DeleteGlobalRef(s_core_device_control_class);
|
||||
env->DeleteGlobalRef(s_camera_class);
|
||||
env->DeleteGlobalRef(s_input_detector_class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,6 +112,11 @@ jclass GetCoreDeviceControlClass();
|
|||
jfieldID GetCoreDeviceControlPointer();
|
||||
jmethodID GetCoreDeviceControlConstructor();
|
||||
|
||||
jclass GetCameraClass();
|
||||
jmethodID GetCameraStart();
|
||||
jmethodID GetCameraResume();
|
||||
jmethodID GetCameraStop();
|
||||
|
||||
jclass GetInputDetectorClass();
|
||||
jfieldID GetInputDetectorPointer();
|
||||
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "Core/HW/Wiimote.h"
|
||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||
#include "Core/Host.h"
|
||||
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "Core/State.h"
|
||||
|
@ -136,6 +137,18 @@ void Host_UpdateTitle(const std::string& title)
|
|||
__android_log_write(ANDROID_LOG_INFO, DOLPHIN_TAG, title.c_str());
|
||||
}
|
||||
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetCameraClass(), IDCache::GetCameraStart(), width, height);
|
||||
}
|
||||
|
||||
void Host_CameraStop()
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
env->CallStaticVoidMethod(IDCache::GetCameraClass(), IDCache::GetCameraStop());
|
||||
}
|
||||
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
}
|
||||
|
@ -488,6 +501,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
|
|||
if (g_presenter)
|
||||
g_presenter->ChangeSurface(s_surf);
|
||||
|
||||
env->CallStaticVoidMethod(IDCache::GetCameraClass(), IDCache::GetCameraResume());
|
||||
|
||||
s_surface_cv.notify_all();
|
||||
}
|
||||
|
||||
|
@ -832,4 +847,13 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCurrentTitleDescriptionUnchecked
|
|||
|
||||
return ToJString(env, description);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_dolphinemu_dolphinemu_NativeLibrary_CameraSetData(JNIEnv* env, jclass, jbyteArray image)
|
||||
{
|
||||
jlong size = env->GetArrayLength(image);
|
||||
jbyte* buffer = env->GetByteArrayElements(image, nullptr);
|
||||
Core::System::GetInstance().GetCameraBase().SetData(reinterpret_cast<const u8 *>(buffer), size);
|
||||
env->ReleaseByteArrayElements(image, buffer, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -425,8 +425,14 @@ add_library(core
|
|||
IOS/USB/Bluetooth/WiimoteHIDAttr.h
|
||||
IOS/USB/Common.cpp
|
||||
IOS/USB/Common.h
|
||||
IOS/USB/Emulated/CameraBase.cpp
|
||||
IOS/USB/Emulated/CameraBase.h
|
||||
IOS/USB/Emulated/DuelScanner.cpp
|
||||
IOS/USB/Emulated/DuelScanner.h
|
||||
IOS/USB/Emulated/Infinity.cpp
|
||||
IOS/USB/Emulated/Infinity.h
|
||||
IOS/USB/Emulated/MotionCamera.cpp
|
||||
IOS/USB/Emulated/MotionCamera.h
|
||||
IOS/USB/Emulated/Skylanders/Skylander.cpp
|
||||
IOS/USB/Emulated/Skylanders/Skylander.h
|
||||
IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp
|
||||
|
|
|
@ -585,6 +585,12 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
|
|||
|
||||
// Main.EmulatedUSBDevices
|
||||
|
||||
const Info<int> MAIN_EMULATED_CAMERA{
|
||||
{System::Main, "EmulatedUSBDevices", "EmulatedCamera"}, 0};
|
||||
|
||||
const Info<std::string> MAIN_SELECTED_CAMERA{
|
||||
{System::Main, "EmulatedUSBDevices", "SelectedCamera"}, ""};
|
||||
|
||||
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
|
||||
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
||||
|
||||
|
|
|
@ -358,6 +358,8 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
|||
|
||||
// Main.EmulatedUSBDevices
|
||||
|
||||
extern const Info<int> MAIN_EMULATED_CAMERA;
|
||||
extern const Info<std::string> MAIN_SELECTED_CAMERA;
|
||||
extern const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL;
|
||||
extern const Info<bool> MAIN_EMULATE_INFINITY_BASE;
|
||||
|
||||
|
|
|
@ -900,6 +900,8 @@ void UpdateTitle(Core::System& system)
|
|||
}
|
||||
|
||||
Host_UpdateTitle(message);
|
||||
//Host_CameraStart(320, 240);
|
||||
//Host_CameraStart(640, 480);
|
||||
}
|
||||
|
||||
void Shutdown(Core::System& system)
|
||||
|
|
|
@ -68,6 +68,9 @@ void Host_UpdateTitle(const std::string& title);
|
|||
void Host_YieldToUI();
|
||||
void Host_TitleChanged();
|
||||
|
||||
void Host_CameraStart(u16 width, u16 height);
|
||||
void Host_CameraStop();
|
||||
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id = {});
|
||||
bool Host_UpdateDiscordPresenceRaw(const std::string& details = {}, const std::string& state = {},
|
||||
const std::string& large_image_key = {},
|
||||
|
|
|
@ -29,6 +29,7 @@ enum ControlRequestTypes
|
|||
DIR_HOST2DEVICE = 0,
|
||||
DIR_DEVICE2HOST = 1,
|
||||
TYPE_STANDARD = 0,
|
||||
TYPE_CLASS = 1,
|
||||
TYPE_VENDOR = 2,
|
||||
REC_DEVICE = 0,
|
||||
REC_INTERFACE = 1,
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
#### Support:
|
||||
- [x] Yu-Gi-Oh! 5D's: Duel Transer / 640x480
|
||||
- [x] Your Shape / 320x240
|
||||
- [x] Fit in Six / 320x240
|
||||
- [?] Racket Sports Party / 160x120
|
||||
|
||||
#### QTMultimedia:
|
||||
```
|
||||
git clone https://github.com/dolphin-emu/qsc.git _qsc
|
||||
cd _qsc
|
||||
py -m pip install -r requirements.txt
|
||||
$env:Path += ';C:\Program Files\Git\usr\bin\'
|
||||
```
|
||||
```diff
|
||||
diff --git a/examples/dolphin-x64.yml b/examples/dolphin-x64.yml
|
||||
index 5486190..a895eaf 100644
|
||||
--- a/examples/dolphin-x64.yml
|
||||
+++ b/examples/dolphin-x64.yml
|
||||
@@ -29,7 +29,6 @@ configure:
|
||||
- qtlocation
|
||||
- qtlottie
|
||||
- qtmqtt
|
||||
- - qtmultimedia
|
||||
- qtnetworkauth
|
||||
- qtopcua
|
||||
- qtpositioning
|
||||
@@ -42,7 +41,6 @@ configure:
|
||||
- qtsensors
|
||||
- qtserialbus
|
||||
- qtserialport
|
||||
- - qtshadertools
|
||||
- qtspeech
|
||||
- qttools
|
||||
- qttranslations
|
||||
@@ -53,13 +51,13 @@ configure:
|
||||
- qtwebsockets
|
||||
- qtwebview
|
||||
feature:
|
||||
- concurrent: false
|
||||
+ concurrent: true
|
||||
dbus: false
|
||||
gif: false
|
||||
ico: false
|
||||
imageformat_bmp: false
|
||||
jpeg: false
|
||||
- network: false
|
||||
+ network: true
|
||||
printsupport: false
|
||||
qmake: false
|
||||
sql: false
|
||||
```
|
||||
```
|
||||
py -m qsc examples\dolphin-x64.yml
|
||||
```
|
|
@ -0,0 +1,127 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
CameraBase::~CameraBase()
|
||||
{
|
||||
if (m_image_data)
|
||||
{
|
||||
free(m_image_data);
|
||||
m_image_data = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void CameraBase::CreateSample(const u16 width, const u16 height)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "CameraBase::CreateSample width={}, height={}", width, height);
|
||||
u32 new_size = width * height * 2;
|
||||
if (m_image_size != new_size)
|
||||
{
|
||||
m_image_size = new_size;
|
||||
if (m_image_data)
|
||||
{
|
||||
free(m_image_data);
|
||||
}
|
||||
m_image_data = (u8*) calloc(1, m_image_size);
|
||||
}
|
||||
|
||||
for (int line = 0 ; line < height; line++) {
|
||||
for (int col = 0; col < width; col++) {
|
||||
u8 *pos = m_image_data + 2 * (width * line + col);
|
||||
|
||||
u8 r = col * 255 / width;
|
||||
u8 g = col * 255 / width;
|
||||
u8 b = line * 255 / height;
|
||||
|
||||
u8 y = (( 66 * r + 129 * g + 25 * b + 128) / 256) + 16;
|
||||
u8 u = ((-38 * r - 74 * g + 112 * b + 128) / 256) + 128;
|
||||
u8 v = ((112 * r - 94 * g - 18 * b + 128) / 256) + 128;
|
||||
|
||||
pos[0] = y;
|
||||
pos[1] = (col % 2 == 0) ? u : v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CameraBase::SetData(const u8* data, u32 length)
|
||||
{
|
||||
if (length > m_image_size)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "CameraBase::SetData length({}) > m_image_size({})", length, m_image_size);
|
||||
return;
|
||||
}
|
||||
m_image_size = length;
|
||||
memcpy(m_image_data, data, length);
|
||||
//ERROR_LOG_FMT(IOS_USB, "SetData length={:x}", length);
|
||||
//static bool done = false;
|
||||
//if (!done)
|
||||
//{
|
||||
// FILE* f = fopen("yugioh_dualscanner.raw", "wb");
|
||||
// if (!f)
|
||||
// {
|
||||
// ERROR_LOG_FMT(IOS_USB, "yugioh_dualscanner null");
|
||||
// return;
|
||||
// }
|
||||
// fwrite(m_image_data, m_image_size, 1, f);
|
||||
// fclose(f);
|
||||
// done = true;
|
||||
//}
|
||||
}
|
||||
|
||||
void CameraBase::GetData(const u8* data, u32 length)
|
||||
{
|
||||
if (length > m_image_size)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "CameraBase::GetData length({}) > m_image_size({})", length, m_image_size);
|
||||
return;
|
||||
}
|
||||
memcpy((void*)data, m_image_data, length);
|
||||
}
|
||||
|
||||
std::string CameraBase::getUVCVideoStreamingControl(u8 value)
|
||||
{
|
||||
std::string names[] = { "VS_CONTROL_UNDEFINED", "VS_PROBE", "VS_COMMIT" };
|
||||
if (value <= VS_COMMIT)
|
||||
return names[value];
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string CameraBase::getUVCRequest(u8 value)
|
||||
{
|
||||
if (value == SET_CUR)
|
||||
return "SET_CUR";
|
||||
std::string names[] = { "GET_CUR", "GET_MIN", "GET_MAX", "GET_RES", "GET_LEN", "GET_INF", "GET_DEF" };
|
||||
if (GET_CUR <= value && value <= GET_DEF)
|
||||
return names[value - GET_CUR];
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string CameraBase::getUVCTerminalControl(u8 value)
|
||||
{
|
||||
std::string names[] = { "CONTROL_UNDEFINED", "SCANNING_MODE", "AE_MODE", "AE_PRIORITY", "EXPOSURE_TIME_ABSOLUTE",
|
||||
"EXPOSURE_TIME_RELATIVE", "FOCUS_ABSOLUTE", "FOCUS_RELATIVE", "FOCUS_AUTO", "IRIS_ABSOLUTE", "IRIS_RELATIVE",
|
||||
"ZOOM_ABSOLUTE", "ZOOM_RELATIVE", "PANTILT_ABSOLUTE", "PANTILT_RELATIVE", "ROLL_ABSOLUTE", "ROLL_RELATIVE",
|
||||
"PRIVACY" };
|
||||
if (value <= CT_PRIVACY)
|
||||
return names[value];
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::string CameraBase::getUVCProcessingUnitControl(u8 value)
|
||||
{
|
||||
std::string names[] = { "CONTROL_UNDEFINED", "BACKLIGHT_COMPENSATION", "BRIGHTNESS", "CONTRAST", "GAIN",
|
||||
"POWER_LINE_FREQUENCY", "HUE", "SATURATION", "SHARPNESS", "GAMMA", "WHITE_BALANCE_TEMPERATURE",
|
||||
"WHITE_BALANCE_TEMPERATURE_AUTO", "WHITE_BALANCE_COMPONENT", "WHITE_BALANCE_COMPONENT_AUTO", "DIGITAL_MULTIPLIER",
|
||||
"DIGITAL_MULTIPLIER_LIMIT", "HUE_AUTO", "ANALOG_VIDEO_STANDARD", "ANALOG_LOCK_STATUS" };
|
||||
if (value <= PU_ANALOG_LOCK_STATUS)
|
||||
return names[value];
|
||||
return "Unknown";
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
enum UVCRequestCodes
|
||||
{
|
||||
SET_CUR = 0x01,
|
||||
GET_CUR = 0x81,
|
||||
GET_MIN = 0x82,
|
||||
GET_MAX = 0x83,
|
||||
GET_RES = 0x84,
|
||||
GET_LEN = 0x85,
|
||||
GET_INF = 0x86,
|
||||
GET_DEF = 0x87,
|
||||
};
|
||||
|
||||
enum UVCVideoStreamingControl
|
||||
{
|
||||
VS_CONTROL_UNDEFINED = 0x00,
|
||||
VS_PROBE = 0x01,
|
||||
VS_COMMIT = 0x02,
|
||||
VS_STILL_PROBE = 0x03,
|
||||
VS_STILL_COMMIT = 0x04,
|
||||
VS_STILL_IMAGE_TRIGGER = 0x05,
|
||||
VS_STREAM_ERROR_CODE = 0x06,
|
||||
VS_GENERATE_KEY_FRAME = 0x07,
|
||||
VS_UPDATE_FRAME_SEGMENT = 0x08,
|
||||
VS_SYNCH_DELAY = 0x09,
|
||||
};
|
||||
|
||||
enum UVCTerminalControl
|
||||
{
|
||||
CT_CONTROL_UNDEFINED = 0x00,
|
||||
CT_SCANNING_MODE = 0x01,
|
||||
CT_AE_MODE = 0x02,
|
||||
CT_AE_PRIORITY = 0x03,
|
||||
CT_EXPOSURE_TIME_ABSOLUTE = 0x04,
|
||||
CT_EXPOSURE_TIME_RELATIVE = 0x05,
|
||||
CT_FOCUS_ABSOLUTE = 0x06,
|
||||
CT_FOCUS_RELATIVE = 0x07,
|
||||
CT_FOCUS_AUTO = 0x08,
|
||||
CT_IRIS_ABSOLUTE = 0x09,
|
||||
CT_IRIS_RELATIVE = 0x0A,
|
||||
CT_ZOOM_ABSOLUTE = 0x0B,
|
||||
CT_ZOOM_RELATIVE = 0x0C,
|
||||
CT_PANTILT_ABSOLUTE = 0x0D,
|
||||
CT_PANTILT_RELATIVE = 0x0E,
|
||||
CT_ROLL_ABSOLUTE = 0x0F,
|
||||
CT_ROLL_RELATIVE = 0x10,
|
||||
CT_PRIVACY = 0x11,
|
||||
};
|
||||
|
||||
enum UVCProcessingUnitControl
|
||||
{
|
||||
PU_CONTROL_UNDEFINED = 0x00,
|
||||
PU_BACKLIGHT_COMPENSATION = 0x01,
|
||||
PU_BRIGHTNESS = 0x02,
|
||||
PU_CONTRAST = 0x03,
|
||||
PU_GAIN = 0x04,
|
||||
PU_POWER_LINE_FREQUENCY = 0x05,
|
||||
PU_HUE = 0x06,
|
||||
PU_SATURATION = 0x07,
|
||||
PU_SHARPNESS = 0x08,
|
||||
PU_GAMMA = 0x09,
|
||||
PU_WHITE_BALANCE_TEMPERATURE = 0x0A,
|
||||
PU_WHITE_BALANCE_TEMPERATURE_AUTO = 0x0B,
|
||||
PU_WHITE_BALANCE_COMPONENT = 0x0C,
|
||||
PU_WHITE_BALANCE_COMPONENT_AUTO = 0x0D,
|
||||
PU_DIGITAL_MULTIPLIER = 0x0E,
|
||||
PU_DIGITAL_MULTIPLIER_LIMIT = 0x0F,
|
||||
PU_HUE_AUTO = 0x10,
|
||||
PU_ANALOG_VIDEO_STANDARD = 0x11,
|
||||
PU_ANALOG_LOCK_STATUS = 0x12,
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct UVCHeader
|
||||
{
|
||||
u8 bHeaderLength;
|
||||
union {
|
||||
u8 bmHeaderInfo;
|
||||
struct {
|
||||
u8 frameId : 1;
|
||||
u8 endOfFrame : 1;
|
||||
u8 presentationTimeStamp : 1;
|
||||
u8 sourceClockReference : 1;
|
||||
u8 : 1;
|
||||
u8 stillImage : 1;
|
||||
u8 error : 1;
|
||||
u8 endOfHeader : 1;
|
||||
};
|
||||
};
|
||||
// let's skip the optional fiels for now and see how it goes
|
||||
/*
|
||||
u32 dwPresentationTime;
|
||||
union {
|
||||
u8 scrSourceClock[6];
|
||||
struct {
|
||||
u32 sourceTimeClock;
|
||||
u16 sofCounter : 11;
|
||||
u16 : 5;
|
||||
};
|
||||
};
|
||||
*/
|
||||
};
|
||||
|
||||
struct UVCProbeCommitControl
|
||||
{
|
||||
union {
|
||||
u16 bmHint;
|
||||
struct {
|
||||
u16 frameInterval : 1;
|
||||
u16 keyFrameRate : 1;
|
||||
u16 frameRate : 1;
|
||||
u16 compQuality : 1;
|
||||
u16 compWindowSize : 1;
|
||||
u16 : 11;
|
||||
};
|
||||
};
|
||||
u8 bFormatIndex;
|
||||
u8 bFrameIndex;
|
||||
u32 dwFrameInterval;
|
||||
u16 wKeyFrameRate;
|
||||
u16 wPFrameRate;
|
||||
u16 wCompQuality;
|
||||
u16 wCompWindowSize;
|
||||
u16 wDelay;
|
||||
u32 dwMaxVideoFrameSize;
|
||||
u32 dwMaxPayloadTransferSize;
|
||||
};
|
||||
|
||||
struct UVCImageSize
|
||||
{
|
||||
u16 width;
|
||||
u16 height;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class CameraBase final
|
||||
{
|
||||
public:
|
||||
CameraBase() {};
|
||||
~CameraBase();
|
||||
void CreateSample(const u16 width, const u16 height);
|
||||
void SetData(const u8* data, u32 length);
|
||||
void GetData(const u8* data, u32 length);
|
||||
static std::string getUVCVideoStreamingControl(u8 value);
|
||||
static std::string getUVCRequest(u8 value);
|
||||
static std::string getUVCTerminalControl(u8 value);
|
||||
static std::string getUVCProcessingUnitControl(u8 value);
|
||||
|
||||
private:
|
||||
u32 m_image_size = 0;
|
||||
u8 *m_image_data = nullptr;
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,472 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/IOS/USB/Emulated/DuelScanner.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
const u8 usb_config_desc[] = {
|
||||
0x09, 0x02, 0x1f, 0x02, 0x02, 0x01, 0x30, 0x80, 0xfa, 0x08, 0x0b, 0x00, 0x02, 0x0e, 0x03, 0x00,
|
||||
0x60, 0x09, 0x04, 0x00, 0x00, 0x01, 0x0e, 0x01, 0x00, 0x60, 0x0d, 0x24, 0x01, 0x00, 0x01, 0x4d,
|
||||
0x00, 0xc0, 0xe1, 0xe4, 0x00, 0x01, 0x01, 0x09, 0x24, 0x03, 0x02, 0x01, 0x01, 0x00, 0x04, 0x00,
|
||||
0x1a, 0x24, 0x06, 0x04, 0xf0, 0x77, 0x35, 0xd1, 0x89, 0x8d, 0x00, 0x47, 0x81, 0x2e, 0x7d, 0xd5,
|
||||
0xe2, 0xfd, 0xb8, 0x98, 0x08, 0x01, 0x03, 0x01, 0xff, 0x00, 0x12, 0x24, 0x02, 0x01, 0x01, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
// 0x00, 0x02, 0x00,
|
||||
0x00, 0x00, 0x00,
|
||||
0x0b, 0x24, 0x05, 0x03,
|
||||
0x01, 0x00, 0x00, 0x02,
|
||||
// 0x7f, 0x05,
|
||||
0x00, 0x00,
|
||||
0x00, 0x07, 0x05, 0x82, 0x03, 0x10, 0x00, 0x06, 0x05, 0x25,
|
||||
0x03, 0x10, 0x00, 0x09, 0x04, 0x01, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x0e, 0x24, 0x01, 0x01,
|
||||
0x43, 0x01, 0x81, 0x00, 0x02, 0x02, 0x01, 0x00, 0x01, 0x00, 0x1b, 0x24, 0x04, 0x01, 0x05, 0x59,
|
||||
0x55, 0x59, 0x32, 0x00, 0x00, 0x10, 0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x10,
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x32, 0x24, 0x05, 0x01, 0x00, 0x80, 0x02, 0xe0, 0x01, 0x00, 0x60,
|
||||
0x09, 0x00, 0x00, 0x40, 0x19, 0x01, 0x00, 0x60, 0x09, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15,
|
||||
0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80,
|
||||
0x84, 0x1e, 0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x02, 0x00, 0x40, 0x01, 0xf0, 0x00,
|
||||
0x00, 0x58, 0x02, 0x00, 0x00, 0x50, 0x46, 0x00, 0x00, 0x58, 0x02, 0x00, 0x15, 0x16, 0x05, 0x00,
|
||||
0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f,
|
||||
0x00, 0x80, 0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x03, 0x00, 0xa0, 0x00,
|
||||
0x78, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0x94, 0x11, 0x00, 0x00, 0x96, 0x00, 0x00, 0x15, 0x16,
|
||||
0x05, 0x00, 0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40,
|
||||
0x42, 0x0f, 0x00, 0x80, 0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x04, 0x00,
|
||||
0xb0, 0x00, 0x90, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x00, 0x34, 0x17, 0x00, 0x00, 0xc6, 0x00, 0x00,
|
||||
0x15, 0x16, 0x05, 0x00, 0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a,
|
||||
0x00, 0x40, 0x42, 0x0f, 0x00, 0x80, 0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05,
|
||||
0x05, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0x18, 0x03, 0x00, 0x00, 0xd0, 0x5c, 0x00, 0x00, 0x18,
|
||||
0x03, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a,
|
||||
0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80, 0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x1a,
|
||||
0x24, 0x03, 0x00, 0x05, 0x80, 0x02, 0xe0, 0x01, 0x40, 0x01, 0xf0, 0x00, 0xa0, 0x00, 0x78, 0x00,
|
||||
0xb0, 0x00, 0x90, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0x06, 0x24, 0x0d, 0x01, 0x01, 0x04, 0x09,
|
||||
0x04, 0x01, 0x01, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x60, 0x0a, 0x01, 0x09,
|
||||
0x04, 0x01, 0x02, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x00, 0x0b, 0x01, 0x09,
|
||||
0x04, 0x01, 0x03, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x20, 0x0b, 0x01, 0x09,
|
||||
0x04, 0x01, 0x04, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x00, 0x13, 0x01, 0x09,
|
||||
0x04, 0x01, 0x05, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x20, 0x13, 0x01, 0x09,
|
||||
0x04, 0x01, 0x06, 0x01, 0x0e, 0x02, 0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0xfc, 0x13, 0x01
|
||||
};
|
||||
|
||||
DeviceDescriptor DuelScanner::s_device_descriptor{
|
||||
.bLength = 0x12,
|
||||
.bDescriptorType = 0x01,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0xef,
|
||||
.bDeviceSubClass = 0x02,
|
||||
.bDeviceProtocol = 0x01,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = 0x057e,
|
||||
.idProduct = 0x030d,
|
||||
.bcdDevice = 0x0705,
|
||||
.iManufacturer = 0x30,
|
||||
.iProduct = 0x60,
|
||||
.iSerialNumber = 0x00,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
std::vector<ConfigDescriptor> DuelScanner::s_config_descriptor{
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x02,
|
||||
.wTotalLength = 0x021f,
|
||||
.bNumInterfaces = 0x02,
|
||||
.bConfigurationValue = 0x01,
|
||||
.iConfiguration = 0x30,
|
||||
.bmAttributes = 0x80,
|
||||
.MaxPower = 0xfa,
|
||||
}
|
||||
};
|
||||
std::vector<InterfaceDescriptor> DuelScanner::s_interface_descriptor{
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x00,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x01,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x60,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 0x00,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x01,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x02,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x03,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x04,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x05,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x06,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
}
|
||||
};
|
||||
std::vector<EndpointDescriptor> DuelScanner::s_endpoint_descriptor{
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x82,
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = 0x0010,
|
||||
.bInterval = 0x06,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0a60,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0b00,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0b20,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x1300,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x1320,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x13fc,
|
||||
.bInterval = 0x01,
|
||||
}
|
||||
};
|
||||
|
||||
DuelScanner::DuelScanner(EmulationKernel& ios) : m_ios(ios)
|
||||
{
|
||||
m_id = (u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1));
|
||||
}
|
||||
|
||||
DuelScanner::~DuelScanner() {
|
||||
if (m_active_altsetting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStop");
|
||||
Host_CameraStop();
|
||||
}
|
||||
}
|
||||
|
||||
DeviceDescriptor DuelScanner::GetDeviceDescriptor() const
|
||||
{
|
||||
return s_device_descriptor;
|
||||
}
|
||||
|
||||
std::vector<ConfigDescriptor> DuelScanner::GetConfigurations() const
|
||||
{
|
||||
return s_config_descriptor;
|
||||
}
|
||||
|
||||
std::vector<InterfaceDescriptor> DuelScanner::GetInterfaces(u8 config) const
|
||||
{
|
||||
return s_interface_descriptor;
|
||||
}
|
||||
|
||||
std::vector<EndpointDescriptor> DuelScanner::GetEndpoints(u8 config, u8 interface, u8 alt) const
|
||||
{
|
||||
std::vector<EndpointDescriptor> ret;
|
||||
if (interface == 0)
|
||||
ret.push_back(s_endpoint_descriptor[0]);
|
||||
else if (interface == 1 && alt > 0)
|
||||
ret.push_back(s_endpoint_descriptor[alt]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool DuelScanner::Attach()
|
||||
{
|
||||
//NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DuelScanner::AttachAndChangeInterface(const u8 interface)
|
||||
{
|
||||
if (!Attach())
|
||||
return false;
|
||||
|
||||
if (interface != m_active_interface)
|
||||
return ChangeInterface(interface) == 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int DuelScanner::CancelTransfer(const u8 endpoint)
|
||||
{
|
||||
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
|
||||
m_active_interface, endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int DuelScanner::ChangeInterface(const u8 interface)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Changing interface to {}", m_vid, m_pid, m_active_interface, interface);
|
||||
m_active_interface = interface;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DuelScanner::GetNumberOfAltSettings(u8 interface)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] GetNumberOfAltSettings: interface={:02x}", m_vid, m_pid, interface);
|
||||
return (interface == 1) ? 7 : 1;
|
||||
}
|
||||
|
||||
int DuelScanner::SetAltSetting(u8 alt_setting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] SetAltSetting: alt_setting={:02x}", m_vid, m_pid, alt_setting);
|
||||
m_active_altsetting = alt_setting;
|
||||
if (alt_setting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStart({}x{})", m_active_size.width, m_active_size.height);
|
||||
auto& system = m_ios.GetSystem();
|
||||
system.GetCameraBase().CreateSample(m_active_size.width, m_active_size.height);
|
||||
Host_CameraStart(m_active_size.width, m_active_size.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStop");
|
||||
Host_CameraStop();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int DuelScanner::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||
{
|
||||
switch ((cmd->request_type << 8) | cmd->request)
|
||||
{
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_DEVICE, REQUEST_GET_DESCRIPTOR): // 0x80 0x06
|
||||
{
|
||||
std::vector<u8> control_response(usb_config_desc, usb_config_desc + sizeof(usb_config_desc));
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, SET_CUR): // 0x21 0x01
|
||||
{
|
||||
u8 unit = cmd->index >> 8;
|
||||
u8 control = cmd->value >> 8;
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x} // {} / {}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length,
|
||||
CameraBase::getUVCRequest(cmd->request),
|
||||
(unit == 0) ? CameraBase::getUVCVideoStreamingControl(control)
|
||||
: (unit == 1) ? CameraBase::getUVCTerminalControl(control)
|
||||
: (unit == 3) ? CameraBase::getUVCProcessingUnitControl(control)
|
||||
: "");
|
||||
if (unit == 0 && control == VS_COMMIT)
|
||||
{
|
||||
auto& system = m_ios.GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
UVCProbeCommitControl* commit = (UVCProbeCommitControl*) memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||
m_active_size = m_supported_sizes[commit->bFrameIndex - 1];
|
||||
m_delay = commit->dwFrameInterval / 10;
|
||||
u32 new_size = m_active_size.width * m_active_size.height * 2;
|
||||
if (m_image_size != new_size)
|
||||
{
|
||||
m_image_size = new_size;
|
||||
if (m_image_data)
|
||||
{
|
||||
free(m_image_data);
|
||||
}
|
||||
m_image_data = (u8*) calloc(1, m_image_size);
|
||||
}
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] VS_COMMIT: bFormatIndex={:02x} bFrameIndex={:02x} dwFrameInterval={:04x} / size={}x{} delay={}",
|
||||
m_vid, m_pid, commit->bFormatIndex, commit->bFrameIndex, commit->dwFrameInterval,
|
||||
m_active_size.width, m_active_size.height,
|
||||
m_delay
|
||||
);
|
||||
}
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_CUR): // 0xa1 0x81
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_MIN): // 0xa1 0x82
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_MAX): // 0xa1 0x83
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_RES): // 0xa1 0x84
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_LEN): // 0xa1 0x85
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_INF): // 0xa1 0x86
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_DEF): // 0xa1 0x87
|
||||
{
|
||||
u8 unit = cmd->index >> 8;
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x} // {} / {}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length,
|
||||
CameraBase::getUVCRequest(cmd->request),
|
||||
(unit == 0) ? CameraBase::getUVCVideoStreamingControl(cmd->value >> 8)
|
||||
: (unit == 1) ? CameraBase::getUVCTerminalControl(cmd->value >> 8)
|
||||
: (unit == 3) ? CameraBase::getUVCProcessingUnitControl(cmd->value >> 8)
|
||||
: "");
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length);
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
}
|
||||
}
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int DuelScanner::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={:04x} endpoint={:02x}", m_vid, m_pid,
|
||||
m_active_interface, cmd->length, cmd->endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int DuelScanner::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={:04x} endpoint={:02x}", m_vid,
|
||||
m_pid, m_active_interface, cmd->length, cmd->endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int DuelScanner::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
||||
{
|
||||
auto& system = m_ios.GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
u8* iso_buffer = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||
if (!iso_buffer)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "DuelScanner iso buf error");
|
||||
return IPC_EINVAL;
|
||||
}
|
||||
//ERROR_LOG_FMT(IOS_USB, "cmd->length = 0x{:02x} / 0x{:02x}", cmd->length, cmd->packet_sizes[0]);
|
||||
|
||||
u8* iso_buffer_pos = iso_buffer;
|
||||
|
||||
for (std::size_t i = 0; i < cmd->num_packets; i++)
|
||||
{
|
||||
UVCHeader uvc_header{};
|
||||
uvc_header.bHeaderLength = sizeof(UVCHeader);
|
||||
uvc_header.endOfHeader = 1;
|
||||
uvc_header.frameId = m_frame_id;
|
||||
|
||||
u32 data_size = std::min(cmd->packet_sizes[i] - (u32)sizeof(uvc_header), m_image_size - m_image_pos);
|
||||
if (data_size > 0 && m_image_pos + data_size == m_image_size)
|
||||
{
|
||||
m_frame_id ^= 1;
|
||||
uvc_header.endOfFrame = 1;
|
||||
}
|
||||
std::memcpy(iso_buffer_pos, &uvc_header, sizeof(uvc_header));
|
||||
if (data_size > 0)
|
||||
{
|
||||
std::memcpy(iso_buffer_pos + sizeof(uvc_header), m_image_data + m_image_pos, data_size);
|
||||
}
|
||||
m_image_pos += data_size;
|
||||
iso_buffer_pos += sizeof(uvc_header) + data_size;
|
||||
cmd->SetPacketReturnValue(i, (u32)sizeof(uvc_header) + data_size);
|
||||
}
|
||||
|
||||
if (m_image_pos == m_image_size)
|
||||
{
|
||||
system.GetCameraBase().GetData(m_image_data, m_image_size);
|
||||
m_image_pos = 0;
|
||||
}
|
||||
|
||||
cmd->ScheduleTransferCompletion(IPC_SUCCESS, m_delay);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
void DuelScanner::ScheduleTransfer(std::unique_ptr<TransferCommand> command,
|
||||
const std::vector<u8>& data, u64 expected_time_us)
|
||||
{
|
||||
command->FillBuffer(data.data(), static_cast<const size_t>(data.size()));
|
||||
command->ScheduleTransferCompletion(static_cast<s32>(data.size()), expected_time_us);
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/CameraBase.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
class DuelScanner final : public Device
|
||||
{
|
||||
public:
|
||||
DuelScanner(EmulationKernel& ios);
|
||||
~DuelScanner() override;
|
||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
|
||||
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
|
||||
bool Attach() override;
|
||||
bool AttachAndChangeInterface(u8 interface) override;
|
||||
int CancelTransfer(u8 endpoint) override;
|
||||
int ChangeInterface(u8 interface) override;
|
||||
int GetNumberOfAltSettings(u8 interface) override;
|
||||
int SetAltSetting(u8 alt_setting) override;
|
||||
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
||||
|
||||
private:
|
||||
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::vector<u8>& data,
|
||||
u64 expected_time_us);
|
||||
|
||||
static DeviceDescriptor s_device_descriptor;
|
||||
static std::vector<ConfigDescriptor> s_config_descriptor;
|
||||
static std::vector<InterfaceDescriptor> s_interface_descriptor;
|
||||
static std::vector<EndpointDescriptor> s_endpoint_descriptor;
|
||||
|
||||
EmulationKernel& m_ios;
|
||||
const u16 m_vid = 0x057e;
|
||||
const u16 m_pid = 0x030d;
|
||||
u8 m_active_interface = 0;
|
||||
u8 m_active_altsetting = 0;
|
||||
const struct UVCImageSize m_supported_sizes[5] = {{640, 480}, {320, 240}, {160, 120}, {176, 144}, {352, 288}};
|
||||
struct UVCImageSize m_active_size;
|
||||
u32 m_delay = 0;
|
||||
u32 m_image_size = 0;
|
||||
u32 m_image_pos = 0;
|
||||
u8 *m_image_data = nullptr;
|
||||
bool m_frame_id = 0;
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,487 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "Core/Host.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
const u8 usb_config_desc[] = {
|
||||
0x09, 0x02, 0x09, 0x03, 0x02, 0x01, 0x30, 0x80, 0xfa, 0x08, 0x0b, 0x00, 0x02, 0x0e, 0x03, 0x00,
|
||||
0x60, 0x09, 0x04, 0x00, 0x00, 0x01, 0x0e, 0x01, 0x00, 0x60, 0x0d, 0x24, 0x01, 0x00, 0x01, 0x4d,
|
||||
0x00, 0xc0, 0xe1, 0xe4, 0x00, 0x01, 0x01, 0x09, 0x24, 0x03, 0x02, 0x01, 0x01, 0x00, 0x04, 0x00,
|
||||
0x1a, 0x24, 0x06, 0x04, 0xf0, 0x77, 0x35, 0xd1, 0x89, 0x8d, 0x00, 0x47, 0x81, 0x2e, 0x7d, 0xd5,
|
||||
0xe2, 0xfd, 0xb8, 0x98, 0x08, 0x01, 0x03, 0x01, 0xff, 0x00, 0x12, 0x24, 0x02, 0x01, 0x01, 0x02,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
|
||||
// 0x0A, 0x02, 0x00, // patch bmControls to avoid unnecessary requests
|
||||
0x00, 0x00, 0x00,
|
||||
0x0b, 0x24, 0x05, 0x03,
|
||||
0x01, 0x00, 0x00, 0x02,
|
||||
// 0x7F, 0x15, // patch bmControls to avoid unnecessary requests
|
||||
0x00, 0x00,
|
||||
0x00, 0x07, 0x05, 0x82, 0x03, 0x10, 0x00, 0x06, 0x05, 0x25,
|
||||
0x03, 0x10, 0x00, 0x09, 0x04, 0x01, 0x00, 0x00, 0x0e, 0x02, 0x00, 0x00, 0x0f, 0x24, 0x01, 0x02,
|
||||
0x2d, 0x02, 0x81, 0x00, 0x02, 0x02, 0x01, 0x00, 0x01, 0x00, 0x00, 0x0b, 0x24, 0x06, 0x01, 0x05,
|
||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x26, 0x24, 0x07, 0x01, 0x00, 0x80, 0x02, 0xe0, 0x01, 0x00,
|
||||
0xf4, 0x01, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x00, 0x08, 0x07, 0x00, 0x15, 0x16, 0x05, 0x00, 0x00,
|
||||
0x15, 0x16, 0x05, 0x00, 0x76, 0x96, 0x98, 0x00, 0x15, 0x16, 0x05, 0x00, 0x26, 0x24, 0x07, 0x02,
|
||||
0x00, 0x40, 0x01, 0xf0, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x30, 0x2a, 0x00, 0x00, 0xc2, 0x01,
|
||||
0x00, 0x15, 0x16, 0x05, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x76, 0x96, 0x98, 0x00, 0x15, 0x16,
|
||||
0x05, 0x00, 0x26, 0x24, 0x07, 0x03, 0x00, 0xa0, 0x00, 0x78, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00,
|
||||
0x8c, 0x0a, 0x00, 0x80, 0x70, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00,
|
||||
0x76, 0x96, 0x98, 0x00, 0x15, 0x16, 0x05, 0x00, 0x26, 0x24, 0x07, 0x04, 0x00, 0xb0, 0x00, 0x90,
|
||||
0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0xec, 0x0d, 0x00, 0x80, 0x94, 0x00, 0x00, 0x15, 0x16, 0x05,
|
||||
0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x76, 0x96, 0x98, 0x00, 0x15, 0x16, 0x05, 0x00, 0x26, 0x24,
|
||||
0x07, 0x05, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0xf4, 0x01, 0x00, 0x00, 0xb0, 0x37, 0x00, 0x00,
|
||||
0x52, 0x02, 0x00, 0x15, 0x16, 0x05, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x76, 0x96, 0x98, 0x00,
|
||||
0x15, 0x16, 0x05, 0x00, 0x1a, 0x24, 0x03, 0x00, 0x05, 0x80, 0x02, 0xe0, 0x01, 0x40, 0x01, 0xf0,
|
||||
0x00, 0xa0, 0x00, 0x78, 0x00, 0xb0, 0x00, 0x90, 0x00, 0x60, 0x01, 0x20, 0x01, 0x00, 0x06, 0x24,
|
||||
0x0d, 0x01, 0x01, 0x04, 0x1b, 0x24, 0x04, 0x02, 0x05, 0x59, 0x55, 0x59, 0x32, 0x00, 0x00, 0x10,
|
||||
0x00, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71, 0x10, 0x01, 0x00, 0x00, 0x00, 0x00, 0x32,
|
||||
0x24, 0x05, 0x01, 0x00, 0x80, 0x02, 0xe0, 0x01, 0x00, 0x60, 0x09, 0x00, 0x00, 0x40, 0x19, 0x01,
|
||||
0x00, 0x60, 0x09, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07,
|
||||
0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80, 0x84, 0x1e, 0x00, 0x80, 0x96, 0x98,
|
||||
0x00, 0x32, 0x24, 0x05, 0x02, 0x00, 0x40, 0x01, 0xf0, 0x00, 0x00, 0x58, 0x02, 0x00, 0x00, 0x50,
|
||||
0x46, 0x00, 0x00, 0x58, 0x02, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15, 0x16, 0x05, 0x00, 0x20,
|
||||
0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80, 0x84, 0x0f, 0x00, 0x80,
|
||||
0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x03, 0x00, 0xa0, 0x00, 0x78, 0x00, 0x00, 0x96, 0x00, 0x00,
|
||||
0x00, 0x94, 0x11, 0x00, 0x00, 0x96, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15, 0x16, 0x05,
|
||||
0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80, 0x84, 0x0f,
|
||||
0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x04, 0x00, 0xb0, 0x00, 0x90, 0x00, 0x00, 0xc6,
|
||||
0x00, 0x00, 0x00, 0x34, 0x17, 0x00, 0x00, 0xc6, 0x00, 0x00, 0x15, 0x16, 0x05, 0x00, 0x06, 0x15,
|
||||
0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f, 0x00, 0x80,
|
||||
0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x32, 0x24, 0x05, 0x05, 0x00, 0x60, 0x01, 0x20, 0x01,
|
||||
0x00, 0x18, 0x03, 0x00, 0x00, 0xd0, 0x5c, 0x00, 0x00, 0x18, 0x03, 0x00, 0x15, 0x16, 0x05, 0x00,
|
||||
0x06, 0x15, 0x16, 0x05, 0x00, 0x20, 0xa1, 0x07, 0x00, 0x2a, 0x2c, 0x0a, 0x00, 0x40, 0x42, 0x0f,
|
||||
0x00, 0x80, 0x84, 0x0f, 0x00, 0x80, 0x96, 0x98, 0x00, 0x1a, 0x24, 0x03, 0x00, 0x05, 0x80, 0x02,
|
||||
0xe0, 0x01, 0x40, 0x01, 0xf0, 0x00, 0xa0, 0x00, 0x78, 0x00, 0xb0, 0x00, 0x90, 0x00, 0x60, 0x01,
|
||||
0x20, 0x01, 0x00, 0x06, 0x24, 0x0d, 0x01, 0x01, 0x04, 0x09, 0x04, 0x01, 0x01, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x60, 0x0a, 0x01, 0x09, 0x04, 0x01, 0x02, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x00, 0x0b, 0x01, 0x09, 0x04, 0x01, 0x03, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x20, 0x0b, 0x01, 0x09, 0x04, 0x01, 0x04, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x00, 0x13, 0x01, 0x09, 0x04, 0x01, 0x05, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0x20, 0x13, 0x01, 0x09, 0x04, 0x01, 0x06, 0x01, 0x0e, 0x02,
|
||||
0x00, 0x00, 0x07, 0x05, 0x81, 0x05, 0xfc, 0x13, 0x01
|
||||
};
|
||||
|
||||
DeviceDescriptor MotionCamera::s_device_descriptor{
|
||||
.bLength = 0x12,
|
||||
.bDescriptorType = 0x01,
|
||||
.bcdUSB = 0x0200,
|
||||
.bDeviceClass = 0xef,
|
||||
.bDeviceSubClass = 0x02,
|
||||
.bDeviceProtocol = 0x01,
|
||||
.bMaxPacketSize0 = 0x40,
|
||||
.idVendor = 0x057e,
|
||||
.idProduct = 0x030a,
|
||||
.bcdDevice = 0x0924,
|
||||
.iManufacturer = 0x30,
|
||||
.iProduct = 0x60,
|
||||
.iSerialNumber = 0x00,
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
std::vector<ConfigDescriptor> MotionCamera::s_config_descriptor{
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x02,
|
||||
.wTotalLength = 0x0309,
|
||||
.bNumInterfaces = 0x02,
|
||||
.bConfigurationValue = 0x01,
|
||||
.iConfiguration = 0x30,
|
||||
.bmAttributes = 0x80,
|
||||
.MaxPower = 0xfa,
|
||||
}
|
||||
};
|
||||
std::vector<InterfaceDescriptor> MotionCamera::s_interface_descriptor{
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x00,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x01,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x60,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x00,
|
||||
.bNumEndpoints = 0x00,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x01,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x02,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x03,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x04,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x05,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
},
|
||||
{
|
||||
.bLength = 0x09,
|
||||
.bDescriptorType = 0x04,
|
||||
.bInterfaceNumber = 0x01,
|
||||
.bAlternateSetting = 0x06,
|
||||
.bNumEndpoints = 0x01,
|
||||
.bInterfaceClass = 0x0e,
|
||||
.bInterfaceSubClass = 0x02,
|
||||
.bInterfaceProtocol = 0x00,
|
||||
.iInterface = 0x00,
|
||||
}
|
||||
};
|
||||
std::vector<EndpointDescriptor> MotionCamera::s_endpoint_descriptor{
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x82,
|
||||
.bmAttributes = 0x03,
|
||||
.wMaxPacketSize = 0x0010,
|
||||
.bInterval = 0x06,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0a60,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0b00,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x0b20,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x1300,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x1320,
|
||||
.bInterval = 0x01,
|
||||
},
|
||||
{
|
||||
.bLength = 0x07,
|
||||
.bDescriptorType = 0x05,
|
||||
.bEndpointAddress = 0x81,
|
||||
.bmAttributes = 0x05,
|
||||
.wMaxPacketSize = 0x13fc,
|
||||
.bInterval = 0x01,
|
||||
}
|
||||
};
|
||||
|
||||
MotionCamera::MotionCamera(EmulationKernel& ios) : m_ios(ios)
|
||||
{
|
||||
m_id = (u64(m_vid) << 32 | u64(m_pid) << 16 | u64(9) << 8 | u64(1));
|
||||
}
|
||||
|
||||
MotionCamera::~MotionCamera() {
|
||||
if (m_active_altsetting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStop");
|
||||
Host_CameraStop();
|
||||
}
|
||||
}
|
||||
|
||||
DeviceDescriptor MotionCamera::GetDeviceDescriptor() const
|
||||
{
|
||||
return s_device_descriptor;
|
||||
}
|
||||
|
||||
std::vector<ConfigDescriptor> MotionCamera::GetConfigurations() const
|
||||
{
|
||||
return s_config_descriptor;
|
||||
}
|
||||
|
||||
std::vector<InterfaceDescriptor> MotionCamera::GetInterfaces(u8 config) const
|
||||
{
|
||||
return s_interface_descriptor;
|
||||
}
|
||||
|
||||
std::vector<EndpointDescriptor> MotionCamera::GetEndpoints(u8 config, u8 interface, u8 alt) const
|
||||
{
|
||||
std::vector<EndpointDescriptor> ret;
|
||||
if (interface == 0)
|
||||
ret.push_back(s_endpoint_descriptor[0]);
|
||||
else if (interface == 1 && alt > 0)
|
||||
ret.push_back(s_endpoint_descriptor[alt]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool MotionCamera::Attach()
|
||||
{
|
||||
//NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] Opening device", m_vid, m_pid);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MotionCamera::AttachAndChangeInterface(const u8 interface)
|
||||
{
|
||||
if (!Attach())
|
||||
return false;
|
||||
|
||||
if (interface != m_active_interface)
|
||||
return ChangeInterface(interface) == 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int MotionCamera::CancelTransfer(const u8 endpoint)
|
||||
{
|
||||
INFO_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Cancelling transfers (endpoint {:#x})", m_vid, m_pid,
|
||||
m_active_interface, endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int MotionCamera::ChangeInterface(const u8 interface)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Changing interface to {}", m_vid, m_pid, m_active_interface, interface);
|
||||
m_active_interface = interface;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MotionCamera::GetNumberOfAltSettings(u8 interface)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] GetNumberOfAltSettings: interface={:02x}", m_vid, m_pid, interface);
|
||||
return (interface == 1) ? 7 : 1;
|
||||
}
|
||||
|
||||
int MotionCamera::SetAltSetting(u8 alt_setting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] SetAltSetting: alt_setting={:02x}", m_vid, m_pid, alt_setting);
|
||||
m_active_altsetting = alt_setting;
|
||||
if (alt_setting)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStart({}x{})", m_active_size.width, m_active_size.height);
|
||||
auto& system = m_ios.GetSystem();
|
||||
system.GetCameraBase().CreateSample(m_active_size.width, m_active_size.height);
|
||||
Host_CameraStart(m_active_size.width, m_active_size.height);
|
||||
}
|
||||
else
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "Host_CameraStop");
|
||||
Host_CameraStop();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MotionCamera::SubmitTransfer(std::unique_ptr<CtrlMessage> cmd)
|
||||
{
|
||||
switch ((cmd->request_type << 8) | cmd->request)
|
||||
{
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_STANDARD, REC_DEVICE, REQUEST_GET_DESCRIPTOR): // 0x80 0x06
|
||||
{
|
||||
std::vector<u8> control_response(usb_config_desc, usb_config_desc + sizeof(usb_config_desc));
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
case USBHDR(DIR_HOST2DEVICE, TYPE_CLASS, REC_INTERFACE, SET_CUR): // 0x21 0x01
|
||||
{
|
||||
u8 unit = cmd->index >> 8;
|
||||
u8 control = cmd->value >> 8;
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x} // {} / {}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length,
|
||||
CameraBase::getUVCRequest(cmd->request),
|
||||
(unit == 0) ? CameraBase::getUVCVideoStreamingControl(control)
|
||||
: (unit == 1) ? CameraBase::getUVCTerminalControl(control)
|
||||
: (unit == 3) ? CameraBase::getUVCProcessingUnitControl(control)
|
||||
: "");
|
||||
if (unit == 0 && control == VS_COMMIT)
|
||||
{
|
||||
auto& system = m_ios.GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
UVCProbeCommitControl* commit = (UVCProbeCommitControl*) memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||
m_active_size = m_supported_sizes[commit->bFrameIndex - 1];
|
||||
m_delay = commit->dwFrameInterval / 10;
|
||||
u32 new_size = m_active_size.width * m_active_size.height * 2;
|
||||
if (m_image_size != new_size)
|
||||
{
|
||||
m_image_size = new_size;
|
||||
if (m_image_data)
|
||||
{
|
||||
free(m_image_data);
|
||||
}
|
||||
m_image_data = (u8*) calloc(1, m_image_size);
|
||||
}
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x}] VS_COMMIT: bFormatIndex={:02x} bFrameIndex={:02x} dwFrameInterval={:04x} / size={}x{} delay={}",
|
||||
m_vid, m_pid, commit->bFormatIndex, commit->bFrameIndex, commit->dwFrameInterval,
|
||||
m_active_size.width, m_active_size.height,
|
||||
m_delay
|
||||
);
|
||||
}
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_CUR): // 0xa1 0x81
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_MIN): // 0xa1 0x82
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_MAX): // 0xa1 0x83
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_RES): // 0xa1 0x84
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_LEN): // 0xa1 0x85
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_INF): // 0xa1 0x86
|
||||
case USBHDR(DIR_DEVICE2HOST, TYPE_CLASS, REC_INTERFACE, GET_DEF): // 0xa1 0x87
|
||||
{
|
||||
u8 unit = cmd->index >> 8;
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x} // {} / {}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length,
|
||||
CameraBase::getUVCRequest(cmd->request),
|
||||
(unit == 0) ? CameraBase::getUVCVideoStreamingControl(cmd->value >> 8)
|
||||
: (unit == 1) ? CameraBase::getUVCTerminalControl(cmd->value >> 8)
|
||||
: (unit == 3) ? CameraBase::getUVCProcessingUnitControl(cmd->value >> 8)
|
||||
: "");
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Control: bRequestType={:02x} bRequest={:02x} wValue={:04x} wIndex={:04x} wLength={:04x}",
|
||||
m_vid, m_pid, m_active_interface, cmd->request_type, cmd->request, cmd->value, cmd->index, cmd->length);
|
||||
std::vector<u8> control_response = {};
|
||||
ScheduleTransfer(std::move(cmd), control_response, 0);
|
||||
}
|
||||
}
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int MotionCamera::SubmitTransfer(std::unique_ptr<BulkMessage> cmd)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Bulk: length={:04x} endpoint={:02x}", m_vid, m_pid,
|
||||
m_active_interface, cmd->length, cmd->endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int MotionCamera::SubmitTransfer(std::unique_ptr<IntrMessage> cmd)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "[{:04x}:{:04x} {}] Interrupt: length={:04x} endpoint={:02x}", m_vid,
|
||||
m_pid, m_active_interface, cmd->length, cmd->endpoint);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
int MotionCamera::SubmitTransfer(std::unique_ptr<IsoMessage> cmd)
|
||||
{
|
||||
auto& system = m_ios.GetSystem();
|
||||
auto& memory = system.GetMemory();
|
||||
u8* iso_buffer = memory.GetPointerForRange(cmd->data_address, cmd->length);
|
||||
if (!iso_buffer)
|
||||
{
|
||||
ERROR_LOG_FMT(IOS_USB, "MotionCamera iso buf error");
|
||||
return IPC_EINVAL;
|
||||
}
|
||||
|
||||
u8* iso_buffer_pos = iso_buffer;
|
||||
|
||||
for (std::size_t i = 0; i < cmd->num_packets; i++)
|
||||
{
|
||||
UVCHeader uvc_header{};
|
||||
uvc_header.bHeaderLength = sizeof(UVCHeader);
|
||||
uvc_header.endOfHeader = 1;
|
||||
uvc_header.frameId = m_frame_id;
|
||||
|
||||
u32 data_size = std::min(cmd->packet_sizes[i] - (u32)sizeof(uvc_header), m_image_size - m_image_pos);
|
||||
if (data_size > 0 && m_image_pos + data_size == m_image_size)
|
||||
{
|
||||
m_frame_id ^= 1;
|
||||
uvc_header.endOfFrame = 1;
|
||||
}
|
||||
std::memcpy(iso_buffer_pos, &uvc_header, sizeof(uvc_header));
|
||||
if (data_size > 0)
|
||||
{
|
||||
std::memcpy(iso_buffer_pos + sizeof(uvc_header), m_image_data + m_image_pos, data_size);
|
||||
}
|
||||
m_image_pos += data_size;
|
||||
iso_buffer_pos += sizeof(uvc_header) + data_size;
|
||||
cmd->SetPacketReturnValue(i, (u32)sizeof(uvc_header) + data_size);
|
||||
}
|
||||
|
||||
if (m_image_pos == m_image_size)
|
||||
{
|
||||
system.GetCameraBase().GetData(m_image_data, m_image_size);
|
||||
m_image_pos = 0;
|
||||
}
|
||||
|
||||
// 15 fps, one frame every 66ms, half a frame per transfer, one transfer every 33ms
|
||||
cmd->ScheduleTransferCompletion(IPC_SUCCESS, m_delay);
|
||||
return IPC_SUCCESS;
|
||||
}
|
||||
|
||||
void MotionCamera::ScheduleTransfer(std::unique_ptr<TransferCommand> command,
|
||||
const std::vector<u8>& data, u64 expected_time_us)
|
||||
{
|
||||
command->FillBuffer(data.data(), static_cast<const size_t>(data.size()));
|
||||
command->ScheduleTransferCompletion(static_cast<s32>(data.size()), expected_time_us);
|
||||
}
|
||||
} // namespace IOS::HLE::USB
|
|
@ -0,0 +1,55 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/CameraBase.h"
|
||||
|
||||
namespace IOS::HLE::USB
|
||||
{
|
||||
|
||||
class MotionCamera final : public Device
|
||||
{
|
||||
public:
|
||||
MotionCamera(EmulationKernel& ios);
|
||||
~MotionCamera() override;
|
||||
DeviceDescriptor GetDeviceDescriptor() const override;
|
||||
std::vector<ConfigDescriptor> GetConfigurations() const override;
|
||||
std::vector<InterfaceDescriptor> GetInterfaces(u8 config) const override;
|
||||
std::vector<EndpointDescriptor> GetEndpoints(u8 config, u8 interface, u8 alt) const override;
|
||||
bool Attach() override;
|
||||
bool AttachAndChangeInterface(u8 interface) override;
|
||||
int CancelTransfer(u8 endpoint) override;
|
||||
int ChangeInterface(u8 interface) override;
|
||||
int GetNumberOfAltSettings(u8 interface) override;
|
||||
int SetAltSetting(u8 alt_setting) override;
|
||||
int SubmitTransfer(std::unique_ptr<CtrlMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<BulkMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IntrMessage> message) override;
|
||||
int SubmitTransfer(std::unique_ptr<IsoMessage> message) override;
|
||||
|
||||
private:
|
||||
void ScheduleTransfer(std::unique_ptr<TransferCommand> command, const std::vector<u8>& data,
|
||||
u64 expected_time_us);
|
||||
|
||||
static DeviceDescriptor s_device_descriptor;
|
||||
static std::vector<ConfigDescriptor> s_config_descriptor;
|
||||
static std::vector<InterfaceDescriptor> s_interface_descriptor;
|
||||
static std::vector<EndpointDescriptor> s_endpoint_descriptor;
|
||||
|
||||
EmulationKernel& m_ios;
|
||||
const u16 m_vid = 0x057e;
|
||||
const u16 m_pid = 0x030a;
|
||||
u8 m_active_interface = 0;
|
||||
u8 m_active_altsetting = 0;
|
||||
const struct UVCImageSize m_supported_sizes[5] = {{640, 480}, {320, 240}, {160, 120}, {176, 144}, {352, 288}};
|
||||
struct UVCImageSize m_active_size;
|
||||
u32 m_delay = 0;
|
||||
u32 m_image_size = 0;
|
||||
u32 m_image_pos = 0;
|
||||
u8 *m_image_data = nullptr;
|
||||
bool m_frame_id = 0;
|
||||
};
|
||||
} // namespace IOS::HLE::USB
|
|
@ -22,7 +22,9 @@
|
|||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Common.h"
|
||||
#include "Core/IOS/USB/Emulated/DuelScanner.h"
|
||||
#include "Core/IOS/USB/Emulated/Infinity.h"
|
||||
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "Core/IOS/USB/LibusbDevice.h"
|
||||
#include "Core/NetPlayProto.h"
|
||||
|
@ -185,6 +187,16 @@ void USBHost::DispatchHooks(const DeviceChangeHooks& hooks)
|
|||
void USBHost::AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||
bool always_add_hooks)
|
||||
{
|
||||
if (Config::Get(Config::MAIN_EMULATED_CAMERA) == 1 && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto duel_scanner = std::make_unique<USB::DuelScanner>(GetEmulationKernel());
|
||||
CheckAndAddDevice(std::move(duel_scanner), new_devices, hooks, always_add_hooks);
|
||||
}
|
||||
if (Config::Get(Config::MAIN_EMULATED_CAMERA) == 2 && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto motion_camera = std::make_unique<USB::MotionCamera>(GetEmulationKernel());
|
||||
CheckAndAddDevice(std::move(motion_camera), new_devices, hooks, always_add_hooks);
|
||||
}
|
||||
if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning())
|
||||
{
|
||||
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(GetEmulationKernel());
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/PowerPC/PowerPC.h"
|
||||
#include "IOS/USB/Emulated/Infinity.h"
|
||||
#include "IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/CommandProcessor.h"
|
||||
|
@ -78,6 +79,7 @@ struct System::Impl
|
|||
HSP::HSPManager m_hsp;
|
||||
IOS::HLE::USB::InfinityBase m_infinity_base;
|
||||
IOS::HLE::USB::SkylanderPortal m_skylander_portal;
|
||||
IOS::HLE::USB::CameraBase m_camera_data;
|
||||
IOS::WiiIPC m_wii_ipc;
|
||||
Memory::MemoryManager m_memory;
|
||||
MemoryInterface::MemoryInterfaceManager m_memory_interface;
|
||||
|
@ -243,6 +245,11 @@ IOS::HLE::USB::InfinityBase& System::GetInfinityBase() const
|
|||
return m_impl->m_infinity_base;
|
||||
}
|
||||
|
||||
IOS::HLE::USB::CameraBase& System::GetCameraBase() const
|
||||
{
|
||||
return m_impl->m_camera_data;
|
||||
}
|
||||
|
||||
IOS::WiiIPC& System::GetWiiIPC() const
|
||||
{
|
||||
return m_impl->m_wii_ipc;
|
||||
|
|
|
@ -69,6 +69,7 @@ namespace IOS::HLE::USB
|
|||
{
|
||||
class SkylanderPortal;
|
||||
class InfinityBase;
|
||||
class CameraBase;
|
||||
} // namespace IOS::HLE::USB
|
||||
namespace Memory
|
||||
{
|
||||
|
@ -176,6 +177,7 @@ public:
|
|||
JitInterface& GetJitInterface() const;
|
||||
IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
|
||||
IOS::HLE::USB::InfinityBase& GetInfinityBase() const;
|
||||
IOS::HLE::USB::CameraBase& GetCameraBase() const;
|
||||
IOS::WiiIPC& GetWiiIPC() const;
|
||||
Memory::MemoryManager& GetMemory() const;
|
||||
MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
|
||||
|
|
|
@ -400,7 +400,10 @@
|
|||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Common.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\CameraBase.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\DuelScanner.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Infinity.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\MotionCamera.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\Skylander.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.h" />
|
||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.h" />
|
||||
|
@ -1063,7 +1066,10 @@
|
|||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteDevice.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Common.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\CameraBase.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\DuelScanner.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Infinity.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\MotionCamera.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\Skylander.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.cpp" />
|
||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.cpp" />
|
||||
|
|
|
@ -139,6 +139,14 @@ void Host_TitleChanged()
|
|||
#endif
|
||||
}
|
||||
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
}
|
||||
|
||||
void Host_CameraStop()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
|
|
|
@ -14,7 +14,7 @@ endif()
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Svg)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Svg Multimedia)
|
||||
message(STATUS "Found Qt version ${Qt6_VERSION}")
|
||||
|
||||
set_property(TARGET Qt6::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
||||
|
@ -40,6 +40,8 @@ add_executable(dolphin-emu
|
|||
Achievements/AchievementSettingsWidget.h
|
||||
Achievements/AchievementsWindow.cpp
|
||||
Achievements/AchievementsWindow.h
|
||||
CameraQt/CameraQt.cpp
|
||||
CameraQt/CameraQt.h
|
||||
Config/ARCodeWidget.cpp
|
||||
Config/ARCodeWidget.h
|
||||
Config/CheatCodeEditor.cpp
|
||||
|
@ -427,6 +429,7 @@ target_link_libraries(dolphin-emu
|
|||
PRIVATE
|
||||
core
|
||||
Qt6::Widgets
|
||||
Qt6::Multimedia
|
||||
uicommon
|
||||
imgui
|
||||
implot
|
||||
|
|
|
@ -0,0 +1,191 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QCameraDevice>
|
||||
#include <QBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMediaDevices>
|
||||
#include <QMediaCaptureSession>
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||
#include "Core/System.h"
|
||||
#include "DolphinQt/CameraQt/CameraQt.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
|
||||
CameraWindow::CameraWindow(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("Duel Scanner / Motion Tracking Camera"));
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
auto* main_layout = new QVBoxLayout();
|
||||
auto* layout1 = new QHBoxLayout();
|
||||
auto* layout2 = new QHBoxLayout();
|
||||
|
||||
auto* label1 = new QLabel(tr("Emulated Camera:"));
|
||||
label1->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
layout1->addWidget(label1);
|
||||
|
||||
m_emudevCombobox = new QComboBox();
|
||||
QString emuDevs[] = {tr("Disabled"), tr("Duel Scanner"), tr("Motion Tracking Camera")};
|
||||
for (auto& dev : emuDevs)
|
||||
{
|
||||
m_emudevCombobox->addItem(dev);
|
||||
}
|
||||
m_emudevCombobox->setCurrentIndex(Config::Get(Config::MAIN_EMULATED_CAMERA));
|
||||
connect(m_emudevCombobox, &QComboBox::currentIndexChanged, this, &CameraWindow::EmulatedDeviceSelected);
|
||||
layout1->addWidget(m_emudevCombobox);
|
||||
|
||||
auto* label2 = new QLabel(tr("Selected Camera:"));
|
||||
label2->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
layout2->addWidget(label2);
|
||||
|
||||
m_hostdevCombobox = new QComboBox();
|
||||
m_hostdevCombobox->setMinimumWidth(200);
|
||||
RefreshDeviceList();
|
||||
connect(m_hostdevCombobox, &QComboBox::currentIndexChanged, this, &CameraWindow::HostDeviceSelected);
|
||||
m_hostdevCombobox->setDisabled(0 == Config::Get(Config::MAIN_EMULATED_CAMERA));
|
||||
layout2->addWidget(m_hostdevCombobox);
|
||||
|
||||
m_refreshButton = new QPushButton(tr("Refresh"));
|
||||
m_refreshButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
connect(m_refreshButton, &QPushButton::pressed, this, &CameraWindow::RefreshDeviceList);
|
||||
m_refreshButton->setDisabled(0 == Config::Get(Config::MAIN_EMULATED_CAMERA));
|
||||
layout2->addWidget(m_refreshButton);
|
||||
|
||||
main_layout->addLayout(layout1);
|
||||
main_layout->addLayout(layout2);
|
||||
setLayout(main_layout);
|
||||
}
|
||||
|
||||
CameraWindow::~CameraWindow() = default;
|
||||
|
||||
void CameraWindow::RefreshDeviceList()
|
||||
{
|
||||
disconnect(m_hostdevCombobox, &QComboBox::currentIndexChanged, this, &CameraWindow::HostDeviceSelected);
|
||||
m_hostdevCombobox->clear();
|
||||
m_hostdevCombobox->addItem(tr("Fake"));
|
||||
auto selectedDevice = Config::Get(Config::MAIN_SELECTED_CAMERA);
|
||||
const QList<QCameraDevice> availableCameras = QMediaDevices::videoInputs();
|
||||
for (const QCameraDevice& camera : availableCameras)
|
||||
{
|
||||
m_hostdevCombobox->addItem(camera.description());
|
||||
if (camera.description().toStdString() == selectedDevice)
|
||||
{
|
||||
m_hostdevCombobox->setCurrentIndex(m_hostdevCombobox->count() - 1);
|
||||
}
|
||||
}
|
||||
connect(m_hostdevCombobox, &QComboBox::currentIndexChanged, this, &CameraWindow::HostDeviceSelected);
|
||||
}
|
||||
|
||||
void CameraWindow::EmulatedDeviceSelected(int index)
|
||||
{
|
||||
m_hostdevCombobox->setDisabled(0 == index);
|
||||
m_refreshButton->setDisabled(0 == index);
|
||||
Config::SetBaseOrCurrent(Config::MAIN_EMULATED_CAMERA, index);
|
||||
}
|
||||
|
||||
void CameraWindow::HostDeviceSelected(int index)
|
||||
{
|
||||
std::string camera = m_hostdevCombobox->currentText().toStdString();
|
||||
Config::SetBaseOrCurrent(Config::MAIN_SELECTED_CAMERA, camera);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
|
||||
CameraManager::CameraManager()
|
||||
{
|
||||
m_captureSession = new QMediaCaptureSession();
|
||||
m_videoSink = new QVideoSink;
|
||||
connect(Host::GetInstance(), &Host::CameraStart, this, &CameraManager::Start);
|
||||
connect(Host::GetInstance(), &Host::CameraStop, this, &CameraManager::Stop);
|
||||
}
|
||||
|
||||
CameraManager::~CameraManager()
|
||||
{
|
||||
disconnect(Host::GetInstance(), &Host::CameraStart, this, &CameraManager::Start);
|
||||
disconnect(Host::GetInstance(), &Host::CameraStop, this, &CameraManager::Stop);
|
||||
}
|
||||
|
||||
void CameraManager::Start(u16 width, u16 heigth)
|
||||
{
|
||||
auto selectedCamera = Config::Get(Config::MAIN_SELECTED_CAMERA);
|
||||
const auto videoInputs = QMediaDevices::videoInputs();
|
||||
for (const QCameraDevice &camera : videoInputs)
|
||||
{
|
||||
if (camera.description().toStdString() == selectedCamera)
|
||||
{
|
||||
m_camera = new QCamera(camera);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!m_camera)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_captureSession->setCamera(m_camera);
|
||||
m_captureSession->setVideoSink(m_videoSink);
|
||||
|
||||
const auto videoFormats = m_camera->cameraDevice().videoFormats();
|
||||
for (const auto &format : videoFormats) {
|
||||
if (format.pixelFormat() == QVideoFrameFormat::Format_NV12
|
||||
&& format.resolution().width() == width
|
||||
&& format.resolution().height() == heigth) {
|
||||
m_camera->setCameraFormat(format);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_videoSink, &QVideoSink::videoFrameChanged, this, &CameraManager::VideoFrameChanged);
|
||||
m_camera_active = true;
|
||||
m_camera->start();
|
||||
}
|
||||
|
||||
void CameraManager::Stop()
|
||||
{
|
||||
if (m_camera_active)
|
||||
{
|
||||
disconnect(m_videoSink, &QVideoSink::videoFrameChanged, this, &CameraManager::VideoFrameChanged);
|
||||
m_camera->stop();
|
||||
}
|
||||
m_camera_active = false;
|
||||
}
|
||||
|
||||
void CameraManager::VideoFrameChanged(const QVideoFrame& frame)
|
||||
{
|
||||
QVideoFrame rwFrame(frame);
|
||||
if (rwFrame.pixelFormat() != QVideoFrameFormat::PixelFormat::Format_NV12)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "VideoFrameChanged : Unhandled format:{}", (u16)rwFrame.pixelFormat());
|
||||
return;
|
||||
}
|
||||
rwFrame.map(QVideoFrame::MapMode::ReadOnly);
|
||||
|
||||
// Convert NV12 to YUY2
|
||||
u32 yuy2Size = 2 * frame.width() * frame.height();
|
||||
u8 *yuy2Image = (u8*) calloc(1, yuy2Size);
|
||||
if (!yuy2Image)
|
||||
{
|
||||
NOTICE_LOG_FMT(IOS_USB, "alloc faied");
|
||||
rwFrame.unmap();
|
||||
return;
|
||||
}
|
||||
for (int line = 0; line < frame.height(); line++) {
|
||||
for (int col = 0; col < frame.width(); col++) {
|
||||
u8 *yuyvPos = yuy2Image + 2 * (frame.width() * line + col);
|
||||
const u8 *yPos = rwFrame.bits(0) + (frame.width() * line + col);
|
||||
const u8 *uvPos = rwFrame.bits(1) + (frame.width() * (line & ~1U) / 2 + (col & ~1U));
|
||||
yuyvPos[0] = yPos[0];
|
||||
yuyvPos[1] = (col % 2 == 0) ? uvPos[0] : uvPos[1];
|
||||
}
|
||||
}
|
||||
|
||||
Core::System::GetInstance().GetCameraBase().SetData(yuy2Image, yuy2Size);
|
||||
free(yuy2Image);
|
||||
rwFrame.unmap();
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright 2025 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <QCamera>
|
||||
#include <QVideoFrame>
|
||||
#include <QVideoSink>
|
||||
#include <QWidget>
|
||||
#include <QPushButton>
|
||||
|
||||
class CameraWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CameraWindow(QWidget* parent = nullptr);
|
||||
~CameraWindow() override;
|
||||
|
||||
private:
|
||||
void RefreshDeviceList();
|
||||
void EmulatedDeviceSelected(int index);
|
||||
void HostDeviceSelected(int index);
|
||||
QComboBox *m_emudevCombobox = nullptr;
|
||||
QComboBox *m_hostdevCombobox = nullptr;
|
||||
QPushButton *m_refreshButton = nullptr;
|
||||
};
|
||||
|
||||
class CameraManager : public QObject
|
||||
{
|
||||
public:
|
||||
CameraManager();
|
||||
~CameraManager();
|
||||
void Start(u16 width, u16 heigth);
|
||||
void Stop();
|
||||
void VideoFrameChanged(const QVideoFrame& frame);
|
||||
|
||||
private:
|
||||
QCamera *m_camera = nullptr;
|
||||
QMediaCaptureSession *m_captureSession = nullptr;
|
||||
QVideoSink *m_videoSink = nullptr;
|
||||
bool m_camera_active = false;
|
||||
};
|
|
@ -54,6 +54,7 @@
|
|||
<ClCompile Include="Achievements\AchievementProgressWidget.cpp" />
|
||||
<ClCompile Include="Achievements\AchievementSettingsWidget.cpp" />
|
||||
<ClCompile Include="Achievements\AchievementsWindow.cpp" />
|
||||
<ClCompile Include="CameraQt\CameraQt.cpp" />
|
||||
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
||||
<ClCompile Include="Config\CheatCodeEditor.cpp" />
|
||||
<ClCompile Include="Config\CheatWarningWidget.cpp" />
|
||||
|
@ -240,6 +241,7 @@
|
|||
also be modified using the VS UI.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<ClInclude Include="CameraQt\CameraQt.h" />
|
||||
<ClInclude Include="Config\CheatCodeEditor.h" />
|
||||
<ClInclude Include="Config\ConfigControls\ConfigControl.h" />
|
||||
<ClInclude Include="Config\GameConfigEdit.h" />
|
||||
|
@ -278,6 +280,7 @@
|
|||
<QtMoc Include="Achievements\AchievementProgressWidget.h" />
|
||||
<QtMoc Include="Achievements\AchievementSettingsWidget.h" />
|
||||
<QtMoc Include="Achievements\AchievementsWindow.h" />
|
||||
<QtMoc Include="CameraQt\CameraQt.h" />
|
||||
<QtMoc Include="Config\ARCodeWidget.h" />
|
||||
<QtMoc Include="Config\CheatWarningWidget.h" />
|
||||
<QtMoc Include="Config\CommonControllersWidget.h" />
|
||||
|
|
|
@ -307,6 +307,16 @@ void Host_TitleChanged()
|
|||
#endif
|
||||
}
|
||||
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
QueueOnObject(QApplication::instance(), [=] { emit Host::GetInstance()->CameraStart(width, height); });
|
||||
}
|
||||
|
||||
void Host_CameraStop()
|
||||
{
|
||||
QueueOnObject(QApplication::instance(), [] { emit Host::GetInstance()->CameraStop(); });
|
||||
}
|
||||
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
#ifdef USE_DISCORD_PRESENCE
|
||||
|
|
|
@ -44,6 +44,8 @@ signals:
|
|||
void JitProfileDataWiped();
|
||||
void PPCSymbolsChanged();
|
||||
void PPCBreakpointsChanged();
|
||||
void CameraStart(u16 width, u16 height);
|
||||
void CameraStop();
|
||||
|
||||
private:
|
||||
Host();
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "Core/DolphinAnalytics.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "DolphinQt/CameraQt/CameraQt.h"
|
||||
#include "DolphinQt/Host.h"
|
||||
#include "DolphinQt/MainWindow.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
|
@ -190,6 +191,8 @@ int main(int argc, char* argv[])
|
|||
QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
|
||||
&app, [] { Core::HostDispatchJobs(Core::System::GetInstance()); });
|
||||
|
||||
CameraManager camera;
|
||||
|
||||
std::optional<std::string> save_state_path;
|
||||
if (options.is_set("save_state"))
|
||||
{
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#include "DolphinQt/AboutDialog.h"
|
||||
#include "DolphinQt/Achievements/AchievementsWindow.h"
|
||||
#include "DolphinQt/CheatsManager.h"
|
||||
#include "DolphinQt/CameraQt/CameraQt.h"
|
||||
#include "DolphinQt/Config/ControllersWindow.h"
|
||||
#include "DolphinQt/Config/FreeLookWindow.h"
|
||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.h"
|
||||
|
@ -570,6 +571,7 @@ void MainWindow::ConnectMenuBar()
|
|||
connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
|
||||
connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser);
|
||||
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
|
||||
connect(m_menu_bar, &MenuBar::ShowCameraWindow, this, &MainWindow::ShowCameraWindow);
|
||||
connect(m_menu_bar, &MenuBar::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
|
||||
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
|
||||
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
||||
|
@ -1387,6 +1389,19 @@ void MainWindow::ShowFIFOPlayer()
|
|||
m_fifo_window->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::ShowCameraWindow()
|
||||
{
|
||||
if (!m_camera_window)
|
||||
{
|
||||
m_camera_window = new CameraWindow();
|
||||
}
|
||||
|
||||
SetQWidgetWindowDecorations(m_camera_window);
|
||||
m_camera_window->show();
|
||||
m_camera_window->raise();
|
||||
m_camera_window->activateWindow();
|
||||
}
|
||||
|
||||
void MainWindow::ShowSkylanderPortal()
|
||||
{
|
||||
if (!m_skylander_window)
|
||||
|
|
|
@ -21,6 +21,7 @@ class AchievementsWindow;
|
|||
class AssemblerWidget;
|
||||
class BreakpointWidget;
|
||||
struct BootParameters;
|
||||
class CameraWindow;
|
||||
class CheatsManager;
|
||||
class CodeWidget;
|
||||
class ControllersWindow;
|
||||
|
@ -171,6 +172,7 @@ private:
|
|||
void ShowNetPlaySetupDialog();
|
||||
void ShowNetPlayBrowser();
|
||||
void ShowFIFOPlayer();
|
||||
void ShowCameraWindow();
|
||||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ShowMemcardManager();
|
||||
|
@ -243,6 +245,7 @@ private:
|
|||
SettingsWindow* m_settings_window = nullptr;
|
||||
GraphicsWindow* m_graphics_window = nullptr;
|
||||
FIFOPlayerWindow* m_fifo_window = nullptr;
|
||||
CameraWindow* m_camera_window = nullptr;
|
||||
SkylanderPortalWindow* m_skylander_window = nullptr;
|
||||
InfinityBaseWindow* m_infinity_window = nullptr;
|
||||
MappingWindow* m_hotkey_window = nullptr;
|
||||
|
|
|
@ -272,6 +272,7 @@ void MenuBar::AddToolsMenu()
|
|||
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
||||
|
||||
auto* usb_device_menu = new QMenu(tr("Emulated USB Devices"), tools_menu);
|
||||
usb_device_menu->addAction(tr("&Duel Scanner / Motion Tracking Camera"), this, &MenuBar::ShowCameraWindow);
|
||||
usb_device_menu->addAction(tr("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
|
||||
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
|
||||
tools_menu->addMenu(usb_device_menu);
|
||||
|
|
|
@ -89,6 +89,7 @@ signals:
|
|||
void ShowAboutDialog();
|
||||
void ShowCheatsManager();
|
||||
void ShowResourcePackManager();
|
||||
void ShowCameraWindow();
|
||||
void ShowSkylanderPortal();
|
||||
void ShowInfinityBase();
|
||||
void ConnectWiiRemote(int id);
|
||||
|
|
|
@ -46,6 +46,14 @@ void Host_UpdateTitle(const std::string& title)
|
|||
{
|
||||
}
|
||||
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
}
|
||||
|
||||
void Host_CameraStop()
|
||||
{
|
||||
}
|
||||
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ static const std::map<std::pair<u16, u16>, std::string_view> s_wii_peripherals{{
|
|||
{{0x057e, 0x0308}, "Wii Speak"},
|
||||
{{0x057e, 0x0309}, "Nintendo USB Microphone"},
|
||||
{{0x057e, 0x030a}, "Ubisoft Motion Tracking Camera"},
|
||||
{{0x057e, 0x030d}, "Duel Scanner"},
|
||||
{{0x0e6f, 0x0129}, "Disney Infinity Reader (Portal Device)"},
|
||||
{{0x1430, 0x0100}, "Tony Hawk Ride Skateboard"},
|
||||
{{0x1430, 0x0150}, "Skylanders Portal"},
|
||||
|
|
|
@ -28,6 +28,12 @@ void Host_Message(HostMessageID)
|
|||
void Host_UpdateTitle(const std::string&)
|
||||
{
|
||||
}
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
}
|
||||
void Host_CameraStop()
|
||||
{
|
||||
}
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -28,6 +28,12 @@ void Host_Message(HostMessageID)
|
|||
void Host_UpdateTitle(const std::string&)
|
||||
{
|
||||
}
|
||||
void Host_CameraStart(u16 width, u16 height)
|
||||
{
|
||||
}
|
||||
void Host_CameraStop()
|
||||
{
|
||||
}
|
||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtGui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtWidgets;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtMultimedia;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<!--
|
||||
As of Qt6.3, Qt needs user code deriving from certain Qt types to have RTTI (AS WELL AS MOC, UGH).
|
||||
Do NOT enable in dolphin outside of Qt-dependant code.
|
||||
|
@ -34,7 +35,7 @@
|
|||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(QtLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>Qt6Core$(QtLibSuffix).lib;Qt6Gui$(QtLibSuffix).lib;Qt6Widgets$(QtLibSuffix).lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>Qt6Core$(QtLibSuffix).lib;Qt6Gui$(QtLibSuffix).lib;Qt6Widgets$(QtLibSuffix).lib;Qt6Multimedia$(QtLibSuffix).lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<!--
|
||||
<AdditionalOptions>"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\" %(AdditionalOptions)</AdditionalOptions>
|
||||
|
@ -109,11 +110,11 @@
|
|||
|
||||
<!--Copy the needed dlls-->
|
||||
<ItemGroup>
|
||||
<QtDllNames_ Include="Qt6Core;Qt6Gui;Qt6Widgets;Qt6Svg" />
|
||||
<QtDllNames_ Include="Qt6Core;Qt6Gui;Qt6Widgets;Qt6Svg;Qt6Multimedia;Qt6Network" />
|
||||
<QtDllNames Include="@(QtDllNames_ -> '%(Identity)$(QtLibSuffix).dll')" />
|
||||
<QtDllsSrc Include="@(QtDllNames -> '$(QtBinDir)%(Identity)')" />
|
||||
<QtDllsDst Include="@(QtDllNames -> '$(BinaryOutputDir)%(Identity)')" />
|
||||
<QtPluginNames_ Include="iconengines\qsvgicon;imageformats\qsvg;platforms\qdirect2d;platforms\qwindows;styles\qwindowsvistastyle"/>
|
||||
<QtPluginNames_ Include="iconengines\qsvgicon;imageformats\qsvg;platforms\qdirect2d;platforms\qwindows;styles\qwindowsvistastyle;multimedia\windowsmediaplugin"/>
|
||||
<QtPluginNames Include="@(QtPluginNames_ -> '%(Identity)$(QtLibSuffix).dll')" />
|
||||
<QtPluginsSrc Include="@(QtPluginNames -> '$(QtPluginsDir)%(Identity)')" />
|
||||
<QtPluginsDst Include="@(QtPluginNames -> '$(BinaryOutputDir)$(QtPluginFolder)\%(Identity)')" />
|
||||
|
|
Loading…
Reference in New Issue