summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp8
-rw-r--r--src/core/hle/kernel/k_process.cpp11
-rw-r--r--src/core/hle/kernel/k_process.h3
-rw-r--r--src/core/hle/kernel/svc.cpp130
-rw-r--r--src/core/hle/service/set/set.cpp9
-rw-r--r--src/core/hle/service/set/set.h1
-rw-r--r--src/core/hle/service/set/set_sys.cpp10
-rw-r--r--src/core/hle/service/set/set_sys.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp3
-rw-r--r--src/yuzu/bootmanager.cpp5
-rw-r--r--src/yuzu/bootmanager.h5
-rw-r--r--src/yuzu/configuration/config.cpp3
-rw-r--r--src/yuzu/configuration/configure_system.cpp4
-rw-r--r--src/yuzu/configuration/configure_system.ui14
-rw-r--r--src/yuzu/game_list.cpp14
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/main.cpp192
-rw-r--r--src/yuzu/main.h7
-rw-r--r--src/yuzu/uisettings.h1
21 files changed, 339 insertions, 91 deletions
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index d8ffe34c3..149e621f9 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -40,6 +40,7 @@ void LogSettings() {
40 LOG_INFO(Config, "yuzu Configuration:"); 40 LOG_INFO(Config, "yuzu Configuration:");
41 log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue()); 41 log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
42 log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0)); 42 log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
43 log_setting("System_DeviceName", values.device_name.GetValue());
43 log_setting("System_CurrentUser", values.current_user.GetValue()); 44 log_setting("System_CurrentUser", values.current_user.GetValue());
44 log_setting("System_LanguageIndex", values.language_index.GetValue()); 45 log_setting("System_LanguageIndex", values.language_index.GetValue());
45 log_setting("System_RegionIndex", values.region_index.GetValue()); 46 log_setting("System_RegionIndex", values.region_index.GetValue());
diff --git a/src/common/settings.h b/src/common/settings.h
index 7ce9ea23c..6b199af93 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -458,6 +458,7 @@ struct Values {
458 458
459 // System 459 // System
460 SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"}; 460 SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
461 Setting<std::string> device_name{"Yuzu", "device_name"};
461 // Measured in seconds since epoch 462 // Measured in seconds since epoch
462 std::optional<s64> custom_rtc; 463 std::optional<s64> custom_rtc;
463 // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc` 464 // Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 06010b8d1..e6479c131 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -167,6 +167,9 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
167 } 167 }
168 if (incoming) { 168 if (incoming) {
169 // Populate the object lists with the data in the IPC request. 169 // Populate the object lists with the data in the IPC request.
170 incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy);
171 incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move);
172
170 for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { 173 for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
171 incoming_copy_handles.push_back(rp.Pop<Handle>()); 174 incoming_copy_handles.push_back(rp.Pop<Handle>());
172 } 175 }
@@ -181,6 +184,11 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
181 } 184 }
182 } 185 }
183 186
187 buffer_x_desciptors.reserve(command_header->num_buf_x_descriptors);
188 buffer_a_desciptors.reserve(command_header->num_buf_a_descriptors);
189 buffer_b_desciptors.reserve(command_header->num_buf_b_descriptors);
190 buffer_w_desciptors.reserve(command_header->num_buf_w_descriptors);
191
184 for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) { 192 for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
185 buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>()); 193 buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
186 } 194 }
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index d1dc62401..a1abf5d68 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -285,6 +285,17 @@ void KProcess::UnregisterThread(KThread* thread) {
285 thread_list.remove(thread); 285 thread_list.remove(thread);
286} 286}
287 287
288u64 KProcess::GetFreeThreadCount() const {
289 if (resource_limit != nullptr) {
290 const auto current_value =
291 resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax);
292 const auto limit_value = resource_limit->GetLimitValue(LimitableResource::ThreadCountMax);
293 return limit_value - current_value;
294 } else {
295 return 0;
296 }
297}
298
288Result KProcess::Reset() { 299Result KProcess::Reset() {
289 // Lock the process and the scheduler. 300 // Lock the process and the scheduler.
290 KScopedLightLock lk(state_lock); 301 KScopedLightLock lk(state_lock);
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 2e0cc3d0b..09bf2f1d0 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -304,6 +304,9 @@ public:
304 /// from this process' thread list. 304 /// from this process' thread list.
305 void UnregisterThread(KThread* thread); 305 void UnregisterThread(KThread* thread);
306 306
307 /// Retrieves the number of available threads for this process.
308 u64 GetFreeThreadCount() const;
309
307 /// Clears the signaled state of the process if and only if it's signaled. 310 /// Clears the signaled state of the process if and only if it's signaled.
308 /// 311 ///
309 /// @pre The process must not be already terminated. If this is called on a 312 /// @pre The process must not be already terminated. If this is called on a
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index e520cab47..788ee2160 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -784,63 +784,29 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
784 LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id, 784 LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
785 info_sub_id, handle); 785 info_sub_id, handle);
786 786
787 enum class GetInfoType : u64 { 787 const auto info_id_type = static_cast<InfoType>(info_id);
788 // 1.0.0+
789 AllowedCPUCoreMask = 0,
790 AllowedThreadPriorityMask = 1,
791 MapRegionBaseAddr = 2,
792 MapRegionSize = 3,
793 HeapRegionBaseAddr = 4,
794 HeapRegionSize = 5,
795 TotalPhysicalMemoryAvailable = 6,
796 TotalPhysicalMemoryUsed = 7,
797 IsCurrentProcessBeingDebugged = 8,
798 RegisterResourceLimit = 9,
799 IdleTickCount = 10,
800 RandomEntropy = 11,
801 ThreadTickCount = 0xF0000002,
802 // 2.0.0+
803 ASLRRegionBaseAddr = 12,
804 ASLRRegionSize = 13,
805 StackRegionBaseAddr = 14,
806 StackRegionSize = 15,
807 // 3.0.0+
808 SystemResourceSize = 16,
809 SystemResourceUsage = 17,
810 TitleId = 18,
811 // 4.0.0+
812 PrivilegedProcessId = 19,
813 // 5.0.0+
814 UserExceptionContextAddr = 20,
815 // 6.0.0+
816 TotalPhysicalMemoryAvailableWithoutSystemResource = 21,
817 TotalPhysicalMemoryUsedWithoutSystemResource = 22,
818
819 // Homebrew only
820 MesosphereCurrentProcess = 65001,
821 };
822
823 const auto info_id_type = static_cast<GetInfoType>(info_id);
824 788
825 switch (info_id_type) { 789 switch (info_id_type) {
826 case GetInfoType::AllowedCPUCoreMask: 790 case InfoType::CoreMask:
827 case GetInfoType::AllowedThreadPriorityMask: 791 case InfoType::PriorityMask:
828 case GetInfoType::MapRegionBaseAddr: 792 case InfoType::AliasRegionAddress:
829 case GetInfoType::MapRegionSize: 793 case InfoType::AliasRegionSize:
830 case GetInfoType::HeapRegionBaseAddr: 794 case InfoType::HeapRegionAddress:
831 case GetInfoType::HeapRegionSize: 795 case InfoType::HeapRegionSize:
832 case GetInfoType::ASLRRegionBaseAddr: 796 case InfoType::AslrRegionAddress:
833 case GetInfoType::ASLRRegionSize: 797 case InfoType::AslrRegionSize:
834 case GetInfoType::StackRegionBaseAddr: 798 case InfoType::StackRegionAddress:
835 case GetInfoType::StackRegionSize: 799 case InfoType::StackRegionSize:
836 case GetInfoType::TotalPhysicalMemoryAvailable: 800 case InfoType::TotalMemorySize:
837 case GetInfoType::TotalPhysicalMemoryUsed: 801 case InfoType::UsedMemorySize:
838 case GetInfoType::SystemResourceSize: 802 case InfoType::SystemResourceSizeTotal:
839 case GetInfoType::SystemResourceUsage: 803 case InfoType::SystemResourceSizeUsed:
840 case GetInfoType::TitleId: 804 case InfoType::ProgramId:
841 case GetInfoType::UserExceptionContextAddr: 805 case InfoType::UserExceptionContextAddress:
842 case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: 806 case InfoType::TotalNonSystemMemorySize:
843 case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: { 807 case InfoType::UsedNonSystemMemorySize:
808 case InfoType::IsApplication:
809 case InfoType::FreeThreadCount: {
844 if (info_sub_id != 0) { 810 if (info_sub_id != 0) {
845 LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id, 811 LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
846 info_sub_id); 812 info_sub_id);
@@ -856,79 +822,83 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
856 } 822 }
857 823
858 switch (info_id_type) { 824 switch (info_id_type) {
859 case GetInfoType::AllowedCPUCoreMask: 825 case InfoType::CoreMask:
860 *result = process->GetCoreMask(); 826 *result = process->GetCoreMask();
861 return ResultSuccess; 827 return ResultSuccess;
862 828
863 case GetInfoType::AllowedThreadPriorityMask: 829 case InfoType::PriorityMask:
864 *result = process->GetPriorityMask(); 830 *result = process->GetPriorityMask();
865 return ResultSuccess; 831 return ResultSuccess;
866 832
867 case GetInfoType::MapRegionBaseAddr: 833 case InfoType::AliasRegionAddress:
868 *result = process->PageTable().GetAliasRegionStart(); 834 *result = process->PageTable().GetAliasRegionStart();
869 return ResultSuccess; 835 return ResultSuccess;
870 836
871 case GetInfoType::MapRegionSize: 837 case InfoType::AliasRegionSize:
872 *result = process->PageTable().GetAliasRegionSize(); 838 *result = process->PageTable().GetAliasRegionSize();
873 return ResultSuccess; 839 return ResultSuccess;
874 840
875 case GetInfoType::HeapRegionBaseAddr: 841 case InfoType::HeapRegionAddress:
876 *result = process->PageTable().GetHeapRegionStart(); 842 *result = process->PageTable().GetHeapRegionStart();
877 return ResultSuccess; 843 return ResultSuccess;
878 844
879 case GetInfoType::HeapRegionSize: 845 case InfoType::HeapRegionSize:
880 *result = process->PageTable().GetHeapRegionSize(); 846 *result = process->PageTable().GetHeapRegionSize();
881 return ResultSuccess; 847 return ResultSuccess;
882 848
883 case GetInfoType::ASLRRegionBaseAddr: 849 case InfoType::AslrRegionAddress:
884 *result = process->PageTable().GetAliasCodeRegionStart(); 850 *result = process->PageTable().GetAliasCodeRegionStart();
885 return ResultSuccess; 851 return ResultSuccess;
886 852
887 case GetInfoType::ASLRRegionSize: 853 case InfoType::AslrRegionSize:
888 *result = process->PageTable().GetAliasCodeRegionSize(); 854 *result = process->PageTable().GetAliasCodeRegionSize();
889 return ResultSuccess; 855 return ResultSuccess;
890 856
891 case GetInfoType::StackRegionBaseAddr: 857 case InfoType::StackRegionAddress:
892 *result = process->PageTable().GetStackRegionStart(); 858 *result = process->PageTable().GetStackRegionStart();
893 return ResultSuccess; 859 return ResultSuccess;
894 860
895 case GetInfoType::StackRegionSize: 861 case InfoType::StackRegionSize:
896 *result = process->PageTable().GetStackRegionSize(); 862 *result = process->PageTable().GetStackRegionSize();
897 return ResultSuccess; 863 return ResultSuccess;
898 864
899 case GetInfoType::TotalPhysicalMemoryAvailable: 865 case InfoType::TotalMemorySize:
900 *result = process->GetTotalPhysicalMemoryAvailable(); 866 *result = process->GetTotalPhysicalMemoryAvailable();
901 return ResultSuccess; 867 return ResultSuccess;
902 868
903 case GetInfoType::TotalPhysicalMemoryUsed: 869 case InfoType::UsedMemorySize:
904 *result = process->GetTotalPhysicalMemoryUsed(); 870 *result = process->GetTotalPhysicalMemoryUsed();
905 return ResultSuccess; 871 return ResultSuccess;
906 872
907 case GetInfoType::SystemResourceSize: 873 case InfoType::SystemResourceSizeTotal:
908 *result = process->GetSystemResourceSize(); 874 *result = process->GetSystemResourceSize();
909 return ResultSuccess; 875 return ResultSuccess;
910 876
911 case GetInfoType::SystemResourceUsage: 877 case InfoType::SystemResourceSizeUsed:
912 LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage"); 878 LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
913 *result = process->GetSystemResourceUsage(); 879 *result = process->GetSystemResourceUsage();
914 return ResultSuccess; 880 return ResultSuccess;
915 881
916 case GetInfoType::TitleId: 882 case InfoType::ProgramId:
917 *result = process->GetProgramID(); 883 *result = process->GetProgramID();
918 return ResultSuccess; 884 return ResultSuccess;
919 885
920 case GetInfoType::UserExceptionContextAddr: 886 case InfoType::UserExceptionContextAddress:
921 *result = process->GetProcessLocalRegionAddress(); 887 *result = process->GetProcessLocalRegionAddress();
922 return ResultSuccess; 888 return ResultSuccess;
923 889
924 case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource: 890 case InfoType::TotalNonSystemMemorySize:
925 *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource(); 891 *result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
926 return ResultSuccess; 892 return ResultSuccess;
927 893
928 case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: 894 case InfoType::UsedNonSystemMemorySize:
929 *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource(); 895 *result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
930 return ResultSuccess; 896 return ResultSuccess;
931 897
898 case InfoType::FreeThreadCount:
899 *result = process->GetFreeThreadCount();
900 return ResultSuccess;
901
932 default: 902 default:
933 break; 903 break;
934 } 904 }
@@ -937,11 +907,11 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
937 return ResultInvalidEnumValue; 907 return ResultInvalidEnumValue;
938 } 908 }
939 909
940 case GetInfoType::IsCurrentProcessBeingDebugged: 910 case InfoType::DebuggerAttached:
941 *result = 0; 911 *result = 0;
942 return ResultSuccess; 912 return ResultSuccess;
943 913
944 case GetInfoType::RegisterResourceLimit: { 914 case InfoType::ResourceLimit: {
945 if (handle != 0) { 915 if (handle != 0) {
946 LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle); 916 LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle);
947 return ResultInvalidHandle; 917 return ResultInvalidHandle;
@@ -969,7 +939,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
969 return ResultSuccess; 939 return ResultSuccess;
970 } 940 }
971 941
972 case GetInfoType::RandomEntropy: 942 case InfoType::RandomEntropy:
973 if (handle != 0) { 943 if (handle != 0) {
974 LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}", 944 LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}",
975 handle); 945 handle);
@@ -985,13 +955,13 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
985 *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id); 955 *result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id);
986 return ResultSuccess; 956 return ResultSuccess;
987 957
988 case GetInfoType::PrivilegedProcessId: 958 case InfoType::InitialProcessIdRange:
989 LOG_WARNING(Kernel_SVC, 959 LOG_WARNING(Kernel_SVC,
990 "(STUBBED) Attempted to query privileged process id bounds, returned 0"); 960 "(STUBBED) Attempted to query privileged process id bounds, returned 0");
991 *result = 0; 961 *result = 0;
992 return ResultSuccess; 962 return ResultSuccess;
993 963
994 case GetInfoType::ThreadTickCount: { 964 case InfoType::ThreadTickCount: {
995 constexpr u64 num_cpus = 4; 965 constexpr u64 num_cpus = 4;
996 if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) { 966 if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
997 LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus, 967 LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus,
@@ -1026,7 +996,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
1026 *result = out_ticks; 996 *result = out_ticks;
1027 return ResultSuccess; 997 return ResultSuccess;
1028 } 998 }
1029 case GetInfoType::IdleTickCount: { 999 case InfoType::IdleTickCount: {
1030 // Verify the input handle is invalid. 1000 // Verify the input handle is invalid.
1031 R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); 1001 R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
1032 1002
@@ -1040,7 +1010,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
1040 *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime(); 1010 *result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
1041 return ResultSuccess; 1011 return ResultSuccess;
1042 } 1012 }
1043 case GetInfoType::MesosphereCurrentProcess: { 1013 case InfoType::MesosphereCurrentProcess: {
1044 // Verify the input handle is invalid. 1014 // Verify the input handle is invalid.
1045 R_UNLESS(handle == InvalidHandle, ResultInvalidHandle); 1015 R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
1046 1016
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 4f1a8d6b7..16c5eaf75 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -191,6 +191,13 @@ void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
191 GetKeyCodeMapImpl(ctx); 191 GetKeyCodeMapImpl(ctx);
192} 192}
193 193
194void SET::GetDeviceNickName(Kernel::HLERequestContext& ctx) {
195 LOG_DEBUG(Service_SET, "called");
196 IPC::ResponseBuilder rb{ctx, 2};
197 rb.Push(ResultSuccess);
198 ctx.WriteBuffer(Settings::values.device_name.GetValue());
199}
200
194SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} { 201SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} {
195 // clang-format off 202 // clang-format off
196 static const FunctionInfo functions[] = { 203 static const FunctionInfo functions[] = {
@@ -205,7 +212,7 @@ SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} {
205 {8, &SET::GetQuestFlag, "GetQuestFlag"}, 212 {8, &SET::GetQuestFlag, "GetQuestFlag"},
206 {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"}, 213 {9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
207 {10, nullptr, "GetFirmwareVersionForDebug"}, 214 {10, nullptr, "GetFirmwareVersionForDebug"},
208 {11, nullptr, "GetDeviceNickName"}, 215 {11, &SET::GetDeviceNickName, "GetDeviceNickName"},
209 }; 216 };
210 // clang-format on 217 // clang-format on
211 218
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 60cad3e6f..375975711 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -50,6 +50,7 @@ private:
50 void GetRegionCode(Kernel::HLERequestContext& ctx); 50 void GetRegionCode(Kernel::HLERequestContext& ctx);
51 void GetKeyCodeMap(Kernel::HLERequestContext& ctx); 51 void GetKeyCodeMap(Kernel::HLERequestContext& ctx);
52 void GetKeyCodeMap2(Kernel::HLERequestContext& ctx); 52 void GetKeyCodeMap2(Kernel::HLERequestContext& ctx);
53 void GetDeviceNickName(Kernel::HLERequestContext& ctx);
53}; 54};
54 55
55} // namespace Service::Set 56} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index d7cea6aac..94c20edda 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -3,6 +3,7 @@
3 3
4#include "common/assert.h" 4#include "common/assert.h"
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "common/settings.h"
6#include "core/file_sys/errors.h" 7#include "core/file_sys/errors.h"
7#include "core/file_sys/system_archive/system_version.h" 8#include "core/file_sys/system_archive/system_version.h"
8#include "core/hle/ipc_helpers.h" 9#include "core/hle/ipc_helpers.h"
@@ -176,6 +177,13 @@ void SET_SYS::GetSettingsItemValue(Kernel::HLERequestContext& ctx) {
176 rb.Push(response); 177 rb.Push(response);
177} 178}
178 179
180void SET_SYS::GetDeviceNickName(Kernel::HLERequestContext& ctx) {
181 LOG_DEBUG(Service_SET, "called");
182 IPC::ResponseBuilder rb{ctx, 2};
183 rb.Push(ResultSuccess);
184 ctx.WriteBuffer(::Settings::values.device_name.GetValue());
185}
186
179SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} { 187SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
180 // clang-format off 188 // clang-format off
181 static const FunctionInfo functions[] = { 189 static const FunctionInfo functions[] = {
@@ -253,7 +261,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
253 {74, nullptr, "SetWirelessLanEnableFlag"}, 261 {74, nullptr, "SetWirelessLanEnableFlag"},
254 {75, nullptr, "GetInitialLaunchSettings"}, 262 {75, nullptr, "GetInitialLaunchSettings"},
255 {76, nullptr, "SetInitialLaunchSettings"}, 263 {76, nullptr, "SetInitialLaunchSettings"},
256 {77, nullptr, "GetDeviceNickName"}, 264 {77, &SET_SYS::GetDeviceNickName, "GetDeviceNickName"},
257 {78, nullptr, "SetDeviceNickName"}, 265 {78, nullptr, "SetDeviceNickName"},
258 {79, nullptr, "GetProductModel"}, 266 {79, nullptr, "GetProductModel"},
259 {80, nullptr, "GetLdnChannel"}, 267 {80, nullptr, "GetLdnChannel"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 258ef8c57..464ac3da1 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -29,6 +29,7 @@ private:
29 void GetFirmwareVersion2(Kernel::HLERequestContext& ctx); 29 void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
30 void GetColorSetId(Kernel::HLERequestContext& ctx); 30 void GetColorSetId(Kernel::HLERequestContext& ctx);
31 void SetColorSetId(Kernel::HLERequestContext& ctx); 31 void SetColorSetId(Kernel::HLERequestContext& ctx);
32 void GetDeviceNickName(Kernel::HLERequestContext& ctx);
32 33
33 ColorSet color_set = ColorSet::BasicWhite; 34 ColorSet color_set = ColorSet::BasicWhite;
34}; 35};
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index a8b82abae..4b7126c30 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -658,8 +658,7 @@ void RasterizerVulkan::BeginTransformFeedback() {
658 return; 658 return;
659 } 659 }
660 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) || 660 UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
661 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation) || 661 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
662 regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry));
663 scheduler.Record( 662 scheduler.Record(
664 [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); }); 663 [](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); });
665} 664}
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 1a47fb9c9..642f96690 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -79,6 +79,11 @@ void EmuThread::run() {
79 79
80 system.GetCpuManager().OnGpuReady(); 80 system.GetCpuManager().OnGpuReady();
81 81
82 system.RegisterExitCallback([this]() {
83 stop_source.request_stop();
84 SetRunning(false);
85 });
86
82 // Holds whether the cpu was running during the last iteration, 87 // Holds whether the cpu was running during the last iteration,
83 // so that the DebugModeLeft signal can be emitted before the 88 // so that the DebugModeLeft signal can be emitted before the
84 // next execution step 89 // next execution step
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index f4deae4ee..f0edad6e4 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -84,9 +84,10 @@ public:
84 } 84 }
85 85
86 /** 86 /**
87 * Requests for the emulation thread to stop running 87 * Requests for the emulation thread to immediately stop running
88 */ 88 */
89 void RequestStop() { 89 void ForceStop() {
90 LOG_WARNING(Frontend, "Force stopping EmuThread");
90 stop_source.request_stop(); 91 stop_source.request_stop();
91 SetRunning(false); 92 SetRunning(false);
92 } 93 }
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 90fb4b0a4..a8d47a2f9 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -783,6 +783,8 @@ void Config::ReadSystemValues() {
783 } 783 }
784 } 784 }
785 785
786 ReadBasicSetting(Settings::values.device_name);
787
786 if (global) { 788 if (global) {
787 ReadBasicSetting(Settings::values.current_user); 789 ReadBasicSetting(Settings::values.current_user);
788 Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0, 790 Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
@@ -1405,6 +1407,7 @@ void Config::SaveSystemValues() {
1405 Settings::values.rng_seed.UsingGlobal()); 1407 Settings::values.rng_seed.UsingGlobal());
1406 WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.GetValue(global).value_or(0), 1408 WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.GetValue(global).value_or(0),
1407 0, Settings::values.rng_seed.UsingGlobal()); 1409 0, Settings::values.rng_seed.UsingGlobal());
1410 WriteBasicSetting(Settings::values.device_name);
1408 1411
1409 if (global) { 1412 if (global) {
1410 WriteBasicSetting(Settings::values.current_user); 1413 WriteBasicSetting(Settings::values.current_user);
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index bc9d9d77a..9b14e5903 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -72,6 +72,8 @@ void ConfigureSystem::SetConfiguration() {
72 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value()); 72 ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
73 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value()); 73 ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
74 ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time)); 74 ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));
75 ui->device_name_edit->setText(
76 QString::fromUtf8(Settings::values.device_name.GetValue().c_str()));
75 77
76 if (Settings::IsConfiguringGlobal()) { 78 if (Settings::IsConfiguringGlobal()) {
77 ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue()); 79 ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
@@ -115,6 +117,8 @@ void ConfigureSystem::ApplyConfiguration() {
115 } 117 }
116 } 118 }
117 119
120 Settings::values.device_name = ui->device_name_edit->text().toStdString();
121
118 if (!enabled) { 122 if (!enabled) {
119 return; 123 return;
120 } 124 }
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index b234ea87b..46892f5c1 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -432,6 +432,13 @@
432 </property> 432 </property>
433 </widget> 433 </widget>
434 </item> 434 </item>
435 <item row="7" column="0">
436 <widget class="QLabel" name="device_name_label">
437 <property name="text">
438 <string>Device Name</string>
439 </property>
440 </widget>
441 </item>
435 <item row="3" column="1"> 442 <item row="3" column="1">
436 <widget class="QComboBox" name="combo_sound"> 443 <widget class="QComboBox" name="combo_sound">
437 <item> 444 <item>
@@ -476,6 +483,13 @@
476 </property> 483 </property>
477 </widget> 484 </widget>
478 </item> 485 </item>
486 <item row="7" column="1">
487 <widget class="QLineEdit" name="device_name_edit">
488 <property name="maxLength">
489 <number>128</number>
490 </property>
491 </widget>
492 </item>
479 <item row="6" column="1"> 493 <item row="6" column="1">
480 <widget class="QLineEdit" name="rng_seed_edit"> 494 <widget class="QLineEdit" name="rng_seed_edit">
481 <property name="sizePolicy"> 495 <property name="sizePolicy">
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 5c33c1b0f..22aa19c56 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -554,6 +554,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
554 QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); 554 QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
555 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 555 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
556 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 556 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
557#ifndef WIN32
558 QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
559 QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
560 QAction* create_applications_menu_shortcut =
561 shortcut_menu->addAction(tr("Add to Applications Menu"));
562#endif
557 context_menu.addSeparator(); 563 context_menu.addSeparator();
558 QAction* properties = context_menu.addAction(tr("Properties")); 564 QAction* properties = context_menu.addAction(tr("Properties"));
559 565
@@ -619,6 +625,14 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
619 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { 625 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
620 emit NavigateToGamedbEntryRequested(program_id, compatibility_list); 626 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
621 }); 627 });
628#ifndef WIN32
629 connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
630 emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
631 });
632 connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
633 emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
634 });
635#endif
622 connect(properties, &QAction::triggered, 636 connect(properties, &QAction::triggered,
623 [this, path]() { emit OpenPerGameGeneralRequested(path); }); 637 [this, path]() { emit OpenPerGameGeneralRequested(path); });
624}; 638};
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index cdf085019..f7ff93ed9 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -52,6 +52,11 @@ enum class DumpRomFSTarget {
52 SDMC, 52 SDMC,
53}; 53};
54 54
55enum class GameListShortcutTarget {
56 Desktop,
57 Applications,
58};
59
55enum class InstalledEntryType { 60enum class InstalledEntryType {
56 Game, 61 Game,
57 Update, 62 Update,
@@ -108,6 +113,8 @@ signals:
108 const std::string& game_path); 113 const std::string& game_path);
109 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); 114 void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
110 void CopyTIDRequested(u64 program_id); 115 void CopyTIDRequested(u64 program_id);
116 void CreateShortcut(u64 program_id, const std::string& game_path,
117 GameListShortcutTarget target);
111 void NavigateToGamedbEntryRequested(u64 program_id, 118 void NavigateToGamedbEntryRequested(u64 program_id,
112 const CompatibilityList& compatibility_list); 119 const CompatibilityList& compatibility_list);
113 void OpenPerGameGeneralRequested(const std::string& file); 120 void OpenPerGameGeneralRequested(const std::string& file);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 885e24990..70552bdb8 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -4,6 +4,8 @@
4#include <cinttypes> 4#include <cinttypes>
5#include <clocale> 5#include <clocale>
6#include <cmath> 6#include <cmath>
7#include <fstream>
8#include <iostream>
7#include <memory> 9#include <memory>
8#include <thread> 10#include <thread>
9#ifdef __APPLE__ 11#ifdef __APPLE__
@@ -1249,6 +1251,7 @@ void GMainWindow::ConnectWidgetEvents() {
1249 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 1251 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
1250 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 1252 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
1251 &GMainWindow::OnGameListNavigateToGamedbEntry); 1253 &GMainWindow::OnGameListNavigateToGamedbEntry);
1254 connect(game_list, &GameList::CreateShortcut, this, &GMainWindow::OnGameListCreateShortcut);
1252 connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory); 1255 connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
1253 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this, 1256 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
1254 &GMainWindow::OnGameListAddDirectory); 1257 &GMainWindow::OnGameListAddDirectory);
@@ -1707,9 +1710,6 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
1707 system->RegisterExecuteProgramCallback( 1710 system->RegisterExecuteProgramCallback(
1708 [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); }); 1711 [this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
1709 1712
1710 // Register an Exit callback such that Core can exit the currently running application.
1711 system->RegisterExitCallback([this]() { render_window->Exit(); });
1712
1713 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); 1713 connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
1714 connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity); 1714 connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
1715 // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views 1715 // BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
@@ -1796,12 +1796,16 @@ void GMainWindow::ShutdownGame() {
1796 system->SetShuttingDown(true); 1796 system->SetShuttingDown(true);
1797 system->DetachDebugger(); 1797 system->DetachDebugger();
1798 discord_rpc->Pause(); 1798 discord_rpc->Pause();
1799 emu_thread->RequestStop(); 1799
1800 RequestGameExit();
1800 1801
1801 emit EmulationStopping(); 1802 emit EmulationStopping();
1802 1803
1803 // Wait for emulation thread to complete and delete it 1804 // Wait for emulation thread to complete and delete it
1804 emu_thread->wait(); 1805 if (!emu_thread->wait(5000)) {
1806 emu_thread->ForceStop();
1807 emu_thread->wait();
1808 }
1805 emu_thread = nullptr; 1809 emu_thread = nullptr;
1806 1810
1807 emulation_running = false; 1811 emulation_running = false;
@@ -2378,6 +2382,152 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
2378 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); 2382 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
2379} 2383}
2380 2384
2385void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
2386 GameListShortcutTarget target) {
2387 // Get path to yuzu executable
2388 const QStringList args = QApplication::arguments();
2389 std::filesystem::path yuzu_command = args[0].toStdString();
2390
2391#if defined(__linux__) || defined(__FreeBSD__)
2392 // If relative path, make it an absolute path
2393 if (yuzu_command.c_str()[0] == '.') {
2394 yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
2395 }
2396
2397#if defined(__linux__)
2398 // Warn once if we are making a shortcut to a volatile AppImage
2399 const std::string appimage_ending =
2400 std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
2401 if (yuzu_command.string().ends_with(appimage_ending) &&
2402 !UISettings::values.shortcut_already_warned) {
2403 if (QMessageBox::warning(this, tr("Create Shortcut"),
2404 tr("This will create a shortcut to the current AppImage. This may "
2405 "not work well if you update. Continue?"),
2406 QMessageBox::StandardButton::Ok |
2407 QMessageBox::StandardButton::Cancel) ==
2408 QMessageBox::StandardButton::Cancel) {
2409 return;
2410 }
2411 UISettings::values.shortcut_already_warned = true;
2412 }
2413#endif // __linux__
2414#endif // __linux__ || __FreeBSD__
2415
2416 std::filesystem::path target_directory{};
2417 // Determine target directory for shortcut
2418#if defined(__linux__) || defined(__FreeBSD__)
2419 const char* home = std::getenv("HOME");
2420 const std::filesystem::path home_path = (home == nullptr ? "~" : home);
2421 const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
2422
2423 if (target == GameListShortcutTarget::Desktop) {
2424 target_directory = home_path / "Desktop";
2425 if (!Common::FS::IsDir(target_directory)) {
2426 QMessageBox::critical(
2427 this, tr("Create Shortcut"),
2428 tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
2429 .arg(QString::fromStdString(target_directory)),
2430 QMessageBox::StandardButton::Ok);
2431 return;
2432 }
2433 } else if (target == GameListShortcutTarget::Applications) {
2434 target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
2435 "applications";
2436 if (!Common::FS::CreateDirs(target_directory)) {
2437 QMessageBox::critical(this, tr("Create Shortcut"),
2438 tr("Cannot create shortcut in applications menu. Path \"%1\" "
2439 "does not exist and cannot be created.")
2440 .arg(QString::fromStdString(target_directory)),
2441 QMessageBox::StandardButton::Ok);
2442 return;
2443 }
2444 }
2445#endif
2446
2447 const std::string game_file_name = std::filesystem::path(game_path).filename().string();
2448 // Determine full paths for icon and shortcut
2449#if defined(__linux__) || defined(__FreeBSD__)
2450 std::filesystem::path system_icons_path =
2451 (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
2452 "icons/hicolor/256x256";
2453 if (!Common::FS::CreateDirs(system_icons_path)) {
2454 QMessageBox::critical(
2455 this, tr("Create Icon"),
2456 tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
2457 .arg(QString::fromStdString(system_icons_path)),
2458 QMessageBox::StandardButton::Ok);
2459 return;
2460 }
2461 std::filesystem::path icon_path =
2462 system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name)
2463 : fmt::format("yuzu-{:016X}.png", program_id));
2464 const std::filesystem::path shortcut_path =
2465 target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
2466 : fmt::format("yuzu-{:016X}.desktop", program_id));
2467#else
2468 const std::filesystem::path icon_path{};
2469 const std::filesystem::path shortcut_path{};
2470#endif
2471
2472 // Get title from game file
2473 const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
2474 system->GetContentProvider()};
2475 const auto control = pm.GetControlMetadata();
2476 const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
2477
2478 std::string title{fmt::format("{:016X}", program_id)};
2479
2480 if (control.first != nullptr) {
2481 title = control.first->GetApplicationName();
2482 } else {
2483 loader->ReadTitle(title);
2484 }
2485
2486 // Get icon from game file
2487 std::vector<u8> icon_image_file{};
2488 if (control.second != nullptr) {
2489 icon_image_file = control.second->ReadAllBytes();
2490 } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
2491 LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
2492 }
2493
2494 QImage icon_jpeg =
2495 QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
2496#if defined(__linux__) || defined(__FreeBSD__)
2497 // Convert and write the icon as a PNG
2498 if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
2499 LOG_ERROR(Frontend, "Could not write icon as PNG to file");
2500 } else {
2501 LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
2502 }
2503#endif // __linux__
2504
2505#if defined(__linux__) || defined(__FreeBSD__)
2506 const std::string comment =
2507 tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
2508 const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
2509 const std::string categories = "Game;Emulator;Qt;";
2510 const std::string keywords = "Switch;Nintendo;";
2511#else
2512 const std::string comment{};
2513 const std::string arguments{};
2514 const std::string categories{};
2515 const std::string keywords{};
2516#endif
2517 if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
2518 yuzu_command.string(), arguments, categories, keywords)) {
2519 QMessageBox::critical(this, tr("Create Shortcut"),
2520 tr("Failed to create a shortcut at %1")
2521 .arg(QString::fromStdString(shortcut_path.string())));
2522 return;
2523 }
2524
2525 LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string());
2526 QMessageBox::information(
2527 this, tr("Create Shortcut"),
2528 tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
2529}
2530
2381void GMainWindow::OnGameListOpenDirectory(const QString& directory) { 2531void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
2382 std::filesystem::path fs_path; 2532 std::filesystem::path fs_path;
2383 if (directory == QStringLiteral("SDMC")) { 2533 if (directory == QStringLiteral("SDMC")) {
@@ -3301,6 +3451,38 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
3301 } 3451 }
3302} 3452}
3303 3453
3454bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
3455 const std::string& comment, const std::string& icon_path,
3456 const std::string& command, const std::string& arguments,
3457 const std::string& categories, const std::string& keywords) {
3458#if defined(__linux__) || defined(__FreeBSD__)
3459 // This desktop file template was writting referencing
3460 // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
3461 std::string shortcut_contents{};
3462 shortcut_contents.append("[Desktop Entry]\n");
3463 shortcut_contents.append("Type=Application\n");
3464 shortcut_contents.append("Version=1.0\n");
3465 shortcut_contents.append(fmt::format("Name={:s}\n", title));
3466 shortcut_contents.append(fmt::format("Comment={:s}\n", comment));
3467 shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path));
3468 shortcut_contents.append(fmt::format("TryExec={:s}\n", command));
3469 shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments));
3470 shortcut_contents.append(fmt::format("Categories={:s}\n", categories));
3471 shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords));
3472
3473 std::ofstream shortcut_stream(shortcut_path);
3474 if (!shortcut_stream.is_open()) {
3475 LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path);
3476 return false;
3477 }
3478 shortcut_stream << shortcut_contents;
3479 shortcut_stream.close();
3480
3481 return true;
3482#endif
3483 return false;
3484}
3485
3304void GMainWindow::OnLoadAmiibo() { 3486void GMainWindow::OnLoadAmiibo() {
3305 if (emu_thread == nullptr || !emu_thread->IsRunning()) { 3487 if (emu_thread == nullptr || !emu_thread->IsRunning()) {
3306 return; 3488 return;
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 27644fae5..1047ba276 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -38,6 +38,7 @@ class QProgressDialog;
38class WaitTreeWidget; 38class WaitTreeWidget;
39enum class GameListOpenTarget; 39enum class GameListOpenTarget;
40enum class GameListRemoveTarget; 40enum class GameListRemoveTarget;
41enum class GameListShortcutTarget;
41enum class DumpRomFSTarget; 42enum class DumpRomFSTarget;
42enum class InstalledEntryType; 43enum class InstalledEntryType;
43class GameListPlaceholder; 44class GameListPlaceholder;
@@ -293,6 +294,8 @@ private slots:
293 void OnGameListCopyTID(u64 program_id); 294 void OnGameListCopyTID(u64 program_id);
294 void OnGameListNavigateToGamedbEntry(u64 program_id, 295 void OnGameListNavigateToGamedbEntry(u64 program_id,
295 const CompatibilityList& compatibility_list); 296 const CompatibilityList& compatibility_list);
297 void OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
298 GameListShortcutTarget target);
296 void OnGameListOpenDirectory(const QString& directory); 299 void OnGameListOpenDirectory(const QString& directory);
297 void OnGameListAddDirectory(); 300 void OnGameListAddDirectory();
298 void OnGameListShowList(bool show); 301 void OnGameListShowList(bool show);
@@ -366,6 +369,10 @@ private:
366 bool CheckDarkMode(); 369 bool CheckDarkMode();
367 370
368 QString GetTasStateDescription() const; 371 QString GetTasStateDescription() const;
372 bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
373 const std::string& comment, const std::string& icon_path,
374 const std::string& command, const std::string& arguments,
375 const std::string& categories, const std::string& keywords);
369 376
370 std::unique_ptr<Ui::MainWindow> ui; 377 std::unique_ptr<Ui::MainWindow> ui;
371 378
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 452038cd9..2006b883e 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -138,6 +138,7 @@ struct Values {
138 138
139 bool configuration_applied; 139 bool configuration_applied;
140 bool reset_to_defaults; 140 bool reset_to_defaults;
141 bool shortcut_already_warned{false};
141 Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"}; 142 Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
142}; 143};
143 144