Black Desert Online -Nude body, Costume Mods for Meta Injector by Suzu (3 Viewers)

Status
Not open for further replies.

suzu ( 鈴

Content Creator
3D Artist
Modder
Joined
Mar 27, 2016
Yes, I did that like your older mods, but I just checked those textures, and seems like the only modified texture was the tattoo. Or did I missed something ?

EDIT : OH NVM, Now it works ... weird

In this modified Efferia marina suit, the texture is changed only for tattoos.
What is important is the PAC file containing the polygon mesh
Please execute meta injector after placement
META INJECTOR
This tool allows you to put modified files inside the game.​

What does it do exactly:
It makes the game look for the files in the Black Desert root folder instead of looking inside the compressed PADxxxxx.PAZ files, so you can use modified files.
meta-1-gif.59624

Download Link: Meta Injector

Source Code : Included in the .zip file, under "source_codes"
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.
Older versions: Click here

Instructions:
1 - Extract the zip file to your "PAZ\" folder, which is located inside your installation folder.
(Note: For steam users it's usually under: "C:\Program Files (x86)\Steam\steamapps\common\Black Desert Online\").
2 - Put all your modified files in the "files_to_patch" folder.
3 - Run "meta_injector.exe"
4 - Follow the screen instructions.

Do this every time a game update is released:
- Restore you last backup using the tool, before you update the game, otherwise you will get a "corrupted files" message from the launcher.
If that happens, close the launcher, use the "Restore a backup" option, and open the launcher again.

Uninstall:
Run meta_injector.exe and select the "Restore backup" option.
The latest ones are the most up to date.
 

hyko23

Casual Client
Joined
Nov 11, 2016
In this modified Efferia marina suit, the texture is changed only for tattoos.
What is important is the PAC file containing the polygon mesh
Please execute meta injector after placement
yep did that again and it worked thank you for the help ! <3
 

suzu ( 鈴

Content Creator
3D Artist
Modder
Joined
Mar 27, 2016
The basis of my modification
Modifications that can be done in the game
For example I will not remodel dyeing
Because I wish that a black desert would last long.
 

Ana

Potential Patron
Joined
Aug 19, 2012
Why do not you do it yourself for practice? Very easy:rolleyes:
Is it? I'm sorry, I'm not entirely computer illiterate, but I don't even know where to start with this stuff. If it's just a matter of modifying (cropping) out textures then I can probably manage, but as soon as you get to meshes I'm completely out of my depth. I'll look at both of yours, the new one with the shirt and the old one with the skirt, and see if I can't figure out how to smash them together.

Once again, thank you for all the fine work you do. You didn't need to give me any of your time, so please know I appreciate that you did. :)
 

pyruk4

Vivacious Visitor
Joined
Aug 15, 2014
File placement and folder tree
· Daum \ Black Desert \ character \ texture

Put all modified textures here.
· Daum \ Black Desert \ character \ model

Put all modified holder in pac files here.
· Daum \ Black Desert \ character

Put partcutdesc.xml here


· Daum \ Black Desert \ paz
Place Meta Injector Reloaded.exe here



· Daum \ Black Desert \ paz \ files_patch \ character

Put partcutdesc.xml here

· Daum \ Black Desert \ paz \ files_patch \ character \ texture

Put all the texture here as well
・Daum \ Black Desert \ paz \ files_patch \ character \ model

Put all the holder in pac files here as well

Arranged in two placeis

Is it also inclued BlackFireBr files?
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Is it also inclued BlackFireBr files?
In reality, all you need to do is place all of suzu's files in the "files_to_patch\" folder and run "meta_injector.exe". The tool should take care of the rest.
About my files, make sure you run "resorepless.exe" before you run "meta_injector" since suzu included his own partcutdesc.xml file, witch would be overwritten by resorepless
 

pyruk4

Vivacious Visitor
Joined
Aug 15, 2014
In reality, all you need to do is place all of suzu's files in the "files_to_patch\" folder and run "meta_injector.exe". The tool should take care of the rest.
About my files, make sure you run "resorepless.exe" before you run "meta_injector" since suzu included his own partcutdesc.xml file, witch would be overwritten by resorepless
Ah ok thx
 

suzu ( 鈴

Content Creator
3D Artist
Modder
Joined
Mar 27, 2016
I think that the vixen costume tail would work if you manage to add it to any costume. Personally i dont know the file of the tail, the wings neither.
Since the tail is transparently processed, it is limited to a mesh that is transparent in the same way when exchanging.
 

alex993

Potential Patron
Joined
Mar 1, 2017
When meta injcetor finish nothing happens i think i did everything fine idk what to do
 

Attachments

  • 231321321.png
    231321321.png
    54.7 KB · Views: 115
Status
Not open for further replies.

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.