- 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.
Download Link: Meta Injector
Source Code : Included in the .zip file, under "source_codes"
Older versions: Click hereThe 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:
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.Code:getAllFiles(char* pathToFiles, char* extFilter, long* filesCount)
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:
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:File Blocks End = File Blocks Start + (Files Count * (size of one registry))
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:
toCode:printf("\nSearching for hash: %ld\n", multiplemodeldesc_hash);
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:
All we did, was reading this: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++; }
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:
I simply used the code posted here:Code:/// ******************* FOLDER NAMES DECRYPTION **********************
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
We are basically puting each "folder name" into each position of the array "folderNamesArray".Code:/// FOLDER NAMES FILLING
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:
We basically copy the content of each "folderNamesArray", to the "fileBlocks" structureCode:// Assigns the right File Name from the fileNameArray to the right File Block, based on the File Num
(We have to do this because the order from "folderNamesArray" is different from what we read in the "File Blocks" section)
The next thing:
andCode:/// ******************* FILE NAMES DECRYPTION **********************
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.Code:/// FILE NAMES FILLING
In the end I do this:
But it's just to make searching in this fileBlocks faster, it's not really necessary.Code:qsort(fileBlocks,metaFileInfo->fileBlocksCount,sizeof(FileBlock),compare);
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:
toCode:printf("\nSearching for files in the meta file...");
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:
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:Code:copyFilesBack(filesToPatch, filesToPatchCount);
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:
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).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); }
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.
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.