@xiaozhu2007/koishi-plugin-litematic-renderer 0.4.30 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
Binary file
|
package/lib/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { Context, Schema, Session } from 'koishi';
|
|
1
|
+
import { Context, h, Schema, Session } from 'koishi';
|
|
2
2
|
export declare const name = "litematic-renderer";
|
|
3
3
|
export declare const inject: {
|
|
4
4
|
optional: string[];
|
|
5
5
|
};
|
|
6
6
|
export interface Config {
|
|
7
|
+
qqBotType: QqBotType;
|
|
7
8
|
standaloneJavaCommand: string;
|
|
8
9
|
minecraftJarPath: string;
|
|
9
10
|
resourcePackPaths: string[];
|
|
@@ -14,20 +15,22 @@ export interface Config {
|
|
|
14
15
|
standaloneJavaRetryMaxHeapMb: number;
|
|
15
16
|
standaloneJavaMemoryRestartLimit: number;
|
|
16
17
|
maxFileSize: number;
|
|
18
|
+
maxBlocks: number;
|
|
17
19
|
outputSize: number;
|
|
18
|
-
cellSize: number;
|
|
19
20
|
isometricCellSize: number;
|
|
20
21
|
background: string;
|
|
21
22
|
transparentBackground: boolean;
|
|
22
23
|
sendAsForward: boolean;
|
|
23
24
|
showViewTitles: boolean;
|
|
24
25
|
replyAndMention: boolean;
|
|
26
|
+
sixFaceOverview: boolean;
|
|
27
|
+
sixFaceLayout: SixFaceLayout;
|
|
25
28
|
groupSendOptions: GroupSendOption[];
|
|
26
29
|
renderTimeout: number;
|
|
27
30
|
cacheDirectory: string;
|
|
28
31
|
cacheMaxSizeGb: number;
|
|
29
32
|
diagnosticsFilePath: string;
|
|
30
|
-
|
|
33
|
+
patchOneBotGroupUpload: boolean;
|
|
31
34
|
javaBridgeDirectory: string;
|
|
32
35
|
gpuRendererCommand: string;
|
|
33
36
|
javaRenderTimeout: number;
|
|
@@ -43,6 +46,8 @@ export interface Config {
|
|
|
43
46
|
}
|
|
44
47
|
export type SendMode = 'forward' | 'combined';
|
|
45
48
|
export type ReplyAndMentionOverride = 'inherit' | 'enabled' | 'disabled';
|
|
49
|
+
export type SixFaceLayout = 'horizontal' | 'vertical';
|
|
50
|
+
export type QqBotType = 'official' | 'selfHosted';
|
|
46
51
|
export interface GroupSendOption {
|
|
47
52
|
groupId: string;
|
|
48
53
|
sendMode: SendMode;
|
|
@@ -85,30 +90,48 @@ export interface LitematicMetadata {
|
|
|
85
90
|
}
|
|
86
91
|
export declare function apply(ctx: Context, config: Config): void;
|
|
87
92
|
export declare function hashRenderConfiguration(configuration: unknown): string;
|
|
93
|
+
interface SourcePatchResult {
|
|
94
|
+
source: string;
|
|
95
|
+
changed: boolean;
|
|
96
|
+
alreadyCompatible: boolean;
|
|
97
|
+
}
|
|
98
|
+
export declare function patchOneBotAdapterSource(source: string): SourcePatchResult;
|
|
88
99
|
export declare function cacheNameSegment(filename: string): string;
|
|
89
100
|
export declare function enforceCacheLimit(cacheDirectory: string, maxBytes: number, protectedDirectory?: string): Promise<{
|
|
90
101
|
totalBytes: number;
|
|
91
102
|
removedBytes: number;
|
|
92
103
|
removedEntries: number;
|
|
93
104
|
}>;
|
|
94
|
-
|
|
105
|
+
type FileSession = Pick<Session, 'elements' | 'content'> & {
|
|
106
|
+
event?: unknown;
|
|
107
|
+
_data?: unknown;
|
|
108
|
+
data?: unknown;
|
|
109
|
+
message?: unknown;
|
|
110
|
+
};
|
|
111
|
+
export declare function findLitematicFile(session: FileSession): FileElement | undefined;
|
|
95
112
|
export declare function supportsStandaloneJavaVersion(version: number | undefined): boolean;
|
|
96
113
|
export declare function standaloneJvmOptions(maxHeapMb: number): string[];
|
|
97
114
|
export declare function isJavaMemoryFailure(output: string, exitCode?: number | null): boolean;
|
|
98
115
|
export declare function formatRenderError(error: unknown, config: Pick<Config, 'maxFileSize'>): string;
|
|
99
116
|
export interface ResolvedSendOptions {
|
|
117
|
+
qqBotType: QqBotType;
|
|
100
118
|
sendMode: SendMode;
|
|
101
119
|
replyAndMention: boolean;
|
|
102
120
|
showViewTitles: boolean;
|
|
121
|
+
sixFaceOverview: boolean;
|
|
103
122
|
}
|
|
104
|
-
type SendConfig = Pick<Config, 'sendAsForward' | 'replyAndMention' | 'groupSendOptions'> & Partial<Pick<Config, 'showViewTitles'>>;
|
|
123
|
+
type SendConfig = Pick<Config, 'sendAsForward' | 'replyAndMention' | 'groupSendOptions'> & Partial<Pick<Config, 'qqBotType' | 'showViewTitles' | 'sixFaceOverview'>>;
|
|
105
124
|
export declare function resolveSendOptions(config: SendConfig, groupId?: string): ResolvedSendOptions;
|
|
106
|
-
export declare function sendImages(session: Session, images: ImageResult[], metadata: string, options: ResolvedSendOptions): Promise<void>;
|
|
125
|
+
export declare function sendImages(session: Session, images: ImageResult[], metadata: string, options: ResolvedSendOptions, projectionName?: string): Promise<void>;
|
|
126
|
+
export declare function composeQqOverview(images: ImageResult[]): Promise<string>;
|
|
127
|
+
export declare function replyElements(session: Session, qqBotType?: QqBotType): h[];
|
|
107
128
|
export declare function parseLitematicMetadata(data: Buffer): LitematicMetadata;
|
|
129
|
+
export declare function validateProjectionLimits(data: Buffer, metadata: LitematicMetadata, configuredLimit?: number): void;
|
|
108
130
|
export declare function formatLitematicMetadata(metadata: LitematicMetadata, filename?: string): string;
|
|
109
131
|
export declare function parseLitematic(data: Buffer): Block[];
|
|
110
132
|
export declare function renderSchematic(blocks: Block[], config: Config): {
|
|
111
133
|
title: string;
|
|
112
134
|
png: Buffer<ArrayBuffer>;
|
|
113
135
|
}[];
|
|
136
|
+
export declare function renderSixFaceOverview(blocks: Block[], config: Config, layout?: SixFaceLayout): Buffer<ArrayBuffer>;
|
|
114
137
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Config=exports.inject=exports.name=void 0,exports.apply=apply,exports.hashRenderConfiguration=hashRenderConfiguration,exports.cacheNameSegment=cacheNameSegment,exports.enforceCacheLimit=enforceCacheLimit,exports.findLitematicFile=findLitematicFile,exports.supportsStandaloneJavaVersion=supportsStandaloneJavaVersion,exports.standaloneJvmOptions=standaloneJvmOptions,exports.isJavaMemoryFailure=isJavaMemoryFailure,exports.formatRenderError=formatRenderError,exports.resolveSendOptions=resolveSendOptions,exports.sendImages=sendImages,exports.parseLitematicMetadata=parseLitematicMetadata,exports.formatLitematicMetadata=formatLitematicMetadata,exports.parseLitematic=parseLitematic,exports.renderSchematic=renderSchematic;const koishi_1=require("koishi"),node_crypto_1=require("node:crypto"),node_fs_1=require("node:fs"),node_path_1=require("node:path"),node_url_1=require("node:url"),node_child_process_1=require("node:child_process"),node_zlib_1=require("node:zlib");exports.name="litematic-renderer",exports.inject={optional:["puppeteer","server"]};const CACHE_FORMAT_VERSION=13,packageVersion=require("../package.json").version,PLUGIN_VERSION="string"==typeof packageVersion?packageVersion:"unknown";exports.Config=koishi_1.Schema.object({standaloneJavaCommand:koishi_1.Schema.string().default("").description("【重点】独立渲染器使用的 Java 可执行文件;推荐 Java 21+,留空自动查找,也可填写自定义路径或命令"),minecraftJarPath:koishi_1.Schema.string().default("").description("次要设置:可选的 Minecraft 客户端 JAR 或基础资源包;留空或路径不存在时使用插件内置的 26.2 原版资源"),resourcePackPaths:koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("次要设置:独立渲染的自定义材质包路径;默认为空,使用插件内置的 26.2 原版资源,越靠后优先级越高"),renderEngine:koishi_1.Schema.union([koishi_1.Schema.const("standalone"),koishi_1.Schema.const("java"),koishi_1.Schema.const("webgl"),koishi_1.Schema.const("cpu")]).default("standalone").description("渲染引擎;standalone 为无需客户端的独立 Java 资源包渲染器"),standaloneRendererJar:koishi_1.Schema.string().default("").description("独立渲染器 JAR;留空使用插件内置版本"),standaloneRenderTimeout:koishi_1.Schema.natural().min(1e4).default(18e4).description("独立 Java 渲染超时(毫秒)"),standaloneJavaMaxHeapMb:koishi_1.Schema.natural().min(128).max(32768).step(8).default(200).description("首次独立 Java 渲染的最大堆内存(MiB)"),standaloneJavaRetryMaxHeapMb:koishi_1.Schema.natural().min(256).max(32768).step(128).default(2048).description("首次渲染内存不足时,新 Java 进程重试使用的最大堆内存(MiB)"),standaloneJavaMemoryRestartLimit:koishi_1.Schema.natural().max(3).default(1).description("检测到 Java 内存不足后,启动全新进程重试的次数"),maxFileSize:koishi_1.Schema.natural().min(1).default(1024).description("自动处理的最大文件大小(KB)"),outputSize:koishi_1.Schema.natural().min(128).max(4096).default(1024).description("正交视图最长边的最大像素数"),cellSize:koishi_1.Schema.natural().min(1).max(32).default(8).description("正交投影每个方块的基础像素大小"),isometricCellSize:koishi_1.Schema.natural().min(2).max(32).default(7).description("正二轴测方块菱形半宽"),background:koishi_1.Schema.string().default("#000000").description("PNG 背景颜色"),transparentBackground:koishi_1.Schema.boolean().default(!1).description("输出透明背景"),sendAsForward:koishi_1.Schema.boolean().default(!1).description("默认发送方式;开启为合并转发,关闭为两张图片和信息组成一条普通消息"),showViewTitles:koishi_1.Schema.boolean().default(!1).description("发送图片时显示正二轴测和反向正二轴测标题;默认关闭以节省空间"),replyAndMention:koishi_1.Schema.boolean().default(!1).description("默认是否引用原消息并 @ 投影发送者"),groupSendOptions:koishi_1.Schema.array(koishi_1.Schema.object({groupId:koishi_1.Schema.string().description("QQ群号"),sendMode:koishi_1.Schema.union([koishi_1.Schema.const("forward").description("合并转发"),koishi_1.Schema.const("combined").description("联合发送(一条普通消息)")]).default("forward"),replyAndMention:koishi_1.Schema.union([koishi_1.Schema.const("inherit").description("继承全局设置"),koishi_1.Schema.const("enabled").description("开启回复 @"),koishi_1.Schema.const("disabled").description("关闭回复 @")]).default("inherit")})).default([]).description("按群覆盖发送方式和回复 @ 设置;相同群号以最后一项为准"),renderTimeout:koishi_1.Schema.natural().min(1e3).default(3e4).description("下载与渲染超时(毫秒)"),cacheDirectory:koishi_1.Schema.string().default("data/litematic-renderer-cache").description("持久缓存目录;按插件版本和投影 SHA-256 分区"),cacheMaxSizeGb:koishi_1.Schema.number().min(1).max(1024).step(1).default(20).description("所有版本缓存的总上限(GiB),超出后按最久未使用清理"),diagnosticsFilePath:koishi_1.Schema.string().default("data/litematic-renderer-diagnostics.json").description("无法正常渲染的方块诊断持久化文件路径"),diagnosticsExport:koishi_1.Schema.string().default("/litematic-renderer/diagnostics").role("link").description("导出错误报告(点击下载 JSON)"),javaRenderTimeout:koishi_1.Schema.natural().min(1e4).default(18e4).description("等待 Minecraft Java 渲染完成的超时(毫秒)"),javaResolution:koishi_1.Schema.natural().min(256).max(4096).default(1024).description("Java 渲染最终输出边长"),javaSupersampling:koishi_1.Schema.natural().min(1).max(4).default(2).description("Fabrishot 式离屏超采样倍数"),webglQuality:koishi_1.Schema.union([koishi_1.Schema.const("standard"),koishi_1.Schema.const("high"),koishi_1.Schema.const("ultra")]).default("high").description("WebGL 渲染质量"),webglWidth:koishi_1.Schema.natural().min(256).max(2048).default(800).description("WebGL 单张图宽度"),webglHeight:koishi_1.Schema.natural().min(256).max(2048).default(600).description("WebGL 单张图高度"),isometricSquare:koishi_1.Schema.boolean().default(!0).description("将正二轴测图输出为正方形"),isometricFill:koishi_1.Schema.percent().default(.78).description("正二轴测主体在画布中的最大占比"),isometricRotation:koishi_1.Schema.number().min(0).max(360).step(1).default(135).description("Isometric Renders 绕竖直轴的基准旋转角"),isometricSlant:koishi_1.Schema.number().min(-90).max(90).step(1).default(36).description("Isometric Renders 俯仰角;36° 为正二轴测预设"),javaBridgeDirectory:koishi_1.Schema.string().default("").description("Fabric Java 渲染桥任务目录;桥接功能已隐藏,仅保留兼容配置").hidden(),gpuRendererCommand:koishi_1.Schema.string().default("").description("GPU 渲染器命令;高级功能已隐藏,仅保留兼容配置").hidden()});const activeStandaloneJavaProcesses=new Set;function apply(e,t){const a=e.logger(exports.name),i=1024*t.maxFileSize;e.on("dispose",()=>terminateActiveStandaloneJavaProcesses(a));const n=(0,node_path_1.resolve)(stringOrDefault(t.diagnosticsFilePath,"data/litematic-renderer-diagnostics.json")),r=(0,node_path_1.join)((0,node_path_1.dirname)(n),"litematic-renderer-diagnostics-archive"),o=(0,node_path_1.join)((0,node_path_1.dirname)(n),"litematic-renderer-diagnostics-disabled");rotateDiagnosticsFile(n,r,a);const s=(0,node_path_1.resolve)(stringOrDefault(t.cacheDirectory,"data/litematic-renderer-cache")),c=(0,node_path_1.join)(s,`v${cachePathSegment(PLUGIN_VERSION)}`),d=Math.floor(t.cacheMaxSizeGb*1024**3),l=new Map;node_fs_1.promises.mkdir(c,{recursive:!0}).then(()=>enforceCacheLimit(s,d)).catch(e=>a.warn(`缓存初始化失败:${e instanceof Error?e.message:String(e)}`));const m=async(r,o="schematic.litematic",m)=>{const u=m??await download(e,r,i,t.renderTimeout),h=formatLitematicMetadata(parseLitematicMetadata(u),o),f=(0,node_crypto_1.createHash)("sha256").update(u).digest("hex"),p="standalone"===t.renderEngine?await resolveMinecraftResources(t):"",g="standalone"===t.renderEngine?await fingerprintFiles([p,...resourcePackPaths(t.resourcePackPaths)]):[],_=hashRenderConfiguration({version:CACHE_FORMAT_VERSION,outputSize:t.outputSize,cellSize:t.cellSize,isometricCellSize:t.isometricCellSize,background:t.background,transparentBackground:t.transparentBackground,showViewTitles:t.showViewTitles,gpuRendererCommand:t.gpuRendererCommand,renderEngine:t.renderEngine,standaloneRendererJar:t.standaloneRendererJar,resourceFingerprint:g,javaResolution:t.javaResolution,javaSupersampling:t.javaSupersampling,webglQuality:t.webglQuality,webglWidth:t.webglWidth,webglHeight:t.webglHeight,isometricSquare:t.isometricSquare,isometricFill:t.isometricFill,isometricRotation:t.isometricRotation,isometricSlant:t.isometricSlant}),w=cacheNameSegment(o),S=(0,node_path_1.join)(c,`${w}-${f}-${_}`),v=(0,node_path_1.join)(S,"projection.litematic");await node_fs_1.promises.mkdir(S,{recursive:!0}),await exists(v)||await node_fs_1.promises.writeFile(v,u),await ensureCacheMetadata(S,{pluginVersion:PLUGIN_VERSION,cacheFormatVersion:CACHE_FORMAT_VERSION,contentSha256:f,renderConfigSha256:_,sourceFilename:(0,node_path_1.basename)(o),createdAt:(new Date).toISOString()});const y=["isometric.png","isometric-reverse.png"].map(e=>(0,node_path_1.join)(S,e));if((await Promise.all(y.map(exists))).every(Boolean))a.debug(`命中投影缓存:${f}`);else{let i=l.get(S);i?a.debug(`等待相同投影的缓存任务:${f}`):(i=(async()=>{try{if((await Promise.all(y.map(exists))).every(Boolean))return;if(!Boolean(t.gpuRendererCommand&&await renderWithGpu(t.gpuRendererCommand,v,S,t.renderTimeout,a))||!(await Promise.all(y.map(exists))).every(Boolean))if("standalone"===t.renderEngine)await renderWithStandalone(v,S,p,t,a),await mergeRenderDiagnostics(S,n,a);else if("java"===t.renderEngine)await renderWithJavaBridge(v,S,t);else{const i=parseLitematic(u);let n;if("webgl"===t.renderEngine&&e.puppeteer)try{n=await renderWithWebgl(e,u,t)}catch(e){a.warn(`WebGL 渲染失败,回退 CPU:${e instanceof Error?e.message:String(e)}`),n=renderSchematic(i,t)}else n=renderSchematic(i,t);await Promise.all(n.map(e=>node_fs_1.promises.writeFile((0,node_path_1.join)(S,e.title),e.png)))}if(!(await Promise.all(y.map(exists))).every(Boolean))throw new Error("渲染器没有生成两张正二轴测 PNG")}catch(e){throw await appendRenderError(S,n,t.renderEngine,e,a),e}})().finally(()=>l.delete(S)),l.set(S,i)),await i}return await touchCacheEntry(S),await enforceCacheLimit(s,d,S),{images:y.map((e,t)=>({title:["正二轴测","反向正二轴测(旋转 180°)"][t],path:e})),metadata:h}};e.middleware(async(e,r)=>{if(!e.guildId)return r();const o=findLitematicFile(e),s=o&&await resolveFileUrl(e,o);if(!s||!o)return r();if(isOverLimit(o.size,i))return await appendGlobalRenderError(n,`input:${(0,node_path_1.basename)(o.name??"unknown")}`,"input",`file size exceeds ${t.maxFileSize} KB`,a),await e.send([...replyElements(e),(0,koishi_1.h)("text",{content:`文件大小超过 ${(t.maxFileSize/1024).toFixed(2)} MB,无法渲染`})]),r();if(!isUnderLimit(o.size,i))return r();try{const a=await m(s,o.name);await sendImages(e,a.images,a.metadata,resolveSendOptions(t,e.guildId))}catch(i){return a.warn(i),await e.send([...replyElements(e),(0,koishi_1.h)("text",{content:formatRenderError(i,t)})]),r()}return r()}),e.command("litematic.render <url:string>","渲染可直接下载的 .litematic 文件").action(async({session:e},a)=>{if(!a)return"请提供 .litematic 文件的直链";if(!e)return"此命令只能在消息会话中执行";try{const i=await m(a);await sendImages(e,i.images,i.metadata,resolveSendOptions(t,e.guildId))}catch(e){return formatRenderError(e,t)}}),e.command("litematic.cache.clear","清理投影渲染缓存",{authority:3}).action(async()=>(await node_fs_1.promises.rm(s,{recursive:!0,force:!0}),await node_fs_1.promises.mkdir(c,{recursive:!0}),"投影渲染缓存已清理")),e.command("litematic.errors.export","export render diagnostics",{authority:3}).action(async()=>`Diagnostics file: ${n}`),e.command("litematic.errors.disable","disable current render diagnostics snapshot",{authority:3}).action(async()=>disableDiagnosticsSnapshot(n,o));const u=e.server;u?.get&&u.get("/litematic-renderer/diagnostics",async e=>{e.type="application/json",e.set("Content-Disposition",'attachment; filename="litematic-renderer-diagnostics.json"'),e.body=await node_fs_1.promises.readFile(n,"utf8").catch(()=>'{"format":2,"blocks":[],"errors":[]}')})}function hashRenderConfiguration(e){return(0,node_crypto_1.createHash)("sha256").update(JSON.stringify(e)).digest("hex").slice(0,16)}function cachePathSegment(e){return e.replace(/[^a-zA-Z0-9._-]/g,"_")||"unknown"}function cacheNameSegment(e){return((0,node_path_1.basename)(e||"schematic.litematic",(0,node_path_1.extname)(e||"")).replace(/[<>:"/\\|?*\u0000-\u001f]/g,"_").replace(/[. ]+$/g,"").trim()||"schematic").slice(0,80)}async function ensureCacheMetadata(e,t){const a=(0,node_path_1.join)(e,"cache.json");await exists(a)||await node_fs_1.promises.writeFile(a,JSON.stringify(t,null,2)+"\n")}async function touchCacheEntry(e){const t=new Date;await node_fs_1.promises.utimes(e,t,t).catch(()=>{})}async function directorySize(e){let t=0;const a=await node_fs_1.promises.readdir(e,{withFileTypes:!0}).catch(e=>{if("ENOENT"===e?.code)return[];throw e});for(const i of a){const a=(0,node_path_1.join)(e,i.name);i.isDirectory()?t+=await directorySize(a):i.isFile()&&(t+=(await node_fs_1.promises.stat(a)).size)}return t}async function enforceCacheLimit(e,t,a){const i=[],n=await node_fs_1.promises.readdir(e,{withFileTypes:!0}).catch(e=>{if("ENOENT"===e?.code)return[];throw e});for(const t of n){if(!t.isDirectory())continue;const a=(0,node_path_1.join)(e,t.name),n=await node_fs_1.promises.readdir(a,{withFileTypes:!0});for(const e of n){if(!e.isDirectory())continue;const t=(0,node_path_1.join)(a,e.name),[n,r]=await Promise.all([directorySize(t),node_fs_1.promises.stat(t)]);i.push({path:t,size:n,lastUsed:r.mtimeMs})}}let r=i.reduce((e,t)=>e+t.size,0),o=0,s=0;const c=a&&(0,node_path_1.resolve)(a);i.sort((e,t)=>e.lastUsed-t.lastUsed);for(const e of i){if(r<=t)break;c&&(0,node_path_1.resolve)(e.path)===c||(await node_fs_1.promises.rm(e.path,{recursive:!0,force:!0}),r-=e.size,o+=e.size,s++)}return{totalBytes:r,removedBytes:o,removedEntries:s}}function fileElements(e){return e.elements??koishi_1.h.parse(e.content??"")}function findLitematicFile(e){return fileElements(e).find(e=>"file"===e.type&&".litematic"===(0,node_path_1.extname)(e.attrs.name??"").toLowerCase())?.attrs}async function resolveFileUrl(e,t){if(t.url||t.src)return t.url??t.src;const a=t.id??t.file_id??t.fileId,i=e.bot.internal;if(!e.guildId||!a||"function"!=typeof i?.getGroupFileUrl)return;const n=await i.getGroupFileUrl(e.guildId,a,Number(t.busid??0));return"string"==typeof n?n:n?.url}function isUnderLimit(e,t){if(null==e||""===e)return!0;const a=Number(e);return Number.isFinite(a)&&a>0&&a<=t}async function download(e,t,a,i){const n=await e.http.get(t,{responseType:"arraybuffer",timeout:i}),r=Buffer.from(n);if(r.byteLength>a)throw new Error(`文件超过 ${(a/1024/1024).toFixed(2)} MB 限制`);return r}async function exists(e){try{return await node_fs_1.promises.access(e,node_fs_1.constants.R_OK),!0}catch{return!1}}function stringOrDefault(e,t){return"string"==typeof e&&e.trim()?e:t}function resourcePackPaths(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&Boolean(e.trim())):[]}async function fingerprintFiles(e){return Promise.all(e.filter(e=>"string"==typeof e&&Boolean(e.trim())).map(async e=>{const t=(0,node_path_1.resolve)(e);try{const e=await node_fs_1.promises.stat(t);return{path:t,size:e.size,modified:e.mtimeMs}}catch{return{path:t,missing:!0}}}))}async function resolveStandaloneJavaCommand(e){const t=e.trim();if(t)return t;const a="win32"===process.platform?"java.exe":"java",i=new Set;if(process.env.JAVA_HOME&&i.add((0,node_path_1.join)(process.env.JAVA_HOME,"bin",a)),"win32"===process.platform){const e=new Set([(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Java"),(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Eclipse Adoptium"),(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Microsoft","jdk")]);for(const t of e)try{const e=await node_fs_1.promises.readdir(t,{withFileTypes:!0});for(const n of e)n.isDirectory()&&/(?:^|[-_])21(?:[._-]|$)/.test(n.name)&&i.add((0,node_path_1.join)(t,n.name,"bin",a))}catch{}}i.add("java");for(const e of i)if(supportsStandaloneJavaVersion(await javaMajorVersion(e)))return e;throw new Error("未找到 Java 21+;请安装 Java 21 或更高版本,设置 JAVA_HOME,或填写 standaloneJavaCommand")}function isOverLimit(e,t){if(null==e||""===e)return!1;const a=Number(e);return Number.isFinite(a)&&a>t}async function javaMajorVersion(e){return new Promise(t=>{let a="",i=!1;const n=e=>{i||(i=!0,clearTimeout(s),t(e))},r=(0,node_child_process_1.spawn)(e,["-version"],{shell:!1,windowsHide:!0}),o=e=>{a=(a+String(e)).slice(-2e3)};r.stdout?.on("data",o),r.stderr?.on("data",o),r.once("error",()=>n()),r.once("close",()=>{const e=a.match(/(?:java|openjdk) version "(\d+)/i);n(e?Number(e[1]):void 0)});const s=setTimeout(()=>{r.kill(),n()},5e3)})}function supportsStandaloneJavaVersion(e){return void 0!==e&&e>=21}function standaloneJvmOptions(e){return["-Xms128m",`-Xmx${Math.max(128,Math.min(32768,Math.floor(e)))}m`,"-XX:+UseG1GC","-XX:+UseStringDeduplication","-XX:+ExitOnOutOfMemoryError"]}function isJavaMemoryFailure(e,t){return 137===t||/outofmemoryerror|java heap space|gc overhead limit|cannot reserve enough space|native memory allocation|insufficient memory|malloc failed|os::commit_memory/i.test(e)}async function resolveMinecraftResources(e){const t="string"==typeof e.minecraftJarPath?e.minecraftJarPath.trim():"";if(t&&await exists((0,node_path_1.resolve)(t)))return(0,node_path_1.resolve)(t);const a=(0,node_path_1.resolve)((0,node_path_1.join)(__dirname,"../assets/vanilla-resources/Minecraft-26.2-Vanilla-Resources.zip"));if(!await exists(a))throw new Error(`插件内置的 Minecraft 26.2 原版资源包不存在:${a}`);return a}async function renderWithStandalone(e,t,a,i,n){const r=(0,node_path_1.resolve)(i.standaloneRendererJar||(0,node_path_1.join)(__dirname,"../assets/standalone-renderer/litematic-standalone-renderer-0.2.6.jar"));if(!await exists(r))throw new Error(`独立 Java 渲染器不存在:${r}`);if(!await exists(a))throw new Error(`Minecraft 资源 JAR 不存在:${a}`);for(const e of resourcePackPaths(i.resourcePackPaths))if(!await exists((0,node_path_1.resolve)(e)))throw new Error(`材质包不存在:${e}`);const o=["-Djava.awt.headless=true","-jar",r,"--input",(0,node_path_1.resolve)(e),"--output",(0,node_path_1.resolve)(t),"--minecraft-jar",a,"--resolution",String(i.javaResolution),"--supersampling",String(i.javaSupersampling),"--rotation",String(i.isometricRotation),"--slant",String(i.isometricSlant),"--fill",String(i.isometricFill),"--background",i.background];i.transparentBackground&&o.push("--transparent-background");for(const e of resourcePackPaths(i.resourcePackPaths))o.push("--resource-pack",(0,node_path_1.resolve)(e));const s=await resolveStandaloneJavaCommand(i.standaloneJavaCommand);for(let e=0;e<=i.standaloneJavaMemoryRestartLimit;e++){const a=0===e?i.standaloneJavaMaxHeapMb:i.standaloneJavaRetryMaxHeapMb,r=await runStandaloneJava(s,[...standaloneJvmOptions(a),...o],i.standaloneRenderTimeout);if(0===r.code)return;const c=isJavaMemoryFailure(r.stderr,r.code);if(c&&e<i.standaloneJavaMemoryRestartLimit){n.warn(`独立 Java 渲染超出 ${i.standaloneJavaMaxHeapMb} MiB 堆内存限制;正在启动全新进程重试(${e+1}/${i.standaloneJavaMemoryRestartLimit})`),await Promise.all(["isometric.png","isometric-reverse.png"].map(e=>node_fs_1.promises.rm((0,node_path_1.join)(t,e),{force:!0})));continue}const d=c?`;Java 内存不足(最大堆 ${i.standaloneJavaMaxHeapMb} MiB,已重启 ${e} 次)`:"";throw new Error(`独立 Java 渲染器退出码 ${r.code}${d}${r.stderr?`:${r.stderr.trim()}`:""}`)}}async function runStandaloneJava(e,t,a){return new Promise((i,n)=>{const r=(0,node_child_process_1.spawn)(e,t,{shell:!1,windowsHide:!0});activeStandaloneJavaProcesses.add(r);let o="",s=!1;const c=()=>activeStandaloneJavaProcesses.delete(r);r.stderr?.on("data",e=>{o=(o+String(e)).slice(-6e3)});const d=setTimeout(()=>{s||(s=!0,(async()=>{await terminateStandaloneJavaProcess(r),c(),n(new Error("Standalone Java render timed out; process terminated and memory reclaimed"))})())},a);r.once("error",e=>{s||(s=!0,clearTimeout(d),terminateStandaloneJavaProcess(r),c(),n(e))}),r.once("close",e=>{s||(s=!0,clearTimeout(d),c(),i({code:e,stderr:o}))})})}async function terminateStandaloneJavaProcess(e){const t=e.pid;t&&("win32"===process.platform&&await new Promise(e=>{const a=(0,node_child_process_1.spawn)("taskkill.exe",["/PID",String(t),"/T","/F"],{windowsHide:!0});a.once("close",()=>e()),a.once("error",()=>e())}),null!=e.exitCode||e.killed||e.kill())}async function terminateActiveStandaloneJavaProcesses(e){const t=[...activeStandaloneJavaProcesses];t.length&&(await Promise.all(t.map(e=>terminateStandaloneJavaProcess(e))),e.info(`已终止 ${t.length} 个独立渲染 Java 进程并回收其子进程`))}async function renderWithJavaBridge(e,t,a){if(!a.javaBridgeDirectory.trim())throw new Error("Fabric Java 渲染桥任务目录未配置;使用 java 后端时请填写 javaBridgeDirectory");const i=(0,node_path_1.resolve)(a.javaBridgeDirectory),n=await readJson((0,node_path_1.join)(i,"status.json"));if(!n?.timestamp||Date.now()-n.timestamp>5e3)throw new Error("Minecraft Java 渲染桥未运行;请启动 1.21.1-Fabric 客户端并进入一个世界");if(!n.inWorld)throw new Error("Minecraft Java 渲染桥已启动,但客户端尚未进入世界");const r=n.resourcePacks??[];if(![["XeKr","3.6forMC1.20.2~1.21.5.zip"],["XKRDA","1.19.4~1.21snapshot.zip"]].every(e=>r.some(t=>e.every(e=>t.includes(e)))))throw new Error("Minecraft 客户端没有同时启用 XeKr 红显基础包和 XKRDA 附加包");const o=(0,node_crypto_1.randomUUID)(),s=(0,node_path_1.join)(i,"jobs"),c=(0,node_path_1.join)(i,"results",`${o}.result.json`),d=(0,node_path_1.join)(s,`${o}.job.json`),l=`${d}.tmp`;await node_fs_1.promises.mkdir(s,{recursive:!0}),await node_fs_1.promises.rm(c,{force:!0}),await node_fs_1.promises.writeFile(l,JSON.stringify({id:o,input:(0,node_path_1.resolve)(e),outputDirectory:(0,node_path_1.resolve)(t),resolution:a.javaResolution,supersampling:a.javaSupersampling,rotation:a.isometricRotation,slant:a.isometricSlant,fill:a.isometricFill,background:a.background,transparentBackground:a.transparentBackground})),await node_fs_1.promises.rename(l,d);const m=Date.now()+a.javaRenderTimeout;for(;Date.now()<m;){const e=await readJson(c);if(e){if(await node_fs_1.promises.rm(c,{force:!0}),!e.success)throw new Error(`Minecraft Java 渲染失败:${e.error??"未知错误"}`);return}await new Promise(e=>setTimeout(e,200))}throw new Error(`Minecraft Java 渲染超过 ${Math.round(a.javaRenderTimeout/1e3)} 秒`)}async function readJson(e){try{return JSON.parse(await node_fs_1.promises.readFile(e,"utf8"))}catch(e){if("ENOENT"===e?.code||e instanceof SyntaxError)return;throw e}}async function mergeRenderDiagnostics(e,t,a){const i=(0,node_path_1.join)(e,"render-diagnostics.json"),n=await readJson(i);if(!n)return;const r=(0,node_path_1.resolve)(t),o=await readJson(r)??{format:2,generatedAt:(new Date).toISOString(),blocks:[],errors:[]},s=new Map;for(const e of o.blocks??[])s.set(`${e.state}\0${e.reason}`,e);for(const e of n.blocks??[]){const t=`${e.state}\0${e.reason}`,a=s.get(t);a?(a.count=Number(a.count??0)+Number(e.count??0),a.samples=[...a.samples??[],...e.samples??[]].slice(0,20)):s.set(t,{...e,samples:e.samples??[]})}const c=new Map;for(const e of o.errors??[])c.set(`${e.renderId}\0${e.engine}\0${e.message}`,e);for(const e of n.errors??[]){const t=`${e.renderId}\0${e.engine}\0${e.message}`,a=c.get(t);a?a.count=Number(a.count??0)+Number(e.count??0):c.set(t,{...e})}const d={format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[...s.values()],errors:[...c.values()]};await node_fs_1.promises.mkdir((0,node_path_1.resolve)(t,".."),{recursive:!0}).catch(()=>{}),await node_fs_1.promises.writeFile(r,JSON.stringify(d,null,2)+"\n"),a.warn(`记录 ${n.blocks?.length??0} 类无法正常渲染的方块;诊断已保存到 ${r}`)}async function rotateDiagnosticsFile(e,t,a){const i=await readJson(e);if(!i||i.pluginVersion===PLUGIN_VERSION)return;await node_fs_1.promises.mkdir(t,{recursive:!0});const n=cachePathSegment("string"==typeof i.pluginVersion?i.pluginVersion:"legacy"),r=formatDiagnosticsTimestamp(new Date),o=(0,node_path_1.join)(t,`litematic-renderer-diagnostics-v${n}-${r}.json`);await node_fs_1.promises.rename(e,o),a.info(`已将旧版诊断汇总归档到 ${o}`)}async function disableDiagnosticsSnapshot(e,t){if(!await readJson(e))return"当前没有可禁用的诊断汇总";await node_fs_1.promises.mkdir(t,{recursive:!0});const a=(0,node_path_1.join)(t,`litematic-renderer-diagnostics-disabled-${formatDiagnosticsTimestamp(new Date)}.json`);return await node_fs_1.promises.rename(e,a),await node_fs_1.promises.writeFile(e,JSON.stringify({format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[],errors:[]},null,2)+"\n"),`当前诊断已移入禁用文件夹:${a}`}async function appendGlobalRenderError(e,t,a,i,n){const r=(0,node_path_1.resolve)(e),o=await readJson(r)??{format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[],errors:[]};o.format=2,o.pluginVersion=PLUGIN_VERSION,o.errors??=[];const s=o.errors.find(e=>e.renderId===t&&e.engine===a&&e.message===i);s?s.count=Number(s.count??0)+1:o.errors.push({renderId:t,engine:a,message:i,count:1,generatedAt:(new Date).toISOString()}),await node_fs_1.promises.mkdir((0,node_path_1.dirname)(r),{recursive:!0}),await node_fs_1.promises.writeFile(r,JSON.stringify(o,null,2)+"\n"),n.warn(`渲染问题已记录到 ${r}`)}function formatDiagnosticsTimestamp(e){const t=e=>String(e).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}async function appendRenderError(e,t,a,i,n){const r=(0,node_path_1.join)(e,"render-diagnostics.json"),o=await readJson(r)??{format:2,generatedAt:(new Date).toISOString(),blocks:[],errors:[]};o.format=2,o.errors??=[];const s=i instanceof Error?i.message:String(i),c=(0,node_path_1.basename)(e),d=o.errors.find(e=>e.renderId===c&&e.engine===a&&e.message===s);d?d.count=Number(d.count??0)+1:o.errors.push({renderId:c,engine:a,message:s,count:1,generatedAt:(new Date).toISOString()}),await node_fs_1.promises.writeFile(r,JSON.stringify(o,null,2)+"\n"),await mergeRenderDiagnostics(e,t,n)}function formatRenderError(e,t){const a=e instanceof Error?e.message:String(e);return/文件超过|file size|file.*limit/i.test(a)?`文件大小超过 ${(t.maxFileSize/1024).toFixed(2)} MB,不渲染`:/java 21\+|未找到.*java|java.*not found/i.test(a)?"未找到 Java 21+,请在插件配置中检查 Java 可执行文件设置":/standalone renderer|独立.*渲染器.*不存在|渲染器.*not exist/i.test(a)?"独立渲染器不可用,请重装插件或检查 Java 渲染配置":/minecraft.*resource|resource pack|原版资源|材质包.*不存在/i.test(a)?"Minecraft 原版资源包不可用,请检查资源包配置":"投影渲染失败,请检查渲染器配置或导出诊断文件"}async function renderWithGpu(e,t,a,i,n){try{return await new Promise((n,r)=>{const o=(0,node_child_process_1.spawn)(e,["--input",t,"--output",a,"--views","isometric,isometric-reverse"],{shell:!1,windowsHide:!0}),s=setTimeout(()=>{o.kill(),r(new Error("GPU 渲染超时"))},i);o.once("error",r),o.once("exit",e=>0===e?n():r(new Error(`GPU 渲染器退出码:${e}`))),o.once("close",()=>clearTimeout(s))}),!0}catch(e){return n.warn(`GPU 渲染器不可用,回退 CPU:${e instanceof Error?e.message:String(e)}`),!1}}async function renderWithWebgl(e,t,a){const i=await e.puppeteer.page();try{const e=(0,node_url_1.pathToFileURL)((0,node_path_1.resolve)(__dirname,"../assets/threeviews/index.html")).href;await i.goto(e,{waitUntil:"networkidle0",timeout:a.renderTimeout}),await i.waitForFunction(()=>Boolean(window.litematicViewerAPI&&window.deepslateResources),{timeout:a.renderTimeout});const n=await i.evaluate(async({base64:e,width:t,height:a,quality:i,isometricSquare:n,isometricFill:r,isometricRotation:o,isometricSlant:s})=>{const c=atob(e),d=new Uint8Array(c.length);for(let e=0;e<c.length;e++)d[e]=c.charCodeAt(e);const l=new File([d],"schematic.litematic",{type:"application/octet-stream"});return await window.litematicViewerAPI.loadFromFile(l),window.litematicViewerAPI.renderIsometricViews({width:t,height:a,quality:i,isometricSquare:n,isometricFill:r,isometricRotation:o,isometricSlant:s})},{base64:t.toString("base64"),width:a.webglWidth,height:a.webglHeight,quality:a.webglQuality,isometricSquare:a.isometricSquare,isometricFill:a.isometricFill,isometricRotation:a.isometricRotation,isometricSlant:a.isometricSlant});return[{title:"isometric.png",png:decodeDataUrl(n.isometricView)},{title:"isometric-reverse.png",png:decodeDataUrl(n.reverseIsometricView)}]}finally{await i.close().catch(()=>{})}}function decodeDataUrl(e){if("string"!=typeof e||!e.startsWith("data:image/"))throw new Error("WebGL 渲染器没有返回 PNG 数据");const t=e.indexOf(",");if(t<0)throw new Error("WebGL 渲染器返回了无效图像数据");return Buffer.from(e.slice(t+1),"base64")}function resolveSendOptions(e,t){const a=t?[...e.groupSendOptions].reverse().find(e=>e.groupId.trim()===t):void 0,i="enabled"===a?.replyAndMention||"disabled"!==a?.replyAndMention&&e.replyAndMention;return{sendMode:a?.sendMode??(e.sendAsForward?"forward":"combined"),replyAndMention:i,showViewTitles:e.showViewTitles??!1}}async function sendImages(e,t,a,i){const n=await Promise.all(t.map(async({title:e,path:t})=>({title:e,source:`base64://${(await node_fs_1.promises.readFile(t)).toString("base64")}`}))),r=n.map(({title:t,source:a})=>(0,koishi_1.h)("message",{userId:e.selfId,nickname:"投影渲染"},[...i.showViewTitles?[(0,koishi_1.h)("text",{content:t})]:[],(0,koishi_1.h)("img",{src:a})])),o=(0,koishi_1.h)("message",{userId:e.selfId,nickname:"投影信息"},[(0,koishi_1.h)("text",{content:a})]);if("forward"===i.sendMode)return void await e.send((0,koishi_1.h)("figure",{},[...r,o]));const s=i.replyAndMention?replyElements(e):[],c=n.flatMap(({title:e,source:t},a)=>[...i.showViewTitles?[(0,koishi_1.h)("text",{content:`${a?"\n":""}${e}\n`})]:[],(0,koishi_1.h)("img",{src:t})]);await e.send([...s,...c,(0,koishi_1.h)("text",{content:`\n${a}`})])}function replyElements(e){const t=[];return e.messageId&&t.push((0,koishi_1.h)("quote",{id:e.messageId})),e.userId&&t.push((0,koishi_1.h)("at",{id:e.userId})),t.length&&t.push((0,koishi_1.h)("text",{content:"\n"})),t}class NbtReader{data;offset=0;constructor(e){this.data=e}byte(){return this.data.readInt8(this.offset++)}ubyte(){return this.data.readUInt8(this.offset++)}short(){const e=this.data.readInt16BE(this.offset);return this.offset+=2,e}int(){const e=this.data.readInt32BE(this.offset);return this.offset+=4,e}float(){const e=this.data.readFloatBE(this.offset);return this.offset+=4,e}double(){const e=this.data.readDoubleBE(this.offset);return this.offset+=8,e}long(){const e=this.data.readBigInt64BE(this.offset);return this.offset+=8,e}text(){const e=this.data.readUInt16BE(this.offset);this.offset+=2;const t=this.data.toString("utf8",this.offset,this.offset+e);return this.offset+=e,t}readNamedRoot(){if(10!==this.ubyte())throw new Error("不是 NBT Compound 数据");return this.text(),this.value(10)}value(e){switch(e){case 0:return null;case 1:return this.byte();case 2:return this.short();case 3:return this.int();case 4:return this.long();case 5:return this.float();case 6:return this.double();case 7:{const e=this.int(),t=this.data.subarray(this.offset,this.offset+e);return this.offset+=e,t}case 8:return this.text();case 9:{const e=this.ubyte(),t=this.int();return Array.from({length:t},()=>this.value(e))}case 10:{const e={};for(;;){const t=this.ubyte();if(!t)return e;e[this.text()]=this.value(t)}}case 11:{const e=this.int();return Array.from({length:e},()=>this.int())}case 12:{const e=this.int();return Array.from({length:e},()=>this.long())}default:throw new Error(`不支持的 NBT 标签类型:${e}`)}}}const MINECRAFT_DATA_VERSIONS=new Map([[4903,"26.2"],[4790,"26.1.2"],[4671,"1.21.11"],[4557,"1.21.10"],[4555,"1.21.9"],[4440,"1.21.8"],[4438,"1.21.7"],[4435,"1.21.6"],[4325,"1.21.5"],[4189,"1.21.4"],[4082,"1.21.3"],[4080,"1.21.2"],[3955,"1.21.1"],[3953,"1.21"],[3839,"1.20.6"],[3837,"1.20.5"],[3700,"1.20.4"],[3698,"1.20.3"],[3578,"1.20.2"],[3465,"1.20.1"],[3463,"1.20"],[3337,"1.19.4"],[3218,"1.19.3"],[3120,"1.19.2"],[3117,"1.19.1"],[3105,"1.19"],[2975,"1.18.2"]]);function readLitematicRoot(e){let t;try{t=(0,node_zlib_1.gunzipSync)(e)}catch{t=e}return new NbtReader(t).readNamedRoot()}function compoundValue(e){return!e||"object"!=typeof e||Array.isArray(e)||Buffer.isBuffer(e)?void 0:e}function numericValue(e){if("number"==typeof e&&Number.isFinite(e))return e;if("bigint"==typeof e){const t=Number(e);if(Number.isSafeInteger(t))return t}}function readMetadataSize(e,t){const a=compoundValue(t.EnclosingSize),i=a&&[numericValue(a.x),numericValue(a.y),numericValue(a.z)];if(i?.every(e=>null!=e))return i.map(e=>Math.abs(e));const n=compoundValue(e.Regions);if(!n)return;let r=1/0,o=1/0,s=1/0,c=-1/0,d=-1/0,l=-1/0;for(const e of Object.values(n)){const t=compoundValue(e),a=compoundValue(t?.Position),i=compoundValue(t?.Size),n=numericValue(a?.x),m=numericValue(a?.y),u=numericValue(a?.z),h=numericValue(i?.x),f=numericValue(i?.y),p=numericValue(i?.z);if([n,m,u,h,f,p].some(e=>null==e)||0===h||0===f||0===p)continue;const g=n+h-Math.sign(h),_=m+f-Math.sign(f),w=u+p-Math.sign(p);r=Math.min(r,n,g),o=Math.min(o,m,_),s=Math.min(s,u,w),c=Math.max(c,n,g),d=Math.max(d,m,_),l=Math.max(l,u,w)}return Number.isFinite(r)?[c-r+1,d-o+1,l-s+1]:void 0}function formatCreatedAt(e){if(null==e||e<=0)return"未知";const t=new Date(e<1e10?1e3*e:e);if(Number.isNaN(t.getTime()))return"未知";const a=new Intl.DateTimeFormat("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(t),i=Object.fromEntries(a.map(e=>[e.type,e.value]));return`${i.year}-${i.month}-${i.day} ${i.hour}:${i.minute}:${i.second}`}function parseLitematicMetadata(e){const t=readLitematicRoot(e),a=compoundValue(t.Metadata)??{},i=numericValue(t.MinecraftDataVersion);return{author:"string"==typeof a.Author&&a.Author.trim()?a.Author.trim():"未知",createdAt:formatCreatedAt(numericValue(a.TimeCreated)),totalBlocks:numericValue(a.TotalBlocks),totalVolume:numericValue(a.TotalVolume),size:readMetadataSize(t,a),litematicVersion:numericValue(t.Version),minecraftDataVersion:i,minecraftVersion:null==i?void 0:MINECRAFT_DATA_VERSIONS.get(i)}}function formatLitematicMetadata(e,t){const a=null==e.totalBlocks?"未知":String(e.totalBlocks),i=null==e.totalVolume?"未知":String(e.totalVolume),n=e.size?.join(" × ")??"未知",r=null==e.litematicVersion?"未知":String(e.litematicVersion),o=e.minecraftVersion??"未知",s=null==e.minecraftDataVersion?"":`(数据版本:${e.minecraftDataVersion})`,c="string"==typeof t&&t.trim()?(0,node_path_1.basename)(t).replace(/\.litematic$/i,""):"";return[...c?[`投影名称:${c}`]:[],`保存者游戏 ID:${e.author}`,`创建时间:${e.createdAt}`,`方块数/体积:${a}/${i}`,`尺寸:${n}`,`Litematic 版本:${r}`,`游戏版本:${o}${s}`].join("\n")}function parseLitematic(e){const t=readLitematicRoot(e).Regions;if(!t||"object"!=typeof t)throw new Error("文件不含 Litematica Regions 数据");const a=[];for(const e of Object.values(t))parseRegion(e,a);if(!a.length)throw new Error("投影中没有非空气方块");return a}function parseRegion(e,t){const a=e.Position,i=e.Size,n=e.BlockStatePalette,r=e.BlockStates;if(!(a&&i&&Array.isArray(n)&&Array.isArray(r)))return;const o=Math.abs(i.x),s=Math.abs(i.y),c=Math.abs(i.z),d=o*s*c;if(!d)return;const l=Math.max(2,Math.ceil(Math.log2(n.length))),m=i.x<0?-1:1,u=i.y<0?-1:1,h=i.z<0?-1:1;for(let e=0;e<d;e++){const i=unpackState(r,e,l),s=n[i]?.Name;if(!s||"minecraft:air"===s||"minecraft:cave_air"===s||"minecraft:void_air"===s)continue;const d=e%o,f=Math.floor(e/o)%c,p=Math.floor(e/(o*c));t.push({x:a.x+d*m,y:a.y+p*u,z:a.z+f*h,name:s})}}function unpackState(e,t,a){const i=BigInt(t*a),n=Number(i/64n),r=i%64n;if(n>=e.length)return 0;const o=(1n<<BigInt(a))-1n;let s=BigInt.asUintN(64,e[n])>>r&o;return r+BigInt(a)>64n&&n+1<e.length&&(s|=BigInt.asUintN(64,e[n+1])<<64n-r&o),Number(s)}function renderSchematic(e,t){const a=getBounds(e);return[{title:"isometric.png",png:renderIsometric(e,a,t,!1)},{title:"isometric-reverse.png",png:renderIsometric(e,a,t,!0)}]}function getBounds(e){return e.reduce((e,t)=>({minX:Math.min(e.minX,t.x),minY:Math.min(e.minY,t.y),minZ:Math.min(e.minZ,t.z),maxX:Math.max(e.maxX,t.x),maxY:Math.max(e.maxY,t.y),maxZ:Math.max(e.maxZ,t.z)}),{minX:1/0,minY:1/0,minZ:1/0,maxX:-1/0,maxY:-1/0,maxZ:-1/0})}function renderOrthographic(e,t,a,i){const n="side"===a?t.maxZ-t.minZ+1:t.maxX-t.minX+1,r="top"===a?t.maxZ-t.minZ+1:t.maxY-t.minY+1,o=Math.max(1,Math.min(i.cellSize,Math.floor(i.outputSize/Math.max(n,r)))),s=new Raster(n*o,r*o,i),c=new Map;for(const i of e){const e="side"===a?i.z-t.minZ:i.x-t.minX,n="top"===a?t.maxZ-i.z:t.maxY-i.y,r="top"===a?i.y:"front"===a?-i.z:i.x,o=`${e},${n}`,s=c.get(o),d=s&&("top"===a?s.y:"front"===a?-s.z:s.x);(!s||r>d)&&c.set(o,i)}for(const[e,t]of c){const[a,i]=e.split(",").map(Number);s.rect(a*o,i*o,o,o,colorFor(t.name))}return s.png()}function renderIsometric(e,t,a,i){const n=a.isometricCellSize,r=Math.ceil(n/2),o=(t.maxX-t.minX+t.maxZ-t.minZ+2)*n+4,s=(t.maxX-t.minX+t.maxZ-t.minZ+2)*r+(t.maxY-t.minY+2)*n+4,c=Math.max(1,Math.min(1,a.outputSize/Math.max(o,s))),d=new Raster(Math.ceil(o*c),Math.ceil(s*c),a),l=e=>({x:i?t.maxX-e.x:e.x-t.minX,z:i?t.maxZ-e.z:e.z-t.minZ}),m=[...e].sort((e,t)=>{const a=l(e),i=l(t);return a.x+a.z+e.y-(i.x+i.z+t.y)});for(const e of m){const a=l(e),i=(a.x-a.z)*n+o/2,s=(a.x+a.z)*r+(t.maxY-e.y+1)*n;d.voxel(Math.round(i*c),Math.round(s*c),Math.max(2,Math.round(n*c)),colorFor(e.name))}return d.png()}const COLORS={stone:"#777777",cobblestone:"#707070",deepslate:"#4b4b50",dirt:"#855b37",grass_block:"#63913d",sand:"#dbca85",glass:"#a7d9df",water:"#3f76e4",lava:"#e05b22",oak_planks:"#b9915a",spruce_planks:"#765033",birch_planks:"#d9c485",iron_block:"#d7d7d7",gold_block:"#f6c543",diamond_block:"#5ed6c7",redstone_block:"#b21d1d",netherrack:"#823332",quartz_block:"#e9e4dc",obsidian:"#211d2d",white_wool:"#e9e9e9",black_wool:"#1d1d1d",red_wool:"#a12722",blue_wool:"#3549a7",green_wool:"#4c702c",yellow_wool:"#e5c440"};function colorFor(e){const t=e.replace(/^minecraft:/,"");if(COLORS[t])return COLORS[t];for(const[e,a]of Object.entries(COLORS))if(t.includes(e))return a;const a=(0,node_crypto_1.createHash)("md5").update(t).digest();return`#${(90+a[0]%120).toString(16).padStart(2,"0")}${(90+a[1]%120).toString(16).padStart(2,"0")}${(90+a[2]%120).toString(16).padStart(2,"0")}`}class Raster{width;height;data;background;constructor(e,t,a){this.width=e,this.height=t,this.background=parseColor(a.background,a.transparentBackground?0:255),this.data=Buffer.alloc(e*t*4);for(let e=0;e<this.data.length;e+=4)this.data.set(this.background,e)}rect(e,t,a,i,n){for(let r=Math.max(0,t);r<Math.min(this.height,t+i);r++)for(let t=Math.max(0,e);t<Math.min(this.width,e+a);t++)this.pixel(t,r,n)}pixel(e,t,a){e<0||t<0||e>=this.width||t>=this.height||this.data.set(parseColor(a),4*(t*this.width+e))}voxel(e,t,a,i){const n=adjust(i,1.15),r=adjust(i,.78),o=adjust(i,.6),s=Math.ceil(a/2);for(let i=0;i<s;i++)for(let r=-a+i;r<=a-i;r++)this.pixel(e+r,t-s+i,n);for(let i=0;i<a;i++)for(let n=-a+Math.floor(i/2);n<=0;n++)this.pixel(e+n,t+i,r);for(let i=0;i<a;i++)for(let n=0;n<=a-Math.floor(i/2);n++)this.pixel(e+n,t+i,o)}png(){const e=Buffer.alloc((4*this.width+1)*this.height);for(let t=0;t<this.height;t++)e[t*(4*this.width+1)]=0,this.data.copy(e,t*(4*this.width+1)+1,t*this.width*4,(t+1)*this.width*4);const t=Buffer.alloc(13);return t.writeUInt32BE(this.width,0),t.writeUInt32BE(this.height,4),t.set([8,6,0,0,0],8),Buffer.concat([Buffer.from("PNG\r\n\n","binary"),chunk("IHDR",t),chunk("IDAT",(0,node_zlib_1.deflateSync)(e)),chunk("IEND",Buffer.alloc(0))])}}function parseColor(e,t=255){const a=/^#?([0-9a-f]{6})$/i.exec(e)?.[1]??"182026";return[parseInt(a.slice(0,2),16),parseInt(a.slice(2,4),16),parseInt(a.slice(4,6),16),t]}function adjust(e,t){const[a,i,n]=parseColor(e);return`#${[a,i,n].map(e=>Math.round(Math.min(255,e*t)).toString(16).padStart(2,"0")).join("")}`}function chunk(e,t){const a=Buffer.from(e),i=Buffer.alloc(4);i.writeUInt32BE(t.length);const n=Buffer.alloc(4);return n.writeUInt32BE(crc32(Buffer.concat([a,t]))>>>0),Buffer.concat([i,a,t,n])}const CRC_TABLE=Array.from({length:256},(e,t)=>{let a=t;for(let e=0;e<8;e++)a=1&a?3988292384^a>>>1:a>>>1;return a>>>0});function crc32(e){let t=4294967295;for(const a of e)t=CRC_TABLE[255&(t^a)]^t>>>8;return(4294967295^t)>>>0}
|
|
1
|
+
"use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.Config=exports.inject=exports.name=void 0,exports.apply=apply,exports.hashRenderConfiguration=hashRenderConfiguration,exports.patchOneBotAdapterSource=patchOneBotAdapterSource,exports.cacheNameSegment=cacheNameSegment,exports.enforceCacheLimit=enforceCacheLimit,exports.findLitematicFile=findLitematicFile,exports.supportsStandaloneJavaVersion=supportsStandaloneJavaVersion,exports.standaloneJvmOptions=standaloneJvmOptions,exports.isJavaMemoryFailure=isJavaMemoryFailure,exports.formatRenderError=formatRenderError,exports.resolveSendOptions=resolveSendOptions,exports.sendImages=sendImages,exports.composeQqOverview=composeQqOverview,exports.replyElements=replyElements,exports.parseLitematicMetadata=parseLitematicMetadata,exports.validateProjectionLimits=validateProjectionLimits,exports.formatLitematicMetadata=formatLitematicMetadata,exports.parseLitematic=parseLitematic,exports.renderSchematic=renderSchematic,exports.renderSixFaceOverview=renderSixFaceOverview;const koishi_1=require("koishi"),node_crypto_1=require("node:crypto"),node_fs_1=require("node:fs"),node_path_1=require("node:path"),node_url_1=require("node:url"),node_child_process_1=require("node:child_process"),node_zlib_1=require("node:zlib"),pngjs_1=require("pngjs");exports.name="litematic-renderer",exports.inject={optional:["puppeteer","server"]};const CACHE_FORMAT_VERSION=14,packageVersion=require("../package.json").version,PLUGIN_VERSION="string"==typeof packageVersion?packageVersion:"unknown";exports.Config=koishi_1.Schema.intersect([koishi_1.Schema.object({qqBotType:koishi_1.Schema.union([koishi_1.Schema.const("official").description("QQ 官方机器人"),koishi_1.Schema.const("selfHosted").description("自建 QQ(OneBot / NapCat)")]).role("radio").default("selfHosted").description("机器人接入类型;决定图片、合并转发、引用和 @ 的发送方式"),patchOneBotGroupUpload:koishi_1.Schema.boolean().default(!0).description("自建 QQ:启动时修复 OneBot 群文件上传事件;官方机器人不使用此项")}).description("机器人接入"),koishi_1.Schema.object({renderEngine:koishi_1.Schema.union([koishi_1.Schema.const("standalone"),koishi_1.Schema.const("java"),koishi_1.Schema.const("webgl"),koishi_1.Schema.const("cpu")]).default("standalone").description("渲染引擎;standalone 为无需客户端的独立 Java 资源包渲染器"),maxFileSize:koishi_1.Schema.natural().min(1).default(1024).description("自动处理的最大文件大小(KB)"),maxBlocks:koishi_1.Schema.natural().min(1).max(2e9).default(5e6).description("投影区域体积及声明方块数上限;用于拒绝尺寸异常或资源消耗过大的投影"),outputSize:koishi_1.Schema.natural().min(128).max(4096).default(1024).description("六面图和 CPU 渲染最长边的最大像素数"),background:koishi_1.Schema.string().default("#000000").description("PNG 背景颜色"),transparentBackground:koishi_1.Schema.boolean().default(!1).description("输出透明背景"),isometricFill:koishi_1.Schema.percent().default(.78).description("正二轴测主体在画布中的最大占比"),isometricRotation:koishi_1.Schema.number().min(0).max(360).step(1).default(135).description("Isometric Renders 绕竖直轴的基准旋转角"),isometricSlant:koishi_1.Schema.number().min(-90).max(90).step(1).default(36).description("Isometric Renders 俯仰角;36° 为正二轴测预设")}).description("渲染设置"),koishi_1.Schema.object({sendAsForward:koishi_1.Schema.boolean().default(!1).description("仅自建 QQ:开启为合并转发,关闭为联合发送;官方 QQ 忽略此项"),showViewTitles:koishi_1.Schema.boolean().default(!1).description("仅自建 QQ:发送图片时显示视图标题"),replyAndMention:koishi_1.Schema.boolean().default(!1).description("自建 QQ 会引用并 @ 发送者;官方 QQ 仅引用,避免显示 OpenID"),sixFaceOverview:koishi_1.Schema.boolean().default(!0).description("合并转发时生成并附加上、下、东、南、西、北六面正交合成图"),sixFaceLayout:koishi_1.Schema.union([koishi_1.Schema.const("horizontal").description("横向 3×2"),koishi_1.Schema.const("vertical").description("纵向 2×3")]).default("horizontal").description("六面正交合成图布局;最长边由 outputSize 控制"),groupSendOptions:koishi_1.Schema.array(koishi_1.Schema.object({groupId:koishi_1.Schema.string().description("QQ群号"),sendMode:koishi_1.Schema.union([koishi_1.Schema.const("forward").description("合并转发"),koishi_1.Schema.const("combined").description("联合发送(一条普通消息)")]).default("forward"),replyAndMention:koishi_1.Schema.union([koishi_1.Schema.const("inherit").description("继承全局设置"),koishi_1.Schema.const("enabled").description("开启回复 @"),koishi_1.Schema.const("disabled").description("关闭回复 @")]).default("inherit")})).default([]).description("按群覆盖发送方式和回复设置;官方 QQ 只使用引用开关,自建 QQ 同时使用发送模式和 @")}).description("发送设置"),koishi_1.Schema.object({standaloneJavaCommand:koishi_1.Schema.string().default("").description("独立渲染器使用的 Java 可执行文件;推荐 Java 21+,留空自动查找"),minecraftJarPath:koishi_1.Schema.string().default("").description("可选的 Minecraft 客户端 JAR 或基础资源包;留空使用内置 26.2 原版资源"),resourcePackPaths:koishi_1.Schema.array(koishi_1.Schema.string()).default([]).description("自定义材质包路径;越靠后优先级越高"),standaloneRendererJar:koishi_1.Schema.string().default("").description("独立渲染器 JAR;留空使用插件内置版本"),standaloneRenderTimeout:koishi_1.Schema.natural().min(1e4).default(18e4).description("独立 Java 渲染超时(毫秒)"),standaloneJavaMaxHeapMb:koishi_1.Schema.natural().min(128).max(32768).step(8).default(200).description("首次独立渲染的最大堆内存(MiB)"),standaloneJavaRetryMaxHeapMb:koishi_1.Schema.natural().min(256).max(32768).step(128).default(2048).description("内存不足时新进程重试的最大堆内存(MiB)"),standaloneJavaMemoryRestartLimit:koishi_1.Schema.natural().max(3).default(1).description("检测到内存不足后启动全新进程重试的次数"),javaResolution:koishi_1.Schema.natural().min(256).max(4096).default(1024).description("Java 渲染最终输出边长"),javaSupersampling:koishi_1.Schema.natural().min(1).max(4).default(2).description("离屏超采样倍数")}).description("独立渲染器"),koishi_1.Schema.object({renderTimeout:koishi_1.Schema.natural().min(1e3).default(3e4).description("文件下载超时(毫秒)"),cacheDirectory:koishi_1.Schema.string().default("data/litematic-renderer-cache").description("持久缓存目录;按插件版本和投影 SHA-256 分区"),cacheMaxSizeGb:koishi_1.Schema.number().min(1).max(1024).step(1).default(20).description("所有版本缓存总上限(GiB),超出后按最久未使用清理"),diagnosticsFilePath:koishi_1.Schema.string().default("data/litematic-renderer-diagnostics.json").description("渲染诊断持久化文件路径;导出地址固定为 /litematic-renderer/diagnostics")}).description("缓存与诊断"),koishi_1.Schema.object({isometricCellSize:koishi_1.Schema.natural().min(2).max(32).default(7).description("CPU 正二轴测方块菱形半宽"),javaRenderTimeout:koishi_1.Schema.natural().min(1e4).default(18e4).description("等待 Minecraft Java 渲染桥的超时(毫秒)"),webglQuality:koishi_1.Schema.union([koishi_1.Schema.const("standard"),koishi_1.Schema.const("high"),koishi_1.Schema.const("ultra")]).default("high").description("WebGL 渲染质量"),webglWidth:koishi_1.Schema.natural().min(256).max(2048).default(800).description("WebGL 单张图宽度"),webglHeight:koishi_1.Schema.natural().min(256).max(2048).default(600).description("WebGL 单张图高度"),isometricSquare:koishi_1.Schema.boolean().default(!0).description("将 WebGL 正二轴测图输出为正方形"),javaBridgeDirectory:koishi_1.Schema.string().default("").description("renderEngine=java 时使用的 Fabric Java 渲染桥任务目录"),gpuRendererCommand:koishi_1.Schema.string().default("").description("可选外部 GPU 渲染器命令;非空时优先尝试,失败后回退到 renderEngine")}).description("高级设置")]);const activeStandaloneJavaProcesses=new Set;function apply(e,t){const i=e.logger(exports.name),a=1024*t.maxFileSize;e.on("dispose",()=>terminateActiveStandaloneJavaProcesses(i)),"selfHosted"===t.qqBotType&&ensureOneBotGroupUploadCompatibility(i,t.patchOneBotGroupUpload);const n=(0,node_path_1.resolve)(stringOrDefault(t.diagnosticsFilePath,"data/litematic-renderer-diagnostics.json")),r=(0,node_path_1.join)((0,node_path_1.dirname)(n),"litematic-renderer-diagnostics-archive"),o=(0,node_path_1.join)((0,node_path_1.dirname)(n),"litematic-renderer-diagnostics-disabled");rotateDiagnosticsFile(n,r,i);const s=(0,node_path_1.resolve)(stringOrDefault(t.cacheDirectory,"data/litematic-renderer-cache")),c=(0,node_path_1.join)(s,`v${cachePathSegment(PLUGIN_VERSION)}`),d=Math.floor(t.cacheMaxSizeGb*1024**3),l=new Map;node_fs_1.promises.mkdir(c,{recursive:!0}).then(()=>enforceCacheLimit(s,d)).catch(e=>i.warn(`缓存初始化失败:${e instanceof Error?e.message:String(e)}`));const m=async(r,o="schematic.litematic",m,u=!1)=>{const h=m??await download(e,r,a,t.renderTimeout),f=parseLitematicMetadata(h);validateProjectionLimits(h,f,t.maxBlocks);const p=formatLitematicMetadata(f,o),g=projectionNameFromFilename(o),_=(0,node_crypto_1.createHash)("sha256").update(h).digest("hex"),w="standalone"===t.renderEngine?await resolveMinecraftResources(t):"",y="standalone"===t.renderEngine?await fingerprintFiles([w,...resourcePackPaths(t.resourcePackPaths)]):[],S=hashRenderConfiguration({version:CACHE_FORMAT_VERSION,outputSize:t.outputSize,isometricCellSize:t.isometricCellSize,background:t.background,transparentBackground:t.transparentBackground,gpuRendererCommand:t.gpuRendererCommand,renderEngine:t.renderEngine,standaloneRendererJar:t.standaloneRendererJar,resourceFingerprint:y,javaResolution:t.javaResolution,javaSupersampling:t.javaSupersampling,webglQuality:t.webglQuality,webglWidth:t.webglWidth,webglHeight:t.webglHeight,isometricSquare:t.isometricSquare,isometricFill:t.isometricFill,isometricRotation:t.isometricRotation,isometricSlant:t.isometricSlant,sixFaceLayout:t.sixFaceLayout}),v=cacheNameSegment(o),b=(0,node_path_1.join)(c,`${v}-${_}-${S}`),x=(0,node_path_1.join)(b,"projection.litematic");await node_fs_1.promises.mkdir(b,{recursive:!0}),await exists(x)||await node_fs_1.promises.writeFile(x,h),await ensureCacheMetadata(b,{pluginVersion:PLUGIN_VERSION,cacheFormatVersion:CACHE_FORMAT_VERSION,contentSha256:_,renderConfigSha256:S,sourceFilename:(0,node_path_1.basename)(o),createdAt:(new Date).toISOString()});const k=["isometric.png","isometric-reverse.png"].map(e=>(0,node_path_1.join)(b,e));if((await Promise.all(k.map(exists))).every(Boolean))i.debug(`命中投影缓存:${_}`);else{let a=l.get(b);a?i.debug(`等待相同投影的缓存任务:${_}`):(a=(async()=>{try{if((await Promise.all(k.map(exists))).every(Boolean))return;if(!Boolean(t.gpuRendererCommand&&await renderWithGpu(t.gpuRendererCommand,x,b,t.renderTimeout,i))||!(await Promise.all(k.map(exists))).every(Boolean))if("standalone"===t.renderEngine)await renderWithStandalone(x,b,w,t,i),await mergeRenderDiagnostics(b,n,i);else if("java"===t.renderEngine)await renderWithJavaBridge(x,b,t);else{const a=parseLitematic(h);let n;if("webgl"===t.renderEngine&&e.puppeteer)try{n=await renderWithWebgl(e,h,t)}catch(e){i.warn(`WebGL 渲染失败,回退 CPU:${e instanceof Error?e.message:String(e)}`),n=renderSchematic(a,t)}else n=renderSchematic(a,t);await Promise.all(n.map(e=>node_fs_1.promises.writeFile((0,node_path_1.join)(b,e.title),e.png)))}if(!(await Promise.all(k.map(exists))).every(Boolean))throw new Error("渲染器没有生成两张正二轴测 PNG")}catch(e){throw await appendRenderError(b,n,t.renderEngine,e,i),e}})().finally(()=>l.delete(b)),l.set(b,a)),await a}const M=k.map((e,t)=>({title:["正二轴测","反向正二轴测(旋转 180°)"][t],path:e}));if(u){const e=(0,node_path_1.join)(b,"six-faces.png");if(!await exists(e)){const a=`${b}:six-face`;let n=l.get(a);n||(n=(async()=>{try{if("standalone"===t.renderEngine)try{await renderWithStandalone(x,b,w,t,i,"six-face")}catch(e){i.warn(`真实材质六面图生成失败,回退方块颜色投影:${e instanceof Error?e.message:String(e)}`)}if(!await exists(e)){const i=parseLitematic(h);await node_fs_1.promises.writeFile(e,renderSixFaceOverview(i,t,t.sixFaceLayout))}}catch(e){i.warn(`六面正交图生成失败,继续发送轴测图:${e instanceof Error?e.message:String(e)}`)}})().finally(()=>l.delete(a)),l.set(a,n)),await n}await exists(e)&&M.push({title:"六面正投影",path:e})}return await touchCacheEntry(b),await enforceCacheLimit(s,d,b),{images:M,metadata:p,projectionName:g}};e.middleware(async(e,r)=>{if(!e.guildId)return r();const o=findLitematicFile(e);if(!o)return r();const s=await resolveFileUrl(e,o);if(!s)return i.warn(`检测到 Litematic 文件但无法获取下载地址:${fileName(o)??"unknown.litematic"};请检查 OneBot/NapCat 的 get_group_file_url 接口`),await e.send([...replyElements(e,t.qqBotType),(0,koishi_1.h)("text",{content:"检测到投影文件,但无法获取群文件下载地址,请检查机器人文件接口"})]),r();if(isOverLimit(o.size,a))return await appendGlobalRenderError(n,`input:${(0,node_path_1.basename)(fileName(o)??"unknown.litematic")}`,"input",`file size exceeds ${t.maxFileSize} KB`,i),await e.send([...replyElements(e,t.qqBotType),(0,koishi_1.h)("text",{content:`文件大小超过 ${(t.maxFileSize/1024).toFixed(2)} MB,无法渲染`})]),r();if(!isUnderLimit(o.size,a))return r();try{const i=resolveSendOptions(t,e.guildId),a=await m(s,fileName(o)??"schematic.litematic",void 0,i.sixFaceOverview);await sendImages(e,a.images,a.metadata,i,a.projectionName)}catch(a){return i.warn(a),await e.send([...replyElements(e,t.qqBotType),(0,koishi_1.h)("text",{content:formatRenderError(a,t)})]),r()}return r()}),e.command("litematic.render <url:string>","渲染可直接下载的 .litematic 文件").action(async({session:e},i)=>{if(!i)return"请提供 .litematic 文件的直链";if(!e)return"此命令只能在消息会话中执行";try{const a=resolveSendOptions(t,e.guildId),n=await m(i,"schematic.litematic",void 0,a.sixFaceOverview);await sendImages(e,n.images,n.metadata,a,n.projectionName)}catch(e){return formatRenderError(e,t)}}),e.command("litematic.cache.clear","清理投影渲染缓存",{authority:3}).action(async()=>(await node_fs_1.promises.rm(s,{recursive:!0,force:!0}),await node_fs_1.promises.mkdir(c,{recursive:!0}),"投影渲染缓存已清理")),e.command("litematic.errors.export","export render diagnostics",{authority:3}).action(async()=>`Diagnostics file: ${n}`),e.command("litematic.errors.disable","disable current render diagnostics snapshot",{authority:3}).action(async()=>disableDiagnosticsSnapshot(n,o));const u=e.server;u?.get&&u.get("/litematic-renderer/diagnostics",async e=>{e.type="application/json",e.set("Content-Disposition",'attachment; filename="litematic-renderer-diagnostics.json"'),e.body=await node_fs_1.promises.readFile(n,"utf8").catch(()=>'{"format":2,"blocks":[],"errors":[]}')})}function hashRenderConfiguration(e){return(0,node_crypto_1.createHash)("sha256").update(JSON.stringify(e)).digest("hex").slice(0,16)}function patchOneBotAdapterSource(e){if(/^\s*case ["']group_upload["']:/m.test(e))return{source:e,changed:!1,alreadyCompatible:!0};const t=e.match(/\(0,\s*(import_[\w$]+)\.hyphenate\)\(data\.sub_type\)/)?.[1]??"import_koishi2",i=/ \/\/ https:\/\/github\.com\/koishijs\/koishi-plugin-adapter-onebot\/issues\/33\r?\n \/\/ case 'offline_file':\r?\n \/\/ session\.elements = \[h\('file', data\.file\)\]\r?\n \/\/ session\.type = 'message'\r?\n \/\/ session\.subtype = 'private'\r?\n \/\/ session\.isDirect = true\r?\n \/\/ session\.subsubtype = 'offline-file-added'\r?\n \/\/ break\r?\n \/\/ case 'group_upload':\r?\n \/\/ session\.elements = \[h\('file', data\.file\)\]\r?\n \/\/ session\.type = 'message'\r?\n \/\/ session\.subtype = 'group'\r?\n \/\/ session\.subsubtype = 'guild-file-added'\r?\n \/\/ break/;if(!i.test(e))return{source:e,changed:!1,alreadyCompatible:!1};const a=[" // Restored by koishi-plugin-litematic-renderer so NapCat group file uploads reach middleware.",' case "offline_file":',` session.elements = [(0, ${t}.h)("file", data.file)];`,' session.type = "message";',' session.subtype = "private";'," session.isDirect = true;",' session.subsubtype = "offline-file-added";'," break;",' case "group_upload":',` session.elements = [(0, ${t}.h)("file", data.file)];`,' session.type = "message";',' session.subtype = "group";',' session.subsubtype = "guild-file-added";'," break;"].join("\n");return{source:e.replace(i,a),changed:!0,alreadyCompatible:!1}}async function ensureOneBotGroupUploadCompatibility(e,t){if(!t)return;let i;try{i=require.resolve("koishi-plugin-adapter-onebot/package.json",{paths:[process.cwd(),__dirname]})}catch{return void e.debug("未安装 koishi-plugin-adapter-onebot,跳过群文件上传事件兼容修复")}try{const t=(0,node_path_1.dirname)(i),a=JSON.parse(await node_fs_1.promises.readFile(i,"utf8")),n=(0,node_path_1.join)(t,"lib","index.js"),r=await node_fs_1.promises.readFile(n,"utf8"),o=patchOneBotAdapterSource(r);if(o.alreadyCompatible)return void e.debug(`koishi-plugin-adapter-onebot@${a.version??"unknown"} 已支持 group_upload`);if(!o.changed)return void e.warn(`koishi-plugin-adapter-onebot@${a.version??"unknown"} 未识别到可自动修复的 group_upload 代码段`);const s=`${n}.litematic-renderer.bak`;await exists(s)||await node_fs_1.promises.writeFile(s,r),await node_fs_1.promises.writeFile(n,o.source),e.warn(`已修复 koishi-plugin-adapter-onebot@${a.version??"unknown"} 的群文件上传事件映射;请重启 Koishi 让已加载的 OneBot 适配器生效`)}catch(t){e.warn(`修复 OneBot 群文件上传事件失败:${t instanceof Error?t.message:String(t)}`)}}function cachePathSegment(e){return e.replace(/[^a-zA-Z0-9._-]/g,"_")||"unknown"}function cacheNameSegment(e){return((0,node_path_1.basename)(e||"schematic.litematic",(0,node_path_1.extname)(e||"")).replace(/[<>:"/\\|?*\u0000-\u001f]/g,"_").replace(/[. ]+$/g,"").trim()||"schematic").slice(0,80)}function projectionNameFromFilename(e){return(0,node_path_1.basename)(e||"schematic.litematic").replace(/\.litematic$/i,"").trim()||"schematic"}async function ensureCacheMetadata(e,t){const i=(0,node_path_1.join)(e,"cache.json");await exists(i)||await node_fs_1.promises.writeFile(i,JSON.stringify(t,null,2)+"\n")}async function touchCacheEntry(e){const t=new Date;await node_fs_1.promises.utimes(e,t,t).catch(()=>{})}async function directorySize(e){let t=0;const i=await node_fs_1.promises.readdir(e,{withFileTypes:!0}).catch(e=>{if("ENOENT"===e?.code)return[];throw e});for(const a of i){const i=(0,node_path_1.join)(e,a.name);a.isDirectory()?t+=await directorySize(i):a.isFile()&&(t+=(await node_fs_1.promises.stat(i)).size)}return t}async function enforceCacheLimit(e,t,i){const a=[],n=await node_fs_1.promises.readdir(e,{withFileTypes:!0}).catch(e=>{if("ENOENT"===e?.code)return[];throw e});for(const t of n){if(!t.isDirectory())continue;const i=(0,node_path_1.join)(e,t.name),n=await node_fs_1.promises.readdir(i,{withFileTypes:!0});for(const e of n){if(!e.isDirectory())continue;const t=(0,node_path_1.join)(i,e.name),[n,r]=await Promise.all([directorySize(t),node_fs_1.promises.stat(t)]);a.push({path:t,size:n,lastUsed:r.mtimeMs})}}let r=a.reduce((e,t)=>e+t.size,0),o=0,s=0;const c=i&&(0,node_path_1.resolve)(i);a.sort((e,t)=>e.lastUsed-t.lastUsed);for(const e of a){if(r<=t)break;c&&(0,node_path_1.resolve)(e.path)===c||(await node_fs_1.promises.rm(e.path,{recursive:!0,force:!0}),r-=e.size,o+=e.size,s++)}return{totalBytes:r,removedBytes:o,removedEntries:s}}function fileElements(e){return[...e.elements??[],...koishi_1.h.parse(e.content??"")]}function findLitematicFile(e){const t=fileElements(e).filter(e=>"file"===e.type),i=t.find(e=>".litematic"===(0,node_path_1.extname)(fileName(e.attrs)??"").toLowerCase());if(i?.attrs)return i.attrs;for(const t of[e.event,e._data,e.data,e.message]){const e=findLitematicInFilePayload(t);if(e)return e}const a=inferLitematicName(e.content);return a&&t[0]?.attrs?{...t[0].attrs,name:a}:void 0}function findLitematicInFilePayload(e,t=new Set,i=0){if(!e||"object"!=typeof e||i>6||t.has(e))return;t.add(e);const a=e,n=a,r=n.file&&"object"==typeof n.file?n.file:void 0;if(r&&fileName(r)&&".litematic"===(0,node_path_1.extname)(fileName(r)).toLowerCase())return{...r,id:r.id??n.id,file_id:r.file_id??n.file_id,fileId:r.fileId??n.fileId,busid:r.busid??n.busid};if(fileName(n)&&".litematic"===(0,node_path_1.extname)(fileName(n)).toLowerCase())return n;for(const[e,n]of Object.entries(a))if(n&&"object"==typeof n&&/file|upload|message|data|event/i.test(e)){const e=findLitematicInFilePayload(n,t,i+1);if(e)return e}}function inferLitematicName(e){if(!e)return;const t=e.match(/(?:file|文件)\s+(.+?\.litematic)(?=$|[\]】\r\n])/i);return t?.[1]?t[1].trim():e.match(/([^\s\]】<>"']+\.litematic)(?=$|[\s\]】])/i)?.[1]?.trim()}function fileName(e){const t=e.file&&"object"==typeof e.file?e.file:void 0,i=e.name??e.filename??e.file_name??e.fileName??t?.name??t?.filename??t?.file_name??t?.fileName;return"string"==typeof i&&i.trim()?i.trim():void 0}async function resolveFileUrl(e,t){const i=t.file&&"object"==typeof t.file?t.file:void 0,a=t.url??t.src??("string"==typeof t.file?t.file:void 0)??i?.url??i?.src;if("string"==typeof a&&a)return a;const n=t.id??t.file_id??t.fileId??i?.id??i?.file_id??i?.fileId,r=e.bot.internal;if(!e.guildId||!n||"function"!=typeof r?.getGroupFileUrl)return;const o=await r.getGroupFileUrl(e.guildId,n,Number(t.busid??i?.busid??0));return"string"==typeof o?o:o?.url}function isUnderLimit(e,t){if(null==e||""===e)return!0;const i=Number(e);return Number.isFinite(i)&&i>0&&i<=t}async function download(e,t,i,a){const n=await e.http.get(t,{responseType:"arraybuffer",timeout:a}),r=Buffer.from(n);if(r.byteLength>i)throw new Error(`文件超过 ${(i/1024/1024).toFixed(2)} MB 限制`);return r}async function exists(e){try{return await node_fs_1.promises.access(e,node_fs_1.constants.R_OK),!0}catch{return!1}}function stringOrDefault(e,t){return"string"==typeof e&&e.trim()?e:t}function resourcePackPaths(e){return Array.isArray(e)?e.filter(e=>"string"==typeof e&&Boolean(e.trim())):[]}async function fingerprintFiles(e){return Promise.all(e.filter(e=>"string"==typeof e&&Boolean(e.trim())).map(async e=>{const t=(0,node_path_1.resolve)(e);try{const e=await node_fs_1.promises.stat(t);return{path:t,size:e.size,modified:e.mtimeMs}}catch{return{path:t,missing:!0}}}))}async function resolveStandaloneJavaCommand(e){const t=e.trim();if(t)return t;const i="win32"===process.platform?"java.exe":"java",a=new Set;if(process.env.JAVA_HOME&&a.add((0,node_path_1.join)(process.env.JAVA_HOME,"bin",i)),"win32"===process.platform){const e=new Set([(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Java"),(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Eclipse Adoptium"),(0,node_path_1.join)(process.env.ProgramFiles??"C:/Program Files","Microsoft","jdk")]);for(const t of e)try{const e=await node_fs_1.promises.readdir(t,{withFileTypes:!0});for(const n of e)n.isDirectory()&&/(?:^|[-_])21(?:[._-]|$)/.test(n.name)&&a.add((0,node_path_1.join)(t,n.name,"bin",i))}catch{}}a.add("java");for(const e of a)if(supportsStandaloneJavaVersion(await javaMajorVersion(e)))return e;throw new Error("未找到 Java 21+;请安装 Java 21 或更高版本,设置 JAVA_HOME,或填写 standaloneJavaCommand")}function isOverLimit(e,t){if(null==e||""===e)return!1;const i=Number(e);return Number.isFinite(i)&&i>t}async function javaMajorVersion(e){return new Promise(t=>{let i="",a=!1;const n=e=>{a||(a=!0,clearTimeout(s),t(e))},r=(0,node_child_process_1.spawn)(e,["-version"],{shell:!1,windowsHide:!0}),o=e=>{i=(i+String(e)).slice(-2e3)};r.stdout?.on("data",o),r.stderr?.on("data",o),r.once("error",()=>n()),r.once("close",()=>{const e=i.match(/(?:java|openjdk) version "(\d+)/i);n(e?Number(e[1]):void 0)});const s=setTimeout(()=>{r.kill(),n()},5e3)})}function supportsStandaloneJavaVersion(e){return void 0!==e&&e>=21}function standaloneJvmOptions(e){return["-Xms128m",`-Xmx${Math.max(128,Math.min(32768,Math.floor(e)))}m`,"-XX:+UseG1GC","-XX:+UseStringDeduplication","-XX:+ExitOnOutOfMemoryError"]}function isJavaMemoryFailure(e,t){return 137===t||/outofmemoryerror|java heap space|gc overhead limit|cannot reserve enough space|native memory allocation|insufficient memory|malloc failed|os::commit_memory/i.test(e)}async function resolveMinecraftResources(e){const t="string"==typeof e.minecraftJarPath?e.minecraftJarPath.trim():"";if(t&&await exists((0,node_path_1.resolve)(t)))return(0,node_path_1.resolve)(t);const i=(0,node_path_1.resolve)((0,node_path_1.join)(__dirname,"../assets/vanilla-resources/Minecraft-26.2-Vanilla-Resources.zip"));if(!await exists(i))throw new Error(`插件内置的 Minecraft 26.2 原版资源包不存在:${i}`);return i}async function renderWithStandalone(e,t,i,a,n,r="isometric"){const o=(0,node_path_1.resolve)(a.standaloneRendererJar||(0,node_path_1.join)(__dirname,"../assets/standalone-renderer/litematic-standalone-renderer-0.2.7.jar"));if(!await exists(o))throw new Error(`独立 Java 渲染器不存在:${o}`);if(!await exists(i))throw new Error(`Minecraft 资源 JAR 不存在:${i}`);for(const e of resourcePackPaths(a.resourcePackPaths))if(!await exists((0,node_path_1.resolve)(e)))throw new Error(`材质包不存在:${e}`);const s=["-Djava.awt.headless=true","-jar",o,"--input",(0,node_path_1.resolve)(e),"--output",(0,node_path_1.resolve)(t),"--minecraft-jar",i,"--resolution",String(a.javaResolution),"--supersampling",String(a.javaSupersampling),"--rotation",String(a.isometricRotation),"--slant",String(a.isometricSlant),"--fill",String(a.isometricFill),"--background",a.background];"six-face"===r&&s.push("--six-face-only","--six-face-resolution",String(a.outputSize),"--six-face-layout",a.sixFaceLayout),a.transparentBackground&&s.push("--transparent-background");for(const e of resourcePackPaths(a.resourcePackPaths))s.push("--resource-pack",(0,node_path_1.resolve)(e));const c=await resolveStandaloneJavaCommand(a.standaloneJavaCommand);for(let e=0;e<=a.standaloneJavaMemoryRestartLimit;e++){const i=0===e?a.standaloneJavaMaxHeapMb:a.standaloneJavaRetryMaxHeapMb,o=await runStandaloneJava(c,[...standaloneJvmOptions(i),...s],a.standaloneRenderTimeout);if(0===o.code)return;const d=isJavaMemoryFailure(o.stderr,o.code);if(d&&e<a.standaloneJavaMemoryRestartLimit){n.warn(`独立 Java 渲染超出 ${a.standaloneJavaMaxHeapMb} MiB 堆内存限制;正在启动全新进程重试(${e+1}/${a.standaloneJavaMemoryRestartLimit})`);const i="six-face"===r?["six-faces.png"]:["isometric.png","isometric-reverse.png"];await Promise.all(i.map(e=>node_fs_1.promises.rm((0,node_path_1.join)(t,e),{force:!0})));continue}const l=d?`;Java 内存不足(最大堆 ${a.standaloneJavaMaxHeapMb} MiB,已重启 ${e} 次)`:"";throw new Error(`独立 Java 渲染器退出码 ${o.code}${l}${o.stderr?`:${o.stderr.trim()}`:""}`)}}async function runStandaloneJava(e,t,i){return new Promise((a,n)=>{const r=(0,node_child_process_1.spawn)(e,t,{shell:!1,windowsHide:!0});activeStandaloneJavaProcesses.add(r);let o="",s=!1;const c=()=>activeStandaloneJavaProcesses.delete(r);r.stderr?.on("data",e=>{o=(o+String(e)).slice(-6e3)});const d=setTimeout(()=>{s||(s=!0,(async()=>{await terminateStandaloneJavaProcess(r),c(),n(new Error("Standalone Java render timed out; process terminated and memory reclaimed"))})())},i);r.once("error",e=>{s||(s=!0,clearTimeout(d),terminateStandaloneJavaProcess(r),c(),n(e))}),r.once("close",e=>{s||(s=!0,clearTimeout(d),c(),a({code:e,stderr:o}))})})}async function terminateStandaloneJavaProcess(e){const t=e.pid;t&&("win32"===process.platform&&await new Promise(e=>{const i=(0,node_child_process_1.spawn)("taskkill.exe",["/PID",String(t),"/T","/F"],{windowsHide:!0});i.once("close",()=>e()),i.once("error",()=>e())}),null!=e.exitCode||e.killed||e.kill())}async function terminateActiveStandaloneJavaProcesses(e){const t=[...activeStandaloneJavaProcesses];t.length&&(await Promise.all(t.map(e=>terminateStandaloneJavaProcess(e))),e.info(`已终止 ${t.length} 个独立渲染 Java 进程并回收其子进程`))}async function renderWithJavaBridge(e,t,i){if(!i.javaBridgeDirectory.trim())throw new Error("Fabric Java 渲染桥任务目录未配置;使用 java 后端时请填写 javaBridgeDirectory");const a=(0,node_path_1.resolve)(i.javaBridgeDirectory),n=await readJson((0,node_path_1.join)(a,"status.json"));if(!n?.timestamp||Date.now()-n.timestamp>5e3)throw new Error("Minecraft Java 渲染桥未运行;请启动 1.21.1-Fabric 客户端并进入一个世界");if(!n.inWorld)throw new Error("Minecraft Java 渲染桥已启动,但客户端尚未进入世界");const r=n.resourcePacks??[];if(![["XeKr","3.6forMC1.20.2~1.21.5.zip"],["XKRDA","1.19.4~1.21snapshot.zip"]].every(e=>r.some(t=>e.every(e=>t.includes(e)))))throw new Error("Minecraft 客户端没有同时启用 XeKr 红显基础包和 XKRDA 附加包");const o=(0,node_crypto_1.randomUUID)(),s=(0,node_path_1.join)(a,"jobs"),c=(0,node_path_1.join)(a,"results",`${o}.result.json`),d=(0,node_path_1.join)(s,`${o}.job.json`),l=`${d}.tmp`;await node_fs_1.promises.mkdir(s,{recursive:!0}),await node_fs_1.promises.rm(c,{force:!0}),await node_fs_1.promises.writeFile(l,JSON.stringify({id:o,input:(0,node_path_1.resolve)(e),outputDirectory:(0,node_path_1.resolve)(t),resolution:i.javaResolution,supersampling:i.javaSupersampling,rotation:i.isometricRotation,slant:i.isometricSlant,fill:i.isometricFill,background:i.background,transparentBackground:i.transparentBackground})),await node_fs_1.promises.rename(l,d);const m=Date.now()+i.javaRenderTimeout;for(;Date.now()<m;){const e=await readJson(c);if(e){if(await node_fs_1.promises.rm(c,{force:!0}),!e.success)throw new Error(`Minecraft Java 渲染失败:${e.error??"未知错误"}`);return}await new Promise(e=>setTimeout(e,200))}throw new Error(`Minecraft Java 渲染超过 ${Math.round(i.javaRenderTimeout/1e3)} 秒`)}async function readJson(e){try{return JSON.parse(await node_fs_1.promises.readFile(e,"utf8"))}catch(e){if("ENOENT"===e?.code||e instanceof SyntaxError)return;throw e}}async function mergeRenderDiagnostics(e,t,i){const a=(0,node_path_1.join)(e,"render-diagnostics.json"),n=await readJson(a);if(!n)return;const r=(0,node_path_1.resolve)(t),o=await readJson(r)??{format:2,generatedAt:(new Date).toISOString(),blocks:[],errors:[]},s=new Map;for(const e of o.blocks??[])s.set(`${e.state}\0${e.reason}`,e);for(const e of n.blocks??[]){const t=`${e.state}\0${e.reason}`,i=s.get(t);i?(i.count=Number(i.count??0)+Number(e.count??0),i.samples=[...i.samples??[],...e.samples??[]].slice(0,20)):s.set(t,{...e,samples:e.samples??[]})}const c=new Map;for(const e of o.errors??[])c.set(`${e.renderId}\0${e.engine}\0${e.message}`,e);for(const e of n.errors??[]){const t=`${e.renderId}\0${e.engine}\0${e.message}`,i=c.get(t);i?i.count=Number(i.count??0)+Number(e.count??0):c.set(t,{...e})}const d={format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[...s.values()],errors:[...c.values()]};await node_fs_1.promises.mkdir((0,node_path_1.resolve)(t,".."),{recursive:!0}).catch(()=>{}),await node_fs_1.promises.writeFile(r,JSON.stringify(d,null,2)+"\n"),i.warn(`记录 ${n.blocks?.length??0} 类无法正常渲染的方块;诊断已保存到 ${r}`)}async function rotateDiagnosticsFile(e,t,i){const a=await readJson(e);if(!a||a.pluginVersion===PLUGIN_VERSION)return;await node_fs_1.promises.mkdir(t,{recursive:!0});const n=cachePathSegment("string"==typeof a.pluginVersion?a.pluginVersion:"legacy"),r=formatDiagnosticsTimestamp(new Date),o=(0,node_path_1.join)(t,`litematic-renderer-diagnostics-v${n}-${r}.json`);await node_fs_1.promises.rename(e,o),i.info(`已将旧版诊断汇总归档到 ${o}`)}async function disableDiagnosticsSnapshot(e,t){if(!await readJson(e))return"当前没有可禁用的诊断汇总";await node_fs_1.promises.mkdir(t,{recursive:!0});const i=(0,node_path_1.join)(t,`litematic-renderer-diagnostics-disabled-${formatDiagnosticsTimestamp(new Date)}.json`);return await node_fs_1.promises.rename(e,i),await node_fs_1.promises.writeFile(e,JSON.stringify({format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[],errors:[]},null,2)+"\n"),`当前诊断已移入禁用文件夹:${i}`}async function appendGlobalRenderError(e,t,i,a,n){const r=(0,node_path_1.resolve)(e),o=await readJson(r)??{format:2,pluginVersion:PLUGIN_VERSION,generatedAt:(new Date).toISOString(),blocks:[],errors:[]};o.format=2,o.pluginVersion=PLUGIN_VERSION,o.errors??=[];const s=o.errors.find(e=>e.renderId===t&&e.engine===i&&e.message===a);s?s.count=Number(s.count??0)+1:o.errors.push({renderId:t,engine:i,message:a,count:1,generatedAt:(new Date).toISOString()}),await node_fs_1.promises.mkdir((0,node_path_1.dirname)(r),{recursive:!0}),await node_fs_1.promises.writeFile(r,JSON.stringify(o,null,2)+"\n"),n.warn(`渲染问题已记录到 ${r}`)}function formatDiagnosticsTimestamp(e){const t=e=>String(e).padStart(2,"0");return`${e.getFullYear()}${t(e.getMonth()+1)}${t(e.getDate())}-${t(e.getHours())}${t(e.getMinutes())}${t(e.getSeconds())}`}async function appendRenderError(e,t,i,a,n){const r=(0,node_path_1.join)(e,"render-diagnostics.json"),o=await readJson(r)??{format:2,generatedAt:(new Date).toISOString(),blocks:[],errors:[]};o.format=2,o.errors??=[];const s=a instanceof Error?a.message:String(a),c=(0,node_path_1.basename)(e),d=o.errors.find(e=>e.renderId===c&&e.engine===i&&e.message===s);d?d.count=Number(d.count??0)+1:o.errors.push({renderId:c,engine:i,message:s,count:1,generatedAt:(new Date).toISOString()}),await node_fs_1.promises.writeFile(r,JSON.stringify(o,null,2)+"\n"),await mergeRenderDiagnostics(e,t,n)}function formatRenderError(e,t){const i=e instanceof Error?e.message:String(e);return/投影方块数或区域体积超过/i.test(i)?`${i},不渲染`:/文件超过|file size|file.*limit/i.test(i)?`文件大小超过 ${(t.maxFileSize/1024).toFixed(2)} MB,不渲染`:/java 21\+|未找到.*java|java.*not found/i.test(i)?"未找到 Java 21+,请在插件配置中检查 Java 可执行文件设置":/standalone renderer|独立.*渲染器.*不存在|渲染器.*not exist/i.test(i)?"独立渲染器不可用,请重装插件或检查 Java 渲染配置":/minecraft.*resource|resource pack|原版资源|材质包.*不存在/i.test(i)?"Minecraft 原版资源包不可用,请检查资源包配置":"投影渲染失败,请检查渲染器配置或导出诊断文件"}async function renderWithGpu(e,t,i,a,n){try{return await new Promise((n,r)=>{const o=(0,node_child_process_1.spawn)(e,["--input",t,"--output",i,"--views","isometric,isometric-reverse"],{shell:!1,windowsHide:!0}),s=setTimeout(()=>{o.kill(),r(new Error("GPU 渲染超时"))},a);o.once("error",r),o.once("exit",e=>0===e?n():r(new Error(`GPU 渲染器退出码:${e}`))),o.once("close",()=>clearTimeout(s))}),!0}catch(e){return n.warn(`GPU 渲染器不可用,回退 CPU:${e instanceof Error?e.message:String(e)}`),!1}}async function renderWithWebgl(e,t,i){const a=await e.puppeteer.page();try{const e=(0,node_url_1.pathToFileURL)((0,node_path_1.resolve)(__dirname,"../assets/threeviews/index.html")).href;await a.goto(e,{waitUntil:"networkidle0",timeout:i.renderTimeout}),await a.waitForFunction(()=>Boolean(window.litematicViewerAPI&&window.deepslateResources),{timeout:i.renderTimeout});const n=await a.evaluate(async({base64:e,width:t,height:i,quality:a,isometricSquare:n,isometricFill:r,isometricRotation:o,isometricSlant:s})=>{const c=atob(e),d=new Uint8Array(c.length);for(let e=0;e<c.length;e++)d[e]=c.charCodeAt(e);const l=new File([d],"schematic.litematic",{type:"application/octet-stream"});return await window.litematicViewerAPI.loadFromFile(l),window.litematicViewerAPI.renderIsometricViews({width:t,height:i,quality:a,isometricSquare:n,isometricFill:r,isometricRotation:o,isometricSlant:s})},{base64:t.toString("base64"),width:i.webglWidth,height:i.webglHeight,quality:i.webglQuality,isometricSquare:i.isometricSquare,isometricFill:i.isometricFill,isometricRotation:i.isometricRotation,isometricSlant:i.isometricSlant});return[{title:"isometric.png",png:decodeDataUrl(n.isometricView)},{title:"isometric-reverse.png",png:decodeDataUrl(n.reverseIsometricView)}]}finally{await a.close().catch(()=>{})}}function decodeDataUrl(e){if("string"!=typeof e||!e.startsWith("data:image/"))throw new Error("WebGL 渲染器没有返回 PNG 数据");const t=e.indexOf(",");if(t<0)throw new Error("WebGL 渲染器返回了无效图像数据");return Buffer.from(e.slice(t+1),"base64")}function resolveSendOptions(e,t){const i=t?[...e.groupSendOptions].reverse().find(e=>e.groupId.trim()===t):void 0,a="enabled"===i?.replyAndMention||"disabled"!==i?.replyAndMention&&e.replyAndMention,n=i?.sendMode??(e.sendAsForward?"forward":"combined"),r=e.qqBotType??"selfHosted";return{qqBotType:r,sendMode:n,replyAndMention:a,showViewTitles:e.showViewTitles??!1,sixFaceOverview:("official"===r||"forward"===n)&&(e.sixFaceOverview??!0)}}async function sendImages(e,t,i,a,n="schematic"){if("official"===a.qqBotType){const r=await composeQqOverview(t),o=[...a.replyAndMention&&e.messageId?[(0,koishi_1.h)("quote",{id:e.messageId})]:[],koishi_1.h.image((0,node_url_1.pathToFileURL)(r).href),(0,koishi_1.h)("text",{content:`${i}\n${n} 已渲染成功,结果如上`})];return void await e.send(o)}const r=await Promise.all(t.map(async({title:e,path:t})=>({title:e,source:`base64://${(await node_fs_1.promises.readFile(t)).toString("base64")}`}))),o=r.map(({title:t,source:i})=>(0,koishi_1.h)("message",{userId:e.selfId,nickname:"投影渲染"},[...a.showViewTitles?[(0,koishi_1.h)("text",{content:t})]:[],(0,koishi_1.h)("img",{src:i})])),s=(0,koishi_1.h)("message",{userId:e.selfId,nickname:"投影信息"},[(0,koishi_1.h)("text",{content:i})]);if("forward"===a.sendMode)return void await e.send((0,koishi_1.h)("figure",{},[...o,s]));const c=a.replyAndMention?replyElements(e,a.qqBotType):[],d=r.flatMap(({title:e,source:t},i)=>[...a.showViewTitles?[(0,koishi_1.h)("text",{content:`${i?"\n":""}${e}\n`})]:[],(0,koishi_1.h)("img",{src:t})]);await e.send([...c,...d,(0,koishi_1.h)("text",{content:`\n${i}\n${n} 已渲染成功,结果如上`})])}async function composeQqOverview(e){if(!e.length)throw new Error("没有可合并的渲染图片");const t=await Promise.all(e.map(async e=>pngjs_1.PNG.sync.read(await node_fs_1.promises.readFile((0,node_path_1.resolve)(e.path))))),i=Math.max(...t.map(e=>e.width)),a=Math.max(4,Math.round(.01*i)),n=[];let r=0;const o=t.slice(0,Math.min(2,t.length)),s=1===o.length?i:Math.floor((i-a)/2),c=o.map(e=>Math.max(1,Math.round(e.height*s/e.width))),d=Math.max(...c);for(let e=0;e<o.length;e++)n.push({image:o[e],x:e*(s+a),y:Math.floor((d-c[e])/2),width:s,height:c[e]});r+=d;for(const e of t.slice(2)){r+=a;const t=Math.max(1,Math.round(e.height*i/e.width));n.push({image:e,x:0,y:r,width:i,height:t}),r+=t}const l=new pngjs_1.PNG({width:i,height:r});for(const e of n)drawScaledPng(l,e);const m=(0,node_path_1.resolve)((0,node_path_1.dirname)(e[0].path),"qq-overview.png");return await node_fs_1.promises.writeFile(m,pngjs_1.PNG.sync.write(l)),m}function drawScaledPng(e,t){const{image:i,x:a,y:n,width:r,height:o}=t;for(let t=0;t<o;t++){const s=Math.min(i.height-1,Math.floor((t+.5)*i.height/o));for(let o=0;o<r;o++){const c=Math.min(i.width-1,Math.floor((o+.5)*i.width/r)),d=4*(s*i.width+c),l=4*((n+t)*e.width+a+o);i.data.copy(e.data,l,d,d+4)}}}function replyElements(e,t=("qq"===e.platform?"official":"selfHosted")){const i=[];return e.messageId&&i.push((0,koishi_1.h)("quote",{id:e.messageId})),"selfHosted"===t&&e.userId&&(i.push((0,koishi_1.h)("at",{id:e.userId})),i.push((0,koishi_1.h)("text",{content:"\n"}))),i}class NbtReader{data;offset=0;constructor(e){this.data=e}byte(){return this.data.readInt8(this.offset++)}ubyte(){return this.data.readUInt8(this.offset++)}short(){const e=this.data.readInt16BE(this.offset);return this.offset+=2,e}int(){const e=this.data.readInt32BE(this.offset);return this.offset+=4,e}float(){const e=this.data.readFloatBE(this.offset);return this.offset+=4,e}double(){const e=this.data.readDoubleBE(this.offset);return this.offset+=8,e}long(){const e=this.data.readBigInt64BE(this.offset);return this.offset+=8,e}text(){const e=this.data.readUInt16BE(this.offset);this.offset+=2;const t=this.data.toString("utf8",this.offset,this.offset+e);return this.offset+=e,t}readNamedRoot(){if(10!==this.ubyte())throw new Error("不是 NBT Compound 数据");return this.text(),this.value(10)}value(e){switch(e){case 0:return null;case 1:return this.byte();case 2:return this.short();case 3:return this.int();case 4:return this.long();case 5:return this.float();case 6:return this.double();case 7:{const e=this.int(),t=this.data.subarray(this.offset,this.offset+e);return this.offset+=e,t}case 8:return this.text();case 9:{const e=this.ubyte(),t=this.int();return Array.from({length:t},()=>this.value(e))}case 10:{const e={};for(;;){const t=this.ubyte();if(!t)return e;e[this.text()]=this.value(t)}}case 11:{const e=this.int();return Array.from({length:e},()=>this.int())}case 12:{const e=this.int();return Array.from({length:e},()=>this.long())}default:throw new Error(`不支持的 NBT 标签类型:${e}`)}}}const MINECRAFT_DATA_VERSIONS=new Map([[4903,"26.2"],[4790,"26.1.2"],[4671,"1.21.11"],[4557,"1.21.10"],[4555,"1.21.9"],[4440,"1.21.8"],[4438,"1.21.7"],[4435,"1.21.6"],[4325,"1.21.5"],[4189,"1.21.4"],[4082,"1.21.3"],[4080,"1.21.2"],[3955,"1.21.1"],[3953,"1.21"],[3839,"1.20.6"],[3837,"1.20.5"],[3700,"1.20.4"],[3698,"1.20.3"],[3578,"1.20.2"],[3465,"1.20.1"],[3463,"1.20"],[3337,"1.19.4"],[3218,"1.19.3"],[3120,"1.19.2"],[3117,"1.19.1"],[3105,"1.19"],[2975,"1.18.2"]]);function readLitematicRoot(e){let t;try{t=(0,node_zlib_1.gunzipSync)(e)}catch{t=e}return new NbtReader(t).readNamedRoot()}function compoundValue(e){return!e||"object"!=typeof e||Array.isArray(e)||Buffer.isBuffer(e)?void 0:e}function numericValue(e){if("number"==typeof e&&Number.isFinite(e))return e;if("bigint"==typeof e){const t=Number(e);if(Number.isSafeInteger(t))return t}}function readMetadataSize(e,t){const i=compoundValue(t.EnclosingSize),a=i&&[numericValue(i.x),numericValue(i.y),numericValue(i.z)];if(a?.every(e=>null!=e))return a.map(e=>Math.abs(e));const n=compoundValue(e.Regions);if(!n)return;let r=1/0,o=1/0,s=1/0,c=-1/0,d=-1/0,l=-1/0;for(const e of Object.values(n)){const t=compoundValue(e),i=compoundValue(t?.Position),a=compoundValue(t?.Size),n=numericValue(i?.x),m=numericValue(i?.y),u=numericValue(i?.z),h=numericValue(a?.x),f=numericValue(a?.y),p=numericValue(a?.z);if([n,m,u,h,f,p].some(e=>null==e)||0===h||0===f||0===p)continue;const g=n+h-Math.sign(h),_=m+f-Math.sign(f),w=u+p-Math.sign(p);r=Math.min(r,n,g),o=Math.min(o,m,_),s=Math.min(s,u,w),c=Math.max(c,n,g),d=Math.max(d,m,_),l=Math.max(l,u,w)}return Number.isFinite(r)?[c-r+1,d-o+1,l-s+1]:void 0}function formatCreatedAt(e){if(null==e||e<=0)return"未知";const t=new Date(e<1e10?1e3*e:e);if(Number.isNaN(t.getTime()))return"未知";const i=new Intl.DateTimeFormat("zh-CN",{timeZone:"Asia/Shanghai",year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hourCycle:"h23"}).formatToParts(t),a=Object.fromEntries(i.map(e=>[e.type,e.value]));return`${a.year}-${a.month}-${a.day} ${a.hour}:${a.minute}:${a.second}`}function parseLitematicMetadata(e){const t=readLitematicRoot(e),i=compoundValue(t.Metadata)??{},a=numericValue(t.MinecraftDataVersion);return{author:"string"==typeof i.Author&&i.Author.trim()?i.Author.trim():"未知",createdAt:formatCreatedAt(numericValue(i.TimeCreated)),totalBlocks:numericValue(i.TotalBlocks),totalVolume:numericValue(i.TotalVolume),size:readMetadataSize(t,i),litematicVersion:numericValue(t.Version),minecraftDataVersion:a,minecraftVersion:null==a?void 0:MINECRAFT_DATA_VERSIONS.get(a)}}function validateProjectionLimits(e,t,i){const a=Number.isSafeInteger(i)&&i>0?i:5e6,n=()=>{throw new Error(`投影方块数或区域体积超过 ${a.toLocaleString("en-US")} 限制`)};(Math.abs(t.totalBlocks??0)>a||Math.abs(t.totalVolume??0)>a)&&n();const r=compoundValue(readLitematicRoot(e).Regions);if(!r)return;let o=0;for(const e of Object.values(r)){const t=compoundValue(compoundValue(e)?.Size),i=[numericValue(t?.x),numericValue(t?.y),numericValue(t?.z)];if(i.some(e=>null==e))continue;const r=i.reduce((e,t)=>e*Math.abs(t),1);(!Number.isSafeInteger(r)||r>a-o)&&n(),o+=r}}function formatLitematicMetadata(e,t){const i=null==e.totalBlocks?"未知":String(e.totalBlocks),a=null==e.totalVolume?"未知":String(e.totalVolume),n=e.size?.join(" × ")??"未知",r=null==e.litematicVersion?"未知":String(e.litematicVersion),o=e.minecraftVersion??"未知",s=null==e.minecraftDataVersion?"":`(数据版本:${e.minecraftDataVersion})`,c="string"==typeof t&&t.trim()?(0,node_path_1.basename)(t).replace(/\.litematic$/i,""):"";return[...c?[`投影名称:${c}`]:[],`保存者游戏 ID:${e.author}`,`创建时间:${e.createdAt}`,`方块数/体积:${i}/${a}`,`尺寸:${n}`,`Litematic 版本:${r}`,`游戏版本:${o}${s}`].join("\n")}function parseLitematic(e){const t=readLitematicRoot(e).Regions;if(!t||"object"!=typeof t)throw new Error("文件不含 Litematica Regions 数据");const i=[];for(const e of Object.values(t))parseRegion(e,i);if(!i.length)throw new Error("投影中没有非空气方块");return i}function parseRegion(e,t){const i=e.Position,a=e.Size,n=e.BlockStatePalette,r=e.BlockStates;if(!(i&&a&&Array.isArray(n)&&Array.isArray(r)))return;const o=Math.abs(a.x),s=Math.abs(a.y),c=Math.abs(a.z),d=o*s*c;if(!d)return;const l=Math.max(2,Math.ceil(Math.log2(n.length))),m=a.x<0?-1:1,u=a.y<0?-1:1,h=a.z<0?-1:1;for(let e=0;e<d;e++){const a=unpackState(r,e,l),s=n[a]?.Name;if(!s||"minecraft:air"===s||"minecraft:cave_air"===s||"minecraft:void_air"===s)continue;const d=e%o,f=Math.floor(e/o)%c,p=Math.floor(e/(o*c));t.push({x:i.x+d*m,y:i.y+p*u,z:i.z+f*h,name:s})}}function unpackState(e,t,i){const a=BigInt(t*i),n=Number(a/64n),r=a%64n;if(n>=e.length)return 0;const o=(1n<<BigInt(i))-1n;let s=BigInt.asUintN(64,e[n])>>r&o;return r+BigInt(i)>64n&&n+1<e.length&&(s|=BigInt.asUintN(64,e[n+1])<<64n-r&o),Number(s)}function renderSchematic(e,t){const i=getBounds(e);return[{title:"isometric.png",png:renderIsometric(e,i,t,!1)},{title:"isometric-reverse.png",png:renderIsometric(e,i,t,!0)}]}function getBounds(e){return e.reduce((e,t)=>({minX:Math.min(e.minX,t.x),minY:Math.min(e.minY,t.y),minZ:Math.min(e.minZ,t.z),maxX:Math.max(e.maxX,t.x),maxY:Math.max(e.maxY,t.y),maxZ:Math.max(e.maxZ,t.z)}),{minX:1/0,minY:1/0,minZ:1/0,maxX:-1/0,maxY:-1/0,maxZ:-1/0})}const SIX_FACE_ORDER=["up","down","east","south","west","north"],SIX_FACE_LABELS={up:["00000100000","00000100000","00000100000","00000100000","00111111100","00000100000","00000100000","00000100000","11111111111","00000000000","00000000000"],down:["11111111111","00000100000","00000100000","00000110000","00000101000","00000100100","00000100010","00000100000","00000100000","00000100000","00000000000"],east:["00000100000","00111111100","00000100000","11111111111","10000100001","00000100000","00001110000","00010101000","00100100100","01000100010","00000100000"],south:["00000100000","11111111111","00000100000","01111111110","01000100010","01011111010","01000100010","01010101010","01001110010","01000100010","01000000110"],west:["11111111111","00010100000","01111111110","01010101010","01010101010","01010011010","01010001010","01011111010","01000000010","01111111110","00000000000"],north:["00100001000","00100001000","00101111000","00110001000","11100001000","00100001000","00100001010","00100001100","00100101000","00111001000","00100000111"]};function renderSixFaceOverview(e,t,i=t.sixFaceLayout??"horizontal"){if(!e.length)throw new Error("投影中没有可生成六面图的方块");const a=Math.max(128,Math.floor(t.outputSize)),n=Math.max(4,Math.round(.01*a)),r="vertical"===i?2:3,o="vertical"===i?3:2,s=Math.floor((a-n*("vertical"===i?o+1:r+1))/("vertical"===i?o:r)),c=new Raster("vertical"===i?r*s+n*(r+1):a,"vertical"===i?a:o*s+n*(o+1),t),d=getBounds(e);for(const[t,i]of SIX_FACE_ORDER.entries()){const a=n+t%r*(s+n),o=n+Math.floor(t/r)*(s+n);drawOrthographicFace(c,e,d,i,a,o,s,n),drawFaceLabel(c,i,a,o,s,n)}return c.png()}function drawOrthographicFace(e,t,i,a,n,r,o,s){const c="east"===a||"west"===a?i.maxZ-i.minZ+1:i.maxX-i.minX+1,d="up"===a||"down"===a?i.maxZ-i.minZ+1:i.maxY-i.minY+1,l=new Map;for(const e of t){const t=orthographicCoordinates(e,i,a),n=`${t.a},${t.b}`,r=l.get(n);(!r||t.depth>r.depth)&&l.set(n,{block:e,...t})}const m=Math.max(2,Math.floor(s/2)),u=Math.max(1,o-2*m),h=Math.min(u/c,u/d),f=n+m+(u-c*h)/2,p=r+m+(u-d*h)/2;for(const{block:t,a:i,b:a}of[...l.values()].sort((e,t)=>e.b-t.b||e.a-t.a)){const n=Math.floor(f+i*h),r=Math.floor(p+a*h),o=Math.max(n+1,Math.ceil(f+(i+1)*h)),s=Math.max(r+1,Math.ceil(p+(a+1)*h));e.rect(n,r,o-n,s-r,colorFor(t.name))}}function orthographicCoordinates(e,t,i){switch(i){case"up":return{a:e.x-t.minX,b:e.z-t.minZ,depth:e.y};case"down":return{a:t.maxX-e.x,b:e.z-t.minZ,depth:-e.y};case"east":return{a:t.maxZ-e.z,b:t.maxY-e.y,depth:e.x};case"south":return{a:e.x-t.minX,b:t.maxY-e.y,depth:e.z};case"west":return{a:e.z-t.minZ,b:t.maxY-e.y,depth:-e.x};case"north":return{a:t.maxX-e.x,b:t.maxY-e.y,depth:-e.z}}}function drawFaceLabel(e,t,i,a,n,r){const o=SIX_FACE_LABELS[t],s=Math.max(1,Math.min(6,Math.floor(n/80))),c=Math.max(1,Math.floor(s/2)),d=i+Math.max(2,Math.floor(r/2)),l=a+Math.max(2,Math.floor(r/2));e.rect(d,l,o[0].length*s+2*c,o.length*s+2*c,"#000000");for(let t=0;t<o.length;t++)for(let i=0;i<o[t].length;i++)"1"===o[t][i]&&e.rect(d+c+i*s,l+c+t*s,s,s,"#ffffff")}function renderIsometric(e,t,i,a){const n=i.isometricCellSize,r=Math.ceil(n/2),o=(t.maxX-t.minX+t.maxZ-t.minZ+2)*n+4,s=(t.maxX-t.minX+t.maxZ-t.minZ+2)*r+(t.maxY-t.minY+2)*n+4,c=Math.max(1,Math.min(1,i.outputSize/Math.max(o,s))),d=new Raster(Math.ceil(o*c),Math.ceil(s*c),i),l=e=>({x:a?t.maxX-e.x:e.x-t.minX,z:a?t.maxZ-e.z:e.z-t.minZ}),m=[...e].sort((e,t)=>{const i=l(e),a=l(t);return i.x+i.z+e.y-(a.x+a.z+t.y)});for(const e of m){const i=l(e),a=(i.x-i.z)*n+o/2,s=(i.x+i.z)*r+(t.maxY-e.y+1)*n;d.voxel(Math.round(a*c),Math.round(s*c),Math.max(2,Math.round(n*c)),colorFor(e.name))}return d.png()}const COLORS={stone:"#777777",cobblestone:"#707070",deepslate:"#4b4b50",dirt:"#855b37",grass_block:"#63913d",sand:"#dbca85",glass:"#a7d9df",water:"#3f76e4",lava:"#e05b22",oak_planks:"#b9915a",spruce_planks:"#765033",birch_planks:"#d9c485",iron_block:"#d7d7d7",gold_block:"#f6c543",diamond_block:"#5ed6c7",redstone_block:"#b21d1d",netherrack:"#823332",quartz_block:"#e9e4dc",obsidian:"#211d2d",white_wool:"#e9e9e9",black_wool:"#1d1d1d",red_wool:"#a12722",blue_wool:"#3549a7",green_wool:"#4c702c",yellow_wool:"#e5c440"};function colorFor(e){const t=e.replace(/^minecraft:/,"");if(COLORS[t])return COLORS[t];for(const[e,i]of Object.entries(COLORS))if(t.includes(e))return i;const i=(0,node_crypto_1.createHash)("md5").update(t).digest();return`#${(90+i[0]%120).toString(16).padStart(2,"0")}${(90+i[1]%120).toString(16).padStart(2,"0")}${(90+i[2]%120).toString(16).padStart(2,"0")}`}class Raster{width;height;data;background;constructor(e,t,i){this.width=e,this.height=t,this.background=parseColor(i.background,i.transparentBackground?0:255),this.data=Buffer.alloc(e*t*4);for(let e=0;e<this.data.length;e+=4)this.data.set(this.background,e)}rect(e,t,i,a,n){for(let r=Math.max(0,t);r<Math.min(this.height,t+a);r++)for(let t=Math.max(0,e);t<Math.min(this.width,e+i);t++)this.pixel(t,r,n)}pixel(e,t,i){e<0||t<0||e>=this.width||t>=this.height||this.data.set(parseColor(i),4*(t*this.width+e))}voxel(e,t,i,a){const n=adjust(a,1.15),r=adjust(a,.78),o=adjust(a,.6),s=Math.ceil(i/2);for(let a=0;a<s;a++)for(let r=-i+a;r<=i-a;r++)this.pixel(e+r,t-s+a,n);for(let a=0;a<i;a++)for(let n=-i+Math.floor(a/2);n<=0;n++)this.pixel(e+n,t+a,r);for(let a=0;a<i;a++)for(let n=0;n<=i-Math.floor(a/2);n++)this.pixel(e+n,t+a,o)}png(){const e=Buffer.alloc((4*this.width+1)*this.height);for(let t=0;t<this.height;t++)e[t*(4*this.width+1)]=0,this.data.copy(e,t*(4*this.width+1)+1,t*this.width*4,(t+1)*this.width*4);const t=Buffer.alloc(13);return t.writeUInt32BE(this.width,0),t.writeUInt32BE(this.height,4),t.set([8,6,0,0,0],8),Buffer.concat([Buffer.from("PNG\r\n\n","binary"),chunk("IHDR",t),chunk("IDAT",(0,node_zlib_1.deflateSync)(e)),chunk("IEND",Buffer.alloc(0))])}}function parseColor(e,t=255){const i=/^#?([0-9a-f]{6})$/i.exec(e)?.[1]??"182026";return[parseInt(i.slice(0,2),16),parseInt(i.slice(2,4),16),parseInt(i.slice(4,6),16),t]}function adjust(e,t){const[i,a,n]=parseColor(e);return`#${[i,a,n].map(e=>Math.round(Math.min(255,e*t)).toString(16).padStart(2,"0")).join("")}`}function chunk(e,t){const i=Buffer.from(e),a=Buffer.alloc(4);a.writeUInt32BE(t.length);const n=Buffer.alloc(4);return n.writeUInt32BE(crc32(Buffer.concat([i,t]))>>>0),Buffer.concat([a,i,t,n])}const CRC_TABLE=Array.from({length:256},(e,t)=>{let i=t;for(let e=0;e<8;e++)i=1&i?3988292384^i>>>1:i>>>1;return i>>>0});function crc32(e){let t=4294967295;for(const i of e)t=CRC_TABLE[255&(t^i)]^t>>>8;return(4294967295^t)>>>0}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaozhu2007/koishi-plugin-litematic-renderer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "Render small Litematica schematics sent in QQ groups",
|
|
5
5
|
"author": "halfkite, xiaozhu2007",
|
|
6
6
|
"repository": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"lib",
|
|
21
21
|
"assets/threeviews",
|
|
22
22
|
"assets/java-bridge",
|
|
23
|
-
"assets/standalone-renderer/litematic-standalone-renderer-0.2.
|
|
23
|
+
"assets/standalone-renderer/litematic-standalone-renderer-0.2.7.jar",
|
|
24
24
|
"assets/vanilla-resources",
|
|
25
25
|
"README.md"
|
|
26
26
|
],
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@koishijs/plugin-adapter-onebot": "^6.0.2",
|
|
39
39
|
"@types/node": "^22.0.0",
|
|
40
|
+
"@types/pngjs": "^6.0.5",
|
|
40
41
|
"koishi": "^4.18.0",
|
|
41
42
|
"terser": "^5.51.2",
|
|
42
43
|
"typescript": "^6.0.3"
|
|
@@ -50,5 +51,8 @@
|
|
|
50
51
|
"description": {
|
|
51
52
|
"zh": "自动渲染群内 Litematica 投影文件"
|
|
52
53
|
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"pngjs": "^7.0.0"
|
|
53
57
|
}
|
|
54
58
|
}
|