The zero-based character image unpacking tutorial

Disclaimer: This article is a blogger original article, follow the CC 4.0 BY-SA copyright agreement, reproduced, please attach the original source link and this statement.
This link: https://blog.csdn.net/Conyrol/article/details/96781786

Blue poison angel best in the world!
Blue poison angel best in the world!

This article is not limited to unpacking tutorial tomorrow Ark, behind unpacking tutorials will be given other hand travel, such as girls front, blue and other routes
last updated on August 7, 2019

table of Contents

Unpacking introduction and use of tools:

First unpacking the simplest game is nothing more than cannibalize resources, such as a game character image, audio, video, etc., and then the deep split that has taken the game configuration files, apk partially decompile the source code, etc.
In this article, we only introduce cannibalize the game resources and post-processing of game resources, but this way can not guarantee that all hand painted vertical travel can be extracted, processed drawing files and other games of different games different opposition, only a few ideas here XD

可能需要用到的工具有 Unity Studio,装有 opencv 的python或C++,PhotoShop
其中 Unity Studio 是必要工具,可以用来提取市面上大部分手游的游戏资源
装有 opencv 的python或C++,PhotoShop 等可以对提取的游戏资源(立绘)进行处理,如果是要批量处理的话首推 C++python 虽然方便但是太慢,PhotoShop 也可以进行批量处理,但是麻烦且慢

PS:如果有其他有同样功能的工具也可以,不限于上面的几个软件,点击蓝色可以进网盘下载,理论上用 python的 PIL库 更好,不过我直接用 opencv 了,导致最后生成的可执行文件比较大

提取资源的方法:

首先,如果要拆取立绘等文件,就需要对应用程序进行处理,最简单的有两种方式:

  1. 对下载的 apk 安装包 进行解压
    方法如下,在电脑上下载对应游戏的apk安装包,把后缀 .apk 改为压缩包文件格式,如 .rar 等,利用解压软件对其进行解压得到解压文件夹
  2. 对已经安装在手机的游戏,寻找系统目录,找到其文件夹
    以少女前线为例,通常在手机上安装的软件,会把资源文件等放在 Android 文件夹的 data 文件夹下,这个时候找到其对应的游戏文件夹即可,下图是 少女前线 的文件夹,明日方舟比较特殊,它将资源文件隐藏在了其他文件目录下,所以最好使用第一种方法
    Here Insert Picture Description
    通常进入文件夹后按照顺序进入以下文件夹 files 文件夹—— AB 文件夹 ——Android 文件夹,会看到里面的游戏资源文件,如果你是按照第一个解压方法做的,也可以看到上面的文件路径,按照该顺序点进去就行,然后根据游戏的不同,不同的文件可能会被放在一起,也可能会被分类在不同文件夹中,这个时候只需要按照自己需要的进行复制提取到电脑上就好了,注意不要剪切,特别是用第二种方法的,千万别剪切

当然,到这里还没有结束,我们看到的资源文件并不是原来格式,而是以 ab后缀的文件 展示的,这个时候就需要 Unity Studio 来提取目标文件(当然不是所有手游都会用Unity,所以这个方法并不适用于所有手游,如果以后我遇到其他类型的再更新吧XD)
Here Insert Picture Description
这里我们以明日方舟为例,如果你正确的找到了路径,那么你可以看到一个名为 charpack 的文件夹,其中储存了明日方舟的干员立绘文件
Here Insert Picture Description
这个时候打开 Unity Studio ,用如下选项 Extract folder 批量解压提取ab文件,之后使用 load folder 加载处理后的文件
Here Insert Picture Description
然后在 Asset List 中就可以看到提取的内容
Here Insert Picture Description
Here Insert Picture Description

资源的后续处理:

提取出来的图片大部分情况下是有瑕疵的,因为他们需要进一步处理才能得到原图
而对于其中提取的内容,我们通常需要关心很多文件
常规情况下(使用通道分离压缩图片的):我们需要找到一个是Type为Texture2D的原图,一个是Type为Texture2D或Sprite的透明度背景图,而且通常情况下,原图有一个文件名,对应透明度背景图文件名会在原图文件名后面加上alpha字样,我们通过处理这两个图才能得到最终的png图像文件,所以现在就将所有对应的图片文件从 Unity Studio 导出
还有一些情况,有些手游会对图片进行加密或者是做一些额外处理:这种情况我们就需要按照情况来分析哪些文件有用,哪些文件没用,例如碧蓝航线需要使用一张图片 + 一张obj 3D模型文件来还原原图

Here Insert Picture Description

A. 通道分离图像的合成:

首先我们在前面提到过,如果我们能找到一个是Type为Texture2D的原图,一个是Type为Texture2D或Sprite的透明度背景图,那么就说明这个立绘需要进行通道分离图像的合成来得到原图

那么这个时候我们应该如何处理呢,首先 png 图像有4个通道,前三个是 RGB 颜色通道,第四个是透明度通道,实际上为了减少图片的大小,大部分游戏厂商都会先对图片进行 ETC 等压缩算法的处理,再将透明通道单独剥离出去,以达到最大程度的压缩,其中你会发现我们经手的立绘和在游戏中展示的一样,但仔细看会发现不够清晰,正是因为在打包成apk文件之前,游戏公司就已经先对图片进行了压缩处理,丧失了一部分信息,所以说并不是画师的原图,如果想提高清晰度,有两种方法,一种是找原画师或官方公布等途径得到原图,一种是利用图片优化软件对其进行清晰度优化

进入正题,如何处理我们得到文件?

1. 利用 PS 进行处理:

这里我们直接引用一下这位贴吧老哥的方法: 透明立绘简单合成方法,小白一键操作

方法很简单,但缺点是对大量处理文件十分无力,而且需要手工对文件顺序进行排序,也没法判断两个图片尺寸比,适合小量立绘的手工处理

效果图:
Here Insert Picture Description
输出效果:
Here Insert Picture Description

2. 利用python或C++进行处理:

这个方法适合大量处理,而且不用对图片进行分类,甚至你在 Untiy Studio上导出图片时,可以毫无顾忌的将图片一股脑导出来,让程序自己去找原图和透明度背景图
在这里我用到的工具是 opencv 库

以明日方舟为例: 可以发现它 Alpha 图像中 R 通道 的值代表原图片透明度,那么我只需要将这个图片矩阵的 R 通道 信息赋值给原图像的 透明度通道 即可,然后注意一下有些Alpha图像是原图像大小的 1/2 倍,需要放大 2
其中放大方法调用的是 双立方插值算法 ,这个算法效果比较好
另外就是 PhotoShop 的默认放大算法也是这个算法

python代码: (本来想用C++的,但无奈python太方便了Orz,导致处理速度会慢很多)

# -*- coding: utf-8 -*-
import shutil
import time
import cv2
import os

def read_png(s):
	img = cv2.imread(s, cv2.IMREAD_UNCHANGED)
	return img
def save_png(s, img):
	cv2.imwrite(s, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
def exchange(img, x): #放大图像
	w, h = img.shape[0:2]
	tempimg = cv2.resize(img,(w*x,h*x), interpolation=cv2.INTER_CUBIC)
	return tempimg
def Do(a, b):
    a[:,:,3] = b[:,:,0] #合成的基本思路
    return a
    
print("提取图片/合成立绘?1/2")
a = input()
if a == '1': #提取立绘
    file_list = os.listdir("./Input")
    print("共%d个图片:" %len(file_list))
    print('begin')
    for i in file_list:
      	img = cv2.imread("./Input/" + i, cv2.IMREAD_UNCHANGED)
        	if img is not None and img.shape[0] > 512:
         	if '#' in i:
          	i2 = i.replace(i[i.find('#'):], '#1[alpha].png')
            	i3 = i.replace(i[i.find('#'):], '#1.png')
         	else:
         		i2 = i.replace(".png", '[alpha].png')
         		i3 = i
         	if i2 in file_list:
         		hutil.copy("./Input/" + i, "./Texture2D_A/" + i3)
         		os.remove("./Input/" + i)
         		shutil.copy("./Input/" + i2, "./Texture2D_B/" + i2)
         		os.remove("./Input/" + i2)
    	print('over')
    	os.system('pause')
if a == '2': #合成立绘
	file_list = os.listdir("./Texture2D_A")
	print("待合成%d个图片:" %len(file_list))
    	for i in file_list:
    		print('正在处理%s' %i)
    		time_start = time.time()
    		i2 = i.replace(".png", '[Alpha].png')
    		a = read_png('./Texture2D_A/' + i)
    		b = read_png('./Texture2D_B/' + i2)
    		if a.shape[0]/b.shape[0] != 1:
    			b = exchange(b, int(a.shape[0]/b.shape[0]))
    			save_png('./Picture/' + i, Do(a, b))
    			shutil.copy("./Texture2D_A/" + i, "./Used/" + i)
    			os.remove("./Texture2D_A/" + i)
    			time_end = time.time()
    			print("耗时 %f s" %(time_end-time_start))
    	print('over')
    	os.system('pause')

具体操作就是:
1:你只需要把提取出来的立绘图像(可以包括小人动态的分割图像)放进 Input 文件夹中
2:运行exe文件,先输入 1 提取所有合理的立绘图像
3:再次运行exe文件,输入 2 合成立绘,最后图像在 Picture 文件夹中
Here Insert Picture Description
其中 Texture2D_ATexture2D_BUsed 都是程序运行的中间文件夹,可以不用去管,另外就是这个程序支持中断操作,也就是说你在合成图片或提取图片时因为某些原因终止了程序,那么程序会保留你之前的记录,你想要把剩余的图片处理完,只需要再次运行即可


明日方舟立绘集合,目前更新至2019年7月20号: 明日方舟立绘集合,提取码:5iyj
明日方舟立绘合成器,目前更新至2019年7月20号: 明日方舟立绘合成器,提取码:dsfd

同理,少女前线也是使用了通道分离来处理立绘图像,所以代码都差不多,就不贴了

少女前线立绘集合,目前更新至2019年7月27号: 少女前线立绘集合,提取码:xmts
少女前线立绘合成器,目前更新至2019年8月3号: 少女前线立绘合成器,提取码:50f9

B. 碎块图像合成:

比较典型的就是就是碧蓝航线,这款游戏没有用通道分离来压缩图像,而是因不明原因,将图像换成了碎块和记录碎块在原图信息的 obj 文件组成

22 举例:

碎块图(尸块图):

Here Insert Picture Description
obj文件:
Here Insert Picture Description
打开obj文件分析就会发现其中的 v 记录的是每个碎块在原图像的顶点坐标,4 个为一组:
Here Insert Picture Description
其中的 vt 记录的是每个碎块在碎块图像的顶点坐标(分别乘上碎块图像长和宽之后就是顶点坐标,这个小数代表在碎块图像中的比例位置),也是 4 个为一组:
Here Insert Picture Description
那么通过读取 obj 文件的信息来拼接碎尸图,就可以系统的将原图拼接好

python代码:

# -*- coding: utf-8 -*-
import numpy as np
import shutil
import time
import cv2
import os

def read_file(s):
	List1 = []
	Filer = open(s,'r')
	List2 = Filer.readlines()
	Filer.close()
	for i0 in range(0, len(List2)):
		List2[i0] = List2[i0].replace("\n", "")
		List1.append(List2[i0].split())
	return List1
	
def read_png(s):
	img = cv2.imread(s, cv2.IMREAD_UNCHANGED)
	return img
def save_png(s, img):
	cv2.imwrite(s, img, [int(cv2.IMWRITE_PNG_COMPRESSION), 9])
def rotate(img):
	new_img = cv2.flip(img, -1)
	return new_img
	
def manage(s1, s2):
	s1 = "./Mesh/" + s1
	max_height = max_weight = vtjShu = firstx = endx = firsty = endy = 0
	print('正在处理%s' %s2)
	time_start = time.time()
	List = read_file(s1)
	img = read_png("./Texture2D/" + s2)
	height = img.shape[0]
	eight = img.shape[1]
	img = rotate(img)
	img = cv2.flip(img, 1
	for i in range(1, len(List)):
		if List[i][0] != 'v':
			vtjShu = i
			break
		if abs(int(List[i][1])) > max_weight:
			max_weight = abs(int(List[i][1]))
		if abs(int(List[i][2])) > max_height:
			max_height = abs(int(List[i][2])
	Base_img = np.zeros([max_height+1, max_weight+1, 4], np.uint8)
	jShu = vtjShu
	while vtjShu < len(List):
		if List[vtjShu][0] != 'vt':
			break
		firsty = int(float(List[vtjShu][1]) * float(weight) + 0.5)
		firstx = int(float(List[vtjShu][2]) * float(height) + 0.5)
		endy = int(float(List[vtjShu+2][1]) * float(weight) + 0.5)
		endx = int(float(List[vtjShu+2][2]) * float(height) + 0.5)
		y1 = abs(int(List[vtjShu-jShu+1][1]))
		x1 = abs(int(List[vtjShu-jShu+1][2]))
		for i in range(firstx, endx):
			for j in range(firsty, endy):
				Base_img[x1+i-firstx][y1+j-firsty] = img[i][j]
		vtjShu += 4
	Base_img = rotate(Base_img)
	save_png("./Picture/"+s2, Base_img)
	time_end = time.time()
	print("处理完毕,耗时 %f s" %(time_end-time_start))

Mesh_list = os.listdir("./Mesh")
Texture2D_list = os.listdir("./Texture2D")
print("检测到%d组" %len(Texture2D_list))
for i in range(0, len(Texture2D_list)):
	s = Texture2D_list[i].replace(".png", "-mesh.obj")
	if s not in Mesh_list:
		print("Can't find the %s in Mesh" %s)	
	else:
		manage(s, Texture2D_list[i])
		shutil.copy("./Texture2D/" + Texture2D_list[i], "./Used/" + Texture2D_list[i])
		os.remove("./Texture2D/" + Texture2D_list[i])
print("Over")
os.system("pause")


碧蓝航线立绘集合,目前更新至2019年7月31号: 碧蓝航线立绘集合,提取码:65wf
碧蓝航线立绘合成器,目前更新至2019年8月5号: 碧蓝航线立绘合成器,提取码:slcg

To do that:
1: you just need to extract out of the stand-drawn image on Texture2D rather obj files into the Mesh folder, regardless of the order of questions, the program will find themselves
2: Run the exe file to the final result in the Picture folder
Here Insert Picture Description
where Used is running an intermediate file folder, you can not go tube, the other is to keep up with the program tomorrow Fangzhou Li painted a synthetic procedures as also support interrupt operation, so the run will pause halfway accordance with the original progress continues, rather than starting from scratch

C. Other types to be more

Other resource extraction and post-processing method for mobile games may vary, and some mobile games are the same, so it is necessary modifications, if there is time to update this blog should have been

postscript:

I stand unpacking painted only for learning purposes, not for commercial purposes (licking wife prpr)

Guess you like

Origin blog.csdn.net/Conyrol/article/details/96781786