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("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
|
||||||
|
|
||||||
implementation("com.nononsenseapps:filepicker:4.2.1")
|
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 {
|
fun getGitVersion(): String {
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.hardware.gamepad"
|
android:name="android.hardware.gamepad"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
<uses-feature
|
||||||
|
android:name="android.hardware.camera"
|
||||||
|
android:required="false"/>
|
||||||
<uses-feature
|
<uses-feature
|
||||||
android:name="android.software.leanback"
|
android:name="android.software.leanback"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
@ -28,6 +31,7 @@
|
||||||
<uses-permission
|
<uses-permission
|
||||||
android:name="android.permission.VIBRATE"
|
android:name="android.permission.VIBRATE"
|
||||||
android:required="false"/>
|
android:required="false"/>
|
||||||
|
<uses-permission-sdk-23 android:name="android.permission.CAMERA"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".DolphinApplication"
|
android:name=".DolphinApplication"
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
package org.dolphinemu.dolphinemu;
|
package org.dolphinemu.dolphinemu;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.usb.UsbManager;
|
import android.hardware.usb.UsbManager;
|
||||||
|
@ -15,13 +16,15 @@ import org.dolphinemu.dolphinemu.utils.VolleyUtil;
|
||||||
public class DolphinApplication extends Application
|
public class DolphinApplication extends Application
|
||||||
{
|
{
|
||||||
private static DolphinApplication application;
|
private static DolphinApplication application;
|
||||||
|
private static ActivityTracker sActivityTracker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate()
|
public void onCreate()
|
||||||
{
|
{
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
application = this;
|
application = this;
|
||||||
registerActivityLifecycleCallbacks(new ActivityTracker());
|
sActivityTracker = new ActivityTracker();
|
||||||
|
registerActivityLifecycleCallbacks(sActivityTracker);
|
||||||
VolleyUtil.init(getApplicationContext());
|
VolleyUtil.init(getApplicationContext());
|
||||||
System.loadLibrary("main");
|
System.loadLibrary("main");
|
||||||
|
|
||||||
|
@ -36,4 +39,9 @@ public class DolphinApplication extends Application
|
||||||
{
|
{
|
||||||
return application.getApplicationContext();
|
return application.getApplicationContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Activity getAppActivity()
|
||||||
|
{
|
||||||
|
return sActivityTracker.getCurrentActivity();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -459,6 +459,8 @@ public final class NativeLibrary
|
||||||
|
|
||||||
private static native String GetCurrentTitleDescriptionUnchecked();
|
private static native String GetCurrentTitleDescriptionUnchecked();
|
||||||
|
|
||||||
|
public static native void CameraSetData(byte[] image);
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
public static void displayToastMsg(final String text, final boolean long_length)
|
public static void displayToastMsg(final String text, final boolean long_length)
|
||||||
{
|
{
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
package org.dolphinemu.dolphinemu.activities
|
package org.dolphinemu.dolphinemu.activities
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Rect
|
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.AfterDirectoryInitializationRunner
|
||||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
||||||
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
|
||||||
|
import org.dolphinemu.dolphinemu.utils.PermissionsHandler
|
||||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
import org.dolphinemu.dolphinemu.utils.ThemeHelper
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@ -994,6 +994,11 @@ class EmulationActivity : AppCompatActivity(), ThemeProvider {
|
||||||
this.themeId = themeId
|
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 {
|
companion object {
|
||||||
private const val BACKSTACK_NAME_MENU = "menu"
|
private const val BACKSTACK_NAME_MENU = "menu"
|
||||||
private const val BACKSTACK_NAME_SUBMENU = "submenu"
|
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.databinding.DialogInputStringBinding
|
||||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||||
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView
|
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView
|
||||||
import org.dolphinemu.dolphinemu.utils.DirectoryInitialization
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,12 @@ enum class IntSetting(
|
||||||
"DoubleTapButton",
|
"DoubleTapButton",
|
||||||
NativeLibrary.ButtonType.WIIMOTE_BUTTON_A
|
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_LANGUAGE(Settings.FILE_SYSCONF, "IPL", "LNG", 0x01),
|
||||||
SYSCONF_SOUND_MODE(Settings.FILE_SYSCONF, "IPL", "SND", 0x01),
|
SYSCONF_SOUND_MODE(Settings.FILE_SYSCONF, "IPL", "SND", 0x01),
|
||||||
SYSCONF_SENSOR_BAR_POSITION(Settings.FILE_SYSCONF, "BT", "BAR", 0x01),
|
SYSCONF_SENSOR_BAR_POSITION(Settings.FILE_SYSCONF, "BT", "BAR", 0x01),
|
||||||
|
|
|
@ -55,6 +55,12 @@ enum class StringSetting(
|
||||||
"ResourcePackPath",
|
"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_FS_PATH(Settings.FILE_DOLPHIN, Settings.SECTION_INI_GENERAL, "NANDRootPath", ""),
|
||||||
MAIN_WII_SD_CARD_IMAGE_PATH(
|
MAIN_WII_SD_CARD_IMAGE_PATH(
|
||||||
Settings.FILE_DOLPHIN,
|
Settings.FILE_DOLPHIN,
|
||||||
|
|
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.dolphinemu.dolphinemu.NativeLibrary
|
import org.dolphinemu.dolphinemu.NativeLibrary
|
||||||
import org.dolphinemu.dolphinemu.R
|
import org.dolphinemu.dolphinemu.R
|
||||||
import org.dolphinemu.dolphinemu.activities.UserDataActivity
|
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.ControlGroupEnabledSetting
|
||||||
import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting
|
import org.dolphinemu.dolphinemu.features.input.model.InputMappingBooleanSetting
|
||||||
import org.dolphinemu.dolphinemu.features.input.model.InputMappingDoubleSetting
|
import org.dolphinemu.dolphinemu.features.input.model.InputMappingDoubleSetting
|
||||||
|
@ -186,6 +187,28 @@ class SettingsFragmentPresenter(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addTopLevelSettings(sl: ArrayList<SettingsItem>) {
|
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.config, MenuTag.CONFIG))
|
||||||
sl.add(SubmenuSetting(context, R.string.graphics_settings, MenuTag.GRAPHICS))
|
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))
|
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(
|
sl.add(
|
||||||
SwitchSetting(
|
SwitchSetting(
|
||||||
context,
|
context,
|
||||||
|
|
|
@ -21,6 +21,7 @@ import org.dolphinemu.dolphinemu.R
|
||||||
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
import org.dolphinemu.dolphinemu.activities.EmulationActivity
|
||||||
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter
|
import org.dolphinemu.dolphinemu.adapters.PlatformPagerAdapter
|
||||||
import org.dolphinemu.dolphinemu.databinding.ActivityMainBinding
|
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.IntSetting
|
||||||
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
|
||||||
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
|
||||||
|
@ -76,6 +77,7 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
||||||
}
|
}
|
||||||
|
|
||||||
presenter.onCreate()
|
presenter.onCreate()
|
||||||
|
Camera.getInstance(applicationContext)
|
||||||
|
|
||||||
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
|
// Stuff in this block only happens when this activity is newly created (i.e. not a rotation)
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
|
@ -216,6 +218,7 @@ class MainActivity : AppCompatActivity(), MainView, OnRefreshListener, ThemeProv
|
||||||
grantResults: IntArray
|
grantResults: IntArray
|
||||||
) {
|
) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
PermissionsHandler.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
||||||
PermissionsHandler.setWritePermissionDenied()
|
PermissionsHandler.setWritePermissionDenied()
|
||||||
|
|
|
@ -220,6 +220,7 @@ class TvMainActivity : FragmentActivity(), MainView, OnRefreshListener {
|
||||||
grantResults: IntArray
|
grantResults: IntArray
|
||||||
) {
|
) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
PermissionsHandler.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
if (requestCode == PermissionsHandler.REQUEST_CODE_WRITE_PERMISSION) {
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
if (grantResults[0] == PackageManager.PERMISSION_DENIED) {
|
||||||
PermissionsHandler.setWritePermissionDenied()
|
PermissionsHandler.setWritePermissionDenied()
|
||||||
|
|
|
@ -7,12 +7,14 @@ import android.os.Bundle
|
||||||
class ActivityTracker : ActivityLifecycleCallbacks {
|
class ActivityTracker : ActivityLifecycleCallbacks {
|
||||||
val resumedActivities = HashSet<Activity>()
|
val resumedActivities = HashSet<Activity>()
|
||||||
var backgroundExecutionAllowed = false
|
var backgroundExecutionAllowed = false
|
||||||
|
var currentActivity : Activity? = null
|
||||||
|
|
||||||
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
override fun onActivityCreated(activity: Activity, bundle: Bundle?) {}
|
||||||
|
|
||||||
override fun onActivityStarted(activity: Activity) {}
|
override fun onActivityStarted(activity: Activity) {}
|
||||||
|
|
||||||
override fun onActivityResumed(activity: Activity) {
|
override fun onActivityResumed(activity: Activity) {
|
||||||
|
currentActivity = activity
|
||||||
resumedActivities.add(activity)
|
resumedActivities.add(activity)
|
||||||
if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) {
|
if (!backgroundExecutionAllowed && !resumedActivities.isEmpty()) {
|
||||||
backgroundExecutionAllowed = true
|
backgroundExecutionAllowed = true
|
||||||
|
@ -21,6 +23,9 @@ class ActivityTracker : ActivityLifecycleCallbacks {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityPaused(activity: Activity) {
|
override fun onActivityPaused(activity: Activity) {
|
||||||
|
if (currentActivity === activity) {
|
||||||
|
currentActivity = null
|
||||||
|
}
|
||||||
resumedActivities.remove(activity)
|
resumedActivities.remove(activity)
|
||||||
if (backgroundExecutionAllowed && resumedActivities.isEmpty()) {
|
if (backgroundExecutionAllowed && resumedActivities.isEmpty()) {
|
||||||
backgroundExecutionAllowed = false
|
backgroundExecutionAllowed = false
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
package org.dolphinemu.dolphinemu.utils;
|
package org.dolphinemu.dolphinemu.utils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
@ -10,16 +11,21 @@ import android.os.Environment;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.FragmentActivity;
|
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.WRITE_EXTERNAL_STORAGE;
|
||||||
|
import static android.Manifest.permission.CAMERA;
|
||||||
|
|
||||||
public class PermissionsHandler
|
public class PermissionsHandler
|
||||||
{
|
{
|
||||||
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
|
public static final int REQUEST_CODE_WRITE_PERMISSION = 500;
|
||||||
|
public static final int REQUEST_CODE_CAMERA_PERMISSION = 502;
|
||||||
private static boolean sWritePermissionDenied = false;
|
private static boolean sWritePermissionDenied = false;
|
||||||
|
|
||||||
public static void requestWritePermission(final FragmentActivity activity)
|
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;
|
return;
|
||||||
|
|
||||||
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
|
activity.requestPermissions(new String[]{WRITE_EXTERNAL_STORAGE},
|
||||||
|
@ -28,7 +34,7 @@ public class PermissionsHandler
|
||||||
|
|
||||||
public static boolean hasWriteAccess(Context context)
|
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;
|
return true;
|
||||||
|
|
||||||
if (!isExternalStorageLegacy())
|
if (!isExternalStorageLegacy())
|
||||||
|
@ -52,4 +58,27 @@ public class PermissionsHandler
|
||||||
{
|
{
|
||||||
return sWritePermissionDenied;
|
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>
|
<item>5</item>
|
||||||
</integer-array>
|
</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 -->
|
<!-- Texture Cache Accuracy Preference -->
|
||||||
<string-array name="textureCacheAccuracyEntries">
|
<string-array name="textureCacheAccuracyEntries">
|
||||||
<item>@string/accuracy_fast</item>
|
<item>@string/accuracy_fast</item>
|
||||||
|
|
|
@ -911,6 +911,8 @@ It can efficiently compress both junk data and encrypted Wii data.
|
||||||
|
|
||||||
<!-- Emulated USB Devices -->
|
<!-- Emulated USB Devices -->
|
||||||
<string name="emulated_usb_devices">Emulated USB Devices</string>
|
<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="emulate_skylander_portal">Skylanders Portal</string>
|
||||||
<string name="skylanders_manager">Skylanders Manager</string>
|
<string name="skylanders_manager">Skylanders Manager</string>
|
||||||
<string name="create_skylander_title">Create Skylander</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 jfieldID s_core_device_control_pointer;
|
||||||
static jmethodID s_core_device_control_constructor;
|
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 jclass s_input_detector_class;
|
||||||
static jfieldID s_input_detector_pointer;
|
static jfieldID s_input_detector_pointer;
|
||||||
|
|
||||||
|
@ -528,6 +533,26 @@ jmethodID GetCoreDeviceControlConstructor()
|
||||||
return s_core_device_control_constructor;
|
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()
|
jclass GetInputDetectorClass()
|
||||||
{
|
{
|
||||||
return s_input_detector_class;
|
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");
|
"(Lorg/dolphinemu/dolphinemu/features/input/model/CoreDevice;J)V");
|
||||||
env->DeleteLocalRef(core_device_control_class);
|
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 =
|
const jclass input_detector_class =
|
||||||
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/InputDetector");
|
env->FindClass("org/dolphinemu/dolphinemu/features/input/model/InputDetector");
|
||||||
s_input_detector_class = reinterpret_cast<jclass>(env->NewGlobalRef(input_detector_class));
|
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_numeric_setting_class);
|
||||||
env->DeleteGlobalRef(s_core_device_class);
|
env->DeleteGlobalRef(s_core_device_class);
|
||||||
env->DeleteGlobalRef(s_core_device_control_class);
|
env->DeleteGlobalRef(s_core_device_control_class);
|
||||||
|
env->DeleteGlobalRef(s_camera_class);
|
||||||
env->DeleteGlobalRef(s_input_detector_class);
|
env->DeleteGlobalRef(s_input_detector_class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,6 +112,11 @@ jclass GetCoreDeviceControlClass();
|
||||||
jfieldID GetCoreDeviceControlPointer();
|
jfieldID GetCoreDeviceControlPointer();
|
||||||
jmethodID GetCoreDeviceControlConstructor();
|
jmethodID GetCoreDeviceControlConstructor();
|
||||||
|
|
||||||
|
jclass GetCameraClass();
|
||||||
|
jmethodID GetCameraStart();
|
||||||
|
jmethodID GetCameraResume();
|
||||||
|
jmethodID GetCameraStop();
|
||||||
|
|
||||||
jclass GetInputDetectorClass();
|
jclass GetInputDetectorClass();
|
||||||
jfieldID GetInputDetectorPointer();
|
jfieldID GetInputDetectorPointer();
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
#include "Core/HW/Wiimote.h"
|
#include "Core/HW/Wiimote.h"
|
||||||
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
#include "Core/HW/WiimoteReal/WiimoteReal.h"
|
||||||
#include "Core/Host.h"
|
#include "Core/Host.h"
|
||||||
|
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||||
#include "Core/PowerPC/JitInterface.h"
|
#include "Core/PowerPC/JitInterface.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "Core/State.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());
|
__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)
|
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
@ -488,6 +501,8 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang
|
||||||
if (g_presenter)
|
if (g_presenter)
|
||||||
g_presenter->ChangeSurface(s_surf);
|
g_presenter->ChangeSurface(s_surf);
|
||||||
|
|
||||||
|
env->CallStaticVoidMethod(IDCache::GetCameraClass(), IDCache::GetCameraResume());
|
||||||
|
|
||||||
s_surface_cv.notify_all();
|
s_surface_cv.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,4 +847,13 @@ Java_org_dolphinemu_dolphinemu_NativeLibrary_GetCurrentTitleDescriptionUnchecked
|
||||||
|
|
||||||
return ToJString(env, description);
|
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/Bluetooth/WiimoteHIDAttr.h
|
||||||
IOS/USB/Common.cpp
|
IOS/USB/Common.cpp
|
||||||
IOS/USB/Common.h
|
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.cpp
|
||||||
IOS/USB/Emulated/Infinity.h
|
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.cpp
|
||||||
IOS/USB/Emulated/Skylanders/Skylander.h
|
IOS/USB/Emulated/Skylanders/Skylander.h
|
||||||
IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp
|
IOS/USB/Emulated/Skylanders/SkylanderCrypto.cpp
|
||||||
|
|
|
@ -585,6 +585,12 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices)
|
||||||
|
|
||||||
// Main.EmulatedUSBDevices
|
// 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{
|
const Info<bool> MAIN_EMULATE_SKYLANDER_PORTAL{
|
||||||
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
{System::Main, "EmulatedUSBDevices", "EmulateSkylanderPortal"}, false};
|
||||||
|
|
||||||
|
|
|
@ -358,6 +358,8 @@ void SetUSBDeviceWhitelist(const std::set<std::pair<u16, u16>>& devices);
|
||||||
|
|
||||||
// Main.EmulatedUSBDevices
|
// 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_SKYLANDER_PORTAL;
|
||||||
extern const Info<bool> MAIN_EMULATE_INFINITY_BASE;
|
extern const Info<bool> MAIN_EMULATE_INFINITY_BASE;
|
||||||
|
|
||||||
|
|
|
@ -900,6 +900,8 @@ void UpdateTitle(Core::System& system)
|
||||||
}
|
}
|
||||||
|
|
||||||
Host_UpdateTitle(message);
|
Host_UpdateTitle(message);
|
||||||
|
//Host_CameraStart(320, 240);
|
||||||
|
//Host_CameraStart(640, 480);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown(Core::System& system)
|
void Shutdown(Core::System& system)
|
||||||
|
|
|
@ -68,6 +68,9 @@ void Host_UpdateTitle(const std::string& title);
|
||||||
void Host_YieldToUI();
|
void Host_YieldToUI();
|
||||||
void Host_TitleChanged();
|
void Host_TitleChanged();
|
||||||
|
|
||||||
|
void Host_CameraStart(u16 width, u16 height);
|
||||||
|
void Host_CameraStop();
|
||||||
|
|
||||||
void Host_UpdateDiscordClientID(const std::string& client_id = {});
|
void Host_UpdateDiscordClientID(const std::string& client_id = {});
|
||||||
bool Host_UpdateDiscordPresenceRaw(const std::string& details = {}, const std::string& state = {},
|
bool Host_UpdateDiscordPresenceRaw(const std::string& details = {}, const std::string& state = {},
|
||||||
const std::string& large_image_key = {},
|
const std::string& large_image_key = {},
|
||||||
|
|
|
@ -29,6 +29,7 @@ enum ControlRequestTypes
|
||||||
DIR_HOST2DEVICE = 0,
|
DIR_HOST2DEVICE = 0,
|
||||||
DIR_DEVICE2HOST = 1,
|
DIR_DEVICE2HOST = 1,
|
||||||
TYPE_STANDARD = 0,
|
TYPE_STANDARD = 0,
|
||||||
|
TYPE_CLASS = 1,
|
||||||
TYPE_VENDOR = 2,
|
TYPE_VENDOR = 2,
|
||||||
REC_DEVICE = 0,
|
REC_DEVICE = 0,
|
||||||
REC_INTERFACE = 1,
|
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/Config/MainSettings.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "Core/IOS/USB/Common.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/Infinity.h"
|
||||||
|
#include "Core/IOS/USB/Emulated/MotionCamera.h"
|
||||||
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
|
#include "Core/IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||||
#include "Core/IOS/USB/LibusbDevice.h"
|
#include "Core/IOS/USB/LibusbDevice.h"
|
||||||
#include "Core/NetPlayProto.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,
|
void USBHost::AddEmulatedDevices(std::set<u64>& new_devices, DeviceChangeHooks& hooks,
|
||||||
bool always_add_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())
|
if (Config::Get(Config::MAIN_EMULATE_SKYLANDER_PORTAL) && !NetPlay::IsNetPlayRunning())
|
||||||
{
|
{
|
||||||
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(GetEmulationKernel());
|
auto skylanderportal = std::make_unique<USB::SkylanderUSB>(GetEmulationKernel());
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#include "Core/PowerPC/JitInterface.h"
|
#include "Core/PowerPC/JitInterface.h"
|
||||||
#include "Core/PowerPC/PowerPC.h"
|
#include "Core/PowerPC/PowerPC.h"
|
||||||
#include "IOS/USB/Emulated/Infinity.h"
|
#include "IOS/USB/Emulated/Infinity.h"
|
||||||
|
#include "IOS/USB/Emulated/MotionCamera.h"
|
||||||
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
#include "IOS/USB/Emulated/Skylanders/Skylander.h"
|
||||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||||
#include "VideoCommon/CommandProcessor.h"
|
#include "VideoCommon/CommandProcessor.h"
|
||||||
|
@ -78,6 +79,7 @@ struct System::Impl
|
||||||
HSP::HSPManager m_hsp;
|
HSP::HSPManager m_hsp;
|
||||||
IOS::HLE::USB::InfinityBase m_infinity_base;
|
IOS::HLE::USB::InfinityBase m_infinity_base;
|
||||||
IOS::HLE::USB::SkylanderPortal m_skylander_portal;
|
IOS::HLE::USB::SkylanderPortal m_skylander_portal;
|
||||||
|
IOS::HLE::USB::CameraBase m_camera_data;
|
||||||
IOS::WiiIPC m_wii_ipc;
|
IOS::WiiIPC m_wii_ipc;
|
||||||
Memory::MemoryManager m_memory;
|
Memory::MemoryManager m_memory;
|
||||||
MemoryInterface::MemoryInterfaceManager m_memory_interface;
|
MemoryInterface::MemoryInterfaceManager m_memory_interface;
|
||||||
|
@ -243,6 +245,11 @@ IOS::HLE::USB::InfinityBase& System::GetInfinityBase() const
|
||||||
return m_impl->m_infinity_base;
|
return m_impl->m_infinity_base;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IOS::HLE::USB::CameraBase& System::GetCameraBase() const
|
||||||
|
{
|
||||||
|
return m_impl->m_camera_data;
|
||||||
|
}
|
||||||
|
|
||||||
IOS::WiiIPC& System::GetWiiIPC() const
|
IOS::WiiIPC& System::GetWiiIPC() const
|
||||||
{
|
{
|
||||||
return m_impl->m_wii_ipc;
|
return m_impl->m_wii_ipc;
|
||||||
|
|
|
@ -69,6 +69,7 @@ namespace IOS::HLE::USB
|
||||||
{
|
{
|
||||||
class SkylanderPortal;
|
class SkylanderPortal;
|
||||||
class InfinityBase;
|
class InfinityBase;
|
||||||
|
class CameraBase;
|
||||||
} // namespace IOS::HLE::USB
|
} // namespace IOS::HLE::USB
|
||||||
namespace Memory
|
namespace Memory
|
||||||
{
|
{
|
||||||
|
@ -176,6 +177,7 @@ public:
|
||||||
JitInterface& GetJitInterface() const;
|
JitInterface& GetJitInterface() const;
|
||||||
IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
|
IOS::HLE::USB::SkylanderPortal& GetSkylanderPortal() const;
|
||||||
IOS::HLE::USB::InfinityBase& GetInfinityBase() const;
|
IOS::HLE::USB::InfinityBase& GetInfinityBase() const;
|
||||||
|
IOS::HLE::USB::CameraBase& GetCameraBase() const;
|
||||||
IOS::WiiIPC& GetWiiIPC() const;
|
IOS::WiiIPC& GetWiiIPC() const;
|
||||||
Memory::MemoryManager& GetMemory() const;
|
Memory::MemoryManager& GetMemory() const;
|
||||||
MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
|
MemoryInterface::MemoryInterfaceManager& GetMemoryInterface() const;
|
||||||
|
|
|
@ -400,7 +400,10 @@
|
||||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteDevice.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
<ClInclude Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Common.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\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\Skylander.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.h" />
|
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.h" />
|
||||||
<ClInclude Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.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\WiimoteDevice.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
<ClCompile Include="Core\IOS\USB\Bluetooth\WiimoteHIDAttr.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Common.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\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\Skylander.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.cpp" />
|
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderCrypto.cpp" />
|
||||||
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.cpp" />
|
<ClCompile Include="Core\IOS\USB\Emulated\Skylanders\SkylanderFigure.cpp" />
|
||||||
|
|
|
@ -139,6 +139,14 @@ void Host_TitleChanged()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Host_CameraStart(u16 width, u16 height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host_CameraStop()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
|
|
@ -14,7 +14,7 @@ endif()
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC 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}")
|
message(STATUS "Found Qt version ${Qt6_VERSION}")
|
||||||
|
|
||||||
set_property(TARGET Qt6::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
set_property(TARGET Qt6::Core PROPERTY INTERFACE_COMPILE_FEATURES "")
|
||||||
|
@ -40,6 +40,8 @@ add_executable(dolphin-emu
|
||||||
Achievements/AchievementSettingsWidget.h
|
Achievements/AchievementSettingsWidget.h
|
||||||
Achievements/AchievementsWindow.cpp
|
Achievements/AchievementsWindow.cpp
|
||||||
Achievements/AchievementsWindow.h
|
Achievements/AchievementsWindow.h
|
||||||
|
CameraQt/CameraQt.cpp
|
||||||
|
CameraQt/CameraQt.h
|
||||||
Config/ARCodeWidget.cpp
|
Config/ARCodeWidget.cpp
|
||||||
Config/ARCodeWidget.h
|
Config/ARCodeWidget.h
|
||||||
Config/CheatCodeEditor.cpp
|
Config/CheatCodeEditor.cpp
|
||||||
|
@ -427,6 +429,7 @@ target_link_libraries(dolphin-emu
|
||||||
PRIVATE
|
PRIVATE
|
||||||
core
|
core
|
||||||
Qt6::Widgets
|
Qt6::Widgets
|
||||||
|
Qt6::Multimedia
|
||||||
uicommon
|
uicommon
|
||||||
imgui
|
imgui
|
||||||
implot
|
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\AchievementProgressWidget.cpp" />
|
||||||
<ClCompile Include="Achievements\AchievementSettingsWidget.cpp" />
|
<ClCompile Include="Achievements\AchievementSettingsWidget.cpp" />
|
||||||
<ClCompile Include="Achievements\AchievementsWindow.cpp" />
|
<ClCompile Include="Achievements\AchievementsWindow.cpp" />
|
||||||
|
<ClCompile Include="CameraQt\CameraQt.cpp" />
|
||||||
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
<ClCompile Include="Config\ARCodeWidget.cpp" />
|
||||||
<ClCompile Include="Config\CheatCodeEditor.cpp" />
|
<ClCompile Include="Config\CheatCodeEditor.cpp" />
|
||||||
<ClCompile Include="Config\CheatWarningWidget.cpp" />
|
<ClCompile Include="Config\CheatWarningWidget.cpp" />
|
||||||
|
@ -240,6 +241,7 @@
|
||||||
also be modified using the VS UI.
|
also be modified using the VS UI.
|
||||||
-->
|
-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ClInclude Include="CameraQt\CameraQt.h" />
|
||||||
<ClInclude Include="Config\CheatCodeEditor.h" />
|
<ClInclude Include="Config\CheatCodeEditor.h" />
|
||||||
<ClInclude Include="Config\ConfigControls\ConfigControl.h" />
|
<ClInclude Include="Config\ConfigControls\ConfigControl.h" />
|
||||||
<ClInclude Include="Config\GameConfigEdit.h" />
|
<ClInclude Include="Config\GameConfigEdit.h" />
|
||||||
|
@ -278,6 +280,7 @@
|
||||||
<QtMoc Include="Achievements\AchievementProgressWidget.h" />
|
<QtMoc Include="Achievements\AchievementProgressWidget.h" />
|
||||||
<QtMoc Include="Achievements\AchievementSettingsWidget.h" />
|
<QtMoc Include="Achievements\AchievementSettingsWidget.h" />
|
||||||
<QtMoc Include="Achievements\AchievementsWindow.h" />
|
<QtMoc Include="Achievements\AchievementsWindow.h" />
|
||||||
|
<QtMoc Include="CameraQt\CameraQt.h" />
|
||||||
<QtMoc Include="Config\ARCodeWidget.h" />
|
<QtMoc Include="Config\ARCodeWidget.h" />
|
||||||
<QtMoc Include="Config\CheatWarningWidget.h" />
|
<QtMoc Include="Config\CheatWarningWidget.h" />
|
||||||
<QtMoc Include="Config\CommonControllersWidget.h" />
|
<QtMoc Include="Config\CommonControllersWidget.h" />
|
||||||
|
|
|
@ -307,6 +307,16 @@ void Host_TitleChanged()
|
||||||
#endif
|
#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)
|
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||||
{
|
{
|
||||||
#ifdef USE_DISCORD_PRESENCE
|
#ifdef USE_DISCORD_PRESENCE
|
||||||
|
|
|
@ -44,6 +44,8 @@ signals:
|
||||||
void JitProfileDataWiped();
|
void JitProfileDataWiped();
|
||||||
void PPCSymbolsChanged();
|
void PPCSymbolsChanged();
|
||||||
void PPCBreakpointsChanged();
|
void PPCBreakpointsChanged();
|
||||||
|
void CameraStart(u16 width, u16 height);
|
||||||
|
void CameraStop();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Host();
|
Host();
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "Core/DolphinAnalytics.h"
|
#include "Core/DolphinAnalytics.h"
|
||||||
#include "Core/System.h"
|
#include "Core/System.h"
|
||||||
|
|
||||||
|
#include "DolphinQt/CameraQt/CameraQt.h"
|
||||||
#include "DolphinQt/Host.h"
|
#include "DolphinQt/Host.h"
|
||||||
#include "DolphinQt/MainWindow.h"
|
#include "DolphinQt/MainWindow.h"
|
||||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||||
|
@ -190,6 +191,8 @@ int main(int argc, char* argv[])
|
||||||
QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
|
QObject::connect(QAbstractEventDispatcher::instance(), &QAbstractEventDispatcher::aboutToBlock,
|
||||||
&app, [] { Core::HostDispatchJobs(Core::System::GetInstance()); });
|
&app, [] { Core::HostDispatchJobs(Core::System::GetInstance()); });
|
||||||
|
|
||||||
|
CameraManager camera;
|
||||||
|
|
||||||
std::optional<std::string> save_state_path;
|
std::optional<std::string> save_state_path;
|
||||||
if (options.is_set("save_state"))
|
if (options.is_set("save_state"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
#include "DolphinQt/AboutDialog.h"
|
#include "DolphinQt/AboutDialog.h"
|
||||||
#include "DolphinQt/Achievements/AchievementsWindow.h"
|
#include "DolphinQt/Achievements/AchievementsWindow.h"
|
||||||
#include "DolphinQt/CheatsManager.h"
|
#include "DolphinQt/CheatsManager.h"
|
||||||
|
#include "DolphinQt/CameraQt/CameraQt.h"
|
||||||
#include "DolphinQt/Config/ControllersWindow.h"
|
#include "DolphinQt/Config/ControllersWindow.h"
|
||||||
#include "DolphinQt/Config/FreeLookWindow.h"
|
#include "DolphinQt/Config/FreeLookWindow.h"
|
||||||
#include "DolphinQt/Config/Graphics/GraphicsWindow.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::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog);
|
||||||
connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser);
|
connect(m_menu_bar, &MenuBar::BrowseNetPlay, this, &MainWindow::ShowNetPlayBrowser);
|
||||||
connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer);
|
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::ShowSkylanderPortal, this, &MainWindow::ShowSkylanderPortal);
|
||||||
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
|
connect(m_menu_bar, &MenuBar::ShowInfinityBase, this, &MainWindow::ShowInfinityBase);
|
||||||
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
connect(m_menu_bar, &MenuBar::ConnectWiiRemote, this, &MainWindow::OnConnectWiiRemote);
|
||||||
|
@ -1387,6 +1389,19 @@ void MainWindow::ShowFIFOPlayer()
|
||||||
m_fifo_window->activateWindow();
|
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()
|
void MainWindow::ShowSkylanderPortal()
|
||||||
{
|
{
|
||||||
if (!m_skylander_window)
|
if (!m_skylander_window)
|
||||||
|
|
|
@ -21,6 +21,7 @@ class AchievementsWindow;
|
||||||
class AssemblerWidget;
|
class AssemblerWidget;
|
||||||
class BreakpointWidget;
|
class BreakpointWidget;
|
||||||
struct BootParameters;
|
struct BootParameters;
|
||||||
|
class CameraWindow;
|
||||||
class CheatsManager;
|
class CheatsManager;
|
||||||
class CodeWidget;
|
class CodeWidget;
|
||||||
class ControllersWindow;
|
class ControllersWindow;
|
||||||
|
@ -171,6 +172,7 @@ private:
|
||||||
void ShowNetPlaySetupDialog();
|
void ShowNetPlaySetupDialog();
|
||||||
void ShowNetPlayBrowser();
|
void ShowNetPlayBrowser();
|
||||||
void ShowFIFOPlayer();
|
void ShowFIFOPlayer();
|
||||||
|
void ShowCameraWindow();
|
||||||
void ShowSkylanderPortal();
|
void ShowSkylanderPortal();
|
||||||
void ShowInfinityBase();
|
void ShowInfinityBase();
|
||||||
void ShowMemcardManager();
|
void ShowMemcardManager();
|
||||||
|
@ -243,6 +245,7 @@ private:
|
||||||
SettingsWindow* m_settings_window = nullptr;
|
SettingsWindow* m_settings_window = nullptr;
|
||||||
GraphicsWindow* m_graphics_window = nullptr;
|
GraphicsWindow* m_graphics_window = nullptr;
|
||||||
FIFOPlayerWindow* m_fifo_window = nullptr;
|
FIFOPlayerWindow* m_fifo_window = nullptr;
|
||||||
|
CameraWindow* m_camera_window = nullptr;
|
||||||
SkylanderPortalWindow* m_skylander_window = nullptr;
|
SkylanderPortalWindow* m_skylander_window = nullptr;
|
||||||
InfinityBaseWindow* m_infinity_window = nullptr;
|
InfinityBaseWindow* m_infinity_window = nullptr;
|
||||||
MappingWindow* m_hotkey_window = nullptr;
|
MappingWindow* m_hotkey_window = nullptr;
|
||||||
|
|
|
@ -272,6 +272,7 @@ void MenuBar::AddToolsMenu()
|
||||||
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
tools_menu->addAction(tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer);
|
||||||
|
|
||||||
auto* usb_device_menu = new QMenu(tr("Emulated USB Devices"), tools_menu);
|
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("&Skylanders Portal"), this, &MenuBar::ShowSkylanderPortal);
|
||||||
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
|
usb_device_menu->addAction(tr("&Infinity Base"), this, &MenuBar::ShowInfinityBase);
|
||||||
tools_menu->addMenu(usb_device_menu);
|
tools_menu->addMenu(usb_device_menu);
|
||||||
|
|
|
@ -89,6 +89,7 @@ signals:
|
||||||
void ShowAboutDialog();
|
void ShowAboutDialog();
|
||||||
void ShowCheatsManager();
|
void ShowCheatsManager();
|
||||||
void ShowResourcePackManager();
|
void ShowResourcePackManager();
|
||||||
|
void ShowCameraWindow();
|
||||||
void ShowSkylanderPortal();
|
void ShowSkylanderPortal();
|
||||||
void ShowInfinityBase();
|
void ShowInfinityBase();
|
||||||
void ConnectWiiRemote(int id);
|
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)
|
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, 0x0308}, "Wii Speak"},
|
||||||
{{0x057e, 0x0309}, "Nintendo USB Microphone"},
|
{{0x057e, 0x0309}, "Nintendo USB Microphone"},
|
||||||
{{0x057e, 0x030a}, "Ubisoft Motion Tracking Camera"},
|
{{0x057e, 0x030a}, "Ubisoft Motion Tracking Camera"},
|
||||||
|
{{0x057e, 0x030d}, "Duel Scanner"},
|
||||||
{{0x0e6f, 0x0129}, "Disney Infinity Reader (Portal Device)"},
|
{{0x0e6f, 0x0129}, "Disney Infinity Reader (Portal Device)"},
|
||||||
{{0x1430, 0x0100}, "Tony Hawk Ride Skateboard"},
|
{{0x1430, 0x0100}, "Tony Hawk Ride Skateboard"},
|
||||||
{{0x1430, 0x0150}, "Skylanders Portal"},
|
{{0x1430, 0x0150}, "Skylanders Portal"},
|
||||||
|
|
|
@ -28,6 +28,12 @@ void Host_Message(HostMessageID)
|
||||||
void Host_UpdateTitle(const std::string&)
|
void Host_UpdateTitle(const std::string&)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
void Host_CameraStart(u16 width, u16 height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void Host_CameraStop()
|
||||||
|
{
|
||||||
|
}
|
||||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,12 @@ void Host_Message(HostMessageID)
|
||||||
void Host_UpdateTitle(const std::string&)
|
void Host_UpdateTitle(const std::string&)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
void Host_CameraStart(u16 width, u16 height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void Host_CameraStop()
|
||||||
|
{
|
||||||
|
}
|
||||||
void Host_UpdateDiscordClientID(const std::string& client_id)
|
void Host_UpdateDiscordClientID(const std::string& client_id)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(QtIncludeDir)QtCore;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtGui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
<AdditionalIncludeDirectories>$(QtIncludeDir)QtGui;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||||
<AdditionalIncludeDirectories>$(QtIncludeDir)QtWidgets;%(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).
|
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.
|
Do NOT enable in dolphin outside of Qt-dependant code.
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<Link>
|
<Link>
|
||||||
<AdditionalLibraryDirectories>$(QtLibDir);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
<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>
|
<SubSystem>Windows</SubSystem>
|
||||||
<!--
|
<!--
|
||||||
<AdditionalOptions>"/manifestdependency:type='Win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\" %(AdditionalOptions)</AdditionalOptions>
|
<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-->
|
<!--Copy the needed dlls-->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<QtDllNames_ Include="Qt6Core;Qt6Gui;Qt6Widgets;Qt6Svg" />
|
<QtDllNames_ Include="Qt6Core;Qt6Gui;Qt6Widgets;Qt6Svg;Qt6Multimedia;Qt6Network" />
|
||||||
<QtDllNames Include="@(QtDllNames_ -> '%(Identity)$(QtLibSuffix).dll')" />
|
<QtDllNames Include="@(QtDllNames_ -> '%(Identity)$(QtLibSuffix).dll')" />
|
||||||
<QtDllsSrc Include="@(QtDllNames -> '$(QtBinDir)%(Identity)')" />
|
<QtDllsSrc Include="@(QtDllNames -> '$(QtBinDir)%(Identity)')" />
|
||||||
<QtDllsDst Include="@(QtDllNames -> '$(BinaryOutputDir)%(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')" />
|
<QtPluginNames Include="@(QtPluginNames_ -> '%(Identity)$(QtLibSuffix).dll')" />
|
||||||
<QtPluginsSrc Include="@(QtPluginNames -> '$(QtPluginsDir)%(Identity)')" />
|
<QtPluginsSrc Include="@(QtPluginNames -> '$(QtPluginsDir)%(Identity)')" />
|
||||||
<QtPluginsDst Include="@(QtPluginNames -> '$(BinaryOutputDir)$(QtPluginFolder)\%(Identity)')" />
|
<QtPluginsDst Include="@(QtPluginNames -> '$(BinaryOutputDir)$(QtPluginFolder)\%(Identity)')" />
|
||||||
|
|
Loading…
Reference in New Issue