Как сериализовать список обратно в исходную двоичную базу данных?

#python #database #serialization #binary

#python #База данных #сериализация #двоичный

Вопрос:

Я пытаюсь создать правильный сериализатор для двоичных scores.db файлов и collections.db для игры под названием osu!,

Сначала я использовал синтаксический анализатор, найденный здесь https://github.com/KirkSuD/osudb Это позволило мне разобрать мои файлы БД в список, который я могу редактировать, но я не видел ни одного современного сериализатора, который я мог бы использовать для сериализации данных обратно в новый двоичный файл, поэтому я попытался создать его сам.

Я знаю, что могу просто заменить struct.unpack(format,v1) на просто struct.pack(format,v1) , но вот первая проблема:

Я больше не могу прочитать определенное количество байтов, поскольку они просто не являются байтами, и я также не могу этого сделать по понятным причинам, что означает, что я также не могу использовать технику, противоположную используемой разработчиком синтаксического анализатора (для легкого анализа данных).

Прямо сейчас я преобразовал двоичный файл в str for vizualition

https://controlc.com/6cdcffa3 Мне нужно получить это от https://controlc.com/9ddc4af1

Мне нужно получить точно такой же формат, проблема в том, что я начал создавать это:

 def serialize_types_exp(fobj, data_type):
    if data_type == "Int": ## 4 bytes unsigned int
        return struct.pack("<I", fobj)

def Serialize_score_exp(file_path):
    with open(file_path, "r") as fobj:
        with open("./osu!MapSync/output_func.txt", "r ") as otp:
            otp.truncate(0)
            for i in fobj:
                fobj = ast.literal_eval(i)
            print(type(fobj))
            f=0
            while f <= 1:
                res = str(serialize_types_exp(fobj[f], "Int"))
                if f == 0:
                    print(fobj[f],"->",res[0:-1])
                    otp.write(str(res)[0:-1])
                else:
                    print(fobj[f],"->",res[2:-1])
                    otp.write(str(res)[2:-1])
                f  = 1

Serialize_score_exp('./osu!MapSync/output.txt')
 

Что, как я понял, в основном будет то же самое, что просто возвращать строку, которую я хочу, иначе:

 def Serialize_score_exp(filepath):
    return "litteraly a static result copy pasted from the binary file"
 

Это не то, что я хочу, поэтому мне интересно, что я могу использовать, чтобы сериализовать его обратно в нужный мне формат?

РЕДАКТИРОВАТЬ: вот файлы, если кто-нибудь из вас хочет что-то попробовать https://drive.google.com/file/d/1Qmam_u9mVfqxBZ_U5QvFnq1t_01yOQzT/view?usp=sharing

РЕДАКТИРОВАТЬ 01.02.2020: похоже, что прямая запись двоичного файла в файл с использованием wb и последующее его чтение решают большую часть проблемы с визуализацией, но прежде чем я продолжу, в x0b исходных сериализованных данных есть байты (включая пробел), и я не знаю, как получить их из моего списка, есть идеи?

вот мой код прямо сейчас

 def serialize_type_exp(fobj, data_type):
    if data_type == "Boolean": ## False if 0x00 else True
        return struct.pack("<?", fobj)
    elif data_type == "Byte": ## 1 byte int
        return struct.pack("<s", fobj)
    elif data_type == "Double": ## 8 bytes floating point
        return struct.pack("<d", fobj)
    elif data_type == "Int": ## 4 bytes unsigned int
        return struct.pack("<I", fobj)
    elif data_type == "Long": ## 8 bytes unsigned int
        return struct.pack("<Q", fobj)
    elif data_type == "Short": ## 2 bytes unsigned int
        return struct.pack("<H", fobj)
    elif data_type == "Single": ## 4 bytes floating point
        return struct.pack("<f", fobj)
    elif data_type == "String": ## 0x00 or 0x0b - ULE128(n) - UTF-8(length=n)
        bb = fobj
        if bb == None:
            return None
        return fobj.encode("utf-8")
    else:
        raise NotImplementedError('parse_type(fobj, data_type): Unknown data type: "%s".' % data_type)

def serialize_types_exp(fobj, types):
    return [serialize_type_exp(fobj, i) for i in types]

score_data_types = ['Byte', 'Int', 'String', 'String', 'String', 'Short', 'Short', 'Short', 'Short', 'Short', 'Short', 'Int', 'Short', 'Boolean', 'Int', 'String', 'Long', 'Int', 'Long']

def serialize_scoredb_data(file_path):
    with open(file_path, "r") as fobj:
        with open("./osu!MapSync/output_func.db", "wb") as otp:
            otp.truncate(0)
            for i in fobj:
                fobj = ast.literal_eval(i)
            for i in range(2):
                otp.write((serialize_type_exp(fobj[i],'Int')))
            for maps in fobj[2]:
                for i in range(2):
                    if type(maps[i]) == str:
                        otp.write(serialize_type_exp(maps[i],'String'))
                    if type(maps[i]) == int:
                        otp.write(serialize_type_exp(maps[i],'Int'))
                for scores in maps[2]:
                    for idx, stats in enumerate(scores): 
                        if score_data_types[idx] == 'Byte':
                            print(stats,"->",stats.to_bytes,":",type(bytes(stats)))
                            print(type(serialize_type_exp(bytes(stats), score_data_types[idx])))
                            otp.write(serialize_type_exp(bytes(stats), score_data_types[idx]))
                        else:
                            otp.write(serialize_type_exp(stats, score_data_types[idx]))

serialize_scoredb_data('./osu!MapSync/output.txt')
 

РЕДАКТИРОВАТЬ 3: исправлено это x0b и x0b байты, теперь мне приходится сталкиваться с еще одной проблемой, прежде чем она заработает в игре:
некоторые байты, похоже, изменились с x00 на x01 в процессе, и я не знаю почему

 import osudb
import ast
import struct

with open("./osu!MapSync/scores.db", "rb") as f:
    with open("Exp.txt", "w") as i:
        for stuff in f:
            i.write(str(stuff))

parse = osudb.parse_score(r"./osu!MapSync/scores.db")
with open('./osu!MapSync/output.txt', 'w') as f:
    f.write(str(parse)) #[1:-1].split(',')

def serialize_type_exp(fobj, data_type):
    if data_type == "Boolean": ## False if 0x00 else True
        return struct.pack("<?", fobj)
    elif data_type == "Byte": ## 1 byte int
        return struct.pack("<s", fobj)
    elif data_type == "Double": ## 8 bytes floating point
        return struct.pack("<d", fobj)
    elif data_type == "Int": ## 4 bytes unsigned int
        return struct.pack("<I", fobj)
    elif data_type == "Long": ## 8 bytes unsigned int
        return struct.pack("<Q", fobj)
    elif data_type == "Short": ## 2 bytes unsigned int
        return struct.pack("<H", fobj)
    elif data_type == "Single": ## 4 bytes floating point
        return struct.pack("<f", fobj)
    elif data_type == "String": ## 0x00 or 0x0b - ULE128(n) - UTF-8(length=n)
        bb = fobj
        if bb == None:
            return None
        return fobj.encode("utf-8")
    else:
        raise NotImplementedError('parse_type(fobj, data_type): Unknown data type: "%s".' % data_type)

def serialize_types_exp(fobj, types):
    return [serialize_type_exp(fobj, i) for i in types]

score_data_types = ['Byte', 'Int', 'String', 'String', 'String', 'Short', 'Short', 'Short', 'Short', 'Short', 'Short', 'Int', 'Short', 'Boolean', 'Int', 'String', 'Long', 'Int', 'Long']

def serialize_scoredb_data(file_path):
    with open(file_path, "r") as fobj:
        with open("./new osu!.db/scores.db", "wb") as otp:
            otp.truncate(0)
            for i in fobj:
                fobj = ast.literal_eval(i)
            for i in range(2):
                otp.write((serialize_type_exp(fobj[i],'Int')))
            for maps in fobj[2]:
                for i in range(2):
                    if type(maps[i]) == str:
                        otp.write(b'x0b ' serialize_type_exp(maps[i],'String'))
                    if type(maps[i]) == int:
                        otp.write(serialize_type_exp(maps[i],'Int'))
                for scores in maps[2]:
                    for idx, stats in enumerate(scores): 
                        if score_data_types[idx] == 'Byte':
                            otp.write(serialize_type_exp(bytes(stats), score_data_types[idx]))
                        elif stats == None:
                            otp.write(b'x00')
                        elif score_data_types[idx] == 'String' and len(stats) == 32:
                            otp.write(b'x0b ' serialize_type_exp(stats, score_data_types[idx]))
                        elif idx == 3:
                            otp.write(b'x0bx06' serialize_type_exp(stats, score_data_types[idx]))
                        else:    
                            otp.write(serialize_type_exp(stats, score_data_types[idx]))

serialize_scoredb_data('./osu!MapSync/output.txt')

with open("./osu!MapSync/output_func.db", "rb") as f:
    with open("output_func.txt", "w") as i:
        for stuff in f:
            i.write(str(stuff))
 

это то, что я хочу https://controlc.com/6cdcffa3 и вот что я получил https://controlc.com/4e547b64

уже исправлен используемый мной синтаксический анализатор, теперь я обнаружил, что форматирование имени пользователя неверно и варьируется от игрока к другому, мне, видимо, нужно добавить это в мой сериализатор https://en.wikipedia.org/wiki/LEB128#Encode_unsigned_integer кроме меня, я понятия не имею о том, что они означают set high order bit of byte; и как это сделать на python

Ответ №1:

ну, я, наконец, закончил с сериализатором и частью слияния, поэтому последним шагом моего проекта будет правильная реализация FTP в надежде, что он будет работать на всех устройствах (включая Android), поэтому в этой теме больше нет необходимости, весь код опубликован на github rn

https://github.com/Mrcubix/Osu-MapSync