Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
Binary file not shown.
Binary file not shown.
17 changes: 11 additions & 6 deletions DEPLOYMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@

```
OpenYCP/
├── server_python/ # Python Flask 实现
│ ├── app.py # 主应用
├── server_python/ # Python Flask 实现(含 Web 管理面板)
│ ├── app.py # 主应用(授权 API + Web 面板 API + 静态文件)
│ ├── requirements.txt # Python 依赖
│ ├── Dockerfile # Docker 镜像
│ ├── Dockerfile # Docker 镜像(多阶段:自动构建前端)
│ ├── web/ # 前端构建产物(npm run build 生成)
│ └── README_CN.md # Python 版文档
├── server_go/ # Go Gin 实现(高性能)
│ ├── main.go # 主应用
├── server_go/ # Go Gin 实现(高性能,含 Web 管理面板
│ ├── main.go # 主应用(授权 API + Web 面板 API + embed 前端)
│ ├── go.mod # Go 依赖
│ ├── Dockerfile # Docker 镜像
│ ├── Dockerfile # Docker 镜像(多阶段:自动构建前端)
│ ├── web/ # 前端构建产物(embed 嵌入二进制)
│ └── README_CN.md # Go 版文档
├── admin-web/ # Web 管理面板前端源码(React,构建后嵌入服务器)
├── nginx/ # Nginx 反向代理配置
│ └── nginx.conf
├── docker-compose.yml # Docker Compose 编排
└── DEPLOYMENT.md # 本文档
```

> **Web 管理面板已内置**: 启动任一授权服务器后,浏览器访问 `http://localhost:13337/` 即可使用 Web 管理面板(登录页点 "First time? Initialize admin" 初始化账号,与原生协议共用同一账号体系)。

---

## 🚀 快速开始(3 种方式)
Expand Down
6 changes: 6 additions & 0 deletions admin-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

Modern web-based administration panel for YCPPlus license key management.

> **已整合进授权服务器**: 自 2026-08 起,此前独立的 Spring Boot 后端已移植进 `server_go`(Gin)与 `server_python`(Flask),前端构建产物直接嵌入服务器。**无需再单独部署本目录的 backend/frontend**,启动任一授权服务器后访问 `http://localhost:13337/` 即可使用 Web 管理面板。
>
> 本目录现仅作为前端源码(`frontend/`)与原型参考保留。重新构建前端:`cd frontend && npm install && npm run build`,然后将 `dist/` 内容分别拷贝到 `server_go/web/` 与 `server_python/web/`。开发模式下 `npm run dev` 会将 `/api` 代理到 `http://localhost:13337`。
>
> 与独立版的差异:账号密码与原生协议统一(SHA256 哈希、共用 `applications`/`keys` 表),即 Web 面板、原生 Admin Panel(Java GUI)、native `/login` 客户端验证共用同一套数据。

## Architecture

- **Backend**: Spring Boot 3.2 + JWT Authentication
Expand Down
2 changes: 1 addition & 1 deletion admin-web/frontend/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default defineConfig({
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
target: 'http://localhost:13337',
changeOrigin: true
}
}
Expand Down
12 changes: 6 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
version: '3.8'

services:
# Python 版本授权服务器
# Python 版本授权服务器(含 Web 管理面板)
ycp-auth-python:
build:
context: ./server_python
dockerfile: Dockerfile
context: .
dockerfile: server_python/Dockerfile
container_name: ycp-auth-python
ports:
- "13337:13337"
Expand All @@ -15,11 +15,11 @@ services:
environment:
- FLASK_ENV=production

# Go 版本授权服务器(高性能)
# Go 版本授权服务器(高性能,含 Web 管理面板
ycp-auth-go:
build:
context: ./server_go
dockerfile: Dockerfile
context: .
dockerfile: server_go/Dockerfile
container_name: ycp-auth-go
ports:
- "13338:13337" # 使用不同端口避免冲突
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.yumegod.obfuscator.utils.cfg.annotations.ConfigSection;
import com.yumegod.obfuscator.utils.cfg.annotations.StaticConfigReceiver;
import com.yumegod.obfuscator.utils.filter.marker.Marker;
import com.yumegod.obfuscator.utils.protection.QQUtils;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.ClassRemapper;
Expand Down Expand Up @@ -56,8 +55,9 @@ public class YumeCloudProtection {
public static String applicationName;
@ConfigSection("native.auth")
public static boolean auth = true;
// @ConfigSection("auth_url")
public static String authorizationURL = "http://protection.yumegod.com:13337/";
// 自建授权服务器地址(server_go / server_python 均提供 /login 端点)
@ConfigSection("native.auth_url")
public static String authorizationURL = "http://127.0.0.1:13337/";
@ConfigSection("misc.safe_mode")
public static boolean safeMode = false;

Expand Down Expand Up @@ -162,12 +162,6 @@ public void processWithConfig(File configFile) throws Exception {
@SuppressWarnings("BusyWait")
private static Thread getWaitingThread() {
Thread thread = new Thread(() -> {
try {
if (QQUtils.isStupidUser()) {
Runtime.getRuntime().halt(0);
return;
}
} catch (Exception ignored) {}
char[] chars = {'|', '/', '-', '\\'};
try {
int i = 0;
Expand Down Expand Up @@ -387,16 +381,16 @@ public void process(File inputJarFile, File outputJarFile, Path outputDir, List<
Util.copyResource("sources/native_jvm.hpp", cppDir);
Util.copyResource("sources/native_jvm_output.hpp", cppDir);

Util.copyResource("sources/Authorization.h", cppDir);
Util.copyResource("sources/Authorization.lib", cppDir);
Util.copyResource("sources/Authorization.dll", cppDir);

// VMProtect SDK 桩(开源版 no-op 实现;持有商业授权时可用官方头文件覆盖)
Util.copyResource("sources/VMProtectSDK.h", cppDir);
Util.copyResource("sources/VMProtectSDK64.lib", cppDir);
Util.copyResource("sources/VMProtectSDK64.dll", cppDir);
Util.copyResource("sources/vmp.exe", cppDir);
Util.copyResource("sources/YumeCloud_NativeLibrary.vmp", cppDir);
Util.copyResource("sources/YumeCloud_NativeLibrary_NoAuth.vmp", cppDir);

// VMProtect 为可选商业组件:仅在 resources 中存在时复制,
// 缺省(开源分发)自动降级为未加壳构建
Util.copyResourceIfExists("sources/VMProtectSDK64.lib", cppDir);
Util.copyResourceIfExists("sources/VMProtectSDK64.dll", cppDir);
Util.copyResourceIfExists("sources/vmp.exe", cppDir);
Util.copyResourceIfExists("sources/YumeCloud_NativeLibrary.vmp", cppDir);
Util.copyResourceIfExists("sources/YumeCloud_NativeLibrary_NoAuth.vmp", cppDir);
for (ClassNode hiddenClass : hiddenMethodsPool.getClasses()) {
String hiddenClassFileName = "data_" + Util.escapeCppNameString(hiddenClass.name.replace('/', '_'));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ public ClassSourceBuilder(Path cppOutputDir, String className, int classIndex) t

public void addHeader(int strings, int classes, int methods, int fields) throws IOException {
cppWriter.append("#include \"../native_jvm.hpp\"\n");
cppWriter.append("#include \"../Authorization.h\"\n");
cppWriter.append("#include \"../VMProtectSDK.h\"\n");
cppWriter.append("#include <string>\n");
cppWriter.append("#include \"").append(getHppFilename()).append("\"\n");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.yumegod.obfuscator.j2c.source;

import com.yumegod.obfuscator.Main;
import com.yumegod.obfuscator.YumeCloudProtection;
import com.yumegod.obfuscator.utils.Util;

import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;

public class MainSourceBuilder {
Expand Down Expand Up @@ -39,32 +37,19 @@ public void registerDefine(String stringPooledClassName, String classFileName) {

public String build(String nativeDir, int classCount) throws IOException {
String template = Util.readResource("sources/native_jvm_output.cpp");
// 授权客户端已内置于 native_jvm.cpp(开放实现),验证失败时返回 JNI_ERR 终止加载
String authorization = "";
if (YumeCloudProtection.auth) {
includes.append("#include \"Authorization.h\"");
}
StringBuilder dllBytes = new StringBuilder();
if (YumeCloudProtection.auth) {
dllBytes.append("unsigned char dllBytes[] = {");
InputStream dll = MainSourceBuilder.class.getResourceAsStream("/sources/Authorization.dll");
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = dll.read(buffer)) != -1) {
for (int j = 0; j < bytesRead; j++) {
dllBytes.append((int) buffer[j] & 0xFF).append(",");
}
}
dllBytes.append("};").append("\n");
dllBytes.append("size_t dllSize = sizeof(dllBytes);").append("\n");
authorization = " if (!native_jvm::utils::auth(VMProtectDecryptStringA(\"" + YumeCloudProtection.applicationName
+ "\"), VMProtectDecryptStringA(\"" + YumeCloudProtection.authorizationURL + "login\"))) return JNI_ERR;\n";
}
Comment on lines 42 to 45
return Util.dynamicFormat(template, Util.createMap(
"watermark", new String(Base64.getDecoder().decode("ICAgICAgICBzdGQ6OmNvdXQgPDwKICAgICAgICAiXG49PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiIKICAgICAgICAiIF9fICAgICBfXyAgICAgICAgICAgICAgICAgICAgX19fX18gXyAgICAgICAgICAgICAgICAgXyBcbiIKICAgICAgICAiIFxcIFxcICAgLyAvICAgICAgICAgICAgICAgICAgIC8gX19fX3wgfCAgICAgICAgICAgICAgIHwgfFxuIgogICAgICAgICIgIFxcIFxcXy8gLyAgIF8gXyBfXyBfX18gICBfX198IHwgICAgfCB8IF9fXyAgXyAgIF8gIF9ffCB8XG4iCiAgICAgICAgIiAgIFxcICAgLyB8IHwgfCAnXyBgIF8gXFwgLyBfIFxcIHwgICAgfCB8LyBfIFxcfCB8IHwgfC8gX2AgfFxuIgogICAgICAgICIgICAgfCB8fCB8X3wgfCB8IHwgfCB8IHwgIF9fLyB8X19fX3wgfCAoXykgfCB8X3wgfCAoX3wgfFxuIgogICAgICAgICIgICAgfF98IFxcX18sX3xffCB8X3wgfF98XFxfX198XFxfX19fX3xffFxcX19fLyBcXF9fLF98XFxfXyxfXG4iCiAgICAgICAgIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuIgogICAgICAgICJUaGlzIGFwcGxpY2F0aW9uIGlzIHByb3RlY3RlZCBieSBZdW1lQ2xvdWRcbiIKICAgICAgICA8PCBzdGQ6OmVuZGw7")),
"watermark", new String(Base64.getDecoder().decode("ICAgICAgICBzdGQ6OmNvdXQgPDwKICAgICAgICAiXG49PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT1cbiIKICAgICAgICAiIF9fICAgICBfXyAgICAgICAgICAgICAgICAgICAgX19fX18gXyAgICAgICAgICAgICAgICAgXyBcbiIKICAgICAgICAiIFxcIFxcICAgLyAvICAgICAgICAgICAgICAgICAgIC8gX19fX3wgfCAgICAgICAgICAgICAgIHwgfFxuIgogICAgICAgICIgIFxcIFxcXy8gLyAgIF8gXyBfXyBfX8KgICAgX19fX3wgfCAgICB8IHwgX19fICBfICAgXyAgXyAgXyAgXyAgX3wgfFwuciIKICAgICAgICAiICAgXFwgICAvIHwgfCAnXyBgIF8gXFwgLyBfIFxcIHwgICAgfCB8LyBfIFxcfCB8IHwgfC8gX2AgfFxuIgogICAgICAgICIgICB8IHx8IHx3fCB8IHwgfCB8IHwgIF9fLyB8X19fX3wgfCAoXykgfCB8X3wgfCAoX3wgfFxuIgogICAgICAgICIgICB8X3wgXFxfXyxffCB8X3wgfF98XFxfX198XFxfX19fX3xffFxcX19fLyBcXF9fLF98XFxfXyxfXG4iCiAgICAgICAgIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PVxuIgogICAgICAgICJUaGlzIGFwcGxpY2F0aW9uIGlzIHByb3RlY3RlZCBieSBZdW1lQ2xvdWRcbiIKICAgICAgICA8PCBzdGQ6OmVuZGw7")),
"register_code", registerMethods,
"auth_library", YumeCloudProtection.auth ? " native_jvm::utils::init_auth(dllBytes, dllSize);\n" : "",
"dllBytes", dllBytes.toString(),
"includes", includes,
"native_dir", nativeDir,
"class_count", classCount,
"authorization", YumeCloudProtection.auth ? " native_jvm::utils::auth(VMProtectDecryptStringA(\"" + YumeCloudProtection.applicationName + "\"), VMProtectDecryptStringA(\"" + YumeCloudProtection.authorizationURL + "login\"));\n" : ""
"authorization", authorization
));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ public static void compile(Path cppDir, Path outputDir, boolean hotSpot) {

command.append("@" + sources.getAbsolutePath()).append(" ");
command.append("-L\"" + cppDir.toAbsolutePath()).append("\" ");
command.append("-lAuthorization").append(" ");
command.append("-lVMProtectSDK64").append(" ");
// WinSock2:开放授权客户端的 HTTP 通信所需(原由闭源 Authorization.lib 传递链接)
command.append("-lws2_32").append(" ");
// VMProtect SDK 为可选组件:仅当用户在 resources 中提供了官方 lib 时链接(默认使用内置 no-op 桩)
if (Files.exists(cppDir.resolve("VMProtectSDK64.lib"))) {
command.append("-lVMProtectSDK64").append(" ");
}
command.append("-shared");

// For debugging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,25 @@ public class Protect {
private static final Logger logger = LoggerFactory.getLogger(Protect.class);

public static void protect(Path cppDir, Path outputDir) {
logger.info("Start protecting native library...");
try {
String protectCommand = "vmp.exe ";
String project = YumeCloudProtection.auth ? "YumeCloud_NativeLibrary.vmp" : "YumeCloud_NativeLibrary_NoAuth.vmp";

if (YumeCloudProtection.auth) {
protectCommand += "YumeCloud_NativeLibrary.vmp";
} else {
protectCommand += "YumeCloud_NativeLibrary_NoAuth.vmp";
// VMProtect 为可选商业组件:vmp.exe 或对应 .vmp 工程缺失时跳过加壳,直接打包未加壳的原生库
if (!Files.exists(cppDir.resolve("vmp.exe")) || !Files.exists(cppDir.resolve(project))) {
logger.info("VMProtect not found (vmp.exe or {} missing). Skipping protection, shipping the unprotected native library.", project);
try {
Path library = cppDir.resolve("YumeCloud_NativeLibrary.dll");
if (!Files.exists(library)) library = cppDir.resolve("YumeCloud_NativeLibrary");
Util.addFileToZip("YumeCloudProtection/YCVM",
Files.readAllBytes(library), new File(outputDir.toAbsolutePath().toString()));
} catch (IOException e) {
logger.error("Failed to package the unprotected native library.", e);
}
return;
}

logger.info("Start protecting native library...");
try {
String protectCommand = "vmp.exe " + project;

ProcessBuilder processBuilder = new ProcessBuilder();
processBuilder.command("cmd", "/c", protectCommand);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.yumegod.obfuscator.jobf.protection.Protector;
import com.yumegod.obfuscator.jobf.transformer.impl.*;
import com.yumegod.obfuscator.jobf.transformer.impl.flow.ControlFlowObfuscator;
import com.yumegod.obfuscator.jobf.transformer.impl.flow.v2.FlowObfuscator;
import com.yumegod.obfuscator.jobf.transformer.impl.fun.DeadCodeRemover;
import com.yumegod.obfuscator.jobf.transformer.impl.fun.DirectoryClassFileTransformer;
import com.yumegod.obfuscator.jobf.transformer.impl.fun.WatermarkTransformer;
Expand Down
30 changes: 30 additions & 0 deletions obfuscator/src/main/java/com/yumegod/obfuscator/utils/Util.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,15 @@ public static void copyResource(String from, Path to) throws IOException {
}
}

// 资源存在时复制并返回 true,缺失时静默跳过(用于可选的 VMProtect 商业组件)
public static boolean copyResourceIfExists(String from, Path to) throws IOException {
try (InputStream in = YumeCloudProtection.class.getClassLoader().getResourceAsStream(from)) {
if (in == null) return false;
Files.copy(in, to.resolve(Paths.get(from).getFileName()), StandardCopyOption.REPLACE_EXISTING);
return true;
}
}

private static String writeStreamToString(InputStream stream) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Expand Down Expand Up @@ -156,6 +165,27 @@ public static String escapeCommentString(String value) {
return result.toString();
}

// 转义为合法 C++ 字符串字面量(用于生成的 .cpp 源码中的字符串常量)
public static String escapeString(String value) {
StringBuilder result = new StringBuilder(value.length());
for (char c : value.toCharArray()) {
switch (c) {
case '\\': result.append("\\\\"); break;
case '"': result.append("\\\""); break;
case '\n': result.append("\\n"); break;
case '\r': result.append("\\r"); break;
case '\t': result.append("\\t"); break;
default:
if (c >= 32 && c <= 126) {
result.append(c);
} else {
result.append(String.format("\\x%02x\"\"", (int) c & 0xFF));
}
}
}
return result.toString();
}

private static String unicodify(String string) {
StringBuilder result = new StringBuilder();
for (char c : string.toCharArray()) {
Expand Down
1 change: 1 addition & 0 deletions obfuscator/src/main/resources/default-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"native": {
"auth": true,
"auth_url": "http://127.0.0.1:13337/",
"app_name": "MyApplication",
"platform": "HOTSPOT",
"call_encryption": false,
Expand Down
Binary file not shown.
7 changes: 0 additions & 7 deletions obfuscator/src/main/resources/sources/Authorization.h

This file was deleted.

Binary file not shown.
Loading