|
9 | 9 | #include "yuzu_common/fs/path_util.h" |
10 | 10 | #include "yuzu_common/logging/log.h" |
11 | 11 | #include "yuzu_common/polyfill_ranges.h" |
| 12 | +#include "yuzu_common/stb.h" |
12 | 13 | #include "yuzu_common/string_util.h" |
13 | 14 | #include "yuzu_common/swap.h" |
14 | 15 | #include "core/constants.h" |
@@ -42,6 +43,31 @@ static void JPGToMemory(void* context, void* data, int len) { |
42 | 43 | jpg_image->insert(jpg_image->end(), jpg, jpg + len); |
43 | 44 | } |
44 | 45 |
|
| 46 | +static void SanitizeJPEGImageSize(std::vector<u8>& image) { |
| 47 | + constexpr std::size_t max_jpeg_image_size = 0x20000; |
| 48 | + constexpr int profile_dimensions = 256; |
| 49 | + int original_width, original_height, color_channels; |
| 50 | + |
| 51 | + const auto plain_image = |
| 52 | + stbi_load_from_memory(image.data(), static_cast<int>(image.size()), &original_width, |
| 53 | + &original_height, &color_channels, STBI_rgb); |
| 54 | + |
| 55 | + // Resize image to match 256*256 |
| 56 | + if (original_width != profile_dimensions || original_height != profile_dimensions) { |
| 57 | + // Use vector instead of array to avoid overflowing the stack |
| 58 | + std::vector<u8> out_image(profile_dimensions * profile_dimensions * STBI_rgb); |
| 59 | + stbir_resize_uint8_srgb(plain_image, original_width, original_height, 0, out_image.data(), |
| 60 | + profile_dimensions, profile_dimensions, 0, STBI_rgb, 0, |
| 61 | + STBIR_FILTER_BOX); |
| 62 | + image.clear(); |
| 63 | + if (!stbi_write_jpg_to_func(JPGToMemory, &image, profile_dimensions, profile_dimensions, |
| 64 | + STBI_rgb, out_image.data(), 0)) { |
| 65 | + LOG_ERROR(Service_ACC, "Failed to resize the user provided image."); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + image.resize(std::min(image.size(), max_jpeg_image_size)); |
| 70 | +} |
45 | 71 |
|
46 | 72 | class IManagerForSystemService final : public ServiceFramework<IManagerForSystemService> { |
47 | 73 | public: |
@@ -286,6 +312,8 @@ class IProfileCommon : public ServiceFramework<IProfileCommon> { |
286 | 312 | static const FunctionInfo functions[] = { |
287 | 313 | {0, &IProfileCommon::Get, "Get"}, |
288 | 314 | {1, &IProfileCommon::GetBase, "GetBase"}, |
| 315 | + {10, &IProfileCommon::GetImageSize, "GetImageSize"}, |
| 316 | + {11, &IProfileCommon::LoadImage, "LoadImage"}, |
289 | 317 | }; |
290 | 318 |
|
291 | 319 | RegisterHandlers(functions); |
@@ -332,6 +360,58 @@ class IProfileCommon : public ServiceFramework<IProfileCommon> { |
332 | 360 | } |
333 | 361 | } |
334 | 362 |
|
| 363 | + void LoadImage(HLERequestContext& ctx) { |
| 364 | + LOG_DEBUG(Service_ACC, "called"); |
| 365 | + |
| 366 | + IPC::ResponseBuilder rb{ctx, 3}; |
| 367 | + rb.Push(ResultSuccess); |
| 368 | + |
| 369 | + const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, |
| 370 | + Common::FS::FileType::BinaryFile); |
| 371 | + if (!image.IsOpen()) { |
| 372 | + LOG_WARNING(Service_ACC, |
| 373 | + "Failed to load user provided image! Falling back to built-in backup..."); |
| 374 | + ctx.WriteBuffer(Core::Constants::ACCOUNT_BACKUP_JPEG); |
| 375 | + rb.Push(static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); |
| 376 | + return; |
| 377 | + } |
| 378 | + |
| 379 | + std::vector<u8> buffer(image.GetSize()); |
| 380 | + |
| 381 | + if (image.Read(buffer) != buffer.size()) { |
| 382 | + LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image."); |
| 383 | + } |
| 384 | + |
| 385 | + SanitizeJPEGImageSize(buffer); |
| 386 | + |
| 387 | + ctx.WriteBuffer(buffer); |
| 388 | + rb.Push(static_cast<u32>(buffer.size())); |
| 389 | + } |
| 390 | + |
| 391 | + void GetImageSize(HLERequestContext& ctx) { |
| 392 | + LOG_DEBUG(Service_ACC, "called"); |
| 393 | + IPC::ResponseBuilder rb{ctx, 3}; |
| 394 | + rb.Push(ResultSuccess); |
| 395 | + |
| 396 | + const Common::FS::IOFile image(GetImagePath(user_id), Common::FS::FileAccessMode::Read, |
| 397 | + Common::FS::FileType::BinaryFile); |
| 398 | + |
| 399 | + if (!image.IsOpen()) { |
| 400 | + LOG_WARNING(Service_ACC, |
| 401 | + "Failed to load user provided image! Falling back to built-in backup..."); |
| 402 | + rb.Push(static_cast<u32>(Core::Constants::ACCOUNT_BACKUP_JPEG.size())); |
| 403 | + return; |
| 404 | + } |
| 405 | + |
| 406 | + std::vector<u8> buffer(image.GetSize()); |
| 407 | + |
| 408 | + if (image.Read(buffer) != buffer.size()) { |
| 409 | + LOG_ERROR(Service_ACC, "Failed to read all the bytes in the user provided image."); |
| 410 | + } |
| 411 | + |
| 412 | + SanitizeJPEGImageSize(buffer); |
| 413 | + rb.Push(static_cast<u32>(buffer.size())); |
| 414 | + } |
335 | 415 |
|
336 | 416 | void Store(HLERequestContext& ctx) { |
337 | 417 | IPC::RequestParser rp{ctx}; |
@@ -708,13 +788,74 @@ void Module::Interface::IsUserRegistrationRequestPermitted(HLERequestContext& ct |
708 | 788 | rb.Push(profile_manager->CanSystemRegisterUser()); |
709 | 789 | } |
710 | 790 |
|
| 791 | +void Module::Interface::InitializeApplicationInfo(HLERequestContext& ctx) { |
| 792 | + LOG_DEBUG(Service_ACC, "called"); |
| 793 | + IPC::ResponseBuilder rb{ctx, 2}; |
| 794 | + rb.Push(InitializeApplicationInfoBase()); |
| 795 | +} |
| 796 | + |
| 797 | +void Module::Interface::InitializeApplicationInfoRestricted(HLERequestContext& ctx) { |
| 798 | + LOG_WARNING(Service_ACC, "(Partial implementation) called"); |
| 799 | + |
| 800 | + // TODO(ogniK): We require checking if the user actually owns the title and what not. As of |
| 801 | + // currently, we assume the user owns the title. InitializeApplicationInfoBase SHOULD be called |
| 802 | + // first then we do extra checks if the game is a digital copy. |
| 803 | + |
| 804 | + IPC::ResponseBuilder rb{ctx, 2}; |
| 805 | + rb.Push(InitializeApplicationInfoBase()); |
| 806 | +} |
| 807 | + |
| 808 | +Result Module::Interface::InitializeApplicationInfoBase() { |
| 809 | + if (application_info) { |
| 810 | + LOG_ERROR(Service_ACC, "Application already initialized"); |
| 811 | + return Account::ResultApplicationInfoAlreadyInitialized; |
| 812 | + } |
| 813 | + |
| 814 | + // TODO(ogniK): This should be changed to reflect the target process for when we have multiple |
| 815 | + // processes emulated. As we don't actually have pid support we should assume we're just using |
| 816 | + // our own process |
| 817 | + Glue::ApplicationLaunchProperty launch_property{}; |
| 818 | + const auto result = system.GetARPManager().GetLaunchProperty( |
| 819 | + &launch_property, system.GetApplicationProcessProgramID()); |
| 820 | + |
| 821 | + if (result != ResultSuccess) { |
| 822 | + LOG_ERROR(Service_ACC, "Failed to get launch property"); |
| 823 | + return Account::ResultInvalidApplication; |
| 824 | + } |
| 825 | + |
| 826 | + switch (launch_property.base_game_storage_id) { |
| 827 | + case StorageId::GameCard: |
| 828 | + application_info.application_type = ApplicationType::GameCard; |
| 829 | + break; |
| 830 | + case StorageId::Host: |
| 831 | + case StorageId::NandUser: |
| 832 | + case StorageId::SdCard: |
| 833 | + case StorageId::None: // Yuzu specific, differs from hardware |
| 834 | + application_info.application_type = ApplicationType::Digital; |
| 835 | + break; |
| 836 | + default: |
| 837 | + LOG_ERROR(Service_ACC, "Invalid game storage ID! storage_id={}", |
| 838 | + launch_property.base_game_storage_id); |
| 839 | + return Account::ResultInvalidApplication; |
| 840 | + } |
| 841 | + |
| 842 | + LOG_WARNING(Service_ACC, "ApplicationInfo init required"); |
| 843 | + // TODO(ogniK): Actual initialization here |
| 844 | + return ResultSuccess; |
| 845 | +} |
| 846 | + |
711 | 847 | void Module::Interface::GetBaasAccountManagerForApplication(HLERequestContext& ctx) { |
712 | 848 | LOG_DEBUG(Service_ACC, "called"); |
713 | 849 | IPC::ResponseBuilder rb{ctx, 2, 0, 1}; |
714 | 850 | rb.Push(ResultSuccess); |
715 | 851 | rb.PushIpcInterface<IManagerForApplication>(system, profile_manager); |
716 | 852 | } |
717 | 853 |
|
| 854 | +void Module::Interface::IsUserAccountSwitchLocked(HLERequestContext& ctx) { |
| 855 | + LOG_DEBUG(Service_ACC, "called"); |
| 856 | + UNIMPLEMENTED(); |
| 857 | +} |
| 858 | + |
718 | 859 | void Module::Interface::InitializeApplicationInfoV2(HLERequestContext& ctx) { |
719 | 860 | LOG_WARNING(Service_ACC, "(STUBBED) called"); |
720 | 861 |
|
|
0 commit comments