Appearance
C 语言静态库对接(ka-simple-c)
面向 Windows C/C++ 开发者。本 SDK 为 纯 C 静态库:
#include "kauth.h"+ 链接kauth.lib,最终只产出 一个 exe,不需要再加载kauth.dll。
下载(作者交付物)
不提供 SDK 内部源码。 作者从网盘下载编译好的库与示例即可:
https://pan.kauth.cn/C%E8%AF%AD%E8%A8%80%E5%8D%A1%E5%AF%86%E6%A0%B8%E5%BF%83%E6%BA%90%E7%A0%81
典型包内文件:
| 文件 | 说明 |
|---|---|
kauth.h | 公开头文件(结构体与 API 定义以它为准) |
kauth_vmp.h | 可选 VMP 标记宏(完整示例会用到) |
kauth.lib | MSVC x64 /MT 静态库 |
ka_login.c | 快速接入示例(控制台) |
ka_win.c | 完整接入示例(Win32 登录/业务窗/心跳/VMP) |
(可选)VMProtectSDK64.lib / .dll | 仅开启 VMP 时需要 |
协议加密、签名、HTTPS 均在 kauth.lib 内完成。
环境要求
| 项 | 要求 |
|---|---|
| 编译器 | MSVC(Visual Studio),不要用 MinGW |
| 架构 | x64 |
| 运行时库 | 多线程 (/MT),不可与 /MD 混链 |
| 系统 | Windows(HTTPS 使用 Schannel) |
系统库 ws2_32 / secur32 / crypt32 / bcrypt 已由库内自动链接。ka_win.c 另需 comctl32(示例内已 #pragma comment)。
商家后台配置
| 字段 | 来源 |
|---|---|
api_domain | 如 https://api.kauth.cn |
program_id | 程序管理 → 程序列表 |
program_secret | 同上 |
merchant_public_key | 系统设置 → 密钥配置 → RSA 公钥 → PKCS#8 |
签名类型固定 RSA_SHA256_V2(可不传,SDK 默认)。请把示例里的配置换成你自己的再编译。
API 一览(能力总表)
约定:
- 业务接口多数返回
BaseResponse:code == 200成功,失败看msg。 - 带「出参结构体」的接口:返回值仍是
BaseResponse,详细字段写在第二个参数里(部分接口只有请求、无独立出参结构体)。 - 字符串均为 UTF-8;结构体请先
memset再填字段。 CaptchaResponse.captchaCode需标准库free;加密 HTTP POST 返回串需kauth_free。deviceId= kauth 设备绑定;hardwareId= VMP HWID(可选,仅开 VMP 时需要)。
能力速览
| 分类 | 能力 |
|---|---|
| 初始化 / Token | 配置、设/取 token、释放内存 |
| 设备 | 生成 UUID、本机 deviceId |
| 用户认证 | 验证码、卡密/账密/试用登录、注册、退出、心跳单次、用户信息 |
| 充值 / 改密 / 解绑 | 账号充值、以卡充卡、改密、账密/卡密解绑、已登录卡密解绑 |
| 配置 / 程序 | 用户/卡密自定义配置、程序详情、服务器时间 |
| 远程控制 | 远程变量、远程数据 CRUD、远程函数 |
| 脚本 | 最新脚本、脚本下载、脚本错误上报 |
| 自动心跳 | 启停心跳、查询状态 |
| 底层 / 调试 | 加密 HTTP POST |
| VMP(可选) | HWID、设锁、状态、序列号明文 |
每个接口均为二级标题,下面跟方法签名,再写用途 / 入参 / 出参。
初始化 SDK
c
int kauth_init(const kauth_init_options *opt);用途:按字段初始化 SDK。
入参 kauth_init_options:
| 字段 | 类型 | 说明 |
|---|---|---|
api_domain | const char* | API 域名,如 https://api.kauth.cn |
program_id | const char* | 程序 ID |
program_secret | const char* | 程序密钥 |
merchant_public_key | const char* | 商户 RSA 公钥(PKCS#8 Base64) |
sign_type | const char* | 可 NULL,默认 RSA_SHA256_V2 |
出参:返回 0 成功,非 0 失败。
设置访问令牌
c
void kauth_set_token(const char *token);用途:手动写入访问令牌。登录成功时 SDK 一般会自动设置。
入参:token 字符串;传空串可清空。
出参:无。
读取配置与令牌
c
const char *kauth_get_token(void);
const char *kauth_get_api_domain(void);
const char *kauth_get_program_id(void);
const char *kauth_get_program_secret(void);
const char *kauth_get_merchant_public_key(void);
const char *kauth_get_sign_type(void);用途:读取当前内存中的配置或 token。
入参:无。
出参:内部静态缓冲的 const char*(勿 free)。
释放堆内存
c
void kauth_free(char *ptr);用途:释放「加密 HTTP POST」等返回的堆内存。
入参:待释放指针(可为 NULL)。
出参:无。
加密 HTTP POST(底层)
c
char *kauth_http_post(const char *url, const char *data_json);用途:走 SDK 加密协议的 HTTP POST(一般业务请用封装好的 API,不必直接调)。
入参:
| 参数 | 说明 |
|---|---|
url | 路径或完整 URL(以实现为准) |
data_json | 业务 data JSON;空 body 接口可传 "{}" |
出参:envelope JSON 字符串,需 kauth_free;失败可能返回 NULL。
生成 UUID
c
char *kauth_generate_uuid(char *output);用途:生成 UUID 字符串。
入参:output 调用方缓冲(足够长)。
出参:返回 output 指针(便于链式使用)。
获取本机设备 ID
c
char *kauth_get_device_unique_id(char *output, size_t output_size);用途:取本机设备唯一 ID,填入登录/解绑的 deviceId。
入参:输出缓冲及容量。
出参:成功返回 output;失败可能返回 NULL(以实现为准)。
获取图形验证码
c
BaseResponse kauth_get_captcha(CaptchaRequest *request, CaptchaResponse *response);用途:获取图形验证码。
入参 CaptchaRequest:
| 字段 | 说明 |
|---|---|
uuid | 可选;客户端可自带 uuid |
出参 CaptchaResponse:
| 字段 | 说明 |
|---|---|
code / msg | 结果 |
captchaUuid | 后续登录/注册要带回 |
captchaCode | 验证码内容指针,需 free |
captchaData | 附加数据(如 Base64 图) |
同时返回 BaseResponse(与 response 中 code/msg 一致使用即可)。
卡密登录
c
BaseResponse kauth_ka_login(KaLoginRequest *request, LoginResponse *response);用途:卡密登录。成功后 SDK 自动设置 token;若有 vmpSerial 会尝试设锁。
入参 KaLoginRequest:
| 字段 | 说明 |
|---|---|
kaPwd | 卡密 |
captchaCode / captchaUuid | 若后台开启验证码则必填 |
deviceId | 设备绑定 ID |
platformType | 平台标识,如 win / windows |
hardwareId | 可选,VMP HWID |
出参 LoginResponse:
| 字段 | 说明 |
|---|---|
code / msg | 结果;msg 常含到期文案 |
userId / nickName | 用户信息 |
token | 访问令牌 |
pongInterval | 心跳间隔(秒或毫秒,示例会兼容解析) |
vmpSerial | VMP 授权锁;未配置 VMP 时为空 |
账号密码登录
c
BaseResponse kauth_pwd_login(PwdLoginRequest *request, LoginResponse *response);用途:账号密码登录。
入参 PwdLoginRequest:
| 字段 | 说明 |
|---|---|
userName / password | 账号密码 |
captchaCode / captchaUuid | 按需 |
deviceId | 设备 ID |
hardwareId | 可选 VMP HWID |
出参:同 LoginResponse。
试用登录
c
BaseResponse kauth_trial_login(TrialLoginReq *request, LoginResponse *response);用途:试用登录。
入参 TrialLoginReq:
| 字段 | 说明 |
|---|---|
captchaCode / captchaUuid | 按需 |
deviceId | 设备 ID |
platformType | 平台 |
hardwareId | 可选 VMP HWID |
出参:同 LoginResponse。
账号注册
c
BaseResponse kauth_register(RegisterRequest *request, RegisterResponse *response);用途:账号注册(绑定卡密)。成功不等于已登录,需再调登录接口。
入参 RegisterRequest:
| 字段 | 说明 |
|---|---|
userName / password | 账号密码 |
kaPassword | 绑定卡密 |
captchaCode / captchaUuid | 按需 |
出参 RegisterResponse:
| 字段 | 说明 |
|---|---|
code / msg | 结果 |
userId / nickName / token / pongInterval | 若服务端返回则填入;示例会清空 token,避免误入业务 |
退出登录
c
BaseResponse kauth_logout(void);用途:退出登录。
入参:无(使用当前 token)。
出参:BaseResponse。
单次心跳
c
BaseResponse kauth_pong(void);用途:发送一次心跳(手动)。日常更推荐「启动自动心跳」。
入参:无。
出参:BaseResponse。
获取用户信息
c
BaseResponse kauth_get_user_info(UserInfo *response);用途:获取当前登录用户信息。
入参:无(需已登录)。
出参 UserInfo:
| 字段 | 说明 |
|---|---|
code / msg | 结果 |
userIdStr | 用户 ID 字符串 |
serverExpireTime | 服务到期时间 |
serverRemainNum | 剩余次数/点数等 |
serverType | 服务类型 |
trial | 是否试用 |
账号充值
c
BaseResponse kauth_recharge(RechargeRequest *request);用途:用卡密给账号充值。
入参 RechargeRequest:
| 字段 | 说明 |
|---|---|
loginName | 账号 |
kaPassword | 充值卡密 |
deviceId | 设备 ID |
captchaCode / captchaUuid | 按需 |
出参:BaseResponse(无独立业务结构体)。
以卡充卡
c
BaseResponse kauth_recharge_ka(KaRechargeKaReq *request);用途:以卡充卡(充值卡续原卡)。
入参 KaRechargeKaReq:
| 字段 | 说明 |
|---|---|
cardPwd | 原卡密 |
rechargeCardPwd | 充值卡 |
出参:BaseResponse。
修改密码
c
BaseResponse kauth_change_password(ResetPwdRequest *request);用途:修改账号密码。
入参 ResetPwdRequest:
| 字段 | 说明 |
|---|---|
userName | 账号 |
oldPassword | 旧密码 |
newPassword | 新密码 |
confirmPassword | 确认密码(可与新密码相同) |
captchaCode / captchaUuid | 按需 |
出参:BaseResponse。
账密解绑设备(未登录)
c
BaseResponse kauth_unbind_device(UnbindDeviceRequest *request);用途:未登录状态下,用账密解绑设备。
入参 UnbindDeviceRequest:
| 字段 | 说明 |
|---|---|
loginName / password | 账号密码 |
deviceId | 要解绑的设备 |
captchaCode / captchaUuid | 按需 |
出参:BaseResponse。
卡密解绑设备(未登录)
c
BaseResponse kauth_unbind_device_ka_pwd(UnbindDeviceKaPwdRequest *request);用途:未登录状态下,用卡密解绑设备。
入参 UnbindDeviceKaPwdRequest:
| 字段 | 说明 |
|---|---|
kaPwd | 卡密 |
deviceId | 设备 ID |
出参:BaseResponse。
卡密解绑当前设备(已登录)
c
BaseResponse kauth_card_unbind_device(void);用途:已登录卡密会话下,解绑当前设备。
入参:无(使用当前 token)。
出参:BaseResponse。
获取用户自定义配置
c
BaseResponse kauth_get_user_config(CustomConfigResp *response);用途:读取用户自定义配置。
入参:无(需已登录,视后台策略)。
出参 CustomConfigResp:code / msg / configJson。
更新用户自定义配置
c
BaseResponse kauth_update_user_config(UpdateCustomConfigReq *request);用途:更新用户自定义配置。
入参 UpdateCustomConfigReq:config(最长约 4096,JSON 或文本以业务约定为准)。
出参:BaseResponse。
获取卡密自定义配置
c
BaseResponse kauth_get_ka_config(CustomConfigResp *response);用途:读取卡密自定义配置。
出参 CustomConfigResp:code / msg / configJson。
更新卡密自定义配置
c
BaseResponse kauth_update_ka_config(UpdateCustomConfigReq *request);用途:更新卡密自定义配置。
入参 UpdateCustomConfigReq:config。
出参:BaseResponse。
获取程序详情
c
BaseResponse kauth_get_program_detail(ProgramDetailResponse *response);用途:获取程序详情与版本信息(可未登录调用,视后台策略)。
入参:无(使用已 init 的 programId)。
出参 ProgramDetailResponse:
| 字段 | 说明 |
|---|---|
programIdStr / name | 程序 ID / 名称 |
forceUpdateDict | 是否强制更新 |
status | 程序状态 |
enableRegisterDict | 是否允许注册 |
notice | 公告 |
version | versionNo / versionName / versionDesc / versionDownUrl |
获取服务器时间
c
BaseResponse kauth_get_server_time(ServerTimeResp *response);用途:获取服务器时间。
入参:无。
出参 ServerTimeResp:serverTime / serverTimeSec / serverTimeStr。
获取远程变量
c
BaseResponse kauth_get_remote_var(GetRemoteVarReq *request, RemoteVarResp *response);用途:获取远程变量。
入参 GetRemoteVarReq:key。
出参 RemoteVarResp:key / value / type / createTime / updateTime。
获取远程数据
c
BaseResponse kauth_get_remote_data(GetRemoteVarReq *request, RemoteVarResp *response);用途:按 key 读取远程数据。
入参:key。
出参:RemoteVarResp。
添加远程数据
c
BaseResponse kauth_add_remote_data(RemoteDataAddReq *request);用途:新增远程数据。
入参 RemoteDataAddReq:key / value / type。
出参:BaseResponse。
更新远程数据
c
BaseResponse kauth_update_remote_data(RemoteDataUpdateReq *request);用途:更新远程数据。
入参 RemoteDataUpdateReq:key / value。
出参:BaseResponse。
删除远程数据
c
BaseResponse kauth_delete_remote_data(RemoteDataDeleteReq *request);用途:删除远程数据。
入参 RemoteDataDeleteReq:key。
出参:BaseResponse。
调用远程函数
c
BaseResponse kauth_call_function(CallFunctionReq *request, CallFunctionResp *response);用途:调用云端/远程函数。
入参 CallFunctionReq:functionName、paramsJson(参数 JSON 字符串)。
出参 CallFunctionResp:functionName / result / success。
获取最新脚本信息
c
BaseResponse kauth_get_newest_script(GetNewestScriptReq *request, NewestScriptResp *response);用途:查询某脚本最新版本元信息。
入参 GetNewestScriptReq:scriptName。
出参 NewestScriptResp:scriptName / versionNumber / versionDescription / scriptType / scriptReleaseTime。
下载脚本
c
BaseResponse kauth_script_download_v2(ScriptDownloadReq *request, ScriptDownloadResp *response);用途:下载指定版本脚本。
入参 ScriptDownloadReq:scriptName、versionNumber。
出参 ScriptDownloadResp:downloadUrl / md5 / fileSize / scriptContent(内容缓冲有上限,过大请用 URL)。
上报脚本错误
c
BaseResponse kauth_report_script_error(ScriptErrorReportReq *request);用途:上报脚本运行错误。
入参 ScriptErrorReportReq:errorMsg / scriptName / scriptMd5 / lineNo / errorStack。
出参:BaseResponse。
启动自动心跳
c
int kauth_start_heartbeat(int interval_ms, int max_failures, kauth_heartbeat_callback callback);用途:后台线程按间隔调用心跳。
入参:
| 参数 | 说明 |
|---|---|
interval_ms | 间隔毫秒;0 用默认;也可用登录返回的 pongInterval 换算 |
max_failures | 连续失败上限(网络失败累计) |
callback | 回调:(event, code, msg, failure_count) |
event:KAUTH_HEARTBEAT_SUCCESS / NETWORK_FAIL / SERVER_FAIL / STOPPED。
出参:0 启动成功,非 0 失败。
停止自动心跳
c
void kauth_stop_heartbeat(void);用途:停止心跳。
入参 / 出参:无。
查询心跳是否运行中
c
int kauth_is_heartbeat_running(void);用途:是否在跑;非 0 表示运行中。
查询心跳连续失败次数
c
int kauth_get_heartbeat_failure_count(void);用途:当前连续失败次数。
未开启 KAUTH_USE_VMP 宏时,以下 VMP 接口为桩:多数返回 -1 / 空串,不影响普通登录。
获取 VMP 硬件 ID
c
int kauth_vmp_get_hwid(char *out, size_t out_cap);用途:取本机 VMP HWID,填入登录 hardwareId。
出参:0 成功;-1/-2 失败。
设置 VMP 授权锁
c
int kauth_vmp_set_serial(const char *serial);用途:写入授权锁。登录若下发 vmpSerial 时 SDK 会自动调用。
出参:0 表示 SetSerialNumber 成功(与 VMP 约定一致)。
查询授权锁状态
c
int kauth_vmp_get_serial_state(void);用途:查询序列号状态位(0 = OK)。
格式化授权锁状态
c
void kauth_vmp_format_serial_state(int state, char *out, size_t out_cap);用途:把状态格式化为可读文本(含 INVALID / BAD_HWID 等)。
读取授权锁明文
c
int kauth_vmp_get_serial_info(kauth_vmp_serial_info *out);用途:读取序列号明文(需已成功设锁)。
出参结构 kauth_vmp_serial_info:state / userName / email / 到期日 / 最大编译日 / runningTimeMinutes / userData。
格式化授权锁明文
c
void kauth_vmp_format_serial_info(const kauth_vmp_serial_info *info, char *out, size_t out_cap);用途:格式化为多行 UTF-8,便于弹窗展示。
代码标记宏见 kauth_vmp.h:KAUTH_VMP_BEGIN / KAUTH_VMP_BEGIN_LOCK / KAUTH_VMP_END(详见下文 VMP 节)。
接入工程
- 从网盘下载文件,放到工程目录。
- 工程平台 x64,运行时库 /MT;
ka_login用控制台子系统,ka_win用 WINDOWS 子系统。 - Include 指向头文件目录;链接
kauth.lib。 ka_win.c需定义 UNICODE(/DUNICODE /D_UNICODE)。- 调用顺序:初始化 → 登录 → 业务;登录成功后 SDK 自动保存 token。
快速接入(ka_login.c)
控制台最小示例:初始化 → 取设备 ID → 拉程序详情。与网盘中的 ka_login.c 一致,可直接复制编译。
c
/**
* 静态链接示例:编译后只有一个 exe,无需 kauth.dll。
*
* 把下面的字段换成你后台「程序管理」里的值后再运行。
* 用 kauth_init 按字段传入(便于后续混淆、降低关键字检索风险)。
*/
#include "kauth.h"
#include <stdio.h>
#include <string.h>
#ifdef _WIN32
#include <windows.h>
#endif
static int init_sdk(void) {
kauth_init_options opt;
memset(&opt, 0, sizeof(opt));
opt.api_domain = "https://api.kauth.cn";
opt.program_id = "1959821336266936321";
opt.program_secret = "F77VzI7UWAElpWrz";
opt.merchant_public_key =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIpy3ae27yDJOUd5rW/S6tUbAmt/AqJm+VPonT9WJn5VME4FkYJUwdBmIWpzANVQmU+7CA3wv5eVFIOW0xMv9EyoFWDRR24Jt/hgDsZQtUPMaZPivKWxx2S4n4SJWWrGdIRkdC3+fmxrEri1qYicq8PO7mDIrwPR2I0USoKKOFMwIDAQAB";
/* sign_type 可省略,默认 RSA_SHA256_V2 */
return kauth_init(&opt);
}
int main(void) {
BaseResponse r;
ProgramDetailResponse detail;
char device[64];
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
if (init_sdk() != 0) {
printf("init sdk failed\n");
return 1;
}
if (kauth_get_device_unique_id(device, sizeof(device))) {
printf("deviceId=%s\n", device);
}
memset(&detail, 0, sizeof(detail));
r = kauth_get_program_detail(&detail);
printf("program detail code=%d msg=%s name=%s\n", r.code, r.msg, detail.name);
return r.code == 200 ? 0 : 2;
}编译示意(x64 开发者命令提示符):
bat
cl /nologo /O2 /MT /I. ka_login.c /link kauth.lib /OUT:ka_login.exe完整接入(ka_win.c)
Win32 完整示例,涵盖:
- 单码 / 账密双层页签(登录、注册、改密、充值、解绑、试用)
- 登录成功进入业务窗:到期
msg、心跳、退出 - VMP:
hardwareId、vmpSerial、设锁状态、解密弹窗、LockByKey计算演示
需 UNICODE 编译;可选定义 KAUTH_USE_VMP 并链接 VMProtect(见下文)。
与网盘中的 ka_win.c 一致,完整源码如下:
c
/**
* Win32 示例:登录窗口(单码 / 账密页签)+ 登录成功后的业务窗口(心跳 + 到期提示)。
* 编译产物为子系统 WINDOWS 的单个 exe,静态链接 kauth.lib。
* 需以 UNICODE 编译(CMake / build_msvc 已带 /DUNICODE)。
*/
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kauth.h"
#include "kauth_vmp.h"
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#define WM_HB_EVENT (WM_APP + 40)
#define CLR_BG RGB(241, 245, 249)
#define CLR_CARD RGB(255, 255, 255)
#define CLR_HEADER RGB(15, 23, 42)
#define CLR_ACCENT RGB(13, 148, 136)
#define CLR_ACCENT_D RGB(15, 118, 110)
#define CLR_DANGER RGB(185, 28, 28)
#define CLR_TEXT RGB(15, 23, 42)
#define CLR_MUTED RGB(100, 116, 139)
#define CLR_LINE RGB(226, 232, 240)
#define CLR_OK RGB(22, 163, 74)
#define CLR_WARN RGB(217, 119, 6)
#define HEADER_H 72
#define FOOTER_H 64
#define FORM_W 420
enum {
IDC_MAIN_TAB = 1001,
IDC_KA_TAB,
IDC_ACC_TAB,
IDC_STATUS,
IDC_TRIAL_KA,
IDC_TRIAL_ACC,
IDC_KA_LOGIN_PWD,
IDC_KA_LOGIN_BTN,
IDC_KA_RECHARGE_OLD,
IDC_KA_RECHARGE_NEW,
IDC_KA_RECHARGE_BTN,
IDC_KA_UNBIND_PWD,
IDC_KA_UNBIND_BTN,
IDC_ACC_LOGIN_USER,
IDC_ACC_LOGIN_PWD,
IDC_ACC_LOGIN_BTN,
IDC_ACC_REG_USER,
IDC_ACC_REG_PWD,
IDC_ACC_REG_KA,
IDC_ACC_REG_BTN,
IDC_ACC_CHG_USER,
IDC_ACC_CHG_OLD,
IDC_ACC_CHG_NEW,
IDC_ACC_CHG_BTN,
IDC_ACC_RECHARGE_USER,
IDC_ACC_RECHARGE_KA,
IDC_ACC_RECHARGE_BTN,
IDC_ACC_UNBIND_USER,
IDC_ACC_UNBIND_PWD,
IDC_ACC_UNBIND_BTN,
IDC_BIZ_EXPIRE,
IDC_BIZ_HB,
IDC_BIZ_VMP_SERIAL,
IDC_BIZ_VMP_STATE,
IDC_BIZ_VMP_DECODE,
IDC_BIZ_A,
IDC_BIZ_OP,
IDC_BIZ_B,
IDC_BIZ_CALC,
IDC_BIZ_RESULT,
IDC_BIZ_LOGOUT
};
enum {
BTN_PRIMARY = 1,
BTN_SECONDARY = 2,
BTN_DANGER = 3
};
static HINSTANCE g_inst;
static HWND g_login;
static HWND g_biz;
static HWND g_main_tab;
static HWND g_ka_tab;
static HWND g_acc_tab;
static HWND g_ka_pages[3];
static HWND g_acc_pages[5];
static HWND g_status;
static char g_device[128];
static char g_expire_msg[512];
static char g_vmp_serial[1024];
static HFONT g_font_ui;
static HFONT g_font_title;
static HFONT g_font_brand;
static HFONT g_font_small;
static HFONT g_font_btn;
static HBRUSH g_br_bg;
static HBRUSH g_br_card;
static HBRUSH g_br_header;
typedef struct {
int event;
int code;
int failure_count;
char msg[256];
} HbEvent;
static int utf8_to_wide(const char *utf8, wchar_t *out, int out_chars) {
if (!utf8 || !out || out_chars <= 0) return 0;
return MultiByteToWideChar(CP_UTF8, 0, utf8, -1, out, out_chars);
}
static int wide_to_utf8(const wchar_t *wide, char *out, int out_bytes) {
if (!wide || !out || out_bytes <= 0) return 0;
return WideCharToMultiByte(CP_UTF8, 0, wide, -1, out, out_bytes, NULL, NULL);
}
static void set_status(const char *utf8) {
wchar_t w[1024];
if (!g_status) return;
utf8_to_wide(utf8 ? utf8 : "", w, 1024);
SetWindowTextW(g_status, w);
}
static void refresh_vmp_state_ui(void);
static void show_msg(HWND owner, const char *title_utf8, const char *text_utf8) {
wchar_t t[128], m[2048];
utf8_to_wide(title_utf8, t, 128);
utf8_to_wide(text_utf8, m, 2048);
MessageBoxW(owner, m, t, MB_OK | MB_ICONINFORMATION);
}
static void show_vmp_serial_decode(HWND owner) {
kauth_vmp_serial_info info;
char text[2048];
int set_rc;
if (!g_vmp_serial[0]) {
show_msg(owner, "解密 vmpSerial", "当前没有 vmpSerial(登录未下发或程序未配置 VMP 密钥)。");
return;
}
set_rc = kauth_vmp_set_serial(g_vmp_serial);
refresh_vmp_state_ui();
#ifndef KAUTH_USE_VMP
(void)set_rc;
show_msg(owner, "解密 vmpSerial",
"当前未以 KAUTH_USE_VMP 编译,无法调用 VMProtectGetSerialNumberData。\n"
"请用 build_vmp_ka_win.bat 编译后再试(加壳后字段校验才完整生效)。");
return;
#else
if (kauth_vmp_get_serial_info(&info) != 0) {
char tip[512];
char st[160];
kauth_vmp_format_serial_state(set_rc != 0 ? set_rc : kauth_vmp_get_serial_state(), st, sizeof(st));
snprintf(tip, sizeof(tip),
"读取序列号明文失败。\n"
"设锁结果: %s\n\n"
"常见原因:未加壳、或加壳用的 .vmp 与签发密钥不是同一份。",
st);
show_msg(owner, "解密 vmpSerial", tip);
return;
}
kauth_vmp_format_serial_info(&info, text, sizeof(text));
show_msg(owner, "vmpSerial 解密结果", text);
#endif
}
static void get_edit_utf8(HWND root, int id, char *out, size_t out_size) {
wchar_t w[1024];
HWND h = GetDlgItem(root, id);
if (!out || out_size == 0) return;
out[0] = '\0';
if (!h) return;
GetWindowTextW(h, w, 1024);
wide_to_utf8(w, out, (int)out_size);
}
static void paint_header(HDC hdc, RECT *rc, const wchar_t *brand, const wchar_t *subtitle) {
RECT band = *rc;
band.bottom = HEADER_H;
FillRect(hdc, &band, g_br_header);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, RGB(255, 255, 255));
SelectObject(hdc, g_font_brand);
TextOutW(hdc, 28, 14, brand, (int)wcslen(brand));
SetTextColor(hdc, RGB(148, 163, 184));
SelectObject(hdc, g_font_small);
TextOutW(hdc, 28, 44, subtitle, (int)wcslen(subtitle));
}
static void paint_footer(HDC hdc, RECT *rc) {
RECT foot = *rc;
HPEN pen, old;
foot.top = foot.bottom - FOOTER_H;
FillRect(hdc, &foot, g_br_card);
pen = CreatePen(PS_SOLID, 1, CLR_LINE);
old = (HPEN)SelectObject(hdc, pen);
MoveToEx(hdc, 0, foot.top, NULL);
LineTo(hdc, foot.right, foot.top);
SelectObject(hdc, old);
DeleteObject(pen);
}
static void draw_owner_button(DRAWITEMSTRUCT *dis, int style) {
HDC hdc = dis->hDC;
RECT rc = dis->rcItem;
int pressed = (dis->itemState & ODS_SELECTED) != 0;
int disabled = (dis->itemState & ODS_DISABLED) != 0;
COLORREF fill, text;
HBRUSH br;
wchar_t caption[128];
HFONT old_font;
if (style == BTN_DANGER) {
fill = pressed ? RGB(153, 27, 27) : CLR_DANGER;
text = RGB(255, 255, 255);
} else if (style == BTN_SECONDARY) {
fill = pressed ? RGB(226, 232, 240) : RGB(241, 245, 249);
text = CLR_TEXT;
} else {
fill = pressed ? CLR_ACCENT_D : CLR_ACCENT;
text = RGB(255, 255, 255);
}
if (disabled) {
fill = RGB(203, 213, 225);
text = RGB(148, 163, 184);
}
br = CreateSolidBrush(fill);
FillRect(hdc, &rc, br);
DeleteObject(br);
if (style == BTN_SECONDARY) {
HPEN pen = CreatePen(PS_SOLID, 1, CLR_LINE);
HPEN old = (HPEN)SelectObject(hdc, pen);
HBRUSH oldbr = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, rc.left, rc.top, rc.right, rc.bottom);
SelectObject(hdc, oldbr);
SelectObject(hdc, old);
DeleteObject(pen);
}
GetWindowTextW(dis->hwndItem, caption, 128);
SetBkMode(hdc, TRANSPARENT);
SetTextColor(hdc, text);
old_font = (HFONT)SelectObject(hdc, g_font_btn);
DrawTextW(hdc, caption, -1, &rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
SelectObject(hdc, old_font);
if (dis->itemState & ODS_FOCUS) {
RECT fr = rc;
InflateRect(&fr, -3, -3);
DrawFocusRect(hdc, &fr);
}
}
static int button_style_from_id(int id) {
if (id == IDC_BIZ_LOGOUT) return BTN_DANGER;
if (id == IDC_TRIAL_KA || id == IDC_TRIAL_ACC || id == IDC_BIZ_VMP_DECODE) return BTN_SECONDARY;
return BTN_PRIMARY;
}
static HWND create_label(HWND parent, const wchar_t *text, int x, int y, int w, int h, HFONT font, COLORREF color) {
HWND hwnd = CreateWindowExW(0, L"STATIC", text, WS_CHILD | WS_VISIBLE,
x, y, w, h, parent, NULL, g_inst, NULL);
SendMessageW(hwnd, WM_SETFONT, (WPARAM)font, TRUE);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, (LONG_PTR)color);
return hwnd;
}
static HWND create_hint(HWND parent, const wchar_t *text, int x, int y, int w, int h) {
return create_label(parent, text, x, y, w, h, g_font_small, CLR_MUTED);
}
static HWND create_edit(HWND parent, int id, int x, int y, int w, int h, int password) {
DWORD style = WS_CHILD | WS_VISIBLE | ES_AUTOHSCROLL;
HWND hwnd;
if (password) style |= ES_PASSWORD;
hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"", style,
x, y, w, h, parent, (HMENU)(intptr_t)id, g_inst, NULL);
SendMessageW(hwnd, WM_SETFONT, (WPARAM)g_font_ui, TRUE);
return hwnd;
}
static HWND create_button(HWND parent, int id, const wchar_t *text, int x, int y, int w, int h) {
HWND hwnd = CreateWindowExW(0, L"BUTTON", text,
WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,
x, y, w, h, parent, (HMENU)(intptr_t)id, g_inst, NULL);
SendMessageW(hwnd, WM_SETFONT, (WPARAM)g_font_btn, TRUE);
return hwnd;
}
/* 上标签下输入框;返回下一行 y */
static int add_field(HWND parent, const wchar_t *label, int id, int x, int y, int w, int password) {
create_label(parent, label, x, y, w, 20, g_font_small, CLR_MUTED);
create_edit(parent, id, x, y + 22, w, 34, password);
return y + 68;
}
static int add_action(HWND parent, int id, const wchar_t *text, int x, int y, int w) {
create_button(parent, id, text, x, y, w, 40);
return y + 52;
}
static LRESULT CALLBACK PageProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_COMMAND:
if (g_login) return SendMessageW(g_login, msg, wParam, lParam);
break;
case WM_DRAWITEM:
draw_owner_button((DRAWITEMSTRUCT *)lParam, button_style_from_id((int)wParam));
return TRUE;
case WM_CTLCOLORSTATIC: {
HDC hdc = (HDC)wParam;
HWND ctrl = (HWND)lParam;
COLORREF c = (COLORREF)GetWindowLongPtrW(ctrl, GWLP_USERDATA);
if (!c) c = CLR_TEXT;
SetTextColor(hdc, c);
SetBkColor(hdc, CLR_CARD);
return (LRESULT)g_br_card;
}
case WM_ERASEBKGND: {
RECT rc;
GetClientRect(hwnd, &rc);
FillRect((HDC)wParam, &rc, g_br_card);
return 1;
}
default:
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
static HWND create_page(HWND parent) {
return CreateWindowExW(0, L"KauthPage", L"",
WS_CHILD | WS_CLIPSIBLINGS,
0, 0, 10, 10, parent, NULL, g_inst, NULL);
}
static void tab_insert(HWND tab, int index, const wchar_t *title) {
TCITEMW item;
memset(&item, 0, sizeof(item));
item.mask = TCIF_TEXT;
item.pszText = (LPWSTR)title;
TabCtrl_InsertItem(tab, index, &item);
}
static void layout_child_tabs(void) {
RECT client, main_disp, sub_disp;
int main_sel, i;
int top = HEADER_H + 16;
int bottom_pad = FOOTER_H + 12;
GetClientRect(g_login, &client);
SetWindowPos(g_main_tab, NULL, 20, top, client.right - 40, client.bottom - top - bottom_pad, SWP_NOZORDER);
GetClientRect(g_main_tab, &main_disp);
TabCtrl_AdjustRect(g_main_tab, FALSE, &main_disp);
MapWindowPoints(g_main_tab, g_login, (LPPOINT)&main_disp, 2);
main_sel = TabCtrl_GetCurSel(g_main_tab);
ShowWindow(g_ka_tab, main_sel == 0 ? SW_SHOW : SW_HIDE);
ShowWindow(g_acc_tab, main_sel == 1 ? SW_SHOW : SW_HIDE);
SetWindowPos(g_ka_tab, HWND_TOP, main_disp.left + 8, main_disp.top + 8,
main_disp.right - main_disp.left - 16, main_disp.bottom - main_disp.top - 16, 0);
SetWindowPos(g_acc_tab, HWND_TOP, main_disp.left + 8, main_disp.top + 8,
main_disp.right - main_disp.left - 16, main_disp.bottom - main_disp.top - 16, 0);
GetClientRect(g_ka_tab, &sub_disp);
TabCtrl_AdjustRect(g_ka_tab, FALSE, &sub_disp);
MapWindowPoints(g_ka_tab, g_login, (LPPOINT)&sub_disp, 2);
for (i = 0; i < 3; i++) {
SetWindowPos(g_ka_pages[i], HWND_TOP, sub_disp.left + 1, sub_disp.top + 1,
sub_disp.right - sub_disp.left - 2, sub_disp.bottom - sub_disp.top - 2, 0);
ShowWindow(g_ka_pages[i], (main_sel == 0 && i == TabCtrl_GetCurSel(g_ka_tab)) ? SW_SHOW : SW_HIDE);
}
GetClientRect(g_acc_tab, &sub_disp);
TabCtrl_AdjustRect(g_acc_tab, FALSE, &sub_disp);
MapWindowPoints(g_acc_tab, g_login, (LPPOINT)&sub_disp, 2);
for (i = 0; i < 5; i++) {
SetWindowPos(g_acc_pages[i], HWND_TOP, sub_disp.left + 1, sub_disp.top + 1,
sub_disp.right - sub_disp.left - 2, sub_disp.bottom - sub_disp.top - 2, 0);
ShowWindow(g_acc_pages[i], (main_sel == 1 && i == TabCtrl_GetCurSel(g_acc_tab)) ? SW_SHOW : SW_HIDE);
}
}
static int init_sdk(void) {
kauth_init_options opt;
memset(&opt, 0, sizeof(opt));
opt.api_domain = "https://api.kauth.cn";
opt.program_id = "2098969996642799617";
opt.program_secret = "fmx5nxsOtZFr0Rac";
opt.merchant_public_key =
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCIpy3ae27yDJOUd5rW/S6tUbAmt/AqJm+VPonT9WJn5VME4FkYJUwdBmIWpzANVQmU+7CA3wv5eVFIOW0xMv9EyoFWDRR24Jt/hgDsZQtUPMaZPivKWxx2S4n4SJWWrGdIRkdC3+fmxrEri1qYicq8PO7mDIrwPR2I0USoKKOFMwIDAQAB";
if (kauth_init(&opt) != 0) return -1;
kauth_get_device_unique_id(g_device, sizeof(g_device));
return 0;
}
static void on_heartbeat(kauth_heartbeat_event event, int code, const char *msg, int failure_count) {
HbEvent *ev = (HbEvent *)malloc(sizeof(HbEvent));
if (!ev) return;
memset(ev, 0, sizeof(*ev));
ev->event = (int)event;
ev->code = code;
ev->failure_count = failure_count;
if (msg) strncpy(ev->msg, msg, sizeof(ev->msg) - 1);
if (!PostMessageW(g_biz, WM_HB_EVENT, 0, (LPARAM)ev)) free(ev);
}
static void refresh_vmp_state_ui(void) {
char state_txt[256];
char line[320];
wchar_t w[320];
int st = kauth_vmp_get_serial_state();
kauth_vmp_format_serial_state(st, state_txt, sizeof(state_txt));
snprintf(line, sizeof(line), "设锁状态: %s", state_txt);
utf8_to_wide(line, w, 320);
if (GetDlgItem(g_biz, IDC_BIZ_VMP_STATE))
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_STATE), w);
}
static void enter_business(const char *msg_utf8, const char *pong_interval, const char *vmp_serial) {
wchar_t expire_w[512];
wchar_t serial_w[1024];
int interval_ms = 120000;
char status[256];
int set_rc;
strncpy(g_expire_msg, msg_utf8 ? msg_utf8 : "", sizeof(g_expire_msg) - 1);
g_expire_msg[sizeof(g_expire_msg) - 1] = '\0';
utf8_to_wide(g_expire_msg[0] ? g_expire_msg : "(无到期信息)", expire_w, 512);
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_EXPIRE), expire_w);
strncpy(g_vmp_serial, vmp_serial ? vmp_serial : "", sizeof(g_vmp_serial) - 1);
g_vmp_serial[sizeof(g_vmp_serial) - 1] = '\0';
if (g_vmp_serial[0]) {
utf8_to_wide(g_vmp_serial, serial_w, 1024);
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_SERIAL), serial_w);
/* 再设一次锁,便于调试查看返回值(0=成功) */
set_rc = kauth_vmp_set_serial(g_vmp_serial);
{
char tip[320];
char st[128];
wchar_t tw[320];
kauth_vmp_format_serial_state(set_rc != 0 ? set_rc : kauth_vmp_get_serial_state(), st, sizeof(st));
snprintf(tip, sizeof(tip), "SetSerial=%d;当前状态: %s", set_rc, st);
utf8_to_wide(tip, tw, 320);
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_STATE), tw);
}
if (set_rc != 0) {
char tip[512];
char st[128];
kauth_vmp_format_serial_state(set_rc, st, sizeof(st));
snprintf(tip, sizeof(tip),
"vmpSerial 已下发,但 SetSerialNumber 失败:%s\n\n"
"常见原因:加壳用的 .vmp 工程 / 产品密钥,与商家后台上传的 VMP 密钥不是同一套。\n"
"请用后台那份 .vmp 重新加壳 ka_win.exe。",
st);
show_msg(g_biz, "VMP 授权未生效", tip);
}
} else {
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_SERIAL), L"(未颁发:程序未配 VMP 密钥,或未传 hardwareId)");
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_STATE), L"设锁状态: 无序列号");
}
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_HB), L"心跳:启动中...");
if (pong_interval && pong_interval[0]) {
long v = strtol(pong_interval, NULL, 10);
if (v > 1000) interval_ms = (int)v;
else if (v > 0) interval_ms = (int)(v * 1000);
}
ShowWindow(g_login, SW_HIDE);
ShowWindow(g_biz, SW_SHOW);
SetForegroundWindow(g_biz);
kauth_stop_heartbeat();
if (kauth_start_heartbeat(interval_ms, 3, on_heartbeat) != 0) {
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_HB), L"心跳:启动失败");
} else {
snprintf(status, sizeof(status), "心跳:运行中(间隔 %d ms)", interval_ms);
{
wchar_t w[256];
utf8_to_wide(status, w, 256);
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_HB), w);
}
}
}
static void leave_business(void) {
kauth_stop_heartbeat();
kauth_logout();
kauth_set_token("");
g_vmp_serial[0] = '\0';
if (GetDlgItem(g_biz, IDC_BIZ_VMP_SERIAL))
SetWindowTextW(GetDlgItem(g_biz, IDC_BIZ_VMP_SERIAL), L"");
ShowWindow(g_biz, SW_HIDE);
ShowWindow(g_login, SW_SHOW);
set_status("已退出登录");
}
static void fill_vmp_hwid(char *dst, size_t cap) {
if (!dst || cap == 0) return;
if (kauth_vmp_get_hwid(dst, cap) != 0) dst[0] = '\0';
}
static void handle_login_ok(HWND owner, BaseResponse *r, LoginResponse *login) {
char tip[640];
if (r->code != 200) {
snprintf(tip, sizeof(tip), "失败 code=%d\n%s", r->code, r->msg);
show_msg(owner, "提示", tip);
set_status(tip);
return;
}
if (login->vmpSerial[0]) {
snprintf(tip, sizeof(tip), "登录成功(已下发 VMP 授权锁):%s", login->msg);
} else {
snprintf(tip, sizeof(tip), "登录成功:%s", login->msg);
}
set_status(tip);
enter_business(login->msg, login->pongInterval, login->vmpSerial);
}
static void do_ka_login(HWND owner) {
KaLoginRequest req;
LoginResponse login;
BaseResponse r;
memset(&req, 0, sizeof(req));
memset(&login, 0, sizeof(login));
get_edit_utf8(owner, IDC_KA_LOGIN_PWD, req.kaPwd, sizeof(req.kaPwd));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
strncpy(req.platformType, "windows", sizeof(req.platformType) - 1);
fill_vmp_hwid(req.hardwareId, sizeof(req.hardwareId));
if (!req.kaPwd[0]) { show_msg(g_login, "提示", "请输入卡密"); return; }
set_status("正在卡密登录...");
r = kauth_ka_login(&req, &login);
handle_login_ok(g_login, &r, &login);
}
static void do_ka_recharge(HWND owner) {
KaRechargeKaReq req;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
get_edit_utf8(owner, IDC_KA_RECHARGE_OLD, req.cardPwd, sizeof(req.cardPwd));
get_edit_utf8(owner, IDC_KA_RECHARGE_NEW, req.rechargeCardPwd, sizeof(req.rechargeCardPwd));
if (!req.cardPwd[0] || !req.rechargeCardPwd[0]) {
show_msg(g_login, "提示", "请输入原卡密和充值卡");
return;
}
set_status("正在以卡充卡...");
r = kauth_recharge_ka(&req);
snprintf(tip, sizeof(tip), "以卡充卡 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
}
static void do_ka_unbind(HWND owner) {
UnbindDeviceKaPwdRequest req;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
get_edit_utf8(owner, IDC_KA_UNBIND_PWD, req.kaPwd, sizeof(req.kaPwd));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
if (!req.kaPwd[0]) { show_msg(g_login, "提示", "请输入卡密"); return; }
set_status("正在卡密解绑...");
r = kauth_unbind_device_ka_pwd(&req);
snprintf(tip, sizeof(tip), "卡密解绑 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
}
static void do_acc_login(HWND owner) {
PwdLoginRequest req;
LoginResponse login;
BaseResponse r;
memset(&req, 0, sizeof(req));
memset(&login, 0, sizeof(login));
get_edit_utf8(owner, IDC_ACC_LOGIN_USER, req.userName, sizeof(req.userName));
get_edit_utf8(owner, IDC_ACC_LOGIN_PWD, req.password, sizeof(req.password));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
fill_vmp_hwid(req.hardwareId, sizeof(req.hardwareId));
if (!req.userName[0] || !req.password[0]) {
show_msg(g_login, "提示", "请输入账号和密码");
return;
}
set_status("正在账密登录...");
r = kauth_pwd_login(&req, &login);
handle_login_ok(g_login, &r, &login);
}
static void do_acc_register(HWND owner) {
RegisterRequest req;
RegisterResponse resp;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
memset(&resp, 0, sizeof(resp));
get_edit_utf8(owner, IDC_ACC_REG_USER, req.userName, sizeof(req.userName));
get_edit_utf8(owner, IDC_ACC_REG_PWD, req.password, sizeof(req.password));
get_edit_utf8(owner, IDC_ACC_REG_KA, req.kaPassword, sizeof(req.kaPassword));
if (!req.userName[0] || !req.password[0] || !req.kaPassword[0]) {
show_msg(g_login, "提示", "请输入账号、密码和卡密");
return;
}
set_status("正在注册...");
r = kauth_register(&req, &resp);
kauth_set_token(""); /* 注册不视为已登录,避免残留 token */
snprintf(tip, sizeof(tip), "账号注册 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
/* 注册成功后留在登录页,需再走账密登录 */
}
static void do_acc_change_pwd(HWND owner) {
ResetPwdRequest req;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
get_edit_utf8(owner, IDC_ACC_CHG_USER, req.userName, sizeof(req.userName));
get_edit_utf8(owner, IDC_ACC_CHG_OLD, req.oldPassword, sizeof(req.oldPassword));
get_edit_utf8(owner, IDC_ACC_CHG_NEW, req.newPassword, sizeof(req.newPassword));
/* 页面只收新密码;confirmPassword 与 newPassword 对齐后提交 */
strncpy(req.confirmPassword, req.newPassword, sizeof(req.confirmPassword) - 1);
if (!req.userName[0] || !req.oldPassword[0] || !req.newPassword[0]) {
show_msg(g_login, "提示", "请输入账号、旧密码和新密码");
return;
}
set_status("正在改密...");
r = kauth_change_password(&req);
snprintf(tip, sizeof(tip), "账号改密 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
}
static void do_acc_recharge(HWND owner) {
RechargeRequest req;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
get_edit_utf8(owner, IDC_ACC_RECHARGE_USER, req.loginName, sizeof(req.loginName));
get_edit_utf8(owner, IDC_ACC_RECHARGE_KA, req.kaPassword, sizeof(req.kaPassword));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
if (!req.loginName[0] || !req.kaPassword[0]) {
show_msg(g_login, "提示", "请输入账号和卡密");
return;
}
set_status("正在账号充值...");
r = kauth_recharge(&req);
snprintf(tip, sizeof(tip), "账号充值 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
}
static void do_acc_unbind(HWND owner) {
UnbindDeviceRequest req;
BaseResponse r;
char tip[640];
memset(&req, 0, sizeof(req));
get_edit_utf8(owner, IDC_ACC_UNBIND_USER, req.loginName, sizeof(req.loginName));
get_edit_utf8(owner, IDC_ACC_UNBIND_PWD, req.password, sizeof(req.password));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
if (!req.loginName[0] || !req.password[0]) {
show_msg(g_login, "提示", "请输入账号和密码");
return;
}
set_status("正在账号解绑...");
r = kauth_unbind_device(&req);
snprintf(tip, sizeof(tip), "账号解绑 code=%d\n%s", r.code, r.msg);
show_msg(g_login, "提示", tip);
set_status(tip);
}
static void do_trial_login(void) {
TrialLoginReq req;
LoginResponse login;
BaseResponse r;
memset(&req, 0, sizeof(req));
memset(&login, 0, sizeof(login));
strncpy(req.deviceId, g_device, sizeof(req.deviceId) - 1);
strncpy(req.platformType, "windows", sizeof(req.platformType) - 1);
fill_vmp_hwid(req.hardwareId, sizeof(req.hardwareId));
set_status("正在试用登录...");
r = kauth_trial_login(&req, &login);
handle_login_ok(g_login, &r, &login);
}
static void build_page_shell(HWND page, const wchar_t *title, const wchar_t *hint) {
create_label(page, title, 28, 18, FORM_W, 28, g_font_title, CLR_TEXT);
create_hint(page, hint, 28, 48, FORM_W, 20);
}
static void build_ka_pages(HWND parent) {
int y;
g_ka_pages[0] = create_page(parent);
build_page_shell(g_ka_pages[0], L"卡密登录", L"输入卡密后登录,成功将进入业务窗口并开启心跳");
y = add_field(g_ka_pages[0], L"卡密", IDC_KA_LOGIN_PWD, 28, 84, FORM_W, 0);
create_button(g_ka_pages[0], IDC_KA_LOGIN_BTN, L"登录", 28, y, 140, 40);
create_button(g_ka_pages[0], IDC_TRIAL_KA, L"试用登录", 180, y, 140, 40);
g_ka_pages[1] = create_page(parent);
build_page_shell(g_ka_pages[1], L"以卡充卡", L"用充值卡为原卡密续期 / 加时");
y = add_field(g_ka_pages[1], L"原卡密", IDC_KA_RECHARGE_OLD, 28, 84, FORM_W, 0);
y = add_field(g_ka_pages[1], L"充值卡", IDC_KA_RECHARGE_NEW, 28, y, FORM_W, 0);
add_action(g_ka_pages[1], IDC_KA_RECHARGE_BTN, L"充值", 28, y, 140);
g_ka_pages[2] = create_page(parent);
build_page_shell(g_ka_pages[2], L"卡密解绑", L"解绑当前设备与卡密的绑定关系");
y = add_field(g_ka_pages[2], L"卡密", IDC_KA_UNBIND_PWD, 28, 84, FORM_W, 0);
add_action(g_ka_pages[2], IDC_KA_UNBIND_BTN, L"解绑", 28, y, 140);
}
static void build_acc_pages(HWND parent) {
int y;
g_acc_pages[0] = create_page(parent);
build_page_shell(g_acc_pages[0], L"账密登录", L"使用账号和密码登录");
y = add_field(g_acc_pages[0], L"账号", IDC_ACC_LOGIN_USER, 28, 84, FORM_W, 0);
y = add_field(g_acc_pages[0], L"密码", IDC_ACC_LOGIN_PWD, 28, y, FORM_W, 1);
create_button(g_acc_pages[0], IDC_ACC_LOGIN_BTN, L"登录", 28, y, 140, 40);
create_button(g_acc_pages[0], IDC_TRIAL_ACC, L"试用登录", 180, y, 140, 40);
g_acc_pages[1] = create_page(parent);
build_page_shell(g_acc_pages[1], L"账号注册", L"注册账号并绑定卡密,成功后请再使用账密登录");
y = add_field(g_acc_pages[1], L"账号", IDC_ACC_REG_USER, 28, 84, FORM_W, 0);
y = add_field(g_acc_pages[1], L"密码", IDC_ACC_REG_PWD, 28, y, FORM_W, 1);
y = add_field(g_acc_pages[1], L"卡密", IDC_ACC_REG_KA, 28, y, FORM_W, 0);
add_action(g_acc_pages[1], IDC_ACC_REG_BTN, L"注册", 28, y, 140);
g_acc_pages[2] = create_page(parent);
build_page_shell(g_acc_pages[2], L"账号改密", L"填写账号、旧密码与新密码");
y = add_field(g_acc_pages[2], L"账号", IDC_ACC_CHG_USER, 28, 84, FORM_W, 0);
y = add_field(g_acc_pages[2], L"旧密码", IDC_ACC_CHG_OLD, 28, y, FORM_W, 1);
y = add_field(g_acc_pages[2], L"新密码", IDC_ACC_CHG_NEW, 28, y, FORM_W, 1);
add_action(g_acc_pages[2], IDC_ACC_CHG_BTN, L"改密", 28, y, 140);
g_acc_pages[3] = create_page(parent);
build_page_shell(g_acc_pages[3], L"账号充值", L"使用卡密为账号充值");
y = add_field(g_acc_pages[3], L"账号", IDC_ACC_RECHARGE_USER, 28, 84, FORM_W, 0);
y = add_field(g_acc_pages[3], L"卡密", IDC_ACC_RECHARGE_KA, 28, y, FORM_W, 0);
add_action(g_acc_pages[3], IDC_ACC_RECHARGE_BTN, L"充值", 28, y, 140);
g_acc_pages[4] = create_page(parent);
build_page_shell(g_acc_pages[4], L"账号解绑", L"解绑账号与当前设备");
y = add_field(g_acc_pages[4], L"账号", IDC_ACC_UNBIND_USER, 28, 84, FORM_W, 0);
y = add_field(g_acc_pages[4], L"密码", IDC_ACC_UNBIND_PWD, 28, y, FORM_W, 1);
add_action(g_acc_pages[4], IDC_ACC_UNBIND_BTN, L"解绑", 28, y, 140);
}
static void on_login_command(HWND hwnd, int id) {
(void)hwnd;
switch (id) {
case IDC_KA_LOGIN_BTN: do_ka_login(g_ka_pages[0]); break;
case IDC_KA_RECHARGE_BTN: do_ka_recharge(g_ka_pages[1]); break;
case IDC_KA_UNBIND_BTN: do_ka_unbind(g_ka_pages[2]); break;
case IDC_ACC_LOGIN_BTN: do_acc_login(g_acc_pages[0]); break;
case IDC_ACC_REG_BTN: do_acc_register(g_acc_pages[1]); break;
case IDC_ACC_CHG_BTN: do_acc_change_pwd(g_acc_pages[2]); break;
case IDC_ACC_RECHARGE_BTN: do_acc_recharge(g_acc_pages[3]); break;
case IDC_ACC_UNBIND_BTN: do_acc_unbind(g_acc_pages[4]); break;
case IDC_TRIAL_KA:
case IDC_TRIAL_ACC: do_trial_login(); break;
default: break;
}
}
static LRESULT CALLBACK LoginProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE: {
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icc);
g_main_tab = CreateWindowExW(0, WC_TABCONTROLW, L"",
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
0, 0, 100, 100, hwnd, (HMENU)(intptr_t)IDC_MAIN_TAB, g_inst, NULL);
SendMessageW(g_main_tab, WM_SETFONT, (WPARAM)g_font_ui, TRUE);
TabCtrl_SetItemSize(g_main_tab, 120, 32);
tab_insert(g_main_tab, 0, L"单码体系");
tab_insert(g_main_tab, 1, L"账密体系");
g_ka_tab = CreateWindowExW(0, WC_TABCONTROLW, L"",
WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
0, 0, 100, 100, hwnd, (HMENU)(intptr_t)IDC_KA_TAB, g_inst, NULL);
SendMessageW(g_ka_tab, WM_SETFONT, (WPARAM)g_font_ui, TRUE);
tab_insert(g_ka_tab, 0, L"卡密登录");
tab_insert(g_ka_tab, 1, L"以卡充卡");
tab_insert(g_ka_tab, 2, L"卡密解绑");
g_acc_tab = CreateWindowExW(0, WC_TABCONTROLW, L"",
WS_CHILD | WS_CLIPSIBLINGS,
0, 0, 100, 100, hwnd, (HMENU)(intptr_t)IDC_ACC_TAB, g_inst, NULL);
SendMessageW(g_acc_tab, WM_SETFONT, (WPARAM)g_font_ui, TRUE);
tab_insert(g_acc_tab, 0, L"账密登录");
tab_insert(g_acc_tab, 1, L"账号注册");
tab_insert(g_acc_tab, 2, L"账号改密");
tab_insert(g_acc_tab, 3, L"账号充值");
tab_insert(g_acc_tab, 4, L"账号解绑");
build_ka_pages(hwnd);
build_acc_pages(hwnd);
g_status = CreateWindowExW(0, L"STATIC", L"就绪",
WS_CHILD | WS_VISIBLE | SS_LEFT | SS_ENDELLIPSIS,
24, 0, 400, 24, hwnd, (HMENU)(intptr_t)IDC_STATUS, g_inst, NULL);
SendMessageW(g_status, WM_SETFONT, (WPARAM)g_font_small, TRUE);
SetWindowLongPtrW(g_status, GWLP_USERDATA, (LONG_PTR)CLR_MUTED);
layout_child_tabs();
return 0;
}
case WM_SIZE: {
int w = LOWORD(lParam), h = HIWORD(lParam);
SetWindowPos(g_status, NULL, 24, h - 40, w - 48, 24, SWP_NOZORDER);
layout_child_tabs();
InvalidateRect(hwnd, NULL, TRUE);
return 0;
}
case WM_ERASEBKGND: {
RECT rc;
GetClientRect(hwnd, &rc);
FillRect((HDC)wParam, &rc, g_br_bg);
return 1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
RECT rc;
HDC hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
paint_header(hdc, &rc, L"kauth", L"卡密 / 账密登录示例");
paint_footer(hdc, &rc);
EndPaint(hwnd, &ps);
return 0;
}
case WM_CTLCOLORSTATIC: {
HDC hdc = (HDC)wParam;
SetTextColor(hdc, CLR_MUTED);
SetBkColor(hdc, CLR_CARD);
return (LRESULT)g_br_card;
}
case WM_DRAWITEM:
draw_owner_button((DRAWITEMSTRUCT *)lParam, button_style_from_id((int)wParam));
return TRUE;
case WM_NOTIFY: {
NMHDR *hdr = (NMHDR *)lParam;
if (hdr && hdr->code == TCN_SELCHANGE) {
layout_child_tabs();
return 0;
}
break;
}
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED) {
on_login_command(hwnd, LOWORD(wParam));
return 0;
}
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
/**
* LockByKey 演示:加壳后无有效 vmpSerial 时,此函数体不会真正执行。
* 未定义 KAUTH_USE_VMP 时宏为空,本地调试仍可正常计算。
*/
static int biz_locked_calc(double a, int op, double b, double *out) {
int ok = 0;
if (!out) return 0;
*out = 0;
KAUTH_VMP_BEGIN_LOCK("biz_calc");
switch (op) {
case 0: *out = a + b; ok = 1; break;
case 1: *out = a - b; ok = 1; break;
case 2: *out = a * b; ok = 1; break;
case 3:
if (b == 0.0) ok = 0;
else {
*out = a / b;
ok = 1;
}
break;
default:
ok = 0;
break;
}
KAUTH_VMP_END();
return ok;
}
static LRESULT CALLBACK BizProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_CREATE: {
HWND op;
int y = HEADER_H + 24;
create_label(hwnd, L"到期信息", 32, y, 200, 20, g_font_small, CLR_MUTED);
y += 24;
CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL,
32, y, 460, 32, hwnd, (HMENU)(intptr_t)IDC_BIZ_EXPIRE, g_inst, NULL);
SendMessageW(GetDlgItem(hwnd, IDC_BIZ_EXPIRE), WM_SETFONT, (WPARAM)g_font_ui, TRUE);
y += 44;
create_label(hwnd, L"心跳状态", 32, y, 200, 20, g_font_small, CLR_MUTED);
y += 24;
CreateWindowExW(0, L"STATIC", L"心跳:未启动",
WS_CHILD | WS_VISIBLE | SS_LEFT,
32, y, 460, 24, hwnd, (HMENU)(intptr_t)IDC_BIZ_HB, g_inst, NULL);
SendMessageW(GetDlgItem(hwnd, IDC_BIZ_HB), WM_SETFONT, (WPARAM)g_font_ui, TRUE);
SetWindowLongPtrW(GetDlgItem(hwnd, IDC_BIZ_HB), GWLP_USERDATA, (LONG_PTR)CLR_TEXT);
y += 36;
create_label(hwnd, L"VMP 授权锁 vmpSerial", 32, y, 240, 20, g_font_small, CLR_MUTED);
create_button(hwnd, IDC_BIZ_VMP_DECODE, L"解密", 360, y - 4, 100, 28);
y += 22;
CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL | ES_MULTILINE,
32, y, 460, 48, hwnd, (HMENU)(intptr_t)IDC_BIZ_VMP_SERIAL, g_inst, NULL);
SendMessageW(GetDlgItem(hwnd, IDC_BIZ_VMP_SERIAL), WM_SETFONT, (WPARAM)g_font_small, TRUE);
y += 56;
CreateWindowExW(0, L"STATIC", L"设锁状态: -",
WS_CHILD | WS_VISIBLE | SS_LEFT,
32, y, 460, 22, hwnd, (HMENU)(intptr_t)IDC_BIZ_VMP_STATE, g_inst, NULL);
SendMessageW(GetDlgItem(hwnd, IDC_BIZ_VMP_STATE), WM_SETFONT, (WPARAM)g_font_small, TRUE);
SetWindowLongPtrW(GetDlgItem(hwnd, IDC_BIZ_VMP_STATE), GWLP_USERDATA, (LONG_PTR)CLR_MUTED);
y += 32;
create_label(hwnd, L"受保护计算(LockByKey 演示)", 32, y, 400, 20, g_font_small, CLR_MUTED);
y += 22;
create_hint(hwnd, L"加壳 + 登录下发 vmpSerial 后才可执行;未开 VMP 时本地仍可算", 32, y, 460, 20);
y += 28;
create_edit(hwnd, IDC_BIZ_A, 32, y, 100, 32, 0);
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_A), L"35");
op = CreateWindowExW(0, L"COMBOBOX", L"",
WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST | WS_VSCROLL,
144, y, 90, 200, hwnd, (HMENU)(intptr_t)IDC_BIZ_OP, g_inst, NULL);
SendMessageW(op, WM_SETFONT, (WPARAM)g_font_ui, TRUE);
SendMessageW(op, CB_ADDSTRING, 0, (LPARAM)L"加 +");
SendMessageW(op, CB_ADDSTRING, 0, (LPARAM)L"减 -");
SendMessageW(op, CB_ADDSTRING, 0, (LPARAM)L"乘 ×");
SendMessageW(op, CB_ADDSTRING, 0, (LPARAM)L"除 ÷");
SendMessageW(op, CB_SETCURSEL, 2, 0); /* 默认乘法 */
create_edit(hwnd, IDC_BIZ_B, 246, y, 100, 32, 0);
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_B), L"2");
create_button(hwnd, IDC_BIZ_CALC, L"计算", 360, y, 100, 36);
y += 48;
create_label(hwnd, L"结果", 32, y, 60, 20, g_font_small, CLR_MUTED);
CreateWindowExW(WS_EX_CLIENTEDGE, L"EDIT", L"",
WS_CHILD | WS_VISIBLE | ES_READONLY | ES_AUTOHSCROLL,
100, y - 4, 360, 32, hwnd, (HMENU)(intptr_t)IDC_BIZ_RESULT, g_inst, NULL);
SendMessageW(GetDlgItem(hwnd, IDC_BIZ_RESULT), WM_SETFONT, (WPARAM)g_font_ui, TRUE);
y += 52;
create_button(hwnd, IDC_BIZ_LOGOUT, L"退出登录", 32, y, 140, 40);
return 0;
}
case WM_ERASEBKGND: {
RECT rc;
GetClientRect(hwnd, &rc);
FillRect((HDC)wParam, &rc, g_br_bg);
return 1;
}
case WM_PAINT: {
PAINTSTRUCT ps;
RECT rc, card;
HDC hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
paint_header(hdc, &rc, L"kauth", L"已登录 · 业务状态");
card.left = 20;
card.top = HEADER_H + 16;
card.right = rc.right - 20;
card.bottom = rc.bottom - 20;
FillRect(hdc, &card, g_br_card);
{
HPEN pen = CreatePen(PS_SOLID, 1, CLR_LINE);
HPEN old = (HPEN)SelectObject(hdc, pen);
HBRUSH oldbr = (HBRUSH)SelectObject(hdc, GetStockObject(NULL_BRUSH));
Rectangle(hdc, card.left, card.top, card.right, card.bottom);
SelectObject(hdc, oldbr);
SelectObject(hdc, old);
DeleteObject(pen);
}
EndPaint(hwnd, &ps);
return 0;
}
case WM_CTLCOLORSTATIC: {
HDC hdc = (HDC)wParam;
HWND ctrl = (HWND)lParam;
COLORREF c = (COLORREF)GetWindowLongPtrW(ctrl, GWLP_USERDATA);
if (!c) c = CLR_TEXT;
SetTextColor(hdc, c);
SetBkColor(hdc, CLR_CARD);
return (LRESULT)g_br_card;
}
case WM_DRAWITEM:
draw_owner_button((DRAWITEMSTRUCT *)lParam, button_style_from_id((int)wParam));
return TRUE;
case WM_COMMAND:
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_BIZ_VMP_DECODE) {
show_vmp_serial_decode(hwnd);
return 0;
}
if (HIWORD(wParam) == BN_CLICKED && LOWORD(wParam) == IDC_BIZ_CALC) {
char a_s[64], b_s[64], out_s[128];
wchar_t wr[128];
double a, b, result = 0;
int op, ok, st;
/* 计算前检查授权;无效则提示,避免 VMP 直接杀进程 */
if (g_vmp_serial[0]) {
(void)kauth_vmp_set_serial(g_vmp_serial);
}
refresh_vmp_state_ui();
#ifdef KAUTH_USE_VMP
st = kauth_vmp_get_serial_state();
if (st != 0) {
char tip[512];
char sttxt[128];
kauth_vmp_format_serial_state(st, sttxt, sizeof(sttxt));
snprintf(tip, sizeof(tip),
"当前序列号无效:%s\n\n"
"有 vmpSerial 文本 ≠ 加壳程序已接受授权。\n"
"请确认:商家后台上传的 .vmp,与 VMProtect 加壳本 exe 时用的是同一工程/同一产品密钥。",
sttxt);
show_msg(hwnd, "无法计算", tip);
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_RESULT), L"授权无效,未执行受保护计算");
return 0;
}
#else
(void)st;
#endif
get_edit_utf8(hwnd, IDC_BIZ_A, a_s, sizeof(a_s));
get_edit_utf8(hwnd, IDC_BIZ_B, b_s, sizeof(b_s));
op = (int)SendMessageW(GetDlgItem(hwnd, IDC_BIZ_OP), CB_GETCURSEL, 0, 0);
a = atof(a_s);
b = atof(b_s);
ok = biz_locked_calc(a, op, b, &result);
if (!ok) {
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_RESULT), L"计算失败(除零)");
} else {
snprintf(out_s, sizeof(out_s), "%.10g", result);
utf8_to_wide(out_s, wr, 128);
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_RESULT), wr);
}
return 0;
}
if (LOWORD(wParam) == IDC_BIZ_LOGOUT && HIWORD(wParam) == BN_CLICKED) {
leave_business();
return 0;
}
break;
case WM_HB_EVENT: {
HbEvent *ev = (HbEvent *)lParam;
char tip[320];
wchar_t w[320];
COLORREF color = CLR_TEXT;
if (!ev) return 0;
switch (ev->event) {
case KAUTH_HEARTBEAT_SUCCESS:
snprintf(tip, sizeof(tip), "心跳:正常");
color = CLR_OK;
break;
case KAUTH_HEARTBEAT_NETWORK_FAIL:
snprintf(tip, sizeof(tip), "心跳:网络失败 (%d) 连续失败=%d %s", ev->code, ev->failure_count, ev->msg);
color = CLR_WARN;
break;
case KAUTH_HEARTBEAT_SERVER_FAIL:
snprintf(tip, sizeof(tip), "心跳:服务端失败 code=%d %s", ev->code, ev->msg);
color = CLR_DANGER;
break;
case KAUTH_HEARTBEAT_STOPPED:
snprintf(tip, sizeof(tip), "心跳:已停止 code=%d %s", ev->code, ev->msg);
color = CLR_MUTED;
break;
default:
snprintf(tip, sizeof(tip), "心跳:未知事件");
break;
}
SetWindowLongPtrW(GetDlgItem(hwnd, IDC_BIZ_HB), GWLP_USERDATA, (LONG_PTR)color);
utf8_to_wide(tip, w, 320);
SetWindowTextW(GetDlgItem(hwnd, IDC_BIZ_HB), w);
free(ev);
return 0;
}
case WM_CLOSE:
leave_business();
ShowWindow(hwnd, SW_HIDE);
return 0;
case WM_DESTROY:
return 0;
default:
break;
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
static void init_ui_resources(void) {
g_font_brand = CreateFontW(-22, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
g_font_title = CreateFontW(-18, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
g_font_ui = CreateFontW(-15, 0, 0, 0, FW_NORMAL, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
g_font_small = CreateFontW(-13, 0, 0, 0, FW_NORMAL, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
g_font_btn = CreateFontW(-15, 0, 0, 0, FW_SEMIBOLD, 0, 0, 0,
DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, L"Segoe UI");
g_br_bg = CreateSolidBrush(CLR_BG);
g_br_card = CreateSolidBrush(CLR_CARD);
g_br_header = CreateSolidBrush(CLR_HEADER);
}
static void free_ui_resources(void) {
if (g_font_brand) DeleteObject(g_font_brand);
if (g_font_title) DeleteObject(g_font_title);
if (g_font_ui) DeleteObject(g_font_ui);
if (g_font_small) DeleteObject(g_font_small);
if (g_font_btn) DeleteObject(g_font_btn);
if (g_br_bg) DeleteObject(g_br_bg);
if (g_br_card) DeleteObject(g_br_card);
if (g_br_header) DeleteObject(g_br_header);
}
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrev, PWSTR cmd, int show) {
WNDCLASSW wc_login, wc_biz, wc_page;
MSG msg;
int cx, cy;
(void)hPrev;
(void)cmd;
g_inst = hInstance;
init_ui_resources();
if (init_sdk() != 0) {
MessageBoxW(NULL, L"SDK 初始化失败,请检查程序配置", L"kauth", MB_ICONERROR);
free_ui_resources();
return 1;
}
memset(&wc_page, 0, sizeof(wc_page));
wc_page.lpfnWndProc = PageProc;
wc_page.hInstance = hInstance;
wc_page.lpszClassName = L"KauthPage";
wc_page.hbrBackground = g_br_card;
RegisterClassW(&wc_page);
memset(&wc_login, 0, sizeof(wc_login));
wc_login.lpfnWndProc = LoginProc;
wc_login.hInstance = hInstance;
wc_login.lpszClassName = L"KauthLoginWnd";
wc_login.hCursor = LoadCursor(NULL, IDC_ARROW);
wc_login.hbrBackground = g_br_bg;
RegisterClassW(&wc_login);
memset(&wc_biz, 0, sizeof(wc_biz));
wc_biz.lpfnWndProc = BizProc;
wc_biz.hInstance = hInstance;
wc_biz.lpszClassName = L"KauthBizWnd";
wc_biz.hCursor = LoadCursor(NULL, IDC_ARROW);
wc_biz.hbrBackground = g_br_bg;
RegisterClassW(&wc_biz);
cx = GetSystemMetrics(SM_CXSCREEN);
cy = GetSystemMetrics(SM_CYSCREEN);
g_login = CreateWindowExW(0, L"KauthLoginWnd", L"kauth",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_THICKFRAME,
(cx - 640) / 2, (cy - 620) / 2, 640, 620,
NULL, NULL, hInstance, NULL);
g_biz = CreateWindowExW(0, L"KauthBizWnd", L"kauth",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
(cx - 540) / 2, (cy - 700) / 2, 540, 700,
NULL, NULL, hInstance, NULL);
ShowWindow(g_login, show);
UpdateWindow(g_login);
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
kauth_stop_heartbeat();
free_ui_resources();
return (int)msg.wParam;
}编译示意(无 VMP):
bat
cl /nologo /O2 /MT /DUNICODE /D_UNICODE /I. ka_win.c /link kauth.lib user32.lib gdi32.lib comctl32.lib /SUBSYSTEM:WINDOWS /OUT:ka_win.exeVMProtect(可选)
默认可不启用。未定义 KAUTH_USE_VMP 时,登录与业务仍可用;HWID/序列号 API 为桩。详细入参出参见上文「VMP 辅助」。
流程
text
编译定义 KAUTH_USE_VMP + 链接 VMProtectSDK64.lib
→ 登录填 hardwareId(ka_win 已调用 kauth_vmp_get_hwid)
→ 服务端返回 vmpSerial(商家已上传 .vmp)
→ SDK 自动 SetSerialNumber
→ 用同一份 .vmp 对 exe 加壳并启用 Licensing
→ 敏感逻辑用 KAUTH_VMP_BEGIN_LOCK(见 ka_win 计算器)商家后台
程序 → VMP 密钥:上传与加壳 同一份 .vmp。可选参数(过期日 / 最大编译日 / 运行时间等)由 VMP 运行时自动校验,一般不必手写判断。卡密业务到期仍由 kauth 心跳管理。
保护业务代码
c
#include "kauth_vmp.h"
KAUTH_VMP_BEGIN_LOCK("biz_calc");
/* ... */
KAUTH_VMP_END();Begin/End 必须同函数成对。加壳与后台签发必须同一 .vmp,否则序列号 INVALID。
ka_win 业务窗「解密」按钮会弹窗展示 kauth_vmp_get_serial_info 明文。
常见问题
没有 SDK 源码怎么办? 正常。用网盘的 kauth.lib + 头文件 + 示例即可。
MinGW? 不支持,请用 MSVC。
/MD 冲突? 必须 /MT。
没有 vmpSerial? 程序未配 VMP 密钥时不下发,属正常。
有 vmpSerial 但 LockByKey 失败? 加壳 .vmp 须与后台一致。
注册成功会进业务吗? 不会,需再登录(ka_win 已按此处理)。
不要做的事
- 不要用
/MD或 MinGW 链本库 - 不要期望拿到 SDK 内部源码
- 不要把演示配置里的密钥直接用于生产(请换成你自己的)
网盘再次确认:
https://pan.kauth.cn/C语言卡密核心源码
旧版「C 动态库」说明见 C语言接口。新工程请用本文静态库交付包。