Files
a301_launcher/main.go
tolelom 861796fe5a feat: 더블클릭 시 MessageBox로 프로토콜 등록 안내
- 인자 없이 실행(더블클릭) 시 install 확인 팝업 표시
- 모든 결과/에러를 Windows MessageBox로 표시
- -H windowsgui 빌드 플래그로 콘솔창 표시 제거

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-24 22:15:54 +09:00

186 lines
4.3 KiB
Go

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
}