Скрыть объявление

Добро пожаловать на наш форум, уважаемые друзья! Регистрируйтесь и переходите в группу "Местный" для того чтоб иметь все привилегии на форуме, удачи вам:)

[PW] Программирование Структура PCK архивы и с чем их едят

Тема в разделе "[PW] Программирование", создана пользователем Andrey, 28 июл 2016.

  1. Andrey
    Оффлайн

    Andrey Я админ,смекаешь? Команда форума Администратор Зануда Модератор Проверенный Местный Пользователь

    Регистрация:
    18 апр 2015
    Сообщения:
    2.068
    Симпатии:
    1.056
    Структура.
    PCK архив по большей части состоит из запакованных zlib'ов данных, примерная схема файла на скрине ниже.
    1.png
    По поводу ASIG и FSIG, точно не знаю что это, но скорее всего отличительные "знаки" от архивов других игр на этом же движке.
    По файлам думаю все понятно, zlib нужен для сжатия и в какой то степени защите игровых файлов.
    Файловая таблица имеет такую структуру каждого элемента:
    PHP:
    class fileTableEntry
    {
       public 
    string filePath; - путь файла (максимум 260 байтов)
       public 
    uint fileDataOffset; - адрес файла
       
    public int fileDataDecompressedSize; - оригинальные размер
       
    public int fileDataCompressedSize; - размер после сжатия zlib'ом
    }
    Тут туже все довольно просто. У каждого файла есть отведенные 276 байта на таблицу (260 путь + 4 адрес + 4 размер + 4 размер + 4 что то типо пустоты). Ну и как и файлы они сжимаются zlib'ом.
    Адрес файловой таблицы - это адрес первого элемента файловой таблицы, запакованный с помощью ключа1 (про ключи будет написано ниже).
    Коопираты разработчиков - простоя строка в "Angelica File Package, Perfect World.", но интересно то, что если заменить ее нулями игра не примет такой архив.
    Ну с кол-вом файлов думаю все понятно.

    Ключи.
    Ключи используются для зашифровки некоторых данных в архиве. Для этого используется оператор авторизуйтесь . Ключей 2, но на деле можно обойтись только первым.
    Дефолтные ключи от архивов пв:
    PHP:
    int KEY_1 = -1466731422;
    int KEY_2 = -240896429;
    Используются ключи в 2х местах:
    1. Первым ключем шифруется адрес файловой таблицы
    2. Оба используются для зашифровки размера каждого элемента файловой таблицы.
    Сами ключи хранятся в elementclient.exe, первый открыто а второй зашифрован. Найти их очень просто, а второй расшифровывается применением оператора xor с первым.
    Архивы более 2х ГБ.
    Ничего особенного в них нет, нужно просто дописать .pkx файл в конец .pck и с архивом можно спокойно работать. А для разделения нужно просто оставить .pck файл весом 2147483392 байта, а все остальное запиисать в .pkx.
    Примеры работы с архивами.
    По поводу выбора языка на котором писать архиватор, за простоту можно выбрать C#, но мне больше приглянулся Qt т.к там есть встроенная поддержка zlib'a без всяких доп библиотек, так что примеров будет 2.
    C#
    Полнофункциональные исходники можно найти тут
    а так только приведу примеры кода.
    Чтение и запись каждого элемента файловой таблицы, кстате уточню что все строки в файлах указываются в китайской кодировке gbk.
    PHP:
    public static fileTableEntry readTableEntry(byte[] bufferbool compressed)
            {
                
    fileTableEntry fte = new fileTableEntry();
                
    MemoryStream ms = new MemoryStream(buffer);
                if (
    compressed)
                {
                    
    byte[] buf = new byte[276];
                    
    ZOutputStream zos = new ZOutputStream(new MemoryStream(buf));
                    
    CopyStream(new MemoryStream(buffer), zos276);
                    
    buffer buf;
                }
                
    BinaryReader br = new BinaryReader(new MemoryStream(buffer));
                
    fte.filePath Encoding.GetEncoding("GB2312").GetString(br.ReadBytes(260)).Split(new string[] { "\0" }, StringSplitOptions.RemoveEmptyEntries)[0].Replace("/""\\");
                
    fte.fullFilePath string.Empty;
                
    fte.fileDataOffset br.ReadUInt32();
                
    fte.fileDataDecompressedSize br.ReadInt32();
                
    fte.fileDataCompressedSize br.ReadInt32();
                return 
    fte;
            }

            public static 
    byte[] writeTableEntry(fileTableEntry fte)
            {
                
    byte[] buffer = new byte[276];
                
    MemoryStream msb = new MemoryStream(buffer);
                
    BinaryWriter bw = new BinaryWriter(msb);
                
    bw.Write(Encoding.GetEncoding("GB2312").GetBytes(fte.filePath.Replace("/""\\")));
                
    bw.BaseStream.Seek(260SeekOrigin.Begin);
                
    bw.Write(fte.fileDataOffset);
                
    bw.Write(fte.fileDataDecompressedSize);
                
    bw.Write(fte.fileDataCompressedSize);
                
    bw.Write(0);
                
    msb.Seek(0SeekOrigin.Begin);
                
    MemoryStream ms = new MemoryStream();
                
    CompressStream(msbms276);
                return 
    ms.ToArray();
            }
    А ну и код сложения\разделения pck архивов.
    PHP:
    public static void Merge(ref string pck)
            {
                if (new 
    FileInfo(pck).Length 2147483393)
                {
                    
    string pkx pck.Replace(".pck"".pkx");
                    if (
    File.Exists(pkx))
                    {
                        
    Console.WriteLine("\nСоединяем pck и pkx в новый файл");
                        if (
    File.Exists(pck "x")) File.Delete(pck "x");
                        
    File.Copy(pckpck "x");
                        
    CopyStream(new FileStream(pkxFileMode.OpenFileAccess.Read), new FileStream(pck "x"FileMode.OpenFileAccess.Write), 134217728);
                        
    pck pck "x";
                    }
                }
            }

            public static 
    void Split(string pck)
            {
                if (new 
    FileInfo(pck).Length 2147483393)
                {
                    
    Console.WriteLine("\nРазъединяем pck на 2 архива");
                    
    string pkx pck.Replace(".pckx"".pkx");
                    
    FileStream fsPkx = new FileStream(pkxFileMode.CreateFileAccess.Write);
                    
    FileStream fsPckx = new FileStream(pckFileMode.OpenFileAccess.ReadWrite);
                    
    fsPckx.Seek(2147483392SeekOrigin.Begin);
                    
    CopyStream(fsPckxfsPkx134217728);
                    
    fsPckx.Seek(0SeekOrigin.Begin);
                    
    fsPckx.SetLength(2147483392);
                    
    fsPkx.Close();
                    
    fsPckx.Close();
                }
                if (
    File.Exists(pck.Remove(pck.Length 11)))
                {
                    
    File.Delete(pck.Remove(pck.Length 11));
                }
                
    File.Move(pckpck.Remove(pck.Length 11));
            }
    Не буду отрицать, что написано немного по наркомански:Pig01:

    C++
    К сожалению тут у меня есть только пример распаковки:
    PHP:
    void Unpack::doWork()
    {
        
    u->Merge(path);
        
    QString dir path ".files";
        
    QFile qf(path);
        
    qf.open(QIODevice::ReadOnly);
        
    QDataStream qds(&qf);
        
    qds.setByteOrder(QDataStream::LittleEndian);
        
    //Read files count
        
    qf.seek(qf.size() - 8);
        
    qds >> entryCount;
        
    //Read file table offset
        
    qf.seek(qf.size() - 272);
        
    qds >> fileTableOffset;
        
    fileTableOffset fileTableOffset Keys::KEY_1;
        
    qf.seek(fileTableOffset);
        
    fileTableEntryfileTable = new fileTableEntry[entryCount];
        
    int32_t entrySize;
        
    charbuf = new char[272];
        
    emit setProgressMaxValue(entryCount);
        
    emit setProgressValue(0);
        for (
    int i 0entryCount; ++i) {
            
    emit setProgressValue(i);
            
    emit setStatusStringText(QString("Чтение таблицы файлов: %1/%2").arg(QString::number(i), QString::number(entryCount)));
            
    qds >> entrySize;
            
    qds >> entrySize;
            
    entrySize entrySize Keys::KEY_2;
            
    qds.readRawData(bufentrySize);
            
    QByteArray buffer(bufentrySize);
            if (
    entrySize 272) {
                
    fileTable[i] = u->readTableEntry(bufferentrySizetrue);
            } else {
                
    fileTable[i] = u->readTableEntry(bufferentrySizefalse);
            }
            
    u->CreateDir(dirfileTable[i].filePath);
        }
        
    emit setProgressValue(0);
        for (
    int i 0entryCount; ++i) {
            
    emit setProgressValue(i);
            
    emit setStatusStringText(QString("Распаковка файлов: %1/%2").arg(QString::number(i), QString::number(entryCount)));
            
    QString fullpath dir "\\" fileTable[i].filePath;
            
    QFile file(fullpath);
            if (
    file.exists()) {
                
    file.remove();
            }
            
    file.open(QIODevice::WriteOnly);
            
    QDataStream qfile(&file);
            
    charbuf = new char[fileTable[i].fileDataCompressedSize];
            
    qf.seek(fileTable[i].fileDataOffset);
            
    qds.readRawData(buffileTable[i].fileDataCompressedSize);
            
    QByteArray buffer u->decompressStream(QByteArray(buffileTable[i].fileDataCompressedSize), fileTable[i].fileDataCompressedSizefileTable[i].fileDataDecompressedSize);
            
    qfile.writeRawData(bufferfileTable[i].fileDataDecompressedSize);
            
    file.flush();
            
    file.close();
            
    delete [] buf;
        }
        
    qf.close();
        
    emit setProgressValue(0);
        
    emit setStatusStringText("Распаковка успешно завершена!");
    }
    Чтение файловой таблицы:
    PHP:
    fileTableEntry Utils::readTableEntry(QByteArray dataint32_t entrySizebool compressed)
    {
        
    fileTableEntry ftb;
        if (
    compressed) {
            
    data decompressStream(dataentrySize272);
        }
        
    QDataStream qds(&dataQIODevice::ReadOnly);
        
    qds.setByteOrder(QDataStream::LittleEndian);
        
    charbuf = new char[260];
        
    qds.readRawData(buf260);
        
    QTextCodecqtc QTextCodec::codecForName("GB2312");
        
    ftb.filePath QString(qtc->toUnicode(buf)).split("/0")[0].replace("/""\\");
        
    qds >> ftb.fileDataOffset;
        
    qds >> ftb.fileDataDecompressedSize;
        
    qds >> ftb.fileDataCompressedSize;
        
    int a ftb.filePath.lastIndexOf("\\");
        
    ftb.fileDir ftb.filePath;
        
    ftb.fileDir.remove(aftb.filePath.length() - a);
        if (
    ftb.filePath.endsWith("dds") || ftb.filePath.endsWith("png") || ftb.filePath.endsWith("tga") || ftb.filePath.endsWith("jpg")) {
            
    ftb.fileType FileType::Image;
        } else {
            
    ftb.fileType FileType::Text;
        }
        return 
    ftb;
    }
    Распаковка zlib данных:
    PHP:
    QByteArray Utils::decompressStream(QByteArray bufferint32_t sizeCompressedint32_t sizeDecompressed)
    {
        
    unsigned long int compressBufLength sizeCompressed;
        
    unsigned long int uncompressLength sizeDecompressed;
        
    uncompress(uncBuf, &uncompressLengthreinterpret_cast<unsigned char*>(buffer.data()), compressBufLength);
        
    QByteArray data QByteArray::fromRawData(reinterpret_cast<char*>(uncBuf), uncompressLength);
        return 
    data;
    }
     
    RSR нравится это.
  2. Malekith
    Оффлайн

    Malekith Ненавижу когда Волан-Де-Морт моется моим шампунем! Местный Пользователь

    Регистрация:
    25 июн 2018
    Сообщения:
    73
    Симпатии:
    57
    кто то может дописать эту прогу для ярхивов более 4 гб? чтоб разделял на 3 части pck >pkx>pkx1 или что то типо того
     
  3. 功率 Люцифер
    Оффлайн

    功率 Люцифер Модератор - Perfect World Команда форума Фея Технологий Квинтэссенция добра и зла Reverse Engineer Модератор NoLimiT™ Местный Пользователь

    Регистрация:
    27 май 2018
    Сообщения:
    586
    Симпатии:
    670
    Клиент не прочтет
     
    Malekith нравится это.
  4. Malekith
    Оффлайн

    Malekith Ненавижу когда Волан-Де-Морт моется моим шампунем! Местный Пользователь

    Регистрация:
    25 июн 2018
    Сообщения:
    73
    Симпатии:
    57
    мда уже промелькала мысль,других вариантов нет?
    я так понял еще надо редачить кучу файлов чтоб клиент понимал 3 архив
     
  5. 功率 Люцифер
    Оффлайн

    功率 Люцифер Модератор - Perfect World Команда форума Фея Технологий Квинтэссенция добра и зла Reverse Engineer Модератор NoLimiT™ Местный Пользователь

    Регистрация:
    27 май 2018
    Сообщения:
    586
    Симпатии:
    670
    Исходники в шаре достаточно поменять чтение архивов и внести в массив новый архив читаешь функцию в исходниках идешь в иду ищешь такую же переделываешь и радуешься
     
    Damnes и Malekith нравится это.
  6. Malekith
    Оффлайн

    Malekith Ненавижу когда Волан-Де-Морт моется моим шампунем! Местный Пользователь

    Регистрация:
    25 июн 2018
    Сообщения:
    73
    Симпатии:
    57
    Если бы я шарил как ты в функциях,в c# и в иде,я бы не спрашивал
     
  7. RSR
    Оффлайн

    RSR Новый участник Пользователь

    Регистрация:
    14 июл 2022
    Сообщения:
    0
    Симпатии:
    0
    Тема немного старая, но не расскажете подробнее как найти ключи в elementclient.exe?

    Уточню, что elementclient.exe с нестандартными ключами с фришки.

    Успел почитать форумы и поковыряться в файле, зашито при помощи RodySoft PCK Protector, так что думаю оно того не стоит.
     
    Последнее редактирование модератором: 15 июл 2022
Похожие темы
  1. LejosDexp
    Ответов:
    8
    Просмотров:
    1.586
Загрузка...