Files
2026-02-22 20:03:42 +08:00

602 lines
22 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#if false
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.ExtensibleStorage;
using OpenMcdf;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
namespace ShrlAlgoToolkit.RevitCore.Assists;
public class PublishAssist
{
#region FileAttributes
/// <summary>
/// 设置文件为只读
/// </summary>
/// <param name="filePath"></param>
/// <param name="readOnly"></param>
public static void SetReadOnly(string filePath, bool readOnly = true)
{
try
{
if(File.Exists(filePath))
{
var info = new FileInfo(filePath);
info.IsReadOnly = readOnly;
}
else
{
Debug.WriteLine("文件不存在。");
}
} catch(UnauthorizedAccessException)
{
Debug.WriteLine("无权限修改文件属性。");
} catch(Exception ex)
{
Debug.WriteLine($"错误: {ex.Message}");
}
}
/// <summary>
/// 判断文件是否被加密
/// </summary>
/// <param name="filePath"></param>
/// <param name="tag"></param>
/// <returns></returns>
public static bool IsEncrypted(string filePath, string tag = "VmVyc2lvbiAxLjAgU1pNZWRpRGF0YQ==")
{
// 关键:用写入时相同的编码(通常 UTF-8 或 ASCII
var tagBytes = Encoding.UTF8.GetBytes(tag); // 或 Encoding.ASCII
var data = File.ReadAllBytes(filePath);
// 检查开头和结尾是否匹配
var startMatch = data.Length >= tagBytes.Length && ArrayEquals(data, 0, tagBytes, 0, tagBytes.Length);
var endMatch = data.Length >= tagBytes.Length * 2 &&
ArrayEquals(data, data.Length - tagBytes.Length, tagBytes, 0, tagBytes.Length);
return startMatch && endMatch;
}
static bool ArrayEquals(byte[] a, int aStart, byte[] b, int bStart, int length)
{
for(var i = 0; i < length; i++)
{
if(a[aStart + i] != b[bStart + i])
return false;
}
return true;
}
/// <summary>
/// 设置文件为只读
/// </summary>
/// <param name="filePath"></param>
public static void SetPrivate(string filePath)
{
try
{
if(File.Exists(filePath))
{
var info = new FileInfo(filePath);
File.SetAttributes(filePath, FileAttributes.Hidden | FileAttributes.System | FileAttributes.ReadOnly);
}
else
{
Debug.WriteLine("文件不存在。");
}
} catch(UnauthorizedAccessException)
{
Debug.WriteLine("无权限修改文件属性。");
} catch(Exception ex)
{
Debug.WriteLine($"错误: {ex.Message}");
}
}
#endregion
//需要与addin文件里的GUID保持一致
private static readonly Guid SchemaGuid = new Guid("A519E82B-911C-4CA5-9BC5-ED509B2055D2");
/// <summary>
/// 存储图片字节数据列表 (使用 Base64 编码)
/// </summary>
public static void StoreSignatureMap(Document doc, IDictionary<string, byte[]> signatureMap)
{
ProjectInfo projectInfo = doc.ProjectInformation;
if(projectInfo == null)
return;
Schema schema = GetOrCreateSchema();
Entity entity = projectInfo.GetEntity(schema);
if(entity == null || !entity.IsValid())
{
entity = new Entity(schema); // 如果没有,才新建
}
try
{
IDictionary<string, string> stringMap = signatureMap
.ToDictionary(kvp => kvp.Key, kvp => Convert.ToBase64String(kvp.Value));
entity.Set("SignatureMap", stringMap);
projectInfo.SetEntity(entity);
} catch(Exception ex)
{
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 读取签章图片字节数据列表 (使用 Base64 解码)
/// </summary>
public static IDictionary<string, byte[]> RetrieveSignatureMap(Document doc)
{
ProjectInfo projectInfo = doc.ProjectInformation;
var emptyDict = new Dictionary<string, byte[]>();
if(projectInfo == null)
return emptyDict;
Schema schema = Schema.Lookup(SchemaGuid);
if(schema == null)
return emptyDict;
Entity entity = projectInfo.GetEntity(schema);
if(entity == null || !entity.IsValid())
return emptyDict;
IDictionary<string, string> retrievedMap = entity.Get<IDictionary<string, string>>("SignatureMap");
if(retrievedMap == null)
return emptyDict;
return retrievedMap.ToDictionary(kvp => kvp.Key, kvp => Convert.FromBase64String(kvp.Value));
}
/// <summary>
/// 存储基本信息数据列表
/// </summary>
public static void StoreSignatureCommonMap(Document doc, IDictionary<string, string> signatureCommonMap)
{
ProjectInfo projectInfo = doc.ProjectInformation;
if(projectInfo == null)
return;
Schema schema = GetOrCreateSchema();
//每个元素只有一个实体
Entity entity = projectInfo.GetEntity(schema);
if(entity == null || !entity.IsValid())
{
entity = new Entity(schema); // 如果没有,才新建
}
try
{
entity.Set("SignatureCommonMap", signatureCommonMap);
projectInfo.SetEntity(entity);
} catch(Exception ex)
{
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// 读取基本信息数据列表
/// </summary>
public static IDictionary<string, string> RetrieveSignatureCommonMap(Document doc)
{
ProjectInfo projectInfo = doc.ProjectInformation;
var emptyDict = new Dictionary<string, string>();
if(projectInfo == null)
return emptyDict;
Schema schema = Schema.Lookup(SchemaGuid);
if(schema == null)
return emptyDict;
Entity entity = projectInfo.GetEntity(schema);
if(entity == null || !entity.IsValid())
return emptyDict;
IDictionary<string, string> retrievedMap = entity.Get<IDictionary<string, string>>("SignatureCommonMap");
if(retrievedMap == null)
return emptyDict;
return retrievedMap.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
/// <summary>
/// 获取或创建数据模式
/// </summary>
private static Schema GetOrCreateSchema()
{
Schema schema = Schema.Lookup(SchemaGuid);
if(schema != null)
return schema;
try
{
SchemaBuilder schemaBuilder = new SchemaBuilder(SchemaGuid);
schemaBuilder.SetReadAccessLevel(AccessLevel.Application);
schemaBuilder.SetWriteAccessLevel(AccessLevel.Application);
schemaBuilder.SetVendorId("SZMC");
schemaBuilder.SetApplicationGUID(SchemaGuid);
schemaBuilder.SetSchemaName("SignatureStorage");
// 添加 Map 字段用来存储图片字节数据列表
schemaBuilder.AddMapField("SignatureMap", typeof(string), typeof(string));
// 添加 Map 字段用来存储基本信息数据列表
schemaBuilder.AddMapField("SignatureCommonMap", typeof(string), typeof(string));
schema = schemaBuilder.Finish();
} catch(Exception ex)
{
MessageBox.Show(ex.Message, "创建schema错误");
}
return schema;
}
#region ExtensionsStorage
//private const string SchemaGuid = "{FBC67C54-781F-4D0A-B31B-8F54707889E0}";
///// <summary>
///// 计算hash256
///// </summary>
///// <param name="filePath"></param>
///// <returns></returns>
//public static string ComputeFileHash(string filePath)
//{
// using (SHA256 sha256 = SHA256.Create())
// using (FileStream stream = File.OpenRead(filePath))
// {
// byte[] hashBytes = sha256.ComputeHash(stream);
// return BitConverter.ToString(hashBytes).Replace("-", string.Empty).ToLower();
// }
//}
#endregion
#region PreviewSettings
/// <summary>
/// 完全替换缩略图
/// </summary>
/// <param name="rvtFilePath"></param>
/// <param name="imgPath"></param>
internal static void ReplaceThumb(string rvtFilePath, Bitmap bmp)
{
using var cf = new CompoundFile(rvtFilePath, CFSUpdateMode.Update, CFSConfiguration.Default);
// 获取根存储
CFStorage root = cf.RootStorage;
// 更新 Windows 预览
if(root.TryGetStream("RevitPreview4.0", out var previewStream))
{
using var outMs = new MemoryStream();
bmp.Save(outMs, ImageFormat.Png);
previewStream.SetData(outMs.ToArray());
}
// 更新 Revit 预览
if(root.TryGetStream("BasicFileInfo", out var basicFileInfo))
{
// 读取现有数据
byte[] data = basicFileInfo.GetData();
// 基本文件信息头部大小(版本等信息)
const int HEADER_SIZE = 0x2C8;
if(data.Length > HEADER_SIZE)
{
// 转换图片为字节数组
using(var ms = new MemoryStream())
{
// 转换为24位色
using(var temp = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format24bppRgb))
{
using(var g = Graphics.FromImage(temp))
{
g.DrawImage(bmp, 0, 0);
}
temp.Save(ms, ImageFormat.Bmp);
}
byte[] imageBytes = ms.ToArray();
// 跳过BMP文件头(14字节)和信息头(40字节)
int startIndex = 54;
// 计算新数据大小
int newDataSize = HEADER_SIZE + (imageBytes.Length - startIndex);
byte[] newData = new byte[newDataSize];
// 复制原有头部数据
Array.Copy(data, 0, newData, 0, HEADER_SIZE);
// 复制新的图像数据
Array.Copy(imageBytes, startIndex, newData, HEADER_SIZE, imageBytes.Length - startIndex);
// 写回流
basicFileInfo.SetData(newData);
}
}
}
cf.Commit();
}
/// <summary>
/// 合并图片
/// </summary>
/// <param name="thumbnail">缩略图</param>
/// <param name="overlay">图标</param>
/// <returns></returns>
internal static Bitmap CombineImages(Bitmap thumbnail, Bitmap overlay)
{
// 创建画布
Bitmap result = new Bitmap(thumbnail.Width, thumbnail.Height);
using(Graphics graphics = Graphics.FromImage(result))
{
// 绘制缩略图
graphics.DrawImage(thumbnail, 0, 0);
// 计算叠加位置(右下角)
int padding = 5;
int x = 0 + padding;
//int x = thumbnail.Width - overlay.Width - padding;
int y = thumbnail.Height - overlay.Height - padding;
// 保持图标原始大小或按比例缩放
int iconSize = 48;
var resizedOverlay = new Bitmap(overlay, iconSize, iconSize);
// 带透明度绘制
graphics.DrawImage(
resizedOverlay,
new System.Drawing.Rectangle(x, y, iconSize, iconSize),
new System.Drawing.Rectangle(0, 0, overlay.Width, overlay.Height),
GraphicsUnit.Pixel);
}
return result;
}
/// <summary>
/// 添加水印
/// </summary>
/// <param name="rvtFilePath"></param>
/// <param name="bmp"></param>
/// <param name="watermark"></param>
internal static void AddWatermark(string rvtFilePath, Bitmap bmp, string watermark)
{
// 打开 RVT 文件的复合文件结构
using var cf = new CompoundFile(rvtFilePath, CFSUpdateMode.Update, CFSConfiguration.Default);
// 获取根存储
CFStorage root = cf.RootStorage;
// 检查是否存在 "RevitPreview4.0" 流
if(!root.TryGetStream("RevitPreview4.0", out var previewStream))
{
Debug.WriteLine("未找到 RevitPreview4.0 流。");
return;
}
// 在图像上绘制半透明文字水印
if(!string.IsNullOrEmpty(watermark))
{
using var g = Graphics.FromImage(bmp);
var font = new Font("微软雅黑", 20, System.Drawing.FontStyle.Bold);
var brush = new SolidBrush(System.Drawing.Color.FromArgb(128, System.Drawing.Color.Red));
g.DrawString(watermark, font, brush, new PointF(20, bmp.Height - 80));
}
// 将修改后的图像保存为 PNG再写回流
using var outMs = new MemoryStream();
bmp.Save(outMs, ImageFormat.Png);
previewStream.SetData(outMs.ToArray());
// 提交更改
cf.Commit();
}
#endregion
#region Hash
// 公钥(硬编码)
private const string PublicKeyPem = """
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyN+Z04RmVOvzI2Z8Li1t
xB9yv80Y4Pd+M0wiuW9BHccmVlm7LVlWDRZgaAykQxIt0UskZbaIQ5eR4mqywVoM
BygfWqurVtgC6dO3SSbhZiOuSA3g1xJLgje6TOQIPWDGw1Ap0sbWrCuk+bxPFjSi
XANksY3Qfl1e6YETYEEpptTXorM8enM4368/KkvEhFswBqE5/Qf2x7XUpPAfnh3Y
cl45bUu87yDeyAnX4HEuA+aZPHEgXFPmhndtqMiUsLEajQNd8zZQHKmjMbubAq9s
l10w7smZUck+bozHO0RP31ytI6n/7I0T9PXOb/rwjeXBxXD1K0kvU3DPuq2+a5sa
iwIDAQAB
-----END PUBLIC KEY-----
""";
// 私钥(硬编码)
private const string PrivateKeyPem = """
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAyN+Z04RmVOvzI2Z8Li1txB9yv80Y4Pd+M0wiuW9BHccmVlm7
LVlWDRZgaAykQxIt0UskZbaIQ5eR4mqywVoMBygfWqurVtgC6dO3SSbhZiOuSA3g
1xJLgje6TOQIPWDGw1Ap0sbWrCuk+bxPFjSiXANksY3Qfl1e6YETYEEpptTXorM8
enM4368/KkvEhFswBqE5/Qf2x7XUpPAfnh3Ycl45bUu87yDeyAnX4HEuA+aZPHEg
XFPmhndtqMiUsLEajQNd8zZQHKmjMbubAq9sl10w7smZUck+bozHO0RP31ytI6n/
7I0T9PXOb/rwjeXBxXD1K0kvU3DPuq2+a5saiwIDAQABAoIBAACdcETZkedzXBM5
6WofTAof4bWOgovvHJCipBgOiubPKPdG5jNq/bHX2/kSnbl2eWWMQUEuGIiYJccG
/6zF6GDQ3FqHZcNbp8M4gbymNZV0TfTB0KOTyDAaojQ381LgyIK1DcWBHdw2wCRZ
vo6v1BXez4evaUy5ckXQfAA9POiAH6Im7QQlPamaUJfBCfRqFNvTcL6YQngHg8RW
4xFvN8Lxhgnqftw0dY1YzNkDBz+toQ+211LTAJWYC4vZ2sHoGvR74YxZUWPS4wms
QdIq50kRwL45Z9IZcxqp1x27W0pEwBsOO2DhnM6r7mB7Mg9BMEjUFf3oM+vrx67W
w6McEXkCgYEA8q+Bb6ldNqWNa5uLmAziEqXCnN/X9o1Cr81YIeWZpx4Ovx2aR3wH
4OtWl9VmYrU7w758nhPH27q8ADxjSbIB3ShPPdfLcKBoW/5eg+K8Bexr5Fb6Lyqo
SMhFLptxOdNzYLej99XppzRk4CV3s7IZsfQd6okSdfvlqka5ExFjXDkCgYEA0+TZ
IBsc0nCZFTlcDxxMCkmj+c4+c01BGgSeoVSKIgyp5d5DSZoNjxXsrP3Nw3uq2+fh
v4DD/V76BpzQ6v7P+Xg9YbW1QO6QdsG4Cf1bnAFYaTwQwdm2To5SXixYpVnON4Xv
4fOWVL0qrn3PK6aUTJCcEOdhx1YtBl+P4gVq9OMCgYEAhtLcDOXBhE96/rI+Xi2i
FwwBz1dISo/14WcqNEKzFzXKqYBPqrEMS3dS9y02IVZgKoIUB1oj6T2XnmXuHw4G
nQ/83fWZ7ysebyUk6w42uO+8jPGJMlT3gt/IF3mB8Mc5TR0YueIcWajw9dm645jp
T4S4bgjSubtEv1FlFmPNmHkCgYAtmq5ka5FrdVJtL/gxRFzVRf4lsI7eT/039VCS
1lgYdfNHBuwidiZ+6jATF1jmvsIKLED472S/LmbPb4bDXO4z+f3z/qCxBedPt+e9
Nqs1y1BU7dmJbyr+g0EVBaRaihaI+qmjTsLHICOEhI1HDsYSKHl+Zd40gcaU/ZJB
Cft4EwKBgQDeK/3k6OJTPuzskuqMsQy6yHd7hesxvdsfUH78tZrkRB2B9fciOa2v
ulfBss9KKVCQeKaC+91ptLpMOlSJhc9tZUsWvE19vVlNmN0ezQyfSFn+E09Ouqin
0cMfedRgHigwi+T818aFnC8qPzTMqCMQyxRTYxEC43OB9VcPyzo1yA==
-----END RSA PRIVATE KEY-----
""";
private static AsymmetricKeyParameter GetPublicKey()
{
using var reader = new StringReader(PublicKeyPem);
var pemReader = new PemReader(reader);
return (AsymmetricKeyParameter)pemReader.ReadObject();
}
private static AsymmetricKeyParameter GetPrivateKey()
{
using var reader = new StringReader(PrivateKeyPem);
var pemReader = new PemReader(reader);
var keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
return keyPair.Private;
}
private static byte[] ComputeFileHash(string filePath)
{
using var sha256 = SHA256.Create();
using var stream = File.OpenRead(filePath);
return sha256.ComputeHash(stream);
}
private static byte[] EncryptData(byte[] data, AsymmetricKeyParameter publicKey)
{
var rsaEngine = new Org.BouncyCastle.Crypto.Engines.RsaEngine();
rsaEngine.Init(true, publicKey);
return rsaEngine.ProcessBlock(data, 0, data.Length);
}
private static byte[] DecryptData(byte[] encryptedData, AsymmetricKeyParameter privateKey)
{
var rsaEngine = new Org.BouncyCastle.Crypto.Engines.RsaEngine();
rsaEngine.Init(false, privateKey);
return rsaEngine.ProcessBlock(encryptedData, 0, encryptedData.Length);
}
private static readonly string EncryptFileName = ".hashes.enc";
/// <summary>
/// 获取相对路径
/// </summary>
/// <param name="relativeTo">相对于某个目录</param>
/// <param name="path">示例路径</param>
/// <returns></returns>
private static string GetRelativePath(string relativeTo, string path)
{
// 确保路径以目录分隔符结尾(对于目录路径)
string from = Path.GetFullPath(relativeTo).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) +
Path.DirectorySeparatorChar;
string to = Path.GetFullPath(path);
Uri fromUri = new Uri(from);
Uri toUri = new Uri(to);
// 计算相对 URI
Uri relativeUri = fromUri.MakeRelativeUri(toUri);
// 将相对 URI 转换回字符串,并替换可能的 '/' 为当前平台的分隔符
string relativePath = Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar);
return relativePath;
}
public static string GenerateEncFile(string directoryPath)
{
var publicKey = GetPublicKey();
var files = Directory.GetFiles(directoryPath, "*.rvt", SearchOption.AllDirectories);
var encFilePath = Path.Combine(directoryPath, EncryptFileName);
using var fs = new FileStream(encFilePath, FileMode.Create, FileAccess.Write);
using var bw = new BinaryWriter(fs);
foreach(var file in files)
{
//var fileName = Path.GetFileName(file);
var fileName = GetRelativePath(directoryPath, file);
var fileHash = ComputeFileHash(file);
var nameBytes = Encoding.UTF8.GetBytes(fileName);
var combined = nameBytes.Concat(fileHash).ToArray();
var encrypted = EncryptData(combined, publicKey);
bw.Write(nameBytes.Length);
bw.Write(nameBytes);
bw.Write(encrypted.Length);
bw.Write(encrypted);
}
return encFilePath;
}
public static StringBuilder VerifyEncFile(string directoryPath, out bool hasModified)
{
hasModified = false;
var privateKey = GetPrivateKey();
var encFilePath = Path.Combine(directoryPath, EncryptFileName);
StringBuilder sb = new StringBuilder();
if(!File.Exists(encFilePath))
{
sb.AppendLine("验证文件未找到。");
return sb;
}
Dictionary<string, string> allFiles;
try
{
//转成相对路径
allFiles = Directory.GetFiles(directoryPath, "*.rvt", SearchOption.AllDirectories)
.ToDictionary(
file => GetRelativePath(directoryPath, file),
file => file,
StringComparer.OrdinalIgnoreCase);
} catch(Exception ex)
{
MessageBox.Show(ex.Message, "验证出错!", MessageBoxButton.OK, MessageBoxImage.Error);
return sb;
}
using var fs = new FileStream(encFilePath, FileMode.Open, FileAccess.Read);
using var br = new BinaryReader(fs);
while(fs.Position < fs.Length)
{
var nameLen = br.ReadInt32();
//if (nameLen <= 0 || nameLen > 256) // 假设文件名长度不超过256字节
//{
// Console.WriteLine("无效的文件名长度。");
// continue;
//}
var nameBytes = br.ReadBytes(nameLen);
var fileName = Encoding.UTF8.GetString(nameBytes);
var encLen = br.ReadInt32();
var encrypted = br.ReadBytes(encLen);
if(!allFiles.TryGetValue(fileName, out var actualFilePath))
{
sb.AppendLine($"{fileName} 未找到");
continue;
}
var decrypted = DecryptData(encrypted, privateKey);
var expectedName = Encoding.UTF8.GetString(decrypted, 0, nameBytes.Length);
var expectedHash = decrypted.Skip(nameBytes.Length).ToArray();
var currentHash = ComputeFileHash(actualFilePath);
bool match = expectedName == fileName && currentHash.SequenceEqual(expectedHash);
if(!match)
{
hasModified = true;
sb.AppendLine($"{fileName}");
}
}
return sb;
}
#endregion
}
#endif