From 5f463273c776c950df2368bed98c158ff39e5b03c12f808206de3195dd6bcee6 Mon Sep 17 00:00:00 2001 From: copecone Date: Tue, 23 Jun 2026 09:03:59 +0900 Subject: [PATCH] Add `CommonWarningDialogUI` and `RegistryUtil` to Support NVIDIA Driver Warning --- Core/FirstUpdateHook.cs | 43 +++++- Patch/Game/scnEditorPatch.cs | 13 +- RenderMod.cs | 2 + UI/CommonWarningDialogUI.cs | 292 +++++++++++++++++++++++++++++++++++ UI/FFmpegDownloadUI.cs | 11 +- Util/GPUUtil.cs | 60 ++++++- Util/RegistryUtil.cs | 128 +++++++++++++++ 7 files changed, 539 insertions(+), 10 deletions(-) create mode 100644 UI/CommonWarningDialogUI.cs create mode 100644 Util/RegistryUtil.cs diff --git a/Core/FirstUpdateHook.cs b/Core/FirstUpdateHook.cs index 33cb51b..dba8266 100644 --- a/Core/FirstUpdateHook.cs +++ b/Core/FirstUpdateHook.cs @@ -7,13 +7,28 @@ namespace ADOFAIRenderer.Core public class FirstUpdateHook : MonoBehaviour { private bool _checkedFFmpeg; + private bool _checkedNvidiaDriver; private void Update() { if (!_checkedFFmpeg) { + if (FFmpegDownloadUI.Instance) return; + + if (!FFmpegDownloadUI.CheckFFmpegExists()) + { + FFmpegDownloadUI.ShowDownloadUI(); + return; + } _checkedFFmpeg = true; - FFmpegDownloadUI.ShowDownloadUI(); + } + + if (!_checkedNvidiaDriver) + { + if (CommonWarningDialogUI.Instance) return; + + _checkedNvidiaDriver = true; + CheckNvidiaDriverWarning(); } if (RenderManager.Instance) @@ -21,5 +36,31 @@ namespace ADOFAIRenderer.Core RenderManager.Instance.OnPreRenderUpdate(); } } + + private void CheckNvidiaDriverWarning() + { + var driverVer = Util.GPUUtil.GetNvidiaDriverVersion(); + if (driverVer is null or >= 610.00f) return; + + var currentVersion = driverVer.Value.ToString("F2"); + + CommonWarningDialogUI.ShowDialog( + "NVIDIA 그래픽 드라이버 업데이트 권장", + $"현재 감지된 NVIDIA 그래픽 드라이버 버전: {currentVersion}\n\n하드웨어 가속(NVENC)을 사용하여 영상을 렌더링하려면 드라이버 버전 610.00 이상이 필요합니다.\n\n드라이버 버전이 낮으면 인코딩이 실패하거나 소프트웨어(CPU) 인코딩으로 대체되어 렌더링이 매우 느려집니다. 드라이버 업데이트를 진행해 주시기 바랍니다.", + new DialogButton("드라이버 업데이트", () => + { + Application.OpenURL("https://www.nvidia.com/Download/index.aspx"); + Application.Quit(); + }, new Color(0.0f, 0.8f, 1.0f, 1.0f)), + new DialogButton("기능 비활성화", () => + { + RenderMod.IsFeatureDisabled = true; + }), + new DialogButton("이번만 그냥 진행", () => + { + // 아무 동작 안 함 + }) + ); + } } } \ No newline at end of file diff --git a/Patch/Game/scnEditorPatch.cs b/Patch/Game/scnEditorPatch.cs index bbc0e77..d73286e 100644 --- a/Patch/Game/scnEditorPatch.cs +++ b/Patch/Game/scnEditorPatch.cs @@ -14,6 +14,8 @@ namespace ADOFAIRenderer.Patch.Game // ReSharper disable once InconsistentNaming public static void AddButtonRender(scnEditor __instance) { + if (RenderMod.IsFeatureDisabled) return; + var cloneTarget = __instance.buttonExit!.gameObject; var buttonContainer = cloneTarget.transform.parent!; @@ -33,12 +35,11 @@ namespace ADOFAIRenderer.Patch.Game // ReSharper disable once Unity.UnknownResource // images[0] is the background on the root, images[1] is the icon on the child var sprite = Resources.Load("LevelEditor/LevelEvents/MoveCamera"); - if (sprite != null) - { - var iconImage = images[1]; - iconImage.sprite = sprite; - iconImage.rectTransform.sizeDelta = new Vector2(24, 24); - } + if (sprite == null) return; + + var iconImage = images[1]; + iconImage.sprite = sprite; + iconImage.rectTransform.sizeDelta = new Vector2(24, 24); } } } \ No newline at end of file diff --git a/RenderMod.cs b/RenderMod.cs index 4b7e5df..5b3db62 100644 --- a/RenderMod.cs +++ b/RenderMod.cs @@ -17,8 +17,10 @@ namespace ADOFAIRenderer public static AssetBundle FontBundle = null!; // ReSharper disable once MemberCanBePrivate.Global public static AssetBundle SceneBundle = null!; + // ReSharper disable once MemberCanBePrivate.Global public static AssetBundle ShaderBundle = null!; public static ComputeShader ColorConversionShader = null!; + public static bool IsFeatureDisabled = false; public override void OnInitializeMelon() { diff --git a/UI/CommonWarningDialogUI.cs b/UI/CommonWarningDialogUI.cs new file mode 100644 index 0000000..db63372 --- /dev/null +++ b/UI/CommonWarningDialogUI.cs @@ -0,0 +1,292 @@ +using System; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace ADOFAIRenderer.UI +{ + public class DialogButton + { + public string Text; + public Action OnClick; + public Color? BackgroundColor; + + public DialogButton(string text, Action onClick, Color? backgroundColor = null) + { + Text = text; + OnClick = onClick; + BackgroundColor = backgroundColor; + } + } + + public class CommonWarningDialogUI : MonoBehaviour + { + public static CommonWarningDialogUI? Instance { get; private set; } + + private void Awake() + { + Instance = this; + } + + private void OnDestroy() + { + if (Instance == this) + { + Instance = null; + } + } + + private static TMP_FontAsset GetNanumSquareNeoBdFontAsset() + { + TMP_FontAsset? font = null; + if (RenderMod.FontBundle) + { + try + { + font = RenderMod.FontBundle.LoadAsset("NanumSquareNeoOTF-Bd SDF"); + } + catch + { + // 로딩 실패 + } + } + + // 폴백 처리 + if (!font) + { + try + { + font = RDString.fontData.fontTMP; + } + catch + { + // ignored + } + } + if (!font) + { + // ReSharper disable once Unity.UnknownResource + font = Resources.Load("Fonts & Materials/LiberationSans SDF"); + } + + if (font) return font; + var fonts = Resources.FindObjectsOfTypeAll(); + if (fonts.Length > 0) font = fonts[0]; + + return font!; + } + + public static void ShowDialog(string title, string description, params DialogButton[] buttons) + { + if (buttons.Length == 0) return; + + var obj = new GameObject("CommonWarningDialogUI"); + DontDestroyOnLoad(obj); + var dialog = obj.AddComponent(); + dialog.Initialize(title, description, buttons); + } + + private void Initialize(string title, string description, DialogButton[] buttons) + { + // UI 레이어(5) 설정 + gameObject.layer = 5; + + // Canvas 설정 + var canvas = gameObject.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvas.sortingOrder = 32766; // FFmpegDownloadUI(32767) 바로 아래 + + // 해상도 대응 캔버스 스케일러 설정 + var scaler = gameObject.AddComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + scaler.referenceResolution = new Vector2(1920, 1080); + scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight; + scaler.matchWidthOrHeight = 0.5f; + + gameObject.AddComponent(); + + // 1. 전체 화면 오버레이 배경 생성 + var bgPanelObj = new GameObject("BackgroundPanel"); + bgPanelObj.transform.SetParent(transform, false); + bgPanelObj.layer = 5; + + var bgImage = bgPanelObj.AddComponent(); + bgImage.color = new Color(0f, 0f, 0f, 0.65f); // 어두운 투명 오버레이 + bgImage.raycastTarget = true; + + var bgRect = bgPanelObj.GetComponent(); + bgRect.anchorMin = Vector2.zero; + bgRect.anchorMax = Vector2.one; + bgRect.offsetMin = Vector2.zero; + bgRect.offsetMax = Vector2.zero; + + var font = GetNanumSquareNeoBdFontAsset(); + + // 2. 모달 컨테이너 생성 + var modalContainerObj = new GameObject("ModalContainer"); + modalContainerObj.transform.SetParent(bgPanelObj.transform, false); + modalContainerObj.layer = 5; + + var modalImage = modalContainerObj.AddComponent(); + modalImage.color = new Color(0.08f, 0.08f, 0.1f, 0.88f); // 기존 UI의 배경 톤과 동일 + + var modalOutline = modalContainerObj.AddComponent(); + modalOutline.effectColor = Color.white; + modalOutline.effectDistance = new Vector2(3.0f, -3.0f); // 기존 UI 외곽선 스타일 일관성 + + var modalRect = modalContainerObj.GetComponent(); + modalRect.anchorMin = new Vector2(0.5f, 0.5f); + modalRect.anchorMax = new Vector2(0.5f, 0.5f); + modalRect.pivot = new Vector2(0.5f, 0.5f); + modalRect.sizeDelta = new Vector2(650, 360); + + // 3. 타이틀 텍스트 생성 + var titleObj = new GameObject("TitleText"); + titleObj.transform.SetParent(modalContainerObj.transform, false); + titleObj.layer = 5; + + var titleText = titleObj.AddComponent(); + titleText.text = title; + titleText.fontSize = 24; + titleText.font = font; + titleText.color = new Color(1.0f, 0.8f, 0.2f, 1.0f); // 경고 테마에 맞는 주황/황색 계열 + titleText.alignment = TextAlignmentOptions.Center; + + var titleRect = titleObj.GetComponent(); + titleRect.anchorMin = new Vector2(0, 1); + titleRect.anchorMax = new Vector2(1, 1); + titleRect.pivot = new Vector2(0.5f, 1); + titleRect.anchoredPosition = new Vector2(0, -35); + titleRect.sizeDelta = new Vector2(0, 40); + + // 4. 본문 설명 텍스트 생성 + var descObj = new GameObject("DescriptionText"); + descObj.transform.SetParent(modalContainerObj.transform, false); + descObj.layer = 5; + + var descText = descObj.AddComponent(); + descText.text = description; + descText.fontSize = 15; + descText.font = font; + descText.color = new Color(0.85f, 0.85f, 0.85f, 1f); + descText.alignment = TextAlignmentOptions.Center; + descText.lineSpacing = 8; + + var descRect = descObj.GetComponent(); + descRect.anchorMin = new Vector2(0, 0.5f); + descRect.anchorMax = new Vector2(1, 1); + descRect.pivot = new Vector2(0.5f, 0.5f); + descRect.anchoredPosition = new Vector2(0, -65); + descRect.sizeDelta = new Vector2(-40, 120); + + // 5. 버튼 컨테이너 생성 + var buttonContainerObj = new GameObject("ButtonContainer", typeof(RectTransform)); + buttonContainerObj.transform.SetParent(modalContainerObj.transform, false); + buttonContainerObj.layer = 5; + + var btnContainerRect = buttonContainerObj.GetComponent(); + btnContainerRect.anchorMin = new Vector2(0, 0); + btnContainerRect.anchorMax = new Vector2(1, 0); + btnContainerRect.pivot = new Vector2(0.5f, 0); + btnContainerRect.anchoredPosition = new Vector2(0, 35); + btnContainerRect.sizeDelta = new Vector2(0, 50); + + // 6. 가변 버튼 동적 배치 및 스타일링 + float totalWidth = 560f; + float spacing = 15f; + float buttonHeight = 45f; + int numButtons = buttons.Length; + + float buttonWidth = numButtons == 1 + ? 200f + : (totalWidth - (spacing * (numButtons - 1))) / numButtons; + + float startX = numButtons == 1 + ? 0 + : (-totalWidth / 2f) + (buttonWidth / 2f); + + for (int i = 0; i < numButtons; i++) + { + var btnData = buttons[i]; + var btnObj = new GameObject($"Button_{i}"); + btnObj.transform.SetParent(buttonContainerObj.transform, false); + btnObj.layer = 5; + + // 기본 색상 정의 + Color baseColor = btnData.BackgroundColor ?? new Color(0.2f, 0.2f, 0.23f, 1f); // 커스텀 색상 또는 회색톤 기본값 + Color hoverColor = ApplyColorBrightness(baseColor, 1.2f); + Color pressedColor = ApplyColorBrightness(baseColor, 0.8f); + + var btnImage = btnObj.AddComponent(); + btnImage.color = baseColor; + btnImage.raycastTarget = true; + + var btn = btnObj.AddComponent