Black Desert Online Modding Tools

timmytimtim

Potential Patron
Joined
Jul 9, 2016
Hi guys,

What tool would i currently use to display different class armors and or underwear on my characters? Is there an up to date step by step guide for challenged people like me?
 

Kera

Vivacious Visitor
Joined
Mar 29, 2016
Since I started using the metainjector I stopped crashing to desktop in heidel.

But I found a new problem. It's been a few times now but I get these strange graphical glitches when a valkyrie is wearing Acher Guard armor, atleast the 2 times I saw it it was a valkyrie. I'll try to screenshot it next time.

Oh yah I don't seem to get the gltich when it is my own valkyrie wearing the armor. (armor swap and pearl shop, dont know if it would be the same if I bought it for real)
Without knowing specifically what you saw, if you are seeing black artifacts on the textures, that generally means that the replacement version did not have its mipmaps saved correctly. Used to have that problem with some textures in resorep. It generally does not show up on your own character because your own character uses the highest mip (which is affected by the problem).
 

Jensen

Potential Patron
Joined
Jun 4, 2016
Download the Le Vladian underwear textures removed in the post above and follow the instructions I mentioned above.
This should fix your problem (Assuming you don't want to see the Le Vladian underwear
still nothing, get the same problem, when i press show underware i get the holes for some reason, maybe im missing a body texture or something ? idk, if its too much hassle, im fine with it as is
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
Hi guys,

What tool would i currently use to display different class armors and or underwear on my characters? Is there an up to date step by step guide for challenged people like me?
It deppends on what you are talking about.
Are you talking about customize armors (like cutting some parts) or completely swapping the armors?
Check one of those two theads for each option, in case you haven't checked them yet:
Black Desert - How to Swap Armors Costumes and Weapons
Black Desert Online armor/costume mods

For the last of, you are going to have to use Ray Wings tool that it's on the first page of this thead, as well Meta Injector Reloaded


still nothing, get the same problem, when i press show underware i get the holes for some reason, maybe im missing a body texture or something ? idk, if its too much hassle, im fine with it as is
It's not a missing body texture, it's because of textures like these:
pvw_00_uw_0034_01_ao.jpg

It's a texture that tells which parts should be "cut out" of the body., the green is exactly the parts from the Le Vladian Underwear that is missing in your character.

But if you are using my blank yellow texture "pvw_00_uw_0034_01_ao.dds", this shouldn't be a problem.
If you want it to just don't show your underwear at all. Use Armor Swapping Essentials and Remove Underwear Only.

Amazing!!
All mod has been working
Thank you BlackFireBR!
You're welcome :)
 

Attachments

Last edited:

Kenith

Avid Affiliate
Joined
Mar 7, 2016
thanx for updates

v1.0
phw_00_uw_0031_02_dec.dds - success

but
v1.1c
not found at 2/2


1.png
 

Jensen

Potential Patron
Joined
Jun 4, 2016
It's not a missing body texture, it's because of textures like these:
View attachment 55435
It's a texture that tells which parts should be "cut out" of the body., the green is exactly the parts from the Le Vladian Underwear that is missing in your character.

But if you are using my blank yellow texture "pvw_00_uw_0034_01_ao.dds", this shouldn't be a problem.
If you want it to just don't show your underwear at all. Use Armor Swapping Essentials and Remove Underwear Only.
think i found the problem

somethings missing ? idk
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
Okay, let me try to begin to explain how the code for Meta Injector works and how it is divided.

The code is actually already divided in "menu stuff", "file/folder handling", and the actual "patch the game" part.

  • 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.

Forget about everything in this file, file checking, etc, it's not necessary. Just make sure your C# calls that function, which I'm going to explain now.


void runPatcher():
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:


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

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:


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:


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:


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

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)


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\"

(You have to find a way to find that out using C# .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 in C# 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.

Let me know if you need any further explanation on anything, and let me know about your progress.

Best of luck to you.
 
Last edited:

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
thanx for updates

v1.0
phw_00_uw_0031_02_dec.dds - success

but
v1.1c
not found at 2/2


View attachment 55436
Thank you for pointing that out! The tool wasn't saving the bytes properly. (apparently strcpy doesn't work very well for hex numbers)

New version 1.2 comes with that fixed

Meta Injector Reloaded v1.2
- Fixed some files failing to patch in "Part 2" in v1.1, but not in v1.0​
 
Last edited:

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
Meta Injector Reloaded v1.3
- Texture files are now automatically copied to the right place even if the user misplace it.
- After the patch is done, if the user doesn't choose the option to "not do anything", the program scans the game's root folder and sub-folders and if it finds a .dds file, it copies to the place most of the textures should be (character\texture) that way, preventing people from misplacing the textures
- Significant changes in the source code structure (it still does the same thing, but now it's just more divided)

 

Kera

Vivacious Visitor
Joined
Mar 29, 2016
The pad00000.meta file in the patcher resources for NA does not match the one in my main game folder (and is significantly smaller). Is that intended?

Also, having come from the resorepless mod, and trying to mimic resorep with this mod primarily, I noticed it does not back up the original meta file. Ended up needing to do a repair to get back to a runnable state after monkeying with some files. I assume this is because it assumes it has a correct one in the resources file, but since that is not the case for me I ended up broken.
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
The pad00000.meta file in the patcher resources for NA does not match the one in my main game folder (and is significantly smaller). Is that intended?

Also, having come from the resorepless mod, and trying to mimic resorep with this mod primarily, I noticed it does not back up the original meta file. Ended up needing to do a repair to get back to a runnable state after monkeying with some files. I assume this is because it assumes it has a correct one in the resources file, but since that is not the case for me I ended up broken.
Yeah, they are old meta files, from the times that bdmod.exe used to work on them. They are just used so the program finds out which bytes it has to replace in your bigger and updated meta file.

About the backup. I forgot to add that feature in this tool Too much other stuff to worry about. I'll include it later in the next version.
 

Dawn

Potential Patron
Joined
Mar 16, 2016
Could you explain which files I should be putting into files_to_patch? I've tried numerous .dds files, and the entire character/texture/___.dds folders, but the end result is always 0 files patched with no difference in game (I've moved everything back to their proper locations after patching)


 

Kera

Vivacious Visitor
Joined
Mar 29, 2016
Yeah, they are old meta files, from the times that bdmod.exe used to work on them. They are just used so the program finds out which bytes it has to replace in your bigger and updated meta file.

About the backup. I forgot to add that feature in this tool Too much other stuff to worry about. I'll include it later in the next version.
Gotcha, thanks for the clarification. I figured the push to get it released was the reason the backup feature was missing. Made my own local backup for now!
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Location
Brazil
Could you explain which files I should be putting into files_to_patch? I've tried numerous .dds files, and the entire character/texture/___.dds folders, but the end result is always 0 files patched with no difference in game (I've moved everything back to their proper locations after patching)


Download the new version v1.4, version 1.3b was faulty, thanks for showing me.


Meta Injector Reloaded v1.4
- Implemented a new backup system that every time it doesn't find a backup with the same size of your current meta file, it creates a backup with the following format: pad00000[YYY-MM-DD].meta.backup. (You have to manually restore it thought)
- Fixed issue of all files failing from v1.3b

 

dx_treme

Potential Patron
Joined
Mar 10, 2016
Black Desert Online Texture Extractor
A tool that uses quickbms to extract only the texture files from your .PAZ files.

Download Link:

Source Code: Source Code - Texture Extractor.zip
Instructions:
1 - Extract this zip file to your PAZ folder.

2 - Run "Texture Extractor.exe"

3 - Press 1 if you want to create a folder called "extracted_textures" in your "PAZ" folder and extract all the textures there.

4 - Press 2 if you want to specify another folder to extract the textures.

Note: After the textures are extracted, a file called "log-textures.txt" will be created in your PAZ folder which contains the relation between all .PAZ files number and texture names, this can be useful if you want to extract only a specific file in the future without having to extract all .PAZ files again
hi BlackFireBR BlackFireBR , can u make the extractor with another option to extract excel files? like .xlsx .xlsm .bexcel in stringtable folder.
i need tht for translation purpose. Thx in advance :D
 

liritian

Potential Patron
Joined
Jul 9, 2016
Hello friends, I am using Google translate, I am very grateful you develop a range of tools, but I hope you can develop in the near future an intuitive view PAZ file content browser.
I hope this tool can borrow from the PAZ can not decrypt the file contents can be intuitive to find what you want to modify DDS texture file name with path instead of a file and then decrypt PAZ depicting DDS map to view it.
But anyway I am still grateful for your dedication and! Thank you!
 

latuel

Potential Patron
Joined
Mar 24, 2016

Karlstein Armor - pew_00_ub_0033.dds
Plum Panty - pkw_00_uw_0001.dds
Can't patched.

Always thanks to BlackFireFR
 

latuel

Potential Patron
Joined
Mar 24, 2016
Who's know kibelius wing's texture name?
i wanna remove kibelius Wings.
I don't want to change the xml. because some bugs KR Server(New Clothes is Broken).
 

irmof54

Potential Patron
Joined
Jul 5, 2016
Thanks a lot for making such a nice program!

By the way, do you know how to delete mods and restore its original state?
 
Top Bottom