Commit 7f1ff623 authored by Canvas123's avatar Canvas123
Browse files

Initial commit

parents
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
*.dll
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
.static_storage/
.media/
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
# JetBrains IDE
.idea/
.idea_modules/
*.iws
# Test Builds
test_builds/
from .rsc_records import *
\ No newline at end of file
from construct import *
from Krampus.byond_crypt import decrypt, encrypt, checksum, checksum_key
from Krampus.dm_common import ResourceType
from io import SEEK_END
import os
import logging
class EncryptedChecksumAdapter(Adapter):
def _encode(self, obj, context):
if context.encrypted:
return encrypt(checksum(context.content), checksum_key)
return checksum(context.content)
def _decode(self, obj, context):
if context.encrypted:
return decrypt(obj, checksum_key)
return obj
class EncryptedContentAdapter(Adapter):
def _encode(self, obj, context):
if context.encrypted:
return encrypt(obj, decrypt(context.checksum, checksum_key))
return obj
def _decode(self, obj, context):
if context.encrypted:
return decrypt(obj, context.checksum)
return obj
rsc_block_header = Struct(
'size' / Int32ul,
'used' / Byte
)
rsc_block_file = Struct(
'type' / ResourceType,
'checksum' / EncryptedChecksumAdapter(Bytes(4)),
'added' / Int32ul,
'modified' / Int32ul,
'_size' / Rebuild(Int32ul, len_(this.content)),
'path' / CString(encoding='utf8'),
'content' / EncryptedContentAdapter(Bytes(this._size))
)
def extract(rsc_name, debuginfo=False):
f = open(rsc_name, 'rb')
files = []
files_by_checksum = {}
rsc_size = file_size(f)
while f.tell() < rsc_size:
block_header = rsc_block_header.parse(f.read(rsc_block_header.sizeof()))
block = f.read(block_header.size)
if block_header.used != 1:
logging.debug(f'HEADER USED {block_header.used}, {block_header.size} at {f.tell() - rsc_block_header.sizeof()}')
continue
else:
try:
file = rsc_block_file.parse(block)
except:
raise Exception("Wrong .rsc content")
# print(file.type, file.encrypted, file.path[-3:], file.content[:5])
if file.checksum in files_by_checksum:
# A bug/feature of BYOND RSC: if more than one file has the same checksum,
# only the contents of the first one are saved. The rest are just \x00 all over the place.
file.content = files_by_checksum[file.checksum].content
files_by_checksum[file.checksum] = file
files.append(file)
return files
def unpack(rsc_name):
files = extract(rsc_name)
folder_name = os.path.basename(os.path.splitext(rsc_name)[0])
for file in files:
if not file.path:
continue
if ':\\' in file.path:
file.path = file.path.split('\\')[-1]
file_name = folder_name + '/' + file.path
os.makedirs(os.path.dirname(file_name), exist_ok=True)
try:
with open(file_name, 'wb') as f:
f.write(file.content)
except Exception as e:
logging.error(f'{e}, {file.path}')
def file_size(file):
oldpos = file.tell()
file.seek(0, SEEK_END)
size = file.tell()
file.seek(oldpos)
return size
def bytestohex(btes):
return ' '.join('{:02x}'.format(c) for c in btes)
if __name__ == '__main__':
unpack('test_rsc.RSC')
from .implementations import encrypt, decrypt, checksum, get_next_key, lib_used
from .crypt_const import *
# Magic values used in BYOND cryptography.
checksum_key = b'\xA6\x0B\xDD\x45' # Static key used to encrypt checksum in RSC files.
import struct
from ctypes import *
from platform import system
if system() == "Windows":
dtype = "dll"
else:
dtype = "so"
if sizeof(c_voidp) == 4:
# 32-bit Python used
lib = CDLL(f'./rsc.{dtype}')
else:
# 64-bit Python used
lib = CDLL(f'./rsc_x64.{dtype}')
lib.checksum.argtypes = (c_char_p, c_size_t)
lib.checksum.restype = c_char * 4
lib.get_next_key.argtypes = (c_uint32, c_uint8)
lib.get_next_key.restype = c_uint32
get_next_key = lib.get_next_key
lib.encrypt.argtypes = (c_char_p, c_uint32, c_size_t)
lib.encrypt.restype = c_void_p
lib.decrypt.argtypes = (c_char_p, c_uint32, c_size_t)
lib.decrypt.restype = c_void_p
def checksum(data):
crypt_data = (c_char * len(data)).from_buffer_copy(data)
s = lib.checksum(crypt_data, len(data))
return bytes(s)
def encrypt(data, bytes_key):
key = struct.unpack('=L', bytes_key)[0]
crypt_data = (c_char * len(data)).from_buffer_copy(data)
lib.encrypt(crypt_data, key, len(data))
return bytes(crypt_data)
def decrypt(data, bytes_key):
key = struct.unpack('=L', bytes_key)[0]
crypt_data = (c_char * len(data)).from_buffer_copy(data)
lib.decrypt(crypt_data, key, len(data))
return bytes(crypt_data)
import struct
# Pre-generated ByondCRC lookup table
table = [
0, 175, 350, 497, 700, 531, 994, 845,
1400, 1495, 1062, 1161, 1988, 1899, 1690, 1589,
2800, 2655, 2990, 2817, 2124, 2275, 2322, 2493,
3976, 3879, 3798, 3705, 3380, 3483, 3178, 3269,
5600, 5455, 5310, 5137, 5980, 6131, 5634, 5805,
4248, 4151, 4550, 4457, 4644, 4747, 4986, 5077,
7952, 8127, 7758, 7905, 7596, 7427, 7410, 7261,
6760, 6855, 6966, 7065, 6356, 6267, 6538, 6437,
11200, 11119, 10910, 10801, 10620, 10707, 10274, 10381,
11960, 11799, 12262, 12105, 11268, 11435, 11610, 11765,
8496, 8607, 8302, 8385, 9100, 8995, 8914, 8829,
9288, 9447, 9494, 9657, 9972, 9819, 10154, 9989,
15904, 16015, 16254, 16337, 15516, 15411, 15810, 15725,
15192, 15351, 14854, 15017, 14820, 14667, 14522, 14357,
13520, 13439, 13710, 13601, 13932, 14019, 14130, 14237,
12712, 12551, 12534, 12377, 13076, 13243, 12874, 13029,
22400, 22319, 22238, 22129, 21820, 21907, 21602, 21709,
21240, 21079, 21414, 21257, 20548, 20715, 20762, 20917,
23920, 24031, 23598, 23681, 24524, 24419, 24210, 24125,
22536, 22695, 22870, 23033, 23220, 23067, 23530, 23365,
16992, 17103, 17214, 17297, 16604, 16499, 16770, 16685,
18200, 18359, 17990, 18153, 17828, 17675, 17658, 17493,
18576, 18495, 18894, 18785, 18988, 19075, 19314, 19421,
19944, 19783, 19638, 19481, 20308, 20475, 19978, 20133,
31808, 31983, 32030, 32177, 32508, 32339, 32674, 32525,
31032, 31127, 30822, 30921, 31620, 31531, 31450, 31349,
30384, 30239, 30702, 30529, 29708, 29859, 30034, 30205,
29640, 29543, 29334, 29241, 29044, 29147, 28714, 28805,
27040, 26895, 26878, 26705, 27420, 27571, 27202, 27373,
27864, 27767, 28038, 27945, 28260, 28363, 28474, 28565,
25424, 25599, 25102, 25249, 25068, 24899, 24754, 24605,
26152, 26247, 26486, 26585, 25748, 25659, 26058, 25957
]
mod_fix = 0xFFFFFFFF
# ByondCRC - CRC32-like checksum used by BYOND
def checksum(data):
s = 0xFFFFFFFF
for byte in data:
s = table[byte ^ (s >> 24)] ^ ((s << 8) & mod_fix)
return struct.pack('=L', s)
def get_next_key(current_key, current_byte):
a = current_byte + current_key
a = ((a << 12) + a + 0x7ED55D16) & mod_fix
a = ((a >> 19) ^ a ^ 0xC761C23C)
a = ((a << 5) + a + 0x165667B1) & mod_fix
a = ((a << 9) ^ (a - 0x2C5D9B94))
a = ((a << 3) + a - 0x28FB93B) & mod_fix
a = ((a >> 16) ^ a ^ 0xB55A4F09)
return a
def encrypt(data, bytes_key):
key = struct.unpack('=L', bytes_key)[0]
data = bytearray(data)
for i in range(len(data)):
data[i] ^= key & 255
key = get_next_key(key, data[i])
return bytes(data)
def decrypt(data, bytes_key):
key = struct.unpack('=L', bytes_key)[0]
data = bytearray(data)
for i in range(len(data)):
old_byte = data[i]
data[i] ^= key & 255
key = get_next_key(key, old_byte)
return bytes(data)
import logging
try:
from .implementation_c import encrypt, decrypt, checksum, get_next_key
lib_used = True
except OSError as e:
logging.basicConfig(level=logging.INFO)
logging.info(e)
from .implementation_python import encrypt, decrypt, checksum, get_next_key
lib_used = False
from construct import *
class MapListAdapter(Adapter):
def __init__(self, subcon, translate_list):
self.translate_list = translate_list
super().__init__(subcon)
def _encode(self, obj, context):
return self.translate_list.index(obj)
def _decode(self, obj, context):
return self.translate_list[obj]
class ListBitflagAdapter(Adapter):
def __init__(self, subcon, flags):
self.flags = flags
super().__init__(subcon)
def _encode(self, obj, context):
if not obj:
return 0
return_int = 0
for i in range(len(self.flags)):
if self.flags[i] in obj:
return_int |= (2 ** i)
return return_int
def _decode(self, obj, context):
if not obj:
return []
return_flags = []
for i in range(len(self.flags)):
if obj & (2 ** i):
return_flags.append(self.flags[i])
return return_flags
IDSize = 4
IDSizeInt = Int32ul
IDSizeBytes = Padded(IDSize, Bytes(IDSize))
directions_dm = dict(
NORTH=1,
SOUTH=2,
EAST=4,
WEST=8,
NORTHEAST=5,
NORTHWEST=9,
SOUTHEAST=6,
SOUTHWEST=10,
UP=16,
DOWN=32
)
DirectionByte = Enum(Byte, default=Pass, **directions_dm)
resource_types = dict(
UNK=0x0,
MID=0x1, # ".mid|.midi|.mod|.s3m|.xm|.it|.oxm"
WAV=0x2, # ".wav|.ogg|.raw|.wma|.aiff"
DMI=0x3, # ".dmi"
BMP=0x5, # ".bmp"
PNG=0x6, # ".png"
ZIP=0x9, # ".zip"
RSC=0xA, # ".rsc"
JPG=0xB, # ".jpg|.jpeg"
DDMI=0xC, # ".ddmi"
GIF=0xD, # ".gif"
TTF=0xE, # ".ttf"
)
resource_filetypes = dict(
MID=('mid', 'midi', 'mod', 's3m', 'xm', 'it', 'oxm'),
WAV=('wav', 'ogg', 'raw', 'wma', 'aiff'),
DMI=('dmi',),
BMP=('bmp',),
PNG=('png',),
ZIP=('zip',),
RSC=('rsc',),
JPG=('jpg', 'jpeg'),
DDMI=('ddmi',),
GIF=('gif',),
TTF=('ttf',)
)
ResourceType = EmbeddedBitStruct(
'encrypted' / Default(Flag, False),
'resource_type' / Enum(BitsInteger(7), default=Pass, **resource_types)
)
data_types = dict(
NULL=0x00, # NULL - 0 (0xFFFF in ID fields is also considered NULL, except for FLOAT)
TURF=0x01, # turf - Turf Instance ID
OBJ=0x02, # obj - Object Instance ID
MOB=0x03, # mob - Mob Instance ID
AREA=0x04, # area - Area Instance ID
CLIENT=0x05, # client - Client Instance ID
STRING=0x06, # string - String ID
PATH_MOB=0x08, # mob typepath - Mob ID
PATH_OBJ=0x09, # obj typepath - Prototype ID
PATH_TURF=0x0A, # turf typepath - Prototype ID
PATH_AREA=0x0B, # area typepath - Prototype ID
RESOURCE=0x0C, # resource - Resource ID
IMAGE=0x0D, # image - Image Instance ID
WORLD=0x0E, # world - ID unused
LIST=0x0F, # list - List ID
LIST_ARGS=0x10, # magic list
LIST_VERBS=0x12, # magic list
PATH_DATUM=0x20, # datum typepath - Prototype ID
DATUM=0x21, # datum - Datum Instance ID
SAVE=0x23, # savefile - Savefile Instance ID
PATH_SAVE=0x24, # /savefile typepath - 0 (always /savefile)
PATH_PROC=0x26, # proc typepath - Procedure ID
PATH_FILE = 0x27, # /file typepath - 0 (always /file)
PATH_LIST=0x28, # /list typepath - 0 (always /list)
MPROT=0x29, # modified prototype - Modified Prototype ID
FLOAT=0x2A, # float - 32-bit SP float
# NOTE: In data blocks, floats are stored as 2 segments, each containing one 16-bit half.
# In inverse order. Even in 32-bit DMBs. My god.
LIST_VARS=0x31, # Unconfirmed.
APPEARANCE=0x3A,
PATH_CLIENT=0x3B, # /client typepath - 0 (always /client)
CREATE_LIST=0x3E, # created list - List ID (useless? lists are filled by init procs)
PATH_IMAGE=0x3F, # /image typepath - 0 (always /image)
)
DataTypeByte = Enum(Byte, **data_types)
DataTypeIDSize = Enum(IDSizeInt, **data_types)
class IDAdapter(Adapter):
def _encode(self, obj, context):
if obj is None:
return 0xFFFF
return obj
def _decode(self, obj, context):
if obj == 0xFFFF:
return None
return obj
ID = IDAdapter(IDSizeInt)
def set_id_size(size):
global IDSizeInt, ID
IDSizeInt = size
ID = IDAdapter(IDSizeInt)
# Interface binding from Manhattan to Krampus and vice versa
# Is used to provide all Krampus functionality to Manhattan
# Manhattan is made by Canvas123 and ACCount purely on PyQt5
# Any questions about Manhattan send on email: canvas123@protonmail.com
# Licensed under AGPLv3
from PyQt5.QtWidgets import QListWidgetItem, QListWidget, QMessageBox
from PyQt5.QtCore import Qt
from Krampus.RSC import *
def showResources(path, list_widget: QListWidget):
def convert_(num): # this function will convert bytes to MB.... GB... etc
for x in ['bytes', 'KB', 'MB', 'GB', 'TB']:
if num < 1024.0:
return "%3.1f %s" % (num, x)
num /= 1024.0
return num
try:
files = extract(path)
except Exception as e:
error = QMessageBox()
error.critical(None, 'Error', e.__str__())
return
list_widget.clear() # Remove items from widget
for file in files:
item = QListWidgetItem(file.path)
item.setData(Qt.UserRole, file.content)
item.setData(Qt.ToolTipRole, f'Size: {convert_(file._size)}, Checksum: {file.checksum.hex()}')
list_widget.addItem(item)
list_widget.sortItems()
from main_window import *
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
#MainWindow = QtWidgets.QWidget()
ui = KrampusQt()
ui.setupUi()
ui.show()
sys.exit(app.exec_())
# Manhattan is made by Canvas123 and ACCount purely on PyQt5
# Any questions about Manhattan send on email: canvas123@protonmail.com
# Licensed under AGPLv3