Black Desert Online Modding Tools (3 Viewers)

Vyn

Potential Patron
Joined
Jun 2, 2017
Garkin Garkin , with this command
Code:
quickbms.exe -l blackdesert.bms pad05388.paz
-> you already know that pad05388.paz contains stringtable, but I still don't know you find it, I don't think you tried this command 5388 times until you found stringtable.

Is there any command tha will scan all the .paz files and list the files that they contain in a .txt file?

I want to find the paz file which contains the stringtable is SA


Thank you[/code]
 
Last edited:

Garkin

Avid Affiliate
Joined
Dec 28, 2016
V Vyn , with a simple batch you can scan all .paz files:
Code:
@echo off
set /p VER=<..\version.dat
echo Client Version: %VER% > filelist_%VER%.txt
echo. >> filelist_%VER%.txt
for %%f in (*.paz) do (
   echo %%~nxf: >> filelist_%VER%.txt
   quickbms.exe -l blackdesert.bms %%~nxf >> filelist_%VER%.txt
   echo. >> filelist_%VER%.txt
)

Also take a look to a spoiler in my previous comment.
 
Last edited:

BlackFireBR

Content Creator
Joined
Sep 2, 2013
BlackFireBR BlackFireBR Small isue with meta injector - when creating backup meta injector compares if the size of meta file was changed. It doesn't check if backup has the same client version as the latest meta. Today's NA/EU patch from 456 to 457 had meta file of the same size, so new backup file wasn't created.
My suggestion - compare client version (the 1st long in meta file) instead of file size.

Side note: It's not necessary to create multiple backups as you need just one backup of the latest "clean" version (which can be removed when backup is restored). Its because if you restore any other backup, you'll get corrupted files warning.
Thanks for the suggestion.
I was afraid this could happen. Next version I'm just gonna check for client version, like you said.
I was thinking about replacing file in .paz archive or even creating fake .paz archive, however I'm not able to compress it to the format that game would recognize. (Code to decrypt/encrypt and uncompress can be found in quickbms source code, unfortunately there is nothing about compression)
Do you think it's possible to find a way to compress it back to the game's format? I tried a lot of things in the past, and none of them work. The "reverse-decompression" method from quick bms doesn't apply for the compression method 85 (blackdesert).
Anyway you and me or someone else could work on trying to figure it out how to compress it? It would be amazing if we succeeded.
------------------------------------------------------------------------------------------

I'm making a completely new version from Black Desert Online File Extractor , only this time we are going to be able to browse ALL the files inside the .PAZ files, not only the .pac or .dds, among with a lot of other better organization in the previous existing options.
Here's a preview:
fileextractor_preview.gif
There's still a long way to go. It took me a while to create the file structure I needed to manage the folder hierarchy. I even had to draw it to get a better view xD
tree.jpg
In the end I created a non-binary tree, which each leaf is actually a double linked list.
I'm planing on using multi-threads to show the extraction progress while it extracts.

Enough of technical talking, Iet me get back to work on this thing. xD

Peace
 
Last edited:

Spoony

Potential Patron
Joined
Mar 30, 2016
Thanks for the great work, BlackFire. I have a slight issue that you or someone may be able to fix. I extracted Panel_CharacterNameTag.luac, which has both an x64 and x86 copy. When I run meta_injector it shows that it copied the file:
oJTl7su.png

But the path is empty:
GD0V4ra.png

It did copy it to the x86 folder:
wqcVhUK.png

But that's not where I had it (or got it from in the paz files):
vgLZF5c.png


BDO runs as 64 bit every time, and I have no option to run the other version, so I can't verify it works. Does anyone have a good idea on a workaround to get it to make it apply to the correct location?

Edit, nevermind, I got it sorted out. Now I just have to figure out how to re-encode the lua's probably so it doesn't crash. :)
 
Last edited:

Garkin

Avid Affiliate
Joined
Dec 28, 2016
S Spoony I've tried to replace ui_lua_damage.luac with custom file (there was modified just one line to show damage numbers which are disabled for live servers). Game crashed and script was deleted even if I have used original file extracted from paz archive, so I assume that game have protection against replacing lua scripts.
 
Last edited:

Garkin

Avid Affiliate
Joined
Dec 28, 2016
BlackFireBR BlackFireBR Small isue with meta injector - when creating backup meta injector compares if the size of meta file was changed. It doesn't check if backup has the same client version as the latest meta. Today's NA/EU patch from 456 to 457 had meta file of the same size, so new backup file wasn't created.
My suggestion - compare client version (the 1st long in meta file) instead of file size.

Side note: It's not necessary to create multiple backups as you need just one backup of the latest "clean" version (which can be removed when backup is restored). Its because if you restore any other backup, you'll get corrupted files warning.
 

Spoony

Potential Patron
Joined
Mar 30, 2016
S Spoony I've tried to replace damage.luac with custom file. Game crashed and script was deleted even if I have used original file extracted from paz archive, so I assume that game have protection against replacing lua scripts.
Yeah, I know at some point they switched from just bare .lua to .luac (though I'm not sure on when), but I distinctly remember messing the the lua files when the original Engrish patch was done that way. I was able to use Unluac to decompile it, and I used luac53 to compress it again, but the game crashes as soon as the game gets to where my character would be in-game.

I did not, however, have issues with the files getting deleted. Log files only give the message of
Code:
2017-06-13 16:58:48 13172-00-3 eErrNoErrorHappen                            (3749371554) Error!=>[d:/black desert/luaCScript\x64\widget\characternametag\panel_characternametag.luac]이 존재하지 않습니다
 

Garkin

Avid Affiliate
Joined
Dec 28, 2016
Yeah, I know at some point they switched from just bare .lua to .luac (though I'm not sure on when), but I distinctly remember messing the the lua files when the original Engrish patch was done that way. I was able to use Unluac to decompile it, and I used luac53 to compress it again, but the game crashes as soon as the game gets to where my character would be in-game.

I did not, however, have issues with the files getting deleted. Log files only give the message of
Code:
2017-06-13 16:58:48 13172-00-3 eErrNoErrorHappen                            (3749371554) Error!=>[d:/black desert/luaCScript\x64\widget\characternametag\panel_characternametag.luac]이 존재하지 않습니다
Just for the record - game uses Lua v5.1, so it would be better to use version 5.1 instead of 5.3. Which version is used can be found in .luac header (".LuaQ" => Q => 0x51 => version 5.1):
Code:
4 bytes - signature ("\x1bLua")
1 byte - version (0x51)
1 byte - format
1 byte - endianness
5 bytes - sizes of some types

However as I said, game crashes even with original file and I got the same error message as you did - file does not exist.
I was thinking about replacing file in .paz archive or even creating fake .paz archive, however I'm not able to compress it to the format that game would recognize. (Code to decrypt/encrypt and uncompress can be found in quickbms source code, unfortunately there is nothing about compression)
 

Garkin

Avid Affiliate
Joined
Dec 28, 2016
sorry for the question, but with this tool it's possible to swap texture?
Yes, it is. Download Meta Injector Reloaded (MIR), place modified file into the "files_to_patch" folder and run meta_injector.exe. MIR will modify information in pad00000.meta so the game will load modified file from game installation folder instead of original file from pad?????.paz archive.
 

lust95

Potential Patron
Joined
Jun 12, 2016
thank u. another question (o.o): someone know whats the .paz name of grunil set and of ahon kirus armor for dark knight? i want to swap their texture so i don't have to buy the tiket to trasform the ahon kirus armor in a costume. thanks in advance \[T]/ <3

ps: And to do this, I simply replace the textures (I put the name of the grunil part to the corresponding one of the ahon), or I have to replace the name of the pad file containing the grunil set (if it is in one pad ) With the one of the ahon? Excuse incompetence but it's all new to me ahahha
 
Last edited:

Garkin

Avid Affiliate
Joined
Dec 28, 2016
Do you think it's possible to find a way to compress it back to the game's format? I tried a lot of things in the past, and none of them work. The "reverse-decompression" method from quick bms doesn't apply for the compression method 85 (blackdesert).
Anyway you and me or someone else could work on trying to figure it out how to compress it? It would be amazing if we succeeded.
I'm not sure if it's possible to find compression algorithm because this code is not present in client executable file. (Decompression algorithm was originally ripped from updater and then from client executable by Ekey - link.)
But you can be interested in decompression function in this project:
PAZ-Unpacker/Crypt.cpp at master · kukdh1/PAZ-Unpacker · GitHub
 
Last edited:

BlackFireBR

Content Creator
Joined
Sep 2, 2013
*NEW* PAZ Browser / File Extractor v1.4
Ladies and Gentlemen, I proudly present you, the newest version of "Paz Browser" and "File Extractor" for Black Desert Online:
armor_demo3.gif
This version incorporates all the features from File Extractor and PAZ Browser in one single tool, as well as numerous improvements, not only in terms of visuals, but also in functionality and performance.


New Features:
Browse ALL the folders and ALL the files of the game and choose exactly the files you want to extract:
otherfiles_demo.gif


Search
search.gif

You can find a specific file you want to extract without having to go through all the folders to find it.
The search allows you to search multiple files at once, as well, search by extension, and even search all the files that has a certain pattern.

Here's some of the features of the search:
Code:
For multiple files, separate them by commas.
    phw_00_ub_0004.pac,multiplemodeldesc.xml,pew_00_lb_0053_dec.dds

If you want to list all the files from from an extension, do it like this:
    *.xml,*dds,*pac - Will give you all the files with the extension .xml or .dds or .pac

If you want to list only the files that are in a specific folder, do this:
     character/*.xml  This will give you all the files .xml located in the folder "character/"

You can search for parts of the file name, doing this:
     *_00_ub_0001.pac  This will give you all the files that ends with "_00_ub_0001.pac"

You can also do this to find all the armor parts:
     phw_00_*_0001.pac  This will give you: phw_00_ub_0001.pac,phw_00_lb_0001.pac,phw_00_hand_0001.pac
     (Basically the character * makes the program ignore what's between the words it separates)

Extract the whole game
extraction2.gif

If you are familiar with the "Fast Extraction" method from the "File Extractor", this uses the same method, but this time:
  • It extracts 100% of the game files.
  • It displays how many files were already extracted, and what percentage of the extraction is already completed.
Real Outfit and Armor Names Listing
real_names_toggle.gif
Press "N" to toggle between the "Real Names" and the "File Names" when browsing armor files.

This feature uses a .txt file located in "Paz\patcher_resources\" and it's called "real_names.txt":
real_names.jpg

  • The left side accepts the same things as the "search" in this tool.
  • You can add more into this file, obeying the same pattern and the tool will automatically recognize it the next time you launch it.
  • # is interpreted as a comment and the entire line is ignored
I didn't check if all the file names are correct. If you find any mistake, feel free to post them here and I can fix it in a future release.

Also feel free to post here, updated versions of this file made by you.

-----------------------------------------------------------------------------------------------------------------------------------------------------
As it is written in the program, here's what each color represents:
When not browsing the Outfit and Armors:

  • Green: It's a folder
  • White: It's a file
When browsing the Outfit and Armors:
  • Magenta: Cash-shop Item
  • Blue: Non-Cash-Shop Item
  • Green: Class-Exclusive Item
Yellow: If you have at least 2 backups in your Paz folder, the tool compares the files that are present in your previous backup, with the ones that are present in your game right now. The new files that were added in the newest update, are shown as Yellow:
new_files.gif



File Extraction to Folder with Meaningful Names
multiple_selection.gif

Now every time you extract a file that has a "Real Name", the folder it's extracted uses that name, as long as which class it belongs. (See picture above for more details)


Change the order the files are displayed
sorting.gif

Now to make it easier to find the files you want, you can sort them by:

  • File Name
  • Real Name
  • Most Recent
  • Extension
And choose if you want them sorted in a "ascendant" order, or a "descendant" order.

Sorting by "Most Recent" simply sorts them by "File Number" (The order they were added in the game by the developers, at least in theory). So using "Most Recent - Descendant" will give you better chances to find the file from the newest outfit for example.

Multiple File Selection Made it Easy
multiple_selection.gif

Do you want to select multiple files like you are holding "Shift" if you were using a File Explorer? You can do that now by using the key "S" to select both the current file the cursor is pointing and the file bellow.
If you selected more that you wanted by any chance, you can undo that selection by pressing "W".


Extract All The Outfit Parts At Once
parts.gif

Have you ever wanted to extract all the parts of the armor at once (Upperbody, Lowerbody, Shoulder, etc)?
Well, now you can.
Every time you are in the "9_upperbody" folder of any class, when you try to extract one or more files, after prompting where do you want to extract the files to, it asks you if you want to do just that.
If you press 'y' or 'Y' it will extract not only the _ub_ file, but also all the other that are from the same outfit your selected, and then, when it's done, it will open the folder where extracted everything.


-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Meta Injector Source Code Explained
I made a very detailed explanation about the Meta Injector source code (v2.0e) so if people want to mess around with it and create something that uses it, you have basically a step-by-step guide on how to understand the source code.

This explanation is also on the first page.

The only files you need to understand what they do (and not all of it), are these ones:






      • main.c : Handles the menus
      • file_operations.c : Handles things like open files, count files, copy files, check files existence, etc.
      • patcher.c : Does all the magic.
      • meta_explorer.c : Reads the pad00000.meta files and retrieves/decrypt the information of this index file and stores all the information in memory.
Let's start with the file main.c:

main.c : It is responsible only for the user interface (menus), it doesn't do anything but to give the user the options to install or restore a backup. If the user chooses the "Install" option, it calls te "runPatcher()" function, located in the "patcher.c" file.

patcher.c : It's the Meta Injector code itself. If reads the files from the "files_to_patch" folder, and patches the pad00000.meta file and then copies the files to their right location.

void runPatcher():
The most important function in the patcher.c file.
The first thing it does is to call the function:
Code:
getAllFiles(char* pathToFiles, char* extFilter, long* filesCount)
This is located in the file "file_operations.c" and what it does is: It opens the folder specified in "pathToFiles" and if it's a file, it stores the file names and the path to that file into a variable that I called "FileBlock* fileNames", if it's not, it goes inside that folder by calling the same function recursively, but with the "pathToFiles" changed to that folder.


Warning: It's is important that you save the path to each file into the "fileNames[ i ].originalPath" variable because we are going to use this information to copy the files to their right location later.

------------------------------------------------------------------------------------------------------------------------

Going back to the "runPatcher()" function again, now we have to get information from the pad00000.meta file. For that we call those two functions, located in the "meta_explorer.c" file:
Code:
metaFileInfo = getMetaFileInfo(getLatestBackup());
fileBlocks = fillFileBlocks(metaFileInfo);

getLatestBackup() : returns you lastest pad00000[xxxx-xx-xx].meta.backup file name(Implementation located in "utilities.c"). We have to use the backup because we need a clean meta file to retrieve the right information.
If no backup exists, it simply uses the "pad00000.meta" file.


MetaFileInfo* getMetaFileInfo(char* metaFileName)
First, take a look on how the pad00000.meta file is structured:
pad00000-layout-new-jpg.60263


Now, look at the beginning of the .meta file:
XkIWKz2.jpg

The first 4 bytes are just the client version, so we skip it by doing :
Code:
fseek(metaFile,sizeof(long),SEEK_SET);

The next 4 bytes, are how many .PAZ file the game has right now, I store this info in "metaFileInfo->pazCount"

Next, I skip this whole part that are just the PAZ_NUM,PAZ_HASH,PAZ_SIZE by doing:
Code:
fseek(metaFile,(metaFileInfo->pazCount * (3 * sizeof(long))),SEEK_CUR);

(3 * sizeof(long)) skips one "line"

So now, if you read the next 4 bytes, it gives you how many of the "File Blocks" you are going to have next.

File Blocks are the most important thing in the program, they are this:
uuFRjrF.jpg


But we are not going to mess with it in this function yet, we just need to know how many of them there are, so we just store the read value in the "metaFileInfo->filesCount" variable.

Now, the file pointer should be right at the beginning of the "File Blocks" part,
so we just save that location under "metaFileInfo->fileBlocksStart" so we can jump right into that point later.
We do that by doing:
Code:
metaFileInfo->originalFileBlocksStart = ftell(metaFile);

I also calculated where the "File Blocks" should stop, by doing this simple calculation:
Code:
File Blocks End =  File Blocks Start + (Files Count * (size of one registry))
Since each "line" (registry) of the file blocks has 7 fields and each fields is has 4 bytes, and 7*4 = 28, I did:
Code:
#define ONE_REGISTRY 28

That's the end of the getMetaFileInfo function.
------------------------------------------------------------------------------------------------------------------------
FileBlock* fillFileBlocks(MetaFileInfo* metaFileInfo)

This will read that "File Blocks" section from the .meta file, and also, decrypt the File Names and Folder Names, and store all that in the "fileBlocks" variable:
RcONwHr.jpg


Now for this part, I'm going to simplify things a little.
The way I did in my code, was because there were some versions of the game that I couldn't read the first 256000 bytes from the "File Blocks" part, but now this is fixed. But the code works either way.


So all the part from:
Code:
printf("\nSearching for hash: %ld\n", multiplemodeldesc_hash);
to
Code:
printf("FILE_BLOCKS_COUNT: %ld (%ld missing files)\n", metaFileInfo->fileBlocksCount,metaFileInfo->filesCount -  metaFileInfo->fileBlocksCount);

You can delete it and use this code instead:
Code:
    // Allocates the memory for the File Blocks
    fileBlocks = (FileBlock*)calloc(metaFileInfo->filesCount + 1, sizeof(FileBlock));

    // Initialized the variable that counts how many file blocks we have
    metaFileInfo->fileBlocksCount = 0;

    // This will open you last pad00000.meta.backup file,
    // just in case your current pad00000.meta file is already modified.
    FILE* metaFile = openFile(getLatestBackup(),"rb");

    // Go to where the file blocks start
    fseek(metaFile,metaFileInfo->originalFileBlocksStart,SEEK_SET);

   // Fill the File Blocks
   for (i = 0; i < metaFileInfo->filesCount; i++)
   {
        // Saves the exact byte where this registry begins (VERY IMPORTANT)
        fileBlocks[i].metaOffset = ftell(metaFile);

        // Reads the next 28 bytes of the meta file and stores it
        fread(&fileBlocks[i].hash,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].folderNum,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].fileNum,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].pazNum,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].fileOffset,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].zsize,sizeof(long),1,metaFile);
        fread(&fileBlocks[i].size,sizeof(long),1,metaFile);

        metaFileInfo->fileBlocksCount++;
   }
All we did, was reading this:
uuFRjrF.jpg


Now we are going to decrypt and read the folder names (relative paths to the files if they were extracted from the .PAZ file)
Tw4yxGe.jpg

Code:
     // Seek to file blocks end (optional)
    fseek(metaFile,metaFileInfo->fileBlocksEnd,SEEK_SET);

    // Reads how many folder names we will be reading
    long folders_part_length = 0;
    fread(&folders_part_length, sizeof(long),1,metaFile);

For the next part bellow:
Code:
/// ******************* FOLDER NAMES DECRYPTION **********************
I simply used the code posted here:
https://www.undertow.club/posts/138739


What you have to know about it is that it reads the meta file all the encrypted bytes from the "Folder names" part and stores them in the "ctext" variable. After that it reads this "ctext" variable, 8 bytes at the time, and it decrypts these 8 bytes, storing the decrypted data into the "ptext" variable.

After everything is done, this moves the "ptext" pointer back to the beginning to the memory region where you have the decrypted folder names.
Code:
ptext -= folders_part_length;

At this point, if we read the ptext variable, we are going to have this:
Code:
[FOLDER_NUM (4bytes) ][SUBFOLDERS_NUM (4bytes) ]character/'\0'[FOLDER_NUM (4bytes) ][SUBFOLDERS_NUM (4bytes) ] character/texture/'\0' ....

And we actually want:
Code:
folderNames[0] = "character/";
folderNames[1] = "character/texture/";
...

So, for the next part
Code:
/// FOLDER NAMES FILLING
We are basically puting each "folder name" into each position of the array "folderNamesArray".


Like, we are skipping the first 2 numbers:
[FOLDER_NUM (4bytes) ][SUBFOLDERS_NUM (4bytes) ]
Code:
for (j = (2 * sizeof(long)) /* Skips the first 2 numbers */; j < folders_part_length; j++)
{
     ...
     j += (2 * sizeof(long)); /* Skips the first 2 numbers */
}

Reading the string until we find a '\0', and storing it into the folderNamesArray:
Code:
character/'\0'
Code:
        // If it's not a \0
        if (ptext[j] != 0)
        {
            folderNameLength++;
        }
        else // If the \0 is found
        {
                // Store the folder name
         }
---------------------------------------------------------------------------------------
Bellow this comment:
Code:
 // Assigns the right File Name from the fileNameArray to the right File Block, based on the File Num
We basically copy the content of each "folderNamesArray", to the "fileBlocks" structure
(We have to do this because the order from "folderNamesArray" is different from what we read in the "File Blocks" section)
ytec4nx.jpg


The next thing:
Code:
/// ******************* FILE NAMES DECRYPTION **********************
and
Code:
/// FILE NAMES FILLING
It's the same thing. The only difference is that the "File Names" section doesn't have the "FOLDER_NUM" and "SUBFOLDERS_COUNT" numbers in front of each file, so we don't have to skip them every time.


In the end I do this:
Code:
qsort(fileBlocks,metaFileInfo->fileBlocksCount,sizeof(FileBlock),compare);
But it's just to make searching in this fileBlocks faster, it's not really necessary.


This is the end of the fillFileBlocks() function
-----------------------------------------------------------------------------------------------------------------------------------------


Going back to the "runPatcher()" again:
After you have your MetaFileInfo and your FileBlocks filled, it's time to do what Meta Injector actually does.

What we are going to do now, is to look for each file name we discovered in the "files_to_patch" folder, into our "FileBlock* fileBlocks" structure, and if we have a matching name, we are going to copy some information from that "fileBlock[ i ]" that we don't have in out "filesToPatch[ j ]" that is going to be relevant in the future. And also set the variable "needPatch" from that file in the "filesToPatch" array, to 1.

This "needPatch" flag will be used to check if we need to patch this file block in the meta file or not.

Just like before, the current code in the source code, is a little more complicated than it needs to be for your application, so from the lines bellow:
Code:
 printf("\nSearching for files in the meta file...");
to
Code:
 /// COPYING FILES

Delete the code in between and use this code instead:
Code:
   // Searches for the files from the "files_to_patch" folder in the "fileBlocks"
    for (j = 0; j < filesToPatchCount; j++)  // For each file from the "filesToPatch" array
    {
       // Sequential search through all File Blocks
        for (i = 0; i < metaFileInfo->fileBlocksCount; i++)
        {
            // If they have the same file name
            if (strcmpi(fileBlocks[i].fileName,filesToPatch[j].fileName) == 0)
            {
                // Time to get some relevant information from the matched file block:

                // This will be used to patch the meta file
                filesToPatch[j].metaOffset = fileBlocks[i].metaOffset;

                // This will be used to copy the files to where they are suposed to go
                filesToPatch[j].folderName = fileBlocks[i].folderName;
                filesToPatch[j].originalPath = fileBlocks[i].originalPath;

                // Indicates this file block will need to be patched in the meta file
                filesToPatch[j].needPatch = 1;

                // Breaks from the for loop that goes though the file blocks,
                // since we've already found a match for this fileToPatch[j]
                break;
            }
        }
    }

Next thing, we call:
Code:
copyFilesBack(filesToPatch, filesToPatchCount);
Again, you don't need to do what I did in my code, there is a simple way to do what this function does, and I'm going to teach you right now:


You have to copy the files from the "files_to_patch" folder, in wherever subfolder they are (remember this information was stored in the "originalPath" variable), to the path the game will look from them,
which consist in "Black Desert Installation Folder" + filesToPatch[ i ].folderName


So for example. Let's say you installed your game into:
"C:\Program Files (x86)\Black Desert Online\"


(In my code, I get the current working dir, which gives me the path to the meta_injector.exe, and I remove the "\Paz" at the end of the path, which gives me the exact game path.)

and let's say you have this in your filesToPatch[0]:
fileName = "phw_00_uw_0001.dds"
folderName = "character/texture/"

originalPath = "files_to_patch\"

What you are going to do is to make a way to copy "phw_00_uw_0001.dds" from ""C:\Program Files (x86)\Black Desert Online\Paz\files_to_patch\" to ""C:\Program Files (x86)\Black Desert Online\character\texture\"

In a sort of code way, you would have to do this:
Code:
void copyFilesBack(FileBlock* filesToPatch, int filesToPatchCount)
{
    char* bdoRootFolder = "C:/Program Files (x86)/Black Desert Online/";
    int i = 0;
    for (i = 0; i < filesToPatchCount; i++)
    {
        if (filesToPatch[i].needPatch)
        {
            copy filesToPatch[i].fileName from filesToPatch[i].originalPath to bdoRootFolder + filesToPatch[i].folderName
        }
    }
}

After that we call:
Code:
 patchMetaFile(filesToPatch, filesToPatchCount, menu1ChosenOption);

Again, here's a simplified version of it:
Code:
void patchMetaFile(FileBlock* filesToPatch, int filesToPatchCount)
{
    int i = 0;

    // This is going to be used to "break" the index file
    long random_folder_num = 1;
    long random_file_num = 60556;

    // Opens the meta file
    FILE* metaFile = fopen("pad00000.meta","rb+");

    // For each file to patch
    for (i = 0; i < filesToPatchCount; i++)
    {
        // Check if it's marked to patch
        if(filesToPatch[i].needPatch)
        {
            // Goes to the right where the file block starts, and skips the first number (the hash)
            fseek(metaFile,filesToPatch[i].metaOffset + sizeof(long), SEEK_SET);

            // Overrites the folder num and file num written there, to random values
            fwrite(&random_folder_num, sizeof(long),1,metaFile);
            fwrite(&random_file_num, sizeof(long),1,metaFile);
        }
    }
    fclose(metaFile);
}
Let me explain what we just did. The game, when it's loading a file, it goes to the pad00000.meta file and it reads the "File Blocks" part and all the other things we did for the "meta_explorer.c" part. To find a file, the game looks for a combination of "file number" and "folder number" so it can know in which position of the arrays "folderNames" and "filesNames" it need to look into. (just like we did in the meta explorer).


If you put some absurd values in there, the game will not be able to tell in which "PAD0xxxx.PAZ" the file it needs is located, so it searches for the file, in the game's root folder (That's why we copied the files there before).

So for the random values, we chose 1 for the "folder num" and "60556" for the file num. It could be anything, really, as long as you are sure that there is no combination of folderNames[random_folder_num] and fileNames[random_file_num], that when it reads it, it gives the right folder name and file name for the file you are looking for.
(If fact, if you simply increased one of the original numbers by one, it would already do the trick, because it wouldn't find a match for the file)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
And there you go!


This is all that my program does, step by step. Only the important parts.

Send me a PM if you need any further explanation on anything.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
That was a lot of work. I hope this can be useful to someone as it is to me.


Enjoy it!
 
Last edited:

Garkin

Avid Affiliate
Joined
Dec 28, 2016
I have modified .bms script in order to fix corrupted files.

If original script detects file as uncompressed (SIZE <= ZSIZE), it will always produce corrupted file. Its because:
  • File could still have header even if data are not compressed (SIZE <= ZSIZE). Header starts with the following 9 bytes: ID 0x6E or 0x6F (byte), compressed size (long), decompressed size (long). ID = 0x6E uncompressed data, 0x6F compressed data.
  • ICE decryption needs decript whole ZSIZE. If you decrypt just a part of it (SIZE), last bytes of output file will be garbage.
Modified script works the same as before for compressed files (SIZE > ZSIZE). If file SIZE <= ZSIZE and SIZE != 0 (yes, there are 0 byte files), file is decrypted to MEMORY_FILE (log MEMORY_FILE OFFSET ZSIZE). If 1st byte suggest that file has header (byte == 0x6E), data from MEMORY_FILE are sent to decompression function which will strip the header (clog). If file doesn't have header, script copies SIZE from decrypted MEMORY_FILE to output file, so the file will have correct size and end of the file won't be corrupted.

blackdesert.bms:
Code:
# Black Desert (script 0.2.4) modified by Garkin
#
#  My modifications are based on BlackFire's modified script:
#  https://www.undertow.club/posts/129096
#
#  Original script:
#  http://aluigi.altervista.org/bms/blackdesert.bms
#
# If you are using Notepad++, change the Language to "Visual Basic" to get a nice color code

quickbmsver "0.7.4"
comtype blackdesert
set BDO_ICE_KEY binary "\x51\xF3\x0F\x11\x04\x24\x6A\x00"
encryption ice BDO_ICE_KEY

get EXT extension

if EXT == "meta"
   putarray 0 0x4000 ""
   putarray 1 0x2000 ""
   putarray 2 0x80000 ""

   get VERSION long
   get pPAZCount long

   for i = 0 < pPAZCount
       get PAZ_NUM long
       get HASH long
       get PAZ_SIZE long

       string PAZ_NAME p= "PAD%05d.PAZ" PAZ_NUM
       putarray 0 PAZ_NUM PAZ_NAME
   next i

   get FILES_COUNT long
   savepos FILE_BLOCKS_START
   xmath FILE_BLOCKS_END "FILE_BLOCKS_START + (FILES_COUNT * 7 * 4)"
   goto FILE_BLOCKS_END

   get FOLDER_NAMES_TOTAL_LENGTH long
   savepos FOLDER_NAMES_START

   log MEMORY_FILE FOLDER_NAMES_START FOLDER_NAMES_TOTAL_LENGTH

   xmath FOLDER_NAMES_END "FOLDER_NAMES_START + FOLDER_NAMES_TOTAL_LENGTH"
   goto FOLDER_NAMES_END

   get FILE_NAMES_TOTAL_LENGTH long
   savepos FILE_NAMES_START

   log MEMORY_FILE2 FILE_NAMES_START FILE_NAMES_TOTAL_LENGTH

   math FOLDER_NAMES_TOTAL_LENGTH -= 8

   math i = 0
   for TMP = 0 < FOLDER_NAMES_TOTAL_LENGTH
       get INDEX_NUM long MEMORY_FILE
       get SUB_FOLDERS long MEMORY_FILE
       get FOLDER_NAME string MEMORY_FILE

       if FOLDER_NAME == ""
           break
       endif

       putarray 1 i FOLDER_NAME
       savepos TMP MEMORY_FILE
   next i

   math i = 0
   for TMP = 0 < FILE_NAMES_TOTAL_LENGTH
       get FILE_NAME string MEMORY_FILE2

       if FILE_NAME == ""
           break
       endif

       putarray 2 i FILE_NAME
       savepos TMP MEMORY_FILE2
   next i

   goto FILE_BLOCKS_START
   for i = 0 < FILES_COUNT
       get HASH long
       get FOLDER_NUM long
       get FILE_NUM long
       get PAZ_NUM long
       get OFFSET long
       get ZSIZE long
       get SIZE long

       getarray PAZ_NAME 0 PAZ_NUM
       getarray FOLDER_NAME 1 FOLDER_NUM
       getarray FILE_NAME 2 FILE_NUM
       string FILE_PATH = FOLDER_NAME
       string FILE_PATH += FILE_NAME

       open FDSE PAZ_NAME 1

       if SIZE > ZSIZE
           clog FILE_PATH OFFSET ZSIZE SIZE 1
       elseif SIZE == 0
           log FILE_PATH 0 0
       else
           log MEMORY_FILE3 OFFSET ZSIZE 1
           get FLAGS byte MEMORY_FILE3
           encryption "" ""
           if FLAGS == 0x6E
               clog FILE_PATH 0 ZSIZE SIZE MEMORY_FILE3
           else
               log FILE_PATH 0 SIZE MEMORY_FILE3
           endif
           encryption ice BDO_ICE_KEY
       endif
   next i


else if EXT == "PAZ"  #If the file extension is .PAZ
   get DUMMY long
   get TOTAL_FILES long
   get FILE_PATHS_TOTAL_LENGTH long

   savepos FILE_BLOCKS_START
   xmath FILE_BLOCKS_END "FILE_BLOCKS_START + (TOTAL_FILES * 4 * 6)"
   log MEMORY_FILE FILE_BLOCKS_END FILE_PATHS_TOTAL_LENGTH

   math i = 0
   for TMP = 0 < FILE_PATHS_TOTAL_LENGTH
       get FILE_PATH string MEMORY_FILE

       if FILE_PATH == ""
           break
       endif

       putarray 0 i FILE_PATH
       savepos TMP MEMORY_FILE
   next i

   for i = 0 < TOTAL_FILES
       get HASH long
       get FOLDER_NUM long
       get FILE_NUM long
       get OFFSET long
       get ZSIZE long
       get SIZE long

       getarray FOLDER_NAME 0 FOLDER_NUM
       getarray FILE_NAME 0 FILE_NUM
       string FILE_PATH = FOLDER_NAME
       string FILE_PATH += FILE_NAME

       if SIZE > ZSIZE
           clog FILE_PATH OFFSET ZSIZE SIZE
       elseif SIZE == 0
           log FILE_PATH 0 0
       else
           log MEMORY_FILE2 OFFSET ZSIZE
           get FLAGS byte MEMORY_FILE2
           encryption "" ""
           if FLAGS == 0x6E
               clog FILE_PATH 0 ZSIZE SIZE MEMORY_FILE2
           else
               log FILE_PATH 0 SIZE MEMORY_FILE2
           endif
           encryption ice BDO_ICE_KEY
       endif
   next i

endif
 
Last edited:

BlackFireBR

Content Creator
Joined
Sep 2, 2013
I have modified .bms script in order to fix corrupted files.

If original script detects file as uncompressed (SIZE <= ZSIZE), it will always produce corrupted file. Its because:
  • File could be still compressed even if SIZE <= ZSIZE. All compressed files starts with the following 9 bytes: ID 0x6E (byte), original size (long), this file size (long).
  • ICE decryption needs decript whole ZSIZE. If you decrypt just a part of it (SIZE), last bytes of output file will be garbage.
I have modified script that if SIZE <= ZSIZE and SIZE != 0 (yes, there are 0 byte files) file is decrypted to MEMORY_FILE (log MEMORY_FILE OFFSET ZSIZE), then it tests 1st byte if file is compressed (byte == 0x6E). If file is compressed, it will be stored from MEMORY_FILE using clog. If file is not compressed, script copies SIZE from decrypted MEMORY_FILE to output file, so the file will have correct size and end of the file won't be corrupted.

blackdesert.bms:
Code:
# Black Desert (script 0.2.4) modified by Garkin
#
#  My modifications are based on BlackFire's modified script:
#  https://www.undertow.club/posts/129096
#
#  Original script:
#  http://aluigi.altervista.org/bms/blackdesert.bms
#
# If you are using Notepad++, change the Language to "Visual Basic" to get a nice color code

quickbmsver "0.7.4"
comtype blackdesert
set BDO_ICE_KEY binary "\x51\xF3\x0F\x11\x04\x24\x6A\x00"
encryption ice BDO_ICE_KEY

get EXT extension

if EXT == "meta"
   putarray 0 0x4000 ""
   putarray 1 0x2000 ""
   putarray 2 0x80000 ""

   get VERSION long
   get pPAZCount long

   for i = 0 < pPAZCount
       get PAZ_NUM long
       get HASH long
       get PAZ_SIZE long

       string PAZ_NAME p= "PAD%05d.PAZ" PAZ_NUM
       putarray 0 PAZ_NUM PAZ_NAME
   next i

   get FILES_COUNT long
   savepos FILE_BLOCKS_START
   xmath FILE_BLOCKS_END "FILE_BLOCKS_START + (FILES_COUNT * 7 * 4)"
   goto FILE_BLOCKS_END

   get FOLDER_NAMES_TOTAL_LENGTH long
   savepos FOLDER_NAMES_START

   log MEMORY_FILE FOLDER_NAMES_START FOLDER_NAMES_TOTAL_LENGTH

   xmath FOLDER_NAMES_END "FOLDER_NAMES_START + FOLDER_NAMES_TOTAL_LENGTH"
   goto FOLDER_NAMES_END

   get FILE_NAMES_TOTAL_LENGTH long
   savepos FILE_NAMES_START

   log MEMORY_FILE2 FILE_NAMES_START FILE_NAMES_TOTAL_LENGTH

   math FOLDER_NAMES_TOTAL_LENGTH -= 8

   math i = 0
   for TMP = 0 < FOLDER_NAMES_TOTAL_LENGTH
       get INDEX_NUM long MEMORY_FILE
       get SUB_FOLDERS long MEMORY_FILE
       get FOLDER_NAME string MEMORY_FILE

       if FOLDER_NAME == ""
           break
       endif

       putarray 1 i FOLDER_NAME
       savepos TMP MEMORY_FILE
   next i

   math i = 0
   for TMP = 0 < FILE_NAMES_TOTAL_LENGTH
       get FILE_NAME string MEMORY_FILE2

       if FILE_NAME == ""
           break
       endif

       putarray 2 i FILE_NAME
       savepos TMP MEMORY_FILE2
   next i

   goto FILE_BLOCKS_START
   for i = 0 < FILES_COUNT
       get HASH long
       get FOLDER_NUM long
       get FILE_NUM long
       get PAZ_NUM long
       get OFFSET long
       get ZSIZE long
       get SIZE long

       getarray PAZ_NAME 0 PAZ_NUM
       getarray FOLDER_NAME 1 FOLDER_NUM
       getarray FILE_NAME 2 FILE_NUM
       string FILE_PATH = FOLDER_NAME
       string FILE_PATH += FILE_NAME

       open FDSE PAZ_NAME 1

       if SIZE > ZSIZE
           clog FILE_PATH OFFSET ZSIZE SIZE 1
       elseif SIZE == 0
           log FILE_PATH 0 0
       else
           log MEMORY_FILE3 OFFSET ZSIZE 1
           get FLAGS byte MEMORY_FILE3
           encryption "" ""
           if FLAGS == 0x6E
               clog FILE_PATH 0 ZSIZE SIZE MEMORY_FILE3
           else
               log FILE_PATH 0 SIZE MEMORY_FILE3
           endif
           encryption ice BDO_ICE_KEY
       endif
   next i


else if EXT == "PAZ"  #If the file extension is .PAZ
   get DUMMY long
   get TOTAL_FILES long
   get FILE_PATHS_TOTAL_LENGTH long

   savepos FILE_BLOCKS_START
   xmath FILE_BLOCKS_END "FILE_BLOCKS_START + (TOTAL_FILES * 4 * 6)"
   log MEMORY_FILE FILE_BLOCKS_END FILE_PATHS_TOTAL_LENGTH

   math i = 0
   for TMP = 0 < FILE_PATHS_TOTAL_LENGTH
       get FILE_PATH string MEMORY_FILE

       if FILE_PATH == ""
           break
       endif

       putarray 0 i FILE_PATH
       savepos TMP MEMORY_FILE
   next i

   for i = 0 < TOTAL_FILES
       get HASH long
       get FOLDER_NUM long
       get FILE_NUM long
       get OFFSET long
       get ZSIZE long
       get SIZE long

       getarray FOLDER_NAME 0 FOLDER_NUM
       getarray FILE_NAME 0 FILE_NUM
       string FILE_PATH = FOLDER_NAME
       string FILE_PATH += FILE_NAME

       if SIZE > ZSIZE
           clog FILE_PATH OFFSET ZSIZE SIZE
       elseif SIZE == 0
           log FILE_PATH 0 0
       else
           log MEMORY_FILE2 OFFSET ZSIZE
           get FLAGS byte MEMORY_FILE2
           encryption "" ""
           if FLAGS == 0x6E
               clog FILE_PATH 0 ZSIZE SIZE MEMORY_FILE2
           else
               log FILE_PATH 0 SIZE MEMORY_FILE2
           endif
           encryption ice BDO_ICE_KEY
       endif
   next i

endif
It works! Thank you so much for this!
I just released an update for PAZ Browser, with the .bms scripts updated to yours:

PAZ Browser v1.4b
- Updated quickbms scripts with the changes made by Garkin: Now all the files that were unreadable after you extract them in the previous versions, are now readable.

Download: paz_browser_v1.4b.zip
 
Last edited:

krpg392

Potential Patron
Joined
Mar 8, 2016
Thank you Grakin for a script and BlackFireBR for a new PAZ Browser. :wink:
now I can extract readable video file. :grin:
QueYOA5.jpg
 

Kitty Ears

Swell Supporter
Joined
Apr 13, 2016
WOW! How cool! Extractor and browser in one? This is an AWESOME update!
I do have a question. Since this is a new exe and wont update my current paz browser or extractor files as older updates did, should I delete the old paz browser and extractor files? if so, how do I know what files need to be deleted?
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
WOW! How cool! Extractor and browser in one? This is an AWESOME update!
I do have a question. Since this is a new exe and wont update my current paz browser or extractor files as older updates did, should I delete the old paz browser and extractor files? if so, how do I know what files need to be deleted?
I know, right!?
I thought you were going to be excited. I made it specially for people like you because I know how much you would find this useful ^^

Anyway, you don't have to delete anything, just extract everything from the .zip file to your "paz" folder and all the files that need to be overwriten will be. And then just run "paz_browser.exe" later (I kept the same .exe name)
You can delete all the files from the "File Extractor" that are in your "paz" folder, and then extract the files in the new "paz_browser.zip".
 
D

Deleted member 31139

It works! Thank you so much for this!
I just released an update for PAZ Browser, with the .bms scripts updated to yours:

PAZ Browser v1.4b
- Updated quickbms scripts with the changes made by Garkin: Now all the files that were unreadable after you extract them in the previous versions, are now readable.

Download: paz_browser_v1.4b.zip


Hi. Thanks for all the workmanship and hard work you've done with your programs. Just a question, your programs like resorepless and paz browser will have support in the BDO SA?
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Hi. Thanks for all the workmanship and hard work you've done with your programs. Just a question, your programs like resorepless and paz browser will have support in the BDO SA?
Thank you for your support, it really makes me glad that you enjoy my work :)
Yes, everything works fine in BDO SA, I even played the closed beta and it worked perfectly
 

Users who are viewing this thread

Top


Are you 18 or older?

This website requires you to be 18 years of age or older. Please verify your age to view the content, or click Exit to leave.