Black Desert Online Modding Tools (2 Viewers)

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Obrigado pela resposta rápida. A guilda LostinBR (acho que unica br ainda existente no coreano) está enlouquecida com a possibilidade de voltar a traduzir o jogo haha). Bom, entendi em partes. Resumindo, entendi o que tem que fazer, mas não muito bem o passo a passo. Primeiro, não sei inverter o meta_explorer.c para faze-lo re-rencryptar o arquivo. Poderia me dar uma explicação melhor ou até mesmo me passar invertido?
Então, eu acredito que, pelo o que me falaram, pra encryptar é só usar a função:
Code:
ice_key_ecrypt(ik, ctext, ptext);
ao invés da
Code:
ice_key_decrypt(ik, ctext, ptext);
Usando a mesma key e tudo igual. Deve funcionar.

Ou senao, dá pra usar o próprio quickbms com o argumento -r, que ele re-encrypta um arquivo.

Se achar esse segundo mais fácil e quiser saber como faz, me fala que eu eu to de um toque.
 

BlackFireBR

Content Creator
Joined
Sep 2, 2013
Yes, before each string, there is a data that tells you how many bytes of each string, I tried to increase, but it crashed the same way. Files that I extracted and pointed at the root with the meta injector, I can change without needing to change the size in Meta, correct?
Yeah, technically it will just use the extracted file, without checking in the .meta file.
I might have some free time this weekend and maybe I'll give it a shot on that whole "repacking" thing for you.
 

Saga

Potential Patron
Joined
May 12, 2016
Aaaaah Yeaaaah. It was OK xDDD. I do not know what I'd done wrong the other time, but I tried to increase the size of the string from the Start button 03 to 04, and this time the game read perfectly. Thanks for the tip. It's going to be really hard, but I plan to re-translate at least the interface. As soon as someone launches an adequate extraction and repacking tool, it will look much better.
tela start.JPG
 

AtmaDarkwolf

Potential Patron
Joined
Jul 17, 2017
I do not get this problem if I install before detect. If client detect then you need use resorepless option 8 prevent file re-check before you open client. If you do this it will patch like normal. Then you reinstall mods. It work for me just today.
not here, with resorepless options does not solve issue. Still get corrupt file. Even knowing patch awaits, uninstalling everything completely, and patching will result in corrupt files. I figure since only a few people have the issue, its related to both the programs being used at once(Resorepless and meta injector for separate files)

The only way I can avoid corrupt file recheck (and a 2nd corrupt file) is to cut all mods out of the folders, (after uninstalling them totally) and deleting the files in appdata.(one or the other won't do, need to do both) and even then it will have to do file check. (Even with tiny micro patches)
 

Kitty Ears

Swell Supporter
Joined
Apr 13, 2016
not here, with resorepless options does not solve issue. Still get corrupt file. Even knowing patch awaits, uninstalling everything completely, and patching will result in corrupt files. I figure since only a few people have the issue, its related to both the programs being used at once(Resorepless and meta injector for separate files)

The only way I can avoid corrupt file recheck (and a 2nd corrupt file) is to cut all mods out of the folders, (after uninstalling them totally) and deleting the files in appdata.(one or the other won't do, need to do both) and even then it will have to do file check. (Even with tiny micro patches)

I use Meta Injector and Resorepless. I get a recheck sometimes, but not often. And it is usually because I forgot to manual backup. It is strange you are having it happen every time. :frown:
 

picts

Vivacious Visitor
Joined
Feb 14, 2017
[QUOTE =“BlackFireBR,post:129093,member:2727”]
META INJECTOR
該工具允許您將修改後的文件放在遊戲中。​

它做什麼呢?
它使遊戲查找黑沙漠根文件夾中的文件,而不是查看壓縮的PADxxxxx.PAZ文件,因此您可以使用修改的文件。
meta-1-gif.59624

下載鏈接:Meta注射器

源代碼:包含在.zip文件中的“source_codes”下
[SPOILER =“源代碼的詳細說明”]
你需要理解的唯一的文件(而不是全部)是這些文件:
  • main.c:處理菜單
  • file_operations.c:處理打開的文件,計數文件,複製文件,檢查文件存在等等。
  • patcher.c:所有的魔法。
  • 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 a file, 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)
-------------------------------------------------- -------------------------------------------------- ---------------------------
你去吧!

這就是我的程序所做的一切。只有重要的部分。

如果你需要任何進一步的解釋,給我一個下午。
[/ SPOILER]
舊版本: 點擊這裡

說明:
1 - 將zip文件解壓縮到位於安裝文件夾內的“PAZ \”文件夾。
(注意:對於蒸汽用戶,通常情況如下:“C:\ Program Files(x86)\ Steam \ steamapps \ common \ Black Desert Online \”)。
2 - 將所有修改的文件放在“files_to_patch”文件夾中。
3 - 運行“meta_injector.exe”
4 - 按照屏幕說明進行操作。

每次發布遊戲更新時都要執行此操作:
- 在更新遊戲之前,使用該工具恢復上次備份,否則您將從啟動器中獲取“ 損壞的文件 ”消息。
如果發生這種情況,關閉啟動器,使用“恢復備份”選項,再次打開啟動器。

卸載:
運行meta_injector.exe並選擇“還原備份”選項。
最新的是最新的。[/ QUOTE]
[QUOTE =“alcaster4242,post:129209,member:23038”]尼斯,一直在尋找這個。[/ QUOTE]
 

picts

Vivacious Visitor
Joined
Feb 14, 2017
問你,我使用了meta_injector轉換改過的衣服之前使用沒有問題,最近使用的手有一小部分或身體的一部分消失失 我全部安裝新的安裝 仍然如此 各位先進的知道這是為什麼
我玩黑沙漠台灣版的麻煩指教 非常感謝〜
 

picts

Vivacious Visitor
Joined
Feb 14, 2017
[QUOTE =“picts,post:157486,member:92565”]問你,我使用了meta_injector轉換改過的衣服之前使用沒有問題,最近使用的手有一小部分或身體的一部分消失失我全部安裝新的安裝仍然如此各位先進的知道這是為什麼
我玩黑沙漠台灣版的麻煩指教非常感謝〜[/ QUOTE]
 

Kitty Ears

Swell Supporter
Joined
Apr 13, 2016
Anyone have a clue how to edit the facial expressions? They are so awful, especially on Sorceress (her sad face is so ugly and makes no sense). Not sure what method Pearl Abyss used to create the expressions.
 

Vallil

Swell Supporter
Joined
Sep 13, 2013
Anyone have a clue how to edit the facial expressions? They are so awful, especially on Sorceress (her sad face is so ugly and makes no sense). Not sure what method Pearl Abyss used to create the expressions.
i think we can all agree to this...
 

picts

Vivacious Visitor
Joined
Feb 14, 2017
[QUOTE =“BlackFireBR,post:129096,member:2727”]
模糊的東西
本節保留收集我迄今為止能夠發現的關於我的工具從遊戲中使用的文件以及我所做的一些有用的小程序的所有信息。

META文件
pad00000.meta文件位於您的PAZ文件夾中,它是一個索引文件。它告訴遊戲有多少個.PAZ文件,哪個文件是哪個.PAZ,以及它們的偏移量,大小等。當遊戲需要加載任何文件時,這是它查找它在哪裡。

該文件中的文件名和文件夾名稱使用ICE加密密鑰進行加密(可以在下面找到),而.PAZ內的實際文件用黑沙漠的特定算法進行壓縮(在quickbms中它是壓縮算法85),它也使用相同的ICE密鑰加密。

其餘的基本上只是整數,你可以通過讀取4個字節的塊來讀取它們的值,它們沒有加密或任何東西。

這是pad00000.meta文件文件的結構:
注意:該圖像中顯示的數字有所改變,但文件的結構仍然相同
[ATTACH =滿] 60263 [/ ATTACH]
你應該關注的最重要的事情是我稱之為“文件塊”的結構,這是告訴大多數關於這些文件的信息。它有這些字段:
HASH | FOLDER_NUM | FILE_NUM | PAZ_NUM | OFFSET | ZSIZE | SIZE |​

HASH:標識文件的唯一編號,每個文件都有自己的哈希號,您將永遠不會在該文件中找到相同的數字兩次。(獨特)

FOLDER_NUM:遊戲的每個文件夾都被分配一個數字,如果你有一個文件夾名稱數組,你可以使用這個數字,你可以使用這個數字找到數組中對應於我們正在尋找的文件夾的正確位置。

FILE_NUM:與文件夾num相同的東西。每個文件都有一個數字,從0開始,以您在元文件開頭的PAZ_COUNT變量中讀取的數字結束。

PAZ_NUM:該文件所在的.PAZ的編號。例如,如果此數字為1,則文件位於文件PAD00001.PAZ中。

OFFSET:存儲文件的.PAZ中的第一個字節

ZSIZE:當文件仍被壓縮時,文件的大小是在.PAZ文件中讀取的字節數,以便提取完整的壓縮文件。

SIZE:文件的大小,一旦解壓縮。

這是pad00000.meta文件如何使用十六進制編輯程序打開它的樣子:
meta1-jpg.55594



這是一個元文件的簡化版本,只有3個.PAZ文件,3個文件夾和4個文件。這是元文件的基本結構。官方的meta文件遵循相同的模式,它只有更多的文件(更多)。

在我們繼續之前,這是我們需要澄清的幾件事:
- 每個BIG塊代表4個字節。
- 每個小塊表示一個字節。
- 具有白色背景的紅色塊意味著這是加密的,它們不會像這樣,除非您解密這些字節。
- DUMMY is the client version

This is the same file as the first image, but with all the numbers converted from hex to dec:
meta3-jpg.55599


Now let's understand how the game knows which file to load:

Let's pick the first FILE_HASH: 631490897:

- We can see it belongs to a file, which folder num is 2
- When we search for folder num = 2 in the "folders_part" of the file, we find that the name of the folder is "character/" ,so the file is in the "character" folder".
- next, we know the file number is 0, so the game will look in the "file names part" and when it finds the first '\0', if will know that it's the end of the file name we are looking for. (If file num was == 2, we would have to count 2 '\0', and everything that is between the 2nd and the 3rd '\0' is the file name)

So we discovered that the file with the hash 631490897 is "multiplemodeldesc.xml" and it's located in "character/"

One thing you should be aware of, when converting from hex to dec.
For example, the FILE_HASH we just used: 631490897
When you convert it to hex, you get this:
hex-jpg.55600


But when you look at the meta file, you are not going to find 25 A3 C9 51, instead you need to find 51 C9 A3 25
hex2-jpg.55601


So everytime you convert to hex, remember to change the order in blocks of 2.

Warning, don't do this mistake:
25 A3 C9 51 -> 15 9C 3A 52
it's:
25 A3 C9 51 - > 51 C9 A3 25


PAZ File
The .PAZ files are all named like this: PAD0xxxx.PAZ and they contain the actual files of the game, also compressed and encrypted, as usual.

This is how a .PAZ file is structured:
paz_file-jpg.56446


- Light-red colored means encrypted.


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

Bonus: ICE Decryption for Black Desert Online in C:
(Credits to Miau Lightouch)

Code:
// ice cipher source: http://www.darkside.com.au/ice/index.html
#include "ice.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

int main (){

    FILE *fp, *fout;

    int count = 7730712; // copy from folder/file name size
    int count2 = 0;
    fp = fopen("filename.dat","rb"); // encrypted filename block (I dump to a file for test.)
    fout = fopen("output.dat", "wb"); // the file to write out the decrypted text to file.
    uint8_t *ctext = (uint8_t *) malloc(count); // alloc to meet our need.
    uint8_t *ptext = (uint8_t *) malloc(count);


    if (fp==NULL) {
        perror ("Error opening file");
    }
    else {
        // count2 is the real bytes count that program read out, but should be same as "count".
        count2 = fread(ctext, 1, count, fp);
    printf ("Total number of bytes read: %d\n", count2);

    const uint8_t *s = "\x51\xF3\x0F\x11\x04\x24\x6A\x00"; // it's a magic, you know.

    ICE_KEY *ik = ice_key_create(0); // init the key
    ice_key_set(ik, s); // set the key

    long cuts = count2/8;
    while (cuts--){
        ice_key_decrypt(ik, ctext, ptext); // key, in (encrypted text), out (decrypted text)
        ctext +=8;
        ptext +=8;
    }

    ptext -= count2; // reset the pointer back to the begining.

    fwrite(ptext, 1, count, fout);
    fclose(fout);
    fclose(fp);
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Quickbms Script Updated and Commented
This is a modified version of the original script found at http://aluigi.altervista.org/bms/blackdesert.bms
The problem with that old script is that sometimes it produced some corrupted files, when extracted.
Thanks to Garkin whith this script modification, we can extract all files and not having then corrupted.

I commented the script line by line so you can understand better what's going on.

Remember, if you have any doubt on why we are doing this, please refer to this image:
View attachment 60263
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"

# Defines the compression type that will be used, when extracting a file
comtype blackdesert

# Sets the encryption key we are going to use later
set BDO_ICE_KEY binary "\x51\xF3\x0F\x11\x04\x24\x6A\x00"
encryption ice BDO_ICE_KEY

# gets the extension of the opened file
get EXT extension

#If the file extension is .meta
if EXT == "meta"
# pre-allocation, improves speed (doesn't matter if the values are bigger)
   putarray 0 0x4000 ""   # Array that is going to store the PAZs names
   putarray 1 0x2000 ""   # Array that is going to store the folder names
   putarray 2 0x80000 ""  # Array that is going to store the file names

   # Reads the first 4 bytes (long) of the meta file and stores it in the variable VERSION. (In C: fread(&version,sizeof(long),1,metaFile);)
   get VERSION long
 
   # Reads how many PAZ files your game has. (In C: fread(&pPAZCount,sizeof(long),1,metaFile);)
   get pPAZCount long

   # Paz files informations
   for i = 0 < pPAZCount
       get PAZ_NUM long
       get HASH long
       get PAZ_SIZE long

      # Using the number stored in PAZ_NUM with 5 digits,creates a string that will hold the complete .PAZ file name (E.g.:PAD00001.PAZ) (In C: sprintf(paz_name,"PAD%.5d.PAZ",paz_num);)
       string PAZ_NAME p= "PAD%05d.PAZ" PAZ_NUM
 
      # Stores in the array 0 in the position [PAZ_NUM], the string we just created (In C: array0[paz_num] = paz_name;)
       putarray 0 PAZ_NUM PAZ_NAME
   next i

   # Reads how many files your game has. (In C: fread(&files_count,sizeof(long),1,metaFile)
   get FILES_COUNT long
 
   # Saves the current position in the file in a variable. This position is the beginning of the blocks that have the following format: HASH|FOLDER_NUM|FILE_NUM|PAZ_NUM|OFFSET|ZSIZE|SIZE # (In C: long file_blocks_start = ftell(metafile);)
   savepos FILE_BLOCKS_START
 
   # Calculates the position which is where the file blocks ended (each file block has 28 bytes (7 * sizeof(long)) (In C: long file_blocks_end = (current pos - 7 * sizeof(long));)
   xmath FILE_BLOCKS_END "FILE_BLOCKS_START + (FILES_COUNT * 7 * 4)"
 
   # Moves the file pointer to the end of the File Blocks, skipping the whole File Blocks section for now
   goto FILE_BLOCKS_END

   # Gets the total length of the strings that are comming next, that are the folder names strings (In C: fread(&folder_names_total_length,sizeof(long),1,metaFile);)
   get FOLDER_NAMES_TOTAL_LENGTH long
 
   # Saves the position where the folder names strings start (In C: long folder_names_start = ftell(fp);)
   savepos FOLDER_NAMES_START
 
   # Saves all the next "FOLDER_NAMES_TOTAL_LENGTH" bytes, starting from the offset "FOLDER_NAMES_START" in a temporary memory called MEMORY_FILE (In C: fread(memory_file,1,folder_names_total_length,metaFile);)
   log MEMORY_FILE FOLDER_NAMES_START FOLDER_NAMES_TOTAL_LENGTH

   # Calculates which byte the folder names strings end (In C: long folder_names_end = folder_names_start + folders_name_total_length;)
   xmath FOLDER_NAMES_END "FOLDER_NAMES_START + FOLDER_NAMES_TOTAL_LENGTH"
 
   # Goes to that position (In C: fseek(metaFile,folder_names_end,SEEK_SET);)
   goto FOLDER_NAMES_END

   # Gets the total length of the strings that are comming next, that are the file names strings (In C: fread(&file_names_total_length,sizeof(long),1,metaFile);)
   get FILE_NAMES_TOTAL_LENGTH long
 
   # Saves the position where the file names strings start (In C: long file_names_start = ftell(fp);)
   savepos FILE_NAMES_START

   # Saves all the next "FILE_NAMES_TOTAL_LENGTH" bytes, starting from the offset "FILE_NAMES_START" in a temporary memory called MEMORY_FILE2 (In C: fread(memory_file2,1,file_names_total_length,metaFile);)
   log MEMORY_FILE2 FILE_NAMES_START FILE_NAMES_TOTAL_LENGTH

   # Don't know why, but it ignores the last 8 bytes of the folder names string (2 irrelevent longs, maybe?)
   math FOLDER_NAMES_TOTAL_LENGTH -= 8

   # Reads the string which has all the folder names, assigning each folder name to a different position in the array 1 (folders array)
   math i = 0
   for TMP = 0 < FOLDER_NAMES_TOTAL_LENGTH
       get INDEX_NUM long MEMORY_FILE        # Reads from the MEMORY_FILE the the index number of the current folder
       get SUB_FOLDERS long MEMORY_FILE        # Reads from the MEMORY_FILE the number of subfolder of the current folder
       get FOLDER_NAME string MEMORY_FILE    # Reads from the MEMORY_FILE a folder name, as string, until a '\0' is found. E.g: "character/"

      # If no name was read, means that we reached the end of the folder names
       if FOLDER_NAME == ""
           break
       endif

      # Stores the folder name, in the position i of the array 1 (folders array) (In C: array1[i] = folder_name;)
       putarray 1 i FOLDER_NAME
 
      # Updates TMP with the current position we are going to read now in MEMORY_FILE, this is just so the "for" condition stops when we reach "FOLDER_NAMES_TOTAL_LENGTH"
       savepos TMP MEMORY_FILE
   next i

   math i = 0
   # Reads the string which has all the file names, assigning each file name to a different position in the array 2 (files array)
   for TMP = 0 < FILE_NAMES_TOTAL_LENGTH
      # Reads from the MEMORY_FILE a file name, as string, until a '\0' is found.
       get FILE_NAME string MEMORY_FILE2
 
      # If no name was read, means that we reached the end of the file names
       if FILE_NAME == ""
           break
       endif
 
      # Stores the folder name, in the position i of the array 1 (folders array) (In C: array1[i] = file_name;)
       putarray 2 i FILE_NAME
 
      # Updates TMP with the current position we are going to read now in MEMORY_FILE
       savepos TMP MEMORY_FILE2
   next i
 
   # Now we are going to extract the files, combining the information we find in the file blocks, with the arrays filled with the file and folder names

   # Goes back to the beginning of the file blocks
   goto FILE_BLOCKS_START
 
 
   # For each File Block
   for i = 0 < FILES_COUNT
       get HASH long        # Gets the unique indentifier of this file
       get FOLDER_NUM long  # Gets the index in the array 1 (folders array) which has the folder name from this file
       get FILE_NUM long    # Gets the index in the array 2 (files array) which has the file name from this file
       get PAZ_NUM long        # Gets the number of the .PAZ file that the file is located
       get OFFSET long        # Gets the offset inside the .PAZ file specified which the file starts
       get ZSIZE long        # Gets the compressed size of the file
       get SIZE long        # Gets the uncompressed size of the file

      # Gets the PAZ name at the position [PAZ_NUM] of the array 0 (PAZ names array) and stores is in the PAZ_NAME variable (In C: paz_name     = array0[paz_num])
       getarray PAZ_NAME 0 PAZ_NUM
 
      # Gets the folder name at the position [FOLDER_NUM] of the array 1 (folders array) and stores is in the FOLDER_NAME variable (In C: folder_name = array1[paz_num];)
       getarray FOLDER_NAME 1 FOLDER_NUM
 
      # Gets the file name at the position [FILE_NUM] of the array 1 (folders array) and stores is in the FILE_NAME variable (In C: file_name     = array2[paz_num];)
       getarray FILE_NAME 2 FILE_NUM
 
      # Creates a string and concatenates folder name with the file name strings, so we get the full path to the file. Eg: "character/multiplemodeldesc.xml"
       string FILE_PATH = FOLDER_NAME
       string FILE_PATH += FILE_NAME

      # Opens the .PAZ file specified in the PAZ_NAME variable
       open FDSE PAZ_NAME 1

      # If uncompressed size is greater than compressed size, it means that the file is compressed, so we use "clog" which uncompresses and extracts the file
       if SIZE > ZSIZE
          clog FILE_PATH OFFSET ZSIZE SIZE 1
          # FILE_PATH: The path with the file name in the end, where the file will be extracted. Example: character/multiplemodeldesc.xml
          # OFFSET   : The byte in this .PAZ file which the compressed encrypted file starts
          # ZSIZE    : The size of the file should be when still compressed
          # SIZE     : The the file should be, when decompressed
           # 1        : Number of the file associated to the archive
    
      # If the uncompressed size is 0 (Yes there are files with 0 size)
       elseif SIZE == 0
          # Extract the file without decompressing it, and it will have 0 bytes
           log FILE_PATH 0 0
    
      # If uncompressed smaller than the compressed size
       else
          # Extracts the file temporaryly in memory so we can do a check first, without decompressing it
           log MEMORY_FILE3 OFFSET ZSIZE 1
    
          # Gets the first byte from the extracted file to do a check
           get FLAGS byte MEMORY_FILE3
    
              #Even if decompression algorithm supports header where is size stored as byte, this method is not used in .paz files,
            #so it is safe to omit this method. Valid IDs for method where size is stored as long are 0x6E for uncompressed data
            #and 0x6F for compressed data. Also we have to make sure that data contains at least whole header (9 bytes)
            if (FLAGS == 0x6E || FLAGS == 0x6F) && ZSIZE > 9
                get DUMMY long MEMORY_FILE3 #read compressed data size, I have called variable DUMMY because we don't use it
                get SIZE2 long MEMORY_FILE3 #read decompressed data size
            endif
    
          encryption "" ""                 #turn off encryption because data in MEMORY_FILE3 are already decrypted
            if SIZE == SIZE2                #if decompressed size from .paz/.meta file is the same as decompressed size from data header,
                                            #we will consider data header as valid and data can be sent to decompression function                                  
                clog FILE_PATH 0 ZSIZE SIZE MEMORY_FILE3    #send file to decompression function and store result to specified file
            else                            #if data header is not valid, data are uncompressed without header
                log FILE_PATH 0 SIZE MEMORY_FILE3           #store data with length SIZE to specified file
            endif
            encryption ice BDO_ICE_KEY      #turn encryption back on
       endif
   next i

# Same thing as the .meta extension, but if you open a .PAZ file to extract
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
    
            if (FLAGS == 0x6E || FLAGS == 0x6F) && ZSIZE > 9
                get DUMMY long MEMORY_FILE2
                get SIZE2 long MEMORY_FILE2
            endif
    
           encryption "" ""
              if SIZE == SIZE2                                     
                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
You can download this code here:
blackdesert_script_0.2.5.zip

If you need help understanding quickbms scripts, please refer to http://aluigi.altervista.org/papers/quickbms.txt, everything you need is there.
--------------------------------------------------------------------------------------------------------------------------------------------
The 256000 unreadable bytes:
In some older version of the game, there was a section in the meta file, which was exactly 256000 bytes long, and it contained 8000 file blocks that I simply couldn't read, because if I read them as integers (4 bytes at the time), it only gave me huge or negative numbers.
So far I don't know how to read them, but I discovered that all the 8000 missing files that are there, have something in common:
All of their hashes uses only 7 hexadecimal letters to represent it, instead of 8. Let me explain:
Let's compare the hashes from "multiplemodeldesc.xml" to the "pew_00_ub_0031_dec_n.dds" (One of the missing files)

multiplemodeldesc.xml hash:
DECIMAL: 631490897
HEX (Big Endian): 25 A3 C9 51
HEX (Little Endian): 51 C9 A3 25

pew_00_ub_0031_dec_n.dds hash:
DECIMAL: 25743895
HEX (Big Endian): 188D217 (or 01 88 D2 17)
HEX (Little Endian): 17 D2 88 01

The complete list of all missing files and their hashes and file/folder numbers can be found here:
missing_files.txt

As you can see, when it's one of those files, it when you convert the number from dec to hex, you always get only 7 letters, because the 8th one is always 0

So what I'm thinking is that, when they made the .meta file, they simply decided not to store this extra "0" to save some space, and since we are always reading 4bytes, it actually reads the seven letters + 1 more of the next field, giving us just nonsense.

This is no longer a problem and nowadays we can read the file blocks like we used to, except for the TW version, but I don't know if that has changed as well.

QUICKBMS Script that skips those 256kb

Back when we had this problem of the unreadable 256kb, you would get this error, when you try to extract game's files using pad00000.meta
Code:
Error: incomplete input file 0: C:\Program Files (x86)\Black Desert Online\Paz\PAD00000.meta
       Can't read 525213419 bytes from offset 00ffa5c8.
       Anyway don't worry, it's possible that the BMS script has been written
       to exit in this way if it's reached the end of the archive so check it
       or contact its author or verify that all the files have been extracted.
       Please check the following coverage information to know if it's ok.

  coverage file 0    45%   7539936    16754120

Last script line before the error or that produced the error:
  50  log MEMORY_FILE2 TMP NAMES_SIZE

Here is a modified version of the original script, that solves that problem:
blackdesert_script_skip_256.zip

This is just like the original script, but with the only difference is that I skip 256000 bytes after we read all the information about the PAZ files, and also, to discover the new "FILE_BLOCKS_START" and "FILE_BLOCKS_END", this is the algorithm I used:

- Search for the hash: 631490897 (from multiplemodeldesc.xml)
- Go back 28 bytes (7 * 4) (nFields(hash,folderNum,fileNum,pazNum,offset,zsize,size) * sizeof(int))
- Read hash,folderNum,fileNum,pazNum,offset,zsize,size
- If pazNum >= 1 or <= TOTAL_PAZ_COUNT
- Go back 28 bytes
- repeat.

Here's the code in C that does that:
metaexplorer.c

So my goal was to find which byte starts the first hash from the "file blocks" section.
Here is the full list of the file blocks, in the order they appear in the pad000.meta file.
script-meta-output.txt
The order is: HASH|FOLDER_NUM|FILE_NUM|PAZ_NUM|FOLDER_NAME|FILE_NAME

I also made a program that sorts this file by fileNum, and outputs the numbers of the files that are missing and how many of them are missing:
filesort.zip
Also, the sorted file is this:
sorted_file.zip

I've also made a version of the blackdesert.bms file that prints FILE_HASH | FOLDER_NUM | FOLE_NUM | FOLDER_NAME | FILE_NAME reading all from the .meta file (also skips the 256000 bytes, so you might want to delete those lines of code if you want it to work in the newest versions)
quick_bms_script_print.zip[/QUOTE]
 

picts

Vivacious Visitor
Joined
Feb 14, 2017
您好,
不好意思,想請指教,我有一個網路下載的,修改裝備露出裸體的目錄,但是其中2件剛好是我買的商店物品,
女武神的阿塔尼斯盔甲,與女巫的阿勒阿西亨(R)不知貴國是那種名稱,有附圖,我不喜歡直接露,喜歡忽隱若現,
可我找出原檔,修改了Alpha 1通道卻無效0.0,我猜那一定又在於partcutdesc.xml檔寫的改變吧,可是我不懂那個,
我有附上檔案,可教我修改那裡才能拿掉讓我使用透明嗎?或直接幫我改寄回給我好嗎,剛在學習,麻煩你,無以回報,只能說句非常感謝!
 

Attachments

  • s001.jpg
    s001.jpg
    278.1 KB · Views: 251
  • s002.jpg
    s002.jpg
    258.5 KB · Views: 272
partcutdesc.rar
3.5 KB · Views: 157

winnerhead34

Potential Patron
Joined
Oct 18, 2017
Hello BlackFire,
I am editing the textures and wish to 3D preview the results outside the game.
I have both Photoshop and 3dsMax. However, I do not know how to extract the 3D model files from the respective .pac files.
Which extension do I look for and any already-established way of doing this that I missed?

Thanks
 

Vallil

Swell Supporter
Joined
Sep 13, 2013
Hello BlackFire,
I am editing the textures and wish to 3D preview the results outside the game.
I have both Photoshop and 3dsMax. However, I do not know how to extract the 3D model files from the respective .pac files.
Which extension do I look for and any already-established way of doing this that I missed?

Thanks
You could try BlackFire's PAC Browser
 

AtmaDarkwolf

Potential Patron
Joined
Jul 17, 2017
I use Meta Injector and Resorepless. I get a recheck sometimes, but not often. And it is usually because I forgot to manual backup. It is strange you are having it happen every time. :frown:
Ok so maybe I need help here.

Running game (Patching) with both, or either installed, even if I prevent file recheck causes 'currupt file' error. every time.(more often than not, I have to delete the appdata folder to get it to work)

Deleting everything(uninstalling all in repo, undo all changes in meta) sometimes(not always) works to update, but then I have to go reinstall it all over again. Is there maybe a way I can 'save' the settings in Resorpless so that I can (when I get the error again) just quickly apply it how i like it?

And the files I'm using in meta injector are files provided by Resorpless creator to hide the personal sailboat's sails. (But being that even if this is NOT installed and only resporpless is, i still get same error, I am unsure if that would be the issue.)
 

winnerhead34

Potential Patron
Joined
Oct 18, 2017
blender bdo collada.jpg
I am trying to edit the 3d nude model of Dark Knight
In both 3ds Max 2018 and Blender 2.79, the collada file derived from the respective .pac file imports successfully but NOTHING shows up.
Any solutions for this?

Thanks in advance
 

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.