diff --git a/internal/winamp/winamp.go b/internal/winamp/winamp.go index 06c2aa2..a351848 100644 --- a/internal/winamp/winamp.go +++ b/internal/winamp/winamp.go @@ -21,6 +21,7 @@ var ( getWindowThreadProcessId = user32.NewProc("GetWindowThreadProcessId") openProcess = kernel32.NewProc("OpenProcess") readProcessMemory = kernel32.NewProc("ReadProcessMemory") + multiByteToWideChar = kernel32.NewProc("MultiByteToWideChar") ) const ( @@ -238,26 +239,58 @@ type TrackInfo struct { Title string `json:"title"` } -// readRemoteString reads a null-terminated ANSI string from another process. +// readRemoteString reads a null-terminated ANSI string from another process +// and converts it to UTF-8 via MultiByteToWideChar (CP_ACP = 0) so that +// umlauts and other non-ASCII characters are handled correctly regardless of +// the active Windows code page. func readRemoteString(proc syscall.Handle, ptr uintptr) string { if ptr == 0 { return "" } buf := make([]byte, 512) var read uintptr - readProcessMemory.Call( + ok, _, _ := readProcessMemory.Call( uintptr(proc), ptr, uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)), uintptr(unsafe.Pointer(&read)), ) - for i, b := range buf { + if ok == 0 || read == 0 { + return "" + } + + // Trim to null terminator. + ansi := buf[:read] + for i, b := range ansi { if b == 0 { - return string(buf[:i]) + ansi = ansi[:i] + break } } - return string(buf) + if len(ansi) == 0 { + return "" + } + + // First call: query required UTF-16 buffer size. + const cpACP = 0 + n, _, _ := multiByteToWideChar.Call( + cpACP, 0, + uintptr(unsafe.Pointer(&ansi[0])), uintptr(len(ansi)), + 0, 0, + ) + if n == 0 { + return string(ansi) // fallback: treat as latin-1 + } + + // Second call: do the conversion. + wide := make([]uint16, n) + multiByteToWideChar.Call( + cpACP, 0, + uintptr(unsafe.Pointer(&ansi[0])), uintptr(len(ansi)), + uintptr(unsafe.Pointer(&wide[0])), n, + ) + return syscall.UTF16ToString(wide) } // GetPlaylist returns all titles in the current Winamp playlist.