decodeBinaryValue($value); } public function setBinLetterAttribute($value) { // В Table_CharMail колонка binLetter хранится как VARBINARY, // поэтому передаём в UPDATE двоичные данные через HEX-литерал (0x...) // чтобы избежать имплицитного преобразования NVARCHAR -> VARBINARY. $encoded = $this->encodeBinaryValue($value); // ожидается двойной base64-ASCII // Сохраняем ASCII base64 как набор байт (varbinary) — это позволит // корректно прочитать значение и декодировать его в accessor'е. $hex = '0x' . bin2hex($encoded); $this->attributes['binLetter'] = DB::raw($hex); } public function getBinTitleAttribute($value) { return $this->decodeBinaryValue($value); } public function setBinTitleAttribute($value) { $this->attributes['binTitle'] = $this->encodeBinaryValue($value); } private function decodeBinaryValue(?string $value): ?string { // Удаляем завершающие нули – они часто встречаются в char( n ) столбцах $value = $this->stripNulls($value); if ($value === null || $value === '') { return $value; } // Пробуем декодировать максимум два раза (схема хранения – двойной base64) $decoded = $value; for ($i = 0; $i < 2; $i++) { $tmp = base64_decode($decoded, true); if ($tmp === false) { // Строка не похожа на base64 – прекращаем попытки break; } $decoded = $this->stripNulls($tmp); } return $decoded === null ? null : $this->ensureUtf8($decoded); } private function encodeBinaryValue(?string $value): ?string { $value = $this->stripNulls($value); if ($value === null || $value === '') { return $value; } // Если строка уже дважды закодирована – ничего не делаем if ($this->isBase64($value)) { $inner = base64_decode($value, true); if ($inner !== false && $this->isBase64($inner)) { return $value; // уже двойной base64 } // Если только один слой – добавляем ещё один return base64_encode($value); } // Конвертируем в CP1251 (игровой клиент ожидает именно эту кодировку) $prepared = @iconv('UTF-8', 'CP1251//IGNORE', $value); if ($prepared === false) { $prepared = $value; // если iconv не смог – используем как есть } // Двойное кодирование base64 return base64_encode(base64_encode($prepared)); } private function stripNulls(?string $value): ?string { return $value === null ? null : rtrim($value, "\0"); } // Более лояльная проверка base64 (без строгого ===, учитываем отсутствие отступов и символы \r\n) private function isBase64(string $value): bool { if ($value === '') { return false; } // Проверяем набор допустимых символов и кратность 4 if (preg_match('/^[A-Za-z0-9+\/\r\n]+=*$/', $value) !== 1) { return false; } return (strlen($value) % 4 === 0); } private function ensureUtf8(string $value): string { if ($value === '') { return ''; } if (mb_check_encoding($value, 'UTF-8')) { return $this->removeControlChars($value); } $candidates = []; if (function_exists('iconv')) { $candidates[] = fn () => @iconv('CP1251', 'UTF-8//IGNORE', $value); } if (function_exists('mb_convert_encoding')) { $candidates[] = fn () => @mb_convert_encoding($value, 'UTF-8', 'CP1251'); $candidates[] = fn () => @mb_convert_encoding($value, 'UTF-8', 'ISO-8859-1'); } $candidates[] = fn () => utf8_encode($value); foreach ($candidates as $candidate) { $converted = $candidate(); if (is_string($converted) && mb_check_encoding($converted, 'UTF-8')) { return $this->removeControlChars($converted); } } if (function_exists('iconv')) { $sanitized = @iconv('UTF-8', 'UTF-8//IGNORE', $value); if (is_string($sanitized) && mb_check_encoding($sanitized, 'UTF-8')) { return $this->removeControlChars($sanitized); } } return ''; } private function removeControlChars(string $value): string { $clean = preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $value); return is_string($clean) ? $clean : ''; } public function toArray() { $array = parent::toArray(); // Явно декодируем бинарные поля, на случай если accessor не сработал при сериализации if (array_key_exists('binTitle', $this->attributes)) { $array['binTitle'] = $this->decodeBinaryValue($this->attributes['binTitle']); } elseif (array_key_exists('binTitle', $array)) { $array['binTitle'] = $this->decodeBinaryValue($array['binTitle']); } if (array_key_exists('binLetter', $this->attributes)) { $array['binLetter'] = $this->decodeBinaryValue($this->attributes['binLetter']); } elseif (array_key_exists('binLetter', $array)) { $array['binLetter'] = $this->decodeBinaryValue($array['binLetter']); } array_walk_recursive($array, function (&$v) { if (is_string($v)) { $v = $this->ensureUtf8($v); } }); return $array; } }