Edit Texture2D make file corrupted
AXiX-official opened this issue · comments
Code
I use example code to edit Texture2D
from PIL import Image
for obj in env.objects:
if obj.type.name == "Texture2D":
# export texture
data = obj.read()
data.image.save(path)
# edit texture
fp = os.path.join(replace_dir, data.name)
pil_img = Image.open(fp)
data.image = pil_img
data.save()
and save like this
with open(dst, "wb") as f:
f.write(env.file.save(packer="original"))
Error
I got
The file 'archive:/CAB-3a309dcafc425e59c83a625cb055748f/CAB-3a309dcafc425e59c83a625cb055748f' is corrupted! Remove it and launch unity again!
[Position out of bounds!]
UnityEngine.AssetBundle:LoadAsset(String)
ResLoaderBundle:GetRes(String, Object)
Seven.UIBase:DynamicLoadRes(String, String, Boolean)
Seven.UISpine:SetData(String)
XLua.CSObjectWrap.SevenUISpineWrap:_m_SetData(IntPtr)
mabye because the game implemented its own dynamic resource loading strategy
Bug
however,using assetstudio to see dump information about Texture2D before and after edit, something broken
before
Texture2D Base
string m_Name = "UI_jiazai"
int m_ForcedFallbackFormat = 4
bool m_DownscaleFallback = False
int m_Width = 2048
int m_Height = 2048
int m_CompleteImageSize = 4194304
int m_TextureFormat = 47
int m_MipCount = 1
bool m_IsReadable = False
bool m_IgnoreMasterTextureLimit = False
bool m_IsPreProcessed = False
bool m_StreamingMipmaps = False
int m_StreamingMipmapsPriority = 0
int m_ImageCount = 1
int m_TextureDimension = 2
GLTextureSettings m_TextureSettings
int m_FilterMode = 1
int m_Aniso = 1
float m_MipBias = 0
int m_WrapU = 1
int m_WrapV = 1
int m_WrapW = 1
int m_LightmapFormat = 6
int m_ColorSpace = 1
TypelessData image data
int size = 0
StreamingInfo m_StreamData
unsigned int offset = 0
unsigned int size = 4194304
string path = "archive:/CAB-d7da4949770a0f0599e3534deda1589a/CAB-d7da4949770a0f0599e3534deda1589a.resS"
int m_OriginalWidth = 0
int m_OriginalHeight = 0
GUID m_OriginalAssetGuid
unsigned int data[0] = 0
unsigned int data[1] = 0
unsigned int data[2] = 0
unsigned int data[3] = 0
after
Texture2D Base
string m_Name = "UI_jiazai"
int m_ForcedFallbackFormat = 4
bool m_DownscaleFallback = False
int m_Width = 2048
int m_Height = 2048
int m_CompleteImageSize = 4194304
int m_TextureFormat = 47
int m_MipCount = 1
bool m_IsReadable = False
bool m_IgnoreMasterTextureLimit = False
bool m_IsPreProcessed = False
bool m_StreamingMipmaps = False
int m_StreamingMipmapsPriority = 0
int m_ImageCount = 1
int m_TextureDimension = 2
GLTextureSettings m_TextureSettings
int m_FilterMode = 1
int m_Aniso = 1
float m_MipBias = 0
int m_WrapU = 1
int m_WrapV = 1
int m_WrapW = 1
int m_LightmapFormat = 6
int m_ColorSpace = 1
TypelessData image data
int size = 4194304
StreamingInfo m_StreamData
unsigned int offset = 0
unsigned int size = 0
string path = ""
int m_OriginalWidth = 0
int m_OriginalHeight = 9
GUID m_OriginalAssetGuid
unsigned int data[0] = 1784629589
unsigned int data[1] = 1635410281
unsigned int data[2] = 105
unsigned int data[3] = 0
i think that's the problem,i tried uabea and got the same result
To Reproduce
- following data:
- Python 3.8
- UnityPy 1.10.7
- UnityVersion 2019.4.40f1c1、2019.4.40f1、2018.4.34f1
I fixed code in Texture2D.py
tring to edit an existed cab and get error.
in Texture2D.py
@image_data.setter
def image_data(self, data: bytes):
self._image_data = data
# ignore writing to cab for now until it's more stable
if self.version >= (5, 3) and self.m_StreamData.path:
path = self.m_StreamData.path
cab = self.assets_file.get_writeable_cab(path.split("/")[-1])
if cab:
self.m_StreamData.offset = cab.Position
cab.write(data)
self.m_StreamData.size = len(data)
self.m_StreamData.path = cab.path
and i got this
Traceback (most recent call last):
File "F:\blhx\lsns-decryptor\main.py", line 19, in <module>
data.image = pil_img
File "F:\blhx\lsns-decryptor\venv\lib\site-packages\UnityPy\classes\Texture2D.py", line 40, in image
self.image_data = img_data
File "F:\blhx\lsns-decryptor\venv\lib\site-packages\UnityPy\classes\Texture2D.py", line 72, in image_data
cab = self.assets_file.get_writeable_cab(path.split("/")[-1])
File "F:\blhx\lsns-decryptor\venv\lib\site-packages\UnityPy\files\SerializedFile.py", line 417, in get_writeable_cab
cab = self.parent.get_writeable_cab(name)
File "F:\blhx\lsns-decryptor\venv\lib\site-packages\UnityPy\files\File.py", line 106, in get_writeable_cab
raise ValueError(
ValueError: This cab already exists and isn't an EndianBinaryWriter
can i remove this cab and add modified cab myself?
Hello. Can you provide the bundle file? I'm just curious, maybe I can help.
ui_jiazai.zip
the origin file uses unityCN encrypted,I made a decrypt version according to the RazTools/Studio
but the problem i met has nothing to do with this,because it's a widely existed problem.the source code said writing to cab for is not stable
.
I tested two other games in version 2019.4.40f1、2018.4.34f1(one is azur lane)and got the same,though in those games this dosent matter.
use UABEA can remove cab record and use unitypy add a new modfied one,but it changed file struct a low makes my encrypt application strike
It's strange but UnityPy thinks the file is encrypted
LookupError: The BundleFile is encrypted, but no key was provided!
haha,it's becauese in that version of decryptor i forgot fix header's flags and blocks' flag so UnityPy thinks the file is encrypted.
however,i said it's not file's problem.editing cab is a not fully supported feature in both unitypy and uabea
ui_jiazai.zip
I'm happy to tell you that I managed to solve this problem.
In fact if you simply remove the cab-****.resS
file via uabea's remove
and then add the cab
using unitypy it does corrupt the file. The problem is that when uabea removes the .resS
file, it does not remove the corresponding reference in the cab
that references it, and when it adds the cab
later using unitypy, it will add additional records to it. (You can verify this yourself by looking at the contents of the modified file).
So my solution to this is, in cases where the .resS
file I need to work with is just Texture2D
data, to somehow convert the modified.png
that I need to replace to the textual2D binary that unity uses, e.g. by using the unitypy
img_data, tex_format = Texture2DConverter.image_to_texture2d(
img, self.m_TextureFormat
)
Then find the corresponding data in the unpacked bundlefile and replace it directly before repacking.
This completely solves the problem I encountered above.
After that I might write a program to do it? Maybe
You can write an embedded texture using typetree changing just a few values (not in resS).
from PIL import Image
for obj in env.objects:
if obj.type.name == "Texture2D":
data = obj.read()
fp = os.path.join(replace_dir, data.name)
pil_img = Image.open(fp)
# UnityPy converts to original format by default
data.set_image(pil_img)
data.save_via_tree()
classes\Texture2D.py
def save_via_tree(self):
tree = self.read_typetree()
if "m_MipMap" in tree:
tree["m_MipMap"] = self.m_MipMap
else:
tree["m_MipCount"] = self.m_MipCount
tree["m_TextureFormat"] = self.m_TextureFormat
tree["m_CompleteImageSize"] = len(self.image_data)
tree["image data"] = self.image_data
# Reset StreamData
tree["m_StreamData"] = {
"offset": 0,
"size": 0,
"path": ""
}
self.save_typetree_dump(tree)
classes\Object.py
def save_typetree_dump(self, tree: dict, nodes: list = None, writer: EndianBinaryWriter = None):
return self.reader.save_typetree(tree, nodes, writer)
Saving
for name, file in env.files.items():
if hasattr(file, "is_changed") and file.is_changed:
file_name = os.path.basename(name)
with open(os.path.join(dest, file_name), "wb") as f:
f.write(file.save(packer="original"))
thanks for your reply
my solution be like this
import os
from PIL import Image
import UnityPy
from UnityPy.export import Texture2DConverter
dir = r"work"
src = r"test.asset"
dst = r"work\test.asset.fix"
# set unityCN key for unityCN
# bundle_key = ""
# UnityPy.set_assetbundle_decrypt_key(bundle_key)
# load file
env = UnityPy.load(os.path.join(dir, src))
# view Texture2D info
# some file has more than one texture2D data store in .resS
textureList = {}
totalSize = 0
for obj in env.objects:
if obj.type.name == "Texture2D":
data = obj.read()
# print(data.name)
# print(f"offset:{data.m_StreamData.offset}")
# print(f"size:{data.m_StreamData.size}")
# print(f"path:{data.m_StreamData.path}")
# print(f"TextureFormat:{data.m_TextureFormat}")
textureList[data.name] = {
"offset": data.m_StreamData.offset,
"size": data.m_StreamData.size,
"path": data.m_StreamData.path,
"TextureFormat": data.m_TextureFormat
}
totalSize += data.m_StreamData.size
# convert data
# toLoadImg should be the same wide/height as toEditTexture
# so that img_data has the same size as data.m_StreamData.size
toEditTextureName = "example"
toLoadImg = os.path.join(dir, "example.fix.png")
img = Image.open(toLoadImg)
toEditTexture = textureList[toEditTextureName]
img_data, tex_format = Texture2DConverter.image_to_texture2d(
img, toEditTexture["TextureFormat"]
)
# replace data with different size is possible
# when needed i will add support
if len(img_data) != toEditTexture["size"]:
raise ValueError(f"data size missmatch,except {toEditTexture['size']} but recieved {len(img_data)}")
# save file with no compression
newFileData = bytearray(env.file.save(packer="none"))
size = len(newFileData)
begin = size - totalSize + toEditTexture["offset"]
newFileData[begin:begin + toEditTexture["size"]] = img_data
with open(dst, 'wb') as f:
f.write(newFileData)
env = UnityPy.load(dst)
with open(dst, 'wb') as f:
f.write(env.file.save(packer="lz4"))