Compare commits
94 Commits
android-66
...
android-73
Author | SHA1 | Date | |
---|---|---|---|
bbe918aa01 | |||
7282976de3 | |||
c484a61515 | |||
9912704234 | |||
fe771b59f4 | |||
d26c76180d | |||
62d473305d | |||
13d551846a | |||
c05ea35f78 | |||
7f705870d2 | |||
821037e18f | |||
a3f235f8a2 | |||
260bfc4bd2 | |||
8950fe79ad | |||
c8b9467f50 | |||
0d4aa9125e | |||
aa6afb0cfe | |||
832a2fcc69 | |||
958bed4545 | |||
0c688b0bf5 | |||
7e2bd395bc | |||
19053ab631 | |||
6481f4e937 | |||
d1deff6b07 | |||
e8aaab2fc1 | |||
21b133de40 | |||
0c55248f92 | |||
b394389170 | |||
5eceab3ce6 | |||
3ef9673360 | |||
8baed5d95d | |||
4a3cbf0021 | |||
21ecf01a17 | |||
5d52d73c4b | |||
fea5b758bc | |||
c656105a6c | |||
04352a9aef | |||
48dec7e0c9 | |||
b5f99164f1 | |||
eb4ddb2868 | |||
9d7eebde7b | |||
fe70c6f481 | |||
8fb9f78e83 | |||
c2961454fe | |||
5ffa1049ae | |||
3ae3706c84 | |||
2d2c176f03 | |||
0eef4a6c94 | |||
22be3008f8 | |||
7f98f4a38b | |||
0098ecb609 | |||
85e1754728 | |||
3f52b5167b | |||
5b5c69b8f6 | |||
9a0ea90018 | |||
f8985d1cc5 | |||
ce5320c49f | |||
4d138b760b | |||
a2150e456c | |||
1b6852a36c | |||
66f2947854 | |||
ec25f847d8 | |||
bd169f417f | |||
571399930c | |||
36290f9a0a | |||
d6037efe5e | |||
81f50d5132 | |||
8d7d62dc24 | |||
27929d7ca2 | |||
63b239f5c6 | |||
0cdc8b13b7 | |||
baad1238c3 | |||
87c0ba129c | |||
eb9e847380 | |||
5b8fdedf4d | |||
64130d9f01 | |||
3df56dc790 | |||
3b7d112c83 | |||
b011ce023d | |||
36917d8a8f | |||
cad28abe61 | |||
254b2bd9df | |||
7bec8d1c5b | |||
800d6f7d0d | |||
4baaaf6a99 | |||
a02d641042 | |||
716e0a126a | |||
d8943e5bac | |||
e4ebabcd5b | |||
d078cff269 | |||
ea46efd9a2 | |||
ba4b65e4bc | |||
bdd09d6844 | |||
531572b411 |
@ -49,6 +49,8 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
|
||||
|
||||
option(YUZU_USE_PRECOMPILED_HEADERS "Use precompiled headers" ON)
|
||||
|
||||
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_ROOM "Compile LDN room server" ON "NOT ANDROID" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF)
|
||||
@ -61,6 +63,8 @@ option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
|
||||
|
||||
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
|
||||
|
||||
option(YUZU_ENABLE_PORTABLE "Allow yuzu to enable portable mode if a user folder is found in the CWD" ON)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(USE_SYSTEM_MOLTENVK "Use the system MoltenVK lib (instead of the bundled one)" OFF "APPLE" OFF)
|
||||
@ -77,6 +81,24 @@ if (ANDROID OR WIN32 OR APPLE)
|
||||
endif()
|
||||
option(ENABLE_OPENSSL "Enable OpenSSL backend for ISslConnection" ${DEFAULT_ENABLE_OPENSSL})
|
||||
|
||||
if (ANDROID AND YUZU_DOWNLOAD_ANDROID_VVL)
|
||||
set(vvl_version "sdk-1.3.261.1")
|
||||
set(vvl_zip_file "${CMAKE_BINARY_DIR}/externals/vvl-android.zip")
|
||||
if (NOT EXISTS "${vvl_zip_file}")
|
||||
# Download and extract validation layer release to externals directory
|
||||
set(vvl_base_url "https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/download")
|
||||
file(DOWNLOAD "${vvl_base_url}/${vvl_version}/android-binaries-${vvl_version}-android.zip"
|
||||
"${vvl_zip_file}" SHOW_PROGRESS)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${vvl_zip_file}"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
|
||||
endif()
|
||||
|
||||
# Copy the arm64 binary to src/android/app/main/jniLibs
|
||||
set(vvl_lib_path "${CMAKE_CURRENT_SOURCE_DIR}/src/android/app/src/main/jniLibs/arm64-v8a/")
|
||||
file(COPY "${CMAKE_BINARY_DIR}/externals/android-binaries-${vvl_version}/arm64-v8a/libVkLayer_khronos_validation.so"
|
||||
DESTINATION "${vvl_lib_path}")
|
||||
endif()
|
||||
|
||||
# On Android, fetch and compile libcxx before doing anything else
|
||||
if (ANDROID)
|
||||
set(CMAKE_SKIP_INSTALL_RULES ON)
|
||||
|
@ -1,3 +1,11 @@
|
||||
| Pull Request | Commit | Title | Author | Merged? |
|
||||
|----|----|----|----|----|
|
||||
|
||||
|
||||
End of merge log. You can find the original README.md below the break.
|
||||
|
||||
-----
|
||||
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2018 yuzu Emulator Project
|
||||
SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
3
externals/CMakeLists.txt
vendored
3
externals/CMakeLists.txt
vendored
@ -174,6 +174,9 @@ target_include_directories(stb PUBLIC ./stb)
|
||||
add_library(bc_decoder bc_decoder/bc_decoder.cpp)
|
||||
target_include_directories(bc_decoder PUBLIC ./bc_decoder)
|
||||
|
||||
add_library(renderdoc INTERFACE)
|
||||
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc)
|
||||
|
||||
if (ANDROID)
|
||||
if (ARCHITECTURE_arm64)
|
||||
add_subdirectory(libadrenotools)
|
||||
|
744
externals/renderdoc/renderdoc_app.h
vendored
Normal file
744
externals/renderdoc/renderdoc_app.h
vendored
Normal file
@ -0,0 +1,744 @@
|
||||
// SPDX-FileCopyrightText: Baldur Karlsson
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/******************************************************************************
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019-2023 Baldur Karlsson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
******************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Documentation for the API is available at https://renderdoc.org/docs/in_application_api.html
|
||||
//
|
||||
|
||||
#if !defined(RENDERDOC_NO_STDINT)
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
#if defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER)
|
||||
#define RENDERDOC_CC __cdecl
|
||||
#elif defined(__linux__)
|
||||
#define RENDERDOC_CC
|
||||
#elif defined(__APPLE__)
|
||||
#define RENDERDOC_CC
|
||||
#else
|
||||
#error "Unknown platform"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants not used directly in below API
|
||||
|
||||
// This is a GUID/magic value used for when applications pass a path where shader debug
|
||||
// information can be found to match up with a stripped shader.
|
||||
// the define can be used like so: const GUID RENDERDOC_ShaderDebugMagicValue =
|
||||
// RENDERDOC_ShaderDebugMagicValue_value
|
||||
#define RENDERDOC_ShaderDebugMagicValue_struct \
|
||||
{ \
|
||||
0xeab25520, 0x6670, 0x4865, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// as an alternative when you want a byte array (assuming x86 endianness):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_bytearray \
|
||||
{ \
|
||||
0x20, 0x55, 0xb2, 0xea, 0x70, 0x66, 0x65, 0x48, 0x84, 0x29, 0x6c, 0x8, 0x51, 0x54, 0x00, 0xff \
|
||||
}
|
||||
|
||||
// truncated version when only a uint64_t is available (e.g. Vulkan tags):
|
||||
#define RENDERDOC_ShaderDebugMagicValue_truncated 0x48656670eab25520ULL
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc capture options
|
||||
//
|
||||
|
||||
typedef enum RENDERDOC_CaptureOption
|
||||
{
|
||||
// Allow the application to enable vsync
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable vsync at will
|
||||
// 0 - vsync is force disabled
|
||||
eRENDERDOC_Option_AllowVSync = 0,
|
||||
|
||||
// Allow the application to enable fullscreen
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - The application can enable or disable fullscreen at will
|
||||
// 0 - fullscreen is force disabled
|
||||
eRENDERDOC_Option_AllowFullscreen = 1,
|
||||
|
||||
// Record API debugging events and messages
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enable built-in API debugging features and records the results into
|
||||
// the capture, which is matched up with events on replay
|
||||
// 0 - no API debugging is forcibly enabled
|
||||
eRENDERDOC_Option_APIValidation = 2,
|
||||
eRENDERDOC_Option_DebugDeviceMode = 2, // deprecated name of this enum
|
||||
|
||||
// Capture CPU callstacks for API events
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Enables capturing of callstacks
|
||||
// 0 - no callstacks are captured
|
||||
eRENDERDOC_Option_CaptureCallstacks = 3,
|
||||
|
||||
// When capturing CPU callstacks, only capture them from actions.
|
||||
// This option does nothing without the above option being enabled
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Only captures callstacks for actions.
|
||||
// Ignored if CaptureCallstacks is disabled
|
||||
// 0 - Callstacks, if enabled, are captured for every event.
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyDraws = 4,
|
||||
eRENDERDOC_Option_CaptureCallstacksOnlyActions = 4,
|
||||
|
||||
// Specify a delay in seconds to wait for a debugger to attach, after
|
||||
// creating or injecting into a process, before continuing to allow it to run.
|
||||
//
|
||||
// 0 indicates no delay, and the process will run immediately after injection
|
||||
//
|
||||
// Default - 0 seconds
|
||||
//
|
||||
eRENDERDOC_Option_DelayForDebugger = 5,
|
||||
|
||||
// Verify buffer access. This includes checking the memory returned by a Map() call to
|
||||
// detect any out-of-bounds modification, as well as initialising buffers with undefined contents
|
||||
// to a marker value to catch use of uninitialised memory.
|
||||
//
|
||||
// NOTE: This option is only valid for OpenGL and D3D11. Explicit APIs such as D3D12 and Vulkan do
|
||||
// not do the same kind of interception & checking and undefined contents are really undefined.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Verify buffer access
|
||||
// 0 - No verification is performed, and overwriting bounds may cause crashes or corruption in
|
||||
// RenderDoc.
|
||||
eRENDERDOC_Option_VerifyBufferAccess = 6,
|
||||
|
||||
// The old name for eRENDERDOC_Option_VerifyBufferAccess was eRENDERDOC_Option_VerifyMapWrites.
|
||||
// This option now controls the filling of uninitialised buffers with 0xdddddddd which was
|
||||
// previously always enabled
|
||||
eRENDERDOC_Option_VerifyMapWrites = eRENDERDOC_Option_VerifyBufferAccess,
|
||||
|
||||
// Hooks any system API calls that create child processes, and injects
|
||||
// RenderDoc into them recursively with the same options.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - Hooks into spawned child processes
|
||||
// 0 - Child processes are not hooked by RenderDoc
|
||||
eRENDERDOC_Option_HookIntoChildren = 7,
|
||||
|
||||
// By default RenderDoc only includes resources in the final capture necessary
|
||||
// for that frame, this allows you to override that behaviour.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - all live resources at the time of capture are included in the capture
|
||||
// and available for inspection
|
||||
// 0 - only the resources referenced by the captured frame are included
|
||||
eRENDERDOC_Option_RefAllResources = 8,
|
||||
|
||||
// **NOTE**: As of RenderDoc v1.1 this option has been deprecated. Setting or
|
||||
// getting it will be ignored, to allow compatibility with older versions.
|
||||
// In v1.1 the option acts as if it's always enabled.
|
||||
//
|
||||
// By default RenderDoc skips saving initial states for resources where the
|
||||
// previous contents don't appear to be used, assuming that writes before
|
||||
// reads indicate previous contents aren't used.
|
||||
//
|
||||
// Default - disabled
|
||||
//
|
||||
// 1 - initial contents at the start of each captured frame are saved, even if
|
||||
// they are later overwritten or cleared before being used.
|
||||
// 0 - unless a read is detected, initial contents will not be saved and will
|
||||
// appear as black or empty data.
|
||||
eRENDERDOC_Option_SaveAllInitials = 9,
|
||||
|
||||
// In APIs that allow for the recording of command lists to be replayed later,
|
||||
// RenderDoc may choose to not capture command lists before a frame capture is
|
||||
// triggered, to reduce overheads. This means any command lists recorded once
|
||||
// and replayed many times will not be available and may cause a failure to
|
||||
// capture.
|
||||
//
|
||||
// NOTE: This is only true for APIs where multithreading is difficult or
|
||||
// discouraged. Newer APIs like Vulkan and D3D12 will ignore this option
|
||||
// and always capture all command lists since the API is heavily oriented
|
||||
// around it and the overheads have been reduced by API design.
|
||||
//
|
||||
// 1 - All command lists are captured from the start of the application
|
||||
// 0 - Command lists are only captured if their recording begins during
|
||||
// the period when a frame capture is in progress.
|
||||
eRENDERDOC_Option_CaptureAllCmdLists = 10,
|
||||
|
||||
// Mute API debugging output when the API validation mode option is enabled
|
||||
//
|
||||
// Default - enabled
|
||||
//
|
||||
// 1 - Mute any API debug messages from being displayed or passed through
|
||||
// 0 - API debugging is displayed as normal
|
||||
eRENDERDOC_Option_DebugOutputMute = 11,
|
||||
|
||||
// Option to allow vendor extensions to be used even when they may be
|
||||
// incompatible with RenderDoc and cause corrupted replays or crashes.
|
||||
//
|
||||
// Default - inactive
|
||||
//
|
||||
// No values are documented, this option should only be used when absolutely
|
||||
// necessary as directed by a RenderDoc developer.
|
||||
eRENDERDOC_Option_AllowUnsupportedVendorExtensions = 12,
|
||||
|
||||
// Define a soft memory limit which some APIs may aim to keep overhead under where
|
||||
// possible. Anything above this limit will where possible be saved directly to disk during
|
||||
// capture.
|
||||
// This will cause increased disk space use (which may cause a capture to fail if disk space is
|
||||
// exhausted) as well as slower capture times.
|
||||
//
|
||||
// Not all memory allocations may be deferred like this so it is not a guarantee of a memory
|
||||
// limit.
|
||||
//
|
||||
// Units are in MBs, suggested values would range from 200MB to 1000MB.
|
||||
//
|
||||
// Default - 0 Megabytes
|
||||
eRENDERDOC_Option_SoftMemoryLimit = 13,
|
||||
} RENDERDOC_CaptureOption;
|
||||
|
||||
// Sets an option that controls how RenderDoc behaves on capture.
|
||||
//
|
||||
// Returns 1 if the option and value are valid
|
||||
// Returns 0 if either is invalid and the option is unchanged
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionU32)(RENDERDOC_CaptureOption opt, uint32_t val);
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_SetCaptureOptionF32)(RENDERDOC_CaptureOption opt, float val);
|
||||
|
||||
// Gets the current value of an option as a uint32_t
|
||||
//
|
||||
// If the option is invalid, 0xffffffff is returned
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionU32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
// Gets the current value of an option as a float
|
||||
//
|
||||
// If the option is invalid, -FLT_MAX is returned
|
||||
typedef float(RENDERDOC_CC *pRENDERDOC_GetCaptureOptionF32)(RENDERDOC_CaptureOption opt);
|
||||
|
||||
typedef enum RENDERDOC_InputButton
|
||||
{
|
||||
// '0' - '9' matches ASCII values
|
||||
eRENDERDOC_Key_0 = 0x30,
|
||||
eRENDERDOC_Key_1 = 0x31,
|
||||
eRENDERDOC_Key_2 = 0x32,
|
||||
eRENDERDOC_Key_3 = 0x33,
|
||||
eRENDERDOC_Key_4 = 0x34,
|
||||
eRENDERDOC_Key_5 = 0x35,
|
||||
eRENDERDOC_Key_6 = 0x36,
|
||||
eRENDERDOC_Key_7 = 0x37,
|
||||
eRENDERDOC_Key_8 = 0x38,
|
||||
eRENDERDOC_Key_9 = 0x39,
|
||||
|
||||
// 'A' - 'Z' matches ASCII values
|
||||
eRENDERDOC_Key_A = 0x41,
|
||||
eRENDERDOC_Key_B = 0x42,
|
||||
eRENDERDOC_Key_C = 0x43,
|
||||
eRENDERDOC_Key_D = 0x44,
|
||||
eRENDERDOC_Key_E = 0x45,
|
||||
eRENDERDOC_Key_F = 0x46,
|
||||
eRENDERDOC_Key_G = 0x47,
|
||||
eRENDERDOC_Key_H = 0x48,
|
||||
eRENDERDOC_Key_I = 0x49,
|
||||
eRENDERDOC_Key_J = 0x4A,
|
||||
eRENDERDOC_Key_K = 0x4B,
|
||||
eRENDERDOC_Key_L = 0x4C,
|
||||
eRENDERDOC_Key_M = 0x4D,
|
||||
eRENDERDOC_Key_N = 0x4E,
|
||||
eRENDERDOC_Key_O = 0x4F,
|
||||
eRENDERDOC_Key_P = 0x50,
|
||||
eRENDERDOC_Key_Q = 0x51,
|
||||
eRENDERDOC_Key_R = 0x52,
|
||||
eRENDERDOC_Key_S = 0x53,
|
||||
eRENDERDOC_Key_T = 0x54,
|
||||
eRENDERDOC_Key_U = 0x55,
|
||||
eRENDERDOC_Key_V = 0x56,
|
||||
eRENDERDOC_Key_W = 0x57,
|
||||
eRENDERDOC_Key_X = 0x58,
|
||||
eRENDERDOC_Key_Y = 0x59,
|
||||
eRENDERDOC_Key_Z = 0x5A,
|
||||
|
||||
// leave the rest of the ASCII range free
|
||||
// in case we want to use it later
|
||||
eRENDERDOC_Key_NonPrintable = 0x100,
|
||||
|
||||
eRENDERDOC_Key_Divide,
|
||||
eRENDERDOC_Key_Multiply,
|
||||
eRENDERDOC_Key_Subtract,
|
||||
eRENDERDOC_Key_Plus,
|
||||
|
||||
eRENDERDOC_Key_F1,
|
||||
eRENDERDOC_Key_F2,
|
||||
eRENDERDOC_Key_F3,
|
||||
eRENDERDOC_Key_F4,
|
||||
eRENDERDOC_Key_F5,
|
||||
eRENDERDOC_Key_F6,
|
||||
eRENDERDOC_Key_F7,
|
||||
eRENDERDOC_Key_F8,
|
||||
eRENDERDOC_Key_F9,
|
||||
eRENDERDOC_Key_F10,
|
||||
eRENDERDOC_Key_F11,
|
||||
eRENDERDOC_Key_F12,
|
||||
|
||||
eRENDERDOC_Key_Home,
|
||||
eRENDERDOC_Key_End,
|
||||
eRENDERDOC_Key_Insert,
|
||||
eRENDERDOC_Key_Delete,
|
||||
eRENDERDOC_Key_PageUp,
|
||||
eRENDERDOC_Key_PageDn,
|
||||
|
||||
eRENDERDOC_Key_Backspace,
|
||||
eRENDERDOC_Key_Tab,
|
||||
eRENDERDOC_Key_PrtScrn,
|
||||
eRENDERDOC_Key_Pause,
|
||||
|
||||
eRENDERDOC_Key_Max,
|
||||
} RENDERDOC_InputButton;
|
||||
|
||||
// Sets which key or keys can be used to toggle focus between multiple windows
|
||||
//
|
||||
// If keys is NULL or num is 0, toggle keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetFocusToggleKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
// Sets which key or keys can be used to capture the next frame
|
||||
//
|
||||
// If keys is NULL or num is 0, captures keys will be disabled
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureKeys)(RENDERDOC_InputButton *keys, int num);
|
||||
|
||||
typedef enum RENDERDOC_OverlayBits
|
||||
{
|
||||
// This single bit controls whether the overlay is enabled or disabled globally
|
||||
eRENDERDOC_Overlay_Enabled = 0x1,
|
||||
|
||||
// Show the average framerate over several seconds as well as min/max
|
||||
eRENDERDOC_Overlay_FrameRate = 0x2,
|
||||
|
||||
// Show the current frame number
|
||||
eRENDERDOC_Overlay_FrameNumber = 0x4,
|
||||
|
||||
// Show a list of recent captures, and how many captures have been made
|
||||
eRENDERDOC_Overlay_CaptureList = 0x8,
|
||||
|
||||
// Default values for the overlay mask
|
||||
eRENDERDOC_Overlay_Default = (eRENDERDOC_Overlay_Enabled | eRENDERDOC_Overlay_FrameRate |
|
||||
eRENDERDOC_Overlay_FrameNumber | eRENDERDOC_Overlay_CaptureList),
|
||||
|
||||
// Enable all bits
|
||||
eRENDERDOC_Overlay_All = ~0U,
|
||||
|
||||
// Disable all bits
|
||||
eRENDERDOC_Overlay_None = 0,
|
||||
} RENDERDOC_OverlayBits;
|
||||
|
||||
// returns the overlay bits that have been set
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetOverlayBits)();
|
||||
// sets the overlay bits with an and & or mask
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_MaskOverlayBits)(uint32_t And, uint32_t Or);
|
||||
|
||||
// this function will attempt to remove RenderDoc's hooks in the application.
|
||||
//
|
||||
// Note: that this can only work correctly if done immediately after
|
||||
// the module is loaded, before any API work happens. RenderDoc will remove its
|
||||
// injected hooks and shut down. Behaviour is undefined if this is called
|
||||
// after any API functions have been called, and there is still no guarantee of
|
||||
// success.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_RemoveHooks)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.4.1 headers.
|
||||
typedef pRENDERDOC_RemoveHooks pRENDERDOC_Shutdown;
|
||||
|
||||
// This function will unload RenderDoc's crash handler.
|
||||
//
|
||||
// If you use your own crash handler and don't want RenderDoc's handler to
|
||||
// intercede, you can call this function to unload it and any unhandled
|
||||
// exceptions will pass to the next handler.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_UnloadCrashHandler)();
|
||||
|
||||
// Sets the capture file path template
|
||||
//
|
||||
// pathtemplate is a UTF-8 string that gives a template for how captures will be named
|
||||
// and where they will be saved.
|
||||
//
|
||||
// Any extension is stripped off the path, and captures are saved in the directory
|
||||
// specified, and named with the filename and the frame number appended. If the
|
||||
// directory does not exist it will be created, including any parent directories.
|
||||
//
|
||||
// If pathtemplate is NULL, the template will remain unchanged
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// SetCaptureFilePathTemplate("my_captures/example");
|
||||
//
|
||||
// Capture #1 -> my_captures/example_frame123.rdc
|
||||
// Capture #2 -> my_captures/example_frame456.rdc
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFilePathTemplate)(const char *pathtemplate);
|
||||
|
||||
// returns the current capture path template, see SetCaptureFileTemplate above, as a UTF-8 string
|
||||
typedef const char *(RENDERDOC_CC *pRENDERDOC_GetCaptureFilePathTemplate)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.2 headers.
|
||||
typedef pRENDERDOC_SetCaptureFilePathTemplate pRENDERDOC_SetLogFilePathTemplate;
|
||||
typedef pRENDERDOC_GetCaptureFilePathTemplate pRENDERDOC_GetLogFilePathTemplate;
|
||||
|
||||
// returns the number of captures that have been made
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetNumCaptures)();
|
||||
|
||||
// This function returns the details of a capture, by index. New captures are added
|
||||
// to the end of the list.
|
||||
//
|
||||
// filename will be filled with the absolute path to the capture file, as a UTF-8 string
|
||||
// pathlength will be written with the length in bytes of the filename string
|
||||
// timestamp will be written with the time of the capture, in seconds since the Unix epoch
|
||||
//
|
||||
// Any of the parameters can be NULL and they'll be skipped.
|
||||
//
|
||||
// The function will return 1 if the capture index is valid, or 0 if the index is invalid
|
||||
// If the index is invalid, the values will be unchanged
|
||||
//
|
||||
// Note: when captures are deleted in the UI they will remain in this list, so the
|
||||
// capture path may not exist anymore.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_GetCapture)(uint32_t idx, char *filename,
|
||||
uint32_t *pathlength, uint64_t *timestamp);
|
||||
|
||||
// Sets the comments associated with a capture file. These comments are displayed in the
|
||||
// UI program when opening.
|
||||
//
|
||||
// filePath should be a path to the capture file to add comments to. If set to NULL or ""
|
||||
// the most recent capture file created made will be used instead.
|
||||
// comments should be a NULL-terminated UTF-8 string to add as comments.
|
||||
//
|
||||
// Any existing comments will be overwritten.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureFileComments)(const char *filePath,
|
||||
const char *comments);
|
||||
|
||||
// returns 1 if the RenderDoc UI is connected to this application, 0 otherwise
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsTargetControlConnected)();
|
||||
|
||||
// DEPRECATED: compatibility for code compiled against pre-1.1.1 headers.
|
||||
// This was renamed to IsTargetControlConnected in API 1.1.1, the old typedef is kept here for
|
||||
// backwards compatibility with old code, it is castable either way since it's ABI compatible
|
||||
// as the same function pointer type.
|
||||
typedef pRENDERDOC_IsTargetControlConnected pRENDERDOC_IsRemoteAccessConnected;
|
||||
|
||||
// This function will launch the Replay UI associated with the RenderDoc library injected
|
||||
// into the running application.
|
||||
//
|
||||
// if connectTargetControl is 1, the Replay UI will be launched with a command line parameter
|
||||
// to connect to this application
|
||||
// cmdline is the rest of the command line, as a UTF-8 string. E.g. a captures to open
|
||||
// if cmdline is NULL, the command line will be empty.
|
||||
//
|
||||
// returns the PID of the replay UI if successful, 0 if not successful.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_LaunchReplayUI)(uint32_t connectTargetControl,
|
||||
const char *cmdline);
|
||||
|
||||
// RenderDoc can return a higher version than requested if it's backwards compatible,
|
||||
// this function returns the actual version returned. If a parameter is NULL, it will be
|
||||
// ignored and the others will be filled out.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_GetAPIVersion)(int *major, int *minor, int *patch);
|
||||
|
||||
// Requests that the replay UI show itself (if hidden or not the current top window). This can be
|
||||
// used in conjunction with IsTargetControlConnected and LaunchReplayUI to intelligently handle
|
||||
// showing the UI after making a capture.
|
||||
//
|
||||
// This will return 1 if the request was successfully passed on, though it's not guaranteed that
|
||||
// the UI will be on top in all cases depending on OS rules. It will return 0 if there is no current
|
||||
// target control connection to make such a request, or if there was another error
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_ShowReplayUI)();
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Capturing functions
|
||||
//
|
||||
|
||||
// A device pointer is a pointer to the API's root handle.
|
||||
//
|
||||
// This would be an ID3D11Device, HGLRC/GLXContext, ID3D12Device, etc
|
||||
typedef void *RENDERDOC_DevicePointer;
|
||||
|
||||
// A window handle is the OS's native window handle
|
||||
//
|
||||
// This would be an HWND, GLXDrawable, etc
|
||||
typedef void *RENDERDOC_WindowHandle;
|
||||
|
||||
// A helper macro for Vulkan, where the device handle cannot be used directly.
|
||||
//
|
||||
// Passing the VkInstance to this macro will return the RENDERDOC_DevicePointer to use.
|
||||
//
|
||||
// Specifically, the value needed is the dispatch table pointer, which sits as the first
|
||||
// pointer-sized object in the memory pointed to by the VkInstance. Thus we cast to a void** and
|
||||
// indirect once.
|
||||
#define RENDERDOC_DEVICEPOINTER_FROM_VKINSTANCE(inst) (*((void **)(inst)))
|
||||
|
||||
// This sets the RenderDoc in-app overlay in the API/window pair as 'active' and it will
|
||||
// respond to keypresses. Neither parameter can be NULL
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetActiveWindow)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// capture the next frame on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerCapture)();
|
||||
|
||||
// capture the next N frames on whichever window and API is currently considered active
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_TriggerMultiFrameCapture)(uint32_t numFrames);
|
||||
|
||||
// When choosing either a device pointer or a window handle to capture, you can pass NULL.
|
||||
// Passing NULL specifies a 'wildcard' match against anything. This allows you to specify
|
||||
// any API rendering to a specific window, or a specific API instance rendering to any window,
|
||||
// or in the simplest case of one window and one API, you can just pass NULL for both.
|
||||
//
|
||||
// In either case, if there are two or more possible matching (device,window) pairs it
|
||||
// is undefined which one will be captured.
|
||||
//
|
||||
// Note: for headless rendering you can pass NULL for the window handle and either specify
|
||||
// a device pointer or leave it NULL as above.
|
||||
|
||||
// Immediately starts capturing API calls on the specified device pointer and window handle.
|
||||
//
|
||||
// If there is no matching thing to capture (e.g. no supported API has been initialised),
|
||||
// this will do nothing.
|
||||
//
|
||||
// The results are undefined (including crashes) if two captures are started overlapping,
|
||||
// even on separate devices and/oror windows.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_StartFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Returns whether or not a frame capture is currently ongoing anywhere.
|
||||
//
|
||||
// This will return 1 if a capture is ongoing, and 0 if there is no capture running
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_IsFrameCapturing)();
|
||||
|
||||
// Ends capturing immediately.
|
||||
//
|
||||
// This will return 1 if the capture succeeded, and 0 if there was an error capturing.
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_EndFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Ends capturing immediately and discard any data stored without saving to disk.
|
||||
//
|
||||
// This will return 1 if the capture was discarded, and 0 if there was an error or no capture
|
||||
// was in progress
|
||||
typedef uint32_t(RENDERDOC_CC *pRENDERDOC_DiscardFrameCapture)(RENDERDOC_DevicePointer device,
|
||||
RENDERDOC_WindowHandle wndHandle);
|
||||
|
||||
// Only valid to be called between a call to StartFrameCapture and EndFrameCapture. Gives a custom
|
||||
// title to the capture produced which will be displayed in the UI.
|
||||
//
|
||||
// If multiple captures are ongoing, this title will be applied to the first capture to end after
|
||||
// this call. The second capture to end will have no title, unless this function is called again.
|
||||
//
|
||||
// Calling this function has no effect if no capture is currently running, and if it is called
|
||||
// multiple times only the last title will be used.
|
||||
typedef void(RENDERDOC_CC *pRENDERDOC_SetCaptureTitle)(const char *title);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API versions
|
||||
//
|
||||
|
||||
// RenderDoc uses semantic versioning (http://semver.org/).
|
||||
//
|
||||
// MAJOR version is incremented when incompatible API changes happen.
|
||||
// MINOR version is incremented when functionality is added in a backwards-compatible manner.
|
||||
// PATCH version is incremented when backwards-compatible bug fixes happen.
|
||||
//
|
||||
// Note that this means the API returned can be higher than the one you might have requested.
|
||||
// e.g. if you are running against a newer RenderDoc that supports 1.0.1, it will be returned
|
||||
// instead of 1.0.0. You can check this with the GetAPIVersion entry point
|
||||
typedef enum RENDERDOC_Version
|
||||
{
|
||||
eRENDERDOC_API_Version_1_0_0 = 10000, // RENDERDOC_API_1_0_0 = 1 00 00
|
||||
eRENDERDOC_API_Version_1_0_1 = 10001, // RENDERDOC_API_1_0_1 = 1 00 01
|
||||
eRENDERDOC_API_Version_1_0_2 = 10002, // RENDERDOC_API_1_0_2 = 1 00 02
|
||||
eRENDERDOC_API_Version_1_1_0 = 10100, // RENDERDOC_API_1_1_0 = 1 01 00
|
||||
eRENDERDOC_API_Version_1_1_1 = 10101, // RENDERDOC_API_1_1_1 = 1 01 01
|
||||
eRENDERDOC_API_Version_1_1_2 = 10102, // RENDERDOC_API_1_1_2 = 1 01 02
|
||||
eRENDERDOC_API_Version_1_2_0 = 10200, // RENDERDOC_API_1_2_0 = 1 02 00
|
||||
eRENDERDOC_API_Version_1_3_0 = 10300, // RENDERDOC_API_1_3_0 = 1 03 00
|
||||
eRENDERDOC_API_Version_1_4_0 = 10400, // RENDERDOC_API_1_4_0 = 1 04 00
|
||||
eRENDERDOC_API_Version_1_4_1 = 10401, // RENDERDOC_API_1_4_1 = 1 04 01
|
||||
eRENDERDOC_API_Version_1_4_2 = 10402, // RENDERDOC_API_1_4_2 = 1 04 02
|
||||
eRENDERDOC_API_Version_1_5_0 = 10500, // RENDERDOC_API_1_5_0 = 1 05 00
|
||||
eRENDERDOC_API_Version_1_6_0 = 10600, // RENDERDOC_API_1_6_0 = 1 06 00
|
||||
} RENDERDOC_Version;
|
||||
|
||||
// API version changelog:
|
||||
//
|
||||
// 1.0.0 - initial release
|
||||
// 1.0.1 - Bugfix: IsFrameCapturing() was returning false for captures that were triggered
|
||||
// by keypress or TriggerCapture, instead of Start/EndFrameCapture.
|
||||
// 1.0.2 - Refactor: Renamed eRENDERDOC_Option_DebugDeviceMode to eRENDERDOC_Option_APIValidation
|
||||
// 1.1.0 - Add feature: TriggerMultiFrameCapture(). Backwards compatible with 1.0.x since the new
|
||||
// function pointer is added to the end of the struct, the original layout is identical
|
||||
// 1.1.1 - Refactor: Renamed remote access to target control (to better disambiguate from remote
|
||||
// replay/remote server concept in replay UI)
|
||||
// 1.1.2 - Refactor: Renamed "log file" in function names to just capture, to clarify that these
|
||||
// are captures and not debug logging files. This is the first API version in the v1.0
|
||||
// branch.
|
||||
// 1.2.0 - Added feature: SetCaptureFileComments() to add comments to a capture file that will be
|
||||
// displayed in the UI program on load.
|
||||
// 1.3.0 - Added feature: New capture option eRENDERDOC_Option_AllowUnsupportedVendorExtensions
|
||||
// which allows users to opt-in to allowing unsupported vendor extensions to function.
|
||||
// Should be used at the user's own risk.
|
||||
// Refactor: Renamed eRENDERDOC_Option_VerifyMapWrites to
|
||||
// eRENDERDOC_Option_VerifyBufferAccess, which now also controls initialisation to
|
||||
// 0xdddddddd of uninitialised buffer contents.
|
||||
// 1.4.0 - Added feature: DiscardFrameCapture() to discard a frame capture in progress and stop
|
||||
// capturing without saving anything to disk.
|
||||
// 1.4.1 - Refactor: Renamed Shutdown to RemoveHooks to better clarify what is happening
|
||||
// 1.4.2 - Refactor: Renamed 'draws' to 'actions' in callstack capture option.
|
||||
// 1.5.0 - Added feature: ShowReplayUI() to request that the replay UI show itself if connected
|
||||
// 1.6.0 - Added feature: SetCaptureTitle() which can be used to set a title for a
|
||||
// capture made with StartFrameCapture() or EndFrameCapture()
|
||||
|
||||
typedef struct RENDERDOC_API_1_6_0
|
||||
{
|
||||
pRENDERDOC_GetAPIVersion GetAPIVersion;
|
||||
|
||||
pRENDERDOC_SetCaptureOptionU32 SetCaptureOptionU32;
|
||||
pRENDERDOC_SetCaptureOptionF32 SetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_GetCaptureOptionU32 GetCaptureOptionU32;
|
||||
pRENDERDOC_GetCaptureOptionF32 GetCaptureOptionF32;
|
||||
|
||||
pRENDERDOC_SetFocusToggleKeys SetFocusToggleKeys;
|
||||
pRENDERDOC_SetCaptureKeys SetCaptureKeys;
|
||||
|
||||
pRENDERDOC_GetOverlayBits GetOverlayBits;
|
||||
pRENDERDOC_MaskOverlayBits MaskOverlayBits;
|
||||
|
||||
// Shutdown was renamed to RemoveHooks in 1.4.1.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
pRENDERDOC_Shutdown Shutdown;
|
||||
pRENDERDOC_RemoveHooks RemoveHooks;
|
||||
};
|
||||
pRENDERDOC_UnloadCrashHandler UnloadCrashHandler;
|
||||
|
||||
// Get/SetLogFilePathTemplate was renamed to Get/SetCaptureFilePathTemplate in 1.1.2.
|
||||
// These unions allow old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_SetLogFilePathTemplate SetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_SetCaptureFilePathTemplate SetCaptureFilePathTemplate;
|
||||
};
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_GetLogFilePathTemplate GetLogFilePathTemplate;
|
||||
// current name
|
||||
pRENDERDOC_GetCaptureFilePathTemplate GetCaptureFilePathTemplate;
|
||||
};
|
||||
|
||||
pRENDERDOC_GetNumCaptures GetNumCaptures;
|
||||
pRENDERDOC_GetCapture GetCapture;
|
||||
|
||||
pRENDERDOC_TriggerCapture TriggerCapture;
|
||||
|
||||
// IsRemoteAccessConnected was renamed to IsTargetControlConnected in 1.1.1.
|
||||
// This union allows old code to continue compiling without changes
|
||||
union
|
||||
{
|
||||
// deprecated name
|
||||
pRENDERDOC_IsRemoteAccessConnected IsRemoteAccessConnected;
|
||||
// current name
|
||||
pRENDERDOC_IsTargetControlConnected IsTargetControlConnected;
|
||||
};
|
||||
pRENDERDOC_LaunchReplayUI LaunchReplayUI;
|
||||
|
||||
pRENDERDOC_SetActiveWindow SetActiveWindow;
|
||||
|
||||
pRENDERDOC_StartFrameCapture StartFrameCapture;
|
||||
pRENDERDOC_IsFrameCapturing IsFrameCapturing;
|
||||
pRENDERDOC_EndFrameCapture EndFrameCapture;
|
||||
|
||||
// new function in 1.1.0
|
||||
pRENDERDOC_TriggerMultiFrameCapture TriggerMultiFrameCapture;
|
||||
|
||||
// new function in 1.2.0
|
||||
pRENDERDOC_SetCaptureFileComments SetCaptureFileComments;
|
||||
|
||||
// new function in 1.4.0
|
||||
pRENDERDOC_DiscardFrameCapture DiscardFrameCapture;
|
||||
|
||||
// new function in 1.5.0
|
||||
pRENDERDOC_ShowReplayUI ShowReplayUI;
|
||||
|
||||
// new function in 1.6.0
|
||||
pRENDERDOC_SetCaptureTitle SetCaptureTitle;
|
||||
} RENDERDOC_API_1_6_0;
|
||||
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_0_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_1_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_2_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_3_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_0;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_1;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_4_2;
|
||||
typedef RENDERDOC_API_1_6_0 RENDERDOC_API_1_5_0;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// RenderDoc API entry point
|
||||
//
|
||||
// This entry point can be obtained via GetProcAddress/dlsym if RenderDoc is available.
|
||||
//
|
||||
// The name is the same as the typedef - "RENDERDOC_GetAPI"
|
||||
//
|
||||
// This function is not thread safe, and should not be called on multiple threads at once.
|
||||
// Ideally, call this once as early as possible in your application's startup, before doing
|
||||
// any API work, since some configuration functionality etc has to be done also before
|
||||
// initialising any APIs.
|
||||
//
|
||||
// Parameters:
|
||||
// version is a single value from the RENDERDOC_Version above.
|
||||
//
|
||||
// outAPIPointers will be filled out with a pointer to the corresponding struct of function
|
||||
// pointers.
|
||||
//
|
||||
// Returns:
|
||||
// 1 - if the outAPIPointers has been filled with a pointer to the API struct requested
|
||||
// 0 - if the requested version is not supported or the arguments are invalid.
|
||||
//
|
||||
typedef int(RENDERDOC_CC *pRENDERDOC_GetAPI)(RENDERDOC_Version version, void **outAPIPointers);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
@ -85,6 +85,7 @@ if (MSVC)
|
||||
/wd4100 # 'identifier': unreferenced formal parameter
|
||||
/wd4324 # 'struct_name': structure was padded due to __declspec(align())
|
||||
/wd4201 # nonstandard extension used : nameless struct/union
|
||||
/wd4702 # unreachable code (when used with LTO)
|
||||
)
|
||||
|
||||
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
|
||||
|
@ -307,21 +307,6 @@ object NativeLibrary {
|
||||
*/
|
||||
external fun isPaused(): Boolean
|
||||
|
||||
/**
|
||||
* Mutes emulation sound
|
||||
*/
|
||||
external fun muteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Unmutes emulation sound
|
||||
*/
|
||||
external fun unmuteAudio(): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if emulation audio is muted.
|
||||
*/
|
||||
external fun isMuted(): Boolean
|
||||
|
||||
/**
|
||||
* Returns the performance stats for the current game
|
||||
*/
|
||||
|
@ -332,7 +332,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
pictureInPictureActions.add(pauseRemoteAction)
|
||||
}
|
||||
|
||||
if (NativeLibrary.isMuted()) {
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) {
|
||||
val unmuteIcon = Icon.createWithResource(
|
||||
this@EmulationActivity,
|
||||
R.drawable.ic_pip_unmute
|
||||
@ -389,9 +389,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
if (!NativeLibrary.isPaused()) NativeLibrary.pauseEmulation()
|
||||
}
|
||||
if (intent.action == actionUnmute) {
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
|
||||
} else if (intent.action == actionMute) {
|
||||
if (!NativeLibrary.isMuted()) NativeLibrary.muteAudio()
|
||||
if (!BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(true)
|
||||
}
|
||||
buildPictureInPictureParams()
|
||||
}
|
||||
@ -417,7 +417,7 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
|
||||
} catch (ignored: Exception) {
|
||||
}
|
||||
// Always resume audio, since there is no UI button
|
||||
if (NativeLibrary.isMuted()) NativeLibrary.unmuteAudio()
|
||||
if (BooleanSetting.AUDIO_MUTED.boolean) BooleanSetting.AUDIO_MUTED.setBoolean(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,8 @@
|
||||
package org.yuzu.yuzu_emu.adapters
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.net.Uri
|
||||
import android.text.TextUtils
|
||||
import android.view.LayoutInflater
|
||||
@ -15,7 +16,10 @@ import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.pm.ShortcutInfoCompat
|
||||
import androidx.core.content.pm.ShortcutManagerCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.IconCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.navigation.findNavController
|
||||
@ -87,11 +91,24 @@ class GameAdapter(private val activity: AppCompatActivity) :
|
||||
action = Intent.ACTION_VIEW
|
||||
data = Uri.parse(holder.game.path)
|
||||
}
|
||||
|
||||
val layerDrawable = ResourcesCompat.getDrawable(
|
||||
YuzuApplication.appContext.resources,
|
||||
R.drawable.shortcut,
|
||||
null
|
||||
) as LayerDrawable
|
||||
layerDrawable.setDrawableByLayerId(
|
||||
R.id.shortcut_foreground,
|
||||
GameIconUtils.getGameIcon(holder.game).toDrawable(YuzuApplication.appContext.resources)
|
||||
)
|
||||
val inset = YuzuApplication.appContext.resources
|
||||
.getDimensionPixelSize(R.dimen.icon_inset)
|
||||
layerDrawable.setLayerInset(1, inset, inset, inset, inset)
|
||||
val shortcut = ShortcutInfoCompat.Builder(YuzuApplication.appContext, holder.game.path)
|
||||
.setShortLabel(holder.game.title)
|
||||
.setIcon(
|
||||
IconCompat.createWithBitmap(
|
||||
(holder.binding.imageGameScreen.drawable as BitmapDrawable).bitmap
|
||||
IconCompat.createWithAdaptiveBitmap(
|
||||
layerDrawable.toBitmap(config = Bitmap.Config.ARGB_8888)
|
||||
)
|
||||
)
|
||||
.setIntent(openIntent)
|
||||
|
@ -10,8 +10,12 @@ import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.CardHomeOptionBinding
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
@ -86,7 +90,11 @@ class HomeSettingAdapter(
|
||||
binding.optionIcon.alpha = 0.5f
|
||||
}
|
||||
|
||||
option.details.observe(viewLifecycle) { updateOptionDetails(it) }
|
||||
viewLifecycle.lifecycleScope.launch {
|
||||
viewLifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
option.details.collect { updateOptionDetails(it) }
|
||||
}
|
||||
}
|
||||
binding.optionDetail.postDelayed(
|
||||
{
|
||||
binding.optionDetail.ellipsize = TextUtils.TruncateAt.MARQUEE
|
||||
|
@ -10,6 +10,7 @@ enum class BooleanSetting(
|
||||
override val category: Settings.Category,
|
||||
override val androidDefault: Boolean? = null
|
||||
) : AbstractBooleanSetting {
|
||||
AUDIO_MUTED("audio_muted", Settings.Category.Audio),
|
||||
CPU_DEBUG_MODE("cpu_debug_mode", Settings.Category.Cpu),
|
||||
FASTMEM("cpuopt_fastmem", Settings.Category.Cpu),
|
||||
FASTMEM_EXCLUSIVES("cpuopt_fastmem_exclusives", Settings.Category.Cpu),
|
||||
|
@ -80,6 +80,17 @@ object Settings {
|
||||
const val SECTION_THEME = "Theme"
|
||||
const val SECTION_DEBUG = "Debug"
|
||||
|
||||
enum class MenuTag(val titleId: Int) {
|
||||
SECTION_ROOT(R.string.advanced_settings),
|
||||
SECTION_GENERAL(R.string.preferences_general),
|
||||
SECTION_SYSTEM(R.string.preferences_system),
|
||||
SECTION_RENDERER(R.string.preferences_graphics),
|
||||
SECTION_AUDIO(R.string.preferences_audio),
|
||||
SECTION_CPU(R.string.cpu),
|
||||
SECTION_THEME(R.string.preferences_theme),
|
||||
SECTION_DEBUG(R.string.preferences_debug);
|
||||
}
|
||||
|
||||
const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
|
||||
|
||||
const val PREF_OVERLAY_VERSION = "OverlayVersion"
|
||||
|
@ -3,10 +3,12 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.model.view
|
||||
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
|
||||
class SubmenuSetting(
|
||||
titleId: Int,
|
||||
descriptionId: Int,
|
||||
val menuKey: String
|
||||
val menuKey: Settings.MenuTag
|
||||
) : SettingsItem(emptySetting, titleId, descriptionId) {
|
||||
override val type = TYPE_SUBMENU
|
||||
}
|
||||
|
@ -13,9 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.navArgs
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.IOException
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.ActivitySettingsBinding
|
||||
@ -66,25 +71,39 @@ class SettingsActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
|
||||
settingsViewModel.shouldRecreate.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldRecreate.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldRecreate(false)
|
||||
recreate()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldNavigateBack.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldNavigateBack.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldNavigateBack(false)
|
||||
navigateBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
settingsViewModel.shouldShowResetSettingsDialog.observe(this) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldShowResetSettingsDialog.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(false)
|
||||
ResetSettingsDialogFragment().show(
|
||||
supportFragmentManager,
|
||||
ResetSettingsDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.features.settings.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -13,14 +14,19 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.navigation.fragment.navArgs
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
|
||||
class SettingsFragment : Fragment() {
|
||||
@ -51,15 +57,17 @@ class SettingsFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
settingsAdapter = SettingsAdapter(this, requireContext())
|
||||
presenter = SettingsFragmentPresenter(
|
||||
settingsViewModel,
|
||||
settingsAdapter!!,
|
||||
args.menuTag,
|
||||
args.game?.gameId ?: ""
|
||||
args.menuTag
|
||||
)
|
||||
|
||||
binding.toolbarSettingsLayout.title = getString(args.menuTag.titleId)
|
||||
val dividerDecoration = MaterialDividerItemDecoration(
|
||||
requireContext(),
|
||||
LinearLayoutManager.VERTICAL
|
||||
@ -75,28 +83,31 @@ class SettingsFragment : Fragment() {
|
||||
settingsViewModel.setShouldNavigateBack(true)
|
||||
}
|
||||
|
||||
settingsViewModel.toolbarTitle.observe(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) binding.toolbarSettingsLayout.title = it
|
||||
}
|
||||
|
||||
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collectLatest {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
presenter.loadSettingsList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
settingsViewModel.isUsingSearch.collectLatest {
|
||||
if (it) {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
} else {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
settingsViewModel.isUsingSearch.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.Z, true)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.Z, false)
|
||||
} else {
|
||||
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false)
|
||||
exitTransition = MaterialSharedAxis(MaterialSharedAxis.X, true)
|
||||
}
|
||||
}
|
||||
|
||||
if (args.menuTag == SettingsFile.FILE_NAME_CONFIG) {
|
||||
if (args.menuTag == Settings.MenuTag.SECTION_ROOT) {
|
||||
binding.toolbarSettings.inflateMenu(R.menu.menu_settings)
|
||||
binding.toolbarSettings.setOnMenuItemClickListener {
|
||||
when (it.itemId) {
|
||||
|
@ -6,7 +6,6 @@ package org.yuzu.yuzu_emu.features.settings.ui
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.text.TextUtils
|
||||
import android.widget.Toast
|
||||
import androidx.preference.PreferenceManager
|
||||
import org.yuzu.yuzu_emu.R
|
||||
@ -20,15 +19,13 @@ import org.yuzu.yuzu_emu.features.settings.model.LongSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.model.ShortSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.*
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.SettingsViewModel
|
||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||
|
||||
class SettingsFragmentPresenter(
|
||||
private val settingsViewModel: SettingsViewModel,
|
||||
private val adapter: SettingsAdapter,
|
||||
private var menuTag: String,
|
||||
private var gameId: String
|
||||
private var menuTag: Settings.MenuTag
|
||||
) {
|
||||
private var settingsList = ArrayList<SettingsItem>()
|
||||
|
||||
@ -53,24 +50,15 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
fun loadSettingsList() {
|
||||
if (!TextUtils.isEmpty(gameId)) {
|
||||
settingsViewModel.setToolbarTitle(
|
||||
context.getString(
|
||||
R.string.advanced_settings_game,
|
||||
gameId
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val sl = ArrayList<SettingsItem>()
|
||||
when (menuTag) {
|
||||
SettingsFile.FILE_NAME_CONFIG -> addConfigSettings(sl)
|
||||
Settings.SECTION_GENERAL -> addGeneralSettings(sl)
|
||||
Settings.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
Settings.MenuTag.SECTION_ROOT -> addConfigSettings(sl)
|
||||
Settings.MenuTag.SECTION_GENERAL -> addGeneralSettings(sl)
|
||||
Settings.MenuTag.SECTION_SYSTEM -> addSystemSettings(sl)
|
||||
Settings.MenuTag.SECTION_RENDERER -> addGraphicsSettings(sl)
|
||||
Settings.MenuTag.SECTION_AUDIO -> addAudioSettings(sl)
|
||||
Settings.MenuTag.SECTION_THEME -> addThemeSettings(sl)
|
||||
Settings.MenuTag.SECTION_DEBUG -> addDebugSettings(sl)
|
||||
else -> {
|
||||
val context = YuzuApplication.appContext
|
||||
Toast.makeText(
|
||||
@ -86,13 +74,12 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.advanced_settings))
|
||||
sl.apply {
|
||||
add(SubmenuSetting(R.string.preferences_general, 0, Settings.SECTION_GENERAL))
|
||||
add(SubmenuSetting(R.string.preferences_system, 0, Settings.SECTION_SYSTEM))
|
||||
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.SECTION_RENDERER))
|
||||
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.SECTION_AUDIO))
|
||||
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.SECTION_DEBUG))
|
||||
add(SubmenuSetting(R.string.preferences_general, 0, Settings.MenuTag.SECTION_GENERAL))
|
||||
add(SubmenuSetting(R.string.preferences_system, 0, Settings.MenuTag.SECTION_SYSTEM))
|
||||
add(SubmenuSetting(R.string.preferences_graphics, 0, Settings.MenuTag.SECTION_RENDERER))
|
||||
add(SubmenuSetting(R.string.preferences_audio, 0, Settings.MenuTag.SECTION_AUDIO))
|
||||
add(SubmenuSetting(R.string.preferences_debug, 0, Settings.MenuTag.SECTION_DEBUG))
|
||||
add(
|
||||
RunnableSetting(R.string.reset_to_default, 0, false) {
|
||||
settingsViewModel.setShouldShowResetSettingsDialog(true)
|
||||
@ -102,7 +89,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addGeneralSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_general))
|
||||
sl.apply {
|
||||
add(BooleanSetting.RENDERER_USE_SPEED_LIMIT.key)
|
||||
add(ShortSetting.RENDERER_SPEED_LIMIT.key)
|
||||
@ -112,7 +98,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addSystemSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_system))
|
||||
sl.apply {
|
||||
add(BooleanSetting.USE_DOCKED_MODE.key)
|
||||
add(IntSetting.REGION_INDEX.key)
|
||||
@ -123,7 +108,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addGraphicsSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_graphics))
|
||||
sl.apply {
|
||||
add(IntSetting.RENDERER_ACCURACY.key)
|
||||
add(IntSetting.RENDERER_RESOLUTION.key)
|
||||
@ -140,7 +124,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addAudioSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_audio))
|
||||
sl.apply {
|
||||
add(IntSetting.AUDIO_OUTPUT_ENGINE.key)
|
||||
add(ByteSetting.AUDIO_VOLUME.key)
|
||||
@ -148,7 +131,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addThemeSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_theme))
|
||||
sl.apply {
|
||||
val theme: AbstractIntSetting = object : AbstractIntSetting {
|
||||
override val int: Int
|
||||
@ -261,7 +243,6 @@ class SettingsFragmentPresenter(
|
||||
}
|
||||
|
||||
private fun addDebugSettings(sl: ArrayList<SettingsItem>) {
|
||||
settingsViewModel.setToolbarTitle(context.getString(R.string.preferences_debug))
|
||||
sl.apply {
|
||||
add(HeaderSetting(R.string.gpu))
|
||||
add(IntSetting.RENDERER_BACKEND.key)
|
||||
|
@ -39,6 +39,7 @@ import androidx.window.layout.WindowLayoutInfo
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.slider.Slider
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.HomeNavigationDirections
|
||||
import org.yuzu.yuzu_emu.NativeLibrary
|
||||
@ -49,7 +50,6 @@ import org.yuzu.yuzu_emu.databinding.DialogOverlayAdjustBinding
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentEmulationBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.IntSetting
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import org.yuzu.yuzu_emu.model.EmulationViewModel
|
||||
import org.yuzu.yuzu_emu.overlay.InputOverlay
|
||||
@ -129,6 +129,8 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.surfaceEmulation.holder.addCallback(this)
|
||||
binding.showFpsText.setTextColor(Color.YELLOW)
|
||||
@ -163,7 +165,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
R.id.menu_settings -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
true
|
||||
@ -205,59 +207,80 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect { updateFoldableLayout(requireActivity() as EmulationActivity, it) }
|
||||
}
|
||||
}
|
||||
|
||||
GameIconUtils.loadGameIcon(game, binding.loadingImage)
|
||||
binding.loadingTitle.text = game.title
|
||||
binding.loadingTitle.isSelected = true
|
||||
binding.loadingText.isSelected = true
|
||||
|
||||
emulationViewModel.shaderProgress.observe(viewLifecycleOwner) {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value!!) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
WindowInfoTracker.getOrCreate(requireContext())
|
||||
.windowLayoutInfo(requireActivity())
|
||||
.collect {
|
||||
updateFoldableLayout(requireActivity() as EmulationActivity, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderProgress.collectLatest {
|
||||
if (it > 0 && it != emulationViewModel.totalShaders.value) {
|
||||
binding.loadingProgressIndicator.isIndeterminate = false
|
||||
|
||||
if (it == emulationViewModel.totalShaders.value!!) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
if (it < binding.loadingProgressIndicator.max) {
|
||||
binding.loadingProgressIndicator.progress = it
|
||||
}
|
||||
}
|
||||
|
||||
if (it == emulationViewModel.totalShaders.value) {
|
||||
binding.loadingText.setText(R.string.loading)
|
||||
binding.loadingProgressIndicator.isIndeterminate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
emulationViewModel.totalShaders.observe(viewLifecycleOwner) {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
emulationViewModel.shaderMessage.observe(viewLifecycleOwner) {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.totalShaders.collectLatest {
|
||||
binding.loadingProgressIndicator.max = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emulationViewModel.emulationStarted.observe(viewLifecycleOwner) { started ->
|
||||
if (started) {
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
// Setup overlay
|
||||
updateShowFpsOverlay()
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.shaderMessage.collectLatest {
|
||||
if (it.isNotEmpty()) {
|
||||
binding.loadingText.text = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.emulationStarted.collectLatest {
|
||||
if (it) {
|
||||
binding.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
|
||||
ViewUtils.showView(binding.surfaceInputOverlay)
|
||||
ViewUtils.hideView(binding.loadingIndicator)
|
||||
|
||||
emulationViewModel.isEmulationStopping.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
// Setup overlay
|
||||
updateShowFpsOverlay()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
emulationViewModel.isEmulationStopping.collectLatest {
|
||||
if (it) {
|
||||
binding.loadingText.setText(R.string.shutting_down)
|
||||
ViewUtils.showView(binding.loadingIndicator)
|
||||
ViewUtils.hideView(binding.inputContainer)
|
||||
ViewUtils.hideView(binding.showFpsText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -274,9 +297,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (EmulationMenuSettings.showOverlay &&
|
||||
emulationViewModel.emulationStarted.value == true
|
||||
) {
|
||||
if (EmulationMenuSettings.showOverlay && emulationViewModel.emulationStarted.value) {
|
||||
binding.surfaceInputOverlay.post {
|
||||
binding.surfaceInputOverlay.visibility = View.VISIBLE
|
||||
}
|
||||
|
@ -37,7 +37,6 @@ import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||
@ -78,7 +77,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
@ -100,7 +99,7 @@ class HomeSettingsFragment : Fragment() {
|
||||
{
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
Settings.SECTION_THEME
|
||||
Settings.MenuTag.SECTION_THEME
|
||||
)
|
||||
binding.root.findNavController().navigate(action)
|
||||
}
|
||||
|
@ -5,49 +5,75 @@ package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.model.TaskViewModel
|
||||
|
||||
class IndeterminateProgressDialogFragment : DialogFragment() {
|
||||
private val taskViewModel: TaskViewModel by activityViewModels()
|
||||
|
||||
private lateinit var binding: DialogProgressBarBinding
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val titleId = requireArguments().getInt(TITLE)
|
||||
|
||||
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
progressBinding.progressBar.isIndeterminate = true
|
||||
binding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||
binding.progressBar.isIndeterminate = true
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(titleId)
|
||||
.setView(progressBinding.root)
|
||||
.setView(binding.root)
|
||||
.create()
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
|
||||
taskViewModel.isComplete.observe(this) { complete ->
|
||||
if (complete) {
|
||||
dialog.dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG).show()
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
|
||||
if (taskViewModel.isRunning.value == false) {
|
||||
if (!taskViewModel.isRunning.value) {
|
||||
taskViewModel.runTask()
|
||||
}
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
taskViewModel.isComplete.collect {
|
||||
if (it) {
|
||||
dismiss()
|
||||
when (val result = taskViewModel.result.value) {
|
||||
is String -> Toast.makeText(requireContext(), result, Toast.LENGTH_LONG)
|
||||
.show()
|
||||
|
||||
is MessageDialogFragment -> result.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
MessageDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
taskViewModel.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "IndeterminateProgressDialogFragment"
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.fragments
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
@ -17,9 +18,13 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.preference.PreferenceManager
|
||||
import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Locale
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@ -52,6 +57,8 @@ class SearchFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = false)
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
@ -79,21 +86,32 @@ class SearchFragment : Fragment() {
|
||||
filterAndSearch()
|
||||
}
|
||||
|
||||
gamesViewModel.apply {
|
||||
searchFocused.observe(viewLifecycleOwner) { searchFocused ->
|
||||
if (searchFocused) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchFocused.collect {
|
||||
if (it) {
|
||||
focusSearch()
|
||||
gamesViewModel.setSearchFocused(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
games.observe(viewLifecycleOwner) { filterAndSearch() }
|
||||
searchedGames.observe(viewLifecycleOwner) {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.games.collect { filterAndSearch() }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
gamesViewModel.searchedGames.collect {
|
||||
(binding.gridGamesSearch.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noResultsView.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noResultsView.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -109,7 +127,7 @@ class SearchFragment : Fragment() {
|
||||
private inner class ScoredGame(val score: Double, val item: Game)
|
||||
|
||||
private fun filterAndSearch() {
|
||||
val baseList = gamesViewModel.games.value!!
|
||||
val baseList = gamesViewModel.games.value
|
||||
val filteredList: List<Game> = when (binding.chipGroup.checkedChipId) {
|
||||
R.id.chip_recently_played -> {
|
||||
baseList.filter {
|
||||
|
@ -15,10 +15,14 @@ import androidx.core.view.updatePadding
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.divider.MaterialDividerItemDecoration
|
||||
import com.google.android.material.transition.MaterialSharedAxis
|
||||
import info.debatty.java.stringsimilarity.Cosine
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentSettingsSearchBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
@ -79,10 +83,14 @@ class SettingsSearchFragment : Fragment() {
|
||||
search()
|
||||
binding.settingsList.smoothScrollToPosition(0)
|
||||
}
|
||||
settingsViewModel.shouldReloadSettingsList.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
settingsViewModel.shouldReloadSettingsList.collect {
|
||||
if (it) {
|
||||
settingsViewModel.setShouldReloadSettingsList(false)
|
||||
search()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,10 +22,14 @@ import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
@ -206,10 +210,14 @@ class SetupFragment : Fragment() {
|
||||
)
|
||||
}
|
||||
|
||||
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.shouldPageForward.collect {
|
||||
if (it) {
|
||||
pageForward()
|
||||
homeViewModel.setShouldPageForward(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,28 +3,28 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class EmulationViewModel : ViewModel() {
|
||||
private val _emulationStarted = MutableLiveData(false)
|
||||
val emulationStarted: LiveData<Boolean> get() = _emulationStarted
|
||||
val emulationStarted: StateFlow<Boolean> get() = _emulationStarted
|
||||
private val _emulationStarted = MutableStateFlow(false)
|
||||
|
||||
private val _isEmulationStopping = MutableLiveData(false)
|
||||
val isEmulationStopping: LiveData<Boolean> get() = _isEmulationStopping
|
||||
val isEmulationStopping: StateFlow<Boolean> get() = _isEmulationStopping
|
||||
private val _isEmulationStopping = MutableStateFlow(false)
|
||||
|
||||
private val _shaderProgress = MutableLiveData(0)
|
||||
val shaderProgress: LiveData<Int> get() = _shaderProgress
|
||||
val shaderProgress: StateFlow<Int> get() = _shaderProgress
|
||||
private val _shaderProgress = MutableStateFlow(0)
|
||||
|
||||
private val _totalShaders = MutableLiveData(0)
|
||||
val totalShaders: LiveData<Int> get() = _totalShaders
|
||||
val totalShaders: StateFlow<Int> get() = _totalShaders
|
||||
private val _totalShaders = MutableStateFlow(0)
|
||||
|
||||
private val _shaderMessage = MutableLiveData("")
|
||||
val shaderMessage: LiveData<String> get() = _shaderMessage
|
||||
val shaderMessage: StateFlow<String> get() = _shaderMessage
|
||||
private val _shaderMessage = MutableStateFlow("")
|
||||
|
||||
fun setEmulationStarted(started: Boolean) {
|
||||
_emulationStarted.postValue(started)
|
||||
_emulationStarted.value = started
|
||||
}
|
||||
|
||||
fun setIsEmulationStopping(value: Boolean) {
|
||||
@ -50,10 +50,18 @@ class EmulationViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_emulationStarted.value = false
|
||||
_isEmulationStopping.value = false
|
||||
_shaderProgress.value = 0
|
||||
_totalShaders.value = 0
|
||||
_shaderMessage.value = ""
|
||||
setEmulationStarted(false)
|
||||
setIsEmulationStopping(false)
|
||||
setShaderProgress(0)
|
||||
setTotalShaders(0)
|
||||
setShaderMessage("")
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_EMULATION_STARTED = "EmulationStarted"
|
||||
const val KEY_IS_EMULATION_STOPPING = "IsEmulationStarting"
|
||||
const val KEY_SHADER_PROGRESS = "ShaderProgress"
|
||||
const val KEY_TOTAL_SHADERS = "TotalShaders"
|
||||
const val KEY_SHADER_MESSAGE = "ShaderMessage"
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,13 @@ package org.yuzu.yuzu_emu.model
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
@ -24,23 +24,23 @@ import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
class GamesViewModel : ViewModel() {
|
||||
private val _games = MutableLiveData<List<Game>>(emptyList())
|
||||
val games: LiveData<List<Game>> get() = _games
|
||||
val games: StateFlow<List<Game>> get() = _games
|
||||
private val _games = MutableStateFlow(emptyList<Game>())
|
||||
|
||||
private val _searchedGames = MutableLiveData<List<Game>>(emptyList())
|
||||
val searchedGames: LiveData<List<Game>> get() = _searchedGames
|
||||
val searchedGames: StateFlow<List<Game>> get() = _searchedGames
|
||||
private val _searchedGames = MutableStateFlow(emptyList<Game>())
|
||||
|
||||
private val _isReloading = MutableLiveData(false)
|
||||
val isReloading: LiveData<Boolean> get() = _isReloading
|
||||
val isReloading: StateFlow<Boolean> get() = _isReloading
|
||||
private val _isReloading = MutableStateFlow(false)
|
||||
|
||||
private val _shouldSwapData = MutableLiveData(false)
|
||||
val shouldSwapData: LiveData<Boolean> get() = _shouldSwapData
|
||||
val shouldSwapData: StateFlow<Boolean> get() = _shouldSwapData
|
||||
private val _shouldSwapData = MutableStateFlow(false)
|
||||
|
||||
private val _shouldScrollToTop = MutableLiveData(false)
|
||||
val shouldScrollToTop: LiveData<Boolean> get() = _shouldScrollToTop
|
||||
val shouldScrollToTop: StateFlow<Boolean> get() = _shouldScrollToTop
|
||||
private val _shouldScrollToTop = MutableStateFlow(false)
|
||||
|
||||
private val _searchFocused = MutableLiveData(false)
|
||||
val searchFocused: LiveData<Boolean> get() = _searchFocused
|
||||
val searchFocused: StateFlow<Boolean> get() = _searchFocused
|
||||
private val _searchFocused = MutableStateFlow(false)
|
||||
|
||||
init {
|
||||
// Ensure keys are loaded so that ROM metadata can be decrypted.
|
||||
@ -79,36 +79,36 @@ class GamesViewModel : ViewModel() {
|
||||
)
|
||||
)
|
||||
|
||||
_games.postValue(sortedList)
|
||||
_games.value = sortedList
|
||||
}
|
||||
|
||||
fun setSearchedGames(games: List<Game>) {
|
||||
_searchedGames.postValue(games)
|
||||
_searchedGames.value = games
|
||||
}
|
||||
|
||||
fun setShouldSwapData(shouldSwap: Boolean) {
|
||||
_shouldSwapData.postValue(shouldSwap)
|
||||
_shouldSwapData.value = shouldSwap
|
||||
}
|
||||
|
||||
fun setShouldScrollToTop(shouldScroll: Boolean) {
|
||||
_shouldScrollToTop.postValue(shouldScroll)
|
||||
_shouldScrollToTop.value = shouldScroll
|
||||
}
|
||||
|
||||
fun setSearchFocused(searchFocused: Boolean) {
|
||||
_searchFocused.postValue(searchFocused)
|
||||
_searchFocused.value = searchFocused
|
||||
}
|
||||
|
||||
fun reloadGames(directoryChanged: Boolean) {
|
||||
if (isReloading.value == true) {
|
||||
if (isReloading.value) {
|
||||
return
|
||||
}
|
||||
_isReloading.postValue(true)
|
||||
_isReloading.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
NativeLibrary.resetRomMetadata()
|
||||
setGames(GameHelper.getGames())
|
||||
_isReloading.postValue(false)
|
||||
_isReloading.value = false
|
||||
|
||||
if (directoryChanged) {
|
||||
setShouldSwapData(true)
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
data class HomeSetting(
|
||||
val titleId: Int,
|
||||
@ -14,5 +14,5 @@ data class HomeSetting(
|
||||
val isEnabled: () -> Boolean = { true },
|
||||
val disabledTitleId: Int = 0,
|
||||
val disabledMessageId: Int = 0,
|
||||
val details: LiveData<String> = MutableLiveData("")
|
||||
val details: StateFlow<String> = MutableStateFlow("")
|
||||
)
|
||||
|
@ -5,47 +5,43 @@ package org.yuzu.yuzu_emu.model
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.preference.PreferenceManager
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
||||
|
||||
class HomeViewModel : ViewModel() {
|
||||
private val _navigationVisible = MutableLiveData<Pair<Boolean, Boolean>>()
|
||||
val navigationVisible: LiveData<Pair<Boolean, Boolean>> get() = _navigationVisible
|
||||
val navigationVisible: StateFlow<Pair<Boolean, Boolean>> get() = _navigationVisible
|
||||
private val _navigationVisible = MutableStateFlow(Pair(false, false))
|
||||
|
||||
private val _statusBarShadeVisible = MutableLiveData(true)
|
||||
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
|
||||
val statusBarShadeVisible: StateFlow<Boolean> get() = _statusBarShadeVisible
|
||||
private val _statusBarShadeVisible = MutableStateFlow(true)
|
||||
|
||||
private val _shouldPageForward = MutableLiveData(false)
|
||||
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
|
||||
val shouldPageForward: StateFlow<Boolean> get() = _shouldPageForward
|
||||
private val _shouldPageForward = MutableStateFlow(false)
|
||||
|
||||
private val _gamesDir = MutableLiveData(
|
||||
val gamesDir: StateFlow<String> get() = _gamesDir
|
||||
private val _gamesDir = MutableStateFlow(
|
||||
Uri.parse(
|
||||
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
|
||||
.getString(GameHelper.KEY_GAME_PATH, "")
|
||||
).path ?: ""
|
||||
)
|
||||
val gamesDir: LiveData<String> get() = _gamesDir
|
||||
|
||||
var navigatedToSetup = false
|
||||
|
||||
init {
|
||||
_navigationVisible.value = Pair(false, false)
|
||||
}
|
||||
|
||||
fun setNavigationVisibility(visible: Boolean, animated: Boolean) {
|
||||
if (_navigationVisible.value?.first == visible) {
|
||||
if (navigationVisible.value.first == visible) {
|
||||
return
|
||||
}
|
||||
_navigationVisible.value = Pair(visible, animated)
|
||||
}
|
||||
|
||||
fun setStatusBarShadeVisibility(visible: Boolean) {
|
||||
if (_statusBarShadeVisible.value == visible) {
|
||||
if (statusBarShadeVisible.value == visible) {
|
||||
return
|
||||
}
|
||||
_statusBarShadeVisible.value = visible
|
||||
|
@ -3,48 +3,43 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import org.yuzu.yuzu_emu.features.settings.model.view.SettingsItem
|
||||
|
||||
class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
|
||||
class SettingsViewModel : ViewModel() {
|
||||
var game: Game? = null
|
||||
|
||||
var shouldSave = false
|
||||
|
||||
var clickedItem: SettingsItem? = null
|
||||
|
||||
private val _toolbarTitle = MutableLiveData("")
|
||||
val toolbarTitle: LiveData<String> get() = _toolbarTitle
|
||||
val shouldRecreate: StateFlow<Boolean> get() = _shouldRecreate
|
||||
private val _shouldRecreate = MutableStateFlow(false)
|
||||
|
||||
private val _shouldRecreate = MutableLiveData(false)
|
||||
val shouldRecreate: LiveData<Boolean> get() = _shouldRecreate
|
||||
val shouldNavigateBack: StateFlow<Boolean> get() = _shouldNavigateBack
|
||||
private val _shouldNavigateBack = MutableStateFlow(false)
|
||||
|
||||
private val _shouldNavigateBack = MutableLiveData(false)
|
||||
val shouldNavigateBack: LiveData<Boolean> get() = _shouldNavigateBack
|
||||
val shouldShowResetSettingsDialog: StateFlow<Boolean> get() = _shouldShowResetSettingsDialog
|
||||
private val _shouldShowResetSettingsDialog = MutableStateFlow(false)
|
||||
|
||||
private val _shouldShowResetSettingsDialog = MutableLiveData(false)
|
||||
val shouldShowResetSettingsDialog: LiveData<Boolean> get() = _shouldShowResetSettingsDialog
|
||||
val shouldReloadSettingsList: StateFlow<Boolean> get() = _shouldReloadSettingsList
|
||||
private val _shouldReloadSettingsList = MutableStateFlow(false)
|
||||
|
||||
private val _shouldReloadSettingsList = MutableLiveData(false)
|
||||
val shouldReloadSettingsList: LiveData<Boolean> get() = _shouldReloadSettingsList
|
||||
val isUsingSearch: StateFlow<Boolean> get() = _isUsingSearch
|
||||
private val _isUsingSearch = MutableStateFlow(false)
|
||||
|
||||
private val _isUsingSearch = MutableLiveData(false)
|
||||
val isUsingSearch: LiveData<Boolean> get() = _isUsingSearch
|
||||
val sliderProgress: StateFlow<Int> get() = _sliderProgress
|
||||
private val _sliderProgress = MutableStateFlow(-1)
|
||||
|
||||
val sliderProgress = savedStateHandle.getStateFlow(KEY_SLIDER_PROGRESS, -1)
|
||||
val sliderTextValue: StateFlow<String> get() = _sliderTextValue
|
||||
private val _sliderTextValue = MutableStateFlow("")
|
||||
|
||||
val sliderTextValue = savedStateHandle.getStateFlow(KEY_SLIDER_TEXT_VALUE, "")
|
||||
|
||||
val adapterItemChanged = savedStateHandle.getStateFlow(KEY_ADAPTER_ITEM_CHANGED, -1)
|
||||
|
||||
fun setToolbarTitle(value: String) {
|
||||
_toolbarTitle.value = value
|
||||
}
|
||||
val adapterItemChanged: StateFlow<Int> get() = _adapterItemChanged
|
||||
private val _adapterItemChanged = MutableStateFlow(-1)
|
||||
|
||||
fun setShouldRecreate(value: Boolean) {
|
||||
_shouldRecreate.value = value
|
||||
@ -67,8 +62,8 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
|
||||
}
|
||||
|
||||
fun setSliderTextValue(value: Float, units: String) {
|
||||
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||
savedStateHandle[KEY_SLIDER_TEXT_VALUE] = String.format(
|
||||
_sliderProgress.value = value.toInt()
|
||||
_sliderTextValue.value = String.format(
|
||||
YuzuApplication.appContext.getString(R.string.value_with_units),
|
||||
value.toInt().toString(),
|
||||
units
|
||||
@ -76,21 +71,15 @@ class SettingsViewModel(private val savedStateHandle: SavedStateHandle) : ViewMo
|
||||
}
|
||||
|
||||
fun setSliderProgress(value: Float) {
|
||||
savedStateHandle[KEY_SLIDER_PROGRESS] = value
|
||||
_sliderProgress.value = value.toInt()
|
||||
}
|
||||
|
||||
fun setAdapterItemChanged(value: Int) {
|
||||
savedStateHandle[KEY_ADAPTER_ITEM_CHANGED] = value
|
||||
_adapterItemChanged.value = value
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
game = null
|
||||
shouldSave = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val KEY_SLIDER_TEXT_VALUE = "SliderTextValue"
|
||||
const val KEY_SLIDER_PROGRESS = "SliderProgress"
|
||||
const val KEY_ADAPTER_ITEM_CHANGED = "AdapterItemChanged"
|
||||
}
|
||||
}
|
||||
|
@ -3,29 +3,25 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class TaskViewModel : ViewModel() {
|
||||
private val _result = MutableLiveData<Any>()
|
||||
val result: LiveData<Any> = _result
|
||||
val result: StateFlow<Any> get() = _result
|
||||
private val _result = MutableStateFlow(Any())
|
||||
|
||||
private val _isComplete = MutableLiveData<Boolean>()
|
||||
val isComplete: LiveData<Boolean> = _isComplete
|
||||
val isComplete: StateFlow<Boolean> get() = _isComplete
|
||||
private val _isComplete = MutableStateFlow(false)
|
||||
|
||||
private val _isRunning = MutableLiveData<Boolean>()
|
||||
val isRunning: LiveData<Boolean> = _isRunning
|
||||
val isRunning: StateFlow<Boolean> get() = _isRunning
|
||||
private val _isRunning = MutableStateFlow(false)
|
||||
|
||||
lateinit var task: () -> Any
|
||||
|
||||
init {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_result.value = Any()
|
||||
_isComplete.value = false
|
||||
@ -33,15 +29,16 @@ class TaskViewModel : ViewModel() {
|
||||
}
|
||||
|
||||
fun runTask() {
|
||||
if (_isRunning.value == true) {
|
||||
if (isRunning.value) {
|
||||
return
|
||||
}
|
||||
_isRunning.value = true
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val res = task()
|
||||
_result.postValue(res)
|
||||
_isComplete.postValue(true)
|
||||
_result.value = res
|
||||
_isComplete.value = true
|
||||
_isRunning.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
package org.yuzu.yuzu_emu.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -14,8 +15,12 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.transition.MaterialFadeThrough
|
||||
import kotlinx.coroutines.launch
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentGamesBinding
|
||||
@ -44,6 +49,8 @@ class GamesFragment : Fragment() {
|
||||
return binding.root
|
||||
}
|
||||
|
||||
// This is using the correct scope, lint is just acting up
|
||||
@SuppressLint("UnsafeRepeatOnLifecycleDetector")
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
homeViewModel.setNavigationVisibility(visible = true, animated = false)
|
||||
|
||||
@ -80,37 +87,48 @@ class GamesFragment : Fragment() {
|
||||
if (_binding == null) {
|
||||
return@post
|
||||
}
|
||||
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value!!
|
||||
binding.swipeRefresh.isRefreshing = gamesViewModel.isReloading.value
|
||||
}
|
||||
}
|
||||
|
||||
gamesViewModel.apply {
|
||||
// Watch for when we get updates to any of our games lists
|
||||
isReloading.observe(viewLifecycleOwner) { isReloading ->
|
||||
binding.swipeRefresh.isRefreshing = isReloading
|
||||
}
|
||||
games.observe(viewLifecycleOwner) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.GONE
|
||||
viewLifecycleOwner.lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.isReloading.collect { binding.swipeRefresh.isRefreshing = it }
|
||||
}
|
||||
}
|
||||
shouldSwapData.observe(viewLifecycleOwner) { shouldSwapData ->
|
||||
if (shouldSwapData) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value!!
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.games.collect {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.noticeText.visibility = View.VISIBLE
|
||||
} else {
|
||||
binding.noticeText.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the user reselected the games menu item and then scroll to top of the list
|
||||
shouldScrollToTop.observe(viewLifecycleOwner) { shouldScroll ->
|
||||
if (shouldScroll) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldSwapData.collect {
|
||||
if (it) {
|
||||
(binding.gridGames.adapter as GameAdapter).submitList(
|
||||
gamesViewModel.games.value
|
||||
)
|
||||
gamesViewModel.setShouldSwapData(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
gamesViewModel.shouldScrollToTop.collect {
|
||||
if (it) {
|
||||
scrollToTop()
|
||||
gamesViewModel.setShouldScrollToTop(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,9 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
@ -40,7 +42,6 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
|
||||
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
|
||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||
@ -107,7 +108,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
R.id.homeSettingsFragment -> {
|
||||
val action = HomeNavigationDirections.actionGlobalSettingsActivity(
|
||||
null,
|
||||
SettingsFile.FILE_NAME_CONFIG
|
||||
Settings.MenuTag.SECTION_ROOT
|
||||
)
|
||||
navHostFragment.navController.navigate(action)
|
||||
}
|
||||
@ -115,16 +116,22 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
|
||||
}
|
||||
|
||||
// Prevents navigation from being drawn for a short time on recreation if set to hidden
|
||||
if (!homeViewModel.navigationVisible.value?.first!!) {
|
||||
if (!homeViewModel.navigationVisible.value.first) {
|
||||
binding.navigationView.visibility = View.INVISIBLE
|
||||
binding.statusBarShade.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
homeViewModel.navigationVisible.observe(this) {
|
||||
showNavigation(it.first, it.second)
|
||||
}
|
||||
homeViewModel.statusBarShadeVisible.observe(this) { visible ->
|
||||
showStatusBarShade(visible)
|
||||
lifecycleScope.apply {
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.navigationVisible.collect { showNavigation(it.first, it.second) }
|
||||
}
|
||||
}
|
||||
launch {
|
||||
repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
homeViewModel.statusBarShadeVisible.collect { showStatusBarShade(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Dismiss previous notifications (should not happen unless a crash occurred)
|
||||
|
@ -6,9 +6,11 @@ package org.yuzu.yuzu_emu.utils
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.widget.ImageView
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil.ImageLoader
|
||||
import coil.decode.DataSource
|
||||
import coil.executeBlocking
|
||||
import coil.fetch.DrawableResult
|
||||
import coil.fetch.FetchResult
|
||||
import coil.fetch.Fetcher
|
||||
@ -74,4 +76,13 @@ object GameIconUtils {
|
||||
.build()
|
||||
imageLoader.enqueue(request)
|
||||
}
|
||||
|
||||
fun getGameIcon(game: Game): Bitmap {
|
||||
val request = ImageRequest.Builder(YuzuApplication.appContext)
|
||||
.data(game)
|
||||
.error(R.drawable.default_icon)
|
||||
.build()
|
||||
return imageLoader.executeBlocking(request)
|
||||
.drawable!!.toBitmap(config = Bitmap.Config.ARGB_8888)
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +282,7 @@ void Config::ReadValues() {
|
||||
std::stringstream ss(title_list);
|
||||
std::string line;
|
||||
while (std::getline(ss, line, '|')) {
|
||||
const auto title_id = std::stoul(line, nullptr, 16);
|
||||
const auto title_id = std::strtoul(line.c_str(), nullptr, 16);
|
||||
const auto disabled_list = config->Get("AddOns", "disabled_" + line, "");
|
||||
|
||||
std::stringstream inner_ss(disabled_list);
|
||||
|
@ -262,14 +262,12 @@ public:
|
||||
Core::SystemResultStatus InitializeEmulation(const std::string& filepath) {
|
||||
std::scoped_lock lock(m_mutex);
|
||||
|
||||
// Loads the configuration.
|
||||
Config{};
|
||||
|
||||
// Create the render window.
|
||||
m_window = std::make_unique<EmuWindow_Android>(&m_input_subsystem, m_native_window,
|
||||
m_vulkan_library);
|
||||
|
||||
m_system.SetFilesystem(m_vfs);
|
||||
m_system.GetUserChannel().clear();
|
||||
|
||||
// Initialize system.
|
||||
jauto android_keyboard = std::make_unique<SoftwareKeyboard::AndroidKeyboard>();
|
||||
@ -329,12 +327,13 @@ public:
|
||||
m_system.ShutdownMainProcess();
|
||||
m_detached_tasks.WaitForAllTasks();
|
||||
m_load_result = Core::SystemResultStatus::ErrorNotInitialized;
|
||||
m_window.reset();
|
||||
OnEmulationStopped(Core::SystemResultStatus::Success);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tear down the render window.
|
||||
m_window.reset();
|
||||
|
||||
OnEmulationStopped(m_load_result);
|
||||
}
|
||||
|
||||
void PauseEmulation() {
|
||||
@ -671,18 +670,6 @@ jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isPaused(JNIEnv* env, jclass claz
|
||||
return static_cast<jboolean>(EmulationSession::GetInstance().IsPaused());
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_muteAduio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = true;
|
||||
}
|
||||
|
||||
void Java_org_yuzu_yuzu_1emu_NativeLibrary_unmuteAudio(JNIEnv* env, jclass clazz) {
|
||||
Settings::values.audio_muted = false;
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isMuted(JNIEnv* env, jclass clazz) {
|
||||
return static_cast<jboolean>(Settings::values.audio_muted.GetValue());
|
||||
}
|
||||
|
||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_isHandheldOnly(JNIEnv* env, jclass clazz) {
|
||||
return EmulationSession::GetInstance().IsHandheldOnly();
|
||||
}
|
||||
|
11
src/android/app/src/main/res/drawable/shortcut.xml
Normal file
11
src/android/app/src/main/res/drawable/shortcut.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item>
|
||||
<color android:color="@android:color/white" />
|
||||
</item>
|
||||
<item android:id="@+id/shortcut_foreground">
|
||||
<bitmap android:src="@drawable/default_icon" />
|
||||
</item>
|
||||
|
||||
</layer-list>
|
@ -27,7 +27,7 @@
|
||||
app:nullable="true" />
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
|
@ -82,7 +82,7 @@
|
||||
app:nullable="true" />
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
</activity>
|
||||
|
||||
<action
|
||||
|
@ -10,7 +10,7 @@
|
||||
android:label="SettingsFragment">
|
||||
<argument
|
||||
android:name="menuTag"
|
||||
app:argType="string" />
|
||||
app:argType="org.yuzu.yuzu_emu.features.settings.model.Settings$MenuTag" />
|
||||
<argument
|
||||
android:name="game"
|
||||
app:argType="org.yuzu.yuzu_emu.model.Game"
|
||||
|
@ -12,6 +12,7 @@
|
||||
<dimen name="spacing_refresh_end">72dp</dimen>
|
||||
<dimen name="menu_width">256dp</dimen>
|
||||
<dimen name="card_width">165dp</dimen>
|
||||
<dimen name="icon_inset">24dp</dimen>
|
||||
|
||||
<dimen name="dialog_margin">20dp</dimen>
|
||||
<dimen name="elevated_app_bar">3dp</dimen>
|
||||
|
@ -88,8 +88,13 @@ MailboxMessage AudioRenderer::Receive(Direction dir, bool block) {
|
||||
return mailbox.Receive(dir, block);
|
||||
}
|
||||
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept {
|
||||
command_buffers[session_id] = buffer;
|
||||
void AudioRenderer::SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept {
|
||||
command_buffers[session_id].buffer = buffer;
|
||||
command_buffers[session_id].size = size;
|
||||
command_buffers[session_id].time_limit = time_limit;
|
||||
command_buffers[session_id].applet_resource_user_id = applet_resource_user_id;
|
||||
command_buffers[session_id].reset_buffer = reset;
|
||||
}
|
||||
|
||||
u32 AudioRenderer::GetRemainCommandCount(s32 session_id) const noexcept {
|
||||
|
@ -75,7 +75,8 @@ public:
|
||||
void Send(Direction dir, MailboxMessage message);
|
||||
MailboxMessage Receive(Direction dir, bool block = true);
|
||||
|
||||
void SetCommandBuffer(s32 session_id, CommandBuffer& buffer) noexcept;
|
||||
void SetCommandBuffer(s32 session_id, CpuAddr buffer, u64 size, u64 time_limit,
|
||||
u64 applet_resource_user_id, bool reset) noexcept;
|
||||
u32 GetRemainCommandCount(s32 session_id) const noexcept;
|
||||
void ClearRemainCommandCount(s32 session_id) noexcept;
|
||||
u64 GetRenderingStartTick(s32 session_id) const noexcept;
|
||||
|
@ -37,11 +37,6 @@ u32 CommandListProcessor::GetRemainingCommandCount() const {
|
||||
return command_count - processed_command_count;
|
||||
}
|
||||
|
||||
void CommandListProcessor::SetBuffer(const CpuAddr buffer, const u64 size) {
|
||||
commands = reinterpret_cast<u8*>(buffer + sizeof(Renderer::CommandListHeader));
|
||||
commands_buffer_size = size;
|
||||
}
|
||||
|
||||
Sink::SinkStream* CommandListProcessor::GetOutputSinkStream() const {
|
||||
return stream;
|
||||
}
|
||||
|
@ -56,14 +56,6 @@ public:
|
||||
*/
|
||||
u32 GetRemainingCommandCount() const;
|
||||
|
||||
/**
|
||||
* Set the command buffer.
|
||||
*
|
||||
* @param buffer - The buffer to use.
|
||||
* @param size - The size of the buffer.
|
||||
*/
|
||||
void SetBuffer(CpuAddr buffer, u64 size);
|
||||
|
||||
/**
|
||||
* Get the stream for this command list.
|
||||
*
|
||||
|
@ -20,6 +20,12 @@ void AdpcmDataSourceVersion1Command::Process(const AudioRenderer::CommandListPro
|
||||
auto out_buffer{processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count)};
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::Adpcm},
|
||||
.output{out_buffer},
|
||||
|
@ -123,11 +123,13 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_process{
|
||||
std::min(req.end_offset - req.start_offset - req.offset, req.samples_to_read)};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_to_process{std::min(req.end_offset - start_pos, req.samples_to_read)};
|
||||
if (samples_to_process == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto samples_to_read{samples_to_process};
|
||||
auto start_pos{req.start_offset + req.offset};
|
||||
auto samples_remaining_in_frame{start_pos % SamplesPerFrame};
|
||||
auto position_in_frame{(start_pos / SamplesPerFrame) * NibblesPerFrame +
|
||||
samples_remaining_in_frame};
|
||||
@ -225,13 +227,24 @@ static u32 DecodeAdpcm(Core::Memory::Memory& memory, std::span<s16> out_buffer,
|
||||
* @param args - The wavebuffer data, and information for how to decode it.
|
||||
*/
|
||||
void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuffersArgs& args) {
|
||||
static constexpr auto EndWaveBuffer = [](auto& voice_state, auto& wavebuffer, auto& index,
|
||||
auto& played_samples, auto& consumed) -> void {
|
||||
voice_state.wave_buffer_valid[index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_samples = 0;
|
||||
}
|
||||
|
||||
index = (index + 1) % MaxWaveBuffers;
|
||||
consumed++;
|
||||
};
|
||||
auto& voice_state{*args.voice_state};
|
||||
auto remaining_sample_count{args.sample_count};
|
||||
auto fraction{voice_state.fraction};
|
||||
|
||||
const auto sample_rate_ratio{
|
||||
(Common::FixedPoint<49, 15>(args.source_sample_rate) / args.target_sample_rate) *
|
||||
args.pitch};
|
||||
const auto sample_rate_ratio{Common::FixedPoint<49, 15>(
|
||||
(f32)args.source_sample_rate / (f32)args.target_sample_rate * (f32)args.pitch)};
|
||||
const auto size_required{fraction + remaining_sample_count * sample_rate_ratio};
|
||||
|
||||
if (size_required < 0) {
|
||||
@ -298,22 +311,23 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
auto end_offset{wavebuffer.end_offset};
|
||||
|
||||
if (wavebuffer.loop && voice_state.loop_count > 0 &&
|
||||
wavebuffer.loop_start_offset != 0 && wavebuffer.loop_end_offset != 0 &&
|
||||
wavebuffer.loop_start_offset <= wavebuffer.loop_end_offset) {
|
||||
start_offset = wavebuffer.loop_start_offset;
|
||||
end_offset = wavebuffer.loop_end_offset;
|
||||
}
|
||||
|
||||
DecodeArg decode_arg{.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read}};
|
||||
DecodeArg decode_arg{
|
||||
.buffer{wavebuffer.buffer},
|
||||
.buffer_size{wavebuffer.buffer_size},
|
||||
.start_offset{start_offset},
|
||||
.end_offset{end_offset},
|
||||
.channel_count{args.channel_count},
|
||||
.coefficients{},
|
||||
.adpcm_context{nullptr},
|
||||
.target_channel{args.channel},
|
||||
.offset{offset},
|
||||
.samples_to_read{samples_to_read - samples_read},
|
||||
};
|
||||
|
||||
s32 samples_decoded{0};
|
||||
|
||||
@ -350,42 +364,30 @@ void DecodeFromWaveBuffers(Core::Memory::Memory& memory, const DecodeFromWaveBuf
|
||||
temp_buffer_pos += samples_decoded;
|
||||
offset += samples_decoded;
|
||||
|
||||
if (samples_decoded == 0 || offset >= end_offset - start_offset) {
|
||||
offset = 0;
|
||||
if (!wavebuffer.loop) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
if (samples_decoded && offset < end_offset - start_offset) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
} else {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
voice_state.wave_buffer_valid[wavebuffer_index] = false;
|
||||
voice_state.loop_count = 0;
|
||||
|
||||
if (wavebuffer.stream_ended) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
|
||||
wavebuffer_index = (wavebuffer_index + 1) % MaxWaveBuffers;
|
||||
wavebuffers_consumed++;
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
offset = 0;
|
||||
if (wavebuffer.loop) {
|
||||
voice_state.loop_count++;
|
||||
if (wavebuffer.loop_count >= 0 &&
|
||||
(voice_state.loop_count > wavebuffer.loop_count || samples_decoded == 0)) {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
|
||||
if (samples_decoded == 0) {
|
||||
is_buffer_starved = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (args.IsVoicePlayedSampleCountResetAtLoopPointSupported) {
|
||||
played_sample_count = 0;
|
||||
}
|
||||
} else {
|
||||
EndWaveBuffer(voice_state, wavebuffer, wavebuffer_index, played_sample_count,
|
||||
wavebuffers_consumed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,12 @@ void PcmFloatDataSourceVersion1Command::Process(
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmFloat},
|
||||
.output{out_buffer},
|
||||
|
@ -23,6 +23,12 @@ void PcmInt16DataSourceVersion1Command::Process(
|
||||
auto out_buffer = processor.mix_buffers.subspan(output_index * processor.sample_count,
|
||||
processor.sample_count);
|
||||
|
||||
for (auto& wave_buffer : wave_buffers) {
|
||||
wave_buffer.loop_start_offset = wave_buffer.start_offset;
|
||||
wave_buffer.loop_end_offset = wave_buffer.end_offset;
|
||||
wave_buffer.loop_count = wave_buffer.loop ? -1 : 0;
|
||||
}
|
||||
|
||||
DecodeFromWaveBuffersArgs args{
|
||||
.sample_format{SampleFormat::PcmInt16},
|
||||
.output{out_buffer},
|
||||
|
@ -609,17 +609,11 @@ void System::SendCommandToDsp() {
|
||||
time_limit_percent = 70.0f;
|
||||
}
|
||||
|
||||
AudioRenderer::CommandBuffer command_buffer{
|
||||
.buffer{translated_addr},
|
||||
.size{command_size},
|
||||
.time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))},
|
||||
.applet_resource_user_id{applet_resource_user_id},
|
||||
.reset_buffer{reset_command_buffers},
|
||||
};
|
||||
|
||||
audio_renderer.SetCommandBuffer(session_id, command_buffer);
|
||||
auto time_limit{
|
||||
static_cast<u64>((time_limit_percent / 100) * 2'880'000.0 *
|
||||
(static_cast<f32>(render_time_limit_percent) / 100.0f))};
|
||||
audio_renderer.SetCommandBuffer(session_id, translated_addr, command_size, time_limit,
|
||||
applet_resource_user_id, reset_command_buffers);
|
||||
reset_command_buffers = false;
|
||||
command_buffer_size = command_size;
|
||||
if (remaining_command_count == 0) {
|
||||
|
@ -151,6 +151,10 @@ add_library(common STATIC
|
||||
zstd_compression.h
|
||||
)
|
||||
|
||||
if (YUZU_ENABLE_PORTABLE)
|
||||
add_compile_definitions(YUZU_ENABLE_PORTABLE)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_sources(common PRIVATE
|
||||
windows/timer_resolution.cpp
|
||||
|
@ -528,38 +528,41 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
|
||||
// Generic Filesystem Operations
|
||||
|
||||
bool Exists(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::Exists(path);
|
||||
} else {
|
||||
return fs::exists(path);
|
||||
return fs::exists(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::exists(path);
|
||||
return fs::exists(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsFile(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return !Android::IsDirectory(path);
|
||||
} else {
|
||||
return fs::is_regular_file(path);
|
||||
return fs::is_regular_file(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::is_regular_file(path);
|
||||
return fs::is_regular_file(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool IsDir(const fs::path& path) {
|
||||
std::error_code ec;
|
||||
#ifdef ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
return Android::IsDirectory(path);
|
||||
} else {
|
||||
return fs::is_directory(path);
|
||||
return fs::is_directory(path, ec);
|
||||
}
|
||||
#else
|
||||
return fs::is_directory(path);
|
||||
return fs::is_directory(path, ec);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -88,8 +88,9 @@ public:
|
||||
fs::path yuzu_path_config;
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef YUZU_ENABLE_PORTABLE
|
||||
yuzu_path = GetExeDirectory() / PORTABLE_DIR;
|
||||
|
||||
#endif
|
||||
if (!IsDir(yuzu_path)) {
|
||||
yuzu_path = GetAppDataRoamingDirectory() / YUZU_DIR;
|
||||
}
|
||||
@ -101,8 +102,9 @@ public:
|
||||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||
#else
|
||||
#ifdef YUZU_ENABLE_PORTABLE
|
||||
yuzu_path = GetCurrentDir() / PORTABLE_DIR;
|
||||
|
||||
#endif
|
||||
if (Exists(yuzu_path) && IsDir(yuzu_path)) {
|
||||
yuzu_path_cache = yuzu_path / CACHE_DIR;
|
||||
yuzu_path_config = yuzu_path / CONFIG_DIR;
|
||||
|
@ -112,7 +112,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
|
||||
SUB(Service, NCM) \
|
||||
SUB(Service, NFC) \
|
||||
SUB(Service, NFP) \
|
||||
SUB(Service, NGCT) \
|
||||
SUB(Service, NGC) \
|
||||
SUB(Service, NIFM) \
|
||||
SUB(Service, NIM) \
|
||||
SUB(Service, NOTIF) \
|
||||
|
@ -80,7 +80,7 @@ enum class Class : u8 {
|
||||
Service_NCM, ///< The NCM service
|
||||
Service_NFC, ///< The NFC (Near-field communication) service
|
||||
Service_NFP, ///< The NFP service
|
||||
Service_NGCT, ///< The NGCT (No Good Content for Terra) service
|
||||
Service_NGC, ///< The NGC (No Good Content) service
|
||||
Service_NIFM, ///< The NIFM (Network interface) service
|
||||
Service_NIM, ///< The NIM service
|
||||
Service_NOTIF, ///< The NOTIF (Notification) service
|
||||
|
@ -19,8 +19,8 @@
|
||||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lock, token, std::move(pred));
|
||||
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred&& pred) {
|
||||
cv.wait(lk, token, std::move(pred));
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
@ -332,13 +332,17 @@ private:
|
||||
namespace Common {
|
||||
|
||||
template <typename Condvar, typename Lock, typename Pred>
|
||||
void CondvarWait(Condvar& cv, Lock& lock, std::stop_token token, Pred pred) {
|
||||
void CondvarWait(Condvar& cv, std::unique_lock<Lock>& lk, std::stop_token token, Pred pred) {
|
||||
if (token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::stop_callback callback(token, [&] { cv.notify_all(); });
|
||||
cv.wait(lock, [&] { return pred() || token.stop_requested(); });
|
||||
std::stop_callback callback(token, [&] {
|
||||
{ std::scoped_lock lk2{*lk.mutex()}; }
|
||||
cv.notify_all();
|
||||
});
|
||||
|
||||
cv.wait(lk, [&] { return pred() || token.stop_requested(); });
|
||||
}
|
||||
|
||||
template <typename Rep, typename Period>
|
||||
@ -353,8 +357,10 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration<Rep,
|
||||
|
||||
std::stop_callback cb(token, [&] {
|
||||
// Wake up the waiting thread.
|
||||
std::unique_lock lk{m};
|
||||
stop_requested = true;
|
||||
{
|
||||
std::scoped_lock lk{m};
|
||||
stop_requested = true;
|
||||
}
|
||||
cv.notify_one();
|
||||
});
|
||||
|
||||
|
@ -348,6 +348,8 @@ struct Values {
|
||||
Category::RendererDebug};
|
||||
Setting<bool> disable_shader_loop_safety_checks{
|
||||
linkage, false, "disable_shader_loop_safety_checks", Category::RendererDebug};
|
||||
Setting<bool> enable_renderdoc_hotkey{linkage, false, "renderdoc_hotkey",
|
||||
Category::RendererDebug};
|
||||
|
||||
// System
|
||||
SwitchableSetting<Language, true> language_index{linkage,
|
||||
|
@ -225,6 +225,16 @@ public:
|
||||
*/
|
||||
[[nodiscard]] virtual constexpr u32 EnumIndex() const = 0;
|
||||
|
||||
/**
|
||||
* @returns True if the underlying type is a floating point storage
|
||||
*/
|
||||
[[nodiscard]] virtual constexpr bool IsFloatingPoint() const = 0;
|
||||
|
||||
/**
|
||||
* @returns True if the underlying type is an integer storage
|
||||
*/
|
||||
[[nodiscard]] virtual constexpr bool IsIntegral() const = 0;
|
||||
|
||||
/*
|
||||
* Switchable settings
|
||||
*/
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <typeinfo>
|
||||
#include <fmt/core.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/settings_common.h"
|
||||
#include "common/settings_enums.h"
|
||||
@ -115,8 +116,12 @@ protected:
|
||||
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
|
||||
// Compatibility with old AudioEngine setting being a string
|
||||
return CanonicalizeEnum(value_);
|
||||
} else if constexpr (std::is_floating_point_v<Type>) {
|
||||
return fmt::format("{:f}", value_);
|
||||
} else if constexpr (std::is_enum_v<Type>) {
|
||||
return std::to_string(static_cast<u32>(value_));
|
||||
} else {
|
||||
return std::to_string(static_cast<u64>(value_));
|
||||
return std::to_string(value_);
|
||||
}
|
||||
}
|
||||
|
||||
@ -180,13 +185,15 @@ public:
|
||||
this->SetValue(static_cast<u32>(std::stoul(input)));
|
||||
} else if constexpr (std::is_same_v<Type, bool>) {
|
||||
this->SetValue(input == "true");
|
||||
} else if constexpr (std::is_same_v<Type, AudioEngine>) {
|
||||
this->SetValue(ToEnum<Type>(input));
|
||||
} else if constexpr (std::is_same_v<Type, float>) {
|
||||
this->SetValue(std::stof(input));
|
||||
} else {
|
||||
this->SetValue(static_cast<Type>(std::stoll(input)));
|
||||
}
|
||||
} catch (std::invalid_argument&) {
|
||||
this->SetValue(this->GetDefault());
|
||||
} catch (std::out_of_range&) {
|
||||
this->SetValue(this->GetDefault());
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,11 +222,27 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsFloatingPoint() const final {
|
||||
return std::is_floating_point_v<Type>;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsIntegral() const final {
|
||||
return std::is_integral_v<Type>;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MinVal() const override final {
|
||||
return this->ToString(minimum);
|
||||
if constexpr (std::is_arithmetic_v<Type> && !ranged) {
|
||||
return this->ToString(std::numeric_limits<Type>::min());
|
||||
} else {
|
||||
return this->ToString(minimum);
|
||||
}
|
||||
}
|
||||
[[nodiscard]] std::string MaxVal() const override final {
|
||||
return this->ToString(maximum);
|
||||
if constexpr (std::is_arithmetic_v<Type> && !ranged) {
|
||||
return this->ToString(std::numeric_limits<Type>::max());
|
||||
} else {
|
||||
return this->ToString(maximum);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool Ranged() const override {
|
||||
|
@ -584,13 +584,23 @@ add_library(core STATIC
|
||||
hle/service/lm/lm.h
|
||||
hle/service/mig/mig.cpp
|
||||
hle/service/mig/mig.h
|
||||
hle/service/mii/types/char_info.cpp
|
||||
hle/service/mii/types/char_info.h
|
||||
hle/service/mii/types/core_data.cpp
|
||||
hle/service/mii/types/core_data.h
|
||||
hle/service/mii/types/raw_data.cpp
|
||||
hle/service/mii/types/raw_data.h
|
||||
hle/service/mii/types/store_data.cpp
|
||||
hle/service/mii/types/store_data.h
|
||||
hle/service/mii/types/ver3_store_data.cpp
|
||||
hle/service/mii/types/ver3_store_data.h
|
||||
hle/service/mii/mii.cpp
|
||||
hle/service/mii/mii.h
|
||||
hle/service/mii/mii_manager.cpp
|
||||
hle/service/mii/mii_manager.h
|
||||
hle/service/mii/raw_data.cpp
|
||||
hle/service/mii/raw_data.h
|
||||
hle/service/mii/types.h
|
||||
hle/service/mii/mii_result.h
|
||||
hle/service/mii/mii_types.h
|
||||
hle/service/mii/mii_util.h
|
||||
hle/service/mm/mm_u.cpp
|
||||
hle/service/mm/mm_u.h
|
||||
hle/service/mnpp/mnpp_app.cpp
|
||||
@ -617,8 +627,8 @@ add_library(core STATIC
|
||||
hle/service/nfp/nfp_interface.h
|
||||
hle/service/nfp/nfp_result.h
|
||||
hle/service/nfp/nfp_types.h
|
||||
hle/service/ngct/ngct.cpp
|
||||
hle/service/ngct/ngct.h
|
||||
hle/service/ngc/ngc.cpp
|
||||
hle/service/ngc/ngc.h
|
||||
hle/service/nifm/nifm.cpp
|
||||
hle/service/nifm/nifm.h
|
||||
hle/service/nim/nim.cpp
|
||||
@ -854,6 +864,8 @@ add_library(core STATIC
|
||||
telemetry_session.h
|
||||
tools/freezer.cpp
|
||||
tools/freezer.h
|
||||
tools/renderdoc.cpp
|
||||
tools/renderdoc.h
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
@ -869,6 +881,7 @@ else()
|
||||
-Werror=conversion
|
||||
|
||||
-Wno-sign-conversion
|
||||
-Wno-cast-function-type
|
||||
|
||||
$<$<CXX_COMPILER_ID:Clang>:-fsized-deallocation>
|
||||
)
|
||||
@ -877,7 +890,7 @@ endif()
|
||||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core network video_core nx_tzdb)
|
||||
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus)
|
||||
target_link_libraries(core PUBLIC Boost::headers PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls Opus::opus renderdoc)
|
||||
if (MINGW)
|
||||
target_link_libraries(core PRIVATE ${MSWSOCK_LIBRARY})
|
||||
endif()
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "core/reporter.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "core/tools/freezer.h"
|
||||
#include "core/tools/renderdoc.h"
|
||||
#include "network/network.h"
|
||||
#include "video_core/host1x/host1x.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@ -281,6 +282,10 @@ struct System::Impl {
|
||||
microprofile_cpu[2] = MICROPROFILE_TOKEN(ARM_CPU2);
|
||||
microprofile_cpu[3] = MICROPROFILE_TOKEN(ARM_CPU3);
|
||||
|
||||
if (Settings::values.enable_renderdoc_hotkey) {
|
||||
renderdoc_api = std::make_unique<Tools::RenderdocAPI>();
|
||||
}
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
return SystemResultStatus::Success;
|
||||
@ -406,6 +411,7 @@ struct System::Impl {
|
||||
gpu_core->NotifyShutdown();
|
||||
}
|
||||
|
||||
Network::CancelPendingSocketOperations();
|
||||
kernel.SuspendApplication(true);
|
||||
if (services) {
|
||||
services->KillNVNFlinger();
|
||||
@ -427,6 +433,7 @@ struct System::Impl {
|
||||
debugger.reset();
|
||||
kernel.Shutdown();
|
||||
memory.Reset();
|
||||
Network::RestartSocketOperations();
|
||||
|
||||
if (auto room_member = room_network.GetRoomMember().lock()) {
|
||||
Network::GameInfo game_info{};
|
||||
@ -519,6 +526,8 @@ struct System::Impl {
|
||||
std::unique_ptr<Tools::Freezer> memory_freezer;
|
||||
std::array<u8, 0x20> build_id{};
|
||||
|
||||
std::unique_ptr<Tools::RenderdocAPI> renderdoc_api;
|
||||
|
||||
/// Frontend applets
|
||||
Service::AM::Applets::AppletManager applet_manager;
|
||||
|
||||
@ -562,6 +571,8 @@ struct System::Impl {
|
||||
|
||||
std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
|
||||
gpu_dirty_memory_write_manager{};
|
||||
|
||||
std::deque<std::vector<u8>> user_channel;
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
@ -1020,6 +1031,10 @@ const Network::RoomNetwork& System::GetRoomNetwork() const {
|
||||
return impl->room_network;
|
||||
}
|
||||
|
||||
Tools::RenderdocAPI& System::GetRenderdocAPI() {
|
||||
return *impl->renderdoc_api;
|
||||
}
|
||||
|
||||
void System::RunServer(std::unique_ptr<Service::ServerManager>&& server_manager) {
|
||||
return impl->kernel.RunServer(std::move(server_manager));
|
||||
}
|
||||
@ -1036,6 +1051,10 @@ void System::ExecuteProgram(std::size_t program_index) {
|
||||
}
|
||||
}
|
||||
|
||||
std::deque<std::vector<u8>>& System::GetUserChannel() {
|
||||
return impl->user_channel;
|
||||
}
|
||||
|
||||
void System::RegisterExitCallback(ExitCallback&& callback) {
|
||||
impl->exit_callback = std::move(callback);
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@ -101,6 +102,10 @@ namespace Network {
|
||||
class RoomNetwork;
|
||||
}
|
||||
|
||||
namespace Tools {
|
||||
class RenderdocAPI;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
@ -412,6 +417,8 @@ public:
|
||||
/// Gets an immutable reference to the Room Network.
|
||||
[[nodiscard]] const Network::RoomNetwork& GetRoomNetwork() const;
|
||||
|
||||
[[nodiscard]] Tools::RenderdocAPI& GetRenderdocAPI();
|
||||
|
||||
void SetExitLocked(bool locked);
|
||||
bool GetExitLocked() const;
|
||||
|
||||
@ -459,6 +466,12 @@ public:
|
||||
*/
|
||||
void ExecuteProgram(std::size_t program_index);
|
||||
|
||||
/**
|
||||
* Gets a reference to the user channel stack.
|
||||
* It is used to transfer data between programs.
|
||||
*/
|
||||
[[nodiscard]] std::deque<std::vector<u8>>& GetUserChannel();
|
||||
|
||||
/// Type used for the frontend to designate a callback for System to exit the application.
|
||||
using ExitCallback = std::function<void()>;
|
||||
|
||||
|
@ -724,14 +724,14 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto index = std::stoul(out[0].substr(8, 2), nullptr, 16);
|
||||
const auto index = std::strtoul(out[0].substr(8, 2).c_str(), nullptr, 16);
|
||||
keyblobs[index] = Common::HexStringToArray<0x90>(out[1]);
|
||||
} else if (out[0].compare(0, 18, "encrypted_keyblob_") == 0) {
|
||||
if (!ValidCryptoRevisionString(out[0], 18, 2)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
|
||||
const auto index = std::strtoul(out[0].substr(18, 2).c_str(), nullptr, 16);
|
||||
encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
|
||||
} else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
|
||||
eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
|
||||
@ -750,7 +750,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
||||
}
|
||||
if (out[0].compare(0, kv.second.size(), kv.second) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(kv.second.size(), 2), nullptr, 16);
|
||||
std::strtoul(out[0].substr(kv.second.size(), 2).c_str(), nullptr, 16);
|
||||
const auto sub = kv.first.second;
|
||||
if (sub == 0) {
|
||||
s128_keys[{kv.first.first, index, 0}] =
|
||||
@ -770,7 +770,7 @@ void KeyManager::LoadFromFile(const std::filesystem::path& file_path, bool is_ti
|
||||
const auto& match = kak_names[j];
|
||||
if (out[0].compare(0, std::strlen(match), match) == 0) {
|
||||
const auto index =
|
||||
std::stoul(out[0].substr(std::strlen(match), 2), nullptr, 16);
|
||||
std::strtoul(out[0].substr(std::strlen(match), 2).c_str(), nullptr, 16);
|
||||
s128_keys[{S128KeyType::KeyArea, index, j}] =
|
||||
Common::HexStringToArray<16>(out[1]);
|
||||
}
|
||||
|
@ -165,7 +165,7 @@ static std::string EscapeStringSequences(std::string in) {
|
||||
void IPSwitchCompiler::ParseFlag(const std::string& line) {
|
||||
if (StartsWith(line, "@flag offset_shift ")) {
|
||||
// Offset Shift Flag
|
||||
offset_shift = std::stoll(line.substr(19), nullptr, 0);
|
||||
offset_shift = std::strtoll(line.substr(19).c_str(), nullptr, 0);
|
||||
} else if (StartsWith(line, "@little-endian")) {
|
||||
// Set values to read as little endian
|
||||
is_little_endian = true;
|
||||
@ -263,7 +263,7 @@ void IPSwitchCompiler::Parse() {
|
||||
// 11 - 8 hex digit offset + space + minimum two digit overwrite val
|
||||
if (patch_line.length() < 11)
|
||||
break;
|
||||
auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
|
||||
auto offset = std::strtoul(patch_line.substr(0, 8).c_str(), nullptr, 16);
|
||||
offset += static_cast<unsigned long>(offset_shift);
|
||||
|
||||
std::vector<u8> replace;
|
||||
|
@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
|
||||
|
||||
CNMT::~CNMT() = default;
|
||||
|
||||
const CNMTHeader& CNMT::GetHeader() const {
|
||||
return header;
|
||||
}
|
||||
|
||||
u64 CNMT::GetTitleID() const {
|
||||
return header.title_id;
|
||||
}
|
||||
|
@ -89,6 +89,7 @@ public:
|
||||
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
|
||||
~CNMT();
|
||||
|
||||
const CNMTHeader& GetHeader() const;
|
||||
u64 GetTitleID() const;
|
||||
u32 GetTitleVersion() const;
|
||||
TitleType GetType() const;
|
||||
|
@ -294,11 +294,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso, const std::st
|
||||
return out;
|
||||
}
|
||||
|
||||
bool PatchManager::HasNSOPatch(const BuildID& build_id_) const {
|
||||
bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) const {
|
||||
const auto build_id_raw = Common::HexToString(build_id_);
|
||||
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
|
||||
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}", build_id);
|
||||
LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name);
|
||||
|
||||
const auto load_dir = fs_controller.GetModificationLoadRoot(title_id);
|
||||
if (load_dir == nullptr) {
|
||||
|
@ -52,7 +52,7 @@ public:
|
||||
|
||||
// Checks to see if PatchNSO() will have any effect given the NSO's build ID.
|
||||
// Used to prevent expensive copies in NSO loader.
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id) const;
|
||||
[[nodiscard]] bool HasNSOPatch(const BuildID& build_id, std::string_view name) const;
|
||||
|
||||
// Creates a CheatList object with all
|
||||
[[nodiscard]] std::vector<Core::Memory::CheatEntry> CreateCheatList(
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/crypto/key_manager.h"
|
||||
#include "core/file_sys/card_image.h"
|
||||
#include "core/file_sys/common_funcs.h"
|
||||
@ -625,7 +626,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
|
||||
nca->GetTitleId() != title_id) {
|
||||
// Create fake cnmt for patch to multiprogram application
|
||||
const auto sub_nca_result =
|
||||
InstallEntry(*nca, TitleType::Update, overwrite_if_exists, copy);
|
||||
InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
|
||||
if (sub_nca_result != InstallResult::Success) {
|
||||
return sub_nca_result;
|
||||
}
|
||||
@ -672,6 +673,31 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
|
||||
return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
|
||||
}
|
||||
|
||||
InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
|
||||
const ContentRecord& base_record,
|
||||
bool overwrite_if_exists, const VfsCopyFunction& copy) {
|
||||
const CNMTHeader header{
|
||||
.title_id = nca.GetTitleId(),
|
||||
.title_version = base_header.title_version,
|
||||
.type = base_header.type,
|
||||
.reserved = {},
|
||||
.table_offset = 0x10,
|
||||
.number_content_entries = 1,
|
||||
.number_meta_entries = 0,
|
||||
.attributes = 0,
|
||||
.reserved2 = {},
|
||||
.is_committed = 0,
|
||||
.required_download_system_version = 0,
|
||||
.reserved3 = {},
|
||||
};
|
||||
const OptionalHeader opt_header{0, 0};
|
||||
const CNMT new_cnmt(header, opt_header, {base_record}, {});
|
||||
if (!RawInstallYuzuMeta(new_cnmt)) {
|
||||
return InstallResult::ErrorMetaFailed;
|
||||
}
|
||||
return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
|
||||
}
|
||||
|
||||
bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
|
||||
bool removed_data = false;
|
||||
|
||||
|
@ -24,6 +24,7 @@ enum class NCAContentType : u8;
|
||||
enum class TitleType : u8;
|
||||
|
||||
struct ContentRecord;
|
||||
struct CNMTHeader;
|
||||
struct MetaRecord;
|
||||
class RegisteredCache;
|
||||
|
||||
@ -169,6 +170,10 @@ public:
|
||||
InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
|
||||
const ContentRecord& base_record, bool overwrite_if_exists = false,
|
||||
const VfsCopyFunction& copy = &VfsRawCopy);
|
||||
|
||||
// Removes an existing entry based on title id
|
||||
bool RemoveExistingEntry(u64 title_id) const;
|
||||
|
||||
|
@ -154,6 +154,14 @@ NpadIdType HIDCore::GetFirstDisconnectedNpadId() const {
|
||||
return NpadIdType::Player1;
|
||||
}
|
||||
|
||||
void HIDCore::SetLastActiveController(NpadIdType npad_id) {
|
||||
last_active_controller = npad_id;
|
||||
}
|
||||
|
||||
NpadIdType HIDCore::GetLastActiveController() const {
|
||||
return last_active_controller;
|
||||
}
|
||||
|
||||
void HIDCore::EnableAllControllerConfiguration() {
|
||||
player_1->EnableConfiguration();
|
||||
player_2->EnableConfiguration();
|
||||
|
@ -48,6 +48,12 @@ public:
|
||||
/// Returns the first disconnected npad id
|
||||
NpadIdType GetFirstDisconnectedNpadId() const;
|
||||
|
||||
/// Sets the npad id of the last active controller
|
||||
void SetLastActiveController(NpadIdType npad_id);
|
||||
|
||||
/// Returns the npad id of the last controller that pushed a button
|
||||
NpadIdType GetLastActiveController() const;
|
||||
|
||||
/// Sets all emulated controllers into configuring mode.
|
||||
void EnableAllControllerConfiguration();
|
||||
|
||||
@ -77,6 +83,7 @@ private:
|
||||
std::unique_ptr<EmulatedConsole> console;
|
||||
std::unique_ptr<EmulatedDevices> devices;
|
||||
NpadStyleTag supported_style_tag{NpadStyleSet::All};
|
||||
NpadIdType last_active_controller{NpadIdType::Handheld};
|
||||
};
|
||||
|
||||
} // namespace Core::HID
|
||||
|
@ -96,6 +96,7 @@ Result KProcess::Initialize(KProcess* process, Core::System& system, std::string
|
||||
process->m_is_suspended = false;
|
||||
process->m_schedule_count = 0;
|
||||
process->m_is_handle_table_initialized = false;
|
||||
process->m_is_hbl = false;
|
||||
|
||||
// Open a reference to the resource limit.
|
||||
process->m_resource_limit->Open();
|
||||
@ -351,12 +352,14 @@ Result KProcess::SetActivity(ProcessActivity activity) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size) {
|
||||
Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
bool is_hbl) {
|
||||
m_program_id = metadata.GetTitleID();
|
||||
m_ideal_core = metadata.GetMainThreadCore();
|
||||
m_is_64bit_process = metadata.Is64BitProgram();
|
||||
m_system_resource_size = metadata.GetSystemResourceSize();
|
||||
m_image_size = code_size;
|
||||
m_is_hbl = is_hbl;
|
||||
|
||||
if (metadata.GetAddressSpaceType() == FileSys::ProgramAddressSpaceType::Is39Bit) {
|
||||
// For 39-bit processes, the ASLR region starts at 0x800'0000 and is ~512GiB large.
|
||||
|
@ -338,7 +338,8 @@ public:
|
||||
* @returns ResultSuccess if all relevant metadata was able to be
|
||||
* loaded and parsed. Otherwise, an error code is returned.
|
||||
*/
|
||||
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size);
|
||||
Result LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std::size_t code_size,
|
||||
bool is_hbl);
|
||||
|
||||
/**
|
||||
* Starts the main application thread for this process.
|
||||
@ -368,6 +369,10 @@ public:
|
||||
return GetProcessId();
|
||||
}
|
||||
|
||||
bool IsHbl() const {
|
||||
return m_is_hbl;
|
||||
}
|
||||
|
||||
bool IsSignaled() const override;
|
||||
|
||||
void DoWorkerTaskImpl();
|
||||
@ -525,6 +530,7 @@ private:
|
||||
bool m_is_immortal{};
|
||||
bool m_is_handle_table_initialized{};
|
||||
bool m_is_initialized{};
|
||||
bool m_is_hbl{};
|
||||
|
||||
std::atomic<u16> m_num_running_threads{};
|
||||
|
||||
|
@ -14,7 +14,7 @@ Result OutputDebugString(Core::System& system, u64 address, u64 len) {
|
||||
|
||||
std::string str(len, '\0');
|
||||
GetCurrentMemory(system.Kernel()).ReadBlock(address, str.data(), str.size());
|
||||
LOG_DEBUG(Debug_Emulated, "{}", str);
|
||||
LOG_INFO(Debug_Emulated, "{}", str);
|
||||
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/debugger/debugger.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/k_thread.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/hle/kernel/svc_types.h"
|
||||
@ -107,7 +108,10 @@ void Break(Core::System& system, BreakReason reason, u64 info1, u64 info2) {
|
||||
system.ArmInterface(static_cast<std::size_t>(thread_processor_id)).LogBacktrace();
|
||||
}
|
||||
|
||||
if (system.DebuggerEnabled()) {
|
||||
const bool is_hbl = GetCurrentProcess(system.Kernel()).IsHbl();
|
||||
const bool should_break = is_hbl || !notification_only;
|
||||
|
||||
if (system.DebuggerEnabled() && should_break) {
|
||||
auto* thread = system.Kernel().GetCurrentEmuThread();
|
||||
system.GetDebugger().NotifyThreadStopped(thread);
|
||||
thread->RequestSuspend(Kernel::SuspendType::Debug);
|
||||
|
@ -46,7 +46,7 @@ constexpr Result ResultNoMessages{ErrorModule::AM, 3};
|
||||
constexpr Result ResultInvalidOffset{ErrorModule::AM, 503};
|
||||
|
||||
enum class LaunchParameterKind : u32 {
|
||||
ApplicationSpecific = 1,
|
||||
UserChannel = 1,
|
||||
AccountPreselectedUser = 2,
|
||||
};
|
||||
|
||||
@ -1386,7 +1386,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
|
||||
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
|
||||
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
|
||||
{27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
|
||||
{28, nullptr, "GetSaveDataSizeMax"},
|
||||
{28, &IApplicationFunctions::GetSaveDataSizeMax, "GetSaveDataSizeMax"},
|
||||
{29, nullptr, "GetCacheStorageMax"},
|
||||
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
|
||||
{31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"},
|
||||
@ -1518,27 +1518,26 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto kind = rp.PopEnum<LaunchParameterKind>();
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, kind={:08X}", kind);
|
||||
LOG_INFO(Service_AM, "called, kind={:08X}", kind);
|
||||
|
||||
if (kind == LaunchParameterKind::ApplicationSpecific && !launch_popped_application_specific) {
|
||||
const auto backend = BCAT::CreateBackendFromSettings(system, [this](u64 tid) {
|
||||
return system.GetFileSystemController().GetBCATDirectory(tid);
|
||||
});
|
||||
const auto build_id_full = system.GetApplicationProcessBuildID();
|
||||
u64 build_id{};
|
||||
std::memcpy(&build_id, build_id_full.data(), sizeof(u64));
|
||||
|
||||
auto data =
|
||||
backend->GetLaunchParameter({system.GetApplicationProcessProgramID(), build_id});
|
||||
if (data.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(*data));
|
||||
launch_popped_application_specific = true;
|
||||
if (kind == LaunchParameterKind::UserChannel) {
|
||||
auto channel = system.GetUserChannel();
|
||||
if (channel.empty()) {
|
||||
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = channel.back();
|
||||
channel.pop_back();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(data));
|
||||
} else if (kind == LaunchParameterKind::AccountPreselectedUser &&
|
||||
!launch_popped_account_preselect) {
|
||||
// TODO: Verify this is hw-accurate
|
||||
LaunchParameterAccountPreselectedUser params{};
|
||||
|
||||
params.magic = LAUNCH_PARAMETER_ACCOUNT_PRESELECTED_USER_MAGIC;
|
||||
@ -1550,7 +1549,6 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
||||
params.current_user = *uuid;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
std::vector<u8> buffer(sizeof(LaunchParameterAccountPreselectedUser));
|
||||
@ -1558,12 +1556,11 @@ void IApplicationFunctions::PopLaunchParameter(HLERequestContext& ctx) {
|
||||
|
||||
rb.PushIpcInterface<IStorage>(system, std::move(buffer));
|
||||
launch_popped_account_preselect = true;
|
||||
return;
|
||||
} else {
|
||||
LOG_ERROR(Service_AM, "Unknown launch parameter kind.");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_AM, "Attempted to load launch parameter but none was found!");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(AM::ResultNoDataInChannel);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::CreateApplicationAndRequestToStartForQuest(HLERequestContext& ctx) {
|
||||
@ -1824,6 +1821,18 @@ void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
|
||||
rb.PushRaw(resp);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::GetSaveDataSizeMax(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
constexpr u64 size_max_normal = 0xFFFFFFF;
|
||||
constexpr u64 size_max_journal = 0xFFFFFFF;
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(size_max_normal);
|
||||
rb.Push(size_max_journal);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
@ -1855,14 +1864,22 @@ void IApplicationFunctions::ExecuteProgram(HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void IApplicationFunctions::ClearUserChannel(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
system.GetUserChannel().clear();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IApplicationFunctions::UnpopToUserChannel(HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto storage = rp.PopIpcInterface<IStorage>().lock();
|
||||
if (storage) {
|
||||
system.GetUserChannel().push_back(storage->GetData());
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
|
@ -316,6 +316,7 @@ private:
|
||||
void ExtendSaveData(HLERequestContext& ctx);
|
||||
void GetSaveDataSize(HLERequestContext& ctx);
|
||||
void CreateCacheStorage(HLERequestContext& ctx);
|
||||
void GetSaveDataSizeMax(HLERequestContext& ctx);
|
||||
void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
|
||||
void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
|
||||
void BeginBlockingHomeButton(HLERequestContext& ctx);
|
||||
@ -339,7 +340,6 @@ private:
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
bool launch_popped_application_specific = false;
|
||||
bool launch_popped_account_preselect = false;
|
||||
s32 previous_program_index{-1};
|
||||
Kernel::KEvent* gpu_error_detected_event;
|
||||
|
@ -85,15 +85,18 @@ void MiiEdit::Execute() {
|
||||
break;
|
||||
case MiiEditAppletMode::CreateMii:
|
||||
case MiiEditAppletMode::EditMii: {
|
||||
Service::Mii::MiiManager mii_manager;
|
||||
Mii::CharInfo char_info{};
|
||||
Mii::StoreData store_data{};
|
||||
store_data.BuildBase(Mii::Gender::Male);
|
||||
char_info.SetFromStoreData(store_data);
|
||||
|
||||
const MiiEditCharInfo char_info{
|
||||
const MiiEditCharInfo edit_char_info{
|
||||
.mii_info{applet_input_common.applet_mode == MiiEditAppletMode::EditMii
|
||||
? applet_input_v4.char_info.mii_info
|
||||
: mii_manager.BuildBase(Mii::Gender::Male)},
|
||||
: char_info},
|
||||
};
|
||||
|
||||
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, char_info);
|
||||
MiiEditOutputForCharInfoEditing(MiiEditResult::Success, edit_char_info);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -7,7 +7,8 @@
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/service/mii/types/char_info.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
|
@ -193,7 +193,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
shared_memory->system_properties.use_minus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.dual.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::SwitchProController;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::SwitchProController;
|
||||
shared_memory->sixaxis_fullkey_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::Handheld:
|
||||
@ -216,8 +216,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
shared_memory->system_properties.is_charging_joy_right.Assign(
|
||||
battery_level.right.is_charging);
|
||||
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type =
|
||||
AppletFooterUiType::HandheldJoyConLeftJoyConRight;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::HandheldJoyConLeftJoyConRight;
|
||||
shared_memory->sixaxis_handheld_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconDual:
|
||||
@ -247,19 +246,19 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
shared_memory->assignment_mode = NpadJoyAssignmentMode::Dual;
|
||||
|
||||
if (controller.is_dual_left_connected && controller.is_dual_right_connected) {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDual;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDual;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.left;
|
||||
shared_memory->battery_level_dual = battery_level.left.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.left.is_charging);
|
||||
} else if (controller.is_dual_left_connected) {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualLeftOnly;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDualLeftOnly;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.left;
|
||||
shared_memory->battery_level_dual = battery_level.left.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
battery_level.left.is_charging);
|
||||
} else {
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyDualRightOnly;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyDualRightOnly;
|
||||
shared_memory->fullkey_color.fullkey = body_colors.right;
|
||||
shared_memory->battery_level_dual = battery_level.right.battery_level;
|
||||
shared_memory->system_properties.is_charging_joy_dual.Assign(
|
||||
@ -278,7 +277,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
shared_memory->system_properties.use_minus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_left.Assign(
|
||||
battery_level.left.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyLeftHorizontal;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyLeftHorizontal;
|
||||
shared_memory->sixaxis_left_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::JoyconRight:
|
||||
@ -293,7 +292,7 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
shared_memory->system_properties.use_plus.Assign(1);
|
||||
shared_memory->system_properties.is_charging_joy_right.Assign(
|
||||
battery_level.right.is_charging);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::JoyRightHorizontal;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::JoyRightHorizontal;
|
||||
shared_memory->sixaxis_right_properties.is_newly_assigned.Assign(1);
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::GameCube:
|
||||
@ -314,12 +313,12 @@ void Controller_NPad::InitNewlyAddedController(Core::HID::NpadIdType npad_id) {
|
||||
case Core::HID::NpadStyleIndex::SNES:
|
||||
shared_memory->style_tag.lucia.Assign(1);
|
||||
shared_memory->device_type.fullkey.Assign(1);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lucia;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::Lucia;
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::N64:
|
||||
shared_memory->style_tag.lagoon.Assign(1);
|
||||
shared_memory->device_type.fullkey.Assign(1);
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::Lagon;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::Lagon;
|
||||
break;
|
||||
case Core::HID::NpadStyleIndex::SegaGenesis:
|
||||
shared_memory->style_tag.lager.Assign(1);
|
||||
@ -419,9 +418,17 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
|
||||
std::scoped_lock lock{mutex};
|
||||
auto& controller = GetControllerFromNpadIdType(npad_id);
|
||||
const auto controller_type = controller.device->GetNpadStyleIndex();
|
||||
|
||||
if (!controller.device->IsConnected() && controller.is_connected) {
|
||||
DisconnectNpad(npad_id);
|
||||
return;
|
||||
}
|
||||
if (!controller.device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
if (controller.device->IsConnected() && !controller.is_connected) {
|
||||
InitNewlyAddedController(npad_id);
|
||||
}
|
||||
|
||||
// This function is unique to yuzu for the turbo buttons and motion to work properly
|
||||
controller.device->StatusUpdate();
|
||||
@ -468,6 +475,10 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
|
||||
pad_entry.npad_buttons.l.Assign(button_state.zl);
|
||||
pad_entry.npad_buttons.r.Assign(button_state.zr);
|
||||
}
|
||||
|
||||
if (pad_entry.npad_buttons.raw != Core::HID::NpadButton::None) {
|
||||
hid_core.SetLastActiveController(npad_id);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller_NPad::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
|
||||
@ -736,14 +747,6 @@ void Controller_NPad::SetSupportedStyleSet(Core::HID::NpadStyleTag style_set) {
|
||||
|
||||
// Once SetSupportedStyleSet is called controllers are fully initialized
|
||||
is_controller_initialized = true;
|
||||
|
||||
// Connect all active controllers
|
||||
for (auto& controller : controller_data) {
|
||||
const auto& device = controller.device;
|
||||
if (device->IsConnected()) {
|
||||
AddNewControllerAt(device->GetNpadStyleIndex(), device->GetNpadIdType());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Core::HID::NpadStyleTag Controller_NPad::GetSupportedStyleSet() const {
|
||||
@ -1116,7 +1119,7 @@ Result Controller_NPad::DisconnectNpad(Core::HID::NpadIdType npad_id) {
|
||||
.left = {},
|
||||
.right = {},
|
||||
};
|
||||
shared_memory->applet_nfc_xcd.applet_footer.type = AppletFooterUiType::None;
|
||||
shared_memory->applet_footer_type = AppletFooterUiType::None;
|
||||
|
||||
controller.is_dual_left_connected = true;
|
||||
controller.is_dual_right_connected = true;
|
||||
@ -1508,6 +1511,31 @@ Core::HID::NpadButton Controller_NPad::GetAndResetPressState() {
|
||||
return static_cast<Core::HID::NpadButton>(press_state.exchange(0));
|
||||
}
|
||||
|
||||
void Controller_NPad::ApplyNpadSystemCommonPolicy() {
|
||||
Core::HID::NpadStyleTag styletag{};
|
||||
styletag.fullkey.Assign(1);
|
||||
styletag.handheld.Assign(1);
|
||||
styletag.joycon_dual.Assign(1);
|
||||
styletag.system_ext.Assign(1);
|
||||
styletag.system.Assign(1);
|
||||
SetSupportedStyleSet(styletag);
|
||||
|
||||
SetNpadHandheldActivationMode(NpadHandheldActivationMode::Dual);
|
||||
|
||||
supported_npad_id_types.clear();
|
||||
supported_npad_id_types.resize(10);
|
||||
supported_npad_id_types[0] = Core::HID::NpadIdType::Player1;
|
||||
supported_npad_id_types[1] = Core::HID::NpadIdType::Player2;
|
||||
supported_npad_id_types[2] = Core::HID::NpadIdType::Player3;
|
||||
supported_npad_id_types[3] = Core::HID::NpadIdType::Player4;
|
||||
supported_npad_id_types[4] = Core::HID::NpadIdType::Player5;
|
||||
supported_npad_id_types[5] = Core::HID::NpadIdType::Player6;
|
||||
supported_npad_id_types[6] = Core::HID::NpadIdType::Player7;
|
||||
supported_npad_id_types[7] = Core::HID::NpadIdType::Player8;
|
||||
supported_npad_id_types[8] = Core::HID::NpadIdType::Other;
|
||||
supported_npad_id_types[9] = Core::HID::NpadIdType::Handheld;
|
||||
}
|
||||
|
||||
bool Controller_NPad::IsControllerSupported(Core::HID::NpadStyleIndex controller) const {
|
||||
if (controller == Core::HID::NpadStyleIndex::Handheld) {
|
||||
const bool support_handheld =
|
||||
|
@ -190,6 +190,8 @@ public:
|
||||
// Specifically for cheat engine and other features.
|
||||
Core::HID::NpadButton GetAndResetPressState();
|
||||
|
||||
void ApplyNpadSystemCommonPolicy();
|
||||
|
||||
static bool IsNpadIdValid(Core::HID::NpadIdType npad_id);
|
||||
static Result IsDeviceHandleValid(const Core::HID::VibrationDeviceHandle& device_handle);
|
||||
static Result VerifyValidSixAxisSensorHandle(
|
||||
@ -360,7 +362,7 @@ private:
|
||||
enum class AppletFooterUiType : u8 {
|
||||
None = 0,
|
||||
HandheldNone = 1,
|
||||
HandheldJoyConLeftOnly = 1,
|
||||
HandheldJoyConLeftOnly = 2,
|
||||
HandheldJoyConRightOnly = 3,
|
||||
HandheldJoyConLeftJoyConRight = 4,
|
||||
JoyDual = 5,
|
||||
@ -382,13 +384,6 @@ private:
|
||||
Lagon = 21,
|
||||
};
|
||||
|
||||
struct AppletFooterUi {
|
||||
AppletFooterUiAttributes attributes{};
|
||||
AppletFooterUiType type{AppletFooterUiType::None};
|
||||
INSERT_PADDING_BYTES(0x5B); // Reserved
|
||||
};
|
||||
static_assert(sizeof(AppletFooterUi) == 0x60, "AppletFooterUi is an invalid size");
|
||||
|
||||
// This is nn::hid::NpadLarkType
|
||||
enum class NpadLarkType : u32 {
|
||||
Invalid,
|
||||
@ -419,13 +414,6 @@ private:
|
||||
U,
|
||||
};
|
||||
|
||||
struct AppletNfcXcd {
|
||||
union {
|
||||
AppletFooterUi applet_footer{};
|
||||
Lifo<NfcXcdDeviceHandleStateImpl, 0x2> nfc_xcd_device_lifo;
|
||||
};
|
||||
};
|
||||
|
||||
// This is nn::hid::detail::NpadInternalState
|
||||
struct NpadInternalState {
|
||||
Core::HID::NpadStyleTag style_tag{Core::HID::NpadStyleSet::None};
|
||||
@ -452,7 +440,9 @@ private:
|
||||
Core::HID::NpadBatteryLevel battery_level_dual{};
|
||||
Core::HID::NpadBatteryLevel battery_level_left{};
|
||||
Core::HID::NpadBatteryLevel battery_level_right{};
|
||||
AppletNfcXcd applet_nfc_xcd{};
|
||||
AppletFooterUiAttributes applet_footer_attributes{};
|
||||
AppletFooterUiType applet_footer_type{AppletFooterUiType::None};
|
||||
INSERT_PADDING_BYTES(0x5B); // Reserved
|
||||
INSERT_PADDING_BYTES(0x20); // Unknown
|
||||
Lifo<NpadGcTriggerState, hid_entry_count> gc_trigger_lifo{};
|
||||
NpadLarkType lark_type_l_and_main{};
|
||||
|
@ -231,8 +231,10 @@ std::shared_ptr<IAppletResource> Hid::GetAppletResource() {
|
||||
return applet_resource;
|
||||
}
|
||||
|
||||
Hid::Hid(Core::System& system_)
|
||||
: ServiceFramework{system_, "hid"}, service_context{system_, service_name} {
|
||||
Hid::Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
|
||||
: ServiceFramework{system_, "hid"}, applet_resource{applet_resource_}, service_context{
|
||||
system_,
|
||||
service_name} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &Hid::CreateAppletResource, "CreateAppletResource"},
|
||||
@ -2543,8 +2545,9 @@ public:
|
||||
|
||||
class HidSys final : public ServiceFramework<HidSys> {
|
||||
public:
|
||||
explicit HidSys(Core::System& system_)
|
||||
: ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"} {
|
||||
explicit HidSys(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_)
|
||||
: ServiceFramework{system_, "hid:sys"}, service_context{system_, "hid:sys"},
|
||||
applet_resource{applet_resource_} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{31, nullptr, "SendKeyboardLockKeyEvent"},
|
||||
@ -2756,9 +2759,12 @@ public:
|
||||
|
||||
private:
|
||||
void ApplyNpadSystemCommonPolicy(HLERequestContext& ctx) {
|
||||
// We already do this for homebrew so we can just stub it out
|
||||
LOG_WARNING(Service_HID, "called");
|
||||
|
||||
GetAppletResource()
|
||||
->GetController<Controller_NPad>(HidController::NPad)
|
||||
.ApplyNpadSystemCommonPolicy();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
@ -2768,7 +2774,7 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(Core::HID::NpadIdType::Handheld);
|
||||
rb.PushEnum(system.HIDCore().GetLastActiveController());
|
||||
}
|
||||
|
||||
void GetUniquePadsFromNpad(HLERequestContext& ctx) {
|
||||
@ -2821,17 +2827,28 @@ private:
|
||||
rb.PushRaw(touchscreen_config);
|
||||
}
|
||||
|
||||
std::shared_ptr<IAppletResource> GetAppletResource() {
|
||||
if (applet_resource == nullptr) {
|
||||
applet_resource = std::make_shared<IAppletResource>(system, service_context);
|
||||
}
|
||||
|
||||
return applet_resource;
|
||||
}
|
||||
|
||||
Kernel::KEvent* joy_detach_event;
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
std::shared_ptr<IAppletResource> applet_resource;
|
||||
};
|
||||
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
std::shared_ptr<IAppletResource> applet_resource;
|
||||
|
||||
server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system));
|
||||
server_manager->RegisterNamedService("hid", std::make_shared<Hid>(system, applet_resource));
|
||||
server_manager->RegisterNamedService("hidbus", std::make_shared<HidBus>(system));
|
||||
server_manager->RegisterNamedService("hid:dbg", std::make_shared<HidDbg>(system));
|
||||
server_manager->RegisterNamedService("hid:sys", std::make_shared<HidSys>(system));
|
||||
server_manager->RegisterNamedService("hid:sys",
|
||||
std::make_shared<HidSys>(system, applet_resource));
|
||||
|
||||
server_manager->RegisterNamedService("irs", std::make_shared<Service::IRS::IRS>(system));
|
||||
server_manager->RegisterNamedService("irs:sys",
|
||||
|
@ -95,7 +95,7 @@ private:
|
||||
|
||||
class Hid final : public ServiceFramework<Hid> {
|
||||
public:
|
||||
explicit Hid(Core::System& system_);
|
||||
explicit Hid(Core::System& system_, std::shared_ptr<IAppletResource> applet_resource_);
|
||||
~Hid() override;
|
||||
|
||||
std::shared_ptr<IAppletResource> GetAppletResource();
|
||||
|
@ -7,17 +7,16 @@
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/mii/mii.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/mii/mii_result.h"
|
||||
#include "core/hle/service/server_manager.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
constexpr Result ERROR_INVALID_ARGUMENT{ErrorModule::Mii, 1};
|
||||
|
||||
class IDatabaseService final : public ServiceFramework<IDatabaseService> {
|
||||
public:
|
||||
explicit IDatabaseService(Core::System& system_)
|
||||
: ServiceFramework{system_, "IDatabaseService"} {
|
||||
explicit IDatabaseService(Core::System& system_, bool is_system_)
|
||||
: ServiceFramework{system_, "IDatabaseService"}, is_system{is_system_} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IDatabaseService::IsUpdated, "IsUpdated"},
|
||||
@ -54,34 +53,27 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
std::vector<u8> SerializeArray(const std::vector<T>& values) {
|
||||
std::vector<u8> out(values.size() * sizeof(T));
|
||||
std::size_t offset{};
|
||||
for (const auto& value : values) {
|
||||
std::memcpy(out.data() + offset, &value, sizeof(T));
|
||||
offset += sizeof(T);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void IsUpdated(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto source_flag{rp.PopRaw<SourceFlag>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
|
||||
|
||||
const bool is_updated = manager.IsUpdated(metadata, source_flag);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(manager.CheckAndResetUpdateCounter(source_flag, current_update_counter));
|
||||
rb.Push<u8>(is_updated);
|
||||
}
|
||||
|
||||
void IsFullDatabase(HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
const bool is_full_database = manager.IsFullDatabase();
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(manager.IsFullDatabase());
|
||||
rb.Push<u8>(is_full_database);
|
||||
}
|
||||
|
||||
void GetCount(HLERequestContext& ctx) {
|
||||
@ -90,57 +82,63 @@ private:
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
|
||||
|
||||
const u32 mii_count = manager.GetCount(metadata, source_flag);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(manager.GetCount(source_flag));
|
||||
rb.Push(mii_count);
|
||||
}
|
||||
|
||||
void Get(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto source_flag{rp.PopRaw<SourceFlag>()};
|
||||
const auto output_size{ctx.GetWriteBufferNumElements<CharInfoElement>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
|
||||
|
||||
const auto default_miis{manager.GetDefault(source_flag)};
|
||||
if (default_miis.size() > 0) {
|
||||
ctx.WriteBuffer(SerializeArray(default_miis));
|
||||
u32 mii_count{};
|
||||
std::vector<CharInfoElement> char_info_elements(output_size);
|
||||
Result result = manager.Get(metadata, char_info_elements, mii_count, source_flag);
|
||||
|
||||
if (mii_count != 0) {
|
||||
ctx.WriteBuffer(char_info_elements);
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(static_cast<u32>(default_miis.size()));
|
||||
rb.Push(result);
|
||||
rb.Push(mii_count);
|
||||
}
|
||||
|
||||
void Get1(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto source_flag{rp.PopRaw<SourceFlag>()};
|
||||
const auto output_size{ctx.GetWriteBufferNumElements<CharInfo>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}, out_size={}", source_flag, output_size);
|
||||
|
||||
const auto default_miis{manager.GetDefault(source_flag)};
|
||||
u32 mii_count{};
|
||||
std::vector<CharInfo> char_info(output_size);
|
||||
Result result = manager.Get(metadata, char_info, mii_count, source_flag);
|
||||
|
||||
std::vector<CharInfo> values;
|
||||
for (const auto& element : default_miis) {
|
||||
values.emplace_back(element.info);
|
||||
if (mii_count != 0) {
|
||||
ctx.WriteBuffer(char_info);
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(SerializeArray(values));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(static_cast<u32>(default_miis.size()));
|
||||
rb.Push(result);
|
||||
rb.Push(mii_count);
|
||||
}
|
||||
|
||||
void UpdateLatest(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto info{rp.PopRaw<CharInfo>()};
|
||||
const auto char_info{rp.PopRaw<CharInfo>()};
|
||||
const auto source_flag{rp.PopRaw<SourceFlag>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with source_flag={}", source_flag);
|
||||
|
||||
CharInfo new_char_info{};
|
||||
const auto result{manager.UpdateLatest(&new_char_info, info, source_flag)};
|
||||
if (result != ResultSuccess) {
|
||||
const auto result = manager.UpdateLatest(metadata, new_char_info, char_info, source_flag);
|
||||
if (result.IsFailure()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
return;
|
||||
@ -153,7 +151,6 @@ private:
|
||||
|
||||
void BuildRandom(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto age{rp.PopRaw<Age>()};
|
||||
const auto gender{rp.PopRaw<Gender>()};
|
||||
const auto race{rp.PopRaw<Race>()};
|
||||
@ -162,47 +159,48 @@ private:
|
||||
|
||||
if (age > Age::All) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "invalid age={}", age);
|
||||
rb.Push(ResultInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (gender > Gender::All) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "invalid gender={}", gender);
|
||||
rb.Push(ResultInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (race > Race::All) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
LOG_ERROR(Service_Mii, "invalid race={}", race);
|
||||
rb.Push(ResultInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
CharInfo char_info{};
|
||||
manager.BuildRandom(char_info, age, gender, race);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw<CharInfo>(manager.BuildRandom(age, gender, race));
|
||||
rb.PushRaw<CharInfo>(char_info);
|
||||
}
|
||||
|
||||
void BuildDefault(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto index{rp.Pop<u32>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called with index={}", index);
|
||||
LOG_INFO(Service_Mii, "called with index={}", index);
|
||||
|
||||
if (index > 5) {
|
||||
LOG_ERROR(Service_Mii, "invalid argument, index cannot be greater than 5 but is {:08X}",
|
||||
index);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERROR_INVALID_ARGUMENT);
|
||||
rb.Push(ResultInvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
CharInfo char_info{};
|
||||
manager.BuildDefault(char_info, index);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw<CharInfo>(manager.BuildDefault(index));
|
||||
rb.PushRaw<CharInfo>(char_info);
|
||||
}
|
||||
|
||||
void GetIndex(HLERequestContext& ctx) {
|
||||
@ -211,19 +209,21 @@ private:
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
|
||||
u32 index{};
|
||||
s32 index{};
|
||||
const auto result = manager.GetIndex(metadata, info, index);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(manager.GetIndex(info, index));
|
||||
rb.Push(result);
|
||||
rb.Push(index);
|
||||
}
|
||||
|
||||
void SetInterfaceVersion(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
current_interface_version = rp.PopRaw<u32>();
|
||||
const auto interface_version{rp.PopRaw<u32>()};
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called, interface_version={:08X}", current_interface_version);
|
||||
LOG_INFO(Service_Mii, "called, interface_version={:08X}", interface_version);
|
||||
|
||||
UNIMPLEMENTED_IF(current_interface_version != 1);
|
||||
manager.SetInterfaceVersion(metadata, interface_version);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
@ -231,30 +231,27 @@ private:
|
||||
|
||||
void Convert(HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const auto mii_v3{rp.PopRaw<Ver3StoreData>()};
|
||||
|
||||
LOG_INFO(Service_Mii, "called");
|
||||
|
||||
CharInfo char_info{};
|
||||
manager.ConvertV3ToCharInfo(char_info, mii_v3);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2 + sizeof(CharInfo) / sizeof(u32)};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw<CharInfo>(manager.ConvertV3ToCharInfo(mii_v3));
|
||||
rb.PushRaw<CharInfo>(char_info);
|
||||
}
|
||||
|
||||
constexpr bool IsInterfaceVersionSupported(u32 interface_version) const {
|
||||
return current_interface_version >= interface_version;
|
||||
}
|
||||
|
||||
MiiManager manager;
|
||||
|
||||
u32 current_interface_version{};
|
||||
u64 current_update_counter{};
|
||||
MiiManager manager{};
|
||||
DatabaseSessionMetadata metadata{};
|
||||
bool is_system{};
|
||||
};
|
||||
|
||||
class MiiDBModule final : public ServiceFramework<MiiDBModule> {
|
||||
public:
|
||||
explicit MiiDBModule(Core::System& system_, const char* name_)
|
||||
: ServiceFramework{system_, name_} {
|
||||
explicit MiiDBModule(Core::System& system_, const char* name_, bool is_system_)
|
||||
: ServiceFramework{system_, name_}, is_system{is_system_} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &MiiDBModule::GetDatabaseService, "GetDatabaseService"},
|
||||
@ -268,10 +265,12 @@ private:
|
||||
void GetDatabaseService(HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushIpcInterface<IDatabaseService>(system);
|
||||
rb.PushIpcInterface<IDatabaseService>(system, is_system);
|
||||
|
||||
LOG_DEBUG(Service_Mii, "called");
|
||||
}
|
||||
|
||||
bool is_system{};
|
||||
};
|
||||
|
||||
class MiiImg final : public ServiceFramework<MiiImg> {
|
||||
@ -303,8 +302,10 @@ public:
|
||||
void LoopProcess(Core::System& system) {
|
||||
auto server_manager = std::make_unique<ServerManager>(system);
|
||||
|
||||
server_manager->RegisterNamedService("mii:e", std::make_shared<MiiDBModule>(system, "mii:e"));
|
||||
server_manager->RegisterNamedService("mii:u", std::make_shared<MiiDBModule>(system, "mii:u"));
|
||||
server_manager->RegisterNamedService("mii:e",
|
||||
std::make_shared<MiiDBModule>(system, "mii:e", true));
|
||||
server_manager->RegisterNamedService("mii:u",
|
||||
std::make_shared<MiiDBModule>(system, "mii:u", false));
|
||||
server_manager->RegisterNamedService("miiimg", std::make_shared<MiiImg>(system));
|
||||
ServerManager::RunServer(std::move(server_manager));
|
||||
}
|
||||
|
@ -10,385 +10,24 @@
|
||||
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/mii/raw_data.h"
|
||||
#include "core/hle/service/mii/mii_result.h"
|
||||
#include "core/hle/service/mii/mii_util.h"
|
||||
#include "core/hle/service/mii/types/core_data.h"
|
||||
#include "core/hle/service/mii/types/raw_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr Result ERROR_CANNOT_FIND_ENTRY{ErrorModule::Mii, 4};
|
||||
|
||||
constexpr std::size_t DefaultMiiCount{RawData::DefaultMii.size()};
|
||||
|
||||
constexpr MiiStoreData::Name DefaultMiiName{u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
|
||||
constexpr std::array<u8, 8> HairColorLookup{8, 1, 2, 3, 4, 5, 6, 7};
|
||||
constexpr std::array<u8, 6> EyeColorLookup{8, 9, 10, 11, 12, 13};
|
||||
constexpr std::array<u8, 5> MouthColorLookup{19, 20, 21, 22, 23};
|
||||
constexpr std::array<u8, 7> GlassesColorLookup{8, 14, 15, 16, 17, 18, 0};
|
||||
constexpr std::array<u8, 62> EyeRotateLookup{
|
||||
{0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x03, 0x04,
|
||||
0x04, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x03, 0x03, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04,
|
||||
0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x04, 0x04, 0x03, 0x03, 0x03, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x03, 0x04, 0x04, 0x03, 0x04, 0x04}};
|
||||
constexpr std::array<u8, 24> EyebrowRotateLookup{{0x06, 0x06, 0x05, 0x07, 0x06, 0x07, 0x06, 0x07,
|
||||
0x04, 0x07, 0x06, 0x08, 0x05, 0x05, 0x06, 0x06,
|
||||
0x07, 0x07, 0x06, 0x06, 0x05, 0x06, 0x07, 0x05}};
|
||||
MiiManager::MiiManager() {}
|
||||
|
||||
template <typename T, std::size_t SourceArraySize, std::size_t DestArraySize>
|
||||
std::array<T, DestArraySize> ResizeArray(const std::array<T, SourceArraySize>& in) {
|
||||
std::array<T, DestArraySize> out{};
|
||||
std::memcpy(out.data(), in.data(), sizeof(T) * std::min(SourceArraySize, DestArraySize));
|
||||
return out;
|
||||
}
|
||||
|
||||
CharInfo ConvertStoreDataToInfo(const MiiStoreData& data) {
|
||||
MiiStoreBitFields bf;
|
||||
std::memcpy(&bf, data.data.data.data(), sizeof(MiiStoreBitFields));
|
||||
|
||||
return {
|
||||
.uuid = data.data.uuid,
|
||||
.name = ResizeArray<char16_t, 10, 11>(data.data.name),
|
||||
.font_region = static_cast<u8>(bf.font_region.Value()),
|
||||
.favorite_color = static_cast<u8>(bf.favorite_color.Value()),
|
||||
.gender = static_cast<u8>(bf.gender.Value()),
|
||||
.height = static_cast<u8>(bf.height.Value()),
|
||||
.build = static_cast<u8>(bf.build.Value()),
|
||||
.type = static_cast<u8>(bf.type.Value()),
|
||||
.region_move = static_cast<u8>(bf.region_move.Value()),
|
||||
.faceline_type = static_cast<u8>(bf.faceline_type.Value()),
|
||||
.faceline_color = static_cast<u8>(bf.faceline_color.Value()),
|
||||
.faceline_wrinkle = static_cast<u8>(bf.faceline_wrinkle.Value()),
|
||||
.faceline_make = static_cast<u8>(bf.faceline_makeup.Value()),
|
||||
.hair_type = static_cast<u8>(bf.hair_type.Value()),
|
||||
.hair_color = static_cast<u8>(bf.hair_color.Value()),
|
||||
.hair_flip = static_cast<u8>(bf.hair_flip.Value()),
|
||||
.eye_type = static_cast<u8>(bf.eye_type.Value()),
|
||||
.eye_color = static_cast<u8>(bf.eye_color.Value()),
|
||||
.eye_scale = static_cast<u8>(bf.eye_scale.Value()),
|
||||
.eye_aspect = static_cast<u8>(bf.eye_aspect.Value()),
|
||||
.eye_rotate = static_cast<u8>(bf.eye_rotate.Value()),
|
||||
.eye_x = static_cast<u8>(bf.eye_x.Value()),
|
||||
.eye_y = static_cast<u8>(bf.eye_y.Value()),
|
||||
.eyebrow_type = static_cast<u8>(bf.eyebrow_type.Value()),
|
||||
.eyebrow_color = static_cast<u8>(bf.eyebrow_color.Value()),
|
||||
.eyebrow_scale = static_cast<u8>(bf.eyebrow_scale.Value()),
|
||||
.eyebrow_aspect = static_cast<u8>(bf.eyebrow_aspect.Value()),
|
||||
.eyebrow_rotate = static_cast<u8>(bf.eyebrow_rotate.Value()),
|
||||
.eyebrow_x = static_cast<u8>(bf.eyebrow_x.Value()),
|
||||
.eyebrow_y = static_cast<u8>(bf.eyebrow_y.Value() + 3),
|
||||
.nose_type = static_cast<u8>(bf.nose_type.Value()),
|
||||
.nose_scale = static_cast<u8>(bf.nose_scale.Value()),
|
||||
.nose_y = static_cast<u8>(bf.nose_y.Value()),
|
||||
.mouth_type = static_cast<u8>(bf.mouth_type.Value()),
|
||||
.mouth_color = static_cast<u8>(bf.mouth_color.Value()),
|
||||
.mouth_scale = static_cast<u8>(bf.mouth_scale.Value()),
|
||||
.mouth_aspect = static_cast<u8>(bf.mouth_aspect.Value()),
|
||||
.mouth_y = static_cast<u8>(bf.mouth_y.Value()),
|
||||
.beard_color = static_cast<u8>(bf.beard_color.Value()),
|
||||
.beard_type = static_cast<u8>(bf.beard_type.Value()),
|
||||
.mustache_type = static_cast<u8>(bf.mustache_type.Value()),
|
||||
.mustache_scale = static_cast<u8>(bf.mustache_scale.Value()),
|
||||
.mustache_y = static_cast<u8>(bf.mustache_y.Value()),
|
||||
.glasses_type = static_cast<u8>(bf.glasses_type.Value()),
|
||||
.glasses_color = static_cast<u8>(bf.glasses_color.Value()),
|
||||
.glasses_scale = static_cast<u8>(bf.glasses_scale.Value()),
|
||||
.glasses_y = static_cast<u8>(bf.glasses_y.Value()),
|
||||
.mole_type = static_cast<u8>(bf.mole_type.Value()),
|
||||
.mole_scale = static_cast<u8>(bf.mole_scale.Value()),
|
||||
.mole_x = static_cast<u8>(bf.mole_x.Value()),
|
||||
.mole_y = static_cast<u8>(bf.mole_y.Value()),
|
||||
.padding = 0,
|
||||
};
|
||||
}
|
||||
|
||||
u16 GenerateCrc16(const void* data, std::size_t size) {
|
||||
s32 crc{};
|
||||
for (std::size_t i = 0; i < size; i++) {
|
||||
crc ^= static_cast<const u8*>(data)[i] << 8;
|
||||
for (std::size_t j = 0; j < 8; j++) {
|
||||
crc <<= 1;
|
||||
if ((crc & 0x10000) != 0) {
|
||||
crc = (crc ^ 0x1021) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Common::swap16(static_cast<u16>(crc));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T GetRandomValue(T min, T max) {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(static_cast<u64>(min), static_cast<u64>(max));
|
||||
return static_cast<T>(distribution(gen));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T GetRandomValue(T max) {
|
||||
return GetRandomValue<T>({}, max);
|
||||
}
|
||||
|
||||
MiiStoreData BuildRandomStoreData(Age age, Gender gender, Race race, const Common::UUID& user_id) {
|
||||
MiiStoreBitFields bf{};
|
||||
|
||||
if (gender == Gender::All) {
|
||||
gender = GetRandomValue<Gender>(Gender::Maximum);
|
||||
}
|
||||
|
||||
bf.gender.Assign(gender);
|
||||
bf.favorite_color.Assign(GetRandomValue<u8>(11));
|
||||
bf.region_move.Assign(0);
|
||||
bf.font_region.Assign(FontRegion::Standard);
|
||||
bf.type.Assign(0);
|
||||
bf.height.Assign(64);
|
||||
bf.build.Assign(64);
|
||||
|
||||
if (age == Age::All) {
|
||||
const auto temp{GetRandomValue<int>(10)};
|
||||
if (temp >= 8) {
|
||||
age = Age::Old;
|
||||
} else if (temp >= 4) {
|
||||
age = Age::Normal;
|
||||
} else {
|
||||
age = Age::Young;
|
||||
}
|
||||
}
|
||||
|
||||
if (race == Race::All) {
|
||||
const auto temp{GetRandomValue<int>(10)};
|
||||
if (temp >= 8) {
|
||||
race = Race::Black;
|
||||
} else if (temp >= 4) {
|
||||
race = Race::White;
|
||||
} else {
|
||||
race = Race::Asian;
|
||||
}
|
||||
}
|
||||
|
||||
u32 axis_y{};
|
||||
if (gender == Gender::Female && age == Age::Young) {
|
||||
axis_y = GetRandomValue<u32>(3);
|
||||
}
|
||||
|
||||
const std::size_t index{3 * static_cast<std::size_t>(age) +
|
||||
9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
|
||||
|
||||
const auto faceline_type_info{RawData::RandomMiiFaceline.at(index)};
|
||||
const auto faceline_color_info{RawData::RandomMiiFacelineColor.at(
|
||||
3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
|
||||
const auto faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
|
||||
const auto faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
|
||||
const auto hair_type_info{RawData::RandomMiiHairType.at(index)};
|
||||
const auto hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
|
||||
static_cast<std::size_t>(age))};
|
||||
const auto eye_type_info{RawData::RandomMiiEyeType.at(index)};
|
||||
const auto eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
|
||||
const auto eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
|
||||
const auto nose_type_info{RawData::RandomMiiNoseType.at(index)};
|
||||
const auto mouth_type_info{RawData::RandomMiiMouthType.at(index)};
|
||||
const auto glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
|
||||
|
||||
bf.faceline_type.Assign(
|
||||
faceline_type_info.values[GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
|
||||
bf.faceline_color.Assign(
|
||||
faceline_color_info.values[GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
|
||||
bf.faceline_wrinkle.Assign(
|
||||
faceline_wrinkle_info
|
||||
.values[GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
|
||||
bf.faceline_makeup.Assign(
|
||||
faceline_makeup_info
|
||||
.values[GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
|
||||
|
||||
bf.hair_type.Assign(
|
||||
hair_type_info.values[GetRandomValue<std::size_t>(hair_type_info.values_count)]);
|
||||
bf.hair_color.Assign(
|
||||
HairColorLookup[hair_color_info
|
||||
.values[GetRandomValue<std::size_t>(hair_color_info.values_count)]]);
|
||||
bf.hair_flip.Assign(GetRandomValue<HairFlip>(HairFlip::Maximum));
|
||||
|
||||
bf.eye_type.Assign(
|
||||
eye_type_info.values[GetRandomValue<std::size_t>(eye_type_info.values_count)]);
|
||||
|
||||
const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
|
||||
const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
|
||||
const auto eye_rotate_offset{32 - EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
|
||||
const auto eye_rotate{32 - EyeRotateLookup[bf.eye_type]};
|
||||
|
||||
bf.eye_color.Assign(
|
||||
EyeColorLookup[eye_color_info
|
||||
.values[GetRandomValue<std::size_t>(eye_color_info.values_count)]]);
|
||||
bf.eye_scale.Assign(4);
|
||||
bf.eye_aspect.Assign(3);
|
||||
bf.eye_rotate.Assign(eye_rotate_offset - eye_rotate);
|
||||
bf.eye_x.Assign(2);
|
||||
bf.eye_y.Assign(axis_y + 12);
|
||||
|
||||
bf.eyebrow_type.Assign(
|
||||
eyebrow_type_info.values[GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
|
||||
|
||||
const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
|
||||
const auto eyebrow_y{race == Race::Asian ? 9 : 10};
|
||||
const auto eyebrow_rotate_offset{32 - EyebrowRotateLookup[eyebrow_rotate_1] + 6};
|
||||
const auto eyebrow_rotate{
|
||||
32 - EyebrowRotateLookup[static_cast<std::size_t>(bf.eyebrow_type.Value())]};
|
||||
|
||||
bf.eyebrow_color.Assign(bf.hair_color);
|
||||
bf.eyebrow_scale.Assign(4);
|
||||
bf.eyebrow_aspect.Assign(3);
|
||||
bf.eyebrow_rotate.Assign(eyebrow_rotate_offset - eyebrow_rotate);
|
||||
bf.eyebrow_x.Assign(2);
|
||||
bf.eyebrow_y.Assign(axis_y + eyebrow_y);
|
||||
|
||||
const auto nose_scale{gender == Gender::Female ? 3 : 4};
|
||||
|
||||
bf.nose_type.Assign(
|
||||
nose_type_info.values[GetRandomValue<std::size_t>(nose_type_info.values_count)]);
|
||||
bf.nose_scale.Assign(nose_scale);
|
||||
bf.nose_y.Assign(axis_y + 9);
|
||||
|
||||
const auto mouth_color{gender == Gender::Female ? GetRandomValue<int>(4) : 0};
|
||||
|
||||
bf.mouth_type.Assign(
|
||||
mouth_type_info.values[GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
|
||||
bf.mouth_color.Assign(MouthColorLookup[mouth_color]);
|
||||
bf.mouth_scale.Assign(4);
|
||||
bf.mouth_aspect.Assign(3);
|
||||
bf.mouth_y.Assign(axis_y + 13);
|
||||
|
||||
bf.beard_color.Assign(bf.hair_color);
|
||||
bf.mustache_scale.Assign(4);
|
||||
|
||||
if (gender == Gender::Male && age != Age::Young && GetRandomValue<int>(10) < 2) {
|
||||
const auto mustache_and_beard_flag{
|
||||
GetRandomValue<BeardAndMustacheFlag>(BeardAndMustacheFlag::All)};
|
||||
|
||||
auto beard_type{BeardType::None};
|
||||
auto mustache_type{MustacheType::None};
|
||||
|
||||
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
|
||||
BeardAndMustacheFlag::Beard) {
|
||||
beard_type = GetRandomValue<BeardType>(BeardType::Beard1, BeardType::Beard5);
|
||||
}
|
||||
|
||||
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
|
||||
BeardAndMustacheFlag::Mustache) {
|
||||
mustache_type =
|
||||
GetRandomValue<MustacheType>(MustacheType::Mustache1, MustacheType::Mustache5);
|
||||
}
|
||||
|
||||
bf.mustache_type.Assign(mustache_type);
|
||||
bf.beard_type.Assign(beard_type);
|
||||
bf.mustache_y.Assign(10);
|
||||
} else {
|
||||
bf.mustache_type.Assign(MustacheType::None);
|
||||
bf.beard_type.Assign(BeardType::None);
|
||||
bf.mustache_y.Assign(axis_y + 10);
|
||||
}
|
||||
|
||||
const auto glasses_type_start{GetRandomValue<std::size_t>(100)};
|
||||
u8 glasses_type{};
|
||||
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
|
||||
if (++glasses_type >= glasses_type_info.values_count) {
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bf.glasses_type.Assign(glasses_type);
|
||||
bf.glasses_color.Assign(GlassesColorLookup[0]);
|
||||
bf.glasses_scale.Assign(4);
|
||||
bf.glasses_y.Assign(axis_y + 10);
|
||||
|
||||
bf.mole_type.Assign(0);
|
||||
bf.mole_scale.Assign(4);
|
||||
bf.mole_x.Assign(2);
|
||||
bf.mole_y.Assign(20);
|
||||
|
||||
return {DefaultMiiName, bf, user_id};
|
||||
}
|
||||
|
||||
MiiStoreData BuildDefaultStoreData(const DefaultMii& info, const Common::UUID& user_id) {
|
||||
MiiStoreBitFields bf{};
|
||||
|
||||
bf.font_region.Assign(info.font_region);
|
||||
bf.favorite_color.Assign(info.favorite_color);
|
||||
bf.gender.Assign(info.gender);
|
||||
bf.height.Assign(info.height);
|
||||
bf.build.Assign(info.weight);
|
||||
bf.type.Assign(info.type);
|
||||
bf.region_move.Assign(info.region);
|
||||
bf.faceline_type.Assign(info.face_type);
|
||||
bf.faceline_color.Assign(info.face_color);
|
||||
bf.faceline_wrinkle.Assign(info.face_wrinkle);
|
||||
bf.faceline_makeup.Assign(info.face_makeup);
|
||||
bf.hair_type.Assign(info.hair_type);
|
||||
bf.hair_color.Assign(HairColorLookup[info.hair_color]);
|
||||
bf.hair_flip.Assign(static_cast<HairFlip>(info.hair_flip));
|
||||
bf.eye_type.Assign(info.eye_type);
|
||||
bf.eye_color.Assign(EyeColorLookup[info.eye_color]);
|
||||
bf.eye_scale.Assign(info.eye_scale);
|
||||
bf.eye_aspect.Assign(info.eye_aspect);
|
||||
bf.eye_rotate.Assign(info.eye_rotate);
|
||||
bf.eye_x.Assign(info.eye_x);
|
||||
bf.eye_y.Assign(info.eye_y);
|
||||
bf.eyebrow_type.Assign(info.eyebrow_type);
|
||||
bf.eyebrow_color.Assign(HairColorLookup[info.eyebrow_color]);
|
||||
bf.eyebrow_scale.Assign(info.eyebrow_scale);
|
||||
bf.eyebrow_aspect.Assign(info.eyebrow_aspect);
|
||||
bf.eyebrow_rotate.Assign(info.eyebrow_rotate);
|
||||
bf.eyebrow_x.Assign(info.eyebrow_x);
|
||||
bf.eyebrow_y.Assign(info.eyebrow_y - 3);
|
||||
bf.nose_type.Assign(info.nose_type);
|
||||
bf.nose_scale.Assign(info.nose_scale);
|
||||
bf.nose_y.Assign(info.nose_y);
|
||||
bf.mouth_type.Assign(info.mouth_type);
|
||||
bf.mouth_color.Assign(MouthColorLookup[info.mouth_color]);
|
||||
bf.mouth_scale.Assign(info.mouth_scale);
|
||||
bf.mouth_aspect.Assign(info.mouth_aspect);
|
||||
bf.mouth_y.Assign(info.mouth_y);
|
||||
bf.beard_color.Assign(HairColorLookup[info.beard_color]);
|
||||
bf.beard_type.Assign(static_cast<BeardType>(info.beard_type));
|
||||
bf.mustache_type.Assign(static_cast<MustacheType>(info.mustache_type));
|
||||
bf.mustache_scale.Assign(info.mustache_scale);
|
||||
bf.mustache_y.Assign(info.mustache_y);
|
||||
bf.glasses_type.Assign(info.glasses_type);
|
||||
bf.glasses_color.Assign(GlassesColorLookup[info.glasses_color]);
|
||||
bf.glasses_scale.Assign(info.glasses_scale);
|
||||
bf.glasses_y.Assign(info.glasses_y);
|
||||
bf.mole_type.Assign(info.mole_type);
|
||||
bf.mole_scale.Assign(info.mole_scale);
|
||||
bf.mole_x.Assign(info.mole_x);
|
||||
bf.mole_y.Assign(info.mole_y);
|
||||
|
||||
return {DefaultMiiName, bf, user_id};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MiiStoreData::MiiStoreData() = default;
|
||||
|
||||
MiiStoreData::MiiStoreData(const MiiStoreData::Name& name, const MiiStoreBitFields& bit_fields,
|
||||
const Common::UUID& user_id) {
|
||||
data.name = name;
|
||||
data.uuid = Common::UUID::MakeRandomRFC4122V4();
|
||||
|
||||
std::memcpy(data.data.data(), &bit_fields, sizeof(MiiStoreBitFields));
|
||||
data_crc = GenerateCrc16(data.data.data(), sizeof(data));
|
||||
device_crc = GenerateCrc16(&user_id, sizeof(Common::UUID));
|
||||
}
|
||||
|
||||
MiiManager::MiiManager() : user_id{Service::Account::ProfileManager().GetLastOpenedUser()} {}
|
||||
|
||||
bool MiiManager::CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter) {
|
||||
bool MiiManager::IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
|
||||
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool result{current_update_counter != update_counter};
|
||||
|
||||
current_update_counter = update_counter;
|
||||
|
||||
return result;
|
||||
const auto metadata_update_counter = metadata.update_counter;
|
||||
metadata.update_counter = update_counter;
|
||||
return metadata_update_counter != update_counter;
|
||||
}
|
||||
|
||||
bool MiiManager::IsFullDatabase() const {
|
||||
@ -396,306 +35,138 @@ bool MiiManager::IsFullDatabase() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 MiiManager::GetCount(SourceFlag source_flag) const {
|
||||
std::size_t count{};
|
||||
u32 MiiManager::GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const {
|
||||
u32 mii_count{};
|
||||
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
|
||||
mii_count += DefaultMiiCount;
|
||||
}
|
||||
if ((source_flag & SourceFlag::Database) != SourceFlag::None) {
|
||||
// TODO(bunnei): We don't implement the Mii database, but when we do, update this
|
||||
count += 0;
|
||||
}
|
||||
if ((source_flag & SourceFlag::Default) != SourceFlag::None) {
|
||||
count += DefaultMiiCount;
|
||||
}
|
||||
return static_cast<u32>(count);
|
||||
return mii_count;
|
||||
}
|
||||
|
||||
Result MiiManager::UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag) {
|
||||
Result MiiManager::UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
|
||||
const CharInfo& char_info, SourceFlag source_flag) {
|
||||
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
|
||||
return ERROR_CANNOT_FIND_ENTRY;
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
|
||||
return ERROR_CANNOT_FIND_ENTRY;
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
CharInfo MiiManager::BuildRandom(Age age, Gender gender, Race race) {
|
||||
return ConvertStoreDataToInfo(BuildRandomStoreData(age, gender, race, user_id));
|
||||
void MiiManager::BuildDefault(CharInfo& out_char_info, u32 index) const {
|
||||
StoreData store_data{};
|
||||
store_data.BuildDefault(index);
|
||||
out_char_info.SetFromStoreData(store_data);
|
||||
}
|
||||
|
||||
CharInfo MiiManager::BuildBase(Gender gender) {
|
||||
const std::size_t index = gender == Gender::Female ? 1 : 0;
|
||||
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::BaseMii.at(index), user_id));
|
||||
void MiiManager::BuildBase(CharInfo& out_char_info, Gender gender) const {
|
||||
StoreData store_data{};
|
||||
store_data.BuildBase(gender);
|
||||
out_char_info.SetFromStoreData(store_data);
|
||||
}
|
||||
|
||||
CharInfo MiiManager::BuildDefault(std::size_t index) {
|
||||
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
|
||||
void MiiManager::BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const {
|
||||
StoreData store_data{};
|
||||
store_data.BuildRandom(age, gender, race);
|
||||
out_char_info.SetFromStoreData(store_data);
|
||||
}
|
||||
|
||||
CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
|
||||
Service::Mii::MiiManager manager;
|
||||
auto mii = manager.BuildBase(Mii::Gender::Male);
|
||||
void MiiManager::ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const {
|
||||
StoreData store_data{};
|
||||
mii_v3.BuildToStoreData(store_data);
|
||||
out_char_info.SetFromStoreData(store_data);
|
||||
}
|
||||
|
||||
if (!ValidateV3Info(mii_v3)) {
|
||||
return mii;
|
||||
Result MiiManager::Get(const DatabaseSessionMetadata& metadata,
|
||||
std::span<CharInfoElement> out_elements, u32& out_count,
|
||||
SourceFlag source_flag) {
|
||||
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
|
||||
return BuildDefault(out_elements, out_count, source_flag);
|
||||
}
|
||||
|
||||
// TODO: We are ignoring a bunch of data from the mii_v3
|
||||
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
|
||||
|
||||
mii.gender = static_cast<u8>(mii_v3.mii_information.gender);
|
||||
mii.favorite_color = static_cast<u8>(mii_v3.mii_information.favorite_color);
|
||||
mii.height = mii_v3.height;
|
||||
mii.build = mii_v3.build;
|
||||
// Include default Mii at the end of the list
|
||||
return BuildDefault(out_elements, out_count, source_flag);
|
||||
}
|
||||
|
||||
// Copy name until string terminator
|
||||
mii.name = {};
|
||||
for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
|
||||
mii.name[index] = mii_v3.mii_name[index];
|
||||
if (mii.name[index] == 0) {
|
||||
break;
|
||||
}
|
||||
Result MiiManager::Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
|
||||
u32& out_count, SourceFlag source_flag) {
|
||||
if ((source_flag & SourceFlag::Database) == SourceFlag::None) {
|
||||
return BuildDefault(out_char_info, out_count, source_flag);
|
||||
}
|
||||
|
||||
mii.font_region = mii_v3.region_information.character_set;
|
||||
// TODO(bunnei): We don't implement the Mii database, so we can't have an entry
|
||||
|
||||
mii.faceline_type = mii_v3.appearance_bits1.face_shape;
|
||||
mii.faceline_color = mii_v3.appearance_bits1.skin_color;
|
||||
mii.faceline_wrinkle = mii_v3.appearance_bits2.wrinkles;
|
||||
mii.faceline_make = mii_v3.appearance_bits2.makeup;
|
||||
|
||||
mii.hair_type = mii_v3.hair_style;
|
||||
mii.hair_color = mii_v3.appearance_bits3.hair_color;
|
||||
mii.hair_flip = mii_v3.appearance_bits3.flip_hair;
|
||||
|
||||
mii.eye_type = static_cast<u8>(mii_v3.appearance_bits4.eye_type);
|
||||
mii.eye_color = static_cast<u8>(mii_v3.appearance_bits4.eye_color);
|
||||
mii.eye_scale = static_cast<u8>(mii_v3.appearance_bits4.eye_scale);
|
||||
mii.eye_aspect = static_cast<u8>(mii_v3.appearance_bits4.eye_vertical_stretch);
|
||||
mii.eye_rotate = static_cast<u8>(mii_v3.appearance_bits4.eye_rotation);
|
||||
mii.eye_x = static_cast<u8>(mii_v3.appearance_bits4.eye_spacing);
|
||||
mii.eye_y = static_cast<u8>(mii_v3.appearance_bits4.eye_y_position);
|
||||
|
||||
mii.eyebrow_type = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_style);
|
||||
mii.eyebrow_color = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_color);
|
||||
mii.eyebrow_scale = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_scale);
|
||||
mii.eyebrow_aspect = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_yscale);
|
||||
mii.eyebrow_rotate = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_rotation);
|
||||
mii.eyebrow_x = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_spacing);
|
||||
mii.eyebrow_y = static_cast<u8>(mii_v3.appearance_bits5.eyebrow_y_position);
|
||||
|
||||
mii.nose_type = static_cast<u8>(mii_v3.appearance_bits6.nose_type);
|
||||
mii.nose_scale = static_cast<u8>(mii_v3.appearance_bits6.nose_scale);
|
||||
mii.nose_y = static_cast<u8>(mii_v3.appearance_bits6.nose_y_position);
|
||||
|
||||
mii.mouth_type = static_cast<u8>(mii_v3.appearance_bits7.mouth_type);
|
||||
mii.mouth_color = static_cast<u8>(mii_v3.appearance_bits7.mouth_color);
|
||||
mii.mouth_scale = static_cast<u8>(mii_v3.appearance_bits7.mouth_scale);
|
||||
mii.mouth_aspect = static_cast<u8>(mii_v3.appearance_bits7.mouth_horizontal_stretch);
|
||||
mii.mouth_y = static_cast<u8>(mii_v3.appearance_bits8.mouth_y_position);
|
||||
|
||||
mii.mustache_type = static_cast<u8>(mii_v3.appearance_bits8.mustache_type);
|
||||
mii.mustache_scale = static_cast<u8>(mii_v3.appearance_bits9.mustache_scale);
|
||||
mii.mustache_y = static_cast<u8>(mii_v3.appearance_bits9.mustache_y_position);
|
||||
|
||||
mii.beard_type = static_cast<u8>(mii_v3.appearance_bits9.bear_type);
|
||||
mii.beard_color = static_cast<u8>(mii_v3.appearance_bits9.facial_hair_color);
|
||||
|
||||
mii.glasses_type = static_cast<u8>(mii_v3.appearance_bits10.glasses_type);
|
||||
mii.glasses_color = static_cast<u8>(mii_v3.appearance_bits10.glasses_color);
|
||||
mii.glasses_scale = static_cast<u8>(mii_v3.appearance_bits10.glasses_scale);
|
||||
mii.glasses_y = static_cast<u8>(mii_v3.appearance_bits10.glasses_y_position);
|
||||
|
||||
mii.mole_type = static_cast<u8>(mii_v3.appearance_bits11.mole_enabled);
|
||||
mii.mole_scale = static_cast<u8>(mii_v3.appearance_bits11.mole_scale);
|
||||
mii.mole_x = static_cast<u8>(mii_v3.appearance_bits11.mole_x_position);
|
||||
mii.mole_y = static_cast<u8>(mii_v3.appearance_bits11.mole_y_position);
|
||||
|
||||
// TODO: Validate mii data
|
||||
|
||||
return mii;
|
||||
// Include default Mii at the end of the list
|
||||
return BuildDefault(out_char_info, out_count, source_flag);
|
||||
}
|
||||
|
||||
Ver3StoreData MiiManager::BuildFromStoreData(const CharInfo& mii) const {
|
||||
Service::Mii::MiiManager manager;
|
||||
Ver3StoreData mii_v3{};
|
||||
|
||||
// TODO: We are ignoring a bunch of data from the mii_v3
|
||||
|
||||
mii_v3.version = 1;
|
||||
mii_v3.mii_information.gender.Assign(mii.gender);
|
||||
mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
|
||||
mii_v3.height = mii.height;
|
||||
mii_v3.build = mii.build;
|
||||
|
||||
// Copy name until string terminator
|
||||
mii_v3.mii_name = {};
|
||||
for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
|
||||
mii_v3.mii_name[index] = mii.name[index];
|
||||
if (mii_v3.mii_name[index] == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mii_v3.region_information.character_set.Assign(mii.font_region);
|
||||
|
||||
mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
|
||||
mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
|
||||
mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
|
||||
|
||||
mii_v3.hair_style = mii.hair_type;
|
||||
mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
|
||||
|
||||
mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
|
||||
mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
|
||||
mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
|
||||
mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
|
||||
mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
|
||||
mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
|
||||
|
||||
mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
|
||||
mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
|
||||
mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
|
||||
mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
|
||||
mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
|
||||
mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
|
||||
|
||||
mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
|
||||
mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
|
||||
mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
|
||||
|
||||
mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
|
||||
mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
|
||||
mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
|
||||
mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
|
||||
|
||||
mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
|
||||
mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
|
||||
mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
|
||||
|
||||
mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
|
||||
|
||||
mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
|
||||
mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
|
||||
|
||||
mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
|
||||
mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
|
||||
mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
|
||||
mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
|
||||
|
||||
// These types are converted to V3 from a table
|
||||
mii_v3.appearance_bits1.skin_color.Assign(Ver3FacelineColorTable[mii.faceline_color]);
|
||||
mii_v3.appearance_bits3.hair_color.Assign(Ver3HairColorTable[mii.hair_color]);
|
||||
mii_v3.appearance_bits4.eye_color.Assign(Ver3EyeColorTable[mii.eye_color]);
|
||||
mii_v3.appearance_bits5.eyebrow_color.Assign(Ver3HairColorTable[mii.eyebrow_color]);
|
||||
mii_v3.appearance_bits7.mouth_color.Assign(Ver3MouthlineColorTable[mii.mouth_color]);
|
||||
mii_v3.appearance_bits9.facial_hair_color.Assign(Ver3HairColorTable[mii.beard_color]);
|
||||
mii_v3.appearance_bits10.glasses_color.Assign(Ver3GlassColorTable[mii.glasses_color]);
|
||||
mii_v3.appearance_bits10.glasses_type.Assign(Ver3GlassTypeTable[mii.glasses_type]);
|
||||
|
||||
mii_v3.crc = GenerateCrc16(&mii_v3, sizeof(Ver3StoreData) - sizeof(u16));
|
||||
|
||||
// TODO: Validate mii_v3 data
|
||||
|
||||
return mii_v3;
|
||||
}
|
||||
|
||||
NfpStoreDataExtension MiiManager::SetFromStoreData(const CharInfo& mii) const {
|
||||
return {
|
||||
.faceline_color = static_cast<u8>(mii.faceline_color & 0xf),
|
||||
.hair_color = static_cast<u8>(mii.hair_color & 0x7f),
|
||||
.eye_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
|
||||
.eyebrow_color = static_cast<u8>(mii.eyebrow_color & 0x7f),
|
||||
.mouth_color = static_cast<u8>(mii.mouth_color & 0x7f),
|
||||
.beard_color = static_cast<u8>(mii.beard_color & 0x7f),
|
||||
.glass_color = static_cast<u8>(mii.glasses_color & 0x7f),
|
||||
.glass_type = static_cast<u8>(mii.glasses_type & 0x1f),
|
||||
};
|
||||
}
|
||||
|
||||
bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
|
||||
bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
|
||||
|
||||
is_valid = is_valid && (mii_v3.mii_name[0] != 0);
|
||||
|
||||
is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
|
||||
is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
|
||||
is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
|
||||
is_valid = is_valid && (mii_v3.height < 128);
|
||||
is_valid = is_valid && (mii_v3.build < 128);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
|
||||
|
||||
is_valid = is_valid && (mii_v3.hair_style < 132);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
|
||||
|
||||
is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
|
||||
is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
std::vector<MiiInfoElement> MiiManager::GetDefault(SourceFlag source_flag) {
|
||||
std::vector<MiiInfoElement> result;
|
||||
|
||||
Result MiiManager::BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
|
||||
SourceFlag source_flag) {
|
||||
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
|
||||
return result;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
for (std::size_t index = 0; index < DefaultMiiCount; index++) {
|
||||
result.emplace_back(BuildDefault(index), Source::Default);
|
||||
StoreData store_data{};
|
||||
|
||||
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
|
||||
if (out_elements.size() <= static_cast<std::size_t>(out_count)) {
|
||||
return ResultInvalidArgumentSize;
|
||||
}
|
||||
|
||||
store_data.BuildDefault(static_cast<u32>(index));
|
||||
|
||||
out_elements[out_count].source = Source::Default;
|
||||
out_elements[out_count].char_info.SetFromStoreData(store_data);
|
||||
out_count++;
|
||||
}
|
||||
|
||||
return result;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result MiiManager::GetIndex([[maybe_unused]] const CharInfo& info, u32& index) {
|
||||
Result MiiManager::BuildDefault(std::span<CharInfo> out_char_info, u32& out_count,
|
||||
SourceFlag source_flag) {
|
||||
if ((source_flag & SourceFlag::Default) == SourceFlag::None) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
StoreData store_data{};
|
||||
|
||||
for (std::size_t index = 0; index < DefaultMiiCount; ++index) {
|
||||
if (out_char_info.size() <= static_cast<std::size_t>(out_count)) {
|
||||
return ResultInvalidArgumentSize;
|
||||
}
|
||||
|
||||
store_data.BuildDefault(static_cast<u32>(index));
|
||||
|
||||
out_char_info[out_count].SetFromStoreData(store_data);
|
||||
out_count++;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result MiiManager::GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
|
||||
s32& out_index) {
|
||||
|
||||
if (char_info.Verify() != ValidationResult::NoErrors) {
|
||||
return ResultInvalidCharInfo;
|
||||
}
|
||||
|
||||
constexpr u32 INVALID_INDEX{0xFFFFFFFF};
|
||||
|
||||
index = INVALID_INDEX;
|
||||
out_index = INVALID_INDEX;
|
||||
|
||||
// TODO(bunnei): We don't implement the Mii database, so we can't have an index
|
||||
return ERROR_CANNOT_FIND_ENTRY;
|
||||
return ResultNotFound;
|
||||
}
|
||||
|
||||
void MiiManager::SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version) {
|
||||
metadata.interface_version = version;
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
||||
|
@ -6,7 +6,10 @@
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
#include "core/hle/service/mii/types/char_info.h"
|
||||
#include "core/hle/service/mii/types/store_data.h"
|
||||
#include "core/hle/service/mii/types/ver3_store_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
@ -16,26 +19,30 @@ class MiiManager {
|
||||
public:
|
||||
MiiManager();
|
||||
|
||||
bool CheckAndResetUpdateCounter(SourceFlag source_flag, u64& current_update_counter);
|
||||
bool IsUpdated(DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
|
||||
|
||||
bool IsFullDatabase() const;
|
||||
u32 GetCount(SourceFlag source_flag) const;
|
||||
Result UpdateLatest(CharInfo* out_info, const CharInfo& info, SourceFlag source_flag);
|
||||
CharInfo BuildRandom(Age age, Gender gender, Race race);
|
||||
CharInfo BuildBase(Gender gender);
|
||||
CharInfo BuildDefault(std::size_t index);
|
||||
CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
|
||||
bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
|
||||
std::vector<MiiInfoElement> GetDefault(SourceFlag source_flag);
|
||||
Result GetIndex(const CharInfo& info, u32& index);
|
||||
|
||||
// This is nn::mii::detail::Ver::StoreDataRaw::BuildFromStoreData
|
||||
Ver3StoreData BuildFromStoreData(const CharInfo& mii) const;
|
||||
|
||||
// This is nn::mii::detail::NfpStoreDataExtentionRaw::SetFromStoreData
|
||||
NfpStoreDataExtension SetFromStoreData(const CharInfo& mii) const;
|
||||
u32 GetCount(const DatabaseSessionMetadata& metadata, SourceFlag source_flag) const;
|
||||
Result UpdateLatest(DatabaseSessionMetadata& metadata, CharInfo& out_char_info,
|
||||
const CharInfo& char_info, SourceFlag source_flag);
|
||||
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfoElement> out_elements,
|
||||
u32& out_count, SourceFlag source_flag);
|
||||
Result Get(const DatabaseSessionMetadata& metadata, std::span<CharInfo> out_char_info,
|
||||
u32& out_count, SourceFlag source_flag);
|
||||
void BuildDefault(CharInfo& out_char_info, u32 index) const;
|
||||
void BuildBase(CharInfo& out_char_info, Gender gender) const;
|
||||
void BuildRandom(CharInfo& out_char_info, Age age, Gender gender, Race race) const;
|
||||
void ConvertV3ToCharInfo(CharInfo& out_char_info, const Ver3StoreData& mii_v3) const;
|
||||
std::vector<CharInfoElement> GetDefault(SourceFlag source_flag);
|
||||
Result GetIndex(const DatabaseSessionMetadata& metadata, const CharInfo& char_info,
|
||||
s32& out_index);
|
||||
void SetInterfaceVersion(DatabaseSessionMetadata& metadata, u32 version);
|
||||
|
||||
private:
|
||||
const Common::UUID user_id{};
|
||||
Result BuildDefault(std::span<CharInfoElement> out_elements, u32& out_count,
|
||||
SourceFlag source_flag);
|
||||
Result BuildDefault(std::span<CharInfo> out_char_info, u32& out_count, SourceFlag source_flag);
|
||||
|
||||
u64 update_counter{};
|
||||
};
|
||||
|
||||
|
20
src/core/hle/service/mii/mii_result.h
Normal file
20
src/core/hle/service/mii/mii_result.h
Normal file
@ -0,0 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
constexpr Result ResultInvalidArgument{ErrorModule::Mii, 1};
|
||||
constexpr Result ResultInvalidArgumentSize{ErrorModule::Mii, 2};
|
||||
constexpr Result ResultNotUpdated{ErrorModule::Mii, 3};
|
||||
constexpr Result ResultNotFound{ErrorModule::Mii, 4};
|
||||
constexpr Result ResultDatabaseFull{ErrorModule::Mii, 5};
|
||||
constexpr Result ResultInvalidCharInfo{ErrorModule::Mii, 100};
|
||||
constexpr Result ResultInvalidStoreData{ErrorModule::Mii, 109};
|
||||
constexpr Result ResultInvalidOperation{ErrorModule::Mii, 202};
|
||||
constexpr Result ResultPermissionDenied{ErrorModule::Mii, 203};
|
||||
|
||||
}; // namespace Service::Mii
|
694
src/core/hle/service/mii/mii_types.h
Normal file
694
src/core/hle/service/mii/mii_types.h
Normal file
@ -0,0 +1,694 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
constexpr u8 MaxHeight = 127;
|
||||
constexpr u8 MaxBuild = 127;
|
||||
constexpr u8 MaxType = 1;
|
||||
constexpr u8 MaxRegionMove = 3;
|
||||
constexpr u8 MaxEyeScale = 7;
|
||||
constexpr u8 MaxEyeAspect = 6;
|
||||
constexpr u8 MaxEyeRotate = 7;
|
||||
constexpr u8 MaxEyeX = 12;
|
||||
constexpr u8 MaxEyeY = 18;
|
||||
constexpr u8 MaxEyebrowScale = 8;
|
||||
constexpr u8 MaxEyebrowAspect = 6;
|
||||
constexpr u8 MaxEyebrowRotate = 11;
|
||||
constexpr u8 MaxEyebrowX = 12;
|
||||
constexpr u8 MaxEyebrowY = 18;
|
||||
constexpr u8 MaxNoseScale = 8;
|
||||
constexpr u8 MaxNoseY = 18;
|
||||
constexpr u8 MaxMouthScale = 8;
|
||||
constexpr u8 MaxMoutAspect = 6;
|
||||
constexpr u8 MaxMouthY = 18;
|
||||
constexpr u8 MaxMustacheScale = 8;
|
||||
constexpr u8 MasMustacheY = 16;
|
||||
constexpr u8 MaxGlassScale = 7;
|
||||
constexpr u8 MaxGlassY = 20;
|
||||
constexpr u8 MaxMoleScale = 8;
|
||||
constexpr u8 MaxMoleX = 16;
|
||||
constexpr u8 MaxMoleY = 30;
|
||||
constexpr u8 MaxVer3CommonColor = 7;
|
||||
constexpr u8 MaxVer3GlassType = 8;
|
||||
|
||||
enum class Age : u8 {
|
||||
Young,
|
||||
Normal,
|
||||
Old,
|
||||
All, // Default
|
||||
|
||||
Max = All,
|
||||
};
|
||||
|
||||
enum class Gender : u8 {
|
||||
Male,
|
||||
Female,
|
||||
All, // Default
|
||||
|
||||
Max = Female,
|
||||
};
|
||||
|
||||
enum class Race : u8 {
|
||||
Black,
|
||||
White,
|
||||
Asian,
|
||||
All, // Default
|
||||
|
||||
Max = All,
|
||||
};
|
||||
|
||||
enum class HairType : u8 {
|
||||
NormalLong, // Default
|
||||
NormalShort,
|
||||
NormalMedium,
|
||||
NormalExtraLong,
|
||||
NormalLongBottom,
|
||||
NormalTwoPeaks,
|
||||
PartingLong,
|
||||
FrontLock,
|
||||
PartingShort,
|
||||
PartingExtraLongCurved,
|
||||
PartingExtraLong,
|
||||
PartingMiddleLong,
|
||||
PartingSquared,
|
||||
PartingLongBottom,
|
||||
PeaksTop,
|
||||
PeaksSquared,
|
||||
PartingPeaks,
|
||||
PeaksLongBottom,
|
||||
Peaks,
|
||||
PeaksRounded,
|
||||
PeaksSide,
|
||||
PeaksMedium,
|
||||
PeaksLong,
|
||||
PeaksRoundedLong,
|
||||
PartingFrontPeaks,
|
||||
PartingLongFront,
|
||||
PartingLongRounded,
|
||||
PartingFrontPeaksLong,
|
||||
PartingExtraLongRounded,
|
||||
LongRounded,
|
||||
NormalUnknown1,
|
||||
NormalUnknown2,
|
||||
NormalUnknown3,
|
||||
NormalUnknown4,
|
||||
NormalUnknown5,
|
||||
NormalUnknown6,
|
||||
DreadLocks,
|
||||
PlatedMats,
|
||||
Caps,
|
||||
Afro,
|
||||
PlatedMatsLong,
|
||||
Beanie,
|
||||
Short,
|
||||
ShortTopLongSide,
|
||||
ShortUnknown1,
|
||||
ShortUnknown2,
|
||||
MilitaryParting,
|
||||
Military,
|
||||
ShortUnknown3,
|
||||
ShortUnknown4,
|
||||
ShortUnknown5,
|
||||
ShortUnknown6,
|
||||
NoneTop,
|
||||
None,
|
||||
LongUnknown1,
|
||||
LongUnknown2,
|
||||
LongUnknown3,
|
||||
LongUnknown4,
|
||||
LongUnknown5,
|
||||
LongUnknown6,
|
||||
LongUnknown7,
|
||||
LongUnknown8,
|
||||
LongUnknown9,
|
||||
LongUnknown10,
|
||||
LongUnknown11,
|
||||
LongUnknown12,
|
||||
LongUnknown13,
|
||||
LongUnknown14,
|
||||
LongUnknown15,
|
||||
LongUnknown16,
|
||||
LongUnknown17,
|
||||
LongUnknown18,
|
||||
LongUnknown19,
|
||||
LongUnknown20,
|
||||
LongUnknown21,
|
||||
LongUnknown22,
|
||||
LongUnknown23,
|
||||
LongUnknown24,
|
||||
LongUnknown25,
|
||||
LongUnknown26,
|
||||
LongUnknown27,
|
||||
LongUnknown28,
|
||||
LongUnknown29,
|
||||
LongUnknown30,
|
||||
LongUnknown31,
|
||||
LongUnknown32,
|
||||
LongUnknown33,
|
||||
LongUnknown34,
|
||||
LongUnknown35,
|
||||
LongUnknown36,
|
||||
LongUnknown37,
|
||||
LongUnknown38,
|
||||
LongUnknown39,
|
||||
LongUnknown40,
|
||||
LongUnknown41,
|
||||
LongUnknown42,
|
||||
LongUnknown43,
|
||||
LongUnknown44,
|
||||
LongUnknown45,
|
||||
LongUnknown46,
|
||||
LongUnknown47,
|
||||
LongUnknown48,
|
||||
LongUnknown49,
|
||||
LongUnknown50,
|
||||
LongUnknown51,
|
||||
LongUnknown52,
|
||||
LongUnknown53,
|
||||
LongUnknown54,
|
||||
LongUnknown55,
|
||||
LongUnknown56,
|
||||
LongUnknown57,
|
||||
LongUnknown58,
|
||||
LongUnknown59,
|
||||
LongUnknown60,
|
||||
LongUnknown61,
|
||||
LongUnknown62,
|
||||
LongUnknown63,
|
||||
LongUnknown64,
|
||||
LongUnknown65,
|
||||
LongUnknown66,
|
||||
TwoMediumFrontStrandsOneLongBackPonyTail,
|
||||
TwoFrontStrandsLongBackPonyTail,
|
||||
PartingFrontTwoLongBackPonyTails,
|
||||
TwoFrontStrandsOneLongBackPonyTail,
|
||||
LongBackPonyTail,
|
||||
LongFrontTwoLongBackPonyTails,
|
||||
StrandsTwoShortSidedPonyTails,
|
||||
TwoMediumSidedPonyTails,
|
||||
ShortFrontTwoBackPonyTails,
|
||||
TwoShortSidedPonyTails,
|
||||
TwoLongSidedPonyTails,
|
||||
LongFrontTwoBackPonyTails,
|
||||
|
||||
Max = LongFrontTwoBackPonyTails,
|
||||
};
|
||||
|
||||
enum class MoleType : u8 {
|
||||
None, // Default
|
||||
OneDot,
|
||||
|
||||
Max = OneDot,
|
||||
};
|
||||
|
||||
enum class HairFlip : u8 {
|
||||
Left, // Default
|
||||
Right,
|
||||
|
||||
Max = Right,
|
||||
};
|
||||
|
||||
enum class CommonColor : u8 {
|
||||
// For simplicity common colors aren't listed
|
||||
Max = 99,
|
||||
Count = 100,
|
||||
};
|
||||
|
||||
enum class FavoriteColor : u8 {
|
||||
Red, // Default
|
||||
Orange,
|
||||
Yellow,
|
||||
LimeGreen,
|
||||
Green,
|
||||
Blue,
|
||||
LightBlue,
|
||||
Pink,
|
||||
Purple,
|
||||
Brown,
|
||||
White,
|
||||
Black,
|
||||
|
||||
Max = Black,
|
||||
};
|
||||
|
||||
enum class EyeType : u8 {
|
||||
Normal, // Default
|
||||
NormalLash,
|
||||
WhiteLash,
|
||||
WhiteNoBottom,
|
||||
OvalAngledWhite,
|
||||
AngryWhite,
|
||||
DotLashType1,
|
||||
Line,
|
||||
DotLine,
|
||||
OvalWhite,
|
||||
RoundedWhite,
|
||||
NormalShadow,
|
||||
CircleWhite,
|
||||
Circle,
|
||||
CircleWhiteStroke,
|
||||
NormalOvalNoBottom,
|
||||
NormalOvalLarge,
|
||||
NormalRoundedNoBottom,
|
||||
SmallLash,
|
||||
Small,
|
||||
TwoSmall,
|
||||
NormalLongLash,
|
||||
WhiteTwoLashes,
|
||||
WhiteThreeLashes,
|
||||
DotAngry,
|
||||
DotAngled,
|
||||
Oval,
|
||||
SmallWhite,
|
||||
WhiteAngledNoBottom,
|
||||
WhiteAngledNoLeft,
|
||||
SmallWhiteTwoLashes,
|
||||
LeafWhiteLash,
|
||||
WhiteLargeNoBottom,
|
||||
Dot,
|
||||
DotLashType2,
|
||||
DotThreeLashes,
|
||||
WhiteOvalTop,
|
||||
WhiteOvalBottom,
|
||||
WhiteOvalBottomFlat,
|
||||
WhiteOvalTwoLashes,
|
||||
WhiteOvalThreeLashes,
|
||||
WhiteOvalNoBottomTwoLashes,
|
||||
DotWhite,
|
||||
WhiteOvalTopFlat,
|
||||
WhiteThinLeaf,
|
||||
StarThreeLashes,
|
||||
LineTwoLashes,
|
||||
CrowsFeet,
|
||||
WhiteNoBottomFlat,
|
||||
WhiteNoBottomRounded,
|
||||
WhiteSmallBottomLine,
|
||||
WhiteNoBottomLash,
|
||||
WhiteNoPartialBottomLash,
|
||||
WhiteOvalBottomLine,
|
||||
WhiteNoBottomLashTopLine,
|
||||
WhiteNoPartialBottomTwoLashes,
|
||||
NormalTopLine,
|
||||
WhiteOvalLash,
|
||||
RoundTired,
|
||||
WhiteLarge,
|
||||
|
||||
Max = WhiteLarge,
|
||||
};
|
||||
|
||||
enum class MouthType : u8 {
|
||||
Neutral, // Default
|
||||
NeutralLips,
|
||||
Smile,
|
||||
SmileStroke,
|
||||
SmileTeeth,
|
||||
LipsSmall,
|
||||
LipsLarge,
|
||||
Wave,
|
||||
WaveAngrySmall,
|
||||
NeutralStrokeLarge,
|
||||
TeethSurprised,
|
||||
LipsExtraLarge,
|
||||
LipsUp,
|
||||
NeutralDown,
|
||||
Surprised,
|
||||
TeethMiddle,
|
||||
NeutralStroke,
|
||||
LipsExtraSmall,
|
||||
Malicious,
|
||||
LipsDual,
|
||||
NeutralComma,
|
||||
NeutralUp,
|
||||
TeethLarge,
|
||||
WaveAngry,
|
||||
LipsSexy,
|
||||
SmileInverted,
|
||||
LipsSexyOutline,
|
||||
SmileRounded,
|
||||
LipsTeeth,
|
||||
NeutralOpen,
|
||||
TeethRounded,
|
||||
WaveAngrySmallInverted,
|
||||
NeutralCommaInverted,
|
||||
TeethFull,
|
||||
SmileDownLine,
|
||||
Kiss,
|
||||
|
||||
Max = Kiss,
|
||||
};
|
||||
|
||||
enum class FontRegion : u8 {
|
||||
Standard, // Default
|
||||
China,
|
||||
Korea,
|
||||
Taiwan,
|
||||
|
||||
Max = Taiwan,
|
||||
};
|
||||
|
||||
enum class FacelineType : u8 {
|
||||
Sharp, // Default
|
||||
Rounded,
|
||||
SharpRounded,
|
||||
SharpRoundedSmall,
|
||||
Large,
|
||||
LargeRounded,
|
||||
SharpSmall,
|
||||
Flat,
|
||||
Bump,
|
||||
Angular,
|
||||
FlatRounded,
|
||||
AngularSmall,
|
||||
|
||||
Max = AngularSmall,
|
||||
};
|
||||
|
||||
enum class FacelineColor : u8 {
|
||||
Beige, // Default
|
||||
WarmBeige,
|
||||
Natural,
|
||||
Honey,
|
||||
Chestnut,
|
||||
Porcelain,
|
||||
Ivory,
|
||||
WarmIvory,
|
||||
Almond,
|
||||
Espresso,
|
||||
|
||||
Max = Espresso,
|
||||
Count = Max + 1,
|
||||
};
|
||||
|
||||
enum class FacelineWrinkle : u8 {
|
||||
None, // Default
|
||||
TearTroughs,
|
||||
FacialPain,
|
||||
Cheeks,
|
||||
Folds,
|
||||
UnderTheEyes,
|
||||
SplitChin,
|
||||
Chin,
|
||||
BrowDroop,
|
||||
MouthFrown,
|
||||
CrowsFeet,
|
||||
FoldsCrowsFrown,
|
||||
|
||||
Max = FoldsCrowsFrown,
|
||||
};
|
||||
|
||||
enum class FacelineMake : u8 {
|
||||
None, // Default
|
||||
CheekPorcelain,
|
||||
CheekNatural,
|
||||
EyeShadowBlue,
|
||||
CheekBlushPorcelain,
|
||||
CheekBlushNatural,
|
||||
CheekPorcelainEyeShadowBlue,
|
||||
CheekPorcelainEyeShadowNatural,
|
||||
CheekBlushPorcelainEyeShadowEspresso,
|
||||
Freckles,
|
||||
LionsManeBeard,
|
||||
StubbleBeard,
|
||||
|
||||
Max = StubbleBeard,
|
||||
};
|
||||
|
||||
enum class EyebrowType : u8 {
|
||||
FlatAngledLarge, // Default
|
||||
LowArchRoundedThin,
|
||||
SoftAngledLarge,
|
||||
MediumArchRoundedThin,
|
||||
RoundedMedium,
|
||||
LowArchMedium,
|
||||
RoundedThin,
|
||||
UpThin,
|
||||
MediumArchRoundedMedium,
|
||||
RoundedLarge,
|
||||
UpLarge,
|
||||
FlatAngledLargeInverted,
|
||||
MediumArchFlat,
|
||||
AngledThin,
|
||||
HorizontalLarge,
|
||||
HighArchFlat,
|
||||
Flat,
|
||||
MediumArchLarge,
|
||||
LowArchThin,
|
||||
RoundedThinInverted,
|
||||
HighArchLarge,
|
||||
Hairy,
|
||||
Dotted,
|
||||
None,
|
||||
|
||||
Max = None,
|
||||
};
|
||||
|
||||
enum class NoseType : u8 {
|
||||
Normal, // Default
|
||||
Rounded,
|
||||
Dot,
|
||||
Arrow,
|
||||
Roman,
|
||||
Triangle,
|
||||
Button,
|
||||
RoundedInverted,
|
||||
Potato,
|
||||
Grecian,
|
||||
Snub,
|
||||
Aquiline,
|
||||
ArrowLeft,
|
||||
RoundedLarge,
|
||||
Hooked,
|
||||
Fat,
|
||||
Droopy,
|
||||
ArrowLarge,
|
||||
|
||||
Max = ArrowLarge,
|
||||
};
|
||||
|
||||
enum class BeardType : u8 {
|
||||
None,
|
||||
Goatee,
|
||||
GoateeLong,
|
||||
LionsManeLong,
|
||||
LionsMane,
|
||||
Full,
|
||||
|
||||
Min = Goatee,
|
||||
Max = Full,
|
||||
};
|
||||
|
||||
enum class MustacheType : u8 {
|
||||
None,
|
||||
Walrus,
|
||||
Pencil,
|
||||
Horseshoe,
|
||||
Normal,
|
||||
Toothbrush,
|
||||
|
||||
Min = Walrus,
|
||||
Max = Toothbrush,
|
||||
};
|
||||
|
||||
enum class GlassType : u8 {
|
||||
None,
|
||||
Oval,
|
||||
Wayfarer,
|
||||
Rectangle,
|
||||
TopRimless,
|
||||
Rounded,
|
||||
Oversized,
|
||||
CatEye,
|
||||
Square,
|
||||
BottomRimless,
|
||||
SemiOpaqueRounded,
|
||||
SemiOpaqueCatEye,
|
||||
SemiOpaqueOval,
|
||||
SemiOpaqueRectangle,
|
||||
SemiOpaqueAviator,
|
||||
OpaqueRounded,
|
||||
OpaqueCatEye,
|
||||
OpaqueOval,
|
||||
OpaqueRectangle,
|
||||
OpaqueAviator,
|
||||
|
||||
Max = OpaqueAviator,
|
||||
Count = Max + 1,
|
||||
};
|
||||
|
||||
enum class BeardAndMustacheFlag : u32 {
|
||||
Beard = 1,
|
||||
Mustache,
|
||||
All = Beard | Mustache,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
|
||||
|
||||
enum class Source : u32 {
|
||||
Database = 0,
|
||||
Default = 1,
|
||||
Account = 2,
|
||||
Friend = 3,
|
||||
};
|
||||
|
||||
enum class SourceFlag : u32 {
|
||||
None = 0,
|
||||
Database = 1 << 0,
|
||||
Default = 1 << 1,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
|
||||
|
||||
enum class ValidationResult : u32 {
|
||||
NoErrors = 0x0,
|
||||
InvalidBeardColor = 0x1,
|
||||
InvalidBeardType = 0x2,
|
||||
InvalidBuild = 0x3,
|
||||
InvalidEyeAspect = 0x4,
|
||||
InvalidEyeColor = 0x5,
|
||||
InvalidEyeRotate = 0x6,
|
||||
InvalidEyeScale = 0x7,
|
||||
InvalidEyeType = 0x8,
|
||||
InvalidEyeX = 0x9,
|
||||
InvalidEyeY = 0xa,
|
||||
InvalidEyebrowAspect = 0xb,
|
||||
InvalidEyebrowColor = 0xc,
|
||||
InvalidEyebrowRotate = 0xd,
|
||||
InvalidEyebrowScale = 0xe,
|
||||
InvalidEyebrowType = 0xf,
|
||||
InvalidEyebrowX = 0x10,
|
||||
InvalidEyebrowY = 0x11,
|
||||
InvalidFacelineColor = 0x12,
|
||||
InvalidFacelineMake = 0x13,
|
||||
InvalidFacelineWrinkle = 0x14,
|
||||
InvalidFacelineType = 0x15,
|
||||
InvalidColor = 0x16,
|
||||
InvalidFont = 0x17,
|
||||
InvalidGender = 0x18,
|
||||
InvalidGlassColor = 0x19,
|
||||
InvalidGlassScale = 0x1a,
|
||||
InvalidGlassType = 0x1b,
|
||||
InvalidGlassY = 0x1c,
|
||||
InvalidHairColor = 0x1d,
|
||||
InvalidHairFlip = 0x1e,
|
||||
InvalidHairType = 0x1f,
|
||||
InvalidHeight = 0x20,
|
||||
InvalidMoleScale = 0x21,
|
||||
InvalidMoleType = 0x22,
|
||||
InvalidMoleX = 0x23,
|
||||
InvalidMoleY = 0x24,
|
||||
InvalidMouthAspect = 0x25,
|
||||
InvalidMouthColor = 0x26,
|
||||
InvalidMouthScale = 0x27,
|
||||
InvalidMouthType = 0x28,
|
||||
InvalidMouthY = 0x29,
|
||||
InvalidMustacheScale = 0x2a,
|
||||
InvalidMustacheType = 0x2b,
|
||||
InvalidMustacheY = 0x2c,
|
||||
InvalidNoseScale = 0x2e,
|
||||
InvalidNoseType = 0x2f,
|
||||
InvalidNoseY = 0x30,
|
||||
InvalidRegionMove = 0x31,
|
||||
InvalidCreateId = 0x32,
|
||||
InvalidName = 0x33,
|
||||
InvalidType = 0x35,
|
||||
};
|
||||
|
||||
struct Nickname {
|
||||
static constexpr std::size_t MaxNameSize = 10;
|
||||
std::array<char16_t, MaxNameSize> data;
|
||||
|
||||
// Checks for null, non-zero terminated or dirty strings
|
||||
bool IsValid() const {
|
||||
if (data[0] == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data[MaxNameSize] != 0) {
|
||||
return false;
|
||||
}
|
||||
std::size_t index = 1;
|
||||
while (data[index] != 0) {
|
||||
index++;
|
||||
}
|
||||
while (index < MaxNameSize && data[index] == 0) {
|
||||
index++;
|
||||
}
|
||||
return index == MaxNameSize;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Nickname) == 0x14, "Nickname is an invalid size");
|
||||
|
||||
struct DefaultMii {
|
||||
u32 face_type{};
|
||||
u32 face_color{};
|
||||
u32 face_wrinkle{};
|
||||
u32 face_makeup{};
|
||||
u32 hair_type{};
|
||||
u32 hair_color{};
|
||||
u32 hair_flip{};
|
||||
u32 eye_type{};
|
||||
u32 eye_color{};
|
||||
u32 eye_scale{};
|
||||
u32 eye_aspect{};
|
||||
u32 eye_rotate{};
|
||||
u32 eye_x{};
|
||||
u32 eye_y{};
|
||||
u32 eyebrow_type{};
|
||||
u32 eyebrow_color{};
|
||||
u32 eyebrow_scale{};
|
||||
u32 eyebrow_aspect{};
|
||||
u32 eyebrow_rotate{};
|
||||
u32 eyebrow_x{};
|
||||
u32 eyebrow_y{};
|
||||
u32 nose_type{};
|
||||
u32 nose_scale{};
|
||||
u32 nose_y{};
|
||||
u32 mouth_type{};
|
||||
u32 mouth_color{};
|
||||
u32 mouth_scale{};
|
||||
u32 mouth_aspect{};
|
||||
u32 mouth_y{};
|
||||
u32 mustache_type{};
|
||||
u32 beard_type{};
|
||||
u32 beard_color{};
|
||||
u32 mustache_scale{};
|
||||
u32 mustache_y{};
|
||||
u32 glasses_type{};
|
||||
u32 glasses_color{};
|
||||
u32 glasses_scale{};
|
||||
u32 glasses_y{};
|
||||
u32 mole_type{};
|
||||
u32 mole_scale{};
|
||||
u32 mole_x{};
|
||||
u32 mole_y{};
|
||||
u32 height{};
|
||||
u32 weight{};
|
||||
u32 gender{};
|
||||
u32 favorite_color{};
|
||||
u32 region_move{};
|
||||
u32 font_region{};
|
||||
u32 type{};
|
||||
Nickname nickname;
|
||||
};
|
||||
static_assert(sizeof(DefaultMii) == 0xd8, "DefaultMii has incorrect size.");
|
||||
|
||||
struct DatabaseSessionMetadata {
|
||||
u32 interface_version;
|
||||
u32 magic;
|
||||
u64 update_counter;
|
||||
|
||||
bool IsInterfaceVersionSupported(u32 version) const {
|
||||
return version <= interface_version;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Service::Mii
|
59
src/core/hle/service/mii/mii_util.h
Normal file
59
src/core/hle/service/mii/mii_util.h
Normal file
@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <random>
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/uuid.h"
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
class MiiUtil {
|
||||
public:
|
||||
static u16 CalculateCrc16(const void* data, std::size_t size) {
|
||||
s32 crc{};
|
||||
for (std::size_t i = 0; i < size; i++) {
|
||||
crc ^= static_cast<const u8*>(data)[i] << 8;
|
||||
for (std::size_t j = 0; j < 8; j++) {
|
||||
crc <<= 1;
|
||||
if ((crc & 0x10000) != 0) {
|
||||
crc = (crc ^ 0x1021) & 0xFFFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Common::swap16(static_cast<u16>(crc));
|
||||
}
|
||||
|
||||
static Common::UUID MakeCreateId() {
|
||||
return Common::UUID::MakeRandomRFC4122V4();
|
||||
}
|
||||
|
||||
static Common::UUID GetDeviceId() {
|
||||
// This should be nn::settings::detail::GetMiiAuthorId()
|
||||
return Common::UUID::MakeDefault();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T GetRandomValue(T min, T max) {
|
||||
std::random_device device;
|
||||
std::mt19937 gen(device());
|
||||
std::uniform_int_distribution<u64> distribution(static_cast<u64>(min),
|
||||
static_cast<u64>(max));
|
||||
return static_cast<T>(distribution(gen));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T GetRandomValue(T max) {
|
||||
return GetRandomValue<T>({}, max);
|
||||
}
|
||||
|
||||
static bool IsFontRegionValid(FontRegion font, std::span<const char16_t> text) {
|
||||
// TODO: This function needs to check against the font tables
|
||||
return true;
|
||||
}
|
||||
};
|
||||
} // namespace Service::Mii
|
@ -1,27 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "core/hle/service/mii/types.h"
|
||||
|
||||
namespace Service::Mii::RawData {
|
||||
|
||||
extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
|
||||
extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFaceline;
|
||||
extern const std::array<Service::Mii::RandomMiiData3, 6> RandomMiiFacelineColor;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineWrinkle;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiFacelineMakeup;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiHairType;
|
||||
extern const std::array<Service::Mii::RandomMiiData3, 9> RandomMiiHairColor;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyeType;
|
||||
extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiEyeColor;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiEyebrowType;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiNoseType;
|
||||
extern const std::array<Service::Mii::RandomMiiData4, 18> RandomMiiMouthType;
|
||||
extern const std::array<Service::Mii::RandomMiiData2, 3> RandomMiiGlassType;
|
||||
|
||||
} // namespace Service::Mii::RawData
|
@ -1,553 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <type_traits>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/uuid.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
enum class Age : u32 {
|
||||
Young,
|
||||
Normal,
|
||||
Old,
|
||||
All,
|
||||
};
|
||||
|
||||
enum class BeardType : u32 {
|
||||
None,
|
||||
Beard1,
|
||||
Beard2,
|
||||
Beard3,
|
||||
Beard4,
|
||||
Beard5,
|
||||
};
|
||||
|
||||
enum class BeardAndMustacheFlag : u32 {
|
||||
Beard = 1,
|
||||
Mustache,
|
||||
All = Beard | Mustache,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(BeardAndMustacheFlag);
|
||||
|
||||
enum class FontRegion : u32 {
|
||||
Standard,
|
||||
China,
|
||||
Korea,
|
||||
Taiwan,
|
||||
};
|
||||
|
||||
enum class Gender : u32 {
|
||||
Male,
|
||||
Female,
|
||||
All,
|
||||
Maximum = Female,
|
||||
};
|
||||
|
||||
enum class HairFlip : u32 {
|
||||
Left,
|
||||
Right,
|
||||
Maximum = Right,
|
||||
};
|
||||
|
||||
enum class MustacheType : u32 {
|
||||
None,
|
||||
Mustache1,
|
||||
Mustache2,
|
||||
Mustache3,
|
||||
Mustache4,
|
||||
Mustache5,
|
||||
};
|
||||
|
||||
enum class Race : u32 {
|
||||
Black,
|
||||
White,
|
||||
Asian,
|
||||
All,
|
||||
};
|
||||
|
||||
enum class Source : u32 {
|
||||
Database = 0,
|
||||
Default = 1,
|
||||
Account = 2,
|
||||
Friend = 3,
|
||||
};
|
||||
|
||||
enum class SourceFlag : u32 {
|
||||
None = 0,
|
||||
Database = 1 << 0,
|
||||
Default = 1 << 1,
|
||||
};
|
||||
DECLARE_ENUM_FLAG_OPERATORS(SourceFlag);
|
||||
|
||||
// nn::mii::CharInfo
|
||||
struct CharInfo {
|
||||
Common::UUID uuid;
|
||||
std::array<char16_t, 11> name;
|
||||
u8 font_region;
|
||||
u8 favorite_color;
|
||||
u8 gender;
|
||||
u8 height;
|
||||
u8 build;
|
||||
u8 type;
|
||||
u8 region_move;
|
||||
u8 faceline_type;
|
||||
u8 faceline_color;
|
||||
u8 faceline_wrinkle;
|
||||
u8 faceline_make;
|
||||
u8 hair_type;
|
||||
u8 hair_color;
|
||||
u8 hair_flip;
|
||||
u8 eye_type;
|
||||
u8 eye_color;
|
||||
u8 eye_scale;
|
||||
u8 eye_aspect;
|
||||
u8 eye_rotate;
|
||||
u8 eye_x;
|
||||
u8 eye_y;
|
||||
u8 eyebrow_type;
|
||||
u8 eyebrow_color;
|
||||
u8 eyebrow_scale;
|
||||
u8 eyebrow_aspect;
|
||||
u8 eyebrow_rotate;
|
||||
u8 eyebrow_x;
|
||||
u8 eyebrow_y;
|
||||
u8 nose_type;
|
||||
u8 nose_scale;
|
||||
u8 nose_y;
|
||||
u8 mouth_type;
|
||||
u8 mouth_color;
|
||||
u8 mouth_scale;
|
||||
u8 mouth_aspect;
|
||||
u8 mouth_y;
|
||||
u8 beard_color;
|
||||
u8 beard_type;
|
||||
u8 mustache_type;
|
||||
u8 mustache_scale;
|
||||
u8 mustache_y;
|
||||
u8 glasses_type;
|
||||
u8 glasses_color;
|
||||
u8 glasses_scale;
|
||||
u8 glasses_y;
|
||||
u8 mole_type;
|
||||
u8 mole_scale;
|
||||
u8 mole_x;
|
||||
u8 mole_y;
|
||||
u8 padding;
|
||||
};
|
||||
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
|
||||
static_assert(std::has_unique_object_representations_v<CharInfo>,
|
||||
"All bits of CharInfo must contribute to its value.");
|
||||
|
||||
#pragma pack(push, 4)
|
||||
|
||||
struct MiiInfoElement {
|
||||
MiiInfoElement(const CharInfo& info_, Source source_) : info{info_}, source{source_} {}
|
||||
|
||||
CharInfo info{};
|
||||
Source source{};
|
||||
};
|
||||
static_assert(sizeof(MiiInfoElement) == 0x5c, "MiiInfoElement has incorrect size.");
|
||||
|
||||
struct MiiStoreBitFields {
|
||||
union {
|
||||
u32 word_0{};
|
||||
|
||||
BitField<0, 8, u32> hair_type;
|
||||
BitField<8, 7, u32> height;
|
||||
BitField<15, 1, u32> mole_type;
|
||||
BitField<16, 7, u32> build;
|
||||
BitField<23, 1, HairFlip> hair_flip;
|
||||
BitField<24, 7, u32> hair_color;
|
||||
BitField<31, 1, u32> type;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_1{};
|
||||
|
||||
BitField<0, 7, u32> eye_color;
|
||||
BitField<7, 1, Gender> gender;
|
||||
BitField<8, 7, u32> eyebrow_color;
|
||||
BitField<16, 7, u32> mouth_color;
|
||||
BitField<24, 7, u32> beard_color;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_2{};
|
||||
|
||||
BitField<0, 7, u32> glasses_color;
|
||||
BitField<8, 6, u32> eye_type;
|
||||
BitField<14, 2, u32> region_move;
|
||||
BitField<16, 6, u32> mouth_type;
|
||||
BitField<22, 2, FontRegion> font_region;
|
||||
BitField<24, 5, u32> eye_y;
|
||||
BitField<29, 3, u32> glasses_scale;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_3{};
|
||||
|
||||
BitField<0, 5, u32> eyebrow_type;
|
||||
BitField<5, 3, MustacheType> mustache_type;
|
||||
BitField<8, 5, u32> nose_type;
|
||||
BitField<13, 3, BeardType> beard_type;
|
||||
BitField<16, 5, u32> nose_y;
|
||||
BitField<21, 3, u32> mouth_aspect;
|
||||
BitField<24, 5, u32> mouth_y;
|
||||
BitField<29, 3, u32> eyebrow_aspect;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_4{};
|
||||
|
||||
BitField<0, 5, u32> mustache_y;
|
||||
BitField<5, 3, u32> eye_rotate;
|
||||
BitField<8, 5, u32> glasses_y;
|
||||
BitField<13, 3, u32> eye_aspect;
|
||||
BitField<16, 5, u32> mole_x;
|
||||
BitField<21, 3, u32> eye_scale;
|
||||
BitField<24, 5, u32> mole_y;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_5{};
|
||||
|
||||
BitField<0, 5, u32> glasses_type;
|
||||
BitField<8, 4, u32> favorite_color;
|
||||
BitField<12, 4, u32> faceline_type;
|
||||
BitField<16, 4, u32> faceline_color;
|
||||
BitField<20, 4, u32> faceline_wrinkle;
|
||||
BitField<24, 4, u32> faceline_makeup;
|
||||
BitField<28, 4, u32> eye_x;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_6{};
|
||||
|
||||
BitField<0, 4, u32> eyebrow_scale;
|
||||
BitField<4, 4, u32> eyebrow_rotate;
|
||||
BitField<8, 4, u32> eyebrow_x;
|
||||
BitField<12, 4, u32> eyebrow_y;
|
||||
BitField<16, 4, u32> nose_scale;
|
||||
BitField<20, 4, u32> mouth_scale;
|
||||
BitField<24, 4, u32> mustache_scale;
|
||||
BitField<28, 4, u32> mole_scale;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(MiiStoreBitFields) == 0x1c, "MiiStoreBitFields has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<MiiStoreBitFields>,
|
||||
"MiiStoreBitFields is not trivially copyable.");
|
||||
|
||||
// This is nn::mii::Ver3StoreData
|
||||
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
|
||||
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
|
||||
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
|
||||
struct Ver3StoreData {
|
||||
u8 version;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 1, u8> allow_copying;
|
||||
BitField<1, 1, u8> profanity_flag;
|
||||
BitField<2, 2, u8> region_lock;
|
||||
BitField<4, 2, u8> character_set;
|
||||
} region_information;
|
||||
u16_be mii_id;
|
||||
u64_be system_id;
|
||||
u32_be specialness_and_creation_date;
|
||||
std::array<u8, 0x6> creator_mac;
|
||||
u16_be padding;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 1, u16> gender;
|
||||
BitField<1, 4, u16> birth_month;
|
||||
BitField<5, 5, u16> birth_day;
|
||||
BitField<10, 4, u16> favorite_color;
|
||||
BitField<14, 1, u16> favorite;
|
||||
} mii_information;
|
||||
std::array<char16_t, 0xA> mii_name;
|
||||
u8 height;
|
||||
u8 build;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 1, u8> disable_sharing;
|
||||
BitField<1, 4, u8> face_shape;
|
||||
BitField<5, 3, u8> skin_color;
|
||||
} appearance_bits1;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 4, u8> wrinkles;
|
||||
BitField<4, 4, u8> makeup;
|
||||
} appearance_bits2;
|
||||
u8 hair_style;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 3, u8> hair_color;
|
||||
BitField<3, 1, u8> flip_hair;
|
||||
} appearance_bits3;
|
||||
union {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 6, u32> eye_type;
|
||||
BitField<6, 3, u32> eye_color;
|
||||
BitField<9, 4, u32> eye_scale;
|
||||
BitField<13, 3, u32> eye_vertical_stretch;
|
||||
BitField<16, 5, u32> eye_rotation;
|
||||
BitField<21, 4, u32> eye_spacing;
|
||||
BitField<25, 5, u32> eye_y_position;
|
||||
} appearance_bits4;
|
||||
union {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 5, u32> eyebrow_style;
|
||||
BitField<5, 3, u32> eyebrow_color;
|
||||
BitField<8, 4, u32> eyebrow_scale;
|
||||
BitField<12, 3, u32> eyebrow_yscale;
|
||||
BitField<16, 4, u32> eyebrow_rotation;
|
||||
BitField<21, 4, u32> eyebrow_spacing;
|
||||
BitField<25, 5, u32> eyebrow_y_position;
|
||||
} appearance_bits5;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 5, u16> nose_type;
|
||||
BitField<5, 4, u16> nose_scale;
|
||||
BitField<9, 5, u16> nose_y_position;
|
||||
} appearance_bits6;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 6, u16> mouth_type;
|
||||
BitField<6, 3, u16> mouth_color;
|
||||
BitField<9, 4, u16> mouth_scale;
|
||||
BitField<13, 3, u16> mouth_horizontal_stretch;
|
||||
} appearance_bits7;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 5, u8> mouth_y_position;
|
||||
BitField<5, 3, u8> mustache_type;
|
||||
} appearance_bits8;
|
||||
u8 allow_copying;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 3, u16> bear_type;
|
||||
BitField<3, 3, u16> facial_hair_color;
|
||||
BitField<6, 4, u16> mustache_scale;
|
||||
BitField<10, 5, u16> mustache_y_position;
|
||||
} appearance_bits9;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 4, u16> glasses_type;
|
||||
BitField<4, 3, u16> glasses_color;
|
||||
BitField<7, 4, u16> glasses_scale;
|
||||
BitField<11, 5, u16> glasses_y_position;
|
||||
} appearance_bits10;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 1, u16> mole_enabled;
|
||||
BitField<1, 4, u16> mole_scale;
|
||||
BitField<5, 5, u16> mole_x_position;
|
||||
BitField<10, 5, u16> mole_y_position;
|
||||
} appearance_bits11;
|
||||
|
||||
std::array<u16_le, 0xA> author_name;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
u16_be crc;
|
||||
};
|
||||
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
|
||||
|
||||
struct NfpStoreDataExtension {
|
||||
u8 faceline_color;
|
||||
u8 hair_color;
|
||||
u8 eye_color;
|
||||
u8 eyebrow_color;
|
||||
u8 mouth_color;
|
||||
u8 beard_color;
|
||||
u8 glass_color;
|
||||
u8 glass_type;
|
||||
};
|
||||
static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
|
||||
|
||||
constexpr std::array<u8, 0x10> Ver3FacelineColorTable{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x0, 0x1, 0x5, 0x5,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 100> Ver3HairColorTable{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x0, 0x4, 0x3, 0x5, 0x4, 0x4, 0x6, 0x2, 0x0,
|
||||
0x6, 0x4, 0x3, 0x2, 0x2, 0x7, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
|
||||
0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x4,
|
||||
0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5,
|
||||
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x7, 0x5, 0x7, 0x7, 0x7, 0x7, 0x7, 0x6, 0x7,
|
||||
0x7, 0x7, 0x7, 0x7, 0x3, 0x7, 0x7, 0x7, 0x7, 0x7, 0x0, 0x4, 0x4, 0x4, 0x4,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 100> Ver3EyeColorTable{
|
||||
0x0, 0x2, 0x2, 0x2, 0x1, 0x3, 0x2, 0x3, 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x2, 0x2, 0x4,
|
||||
0x2, 0x1, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
|
||||
0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x0, 0x4, 0x4,
|
||||
0x4, 0x4, 0x4, 0x4, 0x4, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
|
||||
0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2,
|
||||
0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 100> Ver3MouthlineColorTable{
|
||||
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
|
||||
0x4, 0x4, 0x0, 0x1, 0x2, 0x3, 0x4, 0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x1, 0x4,
|
||||
0x4, 0x2, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
|
||||
0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x4,
|
||||
0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x4, 0x0, 0x3, 0x3, 0x3, 0x3, 0x4, 0x3, 0x3, 0x3, 0x3,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 100> Ver3GlassColorTable{
|
||||
0x0, 0x1, 0x1, 0x1, 0x5, 0x1, 0x1, 0x4, 0x0, 0x5, 0x1, 0x1, 0x3, 0x5, 0x1, 0x2, 0x3,
|
||||
0x4, 0x5, 0x4, 0x2, 0x2, 0x4, 0x4, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2,
|
||||
0x2, 0x2, 0x2, 0x2, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3,
|
||||
0x3, 0x3, 0x3, 0x3, 0x3, 0x0, 0x0, 0x0, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x0, 0x5, 0x5,
|
||||
0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5, 0x1, 0x4,
|
||||
0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x5, 0x5, 0x5, 0x5, 0x5, 0x5,
|
||||
};
|
||||
|
||||
constexpr std::array<u8, 20> Ver3GlassTypeTable{
|
||||
0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x1,
|
||||
0x2, 0x1, 0x3, 0x7, 0x7, 0x6, 0x7, 0x8, 0x7, 0x7,
|
||||
};
|
||||
|
||||
struct MiiStoreData {
|
||||
using Name = std::array<char16_t, 10>;
|
||||
|
||||
MiiStoreData();
|
||||
MiiStoreData(const Name& name, const MiiStoreBitFields& bit_fields,
|
||||
const Common::UUID& user_id);
|
||||
|
||||
// This corresponds to the above structure MiiStoreBitFields. I did it like this because the
|
||||
// BitField<> type makes this (and any thing that contains it) not trivially copyable, which is
|
||||
// not suitable for our uses.
|
||||
struct {
|
||||
std::array<u8, 0x1C> data{};
|
||||
static_assert(sizeof(MiiStoreBitFields) == sizeof(data), "data field has incorrect size.");
|
||||
|
||||
Name name{};
|
||||
Common::UUID uuid{};
|
||||
} data;
|
||||
|
||||
u16 data_crc{};
|
||||
u16 device_crc{};
|
||||
};
|
||||
static_assert(sizeof(MiiStoreData) == 0x44, "MiiStoreData has incorrect size.");
|
||||
|
||||
struct MiiStoreDataElement {
|
||||
MiiStoreData data{};
|
||||
Source source{};
|
||||
};
|
||||
static_assert(sizeof(MiiStoreDataElement) == 0x48, "MiiStoreDataElement has incorrect size.");
|
||||
|
||||
struct MiiDatabase {
|
||||
u32 magic{}; // 'NFDB'
|
||||
std::array<MiiStoreData, 0x64> miis{};
|
||||
INSERT_PADDING_BYTES(1);
|
||||
u8 count{};
|
||||
u16 crc{};
|
||||
};
|
||||
static_assert(sizeof(MiiDatabase) == 0x1A98, "MiiDatabase has incorrect size.");
|
||||
|
||||
struct RandomMiiValues {
|
||||
std::array<u8, 0xbc> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
|
||||
|
||||
struct RandomMiiData4 {
|
||||
Gender gender{};
|
||||
Age age{};
|
||||
Race race{};
|
||||
u32 values_count{};
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
|
||||
|
||||
struct RandomMiiData3 {
|
||||
u32 arg_1;
|
||||
u32 arg_2;
|
||||
u32 values_count;
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
|
||||
|
||||
struct RandomMiiData2 {
|
||||
u32 arg_1;
|
||||
u32 values_count;
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
|
||||
|
||||
struct DefaultMii {
|
||||
u32 face_type{};
|
||||
u32 face_color{};
|
||||
u32 face_wrinkle{};
|
||||
u32 face_makeup{};
|
||||
u32 hair_type{};
|
||||
u32 hair_color{};
|
||||
u32 hair_flip{};
|
||||
u32 eye_type{};
|
||||
u32 eye_color{};
|
||||
u32 eye_scale{};
|
||||
u32 eye_aspect{};
|
||||
u32 eye_rotate{};
|
||||
u32 eye_x{};
|
||||
u32 eye_y{};
|
||||
u32 eyebrow_type{};
|
||||
u32 eyebrow_color{};
|
||||
u32 eyebrow_scale{};
|
||||
u32 eyebrow_aspect{};
|
||||
u32 eyebrow_rotate{};
|
||||
u32 eyebrow_x{};
|
||||
u32 eyebrow_y{};
|
||||
u32 nose_type{};
|
||||
u32 nose_scale{};
|
||||
u32 nose_y{};
|
||||
u32 mouth_type{};
|
||||
u32 mouth_color{};
|
||||
u32 mouth_scale{};
|
||||
u32 mouth_aspect{};
|
||||
u32 mouth_y{};
|
||||
u32 mustache_type{};
|
||||
u32 beard_type{};
|
||||
u32 beard_color{};
|
||||
u32 mustache_scale{};
|
||||
u32 mustache_y{};
|
||||
u32 glasses_type{};
|
||||
u32 glasses_color{};
|
||||
u32 glasses_scale{};
|
||||
u32 glasses_y{};
|
||||
u32 mole_type{};
|
||||
u32 mole_scale{};
|
||||
u32 mole_x{};
|
||||
u32 mole_y{};
|
||||
u32 height{};
|
||||
u32 weight{};
|
||||
Gender gender{};
|
||||
u32 favorite_color{};
|
||||
u32 region{};
|
||||
FontRegion font_region{};
|
||||
u32 type{};
|
||||
INSERT_PADDING_WORDS(5);
|
||||
};
|
||||
static_assert(sizeof(DefaultMii) == 0xd8, "MiiStoreData has incorrect size.");
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
} // namespace Service::Mii
|
482
src/core/hle/service/mii/types/char_info.cpp
Normal file
482
src/core/hle/service/mii/types/char_info.cpp
Normal file
@ -0,0 +1,482 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/mii/types/char_info.h"
|
||||
#include "core/hle/service/mii/types/store_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
void CharInfo::SetFromStoreData(const StoreData& store_data) {
|
||||
name = store_data.GetNickname();
|
||||
null_terminator = '\0';
|
||||
create_id = store_data.GetCreateId();
|
||||
font_region = store_data.GetFontRegion();
|
||||
favorite_color = store_data.GetFavoriteColor();
|
||||
gender = store_data.GetGender();
|
||||
height = store_data.GetHeight();
|
||||
build = store_data.GetBuild();
|
||||
type = store_data.GetType();
|
||||
region_move = store_data.GetRegionMove();
|
||||
faceline_type = store_data.GetFacelineType();
|
||||
faceline_color = store_data.GetFacelineColor();
|
||||
faceline_wrinkle = store_data.GetFacelineWrinkle();
|
||||
faceline_make = store_data.GetFacelineMake();
|
||||
hair_type = store_data.GetHairType();
|
||||
hair_color = store_data.GetHairColor();
|
||||
hair_flip = store_data.GetHairFlip();
|
||||
eye_type = store_data.GetEyeType();
|
||||
eye_color = store_data.GetEyeColor();
|
||||
eye_scale = store_data.GetEyeScale();
|
||||
eye_aspect = store_data.GetEyeAspect();
|
||||
eye_rotate = store_data.GetEyeRotate();
|
||||
eye_x = store_data.GetEyeX();
|
||||
eye_y = store_data.GetEyeY();
|
||||
eyebrow_type = store_data.GetEyebrowType();
|
||||
eyebrow_color = store_data.GetEyebrowColor();
|
||||
eyebrow_scale = store_data.GetEyebrowScale();
|
||||
eyebrow_aspect = store_data.GetEyebrowAspect();
|
||||
eyebrow_rotate = store_data.GetEyebrowRotate();
|
||||
eyebrow_x = store_data.GetEyebrowX();
|
||||
eyebrow_y = store_data.GetEyebrowY();
|
||||
nose_type = store_data.GetNoseType();
|
||||
nose_scale = store_data.GetNoseScale();
|
||||
nose_y = store_data.GetNoseY();
|
||||
mouth_type = store_data.GetMouthType();
|
||||
mouth_color = store_data.GetMouthColor();
|
||||
mouth_scale = store_data.GetMouthScale();
|
||||
mouth_aspect = store_data.GetMouthAspect();
|
||||
mouth_y = store_data.GetMouthY();
|
||||
beard_color = store_data.GetBeardColor();
|
||||
beard_type = store_data.GetBeardType();
|
||||
mustache_type = store_data.GetMustacheType();
|
||||
mustache_scale = store_data.GetMustacheScale();
|
||||
mustache_y = store_data.GetMustacheY();
|
||||
glass_type = store_data.GetGlassType();
|
||||
glass_color = store_data.GetGlassColor();
|
||||
glass_scale = store_data.GetGlassScale();
|
||||
glass_y = store_data.GetGlassY();
|
||||
mole_type = store_data.GetMoleType();
|
||||
mole_scale = store_data.GetMoleScale();
|
||||
mole_x = store_data.GetMoleX();
|
||||
mole_y = store_data.GetMoleY();
|
||||
padding = '\0';
|
||||
}
|
||||
|
||||
ValidationResult CharInfo::Verify() const {
|
||||
if (!create_id.IsValid()) {
|
||||
return ValidationResult::InvalidCreateId;
|
||||
}
|
||||
if (!name.IsValid()) {
|
||||
return ValidationResult::InvalidName;
|
||||
}
|
||||
if (font_region > FontRegion::Max) {
|
||||
return ValidationResult::InvalidFont;
|
||||
}
|
||||
if (favorite_color > FavoriteColor::Max) {
|
||||
return ValidationResult::InvalidColor;
|
||||
}
|
||||
if (gender > Gender::Max) {
|
||||
return ValidationResult::InvalidGender;
|
||||
}
|
||||
if (height > MaxHeight) {
|
||||
return ValidationResult::InvalidHeight;
|
||||
}
|
||||
if (build > MaxBuild) {
|
||||
return ValidationResult::InvalidBuild;
|
||||
}
|
||||
if (type > MaxType) {
|
||||
return ValidationResult::InvalidType;
|
||||
}
|
||||
if (region_move > MaxRegionMove) {
|
||||
return ValidationResult::InvalidRegionMove;
|
||||
}
|
||||
if (faceline_type > FacelineType::Max) {
|
||||
return ValidationResult::InvalidFacelineType;
|
||||
}
|
||||
if (faceline_color > FacelineColor::Max) {
|
||||
return ValidationResult::InvalidFacelineColor;
|
||||
}
|
||||
if (faceline_wrinkle > FacelineWrinkle::Max) {
|
||||
return ValidationResult::InvalidFacelineWrinkle;
|
||||
}
|
||||
if (faceline_make > FacelineMake::Max) {
|
||||
return ValidationResult::InvalidFacelineMake;
|
||||
}
|
||||
if (hair_type > HairType::Max) {
|
||||
return ValidationResult::InvalidHairType;
|
||||
}
|
||||
if (hair_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidHairColor;
|
||||
}
|
||||
if (hair_flip > HairFlip::Max) {
|
||||
return ValidationResult::InvalidHairFlip;
|
||||
}
|
||||
if (eye_type > EyeType::Max) {
|
||||
return ValidationResult::InvalidEyeType;
|
||||
}
|
||||
if (eye_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidEyeColor;
|
||||
}
|
||||
if (eye_scale > MaxEyeScale) {
|
||||
return ValidationResult::InvalidEyeScale;
|
||||
}
|
||||
if (eye_aspect > MaxEyeAspect) {
|
||||
return ValidationResult::InvalidEyeAspect;
|
||||
}
|
||||
if (eye_rotate > MaxEyeX) {
|
||||
return ValidationResult::InvalidEyeRotate;
|
||||
}
|
||||
if (eye_x > MaxEyeX) {
|
||||
return ValidationResult::InvalidEyeX;
|
||||
}
|
||||
if (eye_y > MaxEyeY) {
|
||||
return ValidationResult::InvalidEyeY;
|
||||
}
|
||||
if (eyebrow_type > EyebrowType::Max) {
|
||||
return ValidationResult::InvalidEyebrowType;
|
||||
}
|
||||
if (eyebrow_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidEyebrowColor;
|
||||
}
|
||||
if (eyebrow_scale > MaxEyebrowScale) {
|
||||
return ValidationResult::InvalidEyebrowScale;
|
||||
}
|
||||
if (eyebrow_aspect > MaxEyebrowAspect) {
|
||||
return ValidationResult::InvalidEyebrowAspect;
|
||||
}
|
||||
if (eyebrow_rotate > MaxEyebrowRotate) {
|
||||
return ValidationResult::InvalidEyebrowRotate;
|
||||
}
|
||||
if (eyebrow_x > MaxEyebrowX) {
|
||||
return ValidationResult::InvalidEyebrowX;
|
||||
}
|
||||
if (eyebrow_y > MaxEyebrowY) {
|
||||
return ValidationResult::InvalidEyebrowY;
|
||||
}
|
||||
if (nose_type > NoseType::Max) {
|
||||
return ValidationResult::InvalidNoseType;
|
||||
}
|
||||
if (nose_scale > MaxNoseScale) {
|
||||
return ValidationResult::InvalidNoseScale;
|
||||
}
|
||||
if (nose_y > MaxNoseY) {
|
||||
return ValidationResult::InvalidNoseY;
|
||||
}
|
||||
if (mouth_type > MouthType::Max) {
|
||||
return ValidationResult::InvalidMouthType;
|
||||
}
|
||||
if (mouth_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidMouthColor;
|
||||
}
|
||||
if (mouth_scale > MaxMouthScale) {
|
||||
return ValidationResult::InvalidMouthScale;
|
||||
}
|
||||
if (mouth_aspect > MaxMoutAspect) {
|
||||
return ValidationResult::InvalidMouthAspect;
|
||||
}
|
||||
if (mouth_y > MaxMouthY) {
|
||||
return ValidationResult::InvalidMoleY;
|
||||
}
|
||||
if (beard_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidBeardColor;
|
||||
}
|
||||
if (beard_type > BeardType::Max) {
|
||||
return ValidationResult::InvalidBeardType;
|
||||
}
|
||||
if (mustache_type > MustacheType::Max) {
|
||||
return ValidationResult::InvalidMustacheType;
|
||||
}
|
||||
if (mustache_scale > MaxMustacheScale) {
|
||||
return ValidationResult::InvalidMustacheScale;
|
||||
}
|
||||
if (mustache_y > MasMustacheY) {
|
||||
return ValidationResult::InvalidMustacheY;
|
||||
}
|
||||
if (glass_type > GlassType::Max) {
|
||||
return ValidationResult::InvalidGlassType;
|
||||
}
|
||||
if (glass_color > CommonColor::Max) {
|
||||
return ValidationResult::InvalidGlassColor;
|
||||
}
|
||||
if (glass_scale > MaxGlassScale) {
|
||||
return ValidationResult::InvalidGlassScale;
|
||||
}
|
||||
if (glass_y > MaxGlassY) {
|
||||
return ValidationResult::InvalidGlassY;
|
||||
}
|
||||
if (mole_type > MoleType::Max) {
|
||||
return ValidationResult::InvalidMoleType;
|
||||
}
|
||||
if (mole_scale > MaxMoleScale) {
|
||||
return ValidationResult::InvalidMoleScale;
|
||||
}
|
||||
if (mole_x > MaxMoleX) {
|
||||
return ValidationResult::InvalidMoleX;
|
||||
}
|
||||
if (mole_y > MaxMoleY) {
|
||||
return ValidationResult::InvalidMoleY;
|
||||
}
|
||||
return ValidationResult::NoErrors;
|
||||
}
|
||||
|
||||
Common::UUID CharInfo::GetCreateId() const {
|
||||
return create_id;
|
||||
}
|
||||
|
||||
Nickname CharInfo::GetNickname() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
FontRegion CharInfo::GetFontRegion() const {
|
||||
return font_region;
|
||||
}
|
||||
|
||||
FavoriteColor CharInfo::GetFavoriteColor() const {
|
||||
return favorite_color;
|
||||
}
|
||||
|
||||
Gender CharInfo::GetGender() const {
|
||||
return gender;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetBuild() const {
|
||||
return build;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetRegionMove() const {
|
||||
return region_move;
|
||||
}
|
||||
|
||||
FacelineType CharInfo::GetFacelineType() const {
|
||||
return faceline_type;
|
||||
}
|
||||
|
||||
FacelineColor CharInfo::GetFacelineColor() const {
|
||||
return faceline_color;
|
||||
}
|
||||
|
||||
FacelineWrinkle CharInfo::GetFacelineWrinkle() const {
|
||||
return faceline_wrinkle;
|
||||
}
|
||||
|
||||
FacelineMake CharInfo::GetFacelineMake() const {
|
||||
return faceline_make;
|
||||
}
|
||||
|
||||
HairType CharInfo::GetHairType() const {
|
||||
return hair_type;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetHairColor() const {
|
||||
return hair_color;
|
||||
}
|
||||
|
||||
HairFlip CharInfo::GetHairFlip() const {
|
||||
return hair_flip;
|
||||
}
|
||||
|
||||
EyeType CharInfo::GetEyeType() const {
|
||||
return eye_type;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetEyeColor() const {
|
||||
return eye_color;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyeScale() const {
|
||||
return eye_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyeAspect() const {
|
||||
return eye_aspect;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyeRotate() const {
|
||||
return eye_rotate;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyeX() const {
|
||||
return eye_x;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyeY() const {
|
||||
return eye_y;
|
||||
}
|
||||
|
||||
EyebrowType CharInfo::GetEyebrowType() const {
|
||||
return eyebrow_type;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetEyebrowColor() const {
|
||||
return eyebrow_color;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyebrowScale() const {
|
||||
return eyebrow_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyebrowAspect() const {
|
||||
return eyebrow_aspect;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyebrowRotate() const {
|
||||
return eyebrow_rotate;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyebrowX() const {
|
||||
return eyebrow_x;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetEyebrowY() const {
|
||||
return eyebrow_y;
|
||||
}
|
||||
|
||||
NoseType CharInfo::GetNoseType() const {
|
||||
return nose_type;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetNoseScale() const {
|
||||
return nose_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetNoseY() const {
|
||||
return nose_y;
|
||||
}
|
||||
|
||||
MouthType CharInfo::GetMouthType() const {
|
||||
return mouth_type;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetMouthColor() const {
|
||||
return mouth_color;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMouthScale() const {
|
||||
return mouth_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMouthAspect() const {
|
||||
return mouth_aspect;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMouthY() const {
|
||||
return mouth_y;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetBeardColor() const {
|
||||
return beard_color;
|
||||
}
|
||||
|
||||
BeardType CharInfo::GetBeardType() const {
|
||||
return beard_type;
|
||||
}
|
||||
|
||||
MustacheType CharInfo::GetMustacheType() const {
|
||||
return mustache_type;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMustacheScale() const {
|
||||
return mustache_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMustacheY() const {
|
||||
return mustache_y;
|
||||
}
|
||||
|
||||
GlassType CharInfo::GetGlassType() const {
|
||||
return glass_type;
|
||||
}
|
||||
|
||||
CommonColor CharInfo::GetGlassColor() const {
|
||||
return glass_color;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetGlassScale() const {
|
||||
return glass_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetGlassY() const {
|
||||
return glass_y;
|
||||
}
|
||||
|
||||
MoleType CharInfo::GetMoleType() const {
|
||||
return mole_type;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMoleScale() const {
|
||||
return mole_scale;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMoleX() const {
|
||||
return mole_x;
|
||||
}
|
||||
|
||||
u8 CharInfo::GetMoleY() const {
|
||||
return mole_y;
|
||||
}
|
||||
|
||||
bool CharInfo::operator==(const CharInfo& info) {
|
||||
bool is_identical = info.Verify() == ValidationResult::NoErrors;
|
||||
is_identical &= name.data == info.GetNickname().data;
|
||||
is_identical &= create_id == info.GetCreateId();
|
||||
is_identical &= font_region == info.GetFontRegion();
|
||||
is_identical &= favorite_color == info.GetFavoriteColor();
|
||||
is_identical &= gender == info.GetGender();
|
||||
is_identical &= height == info.GetHeight();
|
||||
is_identical &= build == info.GetBuild();
|
||||
is_identical &= type == info.GetType();
|
||||
is_identical &= region_move == info.GetRegionMove();
|
||||
is_identical &= faceline_type == info.GetFacelineType();
|
||||
is_identical &= faceline_color == info.GetFacelineColor();
|
||||
is_identical &= faceline_wrinkle == info.GetFacelineWrinkle();
|
||||
is_identical &= faceline_make == info.GetFacelineMake();
|
||||
is_identical &= hair_type == info.GetHairType();
|
||||
is_identical &= hair_color == info.GetHairColor();
|
||||
is_identical &= hair_flip == info.GetHairFlip();
|
||||
is_identical &= eye_type == info.GetEyeType();
|
||||
is_identical &= eye_color == info.GetEyeColor();
|
||||
is_identical &= eye_scale == info.GetEyeScale();
|
||||
is_identical &= eye_aspect == info.GetEyeAspect();
|
||||
is_identical &= eye_rotate == info.GetEyeRotate();
|
||||
is_identical &= eye_x == info.GetEyeX();
|
||||
is_identical &= eye_y == info.GetEyeY();
|
||||
is_identical &= eyebrow_type == info.GetEyebrowType();
|
||||
is_identical &= eyebrow_color == info.GetEyebrowColor();
|
||||
is_identical &= eyebrow_scale == info.GetEyebrowScale();
|
||||
is_identical &= eyebrow_aspect == info.GetEyebrowAspect();
|
||||
is_identical &= eyebrow_rotate == info.GetEyebrowRotate();
|
||||
is_identical &= eyebrow_x == info.GetEyebrowX();
|
||||
is_identical &= eyebrow_y == info.GetEyebrowY();
|
||||
is_identical &= nose_type == info.GetNoseType();
|
||||
is_identical &= nose_scale == info.GetNoseScale();
|
||||
is_identical &= nose_y == info.GetNoseY();
|
||||
is_identical &= mouth_type == info.GetMouthType();
|
||||
is_identical &= mouth_color == info.GetMouthColor();
|
||||
is_identical &= mouth_scale == info.GetMouthScale();
|
||||
is_identical &= mouth_aspect == info.GetMouthAspect();
|
||||
is_identical &= mouth_y == info.GetMouthY();
|
||||
is_identical &= beard_color == info.GetBeardColor();
|
||||
is_identical &= beard_type == info.GetBeardType();
|
||||
is_identical &= mustache_type == info.GetMustacheType();
|
||||
is_identical &= mustache_scale == info.GetMustacheScale();
|
||||
is_identical &= mustache_y == info.GetMustacheY();
|
||||
is_identical &= glass_type == info.GetGlassType();
|
||||
is_identical &= glass_color == info.GetGlassColor();
|
||||
is_identical &= glass_scale == info.GetGlassScale();
|
||||
is_identical &= glass_y == info.GetGlassY();
|
||||
is_identical &= mole_type == info.GetMoleType();
|
||||
is_identical &= mole_scale == info.GetMoleScale();
|
||||
is_identical &= mole_x == info.GetMoleX();
|
||||
is_identical &= mole_y == info.GetMoleY();
|
||||
return is_identical;
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
137
src/core/hle/service/mii/types/char_info.h
Normal file
137
src/core/hle/service/mii/types/char_info.h
Normal file
@ -0,0 +1,137 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
class StoreData;
|
||||
|
||||
// This is nn::mii::detail::CharInfoRaw
|
||||
class CharInfo {
|
||||
public:
|
||||
void SetFromStoreData(const StoreData& store_data_raw);
|
||||
|
||||
ValidationResult Verify() const;
|
||||
|
||||
Common::UUID GetCreateId() const;
|
||||
Nickname GetNickname() const;
|
||||
FontRegion GetFontRegion() const;
|
||||
FavoriteColor GetFavoriteColor() const;
|
||||
Gender GetGender() const;
|
||||
u8 GetHeight() const;
|
||||
u8 GetBuild() const;
|
||||
u8 GetType() const;
|
||||
u8 GetRegionMove() const;
|
||||
FacelineType GetFacelineType() const;
|
||||
FacelineColor GetFacelineColor() const;
|
||||
FacelineWrinkle GetFacelineWrinkle() const;
|
||||
FacelineMake GetFacelineMake() const;
|
||||
HairType GetHairType() const;
|
||||
CommonColor GetHairColor() const;
|
||||
HairFlip GetHairFlip() const;
|
||||
EyeType GetEyeType() const;
|
||||
CommonColor GetEyeColor() const;
|
||||
u8 GetEyeScale() const;
|
||||
u8 GetEyeAspect() const;
|
||||
u8 GetEyeRotate() const;
|
||||
u8 GetEyeX() const;
|
||||
u8 GetEyeY() const;
|
||||
EyebrowType GetEyebrowType() const;
|
||||
CommonColor GetEyebrowColor() const;
|
||||
u8 GetEyebrowScale() const;
|
||||
u8 GetEyebrowAspect() const;
|
||||
u8 GetEyebrowRotate() const;
|
||||
u8 GetEyebrowX() const;
|
||||
u8 GetEyebrowY() const;
|
||||
NoseType GetNoseType() const;
|
||||
u8 GetNoseScale() const;
|
||||
u8 GetNoseY() const;
|
||||
MouthType GetMouthType() const;
|
||||
CommonColor GetMouthColor() const;
|
||||
u8 GetMouthScale() const;
|
||||
u8 GetMouthAspect() const;
|
||||
u8 GetMouthY() const;
|
||||
CommonColor GetBeardColor() const;
|
||||
BeardType GetBeardType() const;
|
||||
MustacheType GetMustacheType() const;
|
||||
u8 GetMustacheScale() const;
|
||||
u8 GetMustacheY() const;
|
||||
GlassType GetGlassType() const;
|
||||
CommonColor GetGlassColor() const;
|
||||
u8 GetGlassScale() const;
|
||||
u8 GetGlassY() const;
|
||||
MoleType GetMoleType() const;
|
||||
u8 GetMoleScale() const;
|
||||
u8 GetMoleX() const;
|
||||
u8 GetMoleY() const;
|
||||
|
||||
bool operator==(const CharInfo& info);
|
||||
|
||||
private:
|
||||
Common::UUID create_id;
|
||||
Nickname name;
|
||||
u16 null_terminator;
|
||||
FontRegion font_region;
|
||||
FavoriteColor favorite_color;
|
||||
Gender gender;
|
||||
u8 height;
|
||||
u8 build;
|
||||
u8 type;
|
||||
u8 region_move;
|
||||
FacelineType faceline_type;
|
||||
FacelineColor faceline_color;
|
||||
FacelineWrinkle faceline_wrinkle;
|
||||
FacelineMake faceline_make;
|
||||
HairType hair_type;
|
||||
CommonColor hair_color;
|
||||
HairFlip hair_flip;
|
||||
EyeType eye_type;
|
||||
CommonColor eye_color;
|
||||
u8 eye_scale;
|
||||
u8 eye_aspect;
|
||||
u8 eye_rotate;
|
||||
u8 eye_x;
|
||||
u8 eye_y;
|
||||
EyebrowType eyebrow_type;
|
||||
CommonColor eyebrow_color;
|
||||
u8 eyebrow_scale;
|
||||
u8 eyebrow_aspect;
|
||||
u8 eyebrow_rotate;
|
||||
u8 eyebrow_x;
|
||||
u8 eyebrow_y;
|
||||
NoseType nose_type;
|
||||
u8 nose_scale;
|
||||
u8 nose_y;
|
||||
MouthType mouth_type;
|
||||
CommonColor mouth_color;
|
||||
u8 mouth_scale;
|
||||
u8 mouth_aspect;
|
||||
u8 mouth_y;
|
||||
CommonColor beard_color;
|
||||
BeardType beard_type;
|
||||
MustacheType mustache_type;
|
||||
u8 mustache_scale;
|
||||
u8 mustache_y;
|
||||
GlassType glass_type;
|
||||
CommonColor glass_color;
|
||||
u8 glass_scale;
|
||||
u8 glass_y;
|
||||
MoleType mole_type;
|
||||
u8 mole_scale;
|
||||
u8 mole_x;
|
||||
u8 mole_y;
|
||||
u8 padding;
|
||||
};
|
||||
static_assert(sizeof(CharInfo) == 0x58, "CharInfo has incorrect size.");
|
||||
static_assert(std::has_unique_object_representations_v<CharInfo>,
|
||||
"All bits of CharInfo must contribute to its value.");
|
||||
|
||||
struct CharInfoElement {
|
||||
CharInfo char_info{};
|
||||
Source source{};
|
||||
};
|
||||
static_assert(sizeof(CharInfoElement) == 0x5c, "CharInfoElement has incorrect size.");
|
||||
|
||||
}; // namespace Service::Mii
|
601
src/core/hle/service/mii/types/core_data.cpp
Normal file
601
src/core/hle/service/mii/types/core_data.cpp
Normal file
@ -0,0 +1,601 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/service/mii/mii_util.h"
|
||||
#include "core/hle/service/mii/types/core_data.h"
|
||||
#include "core/hle/service/mii/types/raw_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
void CoreData::SetDefault() {
|
||||
data = {};
|
||||
name = GetDefaultNickname();
|
||||
}
|
||||
|
||||
void CoreData::BuildRandom(Age age, Gender gender, Race race) {
|
||||
if (gender == Gender::All) {
|
||||
gender = MiiUtil::GetRandomValue(Gender::Max);
|
||||
}
|
||||
|
||||
if (age == Age::All) {
|
||||
const auto random{MiiUtil::GetRandomValue<int>(10)};
|
||||
if (random >= 8) {
|
||||
age = Age::Old;
|
||||
} else if (random >= 4) {
|
||||
age = Age::Normal;
|
||||
} else {
|
||||
age = Age::Young;
|
||||
}
|
||||
}
|
||||
|
||||
if (race == Race::All) {
|
||||
const auto random{MiiUtil::GetRandomValue<int>(10)};
|
||||
if (random >= 8) {
|
||||
race = Race::Black;
|
||||
} else if (random >= 4) {
|
||||
race = Race::White;
|
||||
} else {
|
||||
race = Race::Asian;
|
||||
}
|
||||
}
|
||||
|
||||
SetGender(gender);
|
||||
SetFavoriteColor(MiiUtil::GetRandomValue(FavoriteColor::Max));
|
||||
SetRegionMove(0);
|
||||
SetFontRegion(FontRegion::Standard);
|
||||
SetType(0);
|
||||
SetHeight(64);
|
||||
SetBuild(64);
|
||||
|
||||
u32 axis_y{};
|
||||
if (gender == Gender::Female && age == Age::Young) {
|
||||
axis_y = MiiUtil::GetRandomValue<u32>(3);
|
||||
}
|
||||
|
||||
const std::size_t index{3 * static_cast<std::size_t>(age) +
|
||||
9 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race)};
|
||||
|
||||
const auto& faceline_type_info{RawData::RandomMiiFaceline.at(index)};
|
||||
const auto& faceline_color_info{RawData::RandomMiiFacelineColor.at(
|
||||
3 * static_cast<std::size_t>(gender) + static_cast<std::size_t>(race))};
|
||||
const auto& faceline_wrinkle_info{RawData::RandomMiiFacelineWrinkle.at(index)};
|
||||
const auto& faceline_makeup_info{RawData::RandomMiiFacelineMakeup.at(index)};
|
||||
const auto& hair_type_info{RawData::RandomMiiHairType.at(index)};
|
||||
const auto& hair_color_info{RawData::RandomMiiHairColor.at(3 * static_cast<std::size_t>(race) +
|
||||
static_cast<std::size_t>(age))};
|
||||
const auto& eye_type_info{RawData::RandomMiiEyeType.at(index)};
|
||||
const auto& eye_color_info{RawData::RandomMiiEyeColor.at(static_cast<std::size_t>(race))};
|
||||
const auto& eyebrow_type_info{RawData::RandomMiiEyebrowType.at(index)};
|
||||
const auto& nose_type_info{RawData::RandomMiiNoseType.at(index)};
|
||||
const auto& mouth_type_info{RawData::RandomMiiMouthType.at(index)};
|
||||
const auto& glasses_type_info{RawData::RandomMiiGlassType.at(static_cast<std::size_t>(age))};
|
||||
|
||||
data.faceline_type.Assign(
|
||||
faceline_type_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_type_info.values_count)]);
|
||||
data.faceline_color.Assign(
|
||||
faceline_color_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_color_info.values_count)]);
|
||||
data.faceline_wrinkle.Assign(
|
||||
faceline_wrinkle_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_wrinkle_info.values_count)]);
|
||||
data.faceline_makeup.Assign(
|
||||
faceline_makeup_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(faceline_makeup_info.values_count)]);
|
||||
|
||||
data.hair_type.Assign(
|
||||
hair_type_info.values[MiiUtil::GetRandomValue<std::size_t>(hair_type_info.values_count)]);
|
||||
SetHairColor(RawData::GetHairColorFromVer3(
|
||||
hair_color_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(hair_color_info.values_count)]));
|
||||
SetHairFlip(MiiUtil::GetRandomValue(HairFlip::Max));
|
||||
|
||||
data.eye_type.Assign(
|
||||
eye_type_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_type_info.values_count)]);
|
||||
|
||||
const auto eye_rotate_1{gender != Gender::Male ? 4 : 2};
|
||||
const auto eye_rotate_2{gender != Gender::Male ? 3 : 4};
|
||||
const auto eye_rotate_offset{32 - RawData::EyeRotateLookup[eye_rotate_1] + eye_rotate_2};
|
||||
const auto eye_rotate{32 - RawData::EyeRotateLookup[data.eye_type]};
|
||||
|
||||
SetEyeColor(RawData::GetEyeColorFromVer3(
|
||||
eye_color_info.values[MiiUtil::GetRandomValue<std::size_t>(eye_color_info.values_count)]));
|
||||
SetEyeScale(4);
|
||||
SetEyeAspect(3);
|
||||
SetEyeRotate(static_cast<u8>(eye_rotate_offset - eye_rotate));
|
||||
SetEyeX(2);
|
||||
SetEyeY(static_cast<u8>(axis_y + 12));
|
||||
|
||||
data.eyebrow_type.Assign(
|
||||
eyebrow_type_info
|
||||
.values[MiiUtil::GetRandomValue<std::size_t>(eyebrow_type_info.values_count)]);
|
||||
|
||||
const auto eyebrow_rotate_1{race == Race::Asian ? 6 : 0};
|
||||
const auto eyebrow_y{race == Race::Asian ? 9 : 10};
|
||||
const auto eyebrow_rotate_offset{32 - RawData::EyebrowRotateLookup[eyebrow_rotate_1] + 6};
|
||||
const auto eyebrow_rotate{
|
||||
32 - RawData::EyebrowRotateLookup[static_cast<std::size_t>(data.eyebrow_type.Value())]};
|
||||
|
||||
SetEyebrowColor(GetHairColor());
|
||||
SetEyebrowScale(4);
|
||||
SetEyebrowAspect(3);
|
||||
SetEyebrowRotate(static_cast<u8>(eyebrow_rotate_offset - eyebrow_rotate));
|
||||
SetEyebrowX(2);
|
||||
SetEyebrowY(static_cast<u8>(axis_y + eyebrow_y));
|
||||
|
||||
data.nose_type.Assign(
|
||||
nose_type_info.values[MiiUtil::GetRandomValue<std::size_t>(nose_type_info.values_count)]);
|
||||
SetNoseScale(gender == Gender::Female ? 3 : 4);
|
||||
SetNoseY(static_cast<u8>(axis_y + 9));
|
||||
|
||||
const auto mouth_color{gender == Gender::Female ? MiiUtil::GetRandomValue<int>(4) : 0};
|
||||
|
||||
data.mouth_type.Assign(
|
||||
mouth_type_info.values[MiiUtil::GetRandomValue<std::size_t>(mouth_type_info.values_count)]);
|
||||
SetMouthColor(RawData::GetMouthColorFromVer3(mouth_color));
|
||||
SetMouthScale(4);
|
||||
SetMouthAspect(3);
|
||||
SetMouthY(static_cast<u8>(axis_y + 13));
|
||||
|
||||
SetBeardColor(GetHairColor());
|
||||
SetMustacheScale(4);
|
||||
|
||||
if (gender == Gender::Male && age != Age::Young && MiiUtil::GetRandomValue<int>(10) < 2) {
|
||||
const auto mustache_and_beard_flag{MiiUtil::GetRandomValue(BeardAndMustacheFlag::All)};
|
||||
|
||||
auto beard_type{BeardType::None};
|
||||
auto mustache_type{MustacheType::None};
|
||||
|
||||
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Beard) ==
|
||||
BeardAndMustacheFlag::Beard) {
|
||||
beard_type = MiiUtil::GetRandomValue(BeardType::Min, BeardType::Max);
|
||||
}
|
||||
|
||||
if ((mustache_and_beard_flag & BeardAndMustacheFlag::Mustache) ==
|
||||
BeardAndMustacheFlag::Mustache) {
|
||||
mustache_type = MiiUtil::GetRandomValue(MustacheType::Min, MustacheType::Max);
|
||||
}
|
||||
|
||||
SetMustacheType(mustache_type);
|
||||
SetBeardType(beard_type);
|
||||
SetMustacheY(10);
|
||||
} else {
|
||||
SetMustacheType(MustacheType::None);
|
||||
SetBeardType(BeardType::None);
|
||||
SetMustacheY(static_cast<u8>(axis_y + 10));
|
||||
}
|
||||
|
||||
const auto glasses_type_start{MiiUtil::GetRandomValue<std::size_t>(100)};
|
||||
u8 glasses_type{};
|
||||
while (glasses_type_start < glasses_type_info.values[glasses_type]) {
|
||||
if (++glasses_type >= glasses_type_info.values_count) {
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SetGlassType(static_cast<GlassType>(glasses_type));
|
||||
SetGlassColor(RawData::GetGlassColorFromVer3(0));
|
||||
SetGlassScale(4);
|
||||
|
||||
SetMoleType(MoleType::None);
|
||||
SetMoleScale(4);
|
||||
SetMoleX(2);
|
||||
SetMoleY(20);
|
||||
}
|
||||
|
||||
u32 CoreData::IsValid() const {
|
||||
// TODO: Complete this
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CoreData::SetFontRegion(FontRegion value) {
|
||||
data.font_region.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetFavoriteColor(FavoriteColor value) {
|
||||
data.favorite_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetGender(Gender value) {
|
||||
data.gender.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetHeight(u8 value) {
|
||||
data.height.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetBuild(u8 value) {
|
||||
data.build.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetType(u8 value) {
|
||||
data.type.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetRegionMove(u8 value) {
|
||||
data.region_move.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetFacelineType(FacelineType value) {
|
||||
data.faceline_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetFacelineColor(FacelineColor value) {
|
||||
data.faceline_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetFacelineWrinkle(FacelineWrinkle value) {
|
||||
data.faceline_wrinkle.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetFacelineMake(FacelineMake value) {
|
||||
data.faceline_makeup.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetHairType(HairType value) {
|
||||
data.hair_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetHairColor(CommonColor value) {
|
||||
data.hair_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetHairFlip(HairFlip value) {
|
||||
data.hair_flip.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetEyeType(EyeType value) {
|
||||
data.eye_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetEyeColor(CommonColor value) {
|
||||
data.eye_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetEyeScale(u8 value) {
|
||||
data.eye_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyeAspect(u8 value) {
|
||||
data.eye_aspect.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyeRotate(u8 value) {
|
||||
data.eye_rotate.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyeX(u8 value) {
|
||||
data.eye_x.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyeY(u8 value) {
|
||||
data.eye_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowType(EyebrowType value) {
|
||||
data.eyebrow_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowColor(CommonColor value) {
|
||||
data.eyebrow_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowScale(u8 value) {
|
||||
data.eyebrow_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowAspect(u8 value) {
|
||||
data.eyebrow_aspect.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowRotate(u8 value) {
|
||||
data.eyebrow_rotate.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowX(u8 value) {
|
||||
data.eyebrow_x.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetEyebrowY(u8 value) {
|
||||
data.eyebrow_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetNoseType(NoseType value) {
|
||||
data.nose_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetNoseScale(u8 value) {
|
||||
data.nose_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetNoseY(u8 value) {
|
||||
data.nose_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMouthType(u8 value) {
|
||||
data.mouth_type.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMouthColor(CommonColor value) {
|
||||
data.mouth_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetMouthScale(u8 value) {
|
||||
data.mouth_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMouthAspect(u8 value) {
|
||||
data.mouth_aspect.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMouthY(u8 value) {
|
||||
data.mouth_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetBeardColor(CommonColor value) {
|
||||
data.beard_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetBeardType(BeardType value) {
|
||||
data.beard_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetMustacheType(MustacheType value) {
|
||||
data.mustache_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetMustacheScale(u8 value) {
|
||||
data.mustache_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMustacheY(u8 value) {
|
||||
data.mustache_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetGlassType(GlassType value) {
|
||||
data.glasses_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetGlassColor(CommonColor value) {
|
||||
data.glasses_color.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetGlassScale(u8 value) {
|
||||
data.glasses_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetGlassY(u8 value) {
|
||||
data.glasses_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMoleType(MoleType value) {
|
||||
data.mole_type.Assign(static_cast<u32>(value));
|
||||
}
|
||||
|
||||
void CoreData::SetMoleScale(u8 value) {
|
||||
data.mole_scale.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMoleX(u8 value) {
|
||||
data.mole_x.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetMoleY(u8 value) {
|
||||
data.mole_y.Assign(value);
|
||||
}
|
||||
|
||||
void CoreData::SetNickname(Nickname nickname) {
|
||||
name = nickname;
|
||||
}
|
||||
|
||||
FontRegion CoreData::GetFontRegion() const {
|
||||
return static_cast<FontRegion>(data.font_region.Value());
|
||||
}
|
||||
|
||||
FavoriteColor CoreData::GetFavoriteColor() const {
|
||||
return static_cast<FavoriteColor>(data.favorite_color.Value());
|
||||
}
|
||||
|
||||
Gender CoreData::GetGender() const {
|
||||
return static_cast<Gender>(data.gender.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetHeight() const {
|
||||
return static_cast<u8>(data.height.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetBuild() const {
|
||||
return static_cast<u8>(data.build.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetType() const {
|
||||
return static_cast<u8>(data.type.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetRegionMove() const {
|
||||
return static_cast<u8>(data.region_move.Value());
|
||||
}
|
||||
|
||||
FacelineType CoreData::GetFacelineType() const {
|
||||
return static_cast<FacelineType>(data.faceline_type.Value());
|
||||
}
|
||||
|
||||
FacelineColor CoreData::GetFacelineColor() const {
|
||||
return static_cast<FacelineColor>(data.faceline_color.Value());
|
||||
}
|
||||
|
||||
FacelineWrinkle CoreData::GetFacelineWrinkle() const {
|
||||
return static_cast<FacelineWrinkle>(data.faceline_wrinkle.Value());
|
||||
}
|
||||
|
||||
FacelineMake CoreData::GetFacelineMake() const {
|
||||
return static_cast<FacelineMake>(data.faceline_makeup.Value());
|
||||
}
|
||||
|
||||
HairType CoreData::GetHairType() const {
|
||||
return static_cast<HairType>(data.hair_type.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetHairColor() const {
|
||||
return static_cast<CommonColor>(data.hair_color.Value());
|
||||
}
|
||||
|
||||
HairFlip CoreData::GetHairFlip() const {
|
||||
return static_cast<HairFlip>(data.hair_flip.Value());
|
||||
}
|
||||
|
||||
EyeType CoreData::GetEyeType() const {
|
||||
return static_cast<EyeType>(data.eye_type.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetEyeColor() const {
|
||||
return static_cast<CommonColor>(data.eye_color.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyeScale() const {
|
||||
return static_cast<u8>(data.eye_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyeAspect() const {
|
||||
return static_cast<u8>(data.eye_aspect.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyeRotate() const {
|
||||
return static_cast<u8>(data.eye_rotate.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyeX() const {
|
||||
return static_cast<u8>(data.eye_x.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyeY() const {
|
||||
return static_cast<u8>(data.eye_y.Value());
|
||||
}
|
||||
|
||||
EyebrowType CoreData::GetEyebrowType() const {
|
||||
return static_cast<EyebrowType>(data.eyebrow_type.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetEyebrowColor() const {
|
||||
return static_cast<CommonColor>(data.eyebrow_color.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyebrowScale() const {
|
||||
return static_cast<u8>(data.eyebrow_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyebrowAspect() const {
|
||||
return static_cast<u8>(data.eyebrow_aspect.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyebrowRotate() const {
|
||||
return static_cast<u8>(data.eyebrow_rotate.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyebrowX() const {
|
||||
return static_cast<u8>(data.eyebrow_x.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetEyebrowY() const {
|
||||
return static_cast<u8>(data.eyebrow_y.Value());
|
||||
}
|
||||
|
||||
NoseType CoreData::GetNoseType() const {
|
||||
return static_cast<NoseType>(data.nose_type.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetNoseScale() const {
|
||||
return static_cast<u8>(data.nose_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetNoseY() const {
|
||||
return static_cast<u8>(data.nose_y.Value());
|
||||
}
|
||||
|
||||
MouthType CoreData::GetMouthType() const {
|
||||
return static_cast<MouthType>(data.mouth_type.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetMouthColor() const {
|
||||
return static_cast<CommonColor>(data.mouth_color.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMouthScale() const {
|
||||
return static_cast<u8>(data.mouth_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMouthAspect() const {
|
||||
return static_cast<u8>(data.mouth_aspect.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMouthY() const {
|
||||
return static_cast<u8>(data.mouth_y.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetBeardColor() const {
|
||||
return static_cast<CommonColor>(data.beard_color.Value());
|
||||
}
|
||||
|
||||
BeardType CoreData::GetBeardType() const {
|
||||
return static_cast<BeardType>(data.beard_type.Value());
|
||||
}
|
||||
|
||||
MustacheType CoreData::GetMustacheType() const {
|
||||
return static_cast<MustacheType>(data.mustache_type.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMustacheScale() const {
|
||||
return static_cast<u8>(data.mustache_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMustacheY() const {
|
||||
return static_cast<u8>(data.mustache_y.Value());
|
||||
}
|
||||
|
||||
GlassType CoreData::GetGlassType() const {
|
||||
return static_cast<GlassType>(data.glasses_type.Value());
|
||||
}
|
||||
|
||||
CommonColor CoreData::GetGlassColor() const {
|
||||
return static_cast<CommonColor>(data.glasses_color.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetGlassScale() const {
|
||||
return static_cast<u8>(data.glasses_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetGlassY() const {
|
||||
return static_cast<u8>(data.glasses_y.Value());
|
||||
}
|
||||
|
||||
MoleType CoreData::GetMoleType() const {
|
||||
return static_cast<MoleType>(data.mole_type.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMoleScale() const {
|
||||
return static_cast<u8>(data.mole_scale.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMoleX() const {
|
||||
return static_cast<u8>(data.mole_x.Value());
|
||||
}
|
||||
|
||||
u8 CoreData::GetMoleY() const {
|
||||
return static_cast<u8>(data.mole_y.Value());
|
||||
}
|
||||
|
||||
Nickname CoreData::GetNickname() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
Nickname CoreData::GetDefaultNickname() const {
|
||||
return {u'n', u'o', u' ', u'n', u'a', u'm', u'e'};
|
||||
}
|
||||
|
||||
Nickname CoreData::GetInvalidNickname() const {
|
||||
return {u'?', u'?', u'?'};
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
216
src/core/hle/service/mii/types/core_data.h
Normal file
216
src/core/hle/service/mii/types/core_data.h
Normal file
@ -0,0 +1,216 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
struct StoreDataBitFields {
|
||||
union {
|
||||
u32 word_0{};
|
||||
|
||||
BitField<0, 8, u32> hair_type;
|
||||
BitField<8, 7, u32> height;
|
||||
BitField<15, 1, u32> mole_type;
|
||||
BitField<16, 7, u32> build;
|
||||
BitField<23, 1, u32> hair_flip;
|
||||
BitField<24, 7, u32> hair_color;
|
||||
BitField<31, 1, u32> type;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_1{};
|
||||
|
||||
BitField<0, 7, u32> eye_color;
|
||||
BitField<7, 1, u32> gender;
|
||||
BitField<8, 7, u32> eyebrow_color;
|
||||
BitField<16, 7, u32> mouth_color;
|
||||
BitField<24, 7, u32> beard_color;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_2{};
|
||||
|
||||
BitField<0, 7, u32> glasses_color;
|
||||
BitField<8, 6, u32> eye_type;
|
||||
BitField<14, 2, u32> region_move;
|
||||
BitField<16, 6, u32> mouth_type;
|
||||
BitField<22, 2, u32> font_region;
|
||||
BitField<24, 5, u32> eye_y;
|
||||
BitField<29, 3, u32> glasses_scale;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_3{};
|
||||
|
||||
BitField<0, 5, u32> eyebrow_type;
|
||||
BitField<5, 3, u32> mustache_type;
|
||||
BitField<8, 5, u32> nose_type;
|
||||
BitField<13, 3, u32> beard_type;
|
||||
BitField<16, 5, u32> nose_y;
|
||||
BitField<21, 3, u32> mouth_aspect;
|
||||
BitField<24, 5, u32> mouth_y;
|
||||
BitField<29, 3, u32> eyebrow_aspect;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_4{};
|
||||
|
||||
BitField<0, 5, u32> mustache_y;
|
||||
BitField<5, 3, u32> eye_rotate;
|
||||
BitField<8, 5, u32> glasses_y;
|
||||
BitField<13, 3, u32> eye_aspect;
|
||||
BitField<16, 5, u32> mole_x;
|
||||
BitField<21, 3, u32> eye_scale;
|
||||
BitField<24, 5, u32> mole_y;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_5{};
|
||||
|
||||
BitField<0, 5, u32> glasses_type;
|
||||
BitField<8, 4, u32> favorite_color;
|
||||
BitField<12, 4, u32> faceline_type;
|
||||
BitField<16, 4, u32> faceline_color;
|
||||
BitField<20, 4, u32> faceline_wrinkle;
|
||||
BitField<24, 4, u32> faceline_makeup;
|
||||
BitField<28, 4, u32> eye_x;
|
||||
};
|
||||
|
||||
union {
|
||||
u32 word_6{};
|
||||
|
||||
BitField<0, 4, u32> eyebrow_scale;
|
||||
BitField<4, 4, u32> eyebrow_rotate;
|
||||
BitField<8, 4, u32> eyebrow_x;
|
||||
BitField<12, 4, u32> eyebrow_y;
|
||||
BitField<16, 4, u32> nose_scale;
|
||||
BitField<20, 4, u32> mouth_scale;
|
||||
BitField<24, 4, u32> mustache_scale;
|
||||
BitField<28, 4, u32> mole_scale;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(StoreDataBitFields) == 0x1c, "StoreDataBitFields has incorrect size.");
|
||||
static_assert(std::is_trivially_copyable_v<StoreDataBitFields>,
|
||||
"StoreDataBitFields is not trivially copyable.");
|
||||
|
||||
class CoreData {
|
||||
public:
|
||||
void SetDefault();
|
||||
void BuildRandom(Age age, Gender gender, Race race);
|
||||
|
||||
u32 IsValid() const;
|
||||
|
||||
void SetFontRegion(FontRegion value);
|
||||
void SetFavoriteColor(FavoriteColor value);
|
||||
void SetGender(Gender value);
|
||||
void SetHeight(u8 value);
|
||||
void SetBuild(u8 value);
|
||||
void SetType(u8 value);
|
||||
void SetRegionMove(u8 value);
|
||||
void SetFacelineType(FacelineType value);
|
||||
void SetFacelineColor(FacelineColor value);
|
||||
void SetFacelineWrinkle(FacelineWrinkle value);
|
||||
void SetFacelineMake(FacelineMake value);
|
||||
void SetHairType(HairType value);
|
||||
void SetHairColor(CommonColor value);
|
||||
void SetHairFlip(HairFlip value);
|
||||
void SetEyeType(EyeType value);
|
||||
void SetEyeColor(CommonColor value);
|
||||
void SetEyeScale(u8 value);
|
||||
void SetEyeAspect(u8 value);
|
||||
void SetEyeRotate(u8 value);
|
||||
void SetEyeX(u8 value);
|
||||
void SetEyeY(u8 value);
|
||||
void SetEyebrowType(EyebrowType value);
|
||||
void SetEyebrowColor(CommonColor value);
|
||||
void SetEyebrowScale(u8 value);
|
||||
void SetEyebrowAspect(u8 value);
|
||||
void SetEyebrowRotate(u8 value);
|
||||
void SetEyebrowX(u8 value);
|
||||
void SetEyebrowY(u8 value);
|
||||
void SetNoseType(NoseType value);
|
||||
void SetNoseScale(u8 value);
|
||||
void SetNoseY(u8 value);
|
||||
void SetMouthType(u8 value);
|
||||
void SetMouthColor(CommonColor value);
|
||||
void SetMouthScale(u8 value);
|
||||
void SetMouthAspect(u8 value);
|
||||
void SetMouthY(u8 value);
|
||||
void SetBeardColor(CommonColor value);
|
||||
void SetBeardType(BeardType value);
|
||||
void SetMustacheType(MustacheType value);
|
||||
void SetMustacheScale(u8 value);
|
||||
void SetMustacheY(u8 value);
|
||||
void SetGlassType(GlassType value);
|
||||
void SetGlassColor(CommonColor value);
|
||||
void SetGlassScale(u8 value);
|
||||
void SetGlassY(u8 value);
|
||||
void SetMoleType(MoleType value);
|
||||
void SetMoleScale(u8 value);
|
||||
void SetMoleX(u8 value);
|
||||
void SetMoleY(u8 value);
|
||||
void SetNickname(Nickname nickname);
|
||||
|
||||
FontRegion GetFontRegion() const;
|
||||
FavoriteColor GetFavoriteColor() const;
|
||||
Gender GetGender() const;
|
||||
u8 GetHeight() const;
|
||||
u8 GetBuild() const;
|
||||
u8 GetType() const;
|
||||
u8 GetRegionMove() const;
|
||||
FacelineType GetFacelineType() const;
|
||||
FacelineColor GetFacelineColor() const;
|
||||
FacelineWrinkle GetFacelineWrinkle() const;
|
||||
FacelineMake GetFacelineMake() const;
|
||||
HairType GetHairType() const;
|
||||
CommonColor GetHairColor() const;
|
||||
HairFlip GetHairFlip() const;
|
||||
EyeType GetEyeType() const;
|
||||
CommonColor GetEyeColor() const;
|
||||
u8 GetEyeScale() const;
|
||||
u8 GetEyeAspect() const;
|
||||
u8 GetEyeRotate() const;
|
||||
u8 GetEyeX() const;
|
||||
u8 GetEyeY() const;
|
||||
EyebrowType GetEyebrowType() const;
|
||||
CommonColor GetEyebrowColor() const;
|
||||
u8 GetEyebrowScale() const;
|
||||
u8 GetEyebrowAspect() const;
|
||||
u8 GetEyebrowRotate() const;
|
||||
u8 GetEyebrowX() const;
|
||||
u8 GetEyebrowY() const;
|
||||
NoseType GetNoseType() const;
|
||||
u8 GetNoseScale() const;
|
||||
u8 GetNoseY() const;
|
||||
MouthType GetMouthType() const;
|
||||
CommonColor GetMouthColor() const;
|
||||
u8 GetMouthScale() const;
|
||||
u8 GetMouthAspect() const;
|
||||
u8 GetMouthY() const;
|
||||
CommonColor GetBeardColor() const;
|
||||
BeardType GetBeardType() const;
|
||||
MustacheType GetMustacheType() const;
|
||||
u8 GetMustacheScale() const;
|
||||
u8 GetMustacheY() const;
|
||||
GlassType GetGlassType() const;
|
||||
CommonColor GetGlassColor() const;
|
||||
u8 GetGlassScale() const;
|
||||
u8 GetGlassY() const;
|
||||
MoleType GetMoleType() const;
|
||||
u8 GetMoleScale() const;
|
||||
u8 GetMoleX() const;
|
||||
u8 GetMoleY() const;
|
||||
Nickname GetNickname() const;
|
||||
Nickname GetDefaultNickname() const;
|
||||
Nickname GetInvalidNickname() const;
|
||||
|
||||
private:
|
||||
StoreDataBitFields data{};
|
||||
Nickname name{};
|
||||
};
|
||||
static_assert(sizeof(CoreData) == 0x30, "CoreData has incorrect size.");
|
||||
|
||||
}; // namespace Service::Mii
|
File diff suppressed because it is too large
Load Diff
73
src/core/hle/service/mii/types/raw_data.h
Normal file
73
src/core/hle/service/mii/types/raw_data.h
Normal file
@ -0,0 +1,73 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
|
||||
namespace Service::Mii::RawData {
|
||||
|
||||
struct RandomMiiValues {
|
||||
std::array<u8, 188> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiValues) == 0xbc, "RandomMiiValues has incorrect size.");
|
||||
|
||||
struct RandomMiiData4 {
|
||||
u32 gender{};
|
||||
u32 age{};
|
||||
u32 race{};
|
||||
u32 values_count{};
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData4) == 0xcc, "RandomMiiData4 has incorrect size.");
|
||||
|
||||
struct RandomMiiData3 {
|
||||
u32 arg_1;
|
||||
u32 arg_2;
|
||||
u32 values_count;
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData3) == 0xc8, "RandomMiiData3 has incorrect size.");
|
||||
|
||||
struct RandomMiiData2 {
|
||||
u32 arg_1;
|
||||
u32 values_count;
|
||||
std::array<u32, 47> values{};
|
||||
};
|
||||
static_assert(sizeof(RandomMiiData2) == 0xc4, "RandomMiiData2 has incorrect size.");
|
||||
|
||||
extern const std::array<Service::Mii::DefaultMii, 2> BaseMii;
|
||||
extern const std::array<Service::Mii::DefaultMii, 6> DefaultMii;
|
||||
|
||||
extern const std::array<u8, 62> EyeRotateLookup;
|
||||
extern const std::array<u8, 24> EyebrowRotateLookup;
|
||||
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiFaceline;
|
||||
extern const std::array<RandomMiiData3, 6> RandomMiiFacelineColor;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiFacelineWrinkle;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiFacelineMakeup;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiHairType;
|
||||
extern const std::array<RandomMiiData3, 9> RandomMiiHairColor;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiEyeType;
|
||||
extern const std::array<RandomMiiData2, 3> RandomMiiEyeColor;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiEyebrowType;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiNoseType;
|
||||
extern const std::array<RandomMiiData4, 18> RandomMiiMouthType;
|
||||
extern const std::array<RandomMiiData2, 3> RandomMiiGlassType;
|
||||
|
||||
u8 FromVer3GetFacelineColor(u8 color);
|
||||
u8 FromVer3GetHairColor(u8 color);
|
||||
u8 FromVer3GetEyeColor(u8 color);
|
||||
u8 FromVer3GetMouthlineColor(u8 color);
|
||||
u8 FromVer3GetGlassColor(u8 color);
|
||||
u8 FromVer3GetGlassType(u8 type);
|
||||
|
||||
FacelineColor GetFacelineColorFromVer3(u32 color);
|
||||
CommonColor GetHairColorFromVer3(u32 color);
|
||||
CommonColor GetEyeColorFromVer3(u32 color);
|
||||
CommonColor GetMouthColorFromVer3(u32 color);
|
||||
CommonColor GetGlassColorFromVer3(u32 color);
|
||||
|
||||
} // namespace Service::Mii::RawData
|
643
src/core/hle/service/mii/types/store_data.cpp
Normal file
643
src/core/hle/service/mii/types/store_data.cpp
Normal file
@ -0,0 +1,643 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/mii/mii_util.h"
|
||||
#include "core/hle/service/mii/types/raw_data.h"
|
||||
#include "core/hle/service/mii/types/store_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
void StoreData::BuildDefault(u32 mii_index) {
|
||||
const auto& default_mii = RawData::DefaultMii[mii_index];
|
||||
core_data.SetDefault();
|
||||
|
||||
core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
|
||||
core_data.SetFacelineColor(
|
||||
RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
|
||||
core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
|
||||
core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
|
||||
|
||||
core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
|
||||
core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
|
||||
core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
|
||||
core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
|
||||
core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
|
||||
core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
|
||||
core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
|
||||
core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
|
||||
core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
|
||||
core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
|
||||
|
||||
core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
|
||||
core_data.SetEyebrowColor(
|
||||
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
|
||||
core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
|
||||
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
|
||||
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
|
||||
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
|
||||
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
|
||||
|
||||
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
|
||||
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
|
||||
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
|
||||
|
||||
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
|
||||
core_data.SetMouthColor(
|
||||
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
|
||||
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
|
||||
core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
|
||||
core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
|
||||
|
||||
core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
|
||||
core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
|
||||
core_data.SetBeardColor(
|
||||
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
|
||||
core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
|
||||
core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
|
||||
|
||||
core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
|
||||
core_data.SetGlassColor(
|
||||
RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
|
||||
core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
|
||||
core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
|
||||
|
||||
core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
|
||||
core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
|
||||
core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
|
||||
core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
|
||||
|
||||
core_data.SetHeight(static_cast<u8>(default_mii.height));
|
||||
core_data.SetBuild(static_cast<u8>(default_mii.weight));
|
||||
core_data.SetGender(static_cast<Gender>(default_mii.gender));
|
||||
core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
|
||||
core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
|
||||
core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
|
||||
core_data.SetType(static_cast<u8>(default_mii.type));
|
||||
core_data.SetNickname(default_mii.nickname);
|
||||
|
||||
const auto device_id = MiiUtil::GetDeviceId();
|
||||
create_id = MiiUtil::MakeCreateId();
|
||||
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
|
||||
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
|
||||
}
|
||||
|
||||
void StoreData::BuildBase(Gender gender) {
|
||||
const auto& default_mii = RawData::BaseMii[gender == Gender::Female ? 1 : 0];
|
||||
core_data.SetDefault();
|
||||
|
||||
core_data.SetFacelineType(static_cast<FacelineType>(default_mii.face_type));
|
||||
core_data.SetFacelineColor(
|
||||
RawData::GetFacelineColorFromVer3(static_cast<u8>(default_mii.face_color)));
|
||||
core_data.SetFacelineWrinkle(static_cast<FacelineWrinkle>(default_mii.face_wrinkle));
|
||||
core_data.SetFacelineMake(static_cast<FacelineMake>(default_mii.face_makeup));
|
||||
|
||||
core_data.SetHairType(static_cast<HairType>(default_mii.hair_type));
|
||||
core_data.SetHairColor(RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.hair_color)));
|
||||
core_data.SetHairFlip(static_cast<HairFlip>(default_mii.hair_flip));
|
||||
core_data.SetEyeType(static_cast<EyeType>(default_mii.eye_type));
|
||||
core_data.SetEyeColor(RawData::GetEyeColorFromVer3(static_cast<u8>(default_mii.eye_color)));
|
||||
core_data.SetEyeScale(static_cast<u8>(default_mii.eye_scale));
|
||||
core_data.SetEyeAspect(static_cast<u8>(default_mii.eye_aspect));
|
||||
core_data.SetEyeRotate(static_cast<u8>(default_mii.eye_rotate));
|
||||
core_data.SetEyeX(static_cast<u8>(default_mii.eye_x));
|
||||
core_data.SetEyeY(static_cast<u8>(default_mii.eye_y));
|
||||
|
||||
core_data.SetEyebrowType(static_cast<EyebrowType>(default_mii.eyebrow_type));
|
||||
core_data.SetEyebrowColor(
|
||||
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.eyebrow_color)));
|
||||
core_data.SetEyebrowScale(static_cast<u8>(default_mii.eyebrow_scale));
|
||||
core_data.SetEyebrowAspect(static_cast<u8>(default_mii.eyebrow_aspect));
|
||||
core_data.SetEyebrowRotate(static_cast<u8>(default_mii.eyebrow_rotate));
|
||||
core_data.SetEyebrowX(static_cast<u8>(default_mii.eyebrow_x));
|
||||
core_data.SetEyebrowY(static_cast<u8>(default_mii.eyebrow_y));
|
||||
|
||||
core_data.SetNoseType(static_cast<NoseType>(default_mii.nose_type));
|
||||
core_data.SetNoseScale(static_cast<u8>(default_mii.nose_scale));
|
||||
core_data.SetNoseY(static_cast<u8>(default_mii.nose_y));
|
||||
|
||||
core_data.SetMouthType(static_cast<u8>(default_mii.mouth_type));
|
||||
core_data.SetMouthColor(
|
||||
RawData::GetMouthColorFromVer3(static_cast<u8>(default_mii.mouth_color)));
|
||||
core_data.SetMouthScale(static_cast<u8>(default_mii.mouth_scale));
|
||||
core_data.SetMouthAspect(static_cast<u8>(default_mii.mouth_aspect));
|
||||
core_data.SetMouthY(static_cast<u8>(default_mii.mouth_y));
|
||||
|
||||
core_data.SetMustacheType(static_cast<MustacheType>(default_mii.mustache_type));
|
||||
core_data.SetBeardType(static_cast<BeardType>(default_mii.beard_type));
|
||||
core_data.SetBeardColor(
|
||||
RawData::GetHairColorFromVer3(static_cast<u8>(default_mii.beard_color)));
|
||||
core_data.SetMustacheScale(static_cast<u8>(default_mii.mustache_scale));
|
||||
core_data.SetMustacheY(static_cast<u8>(default_mii.mustache_y));
|
||||
|
||||
core_data.SetGlassType(static_cast<GlassType>(default_mii.glasses_type));
|
||||
core_data.SetGlassColor(
|
||||
RawData::GetGlassColorFromVer3(static_cast<u8>(default_mii.glasses_color)));
|
||||
core_data.SetGlassScale(static_cast<u8>(default_mii.glasses_scale));
|
||||
core_data.SetGlassY(static_cast<u8>(default_mii.glasses_y));
|
||||
|
||||
core_data.SetMoleType(static_cast<MoleType>(default_mii.mole_type));
|
||||
core_data.SetMoleScale(static_cast<u8>(default_mii.mole_scale));
|
||||
core_data.SetMoleX(static_cast<u8>(default_mii.mole_x));
|
||||
core_data.SetMoleY(static_cast<u8>(default_mii.mole_y));
|
||||
|
||||
core_data.SetHeight(static_cast<u8>(default_mii.height));
|
||||
core_data.SetBuild(static_cast<u8>(default_mii.weight));
|
||||
core_data.SetGender(static_cast<Gender>(default_mii.gender));
|
||||
core_data.SetFavoriteColor(static_cast<FavoriteColor>(default_mii.favorite_color));
|
||||
core_data.SetRegionMove(static_cast<u8>(default_mii.region_move));
|
||||
core_data.SetFontRegion(static_cast<FontRegion>(default_mii.font_region));
|
||||
core_data.SetType(static_cast<u8>(default_mii.type));
|
||||
core_data.SetNickname(default_mii.nickname);
|
||||
|
||||
const auto device_id = MiiUtil::GetDeviceId();
|
||||
create_id = MiiUtil::MakeCreateId();
|
||||
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
|
||||
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
|
||||
}
|
||||
|
||||
void StoreData::BuildRandom(Age age, Gender gender, Race race) {
|
||||
core_data.BuildRandom(age, gender, race);
|
||||
const auto device_id = MiiUtil::GetDeviceId();
|
||||
create_id = MiiUtil::MakeCreateId();
|
||||
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
|
||||
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
|
||||
}
|
||||
|
||||
void StoreData::SetInvalidName() {
|
||||
const auto& invalid_name = core_data.GetInvalidNickname();
|
||||
const auto device_id = MiiUtil::GetDeviceId();
|
||||
core_data.SetNickname(invalid_name);
|
||||
device_crc = MiiUtil::CalculateCrc16(&device_id, sizeof(Common::UUID));
|
||||
data_crc = MiiUtil::CalculateCrc16(&core_data, sizeof(CoreData));
|
||||
}
|
||||
|
||||
bool StoreData::IsSpecial() const {
|
||||
return GetType() == 1;
|
||||
}
|
||||
|
||||
u32 StoreData::IsValid() const {
|
||||
// TODO: complete this
|
||||
return 0;
|
||||
}
|
||||
|
||||
void StoreData::SetFontRegion(FontRegion value) {
|
||||
core_data.SetFontRegion(value);
|
||||
}
|
||||
|
||||
void StoreData::SetFavoriteColor(FavoriteColor value) {
|
||||
core_data.SetFavoriteColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetGender(Gender value) {
|
||||
core_data.SetGender(value);
|
||||
}
|
||||
|
||||
void StoreData::SetHeight(u8 value) {
|
||||
core_data.SetHeight(value);
|
||||
}
|
||||
|
||||
void StoreData::SetBuild(u8 value) {
|
||||
core_data.SetBuild(value);
|
||||
}
|
||||
|
||||
void StoreData::SetType(u8 value) {
|
||||
core_data.SetType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetRegionMove(u8 value) {
|
||||
core_data.SetRegionMove(value);
|
||||
}
|
||||
|
||||
void StoreData::SetFacelineType(FacelineType value) {
|
||||
core_data.SetFacelineType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetFacelineColor(FacelineColor value) {
|
||||
core_data.SetFacelineColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetFacelineWrinkle(FacelineWrinkle value) {
|
||||
core_data.SetFacelineWrinkle(value);
|
||||
}
|
||||
|
||||
void StoreData::SetFacelineMake(FacelineMake value) {
|
||||
core_data.SetFacelineMake(value);
|
||||
}
|
||||
|
||||
void StoreData::SetHairType(HairType value) {
|
||||
core_data.SetHairType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetHairColor(CommonColor value) {
|
||||
core_data.SetHairColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetHairFlip(HairFlip value) {
|
||||
core_data.SetHairFlip(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeType(EyeType value) {
|
||||
core_data.SetEyeType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeColor(CommonColor value) {
|
||||
core_data.SetEyeColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeScale(u8 value) {
|
||||
core_data.SetEyeScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeAspect(u8 value) {
|
||||
core_data.SetEyeAspect(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeRotate(u8 value) {
|
||||
core_data.SetEyeRotate(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeX(u8 value) {
|
||||
core_data.SetEyeX(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyeY(u8 value) {
|
||||
core_data.SetEyeY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowType(EyebrowType value) {
|
||||
core_data.SetEyebrowType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowColor(CommonColor value) {
|
||||
core_data.SetEyebrowColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowScale(u8 value) {
|
||||
core_data.SetEyebrowScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowAspect(u8 value) {
|
||||
core_data.SetEyebrowAspect(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowRotate(u8 value) {
|
||||
core_data.SetEyebrowRotate(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowX(u8 value) {
|
||||
core_data.SetEyebrowX(value);
|
||||
}
|
||||
|
||||
void StoreData::SetEyebrowY(u8 value) {
|
||||
core_data.SetEyebrowY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetNoseType(NoseType value) {
|
||||
core_data.SetNoseType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetNoseScale(u8 value) {
|
||||
core_data.SetNoseScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetNoseY(u8 value) {
|
||||
core_data.SetNoseY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMouthType(u8 value) {
|
||||
core_data.SetMouthType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMouthColor(CommonColor value) {
|
||||
core_data.SetMouthColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMouthScale(u8 value) {
|
||||
core_data.SetMouthScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMouthAspect(u8 value) {
|
||||
core_data.SetMouthAspect(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMouthY(u8 value) {
|
||||
core_data.SetMouthY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetBeardColor(CommonColor value) {
|
||||
core_data.SetBeardColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetBeardType(BeardType value) {
|
||||
core_data.SetBeardType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMustacheType(MustacheType value) {
|
||||
core_data.SetMustacheType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMustacheScale(u8 value) {
|
||||
core_data.SetMustacheScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMustacheY(u8 value) {
|
||||
core_data.SetMustacheY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetGlassType(GlassType value) {
|
||||
core_data.SetGlassType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetGlassColor(CommonColor value) {
|
||||
core_data.SetGlassColor(value);
|
||||
}
|
||||
|
||||
void StoreData::SetGlassScale(u8 value) {
|
||||
core_data.SetGlassScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetGlassY(u8 value) {
|
||||
core_data.SetGlassY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMoleType(MoleType value) {
|
||||
core_data.SetMoleType(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMoleScale(u8 value) {
|
||||
core_data.SetMoleScale(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMoleX(u8 value) {
|
||||
core_data.SetMoleX(value);
|
||||
}
|
||||
|
||||
void StoreData::SetMoleY(u8 value) {
|
||||
core_data.SetMoleY(value);
|
||||
}
|
||||
|
||||
void StoreData::SetNickname(Nickname value) {
|
||||
core_data.SetNickname(value);
|
||||
}
|
||||
|
||||
Common::UUID StoreData::GetCreateId() const {
|
||||
return create_id;
|
||||
}
|
||||
|
||||
FontRegion StoreData::GetFontRegion() const {
|
||||
return static_cast<FontRegion>(core_data.GetFontRegion());
|
||||
}
|
||||
|
||||
FavoriteColor StoreData::GetFavoriteColor() const {
|
||||
return core_data.GetFavoriteColor();
|
||||
}
|
||||
|
||||
Gender StoreData::GetGender() const {
|
||||
return core_data.GetGender();
|
||||
}
|
||||
|
||||
u8 StoreData::GetHeight() const {
|
||||
return core_data.GetHeight();
|
||||
}
|
||||
|
||||
u8 StoreData::GetBuild() const {
|
||||
return core_data.GetBuild();
|
||||
}
|
||||
|
||||
u8 StoreData::GetType() const {
|
||||
return core_data.GetType();
|
||||
}
|
||||
|
||||
u8 StoreData::GetRegionMove() const {
|
||||
return core_data.GetRegionMove();
|
||||
}
|
||||
|
||||
FacelineType StoreData::GetFacelineType() const {
|
||||
return core_data.GetFacelineType();
|
||||
}
|
||||
|
||||
FacelineColor StoreData::GetFacelineColor() const {
|
||||
return core_data.GetFacelineColor();
|
||||
}
|
||||
|
||||
FacelineWrinkle StoreData::GetFacelineWrinkle() const {
|
||||
return core_data.GetFacelineWrinkle();
|
||||
}
|
||||
|
||||
FacelineMake StoreData::GetFacelineMake() const {
|
||||
return core_data.GetFacelineMake();
|
||||
}
|
||||
|
||||
HairType StoreData::GetHairType() const {
|
||||
return core_data.GetHairType();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetHairColor() const {
|
||||
return core_data.GetHairColor();
|
||||
}
|
||||
|
||||
HairFlip StoreData::GetHairFlip() const {
|
||||
return core_data.GetHairFlip();
|
||||
}
|
||||
|
||||
EyeType StoreData::GetEyeType() const {
|
||||
return core_data.GetEyeType();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetEyeColor() const {
|
||||
return core_data.GetEyeColor();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyeScale() const {
|
||||
return core_data.GetEyeScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyeAspect() const {
|
||||
return core_data.GetEyeAspect();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyeRotate() const {
|
||||
return core_data.GetEyeRotate();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyeX() const {
|
||||
return core_data.GetEyeX();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyeY() const {
|
||||
return core_data.GetEyeY();
|
||||
}
|
||||
|
||||
EyebrowType StoreData::GetEyebrowType() const {
|
||||
return core_data.GetEyebrowType();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetEyebrowColor() const {
|
||||
return core_data.GetEyebrowColor();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyebrowScale() const {
|
||||
return core_data.GetEyebrowScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyebrowAspect() const {
|
||||
return core_data.GetEyebrowAspect();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyebrowRotate() const {
|
||||
return core_data.GetEyebrowRotate();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyebrowX() const {
|
||||
return core_data.GetEyebrowX();
|
||||
}
|
||||
|
||||
u8 StoreData::GetEyebrowY() const {
|
||||
return core_data.GetEyebrowY();
|
||||
}
|
||||
|
||||
NoseType StoreData::GetNoseType() const {
|
||||
return core_data.GetNoseType();
|
||||
}
|
||||
|
||||
u8 StoreData::GetNoseScale() const {
|
||||
return core_data.GetNoseScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetNoseY() const {
|
||||
return core_data.GetNoseY();
|
||||
}
|
||||
|
||||
MouthType StoreData::GetMouthType() const {
|
||||
return core_data.GetMouthType();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetMouthColor() const {
|
||||
return core_data.GetMouthColor();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMouthScale() const {
|
||||
return core_data.GetMouthScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMouthAspect() const {
|
||||
return core_data.GetMouthAspect();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMouthY() const {
|
||||
return core_data.GetMouthY();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetBeardColor() const {
|
||||
return core_data.GetBeardColor();
|
||||
}
|
||||
|
||||
BeardType StoreData::GetBeardType() const {
|
||||
return core_data.GetBeardType();
|
||||
}
|
||||
|
||||
MustacheType StoreData::GetMustacheType() const {
|
||||
return core_data.GetMustacheType();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMustacheScale() const {
|
||||
return core_data.GetMustacheScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMustacheY() const {
|
||||
return core_data.GetMustacheY();
|
||||
}
|
||||
|
||||
GlassType StoreData::GetGlassType() const {
|
||||
return core_data.GetGlassType();
|
||||
}
|
||||
|
||||
CommonColor StoreData::GetGlassColor() const {
|
||||
return core_data.GetGlassColor();
|
||||
}
|
||||
|
||||
u8 StoreData::GetGlassScale() const {
|
||||
return core_data.GetGlassScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetGlassY() const {
|
||||
return core_data.GetGlassY();
|
||||
}
|
||||
|
||||
MoleType StoreData::GetMoleType() const {
|
||||
return core_data.GetMoleType();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMoleScale() const {
|
||||
return core_data.GetMoleScale();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMoleX() const {
|
||||
return core_data.GetMoleX();
|
||||
}
|
||||
|
||||
u8 StoreData::GetMoleY() const {
|
||||
return core_data.GetMoleY();
|
||||
}
|
||||
|
||||
Nickname StoreData::GetNickname() const {
|
||||
return core_data.GetNickname();
|
||||
}
|
||||
|
||||
bool StoreData::operator==(const StoreData& data) {
|
||||
bool is_identical = data.core_data.IsValid() == 0;
|
||||
is_identical &= core_data.GetNickname().data == data.core_data.GetNickname().data;
|
||||
is_identical &= GetCreateId() == data.GetCreateId();
|
||||
is_identical &= GetFontRegion() == data.GetFontRegion();
|
||||
is_identical &= GetFavoriteColor() == data.GetFavoriteColor();
|
||||
is_identical &= GetGender() == data.GetGender();
|
||||
is_identical &= GetHeight() == data.GetHeight();
|
||||
is_identical &= GetBuild() == data.GetBuild();
|
||||
is_identical &= GetType() == data.GetType();
|
||||
is_identical &= GetRegionMove() == data.GetRegionMove();
|
||||
is_identical &= GetFacelineType() == data.GetFacelineType();
|
||||
is_identical &= GetFacelineColor() == data.GetFacelineColor();
|
||||
is_identical &= GetFacelineWrinkle() == data.GetFacelineWrinkle();
|
||||
is_identical &= GetFacelineMake() == data.GetFacelineMake();
|
||||
is_identical &= GetHairType() == data.GetHairType();
|
||||
is_identical &= GetHairColor() == data.GetHairColor();
|
||||
is_identical &= GetHairFlip() == data.GetHairFlip();
|
||||
is_identical &= GetEyeType() == data.GetEyeType();
|
||||
is_identical &= GetEyeColor() == data.GetEyeColor();
|
||||
is_identical &= GetEyeScale() == data.GetEyeScale();
|
||||
is_identical &= GetEyeAspect() == data.GetEyeAspect();
|
||||
is_identical &= GetEyeRotate() == data.GetEyeRotate();
|
||||
is_identical &= GetEyeX() == data.GetEyeX();
|
||||
is_identical &= GetEyeY() == data.GetEyeY();
|
||||
is_identical &= GetEyebrowType() == data.GetEyebrowType();
|
||||
is_identical &= GetEyebrowColor() == data.GetEyebrowColor();
|
||||
is_identical &= GetEyebrowScale() == data.GetEyebrowScale();
|
||||
is_identical &= GetEyebrowAspect() == data.GetEyebrowAspect();
|
||||
is_identical &= GetEyebrowRotate() == data.GetEyebrowRotate();
|
||||
is_identical &= GetEyebrowX() == data.GetEyebrowX();
|
||||
is_identical &= GetEyebrowY() == data.GetEyebrowY();
|
||||
is_identical &= GetNoseType() == data.GetNoseType();
|
||||
is_identical &= GetNoseScale() == data.GetNoseScale();
|
||||
is_identical &= GetNoseY() == data.GetNoseY();
|
||||
is_identical &= GetMouthType() == data.GetMouthType();
|
||||
is_identical &= GetMouthColor() == data.GetMouthColor();
|
||||
is_identical &= GetMouthScale() == data.GetMouthScale();
|
||||
is_identical &= GetMouthAspect() == data.GetMouthAspect();
|
||||
is_identical &= GetMouthY() == data.GetMouthY();
|
||||
is_identical &= GetBeardColor() == data.GetBeardColor();
|
||||
is_identical &= GetBeardType() == data.GetBeardType();
|
||||
is_identical &= GetMustacheType() == data.GetMustacheType();
|
||||
is_identical &= GetMustacheScale() == data.GetMustacheScale();
|
||||
is_identical &= GetMustacheY() == data.GetMustacheY();
|
||||
is_identical &= GetGlassType() == data.GetGlassType();
|
||||
is_identical &= GetGlassColor() == data.GetGlassColor();
|
||||
is_identical &= GetGlassScale() == data.GetGlassScale();
|
||||
is_identical &= GetGlassY() == data.GetGlassY();
|
||||
is_identical &= GetMoleType() == data.GetMoleType();
|
||||
is_identical &= GetMoleScale() == data.GetMoleScale();
|
||||
is_identical &= GetMoleX() == data.GetMoleX();
|
||||
is_identical &= data.GetMoleY() == data.GetMoleY();
|
||||
return is_identical;
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
145
src/core/hle/service/mii/types/store_data.h
Normal file
145
src/core/hle/service/mii/types/store_data.h
Normal file
@ -0,0 +1,145 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
#include "core/hle/service/mii/types/core_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
class StoreData {
|
||||
public:
|
||||
// nn::mii::detail::StoreDataRaw::BuildDefault
|
||||
void BuildDefault(u32 mii_index);
|
||||
// nn::mii::detail::StoreDataRaw::BuildDefault
|
||||
|
||||
void BuildBase(Gender gender);
|
||||
// nn::mii::detail::StoreDataRaw::BuildRandom
|
||||
void BuildRandom(Age age, Gender gender, Race race);
|
||||
|
||||
bool IsSpecial() const;
|
||||
|
||||
u32 IsValid() const;
|
||||
|
||||
void SetFontRegion(FontRegion value);
|
||||
void SetFavoriteColor(FavoriteColor value);
|
||||
void SetGender(Gender value);
|
||||
void SetHeight(u8 value);
|
||||
void SetBuild(u8 value);
|
||||
void SetType(u8 value);
|
||||
void SetRegionMove(u8 value);
|
||||
void SetFacelineType(FacelineType value);
|
||||
void SetFacelineColor(FacelineColor value);
|
||||
void SetFacelineWrinkle(FacelineWrinkle value);
|
||||
void SetFacelineMake(FacelineMake value);
|
||||
void SetHairType(HairType value);
|
||||
void SetHairColor(CommonColor value);
|
||||
void SetHairFlip(HairFlip value);
|
||||
void SetEyeType(EyeType value);
|
||||
void SetEyeColor(CommonColor value);
|
||||
void SetEyeScale(u8 value);
|
||||
void SetEyeAspect(u8 value);
|
||||
void SetEyeRotate(u8 value);
|
||||
void SetEyeX(u8 value);
|
||||
void SetEyeY(u8 value);
|
||||
void SetEyebrowType(EyebrowType value);
|
||||
void SetEyebrowColor(CommonColor value);
|
||||
void SetEyebrowScale(u8 value);
|
||||
void SetEyebrowAspect(u8 value);
|
||||
void SetEyebrowRotate(u8 value);
|
||||
void SetEyebrowX(u8 value);
|
||||
void SetEyebrowY(u8 value);
|
||||
void SetNoseType(NoseType value);
|
||||
void SetNoseScale(u8 value);
|
||||
void SetNoseY(u8 value);
|
||||
void SetMouthType(u8 value);
|
||||
void SetMouthColor(CommonColor value);
|
||||
void SetMouthScale(u8 value);
|
||||
void SetMouthAspect(u8 value);
|
||||
void SetMouthY(u8 value);
|
||||
void SetBeardColor(CommonColor value);
|
||||
void SetBeardType(BeardType value);
|
||||
void SetMustacheType(MustacheType value);
|
||||
void SetMustacheScale(u8 value);
|
||||
void SetMustacheY(u8 value);
|
||||
void SetGlassType(GlassType value);
|
||||
void SetGlassColor(CommonColor value);
|
||||
void SetGlassScale(u8 value);
|
||||
void SetGlassY(u8 value);
|
||||
void SetMoleType(MoleType value);
|
||||
void SetMoleScale(u8 value);
|
||||
void SetMoleX(u8 value);
|
||||
void SetMoleY(u8 value);
|
||||
void SetNickname(Nickname nickname);
|
||||
void SetInvalidName();
|
||||
|
||||
Common::UUID GetCreateId() const;
|
||||
FontRegion GetFontRegion() const;
|
||||
FavoriteColor GetFavoriteColor() const;
|
||||
Gender GetGender() const;
|
||||
u8 GetHeight() const;
|
||||
u8 GetBuild() const;
|
||||
u8 GetType() const;
|
||||
u8 GetRegionMove() const;
|
||||
FacelineType GetFacelineType() const;
|
||||
FacelineColor GetFacelineColor() const;
|
||||
FacelineWrinkle GetFacelineWrinkle() const;
|
||||
FacelineMake GetFacelineMake() const;
|
||||
HairType GetHairType() const;
|
||||
CommonColor GetHairColor() const;
|
||||
HairFlip GetHairFlip() const;
|
||||
EyeType GetEyeType() const;
|
||||
CommonColor GetEyeColor() const;
|
||||
u8 GetEyeScale() const;
|
||||
u8 GetEyeAspect() const;
|
||||
u8 GetEyeRotate() const;
|
||||
u8 GetEyeX() const;
|
||||
u8 GetEyeY() const;
|
||||
EyebrowType GetEyebrowType() const;
|
||||
CommonColor GetEyebrowColor() const;
|
||||
u8 GetEyebrowScale() const;
|
||||
u8 GetEyebrowAspect() const;
|
||||
u8 GetEyebrowRotate() const;
|
||||
u8 GetEyebrowX() const;
|
||||
u8 GetEyebrowY() const;
|
||||
NoseType GetNoseType() const;
|
||||
u8 GetNoseScale() const;
|
||||
u8 GetNoseY() const;
|
||||
MouthType GetMouthType() const;
|
||||
CommonColor GetMouthColor() const;
|
||||
u8 GetMouthScale() const;
|
||||
u8 GetMouthAspect() const;
|
||||
u8 GetMouthY() const;
|
||||
CommonColor GetBeardColor() const;
|
||||
BeardType GetBeardType() const;
|
||||
MustacheType GetMustacheType() const;
|
||||
u8 GetMustacheScale() const;
|
||||
u8 GetMustacheY() const;
|
||||
GlassType GetGlassType() const;
|
||||
CommonColor GetGlassColor() const;
|
||||
u8 GetGlassScale() const;
|
||||
u8 GetGlassY() const;
|
||||
MoleType GetMoleType() const;
|
||||
u8 GetMoleScale() const;
|
||||
u8 GetMoleX() const;
|
||||
u8 GetMoleY() const;
|
||||
Nickname GetNickname() const;
|
||||
|
||||
bool operator==(const StoreData& data);
|
||||
|
||||
private:
|
||||
CoreData core_data{};
|
||||
Common::UUID create_id{};
|
||||
u16 data_crc{};
|
||||
u16 device_crc{};
|
||||
};
|
||||
static_assert(sizeof(StoreData) == 0x44, "StoreData has incorrect size.");
|
||||
|
||||
struct StoreDataElement {
|
||||
StoreData store_data{};
|
||||
Source source{};
|
||||
};
|
||||
static_assert(sizeof(StoreDataElement) == 0x48, "StoreDataElement has incorrect size.");
|
||||
|
||||
}; // namespace Service::Mii
|
241
src/core/hle/service/mii/types/ver3_store_data.cpp
Normal file
241
src/core/hle/service/mii/types/ver3_store_data.cpp
Normal file
@ -0,0 +1,241 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/service/mii/mii_util.h"
|
||||
#include "core/hle/service/mii/types/raw_data.h"
|
||||
#include "core/hle/service/mii/types/store_data.h"
|
||||
#include "core/hle/service/mii/types/ver3_store_data.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
|
||||
void NfpStoreDataExtension::SetFromStoreData(const StoreData& store_data) {
|
||||
faceline_color = static_cast<u8>(store_data.GetFacelineColor()) & 0xf;
|
||||
hair_color = static_cast<u8>(store_data.GetHairColor()) & 0x7f;
|
||||
eye_color = static_cast<u8>(store_data.GetEyeColor()) & 0x7f;
|
||||
eyebrow_color = static_cast<u8>(store_data.GetEyebrowColor()) & 0x7f;
|
||||
mouth_color = static_cast<u8>(store_data.GetMouthColor()) & 0x7f;
|
||||
beard_color = static_cast<u8>(store_data.GetBeardColor()) & 0x7f;
|
||||
glass_color = static_cast<u8>(store_data.GetGlassColor()) & 0x7f;
|
||||
glass_type = static_cast<u8>(store_data.GetGlassType()) & 0x1f;
|
||||
}
|
||||
|
||||
void Ver3StoreData::BuildToStoreData(StoreData& out_store_data) const {
|
||||
out_store_data.BuildBase(Gender::Male);
|
||||
|
||||
if (!IsValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: We are ignoring a bunch of data from the mii_v3
|
||||
|
||||
out_store_data.SetGender(static_cast<Gender>(mii_information.gender.Value()));
|
||||
out_store_data.SetFavoriteColor(
|
||||
static_cast<FavoriteColor>(mii_information.favorite_color.Value()));
|
||||
out_store_data.SetHeight(height);
|
||||
out_store_data.SetBuild(build);
|
||||
|
||||
out_store_data.SetNickname(mii_name);
|
||||
out_store_data.SetFontRegion(
|
||||
static_cast<FontRegion>(static_cast<u8>(region_information.font_region)));
|
||||
|
||||
out_store_data.SetFacelineType(
|
||||
static_cast<FacelineType>(appearance_bits1.faceline_type.Value()));
|
||||
out_store_data.SetFacelineColor(
|
||||
static_cast<FacelineColor>(appearance_bits1.faceline_color.Value()));
|
||||
out_store_data.SetFacelineWrinkle(
|
||||
static_cast<FacelineWrinkle>(appearance_bits2.faceline_wrinkle.Value()));
|
||||
out_store_data.SetFacelineMake(
|
||||
static_cast<FacelineMake>(appearance_bits2.faceline_make.Value()));
|
||||
|
||||
out_store_data.SetHairType(static_cast<HairType>(hair_type));
|
||||
out_store_data.SetHairColor(static_cast<CommonColor>(appearance_bits3.hair_color.Value()));
|
||||
out_store_data.SetHairFlip(static_cast<HairFlip>(appearance_bits3.hair_flip.Value()));
|
||||
|
||||
out_store_data.SetEyeType(static_cast<EyeType>(appearance_bits4.eye_type.Value()));
|
||||
out_store_data.SetEyeColor(static_cast<CommonColor>(appearance_bits4.eye_color.Value()));
|
||||
out_store_data.SetEyeScale(static_cast<u8>(appearance_bits4.eye_scale));
|
||||
out_store_data.SetEyeAspect(static_cast<u8>(appearance_bits4.eye_aspect));
|
||||
out_store_data.SetEyeRotate(static_cast<u8>(appearance_bits4.eye_rotate));
|
||||
out_store_data.SetEyeX(static_cast<u8>(appearance_bits4.eye_x));
|
||||
out_store_data.SetEyeY(static_cast<u8>(appearance_bits4.eye_y));
|
||||
|
||||
out_store_data.SetEyebrowType(static_cast<EyebrowType>(appearance_bits5.eyebrow_type.Value()));
|
||||
out_store_data.SetEyebrowColor(
|
||||
static_cast<CommonColor>(appearance_bits5.eyebrow_color.Value()));
|
||||
out_store_data.SetEyebrowScale(static_cast<u8>(appearance_bits5.eyebrow_scale));
|
||||
out_store_data.SetEyebrowAspect(static_cast<u8>(appearance_bits5.eyebrow_aspect));
|
||||
out_store_data.SetEyebrowRotate(static_cast<u8>(appearance_bits5.eyebrow_rotate));
|
||||
out_store_data.SetEyebrowX(static_cast<u8>(appearance_bits5.eyebrow_x));
|
||||
out_store_data.SetEyebrowY(static_cast<u8>(appearance_bits5.eyebrow_y));
|
||||
|
||||
out_store_data.SetNoseType(static_cast<NoseType>(appearance_bits6.nose_type.Value()));
|
||||
out_store_data.SetNoseScale(static_cast<u8>(appearance_bits6.nose_scale));
|
||||
out_store_data.SetNoseY(static_cast<u8>(appearance_bits6.nose_y));
|
||||
|
||||
out_store_data.SetMouthType(static_cast<u8>(appearance_bits7.mouth_type));
|
||||
out_store_data.SetMouthColor(static_cast<CommonColor>(appearance_bits7.mouth_color.Value()));
|
||||
out_store_data.SetMouthScale(static_cast<u8>(appearance_bits7.mouth_scale));
|
||||
out_store_data.SetMouthAspect(static_cast<u8>(appearance_bits7.mouth_aspect));
|
||||
out_store_data.SetMouthY(static_cast<u8>(appearance_bits8.mouth_y));
|
||||
|
||||
out_store_data.SetMustacheType(
|
||||
static_cast<MustacheType>(appearance_bits8.mustache_type.Value()));
|
||||
out_store_data.SetMustacheScale(static_cast<u8>(appearance_bits9.mustache_scale));
|
||||
out_store_data.SetMustacheY(static_cast<u8>(appearance_bits9.mustache_y));
|
||||
|
||||
out_store_data.SetBeardType(static_cast<BeardType>(appearance_bits9.beard_type.Value()));
|
||||
out_store_data.SetBeardColor(static_cast<CommonColor>(appearance_bits9.beard_color.Value()));
|
||||
|
||||
out_store_data.SetGlassType(static_cast<GlassType>(appearance_bits10.glass_type.Value()));
|
||||
out_store_data.SetGlassColor(static_cast<CommonColor>(appearance_bits10.glass_color.Value()));
|
||||
out_store_data.SetGlassScale(static_cast<u8>(appearance_bits10.glass_scale));
|
||||
out_store_data.SetGlassY(static_cast<u8>(appearance_bits10.glass_y));
|
||||
|
||||
out_store_data.SetMoleType(static_cast<MoleType>(appearance_bits11.mole_type.Value()));
|
||||
out_store_data.SetMoleScale(static_cast<u8>(appearance_bits11.mole_scale));
|
||||
out_store_data.SetMoleX(static_cast<u8>(appearance_bits11.mole_x));
|
||||
out_store_data.SetMoleY(static_cast<u8>(appearance_bits11.mole_y));
|
||||
}
|
||||
|
||||
void Ver3StoreData::BuildFromStoreData(const StoreData& store_data) {
|
||||
version = 1;
|
||||
mii_information.gender.Assign(static_cast<u8>(store_data.GetGender()));
|
||||
mii_information.favorite_color.Assign(static_cast<u8>(store_data.GetFavoriteColor()));
|
||||
height = store_data.GetHeight();
|
||||
build = store_data.GetBuild();
|
||||
|
||||
mii_name = store_data.GetNickname();
|
||||
region_information.font_region.Assign(static_cast<u8>(store_data.GetFontRegion()));
|
||||
|
||||
appearance_bits1.faceline_type.Assign(static_cast<u8>(store_data.GetFacelineType()));
|
||||
appearance_bits2.faceline_wrinkle.Assign(static_cast<u8>(store_data.GetFacelineWrinkle()));
|
||||
appearance_bits2.faceline_make.Assign(static_cast<u8>(store_data.GetFacelineMake()));
|
||||
|
||||
hair_type = static_cast<u8>(store_data.GetHairType());
|
||||
appearance_bits3.hair_flip.Assign(static_cast<u8>(store_data.GetHairFlip()));
|
||||
|
||||
appearance_bits4.eye_type.Assign(static_cast<u8>(store_data.GetEyeType()));
|
||||
appearance_bits4.eye_scale.Assign(store_data.GetEyeScale());
|
||||
appearance_bits4.eye_aspect.Assign(store_data.GetEyebrowAspect());
|
||||
appearance_bits4.eye_rotate.Assign(store_data.GetEyeRotate());
|
||||
appearance_bits4.eye_x.Assign(store_data.GetEyeX());
|
||||
appearance_bits4.eye_y.Assign(store_data.GetEyeY());
|
||||
|
||||
appearance_bits5.eyebrow_type.Assign(static_cast<u8>(store_data.GetEyebrowType()));
|
||||
appearance_bits5.eyebrow_scale.Assign(store_data.GetEyebrowScale());
|
||||
appearance_bits5.eyebrow_aspect.Assign(store_data.GetEyebrowAspect());
|
||||
appearance_bits5.eyebrow_rotate.Assign(store_data.GetEyebrowRotate());
|
||||
appearance_bits5.eyebrow_x.Assign(store_data.GetEyebrowX());
|
||||
appearance_bits5.eyebrow_y.Assign(store_data.GetEyebrowY());
|
||||
|
||||
appearance_bits6.nose_type.Assign(static_cast<u8>(store_data.GetNoseType()));
|
||||
appearance_bits6.nose_scale.Assign(store_data.GetNoseScale());
|
||||
appearance_bits6.nose_y.Assign(store_data.GetNoseY());
|
||||
|
||||
appearance_bits7.mouth_type.Assign(static_cast<u8>(store_data.GetMouthType()));
|
||||
appearance_bits7.mouth_scale.Assign(store_data.GetMouthScale());
|
||||
appearance_bits7.mouth_aspect.Assign(store_data.GetMouthAspect());
|
||||
appearance_bits8.mouth_y.Assign(store_data.GetMouthY());
|
||||
|
||||
appearance_bits8.mustache_type.Assign(static_cast<u8>(store_data.GetMustacheType()));
|
||||
appearance_bits9.mustache_scale.Assign(store_data.GetMustacheScale());
|
||||
appearance_bits9.mustache_y.Assign(store_data.GetMustacheY());
|
||||
|
||||
appearance_bits9.beard_type.Assign(static_cast<u8>(store_data.GetBeardType()));
|
||||
|
||||
appearance_bits10.glass_scale.Assign(store_data.GetGlassScale());
|
||||
appearance_bits10.glass_y.Assign(store_data.GetGlassY());
|
||||
|
||||
appearance_bits11.mole_type.Assign(static_cast<u8>(store_data.GetMoleType()));
|
||||
appearance_bits11.mole_scale.Assign(store_data.GetMoleScale());
|
||||
appearance_bits11.mole_x.Assign(store_data.GetMoleX());
|
||||
appearance_bits11.mole_y.Assign(store_data.GetMoleY());
|
||||
|
||||
// These types are converted to V3 from a table
|
||||
appearance_bits1.faceline_color.Assign(
|
||||
RawData::FromVer3GetFacelineColor(static_cast<u8>(store_data.GetFacelineColor())));
|
||||
appearance_bits3.hair_color.Assign(
|
||||
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetHairColor())));
|
||||
appearance_bits4.eye_color.Assign(
|
||||
RawData::FromVer3GetEyeColor(static_cast<u8>(store_data.GetEyeColor())));
|
||||
appearance_bits5.eyebrow_color.Assign(
|
||||
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetEyebrowColor())));
|
||||
appearance_bits7.mouth_color.Assign(
|
||||
RawData::FromVer3GetMouthlineColor(static_cast<u8>(store_data.GetMouthColor())));
|
||||
appearance_bits9.beard_color.Assign(
|
||||
RawData::FromVer3GetHairColor(static_cast<u8>(store_data.GetBeardColor())));
|
||||
appearance_bits10.glass_color.Assign(
|
||||
RawData::FromVer3GetGlassColor(static_cast<u8>(store_data.GetGlassColor())));
|
||||
appearance_bits10.glass_type.Assign(
|
||||
RawData::FromVer3GetGlassType(static_cast<u8>(store_data.GetGlassType())));
|
||||
|
||||
crc = MiiUtil::CalculateCrc16(&version, sizeof(Ver3StoreData) - sizeof(u16));
|
||||
}
|
||||
|
||||
u32 Ver3StoreData::IsValid() const {
|
||||
bool is_valid = version == 0 || version == 3;
|
||||
|
||||
is_valid = is_valid && (mii_name.data[0] != '\0');
|
||||
|
||||
is_valid = is_valid && (mii_information.birth_month < 13);
|
||||
is_valid = is_valid && (mii_information.birth_day < 32);
|
||||
is_valid = is_valid && (mii_information.favorite_color <= static_cast<u8>(FavoriteColor::Max));
|
||||
is_valid = is_valid && (height <= MaxHeight);
|
||||
is_valid = is_valid && (build <= MaxBuild);
|
||||
|
||||
is_valid = is_valid && (appearance_bits1.faceline_type <= static_cast<u8>(FacelineType::Max));
|
||||
is_valid = is_valid && (appearance_bits1.faceline_color <= MaxVer3CommonColor - 2);
|
||||
is_valid =
|
||||
is_valid && (appearance_bits2.faceline_wrinkle <= static_cast<u8>(FacelineWrinkle::Max));
|
||||
is_valid = is_valid && (appearance_bits2.faceline_make <= static_cast<u8>(FacelineMake::Max));
|
||||
|
||||
is_valid = is_valid && (hair_type <= static_cast<u8>(HairType::Max));
|
||||
is_valid = is_valid && (appearance_bits3.hair_color <= MaxVer3CommonColor);
|
||||
|
||||
is_valid = is_valid && (appearance_bits4.eye_type <= static_cast<u8>(EyeType::Max));
|
||||
is_valid = is_valid && (appearance_bits4.eye_color <= MaxVer3CommonColor - 2);
|
||||
is_valid = is_valid && (appearance_bits4.eye_scale <= MaxEyeScale);
|
||||
is_valid = is_valid && (appearance_bits4.eye_aspect <= MaxEyeAspect);
|
||||
is_valid = is_valid && (appearance_bits4.eye_rotate <= MaxEyeRotate);
|
||||
is_valid = is_valid && (appearance_bits4.eye_x <= MaxEyeX);
|
||||
is_valid = is_valid && (appearance_bits4.eye_y <= MaxEyeY);
|
||||
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_type <= static_cast<u8>(EyebrowType::Max));
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_color <= MaxVer3CommonColor);
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_scale <= MaxEyebrowScale);
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_aspect <= MaxEyebrowAspect);
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_rotate <= MaxEyebrowRotate);
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_x <= MaxEyebrowX);
|
||||
is_valid = is_valid && (appearance_bits5.eyebrow_y <= MaxEyebrowY);
|
||||
|
||||
is_valid = is_valid && (appearance_bits6.nose_type <= static_cast<u8>(NoseType::Max));
|
||||
is_valid = is_valid && (appearance_bits6.nose_scale <= MaxNoseScale);
|
||||
is_valid = is_valid && (appearance_bits6.nose_y <= MaxNoseY);
|
||||
|
||||
is_valid = is_valid && (appearance_bits7.mouth_type <= static_cast<u8>(MouthType::Max));
|
||||
is_valid = is_valid && (appearance_bits7.mouth_color <= MaxVer3CommonColor - 3);
|
||||
is_valid = is_valid && (appearance_bits7.mouth_scale <= MaxMouthScale);
|
||||
is_valid = is_valid && (appearance_bits7.mouth_aspect <= MaxMoutAspect);
|
||||
is_valid = is_valid && (appearance_bits8.mouth_y <= MaxMouthY);
|
||||
|
||||
is_valid = is_valid && (appearance_bits8.mustache_type <= static_cast<u8>(MustacheType::Max));
|
||||
is_valid = is_valid && (appearance_bits9.mustache_scale < MaxMustacheScale);
|
||||
is_valid = is_valid && (appearance_bits9.mustache_y <= MasMustacheY);
|
||||
|
||||
is_valid = is_valid && (appearance_bits9.beard_type <= static_cast<u8>(BeardType::Max));
|
||||
is_valid = is_valid && (appearance_bits9.beard_color <= MaxVer3CommonColor);
|
||||
|
||||
is_valid = is_valid && (appearance_bits10.glass_type <= MaxVer3GlassType);
|
||||
is_valid = is_valid && (appearance_bits10.glass_color <= MaxVer3CommonColor - 2);
|
||||
is_valid = is_valid && (appearance_bits10.glass_scale <= MaxGlassScale);
|
||||
is_valid = is_valid && (appearance_bits10.glass_y <= MaxGlassScale);
|
||||
|
||||
is_valid = is_valid && (appearance_bits11.mole_type <= static_cast<u8>(MoleType::Max));
|
||||
is_valid = is_valid && (appearance_bits11.mole_scale <= MaxMoleScale);
|
||||
is_valid = is_valid && (appearance_bits11.mole_x <= MaxMoleX);
|
||||
is_valid = is_valid && (appearance_bits11.mole_y <= MaxMoleY);
|
||||
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
} // namespace Service::Mii
|
160
src/core/hle/service/mii/types/ver3_store_data.h
Normal file
160
src/core/hle/service/mii/types/ver3_store_data.h
Normal file
@ -0,0 +1,160 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/mii/mii_types.h"
|
||||
|
||||
namespace Service::Mii {
|
||||
class StoreData;
|
||||
|
||||
// This is nn::mii::Ver3StoreData
|
||||
// Based on citra HLE::Applets::MiiData and PretendoNetwork.
|
||||
// https://github.com/citra-emu/citra/blob/master/src/core/hle/applets/mii_selector.h#L48
|
||||
// https://github.com/PretendoNetwork/mii-js/blob/master/mii.js#L299
|
||||
|
||||
struct NfpStoreDataExtension {
|
||||
void SetFromStoreData(const StoreData& store_data);
|
||||
|
||||
u8 faceline_color;
|
||||
u8 hair_color;
|
||||
u8 eye_color;
|
||||
u8 eyebrow_color;
|
||||
u8 mouth_color;
|
||||
u8 beard_color;
|
||||
u8 glass_color;
|
||||
u8 glass_type;
|
||||
};
|
||||
static_assert(sizeof(NfpStoreDataExtension) == 0x8, "NfpStoreDataExtension is an invalid size");
|
||||
|
||||
#pragma pack(push, 4)
|
||||
class Ver3StoreData {
|
||||
public:
|
||||
void BuildToStoreData(StoreData& out_store_data) const;
|
||||
void BuildFromStoreData(const StoreData& store_data);
|
||||
|
||||
u32 IsValid() const;
|
||||
|
||||
u8 version;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 1, u8> allow_copying;
|
||||
BitField<1, 1, u8> profanity_flag;
|
||||
BitField<2, 2, u8> region_lock;
|
||||
BitField<4, 2, u8> font_region;
|
||||
} region_information;
|
||||
u16_be mii_id;
|
||||
u64_be system_id;
|
||||
u32_be specialness_and_creation_date;
|
||||
std::array<u8, 6> creator_mac;
|
||||
u16_be padding;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 1, u16> gender;
|
||||
BitField<1, 4, u16> birth_month;
|
||||
BitField<5, 5, u16> birth_day;
|
||||
BitField<10, 4, u16> favorite_color;
|
||||
BitField<14, 1, u16> favorite;
|
||||
} mii_information;
|
||||
Nickname mii_name;
|
||||
u8 height;
|
||||
u8 build;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 1, u8> disable_sharing;
|
||||
BitField<1, 4, u8> faceline_type;
|
||||
BitField<5, 3, u8> faceline_color;
|
||||
} appearance_bits1;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 4, u8> faceline_wrinkle;
|
||||
BitField<4, 4, u8> faceline_make;
|
||||
} appearance_bits2;
|
||||
u8 hair_type;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 3, u8> hair_color;
|
||||
BitField<3, 1, u8> hair_flip;
|
||||
} appearance_bits3;
|
||||
union {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 6, u32> eye_type;
|
||||
BitField<6, 3, u32> eye_color;
|
||||
BitField<9, 4, u32> eye_scale;
|
||||
BitField<13, 3, u32> eye_aspect;
|
||||
BitField<16, 5, u32> eye_rotate;
|
||||
BitField<21, 4, u32> eye_x;
|
||||
BitField<25, 5, u32> eye_y;
|
||||
} appearance_bits4;
|
||||
union {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 5, u32> eyebrow_type;
|
||||
BitField<5, 3, u32> eyebrow_color;
|
||||
BitField<8, 4, u32> eyebrow_scale;
|
||||
BitField<12, 3, u32> eyebrow_aspect;
|
||||
BitField<16, 4, u32> eyebrow_rotate;
|
||||
BitField<21, 4, u32> eyebrow_x;
|
||||
BitField<25, 5, u32> eyebrow_y;
|
||||
} appearance_bits5;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 5, u16> nose_type;
|
||||
BitField<5, 4, u16> nose_scale;
|
||||
BitField<9, 5, u16> nose_y;
|
||||
} appearance_bits6;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 6, u16> mouth_type;
|
||||
BitField<6, 3, u16> mouth_color;
|
||||
BitField<9, 4, u16> mouth_scale;
|
||||
BitField<13, 3, u16> mouth_aspect;
|
||||
} appearance_bits7;
|
||||
union {
|
||||
u8 raw;
|
||||
|
||||
BitField<0, 5, u8> mouth_y;
|
||||
BitField<5, 3, u8> mustache_type;
|
||||
} appearance_bits8;
|
||||
u8 allow_copying;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 3, u16> beard_type;
|
||||
BitField<3, 3, u16> beard_color;
|
||||
BitField<6, 4, u16> mustache_scale;
|
||||
BitField<10, 5, u16> mustache_y;
|
||||
} appearance_bits9;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 4, u16> glass_type;
|
||||
BitField<4, 3, u16> glass_color;
|
||||
BitField<7, 4, u16> glass_scale;
|
||||
BitField<11, 5, u16> glass_y;
|
||||
} appearance_bits10;
|
||||
union {
|
||||
u16 raw;
|
||||
|
||||
BitField<0, 1, u16> mole_type;
|
||||
BitField<1, 4, u16> mole_scale;
|
||||
BitField<5, 5, u16> mole_x;
|
||||
BitField<10, 5, u16> mole_y;
|
||||
} appearance_bits11;
|
||||
|
||||
Nickname author_name;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
u16_be crc;
|
||||
};
|
||||
static_assert(sizeof(Ver3StoreData) == 0x60, "Ver3StoreData is an invalid size");
|
||||
#pragma pack(pop)
|
||||
|
||||
}; // namespace Service::Mii
|
@ -28,7 +28,6 @@
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/nfc/common/amiibo_crypto.h"
|
||||
#include "core/hle/service/nfc/common/device.h"
|
||||
#include "core/hle/service/nfc/mifare_result.h"
|
||||
@ -681,12 +680,16 @@ Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
|
||||
return ResultRegistrationIsNotInitialized;
|
||||
}
|
||||
|
||||
Service::Mii::MiiManager manager;
|
||||
Mii::CharInfo char_info{};
|
||||
Mii::StoreData store_data{};
|
||||
tag_data.owner_mii.BuildToStoreData(store_data);
|
||||
char_info.SetFromStoreData(store_data);
|
||||
|
||||
const auto& settings = tag_data.settings;
|
||||
|
||||
// TODO: Validate this data
|
||||
register_info = {
|
||||
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
|
||||
.mii_char_info = char_info,
|
||||
.creation_date = settings.init_date.GetWriteDate(),
|
||||
.amiibo_name = GetAmiiboName(settings),
|
||||
.font_region = settings.settings.font_region,
|
||||
@ -825,8 +828,11 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
|
||||
return ResultWrongDeviceState;
|
||||
}
|
||||
|
||||
Service::Mii::MiiManager manager;
|
||||
const auto mii = manager.BuildBase(Mii::Gender::Male);
|
||||
Service::Mii::StoreData store_data{};
|
||||
Service::Mii::NfpStoreDataExtension extension{};
|
||||
store_data.BuildBase(Mii::Gender::Male);
|
||||
extension.SetFromStoreData(store_data);
|
||||
|
||||
auto& settings = tag_data.settings;
|
||||
|
||||
if (tag_data.settings.settings.amiibo_initialized == 0) {
|
||||
@ -835,8 +841,8 @@ Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& registe
|
||||
}
|
||||
|
||||
SetAmiiboName(settings, register_info.amiibo_name);
|
||||
tag_data.owner_mii = manager.BuildFromStoreData(mii);
|
||||
tag_data.mii_extension = manager.SetFromStoreData(mii);
|
||||
tag_data.owner_mii.BuildFromStoreData(store_data);
|
||||
tag_data.mii_extension = extension;
|
||||
tag_data.unknown = 0;
|
||||
tag_data.unknown2 = {};
|
||||
settings.country_code_id = 0;
|
||||
@ -868,17 +874,19 @@ Result NfcDevice::RestoreAmiibo() {
|
||||
}
|
||||
|
||||
Result NfcDevice::Format() {
|
||||
auto result1 = DeleteApplicationArea();
|
||||
auto result2 = DeleteRegisterInfo();
|
||||
Result result = ResultSuccess;
|
||||
|
||||
if (result1.IsError()) {
|
||||
return result1;
|
||||
if (device_state == DeviceState::TagFound) {
|
||||
result = Mount(NFP::ModelType::Amiibo, NFP::MountTarget::All);
|
||||
}
|
||||
|
||||
if (result2.IsError()) {
|
||||
return result2;
|
||||
if (result.IsError()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
DeleteApplicationArea();
|
||||
DeleteRegisterInfo();
|
||||
|
||||
return Flush();
|
||||
}
|
||||
|
||||
@ -1453,7 +1461,7 @@ void NfcDevice::UpdateRegisterInfoCrc() {
|
||||
|
||||
void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
|
||||
const NFP::EncryptedNTAG215File& encrypted_file) const {
|
||||
Service::Mii::MiiManager manager;
|
||||
Service::Mii::StoreData store_data{};
|
||||
auto& settings = stubbed_tag_data.settings;
|
||||
|
||||
stubbed_tag_data = NFP::AmiiboCrypto::NfcDataToEncodedData(encrypted_file);
|
||||
@ -1467,7 +1475,8 @@ void NfcDevice::BuildAmiiboWithoutKeys(NFP::NTAG215File& stubbed_tag_data,
|
||||
SetAmiiboName(settings, {'y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o'});
|
||||
settings.settings.font_region.Assign(0);
|
||||
settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
|
||||
stubbed_tag_data.owner_mii = manager.BuildFromStoreData(manager.BuildBase(Mii::Gender::Male));
|
||||
store_data.BuildBase(Mii::Gender::Male);
|
||||
stubbed_tag_data.owner_mii.BuildFromStoreData(store_data);
|
||||
|
||||
// Admin info
|
||||
settings.settings.amiibo_initialized.Assign(1);
|
||||
|
@ -6,7 +6,9 @@
|
||||
#include <array>
|
||||
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/mii/types/char_info.h"
|
||||
#include "core/hle/service/mii/types/store_data.h"
|
||||
#include "core/hle/service/mii/types/ver3_store_data.h"
|
||||
#include "core/hle/service/nfc/nfc_types.h"
|
||||
|
||||
namespace Service::NFP {
|
||||
@ -322,7 +324,7 @@ static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
|
||||
|
||||
// This is nn::nfp::RegisterInfoPrivate
|
||||
struct RegisterInfoPrivate {
|
||||
Service::Mii::MiiStoreData mii_store_data;
|
||||
Service::Mii::StoreData mii_store_data;
|
||||
WriteDate creation_date;
|
||||
AmiiboName amiibo_name;
|
||||
u8 font_region;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user