Last active
April 19, 2024 13:04
-
-
Save dnaroma/bf862a99126e6ef4312a57dd908fb37d to your computer and use it in GitHub Desktop.
Extract AppHash from unity assets
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from io import RawIOBase | |
from struct import * | |
from typing import Callable | |
def offset_decorate(func: Callable) -> Callable: | |
def func_wrapper(*args, **kwargs) -> Callable: | |
offset = kwargs.get('offset') | |
if offset is not None: | |
back = args[0].base_stream.tell() | |
args[0].base_stream.seek(offset) | |
d = func(*args) | |
args[0].base_stream.seek(back) | |
return d | |
return func(*args, **kwargs) | |
return func_wrapper | |
class BinaryStream: | |
def __init__(self, base_stream: RawIOBase, endian='little'): | |
self.base_stream = base_stream | |
self.endian = endian | |
def readByte(self) -> bytes: | |
return self.base_stream.read(1) | |
@offset_decorate | |
def readBytes(self, length: int) -> bytes: | |
return self.base_stream.read(length) | |
def readChar(self) -> int: | |
return self.unpack('b') | |
def readUChar(self) -> int: | |
return self.unpack('B') | |
def readBool(self) -> bool: | |
return self.unpack('?') | |
def readInt16(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>h', 2) | |
return self.unpack('h', 2) | |
def readUInt16(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>H', 2) | |
return self.unpack('H', 2) | |
def readInt32(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>i', 4) | |
return self.unpack('i', 4) | |
def readUInt32(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>I', 4) | |
return self.unpack('I', 4) | |
def readInt64(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>q', 8) | |
return self.unpack('q', 8) | |
def readUInt64(self) -> int: | |
if (self.endian == 'big'): | |
return self.unpack('>Q', 8) | |
return self.unpack('Q', 8) | |
def readFloat(self) -> float: | |
return self.unpack('f', 4) | |
def readDouble(self) -> float: | |
return self.unpack('d', 8) | |
def readString(self) -> bytes: | |
length = self.readUInt16() | |
return self.unpack(str(length) + 's', length) | |
@offset_decorate | |
def readStringLength(self, length: int) -> bytes: | |
return self.unpack(str(length) + 's', length) | |
@offset_decorate | |
def readStringToNull(self) -> bytes: | |
byte_str = b'' | |
while 1: | |
b = self.readByte() | |
if (b == b'\x00'): | |
break | |
byte_str += b | |
return byte_str | |
def AlignStream(self, alignment=4): | |
pos = self.base_stream.tell() | |
# print('currPos is: ' + str(pos), pos % alignment) | |
if ((pos % alignment) != 0): | |
self.base_stream.seek(alignment - (pos % alignment), 1) | |
# print('aligned currPos is: ' + str(self.base_stream.tell())) | |
def writeBytes(self, value: bytes): | |
self.base_stream.write(value) | |
def writeChar(self, value: str): | |
self.pack('c', value) | |
def writeUChar(self, value: str): | |
self.pack('C', value) | |
def writeBool(self, value: bool): | |
self.pack('?', value) | |
def writeInt16(self, value: int): | |
self.pack('h', value) | |
def writeUInt16(self, value: int): | |
self.pack('H', value) | |
def writeInt32(self, value: int): | |
self.pack('i', value) | |
def writeUInt32(self, value: int): | |
self.pack('I', value) | |
def writeInt64(self, value: int): | |
self.pack('q', value) | |
def writeUInt64(self, value: int): | |
self.pack('Q', value) | |
def writeFloat(self, value: float): | |
self.pack('f', value) | |
def writeDouble(self, value: float): | |
self.pack('d', value) | |
def writeString(self, value: str): | |
length = len(value) | |
self.writeUInt16(length) | |
self.pack(str(length) + 's', value) | |
def pack(self, fmt: str, data) -> bytes: | |
return self.writeBytes(pack(fmt, data)) | |
def unpack(self, fmt: str, length=1) -> tuple: | |
return unpack(fmt, self.readBytes(length))[0] | |
def unpack_raw(self, fmt: str) -> tuple: | |
length = Struct(fmt).size | |
return unpack(fmt, self.readBytes(length)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import os | |
import sys | |
from io import BytesIO | |
import UnityPy | |
from binary import BinaryStream | |
def readAppHash(path_to_apk: str): | |
env = UnityPy.load(os.path.join(os.getcwd(), path_to_apk)) | |
strings = [] | |
names = [ | |
'memo', 'clientMajorVersion', 'clientMinorVersion', | |
'clientBuildVersion', 'snapshot', 'clientVersionSuffix', | |
'clientDataMajorVersion', 'clientDataMinorVersion', | |
'clientDataBuildVersion', 'clientDataRevision', 'companyName', | |
'productName', 'bundleIdentifier', 'bundleVersion', 'assetHash', | |
'clientAppHash', 'bundleVersionCode' | |
] | |
for obj in env.objects: | |
if obj.type.name == 'ResourceManager': | |
readObj = obj.read() | |
prodObj = readObj.m_Container[ | |
"playersettings/android/production_android"].get_obj().read() | |
bs = BinaryStream(BytesIO(prodObj.raw_data)) | |
while bs.base_stream.tell() < len(prodObj.raw_data) - 4: | |
strLen = bs.readUInt32() | |
strings.append(bs.readStringLength(strLen).decode('utf-8')) | |
bs.AlignStream() | |
app_hash_dict = dict(zip(names, strings)) | |
return app_hash_dict | |
if __name__ == "__main__": | |
if len(sys.argv) > 1: | |
path_to_apk = sys.argv[1] | |
print(readAppHash(path_to_apk)) | |
else: | |
print("Please provide the path to game apk as an argument.") |
erikchan002
commented
Apr 16, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment