August 10Aug 10 Hi, I'm looking for tools/scripts capable of unpacking (preferably, both) the .pkn and .bfz archive types used by the PC game Bubble Fighter / 버블파이터. Below are some notes I've compiled on the two formats so people can get a quick and rough idea of what they are ..pknused around 2016 and onwards for all clients; replacing .bfz file formatproprietary, so no existing game extractors for these archives worksomeone who's developing an offline version of the game claimed he was able to unpack the .pkn files; however, he didn't elaborate on how he managed to do so.bfzused for earlier versions of the game until 2016versions of the game released before it released in Korea may have used different file however none of those versions have resurfaced yet so its hard to tell unlike .pkn archives which are proprietary, bfz files are just zip files under a dif. suffix (.bfz = "Bubble Fighter Zip"?)because .bfz files are just zip files in disguise they should be easier to open than pkn; however, not only are most of the files (minus a good amount of .png files) in these archives stored in deflate format, but seemingly each archive for every different version (this includes updates and not just regional releases) of the game also uses different passwords/keysThe sample files are in the 3 links below. The first one is a .config bfz archive from a 2013 Indonesian client, and the last 2 are config .pkn archives from the latest version of the game before it shut down. If you need me to upload the .exe for the game itself and/or attach the sample files directly to my post than I gladly will.https://files.catbox.moe/pl6ayl.bfz https://files.catbox.moe/df7fiw.pkn https://files.catbox.moe/w9ydwu.pkn
August 11Aug 11 Localization Inspecting the Pet_Snake.bfz file shows that it indeed is a protected ZIP file with legacy ZipCrypto. The game executable is protected by Themida (😒) so I had to dump the running process to perform some analysis.The ZIP password is derived from the archive's resource path. The loader normalizes the separators, gets rid of the filename, takes the final directory component, and finally converts it all to uppercase. The string is then ran through an embedded lookup table and finally the results are all concatenated resulting in the valid password for this particular BFP file.Here is a script I wrote that will automatically generate the proper ZIP password for the given BFP file:TABLES = [ "as89f7d6af98e7f6a9s87f6as98f76asdf8yasuefyae8f7as6ef9ase87fa6se9", "alkjdfLKDFJeofias4894ewfadfklDFJfsafjlasff89a4fhJLKDSfHdf98daf9s", "afoiefajsfiasdf87ysda987dsfvhsdkvhs8dr7vhsdkfghsie4g8hsdfukzDFDf", "faklsjdfiod9f8asdfjKLDFjds9f8jasdklfjsdf9asd8fuseijkasdkHDFKJDf8", "dflkjasdf98asfuyaiufhaos8efhLHFDJSFhasod8fsdfkLDfd9f8aslkejfDLkf", "dkfjapsdoifjas9d8fajsdfiashdJKFSHdfdfaskdljf9sd8fasd89f7asieufa9", ] def bfz_password(package_path): path = package_path.encode("ascii") return "".join( TABLES[i % 6][path[i % len(path)] % 64] for i in range(64) ) def reconstruct_bfz_path(resource_path: str) -> str: normalized_path = resource_path.replace("\\", "/") separator_index = normalized_path.rfind("/") resource_directory = normalized_path[:separator_index] directory_separator = resource_directory.rfind("/") archive_name = resource_directory[directory_separator + 1:] return f"{resource_directory}/{archive_name}.bfz" def derive_bfz_kdf_input(resource_path: str) -> str: archive_path = reconstruct_bfz_path(resource_path) return archive_path.upper() if __name__ == "__main__": resource = "<FILENAME>" logical_archive_path = reconstruct_bfz_path(resource) password_generator_input = derive_bfz_kdf_input(resource) encrypted_password = bfz_password(password_generator_input) print(f"Resource path: {resource}") print(f"Logical archive path: {logical_archive_path}") print(f"KDF input: {password_generator_input}") print(f"Encrypted password: {encrypted_password}")I am taking the Pet_Snake.bfz file as example and I have opened in my tool. You can also open it in any other tool like 7zip, NotePad++, etc... as long as you can get the names of the files inside of the ZIP archive.You simply take any of the names, in this case we take Models/Characters/Pet_Snake/common/pis-basic-jump.face, and paste it in the <FILENAME> placeholder inside of the python script and run it.Resource path: Models/Characters/Pet_Snake/common/pis-basic-jump.face Logical archive path: Models/Characters/Pet_Snake/common/common.bfz KDF input: MODELS/CHARACTERS/PET_SNAKE/COMMON/COMMON.BFZ Encrypted password: 7aejsjajsauk94fjf8afasf9sefskd7f8aDj6fdsud8Lf8ya7odskos8flhps9sdThe encrypted password can then be used as password to extract the data from the ZIP file.I did not yet take a look at the PKN files you've supplied. I am planning on doing this soon. Edited August 11Aug 11 by Falkrian
August 11Aug 11 Localization OK. So I took a look at the Korean Bubble Fighter executable (after stepping into the ring with Themida (😒) again) you've provided and I saw that it uses SNOW 2.0 Stream Cipher but, with a few Nexon introduced twists.thisI used the following C++ implementation of the SNOW Stream Cipher from this GitHub Repository as reference and wrote my own version in Python and added the Nexon induced twists myself.Use the attached Python script as follows:python .\BubbleFighterPKN.py "path\to\pkn\file"I have tested the Python script on the samples you've given me so I hope it will work on the rest of the files as well. I have no idea what your future plans are with this but, I wish you all the best. 👍BubbleFighterPKN.py Edited August 11Aug 11 by Falkrian
8 hours ago8 hr Localization @Falkrian Just tried your script on the Chinese (2011-04-11) & Korean (2026-05-08 & 2025-12-17) version but it doesn't seem to get the key right, the version I'm working with can be found on archive.org (don't wanna link it cause anti piracy rules and stuff)Config0000.zip Edited 8 hours ago8 hr by NeoGT404
8 hours ago8 hr Localization 34 minutes ago, NeoGT404 said:@Falkrian Just tried your script on the Chinese (2011-04-11) & Korean (2026-05-08 & 2025-12-17) version but it doesn't seem to get the key right, the version I'm working with can be found on archive.org (don't wanna link it cause anti piracy rules and stuff)Config0000.zipThank you for letting me know! I will look into it. 👍
2 hours ago2 hr Localization The same modified SNOW 2.0 implementation is used, but the checksum was removed from each file-table record and the stored filenames are relative. This is why the script failed. I have attached a modified version of the script that accounts for this. The usage remains the same.Please let me know if you encounter any other issues. 👍 BubbleFighterPKN_v2.py
Create an account or sign in to comment