using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Web.Script.Serialization;
namespace AyueSdk
{
///
/// 阿越云验证 - C# SDK
///
/// 支持:卡密登录、心跳保活、公告获取、版本检查(含强制更新)、
/// 卡密解绑、获取应用配置、获取外链文件、应用账号登录
///
/// 环境:.NET Framework 4.5+ / .NET Core 3.1+ / .NET 5+
/// 依赖:System.Web.Extensions(.NET Framework)或 System.Text.Json(.NET Core,见注释)
///
/// @version 1.0.0
///
public class AyueVerify
{
private readonly string _baseUrl;
private readonly int _appId;
private readonly string _appKey;
private readonly string _rc4Key;
private readonly int _miType;
private readonly int _miTime;
private readonly bool _signEnabled;
private readonly int _timeoutMs;
public string LastError { get; private set; } = "";
public int LastCode { get; private set; } = 0;
///
/// 初始化
///
/// 验证服务器地址,如 https://yz.example.com
/// 应用 ID
/// 应用密钥
/// 通信密钥
/// 加密类型 0=明文 1=RC4 2=Base64
/// 是否签名
/// 超时毫秒
public AyueVerify(string baseUrl, int appId, string appKey = "", string rc4Key = "",
int miType = 0, bool sign = false, int timeoutMs = 10000, int miTime = 10)
{
_baseUrl = (baseUrl ?? "").TrimEnd('/') + "/";
_appId = appId;
_appKey = appKey ?? "";
_rc4Key = rc4Key ?? "";
_miType = miType;
_miTime = miTime;
_signEnabled = sign;
_timeoutMs = timeoutMs > 0 ? timeoutMs : 10000;
}
// ==================== 业务接口 ====================
/// 卡密登录
public Result Login(string kami, string markcode)
{
var p = new Dictionary { { "kami", kami }, { "markcode", markcode } };
return Normalize(Request("kmlogon", p), true);
}
/// 心跳保活
public Result Heartbeat(string kami, string markcode)
{
var p = new Dictionary { { "kami", kami }, { "markcode", markcode } };
return Normalize(Request("heartbeat", p), true);
}
/// 退出心跳
public Result HeartbeatQuit(string kami) => Heartbeat(kami, "quit");
/// 获取公告
public string Notice()
{
var r = Request("notice", new Dictionary());
if (CodeOf(r) == 200 && r.ContainsKey("msg") && r["msg"] is Dictionary d)
{
return d.ContainsKey("app_gg") && d["app_gg"] != null ? d["app_gg"].ToString() : "";
}
return "";
}
/// 获取应用配置
public Config GetConfig(bool withPay = false)
{
var p = new Dictionary();
if (withPay) p["pay"] = "1";
var r = Request("ini", p);
var c = new Config { Ok = CodeOf(r) == 200, Code = LastCode };
if (c.Ok && r.ContainsKey("msg") && r["msg"] is Dictionary d)
{
c.Version = Str(d, "version");
c.VersionInfo = Str(d, "version_info");
c.UpdateUrl = Str(d, "app_update_url");
c.UpdateShow = Str(d, "app_update_show");
c.MustUpdate = Str(d, "app_update_must") == "y";
}
else
{
c.Msg = LastError;
}
return c;
}
/// 检查更新
public UpdateInfo CheckUpdate(string currentVersion)
{
var cfg = GetConfig();
var u = new UpdateInfo();
if (!cfg.Ok) { u.Msg = cfg.Msg; return u; }
u.Ok = true;
u.HasUpdate = VersionCompare(cfg.Version, currentVersion) > 0;
u.MustUpdate = u.HasUpdate && cfg.MustUpdate;
u.Latest = cfg.Version;
u.Url = cfg.UpdateUrl;
u.Desc = string.IsNullOrEmpty(cfg.UpdateShow) ? cfg.VersionInfo : cfg.UpdateShow;
return u;
}
/// 卡密解绑
public Result Unbind(string kami, string markcode)
{
var p = new Dictionary { { "kami", kami }, { "markcode", markcode } };
return Normalize(Request("kmunmachine", p), false);
}
/// 获取外链文件(原始 JSON)
public string GetFilesRaw()
{
return RequestRaw("getfile", new Dictionary());
}
/// 应用账号登录
public Result UserLogin(string user, string password)
{
var p = new Dictionary { { "user", user }, { "password", password } };
return Normalize(Request("userlogon", p), false);
}
/// 应用账号注册
public Result UserRegister(string user, string password)
{
var p = new Dictionary { { "user", user }, { "password", password } };
return Normalize(Request("userreg", p), false);
}
/// 应用账号心跳
public Result UserHeartbeat(string user, string markcode)
{
var p = new Dictionary { { "user", user }, { "markcode", markcode } };
return Normalize(Request("userheartbeat", p), false);
}
// ==================== 底层通信 ====================
private string RequestRaw(string api, Dictionary fields)
{
LastError = "";
LastCode = 0;
string value = RandomStr(16);
fields["value"] = value;
// 关键:服务端 mi_time > 0 时强制要求 t 参数,缺失返回 108
if (_miTime > 0 && !fields.ContainsKey("t"))
{
fields["t"] = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
}
string url = _baseUrl + "api.php?api=" + Uri.EscapeDataString(api) + "&app=" + _appId;
var post = new Dictionary();
if (_miType == 0)
{
foreach (var kv in fields) post[kv.Key] = kv.Value;
}
else
{
string plain = BuildQuery(fields);
post["data"] = Encrypt(plain);
post["value"] = value;
}
if (_signEnabled)
{
var src = new Dictionary(fields);
if (_miType != 0) src["data"] = post["data"];
post["sign"] = MakeSign(src);
}
var sb = new StringBuilder();
foreach (var kv in post)
{
if (sb.Length > 0) sb.Append('&');
sb.Append(Uri.EscapeDataString(kv.Key)).Append('=').Append(Uri.EscapeDataString(kv.Value ?? ""));
}
byte[] body = Encoding.UTF8.GetBytes(sb.ToString());
try
{
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
var req = (HttpWebRequest)WebRequest.Create(url);
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.UserAgent = "AyueVerify-CSharp-SDK/1.0.0";
req.Timeout = _timeoutMs;
req.ReadWriteTimeout = _timeoutMs;
req.ContentLength = body.Length;
using (var s = req.GetRequestStream()) s.Write(body, 0, body.Length);
using (var resp = (HttpWebResponse)req.GetResponse())
using (var rs = resp.GetResponseStream())
using (var reader = new System.IO.StreamReader(rs, Encoding.UTF8))
{
return reader.ReadToEnd();
}
}
catch (Exception ex)
{
LastError = "网络请求失败: " + ex.Message;
LastCode = -1;
return "{\"code\":-1,\"msg\":\"网络请求失败\"}";
}
}
private Dictionary Request(string api, Dictionary fields)
{
string raw = RequestRaw(api, fields);
try
{
var ser = new JavaScriptSerializer();
// 去除 UTF-8 BOM
raw = raw?.TrimStart('\uFEFF')?.Trim() ?? "";
var obj = ser.Deserialize>(raw);
if (obj == null) throw new Exception("empty");
LastCode = CodeOf(obj);
if (LastCode != 200 && obj.ContainsKey("msg"))
{
LastError = obj["msg"] == null ? "" : obj["msg"].ToString();
}
return obj;
}
catch (Exception e)
{
LastError = "返回格式错误: " + (raw.Length > 120 ? raw.Substring(0, 120) : raw);
LastCode = -1;
return new Dictionary { { "code", -1 }, { "msg", LastError } };
}
}
private Result Normalize(Dictionary r, bool wantExpires)
{
var res = new Result
{
Code = CodeOf(r),
Msg = r.ContainsKey("msg") && r["msg"] != null ? r["msg"].ToString() : ""
};
res.Ok = res.Code == 200;
if (r.ContainsKey("msg") && r["msg"] is Dictionary d)
{
res.Data = d;
if (wantExpires && res.Ok && d.ContainsKey("expires_ts") && d["expires_ts"] != null)
{
long.TryParse(d["expires_ts"].ToString(), out long ts);
res.ExpiresTs = ts;
}
}
return res;
}
private static int CodeOf(Dictionary r)
{
if (r == null || !r.ContainsKey("code") || r["code"] == null) return -1;
return int.TryParse(r["code"].ToString(), out int c) ? c : -1;
}
private static string Str(Dictionary d, string key)
{
return d.ContainsKey(key) && d[key] != null ? d[key].ToString() : "";
}
private string BuildQuery(Dictionary f)
{
var sb = new StringBuilder();
foreach (var kv in f)
{
if (sb.Length > 0) sb.Append('&');
sb.Append(kv.Key).Append('=').Append(kv.Value);
}
return sb.ToString();
}
private string MakeSign(Dictionary fields)
{
var ignore = new HashSet { "sign", "app", "api", "value",
"PHPSESSID", "sec_defend", "sidenav-state" };
var sb = new StringBuilder();
foreach (var kv in fields)
{
if (ignore.Contains(kv.Key)) continue;
sb.Append(kv.Key).Append('=').Append(kv.Value).Append('&');
}
sb.Append(_appKey);
return Md5(sb.ToString());
}
private string Encrypt(string plain)
{
if (_miType == 1)
{
// 关键:服务端 mi_rc4 会先做 UTF-8 -> GBK 转换,客户端必须保持一致。
string d = plain, k = _rc4Key;
try
{
var gbk = Encoding.GetEncoding("GBK");
d = Encoding.GetEncoding("ISO-8859-1").GetString(gbk.GetBytes(plain));
k = Encoding.GetEncoding("ISO-8859-1").GetString(gbk.GetBytes(_rc4Key));
}
catch (Exception)
{
// 目标框架未注册 GBK 时(.NET Core 需注册 CodePagesEncodingProvider)
// 退化为原串,含中文参数可能与服务端不一致
}
return Rc4Hex(d, k);
}
return Convert.ToBase64String(Encoding.UTF8.GetBytes(plain));
}
private static string Rc4Hex(string data, string key)
{
if (string.IsNullOrEmpty(key)) return "";
var s = new int[256];
for (int i = 0; i < 256; i++) s[i] = i;
int j = 0, kl = key.Length;
for (int i = 0; i < 256; i++)
{
j = (j + s[i] + key[i % kl]) % 256;
int t = s[i]; s[i] = s[j]; s[j] = t;
}
int x = 0, y = 0;
var sb = new StringBuilder();
foreach (char ch in data)
{
x = (x + 1) % 256;
y = (y + s[x]) % 256;
int t = s[x]; s[x] = s[y]; s[y] = t;
int c = (ch ^ s[(s[x] + s[y]) % 256]) & 0xFF;
sb.Append(c.ToString("x2"));
}
return sb.ToString();
}
private static string Md5(string s)
{
using (var md5 = MD5.Create())
{
byte[] d = md5.ComputeHash(Encoding.UTF8.GetBytes(s));
var sb = new StringBuilder();
foreach (byte b in d) sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
private static string RandomStr(int n)
{
const string chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
var rnd = new Random(Guid.NewGuid().GetHashCode());
var sb = new StringBuilder();
for (int i = 0; i < n; i++) sb.Append(chars[rnd.Next(chars.Length)]);
return sb.ToString();
}
/// 版本比较:v1 > v2 返回 1
public static int VersionCompare(string v1, string v2)
{
var a = (v1 ?? "").Trim().Split(new[] { '.', '-', '+' });
var b = (v2 ?? "").Trim().Split(new[] { '.', '-', '+' });
int n = Math.Max(a.Length, b.Length);
for (int i = 0; i < n; i++)
{
string x = i < a.Length ? a[i] : "0";
string y = i < b.Length ? b[i] : "0";
if (int.TryParse(x, out int xi) && int.TryParse(y, out int yi))
{
if (xi > yi) return 1;
if (xi < yi) return -1;
}
else
{
int c = string.CompareOrdinal(x, y);
if (c > 0) return 1;
if (c < 0) return -1;
}
}
return 0;
}
/// 生成设备码(首次运行应持久化)
public static string GenerateMarkcode()
{
string seed = Environment.MachineName + "|" + Environment.OSVersion +
"|" + Environment.ProcessorCount + "|" + Environment.UserName;
return Md5(seed).Substring(0, 24).ToUpperInvariant();
}
// ==================== 数据结构 ====================
public class Result
{
public bool Ok;
public int Code;
public string Msg = "";
public Dictionary Data;
public long ExpiresTs;
}
public class Config
{
public bool Ok;
public int Code;
public string Msg = "";
public string Version = "";
public string VersionInfo = "";
public string UpdateUrl = "";
public string UpdateShow = "";
public bool MustUpdate;
}
public class UpdateInfo
{
public bool Ok;
public bool HasUpdate;
public bool MustUpdate;
public string Latest = "";
public string Url = "";
public string Desc = "";
public string Msg = "";
}
}
}