Firmware 20.0.0 Initial Implementation & Android: Uninstall Firmware Button
Co-authored-by: Pavel Barabanov <pavelbarabanov94@gmail.com> Reviewed-on: #89 Co-authored-by: crueter <crueter@noreply.localhost> Co-committed-by: crueter <crueter@noreply.localhost>
This commit is contained in:
parent
668bcb94db
commit
4661909f4c
18 changed files with 155 additions and 20 deletions
|
@ -133,6 +133,11 @@ class InstallableFragment : Fragment() {
|
|||
R.string.install_firmware_description,
|
||||
install = { mainActivity.getFirmware.launch(arrayOf("application/zip")) }
|
||||
),
|
||||
Installable(
|
||||
R.string.uninstall_firmware,
|
||||
R.string.uninstall_firmware_description,
|
||||
install = { mainActivity.uninstallFirmware() }
|
||||
),
|
||||
Installable(
|
||||
R.string.install_prod_keys,
|
||||
R.string.install_prod_keys_description,
|
||||
|
|
|
@ -376,7 +376,31 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
|||
messageToShow
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
|
||||
fun uninstallFirmware() {
|
||||
val firmwarePath = File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
|
||||
ProgressDialogFragment.newInstance(
|
||||
this,
|
||||
R.string.firmware_uninstalling
|
||||
) { progressCallback, _ ->
|
||||
var messageToShow: Any
|
||||
try {
|
||||
// Ensure the firmware directory exists before attempting to delete
|
||||
if (firmwarePath.exists()) {
|
||||
firmwarePath.deleteRecursively()
|
||||
// Optionally reinitialize the system or perform other necessary steps
|
||||
NativeLibrary.initializeSystem(true)
|
||||
homeViewModel.setCheckKeys(true)
|
||||
messageToShow = getString(R.string.firmware_uninstalled_success)
|
||||
} else {
|
||||
messageToShow = getString(R.string.firmware_uninstalled_failure)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error("[MainActivity] Firmware uninstall failed - ${e.message}")
|
||||
messageToShow = getString(R.string.fatal_error)
|
||||
}
|
||||
messageToShow
|
||||
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
|
||||
}
|
||||
val getAmiiboKey =
|
||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||
if (result == null) {
|
||||
|
|
|
@ -143,6 +143,11 @@
|
|||
<string name="firmware_installed_success">Firmware installed successfully</string>
|
||||
<string name="firmware_installed_failure">Firmware installation failed</string>
|
||||
<string name="firmware_installed_failure_description">Make sure the firmware nca files are at the root of the zip and try again.</string>
|
||||
<string name="uninstall_firmware">Uninstall firmware</string>
|
||||
<string name="uninstall_firmware_description">Uninstalling the firmware will remove it from the device and may affect game compatibility.</string>
|
||||
<string name="firmware_uninstalling">Uninstalling firmware</string>
|
||||
<string name="firmware_uninstalled_success">Firmware uninstalled successfully</string>
|
||||
<string name="firmware_uninstalled_failure">Firmware uninstallation failed</string>
|
||||
<string name="share_log">Share debug logs</string>
|
||||
<string name="share_log_description">Share eden\'s log file to debug issues</string>
|
||||
<string name="share_log_missing">No log file found</string>
|
||||
|
|
|
@ -60,24 +60,29 @@ std::unique_ptr<Process> CreateProcessImpl(std::unique_ptr<Loader::AppLoader>& o
|
|||
} // Anonymous namespace
|
||||
|
||||
std::unique_ptr<Process> CreateProcess(Core::System& system, u64 program_id,
|
||||
u8 minimum_key_generation, u8 maximum_key_generation) {
|
||||
FileSys::VirtualFile nca_raw = system.GetContentProviderUnion()
|
||||
.GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
|
||||
u8 minimum_key_generation, u8 maximum_key_generation) {
|
||||
// Attempt to load program NCA.
|
||||
FileSys::VirtualFile nca_raw{};
|
||||
|
||||
// Get the program NCA from storage.
|
||||
auto& storage = system.GetContentProviderUnion();
|
||||
nca_raw = storage.GetEntryRaw(program_id, FileSys::ContentRecordType::Program);
|
||||
|
||||
// Ensure we retrieved a program NCA.
|
||||
if (!nca_raw) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FileSys::NCA nca(nca_raw);
|
||||
if (nca.GetStatus() != Loader::ResultStatus::Success) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u8 current_gen = nca.GetKeyGeneration();
|
||||
if (minimum_key_generation > 0 && (current_gen < minimum_key_generation ||
|
||||
current_gen > maximum_key_generation)) {
|
||||
LOG_WARNING(Service_LDR, "Program {:016X} has unsupported generation {}. "
|
||||
"Attempting to load anyway...", program_id, current_gen);
|
||||
// Ensure we have a suitable version.
|
||||
if (minimum_key_generation > 0) {
|
||||
FileSys::NCA nca(nca_raw);
|
||||
if (nca.GetStatus() == Loader::ResultStatus::Success &&
|
||||
(nca.GetKeyGeneration() < minimum_key_generation ||
|
||||
nca.GetKeyGeneration() > maximum_key_generation)) {
|
||||
LOG_WARNING(Service_LDR, "Skipping program {:016X} with generation {}", program_id,
|
||||
nca.GetKeyGeneration());
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Loader::AppLoader> loader;
|
||||
|
@ -101,7 +106,7 @@ std::unique_ptr<Process> CreateApplicationProcess(std::vector<u8>& out_control,
|
|||
out_control = nacp.GetRawBytes();
|
||||
} else {
|
||||
out_control.resize(sizeof(FileSys::RawNACP));
|
||||
std::fill(out_control.begin(), out_control.end(), (u8) 0);
|
||||
std::fill(out_control.begin(), out_control.end(), 0);
|
||||
}
|
||||
|
||||
auto& storage = system.GetContentProviderUnion();
|
||||
|
|
|
@ -18,6 +18,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys
|
|||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{100, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxy>, "OpenSystemAppletProxy"},
|
||||
{110, D<&IAllSystemAppletProxiesService::OpenSystemAppletProxyForDebug>, "OpenSystemAppletProxyForDebug"},
|
||||
{200, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxyOld>, "OpenLibraryAppletProxyOld"},
|
||||
{201, D<&IAllSystemAppletProxiesService::OpenLibraryAppletProxy>, "OpenLibraryAppletProxy"},
|
||||
{300, nullptr, "OpenOverlayAppletProxy"},
|
||||
|
@ -25,6 +26,7 @@ IAllSystemAppletProxiesService::IAllSystemAppletProxiesService(Core::System& sys
|
|||
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
|
||||
{410, nullptr, "GetSystemAppletControllerForDebug"},
|
||||
{450, D<&IAllSystemAppletProxiesService::GetSystemProcessCommonFunctions>, "GetSystemProcessCommonFunctions"}, // 19.0.0+
|
||||
{460, nullptr, "Unknown460"},
|
||||
{1000, nullptr, "GetDebugFunctions"},
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -49,6 +51,26 @@ Result IAllSystemAppletProxiesService::OpenSystemAppletProxy(
|
|||
}
|
||||
}
|
||||
|
||||
Result IAllSystemAppletProxiesService::OpenSystemAppletProxyForDebug(
|
||||
Out<SharedPointer<ISystemAppletProxy>> out_proxy, ClientProcessId pid) {
|
||||
LOG_DEBUG(Service_AM, "OpenSystemAppletProxyForDebug called");
|
||||
|
||||
auto process = system.ApplicationProcess();
|
||||
if (!process) {
|
||||
LOG_ERROR(Service_AM, "No application process available");
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
if (const auto applet = GetAppletFromProcessId(pid)) {
|
||||
*out_proxy = std::make_shared<ISystemAppletProxy>(
|
||||
system, applet, process, m_window_system);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_AM, "Applet not found for pid={}", pid.pid);
|
||||
R_THROW(ResultUnknown);
|
||||
}
|
||||
|
||||
Result IAllSystemAppletProxiesService::OpenLibraryAppletProxy(
|
||||
Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy, ClientProcessId pid,
|
||||
InCopyHandle<Kernel::KProcess> process_handle,
|
||||
|
|
|
@ -27,6 +27,7 @@ private:
|
|||
Result OpenSystemAppletProxy(Out<SharedPointer<ISystemAppletProxy>> out_system_applet_proxy,
|
||||
ClientProcessId pid,
|
||||
InCopyHandle<Kernel::KProcess> process_handle);
|
||||
Result OpenSystemAppletProxyForDebug(Out<SharedPointer<ISystemAppletProxy>> out_proxy, ClientProcessId pid);
|
||||
Result OpenLibraryAppletProxy(Out<SharedPointer<ILibraryAppletProxy>> out_library_applet_proxy,
|
||||
ClientProcessId pid,
|
||||
InCopyHandle<Kernel::KProcess> process_handle,
|
||||
|
|
|
@ -32,6 +32,11 @@ IAppletCommonFunctions::IAppletCommonFunctions(Core::System& system_,
|
|||
{91, nullptr, "OpenNamedChannelAsChild"},
|
||||
{100, nullptr, "SetApplicationCoreUsageMode"},
|
||||
{300, D<&IAppletCommonFunctions::GetCurrentApplicationId>, "GetCurrentApplicationId"},
|
||||
{310, nullptr, "IsSystemAppletHomeMenu"}, //19.0.0+
|
||||
{311, nullptr, "Unknown311"},
|
||||
{320, nullptr, "SetGpuTimeSliceBoost"}, //19.0.0+
|
||||
{321, nullptr, "SetGpuTimeSliceBoostDueToApplication"}, //19.0.0+
|
||||
{350, nullptr, "Unknown350"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
|
|
@ -111,9 +111,11 @@ std::shared_ptr<ILibraryAppletAccessor> CreateGuestApplet(Core::System& system,
|
|||
Firmware1500 = 15,
|
||||
Firmware1600 = 16,
|
||||
Firmware1700 = 17,
|
||||
Firmware1800 = 18,
|
||||
Firmware1900 = 19,
|
||||
};
|
||||
|
||||
auto process = CreateProcess(system, program_id, Firmware1400, Firmware1700);
|
||||
auto process = CreateProcess(system, program_id, Firmware1400, Firmware1900);
|
||||
if (!process) {
|
||||
// Couldn't initialize the guest process
|
||||
return {};
|
||||
|
|
|
@ -15,7 +15,7 @@ IProcessWindingController::IProcessWindingController(Core::System& system_,
|
|||
static const FunctionInfo functions[] = {
|
||||
{0, D<&IProcessWindingController::GetLaunchReason>, "GetLaunchReason"},
|
||||
{11, D<&IProcessWindingController::OpenCallingLibraryApplet>, "OpenCallingLibraryApplet"},
|
||||
{21, nullptr, "PushContext"},
|
||||
{21, D<&IProcessWindingController::PushContext>, "PushContext"},
|
||||
{22, nullptr, "PopContext"},
|
||||
{23, nullptr, "CancelWindingReservation"},
|
||||
{30, nullptr, "WindAndDoReserved"},
|
||||
|
@ -51,4 +51,9 @@ Result IProcessWindingController::OpenCallingLibraryApplet(
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IProcessWindingController::PushContext() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
} // namespace Service::AM
|
||||
|
|
|
@ -21,7 +21,7 @@ private:
|
|||
Result GetLaunchReason(Out<AppletProcessLaunchReason> out_launch_reason);
|
||||
Result OpenCallingLibraryApplet(
|
||||
Out<SharedPointer<ILibraryAppletAccessor>> out_calling_library_applet);
|
||||
|
||||
Result PushContext();
|
||||
const std::shared_ptr<Applet> m_applet;
|
||||
};
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ ISelfController::ISelfController(Core::System& system_, std::shared_ptr<Applet>
|
|||
{110, nullptr, "SetApplicationAlbumUserData"},
|
||||
{120, D<&ISelfController::SaveCurrentScreenshot>, "SaveCurrentScreenshot"},
|
||||
{130, D<&ISelfController::SetRecordVolumeMuted>, "SetRecordVolumeMuted"},
|
||||
{230, D<&ISelfController::Unknown230>, "Unknown230"},
|
||||
{1000, nullptr, "GetDebugStorageChannel"},
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -394,6 +395,10 @@ Result ISelfController::SaveCurrentScreenshot(Capture::AlbumReportOption album_r
|
|||
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result ISelfController::Unknown230() {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called - function 230 (0xE6)");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result ISelfController::SetRecordVolumeMuted(bool muted) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called. muted={}", muted);
|
||||
|
|
|
@ -62,6 +62,7 @@ private:
|
|||
Result GetAccumulatedSuspendedTickChangedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result SetAlbumImageTakenNotificationEnabled(bool enabled);
|
||||
Result SaveCurrentScreenshot(Capture::AlbumReportOption album_report_option);
|
||||
Result Unknown230();
|
||||
Result SetRecordVolumeMuted(bool muted);
|
||||
|
||||
Kernel::KProcess* const m_process;
|
||||
|
|
|
@ -53,6 +53,8 @@ IAlbumAccessorService::IAlbumAccessorService(Core::System& system_,
|
|||
{8021, nullptr, "GetAlbumEntryFromApplicationAlbumEntryAruid"},
|
||||
{10011, nullptr, "SetInternalErrorConversionEnabled"},
|
||||
{50000, nullptr, "LoadMakerNoteInfoForDebug"},
|
||||
{50011, C<&IAlbumAccessorService::GetAlbumAccessResultForDebug>, "GetAlbumAccessResultForDebug"},
|
||||
{50012, C<&IAlbumAccessorService::SetAlbumAccessResultForDebug>, "SetAlbumAccessResultForDebug"},
|
||||
{60002, nullptr, "OpenAccessorSession"},
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -137,6 +139,15 @@ Result IAlbumAccessorService::LoadAlbumScreenShotThumbnailImageEx1(
|
|||
R_RETURN(TranslateResult(result));
|
||||
}
|
||||
|
||||
Result IAlbumAccessorService::GetAlbumAccessResultForDebug() {
|
||||
LOG_DEBUG(Service_Capture, "(STUBBED) called.");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IAlbumAccessorService::SetAlbumAccessResultForDebug() {
|
||||
LOG_DEBUG(Service_Capture, "(STUBBED) called.");
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result IAlbumAccessorService::TranslateResult(Result in_result) {
|
||||
if (in_result.IsSuccess()) {
|
||||
return in_result;
|
||||
|
|
|
@ -50,6 +50,10 @@ private:
|
|||
OutArray<u8, BufferAttr_HipcMapAlias | BufferAttr_HipcMapTransferAllowsNonSecure> out_image,
|
||||
OutArray<u8, BufferAttr_HipcMapAlias> out_buffer);
|
||||
|
||||
Result GetAlbumAccessResultForDebug();
|
||||
|
||||
Result SetAlbumAccessResultForDebug();
|
||||
|
||||
Result TranslateResult(Result in_result);
|
||||
|
||||
std::shared_ptr<AlbumManager> manager = nullptr;
|
||||
|
|
|
@ -303,6 +303,9 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
|
|||
{3013, nullptr, "IsGameCardEnabled"},
|
||||
{3014, nullptr, "IsLocalContentShareEnabled"},
|
||||
{3050, nullptr, "ListAssignELicenseTaskResult"},
|
||||
{4022, D<&IApplicationManagerInterface::Unknown4022>, "Unknown4022"},
|
||||
{4023, D<&IApplicationManagerInterface::Unknown4023>, "Unknown4023"},
|
||||
{4088, D<&IApplicationManagerInterface::Unknown4088>, "Unknown4088"},
|
||||
{9999, nullptr, "GetApplicationCertificate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
@ -509,6 +512,23 @@ Result IApplicationManagerInterface::CheckApplicationLaunchVersion(u64 applicati
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::Unknown4022(Out<u32> out_unknown) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) Unknown4022 called");
|
||||
*out_unknown = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::Unknown4023(Out<u32> out_unknown) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) Unknown4022 called");
|
||||
|
||||
*out_unknown = 0;
|
||||
R_SUCCEED();
|
||||
}
|
||||
Result IApplicationManagerInterface::Unknown4088() {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) Unknown4088 called");
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IApplicationManagerInterface::GetApplicationTerminateResult(Out<Result> out_result,
|
||||
u64 application_id) {
|
||||
LOG_WARNING(Service_NS, "(STUBBED) called. application_id={:016X}", application_id);
|
||||
|
|
|
@ -48,6 +48,9 @@ public:
|
|||
Result IsApplicationUpdateRequested(Out<bool> out_update_required, Out<u32> out_update_version,
|
||||
u64 application_id);
|
||||
Result CheckApplicationLaunchVersion(u64 application_id);
|
||||
Result Unknown4022(Out<u32> out_unknown);
|
||||
Result Unknown4023(Out<u32> out_unknown);
|
||||
Result Unknown4088();
|
||||
Result GetApplicationTerminateResult(Out<Result> out_result, u64 application_id);
|
||||
|
||||
private:
|
||||
|
|
|
@ -81,12 +81,12 @@ IParentalControlService::IParentalControlService(Core::System& system_, Capabili
|
|||
{1451, D<&IParentalControlService::StartPlayTimer>, "StartPlayTimer"},
|
||||
{1452, D<&IParentalControlService::StopPlayTimer>, "StopPlayTimer"},
|
||||
{1453, D<&IParentalControlService::IsPlayTimerEnabled>, "IsPlayTimerEnabled"},
|
||||
{1454, nullptr, "GetPlayTimerRemainingTime"},
|
||||
{1454, D<&IParentalControlService::GetPlayTimerRemainingTime>, "GetPlayTimerRemainingTime"},
|
||||
{1455, D<&IParentalControlService::IsRestrictedByPlayTimer>, "IsRestrictedByPlayTimer"},
|
||||
{1456, D<&IParentalControlService::GetPlayTimerSettingsOld>, "GetPlayTimerSettingsOld"},
|
||||
{1457, D<&IParentalControlService::GetPlayTimerEventToRequestSuspension>, "GetPlayTimerEventToRequestSuspension"},
|
||||
{1458, D<&IParentalControlService::IsPlayTimerAlarmDisabled>, "IsPlayTimerAlarmDisabled"},
|
||||
{1459, nullptr, "GetPlayTimerRemainingTimeDisplayInfo"}, // 20.0.0+
|
||||
{1459, D<&IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo>, "GetPlayTimerRemainingTimeDisplayInfo"}, // 20.0.0+
|
||||
{1471, nullptr, "NotifyWrongPinCodeInputManyTimes"},
|
||||
{1472, nullptr, "CancelNetworkRequest"},
|
||||
{1473, D<&IParentalControlService::GetUnlinkedEvent>, "GetUnlinkedEvent"},
|
||||
|
@ -388,6 +388,12 @@ Result IParentalControlService::IsPlayTimerEnabled(Out<bool> out_is_play_timer_e
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IParentalControlService::GetPlayTimerRemainingTime(Out<s32> out_remaining_minutes) {
|
||||
*out_remaining_minutes = 0;
|
||||
LOG_WARNING(Service_PCTL, "(STUBBED) called, remaining_minutes={}", *out_remaining_minutes);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IParentalControlService::IsRestrictedByPlayTimer(Out<bool> out_is_restricted_by_play_timer) {
|
||||
*out_is_restricted_by_play_timer = false;
|
||||
LOG_WARNING(Service_PCTL, "(STUBBED) called, restricted={}", *out_is_restricted_by_play_timer);
|
||||
|
@ -422,6 +428,15 @@ Result IParentalControlService::IsPlayTimerAlarmDisabled(Out<bool> out_play_time
|
|||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IParentalControlService::GetPlayTimerRemainingTimeDisplayInfo(
|
||||
Out<s32> out_remaining_minutes, Out<u32> out_unknown) {
|
||||
*out_remaining_minutes = 0;
|
||||
*out_unknown = 0;
|
||||
LOG_WARNING(Service_PCTL, "(STUBBED) called, remaining_minutes={}, unknown={}",
|
||||
*out_remaining_minutes, *out_unknown);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result IParentalControlService::GetUnlinkedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event) {
|
||||
LOG_INFO(Service_PCTL, "called");
|
||||
*out_event = unlinked_event.GetHandle();
|
||||
|
|
|
@ -45,10 +45,12 @@ private:
|
|||
Result StartPlayTimer();
|
||||
Result StopPlayTimer();
|
||||
Result IsPlayTimerEnabled(Out<bool> out_is_play_timer_enabled);
|
||||
Result GetPlayTimerRemainingTime(Out<s32> out_remaining_minutes);
|
||||
Result IsRestrictedByPlayTimer(Out<bool> out_is_restricted_by_play_timer);
|
||||
Result GetPlayTimerSettingsOld(Out<PlayTimerSettings> out_play_timer_settings);
|
||||
Result GetPlayTimerEventToRequestSuspension(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result IsPlayTimerAlarmDisabled(Out<bool> out_play_timer_alarm_disabled);
|
||||
Result GetPlayTimerRemainingTimeDisplayInfo(Out<s32> out_remaining_minutes, Out<u32> out_unknown);
|
||||
Result GetUnlinkedEvent(OutCopyHandle<Kernel::KReadableEvent> out_event);
|
||||
Result GetStereoVisionRestriction(Out<bool> out_stereo_vision_restriction);
|
||||
Result SetStereoVisionRestriction(bool stereo_vision_restriction);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue