2026-01-01 10:02:59 +08:00
|
|
|
|
#if false
|
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
|
using System.Drawing;
|
|
|
|
|
|
using System.Drawing.Imaging;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Security.Cryptography;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
|
|
|
|
using Autodesk.Revit.DB;
|
|
|
|
|
|
using Autodesk.Revit.DB.ExtensibleStorage;
|
|
|
|
|
|
using Org.BouncyCastle.Crypto;
|
|
|
|
|
|
using Org.BouncyCastle.OpenSsl;
|
|
|
|
|
|
using OpenMcdf;
|
|
|
|
|
|
using System.Windows;
|
|
|
|
|
|
|
2026-02-21 16:31:24 +08:00
|
|
|
|
namespace ShrlAlgoToolkit.RevitCore.Assists;
|
2026-01-01 10:02:59 +08:00
|
|
|
|
|
|
|
|
|
|
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>
|
|
|
|
|
|
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 = new Entity(schema);
|
|
|
|
|
|
IDictionary<int, string> imageMap = new Dictionary<int, string>();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
IDictionary<string, string> stringMap = signatureMap
|
|
|
|
|
|
.ToDictionary(kvp => kvp.Key, kvp => Convert.ToBase64String(kvp.Value));
|
|
|
|
|
|
|
|
|
|
|
|
entity.Set<IDictionary<string, string>>("SignatureMap", stringMap);
|
|
|
|
|
|
|
|
|
|
|
|
projectInfo.SetEntity(entity);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 读取图片字节数据列表 (Revit 2020 兼容版, 使用 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>
|
|
|
|
|
|
/// 获取或创建数据模式 (Revit 2020 兼容版)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private static Schema GetOrCreateSchema()
|
|
|
|
|
|
{
|
|
|
|
|
|
Schema schema = Schema.Lookup(SchemaGuid);
|
|
|
|
|
|
if (schema == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
SchemaBuilder schemaBuilder = new SchemaBuilder(SchemaGuid);
|
|
|
|
|
|
schemaBuilder.SetReadAccessLevel(AccessLevel.Application);
|
|
|
|
|
|
schemaBuilder.SetWriteAccessLevel(AccessLevel.Application);
|
|
|
|
|
|
schemaBuilder.SetVendorId("SZMC");
|
|
|
|
|
|
schemaBuilder.SetApplicationGUID(SchemaGuid);
|
|
|
|
|
|
schemaBuilder.SetSchemaName("SignatureStorage");
|
|
|
|
|
|
|
|
|
|
|
|
schemaBuilder.AddMapField("SignatureMap", 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
|