记一次Unity资源解包
免责声明 本文仅用于技术研究与学习交流目的,不鼓励、不支持任何对游戏内容的非法提取、篡改、传播或商业利用。所有操作均基于开源工具对本地已购游戏文件进行的逆向分析,未涉及服务器端数据、未破解DRM、未分发任何游戏资源。 文中所展示的截图与代码片段,仅为说明分析过程,不包含完整资源或可执行内容。 游戏版权归其原开发商与发行商所有。本博客无意侵犯任何知识产权,若权利方认为内容存在不当之处,请在评论区留言,我将第一时间配合处理。 技术无罪,善用为本。请读者遵守当地法律法规及游戏用户协议,切勿将本文内容用于非法用途。 使用工具 AssetStudio : 提取资源 Dnspy : 寻找代码中的加密逻辑 在线Base64解码 : Base64解码 背景 某游戏汉化堪忧,于是尝试自己获取游戏的英文文本,用大语言模型翻译,也是作为萌新解包的一个记录 提取资源 这一步比较简单,打开AssetStudio软件,选择File->Load folder,无视关于版本的报错,待加载完成后直接Export->All assets到一个目录即可。 在途中可以看看感兴趣的内容的浏览。 例如这里能看到是一串肉眼看不出有什么意义的代码,甚至base64解码后也无意义。观察类似文件的base64解码,似乎都带有82 84 74 42 6E 29 3A 0F BD 28 78 28 E4 37 21 3D作为最开始的字节,但并没有现成的文件类型。 那么是否就是资源提取有问题呢?在这里考虑到Unity和C#关系紧密,可以使用dnspy碰碰运气 反编译 首先找找游戏目录下有没有"Assembly-CSharp.dll"的文件,用dnspy打开(这是找的网上的) 依次展开,先随便看看。不过找了找似乎没有直接看到什么感兴趣的东西,那么就去找找类型引用 在类型引用里,我们可以看到LocalisedString这个类型,应该是和语言本地化有关的,双击过去看看。 可以看到很多方法的实现都依赖Language,Ctrl+左键点过去看看 从Language()一路LoadLanguage()到SwitchLanguage()再到DoSwitch,最后我们找到了GetLanguageFileContents,可以看到其最后调用了Encryption.Decrypt(),那么接下来就是去寻找解密函数,然后自己实现就可以了。 让AI帮忙用py写了脚本,测试确实输出了英文文本。 接下来就可以自己尝试翻译了。翻译完成后可以用UABE进行再次封包了
Godot随机prim算法生成方格迷宫
参考 学校永远不会告诉你的迷宫秘密 这里有各种迷宫的生成各种类型的迷宫在线免费生成,支持迷宫查找路径 迷宫表示 直观来看,迷宫总是能一眼看到密密麻麻的墙壁,但是如果反转思路来看,可行的迷宫是自起点通往终点的路径,不考虑迷宫的回路和无法到达的死路,那么迷宫的路径实际上可以看作图论里的树 既然路径是一棵树,那么难免就想到最小生成树算法。当然,此处没有权值,也无需考虑最小,随便生成一棵树即可。在本文里使用prim算法。 随机prim算法 与原版的prim算法不同,随机prim算法每次选择新的顶点不需要根据距离,随便取一个顶点就可以了。 首先将所有格子设置一个false的状态,然后可以随机取一个格子作为起点,并修改其状态为true。需要维护一个数组,里面存后面要进行操作的格子,将起点加入这个数组。 当这个数组里还有未处理的格子时,进行如下操作: 随便取出一个格子a,逐个访问它相邻的格子b,如果它相邻的格子b的状态为false,说明当前没有能访问b的路径,将b加入数组,并修改状态为true;如果为true,那么说明之前已经有路径抵达b,那么a和b之间可以添加墙壁。 当数组为空时,迷宫生成完成。 Godot实现 准备工作 设置一个Node2D根节点,添加TileMapLayer和Camera2D节点,根据我的上一篇博客完成相机的设置 要在TileMapLayer里显示迷宫,需要设置TileSet图集,在这里简单手绘了一张,可以放进去使用。 另外,Godot没有C/C++那种二维数组,在这里手写一个初始化函数得到一个类似的二维数组 1 2 3 4 5 6 7 func create_2d_array(x: int, y: int, default_val): var _array_ = [] for i in range(x): _array_.append([]) for j in range(y): _array_[i].append(default_val) return _array_ 由于需要随机取点,需要先进行初始化或者是种子设置,可以在_ready()函数里添加randomize(),或是手动设置种子seed(998244353) 迷宫生成 接下来其实就算简单了,如果之前接触过最小生成树的话,可以略过这一部分了。 首先是一些变量的定义,包括地图大小和四个方位以及格子的状态, 1 2 3 4 5 6 7 8 var SIZEH = 15 var SIZEW = 15 var directions = [Vector2i(0, -1), Vector2i(0, 1), Vector2i(-1, 0), Vector2i(1, 0)] # 上下左右 func generate_maze(size: Vector2i, start: Vector2i): # 以start为起点,生成迷宫 var map = create_2d_array(size.x, size.y, false) var grids: Array[Vector4i] = [] # 保存哪两格之间的墙 var ans_walls = {} # 保存最后生成的墙 用lambda表达式设置了一个函数用于把后续需要操作的格子放进去,避免后面的格子把之前的格子再判断一次,把之前的格子也复制了一遍,其实也可以全局变量维护下状态,当时懒得改了。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var add_wall = func(current: Vector2i, before = null): for next_grid: Vector2i in directions: var another_grid: Vector2i = current + next_grid if another_grid.x < 0 or another_grid.y < 0 or another_grid.x >= SIZEW or another_grid.y >= SIZEH: continue if before != null and another_grid == before: # 避免加入之前被破掉的墙 continue var grid: Vector4i grid.x = current.x grid.y = current.y grid.z = another_grid.x grid.w = another_grid.y grids.append(grid) 接下来就是把起点放入维护的数组,一遍遍进行操作了。 ...
Godot实现Camera2D的移动放缩
目标 使用WASD平移 使用鼠标中键平移 使用鼠标滚轮按照鼠标位置放缩 平滑动画 准备工作 窗口尺寸设置为 1920 * 1080 ,加入一些输入映射,例如WASD对应"ui_xx",设置空格或者你喜欢的按键作为"ui_recovery" 新建Node2d、Sprite2D和Carmea2d 取消Sprite2D的居中,并把Carmea2d的锚点模式设置为Fixed Top Left 导入一张图片作为Sprite2D的纹理,我这里使用Azgaar的奇幻地图生成器随机生成了一张 运行图片左上角对齐屏幕左上角即可。 给创建的Node2d节点添加GDScript脚本,写入以下代码 1 2 3 4 5 6 7 8 9 10 11 12 13 @onready var camera = $Camera2D @onready var sprite = $Sprite2D var CamScale = Vector2(1.0, 1.0) var CamPos = Vector2(0.0, 0.0) func _ready() -> void: pass func _input(event: InputEvent) -> void: pass func _process(delta: float) -> void: pass 其中CamScale保存变换后的缩放倍率,CamPos保存变换后的锚点位置 缩放 按照鼠标位置缩放 设置的相机锚点为左上角,即(0,0),那么直接对相机的zoom参数改变会以(0,0)为中心缩放。那么如果要实现按照鼠标为中心缩放则需要在缩放的同时进行平移。那么如何进行平移呢? 代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 var scale_delta = 0.05 var mouse_position func _input(event: InputEvent) -> void: mouse_position = get_viewport().get_mouse_position() if event is InputEventMouseButton: var BeforeScale = CamScale #滚轮缩放 if event.button_index == MOUSE_BUTTON_WHEEL_UP: CamScale.x = exp(log(CamScale.x) + scale_delta) CamScale.y = exp(log(CamScale.y) + scale_delta) if event.button_index == MOUSE_BUTTON_WHEEL_DOWN: CamScale.x = exp(log(CamScale.x) - scale_delta) CamScale.y = exp(log(CamScale.y) - scale_delta) var pscale = CamScale / BeforeScale if pscale != Vector2(1, 1): #按照鼠标位置缩放 var delta_mouse_posion = (pscale * (position + mouse_position) - mouse_position) / CamScale CamPos += delta_mouse_posion camera.zoom = CamScale 平移 平移操作分为WASD平移和鼠标中键平移,两者逻辑稍有不同。 我们需要设置变量来保存平移一次的距离,在脚本中加入如下代码 ...

Markdown 样例
原文为Markdown Cheatsheet Demo,结合本主题进行了改动。 基础格式 这是一个普通的段落。这段文字是加粗的。 但这部分不是!斜体字看起来也不错? 那我们来 组合一下。没错就是这样!也可以高亮代码,比如 MyFunction()。好人总会加上超链接,所以 点这里 或者访问 http://www.example.com。 标题 H1 到 H6 H1 标题 H2 标题 H3 标题 H4 标题 H5 标题 H6 标题 脚注 有时候你想给一段话加上脚注。这是第一个脚注的例子 [1]。你甚至还可以添加带链接的脚注 [2]! 引用 前途是光明的,道路是曲折的。 注意: 本主题不支持嵌套引用块。 列表 第一个有序列表项 第二个有序列表项 无序列表使用星号 或者减号 或者加号 代码块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var 模块模式 = (function() { var sum = 0; return { add: function() { sum += 1; return sum; }, reset: function() { return sum = 0; } }; }()); alert(模块模式.add()); // 弹出: 1 alert(模块模式.add()); // 弹出: 2 alert(模块模式.reset()); // 弹出: 0 1 2 s = "Python 语法高亮" print(s) 没有指定语言,因此不会高亮。 不过我们可以加入一些 <b>HTML标签</b>。 $$ E = mc^2 $$ ...