mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Add SJISToUTF8Lossy() that wraps SJISToUTF8() and logs decode errors at slog.Debug level. Replace all 31 call sites across 17 files that previously discarded the error with `_, _ =`. This makes garbled text from malformed SJIS client data debuggable without adding noise at default log levels.
173 lines
4.1 KiB
Go
173 lines
4.1 KiB
Go
package stringsupport
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/text/encoding/japanese"
|
|
"golang.org/x/text/transform"
|
|
)
|
|
|
|
// UTF8ToSJIS encodes a UTF-8 string to Shift-JIS bytes, silently dropping any
|
|
// runes that cannot be represented in Shift-JIS.
|
|
func UTF8ToSJIS(x string) []byte {
|
|
e := japanese.ShiftJIS.NewEncoder()
|
|
xt, _, err := transform.String(e, x)
|
|
if err != nil {
|
|
// Filter out runes that can't be encoded to Shift-JIS instead of
|
|
// crashing the server (see PR #116).
|
|
var filtered []rune
|
|
for _, r := range x {
|
|
if _, _, err := transform.String(japanese.ShiftJIS.NewEncoder(), string(r)); err == nil {
|
|
filtered = append(filtered, r)
|
|
}
|
|
}
|
|
xt, _, _ = transform.String(japanese.ShiftJIS.NewEncoder(), string(filtered))
|
|
}
|
|
return []byte(xt)
|
|
}
|
|
|
|
// SJISToUTF8 decodes Shift-JIS bytes to a UTF-8 string.
|
|
func SJISToUTF8(b []byte) (string, error) {
|
|
d := japanese.ShiftJIS.NewDecoder()
|
|
result, err := io.ReadAll(transform.NewReader(bytes.NewReader(b), d))
|
|
if err != nil {
|
|
return "", fmt.Errorf("ShiftJIS decode: %w", err)
|
|
}
|
|
return string(result), nil
|
|
}
|
|
|
|
// SJISToUTF8Lossy decodes Shift-JIS bytes to a UTF-8 string, logging
|
|
// any decoding error at debug level instead of returning it.
|
|
func SJISToUTF8Lossy(b []byte) string {
|
|
s, err := SJISToUTF8(b)
|
|
if err != nil {
|
|
slog.Debug("SJIS decode failed", "error", err, "raw_len", len(b))
|
|
}
|
|
return s
|
|
}
|
|
|
|
// ToNGWord converts a UTF-8 string into a slice of uint16 values in the
|
|
// Shift-JIS byte-swapped format used by the MHF NG-word (chat filter) system.
|
|
func ToNGWord(x string) []uint16 {
|
|
var w []uint16
|
|
for _, r := range x {
|
|
if r > 0xFF {
|
|
t := UTF8ToSJIS(string(r))
|
|
if len(t) > 1 {
|
|
w = append(w, uint16(t[1])<<8|uint16(t[0]))
|
|
} else if len(t) == 1 {
|
|
w = append(w, uint16(t[0]))
|
|
}
|
|
// Skip runes that produced no SJIS output (unsupported characters)
|
|
} else {
|
|
w = append(w, uint16(r))
|
|
}
|
|
}
|
|
return w
|
|
}
|
|
|
|
// PaddedString returns a fixed-width null-terminated byte slice of the given
|
|
// size. If t is true the string is first encoded to Shift-JIS.
|
|
func PaddedString(x string, size uint, t bool) []byte {
|
|
if t {
|
|
e := japanese.ShiftJIS.NewEncoder()
|
|
xt, _, err := transform.String(e, x)
|
|
if err != nil {
|
|
return make([]byte, size)
|
|
}
|
|
x = xt
|
|
}
|
|
out := make([]byte, size)
|
|
copy(out, x)
|
|
out[len(out)-1] = 0
|
|
return out
|
|
}
|
|
|
|
// CSVAdd appends v to the comma-separated integer list if not already present.
|
|
func CSVAdd(csv string, v int) string {
|
|
if len(csv) == 0 {
|
|
return strconv.Itoa(v)
|
|
}
|
|
if CSVContains(csv, v) {
|
|
return csv
|
|
} else {
|
|
return csv + "," + strconv.Itoa(v)
|
|
}
|
|
}
|
|
|
|
// CSVRemove removes v from the comma-separated integer list.
|
|
func CSVRemove(csv string, v int) string {
|
|
s := strings.Split(csv, ",")
|
|
for i, e := range s {
|
|
if e == strconv.Itoa(v) {
|
|
s[i] = s[len(s)-1]
|
|
s = s[:len(s)-1]
|
|
}
|
|
}
|
|
return strings.Join(s, ",")
|
|
}
|
|
|
|
// CSVContains reports whether v is present in the comma-separated integer list.
|
|
func CSVContains(csv string, v int) bool {
|
|
s := strings.Split(csv, ",")
|
|
for i := 0; i < len(s); i++ {
|
|
j, _ := strconv.ParseInt(s[i], 10, 32)
|
|
if int(j) == v {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CSVLength returns the number of elements in the comma-separated list.
|
|
func CSVLength(csv string) int {
|
|
if csv == "" {
|
|
return 0
|
|
}
|
|
s := strings.Split(csv, ",")
|
|
return len(s)
|
|
}
|
|
|
|
// CSVElems parses the comma-separated integer list into an int slice.
|
|
func CSVElems(csv string) []int {
|
|
var r []int
|
|
if csv == "" {
|
|
return r
|
|
}
|
|
s := strings.Split(csv, ",")
|
|
for i := 0; i < len(s); i++ {
|
|
j, _ := strconv.ParseInt(s[i], 10, 32)
|
|
r = append(r, int(j))
|
|
}
|
|
return r
|
|
}
|
|
|
|
// CSVGetIndex returns the integer at position i in the comma-separated list,
|
|
// or 0 if i is out of range.
|
|
func CSVGetIndex(csv string, i int) int {
|
|
s := CSVElems(csv)
|
|
if i < len(s) {
|
|
return s[i]
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// CSVSetIndex replaces the integer at position i in the comma-separated list
|
|
// with v. If i is out of range the list is returned unchanged.
|
|
func CSVSetIndex(csv string, i int, v int) string {
|
|
s := CSVElems(csv)
|
|
if i < len(s) {
|
|
s[i] = v
|
|
}
|
|
var r []string
|
|
for j := 0; j < len(s); j++ {
|
|
r = append(r, fmt.Sprintf(`%d`, s[j]))
|
|
}
|
|
return strings.Join(r, ",")
|
|
}
|