最近碰到像这种图片被一块块调乱了的CG图,
请问这种图怎么找解密还原的方法?
以下是游戏包,这两游戏都是HCG打成了马赛克,并且live2D导出来打不开
游戏包薇薇與魔法之島Ver1.05
游戏包–帝国后宫计划(PC和安卓版)我只下了PC版
解压密码 dpsvip
另外还有这两个游戏加密HCG样版图片各一张
微微与帝国后宫样版图.zip (8.7 MB)
大佬们帮看看这两个游戏的HCG怎么解码,还有live2D怎么导出来打不开
最近碰到像这种图片被一块块调乱了的CG图,
请问这种图怎么找解密还原的方法?
以下是游戏包,这两游戏都是HCG打成了马赛克,并且live2D导出来打不开
游戏包薇薇與魔法之島Ver1.05
游戏包–帝国后宫计划(PC和安卓版)我只下了PC版
解压密码 dpsvip
另外还有这两个游戏加密HCG样版图片各一张
微微与帝国后宫样版图.zip (8.7 MB)
大佬们帮看看这两个游戏的HCG怎么解码,还有live2D怎么导出来打不开
发原件出来我们才能尝试去拼图
CTF杂项题中也存在很多这种拼图题目应该可以使用相同方法解决
大佬 原件和游戏包我都发出来了,有空帮看看
看样子应该是你说的这个,有空我研究研究
我今天解游戏时也遇到的这个问题,经过提示,通过修改优化的py解决了。改的比较懒人加简单了。
不过可惜的是上面第一个游戏不是很适配这个方法,因为我没法找到对应的配置文件(含cellSize,padding,atlasTextures,textureDataList字段),也可能不在Monobehavior类中?虽然也是这种剪切方式,可以参考一下。
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Utage
{
// Token: 0x02000317 RID: 791
[Serializable]
public class DicingTextureData
{
// Token: 0x17000622 RID: 1570
// (get) Token: 0x06001D7A RID: 7546 RVA: 0x0008E96C File Offset: 0x0008CB6C
public string Name
{
get
{
return this.name;
}
}
// Token: 0x17000623 RID: 1571
// (get) Token: 0x06001D7B RID: 7547 RVA: 0x0008E974 File Offset: 0x0008CB74
public string AtlasName
{
get
{
return this.atlasName;
}
}
// Token: 0x17000624 RID: 1572
// (get) Token: 0x06001D7C RID: 7548 RVA: 0x0008E97C File Offset: 0x0008CB7C
public int Width
{
get
{
return this.width;
}
}
// Token: 0x17000625 RID: 1573
// (get) Token: 0x06001D7D RID: 7549 RVA: 0x0008E984 File Offset: 0x0008CB84
public int Height
{
get
{
return this.height;
}
}
// Token: 0x06001D7E RID: 7550 RVA: 0x0008E98C File Offset: 0x0008CB8C
internal List<DicingTextureData.QuadVerts> GetVerts(DicingTextures textures)
{
if (this.verts == null)
{
this.InitVerts(textures);
}
return this.verts;
}
// Token: 0x06001D7F RID: 7551 RVA: 0x0008E9A4 File Offset: 0x0008CBA4
private void InitVerts(DicingTextures atlas)
{
if (atlas == null)
{
return;
}
this.verts = new List<DicingTextureData.QuadVerts>();
int cellSize = atlas.CellSize;
int num = cellSize - atlas.Padding * 2;
int num2 = Mathf.CeilToInt(1f * (float)this.Width / (float)num);
int num3 = Mathf.CeilToInt(1f * (float)this.Height / (float)num);
int num4 = atlas.GetTexture(this.AtlasName).width;
int num5 = atlas.GetTexture(this.AtlasName).height;
int num6 = Mathf.CeilToInt(1f * (float)num4 / (float)cellSize);
int num7 = 0;
for (int i = 0; i < num3; i++)
{
float num8 = (float)(i * num);
float num9 = Mathf.Min(num8 + (float)num, (float)this.Height);
for (int j = 0; j < num2; j++)
{
DicingTextureData.QuadVerts quadVerts = new DicingTextureData.QuadVerts();
float num10 = (float)(j * num);
float num11 = Mathf.Min(num10 + (float)num, (float)this.Width);
quadVerts.v = new Vector4(num10, num8, num11, num9);
int num12 = this.cellIndexList[num7];
quadVerts.isAllTransparent = (num12 == this.transparentIndex);
float num13 = (float)(num12 % num6 * cellSize);
float num14 = (float)(num12 / num6 * cellSize);
float x = 1f * (num13 + (float)atlas.Padding) / (float)num4;
float y = 1f * (num14 + (float)atlas.Padding) / (float)num5;
float num15 = 1f * (num11 - num10) / (float)num4;
float num16 = 1f * (num9 - num8) / (float)num5;
quadVerts.uvRect = new Rect(x, y, num15, num16);
this.verts.Add(quadVerts);
num7++;
}
}
}
// Token: 0x06001D80 RID: 7552 RVA: 0x0008EB64 File Offset: 0x0008CD64
public void ForeachVertexList(Rect position, Rect uvRect, bool skipTransParentCell, DicingTextures textures, Action<Rect, Rect> function)
{
Vector2 scale = new Vector2(position.width / (float)this.Width, position.height / (float)this.Height);
this.ForeachVertexList(uvRect, skipTransParentCell, textures, delegate(Rect r1, Rect r2)
{
r1.xMin *= scale.x;
r1.xMax *= scale.x;
r1.x += position.x;
r1.yMin *= scale.y;
r1.yMax *= scale.y;
r1.y += position.y;
function(r1, r2);
});
}
// Token: 0x06001D81 RID: 7553 RVA: 0x0008EBD0 File Offset: 0x0008CDD0
public void ForeachVertexList(Rect uvRect, bool skipTransParentCell, DicingTextures textures, Action<Rect, Rect> function)
{
if (uvRect.width == 0f || uvRect.height == 0f)
{
return;
}
if (uvRect.xMin < 0f)
{
uvRect.x += (float)Mathf.CeilToInt(-uvRect.xMin);
}
if (uvRect.yMin < 0f)
{
uvRect.y += (float)Mathf.CeilToInt(-uvRect.yMin);
}
bool flag = false;
if (uvRect.width < 0f)
{
uvRect.width *= -1f;
flag = true;
}
bool flag2 = false;
if (uvRect.height < 0f)
{
uvRect.height *= -1f;
flag2 = true;
}
float scaleX = 1f / uvRect.width;
float fipOffsetX = 0f;
if (flag)
{
scaleX *= -1f;
fipOffsetX = (float)this.Width;
}
float scaleY = 1f / uvRect.height;
float fipOffsetY = 0f;
if (flag2)
{
scaleY *= -1f;
fipOffsetY = (float)this.Height;
}
float num = uvRect.yMin % 1f;
float num2 = uvRect.yMax % 1f;
if (num2 == 0f)
{
num2 = 1f;
}
float offsetY = 0f;
bool flag3 = true;
Rect rect = default(Rect);
bool flag4;
do
{
rect.yMin = (flag3 ? num : 0f);
flag4 = (offsetY + 1f - rect.yMin >= uvRect.height);
rect.yMax = (flag4 ? num2 : 1f);
float num3 = uvRect.xMin % 1f;
float num4 = uvRect.xMax % 1f;
if (num4 == 0f)
{
num4 = 1f;
}
float offsetX = 0f;
bool flag5 = true;
bool flag6;
do
{
rect.xMin = (flag5 ? num3 : 0f);
flag6 = (offsetX + 1f - rect.xMin >= uvRect.width);
rect.xMax = (flag6 ? num4 : 1f);
this.ForeachVertexListSub(rect, skipTransParentCell, textures, delegate(Rect r1, Rect r2)
{
r1.xMin *= scaleX;
r1.xMax *= scaleX;
r1.x += (offsetX - rect.xMin) * scaleX * (float)this.Width + fipOffsetX;
r1.yMin *= scaleY;
r1.yMax *= scaleY;
r1.y += (offsetY - rect.yMin) * scaleY * (float)this.Height + fipOffsetY;
function(r1, r2);
});
offsetX += rect.width;
flag5 = false;
}
while (!flag6);
offsetY += rect.height;
flag3 = false;
}
while (!flag4);
}
// Token: 0x06001D82 RID: 7554 RVA: 0x0008EF1C File Offset: 0x0008D11C
private void ForeachVertexListSub(Rect uvRect, bool skipTransParentCell, DicingTextures textures, Action<Rect, Rect> function)
{
Texture2D texture = textures.GetTexture(this.AtlasName);
float num = (float)texture.width;
float num2 = (float)texture.height;
List<DicingTextureData.QuadVerts> list = this.GetVerts(textures);
Rect rect = new Rect(uvRect.x * (float)this.Width, uvRect.y * (float)this.Height, uvRect.width * (float)this.Width, uvRect.height * (float)this.Height);
for (int i = 0; i < list.Count; i++)
{
DicingTextureData.QuadVerts quadVerts = list[i];
if (!skipTransParentCell || !quadVerts.isAllTransparent)
{
float x = quadVerts.v.x;
float num3 = quadVerts.v.z;
float y = quadVerts.v.y;
float num4 = quadVerts.v.w;
Rect uvRect2 = quadVerts.uvRect;
if (x <= rect.xMax && y <= rect.yMax && num3 >= rect.x && num4 >= rect.y)
{
if (x < rect.x)
{
uvRect2.xMin += (rect.x - x) / num;
x = rect.x;
}
if (num3 > rect.xMax)
{
uvRect2.xMax += (rect.xMax - num3) / num;
num3 = rect.xMax;
}
if (y < rect.y)
{
uvRect2.yMin += (rect.y - y) / num2;
y = rect.y;
}
if (num4 > rect.yMax)
{
uvRect2.yMax += (rect.yMax - num4) / num2;
num4 = rect.yMax;
}
function(new Rect(x, y, num3 - x, num4 - y), uvRect2);
}
}
}
}
// Token: 0x04001144 RID: 4420
[SerializeField]
private string name = "";
// Token: 0x04001145 RID: 4421
[SerializeField]
private string atlasName = "";
// Token: 0x04001146 RID: 4422
[SerializeField]
private int width;
// Token: 0x04001147 RID: 4423
[SerializeField]
private int height;
// Token: 0x04001148 RID: 4424
[SerializeField]
private List<int> cellIndexList = new List<int>();
// Token: 0x04001149 RID: 4425
[SerializeField]
private int transparentIndex;
// Token: 0x0400114A RID: 4426
[NonSerialized]
private List<DicingTextureData.QuadVerts> verts;
// Token: 0x02000EEF RID: 3823
public class QuadVerts
{
// Token: 0x0400561B RID: 22043
public Vector4 v;
// Token: 0x0400561C RID: 22044
public Rect uvRect;
// Token: 0x0400561D RID: 22045
public bool isAllTransparent;
}
}
}
。
mono打包的Unity游戏,存在该DLL:Game_Data\Managed\Utage.dll的,下面可以还原。
import os
import json
from PIL import Image
import UnityPy
import math
def scan_directory(dir_path, required_fields):
valid_jsons = []
for root, _, files in os.walk(dir_path):
for file in files:
if file.lower().endswith('.json'):
json_path = os.path.join(root, file)
try:
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
if all(field in data for field in required_fields):
valid_jsons.append(json_path)
except Exception:
continue
return valid_jsons
def import_unity_resources(path):
import_results = []
if os.path.isfile(path):
files = [path]
elif os.path.isdir(path):
files = []
for root, dirs, filenames in os.walk(path):
for filename in filenames:
files.append(os.path.join(root, filename))
else:
print(f"路径 {path} 不存在。")
return import_results
for file in files:
try:
env = UnityPy.load(file)
import_results.append((file, True))
except Exception as e:
import_results.append((file, False, str(e)))
return import_results
def export_texture_by_path_id(env, path_id, export_dir):
for obj in env.objects:
if obj.path_id == path_id and obj.type.name == "Texture2D":
data = obj.read()
if "Atlas" in data.m_Name:
path = os.path.join(export_dir, f"{data.m_Name}.png")
try:
data.image.save(path)
print(f"成功导出 Texture2D 资源到 {path}")
return path # 返回保存的文件路径
except Exception as e:
print(f"导出 Texture2D 资源时出错: {e}")
return None
print(f"未找到 path_id 为 {path_id} 且文件名包含 'Atlas' 的 Texture2D 资源。")
return None
def reconstruct_from_json(json_path, atlas_dir, output_dir):
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
cell_size = data['cellSize']
padding = data['padding']
texture_list = data.get('textureDataList', [])
atlas_images = {}
for td in texture_list:
atlas_name = td['atlasName']
if atlas_name not in atlas_images:
img_path = os.path.join(atlas_dir, f"{atlas_name}.png")
if not os.path.isfile(img_path):
raise FileNotFoundError(f"Atlas image not found: {img_path}")
atlas_images[atlas_name] = Image.open(img_path).convert("RGBA")
os.makedirs(output_dir, exist_ok=True)
for td in texture_list:
name = td['name']
atlas_name = td['atlasName']
width, height = td['width'], td['height']
indices = td['cellIndexList']
transparent_index = td['transparentIndex']
atlas_img = atlas_images[atlas_name]
atlas_w, atlas_h = atlas_img.size
content_size = cell_size - 2 * padding
cols = math.ceil(width / content_size)
rows = math.ceil(height / content_size)
cells_per_row = atlas_w // cell_size
result = Image.new('RGBA', (width, height), (0, 0, 0, 0))
patches = []
positions = []
idx = 0
for row in range(rows):
for col in range(cols):
ci = indices[idx]
idx += 1
if ci == transparent_index:
continue
cell_col = ci % cells_per_row
cell_row = ci // cells_per_row
crop_w = min(content_size, width - col * content_size)
crop_h = min(content_size, height - row * content_size)
x0 = cell_col * cell_size + padding
y0_unity = cell_row * cell_size + padding
y0 = atlas_h - y0_unity - crop_h
box = (x0, y0, x0 + crop_w, y0 + crop_h)
patch = atlas_img.crop(box)
dst_x = col * content_size
dst_y = height - (row * content_size + crop_h)
patches.append(patch)
positions.append((dst_x, dst_y))
for patch, pos in zip(patches, positions):
result.paste(patch, pos, patch)
base_name = f"{atlas_name}_{name}.png"
out_path = os.path.join(output_dir, base_name)
number = 1
while os.path.exists(out_path):
out_path = os.path.join(output_dir, f"{os.path.splitext(base_name)[0]}_{number}.png")
number += 1
result.save(out_path)
print(f"Saved: {out_path}")
def main():
json_directory = input("请输入要扫描的 JSON 文件目录路径: ").strip('"\'')
unity_resources_directory = input("请输入要扫描的 Unity 资源文件目录路径: ").strip('"\'')
output_directory = input("请输入输出目录路径: ").strip('"\'')
required_fields = ['cellSize', 'padding', 'textureDataList', 'atlasTextures']
if not os.path.isdir(json_directory):
print(f"错误: 目录 '{json_directory}' 不存在")
return
if not os.path.isdir(unity_resources_directory):
print(f"错误: 目录 '{unity_resources_directory}' 不存在")
return
os.makedirs(output_directory, exist_ok=True)
valid_jsons = scan_directory(json_directory, required_fields)
if valid_jsons:
print(f"找到 {len(valid_jsons)} 个包含所有必要字段的 JSON 文件:")
for sum_num, json_path in enumerate(valid_jsons):
print(f"- {json_path}")
json_output_dir = os.path.join(output_directory, f"{sum_num}")
os.makedirs(json_output_dir, exist_ok=True)
with open(json_path, 'r', encoding='utf-8') as f:
data = json.load(f)
path_id = data['atlasTextures'][0]['m_PathID']
import_results = import_unity_resources(unity_resources_directory)
exported_files = []
for result in import_results:
if result[1]:
file = result[0]
env = UnityPy.load(file)
exported_file = export_texture_by_path_id(env, path_id, json_output_dir)
if exported_file:
exported_files.append(exported_file)
reconstruct_from_json(json_path, json_output_dir, json_output_dir)
for file in exported_files:
if os.path.exists(file):
os.remove(file)
print(f"已删除临时文件: {file}")
else:
print(f"未找到包含所有必要字段 ({', '.join(required_fields)}) 的 JSON 文件")
if __name__ == "__main__":
main()
先导出所有的Monobehavior类(第一次导出会选两次文件夹,其中有一次是选Game_Data\Managed文件夹,如果是ilcpp打包则需要il2cppdumper dump出dll),
如果不选择对dll的话导出来全是1kb的没用的json。
然后打开py,
输入剪切png的特征字符,留空则不选择。
输入导出的Monobehavior类文件夹地址,
输入unity资源文件的文件夹地址,
输入输出文件夹即可自动开始,
就是有点慢,而且因为加载了unity的资源文件会占内存(但是搜索导出的png的话会有重名的就不好检索了)。
live2d用缝合版就可以导出。