diff --git a/main.go b/main.go index 761b9f5..f3b6f05 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,12 @@ package main import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" "fmt" + "io" + "net/http" "net/url" "os" "os/exec" @@ -14,22 +19,25 @@ import ( ) const ( - protocolName = "a301" - gameExeName = "A301.exe" + protocolName = "a301" + gameExeName = "A301.exe" + serverInfoURL = "https://a301.tolelom.xyz/api/download/info" + webDownloadURL = "https://a301.tolelom.xyz" ) var ( - user32 = windows.NewLazySystemDLL("user32.dll") - messageBoxW = user32.NewProc("MessageBoxW") + user32 = windows.NewLazySystemDLL("user32.dll") + messageBoxW = user32.NewProc("MessageBoxW") + shellExecuteW = windows.NewLazySystemDLL("shell32.dll").NewProc("ShellExecuteW") ) const ( - mbOK = 0x00000000 - mbIconInfo = 0x00000040 - mbIconError = 0x00000010 - mbYesNo = 0x00000004 + mbOK = 0x00000000 + mbIconInfo = 0x00000040 + mbIconError = 0x00000010 + mbYesNo = 0x00000004 mbIconQuestion = 0x00000020 - idYes = 6 + idYes = 6 ) func msgBox(title, text string, flags uintptr) int { @@ -44,9 +52,14 @@ func msgBox(title, text string, flags uintptr) int { return int(ret) } +func openBrowser(rawURL string) { + u, _ := windows.UTF16PtrFromString(rawURL) + op, _ := windows.UTF16PtrFromString("open") + shellExecuteW.Call(0, uintptr(unsafe.Pointer(op)), uintptr(unsafe.Pointer(u)), 0, 0, 1) +} + func main() { if len(os.Args) < 2 { - // 더블클릭 실행 시 install 안내 후 자동 설치 ret := msgBox( "A301 런처", "게임 실행을 위해 프로토콜을 등록합니다.\n계속하시겠습니까?", @@ -153,6 +166,39 @@ func uninstall() error { return nil } +type downloadInfo struct { + FileHash string `json:"fileHash"` + URL string `json:"url"` +} + +func fetchServerInfo() (*downloadInfo, error) { + resp, err := http.Get(serverInfoURL) + if err != nil { + return nil, fmt.Errorf("서버 연결 실패: %w", err) + } + defer resp.Body.Close() + + var info downloadInfo + if err := json.NewDecoder(resp.Body).Decode(&info); err != nil { + return nil, fmt.Errorf("서버 응답 파싱 실패: %w", err) + } + return &info, nil +} + +func hashFile(path string) (string, error) { + f, err := os.Open(path) + if err != nil { + return "", err + } + defer f.Close() + + h := sha256.New() + if _, err := io.Copy(h, f); err != nil { + return "", err + } + return hex.EncodeToString(h.Sum(nil)), nil +} + func handleURI(rawURI string) error { parsed, err := url.Parse(rawURI) if err != nil { @@ -175,6 +221,32 @@ func handleURI(rawURI string) error { return fmt.Errorf("게임 파일을 찾을 수 없습니다:\n%s", gamePath) } + // 서버에서 최신 해시 조회 + serverInfo, err := fetchServerInfo() + if err != nil { + return fmt.Errorf("버전 확인 실패:\n%w", err) + } + + // 서버에 해시가 등록된 경우에만 검증 + if serverInfo.FileHash != "" { + localHash, err := hashFile(gamePath) + if err != nil { + return fmt.Errorf("게임 파일 검증 실패:\n%w", err) + } + + if !strings.EqualFold(localHash, serverInfo.FileHash) { + ret := msgBox( + "A301 - 업데이트 필요", + "새로운 버전의 게임이 있습니다.\n최신 버전을 다운로드해주세요.\n\n확인을 누르면 다운로드 페이지로 이동합니다.", + mbOK|mbIconInfo, + ) + if ret > 0 { + openBrowser(webDownloadURL) + } + return fmt.Errorf("버전이 최신이 아닙니다") + } + } + cmd := exec.Command(gamePath, "-token", token) cmd.Dir = gameDir if err := cmd.Start(); err != nil {