package main import ( "fmt" "net/url" "os" "os/exec" "path/filepath" "strings" "unsafe" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) const ( protocolName = "a301" 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 { // 더블클릭 실행 시 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] switch { case arg == "install": if err := install(); err != nil { msgBox("A301 런처 - 오류", fmt.Sprintf("등록 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } msgBox("A301 런처", "a301:// 프로토콜이 등록되었습니다.", mbOK|mbIconInfo) case arg == "uninstall": if err := uninstall(); err != nil { msgBox("A301 런처 - 오류", fmt.Sprintf("제거 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } msgBox("A301 런처", "a301:// 프로토콜이 제거되었습니다.", mbOK|mbIconInfo) case strings.HasPrefix(arg, protocolName+"://"): if err := handleURI(arg); err != nil { msgBox("A301 런처 - 오류", fmt.Sprintf("실행 실패:\n%v", err), mbOK|mbIconError) os.Exit(1) } default: msgBox("A301 런처 - 오류", fmt.Sprintf("알 수 없는 명령: %s", arg), mbOK|mbIconError) os.Exit(1) } } func launcherPath() (string, error) { exe, err := os.Executable() if err != nil { return "", err } return filepath.Abs(exe) } func install() error { exePath, err := launcherPath() if err != nil { return fmt.Errorf("실행 파일 경로를 찾을 수 없습니다: %w", err) } key, _, err := registry.CreateKey( registry.CURRENT_USER, `Software\Classes\`+protocolName, registry.SET_VALUE, ) if err != nil { return fmt.Errorf("레지스트리 키 생성 실패: %w", err) } defer key.Close() if err := key.SetStringValue("", "URL:A301 Protocol"); err != nil { return err } if err := key.SetStringValue("URL Protocol", ""); err != nil { return err } cmdKey, _, err := registry.CreateKey( registry.CURRENT_USER, `Software\Classes\`+protocolName+`\shell\open\command`, registry.SET_VALUE, ) if err != nil { return fmt.Errorf("command 키 생성 실패: %w", err) } defer cmdKey.Close() cmdValue := fmt.Sprintf(`"%s" "%%1"`, exePath) return cmdKey.SetStringValue("", cmdValue) } func uninstall() error { paths := []string{ `Software\Classes\` + protocolName + `\shell\open\command`, `Software\Classes\` + protocolName + `\shell\open`, `Software\Classes\` + protocolName + `\shell`, `Software\Classes\` + protocolName, } for _, p := range paths { err := registry.DeleteKey(registry.CURRENT_USER, p) if err != nil && err != registry.ErrNotExist { return err } } return nil } func handleURI(rawURI string) error { parsed, err := url.Parse(rawURI) if err != nil { return fmt.Errorf("URI 파싱 실패: %w", err) } token := parsed.Query().Get("token") if token == "" { return fmt.Errorf("토큰이 없습니다") } exePath, err := launcherPath() if err != nil { return err } gameDir := filepath.Dir(exePath) gamePath := filepath.Join(gameDir, gameExeName) if _, err := os.Stat(gamePath); os.IsNotExist(err) { return fmt.Errorf("게임 파일을 찾을 수 없습니다:\n%s", gamePath) } cmd := exec.Command(gamePath, "-token", token) cmd.Dir = gameDir if err := cmd.Start(); err != nil { return fmt.Errorf("게임 실행 실패: %w", err) } return nil }