feat: 项目初始化 + 3D方块世界原型 + AI助搭系统
CI / Go Backend (push) Canceled after 0s

初始化 monorepo: Go后端(7微服务) + Unity客户端(9模块) + 启动器

HTML5原型: Three.js 3D体素世界, Perlin噪声地形, 原版材质, 22种方块

Minecraft创造模式背包: 双栏布局, 拖拽移动物品, 方向性元件引脚

AI助搭策划文档 + 客户端/服务端骨架 + Docker Compose + CI
This commit is contained in:
xyou
2026-08-08 14:07:56 +08:00
parent 9500c4c80a
commit f70b061d1a
1972 changed files with 159760 additions and 6 deletions
@@ -0,0 +1,54 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace PCL.Core.Utils.Encryption
{
[Obsolete("Do not use this AES mode for Encryption")]
public sealed class AesCbcProvider : IEncryptionProvider
{
public static AesCbcProvider Instance { get; } = new();
private const int SaltSize = 32;
private const int IvSize = 16;
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
using var aes = Aes.Create();
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
var salt = data[..SaltSize];
var iv = data[SaltSize..(SaltSize + IvSize)];
aes.IV = iv.ToArray();
if (data.Length < salt.Length + iv.Length)
{
throw new ArgumentException("AES-CBC: Can not decrypt data, the encrypted data is broken");
}
#pragma warning disable SYSLIB0041
using (var deriveBytes = new Rfc2898DeriveBytes(key.ToArray(), salt.ToArray(), 1000))
{
aes.Key = deriveBytes.GetBytes(aes.KeySize / 8);
}
#pragma warning restore SYSLIB0041
using var ret = new MemoryStream();
using var ms = new MemoryStream(data[(SaltSize + IvSize)..].ToArray());
using var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read);
cs.CopyTo(ret);
return ret.ToArray();
}
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
throw new NotSupportedException("You should no longer use AES-CBC as your encryption method");
}
public bool IsSupported { get => false; }
}
}
@@ -0,0 +1,56 @@
using System;
using System.Security.Cryptography;
namespace PCL.Core.Utils.Encryption
{
public class AesGcmProvider : IEncryptionProvider
{
public static AesGcmProvider Instance { get; } = new();
private const int NonceSize = 12; // 96 bits
private const int TagSize = 16; // 128 bits
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
byte[] nonce = new byte[NonceSize];
RandomNumberGenerator.Fill(nonce);
byte[] tag = new byte[TagSize];
byte[] ciphertext = new byte[data.Length];
using (var aesGcm = new AesGcm(key, TagSize))
{
aesGcm.Encrypt(nonce, data, ciphertext, tag);
}
// [Nonce(12)] [Tag(16)] [Ciphertext(n)]
byte[] result = new byte[NonceSize + TagSize + ciphertext.Length];
Buffer.BlockCopy(nonce, 0, result, 0, NonceSize);
Buffer.BlockCopy(tag, 0, result, NonceSize, TagSize);
Buffer.BlockCopy(ciphertext, 0, result, NonceSize + TagSize, ciphertext.Length);
return result;
}
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
if (data.Length < NonceSize + TagSize)
throw new ArgumentException("加密数据长度不足。");
ReadOnlySpan<byte> nonce = data.Slice(0, NonceSize);
ReadOnlySpan<byte> tag = data.Slice(NonceSize, TagSize);
ReadOnlySpan<byte> ciphertext = data.Slice(NonceSize + TagSize);
byte[] plaintext = new byte[ciphertext.Length];
using (var aesGcm = new AesGcm(key, TagSize))
{
aesGcm.Decrypt(nonce, ciphertext, tag, plaintext);
}
return plaintext;
}
public bool IsSupported { get => AesGcm.IsSupported; }
}
}
@@ -0,0 +1,84 @@
using System;
using System.Security.Cryptography;
namespace PCL.Core.Utils.Encryption;
public sealed class ChaCha20Poly1305Provider : IEncryptionProvider
{
public static ChaCha20Poly1305Provider Instance { get; } = new();
private const int NonceSize = 12; // 96-bit nonce for ChaCha20Poly1305
private const int TagSize = 16; // 128-bit authentication tag
private const int KeySize = 32; // 256-bit key
private const int SaltSize = 16; // 128-bit salt for HKDF
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
// Generate random salt, nonce and the tag
var salt = new byte[SaltSize];
var nonce = new byte[NonceSize];
var tag = new byte[TagSize];
RandomNumberGenerator.Fill(salt);
RandomNumberGenerator.Fill(nonce);
RandomNumberGenerator.Fill(tag);
// Derive key using the salt
Span<byte> outputKey = stackalloc byte[KeySize];
_DeriveKey(key, salt, outputKey);
using var chacha = new ChaCha20Poly1305(outputKey);
// Prepare output arrays
var ciphertext = new byte[data.Length];
// Perform encryption
chacha.Encrypt(nonce, data, ciphertext, tag);
// Make the encryption data: salt + nonce + tag + ciphertext
var result = new byte[SaltSize + NonceSize + ciphertext.Length + TagSize];
var resultSpan = result.AsSpan();
salt.CopyTo(resultSpan[..SaltSize]);
nonce.CopyTo(resultSpan.Slice(SaltSize, NonceSize));
tag.CopyTo(resultSpan.Slice(SaltSize + NonceSize, TagSize));
ciphertext.CopyTo(resultSpan.Slice(SaltSize + NonceSize + TagSize, ciphertext.Length));
return result;
}
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
// Verify minimum data length
if (data.Length < SaltSize + NonceSize + TagSize)
throw new ArgumentException("Invalid encrypted data length");
// Encryption data: salt + nonce + tag + ciphertext
var salt = data[..SaltSize];
var nonce = data.Slice(SaltSize, NonceSize);
var tag = data.Slice(SaltSize + NonceSize, TagSize);
var ciphertext = data[(SaltSize + NonceSize + TagSize)..];
// Derive key using the extracted salt
Span<byte> outputKey = stackalloc byte[KeySize];
_DeriveKey(key, salt, outputKey);
using var chacha = new ChaCha20Poly1305(outputKey);
// Perform decryption
var plaintext = new byte[ciphertext.Length];
chacha.Decrypt(nonce, ciphertext, tag, plaintext);
return plaintext;
}
private static readonly byte[] _Info = "PCL.Core.Utils.Encryption.ChaCha20"u8.ToArray();
private static void _DeriveKey(ReadOnlySpan<byte> ikm, ReadOnlySpan<byte> salt, Span<byte> outputKey)
{
HKDF.DeriveKey(
HashAlgorithmName.SHA256,
ikm,
outputKey,
salt,
_Info.AsSpan());
}
public bool IsSupported { get => ChaCha20Poly1305.IsSupported; }
}
@@ -0,0 +1,120 @@
using System;
using System.Buffers.Binary;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
namespace PCL.Core.Utils.Encryption;
public class ChaCha20SoftwareProvider : IEncryptionProvider
{
public static ChaCha20SoftwareProvider Instance { get; } = new();
// 常量:expand 32-byte k
private static ReadOnlySpan<uint> Sigma => new[] { 0x61707865u, 0x3320646eu, 0x79622d32u, 0x6b206574u };
public static bool IsSupported => true;
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
// 预留 12 字节 Nonce 空间
var result = new byte[data.Length + 12];
var nonce = result.AsSpan(0, 12);
// 生成随机 Nonce
RandomNumberGenerator.Fill(nonce);
_Process(data, result.AsSpan(12), key, nonce);
return result;
}
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key)
{
if (data.Length < 12) throw new ArgumentException("数据长度不足以包含 Nonce");
var nonce = data[..12];
var ciphertext = data[12..];
var plaintext = new byte[ciphertext.Length];
_Process(ciphertext, plaintext, key, nonce);
return plaintext;
}
private static void _Process(ReadOnlySpan<byte> input, Span<byte> output, ReadOnlySpan<byte> key, ReadOnlySpan<byte> nonce)
{
if (key.Length != 32) throw new ArgumentException("Key 必须为 32 字节");
if (nonce.Length != 12) throw new ArgumentException("Nonce 必须为 12 字节");
// 在栈上分配状态矩阵和工作块,避免 GC
Span<uint> state = stackalloc uint[16];
// 1. 初始化状态
Sigma.CopyTo(state[..4]);
for (var i = 0; i < 8; i++)
{
state[4 + i] = BinaryPrimitives.ReadUInt32LittleEndian(key.Slice(i * 4, 4));
}
state[12] = 0; // 计数器初始值
state[13] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[..4]);
state[14] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[4..8]);
state[15] = BinaryPrimitives.ReadUInt32LittleEndian(nonce[8..12]);
Span<uint> workingBlock = stackalloc uint[16];
var offset = 0;
var length = input.Length;
while (offset < length)
{
_GenerateBlock(workingBlock, state);
state[12]++; // 递增计数器
var remaining = Math.Min(64, length - offset);
var inputPart = input.Slice(offset, remaining);
var outputPart = output.Slice(offset, remaining);
// 将 uint 块视为字节流进行异或
var blockBytes = MemoryMarshal.AsBytes(workingBlock);
for (var i = 0; i < remaining; i++)
{
outputPart[i] = (byte)(inputPart[i] ^ blockBytes[i]);
}
offset += 64;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void _GenerateBlock(Span<uint> x, ReadOnlySpan<uint> input)
{
input.CopyTo(x);
for (var i = 0; i < 10; i++) // 20 轮计算
{
// 列变换
_QuarterRound(x, 0, 4, 8, 12);
_QuarterRound(x, 1, 5, 9, 13);
_QuarterRound(x, 2, 6, 10, 14);
_QuarterRound(x, 3, 7, 11, 15);
// 对角线变换
_QuarterRound(x, 0, 5, 10, 15);
_QuarterRound(x, 1, 6, 11, 12);
_QuarterRound(x, 2, 7, 8, 13);
_QuarterRound(x, 3, 4, 9, 14);
}
for (var i = 0; i < 16; i++)
{
x[i] += input[i];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void _QuarterRound(Span<uint> x, int a, int b, int c, int d)
{
x[a] += x[b]; x[d] ^= x[a]; x[d] = BitOperations.RotateLeft(x[d], 16);
x[c] += x[d]; x[b] ^= x[c]; x[b] = BitOperations.RotateLeft(x[b], 12);
x[a] += x[b]; x[d] ^= x[a]; x[d] = BitOperations.RotateLeft(x[d], 8);
x[c] += x[d]; x[b] ^= x[c]; x[b] = BitOperations.RotateLeft(x[b], 7);
}
}
@@ -0,0 +1,11 @@
using System;
namespace PCL.Core.Utils.Encryption;
public interface IEncryptionProvider
{
public byte[] Encrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key);
public byte[] Decrypt(ReadOnlySpan<byte> data, ReadOnlySpan<byte> key);
public static bool IsSupported { get; }
}