// looksLikeName decides whether text read from the game's memory looks // like a real character name, and not binary garbage decoded as if it // were UTF-16. It lives apart from main.go (which is Windows-only) // because it's pure string logic: it can be tested without a PC with the // game open. package main import ( "unicode" "unicode/utf8" ) // charNameMaxChars: from the Cheat Engine table, "GameDataMan // +0C +9C, unicode, length 19". const charNameMaxChars = 19 // script identifies the writing-system family of a character in the name. type script int const ( scriptNone script = iota scriptLatin scriptCyrillic scriptHangul scriptCJK // Han, Hiragana, and Katakana count as a single family ) func scriptOf(r rune) script { switch { case unicode.In(r, unicode.Latin): return scriptLatin case unicode.In(r, unicode.Cyrillic): return scriptCyrillic case unicode.In(r, unicode.Hangul): return scriptHangul case unicode.In(r, unicode.Han, unicode.Hiragana, unicode.Katakana): return scriptCJK default: return scriptNone } } // looksLikeName guards against garbage. "Printable" isn't nearly enough: // a 64-bit pointer read as UTF-16 decodes into things like "ᾘ䱷翶", which // is printable and would pass a filter that only checked that. // // This used to require the Latin alphabet, which left out Chinese, // Korean, and Japanese names. Instead it requires SCRIPT COHERENCE: the // whole name has to come from a single family (Latin, Cyrillic, Hangul, // or Han+Hiragana+Katakana as one family, since Japanese mixes all three // in one name) plus digits/space/punctuation (which don't belong to any // particular family). Mixing families — like Greek + Han in "ᾘ䱷翶" — is // still rejected: that's exactly the signature of binary garbage. func looksLikeName(s string) bool { n := utf8.RuneCountInString(s) if n < 2 || n > charNameMaxChars { return false } sc := scriptNone hadLetter := false for i, r := range s { if r == utf8.RuneError || !unicode.IsPrint(r) { return false } if unicode.IsDigit(r) { continue } if r == ' ' || r == '-' || r == '_' || r == '.' || r == '\'' { if i == 0 { return false // a name doesn't start with punctuation } continue } f := scriptOf(r) if f == scriptNone { return false // script not accepted } if sc == scriptNone { sc = f } else if sc != f { return false // mixed families: the signature of binary garbage } hadLetter = true } return hadLetter }