初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器 HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块 Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚 AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace PCL.Core.Utils.Codecs;
|
||||
|
||||
public static class EncodingDetector
|
||||
{
|
||||
/// <summary>
|
||||
/// 检测流中的文本编码方式(支持 Seek 的流)
|
||||
/// </summary>
|
||||
/// <param name="stream">输入流,必须支持 Seek</param>
|
||||
/// <param name="readFromBegin">是否将流重置到起始点</param>
|
||||
/// <returns>检测到的编码,未识别时返回 UTF-8 或系统默认</returns>
|
||||
public static Encoding DetectEncoding(Stream stream, bool readFromBegin = false)
|
||||
{
|
||||
if (!stream.CanRead)
|
||||
throw new ArgumentException("流必须支持读操作");
|
||||
if (!stream.CanSeek)
|
||||
throw new ArgumentException("流必须支持 Seek 操作");
|
||||
|
||||
var originalPosition = stream.Position;
|
||||
if (readFromBegin) stream.Seek(0, SeekOrigin.Begin);
|
||||
|
||||
try
|
||||
{
|
||||
return _DetectByBom(stream, originalPosition) ?? _DetectWithoutBOM(stream, originalPosition) ?? Encoding.Default;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream.Position = originalPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public static Encoding DetectEncoding(byte[] bytes)
|
||||
{
|
||||
return DetectEncoding(new MemoryStream(bytes), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据 BOM 判断编码
|
||||
/// </summary>
|
||||
private static Encoding? _DetectByBom(Stream stream, long originalPosition)
|
||||
{
|
||||
stream.Position = originalPosition;
|
||||
// 获取最长样本长度
|
||||
var readableLength = stream.Length - stream.Position;
|
||||
var sampleLength = Math.Min(readableLength, 4);
|
||||
var buffer = new byte[sampleLength];
|
||||
var actualRead = stream.Read(buffer, 0, buffer.Length);
|
||||
if (actualRead != sampleLength) throw new Exception("无法获取样本长度");
|
||||
|
||||
// 对样本进行分析
|
||||
if (sampleLength >= 3 && buffer is [0xef, 0xbb, 0xbf])
|
||||
return Encoding.UTF8; // UTF-8
|
||||
|
||||
if (sampleLength >= 2)
|
||||
{
|
||||
if (buffer is [0xfe, 0xff])
|
||||
return Encoding.BigEndianUnicode; // UTF-16 BE
|
||||
if (buffer is [0xff, 0xfe])
|
||||
{
|
||||
if (sampleLength >= 4 && buffer is [_, _, 0x00, 0x00])
|
||||
return Encoding.UTF32; // UTF-32 LE
|
||||
return Encoding.Unicode; // UTF-16 LE
|
||||
}
|
||||
}
|
||||
|
||||
if (sampleLength >= 4)
|
||||
{
|
||||
if (buffer is [0x00, 0x00, 0xfe, 0xff])
|
||||
return Encoding.GetEncoding("utf-32BE"); // UTF-32 BE
|
||||
if (buffer is [0xff, 0xfe, 0x00, 0x00])
|
||||
return Encoding.UTF32; // UTF-32 LE
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// BOM 不存在时的备用检测策略
|
||||
/// </summary>
|
||||
private static Encoding? _DetectWithoutBOM(Stream stream, long originalPosition)
|
||||
{
|
||||
// 尝试验证是否为有效 UTF-8
|
||||
return _IsValidUtf8(stream, originalPosition) ? Encoding.UTF8 : null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证流内容是否为合法 UTF-8(通过 round-trip 验证)
|
||||
/// </summary>
|
||||
private static bool _IsValidUtf8(Stream stream, long originalPosition)
|
||||
{
|
||||
const int sampleSize = 1024;
|
||||
var buffer = new byte[sampleSize];
|
||||
stream.Position = originalPosition;
|
||||
|
||||
try
|
||||
{
|
||||
var decoded = Encoding.UTF8.GetString(buffer);
|
||||
var roundTrip = Encoding.UTF8.GetBytes(decoded);
|
||||
return roundTrip.SequenceEqual(buffer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
namespace PCL.Core.Utils.Codecs;
|
||||
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
public static class EncodingUtils {
|
||||
public static bool IsDefaultEncodingUtf8() => Encoding.Default.CodePage == 65001;
|
||||
|
||||
public static bool IsDefaultEncodingGbk() => Encoding.Default.CodePage == 936;
|
||||
|
||||
/// <summary>
|
||||
/// 解码字节数组为字符串,自动检测 BOM(UTF-8、UTF-16 LE/BE、UTF-32 LE/BE)或回退到 GB18030。
|
||||
/// </summary>
|
||||
/// <param name="bytes">要解码的字节数组。</param>
|
||||
/// <returns>解码后的字符串,失败时返回空字符串。</returns>
|
||||
public static string DecodeBytes(byte[] bytes) {
|
||||
if (bytes.Length == 0) return "";
|
||||
|
||||
// 使用 EncodingDetector 检测编码
|
||||
var encoding = EncodingDetector.DetectEncoding(bytes);
|
||||
|
||||
// 如果检测到有效编码(非 Encoding.Default),直接解码
|
||||
if (!encoding.Equals(Encoding.Default)) {
|
||||
ReadOnlySpan<byte> span = bytes.AsSpan();
|
||||
if (encoding.Equals(Encoding.UTF8) && span.Length >= 3 && span[0] == 0xEF && span[1] == 0xBB && span[2] == 0xBF) {
|
||||
return encoding.GetString(span[3..]); // 跳过 UTF-8 BOM
|
||||
}
|
||||
if (encoding.Equals(Encoding.BigEndianUnicode) && span.Length >= 2 && span[0] == 0xFE && span[1] == 0xFF) {
|
||||
return encoding.GetString(span[2..]); // 跳过 UTF-16 BE BOM
|
||||
}
|
||||
if (encoding.Equals(Encoding.Unicode) && span.Length >= 2 && span[0] == 0xFF && span[1] == 0xFE) {
|
||||
return encoding.GetString(span[2..]); // 跳过 UTF-16 LE BOM
|
||||
}
|
||||
if (encoding.Equals(Encoding.UTF32) && span.Length >= 4 && span[0] == 0xFF && span[1] == 0xFE && span[2] == 0x00 && span[3] == 0x00) {
|
||||
return encoding.GetString(span[4..]); // 跳过 UTF-32 LE BOM
|
||||
}
|
||||
if (encoding.CodePage == Encoding.GetEncoding("utf-32BE").CodePage && span.Length >= 4 && span[0] == 0x00 && span[1] == 0x00 && span[2] == 0xFE && span[3] == 0xFF) {
|
||||
return encoding.GetString(span[4..]); // 跳过 UTF-32 BE BOM
|
||||
}
|
||||
return encoding.GetString(span); // 无 BOM 或其他编码
|
||||
}
|
||||
|
||||
// 无 BOM 或检测为 Encoding.Default,尝试 UTF-8
|
||||
try {
|
||||
var utf8Result = Encoding.UTF8.GetString(bytes);
|
||||
return utf8Result.Contains('\uFFFD') ? Encodings.GB18030.GetString(bytes) : utf8Result; // 无效 UTF-8,回退到 GB18030
|
||||
} catch (DecoderFallbackException) {
|
||||
return Encodings.GB18030.GetString(bytes); // UTF-8 解码失败,回退到 GB18030
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
using System.Text;
|
||||
|
||||
namespace PCL.Core.Utils.Codecs;
|
||||
|
||||
public static class Encodings {
|
||||
public static readonly Encoding GB18030 = Encoding.GetEncoding("GB18030");
|
||||
}
|
||||
Reference in New Issue
Block a user