一般来说 malie 引擎制作的游戏,例如 light 社的游戏(八命阵,神怒之日等),这种引擎在使用 GARbro 或者 土拔鼠的工具 拆包后解出的图片是被切片的,这不是完整的 CG 图片
然后有若干 dzi 的图片坐标索引文件,所以我们要做的事情就是根据这个坐标文件来合成 tex 文件夹里面被切分的图片
然后代码是这样的,嗯。。这是 TypeScript 写的,因为最近几年 Web 写的太多了,C / C++ / C# 什么的忘光了,什么你说 js 凭什么能写这些,为什么不行
import fs from 'fs-extra'
import path from 'path'
import sharp from 'sharp'
const EVENT_DIR = path.resolve(__dirname, 'event')
const TEX_DIR = path.join(EVENT_DIR, 'tex')
const DIST_DIR = path.join(EVENT_DIR, 'dist')
// layer_1 is original size, layer_2 is original size / 0.5,layer_3 is original size / 0.25 ...
const ENABLE_LOWER_LAYERS = true
const parseDzi = async (filePath: string) => {
const content = await fs.readFile(filePath, 'utf-8')
const lines = content.trim().split(/\r?\n/)
const [formatLine, sizeLine, ...restLines] = lines
const [imgWidth, imgHeight] = sizeLine.split(',').map(Number)
const layers: { tiles: string[][]; rows: number; cols: number }[] = []
let i = 0
while (i < restLines.length) {
const [cols, rows] = restLines[i++].split(',').map(Number)
const tileLines: string[][] = []
for (let r = 0; r < rows; r++) {
tileLines.push(restLines[i++].split(','))
}
layers.push({ tiles: tileLines, rows, cols })
}
return { width: imgWidth, height: imgHeight, layers }
}
const composeLayer = async (
tiles: string[][],
group: string,
layerIndex: number,
outputPath: string,
finalWidth: number,
finalHeight: number
) => {
if (!tiles || tiles.length === 0 || tiles[0].length === 0) {
return
}
const rows = tiles.length
const cols = tiles[0].length
const firstTilePath = path.join(TEX_DIR, tiles[0][0] + '.png')
const { width: tileW, height: tileH } = await sharp(firstTilePath).metadata()
if (!tileW || !tileH) {
throw new Error(`Cannot get the tile size: ${firstTilePath}`)
}
const composedWidth = cols * tileW
const composedHeight = rows * tileH
const fullImg = sharp({
create: {
width: composedWidth,
height: composedHeight,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 },
},
})
const composites: sharp.OverlayOptions[] = []
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const tileRelPath = tiles[y][x]
if (!tileRelPath) {
continue
}
const tileAbsPath = path.join(TEX_DIR, tileRelPath + '.png')
const buffer = await fs.readFile(tileAbsPath)
composites.push({
input: buffer,
left: x * tileW,
top: y * tileH,
})
}
}
const layerOutputDir = path.join(outputPath, group)
await fs.ensureDir(layerOutputDir)
const outFile = path.join(layerOutputDir, `layer_${layerIndex}.png`)
const cropWidth = Math.min(finalWidth, composedWidth)
const cropHeight = Math.min(finalHeight, composedHeight)
await fullImg
.composite(composites)
.extract({ left: 0, top: 0, width: cropWidth, height: cropHeight })
.toFile(outFile)
console.log(`Compose successfully: ${outFile}`)
}
const processAllDziFiles = async () => {
const files = await fs.readdir(EVENT_DIR)
if (fs.existsSync(DIST_DIR)) {
await fs.rm(DIST_DIR, { recursive: true, force: true })
}
for (const file of files) {
if (!file.endsWith('.dzi')) continue
const filePath = path.join(EVENT_DIR, file)
const groupName = path.basename(file, '.dzi')
console.log(`Handling ${groupName} ...`)
const {
width: imgWidth,
height: imgHeight,
layers,
} = await parseDzi(filePath)
for (let i = 0; i < layers.length; i++) {
if (i > 1 && !ENABLE_LOWER_LAYERS) {
console.log(`Skip layer_${i} due to config`)
continue
}
const { tiles } = layers[i]
const scaleFactor = Math.pow(0.5, i - 1)
const targetWidth = Math.round(imgWidth * scaleFactor)
const targetHeight = Math.round(imgHeight * scaleFactor)
await composeLayer(
tiles,
groupName,
i,
DIST_DIR,
targetWidth,
targetHeight
)
}
}
console.log('Assemble all cgs successfully!')
}
processAllDziFiles().catch((err) => {
console.error('ERROR OCCURRENT', err)
})
EVENT_DIR
是拆出来的图片文件夹,把这个 event 文件夹放在和这个脚本的同级目录下面
TEX_DIR
就是图中的那个 tex 文件夹,点进去有一堆图片碎片
DIST_DIR
是输出文件夹,合成后的图片会被输出到这个文件夹里面
ENABLE_LOWER_LAYERS
是一个配置项,不知道为什么每一张 CG 都被拆成了 0, 1, 2 三种大小,然后每一种大小都是在原分辨率的基础上长宽变成了 0.5 倍
ENABLE_LOWER_LAYERS
代表连同这些缩小过的 CG 一起合成,但是好像没什么用所以默认是关闭的
使用方法需要有 Node.js
环境,可以使用 pnpm i -g esno
来安装 esno, 然后使用 esno 运行这个脚本
这里还有一个,写了一半但是没有继续写的仓库,我想把这个东东封装成命令行,但是我真的懒得把它打包成可执行文件,哭了
https://github.com/KUN1007/assemble-malie-dzi
里面有一个测试的 Rust 程序,也写了一半,嗯,咕咕咕咕咕
为什么要写,因为今天实在是无聊,突然想到这个了,然后就写一写,为什么要玩这个游戏,因为有小只可爱软萌白毛红瞳女孩子!!!!!!!!!!!!啊啊啊啊啊啊啊啊啊啊啊啊这简直就是天使啊啊啊啊啊啊啊啊!身上有她在爬,看见就会滚来滚去的,嗯