From 861796fe5ad74dac97625ce39e70bc35befa3908 Mon Sep 17 00:00:00 2001 From: tolelom <98kimsungmin@naver.com> Date: Tue, 24 Feb 2026 22:15:54 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=8D=94=EB=B8=94=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20MessageBox=EB=A1=9C=20=ED=94=84=EB=A1=9C=ED=86=A0?= =?UTF-8?q?=EC=BD=9C=20=EB=93=B1=EB=A1=9D=20=EC=95=88=EB=82=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 인자 없이 실행(더블클릭) 시 install 확인 팝업 표시 - 모든 결과/에러를 Windows MessageBox로 표시 - -H windowsgui 빌드 플래그로 콘솔창 표시 제거 Co-Authored-By: Claude Sonnet 4.6 --- main.go | 95 ++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 34 deletions(-) diff --git a/main.go b/main.go index 66e2cfc..761b9f5 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,9 @@ import ( "os/exec" "path/filepath" "strings" + "unsafe" + "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -16,13 +18,49 @@ const ( gameExeName = "A301.exe" ) +var ( + user32 = windows.NewLazySystemDLL("user32.dll") + messageBoxW = user32.NewProc("MessageBoxW") +) + +const ( + mbOK = 0x00000000 + mbIconInfo = 0x00000040 + mbIconError = 0x00000010 + mbYesNo = 0x00000004 + mbIconQuestion = 0x00000020 + idYes = 6 +) + +func msgBox(title, text string, flags uintptr) int { + t, _ := windows.UTF16PtrFromString(title) + m, _ := windows.UTF16PtrFromString(text) + ret, _, _ := messageBoxW.Call( + 0, + uintptr(unsafe.Pointer(m)), + uintptr(unsafe.Pointer(t)), + flags, + ) + return int(ret) +} + func main() { if len(os.Args) < 2 { - fmt.Println("사용법:") - fmt.Println(" launcher.exe install 프로토콜 등록") - fmt.Println(" launcher.exe uninstall 프로토콜 제거") - fmt.Println(" launcher.exe a301://... 게임 실행") - os.Exit(1) + // 더블클릭 실행 시 install 안내 후 자동 설치 + ret := msgBox( + "A301 런처", + "게임 실행을 위해 프로토콜을 등록합니다.\n계속하시겠습니까?", + mbYesNo|mbIconQuestion, + ) + if ret != idYes { + return + } + if err := install(); err != nil { + msgBox("A301 런처 - 오류", fmt.Sprintf("등록 실패:\n%v", err), mbOK|mbIconError) + os.Exit(1) + } + msgBox("A301 런처", "a301:// 프로토콜이 등록되었습니다.\n이제 웹에서 게임 시작 버튼을 사용할 수 있습니다.", mbOK|mbIconInfo) + return } arg := os.Args[1] @@ -30,26 +68,26 @@ func main() { switch { case arg == "install": if err := install(); err != nil { - fmt.Fprintf(os.Stderr, "설치 실패: %v\n", err) + msgBox("A301 런처 - 오류", fmt.Sprintf("등록 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } - fmt.Println("a301:// 프로토콜이 등록되었습니다.") + msgBox("A301 런처", "a301:// 프로토콜이 등록되었습니다.", mbOK|mbIconInfo) case arg == "uninstall": if err := uninstall(); err != nil { - fmt.Fprintf(os.Stderr, "제거 실패: %v\n", err) + msgBox("A301 런처 - 오류", fmt.Sprintf("제거 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } - fmt.Println("a301:// 프로토콜이 제거되었습니다.") + msgBox("A301 런처", "a301:// 프로토콜이 제거되었습니다.", mbOK|mbIconInfo) case strings.HasPrefix(arg, protocolName+"://"): if err := handleURI(arg); err != nil { - fmt.Fprintf(os.Stderr, "실행 실패: %v\n", err) + msgBox("A301 런처 - 오류", fmt.Sprintf("실행 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } default: - fmt.Fprintf(os.Stderr, "알 수 없는 명령: %s\n", arg) + msgBox("A301 런처 - 오류", fmt.Sprintf("알 수 없는 명령: %s", arg), mbOK|mbIconError) os.Exit(1) } } @@ -68,7 +106,6 @@ func install() error { return fmt.Errorf("실행 파일 경로를 찾을 수 없습니다: %w", err) } - // HKCU\Software\Classes\a301 key, _, err := registry.CreateKey( registry.CURRENT_USER, `Software\Classes\`+protocolName, @@ -86,7 +123,6 @@ func install() error { return err } - // shell\open\command cmdKey, _, err := registry.CreateKey( registry.CURRENT_USER, `Software\Classes\`+protocolName+`\shell\open\command`, @@ -98,29 +134,21 @@ func install() error { defer cmdKey.Close() cmdValue := fmt.Sprintf(`"%s" "%%1"`, exePath) - if err := cmdKey.SetStringValue("", cmdValue); err != nil { - return err - } - - return nil + return cmdKey.SetStringValue("", cmdValue) } func uninstall() error { - err := registry.DeleteKey(registry.CURRENT_USER, `Software\Classes\`+protocolName+`\shell\open\command`) - if err != nil && err != registry.ErrNotExist { - return err + paths := []string{ + `Software\Classes\` + protocolName + `\shell\open\command`, + `Software\Classes\` + protocolName + `\shell\open`, + `Software\Classes\` + protocolName + `\shell`, + `Software\Classes\` + protocolName, } - err = registry.DeleteKey(registry.CURRENT_USER, `Software\Classes\`+protocolName+`\shell\open`) - if err != nil && err != registry.ErrNotExist { - return err - } - err = registry.DeleteKey(registry.CURRENT_USER, `Software\Classes\`+protocolName+`\shell`) - if err != nil && err != registry.ErrNotExist { - return err - } - err = registry.DeleteKey(registry.CURRENT_USER, `Software\Classes\`+protocolName) - if err != nil && err != registry.ErrNotExist { - return err + for _, p := range paths { + err := registry.DeleteKey(registry.CURRENT_USER, p) + if err != nil && err != registry.ErrNotExist { + return err + } } return nil } @@ -144,7 +172,7 @@ func handleURI(rawURI string) error { gamePath := filepath.Join(gameDir, gameExeName) if _, err := os.Stat(gamePath); os.IsNotExist(err) { - return fmt.Errorf("게임 파일을 찾을 수 없습니다: %s", gamePath) + return fmt.Errorf("게임 파일을 찾을 수 없습니다:\n%s", gamePath) } cmd := exec.Command(gamePath, "-token", token) @@ -153,6 +181,5 @@ func handleURI(rawURI string) error { return fmt.Errorf("게임 실행 실패: %w", err) } - fmt.Printf("게임이 실행되었습니다 (PID: %d)\n", cmd.Process.Pid) return nil }